Skip to content
C.W.K.
Stream
Lesson 04 of 04 · published

A Letter Carries Itself

~15 min · handoff, state-machines, guards, multi-agent

Level 0Loose Note
0 XP0/36 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete

A Letter Is a Request to Your Next Self

Of the four root files, NEW-SESSION-REMINDER.md was the odd one: not work for another brain, but a note from the session that is ending to the session that will start next. Dad's framing settled where it belonged. A session letter from past-me to future-me is a delegation request — from the current session to the next session of the same brain. Same shape, same lifecycle, so it moved into Coop too.

It first rode along as a third kind of request. A day later, on 2026-05-13, it moved into its own letters table, because a letter needs a richer state machine than open and closed. The defining invariant is that a letter is a self-letter: from_brain == to_brain. The schema does not enforce that with a CHECK; write_letter simply has no way to address anyone else — it resolves the writer's identity once and stores it in both columns. Letters belong to terminal sessions: a WebUI vessel has the vault loaded and does not need a cold-start note.

Six States, and Why read Is One of Them

A letter is unread until the next session reads it. The first read promotes it to read; later reads only bump read_count and last_read_at. That makes reading a letter a write, which is why the engine lists read_latest_letter among its mutating operations. start moves read → working. ack closes it as done from either read or working. abandon closes any live letter with a reason. And superseded happens to a letter automatically, as you will see in a moment.

The read state is the one that earns its keep. It captures Dad and I are brainstorming on this letter, nothing is executing yet. A session that crashes while a letter is read is meaningfully different from one that crashes while it is unread: a conversation happened, no commit landed. Every transition stamps its own timestamp, so the row itself carries that forensic trail.

One Live Letter per Brain

At most one letter per brain is ever live (unread, read or working). write_letter inserts the new letter and then supersedes every other live letter from the same brain: status and close_reason become superseded, superseded_by points at the new id, and each old letter gets a supersede event. The old letters keep their read counts and start times. The chain is walkable in either direction.

Why insist on one? Because the next session reads exactly one letter — the latest live one — and two live letters would force it to choose between them by guessing. Supersession turns "which note is current?" from a judgment into a fact. There is a small nudge on the other side, too: when a todo is marked done, the call returns the id of any live letter whose body mentions that todo, so the command line can remind the session to acknowledge the letter that asked for it.

The Body Is the Handoff

At the end of August 2026, sessions working at the context ceiling did something reasonable-looking. Instead of carrying their state in a letter, they wrote a HANDOFF.md into a render directory. One letter shrank to a short table of contents pointing at such a file. Another HANDOFF.md described open work with uncommitted code and had no Coop row at all — no letter, no todo, no request. It existed only as a file in a large render directory that was not a git repository and was not in any backup list.

The ledger is backed up, synced to the fleet, and never deleted. A working directory is none of those, and a path on one Mac does not exist on the next Mac at all. A pointer to a file that may not be there is not a handoff; it is a hope. And nothing was in the way of doing it right: body is TEXT with no practical length limit (one letter runs to 13,750 characters), and letter write --body - reads standard input, so a heredoc of any size lands in one call. In the design document's words: "The tokens are the same either way; only the destination differs, and only one destination survives."

The Guard and Its One Discriminator

Doctrine alone had already been routed around once, so the rule got a mechanical layer. assert_no_handoff_pointer runs first thing in write_letter, and in ack_letter whenever an acknowledgement body is given. It knows six stems of the handoff genre — handoff, handover, session-notes, session-state, new-session-reminder, context-dump — and looks for each followed by .md, case-insensitively.

The whole design hangs on one character: the one immediately before the stem. If it is / or ~, the text is a path, and a path to a handoff file is the anti-pattern itself, so the write raises HandoffPointerError and nothing is stored. A bare HANDOFF.md is talk about the anti-pattern and stays legal. Replayed over all 140 letters that existed on 2026-08-31, the guard refused exactly one — the real case — and passed every letter that merely used the word "handoff" and every genuine locator, such as a pointer to a long script read.

There is deliberately no override flag. A session at the context ceiling would reach for one, and that is precisely the moment the guard exists for. The refusal message carries the fix instead: inline the state; a path may appear as a locator for bulk material, never as the handoff itself. The test for a good letter is simple: whether or not the next session ever follows a pointer in it, the letter alone must already tell it the state, the open work and the constraints.

One honest edge: the guard covers the letter body and a non-empty ack body. abandon's optional reason is not checked. An abandoned letter is terminal and never handed to the next session, so the gap is small, but it is a gap, and now you know where it is.

A handoff is the state itself, delivered to where the next reader will look. A pointer to a file moves the record out of the one store that survives, and the moment you most want to write one is the moment you are least able to notice what it costs.

Code

The letter state machine, as the domain functions enforce it·text
unread --first read------> read          (read_latest_letter; later reads bump read_count)
read   --start-----------> working       (brainstorming is over, execution begins)
read | working --ack-----> done          (skipping working is fine)
unread | read | working
       --abandon---------> abandoned     ("we decided not to act on this")
       --a new letter----> superseded    (superseded_by = the new letter's id)

At most one letter per brain is ever in unread / read / working.
The engine's guard logic, error text trimmed — runnable as is·python
# The stems and the discriminator are the engine's; the error text is trimmed.
_HANDOFF_STEMS = ("handoff", "handover", "session-notes",
                  "session-state", "new-session-reminder", "context-dump")

class HandoffPointerError(ValueError):
    pass

def assert_no_handoff_pointer(body: str) -> None:
    lowered = body.lower()
    for stem in _HANDOFF_STEMS:
        needle = stem + ".md"
        start = 0
        while (hit := lowered.find(needle, start)) != -1:
            start = hit + 1
            # Path-shaped == a separator or ~ immediately before the stem.
            if hit > 0 and lowered[hit - 1] in "/~":
                raise HandoffPointerError(
                    f"refusing to write a letter that points at '{stem}.md'"
                    " — the letter body IS the handoff.")

bodies = [
    "Never write a HANDOFF.md again; the state is below.",
    "Full handoff: render done, upload pending, see todo #12.",
    "Script read is long; locator: ~/scratch/SCRIPT-READ.md",
    "Everything is in ~/work/ep2/HANDOFF.md",
    "Picked up from ./out/session-notes.md",
]
for body in bodies:
    try:
        assert_no_handoff_pointer(body)
        print("accepted:", body)
    except HandoffPointerError as exc:
        print("REFUSED: ", body, "->", exc)
What it prints·text
accepted: Never write a HANDOFF.md again; the state is below.
accepted: Full handoff: render done, upload pending, see todo #12.
accepted: Script read is long; locator: ~/scratch/SCRIPT-READ.md
REFUSED:  Everything is in ~/work/ep2/HANDOFF.md -> refusing to write a letter that points at 'handoff.md' — the letter body IS the handoff.
REFUSED:  Picked up from ./out/session-notes.md -> refusing to write a letter that points at 'session-notes.md' — the letter body IS the handoff.

External links

Exercise

Find the last handoff you left for your future self or a teammate: an end-of-day note, a pull request description, a message to an AI session that will pick up your work. Give it the next-session test: if every link and file path in it were dead, would the reader still know the current state, the open work, and the constraints? Rewrite it until the answer is yes, keeping paths only as locators for material too large to inline. Then run the guard sketch above against your own old notes and see what it refuses.
Hint
The usual failure is a note that says what to read rather than what is true: "see notes.md", "details in the doc". Turn each of those into the two or three facts the reader actually needed from that file. If you cannot say them, you did not know the state either, which is worth discovering before the next session does.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign in — Please sign in to comment.

No comments yet — be the first.