Skip to content

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


Information passed to checkpointWhen.

Signature

export interface CheckpointInfo {
/** Deltas already stored after the newest base, excluding the change being evaluated. */
readonly deltasSinceBase: number
}

Source

Since v0.0.0

Conversation-scoped state: one value per conversation.

Signature

export interface ConversationDefinition<A> extends Definition<A> {
readonly of: (conversationId: ConversationId) => Ref<A>
}

Source

Since v0.0.0

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>
}

Source

Since v0.0.0

History and fork semantics of conversation state.

Signature

type ConversationSemantics =
| { readonly history: "latest"; readonly fork: "current" | "initial" }
| { readonly history: "rewindable"; readonly fork: DocumentForkPolicy }

Source

Since v0.0.0

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>
}

Source

Since v0.0.0

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
}

Source

Since v0.0.0

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 }

Source

Since v0.0.0

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>
}

Source

Since v0.0.0

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
}

Source

Since v0.0.0

One addressed value: a definition plus its owner.

Signature

export interface Ref<A> {
readonly definition: Definition<A>
readonly address: DocumentAddress
}

Source

Since v0.0.0

Session-scoped state; it is its own ref.

Signature

export interface SessionDefinition<A> extends Definition<A>, Ref<A> {}

Source

Since v0.0.0

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>
}

Source

Since v0.0.0

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>
}

Source

Since v0.0.0

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>
}

Source

Since v0.0.0

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>
}

Source

Since v0.0.0

Define conversation-scoped state.

Signature

declare const conversation: <A>(options: Options<A> & ConversationSemantics) => ConversationDefinition<A>

Source

Since v0.0.0

Define a keyed family of conversation-scoped state.

Signature

declare const conversationFamily: <A, Seed = void>(
options: FamilyOptions<A, Seed> & ConversationSemantics
) => ConversationFamily<A, Seed>

Source

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>

Source

Since v0.0.0

Define Session-scoped state.

Signature

declare const session: <A>(options: Options<A>) => SessionDefinition<A>

Source

Since v0.0.0

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>

Source

Since v0.0.0

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>

Source

Since v0.0.0

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>

Source

Since v0.0.0

Define task-scoped state.

Signature

declare const task: <A>(options: Options<A>) => TaskDefinition<A>

Source

Since v0.0.0

Define a keyed family of task-scoped state.

Signature

declare const taskFamily: <A, Seed = void>(options: FamilyOptions<A, Seed>) => TaskFamily<A, Seed>

Source

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>

Source

Since v0.0.0