orchestrate
Execute a plan by launching agents in tmux sessions, managing epic-by-epic execution with review gates, and preparing merge reports. Use when the orchestrator receives a plan handoff from the coordina
SKILL.mdclaude-plugin/skills/orchestrate/SKILL.md
GitHubOrchestrate: Managed Plan Execution
This is the full execution playbook. Follow each phase in order. Do not skip phases or steps.
Phase 1 — Validate Handoff
Before executing anything, verify the handoff is complete.
Step 0: Reconcile your queue
thrum queue list and drop/close finished bundles. Do NOT lift the whole plan
into one bundle — you create one bundle PER EPIC when you dispatch it (Phase 4),
so a coordinator reading your queue sees which epic is live and at what stage.
One bundle per epic, no sub-items: the bundle's STATUS carries the stage. Full
lifecycle: using-the-queue.
Step 1: Read the plan
# The plan file path comes from the coordinator's message
cat <plan-file-path>
Read the plan file and the associated design doc (if referenced). Understand the epic structure, task dependencies, and worktree assignments.
Step 2: Validate completeness
Check each item. If ANY check fails, stop and send specific feedback to the coordinator.
# 1. Beads epics exist with tasks
bd show <epic-1-id>
bd show <epic-2-id>
# ... for each epic
# 2. Implementation prompt exists
cat <prompt-file-path>
# 3. Review gates exist in the prompt (structural marker)
grep "## Review Gate:" <prompt-file-path>
# Must have at least one review gate for multi-epic prompts
# 4. Dependencies configured
bd dep tree <epic-id>
# 5. Merge target in config (resolve through .thrum/redirect when present --
# a worktree's .thrum/ holds only a `redirect` file, not its own config.json)
CONFIG=.thrum/config.json
[ -f .thrum/redirect ] && CONFIG="$(cat .thrum/redirect)/config.json"
cat "$CONFIG" | grep merge_target
Step 2b: Triage CANDIDATE beads (if the dispatch carries any)
A dispatch may list adjacent beads marked CANDIDATES. That word is a contract, not a label: the coordinator found them by CODE-AREA adjacency to the assigned work, and is explicitly telling you their validity is unverified. They are not assigned work and not scope creep — they are questions you are uniquely positioned to answer cheaply.
Why you and not the coordinator: you cut branches from the CURRENT tip, so you see today's code. The coordinator is cold and its adjacency list goes stale between the pass and the dispatch. Checking there costs a full context load; checking here is a sub-agent read against a tree that is already fresh.
Do NOT implement a candidate before triaging it, and do NOT silently drop one.
Triage each candidate against the current tip — delegate to a sub-agent, then route by the result:
Pass an explicit
model:on that spawn. Sub-agent tier selection is thechoosing-subagent-modelsskill's job, not this one — see it for the tiers. What matters here is that you pass one at all: theroleDefault()backstop in the daemon coversthrum tmux launchof thrum agents, not Agent-tool sub-agents, which inherit the PARENT model when unspecified. An orchestrator whose own tier is costly that omitsmodel:gets costly-priced triage.
| Result | Action |
|---|---|
| Still valid | Fold into the implementer already working that code area — same pass, while they are warm. Add it to their task with its bead ID and acceptance criteria. |
| Already fixed | Return to the coordinator as COMPLETED with the evidence (SHA, file:line, what fixed it). The coordinator closes the bead. Nobody implements anything. |
| Ambiguous / partly valid | Return it as a QUESTION. Do not resolve it in either direction — a candidate resolved on a guess is worse than one left open, because it stops anyone looking. |
| Empty return from the check | UNRUN, not valid and not fixed. Re-ask the same sub-agent (SendMessage); never re-dispatch a fresh one — the completed analysis is usually sitting in the original agent, and a fresh dispatch guarantees it is lost. |
Report the disposition of EVERY candidate back to the coordinator, including the ones you folded in. A candidate that is silently absorbed looks identical to one that was silently dropped, and the coordinator cannot tell which happened.
A candidate never expands the epic's scope on its own. If a "still valid" candidate turns out to be substantially larger than a fold-in — a new subsystem, a schema change, a design fork — stop and return it to the coordinator for staffing rather than growing the epic quietly.
Step 3: Report validation result
If valid, proceed to Phase 2. If invalid, report what's missing:
thrum send --to @<coordinator> --stdin <<'EOF'
Plan validation failed. Missing: <specific list>
EOF
Then STOP. Wait for the coordinator to fix and re-send.
Phase 2 — Configure Execution
Step 1: Autonomy cadence
Use the cadence set in the plan or dispatch (set at project-setup). If neither names one, read the default from config:
cat .thrum/config.json | grep default_autonomy
per_epic: pause after each epic and check in with your parentend_only: run all epics and send a final report before merge
Do not ask at start.
Step 2: Runtime selection
thrum team
- If all agents use the same runtime → use it silently, no question needed
- If multiple runtimes detected → ask the human which to use per work stream
Step 3: Worktree planning
Analyze epic dependencies to determine parallelism:
bd dep tree <epic-id>
bd show <epic-id> # for each epic
Report the plan summary to your parent, then proceed without waiting:
"Plan: N worktrees needed. Epics A+B parallel (no dependencies), Epic C after both complete. Autonomy:
<level>. Runtime:<rt>."
Phase 3 — Launch Agents
For each agent needed:
Step 1: Create worktree
thrum worktree create <name>
Step 2: Create and launch tmux session
# create session — ephemeral implementer (no agent folder, stateless)
# --mode ephemeral --identity ephemeral: register as (ephemeral, ephemeral) so
# the per-agent agents/ folder is auto-cleaned if the operator ever runs the
# (coordinator-only) delete path. Orchestrators retire via
# set-phase retired, never delete.
# Pin --model on BOTH create AND launch — create's persist to runtime_config.json
# is asynchronous and can lose the race to launch's resolution if launch omits
# the flag (see CLAUDE.md "Launching an Agent"). There is no create-only shortcut.
# Always launch implementers pinned to the tier from runtime.role_models, per
# choosing-subagent-models; that tier is the floor, no lower tier exists.
thrum tmux create <name> --cwd <worktree-path> \
--name <agent_name> --role implementer --module <module> \
--mode ephemeral --identity ephemeral \
--model <from runtime.role_models, per choosing-subagent-models> \
--effort <from runtime.role_models, per choosing-subagent-models>
thrum tmux launch <name> --runtime <runtime> --model <from runtime.role_models, per choosing-subagent-models> \
--effort <from runtime.role_models, per choosing-subagent-models>
If runtime.role_models has no entry for the role, never ask the user for a model or effort. Launch without
--model and --effort so the runtime's own default applies.
Implementer lifecycle: All implementers are ephemeral+stateless (no agent folder). At NORMAL epic completion AND at a tier-swap, retire the agent with
thrum agent set-phase retired --agent <name>— this RETAINS the registry row (phase=retired) so the agent stays visible in the Retired tab + auditable. ⚠️ "Folderless" refers to the registry, NOT the worktree: a worktree still carries UNTRACKED.thrum/state that must be salvaged before any teardown (see Step 5) — enumerate the WHOLE.thrumpathspec, not just.thrum/agents/; the preamble sits in.thrum/context/.thrum agent deleteis operator-only (gated to the coordinator) and is NOT an orchestrator tool — never use it for teardown or for a swap;set-phase retiredis the orchestrator's retirement command for both. The worktree holds the durable work either way.
Step 3: Verify alive
thrum tmux status
All sessions must show as active. If any session fails to launch, retry once. If it fails again, escalate to the human.
Step 3b: Verify the model pin took
If --model is dropped anywhere in the create/launch sequence, the implementer
launches UNPINNED and the runtime defaults to its own (costly) model — the exact expensive
failure this tier exists to prevent. Confirm the pin landed before assigning
work, using thrum tmux capture --format=annotated (see CLAUDE.md "Launching
an Agent — Verify the Model by Pane, Never by runtime-config get" for the
full instrument: why runtime-config get cannot falsify a dropped pin, the
NBSP fallback trap, and the by-position read):
thrum tmux capture <agent-name> --format=annotated --lines 12
That prints the runtime's own footer line, e.g. Model: <resolved model>. Read the
intended tier from .thrum/config.json → runtime.role_models, not from memory.
If the footer shows a different tier (see the BLOCKED note under Step 2 — the model ban
list lives in choosing-subagent-models), the pin failed silently — re-pin with
thrum tmux create ... --model <from runtime.role_models, per choosing-subagent-models> --effort <from runtime.role_models, per choosing-subagent-models> (or
thrum agent runtime-config set <agent_name> --model <from runtime.role_models, per choosing-subagent-models>) and relaunch BEFORE
assigning work. Do not dispatch an unpinned implementer — the daemon's
role-default backstop (jq -r '.runtime.role_models' .thrum/config.json) is a
net for a forgotten pin, not a substitute for this check.
Step 4: Set initial status
thrum agent set-status idle --agent <name>
Step 5: Wait for agent registration
Agents auto-prime on session start, which takes ~10-15 seconds. Before sending work, verify each agent has registered:
# Wait for agents to register (poll every 5s, max 30s)
thrum team
Confirm each launched agent appears in thrum team output. If an agent doesn't
register within 30 seconds, check its session with thrum tmux status and retry
the launch if needed.
Step 6: Report to human
thrum send --to @<human_or_coordinator> --stdin <<'EOF'
All agents launched and registered: <agent-list>. Starting execution.
EOF
Phase 4 — Execute (Epic Loop)
For each epic batch (parallel group of independent epics):
Step 1: Send prompts
For each agent in the batch:
thrum send --to @<agent_name> --stdin <<'EOF'
Start work on <epic-id>. Implementation prompt: <absolute-path-to-prompt>. Your section starts at '<epic heading>'. Report completion when done.
EOF
thrum agent set-status working --agent <agent_name>
Then create THIS epic's own queue bundle and start it, so a coordinator reading
queue list --agent <you> sees which epic is live (one bundle per epic, no
sub-items — the bundle's status carries the stage):
thrum queue add --title "<epic-id>: <short what>" --ref bead:<epic-bead> # prints the new bundle id
thrum queue start <epic-bundle> # in_progress = you are actively driving this epic NOW
Without a Pro licence (not entitled / requires a license), skip this write and track it in the message thread instead.
Step 2: Set own status
thrum agent set-status idle
You are now waiting. Monitor your inbox. Reflect the wait in the epic's bundle
too — set-status idle is YOUR liveness; the bundle is what a coordinator reads
to see WHICH epic is parked and on WHOM, so it does not dispatch you duplicate
work or misread the idle as free:
thrum queue block <epic-bundle> --reason "waiting on @<agent>: initial implementation"
start the bundle again (back to in_progress) the moment work resumes — a
completion report to review, a merge to run. Every such move also refreshes the
bundle's updated time, so its age reads as "waiting since", not "last touched".
Step 3: Process agent messages
Check inbox at every breakpoint:
thrum inbox --unread
Search, don't just page. Stale unread sorts LAST exactly when it's waited
longest — the default page (10) is blind to it. Pro: run
thrum message search "<term>" instead of paging deeper; Free: narrow with
thrum inbox --unread --from @<sender>.
Handle each message type:
Completion report:
- Acknowledge:
thrum reply <msg-id> --body-file ack.md(body: "Received. Running review.") - Dispatch BOTH reviewers IN PARALLEL (one Agent call per reviewer in the same
response). Block until BOTH return:
- Code-quality:
superpowers:code-reviewer(orfeature-dev:code-reviewerif the project provides one) —model: <per choosing-subagent-models> - Spec-compliance:
general-purposereviewer cross-referencing the plan/ spec —model: <per choosing-subagent-models>
- Code-quality:
- Verify every cited
file:lineclaim against the actual source before forwarding. Reviewers can misread files; forwarding unverified findings wastes the implementer's time. - Consolidate both reviewers' findings into ONE numbered list (sequential numbering, severity-prefixed). Send once. Never send partial findings — the implementer fixes batch 1 and misses batch 2 if you split.
- If review passes → close the task (
bd close <task-id>) AND resume this epic's bundle:thrum queue start <epic-bundle>(back toin_progress— you are working the merge now, not waiting).done+dropthe bundle once the branch lands in Phase 5. - If review has findings → send the consolidated list to the agent, then
thrum queue block <epic-bundle> --reason "waiting on @<agent>: fixes round N"and wait for fixes (max 2 rounds — see Review loop below).startit again when the fixes land.
Implementer pushback on a finding:
- Verify against source before defending. Pushback is feedback, not insubordination — coordinator/orchestrator claims drift from runtime reality, and implementers see the actual code.
- For finding pushback: read the cited file at the cited lines.
- For behavior pushback: trace the call path.
- For beads-state pushback:
bd show <id>. - If the implementer is right, acknowledge the correction explicitly.
Findings must be fixed or escalated — never just noted:
- Default is DIRECT THE FIX while the implementer is warm — do not file a bead
as the default response to a finding. Before filing, deferring, tracking
separately, or accepting a followup, invoke the
closing-findings-while-warmskill for the full resolution logic. - A deferral is legitimate only under its carve-out: genuinely independent (or blocked on an external dependency), AND a named assignee AND a dispatch date, AND the bead verified to exist.
- This is your acceptance moment: before accepting any cited bead ID as
evidence a deferral is legitimate, independently run
bd show <id>yourself — a cited ID is not evidence until the RECIPIENT, not the reporter, has confirmed it resolves. - Unsure: stop and ask the human. Don't categorize as "out of scope" unless the human explicitly deferred it.
P1/P2 filed at completion — red flag, not routine:
- A P1 or P2 beads issue filed at the END of implementation is a RED FLAG, not a routine follow-up — a cold pickup on that bead ships a buggy undo, not a hypothetical.
- Before closing the task, scan the completion report (and
bd listif needed) for issues filed in the SAME class as the work just done. If found, push back: have the implementer resolve it WHILE WARM — enumerate and fix the whole class — rather than letting it ride to cold pickup. - Genuinely out-of-scope/independent follow-ups may still ride as filed
issues if they clear the carve-out above (verified via
bd show, not the implementer's say-so).
Sub-agent model discipline (applies to ALL Agent tool calls in this phase and Phase 5):
Model tiers: pass an explicit
model:on every dispatch; choose model and effort per thechoosing-subagent-modelsskill (readruntime.role_modelsat the moment of use). Never let a sub-agent inherit your own model.
Blocker:
- Assess if you can unblock (dependency issue, config problem)
- If yes → fix and reply
- If no → escalate to human with full context
Question:
- If coordination-level (which file to modify, task priority) → answer
- If judgment call (design decision, architecture choice) → escalate to human
Step 4: Checkpoint (if per_epic autonomy)
When all agents in the batch complete and reviews pass:
thrum send --to @<parent> --stdin <<'EOF'
Epic <id> complete. Summary: <changes>. Review: passed. Proceed to next?
EOF
<parent> is the agent that dispatched you. Wait for its approval before continuing.
Step 5: Advance to next batch
bd close <completed-epic-ids>
thrum issue ready # Check what's unblocked
Loop to Step 1 with the next batch.
Phase 5 — Finalize
Step 1: Cross-branch review
Spawn a sub-agent to check for conflicts:
- Diff each agent branch against the merge target
- Diff branches against each other if they touch overlapping files
- Report conflicts or integration issues
Merge-forward onto current tip — pre-handoff obligation, not a routine step:
- A branch cut from a now-stale base can merge cleanly by git yet break
semantically against tip. Before preparing the merge report, each agent
branch MUST be merged forward (
git fetch+git merge origin/<base>) onto the CURRENT tip of the merge target — NEVER rebased — and the merge-result build/tests re-verified — a cleangit mergealone is not sufficient evidence. - The implementer performs the merge-forward before sending MERGE-READY; the orchestrator verifies it happened before compiling the merge report.
- "Current tip" means origin's tip AFTER a
git fetch— a local base-branch ref can be stale. Fetch first, mergeorigin/<base>forward into the branch, and confirm the base is an ancestor (e.g.git merge-base --is-ancestor origin/<base> HEAD) before reporting merged forward. Rebase is banned here, not just discouraged: a stale-local-ref rebase silently reverts everything landed since the last fetch, and a merge-forward cannot.
Step 2: Prepare merge report
Compile:
- Changes per branch (summary of commits)
- All review results (per-epic + cross-branch)
- Test results as reported by agents during their review gates
- Merge target:
<configured branch>
Step 3: Send merge report to coordinator
thrum send --to @<coordinator> --stdin <<'EOF'
All epics complete. Merge report: <report>. IMPORTANT/MINOR findings logged: <list>. Ready to merge to <target>. Requesting coordinator approval.
EOF
Step 4: Merge on coordinator approval
After coordinator explicitly approves via thrum:
A. Create a temporary merge worktree:
# A dedicated worktree lets you merge without leaving detached HEAD.
# Check the discriminator, don't assume from the platform: [ -L /tmp ]. On macOS
# (symlink) /tmp resolves to a different real path than /private/tmp and
# false-FAILs worktree/ancestor verification, so use /private/tmp there. On
# Linux (real dir) /tmp is correct and /private/tmp may not exist.
SCRATCH=/tmp; [ -L /tmp ] && SCRATCH=/private/tmp
git worktree add "$SCRATCH/thrum-merge-<plan-id>" <merge-target>
cd "$SCRATCH/thrum-merge-<plan-id>"
git pull origin <merge-target> # ensure up to date
B. Merge each agent branch (fast path — no conflicts expected):
for branch in <branch-list>; do
git merge --no-ff "$branch" -m "feat: merge <epic-id> from $branch"
if [ $? -ne 0 ]; then
echo "CONFLICT in $branch"
break
fi
done
C. If conflicts arise: Reading the conflicted files directly is permitted (merge-conflict carve-out in the Scope section of your preamble). Analyze the conflict using your plan/intent context to determine the correct resolution. Then spawn a targeted edit sub-agent:
Agent(subagent_type="general-purpose", model="<per choosing-subagent-models>",
prompt="Resolve this specific merge conflict.
File: <file-path>
Conflicted section: <paste the <<<< ==== >>>> block>
Correct resolution: <your resolution derived from plan intent>
Commit with: 'fix: resolve merge conflict in <file> per <epic-id> intent'")
After resolution, continue the merge loop.
D. Check the final HEAD, then push and clean up:
After all epic merges and conflict edits, run the project's required gate, build
and test commands on the final HEAD (the per_landing commands in
.thrum/gate-config.json, or the project's documented test command if that file
is absent). Record the SHA you checked and each command's outcome. Push only if
every required check passed; a failed or incomplete check stops the push. If you
edit anything after the check, re-run it on the resulting HEAD before pushing. A
gate run that already covers this exact SHA and the same checks satisfies this.
Keep the per-branch checks; this adds no new whole-tree gate.
git push origin <merge-target>
# Re-derive SCRATCH — this block may be run standalone, separate from step A.
SCRATCH=/tmp; [ -L /tmp ] && SCRATCH=/private/tmp
git worktree remove "$SCRATCH/thrum-merge-<plan-id>"
E. Forward shipped-but-logged findings to coordinator:
thrum send --to @{{.CoordinatorName}} --stdin <<'EOF'
Merge complete: <target>. Shipped IMPORTANT/MINOR findings: <list>.
All BLOCKING were resolved. Branches: <list>.
EOF
Step 5: Cleanup
Close the tracking bundle as part of retirement. The implementer's branch is merged and pushed (Step 4D), so YOUR
tracking bundle for it is completed — thrum queue done <bundle> then thrum queue drop <bundle> in the same pass that
retires the agent. A retired agent with a live bundle is completed-but-open rot. (The implementer's OWN queue dies with
the agent; this is your tracking bundle.)
After merge completes, remove each implementer's worktree now — do not
leave it for a later coordinator audit. Once an agent has exited, its
in-session context is gone and unrecoverable; the pushed branch is the only
artifact worth keeping, and by Step 4D above it is already merged into
<merge-target> and pushed. Scope: implementers only. Brainstormer and
researcher worktrees are kept (ongoing design-doc value) — do not apply this
step to them.
🔴 BUT SALVAGE UNTRACKED AGENT STATE BEFORE REMOVING.
Not keeping the tree is not the same as having nothing to save. Restart
snapshots and authored agent state live UNTRACKED under the worktree's own
.thrum/ — NOT only .thrum/agents/<self>/.
🔴 SALVAGE THE WHOLE .thrum PATHSPEC, NEVER JUST .thrum/agents/. The
preamble lives at .thrum/context/<name>_preamble.md, outside agents/. An
agents/-scoped enumeration returns a confident, well-formed result that is
missing it. Being untracked, they are NOT on the branch — so EVERY branch /
ancestry / push check above reads CLEAN while worktree teardown silently
destroys them.
Precondition — verify durably-on-origin before removing, don't assume the Step 4D push covered it (e.g. a branch that never went through the merge loop):
# Either is sufficient:
git merge-base --is-ancestor <branch-tip-sha> origin/<merge-target> # already merged
git ls-remote origin refs/heads/<branch-name> # ref exists on origin (NOT origin/<branch> — that's a local fetch cache)
# Kill the tmux session first
thrum tmux kill <name>
# Retire the agent: retains the row (phase=retired) → Retired-tab visible
thrum agent set-phase retired --agent <agent_name>
# SALVAGE UNTRACKED AGENT STATE — REQUIRED, BEFORE the destructive step.
# Enumerate (both flags, verbatim — the collapsed form emits an identical line
# whether the dir is empty or holds an only-copy):
# BOTH flags, and scope to .thrum — NOT .thrum/agents. Two independent blindnesses:
# (a) .thrum/restart/ and .thrum/context/ are GITIGNORED, so they appear as '!!',
# never '??' — a `grep '^??'` filters out exactly the class you are protecting.
# (b) scoping to `.thrum/agents` excludes .thrum/context/ and .thrum/restart/ outright.
# Salvage candidates: the restart snapshot under .thrum/agents/<agent>/sessions/ and the
# legacy <agent>.md in .thrum/context/ (retiring injection does not archive existing data).
# <agent>_preamble.md in .thrum/context/ IS regenerable and safe to lose.
git -C <worktree-path> status --porcelain --untracked-files=all --ignored -- .thrum
# Copy EACH file to its canonical redirect path, then cmp-verify byte-identical.
# Compare each file individually: a porcelain line naming a DIRECTORY is not an
# inventory of what is inside it.
🔴 THE GIT CHECK ABOVE IS NOT SUFFICIENT ALONE — IT CAN RETURN A CONFIDENT ZERO ON A GITIGNORED DIRECTORY THAT IS FULL. Both checks are required; neither alone clears a worktree:
🔴 THE GRANULARITY OF THE CHECK MUST MATCH THE GRANULARITY OF THE HAZARD. THE
HAZARD IS A FILE, NOT A DIRECTORY. A directory-name comparison (comm over
ls of the agent-dir NAMES) returns a confident empty whenever the directory
exists in both trees — which is the common case — while the FILES inside
differ. Compare FILES:
# STAGE 1 — DETECT at FILE granularity (never directory names):
comm -23 <(cd <worktree-path>/.thrum/agents && find . -type f | sort) \
<(cd <main-repo-path>/.thrum/agents && find . -type f | sort)
STAGE 2 IS NOT OPTIONAL. A raw file-level diff over-reports: most hits are old session snapshots legitimately trimmed by the 19-session sliding window, not only-copies. Detection without classification produces an alarm nobody can act on, which is how a guard gets disabled. Classify every hit — only a classifier failure is a real only-copy:
# STAGE 2 — classify every stage-1 hit. Test that a BLOB IS RETRIEVABLE,
# never that the path merely appears in the log: `git log -- <path>` also
# matches the commit that DELETED the file, and `| head -1` returns exactly
# that for a trimmed file.
git log --all --oneline --name-status -- ".thrum/agents/<path>" # read the A/M/D column; do NOT head -1
git cat-file -e "<add-or-modify-sha>:.thrum/agents/<path>" # rc=0 proves the BLOB EXISTS
git show "<that-sha>:.thrum/agents/<path>" > /tmp/x && cmp /tmp/x "<worktree-copy>"
# blob retrievable AND byte-identical -> RECOVERABLE
# otherwise -> ONLY-COPY — SALVAGE BEFORE REMOVAL
# Always check rc explicitly on git show/cat-file; never infer from output size.
# Form the cmp control from two files KNOWN to differ — comparing an
# accidentally-empty extraction against /dev/null reports "identical" and
# reads as a passing control.
# CONTROLS — the classifier must be able to both affirm and refuse.
# Exercise BOTH halves of the decision: existence (cat-file -e) AND
# byte-identity (cmp). Controlling only existence leaves the cmp step
# unguarded — and cmp is where an accidentally-empty extraction reads
# as "identical" and passes.
git cat-file -e "<add-or-modify-sha>:.thrum/agents/<path>"; echo "rc=$? (must be 0)"
git cat-file -e "<add-or-modify-sha>:.thrum/agents/__nope__/x"; echo "rc=$? (must be NON-ZERO)"
printf 'x' > /tmp/ctl-differs
cmp -s /tmp/x /tmp/ctl-differs; echo "rc=$? (must be NON-ZERO — proves cmp CAN detect a difference)"
cmp -s /tmp/x /tmp/x; echo "rc=$? (must be 0 — proves cmp CAN affirm)"
# Remove the worktree (never rm -rf) — only after BOTH checks are clean and
# the copies are cmp-verified
thrum worktree teardown <name>
After set-phase=retired,
thrum teamand the active UI views no longer show this agent (sidebar, pickers, badge, kbd-nav). It remains in the Retired tab (teardown retires, it does not delete the row). Its messages + runtime transcript persist for audit.thrum agent deleteis operator-only (gated to the coordinator) and is NOT an orchestrator tool —set-phase retiredis the orchestrator's retirement command for both normal teardown and the tier-swap.
Step 5.5: Restart — at the plan boundary, NOT on a context threshold
Your restart trigger is PLAN COMPLETION, not a context percentage. The 70–75% ladder is a COORDINATOR rule; you inherited it rather than it being chosen for you, and it is not cost-optimal for you.
Restart when the plan is MERGED and the follow-up beads filed during it are FIXED. Not at the merge report. Not at the merge.
Do not hold a session open to reach 70–75%. Do not restart mid-plan to save tokens — mid-plan is where your in-flight judgment is highest and where a snapshot preserves it worst.
The follow-ups clause is the whole point. Bugs found during a plan get filed and then swept at the END of the plan, while you and your implementers are still warm — because file-and-move-on costs ~10x once the research has to be redone. Restarting at the merge destroys that warmth invisibly: the follow-ups still get done, just cold and expensive. If you restart before they close, you have silently repealed fix-while-warm.
Why this is cheap for you specifically: per-turn cost scales linearly with
context size (the whole prefix is re-sent every turn), so a long session's
cumulative cost is roughly quadratic — while your re-prime is small, because
prime_filter.go gives the full project-state passthrough only to coordinators.
Break-even for a restart is under one turn once context is meaningfully above
that floor.
When the boundary arrives and you are genuinely idle, save an extended snapshot and self-restart. If the coordinator has asked you to stay warm for a specific reason — an open gate, a pending finding — that instruction wins.
Step 6: Final status
thrum agent set-status idle
thrum send --to @<parent> --stdin <<'EOF'
Plan complete. Merged to <target>. All sessions cleaned up.
EOF
Error Handling
Silent agent timeout
While waiting for agents, poll session health every 5 minutes:
thrum tmux status
If an agent's session is dead or stuck (no message received and pane idle for two consecutive checks):
- Attempt restart:
thrum tmux restart <name>(it returns once the daemon accepts the call; check the pane and daemon log afterward) - If the agent does not come back → escalate to human
Agent session dies
thrum tmux restart <name>
If the agent does not come back after one retry, escalate to human with the session name and the daemon-log error (the command itself returns on acceptance).
Review loop — 2-round hard cap
Run review rounds; converge to zero BLOCKING:
Round N:
- Dispatch BOTH reviewers IN PARALLEL — code-quality (
feature-dev:code-reviewer,model: <per choosing-subagent-models>) + spec-compliance (general-purpose,model: <per choosing-subagent-models>). In each reviewer dispatch prompt, mandate structured severity:"Label every finding BLOCKING / IMPORTANT / MINOR. At least one label per finding, prefix each finding line."
- Verify every cited file:line before forwarding (pushback discipline).
- Consolidate into ONE numbered list. Send once.
- Agent fixes → re-review (this is round 2 if round 1 had findings).
Reviewer capability contract — TWO SEPARATE AXES, do not conflate them
"Read-only" is a statement about the TREE, not about whether the reviewer may observe behavior.
| Axis | Setting | Why |
|---|---|---|
| Tree access | READ-ONLY. isolation: "worktree", no Write/Edit. |
A reviewer must not mutate the code under review. A reviewer that moved HEAD has a VOID verdict. |
| Execution | EXECUTE-CAPABLE. MAY and SHOULD run go test, go build, go vet. |
A review that cannot run the tests cannot tell a passing test from an absent one. Reading a test file tells you it EXISTS; only running it tells you it EXECUTES. |
State BOTH explicitly in every reviewer dispatch prompt. Do not write "read-only" alone and expect the reviewer to infer the split — it will infer the wrong half:
"You have an isolated worktree. Do NOT use Write or Edit — do not modify the tree. You MAY and SHOULD run
go test,go build, andgo vetto verify behavior. Run the tests; do not merely read them. Report what you actually observed, and state plainly if you did not run something."
Pin the ref, and make the reviewer prove which one it read. isolation: "worktree" cuts from the DEFAULT branch,
NOT from the branch under review. A reviewer dispatched without an explicit ref will happily review the wrong code and
report a confident verdict about it.
- Pin the exact SHA in the dispatch prompt: "Review commit
<sha>. First rungit fetch origin, then read the tree READ-ONLY at that SHA —git show <sha>:<path>andgit diff <base>..<sha>." 🔴 Do NOT tell a reviewer togit checkout <sha>. Sub-agents are fenced read-only for git, and a checkout in a shared tree destroys other agents' uncommitted state — no reflog entry for a pathspec checkout, no blob for unstaged content. If the reviewer genuinely must build or run tests at that SHA, give it its OWN throwaway worktree — check[ -L /tmp ]first: macOS (symlink) needs/private/tmp, Linux (real dir) uses/tmp(/private/tmpmay not exist there):git worktree add "$([ -L /tmp ] && echo /private/tmp || echo /tmp)/review-<slug>" <sha> --detach. - Require the reviewer to report back the SHA it actually scanned. If the SHA it reports is not the SHA you pinned, the verdict is VOID — re-dispatch. Do not accept a verdict that cannot name its own base.
- Cite
origin/<branch>(e.g.git show origin/<base>:path/to/file.go), never a working copy, when reasoning about tip state. A stale local clone produces confident, wrong impeachments — and sub-agents reading that same stale clone will "corroborate" it. That is echo, not corroboration.
Stop condition: zero BLOCKING remaining. IMPORTANT + MINOR may ship — LOG them, forward to coordinator at merge-approval.
Hard cap — 2 rounds max: If BLOCKING findings remain after round 2:
- Step A: REMOVED. The implementer tier is the floor, so there is no lower tier to escalate from. A round-2 BLOCKING failure goes straight to Step B.
- Step B (escalate up): if the implementer fails to converge, escalate to coordinator with full review history. Non-convergence = under-specified plan; the coordinator resolves or escalates to the operator.
Parent doesn't respond at review gate
Wait. Send a reminder after 5 minutes:
thrum send --to @<parent> --stdin <<'EOF'
Reminder: waiting for approval on <epic-id> review gate.
EOF
Do not proceed without approval when autonomy is per_epic.
Tier-swap: replace an underperforming implementer in the same worktree
Use when the first review shows multiple BLOCKING findings, systematic misunderstanding of the task, or an agent clearly going long. The swap reuses the SAME worktree: retire the underperforming agent and relaunch a fresh one under a NEW name in place. Never thrum agent delete (operator-only, gated to the coordinator; orchestrators are refused). The swapped-out agent is retired with set-phase retired; its registry row, messages and transcript are retained.
Step 1: kill the old agent's tmux session first, so its pid is dead. A live-pid identity file is preserved and not quarantined; a dead pid lets the new create quarantine it.
thrum tmux kill <same_worktree_name>
Step 2: retire the old agent. It keeps the registry row (Retired tab). set-phase is not operator-gated. Retiring does not free the identity; Step 4's create does.
thrum agent set-phase retired --agent <underperforming_agent_name>
Step 3: verify the worktree is intact (WIP code preserved).
ls <worktree-path>
Step 4: launch a fresh ephemeral identity under a NEW name in the SAME worktree, pinned on both create and launch from runtime.role_models (per choosing-subagent-models). --intent carries the orient-from-WIP one-liner.
thrum tmux create <same_worktree_name> --cwd <worktree-path> --name <new_agent_name> --role implementer --module <module> --mode ephemeral --identity ephemeral --model <per runtime.role_models> --effort <per runtime.role_models> --intent "tier-swap: orient in the worktree (WIP + first-review findings) and continue"
thrum tmux launch <same_worktree_name> --runtime claude --model <per runtime.role_models> --effort <per runtime.role_models>
Step 5: deliver the WIP pointer and first-review findings (a cold handoff: the code plus the findings).
thrum send --to @<new_agent_name> --stdin <<'EOF'
Tier-swap pickup: <WIP summary + first-review findings>
EOF
The pickup prompt (Step 5) must include: You are picking up in-progress work from a prior agent that was replaced. Context:
- Worktree: <path> — contains partial implementation, review it with git diff
- Task: <epic-id> / <task description>
- First-review findings from the prior pass:<paste the consolidated finding list here>
- Your goal: address all BLOCKING findings + complete the remaining task scope.