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.
| Field | Purpose |
|---|---|
name | Identifies the block. |
version | Declares the revision of the block definition. Informational only: the framework never reads it, and it does not select or run migrations. |
schema | Defines valid stored values using Zod. |
initial | Provides a value when no stored data is available or validation fails. |
ttlMs | Optional 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. |
reducers | Defines the events that can change the value. |
repair | Optional normalization applied to the stored value before schema validation on a read. See Repairing state. |
readers | Defines 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.
