-
Notifications
You must be signed in to change notification settings - Fork 0
Extension State
contractVersion: 1.0.0
Per-extension state persisted by the active Session Store alongside the session manifest. The manifest records only slot metadata (see Session Manifest § Shape); the payload itself lives in a separate persistence surface owned by the store. Declared in the contract; serialized by core; scoped strictly to the declaring extension.
Meta-contract guidance. This page describes the state-slot mechanism shared by every category contract. It is not itself a per-category contract and carries no independent
contractVersionat the meta-shape level. ThecontractVersion: 1.0.0stamped here reflects the first stable version of the slot-payload shape and the verification surface specified below. Breaking changes to the state-slot mechanism are recorded in the Changelog below; individual category contracts own their ownslotVersionfor their specific payload shapes.
An extension's state slot is a blob of typed state identified by the extension's ID. Core owns:
- Storage — the payload lives in a persistence surface owned by the active Session Store. The slim session manifest does not carry per-extension slot metadata; the active store maintains its own slot-summary index out of band. The owning extension's
contractVersionis captured at write time by the store, not by the manifest. - Serialization and deserialization.
- Drift handling on resume.
- Access control (only the owning extension reads or writes its slot).
The extension owns:
- The shape of the data.
- When to update it.
- Migration of its own prior-version data.
classDiagram
class Manifest {
extensions : List
stateSlots : Map~ExtId, SlotMeta~
}
class SlotMeta {
extId : ExtId
slotVersion : SemVer
reference : OpaqueRef
}
class Slot {
extId : ExtId
payload : Opaque
}
Manifest --> SlotMeta : "records metadata"
SlotMeta ..> Slot : "references payload<br/>(separate persistence surface)"
Every contract category may declare a state-slot shape. A category without a state slot is stateless — its extensions cannot persist anything across resumes.
When declared, the contract specifies:
- Shape — a type for the payload (JSON-compatible in v1).
-
Slot version — semver independent of
contractVersion. Bumped when the extension changes its own state shape. - Size class — approximate — "small", "medium" (up to ~1MB), "large" (streamed). Large slots require streaming APIs on the Session Store.
- Sensitivity — whether the slot may contain references to secrets. Slots may never contain resolved secrets. See Secrets Hygiene.
Extensions reach their slot through the Host API:
host.session.stateSlot(extId) // read | write | swap
An extension cannot read another extension's slot. There is no bulk enumeration of slots. Commands that want to clear a slot do so by invoking the owner (through an event the owner listens for, or a dedicated command the owner registered).
A resume may find that the extension set has changed: an extension was removed, its slotVersion was bumped, or its contract version was bumped.
flowchart TD
Start[resume] --> Owner{owner<br/>extension loaded?}
Owner -->|no| Dropped["record 'orphaned slot';<br/>keep in manifest;<br/>do not pass to any extension"]
Owner -->|yes| Version{manifest.slotVersion<br/>== loaded.slotVersion?}
Version -->|yes| Deliver[deliver payload to extension]
Version -->|no| Migrate[call extension.migrate<br/>oldVersion, newVersion, payload]
Migrate -->|success| Deliver
Migrate -->|fail| Fail[drift diagnostic;<br/>severity per contract]
- Orphan slots are preserved for inspection but never delivered. A later session that re-loads the original extension (on the same Session Store) sees its slot again.
-
Migrations are the extension's responsibility. The contract exposes a
migrate(from, to, payload)extension point; if the extension does not implement it, any version bump is treated as a drift failure. -
Drift severity is set by the contract's
validationSeverityfor critical-category extensions (hard-fail on unmigrated drift) and by the extension's config for optional categories.
See also Session Resume flow.
- Atomicity. Slot writes are all-or-nothing per session snapshot. An interrupted write leaves the previous snapshot intact.
- Consistency. A slot read during a turn sees a consistent view; writes are visible to subsequent reads from the same writer within the same turn.
- Isolation. Other extensions never observe a half-written state.
- Durability. After the active Session Store acknowledges persistence, the slot survives crashes.
Parallel SM stage fan-out runs as one compound session turn (Stage Executions — Turn boundary and persistence). The consistency rule above applies per-writer only; across siblings the rule is stricter:
- Every sibling reads the state slot as it stood at the start of the compound turn. Mid-flight writes from peer siblings are not visible to other siblings.
- Each sibling's writes buffer in-memory and land atomically at the compound-turn snapshot alongside any
joinstage's write. -
Commit order is deterministic. Core applies the buffered sibling writes in
NextResult.stages[]index order, then — if declared — thejoinstage's write last. Each write replaces the entire slot payload (see Serialization above); the final write in the sequence wins. - Two siblings writing the same slot therefore mutually clobber under last-writer-wins by index. The canonical way to combine sibling outputs is to declare a
join: its body reads the reducedctx.upstreamand writes the single authoritative payload. See Concurrency and Cancellation — Safe envelope for parallel fan-out.
A sibling cannot observe another sibling's state-slot write during the compound turn. Two mechanisms exist for combining sibling outputs, and they are distinct:
-
Reduced sibling outputs. A
joinstage reads each sibling's reducedStageResultviactx.upstream. This isStageResultpropagation — not a state-slot read; the join does not see siblings' state-slot writes either. -
Committed state-slot writes. The earliest reader of a sibling's (or the
join's) committed state-slot write is a later sequential stage that runs after the compound-turn snapshot has committed.
These guarantees come from the Session Store. Different stores implement them differently (the filesystem store uses a journal-plus-snapshot pattern); the guarantees are contractual, the mechanism is not.
v1 stores slots as JSON. Contracts that need binary blobs declare a base64-encoded field. Large-slot categories (potentially streaming) are a future extension point; v1 does not support streaming state-slot writes.
A slot's payload must be deterministic in its own schema — i.e., two writes with equal data produce equal bytes. This lets the Session Store de-duplicate snapshots and lets replay tooling compare slots across runs.
- Transient turn state. Use in-memory state; it resets on each turn.
- Resolved secrets. Never. See Secrets Hygiene and LLM Context Isolation.
- Large blobs without a streaming opt-in. Blocks the snapshot.
- References to another extension's state. Cross-extension communication rides events/commands/registries, not slot references.
| Category | Typical slot contents |
|---|---|
| State Machines | Current state ID, per-state counters, workflow progress, last user decision |
| Session Stores | Bookkeeping index (slot summaries, write log) — self-referential, so the store writes this first |
| Loggers | Rotation counters (for file loggers) |
| Providers | Auth refresh tokens by reference (the env provider holds the actual value) |
| Context Providers | Cached summaries, last-computed signatures |
| Tools | Retry counters for idempotent tools, cached expensive-to-recompute data |
| Hooks | Debounce counters, window aggregates |
| Commands | Per-command usage counters (non-sensitive) |
| UI | Per-UI preferences (pane sizes, themes) — may also live in config |
These are typical, not normative. Any specific extension's slot shape is defined by that extension's contract and its own versioning.
Every StateSlotShape declaration carries a driftPolicy field. Core consults this policy whenever the stored slotVersion differs from the loaded extension's declared slotVersion.
| Policy | What core does when versions differ |
|---|---|
reject |
Refuse to deliver the slot. Return Session/SlotDriftRejected. Resume continues; the extension receives no state and must start fresh (or fail, if critical). |
warn |
Deliver the stored payload unchanged. Core emits a Deprecation-style warning event on the event bus. The verifier does not emit the event — the caller does. |
migrate |
Call shape.migrate(storedPayload, storedVersion) and deliver the returned payload. A throwing migrator surfaces Session/SlotMigrationFailed. |
A stored blob that carries no slotVersion field is treated as Validation/SlotVersionMissing. This is a hard failure regardless of drift policy — an unversioned blob cannot be safely compared or migrated.
| Code | Class | When |
|---|---|---|
SlotVersionMissing |
Validation |
The stored blob has no slotVersion field. |
SlotDriftRejected |
Session |
Version mismatch under driftPolicy: 'reject'. |
SlotMigrationFailed |
Session |
The migrate function threw; cause carries the original error. |
The session manifest (written by the active Session Store) stores only:
- Message history.
- Attached State Machine state reference (
smExtId,smSlotVersion,smStateRef). - Security mode and project root.
Non-SM extension state slots are not persisted in the slim manifest. Extensions that wish to persist state independently of the SM must implement their own persistence via the Session Store adapter. Core resume must not fail due to non-SM extension drift — missing or drifting non-SM extensions are silently absent on resume.
"Critical-severity drift fails resume" applies only to the attached SM and the Session Store itself. No other extension category can block resume.
- Contract Pattern — where the state slot is declared.
- Session Manifest — how slots are recorded.
- Persistence and Recovery — the durability guarantees.
- Session Resume flow — drift handling in practice.
- Declared
StateSlotShapewithextId,slotVersion,schema,driftPolicy, and optionalmigrate. - Defined three drift policies:
migrate,warn,reject. - Defined
verifyStateSlotfunction with verdict typesStateSlotVerdictandStateSlotFailure. - Defined error codes
SlotVersionMissing,SlotDriftRejected,SlotMigrationFailed. - Exported
stateSlotShapeSchema(AJV-compilable JSON Schema forStateSlotShape). - Documented Q-2 slim-manifest scope: only SM state is in the manifest; non-SM drift never fails resume.
- Execution Model
- Message Loop
- Concurrency and Cancellation
- Error Model
- Event and Command Ordering
- Event Bus
- Command Model
- Interaction Protocol
- Hook Taxonomy
- Host API
- Extension Lifecycle
- Env Provider
- Prompt Registry
- Resource Registry
- Session Lifecycle
- Session Manifest
- Persistence and Recovery
- Stage Executions
- Subagent Sessions
- Contract Pattern
- Versioning and Compatibility
- Deprecation Policy
- Capability Negotiation
- Dependency Resolution
- Validation Pipeline
- Cardinality and Activation
- Extension State
- Conformance and Testing
- Providers
- Provider Params
- Tools
- Hooks
- UI
- Loggers
- State Machines
- SM Stage Lifecycle
- Stage Definitions
- Commands
- Session Store
- Context Providers
- Settings Shape
- Trust Model
- Project Trust
- Extension Isolation
- Extension Integrity
- LLM Context Isolation
- Secrets Hygiene
- Security Modes
- Tool Approvals
- MCP Trust
- Sandboxing
- Configuration Scopes
- Project Root
- Extension Discovery
- Extension Installation
- Extension Reloading
- Headless and Interactor
- Determinism and Ordering
- Launch Arguments
- Network Policy
- Platform Integration
Tools
UI
Session Stores
Loggers
Providers
Hooks
Context Providers
Commands
- First Run
- Default Chat
- Tool Call Cycle
- Hook Interception
- Guard Deny Reproposal
- State Machine Workflow
- SM Stage Retry
- Hot Model Switch
- Capability Mismatch Switch
- Session Resume
- Session Resume Drift
- Approval and Auth
- Interaction Timeout
- Headless Run
- Parallel Tool Approvals
- Subagent Delegation
- Scope Layering
- Project First-Run Trust
- Reload Mid-Turn
- Compaction Warning
- MCP Remote Tool Call
- MCP Prompt Consume
- MCP Resource Bind
- MCP Reconnect