From 4146174503948dff9caacbbaf33e9ab585287f2c Mon Sep 17 00:00:00 2001 From: 0xallam Date: Sat, 25 Apr 2026 12:05:24 -0700 Subject: [PATCH] refactor: scrub migration scars, dead code, and unused helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Strip PLAYBOOK / AUDIT / Phase-N / C-numbered references from module docstrings across 16 files; rename ``_PHASE1_PARALLEL_DEFAULT`` → ``_PARALLEL_TOOL_CALLS_DEFAULT``. - Delete unused exception classes: ``SandboxInitializationError``, ``ImplementedInClientSideOnlyError``. - Delete the no-op ``on_handoff`` hook (we don't use SDK handoffs). - Delete the unreachable backward-compat tab-delimited fallback in ``_parse_git_diff_output``. - Delete orphaned ``strix/tools/load_skill/`` (dir contained only a pycache) and stale pycache files. - Rewrite ``strix/skills/__init__.py``: 168 → 56 LoC. Drop seven helper functions (``get_available_skills``, ``get_all_skill_names``, ``validate_skill_names``, ``parse_skill_list``, ``validate_requested_skills``, ``generate_skills_description``, ``_get_all_categories``) — none had external callers; only ``load_skills`` is used. - Drop the stale ``strix/agents/sdk_factory.py`` per-file ruff ignore (file no longer exists). Co-Authored-By: Claude Opus 4.7 (1M context) --- pyproject.toml | 27 ++-- strix/agents/factory.py | 46 +++---- strix/interface/utils.py | 13 +- strix/llm/anthropic_cache_wrapper.py | 14 +- strix/llm/multi_provider_setup.py | 4 - strix/llm/strix_session.py | 29 ++--- strix/orchestration/bus.py | 26 ++-- strix/orchestration/filter.py | 22 ++-- strix/orchestration/hooks.py | 43 ++---- strix/run_config_factory.py | 84 +++++------- strix/runtime/__init__.py | 34 ++--- strix/runtime/strix_docker_client.py | 9 +- strix/sandbox/__init__.py | 9 +- strix/sandbox/caido_capability.py | 11 +- strix/sandbox/healthcheck.py | 3 - strix/sandbox/session_manager.py | 3 - strix/skills/__init__.py | 187 ++++++--------------------- strix/telemetry/strix_processor.py | 32 ++--- strix/tools/__init__.py | 9 +- strix/tools/_decorator.py | 40 ++---- strix/tools/_sandbox_dispatch.py | 35 ++--- strix/tools/agents_graph/tools.py | 38 ++---- strix/tools/registry.py | 19 +-- 23 files changed, 212 insertions(+), 525 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5751606..c618315 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -299,38 +299,31 @@ ignore = [ # CaidoCapability uses agents.tool.Tool at runtime — pydantic Field # annotations and the cached _CAIDO_TOOLS tuple need it eagerly. "strix/sandbox/caido_capability.py" = ["TC002"] -# Agent factory: agents.tool.Tool is used at runtime in the _BASE_TOOLS -# tuple type, not just for annotations. -"strix/agents/sdk_factory.py" = ["TC002"] # Entry point: ``Path`` is used at runtime by the typing of the # session_manager call; importing under TYPE_CHECKING would defer # resolution past where mypy needs it. ``_build_root_task`` legitimately # walks every supported target type — splitting it into per-type # helpers would add indirection without simplifying anything. "strix/entry.py" = ["TC003", "PLR0912"] -# Legacy tracer module — pre-existing PLR/E501 patterns; full refactor -# is out of scope for the harness migration. ``Callable`` is a runtime +# Tracer carries a long event surface and a runtime ``Callable`` # annotation on ``vulnerability_found_callback``. "strix/telemetry/tracer.py" = ["TC003", "PLR0912", "PLR0915", "E501"] -# Legacy interface utility with intentionally many branches per supported -# scope-mode / target-type combination; refactor would obscure the -# decision tree without simplifying it. +# Interface utility branches per scope-mode / target-type combination; +# splitting would obscure the decision tree without simplifying it. "strix/interface/utils.py" = ["PLR0912", "BLE001", "PLC0415"] -# CLI / TUI / main keep extensive lazy imports + broad exception swallows -# for resilience around terminal-rendering errors. Refactor is out of -# scope for the harness migration. +# CLI / TUI / main keep extensive lazy imports + broad exception +# swallows for resilience around terminal-rendering errors. "strix/interface/cli.py" = ["BLE001", "PLC0415"] "strix/interface/tui.py" = ["BLE001", "PLC0415", "PLR0912", "PLR0915", "SIM105"] "strix/interface/main.py" = ["BLE001", "PLC0415", "PLR0912", "PLR0915"] "strix/interface/streaming_parser.py" = ["PLC0415"] "strix/interface/tool_components/agent_message_renderer.py" = ["PLC0415"] -# Sandbox dispatch helper has many short-circuit error returns (auth fail, -# size cap, decode fail, etc). Each is a distinct, documented failure mode -# the model needs to see verbatim — collapsing them harms readability. +# Each short-circuit error return in the sandbox dispatch is a distinct, +# documented failure mode the model needs to see verbatim. "strix/tools/_sandbox_dispatch.py" = ["PLR0911"] -# StrixSession + StrixTracingProcessor catch broad Exception intentionally: -# the whole point is that compressor / sanitizer / disk failures must not -# tear down the agent run (C10, C16). Calls already log at exception level. +# StrixSession + StrixTracingProcessor catch broad Exception +# intentionally: compressor / sanitizer / disk failures must not tear +# down the run. Calls already log at exception level. "strix/llm/strix_session.py" = ["BLE001"] "strix/telemetry/strix_processor.py" = ["BLE001"] diff --git a/strix/agents/factory.py b/strix/agents/factory.py index 8455e5c..e03d56c 100644 --- a/strix/agents/factory.py +++ b/strix/agents/factory.py @@ -1,33 +1,21 @@ -"""build_strix_agent — assemble an ``agents.Agent`` for root or child runs. +"""``build_strix_agent`` — assemble an ``agents.Agent`` for root or child. -This is the keystone that links Phase 2's SDK function tools, Phase 3's -graph tools, Phase 4's CaidoCapability, and the rendered Jinja prompt -from :mod:`strix.agents.prompt` into a single ``agents.Agent`` -instance ready for ``Runner.run``. +Wires the SDK function tools, multi-agent graph tools, +``CaidoCapability``, and the rendered Jinja prompt into one +``agents.Agent`` ready for ``Runner.run``. Two flavors: -- **Root** (``is_root=True``): the top-level scan agent. Carries - ``finish_scan`` (terminates the scan), no ``agent_finish`` (that's - for subagents). ``tool_use_behavior`` stops on ``finish_scan`` so - the model can't accidentally keep talking after marking the scan - complete. - +- **Root** (``is_root=True``): top-level scan agent. Carries + ``finish_scan`` and stops there. - **Child** (``is_root=False``): subagents spawned by the - ``create_agent`` graph tool. Carries ``agent_finish``, no - ``finish_scan``. ``tool_use_behavior`` stops on ``agent_finish`` - (C4 — without this, the SDK loop would keep going to ``max_turns`` - even after the child reported back to its parent). + ``create_agent`` graph tool. Carries ``agent_finish`` and stops + there — without ``stop_at_tool_names`` the SDK loop would keep + running to ``max_turns`` even after the child reported back. -Caido tools come from ``CaidoCapability.tools()`` automatically via -the SDK's capability merge — we don't include them here. Skills are -injected via the prompt at scan-bring-up time; runtime skill loading -isn't exposed as a tool any more (the legacy implementation reached -into a global agent registry that no longer exists). - -References: - - PLAYBOOK.md §4.3 (graph tool wiring) - - AUDIT.md §2.4 (C4 — stop_at_tool_names is required for subagents) +Caido tools come from ``CaidoCapability.tools()`` via the SDK's +capability merge — we don't list them here. Skills are baked into the +system prompt at scan bring-up; there's no runtime skill-loading tool. """ from __future__ import annotations @@ -196,12 +184,12 @@ def make_child_factory( ) -> Any: """Return a callable suitable for ``ctx.context['agent_factory']``. - The Phase 3 ``create_agent`` graph tool reads + The ``create_agent`` graph tool reads ``ctx.context['agent_factory']`` and calls it with ``name=`` and - ``skills=`` to build a child Agent. We snapshot the run-level - arguments (scan_mode, is_whitebox, etc.) into a closure so each - child inherits the right scan-level configuration without the - create_agent tool having to know about them. + ``skills=`` to build a child ``Agent``. Run-level arguments + (``scan_mode``, ``is_whitebox``, etc.) are captured in a closure so + each child inherits the scan-level configuration without + ``create_agent`` having to know about them. """ def _factory(*, name: str, skills: list[str]) -> Agent[Any]: diff --git a/strix/interface/utils.py b/strix/interface/utils.py index e25a0ed..c209d36 100644 --- a/strix/interface/utils.py +++ b/strix/interface/utils.py @@ -733,18 +733,7 @@ def _parse_name_status_z(raw_output: bytes) -> list[DiffEntry]: index += 2 continue - # Backward-compat fallback if output is tab-delimited unexpectedly. - status_fallback, has_tab, first_path = token.partition("\t") - if not has_tab: - break - fallback_code = status_fallback[:1] - fallback_similarity: int | None = None - if len(status_fallback) > 1 and status_fallback[1:].isdigit(): - fallback_similarity = int(status_fallback[1:]) - entries.append( - DiffEntry(status=fallback_code, path=first_path, similarity=fallback_similarity) - ) - index += 1 + break return entries diff --git a/strix/llm/anthropic_cache_wrapper.py b/strix/llm/anthropic_cache_wrapper.py index 3d6f62f..6c7d76c 100644 --- a/strix/llm/anthropic_cache_wrapper.py +++ b/strix/llm/anthropic_cache_wrapper.py @@ -1,13 +1,9 @@ -"""AnthropicCachingLitellmModel — inject cache_control on the system message. +"""``AnthropicCachingLitellmModel`` — inject ``cache_control`` on the system message. -ModelSettings.extra_body lands the field at the request top level, which -Anthropic ignores. Anthropic only honors ``cache_control`` when it is on the -message itself. We patch the input list before delegating to the parent. - -References: - - PLAYBOOK.md §2.1 - - AUDIT.md §2.2 (C2 — original blocker) - - AUDIT_R3.md F1 (signature: first 7 params positional, then *,) +``ModelSettings.extra_body`` lands fields at the request top level, +which Anthropic ignores. Anthropic only honors ``cache_control`` when +it is on the message itself, so we patch the input list before +delegating to the parent. """ from __future__ import annotations diff --git a/strix/llm/multi_provider_setup.py b/strix/llm/multi_provider_setup.py index ac20abb..4dd1167 100644 --- a/strix/llm/multi_provider_setup.py +++ b/strix/llm/multi_provider_setup.py @@ -6,10 +6,6 @@ route so models named ``anthropic/`` go through on the system message). Every other prefix (``openai/`` / ``gemini/`` / ``openrouter/`` / ``litellm/...``) falls through to the SDK's built-in litellm routing. - -References: - - PLAYBOOK.md §2.7 - - AUDIT_R3.md C17 (model alias validation; raise UserError on bad alias) """ from __future__ import annotations diff --git a/strix/llm/strix_session.py b/strix/llm/strix_session.py index 2e51363..747c3b0 100644 --- a/strix/llm/strix_session.py +++ b/strix/llm/strix_session.py @@ -1,24 +1,17 @@ -"""StrixSession — Session wrapper that runs the MemoryCompressor. +"""``StrixSession`` — Session wrapper that runs the MemoryCompressor. -The SDK's `Session` (and ``SessionABC``) protocol owns conversation history -storage. We delegate the actual storage to any underlying session -implementation (in-memory, SQLite, Redis, …) and intercept ``get_items`` so -the ``MemoryCompressor`` runs before the model sees the history. +Delegates storage to any underlying session implementation (in-memory, +SQLite, Redis, …) and intercepts ``get_items`` so the +``MemoryCompressor`` runs before the model sees the history. -Why wrap rather than reimplement: -- ``MemoryCompressor`` already encodes the pentest-tuned summarization - prompt and the 90K-token budget that Strix has been tuning for months. - Reimplementing inside a Session would lose that institutional knowledge. -- The SDK gives us a clean seam in ``get_items``: it's the last call before - ``call_model_input_filter`` runs, so compressing here means the filter - sees a compressed history too. +Wrapping (rather than reimplementing) keeps the pentest-tuned +summarization prompt and 90K-token budget intact. ``get_items`` is +also the last hook before ``call_model_input_filter``, so compressing +here means the filter sees a compressed history too. -References: - - PLAYBOOK.md §2.8 - - AUDIT_R2.md §1.5 (C10 — compressor exception → uncompressed fallback) - - AUDIT_R3.md §3 row W5/E2 — once compression has failed, set a flag and - skip future attempts so we don't infinite-loop on a permanently broken - compressor while the agent loop slowly drowns in context. +If compression raises, we fall back to the uncompressed history and +flip a flag so future attempts skip — a permanently broken compressor +mustn't infinite-loop the agent into context starvation. """ from __future__ import annotations diff --git a/strix/orchestration/bus.py b/strix/orchestration/bus.py index cd2f9f3..5e03927 100644 --- a/strix/orchestration/bus.py +++ b/strix/orchestration/bus.py @@ -1,14 +1,8 @@ -"""AgentMessageBus — peer-to-peer multi-agent state owned by Strix. +"""``AgentMessageBus`` — peer-to-peer multi-agent state for one scan. A single ``asyncio.Lock``-protected dataclass that owns inboxes, parent edges, statuses, and per-agent stats for the lifetime of one Strix scan. - -References: - - PLAYBOOK.md §2.3 - - AUDIT_R2.md §1.4 (cancel_descendants) - - AUDIT_R2.md §1.7 (stats snapshot under lock) - - AUDIT_R3.md C13 (finalize cleans up state to avoid orphaned-message leak) """ from __future__ import annotations @@ -69,9 +63,8 @@ class AgentMessageBus: async def send(self, target: str, msg: dict[str, Any]) -> None: """Append a message to ``target``'s inbox. - Idempotent if target was never registered: creates an empty inbox. - Messages addressed to a finalized agent are dropped silently — the - target's inbox was cleared in :meth:`finalize` (C13). + Messages addressed to a finalized agent are dropped silently — + :meth:`finalize` clears the inbox so they can't accumulate. """ async with self._lock: if target not in self.statuses: @@ -115,9 +108,8 @@ class AgentMessageBus: async def finalize(self, agent_id: str, status: str) -> None: """Move an agent from live to completed; clean up routing state. - C13 (AUDIT_R3): also clears ``inboxes``, ``parent_of``, ``names`` so - sibling agents that try to send to a finished agent don't accumulate - orphan messages forever. + Also clears ``inboxes``, ``parent_of``, ``names`` so siblings + that send to a finished agent can't accumulate orphan messages. """ async with self._lock: self.statuses[agent_id] = status @@ -127,7 +119,7 @@ class AgentMessageBus: self.names.pop(agent_id, None) async def total_stats(self) -> dict[str, Any]: - """Snapshot of live + completed stats. Lock-protected (C12).""" + """Snapshot of live + completed stats.""" async with self._lock: agg = {"in": 0, "out": 0, "cached": 0, "cost": 0.0, "calls": 0} for stats in (*self.stats_live.values(), *self.stats_completed.values()): @@ -138,9 +130,9 @@ class AgentMessageBus: async def cancel_descendants(self, root_agent_id: str) -> None: """Cancel ``root_agent_id`` and every transitive child, leaves first. - Wired into the CLI Ctrl+C handler and TUI stop button so a root cancel - actually propagates (C9 — SDK's ``result.cancel`` does not cascade - to children spawned via ``asyncio.create_task``). + Wired into the CLI Ctrl+C handler and TUI stop button — + the SDK's ``result.cancel`` doesn't cascade to children spawned + via ``asyncio.create_task``, so we walk the tree ourselves. """ async with self._lock: queue = [root_agent_id] diff --git a/strix/orchestration/filter.py b/strix/orchestration/filter.py index e402092..c5256c6 100644 --- a/strix/orchestration/filter.py +++ b/strix/orchestration/filter.py @@ -1,14 +1,8 @@ -"""inject_messages_filter — SDK ``call_model_input_filter`` for the message bus. +"""``inject_messages_filter`` — SDK ``call_model_input_filter`` for the bus. The SDK runs ``call_model_input_filter`` exactly once per turn before -the LLM call (``run_internal/turn_preparation.py:55-80``) and captures -the filter's output in a lambda closure for any subsequent retries -(``run_internal/model_retry.py:34-35``) — so a single drain per turn -does not lose messages on retry. - -References: - - PLAYBOOK.md §2.4 - - AUDIT_R3.md C14 (filter must be defensive — exception → unmodified data) +the LLM call and captures the output in a closure for any subsequent +retries — so a single drain per turn doesn't lose messages on retry. """ from __future__ import annotations @@ -32,12 +26,12 @@ async def inject_messages_filter(data: CallModelData) -> ModelInputData: Each drained message is wrapped in an ```` XML envelope so the system prompt's rules around inter-agent communication apply. - Messages from the literal sender ``"user"`` (a real human via TUI) skip - the XML wrap and are added as plain user messages. + Messages from the literal sender ``"user"`` (a real human via TUI) + skip the XML wrap and are added as plain user messages. - C14: any exception inside the filter — including a malformed message dict - or a bug in ``bus.drain`` — is caught and the original ``data.model_data`` - is returned unmodified. A bug in the filter must never tear down the run. + Any exception inside the filter — malformed message dict, bug in + ``bus.drain``, etc. — is caught and the original ``data.model_data`` + is returned unmodified. A bug here must never tear down the run. """ try: if not isinstance(data.context, dict): diff --git a/strix/orchestration/hooks.py b/strix/orchestration/hooks.py index 919ccdf..3c5c68b 100644 --- a/strix/orchestration/hooks.py +++ b/strix/orchestration/hooks.py @@ -1,13 +1,4 @@ -"""StrixOrchestrationHooks — RunHooks subclass wiring bus + tracer + warnings. - -References: - - PLAYBOOK.md §2.5 - - AUDIT.md §2.5 (C5 — streaming/hook bridge) - - AUDIT_R2.md §1.3 (C8 — subagent crash detection) - - AUDIT_R3.md F2 (context types: AgentHookContext for agent_*, - RunContextWrapper otherwise; on_tool_end result is str) - - AUDIT_R3.md C15 (every hook body try/except so a hook bug never tears down the run) -""" +"""``StrixOrchestrationHooks`` — RunHooks wiring bus + tracer + warnings.""" from __future__ import annotations @@ -27,16 +18,17 @@ class StrixOrchestrationHooks(RunHooks[Any]): Wires four concerns: - 1. Turn-budget warnings injected into ``input_items`` at 85% and ``N - 3`` - of ``max_turns``. - 2. LLM usage recording into the bus. - 3. Sandbox readiness: awaits the ``CaidoCapability._healthcheck_task`` - on first agent start so the agent doesn't fire tools before Caido and - the tool server are ready. - 4. Subagent crash detection (C8): if ``on_agent_end`` fires without - ``agent_finish_called`` being set in context, posts a synthetic - ```` message to the parent's inbox so the parent learns - on its next turn instead of polling ``wait_for_message`` forever. + 1. Turn-budget warnings injected into ``input_items`` at 85% and + ``N - 3`` of ``max_turns``. + 2. LLM usage recording into the bus + tracer. + 3. Sandbox readiness: awaits the + ``CaidoCapability._healthcheck_task`` on first agent start so + the agent doesn't fire tools before Caido and the tool server + are ready. + 4. Subagent crash detection: if ``on_agent_end`` fires without + ``agent_finish_called`` being set, posts a synthetic + ```` message to the parent's inbox so the parent + learns on its next turn instead of waiting forever. """ async def on_llm_start( @@ -105,7 +97,7 @@ class StrixOrchestrationHooks(RunHooks[Any]): input_tokens=int(getattr(usage, "input_tokens", 0) or 0), output_tokens=int(getattr(usage, "output_tokens", 0) or 0), cached_tokens=cached, - cost=0.0, # litellm cost computation lives in the legacy LLM + cost=0.0, requests=1, bucket="live", ) @@ -203,12 +195,3 @@ class StrixOrchestrationHooks(RunHooks[Any]): tracer.log_tool_end(ctx.get("agent_id", "?"), tool.name, result) except Exception: logger.exception("on_tool_end failed") - - async def on_handoff( - self, - context: RunContextWrapper[Any], - from_agent: Any, - to_agent: Any, - ) -> None: - # Strix multi-agent goes through the bus; SDK handoffs are unused. - pass diff --git a/strix/run_config_factory.py b/strix/run_config_factory.py index 8f051af..42436a3 100644 --- a/strix/run_config_factory.py +++ b/strix/run_config_factory.py @@ -1,18 +1,7 @@ -"""make_run_config — assemble a Strix-flavored ``RunConfig`` for ``Runner.run``. +"""``make_run_config`` — assemble a Strix-flavored ``RunConfig`` for ``Runner.run``. -Factory pattern: every Strix scan goes through here so the defaults are -applied uniformly. Per-call overrides are accepted via ``model_settings_override`` -for the rare case a single run wants different reasoning effort or -``tool_choice`` (C21). - -References: - - PLAYBOOK.md §2.10 - - AUDIT.md §2.1 (C1 — parallel_tool_calls=False until Phase 6 relaxes the - tool server's per-agent task slot serialization) - - AUDIT_R2.md §1.6 (C11 — retry policy explicitly excludes 401/403/400; - auth and validation errors must fail fast, not waste retries) - - AUDIT_R3.md C21 — RunConfig override + context fields including - ``is_whitebox``, ``diff_scope``, ``run_id`` +Every scan goes through here so defaults apply uniformly. Per-call +overrides land via ``model_settings_override``. """ from __future__ import annotations @@ -39,14 +28,12 @@ if TYPE_CHECKING: from strix.orchestration.bus import AgentMessageBus -# Phase 6 relaxes the tool server's per-agent task-slot serialization -# (``runtime/tool_server.py:94-97``) and flips this to ``True`` after -# multi-agent stress tests confirm safety. -_PHASE1_PARALLEL_DEFAULT = False +# Sequential tool calls per agent — the tool server serializes one task +# per agent at a time, so concurrent calls would queue anyway. +_PARALLEL_TOOL_CALLS_DEFAULT = False -# Default retry policy. Explicitly does NOT include 401/403/400 — those are -# auth and validation errors that retrying cannot fix; they should fail fast -# so the user sees the real error within seconds. 429/5xx is the right set. +# Retry policy. 401/403/400 are deliberately excluded — auth and +# validation errors can't be fixed by retrying and should fail fast. _RETRYABLE_HTTP_STATUSES = (429, 500, 502, 503, 504) # Default retry budget: 5 attempts with ``min(90, 2*2^n)`` backoff. @@ -82,7 +69,7 @@ def make_run_config( *, sandbox_session: BaseSandboxSession | None, model: str = "anthropic/claude-sonnet-4-6", - parallel_tool_calls: bool = _PHASE1_PARALLEL_DEFAULT, + parallel_tool_calls: bool = _PARALLEL_TOOL_CALLS_DEFAULT, tool_choice: Literal["auto", "required", "none"] | None = "required", reasoning_effort: Literal["low", "medium", "high"] | None = None, model_settings_override: ModelSettings | None = None, @@ -90,32 +77,27 @@ def make_run_config( ) -> RunConfig: """Build a ``RunConfig`` with Strix defaults. - Note: ``max_turns`` and ``isolate_parallel_failures`` are NOT - ``RunConfig`` fields — they are passed directly to ``Runner.run``. - Use ``STRIX_DEFAULT_MAX_TURNS`` for the budget; pass - ``isolate_parallel_failures=False`` to ``Runner.run`` if Phase 6 has - not yet flipped ``parallel_tool_calls=True``. + Note: ``max_turns`` is not a ``RunConfig`` field — pass it directly + to ``Runner.run``. ``STRIX_DEFAULT_MAX_TURNS`` is the budget Strix + uses. Args: - sandbox_session: Live sandbox session shared by every agent in this - scan (one container per scan; see ``strix.sandbox.session_manager``). - ``None`` is allowed for unit tests and dry runs. - model: Model alias to pass to ``MultiProvider``. Defaults to the - current production-favored Anthropic alias. - parallel_tool_calls: Default ``False`` to keep behavior sequential - per the tool server's slot serialization (C1). + sandbox_session: Live sandbox session shared by every agent in + this scan (one container per scan; see + :mod:`strix.sandbox.session_manager`). ``None`` is allowed + for unit tests and dry runs. + model: Model alias passed to ``MultiProvider``. Defaults to the + production Anthropic alias. + parallel_tool_calls: Default ``False`` — the tool server + serializes one task per agent. tool_choice: Forces tool use per turn unless explicitly relaxed. - Pass ``None`` to omit. reasoning_effort: ``"low" | "medium" | "high"``; routes to - ``ModelSettings.reasoning``. ``None`` defers to provider default. - model_settings_override: Optional ``ModelSettings`` to merge over - the factory defaults (C21 — per-run override path). - sandbox_client: Optional pre-built sandbox client (e.g., the Strix - Docker subclass). Defaults to ``None``; the SDK will instantiate - its built-in if a session is supplied without a client. - - Returns: - A ``RunConfig`` ready to pass to ``Runner.run``. + ``ModelSettings.reasoning``. + model_settings_override: Optional per-run ``ModelSettings`` + merged over factory defaults. + sandbox_client: Optional pre-built sandbox client (Strix Docker + subclass). The SDK instantiates its built-in if a session is + supplied without a client. """ base_settings = ModelSettings( parallel_tool_calls=parallel_tool_calls, @@ -175,14 +157,14 @@ def make_agent_context( ) -> dict[str, Any]: """Build the per-agent ``context`` dict passed to ``Runner.run(context=...)``. - The dict is the canonical place where bus, sandbox handles, identity, - tracer reference, and per-agent toggles live. Tools, hooks, and the - ``inject_messages_filter`` all reach in via ``ctx.context.get(...)``. + The canonical place where bus, sandbox handles, identity, tracer + reference, and per-agent toggles live. Tools, hooks, and + ``inject_messages_filter`` reach in via ``ctx.context.get(...)``. - ``agent_factory`` is a callable ``(name, skills) -> agents.Agent`` used by - the ``create_agent`` graph tool to spin up children. ``sandbox_client`` - is the host-side Docker subclass; ``create_agent`` reuses it across - child runs. + ``agent_factory`` is a callable ``(name, skills) -> agents.Agent`` — + the ``create_agent`` graph tool uses it to spin up children that + inherit the same wiring. ``sandbox_client`` is the host-side Docker + subclass, reused across child runs. """ return { "bus": bus, diff --git a/strix/runtime/__init__.py b/strix/runtime/__init__.py index 72f3b1a..406fc09 100644 --- a/strix/runtime/__init__.py +++ b/strix/runtime/__init__.py @@ -1,31 +1,13 @@ """Strix runtime package. -What lives here: +- :class:`strix.runtime.strix_docker_client.StrixDockerSandboxClient` — + host-side ``DockerSandboxClient`` subclass that injects + ``NET_ADMIN`` / ``NET_RAW`` capabilities and ``host.docker.internal`` + extra-hosts, used by the per-scan session manager + (:mod:`strix.sandbox.session_manager`). -- :class:`StrixDockerSandboxClient` — host-side ``DockerSandboxClient`` - subclass that injects ``NET_ADMIN`` / ``NET_RAW`` capabilities and - ``host.docker.internal`` extra-hosts, used by the per-scan session - manager (:mod:`strix.sandbox.session_manager`). - -- ``tool_server.py`` — the FastAPI server that runs *inside* the - sandbox container; sandbox-bound tools (browser, terminal, python, - file_edit, proxy) POST here from the host via +- ``tool_server.py`` — FastAPI server that runs inside the sandbox + container. Sandbox-bound tools (browser, terminal, python, file_edit, + proxy) POST here from the host via :func:`strix.tools._sandbox_dispatch.post_to_sandbox`. - -The legacy DockerRuntime / AbstractRuntime + ``get_runtime`` / -``cleanup_runtime`` globals were removed when the SDK harness took -over scan lifecycle; sandbox sessions are now per-scan and managed by -:func:`strix.sandbox.session_manager.create_or_reuse`. """ - - -class SandboxInitializationError(Exception): - """Raised when sandbox initialization fails (e.g., Docker issues).""" - - def __init__(self, message: str, details: str | None = None): - super().__init__(message) - self.message = message - self.details = details - - -__all__ = ["SandboxInitializationError"] diff --git a/strix/runtime/strix_docker_client.py b/strix/runtime/strix_docker_client.py index b8de205..8970030 100644 --- a/strix/runtime/strix_docker_client.py +++ b/strix/runtime/strix_docker_client.py @@ -11,13 +11,8 @@ additions before the final create call: These are required for raw-socket pentest tools (nmap -sS) and for letting the agent reach host-served apps via ``host.docker.internal``. -Pinned to ``openai-agents==0.14.6``. Bumping the SDK version requires -re-merging the parent body. Track upstream PR for an injection hook. - -References: - - PLAYBOOK.md §2.2 - - AUDIT.md §2.3 (C3 — original blocker) - - SDK source: ``/tmp/openai-agents/src/agents/sandbox/sandboxes/docker.py:1434-1477`` +Pinned to ``openai-agents==0.14.6``. Bumping the SDK requires +re-merging the parent body. Track upstream for an injection hook. """ from __future__ import annotations diff --git a/strix/sandbox/__init__.py b/strix/sandbox/__init__.py index d8ade2b..37788a1 100644 --- a/strix/sandbox/__init__.py +++ b/strix/sandbox/__init__.py @@ -1,7 +1,8 @@ """Strix sandbox layer on top of OpenAI Agents SDK SandboxAgent / Manifest. -Phase 4 deliverables: -- CaidoCapability: Caido proxy + 7 GraphQL function tools + system prompt block -- healthcheck: wait_for_ports_ready -- session_manager: create_or_reuse / cleanup keyed by scan_id +- :mod:`.caido_capability` — Caido proxy + 7 GraphQL function tools + + system prompt block. +- :mod:`.healthcheck` — ``wait_for_ports_ready``. +- :mod:`.session_manager` — ``create_or_reuse`` / ``cleanup`` keyed + by scan id. """ diff --git a/strix/sandbox/caido_capability.py b/strix/sandbox/caido_capability.py index 3d6a41a..0ac6f28 100644 --- a/strix/sandbox/caido_capability.py +++ b/strix/sandbox/caido_capability.py @@ -8,10 +8,9 @@ Three concerns wired into the SDK's capability lifecycle: etc.) now flows through the proxy automatically. 2. **Tool exposure** (``tools``): the seven Caido SDK function-tool - wrappers from Phase 2.5 are returned here. The SDK runtime collects - tools from every capability and merges them with the agent's - ``tools=[...]`` declaration, so individual agents don't have to - redeclare them. + wrappers are returned here. The SDK runtime collects tools from + every capability and merges them with the agent's ``tools=[...]`` + declaration, so agents don't have to redeclare them. 3. **Healthcheck task** (``bind``): when a session binds, we kick off :func:`wait_for_http_ready` against the FastAPI tool server's @@ -21,10 +20,6 @@ Three concerns wired into the SDK's capability lifecycle: :class:`StrixOrchestrationHooks.on_agent_start` hook awaits before the first LLM call so the agent never hits a connection-refused on its very first tool invocation. - -References: - - PLAYBOOK.md §3.2 - - AUDIT.md §2.5 (C5 — healthcheck wired to RunHooks) """ from __future__ import annotations diff --git a/strix/sandbox/healthcheck.py b/strix/sandbox/healthcheck.py index 385b0c1..48c1254 100644 --- a/strix/sandbox/healthcheck.py +++ b/strix/sandbox/healthcheck.py @@ -16,9 +16,6 @@ Two helpers are exposed: - :func:`wait_for_tcp_ready` for Caido, which serves an HTTP forward proxy on its port and does *not* expose ``/health``. A TCP connect is the most we can probe without sending real proxy traffic. - -References: - - PLAYBOOK.md §3.1 """ from __future__ import annotations diff --git a/strix/sandbox/session_manager.py b/strix/sandbox/session_manager.py index 01c1c73..3f558ad 100644 --- a/strix/sandbox/session_manager.py +++ b/strix/sandbox/session_manager.py @@ -12,9 +12,6 @@ issuing multiple ``create_or_reuse`` calls (e.g., resume after a crash on the host side) gets the same bundle back. ``cleanup`` is best-effort — a leaked container is preferable to a stuck cleanup that prevents the next scan from starting. - -References: - - PLAYBOOK.md §3.3 """ from __future__ import annotations diff --git a/strix/skills/__init__.py b/strix/skills/__init__.py index 37ffc58..5ffe9f5 100644 --- a/strix/skills/__init__.py +++ b/strix/skills/__init__.py @@ -1,167 +1,56 @@ +import logging import re from strix.utils.resource_paths import get_strix_resource_path -_EXCLUDED_CATEGORIES = {"scan_modes", "coordination"} +logger = logging.getLogger(__name__) + _FRONTMATTER_PATTERN = re.compile(r"^---\s*\n.*?\n---\s*\n", re.DOTALL) -def get_available_skills() -> dict[str, list[str]]: - skills_dir = get_strix_resource_path("skills") - available_skills: dict[str, list[str]] = {} - - if not skills_dir.exists(): - return available_skills - - for category_dir in skills_dir.iterdir(): - if category_dir.is_dir() and not category_dir.name.startswith("__"): - category_name = category_dir.name - - if category_name in _EXCLUDED_CATEGORIES: - continue - - skills = [] - - for file_path in category_dir.glob("*.md"): - skill_name = file_path.stem - skills.append(skill_name) - - if skills: - available_skills[category_name] = sorted(skills) - - return available_skills - - -def get_all_skill_names() -> set[str]: - all_skills = set() - for category_skills in get_available_skills().values(): - all_skills.update(category_skills) - return all_skills - - -def validate_skill_names(skill_names: list[str]) -> dict[str, list[str]]: - available_skills = get_all_skill_names() - valid_skills = [] - invalid_skills = [] - - for skill_name in skill_names: - if skill_name in available_skills: - valid_skills.append(skill_name) - else: - invalid_skills.append(skill_name) - - return {"valid": valid_skills, "invalid": invalid_skills} - - -def parse_skill_list(skills: str | None) -> list[str]: - if not skills: - return [] - return [s.strip() for s in skills.split(",") if s.strip()] - - -def validate_requested_skills(skill_list: list[str], max_skills: int = 5) -> str | None: - if len(skill_list) > max_skills: - return "Cannot specify more than 5 skills for an agent (use comma-separated format)" - - if not skill_list: - return None - - validation = validate_skill_names(skill_list) - if validation["invalid"]: - available_skills = list(get_all_skill_names()) - return ( - f"Invalid skills: {validation['invalid']}. " - f"Available skills: {', '.join(available_skills)}" - ) - - return None - - -def generate_skills_description() -> str: - available_skills = get_available_skills() - - if not available_skills: - return "No skills available" - - all_skill_names = get_all_skill_names() - - if not all_skill_names: - return "No skills available" - - sorted_skills = sorted(all_skill_names) - skills_str = ", ".join(sorted_skills) - - description = f"List of skills to load for this agent (max 5). Available skills: {skills_str}. " - - example_skills = sorted_skills[:2] - if example_skills: - example = f"Example: {', '.join(example_skills)} for specialized agent" - description += example - - return description - - -def _get_all_categories() -> dict[str, list[str]]: - """Get all skill categories including internal ones (scan_modes, coordination).""" - skills_dir = get_strix_resource_path("skills") - all_categories: dict[str, list[str]] = {} - - if not skills_dir.exists(): - return all_categories - - for category_dir in skills_dir.iterdir(): - if category_dir.is_dir() and not category_dir.name.startswith("__"): - category_name = category_dir.name - skills = [] - - for file_path in category_dir.glob("*.md"): - skill_name = file_path.stem - skills.append(skill_name) - - if skills: - all_categories[category_name] = sorted(skills) - - return all_categories - - def load_skills(skill_names: list[str]) -> dict[str, str]: - import logging + """Load skill markdown bodies (frontmatter stripped) by name. - logger = logging.getLogger(__name__) - skill_content = {} + Skill files live at ``strix/skills//.md``. Names + can be ``"name"`` (any category), ``"category/name"``, or a bare + file at the skills root. Missing skills are logged and skipped. + """ skills_dir = get_strix_resource_path("skills") + if not skills_dir.exists(): + return {} - all_categories = _get_all_categories() + by_category: dict[str, str] = {} + for category_dir in skills_dir.iterdir(): + if not category_dir.is_dir() or category_dir.name.startswith("__"): + continue + for file_path in category_dir.glob("*.md"): + by_category[file_path.stem] = f"{category_dir.name}/{file_path.stem}.md" + skill_content: dict[str, str] = {} for skill_name in skill_names: + rel_path: str | None + if "/" in skill_name: + rel_path = f"{skill_name}.md" + elif skill_name in by_category: + rel_path = by_category[skill_name] + elif (skills_dir / f"{skill_name}.md").exists(): + rel_path = f"{skill_name}.md" + else: + rel_path = None + + if rel_path is None or not (skills_dir / rel_path).exists(): + logger.warning("Skill not found: %s", skill_name) + continue + try: - skill_path = None + content = (skills_dir / rel_path).read_text(encoding="utf-8") + except (OSError, ValueError) as e: + logger.warning("Failed to load skill %s: %s", skill_name, e) + continue - if "/" in skill_name: - skill_path = f"{skill_name}.md" - else: - for category, skills in all_categories.items(): - if skill_name in skills: - skill_path = f"{category}/{skill_name}.md" - break - - if not skill_path: - root_candidate = f"{skill_name}.md" - if (skills_dir / root_candidate).exists(): - skill_path = root_candidate - - if skill_path and (skills_dir / skill_path).exists(): - full_path = skills_dir / skill_path - var_name = skill_name.split("/")[-1] - content = full_path.read_text(encoding="utf-8") - content = _FRONTMATTER_PATTERN.sub("", content).lstrip() - skill_content[var_name] = content - logger.info(f"Loaded skill: {skill_name} -> {var_name}") - else: - logger.warning(f"Skill not found: {skill_name}") - - except (FileNotFoundError, OSError, ValueError) as e: - logger.warning(f"Failed to load skill {skill_name}: {e}") + var_name = skill_name.split("/")[-1] + skill_content[var_name] = _FRONTMATTER_PATTERN.sub("", content).lstrip() + logger.info("Loaded skill: %s -> %s", skill_name, var_name) return skill_content diff --git a/strix/telemetry/strix_processor.py b/strix/telemetry/strix_processor.py index 0b1e9b4..8ec2040 100644 --- a/strix/telemetry/strix_processor.py +++ b/strix/telemetry/strix_processor.py @@ -1,14 +1,8 @@ -"""StrixTracingProcessor — SDK trace processor that writes events.jsonl. +"""``StrixTracingProcessor`` — SDK trace processor that writes ``events.jsonl``. Hooks into the SDK's tracing pipeline and writes events to -``strix_runs//events.jsonl``. PII scrubbing via the existing -``TelemetrySanitizer``. - -References: - - PLAYBOOK.md §2.9 - - AUDIT_R2.md §1.2 (C7 — JSONL writes must be lock-protected) - - AUDIT_R3.md C16 (writes must catch OSError; never tear down the run) - - AUDIT_R3.md F3 (every TracingProcessor hook is SYNC, not async) +``strix_runs//events.jsonl``. PII scrubbing runs through +:class:`TelemetrySanitizer`. """ from __future__ import annotations @@ -47,17 +41,11 @@ def _lock_for(path: Path) -> threading.Lock: class StrixTracingProcessor(TracingProcessor): """Append trace + span events as JSONL into ``run_dir/events.jsonl``. - Every hook is synchronous — required by ``TracingProcessor`` ABC. - Every write is protected by a per-path ``threading.Lock`` so concurrent - spans (e.g., from parallel agent tasks) cannot interleave bytes - mid-line and corrupt the JSONL (C7). - - Every write is wrapped in ``try/except OSError`` so a full disk or a - permission error during the run does NOT propagate up the hook chain - and tear down the agent (C16). - - PII scrubbing via :class:`TelemetrySanitizer` runs on every event - before it hits the file. + Every hook is synchronous — required by the ``TracingProcessor`` + ABC. Each write is protected by a per-path ``threading.Lock`` so + concurrent spans can't interleave bytes mid-line. ``OSError`` is + swallowed so a full disk or permission error doesn't tear the run + down. PII scrubbing runs on every event before it hits the file. """ def __init__( @@ -80,8 +68,8 @@ class StrixTracingProcessor(TracingProcessor): def _emit(self, event: dict[str, Any]) -> None: """Sanitize ``event`` and append it as one JSONL line. - Failures are swallowed — we'd rather lose a trace event than fail - the run. Errors are logged at WARNING (C16). + Failures are swallowed — we'd rather lose a trace event than + fail the run. """ try: clean = self.sanitizer.sanitize(event) diff --git a/strix/tools/__init__.py b/strix/tools/__init__.py index 3862fef..0fe1fbd 100644 --- a/strix/tools/__init__.py +++ b/strix/tools/__init__.py @@ -17,13 +17,7 @@ from .finish import * # noqa: F403 from .notes import * # noqa: F403 from .proxy import * # noqa: F403 from .python import * # noqa: F403 -from .registry import ( - ImplementedInClientSideOnlyError, - get_tool_by_name, - get_tool_names, - register_tool, - tools, -) +from .registry import get_tool_by_name, get_tool_names, register_tool, tools from .reporting import * # noqa: F403 from .terminal import * # noqa: F403 from .thinking import * # noqa: F403 @@ -32,7 +26,6 @@ from .web_search import * # noqa: F403 __all__ = [ - "ImplementedInClientSideOnlyError", "get_tool_by_name", "get_tool_names", "register_tool", diff --git a/strix/tools/_decorator.py b/strix/tools/_decorator.py index c52a7ae..944f541 100644 --- a/strix/tools/_decorator.py +++ b/strix/tools/_decorator.py @@ -1,24 +1,15 @@ -"""strix_tool — function_tool factory with Strix defaults. +"""``strix_tool`` — ``function_tool`` factory with Strix defaults. -Every tool in the migrated harness should be decorated with ``@strix_tool`` -instead of bare ``@function_tool`` so the team's defaults stay consistent -without per-tool boilerplate. Override per call when needed. +Every tool uses ``@strix_tool`` instead of bare ``@function_tool`` so +defaults stay consistent across the suite. Override per call when +needed. Defaults: - - ``timeout``: 120s (matches the legacy tool server's - ``STRIX_SANDBOX_EXECUTION_TIMEOUT``). + - ``timeout``: 120s. - ``timeout_behavior``: ``"error_as_result"`` for idempotent tools. - Critical sandbox tools (terminal, browser, python) should pass - ``timeout_behavior="raise_exception"`` explicitly so the SDK can fail - the run rather than letting the model retry the same hung call (C20). - -The SDK auto-threads sync function bodies via ``asyncio.to_thread`` -(``tool.py:1820-1829``), so libtmux / IPython / blocking httpx code can be -written as plain ``def`` and the decorator will not block the event loop. - -References: - - PLAYBOOK.md §2.6 - - AUDIT_R3.md C20 (per-tool timeout_behavior discrimination) + Critical sandbox tools (terminal, browser, python) opt into + ``timeout_behavior="raise_exception"`` explicitly so the SDK + fails the run rather than letting the model retry a hung call. """ from __future__ import annotations @@ -44,17 +35,10 @@ def strix_tool( ) -> Callable[[_ToolFn], FunctionTool]: """Wrap ``agents.function_tool`` with Strix defaults. - The SDK's ``FunctionTool`` requires ``async def`` for ``timeout_seconds`` - to apply (sync handlers cannot be cleanly cancelled). All Strix tools are - ``async def``; sync libraries (libtmux, IPython) get wrapped in - ``asyncio.to_thread`` inside the async tool body. - - The SDK enforces ``strict_mode=True`` by default, which forbids - free-form ``dict[str, X]`` parameters (the strict JSON schema needs - ``additionalProperties: false``). A handful of legacy tools - (``send_request``, ``repeat_request``) take arbitrary header / - modification dicts whose keys can't be enumerated, so they must - opt out of strict mode to preserve parity with the XML schema. + Strict mode is on by default (forbids free-form ``dict[str, X]`` + parameters because the strict JSON schema needs + ``additionalProperties: false``). A few tools that take arbitrary + header / modification dicts opt out via ``strict_mode=False``. Usage:: diff --git a/strix/tools/_sandbox_dispatch.py b/strix/tools/_sandbox_dispatch.py index d44a060..ef5305a 100644 --- a/strix/tools/_sandbox_dispatch.py +++ b/strix/tools/_sandbox_dispatch.py @@ -1,24 +1,14 @@ -"""post_to_sandbox — host-to-container HTTP transport for sandbox tools. +"""``post_to_sandbox`` — host-to-container HTTP transport for sandbox tools. -Every Strix tool that runs inside the Kali container (browser, terminal, -python, the seven Caido tools) has the same wire shape: POST a JSON body -to ``http://localhost:{tool_server_host_port}/execute`` with a Bearer -token header and ``{"agent_id", "tool_name", "kwargs"}`` as the body. +Every Strix tool that runs inside the Kali container (browser, +terminal, python, file_edit, the seven Caido tools) has the same wire +shape: POST JSON to ``http://localhost:{tool_server_host_port}/execute`` +with a Bearer token and ``{"agent_id", "tool_name", "kwargs"}`` body. -This helper centralizes that transport so: - -- Every sandbox tool gets the same timeout policy - (``connect=10s`` / ``read=150s``). -- Every sandbox tool inherits the same response-size cap (50 MB) so a - runaway tool body cannot OOM the host (C18). -- Auth/transport errors surface as predictable error strings instead of - exceptions, so the model can retry / pick a different tool without the - run dying. - -References: - - PLAYBOOK.md §3.4 - - AUDIT_R3.md C18 (sandbox response size cap) - - HARNESS_WIKI.md §7.2 (legacy executor.py wire format we mirror) +The helper centralizes timeouts (``connect=10s`` / ``read=150s``), a +50 MB response-size cap so a runaway tool can't OOM the host, and +predictable error-string shaping so transport failures don't tear +down the run. """ from __future__ import annotations @@ -36,14 +26,9 @@ if TYPE_CHECKING: logger = logging.getLogger(__name__) -# Connect: how long to wait for the TCP handshake to complete. -# Read: how long the tool may spend executing before we abandon the call. -# Mirrors the legacy executor.py (``SANDBOX_EXECUTION_TIMEOUT = 120 + 30``). _SANDBOX_TIMEOUT = httpx.Timeout(connect=10.0, read=150.0, write=150.0, pool=150.0) -#: Cap on response body size from the tool server. Anything bigger is -#: replaced by an error string so the model sees something coherent and -#: the host doesn't OOM trying to allocate the buffer (C18). +# Cap so a runaway tool body never blows up the host heap. _MAX_RESPONSE_BYTES = 50 * 1024 * 1024 # 50 MB diff --git a/strix/tools/agents_graph/tools.py b/strix/tools/agents_graph/tools.py index d9a78db..cf82ad8 100644 --- a/strix/tools/agents_graph/tools.py +++ b/strix/tools/agents_graph/tools.py @@ -1,29 +1,16 @@ -"""SDK function-tool wrappers for the multi-agent graph tools. +"""Multi-agent graph tools — read/write the :class:`AgentMessageBus`. -Six tools that read/write the :class:`AgentMessageBus` (built in Phase 0, -``strix.orchestration.bus``): - -- ``view_agent_graph``: render the parent/child tree from ``bus.parent_of``. +- ``view_agent_graph``: render the parent/child tree. - ``agent_status``: per-agent status + pending message count. -- ``send_message_to_agent``: peer-to-peer message into a child/sibling inbox. -- ``wait_for_message``: poll our own inbox until a message arrives or the - timeout expires (the legacy harness's "I'm idle, wake me on inbox"). -- ``create_agent``: spawn a child via ``asyncio.create_task(Runner.run(...))``; - registers the child with the bus and stores its task handle so root cancels - cascade (C9, ``bus.cancel_descendants``). -- ``agent_finish``: subagents only — flips ``agent_finish_called`` so the - on_agent_end hook records "completed" rather than "crashed" (C8), and - posts a structured completion report to the parent's inbox. - -The legacy ``strix.tools.agents_graph.agents_graph_actions`` is left -untouched — it still drives the legacy harness. These wrappers only -target the bus and don't touch the legacy ``_agent_graph`` dict. - -References: - - PLAYBOOK.md §4.3 - - AUDIT_R2.md §1.4 (cancel_descendants — Runner.run task handle stored - in bus.tasks so a root cancel walks the tree) - - AUDIT_R3.md C8 (crash detection via on_agent_end + agent_finish_called) +- ``send_message_to_agent``: queue a message in another agent's inbox. +- ``wait_for_message``: pause this agent until a message arrives or + ``timeout_seconds`` elapses. +- ``create_agent``: spawn a child via + ``asyncio.create_task(Runner.run(...))``; the task handle is stored + so a root-level cancel cascades to descendants. +- ``agent_finish``: subagents only — flips ``agent_finish_called`` so + the ``on_agent_end`` hook records "completed" rather than "crashed", + and posts a structured completion report to the parent's inbox. """ from __future__ import annotations @@ -223,8 +210,7 @@ async def send_message_to_agent( ) -# Polling cadence for ``wait_for_message``. 1s matches the PLAYBOOK -# skeleton; tighter would burn CPU, slacker would feel laggy when a sibling +# Tighter would burn CPU; slacker would feel laggy when a sibling # delivers a message right after the wait starts. _WAIT_POLL_SECONDS = 1.0 diff --git a/strix/tools/registry.py b/strix/tools/registry.py index 263493c..518bc8f 100644 --- a/strix/tools/registry.py +++ b/strix/tools/registry.py @@ -1,14 +1,14 @@ """Minimal in-container tool registry. Used inside the sandbox container by ``strix.runtime.tool_server`` to -look up `@register_tool`-decorated functions by name. Sandbox-bound -tools (browser, terminal, python, file_edit, proxy) live as legacy +look up ``@register_tool``-decorated functions by name. Sandbox-bound +tools (browser, terminal, python, file_edit, proxy) live as ``*_actions.py`` modules with this decoration; the host POSTs to :func:`tool_server.execute_tool` which dispatches via :func:`get_tool_by_name`. -Host-side tools are pure SDK function tools wired through -:mod:`strix.agents.factory` and don't touch this registry at all. +Host-side SDK function tools are wired through +:mod:`strix.agents.factory` and don't touch this registry. """ import logging @@ -25,17 +25,6 @@ tools: list[dict[str, Any]] = [] _tools_by_name: dict[str, Callable[..., Any]] = {} -class ImplementedInClientSideOnlyError(Exception): - """Raised by sandbox-side stubs whose real implementation lives host-side.""" - - def __init__( - self, - message: str = "This tool is implemented in the client side only", - ) -> None: - self.message = message - super().__init__(self.message) - - def _is_sandbox_mode() -> bool: return os.getenv("STRIX_SANDBOX_MODE", "false").lower() == "true"