cristoslc/dispatch-opencode-skill · Archived

dispatch-opencode

Dispatch subagent tasks through opencode (https://opencode.ai) using the async .subagents/ lock-watch protocol. The agent writes a plan YAML, calls run-plan.sh to validate and dispatch, polls lockfiles on its own interval, reads FINAL_OUTPUT.md on completion, and calls subagent-cleanup.sh or subagent-abandon.sh to tear down. Use when you want to hand work off to a background subagent instead of doing it yourself — for example, dispatching a subagent into a worktree, parallelizing independent ta…

First seen Apr 30, 2026

Installation

$ npx skills add cristoslc/dispatch-opencode-skill --skill dispatch-opencode

Stronger alternatives

This repository is archived — consider an actively maintained alternative.

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Declared

Repository health

Stars 1
License MIT
Default branch main
Open issues 2
Status Archived

Skill metadata

Parsed from SKILL.md frontmatter.

Version2.0.0
LicenseMIT
CompatibilityRequires opencode CLI (https://opencode.ai), git, and a running
Declared agents opencode
More metadata
version
2.0.0
status
active
author
cristoslc
spokes
{"0":"path: references\/troubleshooting.md","trigger":"session not found, blank FINAL_OUTPUT.md, dispatch skipped, timeout, stuck, exit code 3, exit code 2, branch not found, orphaned symlink, worktree symlink, prompt file resolves"}

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 20,742 B
  • docs SUMMARY.md 717 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 11 installs

SKILL.md

dispatch-opencode

Routes subagent dispatch through opencode using async file-based signaling. The skill does NOT implement ACP (Agent Client Protocol) or HTTP serve mode — those were dropped per ADR-001. Instead, it uses CLI opencode run as the invocation transport, wrapped in a .subagents/ lock-watch protocol.

For previous versions of this skill (ACP/CLI/HTTP), see ADR-001.

When to use

Primary pattern: two-call dispatch

Use the two-call pattern when the agent is actively running and can poll:

  • The task takes long enough that the agent should not block (refactors,

dependency upgrades, research sprints).

  • The agent wants to parallelize N independent tasks with per-task worktree

isolation.

  • The agent wants per-task model selection (e.g., cheap model for fixes,

capable model for design).

  • The operator wants to attach to a running subagent from another terminal.
  • The agent wants the dispatch artifact (prompt, event log, lock file) on

disk for replay or audit.

  • You are the orchestrator in a sashay: the branch, worktree, and draft PR

already exist on the forge. Use this skill to dispatch a subagent into the worktree with sashay chronicle instructions in the prompt. The subagent will commit, push, and post PR comments throughout its work.

Secondary pattern: background dispatch (watcher daemon)

Use the watcher daemon when the agent will not be around to poll:

  • The agent wants to fire-and-forget a long-running task and continue

working.

  • The operator wants to background work from a terminal session and close

it, letting the watcher daemon handle the lifecycle.

When NOT to use

  • The task is trivial and finishes in seconds — the lock-watch overhead

exceeds any benefit. Use opencode run directly.

  • The host runtime forbids spawning background subprocesses.

Four design constraints (non-negotiable)

  1. **Every dispatch takes an explicit absolute path; verification fails

closed.** No defaults, no inference. If the path is wrong, the script exits non-zero before anything runs.

  1. Every handoff is an on-disk artifact. Every dispatch writes the

prompt, start script, event log, and lock file under .subagents/<task-id>/. The task directory is the source of truth for replay and audit.

  1. One template per dispatch kind. Templates are typed by what the

dispatch is for (e.g., single-file-fix, headless-spike), not parameterized into a single megatemplate.

  1. Smart orchestrator, dumb subagents. The agent decides what is

ready and fires only ready tasks. The skill dispatches what it is given. Subagents are single-responsibility and should work on the cheapest model that can do the job. The agent continues running, polls, and kicks off the next wave. Subagents never coordinate with each other or trigger subsequent work.

Agent workflows

Workflow 1: Single task (two-call pattern)

This is the primary dispatch pattern. The agent calls run-plan.sh to dispatch, then calls poll-subagent.sh later to check in. Between the two calls, the agent can do other work.

Call 1 — dispatch (returns immediately):

run-plan.sh --plan plan.yaml

Returns JSON with lockfile path and PID. The subagent starts in the background. The agent is free to do other work — read files, write the next plan, investigate results from a previous task.

Call 2 — poll (called later):

poll-subagent.sh --task-id <id> --root <path>

Polls the lockfile and events log. Logs event line count each iteration, detects stuck tasks (exit 2), and times out (exit 3).

After poll completes:

  • Exit 0 (completed): read FINAL_OUTPUT.md, merge work, call

subagent-cleanup.sh.

  • Exit 2/3 (stuck/timeout): call subagent-abandon.sh.

Workflow 2: Parallelize N tasks (dispatch-all-then-poll)

Dispatch all N tasks first, then poll each one. This lets N subagents run concurrently while the agent does other work between polls.

  1. Write an N-task plan YAML (only tasks whose dependencies are

satisfied).

  1. Call run-plan.sh --plan plan.yaml once — returns immediately with

N lockfile paths and PIDs.

  1. For each task, call poll-subagent.sh --task-id <id> --root <path>

when ready to check in. The agent can do other work between polls.

  1. Per completed task: read FINAL_OUTPUT.md, merge work, call

subagent-cleanup.sh.

  1. Per failed or stuck task: call subagent-abandon.sh.

Example — dispatch 3 tasks in parallel, then check in on each:

# Phase 1: dispatch all three
run-plan.sh --plan plan.yaml
# → returns 3 lockfile paths, 3 PIDs

# Phase 2: do other work (read files, write prompts, etc.)

# Phase 3: check in on each task
poll-subagent.sh --task-id task-a --root /path
poll-subagent.sh --task-id task-b --root /path
poll-subagent.sh --task-id task-c --root /path

Workflow 3: Stale resource recovery

Call cleanup-stale.sh [--abandon] after a crash or long idle period.

  • Without --abandon: reports stale locks and orphaned worktrees.
  • With --abandon: calls subagent-abandon.sh for each.

Workflow 4: Background dispatch (fire-and-forget) — optional secondary pattern

Use this when the agent will not be around to poll. Requires the watcher daemon to be running.

  1. Ensure the watcher daemon is running (watcher.sh status --root <project-root>).
  2. Write a plan YAML with mode: background (and optionally ttl_sec).
  3. Write the plan to .subagents/watch/<plan-name>.yaml.
  4. Continue working — the watcher picks it up, dispatches, polls, and

cleans up.

  1. Check results later at .subagents/watch/results/<task-id>.md.

Script inventory

Script Agent-facing Purpose
run-plan.sh yes Validate plan, prepare worktrees, dispatch, return lockfile list as JSON
poll-subagent.sh yes Poll subagent lockfile until completion, stuck, or timeout
subagent-cleanup.sh yes Remove completed task's artifacts + worktree
subagent-abandon.sh yes Kill PID, force-remove failed task + worktree
cleanup-stale.sh yes Scan for stale locks and orphaned worktrees
dispatch.sh no (internal) Single-task prepare, spawn, confirm .lock appeared
verify-cwd.sh no (internal) Fail-closed CWD verification
validate-run.sh no (internal) Post-hoc event stream validation
poll-all.sh yes Poll all active tasks in a project root, return summary JSON
watcher.sh yes Daemon entry point — start/stop/status
watcher-process.sh no (internal) Process a single plan from the watch directory

Watcher daemon

The watcher daemon (watcher.sh) is a persistent background process that monitors a directory for plan YAMLs and processes them autonomously.

watcher.sh start --root <project-root> [--watch-dir <path>] [--interval <sec>]
watcher.sh stop --root <project-root>
watcher.sh status --root <project-root>
  • start — launches daemon, writes PID to .subagents/watcher.pid,

logs to .subagents/watcher.log

  • stop — terminates daemon gracefully
  • status — returns JSON with running status, PID, active task count

--root is required on all three subcommands. It sets the project root where .subagents/ lives. Omitting it fails loudly; the script never guesses the root.

The daemon must be running for background dispatch. If it is not running, write plans to .subagents/watch/ anyway — they will be processed when the daemon starts.

Plan schema

dangerously_write_trunk: false   # optional. Set true to allow dispatch to main/master.
tasks:
  - id: fix-auth
    kind: single-file-fix
    model: ollama-cloud/glm-5.1
    agent: build
    prompt: prompts/fix-auth.md
    target: src/auth.py
    worktree: fix-auth-branch     # optional. Branch name for worktree creation.

  - id: refactor-api
    kind: multi-file-fix
    model: ollama-cloud/deepseek-v4-flash:cloud
    agent: build
    prompt: prompts/refactor-api.md
    worktree: refactor-api-branch # optional

Fields: id (required), kind (required), model (required), prompt (required, path to prompt file), target (required for single-file-fix, path inside cwd; not used for multi-file-fix), worktree (optional, branch name), agent (optional, defaults per kind), mode (optional, foreground or background, default foreground), ttl_sec (optional, wall-clock deadline in seconds for background tasks, default 1800).

Top-level field dangerouslywritetrunk (optional, default false): when false, dispatch to a trunk branch (main/master) is rejected with a hint to set this flag. Set to true to allow dispatching directly to the project root — use only when you are certain the subagent should write to trunk.

Store prompt files in prompts/<task-id>.md at the project root. For example, a plan referencing prompt: prompts/fix-auth.md has the prompt file at <project>/prompts/fix-auth.md and the task directory at <project>/.subagents/fix-auth/.

No depends field. The agent writes only tasks that are ready to run right now.

Structured output from run-plan.sh

run-plan.sh returns JSON on stdout (all other output goes to stderr):

{
  "plan_id": "20260528T151600Z",
  "tasks": [
    {
      "id": "fix-auth",
      "lockfile": "/abs/path/.subagents/fix-auth/.lock",
      "task_dir": "/abs/path/.subagents/fix-auth",
      "pid": 48912,
      "worktree": "/abs/path/.worktrees/fix-auth",
      "status": "dispatched"
    },
    {
      "id": "fix-api",
      "status": "skipped",
      "reason": "worktree creation failed: branch already exists"
    }
  ]
}

Directory structure

<project-root>/
  .subagents/
    watcher.pid           ← PID file for watcher daemon
    watcher.log           ← watcher daemon log
    watch/                ← agent drops background plans here
      plan-001.yaml
      processing/         ← watcher moves plans here while working
      completed/          ← watcher moves plans here when done
      failed/             ← watcher moves plans here on failure
      results/            ← watcher writes result summaries here
        fix-auth.md
    <task-id>/
      prompt.md
      start-subagent.sh
      .lock                ← exists while subagent runs
      events.jsonl
      FINAL_OUTPUT.md
      worktree/            ← real git worktree (if task declared one)
  .worktrees/
    <task-id>             ← symlink → ../.subagents/<task-id>/worktree/

.subagents/ is gitignored. The real worktree lives inside the task directory so its lifecycle is bound to the task. The symlink in .worktrees/ lets other tooling discover active worktrees.

poll-subagent.sh

poll-subagent.sh --task-id <id> --root <project-root> \
  [--interval <sec>] [--max-polls <n>] [--stale-threshold <sec>]

Monitors a dispatched subagent by polling its lockfile and events.jsonl. Each iteration logs the event line count and mtime. Exits with:

  • 0 — task completed (lockfile gone)
  • 2 — stuck (events line count unchanged and mtime stale past threshold)
  • 3 — timeout (max polls reached, lockfile still present)
  • 1 — error (bad args, missing task dir)

Defaults: --interval 15, --max-polls 12 (up to 180s), --stale-threshold 60. Use --max-polls 16 for complex tasks (up to 240s).

poll-all.sh

poll-all.sh --root <project-root> [--interval <sec>] [--max-polls <n>]

Polls all active tasks in a project root and returns a summary JSON. Scans .subagents/*/.lock for active tasks, checks events line count and mtime for each, and returns a consolidated report.

Exits with:

  • 0 — all tasks completed (no locks found)
  • 1 — one or more tasks still active

Output (stdout):

{
  "tasks": [
    {"id": "fix-auth", "lines": 42, "mtime": 1717000000, "age_sec": 15},
    {"id": "refactor-api", "lines": 18, "mtime": 1717000010, "age_sec": 5}
  ],
  "active": 2
}

subagent-cleanup.sh

subagent-cleanup.sh --task-id <id> --root <project-root>

Removes .lock, removes .worktrees/ symlink, git worktree removes the real tree (no force — should be clean after merge), removes task dir.

subagent-abandon.sh

subagent-abandon.sh --task-id <id> --root <project-root>

Kills PID (TERM then KILL), removes .lock, removes .worktrees/ symlink, force-removes worktree + deletes branch, removes task dir.

Permission model

The async mode does not use ACP permission relay. Permission policy is enforced by:

  1. Prompt design — the agent writes a tightly-scoped prompt that

constrains the subagent's behaviour.

  1. Config gating — the consumer project's opencode.json can set

per-command rules.

  1. Post-hoc validation — scripts/validate-run.sh checks

events.jsonl for unexpected tool calls.

Default failure-mode mitigations

  • OPENCODEDISABLEAUTOCOMPACT=true set in the start script.
  • OPENCODEDISABLEAUTOUPDATE=true set in the start script.
  • Auto-detects server mode. When OPENCODESERVERURL is set, the

template uses --attach. When unset, falls back to local --dir mode. Avoids session-in-session env-var leak (issue #24747).

  • cleanup-stale.sh — cleans up stale .lock files and orphaned

worktrees whose PIDs are dead.

Dispatch kinds

Kind Status Use for
single-file-fix available One agent edits one file from a focused prompt. Required: target.
multi-file-fix available Full-directory fix/refactor with no single-file target. Works on the entire CWD. No target needed.
headless-spike available Read-only investigation; agent writes a report file but does not edit source. Required: target (report path). Defaults to --agent explore (opencode's read-only built-in).

Sashay dispatch pattern

In a sashay, the calling agent has already created the branch, worktree, and draft PR. The calling agent then dispatches a subagent into the existing worktree using this skill. The calling agent includes chronicle instructions (commit, push, post PR comments) directly in the prompt file — the subagent follows them.

The subagent's --cwd must point to the worktree directory, not the project root. Use multi-file-fix with worktree pointing to an existing branch name. The skill creates a worktree from that branch and sets CWD to it automatically.

Example plan YAML from the calling agent:

tasks:
  - id: implement-fix
    kind: multi-file-fix
    model: ollama-cloud/deepseek-v4-flash:cloud
    agent: build
    prompt: prompts/implement-fix.md
    worktree: fix-branch-name

The prompt file (prompts/implement-fix.md) includes the sashay chronicle instructions:

You are working in a PR-tracked worktree. The draft PR URL is
https://github.com/org/repo/pull/123.

Chronicle rules:
1. Commit and push your changes regularly.
2. After each checkpoint, add a PR comment via the forge CLI.
3. When done, ensure all tests pass and signal completion.

Add a kind by:

  1. Drop a <kind>.sh.j2 in templates/cli/.
  2. Add a row to the table above.
  3. Add an example invocation to references/examples.md.

What this skill does NOT do

  • Run inside an editor as an ACP agent. Editor flows should call

opencode acp directly.

  • Expose opencode via MCP. opencode is an MCP client only.
  • Manage opencode authentication. Run opencode auth login separately.
  • Coordinate between parallel agents beyond per-task isolation and

shared .subagents/ directory. The agent owns all coordination.

  • Merge worktree results. The agent decides merge semantics (commit, PR,

squash, etc.).

References

  • ADR-001: async .subagents/ lock-watch as primary dispatch mode.
  • Trove: async-subagent-dispatch@5ca7b44 — async dispatch patterns.
  • Trove: opencode-runtime-integration@d9bad44 — failure-mode catalogue.
  • SPIKE-001: --attach session visibility on serve daemon.
  • Examples: references/examples.md.

opencode CLI syntax reference

Commands and flags relevant to dispatch-opencode. Full docs at <https://opencode.ai/docs/cli/>;.

Global flags

Flag Description
--help / -h Display help
--version / -v Print version number
--print-logs Print logs to stderr
--log-level DEBUG, INFO, WARN, ERROR
--pure Run without external plugins

opencode run

Non-interactive prompt execution — the core invocation for subagent dispatch. Pass a prompt via stdin or as arguments.

opencode run [message..]
opencode run < prompt.md

Flags

Flag Description
--model / -m provider/model — e.g. ollama-cloud/deepseek-v4-flash:cloud
--agent Agent to use: build (default), explore (read-only), or a custom agent
--attach Attach to a running server — e.g. --attach http://localhost:4096
--password / -p Basic auth password (defaults to OPENCODESERVERPASSWORD)
--username / -u Basic auth username (defaults to OPENCODESERVERUSERNAME or opencode)
--dir Working directory (or path on remote server when attaching)
--file / -f Attach file(s) to the prompt
--format default (formatted) or json (raw JSON events)
--thinking Show thinking blocks
--continue / -c Continue the last session
--session / -s Resume a specific session by ID
--fork Fork session when continuing (use with --continue or --session)
--dangerously-skip-permissions Auto-approve permissions not explicitly denied
--title Title for the session

opencode serve

Headless HTTP server. Required for --attach mode. Set OPENCODESERVERPASSWORD to enable basic auth.

opencode serve [--port <n>] [--hostname <host>] [--mdns]

opencode models

List available models from configured providers. Format: provider/model.

opencode models [provider]

--refresh updates the cached model list. --verbose includes cost metadata.

opencode auth login

Authenticate with an LLM provider. Credentials stored at ~/.local/share/opencode/auth.json.

opencode auth login [--provider <id>] [--method <label>]

Provider env vars (ANTHROPICAPIKEY, OPENAIAPIKEY, etc.) also work without running auth login.

opencode attach

Attach TUI to a running server.

opencode attach <url> [--dir <path>] [--continue]

Relevant environment variables

Dispatched subagents set these automatically (templates):

Variable Purpose
OPENCODEDISABLEAUTOCOMPACT=true Prevent context compaction in subagent
OPENCODEDISABLEAUTOUPDATE=true Prevent update checks in subagent

Server-attach variables (set by the operator before running opencode serve):

Variable Purpose
OPENCODESERVERURL URL of the headless server for --attach mode
OPENCODESERVERPASSWORD Basic auth password
OPENCODESERVERUSERNAME Basic auth username (default opencode)

Troubleshooting

See references/troubleshooting.md for detailed solutions to common issues. Trigger keywords: session not found, blank FINAL_OUTPUT.md, dispatch skipped, timeout, stuck, exit code 3, exit code 2, branch not found, orphaned symlink, worktree symlink, prompt file resolves.