The transcript is a terrible database.

It's append-only, it's lossy on compaction, you can't index it, you can't grep it, and the moment it gets large enough to matter, it starts evicting the parts you needed. The filesystem next to the transcript, meanwhile, is already a pretty good database. Stable. Inspectable. Re-readable. Has tools.

Project context made AGENTS.md part of the harness. This post is the broader claim: the agent's memory of the work should mostly live in files, not in the chat history that wraps them. The chat is working memory; the filesystem is the store.

Split diagram showing a fragile chat transcript beside a durable filesystem with README, fixtures, tests, logs, and references loaded by Pi.

Add an ugly fixture

tiny-ledger parses clean receipts fine by now. Real-world receipts are not clean. Add the worst one I could come up with:

cat > fixtures/cafe-ugly.txt <<'EOF'
*** CAFE EXAMPLE ***
1x Coffee.............$3.50
1x Everything Bagel...$4.25
-- subtotal: $7.75 --
tax...................$1.01
TOTAL.................$8.76
EOF

Dotted leaders, dollar signs, line-item quantities, subtotal sandwiched in dashes, ALL CAPS total. The natural reaction is to paste this whole blob into the prompt: "here is the receipt format I want supported." Don't.

The right move:

pi @README.md @fixtures/cafe-ugly.txt "Add support for this receipt format. Keep the parser small and add a regression test."

Three things changed. The model doesn't have to retransmit the fixture. The harness loads the file by reference. And when the model needs to look at the fixture again later in the loop, it reads the file — it doesn't have to recall it from earlier in the conversation.

Pass-by-reference everywhere

The pattern generalizes. Any artifact that exists on disk should be referenced, not pasted:

  • README, AGENTS, CHANGELOG → pi @README.md
  • the failing test → pi @test/parseReceipt.test.js
  • a directory of fixtures → pi @fixtures/
  • a long log file → pi @logs/run-2026-05-19.log
  • prior session output → pi @.pi/sessions/last-run.md

Every reference replaces a paste. Every paste replaced is a transmission error avoided.

There's a secondary benefit that's easy to miss: when the model needs to re-read something halfway through the session, the file is still there. Compare to the alternative where the only copy of the fixture was in the prompt — the model has to either trust its compressed recollection or ask the human to paste it again. The filesystem keeps the canonical version available; the chat history just has a snapshot at the time it was loaded.

/tree and /compact are part of this

Pi exposes a couple of in-session commands that lean into the filesystem-as-database posture:

  • /tree shows the current repo layout. Cheap, useful when the agent needs to orient.
  • /compact summarizes the current session so far, frees context, and writes the summary into the session log on disk.

/compact is the one that makes the file vs transcript split obvious. The session log is a file. After compaction, the transcript is shorter, but the log still has the full record. Anything the model needs from the pre-compaction work is queryable on disk, not stuck in evicted chat history.

This is the same architecture argument as the black-box recorder piece in my memory series — the transcript is working memory, the filesystem is the black box. Treat them like two stores with different jobs and the harness stops fighting its own context budget.

Transmission errors are the failure mode

Here's the thing nobody pitches you on when they sell you on long context windows. The window is bigger; the transmission through the window still degrades. Every time the model has to remember and re-emit a long artifact (a fixture, a function, an error trace), there's a small probability of corruption — a typo, a missed line, a hallucinated import.

The probability is small. The artifacts pile up. The session compounds. After thirty turns, you're not getting back the same fixture you put in; you're getting back the model's best guess of the fixture filtered through thirty turns of context.

Pass references. Let the model read from disk. Let the file be the source of truth. The harness's job is to keep state out of the model's memory and in inspectable artifacts.

What the filesystem isn't good at

Two honest gaps:

  • Cross-session memory. Files survive a session restart, but they don't tell the next session what to load. That's where AGENTS.md, repo docs, and the trace store come in. The filesystem is the store; the harness still has to know where to point.
  • Semantic recall. Files are great for known-paths. They're bad for "find me the function that does X across this codebase." That's where grep/ripgrep/embedding indexes do their job — and they all read from the filesystem anyway, so the file is still the source.

The filesystem isn't replacing the brain. It's replacing the short-term recitation step the brain shouldn't have to do.

Exercise

For tiny-ledger:

  1. Run a session where you only use references — no pasting fixtures, code, or errors into the prompt. Note what breaks.
  2. Run the same task with everything pasted inline. Note the token count and how often the model re-emits content you already gave it.
  3. Run /compact mid-session in the reference version. Confirm the model can still read the fixture after compaction and the work survives.

The third one is the test that matters. If the post-compact work depends on the model remembering something the harness didn't write to disk, you've found a transmission edge that'll bite you later.

Next primitive

The next post in the series is about verification gates — the move from "the model says it ran the test" to "the harness ran the test and here's the output." Once the filesystem is doing the state job, the gate is what closes the loop.