experimental-a2 0.20.0 → 0.22.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 +55 -0
- package/README.md +1 -1
- package/dist/{actor-C7b-1arn.d.ts → actor-CqibLgjQ.d.ts} +15 -7
- package/dist/actor-CqibLgjQ.d.ts.map +1 -0
- package/dist/actor-client.d.ts +1 -1
- package/dist/actor-client.js +2 -2
- package/dist/actor-react.d.ts +3 -3
- package/dist/actor-react.js +5 -5
- package/dist/{actor-shared-t7w8xIKP.js → actor-shared-DFET5q16.js} +3 -3
- package/dist/actor-shared-DFET5q16.js.map +1 -0
- package/dist/actor.d.ts +1 -1
- package/dist/actor.js +54 -43
- package/dist/actor.js.map +1 -1
- package/dist/{ai-Cz3hqVhk.d.ts → ai-DAu77CDr.d.ts} +8 -8
- package/dist/{ai-Cz3hqVhk.d.ts.map → ai-DAu77CDr.d.ts.map} +1 -1
- package/dist/{ai-mMiWul1T.js → ai-EEfKyc-z.js} +9 -9
- package/dist/ai-EEfKyc-z.js.map +1 -0
- package/dist/ai-client.d.ts +2 -2
- package/dist/ai-client.js +2 -2
- package/dist/ai-fx.d.ts +4 -2
- package/dist/ai-fx.d.ts.map +1 -1
- package/dist/ai-fx.js +214 -67
- package/dist/ai-fx.js.map +1 -1
- package/dist/{ai-harness-CV6rXbB3.d.ts → ai-harness-DTjkAr2T.d.ts} +2 -2
- package/dist/{ai-harness-CV6rXbB3.d.ts.map → ai-harness-DTjkAr2T.d.ts.map} +1 -1
- package/dist/{ai-harness-Clbg5ei7.js → ai-harness-dQeJaR1t.js} +7 -3
- package/dist/ai-harness-dQeJaR1t.js.map +1 -0
- package/dist/ai-pi.d.ts +1 -1
- package/dist/ai-pi.js +3 -3
- package/dist/ai-pi.js.map +1 -1
- package/dist/ai-react.d.ts +5 -5
- package/dist/ai-react.js +2 -2
- package/dist/ai-server.d.ts +14 -17
- package/dist/ai-server.d.ts.map +1 -1
- package/dist/ai-server.js +86 -73
- package/dist/ai-server.js.map +1 -1
- package/dist/ai.d.ts +2 -2
- package/dist/ai.js +2 -2
- package/dist/{client-BhHLoNKF.js → client-BQS4i_V9.js} +29 -6
- package/dist/client-BQS4i_V9.js.map +1 -0
- package/dist/{client-h_DKFAjj.d.ts → client-CUMbS_38.d.ts} +3 -3
- package/dist/{client-h_DKFAjj.d.ts.map → client-CUMbS_38.d.ts.map} +1 -1
- package/dist/client.d.ts +3 -3
- package/dist/client.js +1 -1
- package/dist/{contract-BYKASlCV.js → contract-CWukt01d.js} +2 -2
- package/dist/contract-CWukt01d.js.map +1 -0
- package/dist/index.d.ts +5 -5
- package/dist/index.js +3 -3
- package/dist/{initial-snapshot-Bx-c0pDP.d.ts → initial-snapshot-D3Lve43o.d.ts} +4 -4
- package/dist/initial-snapshot-D3Lve43o.d.ts.map +1 -0
- package/dist/{internal-CscSY7K_.js → internal-CvsrprnU.js} +40 -16
- package/dist/internal-CvsrprnU.js.map +1 -0
- package/dist/otel.d.ts +1 -1
- package/dist/otel.js +1 -1
- package/dist/otel.js.map +1 -1
- package/dist/{react-BBXgHWFc.d.ts → react-BoHvC3v9.d.ts} +4 -4
- package/dist/{react-BBXgHWFc.d.ts.map → react-BoHvC3v9.d.ts.map} +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/{reducer-CqA59Q6U.d.ts → reducer-CYPHsSmd.d.ts} +2 -2
- package/dist/{reducer-CqA59Q6U.d.ts.map → reducer-CYPHsSmd.d.ts.map} +1 -1
- package/dist/{reducer-BAXc8JyF.js → reducer-CjmvZSAb.js} +2 -2
- package/dist/{reducer-BAXc8JyF.js.map → reducer-CjmvZSAb.js.map} +1 -1
- package/dist/runtime-consumer-CIeL33wj.js +12 -0
- package/dist/runtime-consumer-CIeL33wj.js.map +1 -0
- package/dist/scheduler-qstash.d.ts +2 -2
- package/dist/scheduler-qstash.js +3 -3
- package/dist/scheduler-task-X1SToDjP.js.map +1 -1
- package/dist/scheduler-vercel.d.ts +2 -2
- package/dist/scheduler-vercel.js +3 -3
- package/dist/scheduler-vercel.js.map +1 -1
- package/dist/{server-qyb5aJJG.js → server-DtFrWxi3.js} +412 -116
- package/dist/server-DtFrWxi3.js.map +1 -0
- package/dist/{server-DYAATFuT.d.ts → server-qgez0Xny.d.ts} +108 -41
- package/dist/server-qgez0Xny.d.ts.map +1 -0
- package/dist/server.browser.js +1 -1
- package/dist/server.browser.js.map +1 -1
- package/dist/server.d.ts +4 -4
- package/dist/server.js +2 -2
- package/dist/{session-clear-C-QwTXV1.d.ts → session-clear-D1PFe7xl.d.ts} +2 -2
- package/dist/session-clear-D1PFe7xl.d.ts.map +1 -0
- package/dist/{session-clear-DGI4jTQ5.js → session-clear-wHd5GCpZ.js} +5 -4
- package/dist/session-clear-wHd5GCpZ.js.map +1 -0
- package/dist/store-D0MhBvg6.d.ts +532 -0
- package/dist/store-D0MhBvg6.d.ts.map +1 -0
- package/dist/store-N8PXxDAS.js.map +1 -1
- package/dist/store-codec-DwrWlgQx.js +65 -0
- package/dist/store-codec-DwrWlgQx.js.map +1 -0
- package/dist/store-memory.d.ts +1 -1
- package/dist/store-memory.d.ts.map +1 -1
- package/dist/store-memory.js +406 -225
- package/dist/store-memory.js.map +1 -1
- package/dist/{store-polling-7WJV0YWP.js → store-polling-BZt2Rf3D.js} +2 -2
- package/dist/{store-polling-7WJV0YWP.js.map → store-polling-BZt2Rf3D.js.map} +1 -1
- package/dist/store-postgres.d.ts +1 -1
- package/dist/store-postgres.d.ts.map +1 -1
- package/dist/store-postgres.js +1598 -587
- package/dist/store-postgres.js.map +1 -1
- package/dist/store-presence-polling-JuAy2Ck2.js +111 -0
- package/dist/store-presence-polling-JuAy2Ck2.js.map +1 -0
- package/dist/store-redis-http.d.ts +1 -1
- package/dist/store-redis-http.js +4 -4
- package/dist/store-redis-notify-CsJw_hfD.js +3060 -0
- package/dist/store-redis-notify-CsJw_hfD.js.map +1 -0
- package/dist/store-redis.d.ts +1 -1
- package/dist/store-redis.d.ts.map +1 -1
- package/dist/store-redis.js +4 -5
- package/dist/store-redis.js.map +1 -1
- package/dist/store-sqlite.d.ts +1 -1
- package/dist/store-sqlite.d.ts.map +1 -1
- package/dist/store-sqlite.js +527 -299
- package/dist/store-sqlite.js.map +1 -1
- package/dist/stream-buffer-DrTyiIuo.js +57 -0
- package/dist/stream-buffer-DrTyiIuo.js.map +1 -0
- package/dist/{telemetry-Cyfqo6Qt.d.ts → telemetry-CWwsh-Gx.d.ts} +3 -3
- package/dist/telemetry-CWwsh-Gx.d.ts.map +1 -0
- package/dist/{wire-BKxH1vbP.js → wire-CV-Z7wz-.js} +6 -5
- package/dist/wire-CV-Z7wz-.js.map +1 -0
- package/docs/01-quickstart.mdx +21 -15
- package/docs/02-examples.mdx +1 -1
- package/docs/actors/01-introduction.mdx +13 -2
- package/docs/actors/02-concurrency.mdx +1 -1
- package/docs/agents/01-introduction.mdx +1 -1
- package/docs/agents/02-tools.mdx +12 -11
- package/docs/agents/03-conversations.mdx +36 -25
- package/docs/agents/04-harnesses.mdx +14 -7
- package/docs/client/03-presence.mdx +18 -14
- package/docs/contract/01-events.mdx +2 -2
- package/docs/contract/02-state.mdx +18 -14
- package/docs/contract/03-sessions.mdx +32 -25
- package/docs/index.mdx +29 -24
- package/docs/reference/01-core.mdx +7 -6
- package/docs/reference/02-server.mdx +250 -191
- package/docs/reference/04-ai.mdx +33 -23
- package/docs/reference/05-telemetry.mdx +27 -13
- package/docs/reference/06-errors.mdx +25 -19
- package/docs/reference/meta.ts +1 -1
- package/docs/schedulers/01-overview.mdx +8 -8
- package/docs/schedulers/02-vercel-queues.mdx +2 -2
- package/docs/schedulers/03-qstash.mdx +4 -4
- package/docs/server/01-consumers.mdx +418 -0
- package/docs/server/02-timers.mdx +31 -21
- package/docs/server/03-cancellation.mdx +52 -44
- package/docs/server/04-routes.mdx +4 -1
- package/docs/server/05-sql-projections.mdx +391 -0
- package/docs/server/06-guarantees.mdx +61 -51
- package/docs/stores/01-overview.mdx +3 -3
- package/docs/stores/02-redis.mdx +24 -10
- package/docs/stores/03-redis-http.mdx +8 -6
- package/docs/stores/04-postgres.mdx +65 -14
- package/docs/stores/05-sqlite.mdx +5 -3
- package/docs/upgrades/01-overview.mdx +31 -0
- package/docs/upgrades/78-0-22.mdx +115 -0
- package/docs/upgrades/79-0-21.mdx +165 -0
- package/docs/upgrades/80-0-20.mdx +43 -0
- package/docs/upgrades/81-0-19.mdx +33 -0
- package/docs/upgrades/82-0-18.mdx +33 -0
- package/docs/upgrades/83-0-17.mdx +69 -0
- package/docs/upgrades/meta.ts +3 -0
- package/examples/playground/DEPLOY.md +39 -7
- package/examples/playground/app/a0/chat.test.tsx +2 -2
- package/examples/playground/app/a0/sandbox-log.ts +17 -15
- package/examples/playground/app/a0/sandbox.ts +58 -13
- package/examples/playground/app/a0/server.ts +4 -4
- package/examples/playground/app/a2slack/a2slack.css +1067 -0
- package/examples/playground/app/a2slack/c/[channel]/page.tsx +20 -0
- package/examples/playground/app/a2slack/consumers/agent.ts +63 -0
- package/examples/playground/app/a2slack/consumers/feed.ts +164 -0
- package/examples/playground/app/a2slack/consumers/notify.ts +116 -0
- package/examples/playground/app/a2slack/consumers/search.ts +37 -0
- package/examples/playground/app/a2slack/consumers/triage.ts +31 -0
- package/examples/playground/app/a2slack/consumers/welcome.ts +25 -0
- package/examples/playground/app/a2slack/db/a2.ts +209 -0
- package/examples/playground/app/a2slack/db/index.ts +73 -0
- package/examples/playground/app/a2slack/db/migrations/0000_extensions.sql +4 -0
- package/examples/playground/app/a2slack/db/migrations/0001_workspace.sql +74 -0
- package/examples/playground/app/a2slack/db/migrations/0002_a2.sql +215 -0
- package/examples/playground/app/a2slack/db/migrations/0003_a2_batch.sql +67 -0
- package/examples/playground/app/a2slack/db/migrations/0004_a2_session_event_ids.sql +2 -0
- package/examples/playground/app/a2slack/db/migrations/meta/0000_snapshot.json +18 -0
- package/examples/playground/app/a2slack/db/migrations/meta/0001_snapshot.json +500 -0
- package/examples/playground/app/a2slack/db/migrations/meta/0002_snapshot.json +1159 -0
- package/examples/playground/app/a2slack/db/migrations/meta/0003_snapshot.json +1159 -0
- package/examples/playground/app/a2slack/db/migrations/meta/0004_snapshot.json +1165 -0
- package/examples/playground/app/a2slack/db/migrations/meta/_journal.json +41 -0
- package/examples/playground/app/a2slack/db/queries.ts +263 -0
- package/examples/playground/app/a2slack/db/schema.ts +145 -0
- package/examples/playground/app/a2slack/drills/a2slack.drill.ts +708 -0
- package/examples/playground/app/a2slack/drills/harness.ts +244 -0
- package/examples/playground/app/a2slack/events/route.ts +36 -0
- package/examples/playground/app/a2slack/identity.ts +44 -0
- package/examples/playground/app/a2slack/inbox/route.ts +8 -0
- package/examples/playground/app/a2slack/lag/data/route.ts +8 -0
- package/examples/playground/app/a2slack/lag/page.tsx +101 -0
- package/examples/playground/app/a2slack/lag.ts +73 -0
- package/examples/playground/app/a2slack/layout.tsx +43 -0
- package/examples/playground/app/a2slack/messages/route.ts +17 -0
- package/examples/playground/app/a2slack/model.test.ts +208 -0
- package/examples/playground/app/a2slack/model.ts +369 -0
- package/examples/playground/app/a2slack/models.test.ts +72 -0
- package/examples/playground/app/a2slack/page.tsx +10 -0
- package/examples/playground/app/a2slack/scheduler/route.ts +5 -0
- package/examples/playground/app/a2slack/search/route.ts +40 -0
- package/examples/playground/app/a2slack/search-index.ts +108 -0
- package/examples/playground/app/a2slack/server.test.ts +315 -0
- package/examples/playground/app/a2slack/server.ts +55 -0
- package/examples/playground/app/a2slack/sessions.ts +50 -0
- package/examples/playground/app/a2slack/thread/route.ts +8 -0
- package/examples/playground/app/a2slack/ui/ChannelView.tsx +87 -0
- package/examples/playground/app/a2slack/ui/Composer.tsx +117 -0
- package/examples/playground/app/a2slack/ui/DotsShadow.tsx +42 -0
- package/examples/playground/app/a2slack/ui/Feed.tsx +157 -0
- package/examples/playground/app/a2slack/ui/InboxContext.tsx +63 -0
- package/examples/playground/app/a2slack/ui/InboxView.tsx +82 -0
- package/examples/playground/app/a2slack/ui/Menu.tsx +128 -0
- package/examples/playground/app/a2slack/ui/MessageRow.tsx +314 -0
- package/examples/playground/app/a2slack/ui/NameGate.tsx +35 -0
- package/examples/playground/app/a2slack/ui/SearchContext.tsx +146 -0
- package/examples/playground/app/a2slack/ui/SearchResults.tsx +97 -0
- package/examples/playground/app/a2slack/ui/Shell.tsx +34 -0
- package/examples/playground/app/a2slack/ui/Sidebar.tsx +199 -0
- package/examples/playground/app/a2slack/ui/ThreadPanel.tsx +134 -0
- package/examples/playground/app/a2slack/ui/useInbox.ts +71 -0
- package/examples/playground/app/agent/server.ts +3 -3
- package/examples/playground/app/chat/[chatId]/chat-client.tsx +1 -1
- package/examples/playground/app/chat/server.ts +45 -35
- package/examples/playground/app/components/demo-icons.tsx +7 -0
- package/examples/playground/app/components/demos.tsx +7 -0
- package/examples/playground/app/orders/[orderId]/order-client.tsx +2 -2
- package/examples/playground/app/orders/page.tsx +1 -1
- package/examples/playground/app/orders/server.ts +49 -30
- package/examples/playground/app/recovery/[recoveryId]/recovery-client.tsx +14 -15
- package/examples/playground/app/recovery/page.tsx +4 -4
- package/examples/playground/app/recovery/server.ts +23 -32
- package/examples/playground/drizzle.config.ts +14 -0
- package/examples/playground/lib/store.ts +1 -1
- package/examples/playground/next.config.ts +6 -1
- package/examples/playground/package.json +12 -1
- package/examples/playground/scripts/migrate.mjs +31 -0
- package/examples/playground/vercel.json +9 -0
- package/examples/playground/vitest.drills.config.ts +32 -0
- package/package.json +5 -2
- package/sql/postgres/drizzle-schema.ts +209 -0
- package/sql/postgres/schema.sql +227 -67
- package/src/actor-shared.ts +10 -2
- package/src/actor.ts +101 -71
- package/src/ai-agent.ts +162 -111
- package/src/ai-control-server.ts +8 -8
- package/src/ai-fx.ts +324 -82
- package/src/ai-harness.ts +17 -2
- package/src/ai-pi.ts +2 -2
- package/src/ai-server.ts +2 -2
- package/src/ai.ts +7 -7
- package/src/client.ts +19 -2
- package/src/contract.ts +1 -1
- package/src/deterministic-id.ts +27 -4
- package/src/errors.ts +8 -5
- package/src/idempotent-replay.ts +0 -1
- package/src/index.ts +1 -1
- package/src/internal.ts +57 -16
- package/src/otel.ts +1 -1
- package/src/postgres-schema.ts +98 -57
- package/src/presence-recovery.ts +18 -11
- package/src/presence-tombstones.ts +52 -0
- package/src/push-envelope.ts +20 -0
- package/src/runtime-consumer.ts +10 -0
- package/src/scheduler-task.ts +2 -2
- package/src/scheduler-vercel.ts +1 -1
- package/src/server.browser.ts +1 -1
- package/src/server.ts +819 -218
- package/src/session-socket.ts +1 -1
- package/src/store-codec.ts +66 -0
- package/src/store-memory.ts +604 -286
- package/src/store-postgres.ts +2006 -857
- package/src/store-presence-polling.ts +59 -34
- package/src/store-redis-core.ts +1961 -1333
- package/src/store-redis-notify.ts +76 -26
- package/src/store-redis.ts +2 -3
- package/src/store-sqlite.ts +915 -516
- package/src/store.ts +241 -102
- package/src/telemetry.ts +2 -1
- package/src/wire.ts +1 -0
- package/dist/actor-C7b-1arn.d.ts.map +0 -1
- package/dist/actor-shared-t7w8xIKP.js.map +0 -1
- package/dist/ai-harness-Clbg5ei7.js.map +0 -1
- package/dist/ai-mMiWul1T.js.map +0 -1
- package/dist/client-BhHLoNKF.js.map +0 -1
- package/dist/contract-BYKASlCV.js.map +0 -1
- package/dist/idempotent-replay-JdgMQRRm.js +0 -21
- package/dist/idempotent-replay-JdgMQRRm.js.map +0 -1
- package/dist/initial-snapshot-Bx-c0pDP.d.ts.map +0 -1
- package/dist/internal-CscSY7K_.js.map +0 -1
- package/dist/server-DYAATFuT.d.ts.map +0 -1
- package/dist/server-qyb5aJJG.js.map +0 -1
- package/dist/session-clear-C-QwTXV1.d.ts.map +0 -1
- package/dist/session-clear-DGI4jTQ5.js.map +0 -1
- package/dist/store-CyLUy86v.d.ts +0 -404
- package/dist/store-CyLUy86v.d.ts.map +0 -1
- package/dist/store-codec-DTG0Ftek.js +0 -10
- package/dist/store-codec-DTG0Ftek.js.map +0 -1
- package/dist/store-presence-polling-B1ixeqIS.js +0 -94
- package/dist/store-presence-polling-B1ixeqIS.js.map +0 -1
- package/dist/store-redis-notify-CM0AfLY0.js +0 -2544
- package/dist/store-redis-notify-CM0AfLY0.js.map +0 -1
- package/dist/stream-buffer-Cx7AAPGq.js +0 -23
- package/dist/stream-buffer-Cx7AAPGq.js.map +0 -1
- package/dist/telemetry-Cyfqo6Qt.d.ts.map +0 -1
- package/dist/wire-BKxH1vbP.js.map +0 -1
- package/docs/server/01-handlers.mdx +0 -227
package/docs/reference/04-ai.mdx
CHANGED
|
@@ -65,9 +65,10 @@ The `UIMessage` type a contract's input commands (`ai.control.requested`)
|
|
|
65
65
|
carry, such as `AIMessageOf<typeof contract.events>`; `UIMessage` otherwise.
|
|
66
66
|
`agent()` types messages with it.
|
|
67
67
|
|
|
68
|
-
### `
|
|
68
|
+
### `consumerContext(contract?)`
|
|
69
69
|
|
|
70
|
-
Returns the current tool execution's durable
|
|
70
|
+
Returns the current tool execution's durable
|
|
71
|
+
[consumer context](/server/consumers#the-context), an
|
|
71
72
|
`AgentToolContext<D>` with `event`, `attempt`, `session`, and `signal`.
|
|
72
73
|
|
|
73
74
|
- Tools are plain AI SDK `tool()` definitions, passed through uninterpreted;
|
|
@@ -123,15 +124,15 @@ Pure typed builders of `ai.control.requested` commands, for `append()` and
|
|
|
123
124
|
- **Trust.** Browser ingress accepts user messages, approval and input
|
|
124
125
|
responses, interruptions, and explicit retries. `inputs.requestInput()`,
|
|
125
126
|
`inputs.toolResult()`, and non-user `inputs.seed()` messages are trusted
|
|
126
|
-
server appends; only
|
|
127
|
+
server appends; only A2's own consumers append generation requests and AI
|
|
127
128
|
lifecycle events. No builder covers session lifecycle: append
|
|
128
129
|
`ai.session.created` and `ai.session.closed` from trusted server code.
|
|
129
130
|
- **IDs.** Message, seed, and steer commands use the input message ID as their
|
|
130
131
|
event ID, and must. Approval, input-response, and retry builders use stable
|
|
131
132
|
interaction IDs; other commands use the ID `push()` or `append()` assigns,
|
|
132
133
|
kept across transport retries. Caller IDs are nonempty, never start with the
|
|
133
|
-
reserved `a2.ai:`, and are unique
|
|
134
|
-
and conflicting reuse rejects before append. A2's own IDs are opaque,
|
|
134
|
+
reserved `a2.ai:`, and are unique within the session; identical events
|
|
135
|
+
replay and conflicting reuse in the session rejects before append. A2's own IDs are opaque,
|
|
135
136
|
fixed-length, deterministic `a2.ai:` values; an assistant message's ID is its
|
|
136
137
|
initial model-request event's ID.
|
|
137
138
|
- **Passive input.** `generate: false` is valid browser input: it admits the
|
|
@@ -336,7 +337,7 @@ agent({
|
|
|
336
337
|
progress?,
|
|
337
338
|
toolLane?: string, // run local tool executions one at a time on this lane
|
|
338
339
|
toolRecovery?: { [tool]?: ToolRecovery }, // 'rerun' (default) | 'report'
|
|
339
|
-
|
|
340
|
+
consumers?, // your consumers, next to A2's own
|
|
340
341
|
store?, scheduler?, telemetry?,
|
|
341
342
|
}): Agent<Events, State, Name> // an A2Server
|
|
342
343
|
```
|
|
@@ -350,10 +351,10 @@ stub.
|
|
|
350
351
|
| `name` | the built-in protocol, with optional server-only `events` and `messageSchema` |
|
|
351
352
|
| `contract` | a shared `agentContract()`, when clients fold application state |
|
|
352
353
|
| `model` | an AI Gateway model ID, or [`ModelSettings`](#modelsettings) with one, under every harness; a value or per-generation async resolver |
|
|
353
|
-
| `tools` | AI SDK `tool()` definitions; local tools read context with [`
|
|
354
|
+
| `tools` | AI SDK `tool()` definitions; local tools read context with [`consumerContext()`](#consumercontextcontract) |
|
|
354
355
|
| `instructions` | a value or per-generation async resolver |
|
|
355
356
|
| `maxSteps` | a positive integer capping one complete assistant response, under every harness; no limit by default |
|
|
356
|
-
| `harness` | the agent's loop: `{ type: 'ai-sdk', … }` (`AISDKHarness`, the default), [`fx()`](#fx), or [`pi()`](#
|
|
357
|
+
| `harness` | the agent's loop: `{ type: 'ai-sdk', … }` (`AISDKHarness`, the default), [`fx()`](#fx), or [`pi()`](#pi-sandbox-) |
|
|
357
358
|
| `harness.generation` | per-step settings, or a resolver |
|
|
358
359
|
| `harness.messageMetadata` | maps default stream parts to message metadata |
|
|
359
360
|
| `harness.generate` | replaces the default model step |
|
|
@@ -361,7 +362,7 @@ stub.
|
|
|
361
362
|
| `progress` | durable batching: `maxChunks` (128), `maxDelayMs` (0) |
|
|
362
363
|
| `toolLane` | one lane for every local tool execution, one at a time in admission order; model steps keep their own lane |
|
|
363
364
|
| `toolRecovery` | `'rerun'` (default) or `'report'` per tool, the harness's own tools included |
|
|
364
|
-
| `
|
|
365
|
+
| `consumers` | your [consumers](/server/consumers), next to A2's own: `a2.ai.metadata` (concurrent), `a2.ai.control` (one lane for commands, work reports, and closes), `a2.ai.model` (one lane for generations), and `a2.ai.tools` (on `toolLane`, or concurrent). Yours may take any of the contract's events, the agent's own included, and run beside A2's. Names starting with `a2.` are reserved |
|
|
365
366
|
| `store`, `scheduler`, `telemetry` | as for any A2 server; `telemetry` covers Core and AI in one trace ([span catalogue](/reference/telemetry#the-span-catalogue)) |
|
|
366
367
|
|
|
367
368
|
**Contracts.** A contract not built by `agentContract()` throws a `TypeError`
|
|
@@ -384,7 +385,7 @@ overrides model headers. See
|
|
|
384
385
|
**`harness`** is the agent's loop. `{ type: 'ai-sdk' }` (`AISDKHarness`), the
|
|
385
386
|
default, runs one AI SDK `streamText()` step per durable generation, with the
|
|
386
387
|
`generation`, `messageMetadata`, `generate`, and `compaction` options below.
|
|
387
|
-
[`fx()`](#fx) and [`pi()`](#
|
|
388
|
+
[`fx()`](#fx) and [`pi()`](#pi-sandbox-) run a coding agent's own loop instead.
|
|
388
389
|
The four options exist only in `{ type: 'ai-sdk' }`; passing one to `agent()`
|
|
389
390
|
itself throws a `TypeError` naming where it belongs.
|
|
390
391
|
|
|
@@ -445,11 +446,11 @@ compaction. It returns exactly one model step as a
|
|
|
445
446
|
the durable lifecycle. See
|
|
446
447
|
[Replace generation, not durability](/agents/conversations#replace-generation-not-durability).
|
|
447
448
|
|
|
448
|
-
**Tools.** Independent authorized tool
|
|
449
|
-
fixed limit. Results and generation completion
|
|
450
|
-
continuation needs the closed step and every
|
|
451
|
-
Single-owner terminal events commit atomically with
|
|
452
|
-
completion and carry its durable cause; many-owner joins and preliminary
|
|
449
|
+
**Tools.** Independent authorized tool executions run concurrently with no
|
|
450
|
+
fixed limit, unless `toolLane` orders them. Results and generation completion
|
|
451
|
+
arrive in either order; a continuation needs the closed step and every
|
|
452
|
+
required terminal result. Single-owner terminal events commit atomically with
|
|
453
|
+
the parent run's completion and carry its durable cause; many-owner joins and preliminary
|
|
453
454
|
outputs append immediately and idempotently. Provider calls and external side
|
|
454
455
|
effects are at least once: give them their own idempotency. An approved
|
|
455
456
|
provider-executed call can satisfy the join from its approval response, and a
|
|
@@ -489,8 +490,8 @@ See [Delivery semantics](/agents/conversations#delivery-semantics).
|
|
|
489
490
|
- Static options are validated at construction, resolved ones before their
|
|
490
491
|
generation; each session uses its own value.
|
|
491
492
|
- Discovery: without an explicit threshold, the first generation batches a
|
|
492
|
-
metadata request into its start write, and
|
|
493
|
-
model's limits from the public Gateway catalog in `state.modelMetadata`
|
|
493
|
+
metadata request into its start write, and A2's `a2.ai.metadata` consumer
|
|
494
|
+
records the model's limits from the public Gateway catalog in `state.modelMetadata`
|
|
494
495
|
without delaying dispatch (shared one-hour cache, five-second timeout; no new
|
|
495
496
|
request per turn, even after exhausted retries). The default threshold is 75%
|
|
496
497
|
of the context window, lowered for a larger output allowance. While it is
|
|
@@ -525,7 +526,7 @@ See [Compact long conversations](/agents/conversations#compact-long-conversation
|
|
|
525
526
|
**Harnesses.** Under `fx()` and `pi()`, each model call the harness makes is one
|
|
526
527
|
durable step of the response, with the same events, progress, steering,
|
|
527
528
|
interruption, `maxSteps`, and recovery as any step. Tool calls it makes become
|
|
528
|
-
ordinary A2 tool calls, executed by A2's
|
|
529
|
+
ordinary A2 tool calls, executed by A2's `a2.ai.tools` consumer, and `toolRecovery` may
|
|
529
530
|
name the harness's own tools, such as Pi's `bash`. `model` is resolved per step
|
|
530
531
|
as for any agent. `instructions` and `tools` reach the harness; how they
|
|
531
532
|
combine with its own prompt and tools depends on the harness. Tool approval
|
|
@@ -538,7 +539,9 @@ their `https:` or `data:` URLs; any other file fails the turn. See
|
|
|
538
539
|
|
|
539
540
|
A harness resumes after a crash at the interrupted step. Finished model calls
|
|
540
541
|
are not made again and finished tool calls do not run again. A2 rebuilds the
|
|
541
|
-
harness for each step from the log,
|
|
542
|
+
harness for each step from the log, unless [`fx()`](#fx) continues the kernel
|
|
543
|
+
its previous step left in the same process, and keeps nothing durable outside
|
|
544
|
+
the log. Each agent
|
|
542
545
|
keeps, in its process's memory, the harness's state at the end of the last
|
|
543
546
|
response it ran there, FX's checkpoint or Pi's session, so a warm process reads
|
|
544
547
|
and replays only the log since; a cold one reads and replays the session from
|
|
@@ -569,11 +572,11 @@ resolver. `reasoning` becomes the AI SDK `reasoning` call option
|
|
|
569
572
|
model has one; `'standard'` removes a configured speed. Both override the same
|
|
570
573
|
settings in `generation`. See [Choose the model](/agents/introduction#choose-the-model).
|
|
571
574
|
|
|
572
|
-
### `
|
|
575
|
+
### `runWithConsumerContext(ctx, fn)`
|
|
573
576
|
|
|
574
577
|
The test seam for context-reading tools: runs `fn` with `ctx` as the ambient
|
|
575
|
-
|
|
576
|
-
`
|
|
578
|
+
consumer context, so tests can call `execute` directly. Inside `fn`,
|
|
579
|
+
`consumerContext()` returns `ctx`, with or without a contract argument.
|
|
577
580
|
|
|
578
581
|
## `experimental-a2/ai/fx`
|
|
579
582
|
|
|
@@ -599,7 +602,14 @@ response so far from the log: recorded model responses and tool results answer
|
|
|
599
602
|
the kernel without network calls or tool runs, then FX makes its next model
|
|
600
603
|
call. Replay grows with the response, roughly 0.1 to 0.5 seconds for one of 80
|
|
601
604
|
to 200 tool calls. When libfx can't take a checkpoint, A2 replays from an
|
|
602
|
-
earlier one.
|
|
605
|
+
earlier one. The process that ran the step before skips the replay: it keeps
|
|
606
|
+
FX's kernel waiting on that step's tool calls, and the next step gives the
|
|
607
|
+
kernel any steering sent meanwhile, answers its tool calls from the log, and
|
|
608
|
+
lets it go on. A step that finds anything else first, such as changed model
|
|
609
|
+
settings or a model call another process committed, closes that kernel and
|
|
610
|
+
replays. A kept kernel holds its process open, so A2 closes it once the session
|
|
611
|
+
has nothing left to run in that process: however a response ends, its process
|
|
612
|
+
can exit.
|
|
603
613
|
|
|
604
614
|
FX's model calls carry the same [AI Gateway](#agentoptions) defaults as the
|
|
605
615
|
AI SDK harness: `caching: 'auto'`, under any Gateway options FX sets itself,
|
|
@@ -11,13 +11,13 @@ import { orders } from '@/contracts'
|
|
|
11
11
|
export const ordersServer = createServer({
|
|
12
12
|
contract: orders,
|
|
13
13
|
telemetry: otel(),
|
|
14
|
-
// ...store, scheduler, and
|
|
14
|
+
// ...store, scheduler, and consumers
|
|
15
15
|
})
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
With any OpenTelemetry setup registered (on Vercel, `@vercel/otel`), one trace
|
|
19
19
|
shows the request, `a2.append`, the inline `a2.drain`, an `a2.event` span per
|
|
20
|
-
|
|
20
|
+
consumer run, and the appends those runs make. Failures mark their `a2.event`
|
|
21
21
|
span, and `a2.event.outcome = dead_lettered` is the attribute to alert on.
|
|
22
22
|
Agent servers add `a2.ai.*` spans.
|
|
23
23
|
|
|
@@ -38,27 +38,28 @@ A2 ships.
|
|
|
38
38
|
This catalogue is a contract: renames and additions are breaking for
|
|
39
39
|
dashboards and are called out in the package's `CHANGELOG.md`. All spans carry
|
|
40
40
|
`a2.contract` and `a2.session_id`. Core spans describe the durable runtime;
|
|
41
|
-
`a2.ai.*` spans describe the
|
|
41
|
+
`a2.ai.*` spans describe the AI runtime's consumers.
|
|
42
42
|
|
|
43
43
|
| Layer | Span | Wraps |
|
|
44
44
|
| ----- | ---- | ----- |
|
|
45
45
|
| Core | `a2.append` | validation + the store write (one batch) |
|
|
46
|
-
| Core | `a2.drain` | one drain pass, including
|
|
47
|
-
| Core | `a2.event` | one claimed
|
|
46
|
+
| Core | `a2.drain` | one drain pass, including the consumer runs it starts |
|
|
47
|
+
| Core | `a2.event` | one claimed head, including the consumer's run and its completion |
|
|
48
48
|
| Core | `a2.claim` | one `claimAvailable` store operation |
|
|
49
49
|
| Core | `a2.commit` | one `completeAttempt` or `failAttempt` store operation |
|
|
50
50
|
| Core | `a2.state` | one `state()` read: snapshot-plus-tail load + reducer fold |
|
|
51
51
|
| Core | `a2.snapshot` | one background batch of checkpoint writes |
|
|
52
52
|
| Core | `a2.redrive` | one `session.redrive()`: the store operation and starting the session's wakeup |
|
|
53
|
+
| Core | `a2.consume` | one `session.consume()`, including the drains it runs and the waits between them |
|
|
53
54
|
| AI | `a2.ai.control` | applying an input/work report/session-close decision and preparing its returned events |
|
|
54
|
-
| AI | `a2.ai.generate` | one generation
|
|
55
|
+
| AI | `a2.ai.generate` | one generation run, including Core calls and model/SDK work |
|
|
55
56
|
| AI | `a2.ai.context` | constructing model messages, including tool prompt reconstruction when uncached |
|
|
56
57
|
| AI | `a2.ai.compaction` | automatic summarization or the custom compaction callback |
|
|
57
|
-
| AI | `a2.ai.tool` | one durable tool
|
|
58
|
+
| AI | `a2.ai.tool` | one durable tool run, including application execution |
|
|
58
59
|
| AI | `a2.ai.ack` | waiting for a persisted work report's canonical events and control receipt |
|
|
59
60
|
|
|
60
|
-
Durations include nested work: `a2.event` contains its AI
|
|
61
|
-
commit, AI
|
|
61
|
+
Durations include nested work: `a2.event` contains its AI run and later
|
|
62
|
+
commit, AI runs call Core, and `a2.state` includes reducer code.
|
|
62
63
|
Acknowledgements overlap control processing, claims can cover several events,
|
|
63
64
|
and snapshots run in the background, so summing spans, or subtracting summed
|
|
64
65
|
children, does not yield exclusive Core or AI overhead. Model and application
|
|
@@ -71,7 +72,7 @@ mid-span.
|
|
|
71
72
|
|
|
72
73
|
| Attribute | Span | When | Values |
|
|
73
74
|
| --------------------- | ----------- | ----- | ----------------------------------------------------- |
|
|
74
|
-
| `a2.append.source` | `a2.append` | start | `external` \| `
|
|
75
|
+
| `a2.append.source` | `a2.append` | start | `external` \| `consumer` |
|
|
75
76
|
| `a2.append.mode` | `a2.append` | start | `inline` \| `dispatch` |
|
|
76
77
|
| `a2.append.types` | `a2.append` | start | comma-joined event types |
|
|
77
78
|
| `a2.append.count` | `a2.append` | start | batch size |
|
|
@@ -87,9 +88,12 @@ mid-span.
|
|
|
87
88
|
| `a2.event.type` | `a2.event` | start | the event's type |
|
|
88
89
|
| `a2.event.index` | `a2.event` | start | log position |
|
|
89
90
|
| `a2.event.id` | `a2.event` | start | event id |
|
|
91
|
+
| `a2.event.consumer` | `a2.event` | start | the consumer running the event |
|
|
90
92
|
| `a2.event.attempt` | `a2.event` | start | same durable 1-based ordinal as `ctx.attempt` |
|
|
91
93
|
| `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
|
|
94
|
+
| `a2.event.handled` | `a2.event` | start | normally `true`; `false` when a claimed head's consumer is not registered |
|
|
95
|
+
| `a2.event.age_ms` | `a2.event` | start | milliseconds since the event was appended, when its run starts: how far behind its consumer is in time |
|
|
96
|
+
| `a2.event.lag` | `a2.event` | start | events appended after this one when it was claimed: how far behind its consumer is in the log. Absent when the store doesn't report its head |
|
|
93
97
|
| `a2.event.outcome` | `a2.event` | mid | `processed` \| `failed` \| `dead_lettered` \| `superseded` \| `surrendered` |
|
|
94
98
|
| `a2.event.aborted` | `a2.event` | mid | `true` when `abortOn` fired during the run |
|
|
95
99
|
| `a2.state.reducer` | `a2.state` | start | the reducer's name |
|
|
@@ -99,7 +103,12 @@ mid-span.
|
|
|
99
103
|
| `a2.snapshot.reducer` | `a2.snapshot` | start | the reducer's name |
|
|
100
104
|
| `a2.snapshot.index` | `a2.snapshot` | start | greatest checkpoint index in the write batch |
|
|
101
105
|
| `a2.snapshot.count` | `a2.snapshot` | start | number of checkpoints in the write batch |
|
|
106
|
+
| `a2.consume.consumer` | `a2.consume` | start | the consumer being consumed |
|
|
107
|
+
| `a2.consume.through` | `a2.consume` | mid | the index it runs through |
|
|
108
|
+
| `a2.consume.index` | `a2.consume` | mid | the index it handled every event through |
|
|
109
|
+
| `a2.consume.drains` | `a2.consume` | mid | drains it ran to get there |
|
|
102
110
|
| `a2.redrive.index` | `a2.redrive` | start | log position of the event being redriven |
|
|
111
|
+
| `a2.redrive.consumer` | `a2.redrive` | start | the consumer the event is dead for |
|
|
103
112
|
| `a2.redrive.action` | `a2.redrive` | start | `retry` \| `skip` |
|
|
104
113
|
| `a2.redrive.redriven` | `a2.redrive` | mid | `true` when the event was dead-lettered and is now resolved |
|
|
105
114
|
| `a2.redrive.armed` | `a2.redrive` | mid | `false` when the scheduler arm failed and the wakeup degraded to append-driven healing |
|
|
@@ -120,13 +129,18 @@ mid-span.
|
|
|
120
129
|
| `a2.ai.ack.request_id` | `a2.ai.ack` | start | reporting worker event id |
|
|
121
130
|
| `a2.ai.ack.report_id` | `a2.ai.ack` | start | work report event id, matching the control span's id |
|
|
122
131
|
|
|
132
|
+
- Consumer lag: group `a2.event` spans by `a2.event.consumer`. `a2.event.lag`
|
|
133
|
+
is how many events the session had appended after the one it claimed, and
|
|
134
|
+
`a2.event.age_ms` how old that event was when its run started. A consumer
|
|
135
|
+
that keeps up keeps both near zero; a rising `a2.event.lag` means it is
|
|
136
|
+
falling behind its log.
|
|
123
137
|
- Drain outcomes: `settled` means nothing actionable is left; `busy` means live
|
|
124
138
|
per-event claims or a failed event's retry hold remain; `stalled` means a
|
|
125
139
|
caught failure in this drain needs a later retry. `failed` appears only on
|
|
126
140
|
scheduler deliveries: the drain threw after arming its successor watchdog,
|
|
127
141
|
the span records the error, and that successor retries the session.
|
|
128
|
-
- A failing
|
|
129
|
-
status. Alert on `a2.event.outcome = dead_lettered`. A dead-lettered event
|
|
142
|
+
- A failing consumer run marks its `a2.event` span with the exception and
|
|
143
|
+
error status. Alert on `a2.event.outcome = dead_lettered`. A dead-lettered event
|
|
130
144
|
blocks only later events in its lane, until `session.redrive()` resolves it;
|
|
131
145
|
other lanes stay eligible.
|
|
132
146
|
- `superseded` means a stale completion or failure did not change the log.
|
|
@@ -10,7 +10,7 @@ exposes `A2ClientError`, which extends `A2Error`, for server failures and local
|
|
|
10
10
|
ones alike, so `instanceof A2Error` matches both.
|
|
11
11
|
|
|
12
12
|
```ts
|
|
13
|
-
// e.g. in a route
|
|
13
|
+
// e.g. in a route:
|
|
14
14
|
import { A2Error } from 'experimental-a2'
|
|
15
15
|
|
|
16
16
|
export async function POST(req: Request) {
|
|
@@ -39,17 +39,19 @@ export async function POST(req: Request) {
|
|
|
39
39
|
| `UNKNOWN_EVENT_TYPE` | an event type isn't in the contract's `events` map | no |
|
|
40
40
|
| `UNKNOWN_PRESENCE_FIELD` | a presence field isn't in the contract's `presence` map | no |
|
|
41
41
|
| `PRESENCE_NOT_SUPPORTED` | the contract declares `presence` but the store has no presence capability. Thrown at construction; for environment-resolved stores, at first store use | no |
|
|
42
|
-
| `PARTIAL_DUPLICATE_BATCH` | a batch mixes already-appended and fresh events, contains the same id twice,
|
|
43
|
-
| `SUPERSEDED_ATTEMPT` | a
|
|
44
|
-
| `SESSION_CLEARED` | a state read reaches below the latest clear, a
|
|
42
|
+
| `PARTIAL_DUPLICATE_BATCH` | a batch mixes already-appended and fresh events, or contains the same id twice. Event ids are unique per session, so an id used in another session never conflicts. Always a caller bug; nothing was written | no |
|
|
43
|
+
| `SUPERSEDED_ATTEMPT` | a consumer's append or clear comes from an attempt that a recovery claim or a session clear replaced; nothing was written. Also the abort reason on that attempt's `ctx.signal` | no |
|
|
44
|
+
| `SESSION_CLEARED` | a state read reaches below the latest clear, a consumer 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
|
+
| `CONSUMER_DEAD_LETTERED` | `session.consume()` reached an event that dead-lettered for that consumer. Resolve it with `session.redrive()`, then consume again | no |
|
|
46
47
|
| `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), a store's optional peer dependency is missing, or a Postgres
|
|
48
|
+
| `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 query finds one of A2's tables or columns missing, or that its role can't use (and, with `listenConnectionString`, a subscription finds the notification triggers missing). The message names the fix | no |
|
|
48
49
|
|
|
49
50
|
Retrying an *identical* batch is not `PARTIAL_DUPLICATE_BATCH`: it's an
|
|
50
51
|
idempotent success that returns the previously appended events. A failing
|
|
51
|
-
|
|
52
|
-
[dead-letter the event](/server/guarantees#when-a-
|
|
52
|
+
consumer never makes `append` throw; its failures retry and, if persistent,
|
|
53
|
+
[dead-letter the event](/server/guarantees#when-a-consumer-keeps-failing) for
|
|
54
|
+
that consumer. See
|
|
53
55
|
[What append never throws for](/server/guarantees#what-append-never-throws-for).
|
|
54
56
|
|
|
55
57
|
## `A2ClientError`
|
|
@@ -82,33 +84,37 @@ or `RangeError`.
|
|
|
82
84
|
|
|
83
85
|
## NonRetriableError
|
|
84
86
|
|
|
85
|
-
Throw it (or a subclass, matched by `instanceof`) from a
|
|
87
|
+
Throw it (or a subclass, matched by `instanceof`) from a consumer when the
|
|
86
88
|
failure is deterministic: a guard that refuses this attempt refuses every
|
|
87
|
-
retry identically. The event dead-letters
|
|
88
|
-
ten-failure retry budget. Any other thrown error keeps the
|
|
89
|
+
retry identically. The event dead-letters for that consumer immediately,
|
|
90
|
+
instead of spending the ten-failure retry budget. Any other thrown error keeps the
|
|
89
91
|
ordinary retry contract.
|
|
90
92
|
|
|
91
93
|
```ts
|
|
92
94
|
import { z } from 'zod'
|
|
93
95
|
import * as a2 from 'experimental-a2'
|
|
94
96
|
import { NonRetriableError } from 'experimental-a2'
|
|
95
|
-
import { createServer } from 'experimental-a2/server'
|
|
97
|
+
import { consumer, createServer } from 'experimental-a2/server'
|
|
96
98
|
|
|
97
99
|
const orders = a2.contract({
|
|
98
100
|
name: 'orders',
|
|
99
101
|
events: { created: z.object({ chargeable: z.boolean() }) },
|
|
100
102
|
})
|
|
101
103
|
|
|
104
|
+
const charge = consumer({
|
|
105
|
+
contract: orders,
|
|
106
|
+
name: 'charge',
|
|
107
|
+
types: ['created'],
|
|
108
|
+
}).run(async ({ event }) => {
|
|
109
|
+
if (!event.payload.chargeable) {
|
|
110
|
+
throw new NonRetriableError('this order cannot be charged')
|
|
111
|
+
}
|
|
112
|
+
// transient failures below retry as usual
|
|
113
|
+
})
|
|
114
|
+
|
|
102
115
|
export const ordersServer = createServer({
|
|
103
116
|
contract: orders,
|
|
104
|
-
|
|
105
|
-
created: async ({ event }) => {
|
|
106
|
-
if (!event.payload.chargeable) {
|
|
107
|
-
throw new NonRetriableError('this order cannot be charged')
|
|
108
|
-
}
|
|
109
|
-
// transient failures below retry as usual
|
|
110
|
-
},
|
|
111
|
-
},
|
|
117
|
+
consumers: [charge],
|
|
112
118
|
})
|
|
113
119
|
```
|
|
114
120
|
|
package/docs/reference/meta.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export default { order:
|
|
1
|
+
export default { order: 10 }
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Overview
|
|
3
|
-
description: A clock for
|
|
3
|
+
description: A clock for consumer retries and timers.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
```ts server/orders.ts
|
|
@@ -15,9 +15,9 @@ export const ordersServer = createServer({
|
|
|
15
15
|
contract: orders,
|
|
16
16
|
store: redis({ url: process.env.REDIS_URL }),
|
|
17
17
|
scheduler,
|
|
18
|
-
|
|
18
|
+
consumers: [
|
|
19
19
|
/* ... */
|
|
20
|
-
|
|
20
|
+
],
|
|
21
21
|
})
|
|
22
22
|
```
|
|
23
23
|
|
|
@@ -27,7 +27,7 @@ import { billingServer, ordersServer, scheduler } from '@/server'
|
|
|
27
27
|
export const POST = scheduler.handler(ordersServer, billingServer)
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
A scheduler wakes sessions on a clock. It retries a
|
|
30
|
+
A scheduler wakes sessions on a clock. It retries a consumer that failed or
|
|
31
31
|
whose process died, and it delivers [timers](/server/timers), whose delays must
|
|
32
32
|
fit the [provider's limit](/server/timers#provider-limits). Share one
|
|
33
33
|
scheduler across your servers and mount its route once, with every server it
|
|
@@ -41,11 +41,11 @@ loads, so a bad route fails at startup.
|
|
|
41
41
|
|
|
42
42
|
## Without a scheduler
|
|
43
43
|
|
|
44
|
-
If
|
|
44
|
+
If the server has no consumers and nothing calls `session.schedule`, skip it.
|
|
45
45
|
Events settle as they're appended, with no queue messages. That's a complete
|
|
46
46
|
production setup for history, reducers, and live sync.
|
|
47
47
|
|
|
48
|
-
With
|
|
48
|
+
With consumers but no scheduler, a session only wakes on a top-level append or
|
|
49
49
|
an explicit `server.drain()`. Reads never wake it, so a session nobody writes
|
|
50
50
|
to stays stuck. That's fine for internal tools that can heal on the next
|
|
51
51
|
write. For production, add a scheduler.
|
|
@@ -58,8 +58,8 @@ instance its own `topic` and trigger.
|
|
|
58
58
|
|
|
59
59
|
## Dispatch in a fresh invocation
|
|
60
60
|
|
|
61
|
-
`append` starts
|
|
62
|
-
watchdog. `append.dispatch` stores the event and hands its
|
|
61
|
+
`append` starts consumers in the same request and uses the scheduler as a
|
|
62
|
+
watchdog. `append.dispatch` stores the event and hands its consumers to the
|
|
63
63
|
queue instead:
|
|
64
64
|
|
|
65
65
|
```ts app/api/imports/route.ts
|
|
@@ -15,9 +15,9 @@ export const ordersServer = createServer({
|
|
|
15
15
|
contract: orders,
|
|
16
16
|
store: redis({ url: process.env.REDIS_URL }),
|
|
17
17
|
scheduler,
|
|
18
|
-
|
|
18
|
+
consumers: [
|
|
19
19
|
/* ... */
|
|
20
|
-
|
|
20
|
+
],
|
|
21
21
|
})
|
|
22
22
|
```
|
|
23
23
|
|
|
@@ -37,8 +37,8 @@ QStash's local server on the first publish. It needs `@upstash/qstash` 2.11.0
|
|
|
37
37
|
or newer.
|
|
38
38
|
|
|
39
39
|
The route checks `Upstash-Signature` before it touches the store and answers
|
|
40
|
-
unsigned requests with 401. Delivery is at least once, so
|
|
41
|
-
need idempotency keys.
|
|
40
|
+
unsigned requests with 401. Delivery is at least once, so consumer side
|
|
41
|
+
effects need idempotency keys.
|
|
42
42
|
|
|
43
43
|
## Callback URL
|
|
44
44
|
|