Skip to content

Latest commit

 

History

History
322 lines (227 loc) · 17.8 KB

opencode-cli.md

File metadata and controls

322 lines (227 loc) · 17.8 KB

OpenCode CLI Provider

⚠️ Experimental. Multi-agent orchestration (assign / send_message back to a supervisor) now uses a temporary OpenCode-specific inbox polling fallback for #203. This prevents pending supervisor inbox messages from getting stuck after the OpenCode TUI settles, but delivery is still not fully unified with the immediate and watchdog paths until #115 replaces them with a single coordinator.

Overview

The OpenCode CLI provider enables CLI Agent Orchestrator (CAO) to work with OpenCode, a terminal-based AI assistant with a native agent system. OpenCode uses Markdown files with YAML frontmatter as its agent format — nearly identical to CAO's own profile format — making this integration especially clean.

Prerequisites

  1. OpenCode binary — install from opencode.ai:
    npm install -g opencode-ai
    # or
    curl -fsSL https://opencode.ai/install | bash
  2. Node.js 18+ — required by OpenCode for its plugin system
  3. tmux 3.3+ — required by CAO for terminal management
  4. API credentials — configure whichever model provider you want OpenCode to use (Anthropic, OpenAI, etc.) per OpenCode's auth docs

First-launch delay

On its first ever launch against a fresh CAO config directory (~/.aws/opencode/), OpenCode runs npm install @opencode-ai/plugin — roughly 57 MB of dependencies that take 5–30 seconds to install. The TUI will appear blank until the install completes. This is expected; CAO's 120-second initialization timeout covers it automatically.

Subsequent launches complete in ~2 seconds.

Quick Start

1. Install agent profiles

# Built-in profiles
cao install code_supervisor --provider opencode_cli
cao install developer --provider opencode_cli
cao install reviewer --provider opencode_cli

# Custom or example profiles
cao install examples/assign/data_analyst.md --provider opencode_cli
cao install examples/assign/report_generator.md --provider opencode_cli

2. Start the CAO server

uv run cao-server

3. Launch an agent

# Standard launch — shows tool summary and asks for confirmation
cao launch --agents developer --provider opencode_cli

# Skip CAO's launch-time confirmation prompt (tool restrictions still enforced)
cao launch --agents developer --provider opencode_cli --auto-approve

# Specify model override
cao launch --agents developer --provider opencode_cli --model anthropic/claude-sonnet-4-6

# Unrestricted access — install the profile with allowedTools: ["*"] first.
# `cao launch --yolo` is a no-op on opencode_cli (see Known Limitations).
cao install developer --provider opencode_cli   # profile must have allowedTools: ["*"]
cao launch --agents developer --provider opencode_cli

Via HTTP API:

curl -X POST "http://localhost:9889/sessions?provider=opencode_cli&agent_profile=developer"

Config Isolation

CAO runs OpenCode with OPENCODE_CONFIG_DIR and OPENCODE_CONFIG both pointing at ~/.aws/opencode/, which is separate from the user's personal OpenCode config at ~/.config/opencode/. This means:

  • CAO-installed agents are visible in OpenCode's agent picker alongside the built-ins
  • CAO's MCP wiring (opencode.json) never touches the user's personal setup
  • Switching between cao launch and personal opencode usage is safe — they use independent config trees

Storage layout:

~/.aws/opencode/
├── opencode.json          # MCP servers + per-agent tool gating (written by cao install)
├── cao-grants.json        # CAO's record of which tool grants it wrote (see below)
├── package.json           # written by opencode on first launch
├── node_modules/          # ~57 MB, written by opencode on first launch
└── agents/
    ├── code_supervisor.md
    ├── developer.md
    └── ...

Permission and Tool Mapping

OpenCode enforces permissions natively via permission: YAML frontmatter in each agent file. CAO translates its allowedTools list to an OpenCode permission: dict at install time — no entry in utils/tool_mapping.py is needed.

CAO owns the permission decision, so the translator only ever emits allow or deny. The ask value — OpenCode's native runtime prompt — is intentionally never written, which keeps OpenCode aligned with the other CAO providers (Kiro, Q, Claude Code) where allowed tools are allowed outright.

Summary

CAO category OpenCode tools enabled
execute_bash bash
fs_read read
fs_write edit, write
fs_list glob, grep
fs_* read, edit, write, glob, grep
@builtin none — a selector for provider chrome, not a tool grant (as in utils/tool_mapping.py)
@<mcp-server-name> Handled in opencode.json (not frontmatter)

Tools not in any enabled category default to deny. The following tools have hardcoded policies regardless of allowedTools:

Tool Policy Reason
task deny Sub-agents escape CAO's terminal tracking
question deny Blocks unattended flows indefinitely
webfetch, websearch, codesearch deny Network egress — opt-in only
todowrite, skill allow In-memory / additive, no side-effects

To allow all 13 tools including the above, set allowedTools: ["*"] in the profile and re-run cao install. Unlike the other providers, cao launch --yolo does not widen permissions at runtime on opencode_cli — see cao launch --yolo is install-time only below.

cao launch --auto-approve

--auto-approve on cao launch matches the repo-wide semantics: it skips CAO's launch-time confirmation prompt only. Tool restrictions are still enforced, and this flag does not modify any files in OPENCODE_CONFIG_DIR. It has no cao install counterpart — install-time permissions are driven entirely by the profile's allowedTools / role.

Skills

CAO skills (e.g. cao-supervisor-protocols, cao-worker-protocols) are exposed to OpenCode agents through OpenCode's native skill tool with progressive loading — they are not baked into the agent's system prompt.

At cao install --provider opencode_cli time, CAO creates a symlink:

~/.aws/opencode/skills → ~/.aws/cli-agent-orchestrator/skills/

OpenCode auto-discovers <OPENCODE_CONFIG_DIR>/skills/ and makes its contents available through the skill tool. Metadata (name, description) is listed up front; full skill bodies are loaded on demand. This means:

  • Skill additions or removals under ~/.aws/cli-agent-orchestrator/skills/ take effect on the next OpenCode launch with no reinstall required.
  • The agent's system prompt stays lean — only profile.system_prompt/profile.prompt is written to the .md body, with no catalog injection.
  • CAO's load_skill MCP tool remains available as a second path to the same content (cross-provider parity).

Status Detection

The provider detects terminal state from the tmux capture buffer (ANSI-stripped):

State Marker
IDLE ctrl+p commands footer, no esc interrupt
PROCESSING esc interrupt footer keybind
COMPLETED ▣ <agent> · <model> · Ns completion marker followed by idle footer
WAITING_USER_ANSWER △ Permission required or △ Always allow heading
ERROR Fallback — no state marker matched

MCP Server Wiring

cao install --provider opencode_cli writes MCP server declarations into ~/.aws/opencode/opencode.json:

  • Each mcpServers entry from the agent profile is added under the top-level mcp key
  • The server's tools are default-denied globally ("<servername>*": false under tools)
  • Re-enabled per-agent under agent.<agent_id>.tools
  • An entry carries cwd when its source declares one. Agent-plugin servers always do — their working directory defaults to that agent plugin's PLUGIN_ROOT — so an agent plugin whose command or arguments are relative to its own directory starts in the right place rather than in OpenCode's workspace directory

The agent ID is the profile's resolved name: — the same identifier used for the installed .md filename and the runtime opencode --agent <id> argument. This keeps the filename, the --agent arg, and the opencode.json key aligned for any profile name. (Install rejects a name: containing a path separator outright; to_opencode_agent_id still flattens / to __ as defence in depth for any other caller.)

Because the ID comes from the resolved name: rather than the file name, two different profile files can resolve to the same ID. cao install refuses the second install rather than overwriting the first, naming both profiles so one can be renamed. Only an installed profile owns an ID: a packaged built-in or an uninstalled local profile with the same name: does not block. Ownership is read from the installed context copy at its destination path (the configured agents.dirs.cao_installed directory, and the default directory as well when an override is configured), not from profile discovery, and the same check guards installs for every provider, since they all write that copy. The agent file in OPENCODE_AGENTS_DIR is probed too, under that directory's own case rules, so a name that differs from an installed one only in case is refused where that directory folds case even when the context directory does not. A context copy written by a CAO version before the x-cao-source-stem marker existed cannot prove which profile it belongs to; the error tells you to delete that copy and reinstall, and an agent file left behind by a deleted context copy is refused the same way.

Your agent.<id> entry is yours to edit

opencode.json is a shared file that you are expected to hand-edit, so CAO merges into agent.<agent_id> rather than rewriting it. A model, prompt, temperature or tool policy you set on a CAO-installed agent — including a "bash": false or a grant for a server of your own — survives every cao install, every agent-plugin refresh, and the uninstall.

Withdrawing a grant is therefore restricted to keys CAO can prove are its own:

  • keys recorded in cao-grants.json, a sidecar CAO writes next to opencode.json listing the <servername>* keys it granted per agent, and
  • <servername>* for any mcp entry whose paths resolve inside CAO's agent-plugin store, which keeps grants from installs predating the sidecar cleanable

A <servername>* key CAO has no record of and cannot place in the agent-plugin store is left alone rather than deleted on a guess. Deleting cao-grants.json is safe; it only costs CAO the first of the two proofs.

One exception to "your tool policy survives", and it predates this behaviour: a grant is re-applied on every refresh, so setting "<servername>*": false by hand on a server CAO grants this agent is overwritten back to true the next time the agent is installed or a plugin changes. To keep such a server switched off, remove it from the agent's profile rather than denying it here. Reinstalling an agent whose profile no longer declares mcpServers withdraws CAO's grants for it, and drops the agent.<agent_id> entry entirely only when nothing but those grants was in it.

CAO_TERMINAL_ID is not written to opencode.json. OpenCode spawns MCP subprocesses that inherit the tmux window's environment, so the terminal ID propagates naturally — the same mechanism Kiro uses.

End-to-End Testing

# Install profiles first
cao install examples/assign/data_analyst.md --provider opencode_cli
cao install examples/assign/report_generator.md --provider opencode_cli
cao install developer --provider opencode_cli

# Start CAO server
uv run cao-server

# Run all OpenCode CLI e2e tests
uv run pytest -m e2e test/e2e/test_assign.py -k opencode -v

# Run a single test
uv run pytest -m e2e test/e2e/test_assign.py::TestOpenCodeCliAssign::test_assign_with_callback -v

The test_assign_with_callback test validates all four orchestration modes:

  • assign (non-blocking): supervisor terminal created and stays IDLE
  • send_message (inbox delivery): worker pushes result to supervisor inbox
  • status transitions: IDLE → PROCESSING → COMPLETED across concurrent terminals
  • handoff (blocking): inbox delivery triggers supervisor state transition

Known Limitations

cao launch --yolo is install-time only

Unlike every other CAO provider, opencode_cli does not honour cao launch --yolo at runtime. Permissions are baked into the installed agent's frontmatter permission: block at cao install time and cannot be loosened by a launch flag.

Root cause: OpenCode's TUI (the mode CAO drives) has no equivalent to --dangerously-skip-permissions / --yolo / --trust-all-tools. The flag exists only on the opencode run headless one-shot command, which CAO does not use. Tracked upstream in sst/opencode#8463 and sibling issues.

To get unrestricted access on opencode_cli:

# 1. Edit the profile's frontmatter so it contains:
#    allowedTools: ["*"]

# 2. Re-run cao install — this rewrites the permission: block with all tools set to allow.
cao install my_agent --provider opencode_cli

# 3. Launch normally (omit --yolo — it would emit a warning and still only honour what's installed).
cao launch --agents my_agent --provider opencode_cli

Runtime permission bypass support remains unavailable for OpenCode's TUI mode. CAO can revisit this either with a temp-agent workaround or by consuming an upstream TUI flag once one ships.

Project-local opencode.json override

OpenCode's config merge precedence places a project-local opencode.json in the current working directory above OPENCODE_CONFIG (the CAO-managed file). If you cao launch in a directory that has its own opencode.json with conflicting agent.<name>.tools or tools entries, CAO's MCP wiring can be silently overridden for that agent.

Workaround: remove or rename the project-local opencode.json before launching CAO, or move it under .opencode/ (a subdirectory OpenCode also searches but at a lower priority level).

Scrolling enters tmux copy mode

When you scroll (mouse wheel or trackpad) inside a CAO-managed OpenCode terminal, tmux enters copy mode instead of scrolling the TUI conversation history. This is intentional.

CAO launches OpenCode with OPENCODE_DISABLE_MOUSE=1, which prevents OpenCode from requesting application mouse-reporting mode (\x1b[?1000h). Without that request, tmux does not forward scroll events to the OpenCode process — it intercepts them and enters copy mode instead.

The reason for this trade-off: if OpenCode owned scroll events, scrolling the conversation history would move the completion marker (▣ <agent> · <model> · Ns) off screen. The footer (ctrl+p commands, esc interrupt) is pinned to the bottom of the TUI and remains visible regardless of scroll position, so IDLE and PROCESSING detection are unaffected. But COMPLETED detection requires both the completion marker and the idle footer to be present simultaneously in the captured frame — if the marker has scrolled away, CAO never detects COMPLETED even after the agent finishes. Disabling mouse keeps the frame locked to the most recent render.

Press q or Escape to exit copy mode. If you need to read earlier conversation history, use the get_output API endpoint or the /terminals/<id>/output endpoint to retrieve the full captured log.

opencode.json concurrent writes

Parallel cao install --provider opencode_cli invocations (e.g., from a batch script) can race on the shared ~/.aws/opencode/opencode.json file. The second writer may clobber the first's agent entry. Sequential installs are safe. File locking is deferred to a future release.

cao-grants.json is written atomically (temp file + rename), so it is never observed half-written — but it is a whole-file write like opencode.json, so it races the same way under parallel installs.

Troubleshooting

First-launch blank TUI (5–30 seconds)

OpenCode installs @opencode-ai/plugin into ~/.aws/opencode/node_modules/ on the first launch. The terminal will appear blank until npm install completes. CAO's 120-second initialization timeout covers this automatically.

To pre-populate node_modules/ before the first CAO launch (optional):

OPENCODE_CONFIG_DIR=~/.aws/opencode opencode --help

"Unknown provider" error from the server

Ensure the CAO server running on port 9889 is the dev version, not the pre-installed binary:

# Kill any stale installed binary
pkill -f 'cao-server'
# Start the dev server
uv run cao-server

Authentication / model errors

OpenCode itself handles model authentication. Verify your credentials are set for the model provider you want to use. Check ~/.config/opencode/opencode.json (your personal config) for provider API keys, or set them via environment variables before launching.

Permission prompt blocking an automated flow

CAO emits only allow or deny in the permission frontmatter, so △ Permission required should not appear for CAO-managed tools. If it does:

  1. Verify the profile's allowedTools / role grants the tool in question and reinstall — CAO translates allowed tools directly to permission: allow.
  2. If the prompt comes from a tool outside CAO's vocabulary, respond to it manually in the tmux window, or use --yolo to disable all restrictions (DANGEROUS — allows any command including aws, rm, curl).

Status stuck as PROCESSING

This can happen if:

  • OpenCode launched but the TUI hasn't painted yet (transient — the poller recovers)
  • A node_modules install is still in progress (wait up to 120s)
  • The opencode binary isn't on PATH in the tmux window's shell (check echo $PATH inside tmux)