Watch
1
0
Fork
You've already forked souveraine
0
souveraine/saf/memory/02-replay.md
Fimeg aae9344fe1 saf: write the replay page, retire two stale claims
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.
2026-08-13 20:45:21 -04:00

75 lines
3.2 KiB
Markdown

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