Skip to content

idea: per-peer fallback-model chain for do/ask delegate spawns — inspired by Claude Code's native --fallback-model #844

Description

@github-actions

Research brief

Landscape topic for this run: agent_mesh roadmap & comparable OSS agent
frameworks
(OpenHands, SWE-agent, Aider, MetaGPT/ChatDev, Renovate,
claude-code-action). Inspired by Claude Code's own native fallback-model
chain
(--fallback-model sonnet,haiku / fallbackModel settings array;
existed headless-only, extended to interactive sessions in v2.1.166, shipped
June 6 2026).

Gap

Every delegated worker in the mesh runs a single pinned model (per-peer
static selection landed in #457; per-peer extended-thinking effort in #530,
wired via --effort in src/delegate-invocation.js:109). If that peer's
model returns a non-retryable server error (529 overload / transient
unavailability) mid-spawn, the whole delegation surfaces as status:error
to the caller — even though Claude Code itself already ships a resilience
feature for exactly this: an ordered fallback chain the CLI switches into
within the same request, for non-retryable server errors only (auth,
billing, rate-limit and request-size errors are explicitly excluded from
triggering a switch, so this cannot mask a real caller-facing failure class).

Proposed direction

Thread a new optional per-peer registry.json field, fallback_model
(string or short array, e.g. "sonnet,haiku"), through the exact same path
already used for thinking_effort (#530's precedent):

  1. src/a2a/registry.js normalizePeer passes it through (validated:
    1–3 aliases after dedup, matching the CLI's own documented cap).
  2. src/a2a/peer-bridge.js sets agentmesh/fallback_model metadata on
    cross-peer SendMessage calls; src/a2a/stdio-server.js reads it back
    and threads it into runAgent/delegateTask.
  3. src/delegate-invocation.js buildClaudeInvocation appends
    --fallback-model <chain> iff present — identical append-iff-present
    shape as the existing --effort line.

This is intentionally thin plumbing (a registry-configured CLI flag), not
new retry logic invented by the mesh.

Why this does NOT touch the "never auto-retry" invariant

CLAUDE.md's invariant ("Failure is data, not an exception... The mesh never
auto-retries or auto-fails-over") governs delegation-level retries — the
mesh does not re-invoke delegateTask after a done/timeout/refused/
error result. This proposal operates strictly inside one claude -p
spawn
: the model switch happens before that spawn produces any result at
all, entirely inside the claude binary's own request handling. If every
model in the configured chain is exhausted, the spawn still fails and the
mesh still returns a structured error exactly as today — the invariant is
unchanged, only the window for a transient 529 to become a full delegation
failure narrows.

Open questions flagged for human scoping (not yet resolved by this brief)

  • Need a version-gated smoke test of --fallback-model combined with
    -p --output-format json (headless mode) specifically — sources describe
    the flag mainly via interactive/general docs.
  • Whether parseResultEnvelope's usage/cost fields correctly attribute to
    whichever model actually serviced the request after a fallback switch, or
    silently misreport against the primary model — affects run-record cost
    accuracy (src/delegate.js cost-capture, spec
    2026-06-13-delegate-cost-capture-design.md).

Full draft spec

Written to
docs/superpowers/specs/2026-07-23-idea-per-peer-fallback-model-chain-for-do-mode-and-ask-mode-design.md
during this run, but could not be committed/pushed — this session's
GitHub token is the same App-installation credential #771 already tracks as
403'ing on Contents writes (git push / Contents API), confirmed again this
run (gh api user also 403s: "Resource not accessible by integration").
Pasting the spec content below so it isn't lost to the ephemeral checkout:

Full draft spec (click to expand)
# Per-peer fallback-model chain for delegated `claude -p` spawns

Status: **draft** — proposal only, subject to human approval gate (§5.3).
Inspired by: Claude Code's native `--fallback-model` chain (shipped as CLI
flag + `fallbackModel` settings array; extended to interactive sessions in
v2.1.166, June 2026) and the general "resilient model chain" pattern used
by agent frameworks under overload conditions.

## Goal

When a peer's primary model returns a non-retryable server error (529
overload, transient unavailability), let that single delegated `claude -p`
spawn degrade to a configured fallback model **within the same invocation**,
instead of the whole delegation surfacing as `status:error` to the caller.

## Non-goals

- Not a change to mesh-level retry policy. The mesh still never re-invokes
  `delegateTask` after a `done`/`timeout`/`refused`/`error` result — that
  invariant (CLAUDE.md "Failure is data, not an exception") is unchanged.
  This proposal is scoped **strictly inside one spawn**: it is the `claude`
  CLI's own request-level resilience (model switch on 529/unavailable),
  which already exists as a first-party CLI feature — the mesh would only
  be *threading a registry-configured value* into the spawn args, the same
  shape as issue #530's `--effort` wiring, not inventing new retry logic.
- Not a change to which errors are eligible: per Claude Code's own docs,
  auth/billing/rate-limit/request-size/transport errors never trigger a
  model switch — only non-retryable server-side overload does. The mesh
  does not need to (and should not) reimplement that classification; it is
  entirely inside the `claude` binary.
- Not a substitute for #568 (daemon jobs skipping gracefully on
  org-wide quota exhaustion) — that is a different layer (daemon builtin
  tick vs. a single delegate spawn) and orthogonal.

## Design

1. **Registry field**: extend `registry.json` peer entries with an optional
   `fallback_model` field, string or array (e.g. `"fallback_model":
   "sonnet,haiku"` or `["sonnet","haiku"]`), mirroring the precedent set by
   `thinking_effort` (#530). `src/a2a/registry.js` `normalizePeer` gains a
   pass-through: `if (peer.fallback_model !== undefined) normalized.fallback_model = ...`
   (validated: 1-3 model aliases after dedup, per the CLI's own documented
   cap — extra entries are ignored upstream anyway, but validating early
   gives a clear `bad_input` instead of a silent no-op).
2. **Cross-peer threading**: same shape as `thinking_effort` — the peer
   bridge (`src/a2a/peer-bridge.js`) reads `managed.registry.peers[peer]?.fallback_model`
   and sets `message.metadata['agentmesh/fallback_model']`; `stdio-server.js`
   reads it back off `validation.value.metadata` and passes it through to
   `runAgent`/`delegateTask`.
3. **Spawn args**: `buildClaudeInvocation` (`src/delegate-invocation.js`)
   gains a `fallbackModel` param; iff present, appends
   `--fallback-model <comma-joined-chain>` — identical append-iff-present
   shape as the existing `--effort` line (delegate-invocation.js:109).
4. **Observability**: `parseResultEnvelope`'s existing `usage` block capture
   already reports the model that actually serviced the request in the
   terminal JSON envelope (needs verification during implementation — if
   the envelope's `model` field doesn't reflect a mid-run switch, that is a
   pre-existing Claude Code CLI limitation to note, not something to work
   around). No new metrics field is proposed here; this is intentionally
   thin plumbing, not a new cost-attribution feature.

## Invariants upheld

- Anti-spoof: the model-facing `delegate_task` tool surface is unchanged —
  `fallback_model` is a **registry-set, operator-owned** value threaded the
  same way `thinking_effort` already is, never a caller-suppliable tool arg.
- Failure is data: if all models in the chain are unavailable, the spawn
  still fails as today and the mesh still returns a structured `error`
  result — this proposal only narrows the window in which a transient
  529 turns into a full delegation failure.
- No change to path-guard, recursion guard, or write-tool allowlists.

## Risks / open questions (for human scoping before this becomes buildable)

- Need to confirm current `claude` CLI version behavior for `--fallback-model`
  combined with `-p --output-format json` (headless mode) specifically —
  the July 2026 search sources describe the flag primarily via interactive
  and general CLI docs; headless-mode envelope interaction needs a quick
  version-gated smoke test before implementation.
- Whether cost/usage in the JSON envelope correctly attributes to whichever
  model actually serviced the request, or reports the primary model
  regardless — affects whether `total_cost_usd` in the run record stays
  accurate under a fallback event.

## Sources

- Claude Code fallback model chain — https://www.digitalapplied.com/blog/claude-code-safe-mode-fallback-models-production-resilience-guide
- Claude Code fallbackModel overload fix — https://www.aiforanything.io/blog/claude-code-fallback-model-overload-fix-2026
- Claude Code model configuration docs — https://code.claude.com/docs/en/model-config

Dedup confirmation

Checked gh issue list --state open --limit 100 (and a scan of recent
generated:analyst closed issues) before writing this. No existing open or
closed issue mentions fallback, fallback_model, fallback-model, or a
resilience chain for delegate spawns. Closest neighbors, confirmed distinct:

Also checked the other five comparables named in this run's brief
(OpenHands, SWE-agent, Aider, MetaGPT/ChatDev, Renovate) against the current
backlog — every other angle surfaced during research maps onto an already-open
or already-closed issue:

Only the fallback-model chain is genuinely new today; filing just this one
per "quality over volume."

Sources

Metadata

Metadata

Assignees

No one assigned

    Labels

    blockedBlocked on a dependencygenerated:analystAnalyst agent–generated improvement idea (analyst-daily-review)needs-humanspec:draftSpec being drafted

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions