Skip to content

refactor(runtime): derive Session transcripts from RuntimeEvents #4791

Description

@Astro-Han

The problem in one picture

Imagine recording the same conversation in two notebooks. While the conversation is happening, we read notebook A. As soon as it finishes, we switch to notebook B.

Even if both notebooks are meant to say the same thing, they are written separately. A crash, retry, or reconnect can catch them at different points.

That is the current transcript architecture:

flowchart LR
    subgraph Today["Today: one conversation, two ledgers"]
        direction TB
        Run1["Runtime execution"] --> Events1["RuntimeEvent"]
        Run1 --> Messages1["StoredMessage"]
        Events1 --> Active["Active transcript"]
        Messages1 --> Completed["Completed pages"]
    end

    subgraph Target["Target: one ledger, many views"]
        direction TB
        Run2["Runtime execution"] --> Events2["RuntimeEvent"]
        Events2 --> Projection["Bounded transcript projection"]
        Notes["UI notes"] --> Projection
        WorkHub["WorkHub facts"] --> Projection
        Projection --> View["StoredMessage view"]
    end
Loading

RuntimeEvent records what happened. StoredMessage describes how transcript data is presented. They should not both be durable authorities for the same ordinary execution fact.

What happens today

Ordinary runtime conversation facts are persisted through two paths:

  • RuntimeEvent records the execution timeline.
  • AiSdkTurn, ToolRuntime, and AgentRun also persist equivalent StoredMessage conversation records.

Those writes do not share one commit order. The read path then switches authority at the turn boundary:

  • while a root turn is active, its transcript overlay is projected from RuntimeEvent;
  • after the turn becomes terminal, completed pages are read from persisted StoredMessage records.

The same conversation can therefore be reconstructed from different durable representations before and after completion. Recovery, reconnect, pagination, and migration all depend on those representations remaining equivalent even when their writes settle in different orders.

The architectural rule

RuntimeEvent is the single durable semantic authority for ordinary Session execution facts.

Everything else has one clear role:

  • Transcript pages are bounded read projections over the RuntimeEvent timeline. A page must not require loading the full Session.
  • SessionEvent remains live transport for active clients; it is not a second storage authority.
  • StoredMessage remains a presentation and protocol DTO. It is not being deleted; normal runtime execution simply stops persisting it as an equivalent second ledger.
  • UI-only notes and WorkHub coordination keep their own legitimate semantic authorities. They are composed with the runtime projection at the transcript read boundary instead of being forced into RuntimeEvent.

What changes

  1. Normal execution writes each ordinary user, assistant, tool, usage, and turn-state fact to the RuntimeEvent authority.
  2. Active overlays and completed transcript pages use the same bounded projection over that authority.
  3. The projection supplies stable item identities and paging cursors across terminalization, reconnect, and restart.
  4. UI-only and WorkHub facts join the transcript at the read boundary.
  5. Direct normal-runtime persistence of equivalent StoredMessage records is removed from AiSdkTurn, ToolRuntime, and AgentRun.

What does not change

  • StoredMessage remains the client-facing presentation/protocol shape.
  • SessionEvent remains the live transport shape.
  • UI-only notes and WorkHub coordination keep their separate authorities.
  • Provider model-history replay, compaction, and recap remain under refactor(runtime): establish model history boundaries #4779.
  • This issue does not introduce byte-exact provider request replay.

Compatibility and cutover

  • Supported imported Sessions must follow one forward path: migrate or backfill once, or fail at an explicit compatibility boundary.
  • Unmarked 0.1.x data does not need to be supported.
  • Any wire or paging semantic change updates the Runtime Host compatibility epoch.
  • Transitional import, comparison, or repair code has one direction. Obsolete paths are removed after cutover rather than becoming permanent parallel authorities.

Acceptance criteria

  • Active overlays and completed transcript pages derive ordinary execution facts from the same RuntimeEvent authority.
  • Direct normal-runtime persistence of equivalent StoredMessage conversation records is removed.
  • Paging remains bounded and item identities and cursors remain stable across turn completion, reconnect, and process restart.
  • Recovery tests prove that supported transcript projections can be rebuilt from durable facts.
  • Imported Session handling has an explicit one-way migration/backfill or rejection boundary.
  • Transitional comparison and repair paths are removed after cutover.
  • Wire or paging semantic changes update the Runtime Host compatibility epoch.

Why this is separate from #4779

#4779 establishes the provider-history boundary: what model history is replayed, compacted, and summarized for the next provider request.

This issue changes durable transcript storage and paging. It has different failure modes, compatibility concerns, and recovery tests. Folding it into #4779 would make two authority migrations share one review surface without making either safer.

Out of scope

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions