@electric-ax/agents 0.2.1 → 0.2.2

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 (49) hide show
  1. package/dist/entrypoint.js +5 -3
  2. package/dist/index.cjs +5 -3
  3. package/dist/index.js +5 -3
  4. package/docs/entities/agents/horton.md +89 -0
  5. package/docs/entities/agents/worker.md +102 -0
  6. package/docs/entities/patterns/blackboard.md +111 -0
  7. package/docs/entities/patterns/dispatcher.md +77 -0
  8. package/docs/entities/patterns/manager-worker.md +127 -0
  9. package/docs/entities/patterns/map-reduce.md +81 -0
  10. package/docs/entities/patterns/pipeline.md +101 -0
  11. package/docs/entities/patterns/reactive-observers.md +125 -0
  12. package/docs/examples/mega-draw.md +106 -0
  13. package/docs/examples/playground.md +46 -0
  14. package/docs/index.md +208 -0
  15. package/docs/quickstart.md +201 -0
  16. package/docs/reference/agent-config.md +82 -0
  17. package/docs/reference/agent-tool.md +58 -0
  18. package/docs/reference/built-in-collections.md +334 -0
  19. package/docs/reference/cli.md +238 -0
  20. package/docs/reference/entity-definition.md +57 -0
  21. package/docs/reference/entity-handle.md +63 -0
  22. package/docs/reference/entity-registry.md +73 -0
  23. package/docs/reference/handler-context.md +108 -0
  24. package/docs/reference/runtime-handler.md +136 -0
  25. package/docs/reference/shared-state-handle.md +74 -0
  26. package/docs/reference/state-collection-proxy.md +41 -0
  27. package/docs/reference/wake-event.md +132 -0
  28. package/docs/usage/app-setup.md +165 -0
  29. package/docs/usage/clients-and-react.md +191 -0
  30. package/docs/usage/configuring-the-agent.md +136 -0
  31. package/docs/usage/context-composition.md +204 -0
  32. package/docs/usage/defining-entities.md +181 -0
  33. package/docs/usage/defining-tools.md +229 -0
  34. package/docs/usage/embedded-builtins.md +180 -0
  35. package/docs/usage/managing-state.md +93 -0
  36. package/docs/usage/overview.md +284 -0
  37. package/docs/usage/programmatic-runtime-client.md +216 -0
  38. package/docs/usage/shared-state.md +169 -0
  39. package/docs/usage/spawning-and-coordinating.md +165 -0
  40. package/docs/usage/testing.md +76 -0
  41. package/docs/usage/waking-entities.md +148 -0
  42. package/docs/usage/writing-handlers.md +267 -0
  43. package/package.json +2 -1
  44. package/skills/quickstart/scaffold/package.json +16 -3
  45. package/skills/quickstart/scaffold/tsconfig.json +8 -3
  46. package/skills/quickstart/scaffold/vite.config.ts +21 -0
  47. package/skills/quickstart/scaffold-ui/index.html +12 -0
  48. package/skills/quickstart/scaffold-ui/main.tsx +235 -0
  49. package/skills/quickstart.md +244 -334
@@ -0,0 +1,284 @@
1
+ ---
2
+ title: Overview
3
+ titleTemplate: '... - Electric Agents'
4
+ description: >-
5
+ High level overview of the Electric Agents system and developer APIs.
6
+ outline: [2, 3]
7
+ ---
8
+
9
+ # Usage overview
10
+
11
+ High level overview of the Electric Agents system and developer APIs.
12
+
13
+ ## 1. Entity definition (`registry.define()`)
14
+
15
+ Agents are entities that handle events, defined as a:
16
+
17
+ - `handler(ctx, wake)` with
18
+ - `state` and [built in collections](#_8-built-in-collections)
19
+
20
+ And schemas:
21
+
22
+ - `creationSchema` -- validated spawn args
23
+ - `inboxSchemas` -- typed message contracts
24
+ - `outputSchemas` -- what the entity emits (for UI binding)
25
+
26
+ See [Defining entities](/docs/agents/usage/defining-entities) and [EntityDefinition reference](/docs/agents/reference/entity-definition).
27
+
28
+ ## 2. Handler context (`ctx`)
29
+
30
+ The context API passed into the handler:
31
+
32
+ | Property/Method | Purpose |
33
+ | ----------------------------------- | --------------------------------------------------------------------- |
34
+ | `ctx.firstWake` | Boolean -- is this the entity's first activation? |
35
+ | `ctx.entityUrl` | Identity -- `/type/id` |
36
+ | `ctx.entityType` | Type name string |
37
+ | `ctx.args` | Readonly spawn arguments |
38
+ | `ctx.tags` | Entity tags -- key/value metadata |
39
+ | `ctx.db` | Full TanStack DB: `db.actions` for writes, `db.collections` for reads |
40
+ | `ctx.state` | Proxy object keyed by collection name |
41
+ | `ctx.events` | Change events that triggered this wake |
42
+ | `ctx.useAgent()` | Set up the LLM agent |
43
+ | `ctx.useContext()` | Declare context sources with token budgets and cache tiers |
44
+ | `ctx.timelineMessages()` | Project the entity timeline into LLM messages |
45
+ | `ctx.insertContext(id, entry)` | Insert a durable context entry |
46
+ | `ctx.agent.run()` | Execute the agent loop |
47
+ | `ctx.electricTools` | Runtime-provided tools to spread into agent config |
48
+ | `ctx.spawn(type, id, args, opts)` | Create child entity |
49
+ | `ctx.observe(source, opts)` | Subscribe to a source via `entity()`, `cron()`, `entities()`, `db()` |
50
+ | `ctx.send(url, payload, opts)` | Send message to an entity |
51
+ | `ctx.sleep()` | Return to idle |
52
+ | `ctx.mkdb(id, schema)` | Create cross-entity shared state |
53
+ | `ctx.observe(db(id, schema), opts)` | Join existing shared state |
54
+ | `ctx.setTag(key, value)` | Set a tag on this entity |
55
+ | `ctx.removeTag(key)` | Remove a tag from this entity |
56
+
57
+ See [Writing handlers](/docs/agents/usage/writing-handlers) and [HandlerContext reference](/docs/agents/reference/handler-context).
58
+
59
+ ## 3. Agent configuration
60
+
61
+ ```ts
62
+ ctx.useAgent({
63
+ systemPrompt: string,
64
+ model: string | Model<any>, // e.g. 'claude-sonnet-4-5-20250929'
65
+ provider?: KnownProvider, // defaults to 'anthropic' for string models
66
+ tools: AgentTool[], // [...ctx.electricTools, ...custom]
67
+ streamFn?: StreamFn, // optional streaming callback
68
+ testResponses?: string[] // for testing without LLM
69
+ })
70
+ await ctx.agent.run() // blocks until agent finishes
71
+ ```
72
+
73
+ See [Configuring the agent](/docs/agents/usage/configuring-the-agent) and [AgentConfig reference](/docs/agents/reference/agent-config).
74
+
75
+ ## 4. Tool definition
76
+
77
+ **Stateless tools** are pure functions:
78
+
79
+ ```ts
80
+ const myTool: AgentTool = {
81
+ name: 'calculator',
82
+ label: 'Calculator',
83
+ description: 'Evaluate a mathematical expression.',
84
+ parameters: Type.Object({ expression: Type.String() }), // TypeBox
85
+ execute: async (_toolCallId, params) => {
86
+ const { expression } = params as { expression: string }
87
+ const result = evaluate(expression)
88
+ return {
89
+ content: [{ type: 'text', text: String(result) }],
90
+ details: {},
91
+ }
92
+ },
93
+ }
94
+ ```
95
+
96
+ **Stateful tools** are factories receiving `ctx` for state access:
97
+
98
+ ```ts
99
+ function createMemoryTool(ctx: HandlerContext): AgentTool {
100
+ return {
101
+ name: 'memory_store',
102
+ label: 'Memory Store',
103
+ description: 'Persist a key-value memory row.',
104
+ parameters: Type.Object({
105
+ key: Type.String(),
106
+ value: Type.String(),
107
+ }),
108
+ execute: async (_, params) => {
109
+ const { key, value } = params as { key: string; value: string }
110
+ ctx.db.actions.memory_insert({ row: { key, value } }) // writes to entity state
111
+ return { content: [{ type: 'text', text: 'Stored.' }], details: {} }
112
+ },
113
+ }
114
+ }
115
+ ```
116
+
117
+ **Handler-scoped tools** are factories receiving `ctx`:
118
+
119
+ ```ts
120
+ function createDispatchTool(ctx: HandlerContext): AgentTool {
121
+ return {
122
+ name: 'dispatch',
123
+ label: 'Dispatch',
124
+ description: 'Spawn a worker and return its text output.',
125
+ parameters: Type.Object({
126
+ id: Type.String(),
127
+ systemPrompt: Type.String(),
128
+ task: Type.String(),
129
+ }),
130
+ execute: async (_, params) => {
131
+ const { id, systemPrompt, task } = params as {
132
+ id: string
133
+ systemPrompt: string
134
+ task: string
135
+ }
136
+ const child = await ctx.spawn(
137
+ 'worker',
138
+ id,
139
+ { systemPrompt, tools: ['read'] },
140
+ { initialMessage: task, wake: 'runFinished' }
141
+ )
142
+ const text = (await child.text()).join('\n\n')
143
+ return { content: [{ type: 'text', text }], details: {} }
144
+ },
145
+ }
146
+ }
147
+ ```
148
+
149
+ See [Defining tools](/docs/agents/usage/defining-tools) and [AgentTool reference](/docs/agents/reference/agent-tool).
150
+
151
+ ## 5. State collections (`ctx.db`)
152
+
153
+ Custom state is accessed through `ctx.db`:
154
+
155
+ **Writes** via `ctx.db.actions`:
156
+
157
+ - `.<name>_insert({ row })` -- add new row
158
+ - `.<name>_update({ key, updater: (draft) => { ... } })` -- Immer-style mutation
159
+ - `.<name>_delete({ key })` -- remove by primary key
160
+
161
+ **Reads** via `ctx.db.collections`:
162
+
163
+ - `.<name>?.get(key)` -- read one
164
+ - `.<name>?.toArray` -- read all (getter, not method)
165
+
166
+ See [Managing state](/docs/agents/usage/managing-state).
167
+
168
+ ## 6. Entity coordination primitives
169
+
170
+ - **`spawn(type, id, args, opts)`** -> `EntityHandle` -- create child
171
+ - `opts.initialMessage` -- first message to deliver
172
+ - `opts.wake` -- `'runFinished'`, `{ on: 'runFinished', includeResponse? }`, or `{ on: 'change', collections?, debounceMs?, timeoutMs? }`
173
+ - **`observe(source, opts)`** -> `EntityHandle | ObservationHandle` -- subscribe via `entity()`, `cron()`, `entities()`, `db()`
174
+ - **`send(url, payload, opts)`** -- fire-and-forget message
175
+ - **`sleep()`** -- go idle
176
+
177
+ **EntityHandle** returned from spawn/observe:
178
+
179
+ - `.entityUrl`, `.type`, `.db` (read-only TanStack DB)
180
+ - `.run` -- Promise that resolves when child completes
181
+ - `.text()` -- get all completed text output
182
+ - `.send(msg)` -- send follow-up message
183
+ - `.status()` -- `ChildStatus | undefined` (object with `.status`, `.entity_url`, `.entity_type`)
184
+
185
+ See [Spawning & coordinating](/docs/agents/usage/spawning-and-coordinating) and [EntityHandle reference](/docs/agents/reference/entity-handle).
186
+
187
+ ## 7. Shared state (cross-entity)
188
+
189
+ Define a schema map, then create/connect:
190
+
191
+ ```ts
192
+ const schema = {
193
+ findings: {
194
+ schema: z.object({ key: z.string(), text: z.string() }),
195
+ type: 'shared:finding',
196
+ primaryKey: 'key',
197
+ },
198
+ }
199
+ // Parent creates:
200
+ ctx.mkdb('research-123', schema)
201
+ // Children connect:
202
+ const shared = await ctx.observe(db('research-123', schema))
203
+ shared.findings.insert({ key: 'f1', text: '...' })
204
+ ```
205
+
206
+ See [Shared state](/docs/agents/usage/shared-state) and [SharedStateHandle reference](/docs/agents/reference/shared-state-handle).
207
+
208
+ ## 8. Built-in collections
209
+
210
+ Every entity automatically has 17 `ctx.db.collections`:
211
+
212
+ | Collection | Purpose | Key fields |
213
+ | ------------------ | ------------------------- | ---------------------------------------------------------------------- |
214
+ | `runs` | Agent run lifecycle | `status: started/completed/failed` |
215
+ | `steps` | LLM call steps | `step_number, model_id, duration_ms` |
216
+ | `texts` | Text message blocks | `status: streaming/completed` |
217
+ | `textDeltas` | Incremental text chunks | `text_id, delta` |
218
+ | `toolCalls` | Tool invocation lifecycle | `tool_name, status, args, result` |
219
+ | `reasoning` | Extended thinking blocks | `status: streaming/completed` |
220
+ | `errors` | Diagnostic errors | `error_code, message` |
221
+ | `inbox` | Received messages | `from, payload, message_type` |
222
+ | `wakes` | Wake event history | `source, timeout, changes` |
223
+ | `entityCreated` | Bootstrap metadata | `entity_type, args, parent_url` |
224
+ | `entityStopped` | Shutdown signal | `timestamp, reason` |
225
+ | `childStatus` | Child entity status | `entity_url, status` |
226
+ | `manifests` | Wiring declarations | discriminated union: child/source/shared-state/effect/context/schedule |
227
+ | `replayWatermarks` | Replay offset tracking | `source_id, offset` |
228
+ | `tags` | Entity tags/labels | `key, value` |
229
+ | `contextInserted` | Context additions | `id, name, attrs, content, timestamp` |
230
+ | `contextRemoved` | Context removals | `id, name, timestamp` |
231
+
232
+ See [Built-in collections](/docs/agents/reference/built-in-collections).
233
+
234
+ ## 9. CLI (`electric agents`)
235
+
236
+ Interact with the system using the Electric Agents CLI:
237
+
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 |
252
+
253
+ See [CLI reference](/docs/agents/reference/cli).
254
+
255
+ ## 10. App setup
256
+
257
+ ```ts
258
+ const registry = createEntityRegistry()
259
+ registerMyEntity(registry)
260
+
261
+ const runtime = createRuntimeHandler({
262
+ baseUrl: ELECTRIC_AGENTS_URL, // Electric Agents server
263
+ serveEndpoint: `${URL}/webhook`, // callback URL
264
+ registry,
265
+ })
266
+
267
+ // Node HTTP server, forward POST /webhook -> runtime.onEnter(req, res)
268
+ await runtime.registerTypes() // register all types with runtime server
269
+ ```
270
+
271
+ See [App setup](/docs/agents/usage/app-setup) and [RuntimeHandler reference](/docs/agents/reference/runtime-handler).
272
+
273
+ ## 11. App clients and embedded built-ins
274
+
275
+ Use the client and embedding APIs when you need to work with agents outside an entity handler:
276
+
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 |
283
+
284
+ 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).
@@ -0,0 +1,216 @@
1
+ ---
2
+ title: Programmatic runtime client
3
+ titleTemplate: '... - Electric Agents'
4
+ description: >-
5
+ Use createRuntimeServerClient to spawn entities, send messages, register wakes,
6
+ manage schedules, and connect shared state from application code.
7
+ outline: [2, 3]
8
+ ---
9
+
10
+ # Programmatic runtime client
11
+
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
+
14
+ ```ts
15
+ import { createRuntimeServerClient } from '@electric-ax/agents-runtime'
16
+
17
+ const client = createRuntimeServerClient({
18
+ baseUrl: 'http://localhost:4437',
19
+ })
20
+ ```
21
+
22
+ ## Config
23
+
24
+ ```ts
25
+ interface RuntimeServerClientConfig {
26
+ baseUrl: string
27
+ fetch?: typeof globalThis.fetch
28
+ track?: <T>(promise: Promise<T>) => Promise<T>
29
+ }
30
+ ```
31
+
32
+ | Field | Description |
33
+ | --------- | ------------------------------------------------------------------------- |
34
+ | `baseUrl` | Base URL for the Electric Agents server. |
35
+ | `fetch` | Optional fetch implementation, useful in tests or non-standard runtimes. |
36
+ | `track` | Optional wrapper for all requests, useful for telemetry or pending state. |
37
+
38
+ ## Entity Lifecycle
39
+
40
+ ### spawnEntity
41
+
42
+ ```ts
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' },
49
+ })
50
+
51
+ console.log(info.entityUrl) // "/horton/onboarding"
52
+ ```
53
+
54
+ `spawnEntity()` is idempotent for an existing `/{type}/{id}` URL: if the server reports a conflict and returns entity details, the client returns that entity info.
55
+
56
+ ```ts
57
+ interface SpawnEntityOptions {
58
+ type: string
59
+ id: string
60
+ args?: Record<string, unknown>
61
+ parentUrl?: string
62
+ initialMessage?: unknown
63
+ tags?: Record<string, string>
64
+ wake?: {
65
+ subscriberUrl: string
66
+ condition:
67
+ | 'runFinished'
68
+ | {
69
+ on: 'change'
70
+ collections?: string[]
71
+ ops?: Array<'insert' | 'update' | 'delete'>
72
+ }
73
+ debounceMs?: number
74
+ timeoutMs?: number
75
+ includeResponse?: boolean
76
+ }
77
+ }
78
+ ```
79
+
80
+ ### getEntityInfo
81
+
82
+ ```ts
83
+ const info = await client.getEntityInfo('/horton/onboarding')
84
+ // { entityUrl, entityType, streamPath }
85
+ ```
86
+
87
+ ### deleteEntity
88
+
89
+ ```ts
90
+ await client.deleteEntity('/horton/onboarding')
91
+ ```
92
+
93
+ Deleting an already-missing entity is treated as success.
94
+
95
+ ## Messages
96
+
97
+ ```ts
98
+ await client.sendEntityMessage({
99
+ targetUrl: '/horton/onboarding',
100
+ payload: 'What changed since last time?',
101
+ from: 'support-ui',
102
+ type: 'user_message',
103
+ })
104
+ ```
105
+
106
+ ```ts
107
+ interface SendEntityMessageOptions {
108
+ targetUrl: string
109
+ payload: unknown
110
+ from?: string
111
+ type?: string
112
+ afterMs?: number
113
+ }
114
+ ```
115
+
116
+ `afterMs` asks the server to deliver the message later.
117
+
118
+ ## Shared State
119
+
120
+ ```ts
121
+ const streamPath = await client.ensureSharedStateStream('research-123')
122
+ // "/_electric/shared-state/research-123"
123
+
124
+ const samePath = client.getSharedStateStreamPath('research-123')
125
+ ```
126
+
127
+ Use `ensureSharedStateStream()` when app code needs to create a shared-state stream before entities connect to it.
128
+
129
+ ## Wakes and Sources
130
+
131
+ ### registerWake
132
+
133
+ `registerWake()` creates a wake subscription from one source stream to a subscriber entity.
134
+
135
+ ```ts
136
+ await client.registerWake({
137
+ subscriberUrl: '/coordinator/research',
138
+ sourceUrl: '/worker/analyst/main',
139
+ condition: 'runFinished',
140
+ includeResponse: true,
141
+ })
142
+ ```
143
+
144
+ For change wakes:
145
+
146
+ ```ts
147
+ await client.registerWake({
148
+ subscriberUrl: '/monitor/main',
149
+ sourceUrl: '/horton/onboarding/main',
150
+ condition: {
151
+ on: 'change',
152
+ collections: ['runs', 'texts'],
153
+ ops: ['insert', 'update'],
154
+ },
155
+ debounceMs: 250,
156
+ })
157
+ ```
158
+
159
+ ### registerCronSource
160
+
161
+ ```ts
162
+ const streamUrl = await client.registerCronSource('0 9 * * *', 'Europe/London')
163
+ ```
164
+
165
+ ### registerEntitiesSource
166
+
167
+ ```ts
168
+ const source = await client.registerEntitiesSource({ project: 'docs' })
169
+ // { streamUrl, sourceRef }
170
+ ```
171
+
172
+ This is the lower-level operation behind observing `entities({ tags })`.
173
+
174
+ ## Schedules
175
+
176
+ Schedules are stored on an entity manifest and return the write transaction id.
177
+
178
+ ```ts
179
+ 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.',
185
+ })
186
+
187
+ await client.upsertFutureSendSchedule({
188
+ entityUrl: '/horton/onboarding',
189
+ id: 'follow-up',
190
+ fireAt: new Date(Date.now() + 60_000).toISOString(),
191
+ payload: 'Follow up now.',
192
+ })
193
+
194
+ await client.deleteSchedule({
195
+ entityUrl: '/horton/onboarding',
196
+ id: 'follow-up',
197
+ })
198
+ ```
199
+
200
+ ## Tags
201
+
202
+ `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
+
204
+ ```ts
205
+ await client.setTag('/horton/onboarding', 'title', 'Onboarding', writeToken)
206
+ await client.removeTag('/horton/onboarding', 'title', writeToken)
207
+ ```
208
+
209
+ ## Choosing a Client
210
+
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()`. |
@@ -0,0 +1,169 @@
1
+ ---
2
+ title: Shared state
3
+ titleTemplate: '... - Electric Agents'
4
+ description: >-
5
+ Coordinate across entities with shared state streams, schema definition, and cross-entity reads and writes.
6
+ outline: [2, 3]
7
+ ---
8
+
9
+ # Shared state
10
+
11
+ Shared state allows multiple entities to read and write the same collections. A parent entity creates a shared state stream, and children connect to it.
12
+
13
+ ## Schema definition
14
+
15
+ Define a `SharedStateSchemaMap` — a record of collection names to their schemas:
16
+
17
+ ```ts
18
+ const researchSchema = {
19
+ findings: {
20
+ schema: z.object({ key: z.string(), domain: z.string(), text: z.string() }),
21
+ type: 'shared:finding',
22
+ primaryKey: 'key',
23
+ },
24
+ }
25
+ ```
26
+
27
+ Each entry requires `type` and `primaryKey`. `schema` is optional but recommended for validation. The `type` is the event type string written to the backing durable stream.
28
+
29
+ ## Creating shared state
30
+
31
+ The parent entity creates the shared DB stream, typically on `firstWake`:
32
+
33
+ ```ts
34
+ if (ctx.firstWake) {
35
+ ctx.mkdb('research-123', researchSchema)
36
+ }
37
+ const shared = await ctx.observe(db('research-123', researchSchema))
38
+ ```
39
+
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.
41
+
42
+ `observe(db(id, schema))` returns a handle for reading and writing. Call it on any wake to get a handle to an existing shared DB.
43
+
44
+ `observe` accepts an optional `wake` option to re-wake the entity when the shared state changes:
45
+
46
+ ```ts
47
+ const shared = await ctx.observe(db('research-123', researchSchema), {
48
+ wake: { on: 'change', debounceMs: 500 },
49
+ })
50
+ ```
51
+
52
+ ## Connecting from children
53
+
54
+ Pass the shared DB config to children via spawn args:
55
+
56
+ ```ts
57
+ const child = await ctx.spawn(
58
+ 'worker',
59
+ 'specialist-1',
60
+ {
61
+ systemPrompt: '...',
62
+ sharedDb: { id: 'research-123', schema: researchSchema },
63
+ },
64
+ { initialMessage: 'Research topic X', wake: 'runFinished' }
65
+ )
66
+ ```
67
+
68
+ The child entity connects using the args it receives:
69
+
70
+ ```ts
71
+ async handler(ctx) {
72
+ const args = ctx.args as { sharedDb: { id: string; schema: SharedStateSchemaMap } }
73
+ const shared = await ctx.observe(db(args.sharedDb.id, args.sharedDb.schema))
74
+ // Use shared.findings to read and write
75
+ }
76
+ ```
77
+
78
+ ## Using the handle
79
+
80
+ `SharedStateHandle` exposes collection proxies via `StateCollectionProxy` (the same insert/update/delete/get/toArray API):
81
+
82
+ ```ts
83
+ // Insert
84
+ shared.findings.insert({
85
+ key: 'f1',
86
+ domain: 'physics',
87
+ text: 'Finding text...',
88
+ })
89
+
90
+ // Read
91
+ shared.findings.get('f1')
92
+ shared.findings.toArray
93
+
94
+ // Update
95
+ shared.findings.update('f1', (draft) => {
96
+ draft.text = 'Updated'
97
+ })
98
+
99
+ // Delete
100
+ shared.findings.delete('f1')
101
+ ```
102
+
103
+ ## SharedStateHandle type
104
+
105
+ ```ts
106
+ type SharedStateHandle<TSchema extends SharedStateSchemaMap> = {
107
+ id: string
108
+ } & { [K in keyof TSchema]: StateCollectionProxy }
109
+ ```
110
+
111
+ The `id` property holds the stream identifier. Each key from the schema map becomes a `StateCollectionProxy`.
112
+
113
+ ## Example: debate pattern
114
+
115
+ The debate pattern uses shared state for pro/con arguments. A moderator creates the stream, spawns workers for each side, and reads all arguments to make a ruling.
116
+
117
+ ```ts
118
+ const debateSchema = {
119
+ arguments: {
120
+ schema: z.object({
121
+ key: z.string(),
122
+ side: z.enum(['pro', 'con']),
123
+ text: z.string(),
124
+ round: z.number(),
125
+ }),
126
+ type: 'shared:argument',
127
+ primaryKey: 'key',
128
+ },
129
+ }
130
+
131
+ registry.define('debate', {
132
+ state: {
133
+ status: { primaryKey: 'key' },
134
+ },
135
+
136
+ async handler(ctx) {
137
+ if (ctx.firstWake) {
138
+ ctx.mkdb(`debate-${ctx.entityUrl}`, debateSchema)
139
+ }
140
+ const shared = await ctx.observe(
141
+ db(`debate-${ctx.entityUrl}`, debateSchema)
142
+ )
143
+
144
+ // Spawn pro and con workers with shared state access
145
+ const pro = await ctx.spawn(
146
+ 'worker',
147
+ 'debate-pro',
148
+ {
149
+ systemPrompt: 'Argue FOR the topic.',
150
+ sharedDb: { id: `debate-${ctx.entityUrl}`, schema: debateSchema },
151
+ },
152
+ { initialMessage: 'The topic is: ...', wake: 'runFinished' }
153
+ )
154
+
155
+ const con = await ctx.spawn(
156
+ 'worker',
157
+ 'debate-con',
158
+ {
159
+ systemPrompt: 'Argue AGAINST the topic.',
160
+ sharedDb: { id: `debate-${ctx.entityUrl}`, schema: debateSchema },
161
+ },
162
+ { initialMessage: 'The topic is: ...', wake: 'runFinished' }
163
+ )
164
+
165
+ // Read all arguments written by both workers
166
+ const allArgs = shared.arguments.toArray
167
+ },
168
+ })
169
+ ```