Context is provider-owned; history is T3-owned
T3 Code retains a product-visible thread history and an opaque provider continuation cursor, while each provider owns the prompt context it may resume or compact; the pinned system has no universal T3 long-term-memory subsystem.
What this chapter resolves
- Separate a provider's prompt context from T3's durable thread history, session binding, and live context telemetry.
- Explain what a persisted resume cursor can and cannot promise after a server or provider-session restart.
- Read compaction as a provider event normalized into T3 activity, rather than as a universal T3 memory operation.
- Keep client drafts, offline delivery, and historical usage accounting in their proper later or separate lanes.
“Memory” is an attractive word for a system with threads, histories, resumable sessions, cached screens, drafts, and transcript accounting. It is too imprecise to describe the pinned T3 Code implementation.
T3 owns a durable product record for a thread: messages, activities, plans, session projection, and related work state. It also constructs the visible current turn payload—message text, attachment paths, model and mode choices—before calling an adapter. The provider-native session owns the accumulated conversation context, hidden history, and any private compaction state used when that turn reaches a model. T3 stores a provider-specific, opaque resume cursor with the provider-session binding so it can ask that provider to continue later. That cursor is a continuation handle, not a portable copy of a prompt, summary, embedding index, or cross-provider memory format.
1. Five similarly named things have different owners
| Thing | Primary owner | What T3 keeps | What it does not establish |
|---|---|---|---|
| Current turn input | T3 constructs the visible request; adapter maps it to native input | user text, attachments, selected model/mode, and resulting observations | the provider’s accumulated hidden conversation state |
| Accumulated native context | Provider harness and its native session | provider identity, runtime mode, optional cursor, and normalized observations | hidden history, compaction result, model state, or portability to another provider |
| Thread history | T3 orchestration projection | durable messages, activities, plans, session and work overlays | that every provider token, hidden message, or native item was captured |
| Resume cursor | Provider defines the shape; T3 persists it opaquely | JSON-valued cursor bound to one thread/provider instance | a universal checkpoint, a durable provider callback, or a guarantee that native history still exists |
| Context-window telemetry | Provider emits it; T3 normalizes/latest-selects it | a context-window.updated activity when usable |
a full history, exact prompt contents, cost settlement, or a cross-provider meter |
| Client cache, draft, or outbox | A client surface | locally scoped presentation/delivery state | server-side memory or a provider continuation |
The provider-session contract explicitly makes resumeCursor an optional
Unknown value. The persistent provider-runtime row stores it as JSON alongside
provider/instance identity, session status, runtime mode, last-seen timestamp, and
adapter payload. This is intentionally a narrow handoff boundary: a provider
adapter receives its own cursor back at session start, rather than T3 decoding one
generic “conversation memory” representation.
export const ProviderSessionRuntime = Schema.Struct({
threadId: ThreadId,
providerName: Schema.String,
/**
* User-defined routing key for the configured provider instance that
* owns this session. Nullable only at the storage/migration boundary:
* rows persisted before the driver/instance split carry only
* `providerName`. Repository consumers must materialize a concrete
* instance id before routing.
*/
providerInstanceId: Schema.NullOr(ProviderInstanceId),
adapterKey: Schema.String,
runtimeMode: RuntimeMode,
status: ProviderSessionRuntimeStatus,
lastSeenAt: IsoDateTime,
resumeCursor: Schema.NullOr(Schema.Unknown),
runtimePayload: Schema.NullOr(Schema.Unknown),
});
export type ProviderSessionRuntime = typeof ProviderSessionRuntime.Type;
export const GetProviderSessionRuntimeInput = Schema.Struct({ threadId: ThreadId });
export type GetProviderSessionRuntimeInput = typeof GetProviderSessionRuntimeInput.Type;
export const DeleteProviderSessionRuntimeInput = Schema.Struct({ threadId: ThreadId });
export type DeleteProviderSessionRuntimeInput = typeof DeleteProviderSessionRuntimeInput.Type;
/**
* ProviderSessionRuntimeRepository - Service tag for provider runtime persistence.
*/
export class ProviderSessionRuntimeRepository extends Context.Service<
ProviderSessionRuntimeRepository,
{
/**
* Insert or replace a provider runtime row.
*
* Upserts by canonical `threadId`, including JSON payload/cursor fields.
*/
readonly upsert: (
runtime: ProviderSessionRuntime,
) => Effect.Effect<void, ProviderSessionRuntimeRepositoryError>;
/**
* Read provider runtime state by canonical thread id.
*/
readonly getByThreadId: (
input: GetProviderSessionRuntimeInput,
) => Effect.Effect<
Option.Option<ProviderSessionRuntime>,
ProviderSessionRuntimeRepositoryError
>;
/**
* List all provider runtime rows.
*
* Returned in ascending last-seen order.
*/
readonly list: () => Effect.Effect<
ReadonlyArray<ProviderSessionRuntime>,
ProviderSessionRuntimeRepositoryError
>;
/**
* Delete provider runtime state by canonical thread id.
*/
readonly deleteByThreadId: (
input: DeleteProviderSessionRuntimeInput,
) => Effect.Effect<void, ProviderSessionRuntimeRepositoryError>;
}
>()("t3/persistence/ProviderSessionRuntime/ProviderSessionRuntimeRepository") {}
const ProviderSessionRuntimeDbRowSchema = ProviderSessionRuntime.mapFields(
Struct.assign({
resumeCursor: Schema.NullOr(Schema.fromJsonString(Schema.Unknown)),
runtimePayload: Schema.NullOr(Schema.fromJsonString(Schema.Unknown)),
}),
);2. A restart uses a handle, not a replayed prompt
The recovery path first looks for an active compatible session. If none is available, it requires a persisted provider binding and a non-null continuation cursor. It restores the stored working directory, selected model information when available, runtime mode, and the cursor, then asks the same provider adapter to start. The adapter is the place that interprets that cursor.
This distinction is concrete in the adapter implementations:
- Codex turns a valid cursor into the native
threadIdsupplied when opening its app-server thread, then returns the provider thread id as its next cursor. - Claude validates a provider-shaped resume state before choosing the native resume session id.
- OpenCode treats its persisted
ses_…id as a session lookup. It reuses the native session only when its directory matches; a directory move can fork the native session to preserve that provider’s conversation history, while a known missing session begins fresh.
These are adapter-specific policies—not an inter-provider migration protocol. A Codex thread id is not meaningful to Claude; an OpenCode session id is not an executable representation of a Codex prompt.
Zoom with the controls, +/−, or Ctrl/⌘ + trackpad scroll. Enable Pan to drag, use two-finger scrolling, or use the arrow keys. 0 fits the diagram; Esc leaves Pan or expanded view.
Text equivalent
A provider owns accumulated native context. T3 constructs a visible turn request and receives normalized runtime observations. T3 stores product thread history and a provider session binding containing an opaque cursor. On restart T3 gives that cursor to the same provider adapter. The adapter attempts provider-specific native-session recovery. A successful recovery can emit new observations into T3 history. A failed or missing provider session does not cause T3 to synthesize the provider's hidden context from the displayed thread history.
packages/contracts/src/provider.ts:34–86 ↗apps/server/src/persistence/ProviderSessionRuntime.ts:35–110 ↗apps/server/src/provider/Layers/ProviderService.ts:411–544 ↗apps/server/src/provider/Layers/CodexSessionRuntime.ts:466–503 ↗apps/server/src/provider/Layers/CodexSessionRuntime.ts:1749–1769 ↗apps/server/src/provider/Layers/ClaudeAdapter.ts:656–689 ↗apps/server/src/provider/Layers/ClaudeAdapter.ts:4170–4254 ↗apps/server/src/provider/Layers/OpenCodeAdapter.ts:1204–1387 ↗3. Compaction is performed upstream and recorded downstream
The generic runtime vocabulary can report thread.state.changed and a canonical
context_compaction item type. The provider adapters turn their own native signals
into those observations. Codex maps its native thread/compacted notification to
the canonical compacted state. Claude maps a compact_boundary system message
to both an updated usage snapshot (when it can normalize one) and a compacted
thread-state event.
Runtime ingestion accepts a canonical thread.state.changed whose state is
compacted and appends a durable context-compaction activity. It does not
take T3’s message rows, create a provider summary, replace the provider’s native
context, or reset a turn merely because a compaction activity was observed.
This matters for comparisons between provider capabilities. The canonical event union can represent a compaction signal, but an adapter that does not emit one will not fabricate it. A context compaction activity is useful provenance in the work log; it is not itself a complete history of what the provider retained, discarded, or summarized.
4. A context meter is current telemetry, not memory
thread.token-usage.updated carries a provider-normalized snapshot with required
usedTokens and optional maximum/context categories. Runtime ingestion admits a
usable snapshot as one context-window.updated activity. The client reducer
supersedes earlier resolvable snapshots with the same turnId value. The web
selector then walks backward across the activities returned for the thread and
uses the newest valid context-window update it can find. That returned thread
detail is itself bounded to the 500 most recent activities.
Thus the meter answers a narrow operational question: what is the latest usable provider-reported context observation available for this thread? It does not sum all past context sizes. It does not expose the prompt text. It does not turn a provider’s total-processed counter into historical usage, cost, or memory capacity.
At the pinned revision, the live emission paths inspected in Chapter 20 are Codex and Claude. Cursor, Grok, and OpenCode should not be shown as providing a T3 context meter merely because their model metadata or native runtimes may have a context-window concept.
// ── Activities ──────────────────────────────────────────────────
case "thread.activity-appended": {
const activity = event.payload.activity;
// A resolvable context-window update supersedes earlier resolvable ones
// for the same turn: consumers only read the latest value (walking the
// array backwards), and providers stream these updates continuously, so
// retaining the history grows the thread by thousands of rows over a
// long session. Mirrors the server-side snapshot rule in
// dropStaleContextWindowActivities; retention stays per turn so a
// thread.reverted that discards turns can still resolve a value from
// the turns that survive.
const supersedesContextWindow = isResolvableContextWindowActivity(activity);
const activities = pipe(
thread.activities,
Arr.filter(
(entry) =>
entry.id !== activity.id &&
!(
supersedesContextWindow &&
entry.turnId === activity.turnId &&
isResolvableContextWindowActivity(entry)
),
),
Arr.append(activity),
Arr.sort(activityOrder),
);
return {
kind: "updated",
thread: { ...thread, activities, updatedAt: event.occurredAt },
};For the separate historical question—tokens/cost-shaped records from provider transcript files—read Chapter 20, “Usage accounting without a false ledger”. That scan is neither the source of the live context meter nor a T3 memory layer.
5. “Long-term memory” is absent as a universal product subsystem
Inference from the pinned generic contracts and recovery paths: this revision does not establish a universal T3 long-term-memory subsystem. Those inspected paths model projects, threads, messages, activities, plans, checkpoints, provider sessions, current-turn payloads, and opaque continuation state. They do not define a cross-provider memory entity or a generic contract for injecting retrieved facts into a future provider turn.
That is not a claim that a provider lacks its own native memory/history feature. It is a boundary claim about what T3 Code itself standardizes at the source lock. A provider-specific session resume, its own transcript files, a generated plan, a Git checkpoint, and a client draft are not interchangeable evidence of a universal product-memory feature.
6. Client-local state belongs to a different recovery story
The shared client runtime can cache an environment’s thread snapshot and seed its
event-subscription afterSequence from the cached shell sequence. That helps a
screen catch up; it does not act as provider context or server authority. Mobile
additionally owns environment-scoped local drafts and a persisted command outbox.
Those are client resilience/delivery mechanisms and receive their detailed
treatment in Chapter 33 rather than being rebranded as memory here.
The distinction is particularly important after a disconnected mobile action: delivery retry can re-attempt a client command, while provider session recovery still depends on the server’s binding and the provider’s ability to use its cursor. Neither mechanism means a client has captured a complete native model context.
Work the ownership and restart ledger
Choose a teaching event, then inspect which record changes, who is authoritative, and what a later server restart can actually attempt. The lab has no autonomous motion; keyboard tab navigation and a complete static/print ledger are included.
Ask what survives before calling it memory
Select a teaching event. The ledger identifies authority, durable records, and the narrow recovery claim; it never simulates a model or invents a provider prompt.
Event 1 of 5 · live provider work begins
A provider receives a turn in its own native session
T3 constructs the visible current-turn payload. The provider-native session owns the accumulated conversation context and private compaction state.
| Record | Authoritative owner | This event | After server restart |
|---|---|---|---|
| Accumulated native context | provider-native session | T3's explicit request enters provider-owned history | provider-specific resume only |
| T3 thread history | T3 orchestration | message/activity projection may commit | durable product record reloads |
| Resume cursor | provider shape; T3 persists binding | adapter may return/update it | same adapter receives opaque cursor |
| Context telemetry | provider emits; T3 latest-selects | only if a usable event arrives | latest retained activity may render |
| Client cache/draft/outbox | client surface | surface-specific local state | not a provider prompt or server authority |
T3 constructs and records the visible request around a turn. It does not own or serialize the provider's accumulated hidden conversation context.
context-current-turn-payloadprovider-session-contractSelected event: Send a turn.
Static ownership and restart reference
| Question | Answer at the pinned revision |
|---|---|
| Who owns exact model prompt context? | T3 constructs the visible current-turn request; the selected provider's native session owns accumulated conversation context and hidden provider state. |
| What does T3 persist to attempt continuation? | A provider/instance-bound runtime record with an opaque resume cursor, runtime/session metadata, plus separate thread projections. |
| What does compaction add to T3? | A normalized context-compaction activity when an adapter reports a compacted provider thread; no generic T3 summary/prompt replacement is established. |
| What is the context meter? | The newest valid provider-reported context-window activity available in the bounded thread snapshot, not a prompt archive, aggregate usage total, or cost ledger. |
| Is there universal long-term memory? | No such cross-provider T3 subsystem is established by the inspected generic contracts/service paths. This is an audited inference, not a claim about native provider features. |
| What about local drafts/outbox/cache? | Client-owned resilience/presentation state. It is deliberately deferred to Chapter 33 and cannot replace provider-session recovery. |
Boundary checks to carry into another meta-harness
- Persist a provider-continuation handle only with its provider/instance identity; never treat it as a portable prompt serialization.
- Keep a durable product conversation useful on its own terms, while stating that it is not proof of hidden native context or tool state.
- Represent compaction as provider-originated provenance unless the product owns the summarization algorithm and its exact replay/retention semantics.
- Name context telemetry, task usage, and transcript accounting as distinct data products. Chapter 20’s historical ledger must not silently become a memory system.
- If a universal memory feature is proposed, design its user control, source provenance, deletion/retention, isolation, provider injection, and restart failure behavior explicitly rather than piggybacking on a resume cursor.