Skip to content

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.

TypeScript
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:

CorruptionAction
duplicate tool_call id within one run lane (across or within turns)drop the later instance
duplicate tool_result.callId within one turndrop the later instance
tool_result with no matching tool_call in the immediately preceding same-lane assistant turndrop 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:

  1. Filter out turns that never reach the model (system and command roles).
  2. Resolve content refs with options.resolveContentRef.
  3. 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.
  4. ensureEndsWithUserMessage when options.provider is 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.