|
| 1 | +--- |
| 2 | +title: OpenCode |
| 3 | +description: Spawn full coding agents as worker processes via the OpenCode integration. |
| 4 | +--- |
| 5 | + |
| 6 | +# OpenCode |
| 7 | + |
| 8 | +Spacebot can spawn [OpenCode](https://opencode.ai) as a worker backend. Instead of running a Rig agent with shell/file/exec tools, an OpenCode worker delegates to a persistent OpenCode subprocess that has its own tool suite, codebase exploration, and context management. |
| 9 | + |
| 10 | +Use OpenCode workers for multi-file coding tasks. Use builtin workers for one-shot commands, file operations, and non-coding work. |
| 11 | + |
| 12 | +## Enabling |
| 13 | + |
| 14 | +OpenCode is disabled by default. Enable it in your config: |
| 15 | + |
| 16 | +```toml |
| 17 | +[defaults.opencode] |
| 18 | +enabled = true |
| 19 | +path = "opencode" # path to the opencode binary |
| 20 | +``` |
| 21 | + |
| 22 | +The `path` field supports `env:VAR_NAME` resolution: |
| 23 | + |
| 24 | +```toml |
| 25 | +path = "env:OPENCODE_PATH" |
| 26 | +``` |
| 27 | + |
| 28 | +Once enabled, the `spawn_worker` tool gains a `worker_type` parameter. The channel LLM decides whether to use `"builtin"` (default) or `"opencode"` based on the task. |
| 29 | + |
| 30 | +## How It Works |
| 31 | + |
| 32 | +``` |
| 33 | +Channel: "spawn_worker: refactor the auth module, worker_type: opencode, directory: /code/myapp" |
| 34 | + → Spacebot gets/creates an OpenCode server for /code/myapp |
| 35 | + → Creates an HTTP session |
| 36 | + → Sends the task as a prompt |
| 37 | + → Monitors SSE events for progress |
| 38 | + → Tool calls show up as status updates in the channel |
| 39 | + → Result text is returned as WorkerComplete |
| 40 | +``` |
| 41 | + |
| 42 | +The OpenCode worker runs its own agent loop internally. Spacebot monitors it via SSE and translates tool events into status updates visible to the channel. |
| 43 | + |
| 44 | +## Server Pool |
| 45 | + |
| 46 | +OpenCode runs as `opencode serve --port <port>` — a persistent HTTP server per working directory. Spacebot manages a pool of these servers. |
| 47 | + |
| 48 | +**Deterministic ports**: Each directory gets a port derived from its path hash (range 10000-60000). The same directory always maps to the same port. |
| 49 | + |
| 50 | +**Server reattach**: After a Spacebot restart, the pool tries to reconnect to existing OpenCode servers via health check on their deterministic port. If the server is still running, it's reused without spawning a new process. |
| 51 | + |
| 52 | +**Pool limits**: Controlled by `max_servers` (default: 5). When the limit is reached, spawning a worker for a new directory fails. |
| 53 | + |
| 54 | +**Auto-restart**: If a server dies, the pool restarts it automatically (up to `max_restart_retries` times, default: 5). |
| 55 | + |
| 56 | +## Communication Protocol |
| 57 | + |
| 58 | +All communication is localhost HTTP: |
| 59 | + |
| 60 | +| Endpoint | Method | Purpose | |
| 61 | +|----------|--------|---------| |
| 62 | +| `/global/health` | GET | Health check | |
| 63 | +| `/session` | POST | Create session | |
| 64 | +| `/session/{id}/prompt_async` | POST | Send prompt (non-blocking) | |
| 65 | +| `/session/{id}/abort` | POST | Abort session | |
| 66 | +| `/event` | GET | SSE event stream | |
| 67 | +| `/permission/{id}/reply` | POST | Reply to permission request | |
| 68 | +| `/question/{id}/reply` | POST | Reply to question request | |
| 69 | + |
| 70 | +All requests include `?directory=<path>` as a query parameter. |
| 71 | + |
| 72 | +### SSE Events |
| 73 | + |
| 74 | +Spacebot subscribes to the SSE stream and processes: |
| 75 | + |
| 76 | +- **Tool events** — translated to `set_status` updates (e.g. "running: bash", "running: edit") |
| 77 | +- **Session idle** — signals task completion |
| 78 | +- **Session error** — signals failure |
| 79 | +- **Permission asked** — auto-approved (configurable) |
| 80 | +- **Question asked** — auto-selects first option |
| 81 | +- **Retry status** — reports rate limit retries |
| 82 | + |
| 83 | +## OpenCode vs Builtin Workers |
| 84 | + |
| 85 | +| | Builtin Worker | OpenCode Worker | |
| 86 | +|---|---|---| |
| 87 | +| **Agent loop** | Rig agent in-process | OpenCode subprocess | |
| 88 | +| **Tools** | shell, file, exec, set_status, browser | OpenCode's full tool suite (bash, read, edit, glob, grep, write, webfetch, task) | |
| 89 | +| **Context** | Fresh prompt + task | Full OpenCode session with codebase awareness | |
| 90 | +| **Model** | Configured via Spacebot routing | Configured via OpenCode or Spacebot override | |
| 91 | +| **Best for** | One-shot commands, file reads, non-coding tasks | Multi-file refactors, feature implementation, code exploration | |
| 92 | +| **Interactive** | Supported | Supported (same session preserved across follow-ups) | |
| 93 | + |
| 94 | +The channel system prompt includes a decision guide when OpenCode is enabled: |
| 95 | + |
| 96 | +- Need to run a command? Builtin worker. |
| 97 | +- Need to read a file? Builtin worker. |
| 98 | +- Need to write or modify code across multiple files? OpenCode worker. |
| 99 | + |
| 100 | +## Permissions |
| 101 | + |
| 102 | +OpenCode has its own permission system for dangerous operations. Spacebot controls the defaults: |
| 103 | + |
| 104 | +```toml |
| 105 | +[defaults.opencode.permissions] |
| 106 | +edit = "allow" # file editing |
| 107 | +bash = "allow" # shell commands |
| 108 | +webfetch = "allow" # web fetching |
| 109 | +``` |
| 110 | + |
| 111 | +With all permissions set to `"allow"`, OpenCode suppresses most permission prompts. When a permission prompt does fire, Spacebot auto-approves it and emits a `WorkerPermission` event. |
| 112 | + |
| 113 | +These settings are passed to OpenCode via the `OPENCODE_CONFIG_CONTENT` environment variable. LSP and formatter are disabled for headless operation. |
| 114 | + |
| 115 | +## Interactive Sessions |
| 116 | + |
| 117 | +OpenCode workers support the same interactive pattern as builtin workers: |
| 118 | + |
| 119 | +``` |
| 120 | +spawn_worker: task="set up the project", worker_type: opencode, interactive: true, directory: /code/myapp |
| 121 | + → OpenCode creates a session, runs initial task |
| 122 | + → Worker enters WaitingForInput |
| 123 | +route: worker_id=abc, message="now add the database layer" |
| 124 | + → Follow-up sent to the same OpenCode session |
| 125 | + → Context from the first task is preserved |
| 126 | +``` |
| 127 | + |
| 128 | +The OpenCode session accumulates context across follow-ups, so subsequent messages benefit from everything the agent learned during earlier work. |
| 129 | + |
| 130 | +## Model Override |
| 131 | + |
| 132 | +You can override the model used by OpenCode workers: |
| 133 | + |
| 134 | +```toml |
| 135 | +[routing] |
| 136 | +worker = "anthropic/claude-haiku-4.5-20250514" |
| 137 | + |
| 138 | +[routing.task_overrides] |
| 139 | +coding = "anthropic/claude-sonnet-4-20250514" |
| 140 | +``` |
| 141 | + |
| 142 | +When the worker spawns, the routing config determines the model. The model string is split into `provider_id/model_id` and passed to OpenCode's prompt API. |
| 143 | + |
| 144 | +## Full Configuration |
| 145 | + |
| 146 | +```toml |
| 147 | +[defaults.opencode] |
| 148 | +enabled = true |
| 149 | +path = "opencode" # binary path or env:VAR_NAME |
| 150 | +max_servers = 5 # max concurrent OpenCode server processes |
| 151 | +server_startup_timeout_secs = 30 # how long to wait for server health |
| 152 | +max_restart_retries = 5 # auto-restart attempts on server death |
| 153 | + |
| 154 | +[defaults.opencode.permissions] |
| 155 | +edit = "allow" |
| 156 | +bash = "allow" |
| 157 | +webfetch = "allow" |
| 158 | +``` |
| 159 | + |
| 160 | +## Architecture |
| 161 | + |
| 162 | +``` |
| 163 | +┌─────────────┐ HTTP/SSE ┌──────────────────┐ |
| 164 | +│ Spacebot │ ←───────────────→ │ OpenCode Server │ |
| 165 | +│ (worker.rs) │ │ (port 12345) │ |
| 166 | +└─────────────┘ └──────────────────┘ |
| 167 | + │ │ |
| 168 | + │ ProcessEvent::WorkerStatus │ opencode agent loop |
| 169 | + │ ProcessEvent::WorkerComplete │ (bash, edit, read, etc.) |
| 170 | + ↓ ↓ |
| 171 | +┌─────────────┐ ┌──────────────────┐ |
| 172 | +│ Channel │ │ Working Dir │ |
| 173 | +│ (status │ │ /code/myapp │ |
| 174 | +│ block) │ └──────────────────┘ |
| 175 | +└─────────────┘ |
| 176 | +``` |
| 177 | + |
| 178 | +The OpenCode server is a child process managed by Spacebot. It persists across worker invocations for the same directory. Multiple workers targeting the same directory share the same server (different sessions). |
0 commit comments