|
| 1 | +# OpenCode CLI Provider |
| 2 | + |
| 3 | +> ⚠️ **Experimental — single-agent flows only.** Multi-agent orchestration (assign / send_message back to a supervisor) is **not yet reliable** on `opencode_cli`: the supervisor's inbox can deadlock with `pending` messages after its turn settles. Single-agent and pure handoff workflows are unaffected. Tracking: [#203](https://github.com/awslabs/cli-agent-orchestrator/issues/203). |
| 4 | +
|
| 5 | +## Overview |
| 6 | + |
| 7 | +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. |
| 8 | + |
| 9 | +## Prerequisites |
| 10 | + |
| 11 | +1. **OpenCode binary** — install from [opencode.ai](https://opencode.ai): |
| 12 | + ```bash |
| 13 | + npm install -g opencode-ai |
| 14 | + # or |
| 15 | + curl -fsSL https://opencode.ai/install | bash |
| 16 | + ``` |
| 17 | +2. **Node.js 18+** — required by OpenCode for its plugin system |
| 18 | +3. **tmux 3.3+** — required by CAO for terminal management |
| 19 | +4. **API credentials** — configure whichever model provider you want OpenCode to use (Anthropic, OpenAI, etc.) per [OpenCode's auth docs](https://opencode.ai/docs/auth) |
| 20 | + |
| 21 | +### First-launch delay |
| 22 | + |
| 23 | +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. |
| 24 | + |
| 25 | +Subsequent launches complete in ~2 seconds. |
| 26 | + |
| 27 | +## Quick Start |
| 28 | + |
| 29 | +### 1. Install agent profiles |
| 30 | + |
| 31 | +```bash |
| 32 | +# Built-in profiles |
| 33 | +cao install code_supervisor --provider opencode_cli |
| 34 | +cao install developer --provider opencode_cli |
| 35 | +cao install reviewer --provider opencode_cli |
| 36 | + |
| 37 | +# Custom or example profiles |
| 38 | +cao install examples/assign/data_analyst.md --provider opencode_cli |
| 39 | +cao install examples/assign/report_generator.md --provider opencode_cli |
| 40 | +``` |
| 41 | + |
| 42 | +### 2. Start the CAO server |
| 43 | + |
| 44 | +```bash |
| 45 | +uv run cao-server |
| 46 | +``` |
| 47 | + |
| 48 | +### 3. Launch an agent |
| 49 | + |
| 50 | +```bash |
| 51 | +# Standard launch — shows tool summary and asks for confirmation |
| 52 | +cao launch --agents developer --provider opencode_cli |
| 53 | + |
| 54 | +# Skip CAO's launch-time confirmation prompt (tool restrictions still enforced) |
| 55 | +cao launch --agents developer --provider opencode_cli --auto-approve |
| 56 | + |
| 57 | +# Specify model override |
| 58 | +cao launch --agents developer --provider opencode_cli --model anthropic/claude-sonnet-4-6 |
| 59 | + |
| 60 | +# Unrestricted (DANGEROUS) — agent can run any command |
| 61 | +cao launch --agents developer --provider opencode_cli --yolo |
| 62 | +``` |
| 63 | + |
| 64 | +Via HTTP API: |
| 65 | + |
| 66 | +```bash |
| 67 | +curl -X POST "http://localhost:9889/sessions?provider=opencode_cli&agent_profile=developer" |
| 68 | +``` |
| 69 | + |
| 70 | +## Config Isolation |
| 71 | + |
| 72 | +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: |
| 73 | + |
| 74 | +- CAO-installed agents are visible in OpenCode's agent picker alongside the built-ins |
| 75 | +- CAO's MCP wiring (`opencode.json`) never touches the user's personal setup |
| 76 | +- Switching between `cao launch` and personal `opencode` usage is safe — they use independent config trees |
| 77 | + |
| 78 | +Storage layout: |
| 79 | + |
| 80 | +``` |
| 81 | +~/.aws/opencode/ |
| 82 | +├── opencode.json # MCP servers + per-agent tool gating (written by cao install) |
| 83 | +├── package.json # written by opencode on first launch |
| 84 | +├── node_modules/ # ~57 MB, written by opencode on first launch |
| 85 | +└── agents/ |
| 86 | + ├── code_supervisor.md |
| 87 | + ├── developer.md |
| 88 | + └── ... |
| 89 | +``` |
| 90 | + |
| 91 | +## Permission and Tool Mapping |
| 92 | + |
| 93 | +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**. |
| 94 | + |
| 95 | +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. |
| 96 | + |
| 97 | +### Summary |
| 98 | + |
| 99 | +| CAO category | OpenCode tools enabled | |
| 100 | +|---|---| |
| 101 | +| `execute_bash` | `bash` | |
| 102 | +| `fs_read` | `read` | |
| 103 | +| `fs_write` | `edit`, `write` | |
| 104 | +| `fs_list` | `glob`, `grep` | |
| 105 | +| `fs_*` | `read`, `edit`, `write`, `glob`, `grep` | |
| 106 | +| `@<mcp-server-name>` | Handled in `opencode.json` (not frontmatter) | |
| 107 | + |
| 108 | +Tools not in any enabled category default to `deny`. The following tools have hardcoded policies regardless of `allowedTools`: |
| 109 | + |
| 110 | +| Tool | Policy | Reason | |
| 111 | +|---|---|---| |
| 112 | +| `task` | deny | Sub-agents escape CAO's terminal tracking | |
| 113 | +| `question` | deny | Blocks unattended flows indefinitely | |
| 114 | +| `webfetch`, `websearch`, `codesearch` | deny | Network egress — opt-in only | |
| 115 | +| `todowrite`, `skill` | allow | In-memory / additive, no side-effects | |
| 116 | + |
| 117 | +Pass `--yolo` (or set `allowedTools: ["*"]` in the profile) to allow all 13 tools including the above. |
| 118 | + |
| 119 | +### `cao launch --auto-approve` |
| 120 | + |
| 121 | +`--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`. |
| 122 | + |
| 123 | +## Skills |
| 124 | + |
| 125 | +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. |
| 126 | + |
| 127 | +At `cao install --provider opencode_cli` time, CAO creates a symlink: |
| 128 | + |
| 129 | +``` |
| 130 | +~/.aws/opencode/skills → ~/.aws/cli-agent-orchestrator/skills/ |
| 131 | +``` |
| 132 | + |
| 133 | +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: |
| 134 | + |
| 135 | +- Skill additions or removals under `~/.aws/cli-agent-orchestrator/skills/` take effect on the next OpenCode launch with no reinstall required. |
| 136 | +- The agent's system prompt stays lean — only `profile.system_prompt`/`profile.prompt` is written to the `.md` body, with no catalog injection. |
| 137 | +- CAO's `load_skill` MCP tool remains available as a second path to the same content (cross-provider parity). |
| 138 | + |
| 139 | +## Status Detection |
| 140 | + |
| 141 | +The provider detects terminal state from the tmux capture buffer (ANSI-stripped): |
| 142 | + |
| 143 | +| State | Marker | |
| 144 | +|---|---| |
| 145 | +| `IDLE` | `ctrl+p commands` footer, no `esc interrupt` | |
| 146 | +| `PROCESSING` | `esc interrupt` footer keybind | |
| 147 | +| `COMPLETED` | `▣ <agent> · <model> · Ns` completion marker followed by idle footer | |
| 148 | +| `WAITING_USER_ANSWER` | `△ Permission required` or `△ Always allow` heading | |
| 149 | +| `ERROR` | Fallback — no state marker matched | |
| 150 | + |
| 151 | +## MCP Server Wiring |
| 152 | + |
| 153 | +`cao install --provider opencode_cli` writes MCP server declarations into `~/.aws/opencode/opencode.json`: |
| 154 | + |
| 155 | +- Each `mcpServers` entry from the agent profile is added under the top-level `mcp` key |
| 156 | +- The server's tools are default-denied globally (`"<servername>*": false` under `tools`) |
| 157 | +- Re-enabled per-agent under `agent.<agent_id>.tools` |
| 158 | + |
| 159 | +The agent ID is the slash-sanitized form of the profile 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. |
| 160 | + |
| 161 | +Reinstalling an agent whose profile no longer declares `mcpServers` explicitly removes its `agent.<agent_id>` entry from `opencode.json`, so previously-granted MCP tools do not survive as stale grants. |
| 162 | + |
| 163 | +`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. |
| 164 | + |
| 165 | +## End-to-End Testing |
| 166 | + |
| 167 | +```bash |
| 168 | +# Install profiles first |
| 169 | +cao install examples/assign/data_analyst.md --provider opencode_cli |
| 170 | +cao install examples/assign/report_generator.md --provider opencode_cli |
| 171 | +cao install developer --provider opencode_cli |
| 172 | + |
| 173 | +# Start CAO server |
| 174 | +uv run cao-server |
| 175 | + |
| 176 | +# Run all OpenCode CLI e2e tests |
| 177 | +uv run pytest -m e2e test/e2e/test_assign.py -k opencode -v |
| 178 | + |
| 179 | +# Run a single test |
| 180 | +uv run pytest -m e2e test/e2e/test_assign.py::TestOpenCodeCliAssign::test_assign_with_callback -v |
| 181 | +``` |
| 182 | + |
| 183 | +The `test_assign_with_callback` test validates all four orchestration modes: |
| 184 | +- **assign** (non-blocking): supervisor terminal created and stays IDLE |
| 185 | +- **send_message** (inbox delivery): worker pushes result to supervisor inbox |
| 186 | +- **status transitions**: IDLE → PROCESSING → COMPLETED across concurrent terminals |
| 187 | +- **handoff** (blocking): inbox delivery triggers supervisor state transition |
| 188 | + |
| 189 | +## Known Limitations |
| 190 | + |
| 191 | +### Project-local `opencode.json` override |
| 192 | + |
| 193 | +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. |
| 194 | + |
| 195 | +**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). |
| 196 | + |
| 197 | +### Scrolling enters tmux copy mode |
| 198 | + |
| 199 | +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. |
| 200 | + |
| 201 | +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. |
| 202 | + |
| 203 | +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. |
| 204 | + |
| 205 | +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. |
| 206 | + |
| 207 | +### `opencode.json` concurrent writes |
| 208 | + |
| 209 | +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. |
| 210 | + |
| 211 | +## Troubleshooting |
| 212 | + |
| 213 | +### First-launch blank TUI (5–30 seconds) |
| 214 | + |
| 215 | +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. |
| 216 | + |
| 217 | +To pre-populate `node_modules/` before the first CAO launch (optional): |
| 218 | +```bash |
| 219 | +OPENCODE_CONFIG_DIR=~/.aws/opencode opencode --help |
| 220 | +``` |
| 221 | + |
| 222 | +### "Unknown provider" error from the server |
| 223 | + |
| 224 | +Ensure the CAO server running on port 9889 is the **dev version**, not the pre-installed binary: |
| 225 | +```bash |
| 226 | +# Kill any stale installed binary |
| 227 | +pkill -f 'cao-server' |
| 228 | +# Start the dev server |
| 229 | +uv run cao-server |
| 230 | +``` |
| 231 | + |
| 232 | +### Authentication / model errors |
| 233 | + |
| 234 | +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. |
| 235 | + |
| 236 | +### Permission prompt blocking an automated flow |
| 237 | + |
| 238 | +CAO emits only `allow` or `deny` in the permission frontmatter, so `△ Permission required` should not appear for CAO-managed tools. If it does: |
| 239 | +1. Verify the profile's `allowedTools` / `role` grants the tool in question and reinstall — CAO translates allowed tools directly to `permission: allow`. |
| 240 | +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`)**. |
| 241 | + |
| 242 | +### Status stuck as `PROCESSING` |
| 243 | + |
| 244 | +This can happen if: |
| 245 | +- OpenCode launched but the TUI hasn't painted yet (transient — the poller recovers) |
| 246 | +- A `node_modules` install is still in progress (wait up to 120s) |
| 247 | +- The `opencode` binary isn't on PATH in the tmux window's shell (check `echo $PATH` inside tmux) |
0 commit comments