Skip to content
PricingBlog
✨ Markdown

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:

And schemas:

  • creationSchema -- validated spawn args
  • inboxSchemas -- typed message contracts
  • outputSchemas -- what the entity emits (for UI binding)

See Defining entities and EntityDefinition reference.

2. Handler context (ctx)

The context API passed into the handler:

Property/MethodPurpose
ctx.firstWakeBoolean -- initial setup pass while no manifest entries exist
ctx.entityUrlIdentity -- /type/id
ctx.entityTypeType name string
ctx.argsReadonly spawn arguments
ctx.tagsEntity tags -- key/value metadata
ctx.dbFull TanStack DB: db.actions for writes, db.collections for reads
ctx.stateProxy object keyed by collection name
ctx.eventsChange 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.electricToolsRuntime-provided tools to spread into agent config
ctx.spawn(type, id, args, opts)Create child entity
ctx.observe(source, opts)Subscribe to a source via entity(), cron(), entities(), db()
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.setTag(key, value)Set a tag on this entity
ctx.removeTag(key)Remove a tag from this entity

See Writing handlers and HandlerContext reference.

3. Agent configuration

ts
ctx.useAgent({
  systemPrompt: string,
  model: string | Model<any>, // e.g. 'claude-sonnet-4-5-20250929'
  provider?: KnownProvider,   // 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"],
  testResponses?: string[] | TestResponseFn // for testing without LLM
})
await ctx.agent.run()      // blocks until agent finishes

See Configuring the agent and AgentConfig reference.

4. Tool definition

Stateless tools are pure functions:

ts
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:

ts
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:

ts
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: "runFinished" }
      )
      const text = (await child.text()).join("\n\n")
      return { content: [{ type: "text", text }], details: {} }
    },
  }
}

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 child
    • opts.initialMessage -- first message to deliver
    • opts.wake -- 'runFinished', { on: 'runFinished', includeResponse? }, or { on: 'change', collections?, debounceMs?, timeoutMs? }
  • observe(source, opts) -> EntityHandle | ObservationHandle -- subscribe via entity(), cron(), entities(), db()
  • send(url, payload, opts) -- fire-and-forget message
  • recordRun() -> RunHandle -- publish run lifecycle for external work
  • sleep() -- go idle

EntityHandle returned from spawn/observe:

  • .entityUrl, .type, .db (read-only TanStack DB)
  • .run -- Promise that resolves when child completes
  • .text() -- get all completed text output
  • .send(msg) -- send follow-up message
  • .status() -- ChildStatus | undefined (object with .status, .entity_url, .entity_type)

See Spawning & coordinating and EntityHandle reference.

7. Shared state (cross-entity)

Define a schema map, then create/connect:

ts
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.

8. Built-in collections

Every entity automatically has 17 ctx.db.collections:

CollectionPurposeKey fields
runsAgent run lifecyclestatus: started/completed/failed
stepsLLM call stepsstep_number, model_id, duration_ms
textsText message blocksstatus: streaming/completed
textDeltasIncremental text chunkstext_id, delta
toolCallsTool invocation lifecycletool_name, status, args, result
reasoningExtended thinking blocksstatus: streaming/completed
errorsDiagnostic errorserror_code, message
inboxReceived messagesfrom, payload, message_type
wakesWake event historysource, timeout, changes
entityCreatedBootstrap metadataentity_type, args, parent_url
entityStoppedShutdown signaltimestamp, reason
childStatusChild entity statusentity_url, status
manifestsWiring declarationsdiscriminated union: child/source/shared-state/effect/context/schedule
replayWatermarksReplay offset trackingsource_id, offset
tagsEntity tags/labelskey, value
contextInsertedContext additionsid, name, attrs, content, timestamp
contextRemovedContext removalsid, name, timestamp

See Built-in collections.

9. CLI (electric agents)

Interact with the system using the Electric Agents CLI:

CommandPurpose
electric agents typesList 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/idStream entity events
electric agents inspect /type/idShow entity state
electric agents ps [--type --status --parent]List entities
electric agents kill /type/idDelete entity
electric agents startStart local dev environment
electric agents start-builtinStart built-in Horton runtime
electric agents quickstartStart local server and built-ins
electric agents stopStop local dev environment
electric agents init [project-name]Scaffold a starter app

See CLI reference.

10. App setup

ts
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 server

See App setup and RuntimeHandler reference.

11. App clients and embedded built-ins

Use the client and embedding APIs when you need to work with agents outside an entity handler:

APIUse 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
BuiltinAgentsServerHost Horton and worker in your own process

See Clients & React, Programmatic runtime client, and Embedded built-ins.