Harness Configuration
Configure the AI provider (Claude Code, Codex, opencode, pi-mono, Devin, or Claude Managed Agents) 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 does receive the worker's swarm API key as the swarm MCP bearer, same as every other spawned harness |
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 → spawns `codex` CLI process
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 stdioBoth 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.
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 and recommended harness. It spawns the claude CLI as a subprocess with --output-format stream-json for structured event streaming.
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_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 the codex CLI and supports both direct OpenAI API keys and ChatGPT OAuth.
Each task runs inside a throwaway codex-session-runner subprocess. The worker sends the session config over stdin and receives line-delimited events/results over stdout, so @openai/codex-sdk state dies with the task instead of accumulating in the long-lived runner process.
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-runnerprocess, which keeps the worker's baseline memory flat and avoids long-lived SDK heap growth 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 |
| 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, model-refresh, 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 |
OPENCODE_BINARY | No | opencode | Path to the opencode CLI binary (if not in $PATH) |
* At least one credential source is required (OPENROUTER_API_KEY, ANTHROPIC_API_KEY, OPENAI_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 |
Reasoning / Effort
Set the per-agent reasoning/effort level (off | low | medium | high | xhigh) via PATCH /api/agents/{id}/runtime. opencode has no single reasoning knob — the swarm translates the level into provider-keyed options in the per-task opencode.json: anthropic/* models get thinking.budgetTokens (an internal numeric translation, not a user-facing knob), openrouter/* models get reasoning.effort, and OpenAI-compatible models get reasoningEffort. off omits reasoning keys entirely — opencode has no explicit off switch.
opencode Specifics
- Agent-swarm plugin: the
plugin/opencode-plugins/agent-swarm.tsplugin is automatically injected at session creation. It handles heartbeat, task cancellation, identity sync, system-prompt transformation, compaction, and idle hooks — no manual configuration needed. - Per-task isolation: each session gets its own agent file (
.opencode/agents/swarm-<taskId>.md), config file (/tmp/opencode-<taskId>.json), and data directory (/tmp/opencode-data-<taskId>) to prevent cross-task state bleed. - 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.
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 | Direct Google AI API access (only honored when MODEL_OVERRIDE starts with google/) | |
| 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 | When prefixed | — | Google AI API key — required only when MODEL_OVERRIDE uses a google/ prefix |
* 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 |
Reasoning / Effort
Set the per-agent reasoning/effort level (off | low | medium | high | xhigh) 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, so no special-casing is needed there.
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.
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.
Scope today is deliberately minimal: one target (custom, driven entirely by env vars below), no named target profiles, no Docker image wiring beyond the generic worker image, 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 ACP_TARGET_COMMAND 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. 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_COMMAND | Yes | — | Executable to spawn as the 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_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 |
ACP_TARGET itself is reserved for future named target profiles; it must be unset or "custom" today — any other value fails session creation with Unsupported ACP target.
Example targets
Any ACP-speaking agent works. A few starting points:
opencode ships a built-in ACP server:
HARNESS_PROVIDER=acp
ACP_TARGET_COMMAND=opencode
ACP_TARGET_ARGS=["acp"]opencode resolves its own model credentials from ~/.local/share/opencode/auth.json or the usual provider env vars (OPENROUTER_API_KEY, OPENAI_API_KEY, etc.) — configure those the same way you would for HARNESS_PROVIDER=opencode.
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"]Needs CLAUDE_CODE_OAUTH_TOKEN or ANTHROPIC_API_KEY in the worker env — the bridge process picks it up the same way the claude CLI does. 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 an experimental ACP mode:
HARNESS_PROVIDER=acp
ACP_TARGET_COMMAND=gemini
ACP_TARGET_ARGS=["--experimental-acp"]Needs GEMINI_API_KEY (or whatever credential the installed Gemini CLI version expects) in the worker env.
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 the worker's swarm API key.
newSession's swarm MCP server entry carries the worker's bearer as anAuthorizationheader (see How it works above) — the same credential every other spawned harness process receives (claude,codex, and the rest all pass their swarm bearer to the spawned CLI/subprocess the same way).ACP_TARGET_COMMANDis operator-configured and runs in the worker container as the worker; point it only at a binary you trust with that credential. - No swarm-side credential gate. The
acpadapter has noreadyCheck— the worker reportsready: trueimmediately. IfACP_TARGET_COMMANDis unset or unresolvable, the failure surfaces at session-spawn time (task fails withACP target failed during startup: ...), not at boot. - 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.
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 | Pay-per-token via API | 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 |
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.
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 — claude is the default
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.
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