State
Durable state: typed values stored next to the transcript and changed atomically in the same commits.
Use state for anything an agent app tracks besides messages: a todo list, the current plan, which sandbox a conversation runs in. Because a change commits together with entries and task progress, a crash can never leave the two out of sync. (pi-durable calls these “documents”, after its Chord state library.)
A definition pairs a stable kind and version with a Schema. Values are stored as the schema’s JSON encoding,
as a base followed by JsonPatch deltas. Definitions are scoped to the Session, a conversation, or a task; a
conversation or task definition addresses one value per owner with .of(id).
import { type Ids, Session, State, Tx } from "@effective-harness/core"import * as Effect from "effect/Effect"import * as Schema from "effect/Schema"
declare const conversationId: Ids.ConversationId
const Todos = State.conversation({ kind: "app.todos", version: 1, schema: Schema.Struct({ items: Schema.Array(Schema.String) }), history: "latest", fork: "initial", initial: () => ({ items: [] })})
const program = Effect.gen(function* () { yield* Session.commit(Tx.update(Todos.of(conversationId), (todos) => ({ items: [...todos.items, "write docs"] }))) const todos = yield* State.snapshot(Todos.of(conversationId))})Since v0.0.0
CheckpointInfo (interface)
Section titled “CheckpointInfo (interface)”Information passed to checkpointWhen.
Signature
export interface CheckpointInfo { /** Deltas already stored after the newest base, excluding the change being evaluated. */ readonly deltasSinceBase: number}Since v0.0.0
ConversationDefinition (interface)
Section titled “ConversationDefinition (interface)”Conversation-scoped state: one value per conversation.
Signature
export interface ConversationDefinition<A> extends Definition<A> { readonly of: (conversationId: ConversationId) => Ref<A>}Since v0.0.0
ConversationFamily (interface)
Section titled “ConversationFamily (interface)”A keyed family of conversation-scoped state: one value per conversation and key.
Signature
export interface ConversationFamily<A, Seed> extends Definition<A> { readonly of: (conversationId: ConversationId, key: string, ...seed: [seed?: Seed]) => Ref<A>}Since v0.0.0
ConversationSemantics (type alias)
Section titled “ConversationSemantics (type alias)”History and fork semantics of conversation state.
Signature
type ConversationSemantics = | { readonly history: "latest"; readonly fork: "current" | "initial" } | { readonly history: "rewindable"; readonly fork: DocumentForkPolicy }Since v0.0.0
Definition (interface)
Section titled “Definition (interface)”Erased definition used by the Session.
Signature
export interface Definition<A> extends Options<A> { readonly scope: "session" | "conversation" | "task" readonly history?: DocumentHistory readonly fork?: DocumentForkPolicy /** @internal */ readonly codec: Schema.Codec<A, Schema.Json>}Since v0.0.0
FamilyOptions (interface)
Section titled “FamilyOptions (interface)”Options of a keyed family (pi’s defineDocFamily): like Options, but initial receives the seed supplied by the
access that creates a member.
Signature
export interface FamilyOptions<A, Seed> extends Omit<Options<A>, "initial"> { /** Value of a member created by first access; runs only when the member is absent. */ readonly initial: (seed: Seed) => A}Since v0.0.0
Frame (type alias)
Section titled “Frame (type alias)”One committed change delivered to a watcher.
Signature
type Frame<A> = | { readonly _tag: "Changed" readonly seq: Seq readonly value: A /** Patch from the previously delivered value's encoding; a root `replace` after an overflow or version change. */ readonly patch: JsonPatch.JsonPatch } | { readonly _tag: "Retired"; readonly seq: Seq }Since v0.0.0
Live (interface)
Section titled “Live (interface)”A read-only view of committed state that stays current.
Signature
export interface Live<A> { /** The latest committed value. */ readonly get: Effect.Effect<A> /** The current value, then every later committed value. Ends when the state retires or the Session closes. */ readonly changes: Stream.Stream<A>}Since v0.0.0
Options (interface)
Section titled “Options (interface)”Fields shared by every state definition.
Signature
export interface Options<A> { /** Stable persisted kind; part of the public protocol. */ readonly kind: string /** Positive integer version of the stored value shape. */ readonly version: number /** Codec of the value; its JSON encoding (`Schema.toCodecJson`) is what is stored. */ readonly schema: Schema.Codec<A, any, never, never> /** Value of a document created by first access. */ readonly initial: () => A /** Convert a value stored by an older version, given its encoded JSON. */ readonly migrate?: (value: JsonObject, fromVersion: number) => A /** Return true to store this change as a complete base instead of a delta. */ readonly checkpointWhen?: (value: A, patch: JsonPatch.JsonPatch, info: CheckpointInfo) => boolean}Since v0.0.0
Ref (interface)
Section titled “Ref (interface)”One addressed value: a definition plus its owner.
Signature
export interface Ref<A> { readonly definition: Definition<A> readonly address: DocumentAddress}Since v0.0.0
SessionDefinition (interface)
Section titled “SessionDefinition (interface)”Session-scoped state; it is its own ref.
Signature
export interface SessionDefinition<A> extends Definition<A>, Ref<A> {}Since v0.0.0
SessionFamily (interface)
Section titled “SessionFamily (interface)”A keyed family of Session-scoped state: one value per key. A member’s ref carries the seed passed to initial when
a transaction access has to create the member; the seed is ignored for an existing member. Reads (snapshot,
watch, live) never create, so they may omit it.
Signature
export interface SessionFamily<A, Seed> extends Definition<A> { readonly of: (key: string, ...seed: [seed?: Seed]) => Ref<A>}Since v0.0.0
TaskDefinition (interface)
Section titled “TaskDefinition (interface)”Task-scoped state: one value per task, retired when the task becomes terminal.
Signature
export interface TaskDefinition<A> extends Definition<A> { readonly of: (taskId: TaskId) => Ref<A>}Since v0.0.0
TaskFamily (interface)
Section titled “TaskFamily (interface)”A keyed family of task-scoped state: one value per task and key, retired with the task.
Signature
export interface TaskFamily<A, Seed> extends Definition<A> { readonly of: (taskId: TaskId, key: string, ...seed: [seed?: Seed]) => Ref<A>}Since v0.0.0
Watch (interface)
Section titled “Watch (interface)”A watch of one stored incarnation.
Signature
export interface Watch<A> { /** The value at acquisition. */ readonly initial: A /** * Every later committed change, in order, starting right after `initial`. When more than the configured number of * frames are waiting, they collapse into one root replacement. Ends after `Retired` or when the Session closes. */ readonly changes: Stream.Stream<Frame<A>, StateError>}Since v0.0.0
conversation
Section titled “conversation”Define conversation-scoped state.
Signature
declare const conversation: <A>(options: Options<A> & ConversationSemantics) => ConversationDefinition<A>Since v0.0.0
conversationFamily
Section titled “conversationFamily”Define a keyed family of conversation-scoped state.
Signature
declare const conversationFamily: <A, Seed = void>( options: FamilyOptions<A, Seed> & ConversationSemantics) => ConversationFamily<A, Seed>Since v0.0.0
Hydrate existing state into a value that follows every commit (pi’s documentState); None when it was never
created. Lives until the scope closes.
Signature
declare const live: <A>(ref: Ref<A>) => Effect.Effect<Option.Option<Live<A>>, ReadError, Session.Session | Scope.Scope>Since v0.0.0
session
Section titled “session”Define Session-scoped state.
Signature
declare const session: <A>(options: Options<A>) => SessionDefinition<A>Since v0.0.0
sessionFamily
Section titled “sessionFamily”Define a keyed family of Session-scoped state (pi’s defineDocFamily with scope: "session").
Signature
declare const sessionFamily: <A, Seed = void>(options: FamilyOptions<A, Seed>) => SessionFamily<A, Seed>Since v0.0.0
snapshot
Section titled “snapshot”The committed value, or None when it was never created. Never creates.
Signature
declare const snapshot: <A>(ref: Ref<A>) => Effect.Effect<Option.Option<A>, ReadError, Session.Session>Since v0.0.0
snapshotAsOf
Section titled “snapshotAsOf”The value a rewindable conversation state had in the commit that wrote entry at, as seen from the conversation in
the ref. Forked conversations read their ancestor’s history for inherited entries. Never creates.
Signature
declare const snapshotAsOf: <A>( ref: Ref<A>, at: EntryId) => Effect.Effect<Option.Option<A>, ReadError | EntryNotVisible, Session.Session>Since v0.0.0
Define task-scoped state.
Signature
declare const task: <A>(options: Options<A>) => TaskDefinition<A>Since v0.0.0
taskFamily
Section titled “taskFamily”Define a keyed family of task-scoped state.
Signature
declare const taskFamily: <A, Seed = void>(options: FamilyOptions<A, Seed>) => TaskFamily<A, Seed>Since v0.0.0
Watch existing state; None when it was never created (never creates). The baseline and the registration for
later frames are captured atomically. The watch lives until its scope closes.
Signature
declare const watch: <A>( ref: Ref<A>) => Effect.Effect<Option.Option<Watch<A>>, ReadError, Session.Session | Scope.Scope>Since v0.0.0