Skip to main content
The model layer churns weekly and the harness layer churns quarterly. A workflow written against one vendor’s agent inherits every one of that vendor’s failure modes: the 5-hour Claude subscription window closes mid-run and the whole pipeline stalls; a Codex outage takes down a review loop that never needed GPT specifically; a price change on the frontier model makes a hundred cheap classification tasks cost like a hundred hard ones. None of those are workflow bugs. They are binding bugs — the orchestration was coupled to a single provider. Smithers treats the agent as a value you pass to a Task, not an assumption baked into the runtime. Every harness adapter — ClaudeCodeAgent, CodexAgent, CursorAgent, GeminiAgent, PiAgent, HermesAgent, OpenClawAgent, OpenCodeAgent, KimiAgent, AmpAgent, and more — satisfies the same AgentLike contract, and so do in-process SDK agents (AnthropicAgent, OpenAIAgent) built on the Vercel AI SDK. A single workflow can plan with a subscription-billed Claude seat, implement with Codex on an API key, and review with a cheap Gemini model, and the engine fails over between them when one hits a quota wall. This page is about running agents inside workflows. The other direction — wiring Smithers into the coding agent you drive by hand so it can start and steer runs — is covered in Agent Support.

How the industry solves it

Model portability is a solved problem at the API layer and an unsolved one at the harness layer. The gateways below normalize chat/completions; none of them normalize a full coding agent with its own tools, permissions, and sessions. Closed agent platforms take the opposite bet. Claude Code’s Dynamic Workflows fans out subagents — all Claude, inside Anthropic’s runtime. Devin is a hosted engineer you cannot point at your own model. The OpenAI Agents SDK is open code but its sessions, tracing, and handoffs assume OpenAI’s Responses API. Each is excellent inside its walls; none lets you route a step to a competitor when quota runs out. The longer argument is in Durable, open orchestration. A gateway’s failover is per-request. Smithers’ failover is per-attempt inside a durable run: which chain rungs are quota-blocked, and when each provider resets, is state that survives a process restart.

How Smithers does it

One contract, many harnesses

Everything that can execute a Task implements AgentLike (packages/agents/src/AgentLike.ts): a generate() call, an optional preflight() (deterministic startup checks — a rejected preflight fails fast without burning retries), and an optional capabilities registry describing the harness’s tool surface, MCP bootstrap mode, and skill support. CLI harnesses extend BaseCliAgent and spawn the vendor binary as a child process. SDK agents run in-process on the Vercel AI SDK — AnthropicAgent imports @ai-sdk/anthropic directly, and resolveSdkModel accepts either a model-id string or a raw AI SDK LanguageModel instance, so anything the AI SDK supports (including an OpenRouter or LiteLLM endpoint via a custom provider) drops in.

Per-Task agent, model, and fallback chain

The agent prop takes one agent or an ordered chain; fallbackAgent is sugar for a two-rung chain:
Retries walk the chain in order. For explicit cheap-model-first escalation with a predicate (escalateIf: (r) => r.confidence < 0.7) and an optional human fallback, use <EscalationChain>; smithers upgrade uses the same pattern internally — a cheap agent attempts the upgrade and a smart agent is engaged only on failure.

Quota walls are transient, not failures

The engine (packages/engine/src/engine.js) treats AGENT_QUOTA_EXCEEDED specially:
  • A quota-limited attempt never consumes the retry budget — the retry happens as if the attempt never occurred.
  • A rate limit is a property of the provider, not the task, so the next attempt fails over to the next non-blocked rung in the agent chain rather than hammering the blocked one. The blocked-rung set and each provider’s reported reset time are persisted in attempt metadata, so failover survives a restart.
  • Only when every rung is blocked does the run park in waiting-quota — with the earliest reset among the blocked providers, so it wakes as soon as any of them frees up. The gateway’s timer/quota sweep resumes it automatically.
  • Chain rungs disabled for repeated genuine failures exclude quota-failed attempts from that count — otherwise a Codex quota wall would permanently pin every task to its Claude fallback even after the quota reset.

Seats: subscriptions and API keys

smithers agents add registers an account — interactively or via flags — for claude-code, codex, antigravity, kimi, anthropic-api, openai-api, or gemini-api:
Subscription seats are a config directory, not a key: ClaudeCodeAgent clears ANTHROPIC_API_KEY so the CLI bills your Claude Pro/Max plan from credentials at CLAUDE_CONFIG_DIR/.credentials.json; CodexAgent mirrors this with CODEX_HOME/auth.json. Registering multiple labels for one provider gives you a seat pool you wire into distinct agent instances (one configDir each) and place on chain rungs — the quota failover above then rotates work off exhausted seats. Setting apiKey flips the same adapter to pay-per-token API billing, which is what makes cold sandboxes viable (see Sandboxes). smithers agents add also regenerates a previously generated .smithers/agents.ts to reflect the registry; hand-edited files are left alone.

Guarantees and limits

Guaranteed: one AgentLike contract across every adapter; per-Task chains with restart-surviving quota failover; quota attempts exempt from retry budgets; runs park and self-resume on provider reset; structured output enforced by Zod regardless of harness (prompt-injected schema for CLI agents, native structured output where supportsNativeStructuredOutput is set). Not guaranteed — what does not port cleanly:
  • Tool surfaces differ. Each harness ships its own built-ins and its own MCP bootstrap (project-config vs. CLI-flag vs. none). The per-agent capability registry (smithers agents capabilities, validated by smithers agents doctor) makes the differences inspectable, not invisible.
  • Permission models differ. Claude Code’s allowlists, Codex’s sandbox modes, and Cursor’s approval flows are not translated between each other. A prompt tuned to one harness’s permission prompts can behave differently on its fallback.
  • Context windows and prompt caching differ. A prompt near one model’s window may truncate on its fallback, and provider-side prompt-cache economics do not transfer — a fallover attempt is a cold cache.
  • Subscription ToS are the vendor’s. Smithers routes work across the seats you registered; whether pooled or automated use fits your Claude/ChatGPT plan terms is between you and the vendor.
  • Failover is retry-scoped. The chain advances on failure or quota, not on latency or cost mid-attempt; there is no request-level load balancer. If you want weighted balancing across API deployments, put LiteLLM or Portkey behind an SDK agent and let Smithers own the run-level state.

Operating it

smithers status shows which engines and models a run actually used; inspect --pool tallies attempts per engine/model, which is how you audit that the cheap model is really taking the bulk of the work.

See also