OpenCode as an iii worker: the OpenCode API exposed as functions and streams on the iii bus, nothing else. The worker spawns the same opencode CLI the user runs in their terminal, with the same login, filesystem, and tools. opencode::run executes one headless turn (opencode run --format json) and returns the result with token usage and cost; the raw JSON events mirror verbatim onto the opencode::events stream, and a translated AgentEvent view lands on agent::events, so the iii console, the acp worker, and any sibling worker observe an OpenCode run exactly like a native harness turn. The worker also registers run::start_and_wait, the same entrypoint the console and acp worker drive.
iii worker add opencodeRequires the opencode CLI on the host (npm i -g opencode-ai or the official installer) and an API key for the LLM provider you use — ANTHROPIC_API_KEY (or any provider key) in the worker environment, or an existing opencode auth login.
npx skills add iii-hq/workers --skill opencodecurl -fsSL https://install.iii.dev/iii/main/install.sh | sh
iii worker add opencode
iii # starts the engine + workerThen drive it from the console chat, iii trigger, or any SDK:
import { registerWorker } from 'iii-sdk';
const iii = registerWorker('ws://127.0.0.1:49134', { workerName: 'demo' });
const res = await iii.trigger({
function_id: 'opencode::run',
payload: {
prompt: 'Add a /health endpoint to server.ts and run the tests',
cwd: '/path/to/repo',
model: 'anthropic/claude-sonnet-4-5',
},
timeout_ms: 600_000,
});
// { session_id, opencode_session_id, result, stop_reason, usage, total_cost_usd }Or from the terminal:
# one full turn (raise the timeout; the default 30s is too short for agent turns)
iii trigger opencode::run --timeout-ms 600000 \
--json '{"prompt":"add a /health endpoint and run the tests","cwd":"/path/to/repo"}'
# quick reads
iii trigger opencode::sessions::list
iii trigger opencode::status session_id=<session_id>
# background turn + control
iii trigger opencode::start --json '{"prompt":"...","cwd":"/path/to/repo"}'
iii trigger opencode::stop session_id=<session_id>
iii trigger opencode::run --helpA turn from the CLI returns the result with token usage and cost, and opencode::run --help prints the published request schema as a parameter table:
Resume uses the iii session_id, not opencode_session_id. Two ids come back from every run: session_id (a UUID) is the iii key for resume / status / stop and the stream group; opencode_session_id (ses_...) is OpenCode's own id, returned for reference only. To continue a conversation, pass the same iii session_id again. The worker looks up the stored OpenCode session for that key and resumes with --session:
Long turns: use opencode::start to return immediately, then watch agent::events (group_id = session_id) for message_complete, function_execution_start/end, and turn_end. opencode::stop interrupts a live run.
| Function | Purpose |
|---|---|
opencode::run |
Run one turn, wait, return the final result + usage + cost |
opencode::start |
Fire-and-forget turn; progress arrives on agent::events |
opencode::stop |
Interrupt a live run |
opencode::status |
Session state, live flag, usage, cost |
opencode::sessions::list |
All sessions this worker has run |
run::start_and_wait |
Alias for opencode::run under the shared agent entrypoint |
opencode::run accepts a bare prompt or a messages array ([{ role: 'user', content: [{ type: 'text', text }] }]), plus model (provider/model), cwd, agent, and iii_context overrides.
Every line OpenCode emits under --format json is mirrored verbatim onto opencode::events, group_id = session_id:
| OpenCode event | agent::events |
|---|---|
text |
message_complete |
tool_use (completed) |
function_execution_start + function_execution_end |
step_finish |
token usage + cost, surfaced on agent_end and the return value |
| stream end | turn_end + agent_end |
OpenCode reports per-step tokens (input / output / reasoning / cache read+write) and cost inline in the event stream, so opencode::run returns usage + total_cost_usd with no extra bookkeeping.
By default every turn's prompt carries the iii runtime context: the engine-grounded discovery rules retargeted to the iii CLI, which OpenCode reaches through its own shell tool. The agent discovers capabilities from the live engine (engine::functions::list, iii trigger <fn> --help, the registry flow) instead of memory. Local file edits stay on OpenCode's native tools; backend actions go through registered functions. The context is prepended on a fresh session and skipped on resume; turn it off with "iii_context": false per call or in config.yaml.
OpenCode answers by querying the live engine itself, grouping every connected worker by runtime:
engine_url: ws://127.0.0.1:49134
defaults:
model: "" # empty = OpenCode default; else "provider/model"
cwd: "" # default working directory for runs
agent: "" # OpenCode agent; empty = default
events_stream: agent::events # translated AgentEvent frames
raw_events_stream: opencode::events # verbatim OpenCode JSON events
iii_context: true # prepend the iii runtime context on a fresh session
opencode_executable: "" # path to the opencode CLI; empty = resolve on PATHconfig.yaml is the seed: on first boot the worker registers it with the built-in configuration worker as the initial value, then reads the live value back and hot-reloads on configuration:updated. engine_url stays on the local seed (bootstrap).
| OpenCode | iii |
|---|---|
opencode run --format json turn |
opencode::run invocation |
| each JSON event, verbatim | opencode::events stream frame |
text part |
message_complete frame on agent::events |
tool_use part |
function_execution_start / function_execution_end frames |
step_finish tokens + cost |
usage + total_cost_usd on the result + agent_end |
session resume (--session ses_...) |
engine state scope opencode_sessions, keyed by iii session_id |
| extra capability | another iii worker on the bus (shell, database, storage, ...) |



