A tiny OpenCode plugin that gives subagents a voice when run with opencode run --format default. Allows for better manual review when run attended or unattended.
Supports both OpenCode V1 and OpenCode V2 from a single file. The plugin's
default export carries both a V2 { id, setup } shape and a V1 server()
method, so the same file loads on V2 (which reads id/setup) and on V1
>= 1.18.29 (which calls server()). It detects which runtime it's in from
the process argv (V2 loads plugins in the serve process, V1 runs them in the
run process) — not from the @opencode/plugin package, which is V2-only.
Each version's event stream is adapted to the shared reporting core, so output
is identical on both. No external dependencies.
- Streams subagent progress directly to stdout (instead of only showing that a subagent is running)
- Prefixes each line with agent name + instance number (example:
Explore 1:) - Shows tool activity with useful details (file path, command, skill name, etc.)
- Dedupes repeated identical tool calls in a row to reduce terminal noise
- Prints a clear finish marker when a subagent exits
When prompts fan out into multiple subagents, default CLI output can feel opaque. This plugin makes subagent work visible and readable, so you can follow execution in real time without opening the TUI.
Option A — npm package (recommended):
npm i -g opencode-subagent-outputThis does everything automatically:
- Installs the plugin into OpenCode's global discovery directory
(
~/.config/opencode/plugins/), so it loads in every project with no per-project config. (A bare path in the configpluginsarray does not auto-load on V2 — the discovery dir is the documented, reliable mechanism.) - Runs a
postinstallscript that detects your shell (zsh / bash / fish / PowerShell) and installs anopencode()shell function into your rc file (~/.zshrc,~/.bash_profile,~/.config/fish/config.fish, or the PowerShell profile). The function is what makes--pipe-subagentswork — see How the shell function works.
Then reload your shell:
source ~/.zshrc # or restart your terminalNow you can run:
opencode run "your prompt" --pipe-subagentsOption B — local file (no npm):
- Put the plugin file at
~/.config/opencode/plugins/subagent-reporter.ts(V2 discoversplugins/; V1 also readsplugin/). The plugin has no external dependencies, so nothing else needs installing. - Manually add the shell function to your rc file (copy the block shown in
How the shell function works), replacing
<package-dir>with the directory that contains your copy ofscripts/opencode-pipe-subagents.sh.
npm rm -g opencode-subagent-outputThe preuninstall hook removes the shell-function block from your rc file and
the plugin from the global discovery dir (~/.config/opencode/plugins/), then
you just reload your shell.
To remove just the shell function and plugin (keeping the package), run:
node <package-dir>/scripts/install.js uninstallThe postinstall script appends a marker-delimited block to your rc file:
# >>> opencode-subagent-output >>>
opencode() {
local a
for a in "$@"; do
if [ "$a" = "--pipe-subagents" ]; then
exec env OPENCODE_BIN="$(command -v opencode 2>/dev/null || echo "$HOME/.opencode/bin/opencode")" \
bash "<package-dir>/scripts/opencode-pipe-subagents.sh" "$@"
fi
done
command opencode "$@"
}
# <<< opencode-subagent-output <<<It is a function, not an alias — an alias can't inspect its arguments. The
function scans the args; if --pipe-subagents is present it execs the
wrapper script, otherwise it falls through to the real binary with
command opencode (the command builtin skips the function, so there's no
recursion). Re-running the installer is idempotent — it removes any existing
block (including legacy markers) before appending a fresh one, so updates never
duplicate it.
This is the same interception pattern the opentmux plugin uses, except the
arg-inspection lives in the shell function rather than a wrapper binary, so a
plain opencode call has zero overhead.
V2 changed the process model: opencode run is a thin client to a separate
server process, and the plugin loads in that server. The server's stdout is
/dev/null, so the plugin cannot write straight to your terminal the way it
could on V1. The --pipe-subagents wrapper solves this:
- Strips
--pipe-subagentsfrom the args. - Runs with
--standaloneso the server is a child of the run and deterministically inheritsSUBAGENT_REPORTER_OUT(the persistent background daemon would not). - Points the plugin at a temp file (
SUBAGENT_REPORTER_OUT) — the one transport that works from the server process, with no--print-logsand no server log noise. - Tails that file onto stdout in parallel with the run's own transcript.
- Cleans up and propagates the exit code.
It auto-detects --format json and emits NDJSON {"type":"subagent",...}
events. Without --pipe-subagents the wrapper is a transparent pass-through to
opencode run.
If opencode is not on the wrapper's PATH, set OPENCODE_BIN=/path/to/opencode.
You can drive the plugin directly with environment variables:
# Plain text, via the server's stderr (needs --standalone --print-logs):
SUBAGENT_REPORTER_ENABLED=1 opencode run "your prompt" \
--format default --standalone --print-logs 2>&1 | grep -vE '^timestamp='
# Or to a file (use --standalone so the server inherits the env var):
SUBAGENT_REPORTER_ENABLED=1 SUBAGENT_REPORTER_OUT=/tmp/subagents.txt \
opencode run "your prompt" --format default --standalone
# then: cat /tmp/subagents.txtSUBAGENT_REPORTER_ENABLED=1— required on V2. The server process is shared with the TUI and cannot tellrunmode from TUI mode, so reporting is opt-in. (On V1 it is optional:runmode is auto-detected.)SUBAGENT_REPORTER_OUT=<file>— write report lines to a file. The cleanest V2 transport (no log noise). This is what the wrapper uses.- Without a file, V2 writes to stderr, which only reaches the terminal under
--standalone --print-logs(and that flag also dumps the server's own log lines — filter withgrep -vE '^timestamp='). - For
--format json, setSUBAGENT_REPORTER_FORMAT=json— the server process cannot see the client's--formatflag (the wrapper does this for you).
SUBAGENT_REPORTER_FORMAT— forcejsonordefaultoutput. On V1 this is auto-detected from--format json; on V2 the server process cannot see the client flag, so set it explicitly for JSON output.SUBAGENT_REPORTER_ENABLED— force1/true/onto always report, or0/false/offto silence. On V1 it defaults to "report inopencode runmode only, silent in the TUI". On V2 it defaults to off (opt-in required).SUBAGENT_REPORTER_OUT— path to a file to append report lines to (V2). Takes priority over stdout/stderr. Used by the--pipe-subagentswrapper.
Explore 1: -> read /repo/src/agent/task.ts
Explore 1: -> grep message.part.updated
Explore 1: Found the relevant event handler in run.ts
Design 1: -> bash npm test
**** [DESIGN FINISHED] ****
Explore 1: Summarized findings and returned result
**** [EXPLORE FINISHED] ****