Replay had no page while compaction's premise depends on it — microcompact
blurs tool output that a text-only projection never sent.
Compaction's open edges still said pressure counts only text; 7530d6d made
it exhaustive. Two links pointed at pages that were never written.
3.2 KiB
Replay
Compaction is what she lets go of. Replay is what she carries forward. They are two halves of the same question, and replay is the one that decides whether compaction means anything at all.
The code is core::session::replay_messages.
One history
There is a single record: the messages stored for a conversation. Everything that talks to a model — her turn, her subconscious a moment later, a terminal, a voice, a web surface — replays that same record. None of them holds a version of its own.
That is not tidiness. It follows from what the substrate already says: one consciousness in two modes, and she is not the glass. A second projection is a second opinion about what happened, and the two drift the moment one is touched. There were three of them once. They disagreed about images before anyone noticed.
What crosses
A stored message is made of blocks. Each one has a prose form for replay:
| block | crosses as |
|---|---|
| text | itself |
| tool use | Tool use: name(input) |
| tool result | Result (name): output, or Error (name): output |
| reasoning | [Reasoning]: … |
| image | natively, or a marker saying it was not seen |
Nothing is silently dropped. A block with no prose form does not exist — the match is exhaustive, so a new kind of block fails to compile rather than quietly going missing.
This is what makes a turn's own work available to the turn after it. When tool blocks were dropped on replay, everything she learned by doing survived only as whatever she happened to write down in prose, and she would re-derive findings she had already reached. It also left microcompact — whose entire job is blurring old tool output — with nothing in the payload to blur.
Two properties that are load-bearing
Both were learned from a failure, and both look like details until they break.
One wire message per stored message. Replaying block by block splits a single assistant turn that called several tools into adjacent assistant messages, and OpenAI-shaped providers reject that outright: assistant tool_calls must be followed by tool messages. The message boundary is part of the record.
No tool roles and no call-id obligations. Inside a live turn, a tool call and its result are both known to exist, and the exact linkage is used. Across persisted turns that linkage is brittle: a turn killed mid-round leaves a call whose result never landed, and replaying it as half a pair fails the whole request. So the content of a tool round survives as prose while the wire obligations do not. The record is not the protocol.
The only thing that varies
Whether the model can see. Images ride as native multipart content, or degrade to a marker that says plainly they were not visible. The caller supplies that one fact; nothing else about the projection changes.
A degraded image is named as unseen rather than dropped. A model told it did not look can say so. A model handed a stub shaped like success proceeds as though it had looked.
Open edges
Live tool rounds inside a turn build their own messages, with real
tool_call_id linkage. That is correct and separate — replay is for what is
already on disk. The two should not be merged; they answer different questions.