@dougbots/avenor-opencode
OpenCode plugin that registers avenor agent runs as tools in your OpenCode session. When you need a sub-agent to handle a well-defined task — write code, run tests, search a codebase — avenor operates in the background while you keep your session focused.
Setup
Install the package alongside its peer dependency:
bun add @dougbots/avenor-opencode @opencode-ai/[email protected]
OpenCode 2 additionally loads the plugin definition shape directly from the default export, so no OpenCode 2 package is needed at runtime; OpenCode 1 hosts (1.18.29+) call the same entrypoint's server().
You also need the avenor binary. Either add it to your PATH or set the AVENOR_BIN environment variable to its location.
# Option 1 — add to PATH (e.g. via mise, brew, or manual install)
which avenor
# Option 2 — explicit path
export AVENOR_BIN=~/.botfiles/bin/avenor
Register the plugin
Add @dougbots/avenor-opencode to your OpenCode configuration. If you use a global config:
// ~/.opencode.json
{
"plugins": ["@dougbots/avenor-opencode"]
}
For a project-level config, add the same entry to .opencode.json in your repo root.
Once registered, the plugin exposes these tools that your OpenCode agent can call directly — no extra configuration required: avenor_spawn, avenor_status, avenor_result, avenor_inspect, avenor_answer_permission, avenor_message, avenor_peers, avenor_events, avenor_kill, avenor_shutdown, and avenor_workflow.
Behaviour
When a run is dispatched with avenor_spawn, the plugin:
- Blocks by default — the tool call stays open, updating its title and metadata as the sub-agent runs, exactly like OpenCode's own sub-agent tool calls. The tool returns when the run reaches a terminal state.
- Re-prompts on completion — when a
wait=false(fire-and-forget) run finishes, the plugin automatically injects a completion message into the orchestrating session so the LLM picks up without manual polling. - Routes permissions — when a sub-agent running under the
opencode-httpbackend requests a permission, the plugin injects a re-prompt into the orchestrating session. The LLM can callavenor_answer_permissionto respond; the normal permission dialog is also shown as a fallback. - Formats results — OpenCode returns concise, bounded prose in its shared
outputforavenor_status,avenor_result,avenor_answer_permission,avenor_message,avenor_peers,avenor_events,avenor_inspect, andavenor_shutdown. Structured metadata stays in host/session state. - Publishes its own finished turn — each conversation publishes its settled turns as its own bound run through the broker's peer last-message route, in per-conversation order, so a peer's
avenor_peersaction: "last"reads the conversation it names. - Receives messages — a bound conversation runs one
avenor feed brokerconsumer, authenticated as the conversation's own run, and each feed record arrives as a prompt marked as another session's words, one record at a time in arrival order. The native title publishes as the run's harness name, so peers see the conversation's own name; with no native title the conversation takesopencode-<repo>from its directory, with the same hostname and run-id-tail rule the Pi extension uses. See messaging.
MCP clients render their own results.
Tools
avenor_spawn
Dispatch an agent run in the current working directory.
Blocking mode (default, wait=true): the tool call shows live progress updates and returns a structured result when the run finishes.
Fire-and-forget (wait=false): returns immediately with run_id. The orchestrating session is re-prompted automatically on completion.
| Argument | Required | Description |
|---|---|---|
agent | no | Agent name; omission uses the supplied model or runtime defaults |
prompt | no | Prompt text |
prompt_file | no | Path to a prompt file |
label | no | Human-readable label for the run |
timeout | no | Timeout duration (e.g. 3600s, 30m) |
model | no | Model override |
thinking | no | Run-level reasoning control; unsupported effective backends reject explicit values |
backend | no | Backend override; direct mode defaults to Avenor's opencode-acp backend when omitted |
roster_file | no | Path to a top-level roster map |
roster_entry | no | Key within roster_file |
wait | no | Block until complete (default true) |
supervisor_id | no | Reuse an existing supervisor by socket path |
The spawn's parent route is the conversation's own bound run, so it is passed only when the spawn is routed to the supervisor holding that run — the default singleton, or the same socket named explicitly. A spawn pointed at another supervisor still runs there, without a parent route.
The working directory (dir) is injected automatically from your OpenCode session context — it is not a user-facing argument.
Direct and roster modes
Use roster_file plus roster_entry to select a complete identity. Every roster entry requires backend and at least one of agent or model; roster mode rejects direct agent, model, and backend overrides. Direct mode allows all three identity fields to be omitted independently, including backend-only selection. Roster mode does not apply the direct opencode-acp default.
{
"prompt": "Review the repository",
"roster_file": "/repo/roster.json",
"roster_entry": "reviewer",
"wait": true
}
Roster entries carry backend, agent, model, and an optional thinking default; system is rejected. An explicit run-level thinking wins over the entry's, and is validated against the effective backend. A resumed session cannot change backend. The plugin passes roster_file as supplied, so an absolute path avoids ambiguity when the OpenCode process directory differs from the project directory.
avenor_status
Get status of a specific run or all active runs. Use the compact lifecycle view for progress and pending permission checks; full remains the compatibility default.
| Argument | Required | Description |
|---|---|---|
run_id | no | Specific run ID to query (omit for all runs) |
view | no | Response detail: lifecycle or full (default) |
supervisor_id | no | Reuse an existing supervisor by socket path |
avenor_result
Wait for a run to finish and return its complete final output without transcript or event details. This is the output retrieval tool; avenor_status remains a lifecycle check.
| Argument | Required | Description |
|---|---|---|
run_id | yes | Run ID to await |
wait | no | Wait for a terminal result (default true) |
timeout | no | Maximum time to wait (for example 30s or 5m) |
supervisor_id | no | Reuse an existing supervisor by socket path |
A blocked run returns its pending permission instead of waiting forever. If this tool's own timeout expires, it returns the latest status with ready: false and timed_out: true.
avenor_inspect
Use avenor_inspect to review a bounded snapshot. The snapshot includes transcript, completed and live tools, permissions, and final output. Use avenor_events when raw event records are required.
| Argument | Required | Description |
|---|---|---|
run_id | yes | Run ID to inspect |
limit | no | Max events to reduce into the snapshot |
after_seq | no | Only include events after this sequence number |
supervisor_id | no | Reuse an existing supervisor by socket path |
avenor_answer_permission
Answer a pending permission request. The option_id comes from the pending_permission.options array returned by avenor_status.
| Argument | Required | Description |
|---|---|---|
run_id | yes | Run ID with the pending permission |
option_id | yes | Option ID from pending_permission.options |
request_id | no | Request ID (auto-discovered if omitted) |
supervisor_id | no | Reuse an existing supervisor by socket path |
avenor_message
Send a message to another run, withdraw a pending ask, resume a completed run, or deliver a control reply. Every action is explicit and every result states whether a failed ask can still be withdrawn.
action | Required fields | What it does |
|---|---|---|
tell | target, message | Deliver a message without creating an ask edge |
ask | target, message, optional timeout_ms | Deliver a message and wait for a reply |
reply | reply_to, message | Answer an inbound message. An ask this surface received is answered through its claim; a tell is routed back to its sender by the broker. A message id with no route reports edge-absent |
resume | target, message, optional label, idempotency_key | Resume a completed run from its persisted session |
withdraw | message_id | Withdraw a pending ask |
control_reply | target, optional payload | Deliver a control reply where the provider supports it |
target is a run id, a peer label, or "self". timeout_ms must be an integer no greater than the shared ask cap. This tool does not take supervisor_id; it is bound to the default supervisor.
A conversation speaks as its own bound run, not the supervisor: tell, ask, reply and withdraw route as the conversation, resume stays owner-only, and control_reply has no supported surface here. A reply is answered through the feed's claim store — an ask this conversation's feed delivered is answerable, a second reply reports edge-absent.
The tool's structured metadata is the shared contract object in snake_case (action, outcome, text, and message_id, run_id, label, reply, sentinel_file, on_event when the outcome carries them), the same fields the MCP hosts return.
avenor_peers
List the visible agent sessions, rename a run, or read a run's last agent message.
action | Fields | What it does |
|---|---|---|
list (default) | — | List the visible peers |
rename | name, optional session_id | Change a display name, self by default |
last | target | Read the target's last agent message with its stop reason |
last is read-only and returns the shared empty result when the target has no completed turn yet. target accepts a run id, a label, or "self". The structured metadata is {peers}, {renamed} or {last} in snake_case, the same object the MCP hosts return.
avenor_events
Read events from a run, optionally filtered by type. Returns the last N events.
| Argument | Required | Description |
|---|---|---|
run_id | yes | Run ID to read events from |
types | no | Filter by event type (array of strings) |
limit | no | Max events to return (default 50) |
supervisor_id | no | Reuse an existing supervisor by socket path |
avenor_kill
Terminate a run without shutting down the supervisor. Other runs and the supervisor itself keep running.
| Argument | Required | Description |
|---|---|---|
run_id | yes | Run ID or label of the run to terminate |
supervisor_id | no | Supervisor ID for multi-supervisor mode |
avenor_workflow
Drive a durable workflow instance. One action selects the verb; every action takes an optional supervisor_id for multi-supervisor mode. Read-only actions need no permission; gate, complete and cancel keep their authority checks.
| Action | Arguments | Purpose |
|---|---|---|
status | workflow_id | Lightweight instance status |
inspect | workflow_id | Full instance detail |
events | workflow_id, after_seq, limit | The instance's log events |
wait | workflow_id, timeout | Wait for a terminal state or a timeout |
ready | controller_id, limit | Claimable auto-dispatch candidates; grants no lease |
controller_status | controller_id | One controller's status with an advisory candidate count, or a summary list without it |
gate | workflow_id, node_id, gate_id, activation_id, operation, and the operation's fields | Record a gate decision on a parked awaiting_gate activation |
complete | workflow_id, node_id, activation_id, attempt_id, lease_id, owner_token, outcome, outputs, artifacts | Complete a machine/external handoff activation |
cancel | workflow_id, actor, reason | Cancel the whole instance (terminal canceled) |
The underlying control methods are in docs/control-protocol.md.
avenor_shutdown
Shut down the avenor supervisor process and clean up temp files. Call this when you are done delegating work or if the supervisor has crashed.
| Argument | Required | Description |
|---|---|---|
supervisor_id | no | Supervisor to shut down (defaults to the singleton) |
force | no | Force shutdown instead of graceful |
A shutdown names one supervisor. Naming another one leaves this plugin's bound conversations, their feeds and their credentials alone; only a shutdown of the supervisor a conversation is bound to drops it.
Typical workflows
Blocking (default):
1. avenor_spawn → tool call shows live progress, blocks until done
2. (avenor_answer_permission → if a permission is routed to this session mid-run)
3. tool call returns → completion preview with status + session_id
4. avenor_result → retrieve the complete final output when needed
5. avenor_message → optionally iterate with action "resume"
6. avenor_shutdown → clean up when finished
Parallel / fire-and-forget (wait=false):
1. avenor_spawn × N → each returns run_id immediately
2. plugin starts monitoring each run immediately
3. (sub-agent finishes) → plugin re-prompts this session automatically
4. avenor_answer_permission → if a permission was routed here mid-run
5. avenor_result → wait for and retrieve the final output
6. avenor_events → inspect raw event history when needed
Dependencies
- Peer:
@opencode-ai/pluginv1.15.3 (the runtime that host agents use to mount plugins) - Build-time:
@opencode/plugin(type definitions for the OpenCode 2 loader only; the built entrypoint has no runtime reference to it, so neither generation installs it) - Binary:
avenormust be available onPATHor atAVENOR_BIN(checked at first tool invocation, not at plugin load time)