Skip to content

One trace view over two backends, and one implementation of every derived shape

A trace lives in SQLite while its run is live and in Parquet once the run closes. analysis must not know which: it asks for a run's view and reads the same five shapes either way — events(filter), beliefs(persona_id), edges(), verbatims(grouping), resolve(trace_ids). The registry's status decides which backend answers.

A view opens on a live or paused run, not only a finished one. A paused run keeps its completed worlds and partial results are valid results; if views needed finalization first, those kept worlds would be unreadable by the module whose job is to interpret them, no run could be watched while it ran, and the only code path tests exercised would be the one a long study never uses. Because a tick is recorded whole (ADR 0033), a live view is always consistent — it is merely behind.

The binding property: the same view answers identically before and after finalization. Take every read shape on a live world, finalize it, take them again, compare. A digest computed at one moment and at the next must not differ because the storage changed underneath it.

beliefs.parquet and edges.parquet are derived shapes, not separate records: beliefs come from belief snapshots and turns, edges from word-of-mouth deliveries and follows. Their derivation lives in the view and nowhere else, and the Parquet files hold exactly what the view would have computed, written by that same code. A second implementation on the finalized side is how the two backends would come to disagree, and re-creates the leak TraceView exists to prevent: the previous design passed trace views as an undefined wide interface, and derivation logic ended up in two consumers.

Considered options

Views only on finalized runs was rejected: it makes a paused run's kept worlds unreadable, blocks progress reporting, and leaves the live path untested. Handing analysis a Parquet path or a DataFrame was rejected because an open handle is not a closed set — anything the caller can reach past becomes an interface nobody owns. Materialising the derived shapes with their own writer was rejected for the disagreement it invites.

Consequences

trace carries two read implementations behind one protocol and a test that holds them to the same answers. The derived shapes cost computation on the live path, which is acceptable because a live read is for watching and for partial results, not for the hot loop. analysis is written once, against a protocol, and never against storage.