Worker
A generic, sandboxed subagent type. Workers are spawned by other agents (typically Horton) via the spawn_worker tool — the spawner provides a system prompt and picks the subset of tools the worker should have access to.
Source: packages/agents/src/agents/worker.ts
Spawn args
interface WorkerArgs {
systemPrompt: string
tools?: Array<WorkerToolName>
sharedDb?: { id: string; schema: SharedStateSchemaMap }
sharedDbToolMode?: "full" | "write-only"
}| Field | Required | Description |
|---|---|---|
systemPrompt | Yes | The worker's system prompt. Brief it like a colleague: file paths, line numbers, deliverable. |
tools | No | Subset of valid tool names (see below). Unknown names throw at parse time. |
sharedDb | No | Shared state stream id and schema to connect to. |
sharedDbToolMode | No | Shared state tool mode: "full" (default) or "write-only". |
registerWorker(registry, { workingDirectory, streamFn? }) is called by the dev server during bootstrap; you don't usually call it yourself.
Valid tool names
type WorkerToolName =
| "bash"
| "read"
| "write"
| "edit"
| "brave_search"
| "fetch_url"
| "spawn_worker"These are the same primitives Horton uses. Pick the smallest subset the worker needs — tools are the worker's permission set.
The worker must receive at least one tool or a sharedDb config. If sharedDb is provided, the worker gets generated shared-state tools in addition to any selected primitives.
Spawning a worker
The canonical way to spawn a worker is the spawn_worker tool, which Horton calls from inside its agent loop:
spawn_worker({
systemPrompt:
"You are a focused researcher. Find the three most-cited papers on X and return their titles, authors, and DOIs as a markdown table.",
tools: ["brave_search", "fetch_url"],
initialMessage: "Begin research now.",
})| Field | Required | Notes |
|---|---|---|
systemPrompt | Yes | Sets persona and constraints. |
tools | Yes | Subset of WorkerToolName. Must contain at least one entry. |
initialMessage | Yes | First user message. Without this the worker idles — nothing kicks it off. |
The spawn uses wake: { on: 'runFinished', includeResponse: true }, so the spawner wakes when the worker finishes its run and receives the worker's response in the wake message.
What the handler does
- Parses
ctx.argsintoWorkerArgs. Throws ifsystemPromptis empty, iftoolscontains an unknown name, or if neithertoolsnorsharedDbis provided. - Builds the requested tool instances against the worker's
workingDirectory(and a fresh per-wakereadSetfor the read-first-then-edit guard). - If
sharedDbis present, connects withctx.observe(db(id, schema))and exposes generatedread_*,write_*,update_*, anddelete_*tools (write_*only in"write-only"mode). - Configures the agent with
HORTON_MODEL(claude-sonnet-4-5-20250929), the provided system prompt (with a brief reporting-back footer appended), and the assembled tool list. - Runs the agent until the LLM stops.
Least-privilege sandbox
Workers deliberately do not receive ctx.electricTools. The spawner already picked the worker's tool subset; granting entity-runtime primitives (cron, schedule, send-to-arbitrary-entity) would let a worker escape that scope. If a worker needs those primitives, it must spawn its own subagent or report back to the spawner.
Reporting footer
The runtime appends this to every worker's system prompt:
# Reporting back
When you finish, respond with a concise report covering what was done and any key findings. The caller will relay this to the user, so it only needs the essentials.Details
| Property | Value |
|---|---|
| Type name | worker |
| Model | HORTON_MODEL (claude-sonnet-4-5-20250929) |
| Tools | Subset of 7 primitives plus optional shared-state tools. No ctx.electricTools. |
| Working directory | Provided to registerWorker at bootstrap |
| Description | Internal — generic worker spawned by other agents. Configure via spawn args (systemPrompt + tools + optional sharedDb). |