Documentation menu
Documentation

State and SQLite

Use synced state for what interfaces need now, and SQLite for durable records and queries.

Start from a fresh project

This guide is self-contained. Create an empty Ayjnt project, then replace the starter agent with the files shown below. Run commands from the new project directory unless a step says otherwise.

terminal
bunx ayjnt new try-state --empty
cd try-state
bun install

# remove agents/alive once you are ready to add the guide's files

Synced state

Define initialState, read this.state, and replace it with this.setState(next). Updates persist and broadcast to connected clients.

validateStateChange() runs synchronously before persistence. onStateChanged() observes a successful update.

example
type State = { phase: "waiting" | "running"; progress: number };

initialState: State = { phase: "waiting", progress: 0 };

validateStateChange(next: State) {
  if (next.progress < 0 || next.progress > 1) {
    throw new Error("progress must be between 0 and 1");
  }
}

setProgress(progress: number) {
  this.setState({ ...this.state, phase: "running", progress });
}

Embedded SQL

Each agent instance has isolated SQLite. Use the tagged this.sql helper for history, indexes, queues, and data that should not be broadcast as one state object.

example
this.sql`CREATE TABLE IF NOT EXISTS turns (
  id TEXT PRIMARY KEY,
  role TEXT NOT NULL,
  content TEXT NOT NULL
)`;

this.sql`INSERT INTO turns (id, role, content)
  VALUES (${crypto.randomUUID()}, ${role}, ${content})`;

const turns = this.sql<{ role: string; content: string }>`
  SELECT role, content FROM turns ORDER BY rowid
`;

Run it and verify the result

Change state from the UI, refresh the page, and confirm the synchronized snapshot and SQLite rows remain available.

terminal
bun run build
bun run dev

# the local app is now available at http://localhost:8787