experimental-a2 0.16.0 → 0.17.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 (334) hide show
  1. package/CHANGELOG.md +360 -0
  2. package/dist/{actor-ohPC-81x.d.ts → actor-DBbe5xeE.d.ts} +21 -11
  3. package/dist/actor-DBbe5xeE.d.ts.map +1 -0
  4. package/dist/actor-client.d.ts +2 -5
  5. package/dist/actor-client.d.ts.map +1 -1
  6. package/dist/actor-client.js +13 -4
  7. package/dist/actor-client.js.map +1 -1
  8. package/dist/actor-react.d.ts +5 -3
  9. package/dist/actor-react.d.ts.map +1 -1
  10. package/dist/actor-react.js +20 -10
  11. package/dist/actor-react.js.map +1 -1
  12. package/dist/{actor-shared-B5tJfzt-.js → actor-shared-t7w8xIKP.js} +12 -7
  13. package/dist/actor-shared-t7w8xIKP.js.map +1 -0
  14. package/dist/actor.d.ts +1 -1
  15. package/dist/actor.js +42 -23
  16. package/dist/actor.js.map +1 -1
  17. package/dist/{ai-CrLYwNEx.js → ai-UrWfKeuv.js} +966 -541
  18. package/dist/ai-UrWfKeuv.js.map +1 -0
  19. package/dist/ai-client.d.ts +27 -0
  20. package/dist/ai-client.d.ts.map +1 -0
  21. package/dist/ai-client.js +29 -0
  22. package/dist/ai-client.js.map +1 -0
  23. package/dist/ai-react.d.ts +96 -0
  24. package/dist/ai-react.d.ts.map +1 -0
  25. package/dist/ai-react.js +142 -0
  26. package/dist/ai-react.js.map +1 -0
  27. package/dist/ai-server.browser.js +2 -2
  28. package/dist/ai-server.browser.js.map +1 -1
  29. package/dist/ai-server.d.ts +80 -36
  30. package/dist/ai-server.d.ts.map +1 -1
  31. package/dist/ai-server.js +466 -265
  32. package/dist/ai-server.js.map +1 -1
  33. package/dist/{ai-HJ9fHfYI.d.ts → ai-vpGtcaCB.d.ts} +115 -101
  34. package/dist/ai-vpGtcaCB.d.ts.map +1 -0
  35. package/dist/ai.d.ts +2 -2
  36. package/dist/ai.js +2 -2
  37. package/dist/{client-BYzHjkwU.js → client-COaiaDub.js} +447 -80
  38. package/dist/client-COaiaDub.js.map +1 -0
  39. package/dist/{client-CzyacQpJ.d.ts → client-qRnRUzBS.d.ts} +14 -13
  40. package/dist/client-qRnRUzBS.d.ts.map +1 -0
  41. package/dist/client.d.ts +4 -2
  42. package/dist/client.js +1 -1
  43. package/dist/{contract-CKRg_E4q.js → contract-BYKASlCV.js} +12 -21
  44. package/dist/contract-BYKASlCV.js.map +1 -0
  45. package/dist/{idempotent-replay-DVOlyYbx.js → idempotent-replay-JdgMQRRm.js} +2 -2
  46. package/dist/{idempotent-replay-DVOlyYbx.js.map → idempotent-replay-JdgMQRRm.js.map} +1 -1
  47. package/dist/index.d.ts +6 -5
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +4 -3
  50. package/dist/{errors-DvhSXnxk.d.ts → initial-snapshot-BrLso5x_.d.ts} +7 -3
  51. package/dist/initial-snapshot-BrLso5x_.d.ts.map +1 -0
  52. package/dist/{internal-Dq2qYxou.js → internal-CbVvEwad.js} +23 -2
  53. package/dist/internal-CbVvEwad.js.map +1 -0
  54. package/dist/otel.d.ts +1 -1
  55. package/dist/react-BbJ-g6Uu.d.ts +86 -0
  56. package/dist/react-BbJ-g6Uu.d.ts.map +1 -0
  57. package/dist/react.d.ts +2 -122
  58. package/dist/react.js +107 -100
  59. package/dist/react.js.map +1 -1
  60. package/dist/reducer-BAXc8JyF.js +87 -0
  61. package/dist/reducer-BAXc8JyF.js.map +1 -0
  62. package/dist/{reducer-otzHjuJj.d.ts → reducer-BYu6DT7h.d.ts} +45 -29
  63. package/dist/reducer-BYu6DT7h.d.ts.map +1 -0
  64. package/dist/reducer-runtime-DQwz_J59.js +28 -0
  65. package/dist/reducer-runtime-DQwz_J59.js.map +1 -0
  66. package/dist/scheduler-qstash.d.ts +3 -3
  67. package/dist/scheduler-qstash.d.ts.map +1 -1
  68. package/dist/scheduler-qstash.js +33 -10
  69. package/dist/scheduler-qstash.js.map +1 -1
  70. package/dist/{scheduler-task-BpzhPnRS.js → scheduler-task-X1SToDjP.js} +18 -3
  71. package/dist/scheduler-task-X1SToDjP.js.map +1 -0
  72. package/dist/scheduler-vercel.d.ts +4 -9
  73. package/dist/scheduler-vercel.d.ts.map +1 -1
  74. package/dist/scheduler-vercel.js +25 -44
  75. package/dist/scheduler-vercel.js.map +1 -1
  76. package/dist/{server-DbD7IJVK.d.ts → server-CGM8WzRG.d.ts} +87 -26
  77. package/dist/server-CGM8WzRG.d.ts.map +1 -0
  78. package/dist/{server-BD5ckxZb.js → server-Rj64fNvX.js} +854 -202
  79. package/dist/server-Rj64fNvX.js.map +1 -0
  80. package/dist/server.d.ts +4 -4
  81. package/dist/server.js +2 -2
  82. package/dist/{errors-DCk6ch5n.js → session-clear-DGI4jTQ5.js} +17 -2
  83. package/dist/session-clear-DGI4jTQ5.js.map +1 -0
  84. package/dist/session-clear-Dshpuhff.d.ts +12 -0
  85. package/dist/session-clear-Dshpuhff.d.ts.map +1 -0
  86. package/dist/{store-DyZM6fS5.d.ts → store-Gpkq7WBo.d.ts} +44 -3
  87. package/dist/store-Gpkq7WBo.d.ts.map +1 -0
  88. package/dist/store-N8PXxDAS.js.map +1 -1
  89. package/dist/store-memory.d.ts +1 -1
  90. package/dist/store-memory.d.ts.map +1 -1
  91. package/dist/store-memory.js +182 -69
  92. package/dist/store-memory.js.map +1 -1
  93. package/dist/{store-polling-CmxUbV93.js → store-polling-4_y8lETm.js} +21 -4
  94. package/dist/store-polling-4_y8lETm.js.map +1 -0
  95. package/dist/store-postgres.d.ts +1 -1
  96. package/dist/store-postgres.d.ts.map +1 -1
  97. package/dist/store-postgres.js +399 -108
  98. package/dist/store-postgres.js.map +1 -1
  99. package/dist/{store-presence-polling-C7-XZyW9.js → store-presence-polling-Q1C-k2dd.js} +3 -3
  100. package/dist/{store-presence-polling-C7-XZyW9.js.map → store-presence-polling-Q1C-k2dd.js.map} +1 -1
  101. package/dist/store-redis-http.d.ts +1 -1
  102. package/dist/store-redis-http.d.ts.map +1 -1
  103. package/dist/store-redis-http.js +293 -11
  104. package/dist/store-redis-http.js.map +1 -1
  105. package/dist/store-redis-notify-tqWNnMRu.js +2374 -0
  106. package/dist/store-redis-notify-tqWNnMRu.js.map +1 -0
  107. package/dist/store-redis.d.ts +1 -1
  108. package/dist/store-redis.d.ts.map +1 -1
  109. package/dist/store-redis.js +11 -11
  110. package/dist/store-redis.js.map +1 -1
  111. package/dist/store-sqlite.d.ts +1 -1
  112. package/dist/store-sqlite.d.ts.map +1 -1
  113. package/dist/store-sqlite.js +166 -32
  114. package/dist/store-sqlite.js.map +1 -1
  115. package/dist/stream-buffer-Cx7AAPGq.js +23 -0
  116. package/dist/stream-buffer-Cx7AAPGq.js.map +1 -0
  117. package/dist/{telemetry-B5jzpy6y.d.ts → telemetry-cZH-cibj.d.ts} +2 -2
  118. package/dist/telemetry-cZH-cibj.d.ts.map +1 -0
  119. package/dist/{wire-BO5wWCb1.js → wire-DjfHOvdW.js} +132 -10
  120. package/dist/wire-DjfHOvdW.js.map +1 -0
  121. package/docs/01-quickstart.mdx +2 -2
  122. package/docs/actors/01-introduction.mdx +4 -0
  123. package/docs/actors/04-routes.mdx +14 -1
  124. package/docs/concepts/01-contracts.mdx +95 -0
  125. package/docs/concepts/04-state.mdx +3 -3
  126. package/docs/guides/01-timers.mdx +22 -14
  127. package/docs/guides/02-cancellation.mdx +1 -1
  128. package/docs/guides/03-react.mdx +268 -328
  129. package/docs/guides/04-local-first.mdx +32 -73
  130. package/docs/guides/05-production.mdx +40 -27
  131. package/docs/guides/06-ai-agents.mdx +680 -580
  132. package/docs/guides/09-presence.mdx +3 -7
  133. package/docs/guides/10-transports.mdx +5 -5
  134. package/docs/index.mdx +10 -18
  135. package/docs/reference/01-api.mdx +662 -195
  136. package/docs/reference/02-errors.mdx +2 -1
  137. package/examples/playground/DEPLOY.md +52 -6
  138. package/examples/playground/app/a0/[appId]/loading.tsx +16 -0
  139. package/examples/playground/app/a0/[appId]/page.tsx +48 -0
  140. package/examples/playground/app/a0/a0.css +818 -0
  141. package/examples/playground/app/a0/app-list.tsx +211 -0
  142. package/examples/playground/app/a0/app-view.tsx +55 -0
  143. package/examples/playground/app/a0/apps/[appId]/preview/route.ts +27 -0
  144. package/examples/playground/app/a0/apps/[appId]/route.ts +23 -0
  145. package/examples/playground/app/a0/apps.test.ts +116 -0
  146. package/examples/playground/app/a0/apps.ts +87 -0
  147. package/examples/playground/app/a0/chat.tsx +364 -0
  148. package/examples/playground/app/a0/columns.tsx +109 -0
  149. package/examples/playground/app/a0/context-menu.tsx +116 -0
  150. package/examples/playground/app/a0/cookies.ts +46 -0
  151. package/examples/playground/app/a0/crash-on-request.ts +72 -0
  152. package/examples/playground/app/a0/crashes/route.ts +3 -0
  153. package/examples/playground/app/a0/crashes.test.ts +33 -0
  154. package/examples/playground/app/a0/crashes.ts +28 -0
  155. package/examples/playground/app/a0/devtools.tsx +164 -0
  156. package/examples/playground/app/a0/events/route.ts +6 -0
  157. package/examples/playground/app/a0/flags.ts +30 -0
  158. package/examples/playground/app/a0/layout.tsx +38 -0
  159. package/examples/playground/app/a0/list/route.ts +10 -0
  160. package/examples/playground/app/a0/model.test.ts +56 -0
  161. package/examples/playground/app/a0/model.ts +58 -0
  162. package/examples/playground/app/a0/page.tsx +14 -0
  163. package/examples/playground/app/a0/preview-pane.tsx +145 -0
  164. package/examples/playground/app/a0/preview.test.ts +53 -0
  165. package/examples/playground/app/a0/preview.ts +26 -0
  166. package/examples/playground/app/a0/sandbox-log.ts +69 -0
  167. package/examples/playground/app/a0/sandbox.ts +191 -0
  168. package/examples/playground/app/a0/sandboxes/scheduler/route.ts +6 -0
  169. package/examples/playground/app/a0/scheduler/route.ts +7 -0
  170. package/examples/playground/app/a0/server.ts +158 -0
  171. package/examples/playground/app/a0/session.ts +47 -0
  172. package/examples/playground/app/a0/shell.tsx +150 -0
  173. package/examples/playground/app/a0/short/events/route.ts +9 -0
  174. package/examples/playground/app/a0/splitter.tsx +120 -0
  175. package/examples/playground/app/agent/[agentId]/agent-client.tsx +144 -174
  176. package/examples/playground/app/agent/[agentId]/command-receipt.test.tsx +65 -0
  177. package/examples/playground/app/agent/[agentId]/command-receipt.tsx +27 -0
  178. package/examples/playground/app/agent/[agentId]/compaction-event.tsx +25 -24
  179. package/examples/playground/app/agent/[agentId]/compaction-panel.test.tsx +119 -0
  180. package/examples/playground/app/agent/[agentId]/compaction-panel.tsx +208 -165
  181. package/examples/playground/app/agent/[agentId]/page.tsx +2 -16
  182. package/examples/playground/app/agent/events/route.ts +5 -2
  183. package/examples/playground/app/agent/ingress.test.ts +98 -0
  184. package/examples/playground/app/agent/model-input.test.tsx +58 -0
  185. package/examples/playground/app/agent/model-input.tsx +115 -0
  186. package/examples/playground/app/agent/model.test.ts +73 -0
  187. package/examples/playground/app/agent/model.ts +58 -26
  188. package/examples/playground/app/agent/new-agent-session.tsx +37 -19
  189. package/examples/playground/app/agent/page.tsx +4 -4
  190. package/examples/playground/app/agent/server.ts +15 -14
  191. package/examples/playground/app/agent/session.ts +5 -6
  192. package/examples/playground/app/agent/telemetry.test.ts +168 -0
  193. package/examples/playground/app/agent/telemetry.ts +30 -0
  194. package/examples/playground/app/agent/trace.test.ts +23 -0
  195. package/examples/playground/app/agent/trace.ts +18 -0
  196. package/examples/playground/app/canvas/[canvasId]/canvas-client.tsx +11 -11
  197. package/examples/playground/app/canvas/[canvasId]/page.tsx +1 -2
  198. package/examples/playground/app/canvas/[canvasId]/session.ts +2 -2
  199. package/examples/playground/app/canvas/model.ts +53 -53
  200. package/examples/playground/app/chat/[chatId]/agent-state.test.ts +176 -0
  201. package/examples/playground/app/chat/[chatId]/agent-stream-drawer.tsx +16 -1
  202. package/examples/playground/app/chat/[chatId]/chat-client.tsx +55 -77
  203. package/examples/playground/app/chat/[chatId]/chat-view.ts +37 -3
  204. package/examples/playground/app/chat/[chatId]/page.tsx +7 -15
  205. package/examples/playground/app/chat/[chatId]/session.ts +2 -19
  206. package/examples/playground/app/chat/agent/route.ts +9 -0
  207. package/examples/playground/app/chat/model.test.ts +46 -119
  208. package/examples/playground/app/chat/model.ts +82 -128
  209. package/examples/playground/app/chat/scheduler/route.ts +2 -2
  210. package/examples/playground/app/chat/server.test.ts +108 -0
  211. package/examples/playground/app/chat/server.ts +69 -73
  212. package/examples/playground/app/components/activity-feed.tsx +30 -1
  213. package/examples/playground/app/components/activity-history.test.tsx +35 -0
  214. package/examples/playground/app/components/demo-icons.tsx +89 -0
  215. package/examples/playground/app/components/demos.tsx +33 -0
  216. package/examples/playground/app/components/fast-link.test.tsx +58 -0
  217. package/examples/playground/app/components/fast-link.tsx +55 -0
  218. package/examples/playground/app/components/site-header.tsx +96 -0
  219. package/examples/playground/app/counter/counter-client.tsx +3 -3
  220. package/examples/playground/app/counter/model.test.ts +3 -3
  221. package/examples/playground/app/counter/model.ts +11 -21
  222. package/examples/playground/app/counter/page.tsx +2 -15
  223. package/examples/playground/app/counter/session.ts +2 -2
  224. package/examples/playground/app/documents/[documentId]/code-editor.tsx +12 -1
  225. package/examples/playground/app/documents/[documentId]/document-client.tsx +10 -11
  226. package/examples/playground/app/documents/[documentId]/page.tsx +1 -2
  227. package/examples/playground/app/documents/[documentId]/session.ts +2 -2
  228. package/examples/playground/app/documents/model.ts +27 -27
  229. package/examples/playground/app/globals.css +420 -207
  230. package/examples/playground/app/initial-state.test.tsx +54 -0
  231. package/examples/playground/app/layout.tsx +7 -23
  232. package/examples/playground/app/orders/[orderId]/page.tsx +1 -2
  233. package/examples/playground/app/orders/[orderId]/session.ts +2 -2
  234. package/examples/playground/app/orders/model.ts +49 -49
  235. package/examples/playground/app/page.tsx +13 -102
  236. package/examples/playground/app/recovery/[recoveryId]/page.tsx +1 -2
  237. package/examples/playground/app/recovery/[recoveryId]/session.ts +2 -2
  238. package/examples/playground/app/recovery/model.ts +30 -30
  239. package/examples/playground/instrumentation.ts +5 -0
  240. package/examples/playground/package.json +10 -2
  241. package/examples/playground/scripts/a0-template.ts +77 -0
  242. package/examples/playground/vercel.json +18 -0
  243. package/package.json +3 -1
  244. package/src/actor-client.ts +16 -4
  245. package/src/actor-react.ts +25 -11
  246. package/src/actor-shared.ts +21 -6
  247. package/src/actor.ts +76 -31
  248. package/src/ai-agent-actions.ts +219 -0
  249. package/src/ai-agent.ts +2343 -0
  250. package/src/ai-app-state.ts +91 -0
  251. package/src/ai-client-state.ts +39 -2
  252. package/src/ai-client.ts +62 -0
  253. package/src/ai-context-schema.ts +25 -5
  254. package/src/ai-context.ts +90 -40
  255. package/src/ai-control-server.ts +94 -55
  256. package/src/ai-control.ts +2 -0
  257. package/src/ai-coordinator.ts +23 -22
  258. package/src/ai-message-base.ts +209 -0
  259. package/src/ai-message-projection.ts +382 -58
  260. package/src/ai-progress-batches.ts +26 -0
  261. package/src/ai-projector.ts +95 -14
  262. package/src/ai-react.ts +146 -0
  263. package/src/ai-server.browser.ts +2 -2
  264. package/src/ai-server.ts +20 -1951
  265. package/src/ai-stored-state.ts +48 -163
  266. package/src/ai.ts +343 -276
  267. package/src/bootstrap-stream.ts +72 -0
  268. package/src/bounded-cache.ts +55 -0
  269. package/src/client-render-session.ts +49 -0
  270. package/src/client.ts +688 -151
  271. package/src/contract.ts +92 -42
  272. package/src/deterministic-id.ts +37 -0
  273. package/src/equal-json.ts +32 -0
  274. package/src/errors.ts +13 -1
  275. package/src/index.ts +5 -1
  276. package/src/initial-snapshot.ts +39 -0
  277. package/src/internal.ts +45 -0
  278. package/src/postgres-notification-scope.ts +14 -3
  279. package/src/react.ts +171 -206
  280. package/src/redis-http-reply.ts +256 -0
  281. package/src/redis-http-stream.ts +73 -0
  282. package/src/redis-http-subscriptions.ts +13 -5
  283. package/src/reducer-runtime.ts +52 -0
  284. package/src/reducer.ts +92 -10
  285. package/src/scheduler-qstash.ts +49 -20
  286. package/src/scheduler-task.ts +34 -2
  287. package/src/scheduler-vercel.ts +32 -84
  288. package/src/server-fetch.ts +139 -35
  289. package/src/server.ts +957 -237
  290. package/src/session-clear.ts +23 -0
  291. package/src/session-socket.ts +154 -49
  292. package/src/sse.ts +71 -4
  293. package/src/store-memory.ts +220 -78
  294. package/src/store-polling.ts +18 -4
  295. package/src/store-postgres.ts +461 -114
  296. package/src/store-redis-core.ts +1260 -434
  297. package/src/store-redis-http.ts +18 -2
  298. package/src/store-redis-notify.ts +260 -85
  299. package/src/store-redis.ts +19 -9
  300. package/src/store-sqlite.ts +259 -45
  301. package/src/store.ts +46 -1
  302. package/src/stream-buffer.ts +43 -0
  303. package/src/telemetry.ts +13 -1
  304. package/src/wire.ts +241 -13
  305. package/dist/actor-ohPC-81x.d.ts.map +0 -1
  306. package/dist/actor-shared-B5tJfzt-.js.map +0 -1
  307. package/dist/ai-CrLYwNEx.js.map +0 -1
  308. package/dist/ai-HJ9fHfYI.d.ts.map +0 -1
  309. package/dist/client-BYzHjkwU.js.map +0 -1
  310. package/dist/client-CzyacQpJ.d.ts.map +0 -1
  311. package/dist/contract-CKRg_E4q.js.map +0 -1
  312. package/dist/errors-DCk6ch5n.js.map +0 -1
  313. package/dist/errors-DvhSXnxk.d.ts.map +0 -1
  314. package/dist/internal-Dq2qYxou.js.map +0 -1
  315. package/dist/react.d.ts.map +0 -1
  316. package/dist/reducer-DEMjEY_O.js +0 -29
  317. package/dist/reducer-DEMjEY_O.js.map +0 -1
  318. package/dist/reducer-otzHjuJj.d.ts.map +0 -1
  319. package/dist/scheduler-task-BpzhPnRS.js.map +0 -1
  320. package/dist/server-BD5ckxZb.js.map +0 -1
  321. package/dist/server-DbD7IJVK.d.ts.map +0 -1
  322. package/dist/store-DyZM6fS5.d.ts.map +0 -1
  323. package/dist/store-polling-CmxUbV93.js.map +0 -1
  324. package/dist/store-redis-notify-BVLUfI3j.js +0 -1583
  325. package/dist/store-redis-notify-BVLUfI3j.js.map +0 -1
  326. package/dist/telemetry-B5jzpy6y.d.ts.map +0 -1
  327. package/dist/validate-XKT4FSNn.js +0 -30
  328. package/dist/validate-XKT4FSNn.js.map +0 -1
  329. package/dist/wire-BO5wWCb1.js.map +0 -1
  330. package/examples/playground/app/agent/[agentId]/compaction/route.ts +0 -14
  331. package/examples/playground/app/agent/compaction-settings.test.ts +0 -144
  332. package/examples/playground/app/agent/compaction-settings.ts +0 -49
  333. package/examples/playground/app/agent/compaction-timeline.test.ts +0 -377
  334. package/examples/playground/app/agent/compaction-timeline.ts +0 -208
@@ -1,88 +1,47 @@
1
1
  ---
2
- title: Local-first
3
- description: "Cache one A2 session in the browser: instant revisits, stream resumption, and offline writes."
2
+ title: Client lifetime
3
+ description: "Share an in-memory session, control idle eviction, and resume its event stream."
4
4
  ---
5
5
 
6
- ## One option
6
+ ## Reuse a client
7
7
 
8
- ```tsx app/orders/[orderId]/session.ts
8
+ Keep the client in a module so components share session identity, state,
9
+ raw events, pending writes, and connection ownership:
10
+
11
+ ```ts app/session-client.ts
9
12
  'use client'
10
- import { createReact } from 'experimental-a2/react'
11
- import { indexedDb } from 'experimental-a2/cache-indexeddb'
12
- import { ordersReducer } from '@/reducer'
13
+ import { createClient } from 'experimental-a2/client'
14
+ import { orders } from '@/contracts'
13
15
 
14
- export const { SessionProvider, useSession } = createReact({
15
- reducer: ordersReducer,
16
- cache: indexedDb(),
16
+ export const ordersClient = createClient({
17
+ contract: orders,
18
+ api: '/api/order-events',
19
+ gcTime: 5 * 60 * 1_000,
17
20
  })
18
21
  ```
19
22
 
20
- This is the [session module](/guides/react#the-session-module) from Live
21
- UI with one addition. The cache is created here, in client code, where
22
- live objects belong; server components keep importing `SessionProvider`
23
- exactly as before; nothing crosses the server boundary.
24
-
25
- With `cache` set:
26
-
27
- - **Revisits paint instantly.** State folds from the local copy before the
28
- network says a word. Works offline too.
29
- - **The stream resumes from the cached frontier.** Reconnects fetch only
30
- what this browser hasn't seen, no matter how long it's been.
31
- - **Pushes queue while offline**, and replay, in order, when the
32
- connection returns.
33
-
34
- Without it, nothing changes; the provider behaves exactly as before.
35
-
36
- ## Why this is safe
37
-
38
- Local-first is usually hard because it means syncing state, and state
39
- needs merging. A2 caches an append-only log with server-assigned positions
40
- instead. A cached event can never be *wrong*; this browser can only be
41
- *behind*. Catching up is fetching events after an index, which is what
42
- `stream({ startAfter })` does anyway. There's no merge function because
43
- there's nothing to merge.
44
-
45
- ## What gets stored
23
+ `gcTime` is the idle eviction delay, five minutes by default. Active
24
+ connections, listeners, and pending work keep the runtime alive. Another
25
+ client instance has a separate cache even when it uses the same URL.
46
26
 
47
- Three things, in IndexedDB:
27
+ ## Startup and reconnect
48
28
 
49
- | Stored | Invalidated by |
50
- | --------------------------------------------- | ------------------------------------------------------- |
51
- | the session's events | nothing; they're immutable |
52
- | a folded snapshot, keyed by reducer name | a reducer rename (or a `stateSchema` mismatch, when the reducer declares one): discarded, refolded from cached events |
53
- | pending pushes | the server's ack |
29
+ A new unseeded session receives a snapshot on its event subscription.
30
+ A supplied state/index pair skips that read. A cached session keeps its
31
+ state and resumes after its delivered event position. See
32
+ [Live UI](/guides/react).
54
33
 
55
- Note the middle row: the reducer's `name` does the same job here that it
56
- does for [server snapshots](/concepts/state#snapshots-are-a-cache). One
57
- string, both sides of the wire.
34
+ State snapshots bound replay work, not the size of the reducer state or
35
+ raw event collection. Choose reducer contents that fit the application.
58
36
 
59
- Deleting the cache is always safe. It's a replica of what this browser
60
- already saw; the log on the server stays the only authority.
37
+ ## Persistence
61
38
 
62
- ## Offline writes
63
-
64
- `push` while offline persists the events, client ids and all, and
65
- applies them optimistically, same as always. On reconnect they replay in
66
- order. The replay is idempotent by construction: an identical batch is an
67
- idempotent success, so a flaky reconnect, or a second tab replaying the
68
- same queue, causes no duplicates. Rejections refold without the event,
69
- exactly like an online push the server refused.
70
-
71
- What this is *not*: local execution. Handlers run on the server, when the
72
- events arrive. An offline push is a queued intent, not a completed
73
- action. Design the UI accordingly ("sending…" is honest; "sent" is not).
74
-
75
- ## Logout
76
-
77
- Cached events outlive the session cookie. If your app authenticates,
78
- clear the cache when the user leaves:
79
-
80
- ```ts
81
- // in the browser, wherever your logout flow runs:
82
- await cache.clear() // everything, or cache.clear(sessionId) for one session
83
- ```
39
+ This cache is in memory. Reloading the page discards it. A2 does not ship
40
+ an IndexedDB adapter or a persistent offline write queue. The
41
+ `experimental-a2/cache-indexeddb` entry point currently has no implementation.
84
42
 
85
- That's the one obligation `cache` puts on you. The cache never *grants*
86
- access (your routes still authorize every read and write) but it
87
- *retains* what was legitimately seen, and retained data belongs to the
88
- person who saw it.
43
+ Pushes have bounded retries and optimistic rollback when they fail.
44
+ Application-owned persistence can restore a state/index pair through the
45
+ normal hydration options. Only restore data belonging to the same session
46
+ and reducer version, and keep authentication and retained-data policy in
47
+ the application.
@@ -77,11 +77,13 @@ from another client can take up to the idle interval plus query latency to
77
77
  arrive after a quiet period.
78
78
 
79
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.
80
+ session as a Redis Stream and streams push-natively: every write publishes a
81
+ disposable wake-up, one shared subscriber connection per process serves every
82
+ connected viewer, and a safety re-read covers a lost wake-up within ten
83
+ seconds. Connections scale with your processes, not with your audience. On
84
+ providers that bill per command, such as Upstash, each command a script runs
85
+ counts: appending an event with a handler and running it takes 24 commands,
86
+ and an idle live stream takes 6 a minute.
85
87
  `ioredis` is an optional peer dependency; pass `url`, or inject a client.
86
88
  Works on single instances and non-cluster providers such as Upstash, where
87
89
  durability is on by default. When only a REST API is available,
@@ -89,8 +91,12 @@ durability is on by default. When only a REST API is available,
89
91
  same storage over `fetch`. Live events and presence use Upstash’s HTTP
90
92
  `SUBSCRIBE` endpoint (SSE), with the same URL and token. Each active channel
91
93
  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
+ viewers and closed when the last viewer leaves. Writes publish wake-ups inside
95
+ the same Redis script as the append, and each wake-up carries the new events so
96
+ live streams deliver them without another read. Live streams trust those
97
+ messages, so a Redis identity that can publish to the `:notify` channels can add
98
+ or hide live events: grant publish access only to identities that can also
99
+ write the store. Presence writes
94
100
  also publish their applied fields inside the script. A rejected notification
95
101
  does not fail the applied write. Idle event streams
96
102
  read once every ten seconds to recover missed notifications. Subscriptions reconnect automatically and catch up from
@@ -172,29 +178,30 @@ route's protection boundary: Vercel makes the function private, and only queue
172
178
  infrastructure can invoke it. Do not expose the callback as an ordinary public
173
179
  route or add a second authentication scheme around it.
174
180
 
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.
181
+ The adapter uses one queue client for recovery watchdogs, scheduled appends,
182
+ and scheduled clears. Its `deploymentId` setting applies to sends, payload
183
+ fetches, acknowledgements, and visibility changes. Omit it to use the publishing
184
+ deployment, pass a deployment ID to target that deployment, or pass `null` for
185
+ deploymentless delivery. Publishing and consuming clients must use the same
186
+ deployment scope.
178
187
 
179
- You can remove deployment affinity from scheduled appends only:
188
+ Promoting a new deployment does not move pinned tasks. Older deployments keep
189
+ receiving their own work until it is acknowledged or expires. Upgrading does
190
+ not repair an older deployment's callback handler.
180
191
 
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
- ```
192
+ With `deploymentId: null`, Vercel chooses an eligible deploymentless consumer;
193
+ this does not select the latest production deployment. Every eligible consumer
194
+ must use `deploymentId: null` and remain compatible with pending tasks and
195
+ session work.
189
196
 
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.
197
+ Changing the setting does not move already queued tasks. Keep consumers for
198
+ their original deployment scope until those tasks finish. The former
199
+ `scheduledAppends: 'deploymentless'` option affected only timers; its replacement,
200
+ `deploymentId: null`, also makes recovery deploymentless.
195
201
 
196
202
  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
203
+ to 7 days and accepts scheduled appends and clears through 6 days, reserving
204
+ the final day
198
205
  for delivery and retries. Recovery watchdogs are measured in seconds.
199
206
 
200
207
  ### QStash
@@ -446,8 +453,11 @@ identity, stale-timer guards, and the full provider limits.
446
453
  :::warning
447
454
  Do not roll back to an A2 version without timer delivery while scheduled tasks
448
455
  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.
456
+ acknowledge a new append task without writing its events. The same applies to
457
+ scheduled clears and a version without them: an old Vercel Queues route keeps
458
+ failing a clear task until retention ends, and an old QStash route rejects it
459
+ as non-retryable, so QStash moves it to its dead-letter queue. Roll forward, or
460
+ wait for those tasks to deliver or remove them through the provider first.
451
461
  :::
452
462
 
453
463
  ## 3. When an event dead-letters
@@ -494,6 +504,9 @@ alert on when an event [needs manual
494
504
  resolution](#3-when-an-event-dead-letters). See the
495
505
  [API reference](/reference/api#a2otel) for the span catalogue.
496
506
 
507
+ Agent servers use the same telemetry for `a2.ai.*` spans. Core and AI operations
508
+ can nest and overlap, so their durations do not add up to separate layer totals.
509
+
497
510
  ## Running without a scheduler
498
511
 
499
512
  If none of a contract's event types have handlers and the app never calls