@electric-ax/agents 0.4.19 → 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 (40) hide show
  1. package/docs/entities/agents/horton.md +22 -17
  2. package/docs/entities/agents/worker.md +13 -6
  3. package/docs/entities/patterns/blackboard.md +1 -1
  4. package/docs/entities/patterns/dispatcher.md +1 -1
  5. package/docs/entities/patterns/manager-worker.md +10 -5
  6. package/docs/entities/patterns/map-reduce.md +1 -1
  7. package/docs/entities/patterns/pipeline.md +1 -1
  8. package/docs/entities/patterns/reactive-observers.md +1 -1
  9. package/docs/index.md +6 -4
  10. package/docs/quickstart.md +2 -2
  11. package/docs/reference/agent-config.md +13 -3
  12. package/docs/reference/built-in-collections.md +128 -9
  13. package/docs/reference/cli.md +34 -4
  14. package/docs/reference/entity-definition.md +39 -7
  15. package/docs/reference/entity-handle.md +19 -1
  16. package/docs/reference/handler-context.md +130 -5
  17. package/docs/reference/runtime-handler.md +42 -14
  18. package/docs/reference/wake-event.md +29 -1
  19. package/docs/usage/app-setup.md +38 -7
  20. package/docs/usage/attachments.md +129 -0
  21. package/docs/usage/clients-and-react.md +23 -2
  22. package/docs/usage/configuring-the-agent.md +15 -5
  23. package/docs/usage/context-composition.md +2 -1
  24. package/docs/usage/defining-entities.md +9 -5
  25. package/docs/usage/defining-tools.md +1 -1
  26. package/docs/usage/embedded-builtins.md +82 -31
  27. package/docs/usage/managing-state.md +5 -0
  28. package/docs/usage/mcp-servers.md +16 -8
  29. package/docs/usage/overview.md +39 -14
  30. package/docs/usage/permissions-and-principals.md +160 -0
  31. package/docs/usage/programmatic-runtime-client.md +158 -16
  32. package/docs/usage/sandboxing.md +162 -0
  33. package/docs/usage/signals.md +138 -0
  34. package/docs/usage/spawning-and-coordinating.md +30 -11
  35. package/docs/usage/testing.md +1 -1
  36. package/docs/usage/waking-entities.md +34 -6
  37. package/docs/usage/webhook-sources.md +171 -0
  38. package/docs/usage/writing-handlers.md +13 -55
  39. package/docs/walkthrough.md +13 -5
  40. package/package.json +3 -3
@@ -18,13 +18,13 @@ High level overview of the Electric Agents system and developer APIs.
18
18
  Agents are entities that handle events, defined as a:
19
19
 
20
20
  - `handler(ctx, wake)` with
21
- - `state` and [built in collections](#_8-built-in-collections)
21
+ - `state` and [built in collections](#_9-built-in-collections)
22
22
 
23
23
  And schemas:
24
24
 
25
25
  - `creationSchema` -- validated spawn args
26
26
  - `inboxSchemas` -- typed message contracts
27
- - `outputSchemas` -- what the entity emits (for UI binding)
27
+ - `stateSchemas` -- additional registered state schemas
28
28
 
29
29
  See [Defining entities](/docs/agents/usage/defining-entities) and [EntityDefinition reference](/docs/agents/reference/entity-definition).
30
30
 
@@ -35,10 +35,13 @@ The context API passed into the handler:
35
35
  | Property/Method | Purpose |
36
36
  | ----------------------------------- | --------------------------------------------------------------------- |
37
37
  | `ctx.firstWake` | Boolean -- initial setup pass while no manifest entries exist |
38
+ | `ctx.wake` | Current wake, also passed as the second handler argument |
39
+ | `ctx.slashCommands` | Read/register structured composer slash commands |
38
40
  | `ctx.entityUrl` | Identity -- `/type/id` |
39
41
  | `ctx.entityType` | Type name string |
40
42
  | `ctx.args` | Readonly spawn arguments |
41
43
  | `ctx.tags` | Entity tags -- key/value metadata |
44
+ | `ctx.principal` | Principal that caused the current wake, when supplied by the server |
42
45
  | `ctx.db` | Full TanStack DB: `db.actions` for writes, `db.collections` for reads |
43
46
  | `ctx.state` | Proxy object keyed by collection name |
44
47
  | `ctx.events` | Change events that triggered this wake |
@@ -49,14 +52,17 @@ The context API passed into the handler:
49
52
  | `ctx.agent.run()` | Execute the agent loop |
50
53
  | `ctx.electricTools` | Runtime-provided tools to spread into agent config |
51
54
  | `ctx.spawn(type, id, args, opts)` | Create child entity |
52
- | `ctx.observe(source, opts)` | Subscribe to a source via `entity()`, `cron()`, `entities()`, `db()` |
55
+ | `ctx.fork(url, id, opts)` / `ctx.forkSelf(id, opts)` | Branch an entity at its latest completed run |
56
+ | `ctx.observe(source, opts)` | Subscribe to a source via `entity()`, `cron()`, `entities()`, `db()`, `webhook()`, or `pgSync()` |
53
57
  | `ctx.send(url, payload, opts)` | Send message to an entity |
54
58
  | `ctx.sleep()` | Return to idle |
55
59
  | `ctx.mkdb(id, schema)` | Create cross-entity shared state |
56
60
  | `ctx.observe(db(id, schema), opts)` | Join existing shared state |
57
61
  | `ctx.recordRun()` | Record non-LLM work as a run for `runFinished` observers |
62
+ | `ctx.replyText(text)` | Emit a synthetic assistant text reply without invoking the LLM |
63
+ | `ctx.setGoal(input)` / `ctx.getGoal()` | Manage the active goal for long-running agents |
58
64
  | `ctx.setTag(key, value)` | Set a tag on this entity |
59
- | `ctx.removeTag(key)` | Remove a tag from this entity |
65
+ | `ctx.deleteTag(key)` | Delete a tag from this entity |
60
66
 
61
67
  See [Writing handlers](/docs/agents/usage/writing-handlers) and [HandlerContext reference](/docs/agents/reference/handler-context).
62
68
 
@@ -65,12 +71,14 @@ See [Writing handlers](/docs/agents/usage/writing-handlers) and [HandlerContext
65
71
  ```ts
66
72
  ctx.useAgent({
67
73
  systemPrompt: string,
68
- model: string | Model<any>, // e.g. 'claude-sonnet-4-5-20250929'
69
- provider?: KnownProvider, // defaults to 'anthropic' for string models
74
+ model: string | Model<any>, // e.g. 'claude-sonnet-4-6'
75
+ provider?: Provider, // defaults to 'anthropic' for string models
70
76
  tools: AgentTool[], // [...ctx.electricTools, ...custom]
71
77
  streamFn?: StreamFn, // optional streaming callback
72
78
  getApiKey?: (provider: string) => string | Promise<string> | undefined,
73
79
  onPayload?: SimpleStreamOptions["onPayload"],
80
+ modelTimeoutMs?: number,
81
+ modelMaxRetries?: number,
74
82
  testResponses?: string[] | TestResponseFn // for testing without LLM
75
83
  })
76
84
  await ctx.agent.run() // blocks until agent finishes
@@ -177,8 +185,10 @@ See [Managing state](/docs/agents/usage/managing-state).
177
185
 
178
186
  - **`spawn(type, id, args, opts)`** -> `EntityHandle` -- create child
179
187
  - `opts.initialMessage` -- first message to deliver
188
+ - `opts.initialMessageType` -- optional inbox message type for the initial message
180
189
  - `opts.wake` -- `'runFinished'`, `{ on: 'runFinished', includeResponse? }`, or `{ on: 'change', collections?, debounceMs?, timeoutMs? }`
181
- - **`observe(source, opts)`** -> `EntityHandle | ObservationHandle` -- subscribe via `entity()`, `cron()`, `entities()`, `db()`
190
+ - **`fork(sourceEntityUrl, id, opts)` / `forkSelf(id, opts)`** -> `EntityHandle` -- branch an entity at its latest completed run
191
+ - **`observe(source, opts)`** -> `EntityHandle | ObservationHandle` -- subscribe via `entity()`, `cron()`, `entities()`, `db()`, `webhook()`, or `pgSync()`
182
192
  - **`send(url, payload, opts)`** -- fire-and-forget message
183
193
  - **`recordRun()`** -> `RunHandle` -- publish run lifecycle for external work
184
194
  - **`sleep()`** -- go idle
@@ -192,7 +202,17 @@ See [Managing state](/docs/agents/usage/managing-state).
192
202
 
193
203
  See [Spawning & coordinating](/docs/agents/usage/spawning-and-coordinating) and [EntityHandle reference](/docs/agents/reference/entity-handle).
194
204
 
195
- ## 7. Shared state (cross-entity)
205
+ ## 7. Runtime capabilities
206
+
207
+ Use the dedicated guides for runtime features that cut across handlers, clients, and hosted built-ins:
208
+
209
+ - [Permissions & principals](/docs/agents/usage/permissions-and-principals) — principal-scoped access to types and entities.
210
+ - [Sandboxing](/docs/agents/usage/sandboxing) — filesystem, process, and network isolation for LLM-driven tools.
211
+ - [Attachments](/docs/agents/usage/attachments) — upload, read, and hydrate files and images.
212
+ - [Signals](/docs/agents/usage/signals) — interrupt, pause, resume, kill, and notify entities.
213
+ - [Webhook sources](/docs/agents/usage/webhook-sources) — subscribe entities to external webhook-backed feeds.
214
+
215
+ ## 8. Shared state (cross-entity)
196
216
 
197
217
  Define a schema map, then create/connect:
198
218
 
@@ -213,9 +233,9 @@ shared.findings.insert({ key: "f1", text: "..." })
213
233
 
214
234
  See [Shared state](/docs/agents/usage/shared-state) and [SharedStateHandle reference](/docs/agents/reference/shared-state-handle).
215
235
 
216
- ## 8. Built-in collections
236
+ ## 9. Built-in collections
217
237
 
218
- Every entity automatically has 17 `ctx.db.collections`:
238
+ Every entity automatically has 20 `ctx.db.collections`:
219
239
 
220
240
  | Collection | Purpose | Key fields |
221
241
  | ------------------ | ------------------------- | ---------------------------------------------------------------------- |
@@ -225,13 +245,16 @@ Every entity automatically has 17 `ctx.db.collections`:
225
245
  | `textDeltas` | Incremental text chunks | `text_id, delta` |
226
246
  | `toolCalls` | Tool invocation lifecycle | `tool_name, status, args, result` |
227
247
  | `reasoning` | Extended thinking blocks | `status: streaming/completed` |
248
+ | `reasoningDeltas` | Incremental reasoning chunks | `reasoning_id, delta` |
228
249
  | `errors` | Diagnostic errors | `error_code, message` |
229
250
  | `inbox` | Received messages | `from, payload, message_type` |
230
251
  | `wakes` | Wake event history | `source, timeout, changes` |
231
252
  | `entityCreated` | Bootstrap metadata | `entity_type, args, parent_url` |
232
253
  | `entityStopped` | Shutdown signal | `timestamp, reason` |
254
+ | `signals` | Lifecycle signal records | `signal, status, outcome` |
233
255
  | `childStatus` | Child entity status | `entity_url, status` |
234
- | `manifests` | Wiring declarations | discriminated union: child/source/shared-state/effect/context/schedule |
256
+ | `slashCommands` | Composer slash commands | `name, description, args` |
257
+ | `manifests` | Wiring declarations | discriminated union: child/source/shared-state/effect/attachment/context/schedule |
235
258
  | `replayWatermarks` | Replay offset tracking | `source_id, offset` |
236
259
  | `tags` | Entity tags/labels | `key, value` |
237
260
  | `contextInserted` | Context additions | `id, name, attrs, content, timestamp` |
@@ -239,7 +262,7 @@ Every entity automatically has 17 `ctx.db.collections`:
239
262
 
240
263
  See [Built-in collections](/docs/agents/reference/built-in-collections).
241
264
 
242
- ## 9. CLI (`electric agents`)
265
+ ## 10. CLI (`electric agents`)
243
266
 
244
267
  Interact with the system using the Electric Agents CLI:
245
268
 
@@ -250,6 +273,8 @@ Interact with the system using the Electric Agents CLI:
250
273
  | `electric agents spawn /type/id --args '{...}'` | Create entity |
251
274
  | `electric agents send /type/id 'message'` | Send message |
252
275
  | `electric agents observe /type/id` | Stream entity events |
276
+ | `electric agents view /type/id` | Print entity conversation once |
277
+ | `electric agents signal /type/id SIGINT` | Send a lifecycle signal |
253
278
  | `electric agents inspect /type/id` | Show entity state |
254
279
  | `electric agents ps [--type --status --parent]` | List entities |
255
280
  | `electric agents kill /type/id` | Delete entity |
@@ -261,7 +286,7 @@ Interact with the system using the Electric Agents CLI:
261
286
 
262
287
  See [CLI reference](/docs/agents/reference/cli).
263
288
 
264
- ## 10. App setup
289
+ ## 11. App setup
265
290
 
266
291
  ```ts
267
292
  const registry = createEntityRegistry()
@@ -279,7 +304,7 @@ await runtime.registerTypes() // register all types with runtime server
279
304
 
280
305
  See [App setup](/docs/agents/usage/app-setup) and [RuntimeHandler reference](/docs/agents/reference/runtime-handler).
281
306
 
282
- ## 11. App clients and embedded built-ins
307
+ ## 12. App clients and embedded built-ins
283
308
 
284
309
  Use the client and embedding APIs when you need to work with agents outside an entity handler:
285
310
 
@@ -0,0 +1,160 @@
1
+ ---
2
+ title: Permissions & principals
3
+ titleTemplate: "... - Electric Agents"
4
+ description: >-
5
+ Control who can spawn, read, write, signal, fork, schedule, and manage Electric Agents entities using principals and grants.
6
+ outline: [2, 3]
7
+ ---
8
+
9
+ # Permissions & principals
10
+
11
+ Electric Agents servers authorize requests using a **principal** and permission grants. A principal identifies the caller; grants decide what that caller can do to entity types and entity instances.
12
+
13
+ ## Principals
14
+
15
+ Pass a principal key as the `Electric-Principal` header. The key shape is:
16
+
17
+ ```text
18
+ <kind>:<id>
19
+ ```
20
+
21
+ Supported principal kinds are `user`, `agent`, `service`, and `system`. The server turns a key such as `user:sam` into the canonical principal URL `/principal/user%3Asam`.
22
+
23
+ From clients, use `principalKey`:
24
+
25
+ ```ts
26
+ import { createRuntimeServerClient } from "@electric-ax/agents-runtime"
27
+
28
+ const client = createRuntimeServerClient({
29
+ baseUrl: "http://localhost:4437",
30
+ principalKey: "user:sam",
31
+ })
32
+ ```
33
+
34
+ The CLI can pass the same value through the environment:
35
+
36
+ ```sh
37
+ ELECTRIC_AGENTS_PRINCIPAL=user:sam electric agents ps
38
+ ```
39
+
40
+ Servers may also accept additional auth headers through `ELECTRIC_AGENTS_SERVER_HEADERS` or `serverHeaders`, depending on the host.
41
+
42
+ ## Entity type permissions
43
+
44
+ Entity type grants control who can spawn or manage entities of a type. Type-level permissions are:
45
+
46
+ | Permission | Allows |
47
+ | ---------- | ------ |
48
+ | `spawn` | Spawn entities of this type |
49
+ | `manage` | Manage the entity type and acts as the broader type-level permission |
50
+
51
+ Declare initial type grants in an entity definition:
52
+
53
+ ```ts
54
+ registry.define("worker", {
55
+ description: "Internal worker",
56
+ permissionGrants: [
57
+ {
58
+ subject_kind: "principal_kind",
59
+ subject_value: "user",
60
+ permission: "spawn",
61
+ },
62
+ ],
63
+ async handler(ctx) {
64
+ // ...
65
+ },
66
+ })
67
+ ```
68
+
69
+ `subject_kind` can be `principal` for one principal URL/key or `principal_kind` for every principal of a kind.
70
+
71
+ Built-in Horton and Worker registrations grant all `user` principals both `spawn` and `manage` on those entity types. This makes local and hosted user principals able to create built-in sessions and see the type metadata needed by creation UIs. Custom entity types should choose their own `permissionGrants`.
72
+
73
+ ## Entity permissions
74
+
75
+ Entity grants control access to existing entities. Entity-level permissions are:
76
+
77
+ | Permission | Allows |
78
+ | ---------- | ------ |
79
+ | `read` | Read entity metadata and streams |
80
+ | `write` | Send messages and write entity-owned resources |
81
+ | `delete` | Delete or kill the entity |
82
+ | `signal` | Send lifecycle signals |
83
+ | `fork` | Fork from entity history |
84
+ | `schedule` | Create, update, or delete schedules |
85
+ | `spawn` | Spawn children from this entity |
86
+ | `manage` | Manage grants and acts as the broader entity-level permission |
87
+
88
+ Server spawn routes can include initial entity grants:
89
+
90
+ ```ts
91
+ await fetch("http://localhost:4437/_electric/entities/assistant/support-ticket-42", {
92
+ method: "PUT",
93
+ headers: {
94
+ "content-type": "application/json",
95
+ "electric-principal": "user:sam",
96
+ },
97
+ body: JSON.stringify({
98
+ grants: [
99
+ {
100
+ subject_kind: "principal",
101
+ subject_value: "/principal/user%3Asam",
102
+ permission: "read",
103
+ },
104
+ ],
105
+ }),
106
+ })
107
+ ```
108
+
109
+ When spawning from a parent, broad delegation requires `manage` on the parent. This applies to grants such as `manage`, principal-kind grants, descendant propagation, and `copy_to_children`.
110
+
111
+ ## Sharing roles
112
+
113
+ The server stores granular grants, but the UI presents common sharing roles:
114
+
115
+ | Role | Grants |
116
+ | -------- | ------ |
117
+ | `view` | `read`, `fork` |
118
+ | `chat` | `read`, `write`, `signal`, `fork`, `schedule`, `spawn` |
119
+ | `manage` | `manage`, `delete` |
120
+
121
+ These are presets for entity grants. They do not change the underlying permission model, and deployments can still grant individual permissions directly.
122
+
123
+ The server also exposes principal and effective-permission data through Electric shapes for sharing UIs. Point authorization still uses server-side permission checks; Electric visibility is derived from materialized effective permissions.
124
+
125
+ ## Grant propagation
126
+
127
+ Entity grants may include propagation options:
128
+
129
+ ```ts
130
+ {
131
+ subject_kind: "principal",
132
+ subject_value: "/principal/user%3Asam",
133
+ permission: "read",
134
+ propagation: "descendants",
135
+ copy_to_children: true,
136
+ }
137
+ ```
138
+
139
+ - `propagation: "self"` applies to the entity itself.
140
+ - `propagation: "descendants"` applies through descendant entities.
141
+ - `copy_to_children: true` copies the grant when children are spawned.
142
+ - `expires_at` can set a grant expiry timestamp.
143
+
144
+ ## Claim-scoped write tokens
145
+
146
+ Some low-level writes are protected by claim-scoped write tokens. Handler APIs such as `ctx.setTag()` and `ctx.deleteTag()` already have the active claim context. External clients should usually send messages instead of directly mutating entity-owned state.
147
+
148
+ If a host reserves the `Authorization` header for server auth, configure write token transport with `writeTokenHeader` or `claimTokenHeader`:
149
+
150
+ ```ts
151
+ const client = createRuntimeServerClient({
152
+ baseUrl: "http://localhost:4437",
153
+ headers: { authorization: `Bearer ${serverToken}` },
154
+ writeTokenHeader: "electric-claim-token",
155
+ })
156
+ ```
157
+
158
+ ## Development fallback
159
+
160
+ Local development servers can use a development principal fallback. Production deployments should authenticate requests and provide an explicit `Electric-Principal` header for every request.
@@ -25,15 +25,21 @@ const client = createRuntimeServerClient({
25
25
  interface RuntimeServerClientConfig {
26
26
  baseUrl: string
27
27
  fetch?: typeof globalThis.fetch
28
+ headers?: HeadersProvider
29
+ writeTokenHeader?: ClaimTokenHeader
28
30
  track?: <T>(promise: Promise<T>) => Promise<T>
31
+ principalKey?: string
29
32
  }
30
33
  ```
31
34
 
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. |
35
+ | Field | Description |
36
+ | ------------------ | --------------------------------------------------------------------------- |
37
+ | `baseUrl` | Base URL for the Electric Agents server. |
38
+ | `fetch` | Optional fetch implementation, useful in tests or non-standard runtimes. |
39
+ | `headers` | Static or async headers added to requests, useful for auth or tenant scope. |
40
+ | `writeTokenHeader` | Header transport for claim-scoped write tokens: `authorization`, `electric-claim-token`, or `both`. |
41
+ | `track` | Optional wrapper for all requests, useful for telemetry or pending state. |
42
+ | `principalKey` | Principal key sent as `Electric-Principal` on requests. |
37
43
 
38
44
  ## Entity Lifecycle
39
45
 
@@ -46,6 +52,7 @@ const info = await client.spawnEntity({
46
52
  args: { timezone: "Europe/London" },
47
53
  initialMessage: "Help me get started.",
48
54
  tags: { project: "docs" },
55
+ sandbox: { profile: "local", scope: "entity" },
49
56
  })
50
57
 
51
58
  console.log(info.entityUrl) // "/horton/onboarding"
@@ -60,7 +67,17 @@ interface SpawnEntityOptions {
60
67
  args?: Record<string, unknown>
61
68
  parentUrl?: string
62
69
  initialMessage?: unknown
70
+ initialMessageType?: string
63
71
  tags?: Record<string, string>
72
+ sandbox?: {
73
+ profile?: string
74
+ key?: string
75
+ scope?: "entity" | "wake"
76
+ persistent?: boolean
77
+ owner?: boolean
78
+ inherit?: boolean
79
+ }
80
+ dispatch_policy?: DispatchPolicy
64
81
  wake?: {
65
82
  subscriberUrl: string
66
83
  condition:
@@ -73,14 +90,50 @@ interface SpawnEntityOptions {
73
90
  debounceMs?: number
74
91
  timeoutMs?: number
75
92
  includeResponse?: boolean
93
+ manifestKey?: string
94
+ }
95
+ }
96
+ ```
97
+
98
+ ### forkEntity
99
+
100
+ `forkEntity()` wraps `POST /_electric/entities/<type>/<id>/fork` and creates a new entity from a source entity's latest completed run:
101
+
102
+ ```ts
103
+ const fork = await client.forkEntity({
104
+ sourceEntityUrl: "/horton/onboarding",
105
+ instanceId: "onboarding-variant",
106
+ initialMessage: { text: "Try a different approach." },
107
+ tags: { branch: "variant" },
108
+ })
109
+
110
+ console.log(fork.entityUrl)
111
+ ```
112
+
113
+ ```ts
114
+ interface ForkEntityOptions {
115
+ sourceEntityUrl: string
116
+ instanceId?: string
117
+ parent?: string
118
+ wake?: {
119
+ subscriberUrl: string
120
+ condition: RegisterWakeOptions["condition"]
121
+ debounceMs?: number
122
+ timeoutMs?: number
123
+ includeResponse?: boolean
124
+ manifestKey?: string
76
125
  }
126
+ initialMessage?: unknown
127
+ tags?: Record<string, string>
77
128
  }
78
129
  ```
79
130
 
80
- ### getEntityInfo
131
+ `initialMessage` is sent after the fork has been created and dispatch subscriptions are linked, so a partial failure can leave an idle fork. Handler code should prefer `ctx.fork()` or `ctx.forkSelf()`.
132
+
133
+ ### getEntity
81
134
 
82
135
  ```ts
83
- const info = await client.getEntityInfo("/horton/onboarding")
136
+ const info = await client.getEntity("/horton/onboarding")
84
137
  // { entityUrl, entityType, streamPath }
85
138
  ```
86
139
 
@@ -98,8 +151,8 @@ Deleting an already-missing entity is treated as success.
98
151
  await client.sendEntityMessage({
99
152
  targetUrl: "/horton/onboarding",
100
153
  payload: "What changed since last time?",
101
- from: "support-ui",
102
154
  type: "user_message",
155
+ mode: "queued",
103
156
  })
104
157
  ```
105
158
 
@@ -107,13 +160,57 @@ await client.sendEntityMessage({
107
160
  interface SendEntityMessageOptions {
108
161
  targetUrl: string
109
162
  payload: unknown
110
- from?: string
111
163
  type?: string
112
164
  afterMs?: number
165
+ mode?: "immediate" | "queued" | "paused" | "steer"
166
+ position?: string
167
+ fromPrincipal?: string
168
+ fromAgent?: string
169
+ writeToken?: string
113
170
  }
114
171
  ```
115
172
 
116
- `afterMs` asks the server to deliver the message later.
173
+ `afterMs` asks the server to deliver the message later. `mode` controls how the server queues or applies the message. `fromPrincipal`, `fromAgent`, and `writeToken` are advanced fields for claim-scoped runtime writes.
174
+
175
+ ## Signals
176
+
177
+ Send lifecycle signals to an entity:
178
+
179
+ ```ts
180
+ await client.signalEntity({
181
+ entityUrl: "/horton/onboarding",
182
+ signal: "SIGINT",
183
+ reason: "User stopped the current run",
184
+ })
185
+ ```
186
+
187
+ `deleteEntity()` sends `SIGKILL` and treats an already-missing entity as success:
188
+
189
+ ```ts
190
+ await client.deleteEntity("/horton/onboarding")
191
+ ```
192
+
193
+ ## Attachments
194
+
195
+ Attachments are uploaded through entity routes, stored in private attachment streams, and referenced by manifest entries:
196
+
197
+ ```ts
198
+ const { attachment } = await client.createAttachment({
199
+ entityUrl: "/horton/onboarding",
200
+ attachment: {
201
+ bytes: imageBytes,
202
+ mimeType: "image/png",
203
+ filename: "diagram.png",
204
+ subject: { type: "inbox", key: "message-1" },
205
+ role: "input",
206
+ },
207
+ })
208
+
209
+ const bytes = await client.readAttachment({
210
+ entityUrl: "/horton/onboarding",
211
+ id: attachment.id,
212
+ })
213
+ ```
117
214
 
118
215
  ## Shared State
119
216
 
@@ -156,24 +253,69 @@ await client.registerWake({
156
253
  })
157
254
  ```
158
255
 
159
- ### registerCronSource
256
+ ### ensureCronStream
160
257
 
161
258
  ```ts
162
- const streamUrl = await client.registerCronSource(
259
+ const streamUrl = await client.ensureCronStream(
163
260
  "0 9 * * *",
164
261
  "Europe/London"
165
262
  )
166
263
  ```
167
264
 
168
- ### registerEntitiesSource
265
+ ### ensureEntitiesMembershipStream
169
266
 
170
267
  ```ts
171
- const source = await client.registerEntitiesSource({ project: "docs" })
268
+ const source = await client.ensureEntitiesMembershipStream({ project: "docs" })
172
269
  // { streamUrl, sourceRef }
173
270
  ```
174
271
 
175
272
  This is the lower-level operation behind observing `entities({ tags })`.
176
273
 
274
+ ### registerPgSyncSource
275
+
276
+ ```ts
277
+ const source = await client.registerPgSyncSource({
278
+ url: "http://localhost:3000/v1/shape",
279
+ table: "todos",
280
+ where: "project_id = $1",
281
+ params: ["docs"],
282
+ })
283
+ // { streamUrl, sourceRef }
284
+ ```
285
+
286
+ This is the lower-level operation behind observing `pgSync({ url, table, where, params })` sources. The server turns the Postgres shape into an Electric Agents observation stream.
287
+
288
+ Remove an entity's pg-sync observation by source reference:
289
+
290
+ ```ts
291
+ await client.removePgSyncObservation({
292
+ entityUrl: "/horton/onboarding",
293
+ sourceRef: source.sourceRef,
294
+ })
295
+ ```
296
+
297
+ ### Webhook sources
298
+
299
+ Webhook-source APIs expose webhook-backed feeds that agents can subscribe to:
300
+
301
+ ```ts
302
+ const sources = await client.listWebhookSources()
303
+
304
+ await client.subscribeToWebhookSource({
305
+ entityUrl: "/horton/onboarding",
306
+ id: "github-main",
307
+ webhookKey: "github",
308
+ bucketKey: "repo",
309
+ params: { repo: "electric-sql/electric" },
310
+ lifetime: { kind: "until_entity_stopped" },
311
+ })
312
+
313
+ await client.unsubscribeFromWebhookSource({
314
+ entityUrl: "/horton/onboarding",
315
+ id: "github-main",
316
+ })
317
+ ```
318
+
177
319
  ## Schedules
178
320
 
179
321
  Schedules are stored on an entity manifest and return the write transaction id.
@@ -202,11 +344,11 @@ await client.deleteSchedule({
202
344
 
203
345
  ## Tags
204
346
 
205
- `setTag()` and `removeTag()` are primarily for handler/runtime-owned flows that already hold the current claim-scoped write token. External clients should prefer `send()` and write only to an entity's inbox rather than writing entity state directly.
347
+ `setTag()` and `deleteTag()` are primarily for handler/runtime-owned flows that already hold the current claim-scoped write token. External clients should prefer `sendEntityMessage()` and write only to an entity's inbox rather than writing entity state directly.
206
348
 
207
349
  ```ts
208
350
  await client.setTag("/horton/onboarding", "title", "Onboarding", writeToken)
209
- await client.removeTag("/horton/onboarding", "title", writeToken)
351
+ await client.deleteTag("/horton/onboarding", "title", writeToken)
210
352
  ```
211
353
 
212
354
  ## Choosing a Client