Crux
API Reference@use-crux/core

Durable Sessions

Exact session, getSession, handle, subscriptions, streams, lifecycle, inspection, stats, and Runtime read model contracts.

Durable Sessions are keyed, restart-safe owners for one Agent or one exported Flow. Import from the package root or the focused subpath:

import {
  session,
  getSession,
  type Session,
  type SessionFor,
  type SessionForTarget,
  type SessionOptions,
  type FlowSessionOptions,
  type SessionTurnHandle,
  type SessionStatus,
  type SessionInspection,
  type SessionSubscription,
  type SessionEvent,
  type SessionStreamOptions,
  type SessionRuntimeReadModel,
  SessionIdentityConflictError,
  SessionNotFoundError,
  SessionInputError,
  SessionCapabilityError,
  SessionClosedError,
  SessionDeletedError,
  SessionLifecycleError,
  SessionNotClosedError,
  SessionTombstonedError,
  GenerationModelBindingError,
  GenerationModelNotStaticError,
  GenerationModelCapabilityError,
} from "@use-crux/core";

// equivalent focused surface
import { session, getSession } from "@use-crux/core/session";

session(target, options)

// Agent
function session<TAgent extends AnyAgent, TModel extends GenerationModel | undefined>(
  target: TAgent,
  options: SessionOptions<TAgent, TModel> & SessionModelGuard<TAgent, TModel>,
): Promise<SessionForTarget<TAgent>>;

// Flow
function session<TFlow extends AnyFlowTarget>(
  target: TFlow,
  options: FlowSessionOptions,
): Promise<SessionForTarget<TFlow>>;

Create or reopen one inert keyed Session for target.

FieldRequiredMeaning
options.keyyesNon-empty caller key bound to one target within the active Runtime namespace
options.modelAgent only, when Agent has no bound modelImmutable Session-level GenerationModel override

Promise timing: resolves after durable preparation and Thread-owner registration complete. It does not execute the target.

Throws before Session / Work / Thread mutation (Agent):

ErrorCode
SessionIdentityConflictErrorSESSION_IDENTITY_CONFLICT
SessionCapabilityErrorSESSION_UNSUPPORTED
SessionTombstonedErrorSESSION_TOMBSTONED
GenerationModelBindingErrorGENERATION_MODEL_BINDING_MISSING
GenerationModelNotStaticErrorGENERATION_MODEL_NOT_STATIC
GenerationModelCapabilityErrorGENERATION_CAPABILITY_MISSING
TypeErrorempty key

Flow Sessions also reject when the Flow is not exported by the bound Runtime program (TARGET_NOT_EXPORTED).

Requires an active Work host (createWorkHost(...).run(...)) and Runtime store with Sessions support, plus a configured linearizable Thread RecordStore.

getSession(target, key)

function getSession<TTarget extends SessionTarget>(
  target: TTarget,
  key: string,
): Promise<SessionForTarget<TTarget>>;

Retrieve an existing Session without creating one. Repairs interrupted owner preparation when a compatible record exists.

Types

SessionOptions / FlowSessionOptions / SessionModelGuard

type SessionOptions<A extends AnyAgent, M extends GenerationModel | undefined> = {
  readonly key: string;
} & (
  | { readonly model?: M } // when Agent already has a GenerationModel
  | { readonly model: M } // when Agent has no GenerationModel
);

type FlowSessionOptions = {
  readonly key: string;
};

SessionModelGuard is a compile-time Agent model compatibility check. Exact missing language facets become a false type; broad capability evidence remains accepted for runtime preflight.

Session<TInput, TOutput> / SessionForTarget

interface Session<TInput, TOutput = unknown> {
  readonly id: string;
  readonly thread: SessionThreadView;
  readonly forkedFrom?: SessionForkLineage;
  send(input: TInput): Promise<SessionTurnHandle<TOutput>>;
  sendMany(
    inputs: readonly TInput[],
  ): Promise<readonly SessionTurnHandle<TOutput>[]>;
  status(): Promise<SessionStatus>;
  inspect(): Promise<SessionInspection>;
  stats(): Promise<ExecutionStats>;
  stream(options?: SessionStreamOptions): AsyncIterable<SessionEvent>;
  close(): Promise<void>;
  kill(): Promise<void>;
  delete(): Promise<void>;
  fork(): Promise<this>;
  clone(): Promise<this>;
  forks(): Promise<readonly SessionForkSummary[]>;
}

// Agent and Flow handles also carry:
//   targetKind: "agent" | "flow"
//   subscribe / subscriptions (Signal surface)

SessionForTarget<T> retains exact input/output inference for Agent Prompt schemas and Flow definition types. Prefer it over SessionFor when the target may be a Flow.

SessionThreadView

interface SessionThreadView {
  readonly id: string;
  read(options?: ThreadReadOptions): Promise<ThreadSnapshot>;
}

Read-only owner-scoped Thread view. Reads only finalized Session heads. There is no public append/edit/delete surface on this view. After Session delete unregisters the owner, read() returns an empty owner path without resurrection.

Input and turn handles

interface SessionInputHandle {
  readonly id: string;
  readonly cursor: string;
  readonly acceptedAt: Date;
}

interface SessionTurnHandle<TOutput> extends SessionInputHandle {
  work(): Promise<WorkHandle<TOutput>>;
  result(): Promise<TOutput>;
}
MethodPromise timing
send / sendManyDurable acceptance only
work()After an activation opportunity links this input to Work
result()After the linked Work publishes its exact terminal output

sendMany([]) resolves to a frozen empty array without allocating cursors. Invalid members fail the entire batch with SESSION_INPUT_INVALID before any cursor advances.

Signal subscriptions

interface SessionSubscription {
  readonly id: string;
  readonly signalId: string;
  readonly matchKey: string;
  readonly state: "active" | "unsubscribed";
  unsubscribe(): Promise<void>;
}

// on Session handles that include the subscription surface:
subscribe(source: Signal | MatchFilter): Promise<SessionSubscription>;
subscriptions(): Promise<readonly SessionSubscription[]>;

Idempotent by Session + signal id + canonical match key. Predicate Signal views are rejected; use bare Signals or signal.when({ ...match }).

Streams

interface SessionStreamOptions {
  readonly after?: string;
}

type SessionEvent =
  | { type: "session.snapshot"; reason: "initial" | "cursor-expired"; status: SessionStatus; /* … */ }
  | { type: "session.status"; status: SessionStatus; /* … */ }
  | { type: "ingress.accepted"; ingress: SessionIngressSummary; /* … */ }
  | { type: "ingress.delivered"; ingress: SessionIngressSummary; stepIndex: number; workId?: string; /* … */ };

Without after, emits session.snapshot (initial) then retained history. A valid after resumes strictly after that cursor. An expired/unknown after emits session.snapshot (cursor-expired) then continues from the earliest retained event. Closed Sessions end after the terminal session.status event.

Lifecycle

MethodSemantics
close()Joinable ordered barrier; seals send/subscribe; deactivates subscriptions; drains represented pending obligations
kill()Fenced fast terminalization; revokes commit authority; projects as closed
delete()After closed/killed only; tombstones key; unregisters Thread owner
fork() / clone()New owner/head with immutable lineage; never aliases mutable head
forks()Direct children created by fork/clone

SessionStatus

interface SessionStatus {
  readonly state: "parked" | "running" | "blocked" | "closing" | "closed";
  readonly acceptedCursor?: string;
  readonly processedCursor?: string;
  readonly pendingInputs: number;
  readonly pendingWork: number;
}

Killed Sessions project as closed. Deleted Sessions reject status reads.

SessionInspection

interface SessionInspection {
  readonly id: string;
  readonly targetId: string;
  readonly threadId: string;
  readonly wakePending: boolean;
  readonly inputs: readonly SessionInputInspection[];
  readonly checkpoint?: SessionCheckpointInspection;
  readonly recovery?: SessionRecoveryDiagnostic;
  readonly coverage: { readonly inputs: "complete" | "truncated"; readonly limit: 64 };
}

Public handle inspection may list sealed request identities for operator correlation. Runtime Bridge serialization replaces that list with a bounded requestCount and never exposes request payloads.

stats()

Returns the shared ExecutionStats / ScopeStats aggregate for the complete addressed Session lifetime, including exact ingress totals under inputs (accepted / deduplicated / delivered / resumed / dropped) with first-64 identity attribution.

GenerationModel precedence (Agent only)

  1. Optional immutable Session model override supplied to session().
  2. Otherwise the Agent's bound model.

The selected model must be declared on the active RuntimeProgram.generationModels (or generated equivalent). Durable state stores only { definitionId, fingerprint }. See GenerationModel.

SessionRuntimeReadModel

Closed JSON-safe projection used by the Runtime Bridge and session.turn observability. Schema version is 1.

interface SessionRuntimeReadModel {
  readonly schema: 1;
  readonly identity: {
    readonly sessionId: string;
    readonly keyHash: string;
    readonly targetId: string;
    readonly targetKind: "agent" | "flow";
    readonly threadId: string;
  };
  readonly status: SessionStatus;
  readonly wakePending: boolean;
  readonly activation?: { readonly inputId: string; readonly workId: string };
  readonly forkedFrom?: SessionForkLineage;
  readonly thread: { readonly revision: string };
  readonly subscriptions: readonly SessionRuntimeSubscription[];
  readonly inputs: readonly SessionRuntimeInput[];
  readonly checkpoint?: SessionRuntimeCheckpoint;
  readonly recovery?: SessionRecoveryDiagnostic;
  readonly coverage: { readonly inputs: "complete" | "truncated"; readonly limit: 64 };
  readonly stats: ExecutionStats;
}

Dates serialize as ISO strings. Checkpoints expose requestCount, not sealed request ids. Subscriptions, lineage, and statistics come from the same Session ports and ledger the Runtime already persists — not a second source of truth. The projection never includes prompts, inputs, outputs, reasoning, Tool arguments, credentials, or provider-native objects.

Capability and store requirements

RequirementFailure
Runtime store with Sessions portSESSION_UNSUPPORTED
Active Work host / Runtime bindinghost/runtime errors from Work admission
Linearizable Thread RecordStoreThread owner registration / commit failures
Declared generation models (Agent)GENERATION_MODEL_* codes above
Exported Flow target (Flow)TARGET_NOT_EXPORTED
Subscription port when using subscribeSESSION_UNSUPPORTED / capability error

PostgreSQL deployments must initialize Runtime storage and postgresRecordStore against the same database. Convex deployments use the component Runtime and RecordStore together inside the host boundary.

runSessionConformanceTests

import { runSessionConformanceTests } from "@use-crux/core/runtime/testing";

Provider-neutral factory used by memory, PostgreSQL, and Convex adapters. The harness injects only setup, reconstruction, fault, attempt, and observation seams; every law calls public Session, Thread, and Work APIs. Laws cover identity, ordering, subscriptions, lifecycle, fork lineage, stream cursors, and truthful capability reporting.

Errors

CodeClass / surface
SESSION_IDENTITY_CONFLICTSessionIdentityConflictError
SESSION_NOT_FOUNDSessionNotFoundError
SESSION_INPUT_INVALIDSessionInputError
SESSION_UNSUPPORTEDSessionCapabilityError
SESSION_TOMBSTONEDSessionTombstonedError
SESSION_CLOSED / lifecycleSessionClosedError, SessionLifecycleError, SessionNotClosedError
SESSION_DELETEDSessionDeletedError
GENERATION_MODEL_BINDING_MISSINGGenerationModelBindingError
GENERATION_MODEL_NOT_STATICGenerationModelNotStaticError
GENERATION_CAPABILITY_MISSINGGenerationModelCapabilityError
SESSION_TURN_RESULT_ARTIFACT_UNAVAILABLErecovery diagnostic / Runtime error

On this page