experimental-a2 0.16.1 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (406) hide show
  1. package/CHANGELOG.md +373 -0
  2. package/README.md +1 -1
  3. package/dist/{actor-ohPC-81x.d.ts → actor-Bjce9_5U.d.ts} +21 -11
  4. package/dist/actor-Bjce9_5U.d.ts.map +1 -0
  5. package/dist/actor-client.d.ts +2 -5
  6. package/dist/actor-client.d.ts.map +1 -1
  7. package/dist/actor-client.js +13 -4
  8. package/dist/actor-client.js.map +1 -1
  9. package/dist/actor-react.d.ts +5 -3
  10. package/dist/actor-react.d.ts.map +1 -1
  11. package/dist/actor-react.js +20 -10
  12. package/dist/actor-react.js.map +1 -1
  13. package/dist/{actor-shared-B5tJfzt-.js → actor-shared-t7w8xIKP.js} +12 -7
  14. package/dist/actor-shared-t7w8xIKP.js.map +1 -0
  15. package/dist/actor.d.ts +1 -1
  16. package/dist/actor.js +42 -23
  17. package/dist/actor.js.map +1 -1
  18. package/dist/{ai-DgOBltJ_.js → ai-BpOGm-zB.js} +621 -216
  19. package/dist/ai-BpOGm-zB.js.map +1 -0
  20. package/dist/{ai-CrEf6p_W.d.ts → ai-DrUaMyRt.d.ts} +114 -100
  21. package/dist/ai-DrUaMyRt.d.ts.map +1 -0
  22. package/dist/ai-client.d.ts +27 -0
  23. package/dist/ai-client.d.ts.map +1 -0
  24. package/dist/ai-client.js +29 -0
  25. package/dist/ai-client.js.map +1 -0
  26. package/dist/ai-fx.browser.d.ts +1 -0
  27. package/dist/ai-fx.browser.js +6 -0
  28. package/dist/ai-fx.browser.js.map +1 -0
  29. package/dist/ai-fx.d.ts +10 -0
  30. package/dist/ai-fx.d.ts.map +1 -0
  31. package/dist/ai-fx.js +634 -0
  32. package/dist/ai-fx.js.map +1 -0
  33. package/dist/ai-harness-BuLh_EYi.js +273 -0
  34. package/dist/ai-harness-BuLh_EYi.js.map +1 -0
  35. package/dist/ai-harness-DvsPeJeB.d.ts +26 -0
  36. package/dist/ai-harness-DvsPeJeB.d.ts.map +1 -0
  37. package/dist/ai-pi.browser.d.ts +1 -0
  38. package/dist/ai-pi.browser.js +6 -0
  39. package/dist/ai-pi.browser.js.map +1 -0
  40. package/dist/ai-pi.d.ts +17 -0
  41. package/dist/ai-pi.d.ts.map +1 -0
  42. package/dist/ai-pi.js +904 -0
  43. package/dist/ai-pi.js.map +1 -0
  44. package/dist/ai-react.d.ts +96 -0
  45. package/dist/ai-react.d.ts.map +1 -0
  46. package/dist/ai-react.js +142 -0
  47. package/dist/ai-react.js.map +1 -0
  48. package/dist/ai-server.browser.js +2 -2
  49. package/dist/ai-server.browser.js.map +1 -1
  50. package/dist/ai-server.d.ts +109 -43
  51. package/dist/ai-server.d.ts.map +1 -1
  52. package/dist/ai-server.js +526 -283
  53. package/dist/ai-server.js.map +1 -1
  54. package/dist/ai.d.ts +2 -2
  55. package/dist/ai.js +2 -2
  56. package/dist/bounded-cache-CPHWF-8h.js +41 -0
  57. package/dist/bounded-cache-CPHWF-8h.js.map +1 -0
  58. package/dist/{client-BYzHjkwU.js → client-BReKqwZC.js} +447 -80
  59. package/dist/client-BReKqwZC.js.map +1 -0
  60. package/dist/{client-CzyacQpJ.d.ts → client-h_DKFAjj.d.ts} +14 -13
  61. package/dist/client-h_DKFAjj.d.ts.map +1 -0
  62. package/dist/client.d.ts +4 -2
  63. package/dist/client.js +1 -1
  64. package/dist/{contract-CKRg_E4q.js → contract-BYKASlCV.js} +12 -21
  65. package/dist/contract-BYKASlCV.js.map +1 -0
  66. package/dist/{idempotent-replay-DVOlyYbx.js → idempotent-replay-JdgMQRRm.js} +2 -2
  67. package/dist/{idempotent-replay-DVOlyYbx.js.map → idempotent-replay-JdgMQRRm.js.map} +1 -1
  68. package/dist/index.d.ts +6 -5
  69. package/dist/index.d.ts.map +1 -1
  70. package/dist/index.js +4 -3
  71. package/dist/{errors-DvhSXnxk.d.ts → initial-snapshot-Bx-c0pDP.d.ts} +7 -3
  72. package/dist/initial-snapshot-Bx-c0pDP.d.ts.map +1 -0
  73. package/dist/{internal-Dq2qYxou.js → internal-B2T3U4Rq.js} +29 -2
  74. package/dist/internal-B2T3U4Rq.js.map +1 -0
  75. package/dist/otel.d.ts +1 -1
  76. package/dist/react-BBXgHWFc.d.ts +86 -0
  77. package/dist/react-BBXgHWFc.d.ts.map +1 -0
  78. package/dist/react.d.ts +2 -122
  79. package/dist/react.js +107 -100
  80. package/dist/react.js.map +1 -1
  81. package/dist/reducer-BAXc8JyF.js +87 -0
  82. package/dist/reducer-BAXc8JyF.js.map +1 -0
  83. package/dist/{reducer-otzHjuJj.d.ts → reducer-CqA59Q6U.d.ts} +46 -30
  84. package/dist/reducer-CqA59Q6U.d.ts.map +1 -0
  85. package/dist/reducer-runtime-DQwz_J59.js +28 -0
  86. package/dist/reducer-runtime-DQwz_J59.js.map +1 -0
  87. package/dist/scheduler-qstash.d.ts +3 -3
  88. package/dist/scheduler-qstash.d.ts.map +1 -1
  89. package/dist/scheduler-qstash.js +33 -10
  90. package/dist/scheduler-qstash.js.map +1 -1
  91. package/dist/{scheduler-task-BpzhPnRS.js → scheduler-task-X1SToDjP.js} +18 -3
  92. package/dist/scheduler-task-X1SToDjP.js.map +1 -0
  93. package/dist/scheduler-vercel.d.ts +4 -9
  94. package/dist/scheduler-vercel.d.ts.map +1 -1
  95. package/dist/scheduler-vercel.js +25 -44
  96. package/dist/scheduler-vercel.js.map +1 -1
  97. package/dist/{server-BD5ckxZb.js → server-Dw_k_7kO.js} +904 -219
  98. package/dist/server-Dw_k_7kO.js.map +1 -0
  99. package/dist/{server-DbD7IJVK.d.ts → server-En7cbi3v.d.ts} +116 -26
  100. package/dist/server-En7cbi3v.d.ts.map +1 -0
  101. package/dist/server.d.ts +4 -4
  102. package/dist/server.js +2 -2
  103. package/dist/session-clear-Bo-XTFEy.d.ts +12 -0
  104. package/dist/session-clear-Bo-XTFEy.d.ts.map +1 -0
  105. package/dist/{errors-DCk6ch5n.js → session-clear-DGI4jTQ5.js} +17 -2
  106. package/dist/session-clear-DGI4jTQ5.js.map +1 -0
  107. package/dist/{store-DyZM6fS5.d.ts → store-Djdv0bnZ.d.ts} +73 -3
  108. package/dist/store-Djdv0bnZ.d.ts.map +1 -0
  109. package/dist/store-N8PXxDAS.js.map +1 -1
  110. package/dist/store-memory.d.ts +1 -1
  111. package/dist/store-memory.d.ts.map +1 -1
  112. package/dist/store-memory.js +205 -66
  113. package/dist/store-memory.js.map +1 -1
  114. package/dist/{store-polling-CmxUbV93.js → store-polling-B5FcwJfo.js} +21 -4
  115. package/dist/store-polling-B5FcwJfo.js.map +1 -0
  116. package/dist/store-postgres.d.ts +1 -1
  117. package/dist/store-postgres.d.ts.map +1 -1
  118. package/dist/store-postgres.js +450 -108
  119. package/dist/store-postgres.js.map +1 -1
  120. package/dist/{store-presence-polling-C7-XZyW9.js → store-presence-polling-BAwS8RWn.js} +3 -3
  121. package/dist/{store-presence-polling-C7-XZyW9.js.map → store-presence-polling-BAwS8RWn.js.map} +1 -1
  122. package/dist/store-redis-http.d.ts +1 -1
  123. package/dist/store-redis-http.d.ts.map +1 -1
  124. package/dist/store-redis-http.js +293 -11
  125. package/dist/store-redis-http.js.map +1 -1
  126. package/dist/store-redis-notify-BRITti_o.js +2493 -0
  127. package/dist/store-redis-notify-BRITti_o.js.map +1 -0
  128. package/dist/store-redis.d.ts +1 -1
  129. package/dist/store-redis.d.ts.map +1 -1
  130. package/dist/store-redis.js +11 -11
  131. package/dist/store-redis.js.map +1 -1
  132. package/dist/store-sqlite.d.ts +1 -1
  133. package/dist/store-sqlite.d.ts.map +1 -1
  134. package/dist/store-sqlite.js +197 -33
  135. package/dist/store-sqlite.js.map +1 -1
  136. package/dist/stream-buffer-Cx7AAPGq.js +23 -0
  137. package/dist/stream-buffer-Cx7AAPGq.js.map +1 -0
  138. package/dist/{telemetry-B5jzpy6y.d.ts → telemetry-Cyfqo6Qt.d.ts} +2 -2
  139. package/dist/telemetry-Cyfqo6Qt.d.ts.map +1 -0
  140. package/dist/{wire-BO5wWCb1.js → wire-rSUglYo8.js} +132 -10
  141. package/dist/wire-rSUglYo8.js.map +1 -0
  142. package/docs/01-quickstart.mdx +50 -84
  143. package/docs/{guides/07-examples.mdx → 02-examples.mdx} +10 -20
  144. package/docs/actors/01-introduction.mdx +48 -82
  145. package/docs/actors/02-concurrency.mdx +43 -70
  146. package/docs/actors/03-timers.mdx +28 -42
  147. package/docs/actors/04-routes.mdx +110 -137
  148. package/docs/actors/meta.ts +1 -1
  149. package/docs/agents/01-introduction.mdx +257 -0
  150. package/docs/agents/02-tools.mdx +247 -0
  151. package/docs/agents/03-conversations.mdx +538 -0
  152. package/docs/agents/04-harnesses.mdx +136 -0
  153. package/docs/agents/meta.ts +1 -0
  154. package/docs/client/01-react.mdx +294 -0
  155. package/docs/client/02-transports.mdx +86 -0
  156. package/docs/client/03-presence.mdx +224 -0
  157. package/docs/client/meta.ts +1 -0
  158. package/docs/contract/01-events.mdx +57 -0
  159. package/docs/contract/02-state.mdx +142 -0
  160. package/docs/contract/03-sessions.mdx +175 -0
  161. package/docs/index.mdx +112 -156
  162. package/docs/reference/01-core.mdx +139 -0
  163. package/docs/reference/02-server.mdx +855 -0
  164. package/docs/reference/03-client.mdx +301 -0
  165. package/docs/reference/04-ai.mdx +722 -0
  166. package/docs/reference/05-adapters.mdx +351 -0
  167. package/docs/reference/06-errors.mdx +138 -0
  168. package/docs/reference/meta.ts +1 -1
  169. package/docs/server/01-handlers.mdx +200 -0
  170. package/docs/server/02-timers.mdx +130 -0
  171. package/docs/server/03-cancellation.mdx +141 -0
  172. package/docs/server/04-routes.mdx +243 -0
  173. package/docs/server/05-production.mdx +322 -0
  174. package/docs/server/06-guarantees.mdx +123 -0
  175. package/examples/playground/DEPLOY.md +53 -7
  176. package/examples/playground/app/a0/[appId]/loading.tsx +16 -0
  177. package/examples/playground/app/a0/[appId]/page.tsx +53 -0
  178. package/examples/playground/app/a0/a0.css +818 -0
  179. package/examples/playground/app/a0/app-list.tsx +218 -0
  180. package/examples/playground/app/a0/app-view.tsx +63 -0
  181. package/examples/playground/app/a0/apps/[appId]/preview/route.ts +27 -0
  182. package/examples/playground/app/a0/apps/[appId]/route.ts +28 -0
  183. package/examples/playground/app/a0/apps.test.ts +128 -0
  184. package/examples/playground/app/a0/apps.ts +102 -0
  185. package/examples/playground/app/a0/chat.tsx +410 -0
  186. package/examples/playground/app/a0/columns.tsx +109 -0
  187. package/examples/playground/app/a0/context-menu.tsx +116 -0
  188. package/examples/playground/app/a0/cookies.ts +46 -0
  189. package/examples/playground/app/a0/crash-on-request.ts +72 -0
  190. package/examples/playground/app/a0/crashes/route.ts +3 -0
  191. package/examples/playground/app/a0/crashes.test.ts +33 -0
  192. package/examples/playground/app/a0/crashes.ts +28 -0
  193. package/examples/playground/app/a0/devtools.tsx +164 -0
  194. package/examples/playground/app/a0/events/route.ts +6 -0
  195. package/examples/playground/app/a0/flags.ts +30 -0
  196. package/examples/playground/app/a0/fx/events/route.ts +9 -0
  197. package/examples/playground/app/a0/fx/scheduler/route.ts +7 -0
  198. package/examples/playground/app/a0/fx/short/events/route.ts +11 -0
  199. package/examples/playground/app/a0/layout.tsx +38 -0
  200. package/examples/playground/app/a0/list/route.ts +10 -0
  201. package/examples/playground/app/a0/model.test.ts +73 -0
  202. package/examples/playground/app/a0/model.ts +95 -0
  203. package/examples/playground/app/a0/no-photon.cjs +4 -0
  204. package/examples/playground/app/a0/no-quickjs.cjs +3 -0
  205. package/examples/playground/app/a0/page.tsx +14 -0
  206. package/examples/playground/app/a0/pi/events/route.ts +9 -0
  207. package/examples/playground/app/a0/pi/scheduler/route.ts +7 -0
  208. package/examples/playground/app/a0/pi/short/events/route.ts +11 -0
  209. package/examples/playground/app/a0/preview-pane.tsx +145 -0
  210. package/examples/playground/app/a0/preview.test.ts +53 -0
  211. package/examples/playground/app/a0/preview.ts +26 -0
  212. package/examples/playground/app/a0/sandbox-log.ts +75 -0
  213. package/examples/playground/app/a0/sandbox-session.ts +152 -0
  214. package/examples/playground/app/a0/sandbox.ts +191 -0
  215. package/examples/playground/app/a0/sandboxes/scheduler/route.ts +6 -0
  216. package/examples/playground/app/a0/scheduler/route.ts +7 -0
  217. package/examples/playground/app/a0/server.ts +213 -0
  218. package/examples/playground/app/a0/session.ts +59 -0
  219. package/examples/playground/app/a0/shell.tsx +158 -0
  220. package/examples/playground/app/a0/short/events/route.ts +9 -0
  221. package/examples/playground/app/a0/splitter.tsx +120 -0
  222. package/examples/playground/app/agent/[agentId]/agent-client.tsx +144 -174
  223. package/examples/playground/app/agent/[agentId]/command-receipt.test.tsx +65 -0
  224. package/examples/playground/app/agent/[agentId]/command-receipt.tsx +27 -0
  225. package/examples/playground/app/agent/[agentId]/compaction-event.tsx +25 -24
  226. package/examples/playground/app/agent/[agentId]/compaction-panel.test.tsx +119 -0
  227. package/examples/playground/app/agent/[agentId]/compaction-panel.tsx +208 -165
  228. package/examples/playground/app/agent/[agentId]/page.tsx +2 -16
  229. package/examples/playground/app/agent/events/route.ts +5 -2
  230. package/examples/playground/app/agent/ingress.test.ts +101 -0
  231. package/examples/playground/app/agent/model-input.test.tsx +58 -0
  232. package/examples/playground/app/agent/model-input.tsx +115 -0
  233. package/examples/playground/app/agent/model.test.ts +73 -0
  234. package/examples/playground/app/agent/model.ts +58 -26
  235. package/examples/playground/app/agent/new-agent-session.tsx +37 -19
  236. package/examples/playground/app/agent/page.tsx +4 -4
  237. package/examples/playground/app/agent/server.ts +19 -15
  238. package/examples/playground/app/agent/session.ts +5 -6
  239. package/examples/playground/app/agent/telemetry.test.ts +189 -0
  240. package/examples/playground/app/agent/telemetry.ts +30 -0
  241. package/examples/playground/app/agent/trace.test.ts +23 -0
  242. package/examples/playground/app/agent/trace.ts +18 -0
  243. package/examples/playground/app/canvas/[canvasId]/canvas-client.tsx +12 -12
  244. package/examples/playground/app/canvas/[canvasId]/page.tsx +1 -2
  245. package/examples/playground/app/canvas/[canvasId]/session.ts +2 -2
  246. package/examples/playground/app/canvas/model.ts +53 -53
  247. package/examples/playground/app/chat/[chatId]/agent-state.test.ts +176 -0
  248. package/examples/playground/app/chat/[chatId]/agent-stream-drawer.tsx +16 -1
  249. package/examples/playground/app/chat/[chatId]/chat-client.tsx +55 -77
  250. package/examples/playground/app/chat/[chatId]/chat-view.ts +37 -3
  251. package/examples/playground/app/chat/[chatId]/page.tsx +7 -15
  252. package/examples/playground/app/chat/[chatId]/session.ts +2 -19
  253. package/examples/playground/app/chat/agent/route.ts +9 -0
  254. package/examples/playground/app/chat/model.test.ts +46 -119
  255. package/examples/playground/app/chat/model.ts +82 -128
  256. package/examples/playground/app/chat/scheduler/route.ts +2 -2
  257. package/examples/playground/app/chat/server.test.ts +112 -0
  258. package/examples/playground/app/chat/server.ts +113 -117
  259. package/examples/playground/app/components/activity-feed.tsx +30 -1
  260. package/examples/playground/app/components/activity-history.test.tsx +35 -0
  261. package/examples/playground/app/components/demo-icons.tsx +89 -0
  262. package/examples/playground/app/components/demos.tsx +33 -0
  263. package/examples/playground/app/components/fast-link.test.tsx +58 -0
  264. package/examples/playground/app/components/fast-link.tsx +55 -0
  265. package/examples/playground/app/components/site-header.tsx +96 -0
  266. package/examples/playground/app/counter/counter-client.tsx +3 -3
  267. package/examples/playground/app/counter/model.test.ts +3 -3
  268. package/examples/playground/app/counter/model.ts +11 -21
  269. package/examples/playground/app/counter/page.tsx +2 -15
  270. package/examples/playground/app/counter/session.ts +2 -2
  271. package/examples/playground/app/documents/[documentId]/code-editor.tsx +12 -1
  272. package/examples/playground/app/documents/[documentId]/document-client.tsx +10 -11
  273. package/examples/playground/app/documents/[documentId]/page.tsx +1 -2
  274. package/examples/playground/app/documents/[documentId]/session.ts +2 -2
  275. package/examples/playground/app/documents/model.ts +27 -27
  276. package/examples/playground/app/globals.css +420 -207
  277. package/examples/playground/app/initial-state.test.tsx +54 -0
  278. package/examples/playground/app/layout.tsx +7 -23
  279. package/examples/playground/app/orders/[orderId]/page.tsx +1 -2
  280. package/examples/playground/app/orders/[orderId]/session.ts +2 -2
  281. package/examples/playground/app/orders/model.ts +49 -49
  282. package/examples/playground/app/page.tsx +13 -102
  283. package/examples/playground/app/recovery/[recoveryId]/page.tsx +1 -2
  284. package/examples/playground/app/recovery/[recoveryId]/session.ts +2 -2
  285. package/examples/playground/app/recovery/model.ts +30 -30
  286. package/examples/playground/instrumentation.ts +5 -0
  287. package/examples/playground/next.config.ts +17 -5
  288. package/examples/playground/package.json +13 -2
  289. package/examples/playground/scripts/a0-template.ts +77 -0
  290. package/examples/playground/vercel.json +36 -0
  291. package/package.json +29 -3
  292. package/src/actor-client.ts +16 -4
  293. package/src/actor-react.ts +25 -11
  294. package/src/actor-shared.ts +21 -6
  295. package/src/actor.ts +76 -31
  296. package/src/ai-agent-actions.ts +219 -0
  297. package/src/ai-agent.ts +2517 -0
  298. package/src/ai-app-state.ts +91 -0
  299. package/src/ai-client-state.ts +6 -3
  300. package/src/ai-client.ts +62 -0
  301. package/src/ai-context-schema.ts +25 -5
  302. package/src/ai-context.ts +90 -40
  303. package/src/ai-control-server.ts +92 -72
  304. package/src/ai-coordinator.ts +23 -22
  305. package/src/ai-fx.browser.ts +5 -0
  306. package/src/ai-fx.ts +833 -0
  307. package/src/ai-harness.ts +518 -0
  308. package/src/ai-message-base.ts +209 -0
  309. package/src/ai-message-projection.ts +262 -64
  310. package/src/ai-model-metadata.ts +11 -6
  311. package/src/ai-pi.browser.ts +5 -0
  312. package/src/ai-pi.ts +1263 -0
  313. package/src/ai-progress-batches.ts +26 -0
  314. package/src/ai-projector.ts +95 -14
  315. package/src/ai-react.ts +146 -0
  316. package/src/ai-sdk-step.ts +2 -2
  317. package/src/ai-server.browser.ts +2 -2
  318. package/src/ai-server.ts +23 -1965
  319. package/src/ai-stored-state.ts +48 -163
  320. package/src/ai.ts +339 -155
  321. package/src/bootstrap-stream.ts +72 -0
  322. package/src/bounded-cache.ts +55 -0
  323. package/src/client-render-session.ts +49 -0
  324. package/src/client.ts +688 -151
  325. package/src/contract.ts +92 -42
  326. package/src/deterministic-id.ts +37 -0
  327. package/src/equal-json.ts +32 -0
  328. package/src/errors.ts +13 -1
  329. package/src/index.ts +5 -1
  330. package/src/initial-snapshot.ts +39 -0
  331. package/src/internal.ts +52 -0
  332. package/src/libfx.d.ts +34 -0
  333. package/src/postgres-notification-scope.ts +14 -3
  334. package/src/react.ts +171 -206
  335. package/src/redis-http-reply.ts +256 -0
  336. package/src/redis-http-stream.ts +73 -0
  337. package/src/redis-http-subscriptions.ts +13 -5
  338. package/src/reducer-runtime.ts +52 -0
  339. package/src/reducer.ts +93 -11
  340. package/src/scheduler-qstash.ts +49 -20
  341. package/src/scheduler-task.ts +34 -2
  342. package/src/scheduler-vercel.ts +32 -84
  343. package/src/server-fetch.ts +139 -35
  344. package/src/server.ts +1133 -255
  345. package/src/session-clear.ts +23 -0
  346. package/src/session-socket.ts +154 -49
  347. package/src/sse.ts +71 -4
  348. package/src/store-memory.ts +252 -75
  349. package/src/store-polling.ts +18 -4
  350. package/src/store-postgres.ts +541 -114
  351. package/src/store-redis-core.ts +1450 -493
  352. package/src/store-redis-http.ts +18 -2
  353. package/src/store-redis-notify.ts +262 -85
  354. package/src/store-redis.ts +19 -9
  355. package/src/store-sqlite.ts +319 -45
  356. package/src/store.ts +79 -1
  357. package/src/stream-buffer.ts +43 -0
  358. package/src/telemetry.ts +14 -1
  359. package/src/wire.ts +241 -13
  360. package/dist/actor-ohPC-81x.d.ts.map +0 -1
  361. package/dist/actor-shared-B5tJfzt-.js.map +0 -1
  362. package/dist/ai-CrEf6p_W.d.ts.map +0 -1
  363. package/dist/ai-DgOBltJ_.js.map +0 -1
  364. package/dist/client-BYzHjkwU.js.map +0 -1
  365. package/dist/client-CzyacQpJ.d.ts.map +0 -1
  366. package/dist/contract-CKRg_E4q.js.map +0 -1
  367. package/dist/errors-DCk6ch5n.js.map +0 -1
  368. package/dist/errors-DvhSXnxk.d.ts.map +0 -1
  369. package/dist/internal-Dq2qYxou.js.map +0 -1
  370. package/dist/react.d.ts.map +0 -1
  371. package/dist/reducer-DEMjEY_O.js +0 -29
  372. package/dist/reducer-DEMjEY_O.js.map +0 -1
  373. package/dist/reducer-otzHjuJj.d.ts.map +0 -1
  374. package/dist/scheduler-task-BpzhPnRS.js.map +0 -1
  375. package/dist/server-BD5ckxZb.js.map +0 -1
  376. package/dist/server-DbD7IJVK.d.ts.map +0 -1
  377. package/dist/store-DyZM6fS5.d.ts.map +0 -1
  378. package/dist/store-polling-CmxUbV93.js.map +0 -1
  379. package/dist/store-redis-notify-BVLUfI3j.js +0 -1583
  380. package/dist/store-redis-notify-BVLUfI3j.js.map +0 -1
  381. package/dist/telemetry-B5jzpy6y.d.ts.map +0 -1
  382. package/dist/validate-XKT4FSNn.js +0 -30
  383. package/dist/validate-XKT4FSNn.js.map +0 -1
  384. package/dist/wire-BO5wWCb1.js.map +0 -1
  385. package/docs/concepts/01-contracts.mdx +0 -142
  386. package/docs/concepts/02-handlers.mdx +0 -284
  387. package/docs/concepts/03-durability.mdx +0 -314
  388. package/docs/concepts/04-state.mdx +0 -209
  389. package/docs/guides/01-timers.mdx +0 -185
  390. package/docs/guides/02-cancellation.mdx +0 -161
  391. package/docs/guides/03-react.mdx +0 -384
  392. package/docs/guides/04-local-first.mdx +0 -88
  393. package/docs/guides/05-production.mdx +0 -521
  394. package/docs/guides/06-ai-agents.mdx +0 -1318
  395. package/docs/guides/08-application-data.mdx +0 -113
  396. package/docs/guides/09-presence.mdx +0 -266
  397. package/docs/guides/10-transports.mdx +0 -221
  398. package/docs/reference/01-api.mdx +0 -1896
  399. package/docs/reference/02-errors.mdx +0 -149
  400. package/examples/playground/app/agent/[agentId]/compaction/route.ts +0 -14
  401. package/examples/playground/app/agent/compaction-settings.test.ts +0 -144
  402. package/examples/playground/app/agent/compaction-settings.ts +0 -49
  403. package/examples/playground/app/agent/compaction-timeline.test.ts +0 -377
  404. package/examples/playground/app/agent/compaction-timeline.ts +0 -208
  405. /package/docs/{concepts → contract}/meta.ts +0 -0
  406. /package/docs/{guides → server}/meta.ts +0 -0
@@ -1,521 +0,0 @@
1
- ---
2
- title: Going to production
3
- description: Point the store at durable storage, add a scheduler for recovery and timers, and know what to do when an event dead-letters.
4
- ---
5
-
6
- ## Two pieces
7
-
8
- Development needs zero setup: SQLite appears under `.a2/`, and tests run
9
- in memory. Every production server needs a durable store. Servers with handlers
10
- or scheduled events also need a scheduler. Neither choice changes your event
11
- contract.
12
-
13
- ## 1. Choose a store
14
-
15
- Production has no default store, on purpose. A server without one throws
16
- `STORE_NOT_CONFIGURED` at startup. A failed boot beats events written to a
17
- filesystem that evaporates.
18
-
19
- ```ts server/orders.ts
20
- import { createServer } from 'experimental-a2/server'
21
- import { postgres } from 'experimental-a2/store-postgres'
22
- import { orders } from '@/contracts'
23
-
24
- export const ordersServer = createServer({
25
- contract: orders,
26
- store: postgres({ connectionString: process.env.DATABASE_URL }),
27
- handlers: {
28
- /* ... */
29
- },
30
- })
31
- ```
32
-
33
- The Postgres backend uses real transactions; appends serialize per
34
- session on an advisory lock, while handler claims remain concurrent. The live
35
- stream polls the store with an
36
- activity-adaptive cadence: 25ms while a session is producing events
37
- (a token stream reads smoothly, not in clumps), backing off to two seconds
38
- when it goes quiet. Any Postgres works: Neon, Supabase, RDS, your own
39
- box; transaction-mode poolers included, which is exactly why polling
40
- is the default. `pg` is an optional peer dependency; pass
41
- `connectionString`, or inject your own pool as `client`.
42
-
43
- To use native Postgres notifications, pass `listenConnectionString` alongside
44
- `connectionString` (or `client`). It must point to the same database through a
45
- direct connection. Stores with matching connection strings, Postgres environment
46
- settings, and driver defaults share one query pool and one dedicated listener
47
- within the same JavaScript runtime, including across separately loaded copies
48
- of A2. Owned pools also share schema initialization for each schema revision.
49
- The listener handles every store's event and presence channels.
50
-
51
- On Vercel, pools created by A2 use the current request's `waitUntil` internally
52
- to let idle connections close before the instance suspends. Nearby requests
53
- reuse those connections. This adds no database queries and needs no setup.
54
- When you supply `client`, you manage its pool's platform lifecycle.
55
-
56
- Closing a store stops its own subscriptions. The listener closes when its last
57
- subscriber leaves; the query pool closes when its last store releases it.
58
- Injected clients are shared by object identity. Each store keeps its own clock,
59
- ID generator, cursors, and presence state. Different serverless instances still
60
- have separate pools and listeners, so the direct connection needs database
61
- connection headroom.
62
-
63
- Enabling this option installs transactional notification triggers on A2's event
64
- and presence tables. They also cover writes from instances that use only the
65
- pooled connection. The triggers remain installed when listeners disconnect.
66
- Notifications are delivered after commit; a reconnect catches up from the
67
- cursor. Native event and presence subscriptions also reconcile every ten
68
- seconds. Without `listenConnectionString`, the store uses adaptive polling.
69
-
70
- An idle stream stays connected and keeps receiving heartbeats. A new event
71
- returns polling to its fast cadence. A local `session.push()` refreshes an
72
- idle SSE stream immediately from its current cursor, even when the write and
73
- stream routes run in different invocations. WebSocket pushes wake the attached
74
- stream without reopening the socket. A stream that becomes idle while a push
75
- is pending, or before observing its acknowledgement, wakes again. An update
76
- from another client can take up to the idle interval plus query latency to
77
- arrive after a quiet period.
78
-
79
- Prefer Redis? `redis({ url })` from `experimental-a2/store-redis` stores each
80
- session as a Redis Stream and streams push-natively: writes to watched
81
- sessions publish a disposable wake-up (sessions nobody watches cost no extra
82
- command), one shared subscriber connection per process serves every connected
83
- viewer, and a safety re-read covers a lost wake-up within ten seconds.
84
- Connections scale with your processes, not with your audience.
85
- `ioredis` is an optional peer dependency; pass `url`, or inject a client.
86
- Works on single instances and non-cluster providers such as Upstash, where
87
- durability is on by default. When only a REST API is available,
88
- `redisHttp({ url, token })` from `experimental-a2/store-redis-http` speaks the
89
- same storage over `fetch`. Live events and presence use Upstash’s HTTP
90
- `SUBSCRIBE` endpoint (SSE), with the same URL and token. Each active channel
91
- holds one outbound HTTP subscription per store instance, shared by its local
92
- viewers and closed when the last viewer leaves. Writes to watched sessions
93
- publish wake-ups inside the same Redis script as the append. Presence writes
94
- also publish their applied fields inside the script. A rejected notification
95
- does not fail the applied write. Idle event streams
96
- read once every ten seconds to recover missed notifications. Subscriptions reconnect automatically and catch up from
97
- the event cursor. Presence reads wait for subscription readiness; after a
98
- subscription reconnects, A2 re-reads the current presence fields, clears fields
99
- deleted during the gap, and may repeat their latest values. Updates received
100
- during that read take precedence. If the subscription endpoint returns 404, 405, or 501, A2
101
- checks ordinary Redis commands with `PING` and remembers that the store must
102
- poll instead. Events and presence then use adaptive polling, from 25ms to two
103
- seconds while idle. Authentication errors, timeouts, and other failures remain
104
- errors. No Redis client dependency or extra notifier setup is needed.
105
-
106
- This configures storage for A2's session histories. It does not connect A2 to your
107
- application tables or make them part of the append transaction. See
108
- [A2 and your database](/guides/application-data) for that boundary.
109
-
110
- ## 2. Add a scheduler for handlers and timers
111
-
112
- The scheduler puts a clock on recovery and future appends. Vercel Queues is the
113
- recommended default on Vercel. QStash is an alternative when you want HTTP
114
- delivery from separate infrastructure. Both adapters carry A2 recovery
115
- watchdogs and `session.schedule` tasks through the same route.
116
-
117
- ### Vercel Queues (recommended)
118
-
119
- `experimental-a2/scheduler-vercel` rides Vercel Queues (`@vercel/queue` is a
120
- peer dependency). Same `server/orders.ts`, now with `scheduler`:
121
-
122
- ```ts
123
- // server/orders.ts, now with a scheduler:
124
- import { createServer } from 'experimental-a2/server'
125
- import { postgres } from 'experimental-a2/store-postgres'
126
- import { vercelQueues } from 'experimental-a2/scheduler-vercel'
127
- import { orders } from '@/contracts'
128
-
129
- export const scheduler = vercelQueues()
130
-
131
- export const ordersServer = createServer({
132
- contract: orders,
133
- store: postgres({ connectionString: process.env.DATABASE_URL }),
134
- scheduler,
135
- handlers: {
136
- /* ... */
137
- },
138
- })
139
- ```
140
-
141
- One scheduler instance is shared by every server on this route. Mount its
142
- handler with every server it may deliver to:
143
-
144
- ```ts app/api/a2/scheduler/route.ts
145
- import { billingServer, ordersServer, scheduler } from '@/server'
146
-
147
- export const POST = scheduler.handler(ordersServer, billingServer)
148
- ```
149
-
150
- `scheduler.handler(...)` checks the server list synchronously when this module
151
- loads. It requires at least one drainable server and unique contract names. A
152
- bad route fails during startup or a build that evaluates route modules, before
153
- it can accept a delivery.
154
-
155
- ```json vercel.json
156
- {
157
- "functions": {
158
- "app/api/a2/scheduler/route.ts": {
159
- "experimentalTriggers": [
160
- {
161
- "type": "queue/v2beta",
162
- "topic": "a2"
163
- }
164
- ]
165
- }
166
- }
167
- }
168
- ```
169
-
170
- The trigger is required. It is the Vercel Queues consumer integration and the
171
- route's protection boundary: Vercel makes the function private, and only queue
172
- infrastructure can invoke it. Do not expose the callback as an ordinary public
173
- route or add a second authentication scheme around it.
174
-
175
- The adapter pins recovery watchdogs and scheduled appends to the deployment
176
- that published them. This is the safe default. A recovery task always returns
177
- to code that understands the session work it armed.
178
-
179
- You can remove deployment affinity from scheduled appends only:
180
-
181
- ```ts
182
- // server/orders.ts, when every consumer is compatible with pending timers:
183
- import { vercelQueues } from 'experimental-a2/scheduler-vercel'
184
-
185
- export const scheduler = vercelQueues({
186
- scheduledAppends: 'deploymentless',
187
- })
188
- ```
189
-
190
- `deploymentless` does not mean "latest production." It lets Vercel Queues
191
- choose an eligible deploymentless consumer for the topic. Keep task envelopes
192
- and event schemas compatible across every such consumer. Recovery watchdogs
193
- remain deployment-scoped in both modes. Scheduler task envelopes are
194
- versioned.
195
-
196
- Vercel Queues defaults to 24-hour message retention. The adapter sets retention
197
- to 7 days and accepts scheduled appends through 6 days, reserving the final day
198
- for delivery and retries. Recovery watchdogs are measured in seconds.
199
-
200
- ### QStash
201
-
202
- `experimental-a2/scheduler-qstash` uses QStash HTTP delivery
203
- (`@upstash/qstash` 2.11.0 or newer is a peer dependency). The conventional
204
- callback URL is
205
- inferred in QStash development mode and on Vercel production and preview
206
- deployments:
207
-
208
- ```ts
209
- // server/orders.ts, with QStash instead:
210
- import { createServer } from 'experimental-a2/server'
211
- import { postgres } from 'experimental-a2/store-postgres'
212
- import { qstash } from 'experimental-a2/scheduler-qstash'
213
- import { orders } from '@/contracts'
214
-
215
- export const scheduler = qstash()
216
-
217
- export const ordersServer = createServer({
218
- contract: orders,
219
- store: postgres({ connectionString: process.env.DATABASE_URL }),
220
- scheduler,
221
- handlers: {
222
- /* ... */
223
- },
224
- })
225
- ```
226
-
227
- Mount `scheduler.handler(ordersServer, billingServer)` once with the same
228
- `app/api/a2/scheduler/route.ts` module shown above. Local Node.js development
229
- needs no QStash environment variables. With `@upstash/qstash` 2.11.0 or newer,
230
- A2 uses `http://localhost:${PORT ?? 3000}/api/a2/scheduler` and enables the
231
- SDK's development mode. On the first publish or verification, the lazy SDK
232
- transport downloads and starts its local server. `PORT` selects the application
233
- port. `QSTASH_DEV_PORT` optionally selects the QStash development server port
234
- and does not change the callback. This inferred local route sets SDK development
235
- mode directly; `QSTASH_DEV=false` does not disable it. Pass an explicit `url`
236
- to leave SDK mode under environment control.
237
-
238
- A process-less runtime cannot infer local development and needs an explicit
239
- `url`. An Edge runtime that exposes `process.env` can use the inferred localhost
240
- callback, but it cannot start the development server itself. Start the server
241
- from a Node.js instrumentation hook or run it manually. Configure the SDK with
242
- `QSTASH_DEV` or the manual `QSTASH_URL` and development credentials described
243
- in the [QStash local-development guide](https://upstash.com/docs/qstash/howto/local-development).
244
-
245
- A2 disables the SDK's development mode when `NODE_ENV=production`, even if
246
- `QSTASH_DEV` remains set. Production must use deployed QStash credentials and
247
- must not rely on the local development server.
248
-
249
- Production uses `VERCEL_PROJECT_PRODUCTION_URL`; preview uses
250
- `VERCEL_BRANCH_URL`. Both append the conventional `/api/a2/scheduler` path. An
251
- explicit `url` always wins. Pass one for another host or path; an application
252
- port that is not available through `PORT`; Docker networking; a public
253
- development tunnel; or a non-Vercel production deployment. Outside production,
254
- an explicit URL leaves SDK development mode under the SDK's normal environment
255
- configuration. This supports `QSTASH_DEV=true` and manually supplied
256
- `QSTASH_URL` plus credentials without changing the callback URL.
257
-
258
- Production and branch URLs are moving aliases. Pending QStash tasks can reach a
259
- later deployment on that alias, so keep scheduler task envelopes and event
260
- schemas compatible across deployments.
261
-
262
- The deployed route must be internet-reachable by QStash after any platform edge
263
- authentication. The local QStash server calls the inferred localhost route
264
- directly. A2 reads the raw request body and verifies `Upstash-Signature`, the
265
- callback URL, and the optional `Upstash-Region` before parsing the message or
266
- touching the store. Missing or invalid signatures return 401, so ordinary
267
- unsigned traffic cannot execute scheduler work.
268
-
269
- When A2 infers a Vercel preview callback, it opportunistically reads the current
270
- invocation's `x-vercel-oidc-token` on every publish. If the token is a parseable
271
- JWT that remains valid for at least 60 seconds after the task's first due time,
272
- the built-in transport forwards it as
273
- `x-vercel-trusted-oidc-idp-token` and asks QStash to redact that header from its
274
- dashboard and API. This lets near-term tasks enter a protected preview when its
275
- [Trusted Sources](https://vercel.com/docs/deployment-protection/methods-to-bypass-deployment-protection/trusted-sources)
276
- rule accepts the publishing deployment's identity. QStash still receives the
277
- credential so it can deliver it.
278
-
279
- This forwarding is best-effort first-delivery admission, not durable
280
- authentication. A missing, malformed, or near-expiry token leaves the publish
281
- unchanged so an unprotected preview does not regress. An explicit `url` opts out,
282
- and inferred production and local callbacks do not forward the token. QStash
283
- stores the static header and reuses it on retries, so a delayed task or retry can
284
- outlive the identity even if the token passed the first-due-time check.
285
-
286
- Callbacks that need durable access through Deployment Protection still require
287
- an explicit
288
- [automation bypass](https://vercel.com/docs/deployment-protection/methods-to-bypass-deployment-protection/protection-bypass-automation)
289
- or a public ingress. Pass a dedicated bypass as a callback header when that is
290
- the right tradeoff for your project:
291
-
292
- ```ts server/scheduler-qstash-protected.ts
293
- import { qstash } from 'experimental-a2/scheduler-qstash'
294
-
295
- const bypass = process.env.VERCEL_AUTOMATION_BYPASS_SECRET
296
- if (!bypass) throw new Error('Missing VERCEL_AUTOMATION_BYPASS_SECRET')
297
-
298
- export const scheduler = qstash({
299
- headers: { 'x-vercel-protection-bypass': bypass },
300
- })
301
- ```
302
-
303
- A2 asks QStash to redact every configured callback header from its dashboard
304
- and API. QStash still receives, stores, and delivers the original value. A
305
- rotation affects new messages only; already queued messages keep their captured
306
- header. A custom transport must deliver every `headers` entry and apply every
307
- `redact.header` name it receives.
308
-
309
- A separately implemented public Vercel production ingress can instead verify
310
- the QStash signature and relay the task at delivery time with its current
311
- Vercel OIDC token. That architecture requires an explicit
312
- production-to-preview Trusted Sources rule, a strict target allowlist, and a
313
- versioned routing envelope. The adapter does not implement that relay.
314
- `Upstash-Signature` remains the authentication boundary for scheduler work
315
- after any deployment-protection check.
316
-
317
- Servers configured with different scheduler instances use separate routes,
318
- even when both instances come from the same adapter. Each QStash instance needs
319
- its own callback URL, public when cloud QStash delivers to it, so additional
320
- instances pass `url` explicitly. Each Vercel Queues instance needs its own
321
- `topic` and a matching `experimentalTriggers` entry for that route. One route
322
- cannot safely choose a provider after a delivery arrives.
323
-
324
- By default, the adapter reads `QSTASH_TOKEN`,
325
- `QSTASH_CURRENT_SIGNING_KEY`, and `QSTASH_NEXT_SIGNING_KEY`. You can instead
326
- pass `token`, `currentSigningKey`, and `nextSigningKey` directly to `qstash()`.
327
- Pass a configured `@upstash/qstash` `Client` as `client` when you need to tune
328
- its API endpoint, publish-request retry policy, or telemetry. A2 continues to
329
- create the signature `Receiver` from the signing-key options. Use A2's
330
- `headers` option, not client-global headers, for callback credentials. Use the
331
- full `transport` override when publishing and verification both need custom
332
- behavior. An injected client or transport receives raw callback credentials
333
- and must honor A2's redaction request without logging them.
334
- The adapter reads at most 1 MiB per callback. Before an initial publish, it
335
- checks the normalized task with enough reserved room for the largest private
336
- store-outage lineage it may later need. Every accepted task can therefore carry
337
- that lineage under the same `maxBodyBytes` limit. Store-outage successors also
338
- check their actual body before publishing. Set `maxBodyBytes` to a larger plan
339
- limit when your scheduled event batches legitimately need it. Keep the same
340
- value on every deployment that can receive the callback URL. Lowering it during
341
- a rolling deployment can make the new deployment reject a task that the
342
- previous deployment accepted.
343
-
344
- QStash delivery is at least once. Stalled work publishes a fresh successor
345
- watchdog before acknowledging, so QStash's per-message retry budget cannot stop
346
- recovery before A2 reaches its own failure limit. A retryable store outage also
347
- hands recovery or timer delivery to a fresh message, but uses a slower outage
348
- backoff. The first successor waits 30 seconds. Each later generation waits four
349
- times longer, capped at one day. The generation stays in the signed message
350
- body and keeps increasing after the delay reaches the cap. Duplicate callbacks
351
- therefore converge on one successor generation even when they run at different
352
- times. Separate drain episodes use the first failing task's `dueAt` as part of
353
- their lineage. Append episodes use the contract, session, and stable task id.
354
-
355
- If the successor publish fails, the current delivery returns non-2xx and
356
- QStash retries it. The adapter omits `retries` by default, which lets QStash use
357
- its provider default. Pass `retries` to set an explicit non-negative value.
358
- Every delivery attempt is billable. Deterministic publish keys reduce duplicate
359
- watchdogs during QStash's documented 10-minute deduplication window. That is a
360
- cost optimization, not a correctness boundary. Durable store claims, attempt
361
- fencing, and idempotent handler side effects handle duplicate deliveries.
362
-
363
- No callback can publish a successor when QStash cannot reach the route at all.
364
- Monitor QStash's DLQ and redrive those messages after a prolonged endpoint or
365
- deployment outage.
366
-
367
- QStash currently caps a one-shot delay at 7 days on free plans and 1 year on
368
- usage-based plans; fixed plans have no fixed maximum. A2 recovery watchdogs
369
- are seconds apart. Scheduled appends must fit the active plan's limit. Leave at
370
- least one second of margin at a QStash plan maximum because `notBefore` uses
371
- whole Unix seconds.
372
-
373
- Both built-in adapters preserve the original error when a provider rejects a
374
- send. A recognized definite HTTP 4xx status or error is terminal, except 408,
375
- 409, 425, and 429.
376
- Those four statuses, every 5xx response, network failures, and errors without a
377
- recognized status stay retryable because the send may be transient or
378
- ambiguous. The Vercel adapter also recognizes the SDK's name-only bad request,
379
- unauthorized, and forbidden errors. Its native duplicate-message error remains
380
- success. This classification lets durable A2 tools retry transient sends
381
- without retrying permanent credentials or request errors.
382
-
383
- One route, two task kinds. A top-level append that leaves or finds pending
384
- handler work sends a delayed, coalesced "drain this session" watchdog alongside
385
- its inline handler. The handler does not wait for the queue, while `append`
386
- joins the initial send for up to two seconds before it returns. An unresponsive
387
- queue therefore cannot hold the append open indefinitely. Claim renewals send
388
- another watchdog for just after the current window. Live handlers keep moving
389
- their per-event claims and the watchdog forward. A killed holder stops
390
- heartbeating, its claims expire, and the next watchdog retries those pending
391
- events. With Vercel Queues, A2 caps the final claim window at the platform
392
- function deadline when Vercel exposes it, so timeout recovery starts promptly.
393
- A failing handler keeps the current message and redelivers with backoff. Work
394
- outside its lane continues.
395
-
396
- Due times are rounded to one-second slots. Top-level sends, claim renewals, and
397
- racing deliveries targeting the same slot use the same deterministic publish
398
- key. Provider deduplication can fold them into one message. Busy deliveries
399
- continue the current message's heartbeat-aligned slot series, so they do not
400
- create an independent stream of watchdog callbacks.
401
- Events appended or returned by handlers ride their current execution window
402
- and add no scheduler operation of their own.
403
-
404
- Drain tasks carry recovery state and consult the store. Timer tasks carry their
405
- fixed-id events until delivery, then enter the store through an ordinary
406
- top-level append.
407
-
408
- ### Dispatch in a fresh invocation
409
-
410
- Ordinary `append` starts handlers inline and uses the scheduler as the watchdog.
411
- Use `append.dispatch` when the current request should only persist the input
412
- and hand pending work directly to the queue:
413
-
414
- ```ts app/api/imports/route.ts
415
- import { ordersServer } from '@/server'
416
-
417
- export async function POST(request: Request) {
418
- const { orderId, shopId, items } = await request.json()
419
- await ordersServer.session(orderId).append.dispatch({
420
- id: `created:${orderId}`,
421
- type: 'created',
422
- payload: { shopId, items },
423
- })
424
- return Response.json({ accepted: true }, { status: 202 })
425
- }
426
- ```
427
-
428
- The call awaits the immediate queue send and does not start an inline drain.
429
- If the send fails, the event may already be durable. Its explicit ID makes the
430
- retry idempotent. An existing drain for the same session can still claim the
431
- event first; dispatch controls the wakeup path, not worker affinity.
432
-
433
- ### Schedule a future append
434
-
435
- `session.schedule(name, { delay: '30s' }, ...events)` uses this same adapter
436
- and route. The call validates the events, requires JSON payload values, and
437
- awaits provider acceptance. It does not write an event until delivery. The
438
- delivered events are ordinary root appends with `cause: null`.
439
-
440
- Use one stable name, timing, and event batch across retries. A root relative
441
- delay starts from each call, while a handler relative delay starts from its
442
- triggering event's durable `createdAt`. Use `{ at: Date }` when a retried root
443
- call needs one target time. [Timers and delays](/guides/timers) covers
444
- identity, stale-timer guards, and the full provider limits.
445
-
446
- :::warning
447
- Do not roll back to an A2 version without timer delivery while scheduled tasks
448
- are outstanding. New routes accept old recovery messages, but an old route can
449
- acknowledge a new append task without writing its events. Roll forward, or wait
450
- for those tasks to deliver or remove them through the provider first.
451
- :::
452
-
453
- ## 3. When an event dead-letters
454
-
455
- After ten caught handler failures, A2 stops retrying an event. It blocks later
456
- events in the same lane; unlaned events and other lanes continue.
457
- [Durability](/concepts/durability#when-a-handler-keeps-failing) explains this
458
- boundary. Resolution is manual, and has
459
- exactly two shapes:
460
-
461
- - **Fix and retry.** Deploy the handler fix, clear the event's failure
462
- marker.
463
- - **Skip.** Mark the event processed, accepting that its effects never
464
- happened.
465
-
466
- Both are one-column updates on the events table.
467
-
468
- ## Watch it in traces
469
-
470
- A2 takes an optional `telemetry`, an instrumentation hook that
471
- `experimental-a2/otel` implements over OpenTelemetry (`@opentelemetry/api` is a peer
472
- dependency):
473
-
474
- ```ts
475
- // server/orders.ts, with telemetry:
476
- import { createServer } from 'experimental-a2/server'
477
- import { otel } from 'experimental-a2/otel'
478
- import { orders } from '@/contracts'
479
-
480
- export const ordersServer = createServer({
481
- contract: orders,
482
- // ...store, scheduler, and handlers as above
483
- telemetry: otel(),
484
- })
485
- ```
486
-
487
- With any OTel setup registered (on Vercel, `@vercel/otel`), the happy
488
- path lands in a single trace: the request → `a2.append` → the inline
489
- `a2.drain` → an `a2.event` span per handler → the appends those handlers
490
- make: the whole causal chain, visually. Handler failures mark their `a2.event`
491
- span with the exception, each dispatch reports `ctx.attempt` as
492
- `a2.event.attempt`, and `a2.event.outcome = dead_lettered` is the attribute to
493
- alert on when an event [needs manual
494
- resolution](#3-when-an-event-dead-letters). See the
495
- [API reference](/reference/api#a2otel) for the span catalogue.
496
-
497
- ## Running without a scheduler
498
-
499
- If none of a contract's event types have handlers and the app never calls
500
- `session.schedule`, skip `scheduler`. Those events settle in their append
501
- transaction, so they create no drains, claims, queue messages, or scheduler
502
- callbacks. The server is a durable event log for history, reducers, and live
503
- sync. That is a complete production configuration, not degraded recovery,
504
- because there is no reaction or timer to deliver.
505
-
506
- With handlers, skipping `scheduler` means the only wakeups are a top-level
507
- append or explicit `server.drain()`. Reads never wake the session.
508
-
509
- That's a real configuration, not a broken one. Fine for internal tools
510
- and low-stakes apps where "heals on the next write" is acceptable. But
511
- there's no clock in it: a session nobody wakes stays stuck until someone does.
512
- For production, configure a scheduler.
513
-
514
- ## Checklist
515
-
516
- | Piece | Done when |
517
- | ------------------- | -------------------------------------------------------------------- |
518
- | Store | `store: postgres(...)` on every server |
519
- | Scheduler | for servers with handlers or timers: one shared adapter, route, and provider config |
520
- | Idempotent handlers | external side effects take `event.id` as an idempotency key |
521
- | Client split | contracts/reducers isomorphic; only `experimental-a2/server` touches backends |