Skip to content

Transcript projection

Steven Enamakel edited this page Sep 1, 2026 · 3 revisions

Transcript projection

A shared desk has one transcript and several readers. Projection is the fold that turns that transcript into what one participant sees.

The defect this fixes

The host this library was extracted from projected a session into a (role, content) pair, which discards the author of every reply. On a shared desk the consequence is that agent B reads agent A's replies as B's own prior turns. A system notice, a workflow report, and a real teammate all arrive looking identical, and an agent that cannot tell its own words from a colleague's cannot coordinate with that colleague.

SessionMessage carries attribution instead:

pub enum SessionAuthor {
    Operator,
    Person { id: String, label: String },
    Agent  { id: String, label: String },
    System { kind: String, label: String },
}

A label is captured with the row rather than looked up later, so a rename does not rewrite history.

The paging walk

The host owns the log and lends it through one port, implemented in src/session/:

pub trait SessionLog: Send + Sync {
    fn read_before(&self, before: Option<Sequence>, limit: usize) -> SessionFuture<'_>;
}

Implementations return rows newest first and treat before as an exclusive bound. The trait is object safe and picks no executor, so the host keeps its choice of runtime.

project_session walks backwards through pages until it has a window of qualifying messages, then hands them back in chronological order. Three constants bound it:

constant value meaning
SESSION_WINDOW 30 qualifying messages requested for a turn
PAGE_SIZE 512 maximum raw rows in one host read
SCAN_LIMIT 2048 maximum raw rows inspected in one projection

Hitting SCAN_LIMIT is a successful partial projection, not an error. A busy channel with one relevant thread in it should still produce a turn.

A desk is not flat, and the walk knows it. A thread-scoped query returns the root and its direct replies. A channel-level query returns every root and each root's first reply, so an agent reading a desk sees answers rather than a run of unanswered questions, and still never reads a second thread's interior. See Threads.

Page validation is strict. A host that violates ordering, uniqueness, size, or cursor contracts gets a typed error rather than a plausible-looking history.

Continuous sharing

The second defect: the transcript was re-read only when an agent rebound to a different chat. An agent taking two consecutive turns in one thread therefore missed a peer's reply that landed between them, which on a shared desk is most of the point.

sharing replaces that gate with a watermark the caller owns.

let mut state = initialized_state(conversation, watermark);
match prepare_delta(&log, &state, &query).await? {
    SharingPlan::Delta(delta) => { /* apply, then commit the state */ }
    SharingPlan::Reinitialize { reason } => { /* full initialization */ }
}

A delta carries only what the agent has not seen. Reinitialization is requested for three named reasons, and naming them is the point: a host that has to fall back to a full re-seed should know which of these happened.

reason when
ConversationChanged the requested, bound, or stored conversation differs
GapTooLarge the watermark was not crossed inside the bounded scan
WatermarkUnavailable the host log ended before reaching the watermark

State is returned, never applied. The caller commits it after the turn is durably appended, so a crash between the two leaves the watermark behind rather than ahead. note_present records a later row the host already accepted without moving the watermark, bounded by PRESENT_SET_LIMIT at 64.

Clone this wiki locally