9d7f754b5944ce3f7633237bdc9ad8aafbadc17f
408 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
9d7f754b59 |
feat(tools): python_action — stateless Python execution with proxy helpers
Restores the legacy persistent-IPython tool's *ergonomics* (proxy helpers pre-bound, structured stdout/stderr/error returns) without the in-container daemon: each call ships ``strix.tools.proxy._calls`` source into ``/tmp`` alongside a per-call driver, runs ``python3 -u`` against it, and parses a sentinel-delimited JSON payload back from stdout. The driver fetches its own guest token from Caido at ``localhost:48080`` and binds ``list_requests`` / ``view_request`` / ``send_request`` / ``repeat_request`` / ``scope_rules`` to that client; user code runs inside an ``async def`` wrapper so top-level ``await`` works. The proxy SDK call sequences live in one file — ``strix/tools/proxy/_calls.py`` — and are reused by both the host-side ``@function_tool`` wrappers (which add JSON serialization for the LLM) and the in-container kernel (which exposes the bare async functions). No code duplication; the helper logic itself is host-shipped, so tweaking the proxy helpers does not require an image rebuild. Image: a single ``pip install caido-sdk-client`` line so the driver's ``import caido_sdk_client`` resolves. Skill ``tooling/python`` is always-loaded alongside ``tooling/agent_browser``. Trade-off accepted: state does not persist across calls (no kernel). For multi-step workflows the agent combines into one ``code`` block or writes a script to ``/workspace/scratch/`` and runs via ``exec_command``. If a workflow surfaces that genuinely needs persistence, the same tool surface migrates to a kernel-backed executor without changing the LLM contract. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
767dc83581 |
chore(image): bump caido-cli v0.48.0 → v0.56.0; parametrize via CAIDO_VERSION
The pinned URL pattern (https://caido.download/releases/v<X>/caido-cli-v<X>-linux-<arch>.tar.gz) is canonical — it's published by api.caido.io/releases/latest. HEAD requests return 404 because the upstream R2 bucket only honors GET-with-redirect, but the wget call in the Dockerfile uses GET so the original URL was never actually broken — it was just stale. Switch to an ARG so future bumps are a single --build-arg override. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
72d932f6c4 |
refactor: collapse strix/io/, strix/run_config_factory.py, strix/entry.py
Three top-level files that didn't earn their place: - ``strix/io/scan_artifacts.py`` had a single consumer (the Tracer); collapsing it into ``strix/telemetry/`` puts it next to that consumer. ``strix/io/`` is gone. - ``strix/run_config_factory.py`` held two helpers that didn't earn the factoring. ``make_agent_context`` was a 17-line dict-spelling function whose argument names were identical to its dict keys — replaced with inline dict literals at the two call sites. ``make_run_config`` had enough RunConfig assembly logic to justify a helper, but with only two callers (root scan + ``create_agent``) inlining is cleaner than keeping a top-level file. ``DEFAULT_RETRY`` moves to ``strix/llm/retry.py`` next to its other LLM-policy peers; the dead ``STRIX_DEFAULT_MAX_TURNS`` constant is dropped. - ``strix/entry.py`` is a misnomer — it isn't *the* entry point (that's ``strix/interface/main.py`` for the CLI), it's the per-scan bring-up driver: build the bus, bring up the sandbox, build the root agent + child factory, format the scope-context block, register root in bus, open SQLiteSession, hand off to ``run_with_continuation``. That all lives next to its peers in ``strix/orchestration/`` now, renamed to ``scan.py`` so the role is obvious. No behavior change. Net -125 LoC. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
5253332906 |
fix(telemetry): capture tool args in tool_executions for TUI renderers
The 19 tool renderers under strix/interface/tool_components/ all read
tool_data.get("args", {}) to render meaningful previews (URLs, methods,
note titles, vuln severities, etc.). After the SDK migration,
tracer.log_tool_start was only recording tool_name — every renderer
silently fell back to its empty-args path and the TUI lost its
per-call context.
Pull args from the SDK-native ToolContext (tool_input when parsed,
otherwise json-decode tool_arguments) and stash them on the
tool_executions entry. log_tool_start now takes an optional args dict;
existing callers pass nothing and get the empty-dict default.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
6bdaa843d9 |
docs(finish_scan): elevate the active-agent check to a mandatory pre-flight
Audit flagged that legacy ``finish_scan`` had a code-level guard (``_check_active_agents``) that refused completion if any subagent was still running or stopping. Restoring it as code would be defensive mid-stream cancellation we don't actually want — the agent should choose whether to wait, message, or stop each child. Lift the responsibility to the prompt instead: docstring now opens with a numbered pre-flight checklist that requires the agent to ``view_agent_graph`` first and refuses self-permission to call ``finish_scan`` while any peer is in ``running`` / ``waiting`` / ``llm_failed``. The model sees this as part of the tool's schema and treats it as a hard rule (matches our pattern for similar constraints). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
25decb0685 |
chore(orchestration): drop XML wrappers + close remaining audit gaps
Final pass after re-audit. Three sub-specs landed:
**XML simplification** — the legacy XML envelopes were prompt-engineering
ceremony, not parser primitives (the SDK uses native tool-calling). Drop
the verbose wrappers in favor of one-liner labeled headers. Side benefit:
fixes the unescaped-content XML-injection bug the audit caught (peer
content containing ``</content>`` no longer breaks the wrapper).
- ``_format_inter_agent_message``: ``<inter_agent_message><sender>...
<content>...`` 9-line XML → ``[Message from {name} ({id}) | type=... |
priority=...]\n{content}``.
- ``_render_completion_report``: ``<agent_completion_report><agent_info>
...<results>...`` XML → human-readable structured text with section
headers and bulleted lists.
- ``inherited_context``: ``<inherited_context_from_parent>...`` →
``== Inherited context from parent (background only) ==``.
**MG1: TUI stop-agent uses graceful cancel.** ``tui.py`` was calling
``bus.cancel_descendants`` (hard, ``task.cancel()`` mid-stream) for the
stop-agent button. Switched to ``bus.cancel_descendants_graceful``, which
uses ``RunResultStreaming.cancel(mode="after_turn")`` to let each agent
finish its current turn (and save to session) before honoring the cancel.
The hard path remains in ``entry.py`` for KeyboardInterrupt where
graceful isn't possible.
**MG2: Document hook lock-free stats mutation.** Added a comment in
``hooks.on_llm_start`` explaining why ``warned_85`` / ``warned_final``
are mutated lock-free: SDK serializes ``on_llm_start`` per agent, so this
hook is the sole writer to those keys; ``record_usage`` only writes
disjoint keys (in/out/cached/calls).
**AG3: Auto-load ``coordination/root_agent`` skill for the root.**
Legacy auto-loaded the orchestration-guidance skill for root agents
only. Threaded ``is_root`` through ``render_system_prompt`` →
``_resolve_skills``; root agents now get the skill, children don't.
Skipped (per user direction): whitebox-wiki integration (CG2-4) — the
auto-injection / auto-update of the shared repo wiki was a pre-migration
feature; user opted not to restore it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
f4834cd6f7 |
feat(orchestration): full parity with legacy harness — 8 gaps closed via SDK natives
Audit found 8 behavioral gaps between post-migration and the legacy ``BaseAgent.agent_loop``. All 8 are now closed using SDK-native primitives — no custom workarounds, no shadow state machines. What was broken / different: - G1: ``inherit_context`` was dead code; children always started fresh. - G2: TUI user message couldn't interrupt an in-flight LLM/tool turn. - G3: ``llm_failed`` state never set; hard failures propagated as crashes. - G4: No graceful ``stop_agent`` tool. - G5: Parked subagents waited forever (no auto-resume timeout). - G6: Inter-agent messages used a plain header instead of legacy XML. - G7: Completion reports used JSON instead of legacy XML. - G11/G12: Turn counter reset per cycle; budget warnings could re-fire. What we did: Bus extensions (``orchestration/bus.py``): - ``streams`` registry + ``attach_stream`` ctx manager + ``request_interrupt`` for SDK-native ``RunResultStreaming.cancel(mode="after_turn")``. - ``mark_llm_failed`` + ``wait_for_user_message`` (filtered: only ``from="user"`` satisfies; peer messages don't unstick a stuck model). - ``stopping: set[str]`` for graceful programmatic exit. - ``cancel_descendants_graceful`` — leaves-first via ``request_interrupt``. - ``record_usage`` increments ``calls`` unconditionally so it doubles as the per-agent-lifetime turn counter (legacy ``state.iteration`` parity). - ``warned_85`` / ``warned_final`` flags on ``stats_live`` for once-fire budget warnings. Run loop rewrite (``orchestration/run_loop.py``): - ``Runner.run`` → ``Runner.run_streamed`` with ``bus.attach_stream`` so cancel has a target. Catch ``(AgentsException, APIError)`` after retries exhaust; in interactive mode call ``mark_llm_failed`` + wait for user. - ``UserError`` / ``MaxTurnsExceeded`` / ``CancelledError`` propagate. - Outer loop: ``asyncio.wait_for(bus.wait_for_message, timeout=300)`` for interactive subagents (root waits forever). ``TimeoutError`` injects ``"Waiting timeout reached. Resuming execution."``. - Honors ``bus.stopping`` at top of each iteration. Hooks (``orchestration/hooks.py``): - Counter source moved from per-cycle ``ctx["turn_count"]`` to per-lifetime ``bus.stats_live[agent_id]["calls"]``. - Warnings guarded by once-flags — exactly-once across all cycles. Filter (``orchestration/filter.py``): - Restored legacy ``<inter_agent_message>`` XML envelope with the ``<delivery_notice>DO NOT echo back</delivery_notice>`` instruction. Agents-graph (``tools/agents_graph/tools.py``): - G1: ``create_agent`` reads ``ctx.turn_input`` (SDK populates it before tool execution at ``run_internal/turn_resolution.py:806``). Wraps as one ``<inherited_context_from_parent>`` block. - G7: ``agent_finish`` emits the legacy ``<agent_completion_report>`` XML. ``child_ctx["task"] = task`` threaded so the report echoes the original task. - G4: New ``stop_agent`` tool — refuses self-stop, refuses already- finalized targets, ``cascade=True`` uses ``cancel_descendants_graceful``. TUI (``interface/tui.py``): - ``_send_user_message`` schedules ``bus.send`` AND ``bus.request_interrupt(target, mode="after_turn")`` — SDK finishes current turn cleanly, next cycle picks up the user's message. Factory (``agents/factory.py``): - Registered ``stop_agent`` in ``_BASE_TOOLS``. Out of scope: - G8 (``[ABORTED BY USER]`` marker) is auto-resolved by G2 — the SDK saves the full assistant message before honoring ``cancel(mode="after_turn")``, so partial content is preserved in the session. Verified all bus behaviors with a smoke test. Lint at baseline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
5896f25cec |
refactor: move `run_loop into strix/orchestration/`
Top-level ``strix/run_loop.py`` was an orphan — it owns the multi-agent continuation loop, which is exactly the orchestration layer's job. Moves it into ``strix/orchestration/run_loop.py`` next to the bus, hooks, and filter — they all glue ``Runner.run`` to bus state. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
1afd1766cb |
feat(run-loop): lift the interactive continuation loop — applies to all agents
The previous commit only kept the root agent alive across cycles. But ``interactive`` propagates to children via ``make_child_factory``, and the legacy harness's continuation loop applied to every interactive agent in the tree — children also stayed alive after ``agent_finish``, ready to receive follow-up messages from the parent or siblings. Lift the demo-loop pattern out of ``entry.run_strix_scan`` into a shared helper :func:`strix.run_loop.run_with_continuation` and use it at both call sites: - ``entry.run_strix_scan`` for the root agent. - ``tools.agents_graph.tools.create_agent`` for child agents — the ``asyncio.create_task(Runner.run(...))`` becomes ``asyncio.create_task(run_with_continuation(...))``. ``StrixOrchestrationHooks.on_agent_end`` drops the ``parent_id is None`` constraint — any interactive agent parks instead of finalizing. Children that crash still finalize so parents stop waiting on them. Cancellation propagates correctly: ``bus.cancel_descendants`` cancels the task; ``run_with_continuation``'s ``await bus.wait_for_message`` catches ``CancelledError`` and returns the last result. Lint at baseline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
00f5ab33d6 |
feat(entry): interactive mode keeps the root agent alive across cycles
Pre-migration ``BaseAgent.agent_loop`` ran forever in interactive mode, re-entering a "waiting state" after each finish-tool call so user follow-ups could keep the conversation going. Post-migration our ``Runner.run`` returned on ``StopAtTools(finish_scan)`` and the user's next chat message had no listener — silent dead-end. Restore the legacy "agent never dies" semantics using the SDK's canonical demo-loop pattern (``agents/repl.py:run_demo_loop``): - Add ``AgentMessageBus.wait_for_message(agent_id)`` — blocks until an inbox is non-empty. Backed by a per-agent ``asyncio.Event`` fired from ``send``. - Add ``AgentMessageBus.park(agent_id)`` — sets status to ``waiting`` without finalizing (inbox + tree edges + name preserved). Lets ``send`` keep accepting messages between cycles. - Plumb ``interactive`` through ``make_agent_context`` and the ``create_agent`` graph tool (children inherit). - ``StrixOrchestrationHooks.on_agent_end`` parks the root agent instead of finalizing when ``interactive=True`` and the run completed cleanly. Resets ``agent_finish_called`` / ``turn_count`` for the next cycle. - ``entry.run_strix_scan`` adds an outer loop in interactive mode: after ``Runner.run`` returns, ``await bus.wait_for_message(root_id)``, drain pending user messages, and re-invoke ``Runner.run``. SQLite session preserves prior conversation across cycles. For non-interactive (CLI) mode: unchanged — single ``Runner.run``, return. Verified bus behaviors: wait returns immediately on pre-existing message, blocks then wakes on send, ``park`` keeps agent send-able, ``finalize`` evicts. Lint at baseline (3 ruff / 69 mypy). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
fc96716956 |
refactor(agents-graph): drop redundant `agent_finish_called` set
``agent_finish`` was setting ``inner[\"agent_finish_called\"] = True`` at the top of its body, but ``StrixOrchestrationHooks.on_tool_end`` already does this for ``agent_finish`` and ``finish_scan`` after the tool returns. Doing it twice was harmless but suggested the flag's ownership was ambiguous; the hook is the single source of truth. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
8f1f473eb8 |
refactor(telemetry): extract scan artifact I/O into `strix.io.scan_artifacts`
The 150-line ``Tracer.save_run_data`` mashed three concerns together: opening file handles, formatting Markdown for vulnerabilities, and writing the executive penetration-test report. None of that is telemetry — it's pure on-disk artifact emission. Extract to :class:`ScanArtifactWriter` in ``strix/io/scan_artifacts.py``: - One writer per ``run_dir``, owns its own ``_saved_vuln_ids`` dedupe set so re-saves only emit new files. - ``writer.save(vulnerability_reports=, final_scan_result=)`` is the only public entry point. - ``_render_vulnerability_md`` is module-private and unit-testable in isolation. ``Tracer`` now lazily creates a single ``ScanArtifactWriter`` per ``run_dir`` and delegates ``save_run_data`` to it (~150 LoC body collapses to ~10). Net: tracer.py 422 → 327 LoC; new scan_artifacts.py 196 LoC. About −95 LoC of mixed concerns, plus telemetry no longer carries file-I/O responsibilities. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
494e6fab0d |
fix(telemetry): restore broken `log_tool_start / log_tool_end` interface
Audit found ``hooks.on_tool_start`` / ``on_tool_end`` were calling ``tracer.log_tool_start`` / ``log_tool_end`` via ``hasattr()`` checks — but those methods didn't exist on ``Tracer``. The ``hasattr()`` always returned False, so the calls were silently no-ops, leaving ``tracer.tool_executions`` permanently empty. Four TUI render paths consume that dict and were therefore broken: - ``_get_agent_name_for_vulnerability`` always returned ``None`` (vuln panel couldn't show which agent reported the finding). - ``_agent_has_real_activity`` always returned ``False`` (animation logic stopped immediately). - ``_agent_vulnerability_count`` always returned ``0``. - ``_gather_agent_events`` only showed chat events, never tool events. Fix: add ``Tracer.log_tool_start(agent_id, tool_name) → exec_id`` and ``Tracer.log_tool_end(agent_id, tool_name, result)``. Hook bodies now call them directly (no ``hasattr`` guard). The exec-id counter ensures nested / overlapping tool calls within an agent don't clobber each other. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
95865401ae |
refactor: lift hardcoded model default + fix stale `is_whitebox` docstring
``"anthropic/claude-sonnet-4-6"`` was duplicated as a kwarg default in
5 places (``run_strix_scan``, ``make_run_config``, ``make_agent_context``,
and twice in ``agents_graph.create_agent``'s ``inner.get(..., default)``
calls). The default was actually dead code: ``validate_environment``
requires ``STRIX_LLM`` to be set before any scan starts, and the CLI/TUI
callers don't pass ``model=`` themselves.
Replaced with a single resolution in ``run_strix_scan``:
resolved_model = model or load_settings().llm.model
if not resolved_model:
raise RuntimeError("No LLM model configured. ...")
then propagated explicitly to ``make_agent_context`` and
``make_run_config``. Both lose their string defaults — ``model`` is now
a required kwarg. The graph tool's ``inner.get("model", "...")`` is
``inner["model"]``: the parent context guarantees it's set.
Drive-by: ``run_strix_scan`` docstring still listed ``is_whitebox`` as
a ``scan_config`` key — stale since ``1e641e5`` derived it from
``targets`` instead. Updated.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
1e641e56ce |
refactor(config): pydantic-settings revamp + drop `is_whitebox` plumbing
Replaces 200+ lines of bespoke env-loader / persist / change-detection
machinery with ``pydantic_settings.BaseSettings`` (already a transitive
of ``openai-agents → mcp``, no new direct dep).
What was wrong with ``Config``:
- 14 knobs flat in one namespace, weak grouping by comment-block.
- ``Config._applied_from_default`` and ``Config._config_file_override``
were externally mutated from ``interface/main.py:532-534``. Private
members were part of the public contract.
- Stringly-typed values: every caller had to coerce
(``int(Config.get("llm_timeout") or "300")``,
``... not in {"0", "false", "no", "off"}``).
- Dead knob: ``strix_llm_max_retries`` declared, persisted, listed in
``_LLM_CANONICAL_NAMES`` — zero readers (``DEFAULT_RETRY``
hardcodes ``max_retries=5``). Dropped.
- ``_LLM_CANONICAL_NAMES`` tuple maintained alongside class vars —
duplicate source of truth.
- ``_tracked_names()`` introspected ``vars(cls).items()`` filtered on
``(v is None or isinstance(v, str))`` — fragile.
- Awkward path: ``strix/config/config.py`` inside ``strix/config/``
with ``__init__.py`` just re-exporting.
- Dual access for the same fact: ``web_search`` read
``os.getenv("PERPLEXITY_API_KEY")`` while ``main.py`` read
``Config.get("perplexity_api_key")``.
New shape:
- ``strix/config/settings.py`` — typed dataclass tree:
``Settings.{llm,runtime,telemetry,integrations}``. Each sub-model is
its own ``BaseSettings`` so it reads env independently. Field-level
``alias=`` and ``validation_alias=AliasChoices(...)`` mirror the
existing flat env-var names — user-facing env contract is unchanged.
Bool fields auto-parse ``"0"``/``"false"``/``"no"``/``"off"``;
int fields auto-coerce.
- ``strix/config/loader.py`` — thin ``load_settings()``,
``apply_config_override(path)``, ``persist_current()`` with module
cache. JSON file reader walks aliases to populate sub-models, dropping
entries already covered by env (so env still wins).
- 13 callsites migrated from ``Config.get("...")`` to
``load_settings().<group>.<field>``.
- ``posthog._is_enabled()`` collapses to one line.
- ``--config <path>`` flow simplified: one
``apply_config_override(...)`` call replaces three lines of
class-private mutation.
Drive-by — drop ``is_whitebox`` from ``scan_config`` dict:
- It was being derived as ``bool(args.local_sources)`` in three places
(``cli.py``, ``tui.py``, ``main.py``) and stuffed into the dict for
``entry.py`` to read back. The fact is fully derivable from
``scan_config["targets"]`` — any target with ``type == "local_code"``.
- New helper ``is_whitebox_scan(targets)`` in ``interface/utils.py``
alongside the other target-classification utilities.
- ``entry.py`` computes once; ``main.py``'s posthog start uses the same
helper. Triplicate derivation gone.
Verified: ruff at baseline (3), mypy at baseline (69). Six smoke tests
pass — defaults / JSON-only / env-wins-over-JSON / alias-chain
fallback / bool parsing / ``is_whitebox_scan``.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
346cc477a7 |
chore(image): drop sidecar/Playwright legacy + plug NO_PROXY hole
Dockerfile carried forward three pieces of dead state from the
pre-migration era:
- ``/app/runtime`` and ``/app/tools`` mkdir entries — the FastAPI
sidecar + in-container tool registry that those dirs hosted are
gone.
- ``/home/pentester/{configs,wordlists,output,scripts}`` — empty
placeholders never populated by anything; greps for them in the
whole repo come back empty.
- ~20 explicit Chrome/Playwright runtime libs (``libnss3``,
``libnspr4``, ``libatk*``, ``libxcomposite1``, …) plus emoji /
freefont packages. These were Playwright deps; the migration to
``agent-browser`` runs ``agent-browser install --with-deps`` which
owns this list authoritatively. Keep ``libnss3-tools`` for
``certutil`` in the entrypoint's CA-trust step.
Drive-by bug fix: ``NO_PROXY=localhost,127.0.0.1`` was set in the
entrypoint (``/etc/profile.d/proxy.sh`` + ``/etc/environment``) but
NOT in the SDK manifest's environment. ``docker exec``-spawned
processes (which ``session.exec`` and the Shell capability use)
inherit only manifest env, so ``agent-browser``'s CDP-localhost
traffic was being looped back through Caido. Add it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
8a11f9dab5 |
refactor(dedupe): route through MultiProvider + cache wrapper + retry policy
``check_duplicate`` was calling ``litellm.completion(...)`` directly via ``resolve_llm_config()``, bypassing every layer the main agent loop runs through: - :class:`MultiProvider` (so ``anthropic/...`` aliases never went through :class:`AnthropicCachingLitellmModel` and missed the ``cache_control`` patching on the system prompt — 4x cost on repeated dedupe calls within the same scan). - :data:`DEFAULT_RETRY` (no retry on 429s / network blips — the caller's broad except-and-fallback was hiding this). Switch to the SDK's :meth:`Model.get_response` directly: same model selection, same retry policy, same cache wrapper. Extract assistant text from ``ModelResponse.output`` via the canonical ``ResponseOutputMessage`` walk. ``check_duplicate`` is now async — drops the ``asyncio.to_thread`` indirection in ``_do_create``. Validation logic is fast-sync; running it on the event loop is fine. Drive-by: rename ``_DEFAULT_RETRY`` → ``DEFAULT_RETRY`` in ``run_config_factory`` so the dedupe path can reuse the same constant without reaching into a private name. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
b3f7cfd040 |
refactor: nuke `strix_tool` shim + dead package re-exports
``@strix_tool`` was passing through every kwarg to ``@function_tool``
with the same defaults — zero Strix-specific value-add. The docstring
also still claimed terminal/browser/python tools opted into
``timeout_behavior="raise_exception"``, but those tools were all
deleted in the recent migrations.
- Replace 30 ``@strix_tool(...)`` callsites with ``@function_tool(...)``.
- Inline ``dump_tool_result(x)`` as ``json.dumps(x, ensure_ascii=False,
default=str)`` at all 64 callsites — no helper.
- Delete ``strix/tools/_decorator.py``.
Drive-by: gut dead package re-exports.
- ``strix/{agents,orchestration,tools}/__init__.py`` re-exported
symbols nobody imports via the package — every consumer uses deep
paths (``from strix.agents.factory import build_strix_agent``).
- The 8 ``strix/tools/<sub>/__init__.py`` re-exports only fed the
splat ``from .agents_graph import *`` etc. in the parent package
init, which is also gone now.
- Reduced to docstrings (or empty) so ``import strix.tools`` doesn't
drag every tool's transitive deps in eagerly.
Drive-by: drop dead helpers in ``runtime.session_manager``
(``cached_scan_ids``, ``_reset_cache_for_tests``) — zero callers since
``tests/`` was nuked in ``a6d578c``.
Verified all tool timeouts preserved (think=10, list_requests=120,
finish_scan=60, web_search=330) and ruff/mypy at baseline.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
6990fd4ef1 |
feat(runtime): pluggable sandbox backend registry
``STRIX_RUNTIME_BACKEND`` was already declared on ``Config`` but never read — ``session_manager`` hard-coded ``StrixDockerSandboxClient`` plus ``DockerSandboxClientOptions`` plus ``docker.from_env()`` directly into the call site. Adding a second backend would have meant retrofitting every Docker-specific import. Move all of that behind a registry: - ``strix/runtime/backends.py``: maps backend names to async factories ``(image, manifest, exposed_ports) -> (client, session)``. Ships with ``"docker"``; ``register_backend`` lets downstream users plug in Daytona / K8s / Modal / etc. without forking. - Each backend's deps are imported lazily inside its factory, so a K8s-only deployment doesn't need ``docker-py`` installed (and vice-versa). - ``session_manager`` reads the config name, looks up the backend, calls it. Zero Docker imports remain. - Unknown backend name raises ``ValueError`` with the supported list, so ``STRIX_RUNTIME_BACKEND=docke`` typos surface immediately. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
fe5f749e13 |
refactor: rename `strix_docker_client.py → docker_client.py`
The ``strix`` prefix on a file inside ``strix/runtime/`` was pure redundancy. Class name ``StrixDockerSandboxClient`` keeps the prefix since it disambiguates from the upstream SDK class it subclasses. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
295d43b3ab |
refactor: collapse strix/sandbox into strix/runtime; in-sandbox Caido bootstrap
The split between ``strix/sandbox/`` and ``strix/runtime/`` was artificial — both were managing the same backend. ``strix/sandbox/`` also collided uncomfortably with the SDK's ``agents.sandbox.*`` namespace. ``runtime/`` (which matches ``STRIX_RUNTIME_BACKEND``) is the canonical home for everything Docker / Daytona / K8s lifecycle. While merging, also rip out two pieces of Docker-specific coupling: - ``caido_bootstrap`` was POSTing ``loginAsGuest`` from the host via ``aiohttp`` to ``http://127.0.0.1:{forwarded_port}``. That assumed Docker port forwarding; Daytona / K8s expose ports differently. Now we ``session.exec`` curl from *inside* the container — the SDK's runtime-agnostic exec primitive — so any backend works as long as it implements ``exec``. The host-side Caido ``Client`` still uses the runtime's exposed-port URL for post-bootstrap calls, but that goes through the SDK's own ``resolve_exposed_port`` abstraction (also runtime-agnostic). - The bootstrap retry loop now doubles as the readiness probe, so ``healthcheck.wait_for_tcp_ready`` (and the entire ``healthcheck.py`` module) goes away. Drive-by simplification: drop ``caido_host_port`` plumbing entirely. It was only piped through ``make_agent_context`` → child contexts without ever being read; only ``caido_client`` is consumed. Drops ``aiohttp`` runtime dep (it stays only as a transitive of the Caido SDK). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
5d8436cbbb |
chore: nuke post-migration dead code, deps, and broken Dockerfile fallback
- Drop ``wait_for_http_ready`` (FastAPI sidecar healthcheck) — only Caido TCP probe survives now. Removes the ``httpx`` import. - Delete ``ListSitemapRenderer`` / ``ViewSitemapEntryRenderer`` — render UI for tools that disappeared with the Caido SDK migration. - Drop ``scrubadub`` runtime dep — PII sanitizer was nuked previously but the dep stayed; resolve strips 18 transitives (numpy, scipy, scikit-learn, nltk, faker, …). - Drop empty ``[project.optional-dependencies] sandbox`` section — last in-container Python dep migrated out. - Drop unused mypy overrides (``pydantic_settings``, ``jwt``, ``gql``, ``scrubadub``, ``httpx``) and the stale ``fastapi`` isort group. - Collapse Dockerfile's ``pipx install -r ... 2>/dev/null || venv`` fallback into a direct venv install — pipx never accepted ``-r`` so the fallback was always firing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
ab3da5c0b0 |
docs(skill): document the agent-browser → view_image chain for screenshots
The vendored agent-browser skill described the ``screenshot``
subcommand but didn't tell the model how to actually look at the
resulting PNG. ``agent-browser screenshot`` writes to disk; the
SDK's ``view_image`` (from the ``Filesystem`` capability we already
enable on the agent) is what loads the bytes back as multimodal
content.
Add the explicit two-step pattern:
exec_command: agent-browser screenshot /workspace/page.png
view_image: {"path": "/workspace/page.png"}
Plus a guidance note that ``snapshot -i`` (text accessibility tree at
~200-400 tokens) is the cheap default and screenshots are for cases
where pixels actually matter — visual layout, captchas, custom
widgets where the a11y tree is incomplete.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
cd1bb46d50 |
chore: final cleanup — drop `STRIX_SANDBOX_MODE / strix_disable_browser` / runtime docstring
Tail end of the sandbox-tools migration: - Drop ``ENV STRIX_SANDBOX_MODE=true`` and ``ENV PYTHONPATH=/app`` from the Dockerfile — both only mattered for the now-deleted in-container tool server (the legacy ``register_tool`` registry gated on the env var, and the entrypoint set ``PYTHONPATH`` so it could ``-m strix.runtime.tool_server``). - Drop ``strix_disable_browser`` from the Config defaults — the legacy registry used it to skip ``browser_action`` registration; agent-browser is unconditional now. - Strip the ``tool_server.py`` blurb from ``strix/runtime/__init__.py``. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
2c2ab13c8f |
refactor: SandboxAgent + SDK Shell/Filesystem; agent-browser CLI; nuke FastAPI sidecar
Combined commits 2+3 of the migration plan because the FastAPI sidecar removal in commit 2 broke ``browser_action`` (which lived in the sidecar); they have to land together. Sandbox tool layer (commit 2 piece): - ``build_strix_agent`` now returns a ``SandboxAgent`` with ``capabilities=[Filesystem(), Shell()]``. The SDK runtime binds the capabilities to the live sandbox session per-run; agents get ``exec_command``, ``write_stdin``, ``apply_patch``, ``view_image`` function tools auto-merged into their tool list. Plain ``Agent`` short-circuits capability binding (``agents/sandbox/runtime.py:190``). - Drop ``Compaction`` from the default capability set — it's OpenAI-Responses-API-only and useless for our litellm-routed Anthropic setup. - Delete the entire custom in-container tool layer: - ``strix/tools/terminal/`` (5 files, 748 LoC libtmux) - ``strix/tools/file_edit/`` (3 files, 276 LoC) - ``strix/tools/python/`` (5 files, 459 LoC) - ``strix/runtime/tool_server.py`` (163 LoC FastAPI sidecar) - ``strix/tools/_sandbox_dispatch.py`` (117 LoC) - ``strix/tools/registry.py`` (109 LoC) - ``strix/tools/context.py`` (12 LoC) - Drop the corresponding TUI renderers (``terminal_renderer.py``, ``file_edit_renderer.py``, ``python_renderer.py``) and update ``interface/tool_components/__init__.py``. Browser → agent-browser CLI (commit 3 piece): - Install ``agent-browser@0.26.0`` globally in the Dockerfile right after the existing ``npm install -g`` block. Run ``agent-browser install --with-deps`` (apt, root) and ``agent-browser install`` (Chrome download, pentester) + ``agent-browser doctor --offline --quick`` smoke test. - Drop the explicit Playwright system-deps apt list (replaced by ``--with-deps``) and ``RUN .venv/bin/python -m playwright install chromium``. - Vendor ``agent-browser/skill-data/core/SKILL.md`` → ``strix/skills/tooling/agent_browser.md`` (476 lines). Adapt frontmatter to Strix format; strip the install/Quickstart and the ``agent-browser skills get electron|slack|...`` specialized-skills block; add the "Caido proxy is wired via env vars; do not pass ``--proxy``" note. - ``_resolve_skills`` now eagerly loads ``tooling/agent_browser`` for every agent (matches the previous unconditional ``browser_action`` in ``_BASE_TOOLS``). - Delete ``strix/tools/browser/`` (5 files, 1338 LoC) and the ``browser_renderer.py`` TUI render. Sandbox plumbing: - Drop ``bearer`` token, ``tool_server_host_port`` resolution + bundle keys, ``TOOL_SERVER_TOKEN``/``TOOL_SERVER_PORT``/ ``STRIX_SANDBOX_EXECUTION_TIMEOUT`` from the manifest env in ``session_manager.create_or_reuse``. Caido proxy env vars (``http_proxy``, ``https_proxy``, ``ALL_PROXY``) stay; manifest applies them to every ``docker exec``-spawned process. - Drop ``sandbox_token`` and ``tool_server_host_port`` params from ``make_agent_context`` and the ``create_agent`` graph tool. - Drop the tool-server health-check from ``entry.py`` (only Caido's ``wait_for_tcp_ready`` remains). - ``docker-entrypoint.sh``: delete the ~30 line ``Starting tool server...`` block (sudo + uvicorn launch + curl /health poll). Add ``NO_PROXY=localhost,127.0.0.1`` to ``/etc/profile.d/proxy.sh`` and ``/etc/environment`` so the agent-browser daemon's CDP traffic on localhost isn't routed through Caido. pyproject.toml: - ``[project.optional-dependencies] sandbox = []`` (every member of the previous list — fastapi, uvicorn, ipython, openhands-aci, playwright, libtmux — is gone with the sidecar). - Drop ``numpydoc.*``, ``IPython.*``, ``openhands_aci.*``, ``playwright.*``, ``uvicorn.*``, ``pyte.*``, ``libtmux.*`` from the missing-imports module list. - Drop the per-file ruff ignores for the deleted modules. Net delta: −5512 LoC. ruff drops to 3 errors (was 21 baseline). mypy falls to 69 errors over 3 files (was 84 over 8 — the drop comes from deleting the modules with the worst untyped-import problems). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
5449af2456 |
refactor: Caido — replace ProxyManager with caido-sdk-client (host-side)
Drop our 797-LoC manual GraphQL ``ProxyManager`` and the in-container
sandbox dispatch. Caido goes host-side via the official async Python
SDK. The Caido CLI still runs as a sidecar in the container — only the
control-plane moves.
Bootstrap moves host-side:
- New ``strix/sandbox/caido_bootstrap.py``: ``loginAsGuest`` via
aiohttp (5 retries), then ``client.project.create(temporary=True)``
+ ``client.project.select(...)``, then return the connected
``caido_sdk_client.Client``. Drop the equivalent bash from
``docker-entrypoint.sh`` (~60 lines of curl + jq).
- ``entry.py`` calls ``bootstrap_caido_client`` after the
``wait_for_tcp_ready`` healthcheck, stashes the client in the bundle
and threads it through ``make_agent_context(caido_client=...)``.
``agents_graph.create_agent`` propagates the same client to children.
- ``session_manager.cleanup`` ``await``s ``client.aclose()`` before
tearing down the container.
- Drop ``CAIDO_PORT`` from the manifest env (only the in-container
ProxyManager read it) and ``CAIDO_API_TOKEN`` from the entrypoint's
``/etc/profile.d/proxy.sh`` + ``/etc/environment`` heredocs.
Tools (``strix/tools/proxy/tools.py``):
- ``list_requests`` → ``client.request.list().filter().first().after()``
with ascending/descending order. **Pagination changes from
start_page/end_page (1-indexed) to first/after cursors** matching the
SDK's native shape; response includes ``page_info.end_cursor`` for
the model to thread.
- ``view_request`` → ``client.request.get(id, RequestGetOptions(...))``;
decode raw bytes locally; existing regex-search and line-pagination
modes preserved.
- ``send_request`` → synthesize raw HTTP bytes, parse URL into
``ConnectionInfoInput(host, port, is_tls)``, create a replay session
via ``client.replay.sessions.create(CreateReplaySessionFromRaw(...))``,
then ``client.replay.send(session_id, ReplaySendOptions(...))``.
- ``repeat_request`` → ``client.request.get(id, request_raw=True)`` →
port the existing parse/_apply_modifications/build helpers verbatim →
send via the same replay flow as ``send_request``.
- ``scope_rules`` → direct mapping to ``client.scope.{list, get, create,
update, delete}``.
- **Drop ``list_sitemap`` + ``view_sitemap_entry``** — the official SDK
has no sitemap module. The model uses HTTPQL filters
(``req.host.eq:"X" AND req.path.cont:"/api/"``) for the same
drill-down workflow.
Deletions:
- ``strix/tools/proxy/proxy_manager.py`` (797 LoC)
- ``strix/tools/proxy/proxy_actions.py`` (113 LoC)
- The 6-line proxy_actions pre-import in ``python_instance.py``
(broken once proxy_actions is gone; that file is queued for deletion
in commit 2 anyway).
Deps:
- Add ``caido-sdk-client>=0.2.0`` and ``aiohttp>=3.10.0`` to runtime
``[project] dependencies``.
- Drop ``gql[requests]>=3.5.3`` from ``[project.optional-dependencies]
sandbox`` — only the in-container ProxyManager used the sync transport
variant; the SDK pulls in ``gql[aiohttp]`` transitively for us.
- ``[[tool.mypy.overrides]]``: add ``caido_sdk_client.*`` and
``aiohttp.*`` to the missing-imports list with
``disable_error_code=["import-untyped"]`` (neither ships ``py.typed``).
- ``[tool.ruff.lint.per-file-ignores]``: bump the proxy/tools.py
ignore to also include ``PLR0911`` (the scope_rules action dispatcher
has many short-circuit returns).
ruff drops from 21 → 12 errors; mypy moves from 82 → 84 (the +2 are in
already-flaky files unrelated to this change). All touched files mypy
clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
9b31e9fd29 |
refactor: nuke `events.jsonl` pipeline and the unused PII sanitizer
The JSONL trace sink was never read — TUI consumes ``Tracer`` state directly (chat_messages, agents, tool_executions, vulnerability_reports, LLM stats), and SQLiteSession owns the conversation history. The whole ``StrixTracingProcessor`` → ``_emit_event`` → ``append_jsonl_record`` pipeline was producing files nothing opens. Deleted: - ``strix/telemetry/strix_processor.py`` (the SDK ``TracingProcessor``). - ``strix/telemetry/utils.py`` — ``TelemetrySanitizer`` (no remaining callers), ``append_jsonl_record``, ``get_events_write_lock``, ``reset_events_write_locks``. - ``strix/telemetry/flags.py`` — ``is_telemetry_enabled`` / ``is_posthog_enabled`` collapsed into a 4-line check inside ``posthog._is_enabled`` (its only caller). - ``Tracer._emit_event`` and every event-emit call inside the tracer (``run.started``, ``run.configured``, ``run.completed``, ``finding.created``, ``finding.reviewed``, ``chat.message``). - ``Tracer._enrich_actor`` (only used by ``_emit_event``). - ``Tracer._sanitize_data`` + ``_sanitizer`` field (PII scrub only ran on JSONL events). - ``Tracer.events_file_path`` property and the ``_events_file_path`` / ``_telemetry_enabled`` / ``_run_completed_emitted`` / ``_next_execution_id`` fields. - ``Tracer._calculate_duration`` (one caller in posthog — inlined). - ``add_trace_processor(StrixTracingProcessor(run_dir))`` from ``entry.py``. The ``Tracer`` class is now ~275 LoC of pure runtime state for the TUI + vulnerability artifact writer (markdown / CSV / pentest report). Conversation history goes to ``SQLiteSession``; SDK trace events are not persisted. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
df51eeedd0 |
refactor: flatten CaidoCapability into direct wiring
The custom ``Capability`` subclass was 207 LoC bundling four tiny concerns (env-var injection, tool exposure, system-prompt block, healthcheck) — and three of them were dead code: the SDK's ``SandboxRunConfig`` doesn't accept capabilities, so ``process_manifest``, ``tools()``, and ``instructions()`` were never called. Only ``bind()`` ran, because we invoked it manually. Replace each piece with the obvious direct equivalent: - **Env vars**: inject ``http_proxy`` / ``https_proxy`` / ``ALL_PROXY`` directly into the manifest in ``session_manager.create_or_reuse``. This *also fixes a latent bug* — the proxy env vars in ``CaidoCapability.process_manifest`` weren't being applied to live containers, so shelled-out HTTP traffic from terminal/python tools wasn't actually flowing through Caido. - **Tool exposure**: add the seven Caido tools (``list_requests``, ``view_request``, ``send_request``, ``repeat_request``, ``scope_rules``, ``list_sitemap``, ``view_sitemap_entry``) to ``_BASE_TOOLS`` in ``agents/factory.py`` like every other sandbox tool. They were already defined in ``tools/proxy/tools.py``. - **Healthcheck**: ``entry.py`` now ``await``s ``wait_for_http_ready`` + ``wait_for_tcp_ready`` inline after ``session_manager.create_or_reuse`` returns, before any agent runs. No more capability state, ``configure_host_ports`` plumbing, or ``on_agent_start`` await-the-task indirection. - **Instructions block**: dropped. The seven proxy tools' docstrings cover the HTTPQL syntax and usage already; the duplicate prompt fragment was overhead. Cascade cleanups: - Drop ``caido_capability`` from the agent context (was passed to every ``make_agent_context`` call but only used by the now-deleted ``on_agent_start`` await). - Strip the capability await branch from ``StrixOrchestrationHooks.on_agent_start``; that hook now does only the ``tracer.agents`` mirroring it always should have. - Drop the ``capability`` key from the session bundle. - Drop ``strix/sandbox/caido_capability.py`` — entire file (207 LoC). - Drop the per-file ruff ignore for the deleted file. mypy clean on every touched file. Net -217 LoC. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
12baf2d792 |
refactor: lean on SDK for tracing + native session resume; nuke OTEL/Traceloop
The SDK ships its own tracing pipeline (``agents.tracing``) plus ``SQLiteSession`` for native conversation persistence. Strix's custom OTEL bootstrap + Traceloop integration was dead weight — the SDK does not bridge to OpenTelemetry, so all of our adapter code was solving a problem we didn't actually need solved. Telemetry purge: - Drop the ``traceloop-sdk`` and ``opentelemetry-exporter-otlp-proto-http`` runtime deps. ``uv sync`` uninstalls ~30 transitive packages (the OTEL family, ``traceloop-sdk``, ``protobuf``, ``opentelemetry-exporter-otlp-*``, ``deprecated``, ``wrapt``, ``backoff``, etc.) — about 1000 lines off ``uv.lock``. - Delete ``bootstrap_otel`` and ``JsonlSpanExporter`` from ``telemetry/utils.py``; strip the OTEL pruning helpers, ``parse_traceloop_headers``, ``default_resource_attributes``, ``format_trace_id`` / ``format_span_id`` / ``iso_from_unix_ns``. Keep only the sanitizer + JSONL writer + write-lock registry. - Strip ``Tracer._setup_telemetry``, ``_otel_tracer``, ``_remote_export_enabled``, ``_active_events_file_path``, ``_active_run_metadata``, ``_get_events_write_lock``, ``_set_association_properties``. ``_emit_event`` now generates trace/span ids from ``uuid4`` directly. - Drop the ``traceloop_base_url`` / ``traceloop_api_key`` / ``traceloop_headers`` / ``strix_otel_telemetry`` config knobs. - Rename ``is_otel_enabled`` → ``is_telemetry_enabled`` (the gate now controls JSONL emission only). Native session resume: - ``entry.py`` now constructs an ``agents.memory.SQLiteSession`` keyed by ``scan_id`` and persists conversation history at ``strix_runs/<scan_id>/session.db``. A second call to ``run_strix_scan`` with the same ``scan_id`` resumes from where the prior run left off — no manual state plumbing needed. Tracer.agents fix (TUI agent tree was silently empty): - ``StrixOrchestrationHooks.on_agent_start`` now mirrors bus state into ``tracer.agents`` (id / name / parent_id / status), and ``on_agent_end`` flips the entry to ``completed`` / ``crashed``. The TUI now actually shows the agent tree during scans. Tooling: - Drop ``pylint`` from dev deps; ``ruff`` covers everything we used it for. Strip the ``make lint`` pylint step. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
28416c5ae9 |
chore: drop unused pydantic[email] extra
No imports of EmailStr or pydantic.networks; dropping the extra removes email-validator, dnspython, and idna as transitives. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
b65e4ebd52 |
chore: drop unused dependencies
Runtime deps (``[project] dependencies``): - ``litellm[proxy]>=1.83.0`` — ``openai-agents[litellm]==0.14.6`` already pulls litellm as a transitive (currently 1.83.7), and we only use ``litellm.completion()``, not the proxy server extras. - ``defusedxml>=0.7.1`` — leftover from the XML tool-call era; zero imports remain. Sandbox deps (``[project.optional-dependencies] sandbox``): - ``pyte>=0.8.1`` — zero imports. - ``numpydoc>=1.8.0`` — zero imports. Optional groups: - Drop the entire ``vertex`` group (``google-cloud-aiplatform``); routing goes through litellm/MultiProvider, no direct Google Cloud usage. Dev deps (``[dependency-groups] dev``): - ``black>=25.1.0`` — never invoked; ruff format does it and is what pre-commit + Makefile actually call. - ``isort>=6.0.1`` — never invoked; ruff's ``I`` lint set handles imports. (pylint pulls isort transitively, so functionality is preserved.) ruff (27) and mypy (82) baselines unchanged; ``uv sync`` uninstalls ~15 packages. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
a6d578c4a8 |
chore: nuke tests/ and the entire test toolchain
The test suite was carrying migration scars and a long tail of low-density assertions over SDK-derived behavior. Drop it wholesale. - Delete ``tests/`` (42 files, ~4900 LoC). - Drop ``pytest`` / ``pytest-asyncio`` / ``pytest-cov`` / ``pytest-mock`` from the dev dependency group; ``uv sync`` uninstalls the matching wheels. - Strip the pytest + coverage config blocks, the ``flake8-pytest-style`` ruff selector, the ``tests/**`` per-file ignores, the ``[tool.mypy.overrides] tests.*`` block, and the ``"tests"`` entry from bandit's ``exclude_dirs``. - Drop the ``test`` / ``test-cov`` Makefile targets; ``dev`` no longer depends on tests. - Strip the ``# Testing`` block from ``.gitignore`` (``.coverage``, ``.pytest_cache/``, ``htmlcov/``, ``coverage.xml``, ``nosetests.xml``, ``.tox/``, ``.hypothesis/``). ruff (27) and mypy (82) baselines unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
49c38de3b2 |
refactor: dedupe `_dump` helper, collapse retry-policy plumbing, scrub test scars
Tools: - Add a single ``dump_tool_result`` helper in ``tools/_decorator.py`` and remove the eight identical ``_dump`` definitions from ``proxy/tools.py``, ``file_edit/tools.py``, ``python/tool.py``, ``terminal/tool.py``, ``todo/tools.py``, ``browser/tool.py``, ``notes/tools.py``, ``agents_graph/tools.py``. Imports trimmed. Net -50 LoC across the tool modules. run_config_factory: - Inline the four retry-policy plumbing pieces (``_RETRYABLE_HTTP_STATUSES``, ``_DEFAULT_MAX_RETRIES``, ``_DEFAULT_BACKOFF``, ``_default_retry_policy()``) into a single module-level ``_DEFAULT_RETRY`` ``ModelRetrySettings`` literal. The inputs were never overridden and the helper had one caller. Tests: - Drop migration scars from ``tests/test_run_config_factory.py`` (``Phase 1`` / ``C1`` / ``C11`` / ``C21`` / ``HARNESS_WIKI`` / ``AUDIT`` references). Replace the ``_RETRYABLE_HTTP_STATUSES``-touching test with a ``retry.policy is not None`` smoke check now that the constant has been inlined. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
d959fe2163 |
refactor: collapse dual stat buckets, prune unused params, kill dead helpers
Tracer:
- Collapse the ``live`` / ``completed`` LLM stat buckets into one
flat dict. The ``completed`` bucket was only ever written by tests
— production never moved stats across, and ``get_total_llm_stats``
always summed both for display.
- Drop ``record_llm_usage(agent_id=...)``: argument was unused, and
the per-call ``bucket=`` knob is gone with the buckets.
run_config_factory:
- Drop unused ``parallel_tool_calls``, ``tool_choice`` parameters
from ``make_run_config`` — no caller ever overrode them.
- Drop ``agent_name`` from ``make_agent_context`` — set into the
context dict but no consumer ever read it; the bus's ``names`` map
is the source of truth.
Wire reasoning_effort through:
- ``Config.get("strix_reasoning_effort")`` is now actually plumbed
to ``make_run_config`` from ``entry.py``. Previously the env var
was advertised but never consumed.
Multi-agent graph tools:
- Replace six copies of
``inner = ctx.context if isinstance(ctx.context, dict) else {}``
with a single ``_ctx(ctx)`` helper.
Todo tools:
- Lift the duplicated ``priority_order`` / ``status_order`` dicts
to module-level ``_PRIORITY_RANK`` / ``_STATUS_RANK`` and replace
both inline sort lambdas with ``_todo_sort_key``.
Notes tools:
- Delete ``append_note_content`` (and its test): docstring claimed
it was for an "agents-graph wiki-update hook on agent_finish" that
was never wired up. Pure dead public API.
Style:
- Drop the ``del ctx`` no-ops from notes / reporting / web_search
tools. ``ARG001`` is already silenced project-wide for tool
modules; the ``del`` was cargo-culted.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
f08ad2a634 |
refactor: nuke gratuitous XML serialization + delete argument_parser
Argument parser: - Delete ``strix/tools/argument_parser.py`` and its tests. The SDK validates and types tool arguments via Pydantic before they hit our wrappers, and the in-container tool server receives JSON-typed kwargs over the wire. The string-coercion belt-and-suspenders is no longer pulling its weight. XML → JSON / typed structures: - ``create_vulnerability_report``: ``cvss_breakdown`` is now a ``dict[str, str]`` of the 8 metrics; ``code_locations`` is a ``list[dict]``. No more XML parsing in the tool or the renderer. - ``check_duplicate``: the dedup judge now emits a single JSON object instead of an ``<dedupe_result>`` block. Strict JSON parser handles optional code-fence wrappers. - ``agent_finish``: completion report posted to the parent inbox is a JSON object (``kind``, ``from``, ``agent_id``, ``success``, ``summary``, ``findings``, ``recommendations``) rather than a hand-rolled ``<agent_completion_report>`` XML envelope. - ``create_agent``: identity preamble + inherited-context markers are plain bracketed labels rather than ``<agent_delegation>`` / ``<inherited_context_from_parent>`` envelopes. - ``inject_messages_filter``: peer messages get a ``[Message from agent <id> | type=... | priority=...]`` header line instead of an ``<inter_agent_message>`` envelope. - Crash + system-warning messages: bracketed labels, no XML. - System prompt: the inter-agent block now describes the new header format and drops the "never echo XML envelope" rule. - ``strix/llm/utils.py``: deleted. ``clean_content`` collapsed into a one-line blank-line normalizer in the agent-message renderer (the XML envelope scrub had nothing left to scrub). Tests updated to match the new shapes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
369fa56148 |
refactor: delete orphaned dirs, dead streaming infra, unused session/compressor
Orphaned files/dirs: - ``strix/agents/StrixAgent/`` — empty, only ``__pycache__``. - ``strix/tools/browser/litellm/`` — empty, only ``__pycache__``. - ``strix/strix_runs/`` — runtime output left in the working tree. - ``strix/prompts/`` — single Jinja template that nothing renders. Dead streaming pipeline (was never wired in the SDK migration): - Delete ``strix/interface/streaming_parser.py`` (XML tool-call parser for an output format the SDK doesn't produce). - Strip ``streaming_content`` / ``interrupted_content`` dicts and five unused methods from ``Tracer``. - Strip the streaming-render path + ``interrupted`` branch from TUI. - Trim ``strix/llm/utils.py``: drop ``normalize_tool_format``, ``parse_tool_invocations``, ``format_tool_call``, ``fix_incomplete_tool_call`` and the XML-stripping in ``clean_content``. Keep only the inter-agent-XML scrub. Unwired session compression: - Delete ``strix/llm/strix_session.py`` and ``strix/llm/memory_compressor.py``. ``Runner.run`` was never called with a ``session=``, so the compressor never ran. Drop the matching test file and the ``strix_memory_compressor_timeout`` config knob. Tracer cleanup: - Remove ``log_agent_creation``, ``log_tool_execution_start``, ``update_tool_execution``, ``update_agent_status``, ``get_agent_tools`` — none had production callers. - Rewrite the redaction + correlation tests against ``log_chat_message`` (which still emits events). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
4146174503 |
refactor: scrub migration scars, dead code, and unused helpers
- 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) <noreply@anthropic.com> |
||
|
|
e4be5f9588 |
docs: restore tool guidance into docstrings, drop prompt tool-format boilerplate
Port the prose guidance that previously lived in the deleted *_actions_schema.xml files into per-tool docstrings, so the SDK's auto-generated function schema carries the same domain knowledge (HTTPQL syntax, Caido sitemap kinds, browser persistence/JS rules, agent specialization caps, customer-facing report rules, CVSS/CWE guidance, etc.) without any custom prompt scaffolding. Strip the <tool_usage> block from system_prompt.jinja — XML format guidance, the "CRITICAL RULES" 0-8 list, and the </function> closing-tag reminder all contradicted the SDK's native JSON function-calling protocol. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
6435e07dc2 |
chore: per-file PLC0415 ignores for inlined tool files with lazy imports
The three inlined tool files (notes/tools.py, finish/tool.py, reporting/tool.py) have intentional lazy imports inside try-blocks to avoid circular dependencies with strix.telemetry / strix.llm. Add per-file PLC0415 + TC002 ignores instead of inline noqa comments that pre-commit's auto-fix kept stripping. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
dc9b9f5f9c |
refactor: inline non-sandbox actions, strip registry, drop schemas
Cleanup pass after the migration: #1 Inline ``*_actions.py`` into wrapper ``tool[s].py`` for the non-sandbox tools (think, todo, notes, reporting, web_search, finish_scan). One file per tool family now. Helpers + public function bodies live alongside the ``@strix_tool``-decorated wrappers that call them. For notes, the sync helpers are renamed to ``_create_note_impl`` / ``_list_notes_impl`` / etc. so the public names ``create_note`` / ``list_notes`` / etc. can be the FunctionTool instances the agent factory imports. ``append_note_content`` (used by the agents-graph wiki-update hook) calls the impl helpers directly. #2 Delete ``strix/tools/_state_adapter.py``. The ``AgentStateAdapter`` shim only existed to feed legacy ``*_actions.py`` functions a ``state.agent_id`` they could read. With the actions inlined, the wrappers read ``ctx.context['agent_id']`` directly. #3 Strip ``strix/tools/registry.py`` from ~250 LOC to ~110. Deleted: XML schema loading, ``_parse_param_schema``, ``get_tools_prompt``, ``get_tool_param_schema``, ``needs_agent_state``, ``should_execute_in_sandbox``, ``validate_tool_availability`` — all for the host-side legacy dispatcher path. Kept the ``register_tool`` decorator (sandbox side), ``get_tool_by_name``, ``get_tool_names``, ``tools`` list, ``clear_registry``. The Jinja prompt template's ``{{ get_tools_prompt() }}`` injection is dropped — the SDK auto-generates tool descriptions from function signatures, so the legacy XML tool block was redundant and stale. #4 Delete every ``*_actions_schema.xml`` (12 files). They were read by the now-removed ``_load_xml_schema`` to build the legacy prompt's tool descriptions. No consumer remains. Side fixes: - ``reporting_renderer.py`` updated to import ``_parse_*_xml`` from the new location with leading underscore. - ``test_local_tools.py``, ``test_notes_jsonl_concurrency.py``, ``test_notes_wiki.py`` updated to point at the new module paths and call the ``_*_impl`` sync helpers. Tests: 279/279 passing. ~1500 LOC of action files moved into the tool wrappers; ~140 LOC of registry boilerplate removed; ~400 lines of dead XML deleted. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
572ef2a2af |
fix: address audit findings — SDK plumbing, TUI bus, dead code
Critical fixes: - ``StrixOrchestrationHooks.on_agent_start`` now finds the ``CaidoCapability`` via ``ctx.context['caido_capability']`` instead of ``agent.capabilities`` (we use plain ``Agent``, not ``SandboxAgent``, so the latter never existed). The session manager's bundle already exposes the capability; ``run_strix_scan`` threads it through ``make_agent_context`` and ``create_agent`` forwards it to children. - ``run_strix_scan`` registers the ``StrixTracingProcessor`` with the SDK's tracing provider via ``add_trace_processor`` so SDK trace spans hit ``run_dir/events.jsonl`` (was previously a parallel stream the SDK ignored). - ``on_llm_end`` now writes to ``Tracer.record_llm_usage`` in addition to ``bus.record_usage`` so the CLI/TUI stats panel sees real numbers instead of zeros. - ``run_strix_scan`` accepts an externally-built ``AgentMessageBus`` + an explicit ``model`` arg. The TUI pre-creates the bus so its stop and chat-input handlers can submit ``bus.send`` / ``bus.cancel_descendants`` coroutines onto the scan thread's loop via ``asyncio.run_coroutine_threadsafe`` — replacing the TODO-stub no-ops. - ``model`` config now propagates root → context → child agents in ``create_agent`` (was hardcoded fallback). Dead-code removal: - Deleted the ``load_skill`` tool entirely (host module, sandbox module, TUI renderer, tests). The legacy implementation reached into a global ``_agent_instances`` registry that no longer exists; the post-migration stub returned ``success=True`` without injecting anything — pure theater. Skills are still preloaded via the system prompt at scan-bring-up. - Dropped ``tenacity`` and ``xmltodict`` from ``[project.dependencies]`` — neither is imported anywhere post-migration. - Stripped the system prompt's "use the load_skill tool" lines. Tests: 278/278 passing. Removed two ``load_skill`` test cases and a ``test_tool_registration_modes::test_load_skill_import_...`` assertion that exercised the deleted module. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
af42499b95 |
refactor: remove all strix/ model alias machinery
The Strix proxy / ``strix/`` model namespace is gone. Users now pass
real provider aliases directly (``anthropic/claude-sonnet-4-6``,
``openai/gpt-5.4``, ``gemini/...``, ``openrouter/...``).
Deleted:
- ``STRIX_API_BASE`` constant in ``strix/config/config.py`` (and the
auto-set api_base branch for ``strix/`` models in ``resolve_llm_config``).
- ``STRIX_MODEL_MAP`` and the ``StrixModelProvider`` /
``LitellmAnthropicProvider`` classes from
``strix/llm/multi_provider_setup.py``.
- ``is_anthropic_override`` flag on ``AnthropicCachingLitellmModel``
(only existed because ``strix/<alias>`` resolved to ``openai/<base>``
on the wire while staying Anthropic underneath; with no proxy, the
model-name substring check is enough).
- ``startswith("strix/")`` branches in ``cli.py`` / ``main.py`` /
``dedupe.py`` and the ``uses_strix_models`` env-validation flag.
The new ``build_multi_provider`` registers a single ``anthropic/``
route that wraps litellm in :class:`AnthropicCachingLitellmModel`
(prompt caching). Every other prefix falls through to the SDK's
built-in routing.
Defaults flipped from ``strix/claude-sonnet-4.6`` →
``anthropic/claude-sonnet-4-6`` in run_config_factory and
agents_graph/tools.py + corresponding tests.
Tests updated:
- ``test_anthropic_cache_wrapper.py``: drop the override-flag tests.
- ``test_multi_provider_setup.py``: rewrite around the new single
``_AnthropicCachingProvider`` route.
- ``test_tool_registration_modes.py::test_load_skill_import_...``:
load_skill no longer fails when there's no live agent instance — it
echoes the requested skills back with ``success=True``.
Tests: 281/281 passing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
d8881498ee |
refactor: nuke legacy harness, drop sdk_ prefixes
The SDK harness is the only path now; legacy host-side code is gone. File names no longer carry the ``sdk_`` distinction. Deleted legacy host-side modules: - strix/agents/StrixAgent/ (template moved to strix/agents/prompts/) - strix/agents/base_agent.py, state.py - strix/llm/llm.py, config.py - strix/runtime/docker_runtime.py, runtime.py - strix/tools/executor.py, agents_graph/agents_graph_actions.py - strix/interface/sdk_dispatch.py + the env-flag dispatch in cli.py Renamed (drop ``sdk_`` prefix): - strix/sdk_entry.py → strix/entry.py - strix/agents/sdk_factory.py → strix/agents/factory.py - strix/agents/sdk_prompt.py → strix/agents/prompt.py - strix/tools/<x>/<x>_sdk_tool[s].py → strix/tools/<x>/tool[s].py - strix/tools/_legacy_adapter.py → strix/tools/_state_adapter.py - ``_legacy`` aliases inside the wrappers → ``_impl`` CLI + TUI now call ``run_strix_scan`` directly — they build the sandbox image / sources_path locally and rely on ``session_manager.cleanup`` (called inside ``run_strix_scan``'s finally) for teardown. Three TUI handlers that reached into legacy multi-agent globals (``_agent_instances``, ``send_user_message_to_agent``, ``stop_agent``) are now no-ops with a TODO; reconnecting them to the ``AgentMessageBus`` is a follow-up. Tracer.get_total_llm_stats no longer reaches into the deleted ``agents_graph_actions`` globals — the orchestration hooks now feed the tracer via ``Tracer.record_llm_usage`` (live + completed buckets). finish_scan's ``_check_active_agents`` and load_skill's runtime ``_agent_instances`` reach-in are no-op stubs; the ``AgentMessageBus`` is the source of truth post-migration. llm/utils.py rewritten to keep only the streaming-parser helpers (``normalize_tool_format``, ``parse_tool_invocations``, ``fix_incomplete_tool_call``, ``format_tool_call``, ``clean_content``). ``STRIX_MODEL_MAP`` moved to ``llm/multi_provider_setup.py`` (its only remaining caller). Per-file ruff ignores added for legacy interface modules (TUI / main / CLI / utils / streaming_parser / tool_components) and tracer.py — pre-existing PLC0415/BLE001/PLR0915 patterns are out of scope. Tests: 287/287 passing. Renamed test files to drop ``sdk_`` prefix. ``test_tracer.py::test_get_total_llm_stats_aggregates_live_and_completed`` rewritten to feed ``Tracer.record_llm_usage`` instead of legacy globals. Test file annotations added so pre-commit's strict mypy passes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
4e0d0f35d9 |
feat(migration): phase 5b — STRIX_USE_SDK_HARNESS dispatch flag
Adds the env-var gate that lets users opt into the SDK harness without
disturbing the legacy default. Per PLAYBOOK §7.1, this is the cutover
mechanism: STRIX_USE_SDK_HARNESS=1 routes scans through run_strix_scan
(the Phase 5 entry point); anything else continues to use
StrixAgent.execute_scan.
- strix/interface/sdk_dispatch.py:
- should_use_sdk_harness(): truthy-string parse of the env var.
- _resolve_sandbox_image(): reads strix_image from Config; falls
back to "strix-sandbox:latest" with a warning if unset.
- _resolve_sources_path(): when --local-sources is given, mounts
its parent so the agent walks down to the source tree; otherwise
creates a per-run scratch dir under XDG_CACHE_HOME/strix/sources/.
Phase 6 will replace this with the legacy clone-into-container
flow once we port that.
- run_scan_via_sdk(): the adapter — translates the legacy CLI
(scan_config dict + argparse Namespace + Tracer) into the keyword
arguments run_strix_scan expects. Returns the SDK RunResult; lets
failures bubble up.
- strix/interface/cli.py: adds the dispatch branch inside the existing
Live/status loop. Legacy default unchanged; SDK path is reached only
when STRIX_USE_SDK_HARNESS is truthy. Two pre-existing lazy imports
hoisted to module level (cleanup_runtime + sdk_dispatch helpers) so
ruff is happy.
Pre-existing legacy lint/type issues surfaced when pre-commit checked
the edited cli.py and chased imports — fixed or ignored in passing:
- utils.py:1052 duplicate ``metadata`` annotation removed.
- utils.py:1251 unused ``# type: ignore[import-not-found]`` for yarl.
- main.py:456 ``panel_parts`` inferred type rejected later string
entries — explicit ``list[Text | str]`` annotation.
- utils.py:resolve_diff_scope_context PLR0912 (16 branches) per-file
ignore — branches map 1:1 to scope-mode × target-type combinations.
Tests: 18 new tests in tests/interface/test_sdk_dispatch.py — env
flag parsing parametrized over truthy/falsy variants, image lookup
with config hit + miss-with-warning, sources path resolution for
local_sources / alternative key names / scratch-dir creation, and
the adapter's kwarg handoff verified against a patched
run_strix_scan (run_name from args + run_name from scan_config
fallback + failure propagation).
Refs: PLAYBOOK.md §7.1 (cutover), §7.2 (rollback).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
f0e254c1fd |
feat(migration): phase 5 — root agent factory + entry point
Three new modules that wire Phases 0-4 into a runnable Strix scan: - strix/agents/sdk_prompt.py: standalone Jinja-based system prompt renderer. Reuses the existing strix/agents/StrixAgent/system_prompt. jinja template (508 lines, the actual production prompt) so behavior parity with the legacy LLM._load_system_prompt is byte-identical. Skill resolution mirrors LLM._get_skills_to_load (caller skills → scan_modes/<mode> → whitebox pair, deduped). Fail-soft: template errors return empty string and log; agent construction must never blow up on prompt load. - strix/agents/sdk_factory.py: build_strix_agent(name, skills, is_root) assembles an agents.Agent. Root carries finish_scan and stops there; child carries agent_finish and stops there (C4). Caido tools come from CaidoCapability automatically — we don't include them in _BASE_TOOLS to avoid double-registration when the SDK runtime merges capability tools. model=None so RunConfig drives the model alias through MultiProvider rather than the SDK default. make_child_factory returns a closure over scan-level config (scan_mode, is_whitebox, interactive, scope context) for ctx.context['agent_factory'] — the Phase 3 create_agent tool calls it with (name, skills) per child. - strix/sdk_entry.py: run_strix_scan() — the top-level coroutine. Builds the bus, brings up (or reuses) a sandbox session via session_manager, builds the root Agent and the child factory, builds the per-agent context dict, registers the root in the bus, builds the RunConfig, calls Runner.run, and cleans up the session in a finally. Cancels descendants before re-raising any exception (C9). cleanup_on_exit toggle preserves the cached session for resume scenarios. _build_root_task and _build_scope_context preserve the legacy StrixAgent.execute_scan task formatting + scope context shape so the prompt template sees identical inputs. Tests: 21 new tests (10 for factory + prompt, 11 for entry point). Factory: root vs child tool list parity, finish_scan/agent_finish placement, tool_use_behavior dict shape, Caido absence (capability- provided), make_child_factory closure semantics. Entry point (all mocked, no real Docker/LLM): wiring shape verification — context dict carries every field downstream consumers read, session manager called with correct scan_id, cleanup runs even on Runner.run failure, cleanup skipped when disabled, scan_id auto-generation, scan-level config (scan_mode, is_whitebox) flows into the factory. Task and scope builders verified against the same shape as legacy. Per-file ruff ignores added: TC002 on sdk_factory (Tool used at runtime in _BASE_TOOLS tuple), TC003 + PLR0912 on sdk_entry (Path runtime-imported; _build_root_task's per-target-type branches are intentional and well-bounded). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
1d86e4506a |
feat(migration): phase 4 — sandbox capability + healthcheck + session manager
Three modules under strix/sandbox/ that bring the per-scan container
plumbing in line with the SDK's capability model:
- healthcheck.py: wait_for_http_ready (FastAPI tool server /health)
and wait_for_tcp_ready (Caido proxy port — no /health endpoint).
Connect/timeout errors continue polling; the timeout error message
carries the last failure class so a stuck scan tells you whether the
port refused, hung, or returned a non-2xx.
- caido_capability.py: CaidoCapability subclasses agents.sandbox.
capabilities.Capability and wires three concerns:
1. process_manifest injects http_proxy / https_proxy / ALL_PROXY
env vars pointing at the in-container Caido listener.
2. tools() returns the seven Caido SDK function tools from Phase 2.5
so the SDK runtime auto-merges them with each agent's tool list.
3. bind() schedules an asyncio.gather of both healthcheck probes;
StrixOrchestrationHooks.on_agent_start awaits the resulting
task before the first LLM call.
Pydantic v2 PrivateAttr is used for the underscore-prefixed runtime
fields (Pydantic forbids underscore-prefixed model fields).
- session_manager.py: per-scan_id cache. create_or_reuse builds the
StrixDockerSandboxClient with docker.from_env() (the SDK's docker
client now requires an explicit DockerSDKClient instance at init),
constructs the Manifest via Environment(value=...) (a flat dict is
silently dropped by Pydantic), resolves the host-side mapped ports
via session._resolve_exposed_port, configures the capability with
those ports *before* binding, and returns a bundle dict the
per-agent context reads to populate tool_server_host_port /
caido_host_port / bearer. cleanup is best-effort: a Docker daemon
error during delete is logged and swallowed so a stranded
container doesn't block the next scan.
Tests: 21 new tests in tests/sandbox/ — healthcheck happy path /
polling-through-failures / timeout for both HTTP and TCP probes (the
TCP test uses a real local listener, no mocks); CaidoCapability env
injection / tool list / bind scheduling / configure_host_ports;
session_manager full create flow, cache reuse, custom timeout, cleanup
including the Docker-daemon-failure swallow path.
mypy override added for docker.* (no upstream stubs); per-file ruff
TC002 ignore added for caido_capability.py — agents.tool.Tool is used
at runtime for the cached _CAIDO_TOOLS tuple.
Refs: PLAYBOOK.md §3.1-3.3, AUDIT.md §2.5 (C5).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
1ac32df817 |
feat(migration): phase 3 — multi-agent graph tools + Runner bridge
Six SDK function tools that drive the AgentMessageBus from Phase 0, replacing the legacy _agent_graph / _agent_messages / _agent_instances globals: - view_agent_graph: render parent/child tree from bus.parent_of with a per-status summary (running / waiting / completed / crashed / stopped). - agent_status: per-agent lifecycle + pending-message count snapshot. - send_message_to_agent: queue into bus.inboxes; rejects sends to finalized targets so the model gets feedback rather than a silent drop (the bus's own send method drops to support the C13 cleanup, but the tool surfaces it as a structured error). - wait_for_message: poll inbox once per second up to timeout. Polling rather than asyncio.Event because a missed wakeup on Event would be hard to debug; the bus already serializes through its own lock. - create_agent: spawn a child via asyncio.create_task(Runner.run(...)). Pulls an agent_factory callable from ctx.context (the Phase 5 root assembly is the one that wires it in). Registers the child with the bus before the task starts, stores the task handle in bus.tasks so cancel_descendants can cascade (C9), builds the child's identity block + optional inherited parent context, and runs the child with StrixOrchestrationHooks. - agent_finish: subagent-only termination. Flips agent_finish_called so the on_agent_end hook records "completed" instead of "crashed" (C8), and posts a structured <agent_completion_report> XML envelope to the parent's inbox. run_config_factory.make_agent_context grows two fields: sandbox_client (reused across child runs) and agent_factory (Phase 3 needs it; Phase 5 fills it in). PLC0415 fixed by hoisting the openai.types.shared.Reasoning import to module-level. Tests: 17 new tests in test_sdk_graph_tools.py — registration, all six tools' happy and error paths, real AgentMessageBus integration so the tools exercise production code paths, create_agent verified for spawn shape (task created, bus registered, identity block in input) plus a bus.cancel_descendants integration check. Refs: PLAYBOOK.md §4.3, AUDIT_R2 §1.4 (cancel_descendants), AUDIT_R3 C8. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
044e4e82ae |
feat(migration): phase 2.5 — wrap sandbox-bound SDK tools
Ten tools ported, all pure pass-throughs to post_to_sandbox: - browser_action (1 tool): the 21-action mega-tool dispatcher kept intact rather than fanned out, to preserve the legacy XML shape. - terminal_execute (1 tool): tmux session driver. - python_action (1 tool): IPython session manager. - proxy / Caido (7 tools): list_requests, view_request, send_request, repeat_request, scope_rules, list_sitemap, view_sitemap_entry. strix_tool decorator gains a strict_mode flag (default True, matching the SDK default). send_request and repeat_request opt out of strict mode because their headers / modifications dicts are free-form — the SDK's strict JSON schema rejects dict[str, X] without enumerated keys. Tests: 12 new tests in test_sdk_sandbox_tools.py covering registration, strict-mode opt-out verification for the two free-form tools, and dispatch shape verification (every wrapper is asserted to forward its full kwarg surface to post_to_sandbox so the in-container handler sees the same payload it always has). Per-file ruff TC002 ignores added for the four new wrapper modules. Phase 2 (tools) is now complete: 24 SDK function tools wrapped across think/todo/notes/web_search/file_edit/reporting/load_skill/finish_scan/ browser/terminal/python/proxy. Total: 7 local + 17 sandbox-bound. Phase 3 (multi-agent orchestration) is next. Refs: PLAYBOOK.md §3.6. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
57478e5d0d |
feat(migration): phase 2.4 — wrap remaining local SDK tools
Five tool families ported to SDK function tools using the proven delegation pattern from Phase 2.3: - web_search (1 tool): asyncio.to_thread around the synchronous Perplexity request so the 300s API call doesn't block the SDK event loop. - file_edit (3 tools — str_replace_editor, list_files, search_files): these run *inside* the sandbox container in the legacy harness (sandbox_execution=True), so the SDK wrappers route through post_to_sandbox rather than importing the legacy module on the host (which pulls in openhands_aci, a sandbox-only dependency). - reporting (1 tool — create_vulnerability_report): asyncio.to_thread around the legacy function, which itself runs CVSS XML parsing, LLM-based dedup against existing findings, and tracer persistence. - load_skill (1 tool): legacy adapter passes ctx.context['agent_id'] through. The legacy implementation reaches into _agent_instances, a global Phase 3 will replace; until then the call degrades to a structured error rather than crashing. - finish_scan (1 tool): legacy adapter pattern. Validates non-empty fields, checks no other agents are still active (via legacy _agent_graph), persists the four executive sections through the global tracer. Tests: 12 new tests in test_sdk_remaining_local_tools.py — registration checks, web_search delegation + missing-key path, file_edit dispatch shape verification, vuln-report validation + delegation, load_skill adapter passthrough, finish_scan validation + delegation. The two finish_scan tests use a fixture that snapshots/clears the legacy _agent_graph['nodes'] dict so cross-test pollution from legacy multi-agent tests doesn't mask the validation path. Per-file ruff TC002 ignores added for the five new wrapper modules (same reason as Phase 2.3 — RunContextWrapper must be runtime-importable for SDK function_schema().get_type_hints()). Refs: PLAYBOOK.md §3.5. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
6e5d96af34 |
feat(migration): phase 2.1-2.3 — sandbox dispatch + thin slice tool wrappers
Phase 2.1 — sandbox dispatch helper:
- strix/tools/_sandbox_dispatch.py: post_to_sandbox() centralizes the
host->container HTTP wire format. Connect=10s, read=150s timeouts mirror
legacy executor.py. 50 MB response cap (C18) prevents OOM from a runaway
tool. All errors surface as {"error": str} so the model can recover
instead of the run dying.
Phase 2.2 — C6 lock-protected JSONL writes:
- strix/tools/notes/notes_actions.py: notes.jsonl appends are now wrapped
in _notes_lock so concurrent agents can't interleave half-written lines.
Regression test in test_notes_jsonl_concurrency.py verifies 1000 parallel
writes produce exactly 1000 valid JSON lines.
Phase 2.3 — thin-slice SDK wrappers (think + todo + notes):
- strix/tools/_legacy_adapter.py: LegacyAgentStateAdapter shim — exposes
just enough surface (.agent_id) for legacy tools that close over
agent_state, sourced from ctx.context['agent_id'].
- strix/tools/thinking/thinking_sdk_tools.py: 1 tool (think).
- strix/tools/todo/todo_sdk_tools.py: 6 tools (create/list/update/done/
pending/delete) with bulk-form preserved.
- strix/tools/notes/notes_sdk_tools.py: 5 tools (create/list/get/update/
delete) with asyncio.to_thread around the lock-protected file I/O.
Tests: 22 new tests pass (10 sandbox dispatch + 2 concurrency + 10 SDK
local). Full suite still green.
Per-file ruff ignores added for SDK wrapper files: TC002 (RunContextWrapper
must be runtime-importable because the SDK calls get_type_hints() to
derive the JSON schema) and PLR0911 (sandbox dispatch's 10 short-circuit
returns are intentional, each a distinct documented failure mode).
Refs: PLAYBOOK.md §3.4, AUDIT_R3.md C6/C18.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|