Lesson 0002 · ~15 minutes

Handoff lifecycle: outbox → inbox → done

One skill: trace a single git_handoff through directories, helpers, and headers — the domain core of any Rust/TS rewrite.

Win for this lesson

You can walk a handoff on a whiteboard: who writes which file, who moves it, which header appears when — without opening the Babashka sources.

Prefer pictures?

Open the interactive handoff visualization — step through directories, headers, and the agent loop. Best after (or alongside) this lesson.

1. Why files, not “agent chat”

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:

2. Three scripts the agent is allowed to use

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.

3. State is the directory

Each role worktree owns:

.swarmforge/handoffs/
  outbox/   (+ tmp/)     sent/   failed/
  inbox/new/   inbox/in_process/   inbox/completed/
Draft → outbox

Agent writes headers only. swarm_handoff validates, generates body + reserved headers, writes outbox/tmp/…tmp, renames into outbox/*.handoff, deletes the draft.

Daemon delivery

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/).

Claim → complete

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.

Restart rule

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.

4. What the agent drafts vs what the system invents

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):

Filename sort key (priority, time, sequence) orders the queue; scripts still read truth from headers, not from parsing the name.

5. Lossy wake-up, durable truth

After delivery, the daemon types something like:

You have new handoff mail. If idle, run ready_for_next.sh.

Deliberate properties:

For a TUI rewrite: replace tmux send-keys with “flash the role panel / play a tone” — keep the durable inbox.

6. Agent loop (task mode)

  1. Wake or restart → ready_for_next.sh.
  2. NO_TASK → stop waiting for work.
  3. TASK: → use printed PAYLOAD only (and TASK_NAME if present).
  4. Do owned work; for pipeline progress, commit + swarm_handoff.sh.
  5. done_with_current.sh → may print next TASK: or NO_TASK.
  6. While working, ignore extra wake-ups.

Notes are tasks too: act (or acknowledge), then done_with_current before accepting anything else.

Check yourself

Question 1

Which step moves a handoff from inbox/new into inbox/in_process?

Question 2

An agent puts from: coder and created_at: … in the draft. What happens?

Question 3

A wake-up is lost while the agent is idle. Where is the work?

Question 4

After finishing a task, why does done_with_current call ready again?

Question 5

Which commit abbreviation is valid for git_handoff?

Primary source (read next)

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).

Ask the teacher Unclear on batch mode, failed deliveries, or how you’d model this queue in Rust/TS? Ask here before the next lesson (batch + chain-forwarding rules, or launcher/worktrees — depending on what you want next).

Mission link

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.