@electric-ax/agents 0.2.3 → 0.2.4
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 +40 -12
- package/dist/index.cjs +40 -12
- package/dist/index.js +40 -12
- package/docs/entities/agents/coder.md +99 -0
- package/docs/entities/agents/horton.md +16 -13
- package/docs/entities/agents/worker.md +18 -18
- package/docs/entities/patterns/blackboard.md +6 -6
- package/docs/entities/patterns/dispatcher.md +1 -1
- package/docs/entities/patterns/manager-worker.md +1 -1
- package/docs/entities/patterns/map-reduce.md +1 -1
- package/docs/entities/patterns/pipeline.md +1 -1
- package/docs/entities/patterns/reactive-observers.md +2 -2
- package/docs/examples/playground.md +42 -26
- package/docs/index.md +23 -23
- package/docs/quickstart.md +13 -13
- package/docs/reference/agent-config.md +20 -12
- package/docs/reference/agent-tool.md +1 -1
- package/docs/reference/built-in-collections.md +21 -21
- package/docs/reference/cli.md +39 -30
- package/docs/reference/entity-definition.md +9 -9
- package/docs/reference/entity-handle.md +2 -2
- package/docs/reference/entity-registry.md +1 -1
- package/docs/reference/handler-context.md +69 -18
- package/docs/reference/runtime-handler.md +25 -23
- package/docs/reference/shared-state-handle.md +7 -7
- package/docs/reference/state-collection-proxy.md +1 -1
- package/docs/reference/wake-event.md +23 -23
- package/docs/usage/app-setup.md +24 -23
- package/docs/usage/clients-and-react.md +44 -36
- package/docs/usage/configuring-the-agent.md +25 -19
- package/docs/usage/context-composition.md +12 -12
- package/docs/usage/defining-entities.md +36 -36
- package/docs/usage/defining-tools.md +45 -45
- package/docs/usage/embedded-builtins.md +48 -47
- package/docs/usage/managing-state.md +12 -12
- package/docs/usage/overview.md +52 -45
- package/docs/usage/programmatic-runtime-client.md +50 -47
- package/docs/usage/shared-state.md +32 -32
- package/docs/usage/spawning-and-coordinating.md +9 -9
- package/docs/usage/testing.md +14 -14
- package/docs/usage/waking-entities.md +13 -13
- package/docs/usage/writing-handlers.md +57 -26
- package/package.json +4 -1
- package/scripts/sync-docs.mjs +42 -0
- package/docs/examples/mega-draw.md +0 -106
package/docs/usage/overview.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Overview
|
|
3
|
-
titleTemplate:
|
|
3
|
+
titleTemplate: "... - Electric Agents"
|
|
4
4
|
description: >-
|
|
5
5
|
High level overview of the Electric Agents system and developer APIs.
|
|
6
6
|
outline: [2, 3]
|
|
@@ -31,7 +31,7 @@ The context API passed into the handler:
|
|
|
31
31
|
|
|
32
32
|
| Property/Method | Purpose |
|
|
33
33
|
| ----------------------------------- | --------------------------------------------------------------------- |
|
|
34
|
-
| `ctx.firstWake` | Boolean --
|
|
34
|
+
| `ctx.firstWake` | Boolean -- initial setup pass while no manifest entries exist |
|
|
35
35
|
| `ctx.entityUrl` | Identity -- `/type/id` |
|
|
36
36
|
| `ctx.entityType` | Type name string |
|
|
37
37
|
| `ctx.args` | Readonly spawn arguments |
|
|
@@ -44,13 +44,15 @@ The context API passed into the handler:
|
|
|
44
44
|
| `ctx.timelineMessages()` | Project the entity timeline into LLM messages |
|
|
45
45
|
| `ctx.insertContext(id, entry)` | Insert a durable context entry |
|
|
46
46
|
| `ctx.agent.run()` | Execute the agent loop |
|
|
47
|
-
| `ctx.electricTools`
|
|
47
|
+
| `ctx.electricTools` | Runtime-provided tools to spread into agent config |
|
|
48
48
|
| `ctx.spawn(type, id, args, opts)` | Create child entity |
|
|
49
49
|
| `ctx.observe(source, opts)` | Subscribe to a source via `entity()`, `cron()`, `entities()`, `db()` |
|
|
50
50
|
| `ctx.send(url, payload, opts)` | Send message to an entity |
|
|
51
51
|
| `ctx.sleep()` | Return to idle |
|
|
52
52
|
| `ctx.mkdb(id, schema)` | Create cross-entity shared state |
|
|
53
53
|
| `ctx.observe(db(id, schema), opts)` | Join existing shared state |
|
|
54
|
+
| `ctx.useCodingAgent(sessionId, opts)` | Spawn or attach to a built-in `coder` session |
|
|
55
|
+
| `ctx.recordRun()` | Record non-LLM work as a run for `runFinished` observers |
|
|
54
56
|
| `ctx.setTag(key, value)` | Set a tag on this entity |
|
|
55
57
|
| `ctx.removeTag(key)` | Remove a tag from this entity |
|
|
56
58
|
|
|
@@ -65,7 +67,9 @@ ctx.useAgent({
|
|
|
65
67
|
provider?: KnownProvider, // defaults to 'anthropic' for string models
|
|
66
68
|
tools: AgentTool[], // [...ctx.electricTools, ...custom]
|
|
67
69
|
streamFn?: StreamFn, // optional streaming callback
|
|
68
|
-
|
|
70
|
+
getApiKey?: (provider: string) => string | Promise<string> | undefined,
|
|
71
|
+
onPayload?: SimpleStreamOptions["onPayload"],
|
|
72
|
+
testResponses?: string[] | TestResponseFn // for testing without LLM
|
|
69
73
|
})
|
|
70
74
|
await ctx.agent.run() // blocks until agent finishes
|
|
71
75
|
```
|
|
@@ -78,15 +82,15 @@ See [Configuring the agent](/docs/agents/usage/configuring-the-agent) and [Agent
|
|
|
78
82
|
|
|
79
83
|
```ts
|
|
80
84
|
const myTool: AgentTool = {
|
|
81
|
-
name:
|
|
82
|
-
label:
|
|
83
|
-
description:
|
|
85
|
+
name: "calculator",
|
|
86
|
+
label: "Calculator",
|
|
87
|
+
description: "Evaluate a mathematical expression.",
|
|
84
88
|
parameters: Type.Object({ expression: Type.String() }), // TypeBox
|
|
85
89
|
execute: async (_toolCallId, params) => {
|
|
86
90
|
const { expression } = params as { expression: string }
|
|
87
91
|
const result = evaluate(expression)
|
|
88
92
|
return {
|
|
89
|
-
content: [{ type:
|
|
93
|
+
content: [{ type: "text", text: String(result) }],
|
|
90
94
|
details: {},
|
|
91
95
|
}
|
|
92
96
|
},
|
|
@@ -98,9 +102,9 @@ const myTool: AgentTool = {
|
|
|
98
102
|
```ts
|
|
99
103
|
function createMemoryTool(ctx: HandlerContext): AgentTool {
|
|
100
104
|
return {
|
|
101
|
-
name:
|
|
102
|
-
label:
|
|
103
|
-
description:
|
|
105
|
+
name: "memory_store",
|
|
106
|
+
label: "Memory Store",
|
|
107
|
+
description: "Persist a key-value memory row.",
|
|
104
108
|
parameters: Type.Object({
|
|
105
109
|
key: Type.String(),
|
|
106
110
|
value: Type.String(),
|
|
@@ -108,7 +112,7 @@ function createMemoryTool(ctx: HandlerContext): AgentTool {
|
|
|
108
112
|
execute: async (_, params) => {
|
|
109
113
|
const { key, value } = params as { key: string; value: string }
|
|
110
114
|
ctx.db.actions.memory_insert({ row: { key, value } }) // writes to entity state
|
|
111
|
-
return { content: [{ type:
|
|
115
|
+
return { content: [{ type: "text", text: "Stored." }], details: {} }
|
|
112
116
|
},
|
|
113
117
|
}
|
|
114
118
|
}
|
|
@@ -119,9 +123,9 @@ function createMemoryTool(ctx: HandlerContext): AgentTool {
|
|
|
119
123
|
```ts
|
|
120
124
|
function createDispatchTool(ctx: HandlerContext): AgentTool {
|
|
121
125
|
return {
|
|
122
|
-
name:
|
|
123
|
-
label:
|
|
124
|
-
description:
|
|
126
|
+
name: "dispatch",
|
|
127
|
+
label: "Dispatch",
|
|
128
|
+
description: "Spawn a worker and return its text output.",
|
|
125
129
|
parameters: Type.Object({
|
|
126
130
|
id: Type.String(),
|
|
127
131
|
systemPrompt: Type.String(),
|
|
@@ -134,13 +138,13 @@ function createDispatchTool(ctx: HandlerContext): AgentTool {
|
|
|
134
138
|
task: string
|
|
135
139
|
}
|
|
136
140
|
const child = await ctx.spawn(
|
|
137
|
-
|
|
141
|
+
"worker",
|
|
138
142
|
id,
|
|
139
|
-
{ systemPrompt, tools: [
|
|
140
|
-
{ initialMessage: task, wake:
|
|
143
|
+
{ systemPrompt, tools: ["read"] },
|
|
144
|
+
{ initialMessage: task, wake: "runFinished" }
|
|
141
145
|
)
|
|
142
|
-
const text = (await child.text()).join(
|
|
143
|
-
return { content: [{ type:
|
|
146
|
+
const text = (await child.text()).join("\n\n")
|
|
147
|
+
return { content: [{ type: "text", text }], details: {} }
|
|
144
148
|
},
|
|
145
149
|
}
|
|
146
150
|
}
|
|
@@ -172,6 +176,8 @@ See [Managing state](/docs/agents/usage/managing-state).
|
|
|
172
176
|
- `opts.wake` -- `'runFinished'`, `{ on: 'runFinished', includeResponse? }`, or `{ on: 'change', collections?, debounceMs?, timeoutMs? }`
|
|
173
177
|
- **`observe(source, opts)`** -> `EntityHandle | ObservationHandle` -- subscribe via `entity()`, `cron()`, `entities()`, `db()`
|
|
174
178
|
- **`send(url, payload, opts)`** -- fire-and-forget message
|
|
179
|
+
- **`useCodingAgent(sessionId, opts)`** -> `CodingSessionHandle` -- spawn or attach to a built-in Claude Code/Codex session
|
|
180
|
+
- **`recordRun()`** -> `RunHandle` -- publish run lifecycle for external work
|
|
175
181
|
- **`sleep()`** -- go idle
|
|
176
182
|
|
|
177
183
|
**EntityHandle** returned from spawn/observe:
|
|
@@ -192,15 +198,15 @@ Define a schema map, then create/connect:
|
|
|
192
198
|
const schema = {
|
|
193
199
|
findings: {
|
|
194
200
|
schema: z.object({ key: z.string(), text: z.string() }),
|
|
195
|
-
type:
|
|
196
|
-
primaryKey:
|
|
201
|
+
type: "shared:finding",
|
|
202
|
+
primaryKey: "key",
|
|
197
203
|
},
|
|
198
204
|
}
|
|
199
205
|
// Parent creates:
|
|
200
|
-
ctx.mkdb(
|
|
206
|
+
ctx.mkdb("research-123", schema)
|
|
201
207
|
// Children connect:
|
|
202
|
-
const shared = await ctx.observe(db(
|
|
203
|
-
shared.findings.insert({ key:
|
|
208
|
+
const shared = await ctx.observe(db("research-123", schema))
|
|
209
|
+
shared.findings.insert({ key: "f1", text: "..." })
|
|
204
210
|
```
|
|
205
211
|
|
|
206
212
|
See [Shared state](/docs/agents/usage/shared-state) and [SharedStateHandle reference](/docs/agents/reference/shared-state-handle).
|
|
@@ -235,20 +241,21 @@ See [Built-in collections](/docs/agents/reference/built-in-collections).
|
|
|
235
241
|
|
|
236
242
|
Interact with the system using the Electric Agents CLI:
|
|
237
243
|
|
|
238
|
-
| Command
|
|
239
|
-
|
|
|
240
|
-
| `electric agents types`
|
|
241
|
-
| `electric agents types inspect <name>`
|
|
242
|
-
| `electric agents spawn /type/id --args '{...}'` | Create entity
|
|
243
|
-
| `electric agents send /type/id 'message'`
|
|
244
|
-
| `electric agents observe /type/id`
|
|
245
|
-
| `electric agents inspect /type/id`
|
|
246
|
-
| `electric agents ps [--type --status --parent]` | List entities
|
|
247
|
-
| `electric agents kill /type/id`
|
|
248
|
-
| `electric agents start`
|
|
249
|
-
| `electric agents start-builtin`
|
|
250
|
-
| `electric agents quickstart`
|
|
251
|
-
| `electric agents stop`
|
|
244
|
+
| Command | Purpose |
|
|
245
|
+
| -------------------------------------------- | ---------------------------- |
|
|
246
|
+
| `electric agents types` | List registered entity types |
|
|
247
|
+
| `electric agents types inspect <name>` | Show type schema |
|
|
248
|
+
| `electric agents spawn /type/id --args '{...}'` | Create entity |
|
|
249
|
+
| `electric agents send /type/id 'message'` | Send message |
|
|
250
|
+
| `electric agents observe /type/id` | Stream entity events |
|
|
251
|
+
| `electric agents inspect /type/id` | Show entity state |
|
|
252
|
+
| `electric agents ps [--type --status --parent]` | List entities |
|
|
253
|
+
| `electric agents kill /type/id` | Delete entity |
|
|
254
|
+
| `electric agents start` | Start local dev environment |
|
|
255
|
+
| `electric agents start-builtin` | Start built-in Horton runtime |
|
|
256
|
+
| `electric agents quickstart` | Start local server and built-ins |
|
|
257
|
+
| `electric agents stop` | Stop local dev environment |
|
|
258
|
+
| `electric agents init [project-name]` | Scaffold a starter app |
|
|
252
259
|
|
|
253
260
|
See [CLI reference](/docs/agents/reference/cli).
|
|
254
261
|
|
|
@@ -274,11 +281,11 @@ See [App setup](/docs/agents/usage/app-setup) and [RuntimeHandler reference](/do
|
|
|
274
281
|
|
|
275
282
|
Use the client and embedding APIs when you need to work with agents outside an entity handler:
|
|
276
283
|
|
|
277
|
-
| API
|
|
278
|
-
|
|
|
279
|
-
| `createAgentsClient()`
|
|
280
|
-
| `useChat()`
|
|
281
|
-
| `createRuntimeServerClient()`
|
|
282
|
-
| `BuiltinAgentsServer`
|
|
284
|
+
| API | Use case |
|
|
285
|
+
| --------------------------------- | --------------------------------------------- |
|
|
286
|
+
| `createAgentsClient()` | Observe entity, membership, or shared-state streams from app code |
|
|
287
|
+
| `useChat()` | Render an observed `EntityStreamDB` in React |
|
|
288
|
+
| `createRuntimeServerClient()` | Spawn, message, delete, tag, and schedule entities from services |
|
|
289
|
+
| `BuiltinAgentsServer` | Host Horton, worker, and coder in your own process |
|
|
283
290
|
|
|
284
291
|
See [Clients & React](/docs/agents/usage/clients-and-react), [Programmatic runtime client](/docs/agents/usage/programmatic-runtime-client), and [Embedded built-ins](/docs/agents/usage/embedded-builtins).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Programmatic runtime client
|
|
3
|
-
titleTemplate:
|
|
3
|
+
titleTemplate: "... - Electric Agents"
|
|
4
4
|
description: >-
|
|
5
5
|
Use createRuntimeServerClient to spawn entities, send messages, register wakes,
|
|
6
6
|
manage schedules, and connect shared state from application code.
|
|
@@ -12,10 +12,10 @@ outline: [2, 3]
|
|
|
12
12
|
`createRuntimeServerClient()` is the lower-level HTTP client for the Electric Agents server. Handler code should usually use `ctx.spawn()`, `ctx.send()`, `ctx.observe()`, and `ctx.mkdb()` instead. Use this client from application services, tests, CLIs, and integration code that needs to manage entities from outside a handler.
|
|
13
13
|
|
|
14
14
|
```ts
|
|
15
|
-
import { createRuntimeServerClient } from
|
|
15
|
+
import { createRuntimeServerClient } from "@electric-ax/agents-runtime"
|
|
16
16
|
|
|
17
17
|
const client = createRuntimeServerClient({
|
|
18
|
-
baseUrl:
|
|
18
|
+
baseUrl: "http://localhost:4437",
|
|
19
19
|
})
|
|
20
20
|
```
|
|
21
21
|
|
|
@@ -41,11 +41,11 @@ interface RuntimeServerClientConfig {
|
|
|
41
41
|
|
|
42
42
|
```ts
|
|
43
43
|
const info = await client.spawnEntity({
|
|
44
|
-
type:
|
|
45
|
-
id:
|
|
46
|
-
args: { timezone:
|
|
47
|
-
initialMessage:
|
|
48
|
-
tags: { project:
|
|
44
|
+
type: "horton",
|
|
45
|
+
id: "onboarding",
|
|
46
|
+
args: { timezone: "Europe/London" },
|
|
47
|
+
initialMessage: "Help me get started.",
|
|
48
|
+
tags: { project: "docs" },
|
|
49
49
|
})
|
|
50
50
|
|
|
51
51
|
console.log(info.entityUrl) // "/horton/onboarding"
|
|
@@ -64,11 +64,11 @@ interface SpawnEntityOptions {
|
|
|
64
64
|
wake?: {
|
|
65
65
|
subscriberUrl: string
|
|
66
66
|
condition:
|
|
67
|
-
|
|
|
67
|
+
| "runFinished"
|
|
68
68
|
| {
|
|
69
|
-
on:
|
|
69
|
+
on: "change"
|
|
70
70
|
collections?: string[]
|
|
71
|
-
ops?: Array<
|
|
71
|
+
ops?: Array<"insert" | "update" | "delete">
|
|
72
72
|
}
|
|
73
73
|
debounceMs?: number
|
|
74
74
|
timeoutMs?: number
|
|
@@ -80,14 +80,14 @@ interface SpawnEntityOptions {
|
|
|
80
80
|
### getEntityInfo
|
|
81
81
|
|
|
82
82
|
```ts
|
|
83
|
-
const info = await client.getEntityInfo(
|
|
83
|
+
const info = await client.getEntityInfo("/horton/onboarding")
|
|
84
84
|
// { entityUrl, entityType, streamPath }
|
|
85
85
|
```
|
|
86
86
|
|
|
87
87
|
### deleteEntity
|
|
88
88
|
|
|
89
89
|
```ts
|
|
90
|
-
await client.deleteEntity(
|
|
90
|
+
await client.deleteEntity("/horton/onboarding")
|
|
91
91
|
```
|
|
92
92
|
|
|
93
93
|
Deleting an already-missing entity is treated as success.
|
|
@@ -96,10 +96,10 @@ Deleting an already-missing entity is treated as success.
|
|
|
96
96
|
|
|
97
97
|
```ts
|
|
98
98
|
await client.sendEntityMessage({
|
|
99
|
-
targetUrl:
|
|
100
|
-
payload:
|
|
101
|
-
from:
|
|
102
|
-
type:
|
|
99
|
+
targetUrl: "/horton/onboarding",
|
|
100
|
+
payload: "What changed since last time?",
|
|
101
|
+
from: "support-ui",
|
|
102
|
+
type: "user_message",
|
|
103
103
|
})
|
|
104
104
|
```
|
|
105
105
|
|
|
@@ -118,10 +118,10 @@ interface SendEntityMessageOptions {
|
|
|
118
118
|
## Shared State
|
|
119
119
|
|
|
120
120
|
```ts
|
|
121
|
-
const streamPath = await client.ensureSharedStateStream(
|
|
121
|
+
const streamPath = await client.ensureSharedStateStream("research-123")
|
|
122
122
|
// "/_electric/shared-state/research-123"
|
|
123
123
|
|
|
124
|
-
const samePath = client.getSharedStateStreamPath(
|
|
124
|
+
const samePath = client.getSharedStateStreamPath("research-123")
|
|
125
125
|
```
|
|
126
126
|
|
|
127
127
|
Use `ensureSharedStateStream()` when app code needs to create a shared-state stream before entities connect to it.
|
|
@@ -134,9 +134,9 @@ Use `ensureSharedStateStream()` when app code needs to create a shared-state str
|
|
|
134
134
|
|
|
135
135
|
```ts
|
|
136
136
|
await client.registerWake({
|
|
137
|
-
subscriberUrl:
|
|
138
|
-
sourceUrl:
|
|
139
|
-
condition:
|
|
137
|
+
subscriberUrl: "/coordinator/research",
|
|
138
|
+
sourceUrl: "/worker/analyst/main",
|
|
139
|
+
condition: "runFinished",
|
|
140
140
|
includeResponse: true,
|
|
141
141
|
})
|
|
142
142
|
```
|
|
@@ -145,12 +145,12 @@ For change wakes:
|
|
|
145
145
|
|
|
146
146
|
```ts
|
|
147
147
|
await client.registerWake({
|
|
148
|
-
subscriberUrl:
|
|
149
|
-
sourceUrl:
|
|
148
|
+
subscriberUrl: "/monitor/main",
|
|
149
|
+
sourceUrl: "/horton/onboarding/main",
|
|
150
150
|
condition: {
|
|
151
|
-
on:
|
|
152
|
-
collections: [
|
|
153
|
-
ops: [
|
|
151
|
+
on: "change",
|
|
152
|
+
collections: ["runs", "texts"],
|
|
153
|
+
ops: ["insert", "update"],
|
|
154
154
|
},
|
|
155
155
|
debounceMs: 250,
|
|
156
156
|
})
|
|
@@ -159,13 +159,16 @@ await client.registerWake({
|
|
|
159
159
|
### registerCronSource
|
|
160
160
|
|
|
161
161
|
```ts
|
|
162
|
-
const streamUrl = await client.registerCronSource(
|
|
162
|
+
const streamUrl = await client.registerCronSource(
|
|
163
|
+
"0 9 * * *",
|
|
164
|
+
"Europe/London"
|
|
165
|
+
)
|
|
163
166
|
```
|
|
164
167
|
|
|
165
168
|
### registerEntitiesSource
|
|
166
169
|
|
|
167
170
|
```ts
|
|
168
|
-
const source = await client.registerEntitiesSource({ project:
|
|
171
|
+
const source = await client.registerEntitiesSource({ project: "docs" })
|
|
169
172
|
// { streamUrl, sourceRef }
|
|
170
173
|
```
|
|
171
174
|
|
|
@@ -177,23 +180,23 @@ Schedules are stored on an entity manifest and return the write transaction id.
|
|
|
177
180
|
|
|
178
181
|
```ts
|
|
179
182
|
await client.upsertCronSchedule({
|
|
180
|
-
entityUrl:
|
|
181
|
-
id:
|
|
182
|
-
expression:
|
|
183
|
-
timezone:
|
|
184
|
-
payload:
|
|
183
|
+
entityUrl: "/horton/onboarding",
|
|
184
|
+
id: "daily-checkin",
|
|
185
|
+
expression: "0 9 * * *",
|
|
186
|
+
timezone: "Europe/London",
|
|
187
|
+
payload: "Run the daily check-in.",
|
|
185
188
|
})
|
|
186
189
|
|
|
187
190
|
await client.upsertFutureSendSchedule({
|
|
188
|
-
entityUrl:
|
|
189
|
-
id:
|
|
191
|
+
entityUrl: "/horton/onboarding",
|
|
192
|
+
id: "follow-up",
|
|
190
193
|
fireAt: new Date(Date.now() + 60_000).toISOString(),
|
|
191
|
-
payload:
|
|
194
|
+
payload: "Follow up now.",
|
|
192
195
|
})
|
|
193
196
|
|
|
194
197
|
await client.deleteSchedule({
|
|
195
|
-
entityUrl:
|
|
196
|
-
id:
|
|
198
|
+
entityUrl: "/horton/onboarding",
|
|
199
|
+
id: "follow-up",
|
|
197
200
|
})
|
|
198
201
|
```
|
|
199
202
|
|
|
@@ -202,15 +205,15 @@ await client.deleteSchedule({
|
|
|
202
205
|
`setTag()` and `removeTag()` require the entity write token. Handler code should prefer `ctx.setTag()` and `ctx.removeTag()` because the runtime already has the write token.
|
|
203
206
|
|
|
204
207
|
```ts
|
|
205
|
-
await client.setTag(
|
|
206
|
-
await client.removeTag(
|
|
208
|
+
await client.setTag("/horton/onboarding", "title", "Onboarding", writeToken)
|
|
209
|
+
await client.removeTag("/horton/onboarding", "title", writeToken)
|
|
207
210
|
```
|
|
208
211
|
|
|
209
212
|
## Choosing a Client
|
|
210
213
|
|
|
211
|
-
| API
|
|
212
|
-
|
|
|
213
|
-
| `ctx.spawn/send/observe`
|
|
214
|
-
| `createAgentsClient()`
|
|
215
|
-
| `createRuntimeServerClient()`
|
|
216
|
-
| `electric-ax/entity-stream-db` | You need the CLI-style entity stream loader with `close()`.
|
|
214
|
+
| API | Use when |
|
|
215
|
+
| --------------------------- | ------------------------------------------------------------------------ |
|
|
216
|
+
| `ctx.spawn/send/observe` | You are inside an entity handler. |
|
|
217
|
+
| `createAgentsClient()` | You need to observe streams and drive UI state. |
|
|
218
|
+
| `createRuntimeServerClient()` | You need to manage entities, messages, wakes, schedules, or tags externally. |
|
|
219
|
+
| `electric-ax/entity-stream-db` | You need the CLI-style entity stream loader with `close()`. |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Shared state
|
|
3
|
-
titleTemplate:
|
|
3
|
+
titleTemplate: "... - Electric Agents"
|
|
4
4
|
description: >-
|
|
5
5
|
Coordinate across entities with shared state streams, schema definition, and cross-entity reads and writes.
|
|
6
6
|
outline: [2, 3]
|
|
@@ -18,8 +18,8 @@ Define a `SharedStateSchemaMap` — a record of collection names to their schema
|
|
|
18
18
|
const researchSchema = {
|
|
19
19
|
findings: {
|
|
20
20
|
schema: z.object({ key: z.string(), domain: z.string(), text: z.string() }),
|
|
21
|
-
type:
|
|
22
|
-
primaryKey:
|
|
21
|
+
type: "shared:finding",
|
|
22
|
+
primaryKey: "key",
|
|
23
23
|
},
|
|
24
24
|
}
|
|
25
25
|
```
|
|
@@ -32,9 +32,9 @@ The parent entity creates the shared DB stream, typically on `firstWake`:
|
|
|
32
32
|
|
|
33
33
|
```ts
|
|
34
34
|
if (ctx.firstWake) {
|
|
35
|
-
ctx.mkdb(
|
|
35
|
+
ctx.mkdb("research-123", researchSchema)
|
|
36
36
|
}
|
|
37
|
-
const shared = await ctx.observe(db(
|
|
37
|
+
const shared = await ctx.observe(db("research-123", researchSchema))
|
|
38
38
|
```
|
|
39
39
|
|
|
40
40
|
`mkdb` creates the backing stream. It throws if the DB already exists — creation is always a one-time operation guarded by `firstWake` or your own state checks.
|
|
@@ -44,8 +44,8 @@ const shared = await ctx.observe(db('research-123', researchSchema))
|
|
|
44
44
|
`observe` accepts an optional `wake` option to re-wake the entity when the shared state changes:
|
|
45
45
|
|
|
46
46
|
```ts
|
|
47
|
-
const shared = await ctx.observe(db(
|
|
48
|
-
wake: { on:
|
|
47
|
+
const shared = await ctx.observe(db("research-123", researchSchema), {
|
|
48
|
+
wake: { on: "change", debounceMs: 500 },
|
|
49
49
|
})
|
|
50
50
|
```
|
|
51
51
|
|
|
@@ -55,13 +55,13 @@ Pass the shared DB config to children via spawn args:
|
|
|
55
55
|
|
|
56
56
|
```ts
|
|
57
57
|
const child = await ctx.spawn(
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
"worker",
|
|
59
|
+
"specialist-1",
|
|
60
60
|
{
|
|
61
|
-
systemPrompt:
|
|
62
|
-
sharedDb: { id:
|
|
61
|
+
systemPrompt: "...",
|
|
62
|
+
sharedDb: { id: "research-123", schema: researchSchema },
|
|
63
63
|
},
|
|
64
|
-
{ initialMessage:
|
|
64
|
+
{ initialMessage: "Research topic X", wake: "runFinished" }
|
|
65
65
|
)
|
|
66
66
|
```
|
|
67
67
|
|
|
@@ -82,22 +82,22 @@ async handler(ctx) {
|
|
|
82
82
|
```ts
|
|
83
83
|
// Insert
|
|
84
84
|
shared.findings.insert({
|
|
85
|
-
key:
|
|
86
|
-
domain:
|
|
87
|
-
text:
|
|
85
|
+
key: "f1",
|
|
86
|
+
domain: "physics",
|
|
87
|
+
text: "Finding text...",
|
|
88
88
|
})
|
|
89
89
|
|
|
90
90
|
// Read
|
|
91
|
-
shared.findings.get(
|
|
91
|
+
shared.findings.get("f1")
|
|
92
92
|
shared.findings.toArray
|
|
93
93
|
|
|
94
94
|
// Update
|
|
95
|
-
shared.findings.update(
|
|
96
|
-
draft.text =
|
|
95
|
+
shared.findings.update("f1", (draft) => {
|
|
96
|
+
draft.text = "Updated"
|
|
97
97
|
})
|
|
98
98
|
|
|
99
99
|
// Delete
|
|
100
|
-
shared.findings.delete(
|
|
100
|
+
shared.findings.delete("f1")
|
|
101
101
|
```
|
|
102
102
|
|
|
103
103
|
## SharedStateHandle type
|
|
@@ -119,18 +119,18 @@ const debateSchema = {
|
|
|
119
119
|
arguments: {
|
|
120
120
|
schema: z.object({
|
|
121
121
|
key: z.string(),
|
|
122
|
-
side: z.enum([
|
|
122
|
+
side: z.enum(["pro", "con"]),
|
|
123
123
|
text: z.string(),
|
|
124
124
|
round: z.number(),
|
|
125
125
|
}),
|
|
126
|
-
type:
|
|
127
|
-
primaryKey:
|
|
126
|
+
type: "shared:argument",
|
|
127
|
+
primaryKey: "key",
|
|
128
128
|
},
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
-
registry.define(
|
|
131
|
+
registry.define("debate", {
|
|
132
132
|
state: {
|
|
133
|
-
status: { primaryKey:
|
|
133
|
+
status: { primaryKey: "key" },
|
|
134
134
|
},
|
|
135
135
|
|
|
136
136
|
async handler(ctx) {
|
|
@@ -143,23 +143,23 @@ registry.define('debate', {
|
|
|
143
143
|
|
|
144
144
|
// Spawn pro and con workers with shared state access
|
|
145
145
|
const pro = await ctx.spawn(
|
|
146
|
-
|
|
147
|
-
|
|
146
|
+
"worker",
|
|
147
|
+
"debate-pro",
|
|
148
148
|
{
|
|
149
|
-
systemPrompt:
|
|
149
|
+
systemPrompt: "Argue FOR the topic.",
|
|
150
150
|
sharedDb: { id: `debate-${ctx.entityUrl}`, schema: debateSchema },
|
|
151
151
|
},
|
|
152
|
-
{ initialMessage:
|
|
152
|
+
{ initialMessage: "The topic is: ...", wake: "runFinished" }
|
|
153
153
|
)
|
|
154
154
|
|
|
155
155
|
const con = await ctx.spawn(
|
|
156
|
-
|
|
157
|
-
|
|
156
|
+
"worker",
|
|
157
|
+
"debate-con",
|
|
158
158
|
{
|
|
159
|
-
systemPrompt:
|
|
159
|
+
systemPrompt: "Argue AGAINST the topic.",
|
|
160
160
|
sharedDb: { id: `debate-${ctx.entityUrl}`, schema: debateSchema },
|
|
161
161
|
},
|
|
162
|
-
{ initialMessage:
|
|
162
|
+
{ initialMessage: "The topic is: ...", wake: "runFinished" }
|
|
163
163
|
)
|
|
164
164
|
|
|
165
165
|
// Read all arguments written by both workers
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Spawning & coordinating
|
|
3
|
-
titleTemplate:
|
|
3
|
+
titleTemplate: "... - Electric Agents"
|
|
4
4
|
description: >-
|
|
5
5
|
Spawn child entities, observe existing ones, send messages, and use EntityHandle for coordination.
|
|
6
6
|
outline: [2, 3]
|
|
@@ -63,7 +63,7 @@ Wait for a single child:
|
|
|
63
63
|
|
|
64
64
|
```ts
|
|
65
65
|
await child.run
|
|
66
|
-
const output = (await child.text()).join(
|
|
66
|
+
const output = (await child.text()).join("\n\n")
|
|
67
67
|
```
|
|
68
68
|
|
|
69
69
|
Wait for multiple children in parallel:
|
|
@@ -71,7 +71,7 @@ Wait for multiple children in parallel:
|
|
|
71
71
|
```ts
|
|
72
72
|
const results = await Promise.all(
|
|
73
73
|
children.map(async ({ handle }) => ({
|
|
74
|
-
text: (await handle.text()).join(
|
|
74
|
+
text: (await handle.text()).join("\n\n"),
|
|
75
75
|
}))
|
|
76
76
|
)
|
|
77
77
|
```
|
|
@@ -82,7 +82,7 @@ Subscribe to an existing entity without spawning it:
|
|
|
82
82
|
|
|
83
83
|
```ts
|
|
84
84
|
const handle = await ctx.observe(entity(entityUrl), {
|
|
85
|
-
wake: { on:
|
|
85
|
+
wake: { on: "change", collections: ["runs", "childStatus"] },
|
|
86
86
|
})
|
|
87
87
|
```
|
|
88
88
|
|
|
@@ -93,8 +93,8 @@ Returns an `EntityHandle`. Use `wake` to re-invoke the parent handler when the o
|
|
|
93
93
|
Fire-and-forget message to another entity:
|
|
94
94
|
|
|
95
95
|
```ts
|
|
96
|
-
ctx.send(
|
|
97
|
-
ctx.send(
|
|
96
|
+
ctx.send("/assistant/target-id", { text: "Hello" })
|
|
97
|
+
ctx.send("/assistant/target-id", payload, { type: "custom_type" })
|
|
98
98
|
```
|
|
99
99
|
|
|
100
100
|
Messages appear in the target entity's `inbox` collection.
|
|
@@ -152,12 +152,12 @@ const data = await response.json()
|
|
|
152
152
|
|
|
153
153
|
// Pass data, not credentials, to the worker
|
|
154
154
|
await ctx.spawn(
|
|
155
|
-
|
|
155
|
+
"worker",
|
|
156
156
|
id,
|
|
157
|
-
{ systemPrompt:
|
|
157
|
+
{ systemPrompt: "Summarise this data.", tools: ["read"] },
|
|
158
158
|
{
|
|
159
159
|
initialMessage: JSON.stringify(data),
|
|
160
|
-
wake:
|
|
160
|
+
wake: "runFinished",
|
|
161
161
|
}
|
|
162
162
|
)
|
|
163
163
|
```
|