@electric-ax/agents 0.4.18 → 0.6.0

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 +88 -14
  2. package/dist/index.cjs +87 -13
  3. package/dist/index.d.cts +1 -1
  4. package/dist/index.d.ts +1 -1
  5. package/dist/index.js +88 -14
  6. package/docs/entities/agents/horton.md +22 -17
  7. package/docs/entities/agents/worker.md +13 -6
  8. package/docs/entities/patterns/blackboard.md +1 -1
  9. package/docs/entities/patterns/dispatcher.md +1 -1
  10. package/docs/entities/patterns/manager-worker.md +10 -5
  11. package/docs/entities/patterns/map-reduce.md +1 -1
  12. package/docs/entities/patterns/pipeline.md +1 -1
  13. package/docs/entities/patterns/reactive-observers.md +1 -1
  14. package/docs/index.md +6 -4
  15. package/docs/quickstart.md +2 -2
  16. package/docs/reference/agent-config.md +13 -3
  17. package/docs/reference/built-in-collections.md +128 -9
  18. package/docs/reference/cli.md +34 -4
  19. package/docs/reference/entity-definition.md +39 -7
  20. package/docs/reference/entity-handle.md +19 -1
  21. package/docs/reference/handler-context.md +130 -5
  22. package/docs/reference/runtime-handler.md +42 -14
  23. package/docs/reference/wake-event.md +29 -1
  24. package/docs/usage/app-setup.md +38 -7
  25. package/docs/usage/attachments.md +129 -0
  26. package/docs/usage/clients-and-react.md +23 -2
  27. package/docs/usage/configuring-the-agent.md +15 -5
  28. package/docs/usage/context-composition.md +2 -1
  29. package/docs/usage/defining-entities.md +9 -5
  30. package/docs/usage/defining-tools.md +1 -1
  31. package/docs/usage/embedded-builtins.md +82 -31
  32. package/docs/usage/managing-state.md +5 -0
  33. package/docs/usage/mcp-servers.md +16 -8
  34. package/docs/usage/overview.md +39 -14
  35. package/docs/usage/permissions-and-principals.md +160 -0
  36. package/docs/usage/programmatic-runtime-client.md +158 -16
  37. package/docs/usage/sandboxing.md +162 -0
  38. package/docs/usage/signals.md +138 -0
  39. package/docs/usage/spawning-and-coordinating.md +30 -11
  40. package/docs/usage/testing.md +1 -1
  41. package/docs/usage/waking-entities.md +34 -6
  42. package/docs/usage/webhook-sources.md +171 -0
  43. package/docs/usage/writing-handlers.md +13 -55
  44. package/docs/walkthrough.md +13 -5
  45. package/package.json +3 -3
@@ -21,7 +21,9 @@ interface EntityDefinition {
21
21
  ) => Record<string, (...args: unknown[]) => void>
22
22
  creationSchema?: StandardJSONSchemaV1
23
23
  inboxSchemas?: Record<string, StandardJSONSchemaV1>
24
- outputSchemas?: Record<string, StandardJSONSchemaV1>
24
+ stateSchemas?: Record<string, StandardJSONSchemaV1>
25
+ permissionGrants?: EntityTypePermissionGrantDefinition[]
26
+ slashCommands?: SlashCommandDefinition[]
25
27
  handler(ctx: HandlerContext, wake: WakeEvent): void | Promise<void>
26
28
  }
27
29
  ```
@@ -35,7 +37,9 @@ interface EntityDefinition {
35
37
  | `actions` | `(collections) => Record<string, (...args) => void>` | No | Factory for custom non-CRUD actions. Receives TanStack DB collections, returns named action functions exposed on `ctx.actions`. |
36
38
  | `creationSchema` | `StandardJSONSchemaV1` | No | JSON Schema for spawn arguments validation. |
37
39
  | `inboxSchemas` | `Record<string, StandardJSONSchemaV1>` | No | JSON Schemas for inbound message types, keyed by message type. |
38
- | `outputSchemas` | `Record<string, StandardJSONSchemaV1>` | No | JSON Schemas for output event types. Defaults are provided by the runtime. |
40
+ | `stateSchemas` | `Record<string, StandardJSONSchemaV1>` | No | Additional JSON Schemas included in the registered entity type's state schema map. |
41
+ | `permissionGrants` | `EntityTypePermissionGrantDefinition[]` | No | Initial permission grants applied when this entity type is registered. |
42
+ | `slashCommands` | `SlashCommandDefinition[]` | No | Static slash commands exposed to structured composers and available through `ctx.slashCommands`. |
39
43
  | `handler` | `(ctx, wake) => void \| Promise<void>` | Yes | The function invoked on each wake. Receives [`HandlerContext`](./handler-context) and [`WakeEvent`](./wake-event). |
40
44
 
41
45
  ## CollectionDefinition
@@ -47,11 +51,39 @@ interface CollectionDefinition {
47
51
  schema?: StandardSchemaV1
48
52
  type?: string
49
53
  primaryKey?: string
54
+ externallyWritable?: boolean
55
+ contract?: string
56
+ operations?: Array<"insert" | "update" | "delete">
50
57
  }
51
58
  ```
52
59
 
53
- | Field | Type | Default | Description |
54
- | ------------ | ------------------ | ---------------- | -------------------------------------------------- |
55
- | `schema` | `StandardSchemaV1` | - | Zod or Standard Schema validator for the row type. |
56
- | `type` | `string` | `"state:{name}"` | Event type string used in the durable stream. |
57
- | `primaryKey` | `string` | `"key"` | Primary key field name on the row. |
60
+ | Field | Type | Default | Description |
61
+ | -------------------- | ----------------------------------------- | ---------------- | -------------------------------------------------- |
62
+ | `schema` | `StandardSchemaV1` | - | Zod or Standard Schema validator for the row type. |
63
+ | `type` | `string` | `"state:{name}"` | Event type string used in the durable stream. |
64
+ | `primaryKey` | `string` | `"key"` | Primary key field name on the row. |
65
+ | `externallyWritable` | `boolean` | `false` | Opt in to HTTP writes for this collection. |
66
+ | `contract` | `string` | - | Well-known contract implemented by the collection. |
67
+ | `operations` | `Array<"insert" \| "update" \| "delete">` | `["insert"]` for external writes | Allowlist of external write operations when `externallyWritable` is enabled. |
68
+
69
+ ## Permission grants
70
+
71
+ `permissionGrants` lets an entity type declare the initial access grants that the server stores for entities of that type.
72
+
73
+ ```ts
74
+ registry.define("worker", {
75
+ description: "Internal worker agent",
76
+ permissionGrants: [
77
+ {
78
+ subject_kind: "principal_kind",
79
+ subject_value: "user",
80
+ permission: "spawn",
81
+ },
82
+ ],
83
+ async handler(ctx, wake) {
84
+ // ...
85
+ },
86
+ })
87
+ ```
88
+
89
+ The server currently recognizes `read`, `write`, `delete`, `signal`, `fork`, `schedule`, `spawn`, and `manage` permissions. Grants can target a specific principal or a principal kind, and may include propagation options depending on the server route that creates them.
@@ -14,6 +14,9 @@ Handle returned by `ctx.spawn()` and `ctx.observe(entity(...))`. It identifies a
14
14
 
15
15
  ```ts
16
16
  interface EntityHandle {
17
+ sourceType: string
18
+ sourceRef: string
19
+ streamUrl?: string
17
20
  entityUrl: string
18
21
  type?: string
19
22
  db: EntityStreamDB
@@ -27,6 +30,9 @@ interface EntityHandle {
27
30
 
28
31
  | Member | Type | Description |
29
32
  | ----------- | -------------------------- | --------------------------------------------------------------- |
33
+ | `sourceType` | `string` | Observation source type inherited from `ObservationHandle`. |
34
+ | `sourceRef` | `string` | Stable source reference inherited from `ObservationHandle`. |
35
+ | `streamUrl` | `string \| undefined` | Source stream URL when available. |
30
36
  | `entityUrl` | `string` | URL path of the entity, e.g. `"/worker/child-1"`. |
31
37
  | `type` | `string \| undefined` | Entity type name, if known. |
32
38
  | `db` | `EntityStreamDB` | The entity's TanStack DB instance for querying its collections. |
@@ -50,7 +56,7 @@ const child = await ctx.spawn(
50
56
  )
51
57
 
52
58
  ctx.state.children.insert({
53
- key: "analyst-1",
59
+ key: child.entityUrl,
54
60
  url: child.entityUrl,
55
61
  status: "running",
56
62
  })
@@ -87,3 +93,15 @@ interface ChildStatusEntry {
87
93
  status: "spawning" | "running" | "idle" | "paused" | "stopping" | "stopped" | "killed"
88
94
  }
89
95
  ```
96
+
97
+ Status values:
98
+
99
+ | Status | Description |
100
+ | ---------- | ----------------------------------------------------------- |
101
+ | `spawning` | Entity creation is in progress. |
102
+ | `running` | Handler is currently executing. |
103
+ | `idle` | Handler has completed; entity is waiting for the next wake. |
104
+ | `paused` | Entity is paused. |
105
+ | `stopping` | Entity is stopping and rejects normal writes. |
106
+ | `stopped` | Entity has been stopped or deleted. |
107
+ | `killed` | Entity was killed by a terminal lifecycle signal. |
@@ -15,7 +15,10 @@ The handler context is passed as the first argument to every entity handler. It
15
15
  ```ts
16
16
  interface HandlerContext<TState extends StateProxy = StateProxy> {
17
17
  firstWake: boolean
18
+ wake: HandlerWake
19
+ slashCommands: SlashCommandHelpers
18
20
  tags: Readonly<EntityTags>
21
+ principal?: RuntimePrincipal
19
22
  entityUrl: string
20
23
  entityType: string
21
24
  args: Readonly<Record<string, unknown>>
@@ -24,6 +27,8 @@ interface HandlerContext<TState extends StateProxy = StateProxy> {
24
27
  events: Array<ChangeEvent>
25
28
  actions: Record<string, (...args: unknown[]) => unknown>
26
29
  electricTools: AgentTool[]
30
+ signal: AbortSignal
31
+ sandbox: Sandbox
27
32
  useAgent(config: AgentConfig): AgentHandle
28
33
  useContext(config: UseContextConfig): void
29
34
  timelineMessages(opts?: TimelineProjectionOpts): Array<TimestampedMessage>
@@ -31,6 +36,14 @@ interface HandlerContext<TState extends StateProxy = StateProxy> {
31
36
  removeContext(id: string): void
32
37
  getContext(id: string): ContextEntry | undefined
33
38
  listContext(): Array<ContextEntry>
39
+ setGoal(input: GoalInput): GoalEntry
40
+ clearGoal(): boolean
41
+ getGoal(): GoalEntry | undefined
42
+ markGoalComplete(summary?: string): GoalEntry | undefined
43
+ updateGoalUsage(
44
+ tokensUsed: number,
45
+ opts?: { status?: GoalEntry["status"] }
46
+ ): GoalEntry | undefined
34
47
  agent: AgentHandle
35
48
  spawn(
36
49
  type: string,
@@ -38,11 +51,19 @@ interface HandlerContext<TState extends StateProxy = StateProxy> {
38
51
  args?: Record<string, unknown>,
39
52
  opts?: {
40
53
  initialMessage?: unknown
54
+ initialMessageType?: string
41
55
  wake?: Wake
42
56
  tags?: Record<string, string>
43
57
  observe?: boolean
58
+ sandbox?: SpawnSandboxOption
44
59
  }
45
60
  ): Promise<EntityHandle>
61
+ fork(
62
+ sourceEntityUrl: string,
63
+ id: string,
64
+ opts?: ForkOptions
65
+ ): Promise<EntityHandle>
66
+ forkSelf(id: string, opts?: ForkOptions): Promise<EntityHandle>
46
67
  observe(
47
68
  source: ObservationSource & { sourceType: "entity" },
48
69
  opts?: { wake?: Wake }
@@ -55,6 +76,7 @@ interface HandlerContext<TState extends StateProxy = StateProxy> {
55
76
  source: ObservationSource,
56
77
  opts?: { wake?: Wake }
57
78
  ): Promise<ObservationHandle>
79
+ unobserve(sourceRef: string): Promise<void>
58
80
  mkdb<T extends SharedStateSchemaMap>(
59
81
  id: string,
60
82
  schema: T
@@ -63,22 +85,34 @@ interface HandlerContext<TState extends StateProxy = StateProxy> {
63
85
  entityUrl: string,
64
86
  payload: unknown,
65
87
  opts?: { type?: string; afterMs?: number }
88
+ ): Promise<SendResult>
89
+ attachments: AttachmentsApi
90
+ onSignal(
91
+ handler: (signal: {
92
+ signal: EntitySignal
93
+ reason?: string
94
+ payload?: unknown
95
+ }) => void | Promise<void>
66
96
  ): void
67
97
  recordRun(): RunHandle
98
+ replyText(text: string): void
68
99
  setTag(key: string, value: string): Promise<void>
69
- removeTag(key: string): Promise<void>
100
+ deleteTag(key: string): Promise<void>
70
101
  sleep(): void
71
102
  }
72
103
  ```
73
104
 
74
- > **Tip:** Use the helper functions `entity()`, `cron()`, `entities()`, and `db()` from `@electric-ax/agents-runtime` to construct `ObservationSource` values for `observe()`.
105
+ > **Tip:** Use the helper functions `entity()`, `cron()`, `entities()`, `db()`, `webhook()`, and `pgSync()` from `@electric-ax/agents-runtime` to construct `ObservationSource` values for `observe()`.
75
106
 
76
107
  ## Properties
77
108
 
78
109
  | Property | Type | Description |
79
110
  | ------------ | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
80
111
  | `firstWake` | `boolean` | `true` during the initial setup pass while the entity has no persisted manifest entries. Use state checks for one-time plain state initialization. |
112
+ | `wake` | `HandlerWake` | Current wake projected into the handler context. Equivalent to the second handler argument. |
113
+ | `slashCommands` | `SlashCommandHelpers` | Read and manage slash-command definitions exposed to structured composer inputs. |
81
114
  | `tags` | `Readonly<EntityTags>` | Entity tags — key/value metadata associated with this entity. |
115
+ | `principal` | `RuntimePrincipal \| undefined` | Principal that caused the current wake, when the server supplied one. |
82
116
  | `entityUrl` | `string` | URL path of this entity (e.g. `"/chat/my-convo"`). |
83
117
  | `entityType` | `string` | Registered type name (e.g. `"chat"`). |
84
118
  | `args` | `Readonly<Record<string, unknown>>` | Spawn arguments passed when the entity was created. |
@@ -87,6 +121,30 @@ interface HandlerContext<TState extends StateProxy = StateProxy> {
87
121
  | `events` | `Array<ChangeEvent>` | Change events that triggered this wake. |
88
122
  | `actions` | `Record<string, (...args: unknown[]) => unknown>` | Custom non-CRUD actions from the entity definition's `actions` factory. Auto-generated CRUD actions live on `ctx.db.actions` and `ctx.state`. |
89
123
  | `electricTools` | `AgentTool[]` | Host-provided runtime-level tools to spread into agent config when needed. May be empty. |
124
+ | `signal` | `AbortSignal` | Aborts when the current wake should stop early, such as during shutdown or `SIGINT`. Pass it to cancellable work. |
125
+ | `sandbox` | `Sandbox` | Active sandbox for this wake session. Runtime-provided tools use this for filesystem, process, and network access. |
126
+ | `attachments` | `AttachmentsApi` | Read and create manifest-backed attachments for this entity. |
127
+
128
+ ## HandlerWake
129
+
130
+ `ctx.wake` is a normalized convenience view of the raw `WakeEvent` passed as the handler's second argument:
131
+
132
+ ```ts
133
+ type HandlerWake =
134
+ | {
135
+ type: "inbox"
136
+ source: string
137
+ message: { type: string; payload: unknown; from?: string }
138
+ raw: WakeEvent
139
+ }
140
+ | {
141
+ type: "other"
142
+ wakeType: string
143
+ source: string
144
+ payload?: unknown
145
+ raw: WakeEvent
146
+ }
147
+ ```
90
148
 
91
149
  ## Methods
92
150
 
@@ -99,16 +157,83 @@ interface HandlerContext<TState extends StateProxy = StateProxy> {
99
157
  | `removeContext(id)` | `void` | Remove a context entry by id. |
100
158
  | `getContext(id)` | `ContextEntry \| undefined` | Get a context entry by id, or `undefined` if not found. |
101
159
  | `listContext()` | `Array<ContextEntry>` | List all context entries. |
160
+ | `setGoal(input)` | `GoalEntry` | Set or replace the active goal for this entity. |
161
+ | `clearGoal()` | `boolean` | Clear the active goal. Returns whether a goal was removed. |
162
+ | `getGoal()` | `GoalEntry \| undefined` | Read the active goal, if one exists. |
163
+ | `markGoalComplete(summary?)` | `GoalEntry \| undefined` | Mark the active goal complete, optionally recording a summary. |
164
+ | `updateGoalUsage(tokens, opts?)` | `GoalEntry \| undefined` | Add token usage to the active goal and optionally update its status. |
102
165
  | `agent.run(input?)` | `Promise<AgentRunResult>` | Run the configured agent loop. Optional `input` string is appended as a user message before the loop starts. |
103
- | `spawn(type, id, args?, opts?)` | `Promise<EntityHandle>` | Spawn a child entity. `opts` accepts `tags`, `observe`, `initialMessage`, and `wake`. See [`EntityHandle`](./entity-handle). |
166
+ | `spawn(type, id, args?, opts?)` | `Promise<EntityHandle>` | Spawn a child entity. `opts` accepts `tags`, `observe`, `initialMessage`, `initialMessageType`, `wake`, and `sandbox`. See [`EntityHandle`](./entity-handle). |
167
+ | `fork(sourceUrl, id, opts?)` | `Promise<EntityHandle>` | Fork another entity at its latest completed run. By default the fork becomes this entity's child and wakes this entity when the fork's next run finishes. |
168
+ | `forkSelf(id, opts?)` | `Promise<EntityHandle>` | Convenience wrapper for `ctx.fork(ctx.entityUrl, id, opts)`. |
104
169
  | `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. |
170
+ | `unobserve(sourceRef)` | `Promise<void>` | Stop this entity from observing a pg-sync source by source reference. |
105
171
  | `mkdb(id, schema)` | `SharedStateHandle<T>` | Create a new shared state stream. See [`SharedStateHandle`](./shared-state-handle). |
106
- | `send(entityUrl, payload, opts?)` | `void` | Send a message to another entity. `opts` accepts `type` and `afterMs` (delay in milliseconds). |
172
+ | `send(entityUrl, payload, opts?)` | `Promise<SendResult>` | Send a message to another entity. `opts` accepts `type` and `afterMs` (delay in milliseconds). |
173
+ | `onSignal(handler)` | `void` | Register a handler for lifecycle signals delivered during this wake. Runtime-controlled signals such as `SIGINT`, `SIGSTOP`, `SIGCONT`, and `SIGKILL` are handled by the runtime. |
107
174
  | `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. |
175
+ | `replyText(text)` | `void` | Write a synthetic assistant text reply without invoking the LLM. Emits the same run/text rows used by chat UIs. |
108
176
  | `setTag(key, value)` | `Promise<void>` | Set a tag on this entity. |
109
- | `removeTag(key)` | `Promise<void>` | Remove a tag from this entity. |
177
+ | `deleteTag(key)` | `Promise<void>` | Delete a tag from this entity. |
110
178
  | `sleep()` | `void` | End the handler without running an agent. The entity remains idle until the next wake. |
111
179
 
180
+ ## Sandbox
181
+
182
+ `ctx.sandbox` is selected from the entity's sandbox profile at wake-session start. The runtime owns disposal; handlers should not call `sandbox.dispose()` directly. Use it when writing custom tools that need filesystem, subprocess, or network access so the behavior follows the active sandbox profile.
183
+
184
+ Spawned children can inherit or select a sandbox:
185
+
186
+ ```ts
187
+ await ctx.spawn("worker", "analysis", args, {
188
+ sandbox: "inherit",
189
+ initialMessage: "Review the current workspace.",
190
+ })
191
+ ```
192
+
193
+ ## Forking
194
+
195
+ `ctx.fork(sourceEntityUrl, id, opts?)` creates a child fork of another entity at that source entity's latest completed run. `ctx.forkSelf(id, opts?)` forks the current entity. Options mirror spawn where the semantics map:
196
+
197
+ ```ts
198
+ const fork = await ctx.forkSelf("variant-a", {
199
+ initialMessage: { text: "Try a different approach." },
200
+ tags: { branch: "variant-a" },
201
+ })
202
+ ```
203
+
204
+ By default the fork is observed as this entity's child with a `runFinished` wake that includes the fork response. Pass `observe: false` for a fire-and-forget fork with no parent manifest entry, wake subscription, or reply path.
205
+
206
+ ## Attachments
207
+
208
+ `ctx.attachments` exposes manifest-backed attachments associated with the entity. It is used by the runtime to hydrate image and file context and can also be used by custom handlers or tools that need to inspect uploaded files.
209
+
210
+ ## Slash Commands
211
+
212
+ `ctx.slashCommands` exposes structured composer commands registered on the entity. Static commands come from the entity type; handlers can add or replace dynamic commands for UI composers that send `composer_input` messages:
213
+
214
+ ```ts
215
+ ctx.slashCommands.register({
216
+ name: "summarize",
217
+ description: "Summarize the current session",
218
+ })
219
+ ```
220
+
221
+ Use `ctx.wake` or the handler's `wake` argument to inspect incoming composer payloads.
222
+
223
+ ## Lifecycle Signals
224
+
225
+ Use `ctx.signal` for cancellable work and `ctx.onSignal()` for handler-delivered lifecycle signals:
226
+
227
+ ```ts
228
+ ctx.onSignal(async ({ signal, reason }) => {
229
+ if (signal === "SIGTERM") {
230
+ await cleanup(reason)
231
+ }
232
+ })
233
+ ```
234
+
235
+ `SIGINT` aborts the active handler invocation through `ctx.signal`. `SIGSTOP`, `SIGCONT`, and `SIGKILL` are runtime-controlled.
236
+
112
237
  ## RunHandle
113
238
 
114
239
  `recordRun()` is for handlers that perform work outside `ctx.agent.run()` but still want to expose run lifecycle events.
@@ -18,12 +18,22 @@ Factory functions that create the runtime request router and Node HTTP adapter.
18
18
  interface RuntimeRouter {
19
19
  handleRequest(request: Request): Promise<Response | null>
20
20
  handleWebhookRequest(request: Request): Promise<Response>
21
+ dispatchWake(
22
+ notification: WakeNotification,
23
+ options?: Pick<ProcessWakeConfig, "claimHeaders" | "claimTokenHeader">
24
+ ): void
21
25
  dispatchWebhookWake(notification: WebhookNotification): void
22
26
  drainWakes(): Promise<void>
23
27
  waitForSettled(): Promise<void>
24
28
  abortWakes(): void
25
29
  debugState(): RuntimeDebugState
26
30
  readonly typeNames: string[]
31
+ readonly sandboxProfileDescriptors: Array<{
32
+ name: string
33
+ label: string
34
+ description?: string
35
+ remote?: boolean
36
+ }>
27
37
  registerTypes(): Promise<void>
28
38
  }
29
39
  ```
@@ -32,12 +42,14 @@ interface RuntimeRouter {
32
42
  | ----------------------------------- | --------------------------- | ------------------------------------------------------------------------------------------------------- |
33
43
  | `handleRequest(request)` | `Promise<Response \| null>` | Route a fetch `Request`. Returns `null` if the request path does not match `webhookPath`. |
34
44
  | `handleWebhookRequest(request)` | `Promise<Response>` | Handle a webhook request directly, without route matching. |
45
+ | `dispatchWake(notification, opts?)` | `void` | Dispatch an already-parsed wake notification from any transport. |
35
46
  | `dispatchWebhookWake(notification)` | `void` | Dispatch an already-parsed webhook notification. Runs the wake handler in the background. |
36
47
  | `drainWakes()` | `Promise<void>` | Wait for all in-flight wake handlers to settle. Throws if any wake errored. |
37
48
  | `waitForSettled()` | `Promise<void>` | Wait for all in-flight wake handlers to settle. |
38
49
  | `abortWakes()` | `void` | Abort in-flight wakes so host shutdown can complete quickly. |
39
50
  | `debugState()` | `RuntimeDebugState` | Return a runtime-local snapshot for tests and shutdown diagnostics. |
40
51
  | `typeNames` | `string[]` | Names of all registered entity types (read-only). |
52
+ | `sandboxProfileDescriptors` | `Array<{ name, label, description?, remote? }>` | Wire-shape descriptors for sandbox profiles advertised by this runtime (read-only). |
41
53
  | `registerTypes()` | `Promise<void>` | Register all entity types with the Electric Agents runtime server. Uses upsert semantics — safe to call on every startup. |
42
54
 
43
55
  ## RuntimeHandler
@@ -92,6 +104,9 @@ interface RuntimeRouterConfig {
92
104
  handlerUrl?: string
93
105
  registry?: EntityRegistry
94
106
  subscriptionPathForType?: (typeName: string) => string
107
+ defaultDispatchPolicyForType?: (typeName: string) => DispatchPolicy | undefined
108
+ serverHeaders?: HeadersProvider
109
+ webhookSignature?: false | Partial<WebhookSignatureVerifierConfig>
95
110
  idleTimeout?: number
96
111
  heartbeatInterval?: number
97
112
  createElectricTools?: (context: {
@@ -113,26 +128,39 @@ interface RuntimeRouterConfig {
113
128
  payload: unknown
114
129
  targetUrl?: string
115
130
  fireAt: string
116
- from?: string
117
131
  messageType?: string
118
132
  }): Promise<{ txid: string }>
119
133
  deleteSchedule(opts: { id: string }): Promise<{ txid: string }>
134
+ listWebhookSources(): Promise<Array<WebhookSourceContract>>
135
+ subscribeToWebhookSource(
136
+ opts: WebhookSourceSubscriptionInput
137
+ ): Promise<{ txid: string; subscription: WebhookSourceSubscription }>
138
+ unsubscribeFromWebhookSource(opts: { id: string }): Promise<{ txid: string }>
120
139
  }) => AgentTool[] | Promise<AgentTool[]>
121
140
  onWakeError?: (error: Error) => boolean | void
122
141
  registrationConcurrency?: number
142
+ sandboxProfiles?: ReadonlyArray<SandboxProfile>
143
+ publicUrl?: string
144
+ name?: string
123
145
  }
124
146
  ```
125
147
 
126
- | Field | Type | Default | Description |
127
- | ------------------------- | ------------------------------------------------------------- | -------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
128
- | `baseUrl` | `string` | - | Base URL of the Electric Agents runtime server (e.g. `"http://localhost:4437"`). Required. |
129
- | `serveEndpoint` | `string` | - | Full webhook callback URL exposed by your app. Used for type registration. |
130
- | `webhookPath` | `string` | pathname from `serveEndpoint` / `handlerUrl`, or `"/electric-agents"` | Path matched by `handleRequest()`. |
131
- | `handlerUrl` | `string` | - | Backward-compatible alias for `serveEndpoint`; prefer `serveEndpoint` in new code. |
132
- | `registry` | `EntityRegistry` | default registry | Entity registry for this handler. Falls back to the module-level default registry. |
133
- | `subscriptionPathForType` | `(typeName: string) => string` | - | Override the webhook subscription path used per entity type registration. |
134
- | `idleTimeout` | `number` | `20000` | Idle timeout in milliseconds before closing a wake. |
135
- | `heartbeatInterval` | `number` | `30000` | Heartbeat interval in milliseconds. |
136
- | `createElectricTools` | `(context) => AgentTool[] \| Promise<...>` | - | Optional tool factory invoked for each wake context before handler execution. Provides extra tools to the agent. |
137
- | `onWakeError` | `(error: Error) => boolean \| void` | - | Observer for background wake failures. Return `true` to mark the error as handled so it is not rethrown on drain. |
138
- | `registrationConcurrency` | `number` | `8` | Max number of concurrent entity-type registrations. |
148
+ | Field | Type | Default | Description |
149
+ | ------------------------------ | ------------------------------------------- | ----------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
150
+ | `baseUrl` | `string` | - | Base URL of the Electric Agents runtime server (e.g. `"http://localhost:4437"`). Required. |
151
+ | `serveEndpoint` | `string` | - | Full webhook callback URL exposed by your app. Used for type registration. |
152
+ | `webhookPath` | `string` | pathname from `serveEndpoint` / `handlerUrl`, or `"/electric-agents"` | Path matched by `handleRequest()`. |
153
+ | `handlerUrl` | `string` | - | Backward-compatible alias for `serveEndpoint`; prefer `serveEndpoint` in new code. |
154
+ | `registry` | `EntityRegistry` | default registry | Entity registry for this handler. Falls back to the module-level default registry. |
155
+ | `subscriptionPathForType` | `(typeName: string) => string` | - | Override the webhook subscription path used per entity type registration. |
156
+ | `defaultDispatchPolicyForType` | `(typeName: string) => DispatchPolicy \| undefined` | - | Override the default dispatch policy registered per entity type. |
157
+ | `serverHeaders` | `HeadersProvider` | - | Headers sent on control-plane requests to the agents server, including type registration and wake claims. |
158
+ | `webhookSignature` | `false \| Partial<WebhookSignatureVerifierConfig>` | enabled against `${baseUrl}/__ds/jwks.json` | Webhook signature verification config. Set to `false` only for trusted in-process tests. |
159
+ | `idleTimeout` | `number` | `20000` | Idle timeout in milliseconds before closing a wake. |
160
+ | `heartbeatInterval` | `number` | `10000` | Heartbeat interval in milliseconds. |
161
+ | `createElectricTools` | `(context) => AgentTool[] \| Promise<...>` | - | Optional tool factory invoked for each wake context before handler execution. Provides extra tools to the agent. |
162
+ | `onWakeError` | `(error: Error) => boolean \| void` | - | Observer for background wake failures. Return `true` to mark the error as handled so it is not rethrown on drain. |
163
+ | `registrationConcurrency` | `number` | `8` | Max number of concurrent entity-type registrations. |
164
+ | `sandboxProfiles` | `ReadonlyArray<SandboxProfile>` | - | Named sandbox profiles advertised by this runtime. Spawn requests can select one by profile name. |
165
+ | `publicUrl` | `string` | - | Public URL surfaced by server runtime metadata APIs when available. |
166
+ | `name` | `string` | `"default"` | Human-readable runtime name used for runtime metadata de-duplication. |
@@ -76,7 +76,7 @@ type WakeMessage = {
76
76
  other_children?: Array<{
77
77
  url: string
78
78
  type: string
79
- status: "spawning" | "running" | "idle" | "stopped"
79
+ status: "spawning" | "running" | "idle" | "paused" | "stopping" | "stopped" | "killed"
80
80
  }>
81
81
  }
82
82
  ```
@@ -89,9 +89,37 @@ Inspect the payload to distinguish the sub-kind:
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 |
92
+ | Webhook source | `subscribe_webhook_source` tool or `client.subscribeToWebhookSource()` | `payload.type === "webhook_source_wake"` and `payload.events` contains matching webhook events |
92
93
  | Scheduled send | A `future_send` schedule fires | Arrives as `"inbox"` (not `"wake"`) — the schedule produces a message delivery |
93
94
  | Timeout | `timeoutMs` on a `change` wake config elapsed with no changes | `payload.timeout === true`, `payload.changes` is empty |
94
95
 
96
+ Webhook-source wakes use the hydrated payload from `HydratedWebhookSourceWake`:
97
+
98
+ ```ts
99
+ type HydratedWebhookSourceWake = {
100
+ type: "webhook_source_wake"
101
+ source: string
102
+ sourceType: "webhook"
103
+ endpointKey: string
104
+ webhookKey: string
105
+ subscription: {
106
+ id: string
107
+ bucketKey?: string
108
+ params: Record<string, unknown>
109
+ filterKey?: string
110
+ reason?: string
111
+ }
112
+ bucket: string | null
113
+ changes: Array<{
114
+ collection: string
115
+ kind: "insert" | "update" | "delete"
116
+ key: string
117
+ }>
118
+ events: WebhookEventRow[]
119
+ missingEventKeys?: string[]
120
+ }
121
+ ```
122
+
95
123
  For the narrative on how these are produced, see [Waking entities](../usage/waking-entities).
96
124
 
97
125
  ## Wake
@@ -40,8 +40,11 @@ interface RuntimeRouterConfig {
40
40
  handlerUrl?: string // legacy alias for serveEndpoint
41
41
  registry?: EntityRegistry
42
42
  subscriptionPathForType?: (typeName: string) => string
43
+ defaultDispatchPolicyForType?: (typeName: string) => DispatchPolicy | undefined
44
+ serverHeaders?: HeadersProvider
45
+ webhookSignature?: false | Partial<WebhookSignatureVerifierConfig>
43
46
  idleTimeout?: number // ms before closing idle wake (default: 20000)
44
- heartbeatInterval?: number // ms between heartbeats (default: 30000)
47
+ heartbeatInterval?: number // ms between heartbeats (default: 10000)
45
48
  createElectricTools?: (context: {
46
49
  entityUrl: string
47
50
  entityType: string
@@ -61,16 +64,35 @@ interface RuntimeRouterConfig {
61
64
  payload: unknown
62
65
  targetUrl?: string
63
66
  fireAt: string
64
- from?: string
65
67
  messageType?: string
66
68
  }): Promise<{ txid: string }>
67
69
  deleteSchedule(opts: { id: string }): Promise<{ txid: string }>
70
+ listWebhookSources(): Promise<Array<WebhookSourceContract>>
71
+ subscribeToWebhookSource(
72
+ opts: WebhookSourceSubscriptionInput
73
+ ): Promise<{ txid: string; subscription: WebhookSourceSubscription }>
74
+ unsubscribeFromWebhookSource(opts: { id: string }): Promise<{ txid: string }>
68
75
  }) => AgentTool[] | Promise<AgentTool[]> // factory for extra agent tools
69
76
  onWakeError?: (error: Error) => boolean | void // return true to mark handled
70
77
  registrationConcurrency?: number // max concurrent type registrations (default: 8)
78
+ sandboxProfiles?: ReadonlyArray<SandboxProfile>
79
+ publicUrl?: string
80
+ name?: string
71
81
  }
72
82
  ```
73
83
 
84
+ Key fields:
85
+
86
+ | Field | Description |
87
+ | ------------------------------ | ---------------------------------------------------------------------------------------------------------------- |
88
+ | `serveEndpoint` | Public webhook callback URL. When present, type registration includes webhook dispatch unless a default dispatch policy overrides it. |
89
+ | `serverHeaders` | Headers sent on control-plane requests to the agents server, including type registration and wake claims. |
90
+ | `webhookSignature` | Webhook signature verification config. Enabled by default against `${baseUrl}/__ds/jwks.json`; set to `false` only for trusted in-process tests. |
91
+ | `defaultDispatchPolicyForType` | Override the default dispatch policy registered per entity type. Use this for pull-wake runner targets. |
92
+ | `sandboxProfiles` | Named sandbox profiles advertised by this runtime. Spawn requests can select one by profile name. |
93
+ | `publicUrl` | Public URL for this runtime, surfaced by server runtime metadata APIs when available. |
94
+ | `name` | Human-readable runtime name. Defaults to `"default"`. |
95
+
74
96
  ## HTTP server
75
97
 
76
98
  Your app needs an HTTP server to receive webhook callbacks from the Electric Agents runtime server. Forward webhook POSTs to the runtime handler:
@@ -103,10 +125,7 @@ Must be called after your app starts listening.
103
125
  await runtime.registerTypes()
104
126
  ```
105
127
 
106
- This makes two requests per entity type:
107
-
108
- 1. `POST /_electric/entity-types` — registers the type definition and schemas.
109
- 2. `PUT /{type}/**?subscription={type}-handler` — creates a webhook subscription for the type.
128
+ This sends `POST /_electric/entity-types` for each entity type. The request includes the type definition, state schemas, permission grants, optional `serve_endpoint`, and optional default dispatch policy. When `serveEndpoint` is set and no custom default dispatch policy is provided, registration uses webhook dispatch to that endpoint.
110
129
 
111
130
  ## RuntimeHandler
112
131
 
@@ -115,12 +134,22 @@ interface RuntimeHandler {
115
134
  onEnter(req: IncomingMessage, res: ServerResponse): Promise<void>
116
135
  handleRequest(request: Request): Promise<Response | null>
117
136
  handleWebhookRequest(request: Request): Promise<Response>
137
+ dispatchWake(
138
+ notification: WakeNotification,
139
+ options?: Pick<ProcessWakeConfig, "claimHeaders" | "claimTokenHeader">
140
+ ): void
118
141
  dispatchWebhookWake(notification: WebhookNotification): void
119
142
  drainWakes(): Promise<void>
120
143
  waitForSettled(): Promise<void>
121
144
  abortWakes(): void
122
145
  debugState(): RuntimeDebugState
123
146
  readonly typeNames: string[]
147
+ readonly sandboxProfileDescriptors: Array<{
148
+ name: string
149
+ label: string
150
+ description?: string
151
+ remote?: boolean
152
+ }>
124
153
  registerTypes(): Promise<void>
125
154
  }
126
155
 
@@ -137,12 +166,14 @@ interface RuntimeDebugState {
137
166
  | `onEnter` | Node HTTP adapter — reads the request body and delegates to `handleWebhookRequest` |
138
167
  | `handleRequest` | Fetch-native router — returns `null` if the path does not match `webhookPath` |
139
168
  | `handleWebhookRequest` | Processes a webhook POST directly, without path matching |
169
+ | `dispatchWake` | Dispatches a pre-parsed wake notification from any transport |
140
170
  | `dispatchWebhookWake` | Dispatches a pre-parsed notification (fire-and-forget) |
141
171
  | `drainWakes` | Waits for all in-flight wake handlers to settle; throws on errors |
142
172
  | `waitForSettled` | Waits for all in-flight wakes; throws on errors |
143
173
  | `abortWakes` | Cancels all in-flight wake handlers immediately |
144
174
  | `debugState` | Returns a snapshot of internal runtime state for diagnostics |
145
- | `registerTypes` | Registers entity types and webhook subscriptions with the Electric Agents runtime server |
175
+ | `sandboxProfileDescriptors` | Sandbox profile descriptors advertised by this runtime |
176
+ | `registerTypes` | Registers entity types, schemas, permission grants, and default dispatch policy with the Electric Agents runtime server |
146
177
 
147
178
  ## createRuntimeRouter
148
179