OpenCastle

Docs / Use Cases

When a convoy is the right tool, and two convoy recipes, each run end to end on a small Node.js notes API: two source files, four tests, a CLAUDE.md, and Claude Code as the agent runtime. The output is what the CLI printed in real runs, trimmed where marked with ….

Experimental: the convoy engine (opencastle convoy) is experimental and may change. Every command and flag is on the CLI page.

Convoy, /batch or /fleet?

Side by side

Claude Code's /batch and Copilot CLI's /fleet also run several agents at once, inside a session. A convoy runs from the terminal, outside any session. Where the three differ, as each tool's documentation describes it in October 2026:

Claude Code /batchCopilot CLI /fleetConvoy
Tasks that depend on each otherNo; 5 to 30 independent unitsYes, managed by the main agentYes, written into the plan; a task starts when the ones it waits for are done
The planShown once for your approvalDecided by the main agent during the session; it can start from a plan you acceptedChecked in code and saved as a YAML spec you can edit, write yourself and run again
IsolationA git worktree per unitNot documentedA git worktree per task; tasks that share files never run at the same time
Review and checksEach unit runs its testsNot documentedA read-only review per task, except a small change outside sensitive paths (planned tasks: by complexity); your checks run once on the merged result
The resultEach unit publishes its own changeNot documentedOne branch, each task merged onto it in turn
An interrupted runNot documentedNot documentedopencastle convoy resume carries on from .opencastle/convoy.db
ModelsNot documentedA low-cost model by default, or the custom agent you name with @Each agent's tier on Claude Code, with reviews on the economy tier
Agent runtimeClaude CodeCopilot CLIClaude Code, Copilot, Cursor, OpenCode or Codex

Not documented means the tool's page does not say, not that the tool cannot do it.

Which to use

  • /batch for a mechanical change across many files, where every piece looks alike and none needs another: a rename, a codemod, a move from JavaScript to TypeScript.
  • /fleet when you work in Copilot CLI and want a change with several parts done faster in the session you are in, while you watch. GitHub notes it does not help a request that is sequential by nature.
  • A convoy for a feature split across the API, the UI and the data, where order matters and you want one branch to review. Also for work you want to leave running, resume after a stop, and see the time and cost of, or run on Cursor, OpenCode or Codex.
  • None of them for most everyday changes. /oc:implement-feature keeps the work in your session, and the Team Lead hands off only what spans several areas.

Plan and run a feature

Introduction

You describe a feature in one sentence. OpenCastle plans it without changing anything, shows you the plan and asks once. The agents then work in git worktrees of their own, in parallel where the plan allows, and the result lands on a branch for you to review and merge. Your checkout is never touched.

The run quoted here added pagination and DELETE /notes to the notes API: two tasks, planned in 2 minutes 59 seconds and run in 5 minutes 28 seconds, for $2.63 in all.

Before you start

  • Node.js 22.5 or later, and Git
  • A git repository with at least one commit
  • An agent CLI, installed and signed in: Claude Code (claude), GitHub Copilot (copilot), Cursor (cursor-agent), OpenCode (opencode) or Codex (codex). The convoy uses the one for the assistant opencastle init sets up — see Runtime
  • Your project's checks as package.json scripts — typecheck, lint, test, build — if you want them run at the end
1

Step 1: Initialize

Run the initializer in the repository. It finds the CLAUDE.md, so it compiles for Claude Code, and that is the runtime the convoy will use.

Terminal
$ cd ~/notes-api
$ npx opencastle init
  🏰 OpenCastle v1.0.0
  Compiles your AI assistant config for every assistant you use

  Scanning repository…

  Found assistant config:
    • Claude Code (CLAUDE.md)

  Will compile for:
    → Claude Code

  Integrations detected:
    chrome-devtools

  Set this up? [Y/n] y

  Configuring project...
  …
  ✓ Merged into your existing CLAUDE.md
    your content is above the managed block and is never overwritten
  …

Commit what it wrote. Each task's worktree starts from your last commit, so anything uncommitted is not there for the agents.

Terminal
$ git add -A
$ git commit -m "Add OpenCastle"
2

Step 2: Describe the task

Say what you want, with the details that matter to you. Planning changes no code. This request was sized small, so the tasks were planned straight from it; larger work gets a PRD in .opencastle/prds/ first. With --yes, one more session reviews the plan and the run starts without asking.

Terminal
$ npx opencastle convoy "Add pagination to GET /notes: limit and offset query parameters \
  (defaults 20 and 0, limit capped at 100, a negative or non-numeric value is a 400 with \
  a JSON error). Add DELETE /notes, which removes every note and answers 204. Cover both \
  with tests." --yes
  npx opencastle convoy — planning, read-only
  Runtime: Claude Code — configured by npx opencastle init

  ✓ Sized low — planned straight from the request, without a PRD
  ✓ 2 tasks planned
  ✓ Plan checked
  ✓ Plan reviewed

  Plan: Notes pagination delete — 2 tasks, up to 1 at a time

    TASK                       AGENT      DEPENDS ON                FILES
    add-get-notes-pagination   developer  –                         src/notes.js, src/s…
    add-delete-notes-endpoint  developer  add-get-notes-pagination  src/notes.js, src/s…

  Branch convoy/notes-pagination-delete · runtime claude · then once: npm run test
  Spec   .opencastle/convoys/notes-pagination-delete.convoy.yml
  Planned in 2m 59s · 3 sessions · 334K tokens · $0.66

The table is the plan: who does what, after what, touching which files. Below it are the branch, the runtime, the checks that run once at the end, found in package.json, and what planning spent. (This output is from an earlier build; the current one plans the same request in under 30 seconds, for about $0.22.)

Without --yes, the command stops here and asks. Enter runs the plan. n, or no answer at all — as in a script — starts nothing and prints the command for later:

  Run it? [Y/n] n

  Not started. To run it later: npx opencastle convoy run .opencastle/convoys/<name>.convoy.yml
Tip: --dry-run plans and writes the spec, then stops. The spec is plain YAML: edit it and run it with npx opencastle convoy run, as in the second recipe.
3

Step 3: The run

Each task starts as soon as the tasks it depends on are done, in a worktree of its own. When its agent finishes, the work is committed there, gets the review its complexity sets — usually one read-only review, a panel of three for the largest — and is merged onto the run's branch. Here the second task waits for the first, because they change the same files. The test suite ran once, at the end, on the merged result.

  Convoy: Notes pagination delete — 2 tasks
  Models: by agent tier — premium opus, standard sonnet, economy haiku (and reviews)
  Live view: http://127.0.0.1:4300

  Branch convoy/notes-pagination-delete (from main) · 2 tasks · up to 1 at once
  ▶ [add-get-notes-pagination] developer
  ✓ [add-get-notes-pagination] (3m 57s) · $1.40
  ▶ [add-delete-notes-endpoint] developer
  ✓ [add-delete-notes-endpoint] (1m 30s) · $0.56

  Gates:
  ✓ npm run test

  ──────────────────────────────────────
  ✓ Convoy done in 5m 28s — 2/2 tasks done
  Gates: 1/1 passed
  Spent: 3.1M tokens · $1.97
  Convoy: convoy-1791045784848
  Branch: convoy/notes-pagination-delete (from main)
    Review: git diff main...convoy/notes-pagination-delete
    Merge:  git merge convoy/notes-pagination-delete
  Log: .opencastle/logs/convoys/convoy-1791045784848.ndjson

The developer agent is standard tier, so both tasks ran on sonnet; both reviews ran on haiku and passed. The suite went from 4 tests to 12.

Tip: --verbose streams each agent's output.
4

Step 4: Watch it

On a terminal the run starts the read-only Observability dashboard (Live view: above): every run's tasks, what they wait for and spent, reviews, checks, retries, a failed task's reason and the event timeline. To open it at any time:

Terminal
$ npx opencastle convoy dashboard
The Observability dashboard's overview of seven real convoy runs: 7 runs and 22 tasks, all done, 6m 16s on average, 21.28M tokens and $19.44, part of it estimated, with runs per day below

The overview, over seven real runs on copies of this project. Open each of them at opencastle.dev/dashboard.

5

Step 5: Review and merge

The work is on convoy/notes-pagination-delete. Your own branch has only the new spec in .opencastle/convoys/, uncommitted. Read the change, then merge it, or push the branch and open a pull request.

Terminal
$ git diff main...convoy/notes-pagination-delete
$ git merge convoy/notes-pagination-delete

npx opencastle convoy with nothing after it says where the last run stands and the one thing to do next:

  🚚 Convoy (experimental)

  ✓ Notes pagination delete — done
    2/2 done
    Branch convoy/notes-pagination-delete

  Next: npx opencastle convoy "<task>"
  every task is done; start another

Time and cost

Seven runs on this project with Claude Code, all on earlier builds; read them as data points, not a promise. The first three asked for the same change.

What was askedPlannedTasksPlanningRunningTests
Pagination and DELETE /notes (this recipe)sized low, no PRD, 3 sessions2, one at a time2m 59s · $0.665m 28s · $1.974 → 12
The samePRD, fixed once, 7 sessions; a test task folded in3, one at a time7m 25s · $1.676m 10s · $2.134 → 20
The samePRD, 5 sessions; tests as tasks of their own4, up to 2 at once5m 41s · $1.564m 34s · $2.224 → 19
“Add tags to notes: … Reject invalid notes with 400 … Document every endpoint in README.md …”PRD5, up to 3 at oncenot shown4m 53s · $3.214 → 18
“Add a GET /health endpoint…, with a test”PRD2, one at a timenot shown3m 59s · $1.504 → 6
Search and export, stopped and resumedby hand3, two at once—2m 19s + 3m 56s · $2.53 est.4 → 12
Tags, validation and docs, every task on Opusby hand3, one at a time—12m 36s · $5.894 → 39
  • The model matters most. A developer task on sonnet cost $0.48–$1.40; on Opus, the account default before tasks ran by tier, $2.01–$2.60.
  • Planning is now much shorter. The current build plans the pagination request in 24–28 seconds for $0.22, and a feature that needs a PRD in under 2 minutes.
  • Fewer tasks are not always faster. Tasks that change the same files run one after the other.

When something fails

A task failed

A failed attempt runs again, told why it failed: once in a spec you write, up to twice for most planned tasks, as their complexity sets. If it runs out of retries, the tasks that depend on it are skipped and the rest carry on. The summary lists each task that is not done with its reason, and the dashboard shows the end of its output. Fix the cause, then npx opencastle convoy resume.

A review blocked a task

If it has a retry left, the task runs again, given the reviewer's issues, as in the second recipe. A review that cannot run or gives no verdict is recorded as skipped, never as a pass.

The checks failed at the end

A planned spec gives one agent one attempt to fix the merged result. If the checks still fail, the run ends gate-failed with the failing command and its first line of output; the branch has the work so far.

A task could not be merged

A conflict re-runs the task once on top of the work it collided with. If it still cannot merge, the task fails and the summary names the branch its work is kept on.

No runtime found

The command stops before planning and names the CLI to install. To use another one, pass --adapter.

Write a spec, stop it, resume it

Introduction

When you already know the tasks, skip planning and write the spec yourself. This one adds search and an export to the notes API, after opencastle init as in the first recipe. Partway through, the run was stopped with Ctrl+C and then resumed: everything is recorded in .opencastle/convoy.db, so resume carries on with what is not done and keeps what is. The same command picks up a run that failed, or one that was killed outright.

1

Step 1: Write the spec

Give each task an id, an agent, the files it may change and a prompt; depends_on orders them. Keep code and its tests in the same task. Tasks with no files in common run at the same time.

search.convoy.yml
name: Notes search and export
branch: convoy/search-export
gates:
  - npm test
tasks:
  - id: search
    agent: developer
    description: Search notes by text
    files: [src/notes.js, test/notes.test.js]
    prompt: |
      Add searchNotes(query) to src/notes.js: a case-insensitive match on title or body,
      returning matching notes in creation order; an empty or whitespace query returns [].
      Add tests for it in test/notes.test.js and run `npm test`.
  - id: export
    agent: developer
    description: Export notes as JSON Lines
    files: [src/export.js, test/export.test.js]
    prompt: |
      Create src/export.js exporting toJsonLines(notes): one JSON object per line, no trailing
      newline, empty string for no notes. Add test/export.test.js covering empty, one and many
      notes, and run `npm test`.
  - id: routes
    agent: developer
    description: Wire search and export into the server
    depends_on: [search, export]
    files: [src/server.js, test/server.test.js]
    prompt: |
      In src/server.js add GET /notes/search?q=<text> (uses searchNotes; a missing q is 400 with
      a JSON error) and GET /notes/export (uses toJsonLines, content-type application/x-ndjson).
      Add tests in test/server.test.js and run `npm test`.
Tip: a spec you write gets no checks unless it lists them under gates; the planner adds them from package.json. Keep files to paths and directories: a pattern such as src/*.js is refused.
2

Step 2: Check it

--dry-run runs the same checks a run does and shows the order, the runtime, the branch and the gates. It starts nothing and records nothing.

Terminal
$ npx opencastle convoy run search.convoy.yml --dry-run
  Convoy: Notes search and export — 3 tasks, up to 4 at once

    TASK    AGENT      WAITS FOR       FILES
    search  developer  –               src/notes.js, test/notes.test.js
    export  developer  –               src/export.js, test/export.test.js
    routes  developer  search, export  src/server.js, test/server.test.js

  Runtime Claude Code — configured by npx opencastle init
  Branch  convoy/search-export
  Gates   npm test (once, after the tasks)

  Dry run — nothing started, nothing recorded. To run it: npx opencastle convoy run search.convoy.yml
3

Step 3: Run it

Terminal
$ npx opencastle convoy run search.convoy.yml
  Convoy: Notes search and export — 3 tasks
  Runtime: Claude Code — configured by npx opencastle init
  Models: by agent tier — premium opus, standard sonnet, economy haiku (and reviews)
  Live view: http://127.0.0.1:4300

  Branch convoy/search-export (from main) · 3 tasks · up to 4 at once
  ▶ [search] developer
  ▶ [export] developer
  ✓ [search] (1m 2s) · $0.48
  ✓ [export] (1m 49s) · $0.73
  ▶ [routes] developer

search and export ran side by side; routes started once both were merged.

4

Step 4: Press Ctrl+C

The first Ctrl+C stops the agents and starts no new task. The task that was running goes back to the queue, the run is marked interrupted, and the command exits with code 130. A second Ctrl+C quits at once.

  ■ SIGINT — stopping: killing running agents, no new tasks. Press Ctrl+C again to quit at once.

  ──────────────────────────────────────
  ■ Convoy interrupted in 2m 19s — 2/3 tasks done
    • routes (pending): stopped; runs again on resume
  Spent: 2.0M tokens · $1.20 (est.)
  Convoy: convoy-1791044097553
  Branch: convoy/search-export (from main)
    Review: git diff main...convoy/search-export
    Merge:  git merge convoy/search-export
  Log: .opencastle/logs/convoys/convoy-1791044097553.ndjson
  Resume with: npx opencastle convoy resume

The two finished tasks are already merged onto the branch, and your checkout is as it was. A session stopped mid-way never reports what it used, so its tokens are estimated and the run's totals are marked est.

5

Step 5: See where it stands

Terminal
$ npx opencastle convoy
  🚚 Convoy (experimental)

  ! Notes search and export — interrupted
    2/3 done, 1 pending
    Branch convoy/search-export

  Next: npx opencastle convoy resume
  runs what is not done — failed, interrupted and skipped tasks — and keeps the rest

--dry-run lists what resuming would run, and starts nothing:

Terminal
$ npx opencastle convoy resume --dry-run
  Resuming Notes search and export (convoy-1791044097553) — interrupted, 2/3 tasks done

  Would run 1 of 3 task(s):
    routes — developer [pending]

  Dry run — nothing started, nothing recorded.
6

Step 6: Resume

It continues the newest run that is not done, on the same branch and the same runtime, with the spec as it was when the run started. Only routes ran.

Terminal
$ npx opencastle convoy resume
  Resuming Notes search and export (convoy-1791044097553) — interrupted, 2/3 tasks done
  Runtime: Claude Code — the runtime this run started on
  Models: by agent tier — premium opus, standard sonnet, economy haiku (and reviews)
  Live view: http://127.0.0.1:4300

  Branch convoy/search-export (from main) · 1 task(s) to run
  ▶ [routes] developer
  ⚠ [routes] changed files outside its list: .opencastle/KNOWN-ISSUES.md
  ⟳ [routes] review blocked, retry 1/1: Remove change to .opencastle/KNOWN-ISSUES.md — not in allowed files list (src/server.js, test/server.test.js)
  ▶ [routes] developer attempt 2/2
  ✓ [routes] (1m 44s) · $1.32 (est.)

  Gates:
  ✓ npm test

  ──────────────────────────────────────
  ✓ Convoy done in 3m 56s — 3/3 tasks done
  Gates: 1/1 passed
  Spent: 3.7M tokens · $2.53 (est.)
  Convoy: convoy-1791044097553
  Branch: convoy/search-export (from main)
    Review: git diff main...convoy/search-export
    Merge:  git merge convoy/search-export
  Log: .opencastle/logs/convoys/convoy-1791044097553.ndjson

The first attempt also edited a file outside the task's list; the review blocked it, and the second attempt, told why, passed. Spent covers both halves of the run.

Tip: resume also re-runs failed tasks, with their retries back, and the tasks a failure skipped. To change what a task does, edit the spec and start it again with npx opencastle convoy run: resume replays the spec the run started with.
7

Step 7: Review and merge

Terminal
$ git diff main...convoy/search-export
$ git merge convoy/search-export

What a spec can say

Everything else has a default:

FieldDefaultWhat it does
version1The only version there is
branchconvoy/<name>-<id>Where the work lands. Never the branch you have checked out
concurrency4Tasks at once; --concurrency overrides it
on_failurecontinuecontinue skips only what depends on a failed task; stop starts nothing new
adapterthe one opencastle init set upThe runtime, for this spec
gates, gate_retriesnone, 0Commands run once at the end, and how many fix attempts a failure gets
defaults.timeout, timeout30mPer task
defaults.max_retries, max_retries1Attempts after the first
defaults.review, reviewautoauto passes a writer's change, and one of at most 10 lines in 2 files, without a reviewer — never one in auth/, security/, migrations/ or rls/, or from the security or data agent; fast always reviews; panel asks three reviewers; none skips
defaults.model, modelby the agent's tierThe model, for every task or for one
defaults.permission_modeacceptEditsWhat the agents may do: default, acceptEdits, auto, dontAsk, bypassPermissions or plan

The planner writes the same format; open any spec in .opencastle/convoys/ for another example. How the engine reads it is in ARCHITECTURE.md.