Skip to content

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

ts
handler(ctx: HandlerContext, wake: WakeEvent) => void | Promise<void>

HandlerContext

ts
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

PropertyDescription
firstWaketrue on the entity's first activation ever. Use for initialization.
tagsEntity tags -- key/value metadata associated with this entity.
entityUrlThe entity's URL path, e.g. "/assistant/my-chat".
entityTypeThe registered type name, e.g. "assistant".
argsArguments passed when the entity was spawned. Immutable.
dbThe entity's stream database. Use db.actions for writes and db.collections for reads.
stateProxy object keyed by collection name. Each property is a StateCollectionProxy.
eventsChange events that triggered this wake.
actionsNamed action functions from the entity definition's actions factory.
darixToolsBuilt-in tools for spawning, observing, sending, and managing entities. Pass to useAgent.
useAgentConfigures the LLM agent. Returns an AgentHandle. See Configuring the agent.
useContextDeclares context sources with token budgets and cache tiers. See Context composition.
timelineMessagesProjects the entity timeline into LLM messages. See Context composition.
insertContextInserts a durable context entry. See Context composition.
removeContextRemoves a context entry by id.
getContextGets a context entry by id, or undefined if not found.
listContextLists all context entries.
agentThe configured agent handle. Call agent.run() to start the agent loop.
spawnCreates a child entity. See Spawning and coordinating.
observeConnects to another entity's stream or shared db. See Reactive observers and Shared state.
mkdbCreates a new shared state stream. See Shared state.
sendSends a message to another entity's inbox. Supports delayed delivery via afterMs.
setTagSets a tag on this entity.
removeTagRemoves a tag from this entity.
sleepReturns the entity to idle without re-waking.

WakeEvent

Describes what triggered this handler invocation.

ts
type WakeEvent = {
  source: string
  type: string
  fromOffset: number
  toOffset: number
  eventCount: number
  payload?: unknown
  summary?: string
  fullRef?: string
}
FieldDescription
sourceThe stream or entity that caused the wake.
typeThe wake type (e.g. "message", "runFinished", "change").
fromOffsetStart offset of the events that triggered this wake.
toOffsetEnd offset of the events that triggered this wake.
eventCountNumber of new events since last wake.
payloadOptional payload from the trigger event.
summaryOptional human-readable summary.
fullRefOptional 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.

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

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

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

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

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

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

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