Skip to content

Feature Multi Backend

Luigi Colluto edited this page Aug 14, 2026 · 2 revisions

Feature: Multi-Backend

Argo runs the same multi-stage pipeline on a swappable agent backend, so you can run it with whatever you already have — Claude Code, the Codex CLI (OpenAI), or a local/open-source model — without changing any audit logic. This also makes Argo a vehicle for a clean cross-model comparison: identical prompts and pipeline, different model, directly comparable results.

AgentRunner (ABC)
├─ HeadlessClaudeRunner   # `claude -p` — Claude Code
├─ CodexRunner            # `codex exec` — OpenAI, or local/OSS via --oss
├─ MockClaudeRunner       # fixtures (zero tokens; the test suite)
└─ FallbackRunner         # wraps an ordered chain of the above (resilience, below)

--runner {headless|codex|mock} picks the backend (default headless = Claude Code).

The guardrails are backend-neutral, enforced per backend

One SessionPolicy — no network except the two OSINT stages (research + corroborate), and the repo is never writable — and each backend translates it into its own dialect:

Guarantee Claude Codex
Repo read-only via --add-dir + chmod outside the workspace + chmod, never --add-dir'd
Writes only scratch session cwd = scratch -s workspace-write, cwd = scratch
No network (default) tools stripped from allowlist OS sandbox denies egress
Network only for research/corroborate OSINT tools kept for those stages network re-enabled only for those stages
Never a sandbox escape network/mutation tools always disallowed never danger-full-access

Both mappings are unit-tested independently. See Guardrails & Safety.

Local / open-source models (Ollama / LM Studio)

Any model Ollama or LM Studio serves works — including Qwen (qwen2.5-coder, qwen3) and DeepSeek (deepseek-coder, deepseek-v3, deepseek-r1):

ollama pull qwen2.5-coder:32b
python -m argo.cli pipeline --runner codex --codex-oss --codex-local-provider ollama \
  --codex-model qwen2.5-coder:32b --brief BRIEF.txt --repo PATH

Cost is ~$0 for local models. Caveat: capability, not plumbing. Some stages are format-strict — recon must emit prompts carrying the RoE/prohibited-techniques anchors verbatim, audit must emit schema-valid JSON. A capable coder model clears the bar; very small (~7B) models may not — exactly what the Benchmarks & Costs harness measures.

Cost note: Claude Code returns real total_cost_usd per call; Codex reports tokens, not dollars, so Argo estimates USD from a price table (unknown/OSS/local models estimate to $0). The consequence: with Codex, the hard mid-session budget kill is unavailable — the per-run budget abort between stages still applies, on the estimated cost.

Resilience — multi-account & multi-backend fallback

Backends and accounts chain transparently: when one hits a session/rate limit (429), the same call is retried on the next (a per-run circuit breaker disables the walled one; a non-retryable error propagates immediately). Since limits are per-account, two logged-in Claude accounts double your capacity before falling through to Codex:

python -m argo.cli pipeline --repo <url> --calibration \
  --claude-accounts ~/.claude,~/.claude-b --fallback codex
# set up the 2nd account once:  CLAUDE_CONFIG_DIR=~/.claude-b claude login

Codex multi-account works the same way (--codex-accounts ~/.codex,~/.codex-b, via CODEX_HOME). So a long Opus run that used to wall on the Claude session limit mid-validate now self-heals to the next account/backend instead of degrading or failing. When a session-limit error carries a human-readable reset time, it's pulled out into the error message and the run's log, so you know exactly when it's safe to retry.

Failure-kind classification drives the right response, not one-size-fits-all retry

Every RunnerError carries a failure_kind, classified from the real error text against confirmed- live signatures rather than treating every failure the same way:

Kind Real confirmed signature Response
moderation_flagged Codex: "flagged for possible cybersecurity risk". Claude: "...safeguards flagged this message..." same-backend retry with a neutral-register prompt variant (below), short delay
credits_exhausted Codex: "out of credits" 30-minute cooldown on that backend — retrying every few minutes against a dead account is pointless but harmless; a longer bench lets the run spend that time elsewhere
rate_limited a 429 / session-limit response falls through to the next backend/account in the chain immediately
unknown_retryable anything else recoverable (timeout, empty output, transient infra) default cooldown, then retried

A same-backend retry after a moderation_flagged or credits_exhausted classification waits first — back-to-back identical retries of a flagged prompt reliably got flagged again every time, a short spaced-out delay measurably didn't. A genuinely different backend in the fallback chain shares no such cooldown and fires immediately.

Neutral-register retry — a same-backend recovery, not just cross-backend fallback

A moderation flag is a different problem from a rate limit: the same prompt, resent unchanged, tends to get flagged again. On a moderation_flagged failure, AgentRunner.run() retries once, on the same backend, with a caller-supplied neutral-register variant of the prompt — reworded narrative framing (e.g. "independently determine whether the report is correct" instead of "skeptical triager whose job is to reject... prove this one is wrong"; "an untrusted caller" instead of "an attacker"), same technical content, same requested output, same PROHIBITED TECHNIQUES block byte-for-byte. This is a reactive retry, not a preemptive rewrite: the normal prompt goes out by default everywhere, and the neutral variant is only ever generated and used after an actual flag. Covers validate, deep_verify, audit, and the ASan PoC harness-authoring session — each stage supplies its own neutral variant matching how it already builds its own prompt.

This sits underneath the cross-backend FallbackRunner above, not instead of it: if the neutral retry also fails, the failure still propagates normally into the fallback chain. Three independent resilience layers — prompt variant, backend, whole stage (below) — with no interaction risk, since each only ever sees the layer below it's final outcome.

Orchestrator-level stage auto-retry

Beyond a single call's retry/fallback, the orchestrator itself retries a whole stage in place (bounded, a couple of attempts) when a failure is retryable and not credits_exhausted — either after a short pause, or honoring a specific reset-time hint if the backend provided one, capped so an unattended run doesn't silently hang for hours waiting one out. A stopped run is never silently lost: every stage write is atomic (temp file + replace), and argo resume <run_id> picks a run back up from exactly where it stopped — printed automatically on any interruption, including Ctrl+C.

Verify your backend

python -m argo.cli pipeline --smoke                                              # Claude
python -m argo.cli pipeline --smoke --runner codex                               # Codex/OpenAI
python -m argo.cli pipeline --smoke --runner codex --codex-oss --codex-local-provider ollama

Model landscape (why the backend is swappable)

Argo deliberately keeps the model behind the AgentRunner interface, because detection quality tracks the model, and the frontier is moving fast toward security specifically. As security-specialized models become accessible, the swappable backend means Argo gets better by pointing at a stronger engine — no pipeline changes needed.

Related

  • Architecture — the AgentRunner abstraction and per-stage model defaults.
  • Benchmarks & Costs — where cross-model comparisons get measured.
  • ASan PoC Generation — a concrete stage whose harness-authoring sessions rely on the moderation-flag classification and neutral-register retry above.

Clone this wiki locally