@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.
Files changed (45) hide show
  1. package/dist/entrypoint.js +40 -12
  2. package/dist/index.cjs +40 -12
  3. package/dist/index.js +40 -12
  4. package/docs/entities/agents/coder.md +99 -0
  5. package/docs/entities/agents/horton.md +16 -13
  6. package/docs/entities/agents/worker.md +18 -18
  7. package/docs/entities/patterns/blackboard.md +6 -6
  8. package/docs/entities/patterns/dispatcher.md +1 -1
  9. package/docs/entities/patterns/manager-worker.md +1 -1
  10. package/docs/entities/patterns/map-reduce.md +1 -1
  11. package/docs/entities/patterns/pipeline.md +1 -1
  12. package/docs/entities/patterns/reactive-observers.md +2 -2
  13. package/docs/examples/playground.md +42 -26
  14. package/docs/index.md +23 -23
  15. package/docs/quickstart.md +13 -13
  16. package/docs/reference/agent-config.md +20 -12
  17. package/docs/reference/agent-tool.md +1 -1
  18. package/docs/reference/built-in-collections.md +21 -21
  19. package/docs/reference/cli.md +39 -30
  20. package/docs/reference/entity-definition.md +9 -9
  21. package/docs/reference/entity-handle.md +2 -2
  22. package/docs/reference/entity-registry.md +1 -1
  23. package/docs/reference/handler-context.md +69 -18
  24. package/docs/reference/runtime-handler.md +25 -23
  25. package/docs/reference/shared-state-handle.md +7 -7
  26. package/docs/reference/state-collection-proxy.md +1 -1
  27. package/docs/reference/wake-event.md +23 -23
  28. package/docs/usage/app-setup.md +24 -23
  29. package/docs/usage/clients-and-react.md +44 -36
  30. package/docs/usage/configuring-the-agent.md +25 -19
  31. package/docs/usage/context-composition.md +12 -12
  32. package/docs/usage/defining-entities.md +36 -36
  33. package/docs/usage/defining-tools.md +45 -45
  34. package/docs/usage/embedded-builtins.md +48 -47
  35. package/docs/usage/managing-state.md +12 -12
  36. package/docs/usage/overview.md +52 -45
  37. package/docs/usage/programmatic-runtime-client.md +50 -47
  38. package/docs/usage/shared-state.md +32 -32
  39. package/docs/usage/spawning-and-coordinating.md +9 -9
  40. package/docs/usage/testing.md +14 -14
  41. package/docs/usage/waking-entities.md +13 -13
  42. package/docs/usage/writing-handlers.md +57 -26
  43. package/package.json +4 -1
  44. package/scripts/sync-docs.mjs +42 -0
  45. package/docs/examples/mega-draw.md +0 -106
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: Overview
3
- titleTemplate: '... - Electric Agents'
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 -- is this the entity's first activation? |
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` | Runtime-provided tools to spread into agent config |
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
- testResponses?: string[] // for testing without LLM
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: 'calculator',
82
- label: 'Calculator',
83
- description: 'Evaluate a mathematical expression.',
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: 'text', text: String(result) }],
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: 'memory_store',
102
- label: 'Memory Store',
103
- description: 'Persist a key-value memory row.',
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: 'text', text: 'Stored.' }], details: {} }
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: 'dispatch',
123
- label: 'Dispatch',
124
- description: 'Spawn a worker and return its text output.',
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
- 'worker',
141
+ "worker",
138
142
  id,
139
- { systemPrompt, tools: ['read'] },
140
- { initialMessage: task, wake: 'runFinished' }
143
+ { systemPrompt, tools: ["read"] },
144
+ { initialMessage: task, wake: "runFinished" }
141
145
  )
142
- const text = (await child.text()).join('\n\n')
143
- return { content: [{ type: 'text', text }], details: {} }
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: 'shared:finding',
196
- primaryKey: 'key',
201
+ type: "shared:finding",
202
+ primaryKey: "key",
197
203
  },
198
204
  }
199
205
  // Parent creates:
200
- ctx.mkdb('research-123', schema)
206
+ ctx.mkdb("research-123", schema)
201
207
  // Children connect:
202
- const shared = await ctx.observe(db('research-123', schema))
203
- shared.findings.insert({ key: 'f1', text: '...' })
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 | Purpose |
239
- | ----------------------------------------------- | -------------------------------- |
240
- | `electric agents types` | List registered entity types |
241
- | `electric agents types inspect <name>` | Show type schema |
242
- | `electric agents spawn /type/id --args '{...}'` | Create entity |
243
- | `electric agents send /type/id 'message'` | Send message |
244
- | `electric agents observe /type/id` | Stream entity events |
245
- | `electric agents inspect /type/id` | Show entity state |
246
- | `electric agents ps [--type --status --parent]` | List entities |
247
- | `electric agents kill /type/id` | Delete entity |
248
- | `electric agents start` | Start local dev environment |
249
- | `electric agents start-builtin` | Start built-in Horton runtime |
250
- | `electric agents quickstart` | Start local server and built-ins |
251
- | `electric agents stop` | Stop local dev environment |
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 | Use case |
278
- | ----------------------------- | ----------------------------------------------------------------- |
279
- | `createAgentsClient()` | Observe entity, membership, or shared-state streams from app code |
280
- | `useChat()` | Render an observed `EntityStreamDB` in React |
281
- | `createRuntimeServerClient()` | Spawn, message, delete, tag, and schedule entities from services |
282
- | `BuiltinAgentsServer` | Host Horton and worker in your own process |
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: '... - Electric Agents'
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 '@electric-ax/agents-runtime'
15
+ import { createRuntimeServerClient } from "@electric-ax/agents-runtime"
16
16
 
17
17
  const client = createRuntimeServerClient({
18
- baseUrl: 'http://localhost:4437',
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: 'horton',
45
- id: 'onboarding',
46
- args: { timezone: 'Europe/London' },
47
- initialMessage: 'Help me get started.',
48
- tags: { project: 'docs' },
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
- | 'runFinished'
67
+ | "runFinished"
68
68
  | {
69
- on: 'change'
69
+ on: "change"
70
70
  collections?: string[]
71
- ops?: Array<'insert' | 'update' | 'delete'>
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('/horton/onboarding')
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('/horton/onboarding')
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: '/horton/onboarding',
100
- payload: 'What changed since last time?',
101
- from: 'support-ui',
102
- type: 'user_message',
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('research-123')
121
+ const streamPath = await client.ensureSharedStateStream("research-123")
122
122
  // "/_electric/shared-state/research-123"
123
123
 
124
- const samePath = client.getSharedStateStreamPath('research-123')
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: '/coordinator/research',
138
- sourceUrl: '/worker/analyst/main',
139
- condition: 'runFinished',
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: '/monitor/main',
149
- sourceUrl: '/horton/onboarding/main',
148
+ subscriberUrl: "/monitor/main",
149
+ sourceUrl: "/horton/onboarding/main",
150
150
  condition: {
151
- on: 'change',
152
- collections: ['runs', 'texts'],
153
- ops: ['insert', 'update'],
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('0 9 * * *', 'Europe/London')
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: 'docs' })
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: '/horton/onboarding',
181
- id: 'daily-checkin',
182
- expression: '0 9 * * *',
183
- timezone: 'Europe/London',
184
- payload: 'Run the daily check-in.',
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: '/horton/onboarding',
189
- id: 'follow-up',
191
+ entityUrl: "/horton/onboarding",
192
+ id: "follow-up",
190
193
  fireAt: new Date(Date.now() + 60_000).toISOString(),
191
- payload: 'Follow up now.',
194
+ payload: "Follow up now.",
192
195
  })
193
196
 
194
197
  await client.deleteSchedule({
195
- entityUrl: '/horton/onboarding',
196
- id: 'follow-up',
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('/horton/onboarding', 'title', 'Onboarding', writeToken)
206
- await client.removeTag('/horton/onboarding', 'title', writeToken)
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 | Use when |
212
- | ------------------------------ | ---------------------------------------------------------------------------- |
213
- | `ctx.spawn/send/observe` | You are inside an entity handler. |
214
- | `createAgentsClient()` | You need to observe streams and drive UI state. |
215
- | `createRuntimeServerClient()` | You need to manage entities, messages, wakes, schedules, or tags externally. |
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: '... - Electric Agents'
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: 'shared:finding',
22
- primaryKey: 'key',
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('research-123', researchSchema)
35
+ ctx.mkdb("research-123", researchSchema)
36
36
  }
37
- const shared = await ctx.observe(db('research-123', researchSchema))
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('research-123', researchSchema), {
48
- wake: { on: 'change', debounceMs: 500 },
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
- 'worker',
59
- 'specialist-1',
58
+ "worker",
59
+ "specialist-1",
60
60
  {
61
- systemPrompt: '...',
62
- sharedDb: { id: 'research-123', schema: researchSchema },
61
+ systemPrompt: "...",
62
+ sharedDb: { id: "research-123", schema: researchSchema },
63
63
  },
64
- { initialMessage: 'Research topic X', wake: 'runFinished' }
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: 'f1',
86
- domain: 'physics',
87
- text: 'Finding text...',
85
+ key: "f1",
86
+ domain: "physics",
87
+ text: "Finding text...",
88
88
  })
89
89
 
90
90
  // Read
91
- shared.findings.get('f1')
91
+ shared.findings.get("f1")
92
92
  shared.findings.toArray
93
93
 
94
94
  // Update
95
- shared.findings.update('f1', (draft) => {
96
- draft.text = 'Updated'
95
+ shared.findings.update("f1", (draft) => {
96
+ draft.text = "Updated"
97
97
  })
98
98
 
99
99
  // Delete
100
- shared.findings.delete('f1')
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(['pro', 'con']),
122
+ side: z.enum(["pro", "con"]),
123
123
  text: z.string(),
124
124
  round: z.number(),
125
125
  }),
126
- type: 'shared:argument',
127
- primaryKey: 'key',
126
+ type: "shared:argument",
127
+ primaryKey: "key",
128
128
  },
129
129
  }
130
130
 
131
- registry.define('debate', {
131
+ registry.define("debate", {
132
132
  state: {
133
- status: { primaryKey: 'key' },
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
- 'worker',
147
- 'debate-pro',
146
+ "worker",
147
+ "debate-pro",
148
148
  {
149
- systemPrompt: 'Argue FOR the topic.',
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: "runFinished" }
153
153
  )
154
154
 
155
155
  const con = await ctx.spawn(
156
- 'worker',
157
- 'debate-con',
156
+ "worker",
157
+ "debate-con",
158
158
  {
159
- systemPrompt: 'Argue AGAINST the topic.',
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: "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: '... - Electric Agents'
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('\n\n')
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('\n\n'),
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: 'change', collections: ['runs', 'childStatus'] },
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('/assistant/target-id', { text: 'Hello' })
97
- ctx.send('/assistant/target-id', payload, { type: 'custom_type' })
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
- 'worker',
155
+ "worker",
156
156
  id,
157
- { systemPrompt: 'Summarise this data.', tools: ['read'] },
157
+ { systemPrompt: "Summarise this data.", tools: ["read"] },
158
158
  {
159
159
  initialMessage: JSON.stringify(data),
160
- wake: 'runFinished',
160
+ wake: "runFinished",
161
161
  }
162
162
  )
163
163
  ```