New:Introducing Socket Scanning for VS Code Marketplace Extensions.Learn more →
Get Started

opencode-courier

Package Overview
Dependencies
Maintainers
1
Versions
17
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

opencode-courier

Multi-session orchestration for OpenCode V2: spawn child sessions, be woken by them. No polling.

Source
npmnpm
Version
0.4.0
Version published
Weekly downloads
482
-76.07%
Maintainers
1
Weekly downloads
 
Created
Source

opencode-courier

Your OpenCode session hands work to other sessions, ends its turn, and is woken when they report. Multi-session orchestration for OpenCode V2: spawn child sessions, be woken by them. No polling.

CI Quality Gate Coverage Security Rating Maintainability Rating npm Socket Badge License: MIT Supported OpenCode V2 version

A parent spawns two slow children, writes a README itself, and is woken by each report

A parent writes a README while two slow children work, and each report wakes it (the prompt).

Quickstart

npm install -g @opencode/cli@2.0.28   # OpenCode V2, at the version courier is tested on
opencode plugin add opencode-courier  # by name, so OpenCode offers new releases
opencode                              # in any folder

Paste this prompt:

Start two helper sessions with courier_spawn and have each report back to you. One runs `sleep 20; echo $((17 * 23))`, the other `sleep 40; echo $((2 ** 10))`. Do not run the commands yourself. When both have reported, reply with one line: RESULTS <first> <second>

The parent spawns both children and ends its turn. Within a minute the first report starts a new turn on its own, about 20 seconds later the second does, and the parent replies RESULTS 391 1024. If OpenCode asks to allow a child's command, the parent passes the question to you. Free models sometimes stall on a request; if nothing moves for a few minutes, send the prompt again. Next: Using it.

OpenCode V2 native

Courier is built on OpenCode V2's plugin API (@opencode/plugin), not the V1 one (@opencode-ai/plugin), and needs OpenCode V2. Each release is pinned to one V2 version and tested against it end to end; the current one is tested on OpenCode 2.0.28. CI also runs the live suite on the newest OpenCode release, so a host release that breaks the plugin shows up there first. Older releases and their versions: Supported OpenCode version.

Courier or the built-in subagent tool?

OpenCode V2 has its own subagent tool: it starts an agent with fresh context in a child session, and the parent either waits for its final answer or, with background: true, carries on and is notified when the child finishes; passing the child's sessionID back prompts it again. Use it when one result back is all you need: a search, a review, a self-contained task that needs nothing from you on the way. Use courier when the work needs more:

  • Messages from the child, mid-run: a child reports progress, or asks its parent something, whenever it likes with courier_send, and any session can message any other.
  • Waking an idle session from anywhere: a message from a child, a schedule (courier_later) or a webhook (GitHub reviews, comments and CI runs, or any signed POST) starts a new turn in a session that has ended its own.
  • Worktree isolation: isolate: true gives a child its own git worktree, so children can edit files in parallel; each commits on courier/<its session ID> and reports that branch for its parent to merge (nested isolation).
  • Relaying to you: a child's permission requests and questions reach you through the parent, and your answers go back; a form only you can answer is pointed out to you.

How it works

A parent session calls courier_spawn, gets a session id back immediately and ends its turn. The child works on its own and, when it is done or stuck, calls courier_send with the parent's id, a status and what it produced. That message lands in the parent's inbox and OpenCode starts a new turn for the parent if it is idle. There is no polling anywhere: a message is admitted into the target session's inbox through OpenCode's own API, the way OpenCode's background subagents report to their parent. Delivery is steer by default (injected into the target's running turn, or starts one if idle); queue: true waits until the current turn ends.

Demos

The parent does not wait. Prompt: "Spawn two children: one runs sleep 30 then lists the exports of math.js, the other runs sleep 45 then lists the exports of text.js. Don't wait for them: meanwhile write a short README.md for this folder yourself, and add their reports to it when they arrive." The recording at the top is this run: the parent writes its README and finishes its turn 16 seconds in, while both children still sleep; each report (the Message from ses_… line) then starts a new turn on its own, and the parent folds it into the README.

A child's question reaches you. Prompt: "Spawn a child session to add a multiply function to math.js. It must ask me first whether to name it multiply, times or product."

A child's question opens as a form in the parent's session, times is picked, and the child carries on

The child's question opens in the parent's session as OpenCode's own question form, times is picked, and the child adds times to math.js and reports. Both recordings are real runs with a free model on OpenCode Zen (Muse Spark 1.3).

Recursive orchestration

A session can split a goal across child sessions, each child deciding in turn whether to split its part again, with the results checked and integrated at every level on the way back up. The plugin ships the playbook as an OpenCode skill, courier-orchestrate, listed in every session: models are told to load it at the start of an implementation task that may have independent parts, and /skills in the TUI attaches it to your prompt. What it says: the reference.

Install

Supported OpenCode version

Requires OpenCode V2: the opencode command from @opencode/cli (also installed as opencode2). Each release is built and tested against exactly one OpenCode V2 version; a row lists the releases pinned to its version. OpenCode never checks it: the plugin installs and loads on any V2 host, and on a version other than the pinned one writes one line to the server log naming both (more).

opencode-courierOpenCode V2 (opencode and @opencode/plugin)
0.4.02.0.28
0.2.3, 0.3.02.0.26
0.2.22.0.24
0.2.12.0.23
0.2.02.0.22
0.1.60.0.0-beta-19271 (the beta line: opencode2 from @opencode-ai/cli, and @opencode-ai/plugin)

The terminal part relies on the host to provide its UI modules and is tested on OpenCode 2.0.28 only; the server part works as before on older V2 hosts from 2.0.4 on.

Check yours with opencode --version, and install the matching CLI with:

npm install -g @opencode/cli@2.0.28

The plugin

opencode plugin add opencode-courier

This installs the package from npm and adds "opencode-courier" to plugins in the global configuration (~/.config/opencode/opencode.json); its options (session trees, webhooks) go in the object form of that entry. Install it by name, without a version: OpenCode only checks plugins for updates when their entry is not an exact version, so opencode-courier@0.2.1 is never offered a newer release (moving such an entry).

From a local clone, bun install && npm run build, then list its dist directory in opencode.json (V2 uses plugins, plural; a path to a file is ignored with a warning):

{
  "plugins": ["/absolute/path/to/opencode-courier/dist"]
}

Using it

  • Keep the background server running so sessions can be woken while you are away (opencode service start), and run one OpenCode server per data directory where you can: a second one, such as opencode serve next to it, cannot pass your answer on to a child the other runs (why).
  • Give the agents that run children the permissions their work needs: a child that hits an approval prompt waits until you answer it through its parent. Let them use the question tool if they should be able to ask you. Choose a web search provider before children search the web (run one web search in your own session, or use OpenCode's "Third-party search" setting), or the first child to search shows that prompt in its own session, which only you can answer there.
  • Your own session's prompts are relayed nowhere, and each one stalls the whole tree until you answer it. Before an unattended run, allow the orchestrating session's toolchain up front: one rule on bun run * or npm run * (the skill has checks run through the project's scripts), git, and whatever else its checks need.
  • Use isolate: true whenever children edit files in parallel. The worktree is made from the last commit (from an isolated session, its HEAD), so an uncommitted opencode.json is not there: keep providers and models in the global config, or commit the file. A worktree is kept until you courier_cleanup it, which refuses to drop uncommitted changes or unbranched commits unless told to.
  • For a long-running child, schedule a courier_later check-in for yourself and courier_cancel it when the child reports: it covers what the plugin cannot see, such as a server that stops while a child runs.

Tools

ToolDoes
courier_spawnStarts a session on the parent's model with the task and a brief, and returns at once; isolate: true gives it its own git worktree, group holds its report with its siblings'.
courier_sendMessages a session, waking it if idle; from a child to its parent, a status and artifacts make it the child's report.
courier_statusOne look at a session: outcome, idle time, last reply and what it waits on. For check-ins, not for waiting.
courier_childrenLists the sessions this one (or a given one) started, with their state, worktree and join group.
courier_treeLists the whole tree under a session, depth first, with each one's depth and report state.
courier_stopInterrupts a session below you and everything under it, and cancels their scheduled messages; cleanup: true removes their worktrees too.
courier_cleanupRemoves an isolated child's worktree, unless it holds unsaved work, and forgets the child.
courier_answerPasses your answer to a permission request or a question that a session under this one waits on.
courier_laterSchedules a message for a session (this one by default) in delayMinutes or at a time.
courier_cancelDrops a message scheduled with courier_later.
courier_subscribeSubscribes a session to webhook deliveries for a topic (owner/repo, owner/repo#12 or a name).
courier_unsubscribeDrops one topic, or all of a session's.

Each tool's parameters: the reference.

How children reach you

  • A child reports once, with a status: done, partial, blocked (it needs a decision from its parent, and says what) or failed, with its artifacts (branch, commits, files, checks) listed in the message. A message without a status is progress, not the report. More.
  • A join group wakes you once. Start a batch of sessions with the same group name, and their done, partial and failed reports are held until the last one is in and your turn has ended, then delivered in one message. A blocked report, a failure, a question or a permission request still reaches you at once. A member that will not report holds the others back: drop it with courier_cleanup, and the group is released without it. More.
  • A child that fails, or ends its turn without reporting, is reported to its parent by the plugin, with the error or its last reply, waking it if idle; one that waits on a request, its own children, a scheduled message or a webhook is not, nor is one that answers what you typed in its session. More.
  • Permission requests and questions reach you. A child's request or question is passed to the session at the top of its tree, any number of levels down; that session asks you and passes your answer back, or links its own question to the child's so your pick goes straight to it. Whichever answer comes first counts, and the other is withdrawn. More.
  • Some forms only you can answer, such as OpenCode's web search asking for a provider: the top session is told, and you answer in the child's session. More.
  • You can see and stop a whole subtree with courier_tree and courier_stop, or in the terminal. More.

Children, held reports, scheduled messages and open questions survive a compaction or a restart; entries are dropped after 14 days (roster).

Session trees

A spawned session may start sessions of its own, so the sessions form a tree, bounded by three options in the plugin's options, in the global config:

{
  "plugins": [{ "package": "opencode-courier", "options": { "maxDepth": 2, "maxChildren": 3 } }]
}
OptionDefault
maxDepth3How deep the tree goes: your session is depth 0, the sessions it starts depth 1. A session at maxDepth cannot start sessions.
maxChildren5How many sessions one session started may have live at once.
maxTotal20How many spawned sessions may be live at once in one tree.

courier_spawn refuses a spawn past a limit, naming it and saying what to do instead: do the work itself, or wait for a child to report. A session counts while it is live: its turn runs, it owes its parent a report (its task or a message reached it after it last reported, failed or was interrupted), or a session below it is live. One that has reported, with nothing live below it, does not. Each spawned session's brief says when to split its task and when to do it itself (more).

In the terminal

The package ships a part for OpenCode's terminal UI, which the TUI loads from the server's plugin list; the server half behaves the same whether or not a TUI is attached. In a session that started sessions, the prompt's footer shows what the tree under it is doing: how many run, wait on you or are idle, and for each join group how many of its members have reported:

courier 2 running · 1 waiting on you · group pair 1/2

/courier tree opens a panel beside the session with the tree under it, a row per session indented by depth, with what it does, its report and its group's standing; a session that started others gets a bar of the parts reported, a leaf how long it ran, its last tool call and its cost. ↑/↓ select, enter opens the session, s stops it and everything under it after you confirm, as courier_stop would, and q closes the panel; /courier stop <session> stops from the prompt.

ctrl+x k (the leader key, then k), /asks, the palette's "Courier: what waits on you" or a click on "waiting on you" opens the asks inbox: every permission request, question and form waiting anywhere in the tree, oldest first, with where it comes from, answered in a dialog with no model in between; the session that started the tree hears once that you settled it. The TUI plays OpenCode's sounds for a child's report, a group's release and a new ask (more).

Native child sessions

Off by default. With "nativeChildren": true in the plugin's options, a child started without isolate is also created with OpenCode's own parentID, so OpenCode treats it as the parent's subagent: the TUI's view of the top session lists and answers the permission requests and forms of every session under it, and the paired web, desktop or mobile app's request dock answers a grandchild's ask from the top session, on a phone too. Isolated children are created as before, and the courier's own tree, limits and relays work as without the option. What changes (more):

  • Deleting a session deletes its native children, as OpenCode does with subagents.
  • A child does not take its parent's session rules: courier passes it an empty ruleset, so it may do what it could without the option.
  • Providers see a subagent: the child's model requests carry its parent's id for cache routing and two headers naming the parent; on GitHub Copilot, which meters such requests differently from a person's, courier was not tested.
  • A child's asks show in the top session's view, not in its own, and it leaves the TUI's session lists. Its permission requests and questions are answered once, in OpenCode's own prompt there; the top session's model is only told. opencode run and the TUI's auto-accept mode answer them as their own subagents'.
  • Turn it on only with OpenCode's TUI or the paired app, which show those prompts; with a bare API client nobody prompts you, and only courier_answer is left.

Webhooks

GitHub reviews, comments, pull requests and completed CI runs, or anything signed posted to /hook/<name>, can wake a session. The receiver is off unless the plugin has a webhook option, in the global config, since there is one receiver per OpenCode server ("webhook": true takes every default):

{
  "plugins": [
    {
      "package": "opencode-courier",
      "options": { "webhook": { "port": 4097, "secretFile": "~/.config/opencode/courier-webhook-secret" } }
    }
  ]
}
  • Make the secret with openssl rand -hex 32 > ~/.config/opencode/courier-webhook-secret and chmod 600 it. It is never read from opencode.json (a secret key is refused), so the config can be committed; without a usable secret the receiver does not start, and the server log says why.
  • On GitHub, add a webhook to the repository (Settings → Webhooks) with content type application/json, the same secret, the events you want, and <public URL>/github as the payload URL, forwarded to 127.0.0.1:4097 by a tunnel you trust (smee, cloudflared, ngrok). Only signed deliveries are acted on.
  • courier_subscribe a session to owner/repo, owner/repo#N or <name>: each matching delivery arrives as a short summary marked as outside text, queued behind any running turn and waking the session if idle.

The options, which events wake whom, the tunnels, the signing scheme and status codes: the reference.

Development

Commands, the suites and CI: docs/maintainers/development.md. How the code is laid out: architecture.md; what the plugin API does that courier works around: plugin-api-notes.md; releases: releasing.md.

Contributing

Questions and bug reports go to the issues. Pull requests are welcome: a change in behaviour comes with a unit test, an e2e/run.sh scenario and its line in this README or docs/reference.md, and CI must be green.

License

MIT, see LICENSE.

Keywords

opencode

FAQs

Package last updated on 13 Oct 2026

Related posts