Documentation menu
Start here
IntroductionGetting startedWrite an agentProject anatomyUnderstand Ayjnt
Harness engineeringTwo runtimesHuman interfacesHow agents workHost bridgeAgent capabilities
Callable methodsState & SQLiteSessions & memorySchedulingWorkflowsDurable executionInter-agent RPCSub-agentsToolsWebAssembly modulesInterfaces & integrations
Browser clientRouting & middlewareVoiceBrowser toolsMCPEmailObservabilityCLI
Command overviewnewdevrunbuildcompilemigratedeployAPI reference
AgentAgentClientWorkflow classesLocal & cloudMigrationsExamplesState and SQLite
Use synced state for what interfaces need now, and SQLite for durable records and queries.
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.
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.
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
`;