Skip to content

Connect OpenCode (beta)

Guide (informative) · For: operators · Prereqs: Quickstart

OpenCode joins a Cotal mesh as a lateral peer, at parity with Claude Code: the same cotal_* tool surface, the same message delivery and attention model. You spawn it, watch it work in its real TUI, and it coordinates with your other agents.

Beta means the everyday path (spawn, watch, coordinate) works, but two spawn options are not wired yet and fail loud rather than degrade: resuming an existing session (--resume, issue #154) and tool-sharing (connectors.opencode.mcpServers). See Limits.

OpenCode needs no setup step. The picker in cotal setup just records that you want it; there is no plugin to install; the connector auto-wires at spawn. You only need the opencode binary on your PATH. (Claude Code, by contrast, installs a plugin because its wake channel needs one.)

Same launch grammar as any agent (see run-a-mesh.md):

Terminal window
cotal spawn --agent opencode # foreground in this terminal
cotal spawn researcher --agent opencode -d # detached via the manager; reattach with `cotal attach`

Make OpenCode the default harness for spawns that don’t pass --agent:

Terminal window
COTAL_DEFAULT_AGENT=opencode cotal spawn # an explicit --agent always wins

Or in a team manifest, set agent: opencode per agent (or as the team default). Persona, role, and model come from the agent file the same way as for any connector: see agent-files.md and define-a-team.md.

OpenCode model ids use provider/model form, and a model may expose variants (a connector-defined selector, e.g. a reasoning-effort tier). List what the running mesh’s OpenCode can see:

Terminal window
cotal models --agent opencode # ids + variants, from the manager
cotal models --agent opencode --refresh # refresh the provider cache first

Pick one at spawn, or set model: / variant: in the agent file (the flags win over the file):

Terminal window
cotal spawn --agent opencode --model anthropic/claude-sonnet-4-6 --variant high

A --variant on a connector that doesn’t support variants is rejected up front; the OpenCode connector advertises variant support, so this is the connector where it applies.

OpenCode has a native plugin runtime, so the adapter is not an MCP server; a single in-process plugin does everything.

  • Injected, never written. The plugin and its config ride in OPENCODE_CONFIG_CONTENT (inline JSON, OpenCode’s highest merge layer), so your ~/.config/opencode is never touched. Because it’s a merge layer, a spawned OpenCode agent inherits the operator’s MCP servers (the opposite of Claude Code’s strict isolation), which is why tool-sharing is a separate, not-yet-built feature (see Limits).
  • Per-agent database. The session SQLite DB is moved per agent (.cotal/opencode/<name>/opencode.db, rooted at the manager’s workspace) so concurrent managed agents don’t lock each other or drop files into a target repo.
  • The visible TUI. The connector launches the real opencode TUI, foreground and watchable, attached to the one session the plugin drives. It injects each incoming peer batch as a turn on that session, so a human watching sees the agent work and can type into it. Presence is derived from OpenCode’s event stream (busy → working, idle → idle, permission asked → waiting).
  • Observed model. Each new OpenCode prompt reports its actual provider/model and optional variant into presence for roster and dashboard display. Before the first prompt it remains not reported; the connector never invents a default. An explicit model: or variant: pin wins.
  • Quiet stays pull-only. Quiet-channel ambient never gets prepended to a native human prompt or a directed-message turn. cotal_inbox explicitly surfaces and clears it; automatic traffic stays owned by the connector. Quiet-channel @mentions still drive a turn.
  • /new = context reset. Running OpenCode’s built-in /new in that TUI starts a fresh context while keeping the same mesh identity and creds.
  • /reconnect = in-process recovery. OpenCode has no host reconnect surface, so the connector injects a /reconnect command that calls the shared cotal_reconnect tool, rebuilding a wedged mesh link in-process.
  • Spawned agents run autonomously (permission: "allow") so a supervised agent never stalls on a tool-approval prompt.

The generic tool surface and the inbound-message model are shared across connectors: see mcp-tools.md and connect-claude.md.

A session launched with cotal spawn --events publishes a structured account of what it did: run boundaries per turn, assistant text, and each tool call with its arguments, its end, and its result. The channel is events.<owner>.<actor>, named after the session’s principal, and the rules for it are the same on every connector: see connect-claude.md for the channel, the grant, and how to read it. Arming is COTAL_EVENTS, which the launcher sets for --events spawns; a personal opencode with the plugin installed publishes nothing.

Four things are specific to OpenCode and worth knowing before you read a stream:

  • No user-authored text is published, ever. When a peer message is injected into a native prompt, OpenCode prepends it into the human’s own text part, so one record holds peer-authored and human-authored content with no boundary in it to filter on. Rather than guess where one ends, the connector publishes no user text at all. Assistant text, reasoning and tool activity are unaffected.
  • No step events and no usage. OpenCode’s step records carry no step name and no key shared between the start and the finish, and what the finish actually carries is cost and token counts. So the connector emits no step vocabulary rather than inventing a name, and the usage numbers are not carried in this version.
  • /new starts a new thread on the same channel. OpenCode can hold several sessions in one process, and /new is a context reset that keeps the mesh identity. Each session publishes under its own thread id on the one events.<owner>.<actor> channel. Before the switch, the session you are leaving is flushed and its open run is closed, so a reader never holds a run that never ends.
  • A failed turn is published as a run error, not as a finished run. OpenCode reports a turn that died (an upstream API error, a provider auth failure, or an output-length stop) on its own session.error event, and that turn ends its run with RUN_ERROR carrying OpenCode’s reason and its own error name as the code. If that reason cannot fit in the one closing frame, the shared close still publishes exactly one RUN_ERROR that does fit: it keeps the code and says the original detail was omitted or shortened because of the bound, so a reader is never shown a truncated message as complete. A turn you stopped is not a failure and is not published as one: a user cancellation arrives on the same event, and it closes the run as an ordinary end.

Reasoning is off by default.

  • No session resume. cotal spawn --resume <id> is Claude-only; OpenCode throws, because forking into an existing session needs session-creation plumbing, not an argv flag (issue #154).
  • No tool-sharing. connectors.opencode.mcpServers is not implemented and throws if set. OpenCode agents currently inherit the operator’s MCP servers wholesale through the config merge layer; narrowing that to a chosen subset is a separate feature.