Terry — agent-fleet orchestration
Terry is a long-running manager loop that drives a fleet of independent
worker agents. Each worker does ONE well-scoped, long-running task in its OWN
git worktree + branch. They coordinate through a single JSON priority-queue
state file, and Terry is the only one that merges worker branches into an
integration branch — distributed-VCS style.
It is the generalization of four primitives wired into one durable loop:
- agent queue — a JSON state file (
agent_queue.json)
- task list — native
TaskCreate / TaskList
- dispatched agents — the native Agent tool (
run_in_background)
- heartbeat —
ScheduleWakeup (or a /loop) that self-paces the manager
Terry is two tools in one:
- a parallel fleet — N workers run concurrently, each merged as it finishes
(distributed-VCS style); and
- a producer-consumer queue with backpressure — the manager dispatches ONLY
into free capacity, so concurrency stays capped. That cap is your throttle:
keep peak memory under what the machine holds (no OOM), and pace token
spend when you're on a budget — lower the cap (down to 1) for a strictly
serial queue that drains the backlog one task at a time. Because the manager
wakes on completions and otherwise sleeps a long fallback, the loop itself
idles cheaply between ticks; the spend is the workers, and the cap controls the
burn rate.
When to use
Use Terry when you have N independent long-running tasks that each take
many minutes-to-hours, benefit from running concurrently, and are merged as
they finish. Typical shapes:
- parallel exploration of competing hypotheses (one worker per hypothesis)
- a fleet of per-target migrations (one worker per target)
- multiple validation / iteration runs against a shared codebase
- a backlog you want drained at a controlled rate — cap concurrency (even to
- to stay under memory and to pace token spend on a budget
- anything where dispatch → it runs a while → it reports → merge → dispatch
the next blocker repeats.
When NOT to use
- A single quick task — just do it.
- Work that must be strictly sequential because each step needs the previous
step's result — no parallelism to exploit. (Terry's queue still works as a
paced one-at-a-time consumer, but you don't need the fleet machinery.)
- A single job so large that even ONE worker won't fit the machine — Terry paces
concurrency, but can't shrink a worker below its own footprint (see the
resource-wall lesson in
references/lessons-and-antipatterns.md).
The heartbeat loop (5 steps)
Terry wakes on a completion notification or a fallback timer and runs:
- OBSERVE — read completion notifications; poll live state of in-flight
workers (HTTP status endpoint or log tail, preferring structured polling).
Recognize and IGNORE stale completion echoes from already-handled agents.
- INTEGRATE — for each genuinely-completed worker, merge its branch into
the integration branch, resolve conflicts deterministically, and run the
test/verify suite in the main checkout (never the worktree). Commit only
if green.
- DEQUEUE + DISPATCH — pick the next-priority blocker from the queue;
dispatch exactly ONE worker in a fresh worktree+branch off the current
integration HEAD, with a unique resource handle (port/dir).
- RECORD — update the JSON state file (completed →
done_this_session
ledger; add the new in_flight entry; bump integration_head); commit it.
- RE-ARM — schedule the next heartbeat with a LONG fallback delay.
Completions wake Terry sooner; the timer is only a safety net.
Full detail (polling cadence, stale-echo detection, what to do when a slot is
blocked vs. idle): references/heartbeat-loop.md.
Mental model: distributed VCS
Treat the fleet like a team using a shared repo:
- Workers are contributors. Each works on its own branch in its own
worktree, commits CODE ONLY to that branch, and never touches the integration
branch or another worktree.
- Terry is the maintainer. It is the only actor that merges, and it always
verifies in the canonical checkout before accepting a merge.
- The integration branch is "main." It is the single merge point; nothing
edits it directly except Terry's merges and the state-file commits.
- The queue is the backlog. Priority-ordered blockers waiting for a free
slot.
Isolation and merge rules (worktree setup, symlinking deps, never git clean,
conflict resolution): references/worktree-merge-protocol.md.
Worked example: one tick
State before the tick — one worker in flight, two queued:
// agent_queue.json (excerpt)
"integration_head": "a1b2c3d",
"in_flight": [
{ "id": "stage3-warmup", "agent": "agent-7", "wt": "../wt-stage3",
"branch": "terry/stage3-warmup", "port": "8801", "run": "baseline-config",
"title": "warm up to threshold 24 before stage-3 exit; resume cp_pre_stage3; next: pass stage-3 boundary checks" }
],
"queue": [
{ "prio": 1, "id": "fast-path-check", "title": "threshold-24 fast path — confirm it clears stage 4 in one pass" },
{ "prio": 2, "id": "stage5-routing", "title": "stage-5 dependency routing" }
]
The tick:
- OBSERVE — completion notification fires for
stage3-warmup. Poll
http://127.0.0.1:8801/state to confirm it is genuinely done (not a stale
echo): the progress metric reads 24, branch stage3-warmup has commits.
- INTEGRATE —
git merge stage3-warmup into the integration branch; run
uv run pytest tests/ in the main checkout → 168 pass → commit.
- DEQUEUE + DISPATCH — pop
prio:1 fast-path-check; git worktree add -f -b fast-path-check ../wt-fastpath <new-integration-HEAD>; symlink deps; copy
the needed cp_*.ckpt; dispatch one worker on --http-port 8802 with the
dispatch-prompt contract (templates/dispatch-prompt.md).
- RECORD — move
stage3-warmup to done_this_session; add the new
fast-path-check in_flight entry; bump integration_head; commit the
state file.
- RE-ARM —
ScheduleWakeup with a long fallback; yield until the next
completion or timer.
Quick start
Init the queue file from the template:
mkdir -p tools
cp skills/terry/templates/agent_queue.json tools/agent_queue.json
# edit integration_head, seed `queue` with your prioritized blockers
git add tools/agent_queue.json && git commit -m "terry: init queue"
Dispatch the first worker — create its isolated worktree off the current
integration HEAD, then dispatch one Agent (run_in_background) using the
dispatch-prompt contract:
HEAD_SHA=$(git rev-parse --short HEAD)
git worktree add -f -b worker-1 ../wt-worker-1 "$HEAD_SHA"
ln -sfn "$PWD/deps" ../wt-worker-1/deps # symlink heavy untracked deps
cp checkpoints/cp_seed.ckpt ../wt-worker-1/checkpoints/cp_seed_worker1.ckpt # UNIQUE untracked name
Fill templates/dispatch-prompt.md (scope, worktree path, branch, unique
--http-port, determinism bar, report-back contract) and pass it to the
Agent tool.
Arm the heartbeat — schedule the manager loop with a long fallback delay
(ScheduleWakeup or /loop). Completions wake Terry sooner; the timer just
guarantees forward progress if nothing reports in.
References
references/heartbeat-loop.md — the 5-step loop in depth: polling, stale-echo
detection, blocked-vs-idle slots, re-arming.
references/worktree-merge-protocol.md — worktree isolation, symlinks,
manager-only merges, conflict resolution, verify-in-main.
references/lessons-and-antipatterns.md — orphaned runs, resource wall,
handle/port collisions, stale-base regression, tooling false-negatives,
verify-before-claim, parallel-hypotheses-when-stuck.
Templates
templates/agent_queue.json — the priority-queue state file shape.
templates/dispatch-prompt.md — the worker dispatch-prompt contract.
1---2name: terry3description: Invoke when orchestrating a FLEET of parallel long-running agent tasks via a priority-queue manager over git worktrees, OR when you want a producer-consumer task queue for agents that caps concurrency to avoid OOM and pace token spend on a budget — agent queue + task list + dispatched agents + a self-pacing heartbeat loop. Covers capacity-gated dispatch (backpressure), worktree isolation, the manager-only merge protocol, and the resource/orphan/determinism anti-patterns that make fleets fail.4---56# Terry — agent-fleet orchestration78Terry is a long-running **manager loop** that drives a fleet of independent9worker agents. Each worker does ONE well-scoped, long-running task in its OWN10git worktree + branch. They coordinate through a single JSON priority-queue11state file, and **Terry is the only one that merges** worker branches into an12integration branch — distributed-VCS style.1314It is the generalization of four primitives wired into one durable loop:1516- **agent queue** — a JSON state file (`agent_queue.json`)17- **task list** — native `TaskCreate` / `TaskList`18- **dispatched agents** — the native Agent tool (`run_in_background`)19- **heartbeat** — `ScheduleWakeup` (or a `/loop`) that self-paces the manager2021Terry is **two tools in one**:2223- a **parallel fleet** — N workers run concurrently, each merged as it finishes24 (distributed-VCS style); and25- a **producer-consumer queue with backpressure** — the manager dispatches ONLY26 into free capacity, so concurrency stays capped. That cap is your throttle:27 keep peak memory under what the machine holds (**no OOM**), and **pace token28 spend when you're on a budget** — lower the cap (down to 1) for a strictly29 serial queue that drains the backlog one task at a time. Because the manager30 wakes on completions and otherwise sleeps a long fallback, the loop itself31 idles cheaply between ticks; the spend is the workers, and the cap controls the32 burn rate.3334## When to use3536Use Terry when you have **N independent long-running tasks** that each take37many minutes-to-hours, benefit from running concurrently, and are merged as38they finish. Typical shapes:3940- parallel exploration of competing hypotheses (one worker per hypothesis)41- a fleet of per-target migrations (one worker per target)42- multiple validation / iteration runs against a shared codebase43- a **backlog you want drained at a controlled rate** — cap concurrency (even to44 1) to stay under memory and to pace token spend on a budget45- anything where **dispatch → it runs a while → it reports → merge → dispatch46 the next blocker** repeats.4748## When NOT to use4950- A single quick task — just do it.51- Work that must be strictly sequential because each step needs the previous52 step's result — no parallelism to exploit. (Terry's queue still works as a53 paced one-at-a-time consumer, but you don't need the fleet machinery.)54- A single job so large that even ONE worker won't fit the machine — Terry paces55 *concurrency*, but can't shrink a worker below its own footprint (see the56 resource-wall lesson in `references/lessons-and-antipatterns.md`).5758## The heartbeat loop (5 steps)5960Terry wakes on a completion notification or a fallback timer and runs:61621. **OBSERVE** — read completion notifications; poll live state of in-flight63 workers (HTTP status endpoint or log tail, preferring structured polling).64 Recognize and IGNORE stale completion echoes from already-handled agents.652. **INTEGRATE** — for each genuinely-completed worker, merge its branch into66 the integration branch, resolve conflicts deterministically, and run the67 test/verify suite **in the main checkout** (never the worktree). Commit only68 if green.693. **DEQUEUE + DISPATCH** — pick the next-priority blocker from the queue;70 dispatch exactly ONE worker in a fresh worktree+branch off the **current**71 integration HEAD, with a unique resource handle (port/dir).724. **RECORD** — update the JSON state file (completed → `done_this_session`73 ledger; add the new `in_flight` entry; bump `integration_head`); commit it.745. **RE-ARM** — schedule the next heartbeat with a LONG fallback delay.75 Completions wake Terry sooner; the timer is only a safety net.7677Full detail (polling cadence, stale-echo detection, what to do when a slot is78blocked vs. idle): **`references/heartbeat-loop.md`**.7980## Mental model: distributed VCS8182Treat the fleet like a team using a shared repo:8384- **Workers are contributors.** Each works on its own branch in its own85 worktree, commits CODE ONLY to that branch, and never touches the integration86 branch or another worktree.87- **Terry is the maintainer.** It is the only actor that merges, and it always88 verifies in the canonical checkout before accepting a merge.89- **The integration branch is "main."** It is the single merge point; nothing90 edits it directly except Terry's merges and the state-file commits.91- **The queue is the backlog.** Priority-ordered blockers waiting for a free92 slot.9394Isolation and merge rules (worktree setup, symlinking deps, never `git clean`,95conflict resolution): **`references/worktree-merge-protocol.md`**.9697## Worked example: one tick9899State before the tick — one worker in flight, two queued:100101```jsonc102// agent_queue.json (excerpt)103"integration_head": "a1b2c3d",104"in_flight": [105 { "id": "stage3-warmup", "agent": "agent-7", "wt": "../wt-stage3",106 "branch": "terry/stage3-warmup", "port": "8801", "run": "baseline-config",107 "title": "warm up to threshold 24 before stage-3 exit; resume cp_pre_stage3; next: pass stage-3 boundary checks" }108],109"queue": [110 { "prio": 1, "id": "fast-path-check", "title": "threshold-24 fast path — confirm it clears stage 4 in one pass" },111 { "prio": 2, "id": "stage5-routing", "title": "stage-5 dependency routing" }112]113```114115The tick:1161171. **OBSERVE** — completion notification fires for `stage3-warmup`. Poll118 `http://127.0.0.1:8801/state` to confirm it is genuinely done (not a stale119 echo): the progress metric reads 24, branch `stage3-warmup` has commits.1202. **INTEGRATE** — `git merge stage3-warmup` into the integration branch; run121 `uv run pytest tests/` in the main checkout → 168 pass → commit.1223. **DEQUEUE + DISPATCH** — pop `prio:1 fast-path-check`; `git worktree add -f123 -b fast-path-check ../wt-fastpath <new-integration-HEAD>`; symlink `deps`; copy124 the needed `cp_*.ckpt`; dispatch one worker on `--http-port 8802` with the125 dispatch-prompt contract (`templates/dispatch-prompt.md`).1264. **RECORD** — move `stage3-warmup` to `done_this_session`; add the new127 `fast-path-check` `in_flight` entry; bump `integration_head`; commit the128 state file.1295. **RE-ARM** — `ScheduleWakeup` with a long fallback; yield until the next130 completion or timer.131132## Quick start1331341. **Init the queue file** from the template:135136 ```bash137 mkdir -p tools138 cp skills/terry/templates/agent_queue.json tools/agent_queue.json139 # edit integration_head, seed `queue` with your prioritized blockers140 git add tools/agent_queue.json && git commit -m "terry: init queue"141 ```1421432. **Dispatch the first worker** — create its isolated worktree off the current144 integration HEAD, then dispatch one Agent (`run_in_background`) using the145 dispatch-prompt contract:146147 ```bash148 HEAD_SHA=$(git rev-parse --short HEAD)149 git worktree add -f -b worker-1 ../wt-worker-1 "$HEAD_SHA"150 ln -sfn "$PWD/deps" ../wt-worker-1/deps # symlink heavy untracked deps151 cp checkpoints/cp_seed.ckpt ../wt-worker-1/checkpoints/cp_seed_worker1.ckpt # UNIQUE untracked name152 ```153154 Fill `templates/dispatch-prompt.md` (scope, worktree path, branch, unique155 `--http-port`, determinism bar, report-back contract) and pass it to the156 Agent tool.1571583. **Arm the heartbeat** — schedule the manager loop with a long fallback delay159 (`ScheduleWakeup` or `/loop`). Completions wake Terry sooner; the timer just160 guarantees forward progress if nothing reports in.161162## References163164- `references/heartbeat-loop.md` — the 5-step loop in depth: polling, stale-echo165 detection, blocked-vs-idle slots, re-arming.166- `references/worktree-merge-protocol.md` — worktree isolation, symlinks,167 manager-only merges, conflict resolution, verify-in-main.168- `references/lessons-and-antipatterns.md` — orphaned runs, resource wall,169 handle/port collisions, stale-base regression, tooling false-negatives,170 verify-before-claim, parallel-hypotheses-when-stuck.171172## Templates173174- `templates/agent_queue.json` — the priority-queue state file shape.175- `templates/dispatch-prompt.md` — the worker dispatch-prompt contract.