Lesson 0002 · ~15 minutes
One skill: trace a single
git_handoff
through directories, helpers, and headers — the domain core of any
Rust/TS rewrite.
You can walk a handoff on a whiteboard: who writes which file, who moves it, which header appears when — without opening the Babashka sources.
Open the interactive handoff visualization — step through directories, headers, and the agent loop. Best after (or alongside) this lesson.
The handoff protocol exists to stop agents from owning the tmux socket. Work transfer is durable files; tmux only carries a generic wake-up after delivery.
That split is load-bearing for rewrites:
send-keys, a TUI
event, or a beep — as long as it stays lossy and does not name a file.
| Script | When |
|---|---|
swarm_handoff.sh <draft> |
Outbound: after commit (for git handoffs) |
ready_for_next.sh |
Inbound: on wake-up, restart, or after finish |
done_with_current.sh |
Inbound: work fully complete (including notes) |
ready_for_next / done_with_current only
dispatch on the role’s
receive mode
(task vs batch). This lesson sticks to
task mode; batch is a thin wrapper over the same idea.
Each role worktree owns:
.swarmforge/handoffs/
outbox/ (+ tmp/) sent/ failed/
inbox/new/ inbox/in_process/ inbox/completed/
Agent writes headers only. swarm_handoff validates,
generates body + reserved headers, writes
outbox/tmp/…tmp, renames into
outbox/*.handoff, deletes the draft.
handoffd polls outboxes (~1s). Copies to each
recipient inbox/new/ (skip if already there), adds
recipient + enqueued_at, sends wake-up,
moves original to sent/ (or failed/).
ready_for_next_task: if one file already in
in_process, re-print it; else take first sorted
new/ file, set dequeued_at.
done_with_current_task: set completed_at,
move to completed/, then call ready again.
Always run ready_for_next.sh. If work was mid-flight,
the in-process file is the source of truth — not the agent’s memory
and not the lossy wake-up.
Agent draft for a pipeline handoff:
type: git_handoff
to: cleaner
priority: 50
task: task-1-cave-setup
commit: a1b2c3d9e8
Rules enforced by swarm_handoff.bb (and the protocol doc):
id, from, timestamps, …).merge_and_process <sender> <commit>.
Filename sort key (priority, time, sequence) orders the queue; scripts still read truth from headers, not from parsing the name.
After delivery, the daemon types something like:
You have new handoff mail. If idle, run ready_for_next.sh.
Deliberate properties:
inbox/new/.
For a TUI rewrite: replace tmux send-keys with “flash the
role panel / play a tone” — keep the durable inbox.
ready_for_next.sh.NO_TASK → stop waiting for work.TASK: → use printed PAYLOAD only (and TASK_NAME if present).swarm_handoff.sh.done_with_current.sh → may print next TASK: or NO_TASK.
Notes are tasks too: act (or acknowledge), then
done_with_current before accepting anything else.
Which step moves a handoff from inbox/new into
inbox/in_process?
An agent puts from: coder and
created_at: … in the draft. What happens?
A wake-up is lost while the agent is idle. Where is the work?
After finishing a task, why does
done_with_current call ready again?
Which commit abbreviation is valid for
git_handoff?
Read
handoff-protocol.md
sections: Directory Layout,
Message Types (git_handoff),
Queue Helper Scripts (task variants),
Agent Queue Rules. Local:
/tmp/swarm-forge/swarmforge/handoff-protocol.md.
Optional skim of implementation: handoffd.bb (deliver +
notify), swarm_handoff.bb (validate + atomic outbox write).
This lifecycle is the protocol layer you must preserve (or consciously redesign) when migrating. Terminal adapters and Babashka are incidental; directory-as-queue + validation gate + lossy notify is not.