Writing handlers
The handler is the function that runs each time an entity wakes. It receives a HandlerContext and a WakeEvent describing what triggered the invocation.
Signature
handler(ctx: HandlerContext, wake: WakeEvent) => void | Promise<void>HandlerContext
interface HandlerContext<TState extends StateProxy = StateProxy> {
firstWake: boolean
tags: Readonly<EntityTags>
entityUrl: string
entityType: string
args: Readonly<Record<string, unknown>>
db: EntityStreamDBWithActions
state: TState
events: Array<ChangeEvent>
actions: Record<string, (...args: unknown[]) => unknown>
darixTools: AgentTool[]
useAgent: (config: AgentConfig) => AgentHandle
useContext: (config: UseContextConfig) => void
timelineMessages: (opts?: TimelineProjectionOpts) => Array<TimestampedMessage>
insertContext: (id: string, entry: ContextEntryInput) => void
removeContext: (id: string) => void
getContext: (id: string) => ContextEntry | undefined
listContext: () => Array<ContextEntry>
agent: AgentHandle
spawn: (
type: string,
id: string,
args?: Record<string, unknown>,
opts?: {
initialMessage?: unknown
wake?: Wake
tags?: Record<string, string>
observe?: boolean
}
) => Promise<EntityHandle>
observe: (
source: ObservationSource,
opts?: { wake?: Wake }
) => Promise<EntityHandle | SharedStateHandle | ObservationHandle>
mkdb: <T extends SharedStateSchemaMap>(
id: string,
schema: T
) => SharedStateHandle<T>
send: (
entityUrl: string,
payload: unknown,
opts?: { type?: string; afterMs?: number }
) => void
setTag: (key: string, value: string) => Promise<void>
removeTag: (key: string) => Promise<void>
sleep: () => void
}Property reference
| Property | Description |
|---|---|
firstWake | true on the entity's first activation ever. Use for initialization. |
tags | Entity tags -- key/value metadata associated with this entity. |
entityUrl | The entity's URL path, e.g. "/assistant/my-chat". |
entityType | The registered type name, e.g. "assistant". |
args | Arguments passed when the entity was spawned. Immutable. |
db | The entity's stream database. Use db.actions for writes and db.collections for reads. |
state | Proxy object keyed by collection name. Each property is a StateCollectionProxy. |
events | Change events that triggered this wake. |
actions | Named action functions from the entity definition's actions factory. |
darixTools | Built-in tools for spawning, observing, sending, and managing entities. Pass to useAgent. |
useAgent | Configures the LLM agent. Returns an AgentHandle. See Configuring the agent. |
useContext | Declares context sources with token budgets and cache tiers. See Context composition. |
timelineMessages | Projects the entity timeline into LLM messages. See Context composition. |
insertContext | Inserts a durable context entry. See Context composition. |
removeContext | Removes a context entry by id. |
getContext | Gets a context entry by id, or undefined if not found. |
listContext | Lists all context entries. |
agent | The configured agent handle. Call agent.run() to start the agent loop. |
spawn | Creates a child entity. See Spawning and coordinating. |
observe | Connects to another entity's stream or shared db. See Reactive observers and Shared state. |
mkdb | Creates a new shared state stream. See Shared state. |
send | Sends a message to another entity's inbox. Supports delayed delivery via afterMs. |
setTag | Sets a tag on this entity. |
removeTag | Removes a tag from this entity. |
sleep | Returns the entity to idle without re-waking. |
WakeEvent
Describes what triggered this handler invocation.
type WakeEvent = {
source: string
type: string
fromOffset: number
toOffset: number
eventCount: number
payload?: unknown
summary?: string
fullRef?: string
}| Field | Description |
|---|---|
source | The stream or entity that caused the wake. |
type | The wake type (e.g. "message", "runFinished", "change"). |
fromOffset | Start offset of the events that triggered this wake. |
toOffset | End offset of the events that triggered this wake. |
eventCount | Number of new events since last wake. |
payload | Optional payload from the trigger event. |
summary | Optional human-readable summary. |
fullRef | Optional full reference string for the trigger. |
Typical handler pattern
Most handlers follow the same structure: initialize state on first wake, configure the agent, run the agent.
registry.define("assistant", {
description: "A general-purpose assistant",
state: {
status: { primaryKey: "key" },
},
async handler(ctx) {
if (ctx.firstWake) {
ctx.db.actions.status_insert({ row: { key: "current", value: "idle" } })
}
ctx.useAgent({
systemPrompt: "You are a helpful assistant.",
model: "claude-sonnet-4-5-20250929",
tools: [...ctx.darixTools],
})
await ctx.agent.run()
},
})AgentConfig
Passed to ctx.useAgent():
interface AgentConfig {
systemPrompt: string
model: string
tools: AgentTool[]
streamFn?: StreamFn
testResponses?: string[] | TestResponseFn
}firstWake
ctx.firstWake is true only on the entity's very first activation. Use it for one-time initialization:
async handler(ctx) {
if (ctx.firstWake) {
ctx.db.actions.status_insert({ row: { key: 'current', value: 'idle' } })
ctx.db.actions.counters_insert({ row: { key: 'runs', value: 0 } })
}
// ...
}On subsequent wakes (new messages, child completion, etc.), firstWake is false.
sleep
Call ctx.sleep() to return the entity to idle without triggering a re-wake. The handler exits and the entity waits for the next external event.
async handler(ctx, wake) {
if (wake.type === 'some-condition') {
// Nothing to do right now
ctx.sleep()
return
}
// Otherwise, run the agent
ctx.useAgent({ ... })
await ctx.agent.run()
}Using spawn args
Arguments passed at spawn time are available as ctx.args. This is how you parameterize entity behavior:
// Spawning side
const child = await ctx.spawn('worker', 'analysis-1', {
systemPrompt: 'You are an analyst.',
})
// Worker handler
async handler(ctx) {
const { systemPrompt } = ctx.args as { systemPrompt: string }
ctx.useAgent({
systemPrompt,
model: 'claude-sonnet-4-5-20250929',
tools: [...ctx.darixTools],
})
await ctx.agent.run()
}Adding custom tools
Combine ctx.darixTools with custom tools:
async handler(ctx) {
const myTool: AgentTool = {
name: 'lookup',
label: 'Lookup',
description: 'Looks up a value by key',
parameters: Type.Object({
key: Type.String({ description: 'The key to look up' }),
}),
execute: async (_toolCallId, params) => {
const { key } = params as { key: string }
const row = ctx.db.collections.kv?.get(key)
return {
content: [{ type: 'text', text: row ? JSON.stringify(row) : 'Not found' }],
details: {},
}
},
}
ctx.useAgent({
systemPrompt: 'You are an assistant with lookup capabilities.',
model: 'claude-sonnet-4-5-20250929',
tools: [...ctx.darixTools, myTool],
})
await ctx.agent.run()
}Sending messages
Use ctx.send() to deliver a message to another entity's inbox:
ctx.send("/worker/task-1", { action: "process", data: payload })
ctx.send("/worker/task-1", payload, { type: "custom_type" })The target entity will be woken to process the message.