@electric-ax/agents 0.4.11 → 0.4.13
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/dist/entrypoint.js +235 -89
- package/dist/index.cjs +233 -87
- package/dist/index.d.cts +29 -1
- package/dist/index.d.ts +29 -1
- package/dist/index.js +243 -97
- package/docs/entities/patterns/blackboard.md +1 -1
- package/docs/entities/patterns/dispatcher.md +3 -3
- package/docs/entities/patterns/manager-worker.md +11 -23
- package/docs/entities/patterns/map-reduce.md +1 -1
- package/docs/entities/patterns/pipeline.md +3 -3
- package/docs/index.md +61 -39
- package/docs/quickstart.md +26 -22
- package/docs/reference/entity-handle.md +51 -25
- package/docs/reference/handler-context.md +1 -1
- package/docs/reference/wake-event.md +1 -1
- package/docs/usage/defining-tools.md +4 -5
- package/docs/usage/overview.md +10 -6
- package/docs/usage/shared-state.md +3 -3
- package/docs/usage/spawning-and-coordinating.md +34 -18
- package/docs/usage/writing-handlers.md +1 -1
- package/docs/walkthrough.md +1156 -0
- package/package.json +4 -3
- package/skills/quickstart/scaffold/package.json +1 -1
- package/skills/quickstart.md +16 -10
|
@@ -39,7 +39,7 @@ export function registerPipeline(registry: EntityRegistry) {
|
|
|
39
39
|
The pipeline agent exposes a `run_stage` tool. The LLM drives the pipeline one stage at a time:
|
|
40
40
|
|
|
41
41
|
1. The LLM calls `run_stage` with an instruction and input for the current stage.
|
|
42
|
-
2. The tool spawns a worker with the instruction as its system prompt and the input as `initialMessage`, using `wake: 'runFinished'`.
|
|
42
|
+
2. The tool spawns a worker with the instruction as its system prompt and the input as `initialMessage`, using `wake: { on: 'runFinished', includeResponse: true }`.
|
|
43
43
|
3. The tool returns immediately. The pipeline entity is re-invoked when the worker finishes.
|
|
44
44
|
4. On each re-invocation, the wake event contains `finished_child.response` with the stage's output. The LLM then calls `run_stage` again with the next stage's instruction and the previous output as input.
|
|
45
45
|
5. This repeats until all stages are complete.
|
|
@@ -74,7 +74,7 @@ function createRunStageTool(ctx: HandlerContext): AgentTool {
|
|
|
74
74
|
`worker`,
|
|
75
75
|
id,
|
|
76
76
|
{ systemPrompt: instruction, tools: [`read`] },
|
|
77
|
-
{ initialMessage: input, wake: `runFinished
|
|
77
|
+
{ initialMessage: input, wake: { on: `runFinished`, includeResponse: true } }
|
|
78
78
|
)
|
|
79
79
|
ctx.db.actions.children_insert({
|
|
80
80
|
row: { key: id, url: child.entityUrl, stage: stageCount },
|
|
@@ -84,7 +84,7 @@ function createRunStageTool(ctx: HandlerContext): AgentTool {
|
|
|
84
84
|
content: [
|
|
85
85
|
{
|
|
86
86
|
type: `text` as const,
|
|
87
|
-
text: `Stage ${stageCount} spawned.
|
|
87
|
+
text: `Stage ${stageCount} spawned. The pipeline will continue when it finishes.`,
|
|
88
88
|
},
|
|
89
89
|
],
|
|
90
90
|
details: { stage: stageCount },
|
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**.
|
|
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] ✨ 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 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
104
|
+
See [Waking entities](/docs/agents/usage/waking-entities) for more information.
|
|
93
105
|
|
|
94
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
package/docs/quickstart.md
CHANGED
|
@@ -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
|
-
|
|
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
|
|
87
|
+
## Define your own entities
|
|
88
|
+
|
|
89
|
+
Define your own entity types and register them with the runtime server.
|
|
80
90
|
|
|
81
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
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()`.
|
|
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
|
-
|
|
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
|
|
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[]` |
|
|
36
|
-
| `
|
|
37
|
-
| `
|
|
38
|
-
|
|
39
|
-
|
|
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
|
|
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
|
|
package/docs/usage/overview.md
CHANGED
|
@@ -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
|
-
|
|
146
|
-
|
|
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
|
|
184
|
-
- `.
|
|
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 //
|
|
49
|
+
db: EntityStreamDB // TanStack DB for the observed entity stream
|
|
50
50
|
events: ChangeEvent[]
|
|
51
|
-
|
|
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
|
-
##
|
|
58
|
+
## Continuing after children finish
|
|
61
59
|
|
|
62
|
-
|
|
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
|
-
|
|
66
|
-
|
|
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
|
-
|
|
75
|
+
ctx.state.children.insert({
|
|
76
|
+
key: "analyst",
|
|
77
|
+
url: child.entityUrl,
|
|
78
|
+
status: "running",
|
|
79
|
+
})
|
|
80
|
+
return
|
|
81
|
+
}
|
|
70
82
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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) {
|