ziro-core 0.2.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ziro/__init__.py +63 -0
- ziro/_hf_bootstrap.py +78 -0
- ziro/_version.py +24 -0
- ziro/agent.py +511 -0
- ziro/agents/.subagents/doc_writer.agent.md +37 -0
- ziro/agents/.subagents/fact_checker.agent.md +41 -0
- ziro/agents/.subagents/reflector.agent.md +27 -0
- ziro/agents/.subagents/reviewer.agent.md +35 -0
- ziro/agents/.subagents/scout.agent.md +30 -0
- ziro/agents/.subagents/solver.agent.md +37 -0
- ziro/agents/.subagents/summarizer.agent.md +36 -0
- ziro/agents/.subagents/tester.agent.md +42 -0
- ziro/agents/analyst/agent_config.yaml +38 -0
- ziro/agents/analyst/hooks.yaml +29 -0
- ziro/agents/analyst/meta.yaml +13 -0
- ziro/agents/analyst/permissions.yaml +10 -0
- ziro/agents/analyst/shell_policy.yaml +46 -0
- ziro/agents/analyst/subagent_policy.yaml +16 -0
- ziro/agents/analyst/tool_policy.yaml +29 -0
- ziro/agents/architect/agent_config.yaml +38 -0
- ziro/agents/architect/meta.yaml +13 -0
- ziro/agents/architect/permissions.yaml +9 -0
- ziro/agents/architect/subagent_policy.yaml +14 -0
- ziro/agents/architect/tool_policy.yaml +21 -0
- ziro/agents/coder/agent_config.yaml +40 -0
- ziro/agents/coder/hooks.yaml +31 -0
- ziro/agents/coder/meta.yaml +14 -0
- ziro/agents/coder/permissions.yaml +13 -0
- ziro/agents/coder/shell_policy.yaml +47 -0
- ziro/agents/coder/subagent_policy.yaml +17 -0
- ziro/agents/coder/tool_policy.yaml +29 -0
- ziro/agents/default/agent_config.yaml +110 -0
- ziro/agents/default/attachment_policy.yaml +8 -0
- ziro/agents/default/compaction_policy.yaml +24 -0
- ziro/agents/default/fs_policy.yaml +54 -0
- ziro/agents/default/guardrails_policy.yaml +145 -0
- ziro/agents/default/handoff_policy.yaml +12 -0
- ziro/agents/default/hooks.yaml +44 -0
- ziro/agents/default/mcp_servers.yaml +143 -0
- ziro/agents/default/memory_policy.yaml +17 -0
- ziro/agents/default/meta.yaml +19 -0
- ziro/agents/default/modes_policy.yaml +25 -0
- ziro/agents/default/permissions.yaml +27 -0
- ziro/agents/default/plugin_config.yaml +61 -0
- ziro/agents/default/shell_policy.yaml +29 -0
- ziro/agents/default/subagent_policy.yaml +32 -0
- ziro/agents/default/tool_policy.yaml +46 -0
- ziro/agents/default/voice_policy.yaml +29 -0
- ziro/agents/default/webfetch_policy.yaml +14 -0
- ziro/agents/default/websearch_policy.yaml +21 -0
- ziro/agents/life_coach/agent_config.yaml +49 -0
- ziro/agents/life_coach/attachment_policy.yaml +8 -0
- ziro/agents/life_coach/compaction_policy.yaml +24 -0
- ziro/agents/life_coach/guardrails_policy.yaml +145 -0
- ziro/agents/life_coach/handoff_policy.yaml +12 -0
- ziro/agents/life_coach/mcp_servers.yaml +53 -0
- ziro/agents/life_coach/memory_policy.yaml +17 -0
- ziro/agents/life_coach/meta.yaml +19 -0
- ziro/agents/life_coach/permissions.yaml +24 -0
- ziro/agents/life_coach/shell_policy.yaml +28 -0
- ziro/agents/life_coach/subagent_policy.yaml +17 -0
- ziro/agents/life_coach/tool_policy.yaml +15 -0
- ziro/agents/life_coach/voice_policy.yaml +29 -0
- ziro/agents/life_coach/webfetch_policy.yaml +14 -0
- ziro/agents/life_coach/websearch_policy.yaml +19 -0
- ziro/agents/product_manager/agent_config.yaml +36 -0
- ziro/agents/product_manager/meta.yaml +14 -0
- ziro/agents/product_manager/permissions.yaml +9 -0
- ziro/agents/product_manager/subagent_policy.yaml +15 -0
- ziro/agents/product_manager/tool_policy.yaml +22 -0
- ziro/agents/registry.yaml +10 -0
- ziro/agents/researcher/agent_config.yaml +74 -0
- ziro/agents/researcher/attachment_policy.yaml +8 -0
- ziro/agents/researcher/compaction_policy.yaml +25 -0
- ziro/agents/researcher/guardrails_policy.yaml +39 -0
- ziro/agents/researcher/handoff_policy.yaml +12 -0
- ziro/agents/researcher/hooks.yaml +71 -0
- ziro/agents/researcher/mcp_servers.yaml +56 -0
- ziro/agents/researcher/memory_policy.yaml +17 -0
- ziro/agents/researcher/meta.yaml +18 -0
- ziro/agents/researcher/permissions.yaml +22 -0
- ziro/agents/researcher/queue_policy.yaml +13 -0
- ziro/agents/researcher/shell_policy.yaml +29 -0
- ziro/agents/researcher/subagent_policy.yaml +22 -0
- ziro/agents/researcher/tool_policy.yaml +24 -0
- ziro/agents/researcher/voice_policy.yaml +29 -0
- ziro/agents/researcher/webfetch_policy.yaml +15 -0
- ziro/agents/researcher/websearch_policy.yaml +22 -0
- ziro/agents/researcher_docker/agent_config.yaml +67 -0
- ziro/agents/researcher_docker/attachment_policy.yaml +8 -0
- ziro/agents/researcher_docker/compaction_policy.yaml +25 -0
- ziro/agents/researcher_docker/guardrails_policy.yaml +39 -0
- ziro/agents/researcher_docker/handoff_policy.yaml +12 -0
- ziro/agents/researcher_docker/hooks.yaml +71 -0
- ziro/agents/researcher_docker/mcp_servers.yaml +18 -0
- ziro/agents/researcher_docker/memory_policy.yaml +17 -0
- ziro/agents/researcher_docker/meta.yaml +18 -0
- ziro/agents/researcher_docker/permissions.yaml +22 -0
- ziro/agents/researcher_docker/shell_policy.yaml +36 -0
- ziro/agents/researcher_docker/subagent_policy.yaml +20 -0
- ziro/agents/researcher_docker/tool_policy.yaml +19 -0
- ziro/agents/researcher_docker/webfetch_policy.yaml +15 -0
- ziro/agents/researcher_docker/websearch_policy.yaml +22 -0
- ziro/agents/solver/meta.yaml +7 -0
- ziro/agents/solver/permissions.yaml +11 -0
- ziro/agents/tester/meta.yaml +7 -0
- ziro/agents/tester/permissions.yaml +12 -0
- ziro/agents/writer/agent_config.yaml +40 -0
- ziro/agents/writer/meta.yaml +13 -0
- ziro/agents/writer/permissions.yaml +10 -0
- ziro/agents/writer/subagent_policy.yaml +15 -0
- ziro/agents/writer/tool_policy.yaml +24 -0
- ziro/agents_md.py +49 -0
- ziro/capabilities/__init__.py +40 -0
- ziro/capabilities/audit.py +71 -0
- ziro/capabilities/builtins.py +143 -0
- ziro/capabilities/context.py +28 -0
- ziro/capabilities/handle.py +41 -0
- ziro/capabilities/manager.py +376 -0
- ziro/capabilities/models.py +160 -0
- ziro/capabilities/registry.py +57 -0
- ziro/clarify/__init__.py +0 -0
- ziro/clarify/models.py +30 -0
- ziro/clarify/tools.py +70 -0
- ziro/compaction/__init__.py +40 -0
- ziro/compaction/models.py +87 -0
- ziro/compaction/node.py +202 -0
- ziro/compaction/summarizer.py +76 -0
- ziro/compaction/tokenizer.py +87 -0
- ziro/compaction/window.py +192 -0
- ziro/core/__init__.py +0 -0
- ziro/core/agent_md.py +80 -0
- ziro/core/agent_profiles.py +720 -0
- ziro/core/config.py +826 -0
- ziro/core/embeddings.py +142 -0
- ziro/core/flavour_apply.py +83 -0
- ziro/core/flavour_runtime.py +50 -0
- ziro/core/flavours.py +75 -0
- ziro/core/paths.py +289 -0
- ziro/core/retry.py +93 -0
- ziro/core/skills_catalog.py +179 -0
- ziro/core/thread_titles.py +48 -0
- ziro/core/trust.py +203 -0
- ziro/core/ui_prefs.py +47 -0
- ziro/core/update_check.py +110 -0
- ziro/flavours/chat/flavour.yaml +6 -0
- ziro/flavours/chat/persona.md +16 -0
- ziro/flavours/engineering/flavour.yaml +22 -0
- ziro/flavours/engineering/persona.md +8 -0
- ziro/flavours/research/flavour.yaml +30 -0
- ziro/flavours/research/persona.md +3 -0
- ziro/flavours/safe/flavour.yaml +9 -0
- ziro/flavours/safe/persona.md +3 -0
- ziro/fs/__init__.py +0 -0
- ziro/fs/models.py +69 -0
- ziro/fs/scratch.py +82 -0
- ziro/fs/tools.py +430 -0
- ziro/graph/__init__.py +0 -0
- ziro/graph/graph.py +557 -0
- ziro/graph/interrupts.py +327 -0
- ziro/graph/nodes.py +926 -0
- ziro/graph/state.py +147 -0
- ziro/guardrails/__init__.py +34 -0
- ziro/guardrails/backends.py +613 -0
- ziro/guardrails/evaluator.py +90 -0
- ziro/guardrails/models.py +136 -0
- ziro/guardrails/nodes.py +71 -0
- ziro/guardrails/policy_loader.py +30 -0
- ziro/handoff/__init__.py +7 -0
- ziro/handoff/models.py +27 -0
- ziro/handoff/tools.py +54 -0
- ziro/hooks/__init__.py +30 -0
- ziro/hooks/callables.py +126 -0
- ziro/hooks/guards.py +324 -0
- ziro/hooks/models.py +120 -0
- ziro/hooks/registry.py +86 -0
- ziro/hooks/runner.py +31 -0
- ziro/io/__init__.py +1 -0
- ziro/io/attachments.py +155 -0
- ziro/llm/__init__.py +4 -0
- ziro/llm/adapter.py +86 -0
- ziro/llm/capabilities.py +74 -0
- ziro/llm/custom.py +269 -0
- ziro/llm/model_tiers.py +31 -0
- ziro/llm/models.py +72 -0
- ziro/llm/providers/__init__.py +2 -0
- ziro/llm/providers/anthropic/__init__.py +26 -0
- ziro/llm/providers/anthropic/adapter.py +108 -0
- ziro/llm/providers/anthropic/catalog.py +106 -0
- ziro/llm/providers/anthropic/connection.py +15 -0
- ziro/llm/providers/openai/__init__.py +29 -0
- ziro/llm/providers/openai/adapter.py +123 -0
- ziro/llm/providers/openai/catalog.py +127 -0
- ziro/llm/providers/openai/connection.py +22 -0
- ziro/llm/providers/openrouter/__init__.py +27 -0
- ziro/llm/providers/openrouter/adapter.py +127 -0
- ziro/llm/providers/openrouter/catalog.py +291 -0
- ziro/llm/providers/openrouter/connection.py +24 -0
- ziro/llm/registry.py +78 -0
- ziro/memory/__init__.py +0 -0
- ziro/memory/checkpointer.py +52 -0
- ziro/memory/models.py +111 -0
- ziro/memory/node.py +63 -0
- ziro/memory/project_memory.py +153 -0
- ziro/memory/reflection.py +88 -0
- ziro/memory/store.py +215 -0
- ziro/modes/__init__.py +0 -0
- ziro/modes/models.py +50 -0
- ziro/modes/tools.py +62 -0
- ziro/permissions/__init__.py +43 -0
- ziro/permissions/gate.py +49 -0
- ziro/permissions/hook.py +67 -0
- ziro/permissions/mode.py +74 -0
- ziro/permissions/models.py +73 -0
- ziro/permissions/policy.py +85 -0
- ziro/permissions/store.py +37 -0
- ziro/permissions/validate.py +122 -0
- ziro/plugins/__init__.py +24 -0
- ziro/plugins/adapters/__init__.py +1 -0
- ziro/plugins/adapters/import_claude_code.py +190 -0
- ziro/plugins/consent.py +129 -0
- ziro/plugins/discovery.py +568 -0
- ziro/plugins/installer.py +277 -0
- ziro/plugins/marketplace.py +291 -0
- ziro/plugins/models.py +53 -0
- ziro/plugins/registry.py +28 -0
- ziro/profiles/__init__.py +208 -0
- ziro/profiles/spec_profile.py +277 -0
- ziro/py.typed +0 -0
- ziro/queue/__init__.py +9 -0
- ziro/queue/inflight.py +46 -0
- ziro/queue/models.py +75 -0
- ziro/queue/worker.py +310 -0
- ziro/rag/__init__.py +0 -0
- ziro/rag/indexer.py +109 -0
- ziro/rag/retriever.py +309 -0
- ziro/runtime/__init__.py +4 -0
- ziro/runtime/runner.py +1103 -0
- ziro/spec.py +116 -0
- ziro/state/__init__.py +2 -0
- ziro/state/env_store.py +185 -0
- ziro/state/fileio.py +69 -0
- ziro/state/projects_index.py +48 -0
- ziro/state/threads_index.py +309 -0
- ziro/state/users.py +87 -0
- ziro/subagents/__init__.py +12 -0
- ziro/subagents/models.py +85 -0
- ziro/subagents/orchestrator.py +452 -0
- ziro/subagents/tool.py +147 -0
- ziro/tasks/__init__.py +25 -0
- ziro/tasks/models.py +21 -0
- ziro/tasks/reducer.py +58 -0
- ziro/tasks/render.py +36 -0
- ziro/tasks/tools.py +126 -0
- ziro/tools/__init__.py +0 -0
- ziro/tools/bootstrap.py +223 -0
- ziro/tools/indexer.py +36 -0
- ziro/tools/mcp_client.py +305 -0
- ziro/tools/mcp_manager.py +494 -0
- ziro/tools/mcp_models.py +73 -0
- ziro/tools/meta_tools.py +177 -0
- ziro/tools/oauth.py +357 -0
- ziro/tools/registry.py +277 -0
- ziro/tools/shell.py +499 -0
- ziro/tools/shell_audit.py +151 -0
- ziro/tools/shell_models.py +142 -0
- ziro/types.py +27 -0
- ziro/voice/__init__.py +7 -0
- ziro/voice/backends.py +170 -0
- ziro/voice/models.py +65 -0
- ziro/voice/pipeline.py +180 -0
- ziro/webfetch/__init__.py +6 -0
- ziro/webfetch/models.py +22 -0
- ziro/webfetch/tool.py +263 -0
- ziro/websearch/__init__.py +11 -0
- ziro/websearch/models.py +27 -0
- ziro/websearch/tool.py +120 -0
- ziro_core-0.2.6.dist-info/METADATA +66 -0
- ziro_core-0.2.6.dist-info/RECORD +281 -0
- ziro_core-0.2.6.dist-info/WHEEL +4 -0
- ziro_core-0.2.6.dist-info/licenses/LICENSE +21 -0
ziro/__init__.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Ziro — the public, semver'd Python SDK.
|
|
2
|
+
|
|
3
|
+
``import ziro`` is the ONLY supported, documented surface. Everything under
|
|
4
|
+
``app.*`` is internal and may change without a major-version bump. The SDK is a
|
|
5
|
+
thin facade over the F12 ``TurnRunner`` shared turn core: it resolves a profile,
|
|
6
|
+
builds the graph, and drives turns through the one resume loop the CLI also uses.
|
|
7
|
+
|
|
8
|
+
Imports are **lazy** (PEP 562 module ``__getattr__``): ``import ziro`` pulls no
|
|
9
|
+
torch / graph stack — those load only when you touch ``ziro.Ziro`` (i.e. when you
|
|
10
|
+
actually build an agent). This keeps the SDK cheap to import in a host app.
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from typing import TYPE_CHECKING
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"Ziro",
|
|
18
|
+
"CapabilitiesHandle",
|
|
19
|
+
"TurnResult",
|
|
20
|
+
"StreamEvent",
|
|
21
|
+
"create_agent",
|
|
22
|
+
"Agent",
|
|
23
|
+
"AgentSpec",
|
|
24
|
+
"ToolConfig",
|
|
25
|
+
"Guardrails",
|
|
26
|
+
"PluginConfig",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
# Written at build time by hatch-vcs from the git tag (see pyproject.toml).
|
|
31
|
+
from ziro._version import __version__
|
|
32
|
+
except ImportError: # raw source tree with no tag / no VCS metadata
|
|
33
|
+
__version__ = "0.0.0+unknown"
|
|
34
|
+
|
|
35
|
+
if TYPE_CHECKING: # for type-checkers / IDEs only — no runtime import cost
|
|
36
|
+
from ziro.agent import Agent, Ziro, create_agent
|
|
37
|
+
from ziro.capabilities.handle import CapabilitiesHandle
|
|
38
|
+
from ziro.spec import AgentSpec, Guardrails, PluginConfig, ToolConfig
|
|
39
|
+
from ziro.types import StreamEvent, TurnResult
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def __getattr__(name: str):
|
|
43
|
+
if name in ("Ziro", "Agent", "create_agent"):
|
|
44
|
+
from ziro import agent
|
|
45
|
+
|
|
46
|
+
return getattr(agent, name)
|
|
47
|
+
if name in ("AgentSpec", "ToolConfig", "Guardrails", "PluginConfig"):
|
|
48
|
+
from ziro import spec
|
|
49
|
+
|
|
50
|
+
return getattr(spec, name)
|
|
51
|
+
if name == "CapabilitiesHandle":
|
|
52
|
+
from ziro.capabilities.handle import CapabilitiesHandle
|
|
53
|
+
|
|
54
|
+
return CapabilitiesHandle
|
|
55
|
+
if name in ("TurnResult", "StreamEvent"):
|
|
56
|
+
from ziro import types
|
|
57
|
+
|
|
58
|
+
return getattr(types, name)
|
|
59
|
+
raise AttributeError(f"module 'ziro' has no attribute {name!r}")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def __dir__() -> list[str]:
|
|
63
|
+
return sorted(__all__)
|
ziro/_hf_bootstrap.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""F24 — HuggingFace offline bootstrap (runs before any transformers/hub import).
|
|
2
|
+
|
|
3
|
+
``huggingface_hub`` reads ``HF_HUB_OFFLINE`` into a module constant **once, at
|
|
4
|
+
import time**, and it is imported very early (via
|
|
5
|
+
``langchain_core.language_models.base`` → ``transformers``). Setting the env var any
|
|
6
|
+
later has no effect (verified during implementation). The only chokepoint that runs
|
|
7
|
+
before that import is ``app/__init__.py``, which calls :func:`configure_hf_offline`
|
|
8
|
+
here.
|
|
9
|
+
|
|
10
|
+
Going offline removes ~9 s of HuggingFace Hub revision/etag checks per launch (a
|
|
11
|
+
cached-model construct drops from ~9.2 s to ~0.3 s) **and** pins the embedding space
|
|
12
|
+
to the cached revision the FAISS index was built against. To stay correct on a fresh
|
|
13
|
+
machine, we only go offline when the model is **already cached** (a pure filesystem
|
|
14
|
+
check, no import) — otherwise the one-time download still runs. This module imports
|
|
15
|
+
nothing but :mod:`os` so it can run before the heavy import web.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import os
|
|
21
|
+
|
|
22
|
+
_EMB_MODEL = "sentence-transformers/all-MiniLM-L6-v2"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _hub_cache_dir() -> str:
|
|
26
|
+
"""The HuggingFace hub cache directory, honouring the standard env overrides."""
|
|
27
|
+
cache = os.environ.get("HUGGINGFACE_HUB_CACHE")
|
|
28
|
+
if cache:
|
|
29
|
+
return cache
|
|
30
|
+
home = os.environ.get("HF_HOME") or os.path.join(
|
|
31
|
+
os.path.expanduser("~"), ".cache", "huggingface"
|
|
32
|
+
)
|
|
33
|
+
return os.path.join(home, "hub")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _embeddings_cached(model: str = _EMB_MODEL) -> bool:
|
|
37
|
+
"""True iff a usable snapshot of ``model`` is already in the hub cache.
|
|
38
|
+
|
|
39
|
+
A usable snapshot carries ``config.json``; checking for it (rather than just the
|
|
40
|
+
snapshots dir) guards against a half-populated cache.
|
|
41
|
+
"""
|
|
42
|
+
folder = "models--" + model.replace("/", "--")
|
|
43
|
+
snapshots = os.path.join(_hub_cache_dir(), folder, "snapshots")
|
|
44
|
+
if not os.path.isdir(snapshots):
|
|
45
|
+
return False
|
|
46
|
+
try:
|
|
47
|
+
for snap in os.listdir(snapshots):
|
|
48
|
+
if os.path.exists(os.path.join(snapshots, snap, "config.json")):
|
|
49
|
+
return True
|
|
50
|
+
except OSError:
|
|
51
|
+
return False
|
|
52
|
+
return False
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def configure_hf_offline() -> bool:
|
|
56
|
+
"""Set ``HF_HUB_OFFLINE``/``TRANSFORMERS_OFFLINE`` when safe. Returns True iff
|
|
57
|
+
offline is in effect. MUST run before any ``huggingface_hub`` import.
|
|
58
|
+
|
|
59
|
+
Precedence: ``ZIRO_NO_ENV_MUTATION=1`` disables all host-env mutation (for
|
|
60
|
+
embedders — ``import ziro`` must not silently touch the host process env);
|
|
61
|
+
an explicit operator ``HF_HUB_OFFLINE`` is never overridden;
|
|
62
|
+
``ZIRO_EMBEDDINGS_OFFLINE=1`` forces offline regardless of cache; otherwise
|
|
63
|
+
we go offline only when the embeddings model is already cached.
|
|
64
|
+
"""
|
|
65
|
+
if os.environ.get("ZIRO_NO_ENV_MUTATION") == "1":
|
|
66
|
+
# Never write to the host env. Report the current offline state, but leave
|
|
67
|
+
# it to the operator to set HF_HUB_OFFLINE explicitly when embedding Ziro.
|
|
68
|
+
explicit_ro = os.environ.get("HF_HUB_OFFLINE")
|
|
69
|
+
return explicit_ro not in (None, "0", "", "false", "False")
|
|
70
|
+
explicit = os.environ.get("HF_HUB_OFFLINE")
|
|
71
|
+
if explicit is not None: # respect an operator's explicit choice, either way
|
|
72
|
+
return explicit not in ("0", "", "false", "False")
|
|
73
|
+
forced = os.environ.get("ZIRO_EMBEDDINGS_OFFLINE", "") in ("1", "true", "True")
|
|
74
|
+
if forced or _embeddings_cached():
|
|
75
|
+
os.environ["HF_HUB_OFFLINE"] = "1"
|
|
76
|
+
os.environ["TRANSFORMERS_OFFLINE"] = "1"
|
|
77
|
+
return True
|
|
78
|
+
return False
|
ziro/_version.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = '0.2.6'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 2, 6)
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = None
|
ziro/agent.py
ADDED
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
"""``Agent`` — the public facade over the F12 ``TurnRunner`` shared turn core.
|
|
2
|
+
|
|
3
|
+
No new turn logic: resolve a profile (by ``agent=`` YAML id, or an injected
|
|
4
|
+
``AgentSpec`` via ``create_agent``), open the store/checkpointer, build the
|
|
5
|
+
graph, and drive turns through ``TurnRunner.run_turn`` — the same path the TUI
|
|
6
|
+
and ``-p`` use. Sync methods bridge to the async core on a private daemon
|
|
7
|
+
loop; async methods await it directly.
|
|
8
|
+
|
|
9
|
+
**One-loop rule (replan C5).** A single ``Agent`` instance is either driven all-sync
|
|
10
|
+
(``chat``/``stream``/``open``/``close``) or all-async (``achat``/``astream`` under
|
|
11
|
+
``async with``) — never mixed. The sync API funnels *everything* (open, turn,
|
|
12
|
+
close) through one private loop; the async API stays on the caller's loop. Mixing
|
|
13
|
+
them would use the async ``AsyncSqliteSaver`` checkpointer across two event loops,
|
|
14
|
+
which raises. A guard below fails loudly instead of corrupting state.
|
|
15
|
+
|
|
16
|
+
**Torch-free import.** Heavy imports (``TurnRunner``, the graph stack → torch) are
|
|
17
|
+
done inside methods, so ``import ziro`` / ``import ziro.agent`` pull no torch until
|
|
18
|
+
you actually construct (with a ``user=``) or drive an ``Agent``.
|
|
19
|
+
"""
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import asyncio
|
|
23
|
+
import threading
|
|
24
|
+
import warnings
|
|
25
|
+
from contextlib import AsyncExitStack
|
|
26
|
+
from typing import (
|
|
27
|
+
TYPE_CHECKING,
|
|
28
|
+
Any,
|
|
29
|
+
AsyncIterator,
|
|
30
|
+
Callable,
|
|
31
|
+
Iterator,
|
|
32
|
+
Optional,
|
|
33
|
+
Sequence,
|
|
34
|
+
Union,
|
|
35
|
+
cast,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
from ziro.capabilities.handle import CapabilitiesHandle
|
|
39
|
+
from ziro.spec import AgentSpec, Guardrails, PluginConfig, ToolConfig
|
|
40
|
+
from ziro.types import StreamEvent
|
|
41
|
+
|
|
42
|
+
if TYPE_CHECKING:
|
|
43
|
+
from langchain_core.tools import BaseTool
|
|
44
|
+
from langgraph.checkpoint.base import BaseCheckpointSaver
|
|
45
|
+
from langgraph.store.base import BaseStore
|
|
46
|
+
|
|
47
|
+
from ziro.compaction.models import CompactionConfig
|
|
48
|
+
from ziro.hooks.models import HooksConfig, HookSpec
|
|
49
|
+
from ziro.llm.adapter import LLMAdapter
|
|
50
|
+
from ziro.memory.models import MemoryConfig
|
|
51
|
+
from ziro.permissions.models import PermissionPolicy
|
|
52
|
+
from ziro.runtime.runner import TurnResult, TurnRunner
|
|
53
|
+
from ziro.graph.interrupts import InterruptRequest, InterruptResponse
|
|
54
|
+
from ziro.subagents.models import SpawnPolicy
|
|
55
|
+
|
|
56
|
+
InterruptHandler = Callable[[InterruptRequest], InterruptResponse]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class Agent:
|
|
60
|
+
"""A reusable agent session over an ``AgentSpec`` (code-first) or a YAML
|
|
61
|
+
agent id. ``user`` binds at construction OR on the first turn
|
|
62
|
+
(``agent.chat("hi", user="alice")``); one instance = one user.
|
|
63
|
+
|
|
64
|
+
``use_agent()`` switches to a YAML-registry agent even on a spec-driven
|
|
65
|
+
instance (that is what the underlying switch does).
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
spec: an ``AgentSpec`` (code-first, via ``create_agent``), or ``None``
|
|
69
|
+
for the YAML-registry path (``agent=``/``model=``).
|
|
70
|
+
user: user id (scopes long-term memory). May be omitted here and
|
|
71
|
+
supplied on the first ``chat``/``turn``/``stream`` call instead.
|
|
72
|
+
agent: agent id to run (None → the manifest/picker default, non-interactive).
|
|
73
|
+
flavour: reserved (F22); passing it now raises NotImplementedError.
|
|
74
|
+
thread: resume a thread by id (None → a fresh thread).
|
|
75
|
+
model: OpenRouter model override (None → the agent's meta / env default).
|
|
76
|
+
provider: reserved (set the provider in the agent's meta.yaml for now).
|
|
77
|
+
reasoning_effort: high|medium|low|off (None → env / meta default).
|
|
78
|
+
on_interrupt: called for a mid-turn HITL interrupt (permission ``ask`` /
|
|
79
|
+
handoff / clarify). Default = fail-closed deny (the ``chat_once`` posture).
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
def __init__(
|
|
83
|
+
self,
|
|
84
|
+
spec: AgentSpec | None = None,
|
|
85
|
+
*,
|
|
86
|
+
user: Optional[str] = None,
|
|
87
|
+
agent: Optional[str] = None,
|
|
88
|
+
flavour: Optional[str] = None,
|
|
89
|
+
thread: Optional[str] = None,
|
|
90
|
+
model: Optional[str] = None,
|
|
91
|
+
provider: Optional[str] = None,
|
|
92
|
+
reasoning_effort: Optional[str] = None,
|
|
93
|
+
on_interrupt: "Optional[InterruptHandler]" = None,
|
|
94
|
+
) -> None:
|
|
95
|
+
if flavour is not None:
|
|
96
|
+
raise NotImplementedError("flavour selection lands in F22; pass agent=… for now")
|
|
97
|
+
if provider is not None:
|
|
98
|
+
raise NotImplementedError(
|
|
99
|
+
"per-instance provider override is not wired yet: set `provider:` in "
|
|
100
|
+
"the agent's meta.yaml (or build a custom LLMAdapter and pass llm=)"
|
|
101
|
+
)
|
|
102
|
+
if spec is not None and (agent is not None or model is not None):
|
|
103
|
+
raise ValueError("pass either spec= (code-first) or agent=/model= (YAML), not both")
|
|
104
|
+
self._spec = spec
|
|
105
|
+
self._agent_arg = agent
|
|
106
|
+
self._model_arg = model
|
|
107
|
+
self._effort_arg = reasoning_effort
|
|
108
|
+
self._thread_arg = thread
|
|
109
|
+
self._user = user
|
|
110
|
+
self._runner: "TurnRunner | None" = None
|
|
111
|
+
self._on_interrupt = on_interrupt
|
|
112
|
+
self._stack: Optional[AsyncExitStack] = None
|
|
113
|
+
self._opened = False
|
|
114
|
+
# A private daemon loop so the SYNC API can drive the async core without the
|
|
115
|
+
# caller being on an event loop (the TUI's pattern).
|
|
116
|
+
self._loop = asyncio.new_event_loop()
|
|
117
|
+
self._loop_thread = threading.Thread(
|
|
118
|
+
target=self._loop.run_forever, name="ziro-sdk", daemon=True
|
|
119
|
+
)
|
|
120
|
+
self._loop_thread.start()
|
|
121
|
+
# The loop the session was opened on — the one-loop guard compares against it.
|
|
122
|
+
self._session_loop: Optional[asyncio.AbstractEventLoop] = None
|
|
123
|
+
if user is not None:
|
|
124
|
+
self._ensure_runner()
|
|
125
|
+
|
|
126
|
+
# ---- runner construction (lazy — this is where torch loads) -----------
|
|
127
|
+
|
|
128
|
+
def _ensure_runner(self) -> "TurnRunner":
|
|
129
|
+
if self._runner is not None:
|
|
130
|
+
return self._runner
|
|
131
|
+
if self._user is None:
|
|
132
|
+
raise RuntimeError(
|
|
133
|
+
"no user bound: pass user= to the constructor or to the first "
|
|
134
|
+
"chat/turn/stream call"
|
|
135
|
+
)
|
|
136
|
+
from ziro.runtime.runner import TurnRunner # lazy: pulls the graph stack (torch)
|
|
137
|
+
|
|
138
|
+
extra: dict = {}
|
|
139
|
+
effort = self._effort_arg
|
|
140
|
+
if self._spec is not None:
|
|
141
|
+
from ziro.memory.models import MemoryConfig
|
|
142
|
+
from ziro.profiles.spec_profile import SpecProfile
|
|
143
|
+
|
|
144
|
+
spec = self._spec
|
|
145
|
+
extra["profile"] = SpecProfile(spec)
|
|
146
|
+
if spec.llm is not None and not isinstance(spec.llm, str):
|
|
147
|
+
extra["llm"] = spec.llm
|
|
148
|
+
if spec.memory is not None and not isinstance(spec.memory, MemoryConfig):
|
|
149
|
+
extra["store"] = spec.memory
|
|
150
|
+
if spec.checkpointer is not None:
|
|
151
|
+
extra["checkpointer"] = spec.checkpointer
|
|
152
|
+
effort = effort or spec.reasoning_effort
|
|
153
|
+
self._runner = TurnRunner(
|
|
154
|
+
self._user,
|
|
155
|
+
agent=self._agent_arg,
|
|
156
|
+
thread_id=self._thread_arg,
|
|
157
|
+
interactive=False,
|
|
158
|
+
model=self._model_arg,
|
|
159
|
+
reasoning_effort=effort,
|
|
160
|
+
**extra,
|
|
161
|
+
)
|
|
162
|
+
return self._runner
|
|
163
|
+
|
|
164
|
+
def _bind_user(self, user: Optional[str]) -> None:
|
|
165
|
+
if user is not None:
|
|
166
|
+
if self._user is None:
|
|
167
|
+
self._user = user
|
|
168
|
+
elif user != self._user:
|
|
169
|
+
raise RuntimeError(
|
|
170
|
+
f"this Agent instance is bound to user '{self._user}'; "
|
|
171
|
+
"create another instance (or a gateway session) for a second user"
|
|
172
|
+
)
|
|
173
|
+
self._ensure_runner()
|
|
174
|
+
|
|
175
|
+
# ---- interrupt renderer ------------------------------------------------
|
|
176
|
+
|
|
177
|
+
def _renderer(self) -> "InterruptHandler":
|
|
178
|
+
if self._on_interrupt is not None:
|
|
179
|
+
return self._on_interrupt
|
|
180
|
+
from ziro.graph.interrupts import render_non_interactive # fail-closed deny
|
|
181
|
+
|
|
182
|
+
return render_non_interactive
|
|
183
|
+
|
|
184
|
+
# ---- lifecycle ---------------------------------------------------------
|
|
185
|
+
|
|
186
|
+
def _submit(self, coro: Any) -> Any:
|
|
187
|
+
"""Run a coroutine on the private loop from a sync caller and block."""
|
|
188
|
+
return asyncio.run_coroutine_threadsafe(coro, self._loop).result()
|
|
189
|
+
|
|
190
|
+
def _check_loop(self) -> None:
|
|
191
|
+
"""One-loop guard: a turn must run on the same loop that opened the session."""
|
|
192
|
+
if not self._opened:
|
|
193
|
+
return
|
|
194
|
+
try:
|
|
195
|
+
running = asyncio.get_running_loop()
|
|
196
|
+
except RuntimeError:
|
|
197
|
+
return
|
|
198
|
+
if self._session_loop is not None and running is not self._session_loop:
|
|
199
|
+
raise RuntimeError(
|
|
200
|
+
"Agent was opened on a different event loop. Use one instance either "
|
|
201
|
+
"all-sync (chat/stream) or all-async (async with … achat/astream), "
|
|
202
|
+
"never both."
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
async def _aopen(self) -> "Agent":
|
|
206
|
+
if not self._opened:
|
|
207
|
+
runner = self._ensure_runner()
|
|
208
|
+
self._session_loop = asyncio.get_running_loop()
|
|
209
|
+
self._stack = AsyncExitStack()
|
|
210
|
+
await self._stack.enter_async_context(runner.session())
|
|
211
|
+
self._opened = True
|
|
212
|
+
return self
|
|
213
|
+
|
|
214
|
+
async def _aclose(self) -> None:
|
|
215
|
+
if self._stack is not None:
|
|
216
|
+
await self._stack.aclose()
|
|
217
|
+
self._stack = None
|
|
218
|
+
self._opened = False
|
|
219
|
+
self._session_loop = None
|
|
220
|
+
|
|
221
|
+
def open(self) -> "Agent":
|
|
222
|
+
"""Sync open: build the graph + open store/checkpointer on the private loop."""
|
|
223
|
+
self._submit(self._aopen())
|
|
224
|
+
return self
|
|
225
|
+
|
|
226
|
+
def close(self) -> None:
|
|
227
|
+
if self._opened:
|
|
228
|
+
self._submit(self._aclose())
|
|
229
|
+
self._loop.call_soon_threadsafe(self._loop.stop)
|
|
230
|
+
|
|
231
|
+
def __enter__(self) -> "Agent":
|
|
232
|
+
return self.open()
|
|
233
|
+
|
|
234
|
+
def __exit__(self, *exc: Any) -> None:
|
|
235
|
+
self.close()
|
|
236
|
+
|
|
237
|
+
async def __aenter__(self) -> "Agent":
|
|
238
|
+
return await self._aopen()
|
|
239
|
+
|
|
240
|
+
async def __aexit__(self, *exc: Any) -> None:
|
|
241
|
+
await self._aclose()
|
|
242
|
+
self._loop.call_soon_threadsafe(self._loop.stop)
|
|
243
|
+
|
|
244
|
+
# ---- turn API (all delegate to TurnRunner) -----------------------------
|
|
245
|
+
|
|
246
|
+
async def achat(
|
|
247
|
+
self, message: str, *, attach: Optional[list[str]] = None, user: Optional[str] = None
|
|
248
|
+
) -> str:
|
|
249
|
+
result = await self.aturn(message, attach=attach, user=user)
|
|
250
|
+
return result.reply
|
|
251
|
+
|
|
252
|
+
def chat(
|
|
253
|
+
self, message: str, *, attach: Optional[list[str]] = None, user: Optional[str] = None
|
|
254
|
+
) -> str:
|
|
255
|
+
return self._submit(self.achat(message, attach=attach, user=user))
|
|
256
|
+
|
|
257
|
+
async def aturn(
|
|
258
|
+
self, message: str, *, attach: Optional[list[str]] = None, user: Optional[str] = None
|
|
259
|
+
) -> "TurnResult":
|
|
260
|
+
"""Full ``TurnResult`` (guardrail flags, interrupts, raw state).
|
|
261
|
+
``to_json()`` is the exact chat_once contract."""
|
|
262
|
+
self._bind_user(user)
|
|
263
|
+
await self._aopen()
|
|
264
|
+
self._check_loop()
|
|
265
|
+
runner = self._ensure_runner()
|
|
266
|
+
human, _ = runner.make_human(message, attach)
|
|
267
|
+
return await runner.run_turn(human, renderer=self._renderer())
|
|
268
|
+
|
|
269
|
+
def turn(
|
|
270
|
+
self, message: str, *, attach: Optional[list[str]] = None, user: Optional[str] = None
|
|
271
|
+
) -> "TurnResult":
|
|
272
|
+
return self._submit(self.aturn(message, attach=attach, user=user))
|
|
273
|
+
|
|
274
|
+
async def astream(
|
|
275
|
+
self, message: str, *, attach: Optional[list[str]] = None, user: Optional[str] = None
|
|
276
|
+
) -> AsyncIterator[StreamEvent]:
|
|
277
|
+
"""Stream the turn's incremental token/state events, then complete."""
|
|
278
|
+
self._bind_user(user)
|
|
279
|
+
await self._aopen()
|
|
280
|
+
self._check_loop()
|
|
281
|
+
runner = self._ensure_runner()
|
|
282
|
+
human, _ = runner.make_human(message, attach)
|
|
283
|
+
queue: "asyncio.Queue[Optional[StreamEvent]]" = asyncio.Queue()
|
|
284
|
+
|
|
285
|
+
def _sink(event: StreamEvent) -> None:
|
|
286
|
+
queue.put_nowait(event)
|
|
287
|
+
|
|
288
|
+
async def _drive() -> None:
|
|
289
|
+
try:
|
|
290
|
+
await runner.run_turn(human, renderer=self._renderer(), on_event=_sink)
|
|
291
|
+
finally:
|
|
292
|
+
queue.put_nowait(None) # sentinel: turn done
|
|
293
|
+
|
|
294
|
+
task = asyncio.ensure_future(_drive())
|
|
295
|
+
try:
|
|
296
|
+
while True:
|
|
297
|
+
event = await queue.get()
|
|
298
|
+
if event is None:
|
|
299
|
+
break
|
|
300
|
+
yield event
|
|
301
|
+
finally:
|
|
302
|
+
await task
|
|
303
|
+
|
|
304
|
+
def stream(
|
|
305
|
+
self, message: str, *, attach: Optional[list[str]] = None, user: Optional[str] = None
|
|
306
|
+
) -> Iterator[StreamEvent]:
|
|
307
|
+
"""Sync stream: drain the async stream over the private loop."""
|
|
308
|
+
agen = self.astream(message, attach=attach, user=user)
|
|
309
|
+
while True:
|
|
310
|
+
try:
|
|
311
|
+
yield self._submit(agen.__anext__())
|
|
312
|
+
except StopAsyncIteration:
|
|
313
|
+
break
|
|
314
|
+
|
|
315
|
+
# ---- session control -----------------------------------------------------
|
|
316
|
+
|
|
317
|
+
@property
|
|
318
|
+
def thread_id(self) -> str:
|
|
319
|
+
return self._ensure_runner().thread_id
|
|
320
|
+
|
|
321
|
+
@property
|
|
322
|
+
def agent_id(self) -> str:
|
|
323
|
+
return self._ensure_runner().profile.agent_id
|
|
324
|
+
|
|
325
|
+
def use_agent(self, agent_id: str) -> None:
|
|
326
|
+
"""Switch agent (fresh thread, rebuilt graph) — TurnRunner.switch_agent."""
|
|
327
|
+
self._submit(self._ensure_runner().switch_agent(agent_id))
|
|
328
|
+
|
|
329
|
+
def use_model(self, model: str) -> None:
|
|
330
|
+
"""Override the model and rebuild — TurnRunner.switch_model.
|
|
331
|
+
|
|
332
|
+
No-op on a spec-driven agent whose spec injected an ``LLMAdapter``
|
|
333
|
+
object as ``llm``: the injected adapter keeps winning on rebuild
|
|
334
|
+
(``_build_llm``'s short-circuit), so this call has no effect —
|
|
335
|
+
construct a new ``Agent`` with a different spec instead.
|
|
336
|
+
"""
|
|
337
|
+
self._submit(self._ensure_runner().switch_model(model))
|
|
338
|
+
|
|
339
|
+
def use_thread(self, thread_id: str) -> None:
|
|
340
|
+
"""Resume a thread by id (no rebuild) — TurnRunner.switch_thread."""
|
|
341
|
+
self._ensure_runner().switch_thread(thread_id)
|
|
342
|
+
|
|
343
|
+
def use_flavour(self, name: str) -> None:
|
|
344
|
+
"""STUB (F22): apply a capability-preset bundle + swap the default agent."""
|
|
345
|
+
raise NotImplementedError("flavour switching lands in F22")
|
|
346
|
+
|
|
347
|
+
# ---- capability handle (stub interface; F21 fills it) ------------------
|
|
348
|
+
|
|
349
|
+
@property
|
|
350
|
+
def capabilities(self) -> CapabilitiesHandle:
|
|
351
|
+
return CapabilitiesHandle(self._ensure_runner())
|
|
352
|
+
|
|
353
|
+
# ---- embedding: mount as a sub-graph in a host LangGraph / FastAPI app --
|
|
354
|
+
|
|
355
|
+
@property
|
|
356
|
+
def graph(self) -> Any:
|
|
357
|
+
"""The live compiled LangGraph (a Runnable) — includes THIS session's
|
|
358
|
+
checkpointer. Requires the session to be open. For mounting inside a host
|
|
359
|
+
graph that owns its own persistence, prefer ``as_runnable()``.
|
|
360
|
+
|
|
361
|
+
``plugin_gate`` is pre-bound at build from this agent's resolved plugin
|
|
362
|
+
policy (``SpecProfile``/``AgentProfile`` → off-by-default for a code-first
|
|
363
|
+
agent) — the host must **not** supply it; a host-supplied ``thread_id`` /
|
|
364
|
+
``user_id`` / ``agent_id`` still merges in normally alongside it."""
|
|
365
|
+
if not self._opened:
|
|
366
|
+
self.open()
|
|
367
|
+
from ziro.plugins.discovery import _resolve_plugin_gate
|
|
368
|
+
|
|
369
|
+
r = self._ensure_runner()
|
|
370
|
+
assert r.graph is not None # self.open() above ran session(), which builds it
|
|
371
|
+
gate = _resolve_plugin_gate(r.profile)
|
|
372
|
+
return r.graph.with_config({"configurable": {"plugin_gate": gate}})
|
|
373
|
+
|
|
374
|
+
def as_runnable(self) -> Any:
|
|
375
|
+
"""A compiled LangGraph Runnable with **no checkpointer**, for composition
|
|
376
|
+
into a host graph that provides its own persistence (avoids a
|
|
377
|
+
nested-checkpointer conflict). The host must supply the ``configurable``
|
|
378
|
+
keys the graph reads: ``thread_id`` / ``user_id`` / ``agent_id``.
|
|
379
|
+
``plugin_gate`` is pre-bound at build from this agent's resolved plugin
|
|
380
|
+
policy — the host does not (and must not) supply it, so an embedded
|
|
381
|
+
code-first agent stays hermetic (zero plugins unless ``plugins=`` asked
|
|
382
|
+
for them) regardless of what configurable keys the host passes."""
|
|
383
|
+
if not self._opened:
|
|
384
|
+
self.open()
|
|
385
|
+
from ziro.graph.graph import build_graph
|
|
386
|
+
from ziro.plugins.discovery import _resolve_plugin_gate
|
|
387
|
+
|
|
388
|
+
r = self._ensure_runner()
|
|
389
|
+
assert r._store is not None # open() ran session(), which sets the store
|
|
390
|
+
gate = _resolve_plugin_gate(r.profile)
|
|
391
|
+
graph = build_graph(
|
|
392
|
+
r._build_llm(), r._store, None, registry=r._registry, profile=r.profile
|
|
393
|
+
)
|
|
394
|
+
return graph.with_config({"configurable": {"plugin_gate": gate}})
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def _normalize_plugins(
|
|
398
|
+
plugins: "Union[PluginConfig, bool, Sequence[str], None]",
|
|
399
|
+
) -> "PluginConfig | None":
|
|
400
|
+
"""Normalize the ``plugins=`` shorthand to a ``PluginConfig``, mirroring how
|
|
401
|
+
``tools=`` normalizes to ``ToolConfig``. ``None`` stays None so ``SpecProfile``
|
|
402
|
+
can apply the SDK's default-off surface default."""
|
|
403
|
+
if plugins is None or isinstance(plugins, PluginConfig):
|
|
404
|
+
return plugins
|
|
405
|
+
if isinstance(plugins, bool):
|
|
406
|
+
return PluginConfig(enabled=plugins)
|
|
407
|
+
if isinstance(plugins, (str, bytes)):
|
|
408
|
+
raise TypeError(
|
|
409
|
+
"plugins= expects a sequence of plugin ids (e.g. ['cc/x']), not a bare "
|
|
410
|
+
f"str/bytes; got {plugins!r}. Wrap it in a list: plugins=[{plugins!r}]"
|
|
411
|
+
)
|
|
412
|
+
allow = tuple(plugins)
|
|
413
|
+
if not allow:
|
|
414
|
+
# An empty sequence means "no plugins" — must stay distinguishable from
|
|
415
|
+
# plugins=True (all installed) and must NOT produce enabled=True with an
|
|
416
|
+
# empty allow, since the gate treats empty allow as "no allowlist, all pass".
|
|
417
|
+
return PluginConfig(enabled=False)
|
|
418
|
+
return PluginConfig(enabled=True, allow=allow)
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
def create_agent(
|
|
422
|
+
*,
|
|
423
|
+
spec: AgentSpec | None = None,
|
|
424
|
+
llm: "Union[LLMAdapter, str, None]" = None,
|
|
425
|
+
system_prompt: Optional[str] = None,
|
|
426
|
+
soul_prompt: Optional[str] = None,
|
|
427
|
+
tools: "Union[ToolConfig, Sequence[Union[str, BaseTool]], None]" = None,
|
|
428
|
+
guardrails: Optional[Guardrails] = None,
|
|
429
|
+
memory: "Union[BaseStore, MemoryConfig, None]" = None,
|
|
430
|
+
checkpointer: "Optional[BaseCheckpointSaver]" = None,
|
|
431
|
+
compaction: "Optional[CompactionConfig]" = None,
|
|
432
|
+
permissions: "Optional[PermissionPolicy]" = None,
|
|
433
|
+
subagents: "Union[SpawnPolicy, Sequence[str], None]" = None,
|
|
434
|
+
mcp_servers: Optional[dict] = None,
|
|
435
|
+
hooks: "Union[HooksConfig, Sequence[HookSpec], None]" = None,
|
|
436
|
+
reasoning_effort: Optional[str] = None,
|
|
437
|
+
plugins: "Union[PluginConfig, bool, Sequence[str], None]" = None,
|
|
438
|
+
name: str = "custom",
|
|
439
|
+
description: str = "",
|
|
440
|
+
user: Optional[str] = None,
|
|
441
|
+
thread: Optional[str] = None,
|
|
442
|
+
on_interrupt: "Optional[InterruptHandler]" = None,
|
|
443
|
+
) -> Agent:
|
|
444
|
+
"""Build an :class:`Agent` from pluggable objects. Everything omitted keeps
|
|
445
|
+
today's defaults (env model, sqlite store/checkpointer, library policies).
|
|
446
|
+
``create_agent(spec=spec)`` accepts a prebuilt :class:`AgentSpec` directly
|
|
447
|
+
(mutually exclusive with the per-field kwargs)."""
|
|
448
|
+
field_kwargs = (
|
|
449
|
+
llm, system_prompt, soul_prompt, tools, guardrails, memory, checkpointer,
|
|
450
|
+
compaction, permissions, subagents, mcp_servers, hooks, reasoning_effort,
|
|
451
|
+
plugins,
|
|
452
|
+
)
|
|
453
|
+
if spec is not None:
|
|
454
|
+
if any(v is not None for v in field_kwargs) or name != "custom" or description:
|
|
455
|
+
raise ValueError("pass either spec= or per-field kwargs, not both")
|
|
456
|
+
else:
|
|
457
|
+
if tools is None:
|
|
458
|
+
tool_cfg = ToolConfig()
|
|
459
|
+
elif isinstance(tools, ToolConfig):
|
|
460
|
+
tool_cfg = tools
|
|
461
|
+
else:
|
|
462
|
+
tool_cfg = ToolConfig(core=tuple(tools))
|
|
463
|
+
spawn = subagents
|
|
464
|
+
if spawn is not None and not hasattr(spawn, "allowed_children"):
|
|
465
|
+
from ziro.subagents.models import SpawnPolicy
|
|
466
|
+
|
|
467
|
+
spawn = SpawnPolicy(allowed_children=list(spawn))
|
|
468
|
+
hooks_cfg = hooks
|
|
469
|
+
if hooks_cfg is not None and not hasattr(hooks_cfg, "hooks"):
|
|
470
|
+
from ziro.hooks.models import HooksConfig
|
|
471
|
+
|
|
472
|
+
hooks_cfg = HooksConfig(hooks=list(hooks_cfg))
|
|
473
|
+
spec = AgentSpec(
|
|
474
|
+
name=name, description=description, llm=llm,
|
|
475
|
+
system_prompt=system_prompt, soul_prompt=soul_prompt,
|
|
476
|
+
tools=tool_cfg, guardrails=guardrails, memory=memory,
|
|
477
|
+
checkpointer=checkpointer, compaction=compaction,
|
|
478
|
+
permissions=permissions, subagents=cast("SpawnPolicy | None", spawn),
|
|
479
|
+
mcp_servers=mcp_servers, hooks=cast("HooksConfig | None", hooks_cfg),
|
|
480
|
+
reasoning_effort=reasoning_effort,
|
|
481
|
+
plugins=_normalize_plugins(plugins),
|
|
482
|
+
)
|
|
483
|
+
return Agent(spec, user=user, thread=thread, on_interrupt=on_interrupt)
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
class Ziro(Agent):
|
|
487
|
+
"""Deprecated F20 facade name — use :func:`create_agent` / :class:`Agent`.
|
|
488
|
+
Survives one release."""
|
|
489
|
+
|
|
490
|
+
def __init__(
|
|
491
|
+
self,
|
|
492
|
+
*,
|
|
493
|
+
user: str,
|
|
494
|
+
agent: Optional[str] = None,
|
|
495
|
+
flavour: Optional[str] = None,
|
|
496
|
+
thread: Optional[str] = None,
|
|
497
|
+
model: Optional[str] = None,
|
|
498
|
+
provider: Optional[str] = None,
|
|
499
|
+
reasoning_effort: Optional[str] = None,
|
|
500
|
+
on_interrupt: "Optional[InterruptHandler]" = None,
|
|
501
|
+
) -> None:
|
|
502
|
+
warnings.warn(
|
|
503
|
+
"ziro.Ziro is deprecated; use ziro.create_agent(...) / ziro.Agent",
|
|
504
|
+
DeprecationWarning,
|
|
505
|
+
stacklevel=2,
|
|
506
|
+
)
|
|
507
|
+
super().__init__(
|
|
508
|
+
user=user, agent=agent, flavour=flavour, thread=thread, model=model,
|
|
509
|
+
provider=provider, reasoning_effort=reasoning_effort,
|
|
510
|
+
on_interrupt=on_interrupt,
|
|
511
|
+
)
|