Harness Configuration
Configure the AI provider (Claude Code, Codex, opencode, pi-mono, Devin, Claude Managed Agents, dsh, Amp, Cursor, or Grok) that powers your agents
Agent Swarm uses a harness abstraction to decouple task execution from the underlying AI provider. Each worker runs one harness — the harness spawns sessions, manages credentials, and normalizes events so the rest of the system doesn't care which provider is underneath.
Supported Providers
| Provider | HARNESS_PROVIDER | Description |
|---|---|---|
| Claude Code | claude (default) | Anthropic's Claude Code CLI. Recommended for most use cases |
| Codex | codex | OpenAI Codex CLI with API-key or ChatGPT OAuth authentication |
| opencode | opencode | OpenCode coding agent powered by OpenRouter. Uses Qwen Coder Flash by default; supports any OpenRouter, Anthropic, or OpenAI model |
| pi-mono | pi | Open-source coding agent by @badlogic. Supports multiple model backends |
| Devin | devin | Cognition's Devin via the /sessions API — session executes in Devin's managed cloud, ACU-based cost tracking |
| Claude Managed Agents | claude-managed | Anthropic's managed cloud sandbox — sessions execute outside the worker. Requires one-time setup CLI to create the Anthropic-side Agent + Environment |
| ACP (Agent Client Protocol) | acp | Any Agent Client Protocol agent, spawned as a subprocess and driven over stdio. No swarm-side model-provider credential — the target owns its own model auth. It receives a session-scoped aseph_ token as the swarm MCP bearer, granting that agent's swarm MCP access for up to 24 hours; point it only at a binary you trust |
| DeepSeek Harness | dsh | DeepSeek's @deepseek-ai/dsh CLI. A model's openrouter/ prefix selects OpenRouter; bare IDs select DeepSeek's API |
| Amp | amp | Sourcegraph's Amp CLI in execute mode. Amp selects the model for each mode on its service |
| Cursor | cursor | Cursor's local Agent SDK runtime with Cursor-hosted models |
| Grok | grok | xAI's Grok CLI, run as an ACP server with a fresh session home |
How It Works
The HARNESS_PROVIDER environment variable selects which provider adapter is used. The runner creates the adapter at startup:
HARNESS_PROVIDER=claude → ClaudeAdapter → spawns `claude` CLI process
HARNESS_PROVIDER=codex → CodexAdapter → starts an isolated `codex app-server`
HARNESS_PROVIDER=opencode → OpencodeAdapter → spawns `opencode` CLI process
HARNESS_PROVIDER=pi → PiMonoAdapter → creates in-process pi-mono session
HARNESS_PROVIDER=devin → DevinAdapter → POSTs /sessions, polls events
(session executes in Devin cloud)
HARNESS_PROVIDER=claude-managed → ClaudeManagedAdapter → opens SSE stream against
Anthropic's managed sandbox
(session executes server-side)
HARNESS_PROVIDER=acp → ACPAdapter → spawns `ACP_TARGET_COMMAND`,
speaks ndjson ACP over stdio
HARNESS_PROVIDER=dsh → DshAdapter → spawns `dsh` in headless JSON mode
HARNESS_PROVIDER=amp → AmpAdapter → spawns `amp` in execute mode
HARNESS_PROVIDER=cursor → CursorAdapter → creates a local Cursor SDK agent
HARNESS_PROVIDER=grok → GrokAdapter → starts `grok agent` through ACPBoth adapters implement the same ProviderAdapter interface, producing normalized ProviderEvent streams (session init, tool calls, cost data, context usage, etc.) that the runner consumes identically.
For local coding harnesses, Agent Swarm now wires in the context-mode MCP server by default for Claude Code, Codex, and opencode. That gives those providers the same ctx_* compressed-search / fetch-and-index tools out of the box. Hook guidance now nudges agents toward ctx_execute / ctx_batch_execute after every 3 qualifying external-MCP calls by default (override with CONTEXT_MODE_EXTERNAL_MCP_NUDGE_EVERY). Set CONTEXT_MODE_DISABLED=true to opt a worker out: the adapters drop the MCP server and disable the installed context-mode plugin for the session, so its hooks stop too.
When the runner refreshes an already-cloned repo for a new task, dirty working trees are auto-stashed instead of forcing a skipped pull. Any resulting swarm-autostash refs are appended to the composed prompt so the agent can restore them intentionally with git stash apply <ref> or git stash pop <ref> when that work matters to the current task.
Claude Code (Default)
Claude Code is the default harness. It supports two transports: CLI, the default, and the optional Claude Agent SDK. Both transports start the installed Claude Code executable as a child process.
Select Claude transport beneath Harness on the agent details page. Choose CLI, SDK, or Inherit to use the configured default. Saving affects future sessions. An active session retains its transport. Selecting another harness hides this control and preserves the saved Claude setting.
CLAUDE_TRANSPORT=cli|sdk uses the existing configuration precedence: repository, agent, global, deployment environment, then cli.
Repository settings apply when the task supplies a registered repository.
Set the global default under Settings > Configuration.
The SDK supports the same OAuth and API-key credential selection as CLI, including credential pools.
When both credential types exist, Swarm preserves both selected values and prefers OAuth for validation and tracking.
Claude Code determines which credential it uses.
SDK sessions retain Swarm context preambles and never use native session resume.
They share settings, project instructions, plugins, hooks, strict MCP configuration, telemetry, and summary preparation with CLI sessions.
CLAUDE_BINARY selects the executable and any wrapper prefix.
A custom wrapper must support the SDK protocol. An executable override alone does not establish compatibility.
The SDK preserves custom argument order and executable prefixes.
It rejects arguments that replace protocol framing, MCP isolation, the appended Swarm prompt, or native continuation behavior.
CLAUDE_QUEUE_STEERING=0 disables queued input for either transport.
The worker rejects SDK selection when a bridge is effective, including legacy bridge binaries.
Authentication Methods
Claude Code supports two authentication methods, checked in priority order:
| Method | Env Var | How to Get It |
|---|---|---|
| OAuth token (recommended) | CLAUDE_CODE_OAUTH_TOKEN | Run claude setup-token in your terminal |
| API key | ANTHROPIC_API_KEY | From console.anthropic.com |
OAuth is preferred because it uses your Claude Code subscription (Pro/Max/Team) with its included usage, rather than consuming pay-per-token API credits.
Getting an OAuth Token
# Interactive — opens browser for OAuth flow
claude setup-token
# The output contains a token like: sk-ant-oat01-...
# Copy this value into your .env fileOr use the onboard wizard which runs this automatically:
bunx @desplega.ai/agent-swarm onboardEnvironment Variables
| Variable | Required | Default | Description |
|---|---|---|---|
CLAUDE_CODE_OAUTH_TOKEN | Yes* | — | OAuth token from claude setup-token. Supports multi-credential pools |
ANTHROPIC_API_KEY | Alt* | — | Anthropic API key (alternative to OAuth). Also supports multi-credential pools |
SWARM_USE_CLAUDE_BRIDGE | No | false | Enables the bundled @desplega.ai/claude-bridge path for interactive Claude sessions. Requires CLAUDE_CODE_OAUTH_TOKEN; if only ANTHROPIC_API_KEY is available, the worker falls back to stock claude |
CLAUDE_BINARY | No | claude | Path to the Claude CLI binary (if not in $PATH) |
CLAUDE_TRANSPORT | No | cli | Claude execution transport: cli or sdk. Applies to future sessions |
CLAUDE_QUEUE_STEERING | No | automatic probe | Controls raw Claude queue steering. Unset requires stock Claude Code >=2.1.205; 0/false/off/no keeps the legacy -p path; 1/true/on/yes forces stream-json input. |
* One of CLAUDE_CODE_OAUTH_TOKEN or ANTHROPIC_API_KEY is required.
Model Selection
The Claude adapter passes the model string directly to the claude CLI via --model. Common values:
| Model | Description |
|---|---|
opus | Claude Opus 5 (highest capability; 1M context) |
sonnet | Claude Sonnet (balanced) |
haiku | Claude Haiku (fastest) |
fable | Claude Fable 5.1 (1M-context premium shortname) |
mythos | Claude Mythos 5.1 (invite-only premium shortname) |
Models can be set per-task via the API or per-agent via the agent profile. The opus shortname resolves to claude-opus-5; fable and mythos resolve to their 5.1 model IDs. These shortnames also propagate through MCP task-creation surfaces (send-task, task-action, and schedules). See the Anthropic models overview for all available model IDs.
If you want provider-agnostic intent instead of a concrete model string, use modelTier on tasks, schedules, or workflow agent-task nodes. The tier values are smol, regular, smart, and ultra; the claiming worker resolves them to concrete models for its own harness/provider, and local env overrides (MODEL_TIER_MAP, MODEL_TIER_<TIER>) can adjust the mapping without changing the task payload.
Reasoning / Effort
Set a per-agent reasoning/effort level with PATCH /api/agents/{id}/runtime (reasoning_effort: off | low | medium | high | xhigh), persisted as the REASONING_EFFORT_OVERRIDE swarm config key. Claude translates this into the CLAUDE_CODE_EFFORT_LEVEL env var — no CLI flag is used (--effort is buggy in -p mode). off on a legacy model that still exposes a numeric thinking budget instead sets MAX_THINKING_TOKENS=0.
Precedence: if an operator's additionalArgs includes --effort, the CLI flag wins over CLAUDE_CODE_EFFORT_LEVEL — the standard "additionalArgs is an escape hatch" behavior, not special-cased for effort. Not every model supports every level (e.g. some Opus variants don't support off); the dashboard's effort selector greys out unsupported levels per selected model, and the API 400s an unsupported (model, level) combo.
Codex
Codex runs through codex app-server and supports direct OpenAI API keys and ChatGPT OAuth.
Each task runs inside a throwaway codex-session-runner subprocess. That process starts one fresh codex app-server process.
The worker sends session configuration and control messages over stdin. The runner returns line-delimited events and results over stdout. Both processes exit with the task.
Authentication Methods
Codex checks credentials in this order:
| Method | Source | Notes |
|---|---|---|
| OpenAI API key | OPENAI_API_KEY | Standard API billing |
| Auth file | ~/.codex/auth.json | Native Codex CLI auth file |
| ChatGPT OAuth via config store | codex_oauth_<slot> | Restored automatically at worker boot |
For ChatGPT OAuth setup, see Provider Auth: Codex OAuth.
Environment Variables
| Variable | Required | Default | Description |
|---|---|---|---|
HARNESS_PROVIDER | Yes | — | Must be set to codex |
OPENAI_API_KEY | No | — | Optional when using direct OpenAI API access |
API_KEY | Yes | — | Swarm API key used to fetch codex_oauth from the config store |
MCP_BASE_URL | Yes | http://host.docker.internal:3013 | Swarm API URL reachable by the worker |
AGENT_ID | Recommended | Auto-generated | Keep stable across restarts for task resume |
Swarm Config Keys (Codex)
The following keys are stored in the swarm config store (via PUT /api/config or the set-config MCP tool) rather than as environment variables:
| Key | Default | Description |
|---|---|---|
CODEX_CREDITS_EXHAUSTED_COOLDOWN_MS | 7200000 (2 h) | How long a Codex OAuth slot is held out of the pool after a workspace-credits-exhausted error. Must be a positive integer of milliseconds; clamped to [5 min, 7 days]. See Workspace credits exhausted. |
Model Selection
The default model baked into the worker image is gpt-5.4. You can override it with MODEL_OVERRIDE if needed.
Reasoning / Effort
Set the per-agent reasoning/effort level (off | low | medium | high | xhigh) via PATCH /api/agents/{id}/runtime — Codex translates it into the model_reasoning_effort config field, with off mapping to 'none'. show_raw_agent_reasoning stays pinned false regardless of the effort level: setting high costs reasoning tokens (visible in reasoning_output_tokens cost telemetry) but produces no visible reasoning trace in the dashboard.
*-codex (non-max) models reject xhigh; *-codex-max models accept it. The API 400s an unsupported (model, level) combo before it's persisted.
Codex Specifics
- Cross-keyType failover: when Codex workers have both
OPENAI_API_KEYslots andcodex_oauth_*slots available, the runner now fails over across key types instead of retrying a known-exhausted pool. A successful task also clears stale rate-limit state for the credential that proved healthy. - Per-task subprocess isolation: each task gets a fresh
codex-session-runnerandcodex app-serverprocess. This keeps worker memory stable on hot workers. - Small spawn argv: large system prompts are staged to a temp file and passed via
--append-system-prompt-file, avoiding LinuxMAX_ARG_STRLEN/E2BIGfailures on prompt-heavy repos. - Actionable failure reporting: subprocess startup / parse failures are emitted back to the parent as structured errors, and non-TTY runs no longer leak cursor escape sequences into the JSON pipe.
Opencode
opencode is a terminal-based AI coding agent that ships its own session loop and MCP client. The swarm spawns the opencode CLI as a subprocess and attaches the agent-swarm plugin for heartbeat, cancellation, identity sync, and compaction hooks.
When to Use opencode
- You want access to OpenRouter's full model catalog (100+ models) without writing provider glue.
- You prefer a lightweight, quickly-iterating open-source CLI over the heavier Claude Code toolchain.
- You need cost-effective throughput — the default
openrouter/qwen/qwen3-coder-flashmodel is fast and inexpensive.
Authentication Methods
opencode checks credentials in this priority order:
| Method | Env Var | Notes |
|---|---|---|
| OpenRouter API key (recommended) | OPENROUTER_API_KEY | Access 100+ models via openrouter.ai |
| Anthropic API key | ANTHROPIC_API_KEY | Direct Anthropic API billing |
| OpenAI API key | OPENAI_API_KEY | Direct OpenAI API billing |
| NaN API key | NAN_API_KEY (alias HELMCODE_API_NAN) | Flat-rate NaN models. See NaN and Helmcode |
| Helmcode API key | HELMCODE_API_KEY | Flat-rate Helmcode models. See NaN and Helmcode |
| Auth file | ~/.local/share/opencode/auth.json | Native opencode CLI auth file |
At least one credential source is required. The Docker entrypoint validates this on startup.
Environment Variables
| Variable | Required | Default | Description |
|---|---|---|---|
HARNESS_PROVIDER | Yes | — | Must be set to opencode |
OPENROUTER_API_KEY | One of* | — | OpenRouter API key (primary — gives access to all OpenRouter models) |
OPENROUTER_BASE_URL | No | https://openrouter.ai/api/v1 | Route OpenRouter provider and session-summary traffic through an OpenAI-compatible gateway |
ANTHROPIC_API_KEY | One of* | — | Anthropic API key for Claude models |
OPENAI_API_KEY | One of* | — | OpenAI API key for GPT models |
NAN_API_KEY | One of* | — | NaN API key for nan/<id> models. HELMCODE_API_NAN is accepted as an alias; NAN_API_KEY wins when both are set. Comma-separated values form a pool |
HELMCODE_API_KEY | One of* | — | Helmcode API key for helmcode/<id> models. Comma-separated values form a pool |
OPENCODE_SDK_PATH | No | Global npm root | Absolute path to @opencode/sdk/dist/index.js, when it is not under /usr/lib/node_modules or /usr/local/lib/node_modules |
* At least one credential source is required (OPENROUTER_API_KEY, ANTHROPIC_API_KEY, OPENAI_API_KEY, NAN_API_KEY, HELMCODE_API_KEY, or ~/.local/share/opencode/auth.json).
Model Selection
The default model is openrouter/qwen/qwen3-coder-flash. Set MODEL_OVERRIDE to use a different model:
| Format | Example | Notes |
|---|---|---|
| OpenRouter | openrouter/qwen/qwen3-coder-flash | Default. See OpenRouter model catalog |
| Anthropic | anthropic/claude-sonnet-4-6 | Requires ANTHROPIC_API_KEY |
| OpenAI | openai/gpt-4o | Requires OPENAI_API_KEY |
| NaN | nan/glm5.3-flash | Requires NAN_API_KEY (or HELMCODE_API_NAN) |
| Helmcode | helmcode/deepseek-v4-flash | Requires HELMCODE_API_KEY |
Reasoning / Effort
Set the per-agent reasoning/effort level (off | low | medium | high | xhigh | max) via PATCH /api/agents/{id}/runtime. max is offered only when the model declares it and the harness passes it through (codex, cursor, opencode and pi). opencode has no single reasoning knob — the swarm translates the level into provider-keyed options in the per-task opencode config: anthropic/* models get thinking.budgetTokens (an internal numeric translation, not a user-facing knob), or effort for max, openrouter/* models get reasoning.effort, and OpenAI-compatible models (including opencode-go/*) get reasoningEffort. off omits reasoning keys entirely — opencode has no explicit off switch.
opencode Specifics
- In-process OpenCode 2: each task runs its own embedded
@opencode/sdkhost inside the worker process. There is noopencode servechild. Config is passed as a string, the session database is in memory, and nothing is written into the task's working directory. - Agent-swarm plugin:
src/providers/opencode-swarm-plugin.tsis passed to the host as a plugin object. It handles task cancellation, the polling limit, the activity heartbeat, lead concurrent context, and the compaction goal reminder. The adapter runs identity sync, memory auto-indexing, the session summary, and session close from the event stream. - MCP tool discovery: the swarm MCP endpoint is wired in automatically via the per-task config; installed MCP servers are also discovered and merged in. context-mode is added as a stdio MCP server unless
CONTEXT_MODE_DISABLED=true. Before the first prompt the adapter waits up to 30 seconds for the swarm server to connect and registerstore-progress; a refused, unauthorized, or tool-less server fails the task with the reason instead of running without swarm tools. - Session summaries do not read
auth.json: the end-of-session summary uses the shared internal-ai credentials, not OpenCode's~/.local/share/opencode/auth.json. A worker that authenticates OpenCode only through that file runs tasks normally but skips summaries (session_summary skipped (opencode)). To keep them, give the workerOPENROUTER_API_KEY,ANTHROPIC_API_KEYorOPENAI_API_KEY, Codex OAuth (see Provider Auth: Codex OAuth), orCLAUDE_CODE_OAUTH_TOKEN; the first one found wins, in that order.
pi-mono
pi-mono is an open-source coding agent that runs as a library (no external CLI process). It supports multiple LLM backends through a provider/model system. See the coding agent README for detailed configuration and usage.
Authentication
pi-mono supports several authentication methods depending on which model provider you use:
| Provider | Env Var | Description |
|---|---|---|
| Anthropic | ANTHROPIC_API_KEY | Direct Anthropic API access |
| OpenRouter | OPENROUTER_API_KEY | Access 100+ models via OpenRouter |
| OpenAI | OPENAI_API_KEY | Direct OpenAI API access |
GOOGLE_API_KEY or GEMINI_API_KEY | Direct Google AI API access (only honored when MODEL_OVERRIDE starts with google/) | |
| NaN | NAN_API_KEY (alias HELMCODE_API_NAN) | Flat-rate NaN models. See NaN and Helmcode |
| Helmcode | HELMCODE_API_KEY | Flat-rate Helmcode models. See NaN and Helmcode |
| Auth file | ~/.pi/agent/auth.json | Pre-configured auth file |
At least one of these must be available. The Docker entrypoint validates this on startup. When MODEL_OVERRIDE starts with a provider prefix, only the matching key is required; when it's unset, any one of ANTHROPIC_API_KEY / OPENROUTER_API_KEY / OPENAI_API_KEY suffices.
Environment Variables
| Variable | Required | Default | Description |
|---|---|---|---|
HARNESS_PROVIDER | Yes | — | Must be set to pi |
ANTHROPIC_API_KEY | One of* | — | Anthropic API key for Claude models |
OPENROUTER_API_KEY | One of* | — | OpenRouter API key for multi-provider access |
OPENROUTER_BASE_URL | No | https://openrouter.ai/api/v1 | Route OpenRouter model and session-summary traffic through an OpenAI-compatible gateway |
OPENAI_API_KEY | One of* | — | OpenAI API key for GPT models |
GOOGLE_API_KEY or GEMINI_API_KEY | When prefixed | — | Google AI API key — required only when MODEL_OVERRIDE uses a google/ prefix |
NAN_API_KEY | One of* | — | NaN API key for nan/<id> models. HELMCODE_API_NAN is accepted as an alias; NAN_API_KEY wins when both are set. Comma-separated values form a pool |
HELMCODE_API_KEY | One of* | — | Helmcode API key for helmcode/<id> models. Comma-separated values form a pool |
* At least one credential source is required (API key or ~/.pi/agent/auth.json).
OPENROUTER_BASE_URL is shared across the swarm's OpenRouter consumers. Set it on API and worker processes when a deployment must route harnesses, credential checks, internal summaries, and raw-LLM or validation workflow nodes through the same gateway. Unset or blank values keep the direct https://openrouter.ai/api/v1 default.
Do not pass CLAUDE_CODE_OAUTH_TOKEN when using HARNESS_PROVIDER=pi. If Claude credentials are present in the environment, the harness will attempt to use them instead of the configured pi-mono provider, causing misconfiguration. Only pass the credentials relevant to your selected provider (OPENROUTER_API_KEY or ANTHROPIC_API_KEY).
Model Selection
pi-mono resolves models using a provider/model-id format. Set the model via MODEL_OVERRIDE in your environment:
The prefix before the first / selects the provider and the matching credential — the rest is passed through as the model ID (so OpenRouter IDs that themselves contain / work fine).
| Format | Example | Required Credential |
|---|---|---|
| Shortname | opus, sonnet, haiku, fable | ANTHROPIC_API_KEY (maps to Anthropic Claude models) |
anthropic/<id> | anthropic/claude-sonnet-4-6 (Anthropic model IDs) | ANTHROPIC_API_KEY |
openrouter/<id> | openrouter/moonshotai/kimi-k2.5 (OpenRouter catalog) | OPENROUTER_API_KEY |
openai/<id> | openai/gpt-4o | OPENAI_API_KEY |
google/<id> | google/gemini-2.5-pro | GOOGLE_API_KEY or GEMINI_API_KEY |
nan/<id> | nan/mimo-v2.6-flash | NAN_API_KEY (or HELMCODE_API_NAN) |
helmcode/<id> | helmcode/glm5.3-flash | HELMCODE_API_KEY |
opencode-go/<id> | opencode-go/deepseek-v4.1-flash | OPENCODE_API_KEY |
models.dev can list opencode-go ids that pi-ai does not ship yet, and pi runs its default model for those. The worker reports the ids its pi-ai resolves (credStatus.opencodeGo), and the dashboard picker offers only those.
Reasoning / Effort
Set the per-agent reasoning/effort level (off | low | medium | high | xhigh | max) via PATCH /api/agents/{id}/runtime — pi-mono translates it directly into the thinkingLevel session option, a top-level sibling of model on CreateAgentSessionOptions. pi's native vocabulary already includes off and max, so no special-casing is needed there. max is offered only when the model declares it (e.g. opencode-go/deepseek-v4.1-flash), and pi-ai clamps any level to the model's own thinkingLevelMap.
pi-mono Specifics
- Runtime-scoped credentials: pi 0.81 uses a per-session
ModelRuntime. The adapter injects the resolved task credential into that runtime instead of mutating process-wide provider state, and refreshed OAuth credentials are persisted through pi's provider-owned auth store. - AGENTS.md symlink: pi-mono reads
AGENTS.mdfor project instructions (equivalent to Claude'sCLAUDE.md). The adapter automatically creates a symlinkAGENTS.md → CLAUDE.mdduring sessions so your existing project instructions work with both providers. - Lazy provider import: the worker only imports
@earendil-works/pi-coding-agentwhenHARNESS_PROVIDER=pi, so non-pi workers are no longer exposed to pi-mono's module-level side effects at boot. - MCP tool discovery: pi-mono discovers swarm MCP tools at session creation via HTTP and registers them as custom tools. This is handled automatically — no
.mcp.jsonneeded for the swarm connection (though installed MCP servers are also discovered). - Skills sync: The worker mirrors skills into
~/.claude/skills/,~/.pi/agent/skills/,~/.codex/skills/,~/.opencode/skills/, and~/.agents/skills/so local harnesses share the same skill inventory. - Per-task hot-reload: between tasks the worker polls a cheap
GET /api/agents/:id/skills/signature(hash) endpoint and only re-syncs the filesystem when the installed-skill set actually changes. Newly installed / uninstalled skills appear on the next task without restarting the worker, while unchanged sessions skip the work. Foreign skills (~/.claude/skills/<name>/SKILL.mdfiles not owned by the swarm) are preserved across re-syncs. Seesrc/utils/skills-refresh.ts.
NaN and Helmcode
NaN and Helmcode are OpenAI-compatible model providers billed at a flat rate per key. Their models run on the pi and opencode harnesses only, plus the ACP opencode target (HARNESS_PROVIDER=acp, ACP_TARGET=opencode). The claude and codex harnesses reject nan/* and helmcode/* models with the usual harness mismatch error.
| Provider | Env var | Model ids |
|---|---|---|
| NaN | NAN_API_KEY (alias HELMCODE_API_NAN) | Known to support tool calls: nan/deepseek-v4-flash, nan/glm5.3, nan/glm5.3-flash, nan/qwen3.6, nan/qwen3.8-flash, nan/gemma4, nan/mimo-v2.6-flash. Other nan/<id> catalog ids are accepted but untested |
| Helmcode | HELMCODE_API_KEY | helmcode/deepseek-v4-flash, helmcode/glm5.3, helmcode/glm5.3-flash, helmcode/qwen3.6, helmcode/gemma4 |
- Entering keys: use the NaN and Helmcode cards on the dashboard Integrations page, or set the env vars on the worker. A key saved on the dashboard needs no restart.
- Alias:
HELMCODE_API_NANis accepted for the NaN key. When bothNAN_API_KEYandHELMCODE_API_NANare set,NAN_API_KEYwins. - Base URL:
NAN_BASE_URLandHELMCODE_BASE_URLoverride the default API roots (https://api.nan.builders/v1,https://api.helmcode.com/v1), for example to go through a proxy. They are not secrets. Set them on the provider's Integrations card (under Advanced) or as env vars; the pi and opencode config blocks and the key check read them per task. - Catalog: Helmcode is not on models.dev. The swarm builds its catalog section from the five models in the provider registry (see Adding a provider). Helmcode's resold Claude, GPT and Gemini models are not in the catalog.
- Pricing: both providers are flat-rate per key. Sessions record $0 marginal cost with
costSource: "pricing-table". See Cost and context computation. latest:aliases do not cover these providers. Use concrete model ids.
Per-key limits
Both providers cap each key. One key shared across a fleet of workers hits these caps fast. Put several keys in a comma-separated pool (see pi and opencode API-key pools); each task gets one key from the pool.
| Provider | Requests per minute | Concurrency | Tokens per minute |
|---|---|---|---|
| NaN | 60 | 7 concurrent requests in total on the base plan, 10 on premium; 5 per model on qwen3.6, gemma4 and the mimo models | Not documented |
| Helmcode | 100 | 5 concurrent requests per model; 10 on the GLM family, DeepSeek V4 Flash and Qwen 3.8 Flash | 2M |
Default harness and model tiers
When HARNESS_PROVIDER is unset and a NaN or Helmcode key is the worker's only LLM credential, the worker defaults to pi.
The built-in pi and opencode tier defaults still point at openrouter/... models. On a swarm whose only LLM credential is a NaN or Helmcode key, a modelTier task resolves to an OpenRouter model and fails because OPENROUTER_API_KEY is missing. Set the global tier config keys (swarm_config, written with set-config or PUT /api/config) to models you can run, for example:
MODEL_TIER_PI_SMOL=nan/glm5.3-flash
MODEL_TIER_PI_REGULAR=nan/glm5.3-flash
MODEL_TIER_PI_SMART=nan/qwen3.6
MODEL_TIER_PI_ULTRA=nan/qwen3.6
MODEL_TIER_OPENCODE_SMOL=nan/glm5.3-flash
MODEL_TIER_OPENCODE_REGULAR=nan/glm5.3-flash
MODEL_TIER_OPENCODE_SMART=nan/qwen3.6
MODEL_TIER_OPENCODE_ULTRA=nan/qwen3.6A task with neither model nor modelTier runs the agent's MODEL_OVERRIDE, else the adapter default (an OpenRouter model). Set MODEL_OVERRIDE to a nan/ or helmcode/ model on those agents too. See Model tiers.
Adding a provider
NaN and Helmcode are entries in one registry: packages/model-catalog/src/openai-compatible-providers.ts (OPENAI_COMPATIBLE_PROVIDERS). The API server, the worker and the dashboard all derive from it. To add another OpenAI-compatible provider (for example an AI gateway), change two places:
-
The registry entry. Add one entry with these fields:
Field Meaning idModel prefix ( <id>/<model>), catalog section id and provider block iddisplayName,descriptionCard, picker and label text baseUrlDefault API root. <ID>_BASE_URL(the id in upper case) overrides itdocsUrlVendor docs envVar,aliasEnvVarsCanonical key var, and other names the runner copies into it keyNoteOptional extra sentence for the key field catalog"models.dev"when models.dev lists the provider (opencode then ships it built in), else"registry"(the catalog section is built frommodels, and opencode gets a custom provider block)npm@ai-sdk/openai-compatiblemodelsTool-calling models: id, name, context and output limits, input modalities, reasoning, compat.familyandreasoningEffortsfeed a"registry"catalog section -
The glyph. Add an icon component for the id to
ICON_BY_PROVIDERinapps/ui/src/components/shared/provider-icon.tsx. The type check fails until you do.
The new key var is a secret-scrubber rule change: bump SCRUBBER_RULES_VERSION in src/utils/secret-scrubber.ts and its pin in src/tests/secret-scrubber.test.ts, which fails until you do.
Nothing else changes. These surfaces derive from the entry: credential routing and pools, key aliases, the default harness, the opencode credential check, secret scrubbing, E2B and ACP env forwarding, the live key check, the pi models.json block, the opencode provider block, $0 pricing rows, the model catalog, and on the dashboard the Integrations card (key plus base URL), the API keys and usage labels, the setup card and the model picker. The docker-entrypoint.sh boot warning still names NaN and Helmcode only; it is a warning, not a gate.
Claude Managed Agents
claude-managed runs sessions in Anthropic's managed cloud sandbox. The worker becomes a thin SSE relay that maps Anthropic's client.beta.sessions.events.stream output to the swarm's ProviderEvent union — no LLM process, no local CLI, no skill filesystem syncing on the worker.
One-Time Setup
bun run src/cli.tsx claude-managed-setupThis bootstrap CLI (run from your laptop, not inside a worker container):
- Creates an Anthropic-side Environment (sandbox configuration).
- Uploads each
plugin/commands/*.mdskill viaclient.beta.skills.create. - Creates an Anthropic-side Agent with those skills attached.
- Persists the resulting
MANAGED_AGENT_ID+MANAGED_ENVIRONMENT_IDtoswarm_config(encrypted at rest); deployed workers restore them at boot.
Re-run with --force to recreate the agent + environment from scratch (rare — only needed if Anthropic rotates IDs upstream or you intentionally reset).
Environment Variables
| Variable | Required | Default | Description |
|---|---|---|---|
HARNESS_PROVIDER | Yes | — | Must be set to claude-managed |
ANTHROPIC_API_KEY | Yes | — | Anthropic API key. The setup CLI and runtime adapter both use this |
MANAGED_AGENT_ID | Yes | — | Anthropic Agent ID; written by claude-managed-setup |
MANAGED_ENVIRONMENT_ID | Yes | — | Anthropic Environment ID; written by claude-managed-setup |
MCP_BASE_URL | Yes | — | Must be HTTPS-public so Anthropic's sandbox can reach /mcp |
MANAGED_AGENT_MODEL | No | claude-sonnet-5 | Default model on sessions.create; per-task task.model overrides |
MANAGED_GITHUB_VAULT_ID | No | — | Anthropic vault ID holding a GitHub PAT, for repo-bound tasks (recommended for prod) |
MANAGED_GITHUB_TOKEN | No | — | Literal GitHub PAT injected as authorization_token on github_repository resources (dev-only fallback) |
MCP_BASE_URL must be HTTPS and publicly reachable. Anthropic's managed sandbox calls /mcp from the cloud — localhost, host.docker.internal, or self-signed HTTPS will fail. In development, expose the API server via ngrok / Cloudflare Tunnel; in production, point at your deployed swarm API. The adapter and docker-entrypoint.sh both fail-fast at boot if MCP_BASE_URL is unset or doesn't start with https://. (Same constraint already documented for the Jira webhook setup.)
Model Selection
Default: claude-sonnet-5. Override per-worker via MANAGED_AGENT_MODEL, or per-task via the standard task.model field. Cost computation lives in src/providers/claude-managed-models.ts — token rates per Anthropic pricing plus Anthropic's $0.08/session-hour runtime fee.
claude-managed Specifics
- No skill filesystem on the worker. Skills are uploaded to Anthropic via
beta.skills.createduring setup and referenced by ID on the Agent. - System prompt rides in the user message with a
cache_control: { type: "ephemeral" }breakpoint between the static prefix and the per-task body, so the static prefix is cache-hit across tasks for the same agent. - Repo provisioning uses
resources: [{ type: "github_repository", url, authorization_token, checkout: { type: "branch", name: "main" } }]onsessions.createwhen the task carriesvcsRepo. Anthropic clones into/workspace/<repo-name>server-side before the agent runs.
For full provider design rationale (why we don't agents.create at runtime, the SDK shape deviations, the prompt-cache breakpoint decision), see Adding a Harness Provider §12.
Agent Client Protocol (ACP)
acp drives any agent that speaks the Agent Client Protocol — an open, editor-agnostic JSON-RPC protocol for driving coding agents over stdio. If your agent already has an ACP server mode (or you build one), you can run it as a swarm worker with no adapter code of your own. See the protocol's own Get started → Agents guide for what it takes for an agent to speak ACP.
The dashboard offers curated OpenCode, Gemini CLI, GitHub Copilot CLI, Qwen Code, Kiro CLI, and Goose presets and a free-form Custom target. ACP has no live steering (see the steering table in Adding a Harness Provider — ACP exposes a single in-flight session/prompt and the only interrupt is a full session/cancel).
How it works
The ACPAdapter spawns the selected target as a subprocess and speaks ndjson ACP over its stdio using @agentclientprotocol/sdk's ClientSideConnection. On session start it calls initialize then newSession, passing the swarm MCP server ({apiUrl}/mcp, over HTTP with the standard Authorization / X-Agent-ID / X-Source-Task-Id headers) plus every other MCP server installed on the agent, translated into ACP's McpServer shape. It reads the returned configOptions and applies configured knobs with session/set_config_option before the first prompt. Missing or rejected options use the preset fallback and do not fail the session. Sanitized advertised options are reported to the dashboard. Session notifications (session/update) are translated into the swarm's normalized ProviderEvent stream the same way every other provider's events are.
Because the target process owns its own model credentials, acp reports ready: true with no required env vars from the swarm's credential-wait gate, and its cost rows settle at costSource: "unpriced" — the swarm has no visibility into what the target agent is billed for a turn.
Environment Variables
| Variable | Required | Default | Description |
|---|---|---|---|
HARNESS_PROVIDER | Yes | — | Must be set to acp |
ACP_TARGET | No | custom | opencode, gemini, copilot, qwen, kiro or goose for a curated preset, or custom for an operator-supplied command |
ACP_TARGET_COMMAND | Custom only | — | Executable to spawn as the custom ACP agent (also accepts the shorter ACP_COMMAND alias). Must resolve on $PATH or be an absolute path |
ACP_TARGET_ARGS | No | — | Arguments to the target command. Accepts a JSON array (["acp", "--flag"]) or a plain whitespace-separated string |
ACP_TARGET_ENV_KEYS | No | [] | JSON array (or comma-separated list for hand-authored config) of environment/config keys explicitly allowed into a custom target process |
ACP_MODEL_ENV_KEY | No | — | Custom target environment variable that receives MODEL_OVERRIDE when the ACP model option is unavailable |
ACP_CONFIG_OPTIONS | No | {} | JSON object of additional string or boolean ACP option values to apply when advertised |
ACP_SYSTEM_PROMPT_PATH | No | — | Where to write the composed system prompt before spawning, for targets that read their prompt from a file instead of an ACP field. Relative paths resolve against the task cwd; absolute paths are used as-is |
Use the dashboard runtime editor when possible. It updates the harness, MODEL_OVERRIDE, and ACP fields atomically. Selecting ACP from another harness defaults to OpenCode; existing ACP agents with no ACP_TARGET stay custom for backward compatibility.
Example targets
Any ACP-speaking agent works. A few starting points:
opencode ships a built-in ACP server:
HARNESS_PROVIDER=acp
ACP_TARGET=opencode
MODEL_OVERRIDE=opencode/big-pickleThe adapter applies MODEL_OVERRIDE through ACP's advertised model option. It also merges the model into OPENCODE_CONFIG_CONTENT before spawn as a fallback. OpenCode resolves its own model credentials from ~/.local/share/opencode/auth.json or the preset's allowlisted provider env vars (OPENROUTER_API_KEY, OPENAI_API_KEY, etc.).
GitHub Copilot CLI runs as copilot --acp. The full worker image ships it, pinned. On a slim or custom image, install it first (npm install -g @github/copilot); the entrypoint fails boot with that hint when the binary is missing.
With a Copilot subscription, give it a token that has the Copilot Requests permission:
HARNESS_PROVIDER=acp
ACP_TARGET=copilot
COPILOT_GITHUB_TOKEN=github_pat_...
MODEL_OVERRIDE=claude-sonnet-4.5 # optional; the CLI default otherwiseWithout one, bring your own model provider (BYOK). No GitHub login is needed:
HARNESS_PROVIDER=acp
ACP_TARGET=copilot
COPILOT_PROVIDER_BASE_URL=https://openrouter.ai/api/v1
COPILOT_PROVIDER_API_KEY=sk-or-...
COPILOT_PROVIDER_MAX_PROMPT_TOKENS=200000
MODEL_OVERRIDE=google/gemini-3-flash-previewSet COPILOT_PROVIDER_MAX_PROMPT_TOKENS to the model's real context window. For a model it does not know, Copilot assumes a small budget, and the first turn (about 150k tokens of Copilot and swarm tool definitions) fails with "Static system messages and tool definitions exceed the model's usable context budget".
The preset forwards only COPILOT_* variables (COPILOT_GITHUB_TOKEN, every COPILOT_PROVIDER_* BYOK knob, COPILOT_HOME, COPILOT_MODEL) plus GH_HOST and the proxy variables. It never forwards the worker's GITHUB_TOKEN or GH_TOKEN: a git token without the Copilot Requests permission would fail Copilot auth. MODEL_OVERRIDE becomes COPILOT_MODEL. The adapter sets COPILOT_AUTO_UPDATE=false, so the pinned CLI is the one that runs.
Copilot CLI has no system-prompt flag. The adapter writes the swarm system prompt to .github/instructions/agent-swarm.instructions.md in a per-task directory outside the repo, and prepends that directory to COPILOT_CUSTOM_INSTRUCTIONS_DIRS (your own value is kept after it). Copilot reads it alongside the repo's own AGENTS.md and instructions; no repo file is written. The directory is removed when the session ends.
Copilot CLI is proprietary software under the GitHub Copilot CLI License; the image keeps a copy at /usr/share/doc/github-copilot-cli/LICENSE.md. Its use is subject to the GitHub Terms of Service and Copilot terms.
Zed's claude-code-acp bridges Claude Code to ACP:
HARNESS_PROVIDER=acp
ACP_TARGET_COMMAND=npx
ACP_TARGET_ARGS=["-y", "@zed-industries/claude-code-acp"]
ACP_TARGET_ENV_KEYS=["CLAUDE_CODE_OAUTH_TOKEN", "ANTHROPIC_API_KEY"]Needs CLAUDE_CODE_OAUTH_TOKEN or ANTHROPIC_API_KEY in the worker env. ACP_TARGET_ENV_KEYS explicitly forwards both supported credential options to the bridge process. This is a different code path from the swarm's native HARNESS_PROVIDER=claude adapter; prefer claude directly unless you specifically need the ACP transport.
Gemini CLI has a curated preset that runs gemini --acp (the full worker image ships it):
HARNESS_PROVIDER=acp
ACP_TARGET=gemini
MODEL_OVERRIDE=gemini-3-flash-preview
GEMINI_API_KEY=...The preset forwards only Gemini's own auth and endpoint keys: GEMINI_API_KEY, GOOGLE_API_KEY, the Vertex AI set (GOOGLE_GENAI_USE_VERTEXAI, GOOGLE_CLOUD_PROJECT, GOOGLE_CLOUD_LOCATION, GOOGLE_APPLICATION_CREDENTIALS), GOOGLE_GEMINI_BASE_URL, GOOGLE_VERTEX_BASE_URL and GEMINI_CLI_HOME. The adapter writes the composed system prompt to a private temp file outside the task cwd and points GEMINI_SYSTEM_MD at it, so it replaces Gemini CLI's built-in prompt. Gemini CLI does not advertise an ACP model option, so MODEL_OVERRIDE reaches it as GEMINI_MODEL at spawn. The preset also sets GEMINI_CLI_TRUST_WORKSPACE=true, because a worker cannot answer the folder-trust prompt.
Qwen Code runs as qwen --acp. The full worker image ships it, pinned. Give it an OpenAI-compatible endpoint:
HARNESS_PROVIDER=acp
ACP_TARGET=qwen
OPENAI_API_KEY=sk-...
OPENAI_BASE_URL=https://api.openai.com/v1
MODEL_OVERRIDE=gpt-4.1-miniSet OPENAI_BASE_URL explicitly: without it Qwen Code calls DashScope. QWEN_AUTH_TYPE switches to anthropic, gemini, vertex-ai or openai-responses (with that provider's keys); browser-only Qwen OAuth is not supported. MODEL_OVERRIDE is passed as --model. The swarm system prompt replaces Qwen's built-in one through QWEN_SYSTEM_MD, from a temp file outside the repo.
Kiro CLI runs as kiro-cli acp. The full worker image ships it, pinned. It authenticates with a KIRO_API_KEY, which only paid Kiro plans can create:
HARNESS_PROVIDER=acp
ACP_TARGET=kiro
KIRO_API_KEY=ksk_...MODEL_OVERRIDE is passed as --model. Kiro reads its prompt from a Kiro agent config, so the swarm system prompt is not injected. Kiro CLI is proprietary AWS software.
Goose runs as goose acp. The full worker image ships it, pinned. Pick a provider and give it that provider's key:
HARNESS_PROVIDER=acp
ACP_TARGET=goose
GOOSE_PROVIDER=anthropic
ANTHROPIC_API_KEY=sk-ant-...
MODEL_OVERRIDE=claude-sonnet-4-5Goose sends every MCP tool to the model at once. OpenAI-API providers cap a request at 128 tools, and the swarm exposes more, so use a provider without that cap. The preset disables the OS keyring and passes the swarm system prompt through GOOSE_MOIM_MESSAGE_FILE, which Goose adds to each turn's context.
Any executable that speaks ACP over stdio (reads ndjson on stdin, writes ndjson on stdout) works without modification:
HARNESS_PROVIDER=acp
ACP_TARGET_COMMAND=/usr/local/bin/my-acp-agent
ACP_TARGET_ARGS=["--stdio"]See agentclientprotocol.com/get-started/agents for what your agent needs to implement.
Tool availability
An ACP agent's own tools (file edit, shell, etc.) are whatever the target process implements — the swarm doesn't grant or restrict them; that's between you and the target agent's build. What the swarm adapter does provide is the swarm MCP server (store-progress, send-task, memory-search, and the rest) plus every MCP server already installed on the calling agent, both passed into newSession's mcpServers array. Whether the target agent actually calls them depends on the target honoring ACP's mcpServers field the same way opencode's and Zed's ACP servers do — check your target's docs if tool calls aren't appearing. There's no fallback path today if a target ignores mcpServers: unlike claude/pi/codex, which fail open and keep the harness usable without MCP, an ACP target that doesn't wire the passed servers into its own tool loop simply won't call swarm tools at all.
ACP Specifics
- The target receives a session-scoped
aseph_token.newSession's swarm MCP server entry carries this token as anAuthorizationheader (see How it works above). The adapter mints it for the session's agent and task, with a 24-hour expiry, and fails closed if minting fails; it never falls back to the operator key. Session completion starts best-effort revocation. If revocation fails, the token remains valid until expiry.ACP_TARGET_COMMANDis operator-configured and runs in the worker container as the worker; point it only at a binary you trust with that agent's swarm MCP access for up to 24 hours. - No swarm-side credential gate. The
acpadapter has noreadyCheck— the worker reportsready: trueimmediately. The Docker entrypoint does check the target binary at boot: it resolves the command the adapter would spawn (opencode,gemini,copilot,qwen,kiro-cliorgoosefor a preset, otherwiseACP_TARGET_COMMAND/ACP_COMMAND) and exits withFATALif no target is configured or the executable is not on$PATH. Outside the Docker image, an unresolvable target still surfaces at session-spawn time (task fails withACP target failed during startup: ...). - No live steering.
deliverSteeringis not implemented; mid-run messages promote to follow-up tasks the same way an unsupported provider always handles them. - No skill filesystem sync. The generic worker image still seeds
~/.claude/skills,~/.opencode/skills, etc. at boot, so a target that reads one of those directories natively (like opencode) still sees skills; a fully custom target sees whateverACP_SYSTEM_PROMPT_PATHwrites and nothing more.
For full adapter internals (the SwarmAcpClient/ACPSession classes, MCP server translation, event mapping), see src/providers/acp-adapter.ts and Adding a Harness Provider.
DeepSeek Harness (dsh)
Set HARNESS_PROVIDER=dsh and provide DEEPSEEK_API_KEY for bare DeepSeek
model IDs, or OPENROUTER_API_KEY for openrouter/<id> models. The tier
defaults are OpenRouter Flash, OpenRouter Flash, OpenRouter DeepSeek Pro, and
OpenRouter Claude Opus for smol through ultra. Reasoning effort is passed
to dsh when supported. The adapter inserts swarm MCP, has no live steering or
native resume, and accepts DSH_BINARY for a trusted executable override. See
Provider Auth: DeepSeek Harness for the route
selector and a direct-DeepSeek tier map.
Amp
Set HARNESS_PROVIDER=amp and AMP_API_KEY. Its tiers map to Amp modes:
low, medium, high, and ultra. A concrete provider/model pin overrides
the mode; reasoning effort applies only to that pin. Amp inserts swarm MCP,
supports queued steering only, and stores every thread on ampcode.com. See
Provider Auth: Amp for token setup, mode
behavior, and cost tracking.
Cursor
Set HARNESS_PROVIDER=cursor and CURSOR_API_KEY. The defaults are
gpt-5.4-mini, claude-sonnet-5-5, claude-opus-5-5, and
claude-fable-5-1 for smol through ultra; the SDK resolves them against
the account's model list. Cursor passes supported reasoning effort through the
model's own parameter, wires swarm MCP, and supports steer and queue. Sessions
are not resumable. See Provider Auth: Cursor
for plans and key creation.
Grok
Set HARNESS_PROVIDER=grok and XAI_API_KEY, or OPENROUTER_API_KEY for an
openrouter/<id> model. Tier defaults are grok-build-0.1, grok-4.3,
grok-4.6, and grok-4.7. Reasoning effort becomes the Grok CLI's effort
setting when the model supports it. Each session has a fresh GROK_HOME,
includes swarm MCP, and has no live steering or native resume. See
Provider Auth: Grok for route and billing
details.
Devin
Set HARNESS_PROVIDER=devin, DEVIN_API_KEY, and DEVIN_ORG_ID. Every tier
resolves to devin; the adapter does not select a model or apply reasoning
effort. Sessions run in Devin's cloud, track cost in ACUs, and support queued
steering. HAS_MCP=true is not supported. A suspended session can resume, but
an exited or errored one cannot. DEVIN_MAX_ACU_LIMIT optionally caps a task's
ACU usage.
Choosing a Provider
| Consideration | Claude Code | Codex | opencode | pi-mono | Claude Managed | ACP |
|---|---|---|---|---|---|---|
| Setup complexity | Minimal — install CLI + OAuth token | Minimal — CLI plus API key or OAuth bootstrap | Minimal — CLI plus OpenRouter key | Requires API key(s) | One-time claude-managed-setup CLI; HTTPS-public MCP_BASE_URL | Depends entirely on the target agent |
| Billing model | Uses Claude Pro/Max/Team subscription | OpenAI API billing or ChatGPT OAuth | Pay-per-token via OpenRouter/Anthropic/OpenAI, or flat-rate NaN/Helmcode keys | Pay-per-token via API, or flat-rate NaN/Helmcode keys | Anthropic API tokens + $0.08/session-hour runtime fee | Owned by the target process; swarm reports costSource: "unpriced" |
| Model flexibility | Claude models only | Codex/OpenAI models | 100+ models via OpenRouter | Any provider via OpenRouter | Claude models only | Whatever the target agent supports |
| Follow-up continuity | DB-backed context preamble | DB-backed context preamble | DB-backed context preamble | DB-backed context preamble | DB-backed context preamble | DB-backed context preamble |
| MCP integration | Native (.mcp.json config) | Native (~/.codex/config.toml) | Auto-wired per-task via config file | HTTP-based tool discovery | Server-side on the Anthropic Agent | Passed via ACP newSession.mcpServers; target must honor it |
| Where session runs | Worker container | Worker container | Worker container | Worker container | Anthropic's managed cloud sandbox | Worker container (subprocess) |
| Maturity | Production-grade, well-tested | Production-grade, well-tested | Early support (2026-05) | Community project, actively developed | Public beta (2026-04) | Experimental — one generic target, no named profiles |
DeepSeek Harness suits direct DeepSeek or OpenRouter workloads when its model-prefix routing is configured deliberately. Amp is a managed-mode choice with queue-only steering. Cursor suits a Cursor account that needs native model effort controls and live steering. Grok suits xAI API models or OpenRouter models on the Grok CLI. Devin runs work remotely in Cognition's cloud and bills in ACUs rather than worker-side tokens.
Recommendation: Use Claude Code with OAuth for most setups. Use Codex when you specifically want the OpenAI/Codex toolchain or ChatGPT OAuth-backed workers. Use opencode when you want a lightweight open-source runner with access to the full OpenRouter model catalog at low cost. Consider pi-mono when you need a library-based open-source runner. Choose claude-managed when you want managed sandboxing (faster cold-start, vault-based credential isolation) and don't need a local runtime. Choose acp when you already have (or are building) an ACP-speaking agent and want to run it as a swarm worker without writing a new adapter.
Multi-Credential Pools
Claude Code
Both CLAUDE_CODE_OAUTH_TOKEN and ANTHROPIC_API_KEY support comma-separated values for load balancing across multiple subscriptions:
# Multiple OAuth tokens — one is randomly selected per session
CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat-token1,sk-ant-oat-token2,sk-ant-oat-token3
# Also works with API keys
ANTHROPIC_API_KEY=sk-ant-api-key1,sk-ant-api-key2Each session randomly selects one credential from the pool. A log line indicates the selected index (never the credential itself). Single values work unchanged.
This is useful when running multiple concurrent workers that would otherwise hit rate limits on a single subscription.
Model-scoped weekly windows
The Fable weekly limit arrives as rateLimitType: "seven_day_overage_included". Claude Code CLI emits three model-scoped weekly windows on the same rate_limit_event, alongside the key-wide five_hour, seven_day, and overage windows:
rateLimitType | Model |
|---|---|
seven_day_overage_included | Fable |
seven_day_opus | Opus |
seven_day_sonnet | Sonnet |
A rejection on one of these three windows blocks only that model family on the key — the key's status stays available for every other model, unlike a key-wide rejection (five_hour / seven_day / overage) which marks the whole key rate_limited. GET /api/keys/status exposes the derived, readable view as modelLimits per key (model, window, reset time, whether it's still active).
Before picking a key, the worker resolves the task's model (or modelTier) and sends the family on GET /api/keys/available?model=<family> (fable, opus, or sonnet — Haiku has no weekly window in the CLI enum, so a Haiku task sends no model param). The server excludes a key with an active rejected window for that family from the pick.
When every key in a pool is either key-wide rate-limited or blocked by the requested model's window, MODEL_WINDOW_EXHAUSTED_POLICY controls what the worker does:
fail(default) — fail the task immediately with the model name and earliest reset time. Re-dispatch with another model ormodelTier, or wait.fallback— legacy behavior: pick a key at random anyway.
The policy applies at task admission only. Worker boot, credential recovery, and periodic configuration refresh never fail on an exhausted window, so a worker whose default model (MODEL_OVERRIDE) is exhausted still starts and still accepts tasks for another model.
Windows are independent. A model-scoped rejection never clears a key-wide rejection from the same session, and the reverse is also true: the worker reports both. Only a later non-rejected event for the same window clears a rejection. Stored window snapshots merge per window, and a snapshot never replaces one that was observed later (lastSeenAt), so an older allowed read cannot reopen an exhausted window. A structured rejection keeps the time its event was observed, so a rejection reported late cannot overwrite a recovery that another worker saw after it. Only a text-only rejection, which has no event, takes the report time.
Subscription seat limits
The subscription plan of a key decides which model families it can run. A Claude Team standard seat (claude_team_standard) cannot run Fable. GET /api/keys/available?model=fable excludes those keys from the pick and lists them in seatBlockedIndices. A key with no plan, or a plan the swarm does not know, stays eligible.
When no key in the pool can run the model, the task fails at once. The failure reason names the cause, for example No CLAUDE_CODE_OAUTH_TOKEN key can run Fable: 9 keys are on a seat without Fable, 0 keys have the Fable window exhausted. Re-dispatch with another model or modelTier. The swarm does not queue the task and does not change the model. MODEL_WINDOW_EXHAUSTED_POLICY=fallback does not apply to a seat block, because a seat block never resets.
When the CLI rejects a model with errorCode: "credits_required" (for example Fable 5.1 requires usage credits), the worker records a seat mismatch, not a rate limit. The key status stays available. The key row gets lastSeatMismatchAt and lastSeatMismatchModel. For a Fable rejection, when planSource is not manual, the plan becomes claude_team_standard with planSource: detected. A rejection for another model changes no plan, because no plan in the map excludes that model. A manual plan is never changed; the server logs the contradiction instead.
To correct the plan of a key, use the plan picker on the dashboard API keys page, or:
curl -X PATCH "$MCP_BASE_URL/api/keys/plan" \
-H "Authorization: Bearer $AGENT_SWARM_API_KEY" -H "Content-Type: application/json" \
-d '{"keyType":"CLAUDE_CODE_OAUTH_TOKEN","keySuffix":"TJAAA","plan":"claude_team_standard"}'pi and opencode API-key pools
On the pi and opencode harnesses, OPENROUTER_API_KEY, NAN_API_KEY (or its alias HELMCODE_API_NAN) and HELMCODE_API_KEY also accept comma-separated values. Each task gets one key from the pool, chosen by the model's provider prefix (openrouter/, nan/, helmcode/).
NAN_API_KEY=nan-key1,nan-key2,nan-key3
HELMCODE_API_KEY=helmcode-key1,helmcode-key2On opencode, the adapter passes the picked key to the provider config for the task's model. An OPENROUTER_API_KEY pool therefore also works on opencode.
Codex OAuth Pool
Codex workers support a database-backed multi-credential pool stored as codex_oauth_0, codex_oauth_1, ... in the swarm config store. Unlike the Claude comma-separated approach, Codex slots are provisioned via codex-login and persisted centrally so all workers can access them.
Key properties:
- Rate-limit-aware selection: the runner queries
/api/keys/available?keyType=CODEX_OAUTHbefore each task spawn and picks from non-rate-limited slots only. When a task hits a rate limit, the slot is marked unavailable: workspace-credits-exhausted errors use a 2-hour cooldown (tunable viaCODEX_CREDITS_EXHAUSTED_COOLDOWN_MS); other unparseable rate-limit errors fall back to ~5 minutes. See Provider Auth: Codex OAuth — Workspace credits exhausted. - Locked refresh + keep-warm: task-time revalidation and
POST /api/oauth/keep-warm/codexshare the same refresh-lock path, so rarely-used slots can still refresh on a roughly weekly cadence without racing the runner. - Token-refresh write-back: refreshed OAuth tokens are written back to the same
codex_oauth_<slot>key the task started with. Other slots are never touched. - Fail-fast auth errors: a rejected slot refresh now stops the task with the upstream auth failure instead of silently falling back to a stale pool auth file.
- Backwards compatible: single-credential deploys work unchanged. The boot entrypoint seeds
codex_oauth_0from the legacycodex_oauthkey on first run. - Global scope: no per-agent or per-task affinity — any worker can pick any slot.
Provision additional slots:
# Run once per ChatGPT account; each call appends a new slot
bun run src/cli.tsx codex-login --api-url http://localhost:3013 --api-key YOUR_API_KEYFor the full provisioning guide, rate-limit detection details, and verification commands, see Provider Auth: Codex OAuth.
Docker Configuration Examples
Claude Code Worker
# .env.docker
HARNESS_PROVIDER=claude # Optional — defaults to pi with OPENROUTER_API_KEY and neither ANTHROPIC_API_KEY nor CLAUDE_CODE_OAUTH_TOKEN; otherwise claude
CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat-...
API_KEY=your-api-key
MCP_BASE_URL=http://host.docker.internal:3013
AGENT_ID=your-worker-uuid
GITHUB_TOKEN=ghp_...opencode Worker
# .env.docker
HARNESS_PROVIDER=opencode
OPENROUTER_API_KEY=sk-or-... # Or ANTHROPIC_API_KEY / OPENAI_API_KEY
MODEL_OVERRIDE=openrouter/qwen/qwen3-coder-flash # Default; see https://openrouter.ai/models
API_KEY=your-api-key
MCP_BASE_URL=http://host.docker.internal:3013
AGENT_ID=your-worker-uuid
GITHUB_TOKEN=ghp_...pi-mono Worker
# .env.docker
HARNESS_PROVIDER=pi
OPENROUTER_API_KEY=sk-or-... # Or ANTHROPIC_API_KEY
MODEL_OVERRIDE=openrouter/moonshotai/kimi-k2.5 # See https://openrouter.ai/models
API_KEY=your-api-key
MCP_BASE_URL=http://host.docker.internal:3013
AGENT_ID=your-worker-uuid
GITHUB_TOKEN=ghp_...
# Do NOT include CLAUDE_CODE_OAUTH_TOKEN — it will override the pi providerMixed Swarm
You can run different providers for different agents in the same swarm. For example, a Claude Code lead with pi-mono workers:
# .env.docker-lead
HARNESS_PROVIDER=claude
CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat-...
AGENT_ROLE=lead
# .env.docker-worker
HARNESS_PROVIDER=pi
OPENROUTER_API_KEY=sk-or-...Troubleshooting
Worker is parked in waiting_for_credentials
The container booted but no harness credentials are present. This is expected — the worker waits at runtime instead of crash-looping. Set the missing key via PUT /api/config (scope=agent or scope=global) and the worker picks it up within ≤30s. See Worker Credential Recovery for the full lifecycle, the credentialMissing payload, and the configuration knobs (BOOT_INITIAL_BACKOFF_MS, BOOT_MAX_BACKOFF_MS, BOOT_MAX_WAIT_SECONDS).
Claude Code: "Claude CLI not found"
The claude binary must be in $PATH inside the Docker container. The worker Docker image includes it by default. If using a custom image, set CLAUDE_BINARY=/path/to/claude.
Wrong provider selected
Check the startup logs — the entrypoint prints Harness Provider: <value>. If it says claude when you expected pi, ensure HARNESS_PROVIDER=pi is in your env file and not being overridden.
ACP: "ACP target failed during startup"
ACP_TARGET_COMMAND didn't resolve, or the target process exited/errored during initialize/newSession. Confirm the binary is on $PATH inside the worker image (or use an absolute path), and that any args in ACP_TARGET_ARGS are valid for that target — try running the exact ACP_TARGET_COMMAND + ACP_TARGET_ARGS combination by hand first.
ACP: agent never calls swarm MCP tools
The target agent has to honor ACP's newSession.mcpServers field itself; the swarm adapter passes the swarm MCP server and every other installed MCP server into that field, but whether the target connects to them is up to its own implementation. There's no swarm-side fallback here — check the target agent's ACP docs for its MCP support status.
Identifying the Claude transport
The agent list and task detail page label Claude SDK transport separately from the CLI transport. Task details use the recorded session transport, so changing an agent’s setting does not relabel its past sessions.
Related
- Model Gateways — Route supported harnesses through OpenRouter or an OpenRouter-compatible proxy, with current Ramp Router gaps
- Environment Variables — Full reference for all configuration variables including auth credentials
- Deployment Guide — Deploy agents to production with Docker Compose
- Getting Started — Initial setup including credential configuration
- Architecture Overview — How harnesses fit into the overall system
- Harness Providers — Engineering reference for the
ProviderAdaptercontract, including the reasoning/effort capability lookup and per-harness translation