zidane/ session
Sessions, session stores and the provider-neutral message format that persists a conversation.
import { … } from 'zidane/session'src/session/index.ts
Use zidane/session to keep an agent's history across runs: createSession starts a conversation on a SessionStore and loadSession restores one. The built-in stores are createMemoryStore, createRemoteStore and createFileMapStore. The entry also holds the artifact helpers and the converters between zidane's message format and the Anthropic and OpenAI formats. See Sessions.
import { createAgent } from 'zidane'
import { anthropic } from 'zidane/providers/anthropic'
import { createMemoryStore, createSession, loadSession } from 'zidane/session'
const store = createMemoryStore()
const session = await createSession({ store })
const agent = createAgent({ provider: anthropic(), session })
await agent.run({ prompt: 'Remember that my favorite color is green.' })
await session.save()
const restored = await loadSession(store, session.id)
console.log(restored?.turns.length)Functions#
autoDetectAndConvertFunction#
Converts a wire message of unknown provider into a SessionMessage. Block types decide between fromAnthropic and fromOpenAI. Anthropic is the default.
createFileMapStoreFunction#
Create a single-session SessionStore backed by a file-map adapter.
createMemoryStoreFunction#
Creates an in-memory SessionStore. Nothing is persisted.
createRemoteStoreFunction#
Creates a SessionStore that talks to a REST session API over HTTP. See the module comment for the endpoints it calls.
createSessionFunction#
Create a new session. Async so stores that generate IDs server-side (e.g. a database) can be supported.
currentExtensionSessionFunction#
Returns the session bound by withExtensionSession for the current async context, or undefined when none is bound.
deleteExtensionSessionStateFunction#
Removes one value from an extension namespace in the session metadata and saves the session when it has a store. Does nothing when the value does not exist.
demoteStaleRunningStatusFunction#
Demote a persisted 'running' status to 'idle' (in place) and return the
data. A persisted 'running' is necessarily stale: the run that set it lived
in another process (crash, kill -9, power loss) and cannot still be executing
in a freshly-loaded session. Call this on the result of a raw store.load()
so consumers don't render a ghost in-progress state or refuse a new run.
loadSession applies it automatically; direct store.load() callers (e.g.
the TUI) should run loaded data through this.
Run RECORDS get the same treatment: the agent persists each record at
run start with status: 'running' (crash durability — see the
SessionStore.updateRun contract), so a freshly-loaded session may
carry 'running' runs that died with their process. They demote to
'aborted' so replay renders a truthful "aborted" state instead of a
perpetually-in-flight run.
dropInvalidToolPairingsFunction#
Drop-only normalization of persisted history: delete structurally invalid tool blocks so the transcript never re-triggers wire-time pairing repairs turn after turn.
Unlike ensureToolResultPairing — which SYNTHESIZES placeholder
content and must therefore stay wire-only (fabricated results would poison
persisted history and training data) — this pass performs pure deletions of
blocks that are provably invalid regardless of interpretation:
| Corruption | Action |
|---|---|
duplicate tool_call id within one run lane (across or within turns) | drop the later instance |
duplicate tool_result.callId within one turn | drop the later instance |
tool_result with no matching tool_call in the immediately preceding same-lane assistant turn | drop the block |
| turn emptied by the drops above (reasoning-only remainders count as empty) | drop the whole turn |
Duplicate detection is scoped to the WIRE-VIEW lane: tool_call ids only
collide when both instances reach one model request. By default a turn's
lane is its raw runId — correct for parent/subagent interleaving, where
providers minting per-conversation counter ids (call_0, call_1)
legitimately reuse the same id across lanes. Callers that know the run
topology (see createSession) pass laneForRun to widen lanes to true
wire views: all depth-0 runs share the main conversation view, and a
resumed subagent shares its resume-chain ancestors' view — duplicates
across those runIds DO reach a single request (wire-time
ensureToolResultPairing mode 4 would strip them on every send) and are
healed here.
Orphan tool_use blocks (a call whose result was never persisted —
interrupted run) are deliberately KEPT: they are authentic model output,
and the wire-time pass patches them with a synthetic result on every send
without touching history.
Deletions cascade by design: dropping a duplicate tool_call orphans its
tool_result, which is then dropped too; a turn emptied by either is
removed entirely.
Safe to persist: deterministic, idempotent (re-running on healed output is a no-op returning the same reference), and fabricates nothing.
Pure: does not mutate input turns or their content arrays.
fromAnthropicFunction#
Converts an Anthropic wire message into a SessionMessage.
fromOpenAIFunction#
Converts an OpenAI wire message into a SessionMessage.
getExtensionSessionStateFunction#
Reads one value an extension stored in the session metadata. Returns a copy, or undefined when the session, extension or key is missing or a name is unsafe.
interruptedToolResultOutputFunction#
Output of the synthetic result that closes a tool call interrupted by a
process death. Keeps SYNTHETIC_TOOL_RESULT_PLACEHOLDER as a prefix
so consumers that reject fabricated results still match it.
loadSessionFunction#
Load an existing session from a store.
matchesArtifactFilterFunction#
Tests an artifact against a filter. An undefined filter matches everything. limit is not applied here.
mergeArtifactFunction#
Apply an upsert to an existing row: identity fields (id, runId,
turnId, createdAt) stay, label refreshes, meta and projectRoot
refresh when the incoming artifact has them, and updatedAt becomes the
later of the two. Shared by every store so the
merge semantics can't drift.
mergeSessionMetadataFunction#
The metadata a saveMeta should store: incoming wholesale without
keys, otherwise stored with only keys taken from incoming.
normalizeArtifactInputFunction#
Validate + normalize a raw input (typically from tool-result metadata,
i.e. an untrusted boundary). Returns null when the entry is unusable
— missing kind/uri/label or oversized in a way truncation can't fix.
label is clipped, meta is dropped (not the artifact) when it exceeds
the byte bound.
resumeChainRunIdsFunction#
Transitive resumedFromRunId chain starting at runId (inclusive).
A resumed run only persists the turns IT produced — the turns it was seeded with still carry the original run's id. So reconstructing a resumed child's full conversation means collecting turns from every run in its resume lineage, not just the latest link. Cycle-guarded (a malformed chain terminates instead of looping).
selectArtifactEvictionsFunction#
Which artifacts to evict so a session stays at or under cap after
one more insert. file artifacts go first (noisiest, cheapest to
regenerate), oldest first within a kind; ties keep the caller's order
(stable sort), so pass rows in insertion order. Pure — callers delete
the returned ids.
setExtensionSessionStateFunction#
Stores a JSON value under an extension namespace in the session metadata and saves the session when it has a store. Does nothing without a session.
Throws when a name is unsafe or the value exceeds the size limits (16 KiB per item, 64 KiB for all extension state).
sortArtifactsFunction#
Newest first, stable on id.
toAnthropicFunction#
Converts a SessionMessage into an Anthropic wire message. Foreign reasoning blocks are dropped following reasoningWireAction.
toOpenAIFunction#
Converts a SessionMessage into an OpenAI wire message.
toWireMessagesFunction#
Build a wire-ready SessionMessage[] from raw persisted SessionTurn[].
The canonical "I want to send these to a provider" projection. Use
this — not raw session.turns / store.getTurns() — when constructing a
provider request outside agent.run(). The agent's own wire pipeline
(executeTurn in src/loop-turn.ts) applies the same repair just-in-time;
downstream consumers that bypass the loop hit tool_result must be preceded by a tool_call 400s precisely because they shipped raw turns.
Pipeline:
- Filter out turns that never reach the model (
systemandcommandroles). - Resolve content refs with
options.resolveContentRef. ensureToolResultPairing— repairs all six orphan / dup corruption modes: synthetic placeholders for orphan tool_use blocks (preserves the model's tool_use shape), strips for duplicates and orphan tool_results.ensureEndsWithUserMessagewhenoptions.provideris set — guards against assistant-tail prefill rejection (opus 4.7, o-series).
Pure + idempotent. Does not mutate the input turns. Re-running on the output is a no-op.
Do not feed the result back into session.setTurns / appendTurns.
The repair can insert SYNTHETIC_TOOL_RESULT_PLACEHOLDER / [Orphaned tool result removed …] markers; round-tripping those into persisted
state contaminates the session's reasoning history and defeats the
"don't rewrite the past" invariant the wire-only repair was designed
around.
withExtensionSessionFunction#
Run fn with session bound as the active extension session for the
duration of the call (and every async continuation it spawns). Uses
AsyncLocalStorage.run rather than enterWith so concurrent agents in the
same process (e.g. the GUI's multi-session host) never observe each other's
session through currentExtensionSession().
Constants#
ARTIFACT_KINDSConstant#
Built-in kinds. Open set — extensions add their own.
ARTIFACT_META_MAX_BYTESConstant#
Maximum size of an artifact meta value, in bytes of its JSON encoding. Larger values are dropped.
ARTIFACTS_METADATA_KEYConstant#
Reserved tool-result metadata namespace. Tools (and extension
detect hooks) emit metadata.artifacts = ArtifactInput[] and the
loop lifts each entry into the session's artifact store.
ARTIFACTS_PER_SESSION_CAPConstant#
Maximum number of artifacts kept per session. Stores evict the oldest file artifacts first.
SEND_REQUEST_HISTORYConstant#
Request ids kept per session; a claim past it drops the oldest.
Interfaces#
AppendTurnsOptionsInterface#
Options of SessionStore.appendTurns.
ArtifactFilterInterface#
Filter for listing artifacts. Fields combine with AND.
CreateSessionOptionsInterface#
Options for createSession.
FileMapAdapterInterface#
Host-provided file-map adapter. Three methods exchanging Record<string, string>
payloads — the whole persistence surface the wrapper needs.
FileMapStoreOptionsInterface#
Options for createFileMapStore.
LoadSessionOptionsInterface#
Options for loadSession.
PairingDropInterface#
One deletion made by dropInvalidToolPairings.
RemoteStoreOptionsInterface#
Options for createRemoteStore.
RunEstimateInterface#
A run's expected duration (SessionRun.estimate). Times count from the
run's startedAt and leave out time spent waiting on the user
(SessionRun.userWait).
RunEstimateFailureInterface#
Why a run's estimate failed (SessionRun.estimateFailure).
RunOwnerInterface#
Who wrote a stored running marker.
RunUserWaitInterface#
Time a run spent waiting on the user (SessionRun.userWait).
SaveMetaOptionsInterface#
Options for SessionStore.saveMeta.
ScheduleStoreMethodsInterface#
The schedule methods a store implements. Optional on SessionStore: a store without them has no schedules.
SendRequestRecordInterface#
A claimed request id.
SessionInterface#
A live session: the turn history and run records of one conversation, with an optional store that persists them.
SessionArtifactInterface#
A recorded output of a session, such as a pull request, a branch, a commit or a file.
SessionArtifactInputInterface#
What a producer hands to session.addArtifact / the metadata namespace.
SessionDataInterface#
Persisted form of a session: identity, turns, runs and metadata.
SessionHistoryInterface#
A session's turns and runs: what a history edit reads and writes (SessionStore.mutateHistory).
SessionMetaRowInterface#
Compact per-session summary returned by SessionStore.listMeta.
Raw facts only — presentation (title derivation, fallback strings)
stays in the chat layer (listSessionMeta / deriveSessionTitle).
SessionRunInterface#
Record of one agent run inside a session.
SessionRunAgentInterface#
A host agent profile as a run record names it.
SessionRunRetryInterface#
One provider stream retry of a run (see SessionRun.streamRetries).
SessionStoreInterface#
Storage backend for sessions. Implement it to persist sessions somewhere else, or use a bundled store: memory, SQLite, remote or file map.
StoredRunOptionsInterface#
What a checkpoint run keeps of its options (SessionRun.options).
ToWireMessagesOptionsInterface#
Options for toWireMessages.
UpdateRunOptionsInterface#
Options of SessionStore.updateRun.
Types#
PairingDropKindType#
Classification of a single deletion dropInvalidToolPairings
performed. Surfaced via onDrop for telemetry.
SendRequestClaimType#
What claimSendRequest answers: the id is now this caller's, or the record that already holds it.
SessionMetaListenerType#
A Session.onMeta listener: the key and value setMeta just wrote.
SessionSavedListenerType#
A Session.onSaved listener, awaited by save().
Re-exports#
These names are documented on the entry point that owns them.