Usage overview
High level overview of the Electric Agents system and developer APIs.
1. Entity definition (registry.define())
Agents are entities that handle events, defined as a:
handler(ctx, wake)withstateand built in collections
And schemas:
creationSchema-- validated spawn argsinboxSchemas-- typed message contractsstateSchemas-- additional registered state schemas
See Defining entities and EntityDefinition reference.
2. Handler context (ctx)
The context API passed into the handler:
| Property/Method | Purpose |
|---|---|
ctx.firstWake | Boolean -- initial setup pass while no manifest entries exist |
ctx.wake | Current wake, also passed as the second handler argument |
ctx.slashCommands | Read/register structured composer slash commands |
ctx.entityUrl | Identity -- /type/id |
ctx.entityType | Type name string |
ctx.args | Readonly spawn arguments |
ctx.tags | Entity tags -- key/value metadata |
ctx.principal | Principal that caused the current wake, when supplied by the server |
ctx.db | Full TanStack DB: db.actions for writes, db.collections for reads |
ctx.state | Proxy object keyed by collection name |
ctx.events | Change events that triggered this wake |
ctx.useAgent() | Set up the LLM agent |
ctx.useContext() | Declare context sources with token budgets and cache tiers |
ctx.timelineMessages() | Project the entity timeline into LLM messages |
ctx.insertContext(id, entry) | Insert a durable context entry |
ctx.agent.run() | Execute the agent loop |
ctx.electricTools | Runtime-provided tools to spread into agent config |
ctx.spawn(type, id, args, opts) | Create child entity |
ctx.fork(url, id, opts) / ctx.forkSelf(id, opts) | Branch an entity at its latest completed run |
ctx.observe(source, opts) | Subscribe to a source via entity(), cron(), entities(), db(), webhook(), or pgSync() |
ctx.send(url, payload, opts) | Send message to an entity |
ctx.sleep() | Return to idle |
ctx.mkdb(id, schema) | Create cross-entity shared state |
ctx.observe(db(id, schema), opts) | Join existing shared state |
ctx.recordRun() | Record non-LLM work as a run for runFinished observers |
ctx.replyText(text) | Emit a synthetic assistant text reply without invoking the LLM |
ctx.setGoal(input) / ctx.getGoal() | Manage the active goal for long-running agents |
ctx.setTag(key, value) | Set a tag on this entity |
ctx.deleteTag(key) | Delete a tag from this entity |
See Writing handlers and HandlerContext reference.
3. Agent configuration
ctx.useAgent({
systemPrompt: string,
model: string | Model<any>, // e.g. 'claude-sonnet-4-6'
provider?: Provider, // defaults to 'anthropic' for string models
tools: AgentTool[], // [...ctx.electricTools, ...custom]
streamFn?: StreamFn, // optional streaming callback
getApiKey?: (provider: string) => string | Promise<string> | undefined,
onPayload?: SimpleStreamOptions["onPayload"],
modelTimeoutMs?: number,
modelMaxRetries?: number,
testResponses?: string[] | TestResponseFn // for testing without LLM
})
await ctx.agent.run() // blocks until agent finishesSee Configuring the agent and AgentConfig reference.
4. Tool definition
Stateless tools are pure functions:
const myTool: AgentTool = {
name: "calculator",
label: "Calculator",
description: "Evaluate a mathematical expression.",
parameters: Type.Object({ expression: Type.String() }), // TypeBox
execute: async (_toolCallId, params) => {
const { expression } = params as { expression: string }
const result = evaluate(expression)
return {
content: [{ type: "text", text: String(result) }],
details: {},
}
},
}Stateful tools are factories receiving ctx for state access:
function createMemoryTool(ctx: HandlerContext): AgentTool {
return {
name: "memory_store",
label: "Memory Store",
description: "Persist a key-value memory row.",
parameters: Type.Object({
key: Type.String(),
value: Type.String(),
}),
execute: async (_, params) => {
const { key, value } = params as { key: string; value: string }
ctx.db.actions.memory_insert({ row: { key, value } }) // writes to entity state
return { content: [{ type: "text", text: "Stored." }], details: {} }
},
}
}Handler-scoped tools are factories receiving ctx:
function createDispatchTool(ctx: HandlerContext): AgentTool {
return {
name: "dispatch",
label: "Dispatch",
description: "Spawn a worker and return its text output.",
parameters: Type.Object({
id: Type.String(),
systemPrompt: Type.String(),
task: Type.String(),
}),
execute: async (_, params) => {
const { id, systemPrompt, task } = params as {
id: string
systemPrompt: string
task: string
}
const child = await ctx.spawn(
"worker",
id,
{ systemPrompt, tools: ["read"] },
{ initialMessage: task, wake: { on: "runFinished", includeResponse: true } }
)
return {
content: [{ type: "text", text: `Started ${child.entityUrl}; I will continue when it finishes.` }],
details: { childUrl: child.entityUrl },
}
},
}
}See Defining tools and AgentTool reference.
5. State collections (ctx.db)
Custom state is accessed through ctx.db:
Writes via ctx.db.actions:
.<name>_insert({ row })-- add new row.<name>_update({ key, updater: (draft) => { ... } })-- Immer-style mutation.<name>_delete({ key })-- remove by primary key
Reads via ctx.db.collections:
.<name>?.get(key)-- read one.<name>?.toArray-- read all (getter, not method)
See Managing state.
6. Entity coordination primitives
spawn(type, id, args, opts)->EntityHandle-- create childopts.initialMessage-- first message to deliveropts.initialMessageType-- optional inbox message type for the initial messageopts.wake--'runFinished',{ on: 'runFinished', includeResponse? }, or{ on: 'change', collections?, debounceMs?, timeoutMs? }
fork(sourceEntityUrl, id, opts)/forkSelf(id, opts)->EntityHandle-- branch an entity at its latest completed runobserve(source, opts)->EntityHandle | ObservationHandle-- subscribe viaentity(),cron(),entities(),db(),webhook(), orpgSync()send(url, payload, opts)-- fire-and-forget messagerecordRun()->RunHandle-- publish run lifecycle for external worksleep()-- go idle
EntityHandle returned from spawn/observe:
.entityUrl,.type-- identify the entity.db/.events-- inspect the observed entity stream.send(msg)-- send follow-up message.status()--ChildStatus | undefined(object with.status,.entity_url,.entity_type)
See Spawning & coordinating and EntityHandle reference.
7. Runtime capabilities
Use the dedicated guides for runtime features that cut across handlers, clients, and hosted built-ins:
- Permissions & principals — principal-scoped access to types and entities.
- Sandboxing — filesystem, process, and network isolation for LLM-driven tools.
- Attachments — upload, read, and hydrate files and images.
- Signals — interrupt, pause, resume, kill, and notify entities.
- Webhook sources — subscribe entities to external webhook-backed feeds.
8. Shared state (cross-entity)
Define a schema map, then create/connect:
const schema = {
findings: {
schema: z.object({ key: z.string(), text: z.string() }),
type: "shared:finding",
primaryKey: "key",
},
}
// Parent creates:
ctx.mkdb("research-123", schema)
// Children connect:
const shared = await ctx.observe(db("research-123", schema))
shared.findings.insert({ key: "f1", text: "..." })See Shared state and SharedStateHandle reference.
9. Built-in collections
Every entity automatically has 20 ctx.db.collections:
| Collection | Purpose | Key fields |
|---|---|---|
runs | Agent run lifecycle | status: started/completed/failed |
steps | LLM call steps | step_number, model_id, duration_ms |
texts | Text message blocks | status: streaming/completed |
textDeltas | Incremental text chunks | text_id, delta |
toolCalls | Tool invocation lifecycle | tool_name, status, args, result |
reasoning | Extended thinking blocks | status: streaming/completed |
reasoningDeltas | Incremental reasoning chunks | reasoning_id, delta |
errors | Diagnostic errors | error_code, message |
inbox | Received messages | from, payload, message_type |
wakes | Wake event history | source, timeout, changes |
entityCreated | Bootstrap metadata | entity_type, args, parent_url |
entityStopped | Shutdown signal | timestamp, reason |
signals | Lifecycle signal records | signal, status, outcome |
childStatus | Child entity status | entity_url, status |
slashCommands | Composer slash commands | name, description, args |
manifests | Wiring declarations | discriminated union: child/source/shared-state/effect/attachment/context/schedule |
replayWatermarks | Replay offset tracking | source_id, offset |
tags | Entity tags/labels | key, value |
contextInserted | Context additions | id, name, attrs, content, timestamp |
contextRemoved | Context removals | id, name, timestamp |
See Built-in collections.
10. CLI (electric agents)
Interact with the system using the Electric Agents CLI:
| Command | Purpose |
|---|---|
electric agents types | List registered entity types |
electric agents types inspect <name> | Show type schema |
electric agents spawn /type/id --args '{...}' | Create entity |
electric agents send /type/id 'message' | Send message |
electric agents observe /type/id | Stream entity events |
electric agents view /type/id | Print entity conversation once |
electric agents signal /type/id SIGINT | Send a lifecycle signal |
electric agents inspect /type/id | Show entity state |
electric agents ps [--type --status --parent] | List entities |
electric agents kill /type/id | Delete entity |
electric agents start | Start local dev environment |
electric agents start-builtin | Start built-in Horton runtime |
electric agents quickstart | Start local server and built-ins |
electric agents stop | Stop local dev environment |
electric agents init [project-name] | Scaffold a starter app |
See CLI reference.
11. App setup
const registry = createEntityRegistry()
registerMyEntity(registry)
const runtime = createRuntimeHandler({
baseUrl: ELECTRIC_AGENTS_URL, // Electric Agents server
serveEndpoint: `${URL}/webhook`, // callback URL
registry,
})
// Node HTTP server, forward POST /webhook -> runtime.onEnter(req, res)
await runtime.registerTypes() // register all types with runtime serverSee App setup and RuntimeHandler reference.
12. App clients and embedded built-ins
Use the client and embedding APIs when you need to work with agents outside an entity handler:
| API | Use case |
|---|---|
createAgentsClient() | Observe entity, membership, or shared-state streams from app code |
useChat() | Render an observed EntityStreamDB in React |
createRuntimeServerClient() | Spawn, message, delete, tag, and schedule entities from services |
BuiltinAgentsServer | Host Horton and worker in your own process |
See Clients & React, Programmatic runtime client, and Embedded built-ins.