Skip to content

Runtimes

A runtime is the CLI binary behind an agent. teamctl ships adapters for the four major AI coding CLIs — they can all mix freely inside one team.

RuntimeBinaryMCPSession resumeInbox deliveryNotes
Claude Codeclaudeyesalways-on (deterministic UUID; --session-id to create, --resume to attach)channel push (<channel> events)The default. Strongest for planning + tool use.
Codex CLIcodexyes (via per-agent CODEX_HOME config.toml)resume --last scoped to per-agent CODEX_HOMEteam-mailbox tmux nudge (📬 note in the pane)OpenAI’s CLI. Good for deep reasoning on patches.
OpenCodeopencodeyes (per-agent OPENCODE_CONFIG json)-c scoped to per-agent OPENCODE_DBteam-mailbox tmux nudge (📬 note in the pane)Provider-agnostic. Always set model: explicitly — see below.
Gemini CLIgeminiyes (0.3+)n/a (loop-restart)team-mailbox tmux nudge (📬 note in the pane)1M-token context makes it great for research.

Codex agents run under the workspace-write sandbox with network access enabled (sandbox_workspace_write.network_access), so they use plain git / gh / curl for remote work. Without it, a network-blocked sandbox pushes codex onto its ChatGPT connector apps (codex_apps.github.*), which raise a per-tool approval prompt teamctl can’t pre-seed and which would strand an unattended pane. The filesystem stays sandboxed to the workspace and temp dirs, so this is still tighter than a Claude Code agent (not network-sandboxed at all).

Inbox delivery is how an agent learns new mail arrived. Claude Code gets it pushed into the session as <channel source="team"> events (Claude Code Channels). Codex, OpenCode, and Gemini treat MCP as strictly request/response and drop unsolicited notifications, so team-mcp types a short 📬 sender: "short preview…" (+N more) nudge into the agent’s tmux pane instead; the agent then drains its mailbox via inbox_peek / inbox_read / inbox_ack. Since the nudge arrives as keystrokes, the preview is sanitized — single line, control characters stripped — so message bodies can’t inject input into the pane. Either way, agents react on arrival — nobody polls.

OpenCode has three sharp edges teamctl covers, but two need the operator’s attention:

  • Always set model: on opencode agents, in provider/model form (e.g. openai/gpt-5.4-mini-fast). With no model pinned, opencode defaults to the priciest model the account is authed for.
  • Auth must exist at ~/.local/share/opencode/auth.json (opencode auth login). Missing auth does not error — opencode silently falls back to free anonymous models. The wrapper logs a warning when auth is absent, but the agent keeps running on the free tier until you fix it.
  • Autoupdate is disabled by teamctl (OPENCODE_DISABLE_AUTOUPDATE=1 plus autoupdate: false in the rendered config): the TUI otherwise upgrades the shared binary in place at boot — a mid-fleet binary swap.

For Claude Code, every spawn uses a UUIDv5 derived deterministically from teamctl:<project>:<agent>. The wrapper passes --session-id <uuid> to create the session on first spawn, and --resume <uuid> on every subsequent spawn once the session jsonl exists. Same UUID either way, so an agent’s context survives teamctl down/up, crash recovery, and host reboots without operator action. If the session-file at that UUID is ever removed (manual cleanup, claude session-dir reset), the wrapper falls back to --session-id and claude recreates a fresh session at the same UUID. Self-healing by construction.

Adapters live under runtimes/<name>.yaml:

runtimes/claude-code.yaml
binary: claude
supports_mcp: true
default_model: claude-opus-4-8
env:
CLAUDE_PROJECT_DIR_MODE: compose

Referenced from an agent spec:

workers:
dev1:
runtime: codex
model: gpt-5-codex

Adding a new runtime

  1. Drop a runtimes/<yourcli>.yaml with at least binary:.
  2. Extend bin/agent-wrapper.sh with a run_yourcli branch that shells out with the right flags.
  3. Run teamctl reload.

If the binary is missing on $PATH, teamctl up fails fast with a clear error rather than spawning a doomed tmux session.