keel
State

Overview

Define what your application remembers, how it changes, and what each agent can read.

State

Memory across turns

Block 1
Block 2
Block n
Inside every block
A typed contract
Schema
What values are valid
Typed shape
{
  tone: "concise" |
        "detailed"
}
Reducers
How values can change
setTone({ tone: "detailed" })
{ "tone": "concise" }
          ↓
{ "tone": "detailed" }
Readers
What each agent sees
assistant(state)
{ "tone": "detailed" }
          ↓
"User prefers detailed answers."
The block defines the contract. The store holds its values.

State is the information your application keeps across turns: a user's preferences, saved notes, or the progress of a task. Keel organizes this information into blocks. A block is a typed contract: it declares valid data through a schema, allowed changes through reducers, and views for agents through readers. The stored value is an instance of that contract.

TypeScript checks the shapes your code works with, while the Zod schema validates values at runtime as they flow through the block's paths: every render for a reader and every reducer update passes validation. The one exception is a TTL cache hit, which serves the stored value back to a tool call without re-validating it — see Storage and caching.

Why use a block?

With a plain shared object, your application decides where to validate updates and how to expose data to agents. A Keel block puts those decisions in one declaration and checks reducer results before storing them.

Unchecked
Plain object

Caller-managed checks

Update
profile.tone =
  "brief";
Assignment
No validation
Stored · overwritten
{
  "tone": "brief"
}
Validated
Keel block

Contract-enforced checks

Update
setTone({
  tone: "brief"
})
Schema check
"brief" rejected
Stored · unchanged
{
  "tone": "concise"
}
Both start with “concise”. Only “concise” or “detailed” is valid; Keel rejects the invalid update.

This comparison shows a plain object without runtime validation. Other systems can implement the same checks; in Keel, the block contract makes them a standard part of reading and updating state.

Rules by design

Every block follows the same contract:

  1. One schema

    Validate stored values on read and reducer results before writing. An invalid update leaves the previous value unchanged.

    nextState → schema
    valid   → store
    invalid → reject update
  2. Declared access

    Agents declare which blocks they read and write. Every read names a reader and a token budget.

    reads: [{ block, reader,
              budgetTokens }]
    writes: [block]
  3. Explicit views

    Readers render a view for each audience. The model receives that text in its context.

    state
      ├─ assistant → "Full notes…"
      └─ researcher → "3 notes"
  4. Updates through reducers

    Tool code submits an event through feeds or commit; a reducer computes the next value. The model cannot mutate state directly. Keep reducers pure.

    tool → event
               ↓
    reducer(state, event)
               ↓
           nextState
  5. One writer per block per turn

    The first open obtains a writer. Later opens in that turn receive readers. This does not lock the block across independent turns.

    Within one turn
    openBlock(block) → writer
    openBlock(block) → reader
  6. A fallback for invalid data

    Reads apply the optional repair function, then validate. Failed validation returns initial. Repair code must handle arbitrary input without throwing.

    stored → repair? → schema
                        ├─ valid → value
                        └─ invalid → initial
  7. Values scoped to a thread

    Threads share a block definition, while each thread keeps its own values.

    Same block, separate values
    thread A → { "tone": "concise" }
    thread B → { "tone": "detailed" }

Work with state

GuideWhat you'll learn
Defining blocksDeclare a schema, initial value, reducers, and readers.
Reading stateGive agents named views with explicit token budgets.
Updating stateConnect tools to reducers and validate updates.
Repairing stateNormalize older data and fall back when validation fails.
Storage and cachingScope values to threads, choose storage, and cache tool results.

For the model's correction loop, see Failure and recovery.

Next: Defining blocks.