@electric-ax/agents 0.4.12 → 0.4.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/index.md CHANGED
@@ -4,6 +4,9 @@ titleTemplate: "... - Electric Agents"
4
4
  description: >-
5
5
  The durable runtime for long-lived agents — entities, handlers, wakes, agent loops, and coordination, built on Electric Streams, TanStack DB, and pi.
6
6
  outline: [2, 3]
7
+ next:
8
+ text: 'Quickstart'
9
+ link: '/docs/agents/quickstart'
7
10
  ---
8
11
 
9
12
  <script setup>
@@ -12,7 +15,16 @@ import EntityOverviewDiagram from '../../src/components/agents-home/EntityOvervi
12
15
 
13
16
  # Electric Agents
14
17
 
15
- Electric Agents is **the durable runtime for long-lived agents**. It's a runtime and communication fabric for spawning and scaling collaborative agents on serverless compute, using your existing web and AI&nbsp;frameworks.
18
+ Electric Agents is **the durable runtime for long-lived agents**.
19
+
20
+ It's a runtime and communication fabric for spawning and scaling collaborative agents <span class="no-wrap-sm">[on serverless compute](/blog/2026/06/04/serverless-agents)</span> using your existing web systems.
21
+
22
+ > [!Warning] ✨&nbsp; Start using Electric Agents now
23
+ > See the [Quickstart](/docs/agents/quickstart) to fire the system up and try the built-in agents.
24
+ >
25
+ > Dive into the [Walkthrough](/docs/agents/walkthrough) for a step-by-step guide to building a multi-agent system.
26
+
27
+ ## System overview
16
28
 
17
29
  Each agent is an **entity** — an addressable, schema-typed unit of state at `/{type}/{id}`. An entity's session and state live on a durable [Electric&nbsp;Stream](/streams/) of events.
18
30
 
@@ -22,7 +34,7 @@ Every step — runs, tool calls, text deltas, state changes — is appended to t
22
34
 
23
35
  <EntityOverviewDiagram />
24
36
 
25
- Start with the [Quickstart](/docs/agents/quickstart) to run the built-in `horton` and `worker` entities and connect your own app in a few minutes. The [Usage overview](/docs/agents/usage/overview) summarises the full developer surface in a single page.
37
+ See the [Usage overview](/docs/agents/usage/overview) for a summary of the developer surface in a single page.
26
38
 
27
39
  ## How it works
28
40
 
@@ -38,7 +50,7 @@ The runtime SDK is a layer over three foundations:
38
50
 
39
51
  **Outside the handler.** Any app or other entity can call [`createAgentsClient().observe(entity('/type/id'))`](/docs/agents/usage/clients-and-react) to load an entity's stream into a local DB and react to changes in real time, with the same schemas and types as the handler.
40
52
 
41
- ## Entities
53
+ ### Entities
42
54
 
43
55
  Use entities to model anything long-lived and addressable — an agent session, a chat thread, a research job, a coordinator, a worker. You register a **type** with [`registry.define()`](/docs/agents/reference/entity-registry) and spawn **instances** at `/{type}/{id}`. Each instance has its own state, handler, and event stream. See [Defining entities](/docs/agents/usage/defining-entities).
44
56
 
@@ -53,7 +65,7 @@ registry.define("assistant", {
53
65
  })
54
66
  ```
55
67
 
56
- ## Handlers
68
+ ### Handlers
57
69
 
58
70
  The function that runs when an entity wakes. Receives a [`HandlerContext`](/docs/agents/reference/handler-context) (`ctx`) and a [`WakeEvent`](/docs/agents/reference/wake-event) (`wake`). The handler decides how to respond: configure an agent, update state, spawn children, or any combination. See [Writing handlers](/docs/agents/usage/writing-handlers).
59
71
 
@@ -72,9 +84,9 @@ registry.define("support", {
72
84
  })
73
85
  ```
74
86
 
75
- ## Wakes
87
+ ### Waking and notifications
76
88
 
77
- Events that trigger a handler invocation. Wake sources include incoming messages, child completion, state changes, and timers (scheduled sends, cron, timeouts). The [`WakeEvent`](/docs/agents/reference/wake-event) tells the handler why it was woken. See [Waking entities](/docs/agents/usage/waking-entities).
89
+ Events that trigger a handler invocation. Wake sources include incoming messages, child completion, state changes, and timers (scheduled sends, cron, timeouts). The [`WakeEvent`](/docs/agents/reference/wake-event) tells the handler why it was woken.
78
90
 
79
91
  ```ts
80
92
  async handler(ctx, wake) {
@@ -89,35 +101,9 @@ async handler(ctx, wake) {
89
101
  }
90
102
  ```
91
103
 
92
- ## State
104
+ See [Waking entities](/docs/agents/usage/waking-entities) for more information.
93
105
 
94
- Custom persistent collections on the entity. Defined as part of the [entity definition](/docs/agents/reference/entity-definition) and accessed through `ctx.db` alongside the [built-in collections](#built-in-collections). State is local to the entity, typed, and survives restarts. Use it for things that belong to the entity but aren't part of the agent's event stream — an order's items, a research job's findings, a chat session's TODOs. See [Managing state](/docs/agents/usage/managing-state).
95
-
96
- ```ts
97
- registry.define("tracker", {
98
- state: {
99
- items: {
100
- schema: z.object({
101
- key: z.string(),
102
- name: z.string(),
103
- done: z.boolean(),
104
- }),
105
- primaryKey: "key",
106
- },
107
- },
108
- async handler(ctx) {
109
- // read
110
- const item = ctx.db.collections.items.get("item-1")
111
-
112
- // write
113
- ctx.db.actions.items_insert({
114
- row: { key: "item-2", name: "New", done: false },
115
- })
116
- },
117
- })
118
- ```
119
-
120
- ## Agent loop
106
+ ### The agent loop
121
107
 
122
108
  The core pattern is [`ctx.useAgent()`](/docs/agents/reference/agent-config) followed by `ctx.agent.run()`. This runs the LLM in a loop — it generates text, calls tools, and continues until it has nothing left to do. All activity is automatically persisted to the entity's stream. See [Configuring the agent](/docs/agents/usage/configuring-the-agent).
123
109
 
@@ -131,7 +117,7 @@ ctx.useAgent({
131
117
  await ctx.agent.run()
132
118
  ```
133
119
 
134
- ## Tools
120
+ ### Tools
135
121
 
136
122
  Functions the LLM can call during the agent loop. Each tool has a name, description, parameters (defined with [TypeBox](https://github.com/sinclairzx81/typebox) or any [Standard Schema](https://standardschema.dev) validator), and an execute function. Tools run in the handler's context and have access to the entity's state and coordination primitives. See [Defining tools](/docs/agents/usage/defining-tools) and the [`AgentTool` reference](/docs/agents/reference/agent-tool).
137
123
 
@@ -156,7 +142,7 @@ const searchKbTool: AgentTool = {
156
142
  }
157
143
  ```
158
144
 
159
- ## Coordination
145
+ ### Coordination
160
146
 
161
147
  Entities interact through structured primitives. An entity can `spawn` children, `observe` other entities, `send` messages, and [share state](/docs/agents/usage/shared-state). These operations are all durable — they survive restarts and are tracked in the event stream. See [Spawning and coordinating](/docs/agents/usage/spawning-and-coordinating).
162
148
 
@@ -170,7 +156,7 @@ async handler(ctx) {
170
156
  systemPrompt: "Analyse the report",
171
157
  tools: ["read"],
172
158
  },
173
- { initialMessage: "Find the top three issues", wake: "runFinished" }
159
+ { initialMessage: "Find the top three issues", wake: { on: "runFinished", includeResponse: true } }
174
160
  )
175
161
 
176
162
  // send a message to another entity
@@ -183,7 +169,7 @@ async handler(ctx) {
183
169
  }
184
170
  ```
185
171
 
186
- ## Built-in collections
172
+ ### Built-in collections
187
173
 
188
174
  Every entity automatically has collections for runs, steps, texts, tool calls, errors, inbox, and more. These are populated by the runtime as the agent operates and give you live observability into every step of the agent loop — useful for chat UIs, debugging tools, dashboards, and analytics. Query them from the handler or observe them externally. See the [Built-in collections reference](/docs/agents/reference/built-in-collections).
189
175
 
@@ -198,9 +184,45 @@ const db = await client.observe(entity("/support/ticket-42"))
198
184
  console.log(db.collections.texts.toArray)
199
185
  ```
200
186
 
187
+
188
+ ### Custom collections
189
+
190
+ Define custom persistent collections on the entity.
191
+
192
+ Defined as part of the [entity definition](/docs/agents/reference/entity-definition) and accessed through `ctx.db` alongside the [built-in collections](#built-in-collections).
193
+
194
+ ```ts
195
+ registry.define("tracker", {
196
+ state: {
197
+ items: {
198
+ schema: z.object({
199
+ key: z.string(),
200
+ name: z.string(),
201
+ done: z.boolean(),
202
+ }),
203
+ primaryKey: "key",
204
+ },
205
+ },
206
+ async handler(ctx) {
207
+ // read
208
+ const item = ctx.db.collections.items.get("item-1")
209
+
210
+ // write
211
+ ctx.db.actions.items_insert({
212
+ row: { key: "item-2", name: "New", done: false },
213
+ })
214
+ },
215
+ })
216
+ ```
217
+
218
+ State is local to the entity, typed, and survives restarts. Use it for things that belong to the entity but aren't part of the agent's event stream — an order's items, a research job's findings, a chat session's TODOs.
219
+
220
+ See [Managing state](/docs/agents/usage/managing-state) for more information.
221
+
201
222
  ## Next steps
202
223
 
203
- - [Quickstart](/docs/agents/quickstart) — run the built-in `horton` and `worker` entities and connect your own app.
224
+ - [Quickstart](/docs/agents/quickstart) — run the built-in `horton` and `worker` entities and connect your own app
225
+ - [Walkthrough](./walkthrough) — go from a web or mobile app to a <span class="no-wrap">multi-agent</span> system
204
226
  - [Usage overview](/docs/agents/usage/overview) — the full developer surface on one page.
205
227
  - [Defining entities](/docs/agents/usage/defining-entities) — entity types, schemas, and configuration.
206
228
  - [Writing handlers](/docs/agents/usage/writing-handlers) — handler lifecycle and the `ctx` API.
@@ -4,16 +4,24 @@ titleTemplate: "... - Electric Agents"
4
4
  description: >-
5
5
  Run the Electric Agents runtime and the built-in Horton assistant with a single CLI command, then connect from the web UI or define your own entities.
6
6
  outline: [2, 3]
7
+ prev:
8
+ text: 'Overview'
9
+ link: '/docs/agents/'
10
+ next:
11
+ text: 'Walkthrough'
12
+ link: '/docs/agents/walkthrough'
7
13
  ---
8
14
 
9
15
  # Quickstart
10
16
 
11
- One command starts the Electric Agents runtime, the web UI, and a local [Horton](./entities/agents/horton) assistant you can chat with right away. From there, define your own [entities](./usage/defining-entities) in your own app.
17
+ Get started with the Electric Agents runtime, the web UI, and a local [Horton](./entities/agents/horton) assistant you can chat with right away:
12
18
 
13
19
  ```sh
14
20
  npx electric-ax agents quickstart
15
21
  ```
16
22
 
23
+ Or see the [Walkthrough guide](./walkthrough) for a step-by-step guide to defining your own entities and building a multi-agent system from scratch.
24
+
17
25
  ## What you'll need
18
26
 
19
27
  - **Node.js 18+**.
@@ -76,11 +84,14 @@ npx electric-ax agents observe /horton/onboarding
76
84
 
77
85
  See the [CLI reference](./reference/cli) for the full command surface.
78
86
 
79
- ## Define your own entity types
87
+ ## Define your own entities
88
+
89
+ Define your own entity types and register them with the runtime server.
80
90
 
81
- Once you're chatting with Horton, the next step is to define your own entity types in your own app. Your app is just a process that registers entity types with the runtime server and receives webhook callbacks when they wake.
91
+ > [!Tip] See the Walkthrough guide
92
+ > See the [Walkthrough](./walkthrough) for a step-by-step guide on how to go from a web or mobile app to a <span class="no-wrap">multi-agent</span> system with Electric Agents.
82
93
 
83
- ### 1. Install the runtime SDK
94
+ Install the runtime SDK:
84
95
 
85
96
  ```sh
86
97
  mkdir my-agents-app && cd my-agents-app
@@ -89,8 +100,6 @@ npm install @electric-ax/agents-runtime
89
100
  npm install --save-dev tsx
90
101
  ```
91
102
 
92
- ### 2. Create a server
93
-
94
103
  Create `server.ts`:
95
104
 
96
105
  ```ts
@@ -140,28 +149,20 @@ server.listen(PORT, async () => {
140
149
  })
141
150
  ```
142
151
 
143
- This does four things:
144
-
145
- 1. **Defines an entity type** called `assistant` with a handler that configures and runs an LLM agent.
146
- 2. **Creates a runtime handler** that connects to the runtime server.
147
- 3. **Starts an HTTP server** to receive webhook callbacks from the runtime.
148
- 4. **Registers entity types** with the runtime server on startup.
149
-
150
- See [App setup](./usage/app-setup) for the full `createRuntimeHandler` configuration.
152
+ This:
151
153
 
152
- ### 3. Run your app
154
+ 1. **defines an entity type** called `assistant`
155
+ 2. **creates a runtime handler** that connects to the runtime server
156
+ 3. **starts an HTTP server** to receive webhook callbacks from the runtime
157
+ 4. **registers entity types** with the runtime server on startup
153
158
 
154
- With the runtime server already running (from `electric agents quickstart` or `electric agents start`), start your app:
159
+ Make sure `ANTHROPIC_API_KEY` is exported in this shell (or copy your `.env` into `my-agents-app`). Then, with the runtime server already running (from `electric agents quickstart` or `electric agents start`), start your app:
155
160
 
156
161
  ```sh
157
162
  npx tsx server.ts
158
163
  ```
159
164
 
160
- Your handler calls `ctx.useAgent()` in this process, so make sure `ANTHROPIC_API_KEY` is exported in this shell (or copy your `.env` into `my-agents-app`).
161
-
162
- ### 4. Interact with your entity
163
-
164
- Spawn an instance, send it a message, and observe the timeline:
165
+ You can now interact with your custom entity through the [CLI](/docs/agents/reference/cli). For example, to spawn an instance, send it a message, and observe the timeline:
165
166
 
166
167
  ```sh
167
168
  npx electric-ax agents spawn /assistant/my-assistant
@@ -169,7 +170,9 @@ npx electric-ax agents send /assistant/my-assistant 'Hello!'
169
170
  npx electric-ax agents observe /assistant/my-assistant
170
171
  ```
171
172
 
172
- Or open the web UI at `http://localhost:4437` and pick `/assistant/my-assistant` from the entity list.
173
+ Or open the web UI at [localhost:4437](http://localhost:4437) and create a new session, choosing your assistant type from the entity list.
174
+
175
+ See the [Walkthrough](./walkthrough) guide and [App setup](./usage/app-setup) docs for more details.
173
176
 
174
177
  ## Stop the dev environment
175
178
 
@@ -193,6 +196,7 @@ See the [CLI reference](./reference/cli#start) for the full set of commands.
193
196
 
194
197
  ## Next steps
195
198
 
199
+ - [Walkthrough](./walkthrough) — go from a web or mobile app to a <span class="no-wrap">multi-agent</span> system
196
200
  - [Overview](./) — the mental model behind entities, handlers, and wakes.
197
201
  - [Usage overview](./usage/overview) — the full developer surface on one page.
198
202
  - [Defining entities](./usage/defining-entities) — entity types, schemas, and configuration.
@@ -2,13 +2,13 @@
2
2
  title: EntityHandle
3
3
  titleTemplate: "... - Electric Agents"
4
4
  description: >-
5
- API reference for EntityHandle returned by spawn and observe: streams, status, text retrieval, and messaging.
5
+ API reference for EntityHandle returned by spawn and observe: streams, status, and messaging.
6
6
  outline: [2, 3]
7
7
  ---
8
8
 
9
9
  # EntityHandle
10
10
 
11
- Handle returned by `ctx.spawn()` and `ctx.observe()`. Provides access to a child or observed entity's stream and status.
11
+ Handle returned by `ctx.spawn()` and `ctx.observe(entity(...))`. It identifies a child or observed entity and exposes its materialized stream.
12
12
 
13
13
  **Source:** `@electric-ax/agents-runtime`
14
14
 
@@ -18,25 +18,60 @@ interface EntityHandle {
18
18
  type?: string
19
19
  db: EntityStreamDB
20
20
  events: ChangeEvent[]
21
- run: Promise<void>
22
- text(): Promise<string[]>
23
- send(msg: unknown): void
21
+ send(msg: unknown): Promise<SendResult>
24
22
  status(): ChildStatus | undefined
25
23
  }
26
24
  ```
27
25
 
28
26
  ## Members
29
27
 
30
- | Member | Type | Description |
31
- | ----------- | -------------------------- | ----------------------------------------------------------------------------------- |
32
- | `entityUrl` | `string` | URL path of the entity (e.g. `"/chat/my-child"`). |
33
- | `type` | `string \| undefined` | Entity type name, if known. |
34
- | `db` | `EntityStreamDB` | The entity's TanStack DB instance for querying its collections. |
35
- | `events` | `ChangeEvent[]` | All change events received from this entity's stream. |
36
- | `run` | `Promise<void>` | Promise that resolves when the entity's current run completes. Useful with `await`. |
37
- | `text()` | `Promise<string[]>` | Returns all text outputs from the entity's stream. |
38
- | `send(msg)` | `void` | Send a message to this entity. |
39
- | `status()` | `ChildStatus \| undefined` | Current status of the entity, or `undefined` if unknown. |
28
+ | Member | Type | Description |
29
+ | ----------- | -------------------------- | --------------------------------------------------------------- |
30
+ | `entityUrl` | `string` | URL path of the entity, e.g. `"/worker/child-1"`. |
31
+ | `type` | `string \| undefined` | Entity type name, if known. |
32
+ | `db` | `EntityStreamDB` | The entity's TanStack DB instance for querying its collections. |
33
+ | `events` | `ChangeEvent[]` | Change events received from this entity's stream this wake. |
34
+ | `send(msg)` | `Promise<SendResult>` | Send a follow-up message to this entity. |
35
+ | `status()` | `ChildStatus \| undefined` | Current child status, or `undefined` if unknown. |
36
+
37
+ ## Coordinating with completion
38
+
39
+ `EntityHandle` does **not** provide a same-wake “wait for output” API. To continue after a child finishes, spawn or observe it with a wake condition and return from the current handler:
40
+
41
+ ```ts
42
+ const child = await ctx.spawn(
43
+ "worker",
44
+ "analyst-1",
45
+ { systemPrompt: "Analyze this input", tools: ["read"] },
46
+ {
47
+ initialMessage: "...",
48
+ wake: { on: "runFinished", includeResponse: true },
49
+ }
50
+ )
51
+
52
+ ctx.state.children.insert({
53
+ key: "analyst-1",
54
+ url: child.entityUrl,
55
+ status: "running",
56
+ })
57
+ return
58
+ ```
59
+
60
+ On the later wake, inspect the finished child payload and continue orchestration:
61
+
62
+ ```ts
63
+ if (wake.payload?.finished_child) {
64
+ const finished = wake.payload.finished_child
65
+ const response = finished.response ?? ""
66
+
67
+ ctx.state.children.update(finished.url, (draft) => {
68
+ draft.status = finished.run_status
69
+ draft.response = response
70
+ })
71
+ }
72
+ ```
73
+
74
+ For structured or large outputs, have the child write to shared state and use the `runFinished` wake as the signal that it is safe to read/reduce that state.
40
75
 
41
76
  ## ChildStatus
42
77
 
@@ -49,15 +84,6 @@ interface ChildStatusEntry {
49
84
  key: string
50
85
  entity_url: string
51
86
  entity_type: string
52
- status: "spawning" | "running" | "idle" | "stopped"
87
+ status: "spawning" | "running" | "idle" | "paused" | "stopping" | "stopped" | "killed"
53
88
  }
54
89
  ```
55
-
56
- Status values:
57
-
58
- | Status | Description |
59
- | ---------- | ----------------------------------------------------------- |
60
- | `spawning` | Entity creation is in progress. |
61
- | `running` | Handler is currently executing. |
62
- | `idle` | Handler has completed; entity is waiting for the next wake. |
63
- | `stopped` | Entity has been stopped or deleted. |
@@ -104,7 +104,7 @@ interface HandlerContext<TState extends StateProxy = StateProxy> {
104
104
  | `observe(source, opts?)` | `Promise<EntityHandle \| SharedStateHandle \| ObservationHandle>` | Observe a source. Return type depends on source type: `EntityHandle` for entities, `SharedStateHandle & ObservationHandle` for db, `ObservationHandle` otherwise. Use `entity()`, `cron()`, `entities()`, `db()` helpers to build sources. |
105
105
  | `mkdb(id, schema)` | `SharedStateHandle<T>` | Create a new shared state stream. See [`SharedStateHandle`](./shared-state-handle). |
106
106
  | `send(entityUrl, payload, opts?)` | `void` | Send a message to another entity. `opts` accepts `type` and `afterMs` (delay in milliseconds). |
107
- | `recordRun()` | `RunHandle` | Record a non-LLM run in the built-in `runs` collection, so observers using `wake: "runFinished"` are notified when external work completes. |
107
+ | `recordRun()` | `RunHandle` | Record a non-LLM run in the built-in `runs` collection, so observers using `wake: { on: "runFinished", includeResponse: true }` are notified when external work completes. |
108
108
  | `setTag(key, value)` | `Promise<void>` | Set a tag on this entity. |
109
109
  | `removeTag(key)` | `Promise<void>` | Remove a tag from this entity. |
110
110
  | `sleep()` | `void` | End the handler without running an agent. The entity remains idle until the next wake. |
@@ -85,7 +85,7 @@ Inspect the payload to distinguish the sub-kind:
85
85
 
86
86
  | Sub-kind | Producer | Payload marker |
87
87
  | ------------------- | --------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
88
- | Child finished | `ctx.spawn(..., { wake: 'runFinished' })` when the child completes or fails | `payload.finished_child` is set (with `run_status` and optional `response`) |
88
+ | Child finished | `ctx.spawn(..., { wake: { on: 'runFinished', includeResponse: true } })` when the child completes or fails | `payload.finished_child` is set (with `run_status` and optional `response`) |
89
89
  | Observed change | `ctx.observe(..., { wake: { on: 'change' } })` or `observe(db(...))` | `payload.changes` is non-empty |
90
90
  | Shared-state change | `await ctx.observe(db(...), { wake: { on: 'change' } })` | `payload.changes` is non-empty, `payload.source` identifies the shared-state stream |
91
91
  | Cron fired | A cron schedule entry on the entity's manifest | `payload.source` identifies the schedule; `payload.changes` is empty |
@@ -187,20 +187,19 @@ function createDispatchTool(ctx: HandlerContext): AgentTool {
187
187
  { systemPrompt },
188
188
  {
189
189
  initialMessage: task,
190
- wake: "runFinished",
190
+ wake: { on: "runFinished", includeResponse: true },
191
191
  }
192
192
  )
193
- const text = (await child.text()).join("\n\n")
194
193
  return {
195
- content: [{ type: "text", text }],
196
- details: {},
194
+ content: [{ type: "text", text: `Started ${child.entityUrl}; I will continue when it finishes.` }],
195
+ details: { childUrl: child.entityUrl },
197
196
  }
198
197
  },
199
198
  }
200
199
  }
201
200
  ```
202
201
 
203
- `ctx.spawn` returns an `EntityHandle`. Passing `wake: 'runFinished'` means the parent will be woken when the child's agent run completes. `child.text()` returns all text outputs from the child's stream.
202
+ `ctx.spawn` returns an `EntityHandle`. Passing `wake: { on: 'runFinished', includeResponse: true }` means the parent will be woken later when the child's agent run completes, with the child's text response included in the wake payload. Tools should start child work and return; continuation happens in the later handler wake.
204
203
 
205
204
  ## Wiring tools together
206
205
 
@@ -4,6 +4,9 @@ titleTemplate: "... - Electric Agents"
4
4
  description: >-
5
5
  High level overview of the Electric Agents system and developer APIs.
6
6
  outline: [2, 3]
7
+ prev:
8
+ text: 'Walkthrough'
9
+ link: '/docs/agents/walkthrough'
7
10
  ---
8
11
 
9
12
  # Usage overview
@@ -140,10 +143,12 @@ function createDispatchTool(ctx: HandlerContext): AgentTool {
140
143
  "worker",
141
144
  id,
142
145
  { systemPrompt, tools: ["read"] },
143
- { initialMessage: task, wake: "runFinished" }
146
+ { initialMessage: task, wake: { on: "runFinished", includeResponse: true } }
144
147
  )
145
- const text = (await child.text()).join("\n\n")
146
- return { content: [{ type: "text", text }], details: {} }
148
+ return {
149
+ content: [{ type: "text", text: `Started ${child.entityUrl}; I will continue when it finishes.` }],
150
+ details: { childUrl: child.entityUrl },
151
+ }
147
152
  },
148
153
  }
149
154
  }
@@ -180,9 +185,8 @@ See [Managing state](/docs/agents/usage/managing-state).
180
185
 
181
186
  **EntityHandle** returned from spawn/observe:
182
187
 
183
- - `.entityUrl`, `.type`, `.db` (read-only TanStack DB)
184
- - `.run` -- Promise that resolves when child completes
185
- - `.text()` -- get all completed text output
188
+ - `.entityUrl`, `.type` -- identify the entity
189
+ - `.db` / `.events` -- inspect the observed entity stream
186
190
  - `.send(msg)` -- send follow-up message
187
191
  - `.status()` -- `ChildStatus | undefined` (object with `.status`, `.entity_url`, `.entity_type`)
188
192
 
@@ -61,7 +61,7 @@ const child = await ctx.spawn(
61
61
  systemPrompt: "...",
62
62
  sharedDb: { id: "research-123", schema: researchSchema },
63
63
  },
64
- { initialMessage: "Research topic X", wake: "runFinished" }
64
+ { initialMessage: "Research topic X", wake: { on: "runFinished", includeResponse: true } }
65
65
  )
66
66
  ```
67
67
 
@@ -149,7 +149,7 @@ registry.define("debate", {
149
149
  systemPrompt: "Argue FOR the topic.",
150
150
  sharedDb: { id: `debate-${ctx.entityUrl}`, schema: debateSchema },
151
151
  },
152
- { initialMessage: "The topic is: ...", wake: "runFinished" }
152
+ { initialMessage: "The topic is: ...", wake: { on: "runFinished", includeResponse: true } }
153
153
  )
154
154
 
155
155
  const con = await ctx.spawn(
@@ -159,7 +159,7 @@ registry.define("debate", {
159
159
  systemPrompt: "Argue AGAINST the topic.",
160
160
  sharedDb: { id: `debate-${ctx.entityUrl}`, schema: debateSchema },
161
161
  },
162
- { initialMessage: "The topic is: ...", wake: "runFinished" }
162
+ { initialMessage: "The topic is: ...", wake: { on: "runFinished", includeResponse: true } }
163
163
  )
164
164
 
165
165
  // Read all arguments written by both workers
@@ -46,36 +46,52 @@ Returned by `spawn` and `observe`:
46
46
  interface EntityHandle {
47
47
  entityUrl: string
48
48
  type?: string
49
- db: EntityStreamDB // Read-only TanStack DB
49
+ db: EntityStreamDB // TanStack DB for the observed entity stream
50
50
  events: ChangeEvent[]
51
- run: Promise<void> // Resolves when child's run completes
52
- text(): Promise<string[]> // Get completed text outputs
53
- send(msg: unknown): void // Send follow-up message
51
+ send(msg: unknown): Promise<SendResult> // Send follow-up message
54
52
  status(): ChildStatus | undefined
55
53
  }
56
54
  ```
57
55
 
58
56
  `status()` returns a `ChildStatus` object (or `undefined` if no status is known yet) with `.status`, `.entity_url`, `.entity_type`, and `.key`.
59
57
 
60
- ## Waiting for children
58
+ ## Continuing after children finish
61
59
 
62
- Wait for a single child:
60
+ Do not wait for child output inside the same wake. Instead, spawn or observe the child with a wake condition, persist enough metadata to correlate the child, and return.
63
61
 
64
62
  ```ts
65
- await child.run
66
- const output = (await child.text()).join("\n\n")
67
- ```
63
+ async handler(ctx, wake) {
64
+ if (ctx.firstWake) {
65
+ const child = await ctx.spawn(
66
+ "worker",
67
+ "analyst",
68
+ { systemPrompt: "Analyze this input", tools: ["read"] },
69
+ {
70
+ initialMessage: "Initial task.",
71
+ wake: { on: "runFinished", includeResponse: true },
72
+ }
73
+ )
68
74
 
69
- Wait for multiple children in parallel:
75
+ ctx.state.children.insert({
76
+ key: "analyst",
77
+ url: child.entityUrl,
78
+ status: "running",
79
+ })
80
+ return
81
+ }
70
82
 
71
- ```ts
72
- const results = await Promise.all(
73
- children.map(async ({ handle }) => ({
74
- text: (await handle.text()).join("\n\n"),
75
- }))
76
- )
83
+ const finished = wake.payload?.finished_child
84
+ if (finished) {
85
+ ctx.state.children.update(finished.url, (draft) => {
86
+ draft.status = finished.run_status
87
+ draft.response = finished.response ?? ""
88
+ })
89
+ }
90
+ }
77
91
  ```
78
92
 
93
+ Use `includeResponse: true` for simple text handoff. For structured or large outputs, have children write to shared state and use the `runFinished` wake as the continuation signal.
94
+
79
95
  ## observe
80
96
 
81
97
  Subscribe to an existing entity without spawning it:
@@ -122,7 +138,7 @@ async handler(ctx) {
122
138
  { systemPrompt: "...", tools: ["read"] },
123
139
  {
124
140
  initialMessage: "Initial task.",
125
- wake: "runFinished",
141
+ wake: { on: "runFinished", includeResponse: true },
126
142
  }
127
143
  )
128
144
  }
@@ -157,7 +173,7 @@ await ctx.spawn(
157
173
  { systemPrompt: "Summarise this data.", tools: ["read"] },
158
174
  {
159
175
  initialMessage: JSON.stringify(data),
160
- wake: "runFinished",
176
+ wake: { on: "runFinished", includeResponse: true },
161
177
  }
162
178
  )
163
179
  ```
@@ -210,7 +210,7 @@ async handler(ctx, wake) {
210
210
 
211
211
  ## recordRun
212
212
 
213
- Call `ctx.recordRun()` when a handler does work without `ctx.agent.run()` but still needs to publish run lifecycle events. This is how non-LLM entities can wake parents observing them with `wake: "runFinished"`.
213
+ Call `ctx.recordRun()` when a handler does work without `ctx.agent.run()` but still needs to publish run lifecycle events. This is how non-LLM entities can wake parents observing them with `wake: { on: "runFinished", includeResponse: true }`.
214
214
 
215
215
  ```ts
216
216
  async handler(ctx) {