keel
State

Defining blocks

Declare the shape, updates, and views of a state block.

Let's build a profile block that remembers whether a user prefers concise or detailed answers.

Create src/shared/state/profile.block.ts:

// src/shared/state/profile.block.ts
import { z } from "zod";
import { defineBlock } from "@keel-dev/core";

export const profileBlock = defineBlock({
  name: "profile",
  version: 1,
  schema: z.object({
    tone: z.enum(["concise", "detailed"]),
  }),
  initial: { tone: "concise" },
  reducers: {
    setTone: (state, event: { tone: "concise" | "detailed" }) => ({
      ...state,
      tone: event.tone,
    }),
  },
  readers: {
    assistant: (state) => `User prefers ${state.tone} answers.`,
  },
});

The block starts with a concise answer preference. Its setTone reducer changes that preference, and its assistant reader turns the stored value into an instruction the model can use.

FieldPurpose
nameIdentifies the block.
versionDeclares the revision of the block definition. Informational only: the framework never reads it, and it does not select or run migrations.
schemaDefines valid stored values using Zod.
initialProvides a value when no stored data is available or validation fails.
ttlMsOptional freshness period in milliseconds. While the last write is younger than this, a tool that feeds the block is not re-executed. Defaults to null (no caching). See Storage and caching.
reducersDefines the events that can change the value.
repairOptional normalization applied to the stored value before schema validation on a read. See Repairing state.
readersDefines named functions that render the value as text. At least one is required: defineBlock throws a KeelError for a block nobody can render.

Block values belong to a thread. Two threads can use the same profile block definition while keeping different preferences.

State and storage

A block defines the data contract; the store holds its values. The default MemoryStore keeps state in memory. Keeping data across process restarts requires a persistent implementation of the store interface.

See Storage and caching for thread scoping and persistence.

Inspect in Studio

Open your app in Studio and explore its definitions before running a turn. After you connect the block to an agent, the STATE panel shows its renders and updates.

Next: Reading state.

On this page