Strix Agent: Technical Implementation Comparison
Read of usestrix/strix@HEAD (Apache-2.0, ~18.7k LOC Python) on 2026-05-23, mapped against 0sec-labs/0sec@research/strix-comparison. Every claim below is anchored to a strix/<path>:<line> or 0sec packages/<path>:<line> reference; sections marked “not observed in public repo” are exactly that.
1. Executive summary
Section titled “1. Executive summary”- Strix has no programmatic verification layer. “Validation” is just another agent role spawned via
create_agentand bound by prompt to “prove it’s real with PoC” — there is no separate replay runner, no oracle, nosignature_matchedstyle verdict. Compare to 0sec’sverify/replay-runner.ts(726 lines, structuredexpectpredicates) andverify/kernel-verify.ts(690 lines, oracle-driven). This is the most surprising finding in the repo: a 25k-star agent ships zero out-of-band verification. - Strix’s “Docker isolation” is a single shared container per scan, not per-agent. Every sub-agent in a scan hits the same
strix-scan-<scan_id>container over HTTP (strix/runtime/docker_runtime.py:142,strix/agents/StrixAgent/system_prompt.jinja:233). The system prompt literally tells the model: “All agents run in the same shared Docker container for efficiency.” No gVisor, no microVM, no per-agent kernel. 0sec’s equivalent (packages/core/src/agent/docker-executor.ts:1-50) is the same shape — shared kernel — but 0sec at least scopes one container per scan with cleanup; both projects rely ondocker run --rmas the only boundary. - Tool calls are XML, parsed with regex. Strix prompts the model to emit
<function=name><parameter=k>v</parameter></function>and parses it by hand instrix/llm/utils.py(parse_tool_invocations,fix_incomplete_tool_call,_truncate_to_first_function). No JSON-schema tool calling, no provider-native tool API. 0sec added an XML dispatch path for the same reason (cheap models emit malformed JSON —packages/core/src/agent/xml-dispatch.ts,agent/loop.ts:59-66) but keeps JSON tool-calling as the default. Strix has committed to string parsing as the only path. - The system prompt is the architecture.
strix/agents/StrixAgent/system_prompt.jinjais 509 lines of behavioral law: refusal-avoidance, scope-locking, mandatory phase ordering, hard-coded vulnerability priority list, multi-agent workflow diagrams, agent-spawn rules. There is no planner module, no FSM, no orchestrator graph beyond what the LLM does after reading the prompt. The whole system is “one model + giant prompt + recursivecreate_agent.” - No CI test suite for the agent loop.
.github/workflows/build-release.ymlis the only workflow, and it builds the PyInstaller binary — no pytest run, no eval suite, no benchmark in CI. Thetests/tree exists (~1.9k LOC pytest, mostly unit tests of tool registration and Docker runtime mocks) but nothing runs it on PR. 0sec ships 30+ workflows includingjournal-ablation.yml,npm-bench.yml,xbowslices, and a paid eval gate.
2. Strix architecture overview
Section titled “2. Strix architecture overview”Agent loop
Section titled “Agent loop”strix/agents/base_agent.BaseAgent.agent_loop (base_agent.py:152-259) is a single while True with these checks per iteration:
_check_agent_messages— drain the inter-agent inbox (agents_graph_actions._agent_messages[agent_id]).- Waiting / stop /
llm_failedgates. state.increment_iteration(); inject “approaching max iterations” warning at 85% (state.is_approaching_max_iterations), inject “CRITICAL: 3 iterations left” warning atmax_iterations - 3._process_iteration→ stream LLM response, accumulate, regex-truncate at the first</function>outside any<thinking>block (llm/llm.py:35-42,216-223), thenparse_tool_invocations(llm/utils.py)._execute_actions→process_tool_invocations(tools/executor.py:313-342) executes every action in sequence, appends a<tool_result>XML observation to the conversation.finish_scan/agent_finishtools setshould_agent_finish = Trueand exit the loop.
max_iterations = 300 is the default (base_agent.py:50), high relative to 0sec’s 40-turn shellPentest default. There is no early-stop, no oscillation detection, no reflection checkpoint — the only safety net is the budget warning at 85%.
Planner / executor split
Section titled “Planner / executor split”There is none. The “root agent” is just a StrixAgent instance whose system-prompt skill coordination/root_agent.md reminds it: “The root agent’s primary job is orchestration, not hands-on testing” (strix/skills/coordination/root_agent.md). Whether the model obeys is up to the model. There is no enforcement code that prevents the root from calling terminal_execute.
Concurrency
Section titled “Concurrency”create_agent (tools/agents_graph/agents_graph_actions.py:384) spawns sub-agents as Python threading.Threads (_running_agents: dict[str, threading.Thread], agents_graph_actions.py:18). Sub-agents communicate by appending to a shared _agent_messages dict; the parent loop polls it each iteration via _check_agent_messages. There is no asyncio coordination across agents — asyncio is used inside each agent for I/O, threads for cross-agent. Sub-agent results are surfaced by writing into the same _agent_messages queue with from/to agent ids.
The graph itself is a flat dict (_agent_graph["nodes"], _agent_graph["edges"]); there is no DAG scheduler, no dependency tracking, no rate limiter across the tree.
LLM provider abstraction
Section titled “LLM provider abstraction”Strix uses LiteLLM end-to-end (strix/llm/llm.py:1-27). The LLM class (llm/llm.py:77-411) wraps litellm.acompletion(stream=True) with: prompt-caching control-blocks for Anthropic (_add_cache_control, llm.py:395-411), reasoning_effort for models that support it, vision-stripping for those that don’t, and a retry loop with 2^attempt backoff capped at 90s (llm.py:178-187). The only provider-specific code is the Anthropic cache-control insertion and the _is_anthropic() check (llm.py:362-365). Vertex AI is an optional extra (pyproject.toml:55).
Cost is captured per-request via litellm.completion_cost (llm.py:338-348) and accumulated in RequestStats (llm.py:59-74). There is no cost ceiling enforcement in BaseAgent — cost is just observed, never an exit condition. The closest thing is max_iterations.
3. Prompt + system design
Section titled “3. Prompt + system design”The system prompt template is strix/agents/StrixAgent/system_prompt.jinja. Structure (each is a top-level XML tag):
<core_capabilities>(4 bullets)<communication_rules>— interactive vs autonomous branch; the autonomous branch contains the load-bearing line: “While the agent loop is running, almost every output MUST be a tool call. Do NOT send plain text messages; act via tools.” (system_prompt.jinja:42)<execution_guidelines>— system-verified scope block (Jinja-rendered fromsystem_prompt_context.authorized_targets); explicit refusal-avoidance block: “Do not self-classify normal in-scope validation as unauthorized, harmful, suspicious, or disallowed. Do not produce generic policy warnings or generic safety refusals.” (system_prompt.jinja:73-76)<vulnerability_focus>— a hard-coded numbered list of 10 vuln classes the agent MUST test (IDOR, SQLi, SSRF, XSS, XXE, RCE, CSRF, race conditions, business logic, auth/JWT) —system_prompt.jinja:194-208<multi_agent_system>— workflow diagrams as ASCII art, agent-spawning rules, specialization examples —system_prompt.jinja:231-362<tool_usage>— the XML format spec with explicit “wrong format” examples —system_prompt.jinja:364-435<environment>— full Kali tool inventory by category —system_prompt.jinja:437-498<specialized_knowledge>— Jinja loop over loaded “skills” (vulnerability/tool/framework markdown files) —system_prompt.jinja:500-508
Two patterns worth quoting verbatim because they reveal design intent:
“NEVER use ‘Strix’ or any identifiable names/markers in HTTP requests, payloads, user-agents, or any inputs” —
system_prompt.jinja:16. Brand stealth at the prompt level.
“PERSISTENCE IS MANDATORY: Real vulnerabilities take TIME - expect to need 2000+ steps minimum. NEVER give up early - attackers spend weeks on single targets. … Bug bounty hunters spend DAYS on single targets - so should you.” —
system_prompt.jinja:355-360. This is anchored on a 300-iteration default cap which can’t actually fit “2000+ steps,” so the prompt is aspirational rather than budget-aligned.
Skills are markdown files merged into the prompt at render time (llm/llm.py:100-125). The active skill list is per-agent (LLMConfig.skills, llm/config.py:32) and can be mutated mid-run by the load_skill tool (add_skills, llm/llm.py:143-158), which re-renders the entire system prompt. Sub-agents inherit the scan-mode skill (scan_modes/quick|standard|deep) and pick up role-specific skills via create_agent(..., skills=[...]).
4. Tool design
Section titled “4. Tool design”12 tool groups, registered via the @register_tool decorator (tools/registry.py:190-250). Each tool ships an XML schema (<tool name=…><parameters>…</parameters></tool>) next to its Python module, loaded by _load_xml_schema and merged into get_tools_prompt() (registry.py:280-300). Tool catalog:
| Group | Notable tools | Sandbox? |
|---|---|---|
terminal | terminal_execute, session management | yes |
python | python_execute (persistent IPython kernel) | yes |
file_edit | str_replace_editor (OpenHands ACI wrapper) | yes |
browser | full Playwright surface | yes |
proxy | Caido CLI (already running in container) | yes |
terminal + proxy | shared with all sub-agents in the same container | yes |
notes | list_notes, get_note, set_note — shared “wiki” memory across agents | no |
agents_graph | create_agent, send_message, wait_for_message, agent_finish | no |
reporting | create_vulnerability_report (CVSS-validated, dedupe-checked) | no |
todo | per-agent todo list | no |
finish | finish_scan (root only) | no |
thinking | think — no-op tool that returns the message back | no |
web_search | Perplexity sonar-reasoning-pro (requires PERPLEXITY_API_KEY) | no |
load_skill | mutates the system prompt to inject more guidance | no |
Validation: _validate_tool_arguments (tools/executor.py:130-153) checks required/unknown parameters against the XML schema before dispatch. Errors are returned as "Error: …" strings stuffed back into the next user message — there’s no structured tool_error channel.
Result formatting: all results round-trip as a wrapped XML string <tool_result><tool_name>…</tool_name><result>…</result></tool_result> (executor.py:251-256); results longer than 10kB are middle-truncated (executor.py:246-249). Screenshots are detected by a magic screenshot field on dict results and lifted into an image_url content block (executor.py:227-256, 345-364).
The “in-sandbox” tools are not actually called locally — _execute_tool_in_sandbox (tools/executor.py:39-99) HTTP POSTs to a tool_server running inside the container at port 48081 with a bearer token. The tool server is an independent FastAPI process (runtime/tool_server.py referenced from pyproject.toml:60-67 sandbox extras). Auth is a 256-bit secrets.token_urlsafe(32) minted at container creation (docker_runtime.py:162).
Anti-patterns observed
Section titled “Anti-patterns observed”- Fragile regex on streaming output:
_truncate_to_first_function/fix_incomplete_tool_call/normalize_tool_format(llm/utils.py) all depend on the model emitting<function=…></function>correctly. The “wrong formats” section of the system prompt (system_prompt.jinja:393-402) is essentially a list of failure modes they’ve observed; the parser has to forgive<invoke name="…">, code-fenced calls, etc. - Global mutable state for the agent graph:
_agent_graph,_agent_messages,_running_agents,_agent_instances,_agent_statesare all module-level dicts (agents_graph_actions.py:11-30). Only one is lock-guarded (_agent_llm_stats_lock). Concurrent multi-scan in one process would race. except Exception: passin telemetry/cost paths (llm/llm.py:307, 335, 347, 370, 376,telemetry/posthog.py:32-33, 63-64). Real bugs there will silently degrade cost accounting.
5. Planning / decomposition
Section titled “5. Planning / decomposition”No formal planner. Decomposition is delegated entirely to the LLM via two mechanisms:
-
The
root_agentskill prompt (strix/skills/coordination/root_agent.md) describes a four-bucket taxonomy (Reconnaissance / Vulnerability Assessment / Exploitation and Validation / Reporting) and a “Discovery → Validation → Reporting → Fix” delegation chain. -
The system-prompt workflow diagrams for black-box and white-box (
system_prompt.jinja:298-318) prescribe exact spawn sequences:SQL Injection Agent finds vulnerability in login form↓ create_agent("SQLi Validation Agent (Login Form)")↓ create_agent("SQLi Reporting Agent (Login Form)") on success
There is no machine-checkable representation of “what’s been planned” — the prompt asserts state.context["plan"] should be populated but nothing in state.py reads it as a control signal. The todo tool (tools/todo/todo_actions.py, 568 LOC) is the closest thing to a tracked plan, but it’s still LLM-managed text.
Compare to 0sec’s agent/journal/orchestrator.ts:30-42, which has explicit prioritized routing rules (R1–R7) over a structured OrchestratorBrief, and agent/journal/specialists.ts which defines exactly four specialist roles with bounded scope slices and budgets.
6. Context management
Section titled “6. Context management”strix/llm/memory_compressor.py is the whole story.
- Trigger: when total tokens (system + history + reserved) exceed
MAX_TOTAL_TOKENS * 0.9 = 90_000(memory_compressor.py:12, 215). - Strategy: always keep the last
MIN_RECENT_MESSAGES = 15messages (memory_compressor.py:13, 205); chunk older messages into groups of 10 and replace each chunk with an LLM-generated summary (memory_compressor.py:218-224). - Summary model: the same model as the scan (
Config.get("strix_llm")) called via rawlitellm.completion— not the streamingLLMclass, no retries (memory_compressor.py:107-131). If summarization fails, the chunk is dropped silently except formessages[0](memory_compressor.py:131). - Prompt (
memory_compressor.py:15-43): a “compress this for the next security agent” instruction that explicitly lists what to preserve (vulns, creds, tool outputs, dead ends). - Image handling:
_handle_imageskeeps only themax_images = 3most recent images, rewriting older ones to placeholder text (memory_compressor.py:134-149).
Critical gap vs 0sec: no journal. Conversation state lives in AgentState.messages (agents/state.py:33), a plain in-memory list[dict[str, Any]]. There is no append-only fsync’d log, no replay capability, no resume-after-crash. The Tracer (telemetry/tracer.py, 860 LOC) does write JSONL to disk for observability — but that’s a sink for UI rendering, not a source of truth the agent reads back. Compare:
- 0sec
agent/journal/writer.ts:77-100, 90-95—loadJournal()rehydrates from an fsync’djournal.jsonlplus sidecar artifacts directory, both sync-by-runId and async-by-path overloads. - 0sec
agent/journal/orchestrator.ts:122-130—runOrchestratoraccepts{ resume: true }and picks up at the last fsynced entry. - Strix
_initialize_sandbox_and_state(base_agent.py:331-366) — no resume path; if the process dies, the scan dies.
7. Verification / oracle patterns
Section titled “7. Verification / oracle patterns”This is the section where Strix’s design philosophy diverges most sharply from 0sec’s.
Strix has no oracle. A finding is “verified” when:
- A discovery sub-agent calls
create_agent(name="… Validation Agent"). - The validation sub-agent runs whatever it wants (likely
terminal_execute/python_execute) and decides, in natural language, that it succeeded. - The validation sub-agent calls
create_agent(name="… Reporting Agent"). - The reporting sub-agent calls
create_vulnerability_report(tools/reporting/reporting_actions.py:201-339).
create_vulnerability_report does enforce structural checks: required fields, CVSS XML parses to valid cvss>=3.2 vector, optional CVE/CWE regex (reporting_actions.py:9-152). But there is no replay: the tool doesn’t re-run the PoC, doesn’t fetch the URL, doesn’t verify the payload. poc_script_code is just text the model writes.
The one programmatic check is LLM-based deduplication: check_duplicate (llm/dedupe.py:142-213) calls the same LLM with an XML-formatted system prompt asking “is this a duplicate?” and parses the <dedupe_result> block back. On error, it falls open (not duplicate) so the report saves. Confidence is whatever the dedup model reports as a float — no calibration, no floor.
0sec equivalents:
| Layer | 0sec |
|---|---|
| Replay-time oracle | packages/core/src/verify/replay-runner.ts:1-100 — runDeterministicReplay, assertionFromStepExpect, evaluateAssertion, structured expect predicates (exit-zero, http-status, body-contains, body-matches, file-exists). |
| Confidence floors | packages/core/src/agent/finding-confidence.ts:1-60 — hybrid: LLM self-report clamped UP by PoC-status floor (0.6 if pocSteps present, 0.8 if any verifiable expect). |
| Kernel finding verification | packages/core/src/verify/kernel-verify.ts:1-100 — constrained loop with a single allowlisted tool (kernel_run), oracle-driven promotion (signature_matched → confirmed, confidence=1.0), explicit budget, separate from the main agent loop. |
| Flag validator | packages/core/src/agent/flag-validator.ts — for CTF-style flag oracles. |
The split is: 0sec treats verification as a separate runtime concern (not another LLM judging); Strix treats verification as another LLM in a different prompt costume.
8. Test infrastructure
Section titled “8. Test infrastructure”tests/is ~1.9k LOC pytest, organized by module (tests/agents/,tests/tools/,tests/runtime/,tests/telemetry/).- The biggest single test file is
tests/telemetry/test_tracer.py(449 LOC). tests/runtime/test_docker_runtime.py(87 LOC) mocks the Docker client; no real container spin-up in test.tests/tools/test_tool_registration_modes.py(94 LOC) exercises the@register_tooldecorator’s flag handling.- No agent-loop integration test.
tests/agents/__init__.pyexists but the directory is otherwise empty. - No eval / benchmark suite checked into the repo. There is a
benchmarks/directory at repo root but it contains a stub README only. conftest.py(top-level) is empty-init style.
CI: the only workflow is .github/workflows/build-release.yml — matrix-builds the PyInstaller bundle on macOS/Linux/Windows and uploads to a GitHub Release on v* tags. pytest is never invoked in CI. No coverage, no lint gate visible in CI (though ruff/mypy/bandit are in dev deps).
Compare 0sec .github/workflows/: 30+ workflows including journal-ablation.yml, xbow-*, npm-bench.yml, htb-bench.yml, argus-bench.yml, release-smoke.yml, docker-kali-publish.yml, kernel-validator-batch.yml.
9. Operational concerns
Section titled “9. Operational concerns”Isolation (the HN “Docker containers” claim)
Section titled “Isolation (the HN “Docker containers” claim)”Confirmed from strix/runtime/docker_runtime.py:
- One container per scan, named
strix-scan-<scan_id>(docker_runtime.py:143). - Image is
Config.get("strix_image")(default percontainers/Dockerfile:kalilinux/kali-rolling:latest). cap_add=["NET_ADMIN", "NET_RAW"](docker_runtime.py:175) — broader than default. No--security-opt, no seccomp profile, no AppArmor/SELinux profile passed, no user-namespacing config.- Container runs
sleep infinity; commands enter viacontainer.exec_runfor setup and via the in-container tool-server HTTP API for agent tool calls. - All sub-agents share this container (
system_prompt.jinja:233-236makes this explicit to the model).
Isolation boundary is the Linux kernel of the host. This is the standard Docker shared-kernel model — fine for “don’t pollute my filesystem,” not fine for “I’m running untrusted RCE payloads against my workstation kernel.” There is no gVisor, no Firecracker-like microVM, no Kata. The HN comment that Strix runs in “Docker containers” is literally true and is the only isolation layer.
For comparison, 0sec’s agent/docker-executor.ts uses the same shared-kernel Docker model for shell-tool execution. Stronger isolation in 0sec shows up only in the kernel-verify path, which delegates to a separate runner — and per the public-copy rule, that’s described as “isolated kernel per scan” without implementation specifics.
Retries
Section titled “Retries”- LLM-call retries: 5 attempts default,
2^attemptbackoff capped at 90s (llm/llm.py:178-187). Retry decision delegates tolitellm._should_retry(status_code)(llm/llm.py:350-354), so retries fire only on HTTP errors with a retryable status. On final failure,LLMRequestFailedErroris raised and the agent entersllm_failedwaiting state (base_agent.py:568-601). - Tool-call retries: none. If
process_tool_invocationsraises, the error is added to the conversation as text (executor.py:178-186) and the loop continues — the model gets to decide whether to retry. - Sandbox initialization: 2 retries with exponential backoff (
docker_runtime.py:151-209).
Cost ceiling
Section titled “Cost ceiling”None enforced. RequestStats.cost accumulates (llm/llm.py:330-333) and is reported in telemetry and the agent graph (agents_graph_actions.py:60-70), but no check ever terminates the run on a $-threshold. The only termination conditions are max_iterations, finish_scan, stop_requested, and llm_failed.
Telemetry
Section titled “Telemetry”Two layers:
- PostHog (
strix/telemetry/posthog.py) — opt-in viaSTRIX_TELEMETRY=1(telemetry/flags.py). Hard-coded public API key atposthog.py:15. Events:scan_started,finding_reported,scan_ended,error. Properties include model, scan_mode, vulnerability counts by severity, total cost, agent count. - OpenTelemetry / Traceloop (
strix/telemetry/tracer.py,telemetry/utils.py) — whenSTRIX_OTEL_ENABLED=1, exports spans for LLM calls and tool executions via OTLP HTTP. The tracer also writes a local JSONL run log to~/.strix/runs/<run_id>/events.jsonl(referenced via_events_file_path,tracer.py:81).
A first-run anonymous-id file at ~/.strix/.seen (posthog.py:25-34) seeds a first_run: true event on first scan.
10. Per-section comparison vs 0sec
Section titled “10. Per-section comparison vs 0sec”| Concern | Strix | 0sec |
|---|---|---|
| Agent loop entry | strix/agents/base_agent.py:152 agent_loop (single while True, max_iterations=300) | packages/core/src/agent/loop.ts:38 runAgentLoop (300 LOC, session-restore aware); plus native-loop.ts:1449 for the legacy native path |
| Loop dispatch | implicit (one agent class) | not implemented — there is no loop-dispatch.ts and no journalLoop feature flag; agent/native-loop.ts is the single agent loop today (agent/journal/ holds the journal primitives only) |
| Planner | none (prompt-only) | packages/core/src/agent/journal/orchestrator.ts:30-71 rule-based dispatcher with R1–R7 + pluggable decideNext hook for the FSM upgrade (#225) |
| Sub-agent model | Python threading.Thread per sub-agent, shared _agent_messages dict (tools/agents_graph/agents_graph_actions.py:18) | per-role specialist registry; explicit budget per dispatch (journal/specialists.ts:1-50) |
| Tool calling | XML, parsed via regex (llm/utils.py) | JSON tool-calling default; XML dispatch fallback for cheap models (agent/xml-dispatch.ts, agent/loop.ts:59-66) |
| Tool catalog | 12 groups, schemas in XML files (tools/*/[name]_schema.xml) | similar breadth in agent/tools/; kernel-run.ts is a single-purpose verification tool |
| Context compression | LLM-summarize chunks of 10 over 90% of 100k token cap (llm/memory_compressor.py:215-226) | journal summarizer with hard token cap and OrchestratorWindowExceededError (journal/summarizer.ts:20, orchestrator.ts:104-112) — fails loud, doesn’t silently drop |
| Persistence / resume | none (in-memory state.messages) | journal/writer.ts:90-100 loadJournal() with fsync’d JSONL + sidecar artifacts; orchestrator.ts:122-130 resume: true |
| Verification | sub-agent role + create_vulnerability_report shape checks (tools/reporting/reporting_actions.py:201) | verify/replay-runner.ts:1-100 deterministic replay with structured expect predicates; verify/kernel-verify.ts:1-100 oracle-driven kernel verify; agent/finding-confidence.ts:1-60 PoC-status confidence floors |
| Deduplication | LLM-judge, XML parse, fail-open (llm/dedupe.py:142-213) | not directly equivalent; 0sec uses confidence + finding-table dedupe upstream |
| Cost ceiling | observed only (llm/llm.py:330) | PipelineOptions.costCeilingUsd (unified-pipeline.ts:62) plumbed through to the agent runner |
| Provider abstraction | LiteLLM only, single LLM class (llm/llm.py:77) | runtime registry (packages/core/src/runtime/registry.ts) with LlmApiRuntime, native, codex variants |
| Isolation | one shared Docker container per scan; cap_add=NET_ADMIN,NET_RAW (runtime/docker_runtime.py:175) | shared-kernel Docker for shell tools (agent/docker-executor.ts:1-50); isolated kernel per scan for kernel-verify path |
| Telemetry | PostHog + optional OTel (telemetry/) | structured event emitter + JSONL logs; CI-tied ablation pipelines |
| CI | one workflow (build PyInstaller bundle) | 30+ workflows including weekly benchmark suites and journal-ablation A/B gate |
| Tests | ~1.9k LOC pytest, no CI run | thousands of LOC vitest, run on every PR; eval harness in .github/workflows/scripts/ |
11. Recommendations
Section titled “11. Recommendations”Ordered by ROI. “Borrow” = clear win, ship it. “Verify empirically” = sounds good in their prompt, but we don’t know if it actually moves their numbers, so A/B before adopting. “Avoid” = anti-pattern.
Borrow
Section titled “Borrow”-
Inject a hard “approaching max iterations” warning at 85% and a “CRITICAL: 3 left” warning at the tail. Strix
base_agent.py:186-211injects ausermessage into the conversation when the iteration budget gets thin, telling the model to wrap up via the finish tool. 0sec’sagent/loop.tscurrently just hitsmaxTurnsand writesstate.summary = "Agent reached max turns…"(loop.ts:350). A two-stage warning gives the model a chance to actually calldoneand persist a partial finding rather than getting cut off mid-thought. Low risk, single-PR. -
The “skill” pattern as just-in-time prompt injection. Strix’s
load_skilltool (tools/load_skill/load_skill_actions.py, with skills instrix/skills/{vulnerabilities,frameworks,tooling,technologies,protocols}/*.md) lets the model decide at runtime “I need the GraphQL playbook now” and re-renders the system prompt with that markdown appended (llm/llm.py:143-158). 0sec’s dynamic playbooks are injected once after recon (packages/core/src/agent/playbooks.ts); a tool-callable “load this skill” would let mid-run discoveries pull in narrower guidance without bloating every system prompt. Costs prompt cache invalidation; A/B against playbook injection. -
The vulnerability-report structural validator.
reporting_actions.py:201-339enforces a strict schema before any report is accepted: required text fields, CVSS XML parses to a realcvss>=3.2vector, optional CVE/CWE regex shape-checks, file-path validation that rejects..and absolute paths (_validate_file_path,reporting_actions.py:66-74). 0sec’sFindingvalidation is laxer; tightening at the agent boundary would prevent garbage from reachingfindings-tablerendering. Pair with the existingfinding-confidence.tsfloors.
Verify empirically
Section titled “Verify empirically”-
Their refusal-avoidance block.
system_prompt.jinja:71-76(“Do not self-classify normal in-scope validation as unauthorized…”) plus the system-verified-scope block (:48-63) is a coordinated countermeasure against frontier-model safety reflexes that intermittently break 0sec runs too. Worth A/B testing in ourprompts.ts. Don’t borrow blind — Strix’s text is overconfident (“All permission checks have been COMPLETED and APPROVED”) and may trigger different model behavior; test on the same XBOW slice before committing. -
The system-prompt-as-scope-lock pattern. Strix injects
authorized_targetsas Jinja-rendered system-prompt context (strix/agents/StrixAgent/strix_agent.py:22-57) rather than as user-message scope. Mid-conversation user input can’t override system-prompt scope as easily, which matters when sub-agent traffic is appended as"user"role messages. 0sec ships scope mostly via the initial prompt; check whether the same shift reduces scope creep in our runs.
-
Don’t adopt their context-compression as-is. The “summarize chunks of 10, fall back to
messages[0]on failure” (memory_compressor.py:131, 218-224) silently drops work on transient LLM failures. 0sec’sOrchestratorWindowExceededError(agent/journal/orchestrator.ts:104-112) is better — fail loud and surface the bug. Don’t soften that. -
Don’t adopt their threading-based sub-agent model. Global module-level dicts (
_agent_graph,_agent_messages,_running_agents,_agent_instances,_agent_states—agents_graph_actions.py:11-30) with one lock between them is a recipe for multi-scan races. 0sec’s per-run journal scoping is the correct shape; keep it. -
Don’t adopt their “validation is another LLM” approach. This is the central disagreement. Their
create_vulnerability_reportaccepts whatever PoC text the model emits — no replay, no oracle, no calibration. 0sec’sverify/replay-runner.ts+finding-confidence.ts+verify/kernel-verify.tstriangle is the moat. Hold the line.
12. Sources / citations
Section titled “12. Sources / citations”Strix references (all on usestrix/strix@HEAD, cloned 2026-05-23):
- Agent loop:
strix/agents/base_agent.py:152-259,:331-366,:568-601 - Agent state:
strix/agents/state.py:1-186 - LLM provider:
strix/llm/llm.py:77-411,:178-187,:330-348,:395-411 - Memory compression:
strix/llm/memory_compressor.py:12-13,:15-43,:134-149,:215-226 - System prompt:
strix/agents/StrixAgent/system_prompt.jinja:1-509(key blocks::16,:42,:48-76,:194-208,:231-362,:355-360,:393-402) - Scope handling:
strix/agents/StrixAgent/strix_agent.py:22-152 - Tool dispatch:
strix/tools/executor.py:39-99,:130-186,:227-256,:313-342 - Tool registry:
strix/tools/registry.py:190-300 - Vulnerability report tool:
strix/tools/reporting/reporting_actions.py:9-152,:201-339 - Deduplication:
strix/llm/dedupe.py:14-76,:142-213 - Agents graph:
strix/tools/agents_graph/agents_graph_actions.py:11-30,:384,:797 - Docker runtime:
strix/runtime/docker_runtime.py:142-256,:175 - Kali container:
containers/Dockerfile:1-42 - Skills:
strix/skills/coordination/root_agent.md,strix/skills/scan_modes/deep.md - Telemetry:
strix/telemetry/posthog.py:15-138,strix/telemetry/tracer.py:50-90 - CI:
.github/workflows/build-release.yml - Tests:
tests/(totals fromwc -l, no integration tests intests/agents/)
0sec references (all on 0sec-labs/0sec@research/strix-comparison):
- Agent loop:
packages/core/src/agent/loop.ts:38-120,:350 - Native loop:
packages/core/src/agent/native-loop.ts(1449 LOC) - Loop dispatch: planned, not implemented — today
packages/core/src/agent/native-loop.tsis the only loop;packages/core/src/agent/journal/holds the journal primitives - Journal writer:
packages/core/src/agent/journal/writer.ts:64-100 - Journal orchestrator:
packages/core/src/agent/journal/orchestrator.ts:30-71,:104-112,:122-130 - Journal summarizer:
packages/core/src/agent/journal/summarizer.ts:11-30,:67-82 - Journal specialists:
packages/core/src/agent/journal/specialists.ts:1-50 - Verification (general):
packages/core/src/verify/index.ts:1-69 - Replay runner:
packages/core/src/verify/replay-runner.ts:1-100 - Kernel verify:
packages/core/src/verify/kernel-verify.ts:1-120 - Kernel verify prompts:
packages/core/src/verify/kernel-prompts.ts:1-80 - Kernel-run tool:
packages/core/src/agent/tools/kernel-run.ts:1-185 - Finding confidence:
packages/core/src/agent/finding-confidence.ts:1-60 - Docker executor:
packages/core/src/agent/docker-executor.ts:1-50,:194-228 - Pipeline:
packages/core/src/unified-pipeline.ts:46-112 - XML dispatch:
packages/core/src/agent/xml-dispatch.ts(referenced fromagent/loop.ts:59-66) - Prompts:
packages/core/src/agent/prompts.ts:64-80