What it does
dispatch-ticket is the sequence Thomas runs every time a ticket goes from “claimable” to “a
Builder is actually working on it.” Nine steps, always in that order:
- Preflight on the session’s first dispatch: watchdog running, payload committed.
- Resolve the orchestrator row: runtime, model, effort, and the write-set.
- Claim the ticket, then cut the branch and worktree, in that order.
- Open a Herdr tab and pane, gated on
foreground_cwd. - Print the resolved dispatch, then launch through
dispatch-ticket-claude. - Hand over the brief, its first line the phase’s slash command.
- Arm the watcher right after submitting: those are one action, not two.
- Branch on the watcher’s exit status, never on pane status alone.
- Verify by artifact, then clean up.
A step nobody checks is a step that gets skipped silently, and this harness runs several of these sequences in parallel, so silently here means a Builder idle in a pane nobody is watching.
What is different about it is where the boundary sits: the claim happens before the worktree
exists. A ticket gets assigned on the tracker first, so two Thomas sessions picking off the same
frontier see each other’s claims instead of racing to create the same branch. The problem behind
this rule was two sessions sharing one checkout and silently losing commits to a concurrent
git switch. One worktree per session is the fix, and that isolation covers every command that
writes to disk, not only git.
When Thomas reaches for it
| What is in front of you | Reach for |
|---|---|
| A ticket on the frontier with an empty assignee | dispatch-ticket (claim, then worktree) |
| The dispatch itself, once claimed | dispatch-ticket + dispatch-ticket-claude |
| A Shaper, QA or Rin needs a pane, not just a Builder | Same skill, same mechanics; only the tab and pane label prefix changes |
| A pane went idle or blocked mid-turn | WATCHING.md (companion reference, not a separate skill) |
| The Builder handed back and it is time to remove the worktree | CLEANUP.md (companion reference) |
Prerequisites
- The workspace watchdog is running for this project. A dispatch with no watchdog is a hard
stop, not a warning (
exit 1, not anecho). - The harness payload is committed, not just allow-listed past
.gitignore. A worktree only contains tracked content, so an untracked.agents/roles/builder.mdmeans the Builder starts with no contract at all. .agents/orchestrator.mdhas a real row (runtime, model, effort) for the role being dispatched. A row reading<set-me>is undecided, and undecided is a stop at dispatch time.- Read the contract for the dispatched role first (
builder.mdfor a Builder), since this skill only carries the mechanics, not what the role is supposed to do with them.
What it leaves behind
| What happened | Where it lands |
|---|---|
| The claim | The tracker’s assignee field: builder/<ticket-id> |
| The dispatch itself | .astraler/state/dispatch-record.json, keyed by ticket id: branch, worktree, workspace, tab, pane, runtime, write-set |
| The worktree and branch | <repo-root>/.claude/worktrees/<branch-slug>, gitignored, removed at cleanup |
| The brief | Sent as the first line, a plugin-qualified slash command (/mattpocock-skills:implement <ticket>) |
| The Builder’s own work | Commits on the ticket branch, pushed before handback |
Known failures
Pulled from harness/.agents/memory/recurring-failure-modes.md. Everything below is marked
promoted: fixed and already in the contract this page describes.
- Shared checkout lost commits. Two root sessions sharing one checkout silently lost commits to a
concurrent
git switch. Fixed: one worktree per session, no exceptions. - Uncommitted allow-list was invisible. A harness allow-listed but not committed is invisible inside every Builder worktree. Fixed: a commit check before the first dispatch.
- Pasted brief never submitted. A multi-line brief pastes into the composer without submitting, and the
pane reports
idlewhile it sits unsent. Fixed: an explicit Enter after the paste, plus requiring the watcher to observeworkingbefore believing the turn began. - “Done” only meant the turn ended.
TERMINAL:donemeans the turn ended, not that the work finished; a Builder parked on background work reads as done. Fixed: check OS processes and the runtime status line before concluding it finished — the verdict is the markers, checked withscripts/check-simplify-markers.sh. agent promptfused onto a stale, unsent brief instead of replacing it. Re-sending afterNO_STARTappended the new text to whatever was already sitting in the composer and submitted both as one turn, reporting success (AST-154). Fixed: read the composer first; clear it withesc esc—ctrl-candctrl-uwere measured to leave the text in place.- The watcher never re-armed. The per-turn watcher covers one turn and exits; nothing re-arms it, and the re-arm is the step skipped right after a long task. Fixed: every new turn gets a new watcher.
- Work stopped before commit. A Builder that stops before committing leaves work that only exists on disk;
git worktree removedeletes it silently. Fixed: “commit, push, then return” as three separate actions.
It’s working if
- The watchdog was confirmed running before the first dispatch of the session, not assumed.
.astraler/state/dispatch-record.jsonhas an entry for every live ticket, with a write-set.- Every submitted brief has a watcher armed against it within the same action, not as a separate step done afterwards.
- A pane’s tab and pane labels match the role dispatched (
builder:<id>versusspec:<id>,qa:<id>,rin:<id>), because a wrong prefix is invisible to the watchdog. - Cleanup only removes a worktree after
git status --shortis empty andcheck-simplify-markers.shis green.
Where it fits
The frontier query (in thomas.md) decides which ticket is next → dispatch-ticket claims it
and puts a builder role into a pane → the Builder runs its own closed loop (implement →
review → simplify → /skills/codex-arm) and hands back → dispatch-ticket’s CLEANUP.md
retires the worktree once the artifact is verified → a milestone gate goes through
/skills/review-with-rin before merge. thomas and builder are the two role contracts this
skill sits between.