@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.
- package/dist/entrypoint.js +88 -14
- package/dist/index.cjs +87 -13
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +88 -14
- package/docs/entities/agents/horton.md +22 -17
- package/docs/entities/agents/worker.md +13 -6
- package/docs/entities/patterns/blackboard.md +1 -1
- package/docs/entities/patterns/dispatcher.md +1 -1
- package/docs/entities/patterns/manager-worker.md +10 -5
- package/docs/entities/patterns/map-reduce.md +1 -1
- package/docs/entities/patterns/pipeline.md +1 -1
- package/docs/entities/patterns/reactive-observers.md +1 -1
- package/docs/index.md +6 -4
- package/docs/quickstart.md +2 -2
- package/docs/reference/agent-config.md +13 -3
- package/docs/reference/built-in-collections.md +128 -9
- package/docs/reference/cli.md +34 -4
- package/docs/reference/entity-definition.md +39 -7
- package/docs/reference/entity-handle.md +19 -1
- package/docs/reference/handler-context.md +130 -5
- package/docs/reference/runtime-handler.md +42 -14
- package/docs/reference/wake-event.md +29 -1
- package/docs/usage/app-setup.md +38 -7
- package/docs/usage/attachments.md +129 -0
- package/docs/usage/clients-and-react.md +23 -2
- package/docs/usage/configuring-the-agent.md +15 -5
- package/docs/usage/context-composition.md +2 -1
- package/docs/usage/defining-entities.md +9 -5
- package/docs/usage/defining-tools.md +1 -1
- package/docs/usage/embedded-builtins.md +82 -31
- package/docs/usage/managing-state.md +5 -0
- package/docs/usage/mcp-servers.md +16 -8
- package/docs/usage/overview.md +39 -14
- package/docs/usage/permissions-and-principals.md +160 -0
- package/docs/usage/programmatic-runtime-client.md +158 -16
- package/docs/usage/sandboxing.md +162 -0
- package/docs/usage/signals.md +138 -0
- package/docs/usage/spawning-and-coordinating.md +30 -11
- package/docs/usage/testing.md +1 -1
- package/docs/usage/waking-entities.md +34 -6
- package/docs/usage/webhook-sources.md +171 -0
- package/docs/usage/writing-handlers.md +13 -55
- package/docs/walkthrough.md +13 -5
- 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
|
-
|
|
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
|
-
| `
|
|
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
|
|
54
|
-
|
|
|
55
|
-
| `schema`
|
|
56
|
-
| `type`
|
|
57
|
-
| `primaryKey`
|
|
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:
|
|
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
|
-
|
|
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 `
|
|
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 `
|
|
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?)` | `
|
|
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
|
-
| `
|
|
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
|
|
127
|
-
|
|
|
128
|
-
| `baseUrl`
|
|
129
|
-
| `serveEndpoint`
|
|
130
|
-
| `webhookPath`
|
|
131
|
-
| `handlerUrl`
|
|
132
|
-
| `registry`
|
|
133
|
-
| `subscriptionPathForType`
|
|
134
|
-
| `
|
|
135
|
-
| `
|
|
136
|
-
| `
|
|
137
|
-
| `
|
|
138
|
-
| `
|
|
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
|
package/docs/usage/app-setup.md
CHANGED
|
@@ -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:
|
|
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
|
|
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
|
-
| `
|
|
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
|
|