Skip to content

feat(core): persist v2 session context epochs#30789

Draft
kitlangton wants to merge 4 commits into
devfrom
feat/core-v2-session-context-epoch
Draft

feat(core): persist v2 session context epochs#30789
kitlangton wants to merge 4 commits into
devfrom
feat/core-v2-session-context-epoch

Conversation

@kitlangton
Copy link
Copy Markdown
Contributor

@kitlangton kitlangton commented Jun 4, 2026

Why

V1 persisted the transcript but rebuilt most privileged system context at request time. A restarted Session could therefore see a different date, environment, or instruction text than the original run.

This PR gives the V2 runner a durable, private Context Epoch timeline:

Persist the initial context once, append admitted changes chronologically, and replace the baseline only at a safe boundary.

Runtime Flow

The V2 runner samples Location-scoped Context Components immediately before provider dispatch. Exact admitted bytes cross the synchronized Session event boundary before they influence the request.

sequenceDiagram
  participant Runner as V2 Session Runner
  participant Source as Context Components
  participant Events as Session Events
  participant Store as Private Projection
  participant LLM as Provider

  Runner->>Source: sample keyed components
  Source-->>Runner: baseline or changed values
  Runner->>Events: publish initialized / updated / replaced
  Events->>Store: project exact admitted bytes
  Store-->>Runner: baseline + chronological updates
  Runner->>LLM: system baseline + private updates + transcript
Loading

The ordinary transcript API remains unchanged. Hidden context updates are loaded only by the runner.

Epoch Lifecycle

An epoch has one immutable baseline. Changed components append absolute current-state updates. Model switches and completed compactions request lazy replacement; temporary unavailability defers replacement instead of silently dropping admitted context.

stateDiagram-v2
  [*] --> Uninitialized
  Uninitialized --> Active: first safe provider turn\ncontext.initialized
  Active --> Active: component changed\ncontext.updated
  Active --> Active: component removed\ntombstone update
  Active --> ReplacementPending: model switched or compaction ended
  ReplacementPending --> ReplacementPending: admitted component unavailable\ndefer replacement
  ReplacementPending --> Active: next safe provider turn\ncontext.replaced
Loading

The synchronized event types are:

Event Meaning
session.next.context.initialized.1 Persist the first immutable keyed baseline
session.next.context.updated.1 Append changed or removed component state within the epoch
session.next.context.replaced.1 Persist a fresh immutable baseline after a replacement request

Storage Model

Filesystem and service producers answer what exists now. Session events preserve what the model saw then. Disposable projections keep provider-turn reads cheap.

flowchart LR
  Sources[Filesystem and services\ncurrent values] -->|sample| Runner[V2 runner]
  Runner -->|exact bytes| Events[(Synchronized Session events\ncanonical history)]
  Events --> Epoch[(session_context_epoch\nactive baseline + checkpoint)]
  Events --> Updates[(session_context_message\nprivate chronological updates)]
  Transcript[(session_message\nordinary transcript)] --> Request[Provider request]
  Epoch --> Request
  Updates --> Request
Loading

The initial representation deliberately stores admitted bytes in both canonical events and disposable projections. Checkpoint hashes are change detectors, not lookup keys. A content-addressed blob store remains a measured follow-up optimization if large instruction or skills payloads justify it.

Tool Safety

Chronological system updates must never split unresolved local tool calls from their results. The shared protocol guard tracks all unresolved local call IDs across the message prefix, including multi-tool turns.

sequenceDiagram
  participant Assistant
  participant Guard as Protocol Guard
  participant ToolA as Tool Result A
  participant ToolB as Tool Result B

  Assistant->>Guard: local tool calls A and B
  ToolA->>Guard: result A
  Guard--xGuard: reject system update while B is unresolved
  ToolB->>Guard: result B
  Guard-->>Guard: chronological update is now safe
Loading

Unsupported privileged chronological messages lower to escaped <system-update> user wrappers. Anthropic Opus 4.8 retains its existing native lowering where placement is legal.

Scope

This is wired end-to-end into the V2 Session runner before provider dispatch. V1 is unchanged.

Currently populated Context Components:

  • location-scoped environment facts;
  • current date.

Follow-up slices:

  • project and nested instruction files;
  • selected-agent and provider guidance;
  • skills-index guidance;
  • plugin transforms;
  • compaction execution and automatic context-pressure compaction;
  • storage metrics before any blob-store optimization.

The reviewed design for instruction observation, optional watcher-backed caching, URL sources, and later skills reuse lives in specs/v2/refreshable-context-sources.md.

Verification

  • bun typecheck in packages/core
  • bun run migration --check in packages/core
  • focused Core suite: 99 pass, 0 fail
  • isolated Location integration: 1 pass, 0 fail
  • bun typecheck in packages/llm
  • focused protocol suite: 141 pass, 0 fail
  • targeted live chronological-system recordings: OpenAI Chat, OpenAI Responses, Anthropic Messages, and Gemini
  • full LLM suite with recorded-cassette replay: 258 pass, 28 skip, 0 fail
  • git diff --check
  • repository-wide push typecheck: 22 tasks, 0 failures

@kitlangton kitlangton force-pushed the feat/core-v2-session-context-epoch branch from 477bf1d to 4c180e2 Compare June 4, 2026 19:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant