The session loop
Pigeon is built around one loop. If you get the loop right, everything else falls out of it.
- 01 READ
briefMesession start
One call. Returns the previous handoff plus a diff of what changed since. ~300–500 tokens.
- handoff.summary
- structured
- diff.cardsMoved
- since last
- topWork
- 3 candidates
- blockers
- open count
- pulse
- 1-line
- 02 WRITE
Workduring the session
Agent moves cards, updates fields, comments — every write carries an `intent` so the activity feed is readable.
- moveCard
- intent: …
- updateCard
- intent: …
- addComment
- free-form
- planCard
- structured
- 03 PERSIST
saveHandoffsession end
One call. Writes the next handoff, links any new commits via `syncGitActivity`, returns a resume prompt.
- summary
- 1 paragraph
- workingOn
- string[]
- findings
- string[]
- nextSteps
- string[]
- blockers
- string[]
- 04 FORWARD
Resumenext session
Paste the resume prompt into a fresh chat. The next agent calls briefMe and reads exactly what you just wrote.
- boardId
- uuid
- lastHandoff
- ref
- directive
- → briefMe
Why a loop?
Section titled “Why a loop?”Coding-agent conversations have a natural expiration — context windows fill, token costs climb, or you want a clean slate for a new angle. The question isn’t whether the conversation ends, it’s what carries across the gap.
The tracker’s answer: a structured handoff written at the end of one session and read at the start of the next. No re-explaining, no lost decisions, no “wait, what were we doing?”
The four moves
Section titled “The four moves”-
briefMeat the start of every sessionOne tool call. Returns a compact primer — roughly 300–500 tokens — with the last agent’s handoff, a diff of what’s changed since, the top three work-next candidates, active blockers, recent decisions, and staleness warnings.
It replaces the old pattern of calling
getBoardevery session. It’s cheaper, and it focuses the agent on what changed rather than dumping the full board. -
Work, with
intenton every writeWhen the agent moves a card, updates it, or comments on it, it passes a short
intentstring saying why. This shows up in the activity strip and on the card itself.moveCard #12 → "In Progress" (intent: "starting JWT middleware")updateCard #12 (intent: "adding acceptance criteria from spec")addComment #12 "blocked waiting on legal review of token storage"You watch the board update in real time via SSE. Intent is the difference between silent noise and a readable activity feed.
-
saveHandoffbefore wrapping up (or/handoffin Claude Code)One call that does four things:
- Saves a structured handoff (
summary,workingOn,findings,nextSteps,blockers) - Runs
syncGitActivityto link any new commits that reference#N - Reports which cards the agent touched since the last handoff
- Returns a copy-pasteable resume prompt for the next chat
saveHandoffdoes not auto-move cards. Transitions need anintent— a human-readable reason — and the agent should have moved cards to match reality as it worked.For a mid-session checkpoint — when you want to save your place without running the git-sync + touched-cards report — pass
syncGit: false. Same handoff row, lighter ceremony. - Saves a structured handoff (
-
Paste the resume prompt into a fresh chat
The resume prompt is ~2–3 lines. It identifies the board, points at the last handoff, and tells the next agent to call
briefMe. That’s it. The loop closes.
What briefMe actually returns
Section titled “What briefMe actually returns”A trimmed example:
Board: "MVP Sprint" (project: my-saas-app)Last handoff (2h ago, by Claude): Summary: Wired up JWT middleware end-to-end — tests green. Next steps: wire refresh-token rotation, add rate-limit test Blockers: waiting on legal review of token storageDiff since handoff: 3 new commits touching #12, 1 comment on #7Top work-next: #12 auth middleware (In Progress, HIGH) #14 refresh-token rotation (Backlog, MEDIUM) #7 signup form validation (Review, LOW)Blockers: #12 (legal)Open decisions: 0You didn’t explain any of that. The board did.
The summary, nextSteps, and blockers come straight from what the previous agent wrote into saveHandoff. (The full workingOn and findings arrays are persisted too, and you can load them via runTool({ tool: "loadHandoff" }) when you want the detail.)
What goes into saveHandoff
Section titled “What goes into saveHandoff”Think of it as a structured commit message for your mental state. Rough shape:
saveHandoff({ summary: "Wired up JWT middleware end-to-end — tests green.", workingOn: "#12 auth middleware", findings: [ "jose chosen over jsonwebtoken (lighter, native ESM)", "refresh tokens stored hashed via argon2id", ], nextSteps: [ "Add rate-limit test for token rotation", "Document the decision in the auth ADR", ], blockers: ["Legal review of token storage pending"],})Terse is fine. Anything missing goes through as empty fields; the next briefMe will just be a bit thinner.
What not to do
Section titled “What not to do”- Don’t skip
briefMebecause “I remember what we were doing.” You probably don’t, and the agent definitely doesn’t. One tool call, every time. - Don’t move cards without
intent. You’ll lose the signal in the activity feed, which is the whole point. - Don’t write a 2,000-word
summary. Short + specific > long + vague.nextStepsis where the leverage is. - Don’t let cards drift from reality. If the work is done, move the card while you’re working, not at
saveHandoff. The session-end tool won’t fix a stale board.
More on those in Anti-patterns.
Read next
Section titled “Read next”- Design rationale — why local-first, why MCP-native, why the essential + catalog split.
- Anti-patterns — the ways this loop gets misused, and how to avoid them.
- MCP tools — the full tool surface behind the loop.