experimental-a2 0.19.0 → 0.21.1
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 +50 -0
- package/README.md +1 -1
- package/dist/{actor-C7b-1arn.d.ts → actor-HnGmrOZZ.d.ts} +5 -5
- package/dist/{actor-C7b-1arn.d.ts.map → actor-HnGmrOZZ.d.ts.map} +1 -1
- 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-t7w8xIKP.js.map → actor-shared-DFET5q16.js.map} +1 -1
- 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-CvEnqVfo.d.ts} +8 -8
- package/dist/{ai-Cz3hqVhk.d.ts.map → ai-CvEnqVfo.d.ts.map} +1 -1
- package/dist/{ai-mMiWul1T.js → ai-CzE672Q3.js} +9 -9
- package/dist/ai-CzE672Q3.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 +1 -1
- package/dist/ai-fx.d.ts.map +1 -1
- package/dist/ai-fx.js +36 -9
- package/dist/ai-fx.js.map +1 -1
- package/dist/{ai-harness-CV6rXbB3.d.ts → ai-harness-Bfk-Lycn.d.ts} +2 -2
- package/dist/ai-harness-Bfk-Lycn.d.ts.map +1 -0
- package/dist/{ai-harness-BuuC-gJ3.js → ai-harness-dQeJaR1t.js} +17 -4
- package/dist/ai-harness-dQeJaR1t.js.map +1 -0
- package/dist/ai-pi.d.ts +1 -1
- package/dist/ai-pi.js +4 -4
- 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 +84 -75
- 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-9IeTsiAs.js} +5 -5
- package/dist/{client-BhHLoNKF.js.map → client-9IeTsiAs.js.map} +1 -1
- 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-Bc4PvfHk.js} +10 -3
- package/dist/internal-Bc4PvfHk.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-BWmht60z.js +12 -0
- package/dist/runtime-consumer-BWmht60z.js.map +1 -0
- package/dist/scheduler-qstash.d.ts +2 -2
- package/dist/scheduler-qstash.d.ts.map +1 -1
- package/dist/scheduler-qstash.js +7 -7
- package/dist/scheduler-qstash.js.map +1 -1
- package/dist/scheduler-task-X1SToDjP.js.map +1 -1
- package/dist/scheduler-vercel.d.ts +2 -2
- package/dist/scheduler-vercel.d.ts.map +1 -1
- package/dist/scheduler-vercel.js +5 -4
- package/dist/scheduler-vercel.js.map +1 -1
- package/dist/{server-DYAATFuT.d.ts → server-BshFZ0eL.d.ts} +108 -41
- package/dist/server-BshFZ0eL.d.ts.map +1 -0
- package/dist/{server-D-Y4pqwu.js → server-DvIB8kj5.js} +423 -135
- package/dist/server-DvIB8kj5.js.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-Bk_29eEw.d.ts} +2 -2
- package/dist/session-clear-Bk_29eEw.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-N8PXxDAS.js.map +1 -1
- package/dist/store-Z2qjw50n.d.ts +523 -0
- package/dist/store-Z2qjw50n.d.ts.map +1 -0
- 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 +393 -210
- package/dist/store-memory.js.map +1 -1
- package/dist/{store-polling-7WJV0YWP.js → store-polling-DYNyxCev.js} +2 -2
- package/dist/{store-polling-7WJV0YWP.js.map → store-polling-DYNyxCev.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 +1595 -673
- package/dist/store-postgres.js.map +1 -1
- package/dist/{store-presence-polling-B1ixeqIS.js → store-presence-polling-CzQ0wdtM.js} +3 -3
- package/dist/{store-presence-polling-B1ixeqIS.js.map → store-presence-polling-CzQ0wdtM.js.map} +1 -1
- package/dist/store-redis-http.d.ts +1 -1
- package/dist/store-redis-http.js +4 -4
- package/dist/store-redis-notify-IiVdlYHc.js +2919 -0
- package/dist/store-redis-notify-IiVdlYHc.js.map +1 -0
- package/dist/store-redis.d.ts +1 -1
- package/dist/store-redis.js +2 -2
- package/dist/store-sqlite.d.ts +1 -1
- package/dist/store-sqlite.d.ts.map +1 -1
- package/dist/store-sqlite.js +517 -287
- package/dist/store-sqlite.js.map +1 -1
- 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-CZGqNZRx.js} +5 -4
- package/dist/wire-CZGqNZRx.js.map +1 -0
- package/docs/01-quickstart.mdx +28 -19
- package/docs/02-examples.mdx +1 -1
- package/docs/actors/01-introduction.mdx +9 -1
- package/docs/actors/02-concurrency.mdx +1 -1
- package/docs/actors/meta.ts +1 -1
- package/docs/agents/01-introduction.mdx +1 -1
- package/docs/agents/02-tools.mdx +13 -12
- package/docs/agents/03-conversations.mdx +31 -21
- package/docs/agents/04-harnesses.mdx +7 -3
- package/docs/agents/meta.ts +1 -1
- package/docs/client/03-presence.mdx +18 -14
- package/docs/client/meta.ts +1 -1
- 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 +31 -26
- package/docs/reference/01-core.mdx +12 -12
- package/docs/reference/02-server.mdx +247 -192
- package/docs/reference/04-ai.mdx +29 -20
- package/docs/reference/05-telemetry.mdx +146 -0
- package/docs/reference/06-errors.mdx +24 -18
- 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-consumers.mdx +415 -0
- package/docs/server/02-timers.mdx +33 -23
- 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 +60 -50
- package/docs/stores/01-overview.mdx +37 -0
- package/docs/stores/02-redis.mdx +57 -0
- package/docs/stores/03-redis-http.mdx +56 -0
- package/docs/stores/04-postgres.mdx +172 -0
- package/docs/stores/05-sqlite.mdx +36 -0
- package/docs/stores/06-memory.mdx +22 -0
- package/docs/stores/meta.ts +1 -0
- package/docs/upgrades/01-overview.mdx +30 -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 +48 -11
- package/examples/playground/app/a0/apps/[appId]/route.ts +6 -10
- package/examples/playground/app/a0/chat.test.tsx +421 -330
- package/examples/playground/app/a0/sandbox-log.ts +17 -15
- package/examples/playground/app/a0/server.ts +4 -4
- package/examples/playground/app/a0/shell.tsx +1 -1
- package/examples/playground/app/a0/uploads/route.test.ts +59 -0
- 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 +206 -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/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/_journal.json +34 -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/[agentId]/agent-queue.test.tsx +116 -90
- 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/server.ts +3 -3
- package/examples/playground/app/chat/[chatId]/agent-stream-drawer.test.tsx +2 -2
- package/examples/playground/app/chat/[chatId]/chat-client.tsx +1 -1
- package/examples/playground/app/chat/server.test.ts +38 -65
- package/examples/playground/app/chat/server.ts +45 -35
- package/examples/playground/app/components/activity-history.test.tsx +60 -18
- package/examples/playground/app/components/demo-icons.tsx +7 -0
- package/examples/playground/app/components/demos.tsx +7 -0
- package/examples/playground/app/components/fast-link.test.tsx +3 -5
- package/examples/playground/app/initial-state.test.tsx +86 -45
- 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/next.config.ts +6 -1
- package/examples/playground/package.json +13 -1
- package/examples/playground/scripts/migrate.mjs +31 -0
- 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/vercel.json +9 -0
- package/examples/playground/vitest.config.ts +27 -0
- package/examples/playground/vitest.drills.config.ts +32 -0
- package/package.json +7 -2
- package/sql/postgres/drizzle-schema.ts +206 -0
- package/sql/postgres/notifications.sql +24 -0
- package/sql/postgres/schema.sql +276 -0
- package/src/actor.ts +101 -71
- package/src/ai-agent.ts +154 -115
- package/src/ai-control-server.ts +8 -8
- package/src/ai-fx.ts +41 -9
- package/src/ai-harness.ts +28 -2
- package/src/ai-pi.ts +2 -2
- package/src/ai-server.ts +2 -2
- package/src/ai.ts +7 -7
- package/src/contract.ts +1 -1
- package/src/deterministic-id.ts +26 -3
- package/src/errors.ts +9 -6
- package/src/idempotent-replay.ts +0 -1
- package/src/index.ts +1 -1
- package/src/internal.ts +17 -6
- package/src/otel.ts +1 -1
- package/src/postgres-notifications.ts +0 -22
- package/src/postgres-resources.ts +6 -9
- package/src/postgres-schema.ts +121 -0
- package/src/push-envelope.ts +20 -0
- package/src/runtime-consumer.ts +10 -0
- package/src/scheduler-qstash.ts +12 -5
- package/src/scheduler-task.ts +2 -2
- package/src/scheduler-vercel.ts +10 -4
- package/src/server.browser.ts +1 -1
- package/src/server.ts +839 -238
- package/src/session-socket.ts +1 -1
- package/src/store-codec.ts +66 -0
- package/src/store-memory.ts +581 -255
- package/src/store-postgres.ts +1984 -943
- package/src/store-redis-core.ts +1624 -1152
- package/src/store-redis-notify.ts +2 -0
- package/src/store-sqlite.ts +891 -486
- package/src/store.ts +227 -97
- package/src/telemetry.ts +2 -1
- package/src/wire.ts +1 -0
- package/dist/ai-harness-BuuC-gJ3.js.map +0 -1
- package/dist/ai-harness-CV6rXbB3.d.ts.map +0 -1
- package/dist/ai-mMiWul1T.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-D-Y4pqwu.js.map +0 -1
- package/dist/server-DYAATFuT.d.ts.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-redis-notify-CM0AfLY0.js +0 -2544
- package/dist/store-redis-notify-CM0AfLY0.js.map +0 -1
- package/dist/telemetry-Cyfqo6Qt.d.ts.map +0 -1
- package/dist/wire-BKxH1vbP.js.map +0 -1
- package/docs/reference/05-adapters.mdx +0 -351
- package/docs/server/01-handlers.mdx +0 -200
- package/docs/server/05-production.mdx +0 -323
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: '0.21'
|
|
3
|
+
description: Handlers become consumers, and every store keeps their places in a new layout.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Handlers become consumers
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
// before: server/orders.ts on 0.20
|
|
10
|
+
export const ordersServer = createServer({
|
|
11
|
+
contract: orders,
|
|
12
|
+
handlers: {
|
|
13
|
+
created: async ({ event }) => {
|
|
14
|
+
// await sendEmailToShop(event.payload.shopId, { idempotencyKey: event.id })
|
|
15
|
+
return { type: 'shop.notified', payload: {} }
|
|
16
|
+
},
|
|
17
|
+
'shop.started': { lane: 'shop', handler: async () => {} },
|
|
18
|
+
'shop.notified': { lane: 'shop', handler: async () => {} },
|
|
19
|
+
},
|
|
20
|
+
})
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
```ts server/orders.ts
|
|
24
|
+
import { createServer } from 'experimental-a2/server'
|
|
25
|
+
import { orders } from '@/contracts'
|
|
26
|
+
|
|
27
|
+
export const ordersServer = createServer({
|
|
28
|
+
contract: orders,
|
|
29
|
+
consumers: [
|
|
30
|
+
{
|
|
31
|
+
name: 'notify-shop',
|
|
32
|
+
types: ['created'],
|
|
33
|
+
run: async ({ event }) => {
|
|
34
|
+
// await sendEmailToShop(event.payload.shopId, { idempotencyKey: event.id })
|
|
35
|
+
return { type: 'shop.notified', payload: {} }
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
// the types that shared the 'shop' lane, as one consumer:
|
|
39
|
+
{
|
|
40
|
+
name: 'shop-work',
|
|
41
|
+
types: ['shop.started', 'shop.notified'],
|
|
42
|
+
lane: () => 'shop',
|
|
43
|
+
run: async ({ event }) => {
|
|
44
|
+
// your work for event.type
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
})
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`handlers` are gone: [consumers](/server/consumers) are the one way to react
|
|
52
|
+
to events. Each entry of a handler table becomes a consumer that takes its
|
|
53
|
+
type, with the same function as its `run`, and its `abortOn` if it had one. A
|
|
54
|
+
lane belongs to one consumer, so entries that shared a lane become one consumer
|
|
55
|
+
that takes all their types, with a lane function returning that lane. A
|
|
56
|
+
consumer's name is its identity: its cursors, failures, and dead letters key
|
|
57
|
+
on it, so pick names you'll keep.
|
|
58
|
+
|
|
59
|
+
| Before | 0.21 |
|
|
60
|
+
| --- | --- |
|
|
61
|
+
| `createServer({ handlers })` | `createServer({ consumers })` |
|
|
62
|
+
| `handler(entry)` | `{ name, types?, lane?, run }` in `consumers`, or `consumer({ contract, ... }).run(fn)` |
|
|
63
|
+
| `Handler`, `HandlerEntry` | removed: `consumer()` types `run`'s context |
|
|
64
|
+
| `Lane` | `ConsumerLane` |
|
|
65
|
+
| `HandlerContext` | `ConsumerContext` |
|
|
66
|
+
| `HandlerAppend` | `ConsumerAppend` |
|
|
67
|
+
| `handlerContext()`, from `experimental-a2/ai` | `consumerContext()` |
|
|
68
|
+
| `runWithHandlerContext`, from `experimental-a2/ai/server` | `runWithConsumerContext` |
|
|
69
|
+
| `agent({ handlers })` | `agent({ consumers })` |
|
|
70
|
+
| `session.redrive(index, { action })` | `session.redrive({ consumer, index }, { action })` |
|
|
71
|
+
| `a2.append.source` = `handler` | `a2.append.source` = `consumer` |
|
|
72
|
+
|
|
73
|
+
- **Dead letters name their consumer.** An event dead-letters for one
|
|
74
|
+
consumer, so each entry from `session.deadLetters()` carries `consumer`.
|
|
75
|
+
Pass it to `redrive` with the index. See
|
|
76
|
+
[Resolve a dead letter](/server/consumers#resolve-a-dead-letter).
|
|
77
|
+
- **Agents take `consumers`.** Write them with `consumer()` against the agent's
|
|
78
|
+
contract, as in [Conversations](/agents/conversations). A consumer of an AI
|
|
79
|
+
event type runs beside the agent's own, so `agent()` no longer rejects one.
|
|
80
|
+
- **Actors keep `actor({ handlers })`.** Nothing changes in an actor's code.
|
|
81
|
+
- **A2's own consumers start with `a2.`** Actors and agents run on consumers
|
|
82
|
+
named `a2.actor`, `a2.actor.concurrent`, `a2.ai.metadata`, `a2.ai.control`,
|
|
83
|
+
`a2.ai.model`, and `a2.ai.tools`, which their dead letters name. Their event
|
|
84
|
+
ids are unchanged. Names starting with `a2.` are reserved.
|
|
85
|
+
- **Telemetry.** Dashboards that filter `a2.append.source` on `handler` read
|
|
86
|
+
`consumer` instead. Each `a2.event` span also carries `a2.event.consumer`;
|
|
87
|
+
see [Telemetry](/reference/telemetry).
|
|
88
|
+
- **Custom stores** implement the cursor contract: `cursors()`, `wakeLog`, and
|
|
89
|
+
the cursor-keyed claim, completion, failure, and redrive methods. See
|
|
90
|
+
[`A2Store`](/reference/server#a2store).
|
|
91
|
+
|
|
92
|
+
## Postgres
|
|
93
|
+
|
|
94
|
+
0.21 keeps each consumer's place in new tables, `a2_cursors` and
|
|
95
|
+
`a2_lane_events`. Its `schema.sql` creates them, then drops the columns earlier
|
|
96
|
+
releases dispatched by, and the `a2_schema` table 0.20 reads on a cold start.
|
|
97
|
+
Earlier deployments can't run against the new tables, and 0.21 needs them.
|
|
98
|
+
|
|
99
|
+
1. **Let earlier deployments finish their work.** Applying the schema drops
|
|
100
|
+
any work their handlers haven't finished, since 0.21 runs no handlers to
|
|
101
|
+
finish it. This query counts that work, dead letters included (retry or
|
|
102
|
+
skip those first, or accept that they're dropped):
|
|
103
|
+
|
|
104
|
+
```sql
|
|
105
|
+
select count(*) from a2_events where processed_at is null
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
It doesn't count timers. On [Vercel Queues](/schedulers/vercel-queues#deployments),
|
|
109
|
+
each one goes to the deployment that scheduled it, which can't write once
|
|
110
|
+
the schema is applied, so a timer still waiting then is lost. Let them fire,
|
|
111
|
+
or schedule the ones you need again on 0.21.
|
|
112
|
+
|
|
113
|
+
2. **Apply 0.21's `schema.sql` while none of them is running**, as the role
|
|
114
|
+
your app connects with:
|
|
115
|
+
`node_modules/experimental-a2/sql/postgres/schema.sql`. Every event is
|
|
116
|
+
kept. There's no separate migration: the file is the whole schema, and
|
|
117
|
+
each statement creates what's missing or removes what an earlier release
|
|
118
|
+
used, so it brings a database from any release to 0.21 and changes nothing
|
|
119
|
+
when you apply it again.
|
|
120
|
+
3. **Deploy 0.21.** Deployed before the schema, its queries that reach a
|
|
121
|
+
missing table throw `STORE_NOT_CONFIGURED`, naming the file, and the next
|
|
122
|
+
request after you apply it works, with no redeploy.
|
|
123
|
+
|
|
124
|
+
0.21's `schema.sql` also creates a function, `a2_batch`, that each append,
|
|
125
|
+
claim, completion, and `consume` poll runs through in one round trip. Applying
|
|
126
|
+
the file creates it; with Drizzle, add it as a custom migration (see
|
|
127
|
+
[With Drizzle](/stores/postgres#with-drizzle)). Without it, 0.21's writes throw
|
|
128
|
+
`STORE_NOT_CONFIGURED`, naming `schema.sql`.
|
|
129
|
+
|
|
130
|
+
0.21 also checks nothing up front: no schema version read before the first
|
|
131
|
+
query. A query that finds a table or column missing, or that its role can't
|
|
132
|
+
use, throws `STORE_NOT_CONFIGURED`, naming the file to apply or the grant to
|
|
133
|
+
make. With Drizzle, `drizzle-schema.ts` shows the new tables; on a database
|
|
134
|
+
that already has A2's tables, use `schema.sql` as the migration for them. See
|
|
135
|
+
[With Drizzle](/stores/postgres#with-drizzle).
|
|
136
|
+
|
|
137
|
+
## SQLite
|
|
138
|
+
|
|
139
|
+
A file an earlier release wrote throws `STORE_NOT_CONFIGURED` when the store
|
|
140
|
+
opens it. Delete it, `.a2/dev.db` by default, or pass another `path`. Its
|
|
141
|
+
sessions don't carry over.
|
|
142
|
+
|
|
143
|
+
## Redis
|
|
144
|
+
|
|
145
|
+
0.21 keeps each consumer's place in keys of its own, and reads the log,
|
|
146
|
+
snapshots, and clears that earlier releases wrote as they are. Keep your
|
|
147
|
+
`keyPrefix`.
|
|
148
|
+
|
|
149
|
+
1. **Let earlier deployments finish their work, then stop them.** 0.21 runs
|
|
150
|
+
no handlers, so the work theirs haven't finished, dead letters included, is
|
|
151
|
+
dropped. An earlier deployment still running beside 0.21 keeps its own lanes
|
|
152
|
+
on the same prefix, so one lane's events can run on both at once. On
|
|
153
|
+
[Vercel Queues](/schedulers/vercel-queues#deployments) it also keeps
|
|
154
|
+
receiving the timers it scheduled, until they fire: let them, or remove the
|
|
155
|
+
deployment and schedule the ones you need again on 0.21.
|
|
156
|
+
2. **Deploy 0.21.** Every event is kept. A consumer starts at the next event
|
|
157
|
+
appended, or with `backfill` at the beginning of the log.
|
|
158
|
+
|
|
159
|
+
The keys earlier releases dispatched by (`meta`, `meta:{n}`, `meta-split`,
|
|
160
|
+
`pending`, `ready`, `snapshot-pins:{n}`, `snapshot-pin-counts`, under each
|
|
161
|
+
session) stay until you delete them; 0.21 never reads them.
|
|
162
|
+
|
|
163
|
+
## Memory
|
|
164
|
+
|
|
165
|
+
Nothing to do: the memory store starts empty in every process.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: '0.20'
|
|
3
|
+
description: The Postgres store no longer creates its tables. You apply them, before you deploy.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Postgres
|
|
7
|
+
|
|
8
|
+
Before you deploy 0.20, apply the SQL its store needs,
|
|
9
|
+
`node_modules/experimental-a2/sql/postgres/schema.sql`, once per database,
|
|
10
|
+
however you run SQL against it. It creates only what's missing and leaves
|
|
11
|
+
existing data alone, so it's safe on a database an earlier release set up, and
|
|
12
|
+
the deployment running now keeps working against it, without waiting on its
|
|
13
|
+
writes.
|
|
14
|
+
|
|
15
|
+
- **Apply it as the role your app connects with.** It records a schema version
|
|
16
|
+
in a new `a2_schema` table, which the store reads on a cold start. If
|
|
17
|
+
another role applies it, grant your app's role `select` on it:
|
|
18
|
+
|
|
19
|
+
```sql
|
|
20
|
+
grant select on a2_schema to your_app_role;
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
- **With `listenConnectionString`**, also apply
|
|
24
|
+
`sql/postgres/notifications.sql`. It installs the triggers the store used to
|
|
25
|
+
install for you.
|
|
26
|
+
- **The app's role needs no DDL rights.** Read and write access to A2's tables
|
|
27
|
+
is enough.
|
|
28
|
+
|
|
29
|
+
## If you deploy first
|
|
30
|
+
|
|
31
|
+
The store throws `STORE_NOT_CONFIGURED` on its first query, naming the file to
|
|
32
|
+
apply. Until you apply it, nothing is written: appends fail, and scheduled work
|
|
33
|
+
waits. Vercel Queues keeps redelivering it, and the QStash scheduler hands each
|
|
34
|
+
task to a later message, 30 seconds out and backing off to one day. The next
|
|
35
|
+
call after you apply the file succeeds, with no redeploy.
|
|
36
|
+
|
|
37
|
+
## Other stores
|
|
38
|
+
|
|
39
|
+
Nothing to do. SQLite still creates its tables when it opens its file, and
|
|
40
|
+
Redis creates its keys as it writes.
|
|
41
|
+
|
|
42
|
+
Moving past 0.20 to 0.21 or later? Apply that release's `schema.sql` instead,
|
|
43
|
+
the way [0.21](/upgrades/0-21) says. It drops `a2_schema`.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: '0.19'
|
|
3
|
+
description: An agent's log stores each message once, so agent sessions from earlier releases don't fold.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Start new agent sessions
|
|
7
|
+
|
|
8
|
+
0.19 stores each message's content once, in the `ai.control.requested` command
|
|
9
|
+
that brought it in. The AI reducers advance to `a2.ai.state.v27`,
|
|
10
|
+
`a2.ai.context.v7`, and `a2.ai.control.v6`, and a log written before 0.19
|
|
11
|
+
doesn't fold: `reduceAIState()` and `deriveUIMessages()` throw on an
|
|
12
|
+
`ai.message.created` that no command explains. Start new agent sessions after
|
|
13
|
+
you deploy. Earlier sessions keep their events in history, but their state
|
|
14
|
+
can't be read.
|
|
15
|
+
|
|
16
|
+
## Payloads and types
|
|
17
|
+
|
|
18
|
+
Code that reads the AI log directly changes with it:
|
|
19
|
+
|
|
20
|
+
- **`ai.message.created`** records an admission as
|
|
21
|
+
`{ messageId, role, generate? }`, without the message.
|
|
22
|
+
`MessageCreatedPayload` takes no type argument.
|
|
23
|
+
- **`ai.control.committed`** carries no message content. Queued inputs and
|
|
24
|
+
`AIState.active.input` are `InputRef`s, `{ id, role, generate, afterStepOf? }`,
|
|
25
|
+
and `ActiveTurn` takes no type argument. `AIState.inbox.items` keep their
|
|
26
|
+
`message`, now with `role`.
|
|
27
|
+
|
|
28
|
+
## Provider tools under `fx()` and `pi()`
|
|
29
|
+
|
|
30
|
+
`agent()` throws a `TypeError` when a provider-defined tool
|
|
31
|
+
(`type: 'provider'`, such as a provider's web search) is passed with `fx()` or
|
|
32
|
+
`pi()`. Before, both harnesses dropped it without a word, so the model never
|
|
33
|
+
saw it. Take it out of those agents' `tools`.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: '0.18'
|
|
3
|
+
description: An agent's loop is its harness, and the AI SDK loop's options move into it.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Move loop options into `harness`
|
|
7
|
+
|
|
8
|
+
```ts server/assistant.ts
|
|
9
|
+
import { agent } from 'experimental-a2/ai/server'
|
|
10
|
+
|
|
11
|
+
export const assistant = agent({
|
|
12
|
+
name: 'support-agent',
|
|
13
|
+
model: 'openai/gpt-5.6-terra',
|
|
14
|
+
instructions: 'Help the customer. Be concise.',
|
|
15
|
+
harness: {
|
|
16
|
+
type: 'ai-sdk',
|
|
17
|
+
// generation, generate, compaction, and messageMetadata go here:
|
|
18
|
+
compaction: { thresholdTokens: 80_000 },
|
|
19
|
+
},
|
|
20
|
+
})
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`agent()` takes its loop as `harness`. The AI SDK's loop is the default,
|
|
24
|
+
`{ type: 'ai-sdk' }`, and its options move into it: `generation`, `generate`,
|
|
25
|
+
`compaction`, and `messageMetadata` are no longer top-level `agent()` options,
|
|
26
|
+
and passing one there throws a `TypeError`. See
|
|
27
|
+
[Harnesses](/agents/harnesses).
|
|
28
|
+
|
|
29
|
+
## Models are AI Gateway IDs
|
|
30
|
+
|
|
31
|
+
`model` is an AI Gateway model ID, or `ModelSettings` with one, under every
|
|
32
|
+
harness. `agent()` no longer takes AI SDK provider model objects, and
|
|
33
|
+
`HarnessModelSettings` is gone: use `ModelSettings`.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: '0.17'
|
|
3
|
+
description: Redis stores need a fresh key prefix, and agents are served with `agent()`.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Redis: a fresh `keyPrefix`
|
|
7
|
+
|
|
8
|
+
```ts server/store.ts
|
|
9
|
+
import { redis } from 'experimental-a2/store-redis'
|
|
10
|
+
|
|
11
|
+
export const store = redis({
|
|
12
|
+
url: process.env.REDIS_URL,
|
|
13
|
+
// a prefix no earlier release wrote under:
|
|
14
|
+
keyPrefix: 'a2-017',
|
|
15
|
+
})
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Both Redis stores change how they keep event ids and per-event metadata: one
|
|
19
|
+
key per event id instead of one shared hash, and metadata in hashes of 1,000
|
|
20
|
+
events. 0.17 no longer reads the shared id hash, and earlier releases can't
|
|
21
|
+
see what 0.17 writes, so on a shared prefix a retry on either side can append
|
|
22
|
+
a duplicate event, re-run work, or stall a lane. Switch to a fresh `keyPrefix`
|
|
23
|
+
when you upgrade, and don't roll back to an earlier release on a prefix 0.17
|
|
24
|
+
has written. Vercel Queues keeps running recovery and scheduled work on the
|
|
25
|
+
deployment that published it for up to seven days, so earlier deployments keep
|
|
26
|
+
touching their sessions after you upgrade.
|
|
27
|
+
|
|
28
|
+
## Before you clear sessions
|
|
29
|
+
|
|
30
|
+
0.17 adds [`session.clear()`](/contract/sessions#clearing-a-session). Upgrade
|
|
31
|
+
every instance before you clear any session: an earlier release ignores the
|
|
32
|
+
clear and can run cleared events in the new lifetime. Upgrade every scheduler
|
|
33
|
+
route before you schedule clears, and don't roll back while clear tasks are
|
|
34
|
+
pending.
|
|
35
|
+
|
|
36
|
+
## Vercel Queues
|
|
37
|
+
|
|
38
|
+
`vercelQueues({ scheduledAppends })` is replaced by
|
|
39
|
+
`deploymentId?: string | null`: leave it out to deliver to the current
|
|
40
|
+
deployment, pass a deployment ID to target one, or pass `null` for
|
|
41
|
+
deploymentless delivery. It applies to every task, recovery included, so
|
|
42
|
+
`null` makes recovery deploymentless too, unlike `'deploymentless'` did.
|
|
43
|
+
Changing it doesn't move tasks already queued. A custom `QueueTransport.send`
|
|
44
|
+
no longer takes a fourth routing argument. See
|
|
45
|
+
[Deployments](/schedulers/vercel-queues#deployments).
|
|
46
|
+
|
|
47
|
+
## Agents
|
|
48
|
+
|
|
49
|
+
| Before | 0.17 |
|
|
50
|
+
| --- | --- |
|
|
51
|
+
| `createAgentServer({ agent: definition, ... })` | `agent({ name, ... })`, or `agent({ contract, ... })` with a shared contract |
|
|
52
|
+
| `agent()` from `experimental-a2/ai` | `agentContract()` |
|
|
53
|
+
| `createReducer()`, `reducerName` | `agentContract({ reducer })` |
|
|
54
|
+
| `createHandlers()`, `authorizeAgentIngress` | removed: pass `handlers` and `events` to `agent()` |
|
|
55
|
+
| `handlerContext(agent)` | `handlerContext()`, or `handlerContext(contract)` |
|
|
56
|
+
| `request` in `generate` and `messageMetadata` | `event` |
|
|
57
|
+
| `requestId` | `event.id` |
|
|
58
|
+
| `responseMessageId` | `event.payload.responseMessageId` |
|
|
59
|
+
|
|
60
|
+
- **`agent({ contract })` takes only an `agentContract()`.** Any other contract
|
|
61
|
+
throws. A fully custom contract is core A2, served by `createServer()`.
|
|
62
|
+
- **Upgrade clients with the server.** A client from before 0.17 ignores the
|
|
63
|
+
new shape of streamed progress, so a response's output doesn't appear in it
|
|
64
|
+
until it reloads.
|
|
65
|
+
- **Custom telemetry recorders** must accept the new span names: core claim
|
|
66
|
+
and commit spans, and `a2.ai.*`.
|
|
67
|
+
|
|
68
|
+
0.21 later renames `handlers` to `consumers` and `handlerContext()` to
|
|
69
|
+
`consumerContext()`; see [0.21](/upgrades/0-21).
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
The event log runs with zero setup in development (SQLite at `.a2/dev.db`,
|
|
4
4
|
no scheduler). The Agent demo and A0 also need AI Gateway and Vercel Sandbox
|
|
5
|
-
credentials. Production needs
|
|
6
|
-
`node_modules/experimental-a2/docs/
|
|
5
|
+
credentials. Production needs a store
|
|
6
|
+
(`node_modules/experimental-a2/docs/stores/01-overview.mdx`) and a scheduler
|
|
7
|
+
(`node_modules/experimental-a2/docs/schedulers/01-overview.mdx`).
|
|
7
8
|
|
|
8
9
|
## 1. A store
|
|
9
10
|
|
|
@@ -23,7 +24,30 @@ Redis, poll cadence elsewhere. Upstash is durable by default; use its TCP
|
|
|
23
24
|
`rediss://` URL, not the REST one. Each feature creates its own server with
|
|
24
25
|
the shared store; storage remains namespaced per contract. Without either variable, a
|
|
25
26
|
production boot fails fast with `STORE_NOT_CONFIGURED` (by design).
|
|
26
|
-
|
|
27
|
+
Redis needs no setup. Postgres is migrated by every Vercel build: the
|
|
28
|
+
`vercel-build` script runs `scripts/migrate.mjs` (Drizzle's migrator, which,
|
|
29
|
+
unlike `drizzle-kit migrate`, prints the error when one fails) before the
|
|
30
|
+
usual `turbo run build`, through `DATABASE_URL_UNPOOLED` when it's set (the Neon
|
|
31
|
+
integration's direct connection). Previews and production share one database here, so a branch's
|
|
32
|
+
migration reaches it when its preview builds: keep migrations additive.
|
|
33
|
+
Elsewhere, migrate by hand (A2 never creates its tables at runtime):
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
DATABASE_URL=postgres://… pnpm --filter playground db:migrate
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
It applies every migration in `app/a2slack/db/migrations`. A2's tables are in
|
|
40
|
+
the Drizzle schema with a2slack's: `app/a2slack/db/a2.ts` is a copy of
|
|
41
|
+
`experimental-a2`'s `sql/postgres/drizzle-schema.ts`. drizzle-kit generated
|
|
42
|
+
their migration (`0002_a2.sql`) from it, and its SQL is `schema.sql`'s
|
|
43
|
+
instead of the generated `CREATE TABLE`s, because the shared database already
|
|
44
|
+
had A2's tables: it creates what's missing and upgrades the rest. Drizzle
|
|
45
|
+
can't declare A2's `a2_batch` function, so `0003_a2_batch.sql` is a custom
|
|
46
|
+
migration (`drizzle-kit generate --custom`) holding `schema.sql`'s block for
|
|
47
|
+
it. When a
|
|
48
|
+
release of `experimental-a2` changes its tables (its changelog says so),
|
|
49
|
+
update `a2.ts` from the new file, run `pnpm db:generate`, and read the SQL it
|
|
50
|
+
writes.
|
|
27
51
|
|
|
28
52
|
To verify a provider before leaning on it, run the full conformance
|
|
29
53
|
kit against it (throwaway database; the suite isolates by key prefix
|
|
@@ -36,6 +60,19 @@ TEST_REDIS_URL=rediss://… pnpm --filter a2 exec vitest run test/store-redis.co
|
|
|
36
60
|
`pg` and `ioredis` are optional peer dependencies of `experimental-a2` and direct
|
|
37
61
|
dependencies of this app.
|
|
38
62
|
|
|
63
|
+
a2slack keeps its tables in the Postgres at `DATABASE_URL`, defined with
|
|
64
|
+
Drizzle in `app/a2slack/db/schema.ts`. With `REDIS_URL` set too, A2 runs on
|
|
65
|
+
Redis and only a2slack's tables are used in Postgres (A2's are created there
|
|
66
|
+
too, and stay empty). Generate a migration with `pnpm db:generate` after
|
|
67
|
+
changing the schema; SQL drizzle-kit can't express, like an extension, goes
|
|
68
|
+
in a custom one (`pnpm db:generate --custom --name …`).
|
|
69
|
+
|
|
70
|
+
Its search index lives in Upstash Redis Search, through the Redis's REST
|
|
71
|
+
credentials (`KV_REST_API_URL` and `KV_REST_API_TOKEN`, or this project's
|
|
72
|
+
`REDIS_KV_REST_API_*`). The index is created the first time it's written.
|
|
73
|
+
Without them, search reads the Postgres table instead. In development the
|
|
74
|
+
tables live in PGlite at `.a2/a2slack`, migrated when it opens.
|
|
75
|
+
|
|
39
76
|
## 2. Vercel Queues scheduler
|
|
40
77
|
|
|
41
78
|
Queues need no configuration beyond the triggers in `vercel.json`. A0, Orders,
|
|
@@ -105,8 +142,8 @@ A sandbox stays up while it's used: each tool call keeps it running at least
|
|
|
105
142
|
10 more minutes, and so does the preview while it's on screen. Otherwise it
|
|
106
143
|
stops, and resumes with its files the next time it's needed. A new or resumed
|
|
107
144
|
sandbox starts `next dev` in the background, so commands don't wait for it;
|
|
108
|
-
the preview does. Deleting an app
|
|
109
|
-
|
|
145
|
+
the preview does. Deleting an app clears its conversation from the log and
|
|
146
|
+
deletes its sandbox. An app nobody uses for 14 days loses its files.
|
|
110
147
|
|
|
111
148
|
App ids are the only access control: anyone with an A0 URL can open that app,
|
|
112
149
|
and anyone who can reach the deployment can start new apps, each with its own
|
|
@@ -122,8 +159,7 @@ the project, which sets `BLOB_READ_WRITE_TOKEN`; `vercel env pull` brings it
|
|
|
122
159
|
to local runs. Images go to the model with the message; other files go into
|
|
123
160
|
its text as links, which the agent downloads in its sandbox. Each lives at an
|
|
124
161
|
unguessable public URL under `a0/<app id>/`, which anyone with the URL can
|
|
125
|
-
read. Nothing deletes them
|
|
126
|
-
them.
|
|
162
|
+
read. Nothing deletes them, not even deleting their app.
|
|
127
163
|
|
|
128
164
|
A0's dev tools have a Crash button, which ends the function instance running
|
|
129
165
|
an app's agent, mid-turn, so the scheduler resumes the turn in a fresh one.
|
|
@@ -148,10 +184,11 @@ vercel deploy
|
|
|
148
184
|
```
|
|
149
185
|
|
|
150
186
|
The Recovery lab tests a real function timeout. Its dedicated trigger and
|
|
151
|
-
queue consumer both export `maxDuration = 20`. The pipeline
|
|
152
|
-
|
|
153
|
-
does not. A2 keeps dispatching events.
|
|
154
|
-
the third
|
|
187
|
+
queue consumer both export `maxDuration = 20`. The pipeline is four 8-second
|
|
188
|
+
steps, run in order by one A2 consumer, so every step fits in a fresh
|
|
189
|
+
invocation but the 32-second chain does not. A2 keeps dispatching events.
|
|
190
|
+
Vercel stops the first invocation while the third step is running, leaving
|
|
191
|
+
the consumer's cursor on that event.
|
|
155
192
|
The deadline-capped claim expires, its watchdog retries the same event in a
|
|
156
193
|
fresh function, and already completed events do not run again. The dedicated
|
|
157
194
|
`a2-recovery` topic keeps this artificial budget away from the
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { inputs } from 'experimental-a2/ai'
|
|
2
1
|
import { isAppId } from '../../apps'
|
|
3
2
|
import { deleteAppSandbox } from '../../sandbox'
|
|
4
3
|
import { agentServers } from '../../server'
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* Deletes the app: its conversation, then its sandbox. A clear reads no
|
|
7
|
+
* state, so it also deletes a log this A2 can no longer fold, and no pending
|
|
8
|
+
* step of the old conversation runs to bring the sandbox back.
|
|
9
9
|
*/
|
|
10
10
|
export async function DELETE(
|
|
11
11
|
_request: Request,
|
|
@@ -14,14 +14,10 @@ export async function DELETE(
|
|
|
14
14
|
const { appId } = await context.params
|
|
15
15
|
if (!isAppId(appId))
|
|
16
16
|
return Response.json({ error: 'Unknown app.' }, { status: 404 })
|
|
17
|
-
//
|
|
17
|
+
// Only one harness's agent has the app's log; clearing the others writes
|
|
18
|
+
// nothing.
|
|
18
19
|
await Promise.all(
|
|
19
|
-
Object.values(agentServers).map(
|
|
20
|
-
const session = server.session(appId)
|
|
21
|
-
const { state } = await session.state()
|
|
22
|
-
if (state.active)
|
|
23
|
-
await session.append(...inputs.stop({ turnId: state.active.turnId }))
|
|
24
|
-
}),
|
|
20
|
+
Object.values(agentServers).map((server) => server.session(appId).clear()),
|
|
25
21
|
)
|
|
26
22
|
await deleteAppSandbox({ appId })
|
|
27
23
|
return Response.json({ ok: true })
|