experimental-a2 0.18.1 → 0.20.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/CHANGELOG.md +44 -0
- package/dist/actor.js +1 -1
- package/dist/{ai-BMLlI2Ff.d.ts → ai-Cz3hqVhk.d.ts} +33 -16
- package/dist/ai-Cz3hqVhk.d.ts.map +1 -0
- package/dist/ai-client.d.ts +1 -1
- package/dist/ai-client.js +1 -1
- package/dist/ai-control-CvLbW10B.js +46 -0
- package/dist/ai-control-CvLbW10B.js.map +1 -0
- package/dist/ai-fx.d.ts +1 -1
- package/dist/ai-fx.d.ts.map +1 -1
- package/dist/ai-fx.js +63 -13
- package/dist/ai-fx.js.map +1 -1
- package/dist/{ai-harness-B2suP2Qh.d.ts → ai-harness-CV6rXbB3.d.ts} +2 -2
- package/dist/ai-harness-CV6rXbB3.d.ts.map +1 -0
- package/dist/{ai-harness-BuLh_EYi.js → ai-harness-Clbg5ei7.js} +88 -15
- package/dist/ai-harness-Clbg5ei7.js.map +1 -0
- package/dist/{ai-D6mGlO8j.js → ai-mMiWul1T.js} +116 -77
- package/dist/ai-mMiWul1T.js.map +1 -0
- package/dist/ai-pi.d.ts +1 -1
- package/dist/ai-pi.d.ts.map +1 -1
- package/dist/ai-pi.js +56 -11
- package/dist/ai-pi.js.map +1 -1
- package/dist/ai-react.d.ts +1 -1
- package/dist/ai-react.js +1 -1
- package/dist/ai-server.d.ts +2 -2
- package/dist/ai-server.d.ts.map +1 -1
- package/dist/ai-server.js +123 -53
- package/dist/ai-server.js.map +1 -1
- package/dist/ai.d.ts +2 -2
- package/dist/ai.js +1 -1
- package/dist/internal-CscSY7K_.js.map +1 -1
- package/dist/scheduler-qstash.d.ts.map +1 -1
- package/dist/scheduler-qstash.js +5 -5
- package/dist/scheduler-qstash.js.map +1 -1
- package/dist/scheduler-vercel.d.ts.map +1 -1
- package/dist/scheduler-vercel.js +5 -7
- package/dist/scheduler-vercel.js.map +1 -1
- package/dist/server-DYAATFuT.d.ts.map +1 -1
- package/dist/{server-D-Y4pqwu.js → server-qyb5aJJG.js} +39 -25
- package/dist/server-qyb5aJJG.js.map +1 -0
- package/dist/server.js +1 -1
- package/dist/session-clear-DGI4jTQ5.js.map +1 -1
- package/dist/store-postgres.d.ts.map +1 -1
- package/dist/store-postgres.js +38 -136
- package/dist/store-postgres.js.map +1 -1
- package/docs/01-quickstart.mdx +7 -4
- package/docs/actors/01-introduction.mdx +1 -1
- package/docs/actors/meta.ts +1 -1
- package/docs/agents/02-tools.mdx +1 -1
- package/docs/agents/03-conversations.mdx +26 -14
- package/docs/agents/04-harnesses.mdx +51 -5
- package/docs/agents/meta.ts +1 -1
- package/docs/client/meta.ts +1 -1
- package/docs/contract/03-sessions.mdx +2 -2
- package/docs/index.mdx +2 -2
- package/docs/reference/01-core.mdx +8 -8
- package/docs/reference/02-server.mdx +3 -3
- package/docs/reference/04-ai.mdx +36 -19
- package/docs/reference/05-telemetry.mdx +132 -0
- package/docs/reference/06-errors.mdx +1 -1
- package/docs/reference/meta.ts +1 -1
- package/docs/schedulers/01-overview.mdx +112 -0
- package/docs/schedulers/02-vercel-queues.mdx +87 -0
- package/docs/schedulers/03-qstash.mdx +123 -0
- package/docs/schedulers/meta.ts +1 -0
- package/docs/server/01-handlers.mdx +29 -2
- package/docs/server/02-timers.mdx +3 -3
- package/docs/server/06-guarantees.mdx +6 -3
- package/docs/stores/01-overview.mdx +37 -0
- package/docs/stores/02-redis.mdx +49 -0
- package/docs/stores/03-redis-http.mdx +56 -0
- package/docs/stores/04-postgres.mdx +121 -0
- package/docs/stores/05-sqlite.mdx +34 -0
- package/docs/stores/06-memory.mdx +22 -0
- package/docs/stores/meta.ts +1 -0
- package/examples/playground/DEPLOY.md +19 -5
- package/examples/playground/app/a0/a0.css +244 -5
- package/examples/playground/app/a0/app-view.tsx +2 -1
- package/examples/playground/app/a0/apps/[appId]/route.ts +6 -10
- package/examples/playground/app/a0/attachments.ts +133 -0
- package/examples/playground/app/a0/chat.test.tsx +595 -0
- package/examples/playground/app/a0/chat.tsx +623 -63
- package/examples/playground/app/a0/context-menu.tsx +38 -11
- package/examples/playground/app/a0/model.test.ts +28 -0
- package/examples/playground/app/a0/model.ts +30 -0
- package/examples/playground/app/a0/server.ts +5 -0
- package/examples/playground/app/a0/shell.tsx +1 -1
- package/examples/playground/app/a0/uploads/route.test.ts +59 -0
- package/examples/playground/app/a0/uploads/route.ts +29 -0
- package/examples/playground/app/agent/[agentId]/agent-client.tsx +2 -1
- package/examples/playground/app/agent/[agentId]/agent-queue.test.tsx +116 -89
- package/examples/playground/app/agent/[agentId]/compaction-panel.test.tsx +107 -76
- package/examples/playground/app/agent/ingress.test.ts +41 -17
- package/examples/playground/app/agent/model-input.test.tsx +92 -43
- package/examples/playground/app/agent/model.test.ts +12 -3
- package/examples/playground/app/chat/[chatId]/agent-state.test.ts +1 -5
- package/examples/playground/app/chat/[chatId]/agent-stream-drawer.test.tsx +2 -2
- package/examples/playground/app/chat/server.test.ts +38 -65
- package/examples/playground/app/components/activity-history.test.tsx +60 -18
- package/examples/playground/app/components/fast-link.test.tsx +3 -5
- package/examples/playground/app/initial-state.test.tsx +86 -45
- package/examples/playground/next.config.ts +7 -4
- package/examples/playground/package.json +4 -1
- package/examples/playground/test/agent.ts +35 -0
- package/examples/playground/test/model.ts +149 -0
- package/examples/playground/test/serve.ts +184 -0
- package/examples/playground/test/server-only.ts +1 -0
- package/examples/playground/vitest.config.ts +27 -0
- package/package.json +3 -1
- package/sql/postgres/notifications.sql +24 -0
- package/sql/postgres/schema.sql +137 -0
- package/src/ai-agent.ts +9 -4
- package/src/ai-client-state.ts +21 -9
- package/src/ai-context-schema.ts +28 -4
- package/src/ai-context.ts +83 -3
- package/src/ai-control-server.ts +70 -43
- package/src/ai-control-state.ts +3 -4
- package/src/ai-control.ts +59 -10
- package/src/ai-coordinator.ts +7 -6
- package/src/ai-fx.ts +95 -23
- package/src/ai-harness.ts +171 -29
- package/src/ai-message-shape.ts +41 -0
- package/src/ai-pi.ts +88 -32
- package/src/ai-stored-state.ts +1 -3
- package/src/ai.ts +116 -77
- package/src/errors.ts +1 -1
- package/src/internal.ts +2 -1
- package/src/libfx.d.ts +8 -1
- package/src/postgres-notifications.ts +0 -22
- package/src/postgres-resources.ts +6 -9
- package/src/postgres-schema.ts +80 -0
- package/src/scheduler-qstash.ts +12 -5
- package/src/scheduler-vercel.ts +11 -16
- package/src/server.ts +66 -32
- package/src/store-postgres.ts +5 -123
- package/dist/ai-BMLlI2Ff.d.ts.map +0 -1
- package/dist/ai-D6mGlO8j.js.map +0 -1
- package/dist/ai-harness-B2suP2Qh.d.ts.map +0 -1
- package/dist/ai-harness-BuLh_EYi.js.map +0 -1
- package/dist/server-D-Y4pqwu.js.map +0 -1
- package/docs/reference/05-adapters.mdx +0 -351
- package/docs/server/05-production.mdx +0 -323
- package/examples/playground/app/a0/no-photon.cjs +0 -4
package/docs/01-quickstart.mdx
CHANGED
|
@@ -60,7 +60,7 @@ export const ordersServer = createServer({
|
|
|
60
60
|
|
|
61
61
|
Development needs no database setup: events go to SQLite at `.a2/dev.db` and
|
|
62
62
|
survive restarts. Tests get an in-memory store. Production needs an explicit
|
|
63
|
-
store; see [
|
|
63
|
+
store; see [Stores](/stores/overview).
|
|
64
64
|
|
|
65
65
|
## 4. Append the first event
|
|
66
66
|
|
|
@@ -169,11 +169,14 @@ You have a validated write path, a handler, a log you can read, and a view
|
|
|
169
169
|
folded from it. The rest of these docs apply the same two moves, append an
|
|
170
170
|
event and react to it, to timers, cancellation, live UIs, and production.
|
|
171
171
|
|
|
172
|
-
<CardGroup cols={
|
|
172
|
+
<CardGroup cols={3}>
|
|
173
173
|
<Card title="Handlers" href="/server/handlers" icon="file">
|
|
174
174
|
Context, chaining, and re-runs.
|
|
175
175
|
</Card>
|
|
176
|
-
<Card title="
|
|
177
|
-
|
|
176
|
+
<Card title="Stores" href="/stores/overview" icon="rocket">
|
|
177
|
+
Where sessions live in production.
|
|
178
|
+
</Card>
|
|
179
|
+
<Card title="Schedulers" href="/schedulers/overview" icon="clock">
|
|
180
|
+
Retries and timers on a queue.
|
|
178
181
|
</Card>
|
|
179
182
|
</CardGroup>
|
|
@@ -57,7 +57,7 @@ handler's first lines and throw `NonRetriableError` on bad shapes.
|
|
|
57
57
|
|
|
58
58
|
`actor` also accepts `createServer`'s `store`, `scheduler`, and
|
|
59
59
|
`telemetry` options, with the same development default store; see
|
|
60
|
-
[
|
|
60
|
+
[Stores](/stores/overview) and [Schedulers](/schedulers/overview). A protocol can also declare
|
|
61
61
|
[`presence`](/actors/routes#presence).
|
|
62
62
|
|
|
63
63
|
## Call it
|
package/docs/actors/meta.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export default { order:
|
|
1
|
+
export default { order: 8 }
|
package/docs/agents/02-tools.mdx
CHANGED
|
@@ -81,7 +81,7 @@ for the event payload.
|
|
|
81
81
|
|
|
82
82
|
This tool schedules a reminder as a new user message. `ctx.session` belongs to
|
|
83
83
|
the executing agent, so `schedule()` uses that agent's scheduler: add
|
|
84
|
-
`setReminder` to its `tools` and configure a [scheduler](/
|
|
84
|
+
`setReminder` to its `tools` and configure a [scheduler](/schedulers/overview).
|
|
85
85
|
Naming the timer after `toolCallId` and the message after the triggering
|
|
86
86
|
event's ID makes a retry converge on the same timer and message, even if a
|
|
87
87
|
provider reuses tool-call IDs. The six-day maximum is Vercel Queues'
|
|
@@ -393,7 +393,7 @@ retries, and so does one where A2 itself fails while or just after the tool
|
|
|
393
393
|
runs. Every other tool uses the default, `'rerun'`. See
|
|
394
394
|
[`agent()`](/reference/ai#agentoptions) for the exact cases.
|
|
395
395
|
|
|
396
|
-
Configure a [production scheduler](/
|
|
396
|
+
Configure a [production scheduler](/schedulers/overview) as for any A2 server. It
|
|
397
397
|
recovers an incomplete generation after the original serverless invocation
|
|
398
398
|
disappears.
|
|
399
399
|
|
|
@@ -446,9 +446,11 @@ export function Queue({ sessionId }: { sessionId: string }) {
|
|
|
446
446
|
```
|
|
447
447
|
|
|
448
448
|
`sendMessage()` puts a message in the durable inbox, `state.inbox.items`. The
|
|
449
|
-
controller admits one input at a time
|
|
450
|
-
|
|
451
|
-
|
|
449
|
+
controller admits one input at a time and adds it to `state.messages`; pending
|
|
450
|
+
inputs never enter the model prompt, and an edit after admission is rejected.
|
|
451
|
+
The log stores a message once, in the command that sent it or the edit that
|
|
452
|
+
last changed it. `state.active` is
|
|
453
|
+
the logical turn: its ID, its `input`, and `phase`. The turn ID holds
|
|
452
454
|
across model steps, tools, and a pause. Stop ends the turn and admits the next
|
|
453
455
|
input when admission is running.
|
|
454
456
|
|
|
@@ -466,15 +468,20 @@ replay cannot recreate the prompt. Removing an input that is no longer pending
|
|
|
466
468
|
(admitted, removed, or never queued) returns `not-pending`, as does an edit that
|
|
467
469
|
loses to admission; a Stop aimed at a finished turn returns `stale-turn`.
|
|
468
470
|
|
|
469
|
-
`steer()` puts new input
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
471
|
+
`steer()` puts new input right after the turn's earlier steering, or first
|
|
472
|
+
when there is none, so steering hands off in the order it was sent. If the observed turn
|
|
473
|
+
is still active, it finishes its current model response and that response's
|
|
474
|
+
tools, including authorized deferred provider results, then hands off before
|
|
475
|
+
another model step. Everything steering that turn enters together as one new
|
|
476
|
+
turn: the earlier messages as context and the last one generating, so the model
|
|
477
|
+
answers them once. Output produced after the click stays, and unanswered
|
|
478
|
+
approval and input requests close, so the unapproved tool never runs; a denied
|
|
479
|
+
approval never blocks the handoff. With no active turn, `steer()` sends like
|
|
480
|
+
`sendMessage()`.
|
|
481
|
+
|
|
482
|
+
`queue.sendNow()` makes the same handoff with a queued message, after the
|
|
483
|
+
steering sent before it, keeping its ID and latest accepted content and
|
|
484
|
+
promoting passive user input to generation.
|
|
478
485
|
Non-user context returns `not-user-input`, and a stale observed turn returns
|
|
479
486
|
`stale-turn` without changing the queue. Send now uses the accepted content, so
|
|
480
487
|
save editor text first. `steer()` and `queue.sendNow()` both preserve pause: a
|
|
@@ -483,7 +490,8 @@ immediate interruption.
|
|
|
483
490
|
|
|
484
491
|
A queued item's optional `afterStepOf` records the observed turn for steering.
|
|
485
492
|
Editing keeps it, removing the item cancels it, and queue order decides which
|
|
486
|
-
|
|
493
|
+
inputs are admitted at the handoff: the run at the front that steers the same
|
|
494
|
+
turn. See [`inputs`](/reference/ai#inputs) for the
|
|
487
495
|
full handoff rules.
|
|
488
496
|
|
|
489
497
|
### Input identity
|
|
@@ -531,6 +539,10 @@ finish. `pause({ when: 'now' })` also cancels current model and tool work;
|
|
|
531
539
|
`state.active.phase` is `pausing` while old work settles, then `paused`.
|
|
532
540
|
`resume()` resumes retained work before admitting another input.
|
|
533
541
|
|
|
542
|
+
A turn paused with Pause now keeps its steering until Resume. Every
|
|
543
|
+
`steer()` or `queue.sendNow()` sent while it's paused targets it, and on Resume
|
|
544
|
+
they enter together as one turn; the paused turn's own work does not resume.
|
|
545
|
+
|
|
534
546
|
Resume starts a fresh model request from durable state; it does not resume the
|
|
535
547
|
provider's old stream or undo completed tool effects. While paused, accepted
|
|
536
548
|
tool calls are retained with their outcomes recorded, and the turn holds no
|
|
@@ -110,17 +110,63 @@ Both harnesses call models through Vercel AI Gateway. `model` is a Gateway
|
|
|
110
110
|
model ID, or [`ModelSettings`](/reference/ai#modelsettings) with one, resolved
|
|
111
111
|
for each step like any agent's model; `pi()` takes the Gateway models Pi's own
|
|
112
112
|
catalog lists. `reasoning` becomes FX's effort or Pi's thinking level, and
|
|
113
|
-
`speed: 'fast'` asks
|
|
114
|
-
`AI_GATEWAY_API_KEY`, or from Vercel's OIDC
|
|
115
|
-
|
|
116
|
-
|
|
113
|
+
`speed: 'fast'` asks AI Gateway for the model's faster tier under either
|
|
114
|
+
harness. Credentials come from `AI_GATEWAY_API_KEY`, or from Vercel's OIDC
|
|
115
|
+
token on Vercel. Seeded assistant messages do not reach the harness.
|
|
116
|
+
|
|
117
|
+
## Images and files
|
|
118
|
+
|
|
119
|
+
The harness reads each user message's text and its image files. Neither FX nor
|
|
120
|
+
Pi takes URLs, so A2 fetches each image from its `https:` or `data:` URL and
|
|
121
|
+
hands the harness its bytes. The fetch goes through the
|
|
122
|
+
AI SDK's download, which refuses private addresses, checks each redirect, and
|
|
123
|
+
stops past 32 MiB of one message's files. Each step of a response replays its
|
|
124
|
+
turn, so a process keeps the images it fetched instead of fetching them again.
|
|
125
|
+
|
|
126
|
+
A file that isn't an image, or that A2 can't fetch, fails the turn with an
|
|
127
|
+
error that names it. So does a steer with files under FX, which steers with
|
|
128
|
+
text only. The harness applies its own rules to the images it gets: FX fails a
|
|
129
|
+
turn whose model doesn't take images, and Pi resizes them. Every step sends the
|
|
130
|
+
conversation so far, files and all, so a file that fails one turn fails the
|
|
131
|
+
turns after it too.
|
|
132
|
+
|
|
133
|
+
A2 fetches whatever URL a user message names, so check file URLs on the way
|
|
134
|
+
in. An agent's `messageSchema` can require them to point at your storage:
|
|
135
|
+
|
|
136
|
+
```ts server/reviewer.ts
|
|
137
|
+
import type { UIMessage } from 'ai'
|
|
138
|
+
import { agent } from 'experimental-a2/ai/server'
|
|
139
|
+
import { fx } from 'experimental-a2/ai/fx'
|
|
140
|
+
import { z } from 'zod'
|
|
141
|
+
|
|
142
|
+
// your storage's public URL:
|
|
143
|
+
const uploads = 'https://uploads.example.com/'
|
|
144
|
+
|
|
145
|
+
export const reviewer = agent({
|
|
146
|
+
name: 'reviewer',
|
|
147
|
+
harness: fx(),
|
|
148
|
+
model: 'anthropic/claude-sonnet-5.5',
|
|
149
|
+
instructions: 'Review the screenshots the user sends. Keep replies short.',
|
|
150
|
+
messageSchema: z
|
|
151
|
+
.custom<UIMessage>()
|
|
152
|
+
.refine(
|
|
153
|
+
(message) =>
|
|
154
|
+
message.role !== 'user' ||
|
|
155
|
+
message.parts.every(
|
|
156
|
+
(part) => part.type !== 'file' || part.url.startsWith(uploads),
|
|
157
|
+
),
|
|
158
|
+
"A user's files must be uploads",
|
|
159
|
+
),
|
|
160
|
+
})
|
|
161
|
+
```
|
|
117
162
|
|
|
118
163
|
## What the harness owns
|
|
119
164
|
|
|
120
165
|
The harness owns its loop, its prompt, and its compaction, so `generation`,
|
|
121
166
|
`generate`, `compaction`, and `messageMetadata` exist only in
|
|
122
167
|
`{ type: 'ai-sdk' }`. A harness runs its tools without approval, so a tool that
|
|
123
|
-
sets `needsApproval` is rejected.
|
|
168
|
+
sets `needsApproval` is rejected. It hands the engine only tools A2 runs, so a
|
|
169
|
+
provider-defined tool, such as a provider's web search, is rejected too. `maxSteps` limits the model calls in one
|
|
124
170
|
response under every harness. Steering enters at the next step, as with any
|
|
125
171
|
agent: the harness sees the steered message before its next model call.
|
|
126
172
|
|
package/docs/agents/meta.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export default { order:
|
|
1
|
+
export default { order: 7 }
|
package/docs/client/meta.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export default { order:
|
|
1
|
+
export default { order: 6 }
|
|
@@ -70,7 +70,7 @@ authorize your own route before calling it.
|
|
|
70
70
|
|
|
71
71
|
### Scheduling a clear
|
|
72
72
|
|
|
73
|
-
With a [scheduler](/
|
|
73
|
+
With a [scheduler](/schedulers/overview) configured, a handler can schedule a
|
|
74
74
|
clear. This one clears an idle cart a day after its last activity:
|
|
75
75
|
|
|
76
76
|
```ts server/carts.ts
|
|
@@ -109,7 +109,7 @@ append returned.
|
|
|
109
109
|
Delivery runs the same conditional clear and finishes the deletion before it
|
|
110
110
|
acknowledges, so provider retries complete an interrupted delete. No event is
|
|
111
111
|
appended. The clear happens at delivery, not at an exact deadline, within
|
|
112
|
-
provider limits: Vercel Queues accepts delays up to
|
|
112
|
+
provider limits: Vercel Queues accepts delays up to seven days.
|
|
113
113
|
|
|
114
114
|
## Sessions and your database
|
|
115
115
|
|
package/docs/index.mdx
CHANGED
|
@@ -156,7 +156,7 @@ from the log.
|
|
|
156
156
|
<Card title="Timers" href="/server/timers" icon="clock">
|
|
157
157
|
Schedule an event for later.
|
|
158
158
|
</Card>
|
|
159
|
-
<Card title="Production" href="/
|
|
159
|
+
<Card title="Production" href="/stores/overview" icon="rocket">
|
|
160
160
|
A store and a scheduler.
|
|
161
161
|
</Card>
|
|
162
162
|
</CardGroup>
|
|
@@ -212,7 +212,7 @@ A durable store backend, such as Postgres or Redis. Add a queue-backed scheduler
|
|
|
212
212
|
when the contract has handlers or calls `session.schedule`. A history-only
|
|
213
213
|
contract with no timers needs no reaction infrastructure. Development needs
|
|
214
214
|
neither: SQLite appears under `.a2/` and state survives restarts. See
|
|
215
|
-
[
|
|
215
|
+
[Stores](/stores/overview) and [Schedulers](/schedulers/overview).
|
|
216
216
|
|
|
217
217
|
</details>
|
|
218
218
|
|
|
@@ -17,15 +17,15 @@ description: "Every entry point, and the isomorphic `experimental-a2` core: cont
|
|
|
17
17
|
| `experimental-a2/ai/pi` | `pi`: the Pi harness for `agent()` | `ai`, `@earendil-works/pi-coding-agent`, `@earendil-works/pi-ai` |
|
|
18
18
|
| `experimental-a2/ai/client` | `createAgentClient`: contract-free agent client | `ai` |
|
|
19
19
|
| `experimental-a2/ai/react` | `useAgent` | `ai`, `react` |
|
|
20
|
-
| [`experimental-a2/store-postgres`](/
|
|
21
|
-
| [`experimental-a2/store-redis`](/
|
|
22
|
-
| [`experimental-a2/store-redis-http`](/
|
|
23
|
-
| `experimental-a2/store-sqlite` | SQLite store backend | none |
|
|
24
|
-
| `experimental-a2/store-memory` | in-memory store backend | none |
|
|
25
|
-
| `experimental-a2/scheduler-vercel` | `vercelQueues`: recovery and timers | `@vercel/queue` |
|
|
26
|
-
| `experimental-a2/scheduler-qstash` | `qstash`: signed recovery and timers | `@upstash/qstash` |
|
|
20
|
+
| [`experimental-a2/store-postgres`](/stores/postgres) | `postgres`: Postgres store backend | `pg` (or inject a client) |
|
|
21
|
+
| [`experimental-a2/store-redis`](/stores/redis) | `redis`: Redis Streams store backend, push-native streaming | `ioredis` (or inject a client) |
|
|
22
|
+
| [`experimental-a2/store-redis-http`](/stores/redis-http) | `redisHttp`: the same Redis store over provider REST APIs (Upstash) | none |
|
|
23
|
+
| [`experimental-a2/store-sqlite`](/stores/sqlite) | SQLite store backend | none |
|
|
24
|
+
| [`experimental-a2/store-memory`](/stores/memory) | in-memory store backend | none |
|
|
25
|
+
| [`experimental-a2/scheduler-vercel`](/schedulers/vercel-queues) | `vercelQueues`: recovery and timers | `@vercel/queue` |
|
|
26
|
+
| [`experimental-a2/scheduler-qstash`](/schedulers/qstash) | `qstash`: signed recovery and timers | `@upstash/qstash` |
|
|
27
27
|
| `experimental-a2/cache-indexeddb` | `indexedDb` browser cache | none |
|
|
28
|
-
| `experimental-a2/otel` | `otel` telemetry adapter | `@opentelemetry/api` |
|
|
28
|
+
| [`experimental-a2/otel`](/reference/telemetry) | `otel` telemetry adapter | `@opentelemetry/api` |
|
|
29
29
|
|
|
30
30
|
`experimental-a2` imports none of the backends, and a browser-bundle test in CI
|
|
31
31
|
enforces it. Importing a backend's entry point, such as
|
|
@@ -438,7 +438,7 @@ Custom scheduler adapters implement two methods:
|
|
|
438
438
|
|
|
439
439
|
Application routes call `scheduler.handler(...servers)` on the configured
|
|
440
440
|
adapter. See
|
|
441
|
-
[
|
|
441
|
+
[Schedulers](/schedulers/overview).
|
|
442
442
|
|
|
443
443
|
`SchedulerTask` is `SchedulerDrainTask | SchedulerAppendTask |
|
|
444
444
|
SchedulerClearTask`. These task types, plus `ScheduledEvent`, are exported
|
|
@@ -522,7 +522,7 @@ session.append.dispatch(
|
|
|
522
522
|
|
|
523
523
|
Commits the same atomic batch, then sends pending work to the configured
|
|
524
524
|
scheduler instead of starting an inline drain. See
|
|
525
|
-
[Dispatch in a fresh invocation](/
|
|
525
|
+
[Dispatch in a fresh invocation](/schedulers/overview#dispatch-in-a-fresh-invocation).
|
|
526
526
|
|
|
527
527
|
- Awaits acceptance of an immediate scheduler message. A batch that leaves no
|
|
528
528
|
pending handler work sends none.
|
|
@@ -681,7 +681,7 @@ type DeadLetter = Event & {
|
|
|
681
681
|
```
|
|
682
682
|
|
|
683
683
|
The session's dead-lettered events, oldest first, each with why it failed. See
|
|
684
|
-
[
|
|
684
|
+
[Resolve a dead letter](/server/handlers#resolve-a-dead-letter).
|
|
685
685
|
|
|
686
686
|
- `failureCount` counts the attempts that spent its budget: caught failures,
|
|
687
687
|
and attempts that ended before they finished. `attemptCount` counts every
|
package/docs/reference/04-ai.mdx
CHANGED
|
@@ -39,8 +39,8 @@ state: serve it with `agent({ contract })` and use it with
|
|
|
39
39
|
through.
|
|
40
40
|
- An `initialState` key that `AIState` also has throws a `TypeError` and fails
|
|
41
41
|
to typecheck.
|
|
42
|
-
- The reducer name is `AI_STATE_REDUCER` (`a2.ai.state.
|
|
43
|
-
`reducer`, else `${reducer.name}+a2.ai.state.
|
|
42
|
+
- The reducer name is `AI_STATE_REDUCER` (`a2.ai.state.v27`) without
|
|
43
|
+
`reducer`, else `${reducer.name}+a2.ai.state.v27`, so checkpoints advance when
|
|
44
44
|
either fold changes. `AgentReducer` types that name (so
|
|
45
45
|
[`createAgentClient()`](#createagentclientoptions) accepts a plain
|
|
46
46
|
`agentContract()`) and carries `X` to `agent()`'s resolvers.
|
|
@@ -61,9 +61,9 @@ even when `M` declares its own data parts.
|
|
|
61
61
|
|
|
62
62
|
### `AIMessageOf<Events>`
|
|
63
63
|
|
|
64
|
-
The `UIMessage` type a contract's `ai.
|
|
65
|
-
`AIMessageOf<typeof contract.events>`; `UIMessage` otherwise.
|
|
66
|
-
messages with it.
|
|
64
|
+
The `UIMessage` type a contract's input commands (`ai.control.requested`)
|
|
65
|
+
carry, such as `AIMessageOf<typeof contract.events>`; `UIMessage` otherwise.
|
|
66
|
+
`agent()` types messages with it.
|
|
67
67
|
|
|
68
68
|
### `handlerContext(contract?)`
|
|
69
69
|
|
|
@@ -105,13 +105,13 @@ Pure typed builders of `ai.control.requested` commands, for `append()` and
|
|
|
105
105
|
| `inputs.requestInput(request)` | requests application input from trusted server code |
|
|
106
106
|
| `inputs.retry(options)` | retries a failed response |
|
|
107
107
|
| `inputs.stop({ turnId, lastSeenIndex? })` | ends the observed turn and advances when admission is open |
|
|
108
|
-
| `inputs.steer({ turnId, message, lastSeenIndex? })` |
|
|
108
|
+
| `inputs.steer({ turnId, message, lastSeenIndex? })` | queues the new input right after the observed turn's earlier steering, or first, for a handoff after its current step; the turn's steering enters together, its last generating input starting the turn |
|
|
109
109
|
| `inputs.queue.edit({ message })` | changes a pending input; its ID stays fixed |
|
|
110
110
|
| `inputs.queue.move({ inputId, beforeId })` | moves pending input before an ID; `null` means the end |
|
|
111
111
|
| `inputs.queue.remove({ inputId })` | removes pending input; its submission stays in the log |
|
|
112
|
-
| `inputs.queue.sendNow({ inputId, turnId, lastSeenIndex? })` | promotes an existing queued input for a handoff after the current step; preserves pause |
|
|
112
|
+
| `inputs.queue.sendNow({ inputId, turnId, lastSeenIndex? })` | promotes an existing queued input, after the observed turn's earlier steering, for a handoff after the current step; preserves pause |
|
|
113
113
|
| `inputs.pause({ when: 'now' \| 'after-turn' })` | pauses execution now or inbox admission after the turn |
|
|
114
|
-
| `inputs.resume()` | resumes retained work, then inbox admission |
|
|
114
|
+
| `inputs.resume()` | resumes retained work, then inbox admission; a turn paused now with steering hands it off instead |
|
|
115
115
|
| `inputs.toolResult(result)` | records a deferred provider-executed tool result from trusted server code |
|
|
116
116
|
| `inputs.interrupt(options)` | interrupts a specific generation/request owner |
|
|
117
117
|
|
|
@@ -174,7 +174,9 @@ Pure typed builders of `ai.control.requested` commands, for `append()` and
|
|
|
174
174
|
- **Interruption.** `ai.message.interrupted` is `{ messageId, requestId,
|
|
175
175
|
reason?, lastSeenIndex? }` or `{ messageId, generationId, reason?,
|
|
176
176
|
lastSeenIndex? }`: copy `activeRequestId` before the generation starts,
|
|
177
|
-
`activeGeneration.generationId` after.
|
|
177
|
+
`activeGeneration.generationId` after. A2 writes the reason
|
|
178
|
+
`'steered after step'` itself when steering ends a response, so an
|
|
179
|
+
interruption you send can't use it. A request-owned interruption survives
|
|
178
180
|
that request starting first. An older or mismatched message/owner pair is a
|
|
179
181
|
no-op. An accepted one fences its owner for good: later events stay in raw
|
|
180
182
|
history but cannot alter `AIState`, satisfy pending work, or reactivate the
|
|
@@ -249,7 +251,7 @@ extra I/O or raw-history reads.
|
|
|
249
251
|
win.
|
|
250
252
|
- Steer, and Send now against an active turn, show in `state.messages` after
|
|
251
253
|
the current response, not the inbox, until admission; they stay out of the
|
|
252
|
-
prompt. Several steering inputs show in
|
|
254
|
+
prompt. Several steering inputs show in the order they were sent,
|
|
253
255
|
and the display survives receipt and hydration, including while paused.
|
|
254
256
|
- A pending Stop clears the displayed `state.active`, generation, and pending
|
|
255
257
|
approvals and inputs, previews an idle status (paused when the inbox is), and
|
|
@@ -274,8 +276,8 @@ events. Encoding is internal; no event or tool payload is truncated.
|
|
|
274
276
|
| Field | What it holds |
|
|
275
277
|
| --- | --- |
|
|
276
278
|
| `messages` | [`TranscriptMessage<M>`](#transcriptmessagem) values; server-side, without pending inputs |
|
|
277
|
-
| `inbox` | `{ paused, items }`; items are `{ id, message, generate, afterStepOf? }`, where `afterStepOf` is an observed steering turn that hands off after its current step (removal cancels it, admission consumes it) |
|
|
278
|
-
| `active` | `null` or `{ turnId, input, phase }
|
|
279
|
+
| `inbox` | `{ paused, items }`; items are `{ id, role, message, generate, afterStepOf? }`, where `afterStepOf` is an observed steering turn that hands off after its current step (removal cancels it, admission consumes it) |
|
|
280
|
+
| `active` | `null` or `{ turnId, input, phase }`, where `input` is the admitted input's `{ id, role, generate }` |
|
|
279
281
|
| `status` | generation status |
|
|
280
282
|
| `starting` | a speculative optimistic turn |
|
|
281
283
|
| `receipt` | the latest user command receipt |
|
|
@@ -311,7 +313,10 @@ See [What happens after a send](/agents/introduction#what-happens-after-a-send).
|
|
|
311
313
|
|
|
312
314
|
`deriveUIMessages()` folds a raw event history into `TranscriptMessage<M>[]`.
|
|
313
315
|
`reduceAIState()` is the event-at-a-time form the standard reducer uses. Both
|
|
314
|
-
project the same chunk-only protocol synchronously.
|
|
316
|
+
project the same chunk-only protocol synchronously. A message's content is in
|
|
317
|
+
the command that brought it in, so a fold needs each `ai.message.created`'s
|
|
318
|
+
command before it; one without throws. Only A2's control lane writes
|
|
319
|
+
`ai.message.created`.
|
|
315
320
|
|
|
316
321
|
## `experimental-a2/ai/server`
|
|
317
322
|
|
|
@@ -357,7 +362,7 @@ stub.
|
|
|
357
362
|
| `toolLane` | one lane for every local tool execution, one at a time in admission order; model steps keep their own lane |
|
|
358
363
|
| `toolRecovery` | `'rerun'` (default) or `'report'` per tool, the harness's own tools included |
|
|
359
364
|
| `handlers` | application event handlers; naming an event the agent handles throws a `TypeError` |
|
|
360
|
-
| `store`, `scheduler`, `telemetry` | as for any A2 server; `telemetry` covers Core and AI in one trace ([span catalogue](/reference/
|
|
365
|
+
| `store`, `scheduler`, `telemetry` | as for any A2 server; `telemetry` covers Core and AI in one trace ([span catalogue](/reference/telemetry#the-span-catalogue)) |
|
|
361
366
|
|
|
362
367
|
**Contracts.** A contract not built by `agentContract()` throws a `TypeError`
|
|
363
368
|
and fails to typecheck (serve custom contracts with `createServer()`). Messages
|
|
@@ -525,7 +530,11 @@ name the harness's own tools, such as Pi's `bash`. `model` is resolved per step
|
|
|
525
530
|
as for any agent. `instructions` and `tools` reach the harness; how they
|
|
526
531
|
combine with its own prompt and tools depends on the harness. Tool approval
|
|
527
532
|
policy lives in `harness.generation`, so a harness agent's tools run without
|
|
528
|
-
approval, and a tool that sets `needsApproval` is rejected.
|
|
533
|
+
approval, and a tool that sets `needsApproval` is rejected. So is a
|
|
534
|
+
provider-defined tool (`type: 'provider'`), which the engine can't run. A user
|
|
535
|
+
message reaches the harness as its text and its images, which A2 fetches from
|
|
536
|
+
their `https:` or `data:` URLs; any other file fails the turn. See
|
|
537
|
+
[Images and files](/agents/harnesses#images-and-files).
|
|
529
538
|
|
|
530
539
|
A harness resumes after a crash at the interrupted step. Finished model calls
|
|
531
540
|
are not made again and finished tool calls do not run again. A2 rebuilds the
|
|
@@ -589,8 +598,16 @@ continue a response, A2 restores FX's latest checkpoint and replays the
|
|
|
589
598
|
response so far from the log: recorded model responses and tool results answer
|
|
590
599
|
the kernel without network calls or tool runs, then FX makes its next model
|
|
591
600
|
call. Replay grows with the response, roughly 0.1 to 0.5 seconds for one of 80
|
|
592
|
-
to 200 tool calls.
|
|
593
|
-
|
|
601
|
+
to 200 tool calls. When libfx can't take a checkpoint, A2 replays from an
|
|
602
|
+
earlier one.
|
|
603
|
+
|
|
604
|
+
FX's model calls carry the same [AI Gateway](#agentoptions) defaults as the
|
|
605
|
+
AI SDK harness: `caching: 'auto'`, under any Gateway options FX sets itself,
|
|
606
|
+
and the session's stable `x-session-affinity` header in place of FX's own,
|
|
607
|
+
which changes every time A2 rebuilds the kernel. A2 leaves every other byte of
|
|
608
|
+
the request as FX wrote it, so it adds caching only where FX's own options come
|
|
609
|
+
last in the request, as libfx writes them; otherwise the request goes
|
|
610
|
+
unchanged.
|
|
594
611
|
|
|
595
612
|
Credentials come from `AI_GATEWAY_API_KEY`, or Vercel's OIDC token on Vercel.
|
|
596
613
|
This entry point is server-only.
|
|
@@ -617,8 +634,8 @@ forbids, such as a property it does not list. A call to one of the agent's tools
|
|
|
617
634
|
then goes through the tool's schema, as under `{ type: 'ai-sdk' }`: `execute`
|
|
618
635
|
receives the schema's output, defaults included. Input either check rejects goes
|
|
619
636
|
back to the model as an error, without running the tool. `reasoning` becomes
|
|
620
|
-
Pi's thinking level
|
|
621
|
-
lists. Pi works in the sandbox's working directory; its `bash` runs with the
|
|
637
|
+
Pi's thinking level and `speed: 'fast'` asks AI Gateway for the model's fast
|
|
638
|
+
tier; `model` is one of the Gateway models Pi's catalog lists. Pi works in the sandbox's working directory; its `bash` runs with the
|
|
622
639
|
sandbox's environment, never the server's. Pi compacts its own context; a
|
|
623
640
|
compaction appears in the transcript as a `data-a2-compaction` part with its
|
|
624
641
|
summary.
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Telemetry
|
|
3
|
+
description: "Trace A2 with OpenTelemetry, and the spans it reports."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
```ts server/orders.ts
|
|
7
|
+
import { createServer } from 'experimental-a2/server'
|
|
8
|
+
import { otel } from 'experimental-a2/otel'
|
|
9
|
+
import { orders } from '@/contracts'
|
|
10
|
+
|
|
11
|
+
export const ordersServer = createServer({
|
|
12
|
+
contract: orders,
|
|
13
|
+
telemetry: otel(),
|
|
14
|
+
// ...store, scheduler, and handlers
|
|
15
|
+
})
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
With any OpenTelemetry setup registered (on Vercel, `@vercel/otel`), one trace
|
|
19
|
+
shows the request, `a2.append`, the inline `a2.drain`, an `a2.event` span per
|
|
20
|
+
handler, and the appends those handlers make. Failures mark their `a2.event`
|
|
21
|
+
span, and `a2.event.outcome = dead_lettered` is the attribute to alert on.
|
|
22
|
+
Agent servers add `a2.ai.*` spans.
|
|
23
|
+
|
|
24
|
+
## `experimental-a2/otel`
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
otel(options?: { tracer?: Tracer; tracerName?: string }): A2Telemetry
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
OpenTelemetry instrumentation for a server (`@opentelemetry/api` is a peer
|
|
31
|
+
dependency). Pass it as `telemetry`. Spans nest through the active context, so
|
|
32
|
+
the happy path lands in one trace. `A2Telemetry` is a one-method interface:
|
|
33
|
+
anything with `span(name, attributes, fn)` works, and `otel()` is the adapter
|
|
34
|
+
A2 ships.
|
|
35
|
+
|
|
36
|
+
## The span catalogue
|
|
37
|
+
|
|
38
|
+
This catalogue is a contract: renames and additions are breaking for
|
|
39
|
+
dashboards and are called out in the package's `CHANGELOG.md`. All spans carry
|
|
40
|
+
`a2.contract` and `a2.session_id`. Core spans describe the durable runtime;
|
|
41
|
+
`a2.ai.*` spans describe the built-in AI handlers.
|
|
42
|
+
|
|
43
|
+
| Layer | Span | Wraps |
|
|
44
|
+
| ----- | ---- | ----- |
|
|
45
|
+
| Core | `a2.append` | validation + the store write (one batch) |
|
|
46
|
+
| Core | `a2.drain` | one drain pass, including concurrent handlers |
|
|
47
|
+
| Core | `a2.event` | one claimed dispatch, including its handler and completion |
|
|
48
|
+
| Core | `a2.claim` | one `claimAvailable` store operation |
|
|
49
|
+
| Core | `a2.commit` | one `completeAttempt` or `failAttempt` store operation |
|
|
50
|
+
| Core | `a2.state` | one `state()` read: snapshot-plus-tail load + reducer fold |
|
|
51
|
+
| Core | `a2.snapshot` | one background batch of checkpoint writes |
|
|
52
|
+
| Core | `a2.redrive` | one `session.redrive()`: the store operation and starting the session's wakeup |
|
|
53
|
+
| AI | `a2.ai.control` | applying an input/work report/session-close decision and preparing its returned events |
|
|
54
|
+
| AI | `a2.ai.generate` | one generation handler, including Core calls and model/SDK work |
|
|
55
|
+
| AI | `a2.ai.context` | constructing model messages, including tool prompt reconstruction when uncached |
|
|
56
|
+
| AI | `a2.ai.compaction` | automatic summarization or the custom compaction callback |
|
|
57
|
+
| AI | `a2.ai.tool` | one durable tool handler, including application execution |
|
|
58
|
+
| AI | `a2.ai.ack` | waiting for a persisted work report's canonical events and control receipt |
|
|
59
|
+
|
|
60
|
+
Durations include nested work: `a2.event` contains its AI handler and later
|
|
61
|
+
commit, AI handlers call Core, and `a2.state` includes reducer code.
|
|
62
|
+
Acknowledgements overlap control processing, claims can cover several events,
|
|
63
|
+
and snapshots run in the background, so summing spans, or subtracting summed
|
|
64
|
+
children, does not yield exclusive Core or AI overhead. Model and application
|
|
65
|
+
I/O is included; its child spans have the detail.
|
|
66
|
+
|
|
67
|
+
Attributes arrive in two waves: **at start** (passed to `span()`) and
|
|
68
|
+
**mid-span** (via `handle.setAttribute`, for outcomes and counts known only
|
|
69
|
+
after the work ran). A recorder must merge both; the ones you alert on are all
|
|
70
|
+
mid-span.
|
|
71
|
+
|
|
72
|
+
| Attribute | Span | When | Values |
|
|
73
|
+
| --------------------- | ----------- | ----- | ----------------------------------------------------- |
|
|
74
|
+
| `a2.append.source` | `a2.append` | start | `external` \| `handler` |
|
|
75
|
+
| `a2.append.mode` | `a2.append` | start | `inline` \| `dispatch` |
|
|
76
|
+
| `a2.append.types` | `a2.append` | start | comma-joined event types |
|
|
77
|
+
| `a2.append.count` | `a2.append` | start | batch size |
|
|
78
|
+
| `a2.append.armed` | `a2.append` | mid | `false` when the scheduler arm failed and this append degraded to append-driven healing |
|
|
79
|
+
| `a2.drain.outcome` | `a2.drain` | mid | `settled` \| `busy` \| `stalled` \| `failed` |
|
|
80
|
+
| `a2.drain.processed` | `a2.drain` | mid | events processed this pass |
|
|
81
|
+
| `a2.claim.outcome` | `a2.claim` | mid | `claimed` \| `busy` \| `settled` |
|
|
82
|
+
| `a2.claim.count` | `a2.claim` | mid | number of claimed events, or zero |
|
|
83
|
+
| `a2.commit.operation` | `a2.commit` | start | `complete` \| `fail` |
|
|
84
|
+
| `a2.commit.index` | `a2.commit` | start | event log position |
|
|
85
|
+
| `a2.commit.attempt` | `a2.commit` | start | durable 1-based attempt ordinal |
|
|
86
|
+
| `a2.commit.outcome` | `a2.commit` | mid | `completed` \| `failed` \| `dead_lettered` \| `superseded` |
|
|
87
|
+
| `a2.event.type` | `a2.event` | start | the event's type |
|
|
88
|
+
| `a2.event.index` | `a2.event` | start | log position |
|
|
89
|
+
| `a2.event.id` | `a2.event` | start | event id |
|
|
90
|
+
| `a2.event.attempt` | `a2.event` | start | same durable 1-based ordinal as `ctx.attempt` |
|
|
91
|
+
| `a2.event.lane` | `a2.event` | start | stored lane value; absent for concurrent unlaned work |
|
|
92
|
+
| `a2.event.handled` | `a2.event` | start | normally `true`; `false` when a custom-adapter row has no handler |
|
|
93
|
+
| `a2.event.outcome` | `a2.event` | mid | `processed` \| `failed` \| `dead_lettered` \| `superseded` \| `surrendered` |
|
|
94
|
+
| `a2.event.aborted` | `a2.event` | mid | `true` when `abortOn` fired during the run |
|
|
95
|
+
| `a2.state.reducer` | `a2.state` | start | the reducer's name |
|
|
96
|
+
| `a2.state.snapshot` | `a2.state` | mid | `hit` \| `miss` \| `rejected` (schema guard discarded it) \| `memory` (served from the server's in-memory state) |
|
|
97
|
+
| `a2.state.folded` | `a2.state` | mid | events folded past the snapshot |
|
|
98
|
+
| `a2.state.index` | `a2.state` | mid | the frontier the returned state reflects |
|
|
99
|
+
| `a2.snapshot.reducer` | `a2.snapshot` | start | the reducer's name |
|
|
100
|
+
| `a2.snapshot.index` | `a2.snapshot` | start | greatest checkpoint index in the write batch |
|
|
101
|
+
| `a2.snapshot.count` | `a2.snapshot` | start | number of checkpoints in the write batch |
|
|
102
|
+
| `a2.redrive.index` | `a2.redrive` | start | log position of the event being redriven |
|
|
103
|
+
| `a2.redrive.action` | `a2.redrive` | start | `retry` \| `skip` |
|
|
104
|
+
| `a2.redrive.redriven` | `a2.redrive` | mid | `true` when the event was dead-lettered and is now resolved |
|
|
105
|
+
| `a2.redrive.armed` | `a2.redrive` | mid | `false` when the scheduler arm failed and the wakeup degraded to append-driven healing |
|
|
106
|
+
| `a2.ai.control.type` | `a2.ai.control` | start | `ai.control.requested` \| `ai.work.reported` \| `ai.session.closed` |
|
|
107
|
+
| `a2.ai.control.id` | `a2.ai.control` | start | triggering event id |
|
|
108
|
+
| `a2.ai.control.attempt` | `a2.ai.control` | start | durable 1-based attempt ordinal |
|
|
109
|
+
| `a2.ai.generate.request_id` | `a2.ai.generate` | start | generation request event id |
|
|
110
|
+
| `a2.ai.generate.attempt` | `a2.ai.generate` | start | durable 1-based attempt ordinal |
|
|
111
|
+
| `a2.ai.context.request_id` | `a2.ai.context` | start | generation request event id |
|
|
112
|
+
| `a2.ai.context.phase` | `a2.ai.context` | start | `generation` \| `refresh` \| `tool` |
|
|
113
|
+
| `a2.ai.compaction.request_id` | `a2.ai.compaction` | start | generation request event id |
|
|
114
|
+
| `a2.ai.compaction.generation_id` | `a2.ai.compaction` | start | generation attempt id |
|
|
115
|
+
| `a2.ai.compaction.policy` | `a2.ai.compaction` | start | `automatic` \| `custom` |
|
|
116
|
+
| `a2.ai.tool.request_id` | `a2.ai.tool` | start | originating generation request event id |
|
|
117
|
+
| `a2.ai.tool.call_id` | `a2.ai.tool` | start | tool call id |
|
|
118
|
+
| `a2.ai.tool.name` | `a2.ai.tool` | start | tool name |
|
|
119
|
+
| `a2.ai.tool.attempt` | `a2.ai.tool` | start | durable 1-based attempt ordinal |
|
|
120
|
+
| `a2.ai.ack.request_id` | `a2.ai.ack` | start | reporting worker event id |
|
|
121
|
+
| `a2.ai.ack.report_id` | `a2.ai.ack` | start | work report event id, matching the control span's id |
|
|
122
|
+
|
|
123
|
+
- Drain outcomes: `settled` means nothing actionable is left; `busy` means live
|
|
124
|
+
per-event claims or a failed event's retry hold remain; `stalled` means a
|
|
125
|
+
caught failure in this drain needs a later retry. `failed` appears only on
|
|
126
|
+
scheduler deliveries: the drain threw after arming its successor watchdog,
|
|
127
|
+
the span records the error, and that successor retries the session.
|
|
128
|
+
- A failing handler marks its `a2.event` span with the exception and error
|
|
129
|
+
status. Alert on `a2.event.outcome = dead_lettered`. A dead-lettered event
|
|
130
|
+
blocks only later events in its lane, until `session.redrive()` resolves it;
|
|
131
|
+
other lanes stay eligible.
|
|
132
|
+
- `superseded` means a stale completion or failure did not change the log.
|
|
@@ -44,7 +44,7 @@ export async function POST(req: Request) {
|
|
|
44
44
|
| `SESSION_CLEARED` | a state read reaches below the latest clear, a handler reads state past a clear of its event, or an actor call is cleared before it receives its answer | no |
|
|
45
45
|
| `CLAIM_EXPIRED` | never thrown; only the `ctx.signal` abort reason when the attempt's lease lapsed without renewal (assumed loss, decided locally, not proof) | no |
|
|
46
46
|
| `STORE_UNAVAILABLE` | the store backend failed; nothing was written. The original error is the `cause` | **yes** |
|
|
47
|
-
| `STORE_NOT_CONFIGURED` | production boots with no `store` configured (during `next build` page collection, deferred to first use) | no |
|
|
47
|
+
| `STORE_NOT_CONFIGURED` | production boots with no `store` configured (during `next build` page collection, deferred to first use), a store's optional peer dependency is missing, or a Postgres database lacks A2's tables at the version the release needs (or the notification triggers `listenConnectionString` uses). The message names the fix | no |
|
|
48
48
|
|
|
49
49
|
Retrying an *identical* batch is not `PARTIAL_DUPLICATE_BATCH`: it's an
|
|
50
50
|
idempotent success that returns the previously appended events. A failing
|
package/docs/reference/meta.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export default { order:
|
|
1
|
+
export default { order: 9 }
|