Tutorial: run two coordinating sessions for the first time¶
By the end of this tutorial you will have two Claude Code sessions working the same repo side-by-side, sent a message from one to the other, watched the recipient acknowledge it, and handed a piece of work across — all through the durable git blackboard, with nothing online but git.
This is a learning exercise: every step is concrete and you will see real
output. It assumes the protocol is already installed on this repo (it is, on
po-platform). If you are setting it up on a fresh project first, do
how-to-onboard.md and come back here.
What you need: two terminals, the po-platform repo cloned, and
npx lefthook installalready run once (so the hooks fire). You can play both "sessions" yourself — that's the point of the tutorial.
Step 1 — claim your first session¶
In terminal 1, from the repo root:
You'll see something like:
fetching origin/main and creating worktree at /…/po-platform-sA ...
[session-sA <hash>] [sA] docs(ai-sessions): claim sA at 2026-05-31T11:40Z
====================================
Session sA claimed.
Worktree: /…/po-platform-sA
Branch: session-sA
Next:
cd /…/po-platform-sA
====================================
Three things just happened, automatically:
- A git worktree was created at
../po-platform-sA— a separate checkout with its own index. This is Layer 0: terminal 1 can nowgit addfreely without ever touching another session's staging area. - The script picked the lowest unused id (
sA) and the first unused display name from the pool (Sintra). - It wrote your claim row into
docs/ai/sessions/active.mdand committed it atomically.
Now enter the worktree:
Look at the row it added:
| sA · Sintra | (Riff TBD) | worktree `session-sA` → direct-to-main, `[sA]` | 2026-05-31T11:40Z | 2026-05-31T11:40Z · | scope TBD — update this row when you pick a Riff |
That's your identity (sA · Sintra), your branch, your claim time, your presence
timestamp, and a placeholder scope. You are now a visible, claimed session.
Step 2 — claim a second session¶
In terminal 2, from the original repo root (not the worktree):
This time the script sees sA is taken, so it claims sB · Douro, creates
../po-platform-sB, and commits a second claim row. Enter it:
cd ../po-platform-sB
git pull --ff-only origin main # pick up sA's claim row
grep '^| s' docs/ai/sessions/active.md
You should now see both rows — sA · Sintra and sB · Douro. The scoreboard
is the shared, durable picture of who's live.
Try the safety net. Still in the
ThesBworktree, attempt a mislabelled commit:commit-msghook aborts it: That'scheck-worktree-prefix.sh— the backstop that makes failure mode #1 structurally impossible. A correct[sB]subject would pass.
Step 3 — send a message from sB to sA¶
You are sB · Douro and you want to ask sA · Sintra a question. You don't have
sA's phone number — you have the git blackboard.
In terminal 2 (the sB worktree), append a message block to sA's inbox:
cat >> docs/ai/sessions/queue/to-sA.md <<'EOF'
## 2026-05-31T11:45Z · from:sB · to:sA · type:question · ref:— · status:sent
Are you about to touch `docs/ai/parallel-sessions.md`? I want to add a Layer 7
section and don't want to collide.
→ status:seen <ts> · acked <ts> · resolved <ts>
EOF
git add docs/ai/sessions/queue/to-sA.md
git commit -m "[sB] docs(ai-sessions): ask sA about parallel-sessions.md edit"
git push origin HEAD:main
Notice the header grammar (full table in reference.md):
from / to / type / ref / status, and the trailing → status: line that
the recipient will advance. The message is now durable and addressed.
Step 4 — sA receives and acknowledges¶
Switch to terminal 1 (the sA worktree). You're sA · Sintra. Pick up the bus:
There's the question from sB. You advance the ack chain in your own commit.
Edit the → status: line of that block to mark it seen and acked, and append your
answer (still as sA, editing your own inbox is fine):
## 2026-05-31T11:45Z · from:sB · to:sA · type:question · ref:— · status:sent
Are you about to touch `docs/ai/parallel-sessions.md`? ...
→ status:seen 2026-05-31T11:50Z · acked 2026-05-31T11:50Z · resolved 2026-05-31T11:52Z
## 2026-05-31T11:52Z · from:sA · to:sB · type:answer · ref:— · status:sent
No — I'm only in docs/developers/. Go ahead with Layer 7, the file is yours.
→ status:seen <ts> · acked <ts> · resolved <ts>
git add docs/ai/sessions/queue/to-sA.md
git commit -m "[sA] docs(ai-sessions): ack sB's question + answer (file is free)"
git push origin HEAD:main
The key idea: the recipient advances the status, in their own commit. When
sB next pulls, it will read seen/acked/resolved — proof the ball was
caught, not an assumption. That is the lossless ack chain.
Step 5 — watch presence update itself¶
You just committed twice as sA. Look at your scoreboard row again in terminal 1:
The Presence timestamp (5th cell) has moved forward to your last commit time —
you didn't touch it by hand. That's the session-heartbeat.sh post-commit hook.
It bumped last_seen and left the change in your working tree (it never
auto-commits), so it rides along on your next commit. Anyone reading the board now
sees that sA is genuinely recently alive, not just claimed-and-maybe-asleep
(failure mode #6, closed).
Even if the hook were uninstalled, your liveness would still be derivable from the latest
[sA]commit ingit log. The mechanism degrades gracefully.
Step 6 — hand a piece of work across¶
Suppose sA realises the Layer 7 work really belongs to sB, who's already in
that file. That's a handoff — a type:handoff message plus a reassignment.
In terminal 1 (sA):
cat >> docs/ai/sessions/queue/to-sB.md <<'EOF'
## 2026-05-31T11:55Z · from:sA · to:sB · type:handoff · ref:— · status:sent
Handing you the Layer 7 doc work — you're already in parallel-sessions.md and I'm
booked on the comms package. Over to you.
→ status:seen <ts> · acked <ts> · resolved <ts>
EOF
git add docs/ai/sessions/queue/to-sB.md
git commit -m "[sA] docs(ai-sessions): handoff Layer 7 doc work to sB"
git push origin HEAD:main
If this work were tracked as a Riff, the atomic version of the handoff is a
single tracker call swapping assignee → sB and advancing status — the
durable record-of-truth (Tier 2). The queue message carries the narrative
("why, and what next"); the Riff carries the state.
When sB pulls, it sees the handoff, advances it to acked, and the
single-owner invariant holds throughout: at no point were both sessions "owning"
the Layer 7 work.
Step 7 — clean up¶
When a session is done, remove its row (or mark it paused) and remove the worktree. In each terminal:
# edit docs/ai/sessions/active.md — delete your row, or change Scope to
# "paused — resuming <ETA>" — then commit + push:
git add docs/ai/sessions/active.md
git commit -m "[sA] docs(ai-sessions): self-close sA"
git push origin HEAD:main
cd .. # leave the worktree
git worktree remove po-platform-sA # (after pushing)
git branch -D session-sA # optional: drop the local branch
What you learned¶
- Worktree isolation (Layer 0) made two sessions on one repo collision-proof,
and the
commit-msghook proved it by rejecting a mislabelled commit. - The git blackboard carried an addressed message from
sBtosAwith nothing online but git. - The ack chain (
sent → seen → acked → resolved) made the exchange lossless — the sender can confirm the message was caught. - Presence updated itself mechanically via the
post-commithook. - A handoff moved ownership across sessions while preserving the single-owner invariant.
Next: skim reference.md for the exact grammar, or — if you're
taking this to another repo — follow how-to-onboard.md.