Skip to content

The session loop

Pigeon is built around one loop. If you get the loop right, everything else falls out of it.

FIG.02 The four moves, in order Each call carries a specific payload. Get the four right, and the loop closes.
  1. 01 READ

    briefMe

    session 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
  2. 02 WRITE

    Work

    during 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
  3. 03 PERSIST

    saveHandoff

    session 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[]
  4. 04 FORWARD

    Resume

    next 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

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?”

  1. briefMe at the start of every session

    One 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 getBoard every session. It’s cheaper, and it focuses the agent on what changed rather than dumping the full board.

  2. Work, with intent on every write

    When the agent moves a card, updates it, or comments on it, it passes a short intent string 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.

    Card detail panel showing a rich description with What / Why / Try it / Outcome sections, tags, checklist, and comments from both human and agent authors — the structured surface the agent reads and writes.
  3. saveHandoff before wrapping up (or /handoff in Claude Code)

    One call that does four things:

    • Saves a structured handoff (summary, workingOn, findings, nextSteps, blockers)
    • Runs syncGitActivity to 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

    saveHandoff does not auto-move cards. Transitions need an intent — 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.

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

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 storage
Diff since handoff: 3 new commits touching #12, 1 comment on #7
Top 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: 0

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

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.

  • Don’t skip briefMe because “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. nextSteps is 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.

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