Agent SwarmAgent Swarm
Guides

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

ProviderHARNESS_PROVIDERDescription
Claude Codeclaude (default)Anthropic's Claude Code CLI. Recommended for most use cases
CodexcodexOpenAI Codex CLI with API-key or ChatGPT OAuth authentication
opencodeopencodeOpenCode coding agent powered by OpenRouter. Uses Qwen Coder Flash by default; supports any OpenRouter, Anthropic, or OpenAI model
pi-monopiOpen-source coding agent by @badlogic. Supports multiple model backends
DevindevinCognition's Devin via the /sessions API — session executes in Devin's managed cloud, ACU-based cost tracking
Claude Managed Agentsclaude-managedAnthropic's managed cloud sandbox — sessions execute outside the worker. Requires one-time setup CLI to create the Anthropic-side Agent + Environment

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)

Both 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:

MethodEnv VarHow to Get It
OAuth token (recommended)CLAUDE_CODE_OAUTH_TOKENRun claude setup-token in your terminal
API keyANTHROPIC_API_KEYFrom 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 file

Or use the onboard wizard which runs this automatically:

bunx @desplega.ai/agent-swarm onboard

Environment Variables

VariableRequiredDefaultDescription
CLAUDE_CODE_OAUTH_TOKENYes*OAuth token from claude setup-token. Supports multi-credential pools
ANTHROPIC_API_KEYAlt*Anthropic API key (alternative to OAuth). Also supports multi-credential pools
CLAUDE_BINARYNoclaudePath to the Claude CLI binary (if not in $PATH)

* 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:

ModelDescription
opusClaude Opus (highest capability)
sonnetClaude Sonnet (balanced)
haikuClaude Haiku (fastest)

Models can be set per-task via the API or per-agent via the agent profile. See the Anthropic models overview for all available model IDs.


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:

MethodSourceNotes
OpenAI API keyOPENAI_API_KEYStandard API billing
Auth file~/.codex/auth.jsonNative Codex CLI auth file
ChatGPT OAuth via config storecodex_oauthRestored automatically at worker boot

For ChatGPT OAuth setup, see Provider Auth: Codex OAuth.

Environment Variables

VariableRequiredDefaultDescription
HARNESS_PROVIDERYesMust be set to codex
OPENAI_API_KEYNoOptional when using direct OpenAI API access
API_KEYYesSwarm API key used to fetch codex_oauth from the config store
MCP_BASE_URLYeshttp://host.docker.internal:3013Swarm API URL reachable by the worker
AGENT_IDRecommendedAuto-generatedKeep 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:

KeyDefaultDescription
CODEX_CREDITS_EXHAUSTED_COOLDOWN_MS7200000 (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.

Codex Specifics

  • Per-task subprocess isolation: each task gets a fresh codex-session-runner process, 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 Linux MAX_ARG_STRLEN / E2BIG failures 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-flash model is fast and inexpensive.

Authentication Methods

opencode checks credentials in this priority order:

MethodEnv VarNotes
OpenRouter API key (recommended)OPENROUTER_API_KEYAccess 100+ models via openrouter.ai
Anthropic API keyANTHROPIC_API_KEYDirect Anthropic API billing
OpenAI API keyOPENAI_API_KEYDirect OpenAI API billing
Auth file~/.local/share/opencode/auth.jsonNative opencode CLI auth file

At least one credential source is required. The Docker entrypoint validates this on startup.

Environment Variables

VariableRequiredDefaultDescription
HARNESS_PROVIDERYesMust be set to opencode
OPENROUTER_API_KEYOne of*OpenRouter API key (primary — gives access to all OpenRouter models)
ANTHROPIC_API_KEYOne of*Anthropic API key for Claude models
OPENAI_API_KEYOne of*OpenAI API key for GPT models
OPENCODE_BINARYNoopencodePath 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:

FormatExampleNotes
OpenRouteropenrouter/qwen/qwen3-coder-flashDefault. See OpenRouter model catalog
Anthropicanthropic/claude-sonnet-4-6Requires ANTHROPIC_API_KEY
OpenAIopenai/gpt-4oRequires OPENAI_API_KEY

opencode Specifics

  • Agent-swarm plugin: the plugin/opencode-plugins/agent-swarm.ts plugin 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:

ProviderEnv VarDescription
AnthropicANTHROPIC_API_KEYDirect Anthropic API access
OpenRouterOPENROUTER_API_KEYAccess 100+ models via OpenRouter
OpenAIOPENAI_API_KEYDirect OpenAI API access
GoogleGOOGLE_API_KEYDirect Google AI API access (only honored when MODEL_OVERRIDE starts with google/)
Auth file~/.pi/agent/auth.jsonPre-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

VariableRequiredDefaultDescription
HARNESS_PROVIDERYesMust be set to pi
ANTHROPIC_API_KEYOne of*Anthropic API key for Claude models
OPENROUTER_API_KEYOne of*OpenRouter API key for multi-provider access
OPENAI_API_KEYOne of*OpenAI API key for GPT models
GOOGLE_API_KEYWhen prefixedGoogle 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).

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).

FormatExampleRequired Credential
Shortnameopus, sonnet, haikuANTHROPIC_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-4oOPENAI_API_KEY
google/<id>google/gemini-2.5-proGOOGLE_API_KEY

pi-mono Specifics

  • AGENTS.md symlink: pi-mono reads AGENTS.md for project instructions (equivalent to Claude's CLAUDE.md). The adapter automatically creates a symlink AGENTS.md → CLAUDE.md during sessions so your existing project instructions work with both providers.
  • Lazy provider import: the worker only imports @earendil-works/pi-coding-agent when HARNESS_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.json needed for the swarm connection (though installed MCP servers are also discovered).
  • Skills sync: The Docker entrypoint syncs skills to both ~/.claude/skills/ and ~/.pi/agent/skills/ so skills work regardless of provider.
  • 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.md files not owned by the swarm) are preserved across re-syncs. See src/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-setup

This bootstrap CLI (run from your laptop, not inside a worker container):

  1. Creates an Anthropic-side Environment (sandbox configuration).
  2. Uploads each plugin/commands/*.md skill via client.beta.skills.create.
  3. Creates an Anthropic-side Agent with those skills attached.
  4. Persists the resulting MANAGED_AGENT_ID + MANAGED_ENVIRONMENT_ID to swarm_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

VariableRequiredDefaultDescription
HARNESS_PROVIDERYesMust be set to claude-managed
ANTHROPIC_API_KEYYesAnthropic API key. The setup CLI and runtime adapter both use this
MANAGED_AGENT_IDYesAnthropic Agent ID; written by claude-managed-setup
MANAGED_ENVIRONMENT_IDYesAnthropic Environment ID; written by claude-managed-setup
MCP_BASE_URLYesMust be HTTPS-public so Anthropic's sandbox can reach /mcp
MANAGED_AGENT_MODELNoclaude-sonnet-4-6Default model on sessions.create; per-task task.model overrides
MANAGED_GITHUB_VAULT_IDNoAnthropic vault ID holding a GitHub PAT, for repo-bound tasks (recommended for prod)
MANAGED_GITHUB_TOKENNoLiteral 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-4-6. 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.create during 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" } }] on sessions.create when the task carries vcsRepo. 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.


Choosing a Provider

ConsiderationClaude CodeCodexopencodepi-monoClaude Managed
Setup complexityMinimal — install CLI + OAuth tokenMinimal — CLI plus API key or OAuth bootstrapMinimal — CLI plus OpenRouter keyRequires API key(s)One-time claude-managed-setup CLI; HTTPS-public MCP_BASE_URL
Billing modelUses Claude Pro/Max/Team subscriptionOpenAI API billing or ChatGPT OAuthPay-per-token via OpenRouter/Anthropic/OpenAIPay-per-token via APIAnthropic API tokens + $0.08/session-hour runtime fee
Model flexibilityClaude models onlyCodex/OpenAI models100+ models via OpenRouterAny provider via OpenRouterClaude models only
Follow-up continuityDB-backed context preambleDB-backed context preambleDB-backed context preambleDB-backed context preambleDB-backed context preamble
MCP integrationNative (.mcp.json config)Native (~/.codex/config.toml)Auto-wired per-task via config fileHTTP-based tool discoveryServer-side on the Anthropic Agent
Where session runsWorker containerWorker containerWorker containerWorker containerAnthropic's managed cloud sandbox
MaturityProduction-grade, well-testedProduction-grade, well-testedEarly support (2026-05)Community project, actively developedPublic beta (2026-04)

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.


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-key2

Each 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_OAUTH before 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 via CODEX_CREDITS_EXHAUSTED_COOLDOWN_MS); other unparseable rate-limit errors fall back to ~5 minutes. See Provider Auth: Codex OAuth — Workspace credits exhausted.
  • 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.
  • Backwards compatible: single-credential deploys work unchanged. The boot entrypoint seeds codex_oauth_0 from the legacy codex_oauth key 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_KEY

For 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 provider

Mixed 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.

On this page