Defining entities
An entity type is registered with an EntityRegistry. The registry maps type names to EntityDefinition objects that declare the entity's state, schemas, and handler.
Registry
createEntityRegistry() returns an EntityRegistry. Register types with registry.define(name, definition).
import { createEntityRegistry } from "@electric-ax/agents-runtime"
const registry = createEntityRegistry()
registry.define("assistant", {
description: "A general-purpose AI assistant",
async handler(ctx) {
ctx.useAgent({
systemPrompt: "You are a helpful assistant.",
model: "claude-sonnet-4-5-20250929",
tools: [...ctx.electricTools],
})
await ctx.agent.run()
},
})Calling registry.define() with a name that is already registered throws an error.
EntityDefinition
interface EntityDefinition {
description?: string
state?: Record<string, CollectionDefinition>
actions?: (
collections: Record<string, unknown>
) => Record<string, (...args: unknown[]) => void>
creationSchema?: StandardJSONSchemaV1
inboxSchemas?: Record<string, StandardJSONSchemaV1>
outputSchemas?: Record<string, StandardJSONSchemaV1>
handler: (ctx: HandlerContext, wake: WakeEvent) => void | Promise<void>
}| Field | Purpose |
|---|---|
description | Human-readable description. Shown in the Electric Agents UI and CLI. |
state | Custom persistent collections accessed via ctx.state, ctx.db.actions, and ctx.db.collections. |
actions | Factory that returns custom non-CRUD action functions exposed on ctx.actions. |
creationSchema | JSON Schema for arguments passed when the entity is spawned. |
inboxSchemas | JSON Schemas for typed inbox message categories. |
outputSchemas | JSON Schemas for typed output message categories. |
handler | The function that runs each time the entity wakes. Required. |
Custom state
Declare named collections in the state field. Each collection is a CollectionDefinition:
interface CollectionDefinition {
schema?: StandardSchemaV1
type?: string
primaryKey?: string
}| Field | Default | Purpose |
|---|---|---|
schema | none | Optional Standard Schema validator (e.g. Zod). Validates rows on write. |
type | "state:{name}" | Event type string used in the durable stream. |
primaryKey | "key" | The field used as the primary key for the collection. |
Declared collections become available via ctx.state proxies and the lower-level ctx.db.actions / ctx.db.collections APIs:
import { z } from "zod"
const childSchema = z.object({
key: z.string(),
url: z.string(),
kind: z.string(),
})
registry.define("coordinator", {
description: "Spawns and tracks child entities",
state: {
status: { primaryKey: "key" },
children: { schema: childSchema, primaryKey: "key" },
},
async handler(ctx) {
if (!ctx.db.collections.status.get("current")) {
ctx.db.actions.status_insert({ row: { key: "current", value: "idle" } })
}
// Convenience proxy:
ctx.state.children.insert({
key: "child-1",
url: "/worker/child-1",
kind: "worker",
})
// Lower-level APIs:
// Writes: ctx.db.actions.children_insert(), .children_update(), .children_delete()
// Reads: ctx.db.collections.children?.get(key), .children?.toArray
},
})For entity state, ctx.state.<name>.insert/update/delete/get/toArray is the convenience API. Lower-level writes use ctx.db.actions.<name>_insert/update/delete, and reads use ctx.db.collections.<name>?.get(key) and ctx.db.collections.<name>?.toArray.
INFO
StateCollectionProxy is used for both entity-local ctx.state and shared state handles. See StateCollectionProxy for details.
Registry pattern
For projects with multiple entity types, keep a separate registry file and import register functions:
// entities/registry.ts
import { createEntityRegistry } from "@electric-ax/agents-runtime"
import { registerAssistant } from "./assistant"
import { registerWorker } from "./worker"
export const registry = createEntityRegistry()
registerAssistant(registry)
registerWorker(registry)// entities/assistant.ts
import type { EntityRegistry } from "@electric-ax/agents-runtime"
export function registerAssistant(registry: EntityRegistry) {
registry.define("assistant", {
description: "General-purpose assistant",
async handler(ctx) {
ctx.useAgent({
systemPrompt: "You are a helpful assistant.",
model: "claude-sonnet-4-5-20250929",
tools: [...ctx.electricTools],
})
await ctx.agent.run()
},
})
}This keeps each entity type isolated and the registry composition explicit.
Schemas
creationSchema, inboxSchemas, and outputSchemas accept StandardJSONSchemaV1 objects. Any schema library implementing the Standard JSON Schema interface works (e.g. Zod v4). These schemas are used for validation and for generating UI and documentation in the Electric Agents dashboard.
import { z } from "zod/v4"
registry.define("processor", {
description: "Processes structured tasks",
creationSchema: z.object({
priority: z.enum(["low", "medium", "high"]).default("medium"),
}),
inboxSchemas: {
task: z.object({
title: z.string(),
body: z.string().optional(),
}),
},
async handler(ctx) {
// ctx.args.priority is available from creationSchema
},
})