On this page

On this page

Technical reference

Session transcript working sets

Status: proposed design.** This page defines a remaining runtime migration;

Status: proposed design. This page defines a remaining runtime migration; it does not claim that append eviction or the consumer cutover is implemented. It adds no configuration option and changes no schema, persisted bytes, retention, session identity, or permissions.

The canonical transcript belongs to SQLite and its existing storage owners. An active SessionManager should retain current context and bounded navigation facts. Acquiring older history for an operation must not install complete history into the live manager. See session state on disk and database access in workers.

Current boundary

Bounded opening already selects raw entries by byte/event budget. The manager's entry array and ID index share one payload graph, while initial model-context acquisition hydrates overlapping messages separately. Appends and complete history hydration can enlarge the resident view beyond its opening budget. The owners are session-manager-core.ts, session-manager.ts, and session-accessor.sqlite-active-context.ts under src/agents/sessions/ and src/config/sessions/ respectively.

The manager is legitimately retained by AgentSession and active-run callbacks. Reducing its working set complements correct run teardown; it does not replace run settlement or allow clearing data that accepted writes still need.

Resident snapshot

Publish one immutable snapshot containing the target binding, database incarnation, transcript version, selected leaf/append cursor, admission fence, and local manager revision. Retain the header, model/thinking settings, boundary counts, current-context entries, and navigation links only for those entries and their required omitted-boundary anchors.

The selection preserves custom messages, branch summaries, compaction/reset retention, tool pairs, replay checkpoints, and prompt-series metadata. Raw and model views share a frozen payload only when their representations agree; entry-ID equality alone does not establish equivalence after projection, redaction, or rewrite. Worker transfer creates receiver-side objects, so a combined acquisition must avoid sending overlapping raw/model payloads twice.

The snapshot records its byte/event counts and completeness. Arbitrary-history results remain operation-owned; they never become a second manager cache. Committed append, suffix replacement, navigation, and rewrite operations must publish a bounded replacement before notifying observers. Failed writes retain the old snapshot. A committed write whose publication fails invalidates the manager through the existing committed-write error path; it is never replayed.

Proposed asynchronous operations

Extend prepareSessionTranscriptHydration and its existing worker owner. The following names describe proposed internal contracts, not current exports. prepareHistoryRead synchronously captures the bound target, owner incarnation, SessionTranscriptContextVersion (generation, rawSeq, updatedAt), admitted turn receipt/anchor, selected branch, append cursor, and local manager revision. Its returned read capability is bound to that live owner:

ts
type HistorySelection =  | { kind: "context"; maxBytes: number; maxEvents: number }  | { kind: "entry"; entryId: string }  | { kind: "branch-page"; leafId: string; cursor?: string; maxBytes: number; maxEvents: number }  | { kind: "navigation"; fromId: string | null; targetId: string }  | {      kind: "custom-page";      customType?: string;      cursor?: string;      maxBytes: number;      maxEvents: number;    };

read(selection, signal) returns the matching result and captured version. Context returns shared raw/model payloads and resident facts. Entry returns an entry or explicit absence. Branch/custom pages return ordered events, counts, and an opaque continuation; only complete: true establishes the end of the selection. Custom selection preserves historical order and duplicates unless that custom type already defines a different semantic reducer. Navigation returns target facts, common ancestor, and bounded labels/child summaries.

Each page validates its version inside the same SQLite snapshot used to read it. A continuation binds the target, branch, version, and next position; it never follows the latest active branch. Release each page before acquiring the next rather than accumulating the entire transcript. Do not hold a SQLite transaction across provider, plugin, or other asynchronous work. A later page rejects a changed version; append-tolerant continuation requires separate proof from the existing read-fence contract, not invented historical snapshot storage.

prepareRewrite({ replacements, expectedVersion }, signal) uses canonical source rows and anchors in the existing worker, returning a preparation handle, source-to-destination mapping, and byte/count deltas. It does not clone a live manager. commitRewrite(handle, signal) rechecks writer authority and source preconditions through the current writer, then returns committed IDs/version and bounded replacement context. Cancellation closes unused preparations; accepted writes retain custody until native settlement, even if their caller stops waiting. Release handles in finally and on owner close or worker exit.

Before returning any result, recheck cancellation and current read authority. Before publication, also recheck target binding, navigation revision, and transcript version. A captured token is not current authority. Writes recheck live authority at transaction admission and immediately before commit. Existing SQLite admission and foreign-commit freshness rules remain the only owners of schema and connection validation.

Results distinguish completed selection, exact-entry absence, version conflict, owner loss, cancellation, and oversized-event refusal. Missing context or a revoked owner is never an empty successful read. A stale read with no effect may be reacquired by its operation owner; an uncertain or completed write must be reconciled through its receipt, never retried as a fresh mutation.

Oversized entries and cleanup

A strict byte budget cannot simultaneously retain an arbitrary complete row. session-manager-retained-data.test.ts exposes the concrete conflict: a manager opened with a 4,096-byte budget later appends a 5 MiB custom payload. Synchronous removeTrailingEntries must remove an aborted assistant, preserve that custom payload exactly, repair its parent, and keep getEntry consistent with storage. Those assertions do not prove that every method is a shipped SDK contract, but they do establish behavior that unconditional eviction would change.

Do not evict the custom row and report successful zero-removal cleanup from an incomplete resident suffix. Zero removals requires proof that the selected suffix has no match. Awaited cleanup must acquire enough version-fenced suffix facts to resolve the predicate boundary, preserving custom bytes in the storage owner when the predicate needs no payload. Arbitrary caller predicates that inspect custom data need an explicit operation-sized acquisition or a versioned API change; they cannot be serialized into a worker query by assumption.

Oversized raw acquisition must have an explicit result rather than silently truncating bytes or skipping an event. Preserve existing strict-reader refusal and model-only overflow projection rules. Any temporary complete-event exception must be measured separately from the resident budget. Do not impose a new stored-content cap to make a memory assertion pass.

Consumer cutover

Consumer Required contract
Compaction Acquire admitted current context and boundary anchors; preserve the pending user entry and tool pairs. Run hooks/providers without holding SQLite open, commit the marker, then publish committed context/accounting.
Replay repair Locate rejected checkpoints or affected thinking entries at the captured branch/version. Rewrite through the existing owner and remove stale prefix-bound checkpoints from re-appended suffix messages.
Tool-result truncation Build the existing plan from versioned current context, preserving trailing-result policy. Reconcile prompt projections against the committed replacement context.
Suffix cleanup Resolve the complete predicate boundary through bounded reverse acquisition. Preserve custom/opaque bytes and parent remapping; an incomplete window cannot establish a no-op.
Tree navigation and summaries Acquire omitted target/common-ancestor facts without installing complete history. Summarize only the required abandoned path, commit leaf/summary/labels, and publish bounded context.
Forking Copy the canonical selected path in the storage owner; return committed destination identity and bounded context.
Rewrite preparation Replace resident byId source assumptions and cloned detached managers with worker-owned source anchors and preparation handles. Preserve pending-input relocation, compaction identity, and side-append topology.
Setup, settlement, guards, and accounting Consume admitted current-context snapshots or named latest-marker/boundary facts; do not scan history or maintain independent caches.
ReadonlySessionManager extension readers Introduce an awaited, versioned history capability. Keep identity/current-context facts synchronous; migrate bundled readers and hook preparation together.
ProviderReplaySessionStateV2.getCustomEntries Replace its synchronous generic history requirement through an approved versioned capability; preserve ordering/duplicates and propagate read failures rather than returning empty state.

The pure algorithms in packages/agent-core keep explicit prepared inputs. They do not gain database access or a competing interpretation of boundaries.

SDK and incognito migration boundaries

Current ReadonlySessionManager exposes synchronous entry/branch/tree readers. ProviderReplaySessionStateV2 still inherits synchronous getCustomEntries; the provider migration adds awaited writes, not awaited history reads. The synchronous readers already exist in the published stable v2026.9.5 source. A complete audit of the exported reader surface and its completeness guarantees remains necessary. Verify that evidence and obtain SDK-owner acceptance before changing a shipped signature, completeness guarantee, or compatibility window. A legacy complete synchronous-array contract cannot also promise universally bounded residency. Do not silently redefine it as a partial window or keep an internal twin without a verified public contract and removal boundary.

Production incognito continues to use its exact process-owned database until the existing canonical worker migration activates. Never open another :memory: database to satisfy a read. Capture the current owner and reject its closure/replacement. This design does not activate that migration or change incognito expiry, restart loss, or retention.

First slice and completion evidence

Start with worker-owned rewrite acquisition and committed-context publication, migrating replay repair and tool-result truncation together. Remove their cloned-manager preparation path; retain compatibility only where the shipped SDK audit requires it. This private slice does not complete arbitrary tree or extension history access and must not claim a universal memory bound.

Freeze published canonical payloads and use replacement objects for sanitation, redaction, and explicit rewrites. Eviction only drops references: it does not edit a published prompt prefix, remove stored events, or change existing stable prompt refresh rules. Compaction keeps its explicit context-replacement role.

Proof must cover 2k/10k/50k histories, 1,000 committed appends, oversized custom cleanup, omitted-branch navigation, and rewritten history. Measure manager, main-isolate, worker, and transient-operation retention separately; test actual Gateway latency with concurrent sessions and release through real run settlement. Exercise revision changes, target/authority loss after awaits, incognito owner closure, cancellation, and commit-before-publication failure. Preserve compaction, replay, redaction, and prompt-byte behavior. No schema or update migration follows from these process-local projections; earlier code reads the same stored bytes.

Was this useful?