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.
Files changed (320) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +1 -1
  3. package/dist/{actor-C7b-1arn.d.ts → actor-HnGmrOZZ.d.ts} +5 -5
  4. package/dist/{actor-C7b-1arn.d.ts.map → actor-HnGmrOZZ.d.ts.map} +1 -1
  5. package/dist/actor-client.d.ts +1 -1
  6. package/dist/actor-client.js +2 -2
  7. package/dist/actor-react.d.ts +3 -3
  8. package/dist/actor-react.js +5 -5
  9. package/dist/{actor-shared-t7w8xIKP.js → actor-shared-DFET5q16.js} +3 -3
  10. package/dist/{actor-shared-t7w8xIKP.js.map → actor-shared-DFET5q16.js.map} +1 -1
  11. package/dist/actor.d.ts +1 -1
  12. package/dist/actor.js +54 -43
  13. package/dist/actor.js.map +1 -1
  14. package/dist/{ai-Cz3hqVhk.d.ts → ai-CvEnqVfo.d.ts} +8 -8
  15. package/dist/{ai-Cz3hqVhk.d.ts.map → ai-CvEnqVfo.d.ts.map} +1 -1
  16. package/dist/{ai-mMiWul1T.js → ai-CzE672Q3.js} +9 -9
  17. package/dist/ai-CzE672Q3.js.map +1 -0
  18. package/dist/ai-client.d.ts +2 -2
  19. package/dist/ai-client.js +2 -2
  20. package/dist/ai-fx.d.ts +1 -1
  21. package/dist/ai-fx.d.ts.map +1 -1
  22. package/dist/ai-fx.js +36 -9
  23. package/dist/ai-fx.js.map +1 -1
  24. package/dist/{ai-harness-CV6rXbB3.d.ts → ai-harness-Bfk-Lycn.d.ts} +2 -2
  25. package/dist/ai-harness-Bfk-Lycn.d.ts.map +1 -0
  26. package/dist/{ai-harness-BuuC-gJ3.js → ai-harness-dQeJaR1t.js} +17 -4
  27. package/dist/ai-harness-dQeJaR1t.js.map +1 -0
  28. package/dist/ai-pi.d.ts +1 -1
  29. package/dist/ai-pi.js +4 -4
  30. package/dist/ai-pi.js.map +1 -1
  31. package/dist/ai-react.d.ts +5 -5
  32. package/dist/ai-react.js +2 -2
  33. package/dist/ai-server.d.ts +14 -17
  34. package/dist/ai-server.d.ts.map +1 -1
  35. package/dist/ai-server.js +84 -75
  36. package/dist/ai-server.js.map +1 -1
  37. package/dist/ai.d.ts +2 -2
  38. package/dist/ai.js +2 -2
  39. package/dist/{client-BhHLoNKF.js → client-9IeTsiAs.js} +5 -5
  40. package/dist/{client-BhHLoNKF.js.map → client-9IeTsiAs.js.map} +1 -1
  41. package/dist/{client-h_DKFAjj.d.ts → client-CUMbS_38.d.ts} +3 -3
  42. package/dist/{client-h_DKFAjj.d.ts.map → client-CUMbS_38.d.ts.map} +1 -1
  43. package/dist/client.d.ts +3 -3
  44. package/dist/client.js +1 -1
  45. package/dist/{contract-BYKASlCV.js → contract-CWukt01d.js} +2 -2
  46. package/dist/contract-CWukt01d.js.map +1 -0
  47. package/dist/index.d.ts +5 -5
  48. package/dist/index.js +3 -3
  49. package/dist/{initial-snapshot-Bx-c0pDP.d.ts → initial-snapshot-D3Lve43o.d.ts} +4 -4
  50. package/dist/initial-snapshot-D3Lve43o.d.ts.map +1 -0
  51. package/dist/{internal-CscSY7K_.js → internal-Bc4PvfHk.js} +10 -3
  52. package/dist/internal-Bc4PvfHk.js.map +1 -0
  53. package/dist/otel.d.ts +1 -1
  54. package/dist/otel.js +1 -1
  55. package/dist/otel.js.map +1 -1
  56. package/dist/{react-BBXgHWFc.d.ts → react-BoHvC3v9.d.ts} +4 -4
  57. package/dist/{react-BBXgHWFc.d.ts.map → react-BoHvC3v9.d.ts.map} +1 -1
  58. package/dist/react.d.ts +1 -1
  59. package/dist/react.js +1 -1
  60. package/dist/{reducer-CqA59Q6U.d.ts → reducer-CYPHsSmd.d.ts} +2 -2
  61. package/dist/{reducer-CqA59Q6U.d.ts.map → reducer-CYPHsSmd.d.ts.map} +1 -1
  62. package/dist/{reducer-BAXc8JyF.js → reducer-CjmvZSAb.js} +2 -2
  63. package/dist/{reducer-BAXc8JyF.js.map → reducer-CjmvZSAb.js.map} +1 -1
  64. package/dist/runtime-consumer-BWmht60z.js +12 -0
  65. package/dist/runtime-consumer-BWmht60z.js.map +1 -0
  66. package/dist/scheduler-qstash.d.ts +2 -2
  67. package/dist/scheduler-qstash.d.ts.map +1 -1
  68. package/dist/scheduler-qstash.js +7 -7
  69. package/dist/scheduler-qstash.js.map +1 -1
  70. package/dist/scheduler-task-X1SToDjP.js.map +1 -1
  71. package/dist/scheduler-vercel.d.ts +2 -2
  72. package/dist/scheduler-vercel.d.ts.map +1 -1
  73. package/dist/scheduler-vercel.js +5 -4
  74. package/dist/scheduler-vercel.js.map +1 -1
  75. package/dist/{server-DYAATFuT.d.ts → server-BshFZ0eL.d.ts} +108 -41
  76. package/dist/server-BshFZ0eL.d.ts.map +1 -0
  77. package/dist/{server-D-Y4pqwu.js → server-DvIB8kj5.js} +423 -135
  78. package/dist/server-DvIB8kj5.js.map +1 -0
  79. package/dist/server.browser.js +1 -1
  80. package/dist/server.browser.js.map +1 -1
  81. package/dist/server.d.ts +4 -4
  82. package/dist/server.js +2 -2
  83. package/dist/{session-clear-C-QwTXV1.d.ts → session-clear-Bk_29eEw.d.ts} +2 -2
  84. package/dist/session-clear-Bk_29eEw.d.ts.map +1 -0
  85. package/dist/{session-clear-DGI4jTQ5.js → session-clear-wHd5GCpZ.js} +5 -4
  86. package/dist/session-clear-wHd5GCpZ.js.map +1 -0
  87. package/dist/store-N8PXxDAS.js.map +1 -1
  88. package/dist/store-Z2qjw50n.d.ts +523 -0
  89. package/dist/store-Z2qjw50n.d.ts.map +1 -0
  90. package/dist/store-codec-DwrWlgQx.js +65 -0
  91. package/dist/store-codec-DwrWlgQx.js.map +1 -0
  92. package/dist/store-memory.d.ts +1 -1
  93. package/dist/store-memory.d.ts.map +1 -1
  94. package/dist/store-memory.js +393 -210
  95. package/dist/store-memory.js.map +1 -1
  96. package/dist/{store-polling-7WJV0YWP.js → store-polling-DYNyxCev.js} +2 -2
  97. package/dist/{store-polling-7WJV0YWP.js.map → store-polling-DYNyxCev.js.map} +1 -1
  98. package/dist/store-postgres.d.ts +1 -1
  99. package/dist/store-postgres.d.ts.map +1 -1
  100. package/dist/store-postgres.js +1595 -673
  101. package/dist/store-postgres.js.map +1 -1
  102. package/dist/{store-presence-polling-B1ixeqIS.js → store-presence-polling-CzQ0wdtM.js} +3 -3
  103. package/dist/{store-presence-polling-B1ixeqIS.js.map → store-presence-polling-CzQ0wdtM.js.map} +1 -1
  104. package/dist/store-redis-http.d.ts +1 -1
  105. package/dist/store-redis-http.js +4 -4
  106. package/dist/store-redis-notify-IiVdlYHc.js +2919 -0
  107. package/dist/store-redis-notify-IiVdlYHc.js.map +1 -0
  108. package/dist/store-redis.d.ts +1 -1
  109. package/dist/store-redis.js +2 -2
  110. package/dist/store-sqlite.d.ts +1 -1
  111. package/dist/store-sqlite.d.ts.map +1 -1
  112. package/dist/store-sqlite.js +517 -287
  113. package/dist/store-sqlite.js.map +1 -1
  114. package/dist/{telemetry-Cyfqo6Qt.d.ts → telemetry-CWwsh-Gx.d.ts} +3 -3
  115. package/dist/telemetry-CWwsh-Gx.d.ts.map +1 -0
  116. package/dist/{wire-BKxH1vbP.js → wire-CZGqNZRx.js} +5 -4
  117. package/dist/wire-CZGqNZRx.js.map +1 -0
  118. package/docs/01-quickstart.mdx +28 -19
  119. package/docs/02-examples.mdx +1 -1
  120. package/docs/actors/01-introduction.mdx +9 -1
  121. package/docs/actors/02-concurrency.mdx +1 -1
  122. package/docs/actors/meta.ts +1 -1
  123. package/docs/agents/01-introduction.mdx +1 -1
  124. package/docs/agents/02-tools.mdx +13 -12
  125. package/docs/agents/03-conversations.mdx +31 -21
  126. package/docs/agents/04-harnesses.mdx +7 -3
  127. package/docs/agents/meta.ts +1 -1
  128. package/docs/client/03-presence.mdx +18 -14
  129. package/docs/client/meta.ts +1 -1
  130. package/docs/contract/01-events.mdx +2 -2
  131. package/docs/contract/02-state.mdx +18 -14
  132. package/docs/contract/03-sessions.mdx +32 -25
  133. package/docs/index.mdx +31 -26
  134. package/docs/reference/01-core.mdx +12 -12
  135. package/docs/reference/02-server.mdx +247 -192
  136. package/docs/reference/04-ai.mdx +29 -20
  137. package/docs/reference/05-telemetry.mdx +146 -0
  138. package/docs/reference/06-errors.mdx +24 -18
  139. package/docs/reference/meta.ts +1 -1
  140. package/docs/schedulers/01-overview.mdx +112 -0
  141. package/docs/schedulers/02-vercel-queues.mdx +87 -0
  142. package/docs/schedulers/03-qstash.mdx +123 -0
  143. package/docs/schedulers/meta.ts +1 -0
  144. package/docs/server/01-consumers.mdx +415 -0
  145. package/docs/server/02-timers.mdx +33 -23
  146. package/docs/server/03-cancellation.mdx +52 -44
  147. package/docs/server/04-routes.mdx +4 -1
  148. package/docs/server/05-sql-projections.mdx +391 -0
  149. package/docs/server/06-guarantees.mdx +60 -50
  150. package/docs/stores/01-overview.mdx +37 -0
  151. package/docs/stores/02-redis.mdx +57 -0
  152. package/docs/stores/03-redis-http.mdx +56 -0
  153. package/docs/stores/04-postgres.mdx +172 -0
  154. package/docs/stores/05-sqlite.mdx +36 -0
  155. package/docs/stores/06-memory.mdx +22 -0
  156. package/docs/stores/meta.ts +1 -0
  157. package/docs/upgrades/01-overview.mdx +30 -0
  158. package/docs/upgrades/79-0-21.mdx +165 -0
  159. package/docs/upgrades/80-0-20.mdx +43 -0
  160. package/docs/upgrades/81-0-19.mdx +33 -0
  161. package/docs/upgrades/82-0-18.mdx +33 -0
  162. package/docs/upgrades/83-0-17.mdx +69 -0
  163. package/docs/upgrades/meta.ts +3 -0
  164. package/examples/playground/DEPLOY.md +48 -11
  165. package/examples/playground/app/a0/apps/[appId]/route.ts +6 -10
  166. package/examples/playground/app/a0/chat.test.tsx +421 -330
  167. package/examples/playground/app/a0/sandbox-log.ts +17 -15
  168. package/examples/playground/app/a0/server.ts +4 -4
  169. package/examples/playground/app/a0/shell.tsx +1 -1
  170. package/examples/playground/app/a0/uploads/route.test.ts +59 -0
  171. package/examples/playground/app/a2slack/a2slack.css +1067 -0
  172. package/examples/playground/app/a2slack/c/[channel]/page.tsx +20 -0
  173. package/examples/playground/app/a2slack/consumers/agent.ts +63 -0
  174. package/examples/playground/app/a2slack/consumers/feed.ts +164 -0
  175. package/examples/playground/app/a2slack/consumers/notify.ts +116 -0
  176. package/examples/playground/app/a2slack/consumers/search.ts +37 -0
  177. package/examples/playground/app/a2slack/consumers/triage.ts +31 -0
  178. package/examples/playground/app/a2slack/consumers/welcome.ts +25 -0
  179. package/examples/playground/app/a2slack/db/a2.ts +206 -0
  180. package/examples/playground/app/a2slack/db/index.ts +73 -0
  181. package/examples/playground/app/a2slack/db/migrations/0000_extensions.sql +4 -0
  182. package/examples/playground/app/a2slack/db/migrations/0001_workspace.sql +74 -0
  183. package/examples/playground/app/a2slack/db/migrations/0002_a2.sql +215 -0
  184. package/examples/playground/app/a2slack/db/migrations/0003_a2_batch.sql +67 -0
  185. package/examples/playground/app/a2slack/db/migrations/meta/0000_snapshot.json +18 -0
  186. package/examples/playground/app/a2slack/db/migrations/meta/0001_snapshot.json +500 -0
  187. package/examples/playground/app/a2slack/db/migrations/meta/0002_snapshot.json +1159 -0
  188. package/examples/playground/app/a2slack/db/migrations/meta/0003_snapshot.json +1159 -0
  189. package/examples/playground/app/a2slack/db/migrations/meta/_journal.json +34 -0
  190. package/examples/playground/app/a2slack/db/queries.ts +263 -0
  191. package/examples/playground/app/a2slack/db/schema.ts +145 -0
  192. package/examples/playground/app/a2slack/drills/a2slack.drill.ts +708 -0
  193. package/examples/playground/app/a2slack/drills/harness.ts +244 -0
  194. package/examples/playground/app/a2slack/events/route.ts +36 -0
  195. package/examples/playground/app/a2slack/identity.ts +44 -0
  196. package/examples/playground/app/a2slack/inbox/route.ts +8 -0
  197. package/examples/playground/app/a2slack/lag/data/route.ts +8 -0
  198. package/examples/playground/app/a2slack/lag/page.tsx +101 -0
  199. package/examples/playground/app/a2slack/lag.ts +73 -0
  200. package/examples/playground/app/a2slack/layout.tsx +43 -0
  201. package/examples/playground/app/a2slack/messages/route.ts +17 -0
  202. package/examples/playground/app/a2slack/model.test.ts +208 -0
  203. package/examples/playground/app/a2slack/model.ts +369 -0
  204. package/examples/playground/app/a2slack/models.test.ts +72 -0
  205. package/examples/playground/app/a2slack/page.tsx +10 -0
  206. package/examples/playground/app/a2slack/scheduler/route.ts +5 -0
  207. package/examples/playground/app/a2slack/search/route.ts +40 -0
  208. package/examples/playground/app/a2slack/search-index.ts +108 -0
  209. package/examples/playground/app/a2slack/server.test.ts +315 -0
  210. package/examples/playground/app/a2slack/server.ts +55 -0
  211. package/examples/playground/app/a2slack/sessions.ts +50 -0
  212. package/examples/playground/app/a2slack/thread/route.ts +8 -0
  213. package/examples/playground/app/a2slack/ui/ChannelView.tsx +87 -0
  214. package/examples/playground/app/a2slack/ui/Composer.tsx +117 -0
  215. package/examples/playground/app/a2slack/ui/DotsShadow.tsx +42 -0
  216. package/examples/playground/app/a2slack/ui/Feed.tsx +157 -0
  217. package/examples/playground/app/a2slack/ui/InboxContext.tsx +63 -0
  218. package/examples/playground/app/a2slack/ui/InboxView.tsx +82 -0
  219. package/examples/playground/app/a2slack/ui/Menu.tsx +128 -0
  220. package/examples/playground/app/a2slack/ui/MessageRow.tsx +314 -0
  221. package/examples/playground/app/a2slack/ui/NameGate.tsx +35 -0
  222. package/examples/playground/app/a2slack/ui/SearchContext.tsx +146 -0
  223. package/examples/playground/app/a2slack/ui/SearchResults.tsx +97 -0
  224. package/examples/playground/app/a2slack/ui/Shell.tsx +34 -0
  225. package/examples/playground/app/a2slack/ui/Sidebar.tsx +199 -0
  226. package/examples/playground/app/a2slack/ui/ThreadPanel.tsx +134 -0
  227. package/examples/playground/app/a2slack/ui/useInbox.ts +71 -0
  228. package/examples/playground/app/agent/[agentId]/agent-queue.test.tsx +116 -90
  229. package/examples/playground/app/agent/[agentId]/compaction-panel.test.tsx +107 -76
  230. package/examples/playground/app/agent/ingress.test.ts +41 -17
  231. package/examples/playground/app/agent/model-input.test.tsx +92 -43
  232. package/examples/playground/app/agent/server.ts +3 -3
  233. package/examples/playground/app/chat/[chatId]/agent-stream-drawer.test.tsx +2 -2
  234. package/examples/playground/app/chat/[chatId]/chat-client.tsx +1 -1
  235. package/examples/playground/app/chat/server.test.ts +38 -65
  236. package/examples/playground/app/chat/server.ts +45 -35
  237. package/examples/playground/app/components/activity-history.test.tsx +60 -18
  238. package/examples/playground/app/components/demo-icons.tsx +7 -0
  239. package/examples/playground/app/components/demos.tsx +7 -0
  240. package/examples/playground/app/components/fast-link.test.tsx +3 -5
  241. package/examples/playground/app/initial-state.test.tsx +86 -45
  242. package/examples/playground/app/orders/[orderId]/order-client.tsx +2 -2
  243. package/examples/playground/app/orders/page.tsx +1 -1
  244. package/examples/playground/app/orders/server.ts +49 -30
  245. package/examples/playground/app/recovery/[recoveryId]/recovery-client.tsx +14 -15
  246. package/examples/playground/app/recovery/page.tsx +4 -4
  247. package/examples/playground/app/recovery/server.ts +23 -32
  248. package/examples/playground/drizzle.config.ts +14 -0
  249. package/examples/playground/next.config.ts +6 -1
  250. package/examples/playground/package.json +13 -1
  251. package/examples/playground/scripts/migrate.mjs +31 -0
  252. package/examples/playground/test/agent.ts +35 -0
  253. package/examples/playground/test/model.ts +149 -0
  254. package/examples/playground/test/serve.ts +184 -0
  255. package/examples/playground/test/server-only.ts +1 -0
  256. package/examples/playground/vercel.json +9 -0
  257. package/examples/playground/vitest.config.ts +27 -0
  258. package/examples/playground/vitest.drills.config.ts +32 -0
  259. package/package.json +7 -2
  260. package/sql/postgres/drizzle-schema.ts +206 -0
  261. package/sql/postgres/notifications.sql +24 -0
  262. package/sql/postgres/schema.sql +276 -0
  263. package/src/actor.ts +101 -71
  264. package/src/ai-agent.ts +154 -115
  265. package/src/ai-control-server.ts +8 -8
  266. package/src/ai-fx.ts +41 -9
  267. package/src/ai-harness.ts +28 -2
  268. package/src/ai-pi.ts +2 -2
  269. package/src/ai-server.ts +2 -2
  270. package/src/ai.ts +7 -7
  271. package/src/contract.ts +1 -1
  272. package/src/deterministic-id.ts +26 -3
  273. package/src/errors.ts +9 -6
  274. package/src/idempotent-replay.ts +0 -1
  275. package/src/index.ts +1 -1
  276. package/src/internal.ts +17 -6
  277. package/src/otel.ts +1 -1
  278. package/src/postgres-notifications.ts +0 -22
  279. package/src/postgres-resources.ts +6 -9
  280. package/src/postgres-schema.ts +121 -0
  281. package/src/push-envelope.ts +20 -0
  282. package/src/runtime-consumer.ts +10 -0
  283. package/src/scheduler-qstash.ts +12 -5
  284. package/src/scheduler-task.ts +2 -2
  285. package/src/scheduler-vercel.ts +10 -4
  286. package/src/server.browser.ts +1 -1
  287. package/src/server.ts +839 -238
  288. package/src/session-socket.ts +1 -1
  289. package/src/store-codec.ts +66 -0
  290. package/src/store-memory.ts +581 -255
  291. package/src/store-postgres.ts +1984 -943
  292. package/src/store-redis-core.ts +1624 -1152
  293. package/src/store-redis-notify.ts +2 -0
  294. package/src/store-sqlite.ts +891 -486
  295. package/src/store.ts +227 -97
  296. package/src/telemetry.ts +2 -1
  297. package/src/wire.ts +1 -0
  298. package/dist/ai-harness-BuuC-gJ3.js.map +0 -1
  299. package/dist/ai-harness-CV6rXbB3.d.ts.map +0 -1
  300. package/dist/ai-mMiWul1T.js.map +0 -1
  301. package/dist/contract-BYKASlCV.js.map +0 -1
  302. package/dist/idempotent-replay-JdgMQRRm.js +0 -21
  303. package/dist/idempotent-replay-JdgMQRRm.js.map +0 -1
  304. package/dist/initial-snapshot-Bx-c0pDP.d.ts.map +0 -1
  305. package/dist/internal-CscSY7K_.js.map +0 -1
  306. package/dist/server-D-Y4pqwu.js.map +0 -1
  307. package/dist/server-DYAATFuT.d.ts.map +0 -1
  308. package/dist/session-clear-C-QwTXV1.d.ts.map +0 -1
  309. package/dist/session-clear-DGI4jTQ5.js.map +0 -1
  310. package/dist/store-CyLUy86v.d.ts +0 -404
  311. package/dist/store-CyLUy86v.d.ts.map +0 -1
  312. package/dist/store-codec-DTG0Ftek.js +0 -10
  313. package/dist/store-codec-DTG0Ftek.js.map +0 -1
  314. package/dist/store-redis-notify-CM0AfLY0.js +0 -2544
  315. package/dist/store-redis-notify-CM0AfLY0.js.map +0 -1
  316. package/dist/telemetry-Cyfqo6Qt.d.ts.map +0 -1
  317. package/dist/wire-BKxH1vbP.js.map +0 -1
  318. package/docs/reference/05-adapters.mdx +0 -351
  319. package/docs/server/01-handlers.mdx +0 -200
  320. 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).
@@ -0,0 +1,3 @@
1
+ // A release's page is numbered 100 minus its minor version (79-0-21.mdx for
2
+ // 0.21), so the newest sorts first without renaming the others.
3
+ export default { order: 9 }
@@ -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 the deliberate pieces from
6
- `node_modules/experimental-a2/docs/server/05-production.mdx`.
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
- Schemas/keys are created lazily on first use; no migration step.
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 stops its turn and deletes its sandbox; its
109
- conversation stays in the log. An app nobody uses for 14 days loses its files.
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: the conversation that stays in the log still links
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 has four 8-second
152
- handlers, so every handler fits in a fresh invocation but the 32-second chain
153
- does not. A2 keeps dispatching events. Vercel stops the first invocation while
154
- the third handler is running, leaving that event without a processed marker.
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
- * Stops the agent's current turn, so no later tool call brings the sandbox
8
- * back, then deletes it. The conversation stays in the A2 log.
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
- // The app's agent is whichever harness's agent has its log.
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(async (server) => {
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 })