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
@@ -12,12 +12,16 @@ a2.contract(options: {
12
12
  name: string
13
13
  events: Record<string, StandardSchemaV1>
14
14
  presence?: Record<string, StandardSchemaV1>
15
+ reducer?: { name: string; initialState: S; stateSchema?: StandardSchemaV1; fold: (state: S, event: Event) => S }
15
16
  }): Contract
16
17
  ```
17
18
 
18
19
  Defines one kind of lifecycle: a name plus the events it understands. The name
19
20
  must be unique per app; it identifies the contract in storage and queue
20
- messages. The result is a plain, importable, **isomorphic** value: the server
21
+ messages. The name may not contain the control character U+001F, and event
22
+ types may not start with `a2.`, which A2 reserves for its own events, such as
23
+ `a2.cleared`. The result is a plain,
24
+ importable, **isomorphic** value: the server
21
25
  implements it, reducers derive from it, the browser types its pushes off it.
22
26
  See [Contracts and sessions](/concepts/contracts).
23
27
 
@@ -37,17 +41,20 @@ Preserves the literal types in a heterogeneous handler-returned event array.
37
41
  It returns the same events as a readonly tuple; A2 validates them when the
38
42
  handler completes.
39
43
 
40
- ### `contract.reducer(options)`
44
+ ### `reducer(options)`
41
45
 
42
46
  ```ts
43
- contract.reducer(options: {
47
+ reducer(options: {
48
+ contract: Contract
44
49
  name: string // identity; keys cached snapshots
45
50
  initialState: S // the seed (schema input, when stateSchema is set)
46
51
  stateSchema?: StandardSchemaV1 // declares S, checks initialState, guards cached folds
47
52
  }): { fold(fn: (state: S, event: Event) => S): Reducer<S> }
48
53
  ```
49
54
 
50
- Derives a view from the contract. Two steps by design: the first call
55
+ Builds a separate reducer for server reads or for the contract's optional
56
+ `reducer` field. The contract publishes only that selected reducer to clients.
57
+ Inline definitions include `fold` directly. The standalone builder uses two steps: the first call
51
58
  fixes the state and event types, so `.fold()` receives fully concrete
52
59
  ones. Literal unions in your state survive, with zero annotations.
53
60
 
@@ -88,6 +95,15 @@ ID generation unchanged. Changing an existing application's ID recipe changes
88
95
  retry identity, so retries of previously committed requests need to retain
89
96
  their original IDs.
90
97
 
98
+ ### `SessionCleared`
99
+
100
+ The marker a [clear](#sessionclearoptions) leaves in `session.stream()`: an
101
+ `Event` whose `type` is `'a2.cleared'` and whose `payload` is `null`. It
102
+ never reaches handlers or `history()`. Comparing `event.type` with that string
103
+ narrows a stream item to `SessionCleared`. The session client handles the
104
+ marker on its own, so this matters only when you read the server stream
105
+ yourself.
106
+
91
107
  ## `experimental-a2/server`
92
108
 
93
109
  ### `handler(entry)`
@@ -332,6 +348,9 @@ export const POST = ordersServer.fetch
332
348
  A plain GET streams one session over SSE, resumed after the `index` query
333
349
  parameter. A GET with `gte` and `lte` returns the closed history range as JSON
334
350
  and sets `a2-history-covered` to `true` when every requested index was returned.
351
+ When the range includes the latest clear marker, it also sets `a2-clear-index`,
352
+ and coverage counts only the requested indexes after that marker. A range
353
+ entirely below the marker returns `[]`.
335
354
  POST accepts `{ sessionId, events, presence? }` and returns the appended events.
336
355
  Presence is inferred from the contract. A contract that declares presence
337
356
  streams it and accepts presence pushes automatically.
@@ -491,6 +510,7 @@ type StoredEvent = Event & {
491
510
  lastFailedAttempt: number | null
492
511
  lastError: string | null
493
512
  failedAt: Date | null
513
+ clearIndex?: number // on claimed events, once the session has been cleared
494
514
  }
495
515
 
496
516
  type StoreAppendResult = {
@@ -511,8 +531,17 @@ interface A2Store {
511
531
  append(
512
532
  sessionId: string,
513
533
  events: AppendEvent[],
534
+ options?: { originIndex?: number },
514
535
  ): Promise<StoreAppendResult>
515
536
 
537
+ // optional together; required for session.clear()
538
+ clear?(options: {
539
+ sessionId: string
540
+ ifLastIndex?: number
541
+ cause?: EventCause
542
+ }): Promise<{ cleared: boolean; index: number; clearIndex: number }>
543
+ reclaim?(sessionId: string): Promise<{ deleted: number; done: boolean }>
544
+
516
545
  claimAvailable(options: {
517
546
  sessionId: string
518
547
  holder: string
@@ -556,6 +585,22 @@ interface A2Store {
556
585
  | `renewClaims` | Extend the listed live claims still owned by the holder. `renewed` lists the claims that remain owned; `superseded` lists the claims whose event's `attemptCount` passed the listed attempt. An expired claim no successor took appears in neither: it may still complete. Renewal never revives an expired claim. |
557
586
  | `completeAttempt` | Fence on the current attempt, mark the parent processed, store its exact ordered `returnedEventIds`, and append the returned batch in the same transaction. A same-attempt retry returns the committed children; a stale attempt returns `superseded`. |
558
587
  | `failAttempt` | Record one current caught failure, clear its claim, and dead-letter at `maxFailures`. A repeated failure acknowledgment is idempotent; stale attempts return `superseded`. |
588
+ | `clear` | Serialized with `append`. Check `ifLastIndex` against the head and the latest marker, and append a settled clear marker at the next index, touching no existing rows. A `cause` is fenced like a handler append. |
589
+ | `reclaim` | Delete one bounded batch of rows below the latest clear marker, with their bookkeeping, event-id records, lane pointers, and cached state. Report how many rows went and `done` when nothing is left. Never wait on a lock that other methods hold. |
590
+
591
+ A store that supports clearing treats every event at or below the latest clear
592
+ marker as superseded, whether or not its row still exists. `claimAvailable`
593
+ never claims it, lane readiness at append and at completion ignores it (a
594
+ dead-lettered lane head stops blocking), `renewClaims` reports it in
595
+ `superseded`, `completeAttempt` and `failAttempt` return `superseded`, and an
596
+ `append` whose `cause` names it throws `SUPERSEDED_ATTEMPT`. An `append` whose
597
+ `originIndex` is at or below the marker throws `SESSION_CLEARED`. Reads start
598
+ at the marker, `readState` returns its index as `clearIndex`, and claimed
599
+ events carry it as `clearIndex`. In `append` and `completeAttempt`, an event id
600
+ whose row is below this session's marker is free to reuse. Each check belongs
601
+ inside the operation's existing atomic step. Without `clear` and `reclaim`,
602
+ `session.clear()` and `session.schedule.clear()` throw a `TypeError` before
603
+ writing anything.
559
604
 
560
605
  `attemptCount` counts claims, including abandoned ones. `failureCount` counts
561
606
  caught failures and dead-letters at ten. See
@@ -578,49 +623,61 @@ Custom scheduler adapters implement two methods:
578
623
  | Method | Effect |
579
624
  | --- | --- |
580
625
  | `schedule(task: SchedulerTask): Promise<void>` | put one versioned task on durable delivery infrastructure |
581
- | `handler(...servers)` | return the route that receives drain and append tasks |
626
+ | `handler(...servers)` | return the route that receives drain, append, and clear tasks |
582
627
 
583
628
  Application routes call `scheduler.handler(...servers)` on the configured
584
629
  adapter. The handler requires at least one drainable server and rejects
585
630
  duplicate contract names synchronously.
586
631
 
587
- `SchedulerTask` is `SchedulerDrainTask | SchedulerAppendTask`. These task types,
588
- plus `ScheduledEvent`, are exported from `experimental-a2/server`.
632
+ `SchedulerTask` is `SchedulerDrainTask | SchedulerAppendTask |
633
+ SchedulerClearTask`. These task types, plus `ScheduledEvent`, are exported
634
+ from `experimental-a2/server`.
589
635
 
590
636
  | Task | Fields |
591
637
  | --- | --- |
592
638
  | `SchedulerDrainTask` | `version: 1`, `kind: 'drain'`, `contract`, `sessionId`, required epoch-ms `dueAt` |
593
- | `SchedulerAppendTask` | `version: 1`, `kind: 'append'`, stable `id`, `contract`, `sessionId`, required epoch-ms `dueAt`, readonly `events` |
639
+ | `SchedulerAppendTask` | `version: 1`, `kind: 'append'`, stable `id`, `contract`, `sessionId`, required epoch-ms `dueAt`, readonly `events`, optional `originIndex` |
640
+ | `SchedulerClearTask` | `version: 1`, `kind: 'clear'`, stable `id`, `contract`, `sessionId`, required epoch-ms `dueAt`, `ifLastIndex` |
594
641
  | `ScheduledEvent` | fixed `id`, `type`, and JSON `payload` |
595
642
 
596
643
  Drain tasks wake pending work and carry no application payload. Append tasks
597
- carry the events for `session.schedule`.
644
+ carry the events for `session.schedule`; `originIndex` is the scheduling
645
+ handler's event, absent for root schedules. Clear tasks carry the condition
646
+ for `session.schedule.clear`.
598
647
 
599
- Custom adapters deliver an authenticated, normalized append task through the
600
- server helper:
648
+ Custom adapters deliver an authenticated, normalized append or clear task
649
+ through the server helpers:
601
650
 
602
651
  ```ts custom-scheduler.ts
603
652
  import {
604
653
  deliverSchedulerAppend,
654
+ deliverSchedulerClear,
605
655
  type DrainableServer,
606
656
  type SchedulerAppendTask,
657
+ type SchedulerClearTask,
607
658
  } from 'experimental-a2/server'
608
659
 
609
660
  export async function deliver(
610
661
  server: DrainableServer,
611
- task: SchedulerAppendTask,
662
+ task: SchedulerAppendTask | SchedulerClearTask,
612
663
  ) {
613
- await deliverSchedulerAppend(server, task)
664
+ if (task.kind === 'clear') await deliverSchedulerClear(server, task)
665
+ else await deliverSchedulerAppend(server, task)
614
666
  }
615
667
  ```
616
668
 
617
- Import `deliverSchedulerAppend` from `experimental-a2/server`. It verifies the
618
- task and server contract match, revalidates the carried event inputs, and uses
619
- the ordinary top-level append path. The target server must have a scheduler.
620
- When the append leaves pending handler work, the helper resolves only after a
621
- recovery watchdog is durable; a send failure keeps the provider delivery
622
- retryable. Authenticate the transport and validate the task envelope before
623
- calling it.
669
+ Import both from `experimental-a2/server`. Each verifies the task and server
670
+ contract match. `deliverSchedulerAppend` revalidates the carried event inputs
671
+ and uses the ordinary top-level append path. The target server must have a
672
+ scheduler. When the append leaves pending handler work, the helper resolves
673
+ only after a recovery watchdog is durable; a send failure keeps the provider
674
+ delivery retryable. A task whose `originIndex` was cleared resolves without
675
+ appending.
676
+
677
+ `deliverSchedulerClear` runs a conditional `clear({ ifLastIndex })` and
678
+ resolves after its deletion finishes. A store failure rejects, so the provider
679
+ redelivers and the retry finishes deleting. Authenticate the transport and
680
+ validate the task envelope before calling either helper.
624
681
 
625
682
  ## Session
626
683
 
@@ -708,10 +765,100 @@ ordinary top-level append validates that input, so a schema
708
765
  transform produces the stored payload from the original input. The events have
709
766
  `cause: null` and enter normal handlers, lanes, state, and streams.
710
767
 
768
+ A task scheduled from a handler belongs to its triggering event's lifetime. If
769
+ the session is cleared before delivery, the task is acknowledged without
770
+ appending. Once a session has been cleared, handler task ids also include the
771
+ clear, so a trigger id reused in the new lifetime schedules a new task. A root
772
+ task has no triggering event, so its events join whichever lifetime exists when
773
+ it is delivered, and its name identifies one task across lifetimes. Put
774
+ something from the new lifetime in the name when you schedule again after a
775
+ clear.
776
+
711
777
  There is no cancellation, reschedule, cron, or timer-listing API. See
712
778
  [Timers and delays](/guides/timers) for stale-timer guards, provider limits,
713
779
  and deployment compatibility.
714
780
 
781
+ ### `session.clear(options?)`
782
+
783
+ ```ts
784
+ session.clear(options?: { ifLastIndex?: number }): Promise<{
785
+ cleared: boolean
786
+ index: number
787
+ }>
788
+ ```
789
+
790
+ End the session's current lifetime. One atomic step checks the condition and
791
+ appends a reserved clear marker at the next index. The session id stays usable
792
+ and indexes never restart. `ifLastIndex` is a non-negative safe integer; the
793
+ first matching rule decides:
794
+
795
+ | When | Result |
796
+ | --- | --- |
797
+ | the latest marker is at `ifLastIndex + 1` | `{ cleared: true, index }`, a retry of that clear |
798
+ | `ifLastIndex` is given and is not the head | `{ cleared: false, index }`, nothing changes |
799
+ | the head is `0` or already a clear marker | `{ cleared: true, index }`, nothing changes |
800
+ | otherwise | `{ cleared: true, index }`, the new marker |
801
+
802
+ `index` is always the head after the call. Every event at or below the marker
803
+ is superseded: it is never dispatched again and never blocks its lane, its
804
+ running handler's appends throw `SUPERSEDED_ATTEMPT`, and that handler's state
805
+ reads never fold the new lifetime: they throw `SESSION_CLEARED`, except that
806
+ after a clear from another instance, a read bounded at its own event may return
807
+ that event's state from memory until the handler's `ctx.signal` aborts. Pushes and appends after the marker start the
808
+ new lifetime.
809
+
810
+ `clear()` then deletes the old events, their bookkeeping and event-id records,
811
+ and cached state below the marker in short batches, and resolves when they are
812
+ gone. Other work on the session keeps running between batches. If deletion
813
+ fails, the call rejects with the session already cleared; any later clear of
814
+ the session, including a retry, finishes the deletion. There is no background
815
+ sweep. Event ids from the old lifetime can be used again. Presence is not
816
+ cleared.
817
+
818
+ On `ctx.session`, the clear is fenced like the handler's appends and ends the
819
+ handler's own attempt: `ctx.signal` aborts with `SUPERSEDED_ATTEMPT` and
820
+ returned events are discarded. Its deletion keeps running under the platform's
821
+ `waitUntil`. All built-in stores support clearing. See
822
+ [Clearing a session](/concepts/contracts#clearing-a-session).
823
+
824
+ ### `session.schedule.clear(name, options)`
825
+
826
+ ```ts
827
+ session.schedule.clear(
828
+ name: string,
829
+ options: ({ delay: ScheduleDelay } | { at: Date }) & {
830
+ ifLastIndex: number
831
+ },
832
+ ): Promise<void>
833
+ ```
834
+
835
+ Publish a conditional clear to the configured scheduler. `name` is non-empty
836
+ and `ifLastIndex` is required. Timing has the same meaning and provider limits
837
+ as `session.schedule`. The task id derives from the contract, session, name,
838
+ and condition, plus the triggering event id and any earlier clear inside a
839
+ handler; reuse a name only for the same timing. The promise resolves after
840
+ provider acceptance.
841
+
842
+ Delivery runs `clear({ ifLastIndex })` and acknowledges after the deletion
843
+ finishes. A stale or duplicate delivery changes nothing. No event is appended.
844
+ Indexes never repeat, so a condition from an older lifetime never matches a
845
+ newer one. Clear tasks, appends, and recovery drains all use the Vercel
846
+ scheduler's `deploymentId` setting.
847
+
848
+ `ifLastIndex: ctx.event.index` matches only while the handler's event is still
849
+ the head. The handler's own appends and returned events move the head, so a
850
+ handler that writes should use the index of its last `ctx.session.append`
851
+ instead. Actor handlers always commit state, so they can't use the trigger's
852
+ index. A delay short enough to deliver while the handler is still running can
853
+ match before those writes land; the clear then ends the handler's attempt and
854
+ discards its returned events.
855
+
856
+ Each delivery attempt resumes the deletion where the last one stopped. Vercel
857
+ Queues redelivers until retention ends. QStash retries one message up to its
858
+ `retries` budget; a deletion that outlasts every retry leaves the message in
859
+ QStash's dead-letter queue with the rest of the old history still invisible,
860
+ and any later clear of the session finishes it.
861
+
715
862
  ### `session.history()`
716
863
 
717
864
  ```ts
@@ -721,6 +868,8 @@ session.history(options?: { gte?: number; lte?: number }): Promise<Event[]>
721
868
  Raw events from the session, oldest first. `gte` and `lte` are inclusive event
722
869
  indexes; omit a bound to leave that end of the log open.
723
870
  Bounds are non-negative safe integers. `lte: 0` returns `[]`; `gte > lte` throws.
871
+ After a clear, history starts after the marker. The marker itself is not
872
+ returned.
724
873
 
725
874
  Redis reads a fixed committed range in pages of at most 256 events, with up to
726
875
  sixteen page reads in flight. Public history omits dispatch metadata. PostgreSQL
@@ -735,7 +884,7 @@ adapters can implement `readHistory(sessionId, options)` with the same exclusive
735
884
  session.state(
736
885
  reducer: Reducer<S>,
737
886
  options?: { through?: number | 'latest' },
738
- ): Promise<{ state: S; index: number }>
887
+ ): Promise<{ state: S; index: number; clearIndex?: number }>
739
888
  ```
740
889
 
741
890
  The log folded through one committed prefix. `index` is its exact boundary,
@@ -750,6 +899,14 @@ Use `{ through: 'latest' }` inside a handler only when later committed events
750
899
  are intentionally part of the decision. A numeric `through` is an inclusive,
751
900
  non-negative safe integer; `through: 0` returns the reducer's initial state.
752
901
 
902
+ Once a session has been cleared, the result includes `clearIndex`, the latest
903
+ marker, and state folds from there. Forward the whole result when hydrating a
904
+ client. A positive `through` below `clearIndex` throws `SESSION_CLEARED`, and
905
+ `through: 0` returns the seed without `clearIndex`. On
906
+ `ctx.session`, every state read throws it once the triggering event has been
907
+ cleared, including `{ through: 'latest' }`. Handler `history()` and `stream()`
908
+ are not fenced.
909
+
753
910
  The default handler read and numeric handler reads automatically retain their
754
911
  exact folded checkpoint while the triggering event is unfinished. Completion
755
912
  or dead-lettering releases the reference and collects the checkpoint when no
@@ -771,11 +928,11 @@ completed read or attaching snapshot contents.
771
928
  ### `session.stream(options?)`
772
929
 
773
930
  ```ts
774
- session.stream(options?: { startAfter?: number }): AsyncIterable<Event>
931
+ session.stream(options?: { startAfter?: number }): AsyncIterable<Event | SessionCleared>
775
932
  session.stream(options: {
776
933
  startAfter?: number
777
934
  presence: true
778
- }): AsyncIterable<Event | PresencePatch | PresenceSnapshot>
935
+ }): AsyncIterable<Event | SessionCleared | PresencePatch | PresenceSnapshot>
779
936
  ```
780
937
 
781
938
  A live feed of the session's events. `startAfter` is a non-negative safe integer;
@@ -783,6 +940,12 @@ A live feed of the session's events. `startAfter` is a non-negative safe integer
783
940
  Server-side only; `server.fetch` exposes it over SSE and WebSocket. Subscribing
784
941
  never dispatches handlers.
785
942
 
943
+ A clear arrives in order as a `SessionCleared` marker, whose `type` is
944
+ `'a2.cleared'`. A stream that opens below the
945
+ latest marker begins with it. A stream that is already running delivers the
946
+ log in order, so events from before the clear can arrive ahead of the marker.
947
+ Reset when the marker arrives. Clearing never closes a stream.
948
+
786
949
  With `presence: true`, the feed yields one `PresenceSnapshot` first:
787
950
  `{ snapshot }`, the current pruned map with each field's own `value`,
788
951
  `seen`, and `at` stamp. Live presence patches then interleave with
@@ -865,13 +1028,12 @@ type Event = {
865
1028
 
866
1029
  ```ts
867
1030
  createReact(options: {
868
- client: A2Client // bind a shared client: api + reducer + session identity
1031
+ client: A2Client // bind a shared client: api + contract + session identity
869
1032
  }): { SessionProvider, useSession }
870
1033
 
871
- // or, standalone (one client per provider mount):
1034
+ // or, bind the contract and supply api through the Provider:
872
1035
  createReact(options: {
873
- reducer: Reducer // types everything: state, pushes, the feed
874
- cache?: A2ClientCache // optional; see local-first
1036
+ contract: Contract // requires a public reducer
875
1037
  }): { SessionProvider, useSession }
876
1038
  ```
877
1039
 
@@ -879,9 +1041,9 @@ A factory, like `createContext`: call it once in a `'use client'`
879
1041
  module, export the bound pair. Server components import
880
1042
  `SessionProvider` from that module as a client reference; client
881
1043
  components import `useSession` from the same file. See
882
- [Live UI](/guides/react#the-session-module).
1044
+ [Live UI](/guides/react#bind-react-once).
883
1045
 
884
- `createReact({ reducer })` is also supported for provider-bound clients. Use a
1046
+ `createReact({ contract })` is also supported for provider-bound clients. Use a
885
1047
  shared `A2Client` when code outside the provider needs to push before a route
886
1048
  transition and the destination should adopt the same optimistic session.
887
1049
 
@@ -890,35 +1052,61 @@ transition and the destination should adopt the same optimistic session.
890
1052
  | Prop | What it is |
891
1053
  | --------------- | ------------------------------------------------------ |
892
1054
  | `sessionId` | which session to subscribe to |
893
- | `initialState` | server-rendered state |
894
- | `initialIndex` | the fold's frontier, where a new session starts streaming |
1055
+ | `initial` | optional snapshot or Promise of a snapshot; `null` means bootstrap |
895
1056
  | `initialEvents` | optional earlier raw events for a history UI |
896
1057
  | `participant` | overrides the client's `participant`; one of the two is required to call `setPresence` |
897
1058
 
898
- Opens the stream on mount, closes it on unmount, reconnects with
899
- backoff from its last streamed event. `participant` binds at the session's
1059
+ Supplies scope and optional initial data. Mounted enabled hooks own
1060
+ connection leases and reconnect from the last streamed event. `participant` binds at the session's
900
1061
  first resolution; a changed provider prop is ignored for the session's
901
1062
  runtime lifetime.
902
1063
 
903
- `initialState` and `initialIndex` are the complete server-to-client handoff.
904
- For a new session, the stream starts after `initialIndex`, including events
1064
+ `initial` carries the complete state/index snapshot, plus `clearIndex` when
1065
+ the session has been cleared.
1066
+ For a new session, the stream starts after `initial.index`, including events
905
1067
  appended between the server render and the connection. `initialEvents` only preloads the raw
906
1068
  `events` feed for a UI that needs entries from before that frontier. It does
907
1069
  not affect state hydration or stream resumption. Later snapshots advance
908
1070
  state while preserving the stream's resume position, so delayed events
909
1071
  still enter the feed without folding into state again.
910
1072
 
911
- ### `useSession()`
1073
+ `initial` accepts `InitialSnapshot<S>`: `StateSnapshot<S> | null | undefined |
1074
+ Promise<StateSnapshot<S> | null | undefined>`. The type is exported from
1075
+ `experimental-a2` and `experimental-a2/client`. A cold session waits for a
1076
+ supplied Promise before connecting. A snapshot result seeds state; `null`
1077
+ or `undefined` selects subscription bootstrap. Rejection pauses cold startup
1078
+ and reports through `connection.error`; supply a new Promise, snapshot, or
1079
+ `null` to resume. A warm session keeps its current state and connection.
1080
+ The Provider itself does not suspend. Keep Promise identity stable.
1081
+
1082
+ ### `useSession(options?)`
912
1083
 
913
1084
  ```ts
914
- const { state, push, reconnect, events, index, loadHistory, history, connection,
915
- presence, setPresence } = useSession()
1085
+ const { state, hydrated, push, reconnect, events, index, loadHistory, history, connection } = useSession()
916
1086
  ```
917
1087
 
918
- `state` starts from the initial snapshot and folds live events through the
1088
+ `state` has the reducer's client state type. A supplied snapshot seeds the
1089
+ first render. Otherwise it displays `reducer.initialState`, through the client
1090
+ projection when present, until a snapshot replaces it atomically. Without a
1091
+ supplied snapshot, the server sends one first on the same subscription.
1092
+ Bootstrap errors and retries use `connection`.
1093
+ `hydrated` is a boolean shared with `session.getSnapshot()`. It is `false`
1094
+ while only the reducer seed is available and becomes `true` when an initial
1095
+ snapshot or bootstrap is applied, even for an empty state at index 0.
1096
+ It stays `true` while cached state survives reconnects, disabled hooks, or a
1097
+ failed replacement preload. It does not imply a live connection or fresh data.
1098
+ The hook accepts `sessionId`, `client`, `participant`, `initial`, `enabled`, and
1099
+ `suspend` options; scope defaults come from its Provider and bound client.
1100
+ `suspend` defaults to `false`. With `true`, React `use()` reads the supplied
1101
+ `initial` Promise, using the nearest Suspense and Error boundaries. Automatic
1102
+ subscription bootstrap never suspends, including when the Promise resolves
1103
+ to `null` or `undefined`. The default renders the reducer seed while a cold
1104
+ initial Promise is pending. `enabled` controls connection ownership independently.
1105
+
1106
+ `state` starts from that snapshot and folds live events through the
919
1107
  shared reducer. `events` is the raw event feed: observed, explicitly
920
1108
  seeded, or backscrolled. Unless `initialEvents` seeds earlier entries or
921
- `loadHistory` fetches them, it begins after `initialIndex`. `index` is
1109
+ `loadHistory` fetches them, it begins after `initial.index`. `index` is
922
1110
  the latest server-confirmed state index, the `lastSeenIndex` for
923
1111
  [cancellation](/guides/cancellation).
924
1112
 
@@ -933,9 +1121,15 @@ the oldest loaded event. Pass an explicit `before` to read another range.
933
1121
  It never touches `state` or the optimistic overlay. Calls
934
1122
  serialize per session and already-loaded ranges are not refetched.
935
1123
  `history` is the progress: `{ loading, complete, oldestLoaded }`, where
936
- `complete` means the feed reaches index 1 (or the log is empty). The `ws` api has no history lane; there `loadHistory`
1124
+ `complete` means the feed reaches index 1, or the first event after the latest
1125
+ clear (or the log is empty). The `ws` api has no history lane; there `loadHistory`
937
1126
  throws a `TypeError`.
938
1127
 
1128
+ When a clear reaches the session, `state` resets to the reducer's initial
1129
+ state and `events` drops everything at or below the marker. Unconfirmed pushes
1130
+ stay in the overlay and apply to the reset state. A push acked below the marker
1131
+ was cleared with it: it leaves the overlay and its `confirmed` resolves.
1132
+
939
1133
  `presence` is the replicated ephemeral map,
940
1134
  `Record<participantId, { [field]: { value, seen, at } }>`, including
941
1135
  this client. `setPresence(values)` is fire-and-forget: validated
@@ -1015,7 +1209,9 @@ are actually flowing, not "the socket hasn't errored yet". See
1015
1209
  ```ts
1016
1210
  useSession(client: A2Client, sessionId: string, options?: {
1017
1211
  participant?: string // overrides the client's participant
1018
- hydrate?: { state: S; index: number }
1212
+ initial?: InitialSnapshot<S>
1213
+ enabled?: boolean
1214
+ suspend?: boolean
1019
1215
  }): UseSessionResult
1020
1216
  ```
1021
1217
 
@@ -1026,58 +1222,91 @@ handle is identity-mapped: every hook and provider mounting the same
1026
1222
  session shares one runtime. The stream is refcounted: the first mount
1027
1223
  connects, the last unmount closes (StrictMode-safe).
1028
1224
 
1029
- `hydrate` is the hydration input, one atomic option: the
1030
- `{ state, index }` pair `session.state(reducer)` returns, fetched by
1031
- your app through its own route and handed over whenever it lands.
1032
- While it is `undefined`, the hook holds the stream (connecting without
1033
- a fold would replay the whole log). There is no pending flag on the
1034
- result: whether the fold has been handed over is your own input, so
1035
- your data layer's loading state is the pending state.
1036
- When it arrives, the session hydrates under the usual
1037
- never-move-backward rule and the stream connects at that frontier.
1038
- For a deliberate full replay, hand the fold's true starting point:
1039
- `{ state: reducer.initialState, index: 0 }`. State at index 0 is the
1040
- reducer's seed by definition, so the explicit replay needs no special
1041
- vocabulary. See
1042
- [Client-first sessions](/guides/react#client-first-sessions).
1225
+ `initial` accepts the result of `session.state(reducer)` or its Promise. Pass it
1226
+ whole: its `clearIndex` lets a cached client drop history from before a clear
1227
+ it never streamed. The automatic snapshot carries it too. Without it,
1228
+ the subscription obtains a snapshot automatically. To defer automatic I/O,
1229
+ pass `enabled: false`. Disabled hooks keep observing cached state; other
1230
+ owners continue normally. Explicit pushes can still acquire a connection.
1231
+ For deliberate full replay, supply `{ state: reducer.initialState, index: 0 }`.
1232
+ See [Live UI](/guides/react).
1043
1233
 
1044
1234
  ## `experimental-a2/ai`
1045
1235
 
1046
- ### `agent(options)`
1236
+ The isomorphic AI protocol: event schemas, the `AIState` reducer, pure input
1237
+ builders, and `agentContract()` for extending both. It has a peer dependency on
1238
+ AI SDK v7, used as types only here.
1239
+
1240
+ ### `agentContract(options)`
1047
1241
 
1048
1242
  ```ts
1049
- agent(options: {
1243
+ agentContract<M extends UIMessage = UIMessage>(options: {
1050
1244
  name: string
1051
1245
  events?: Record<string, StandardSchemaV1>
1052
- messageSchema?: StandardSchemaV1<UIMessage>
1053
- reducerName?: string
1054
- }): AgentDefinition
1246
+ messageSchema?: StandardSchemaV1<M>
1247
+ reducer?: {
1248
+ name: string // snapshot identity: rename it when fold changes
1249
+ initialState: X // its keys may not shadow AIState's
1250
+ fold: (state: X, event: ContractEvent) => X
1251
+ }
1252
+ }): Contract<Events, {}, AgentReducer<M, Events, X>>
1055
1253
  ```
1056
1254
 
1057
- Defines an isomorphic agent as ordinary A2 parts. Its `contract` contains the
1058
- built-in AI protocol plus `events`, and its `reducer` projects `AIState`. The
1059
- definition exposes only `contract` and `reducer`.
1255
+ A core contract carrying the built-in AI protocol plus `events`, with a reducer
1256
+ that folds `AIState` and, with `reducer`, your application state beside it.
1257
+ The session's state is `AIState<M> & X`. Share it when a client needs
1258
+ application events or state: serve it with `agent({ contract })` and use it
1259
+ with [`useAgent({ api, id, contract })`](#useagentoptions). Otherwise
1260
+ `agent({ name })` builds one on the server and clients use the built-in
1261
+ protocol.
1262
+
1263
+ Built-in event names cannot be replaced by `options.events`. `messageSchema`
1264
+ validates a narrower `UIMessage` type in message payloads; the explicit type
1265
+ argument types messages without one. `reducer.fold` sees `X` and every event,
1266
+ never the transcript; it runs after the AI fold on the server and in every
1267
+ client, so optimistic display carries through. A key of `initialState` that
1268
+ `AIState` also has throws a `TypeError` and fails to typecheck. The reducer name
1269
+ is `AI_STATE_REDUCER` (`a2.ai.state.v26`) without `reducer`, and
1270
+ `${reducer.name}+a2.ai.state.v26` with one, so checkpoints advance when either
1271
+ fold changes. `AgentReducer` types that name, which is what lets
1272
+ [`createAgentClient()`](#createagentclientoptions) accept a plain
1273
+ `agentContract()`, and carries `X` to `agent()`'s resolvers.
1274
+
1275
+ ### `TranscriptMessage<M>`
1060
1276
 
1061
- Built-in event names cannot be replaced by `options.events`.
1277
+ ```ts
1278
+ type TranscriptMessage<M extends UIMessage> = Omit<M, 'parts'> & {
1279
+ parts: Array<M['parts'][number] | CompactionPart>
1280
+ }
1281
+ ```
1062
1282
 
1063
- ### `handlerContext(agent)`
1283
+ The message type of `AIState<M>['messages']` and `deriveUIMessages()`: your
1284
+ message type with the [compaction part](#iscompactionpartpart) added, so
1285
+ `isCompactionPart()` narrows even when `M` declares its own data parts.
1286
+
1287
+ ### `AIMessageOf<Events>`
1288
+
1289
+ The `UIMessage` type a contract's `ai.message.created` schema carries, such as
1290
+ `AIMessageOf<typeof contract.events>`. It falls back to `UIMessage`. `agent()`
1291
+ uses it to type messages for any contract carrying the AI protocol.
1292
+
1293
+ ### `handlerContext(contract?)`
1064
1294
 
1065
1295
  Reads the durable A2 handler context ambient to the current tool
1066
1296
  execution. Tools are plain AI SDK `tool()` definitions; A2 passes their
1067
1297
  options through to the AI SDK without interpreting them and runs each
1068
1298
  local `execute` inside an ambient context scope. Call
1069
- `handlerContext(agent)` (or pass a contract) anywhere below `execute`,
1070
- including awaited helpers and async iteration. The argument types the
1071
- result and is verified against the executing server's contract: a
1072
- mismatched agent, or a call outside any tool execution, throws a
1073
- `TypeError`.
1299
+ `handlerContext()` anywhere below `execute`, including awaited helpers and
1300
+ async iteration. A call outside any tool execution throws a `TypeError`.
1301
+ Without an argument, the context is typed for the built-in protocol. Pass a
1302
+ shared `agentContract()` to type application events; A2 verifies it against the
1303
+ executing agent and throws a `TypeError` on a mismatch.
1074
1304
 
1075
1305
  The returned `AgentToolContext<D>` supplies the current A2 handler's `event`,
1076
1306
  `attempt`, `session`, and `signal`. Local tools execute the explicit
1077
1307
  `ai.tool.execution.requested` work event. Its payload contains the admitted
1078
- `call`, `generation`, `turnId`, and execution `version`. The session has the
1079
- supplied agent's complete event vocabulary. Calls such as
1080
- `ctx.session.schedule()` use the scheduler of the server executing the tool.
1308
+ `call`, `generation`, `turnId`, and execution `version`. Calls such as
1309
+ `ctx.session.schedule()` use the scheduler of the agent executing the tool.
1081
1310
 
1082
1311
  The context is runtime capability, not event data. A2 creates it for the
1083
1312
  current attempt, never persists it, and never sends it to the model or a
@@ -1093,7 +1322,8 @@ functions.
1093
1322
 
1094
1323
  ### `inputs`
1095
1324
 
1096
- Pure typed inputs for `append()` and `push()`:
1325
+ Pure typed inputs for `append()` and `push()`. The `useAgent()` actions build
1326
+ the browser ones:
1097
1327
 
1098
1328
  | Input | Events |
1099
1329
  | --- | --- |
@@ -1122,7 +1352,8 @@ All builders emit `ai.control.requested`. Persistence and application are
1122
1352
  separate: `ai.control.decided` returns `{ commandId, outcome, reason? }` through
1123
1353
  the existing stream. Outcomes are `applied` or `rejected`. Rejection reasons
1124
1354
  include `not-pending`, `role-mismatch`, `stale-turn`, `not-found`,
1125
- `not-user-input`, and `closed`. `state.receipt` holds the latest user receipt.
1355
+ `not-user-input`, and `closed`.
1356
+ `state.receipt` holds the latest user receipt.
1126
1357
 
1127
1358
  Steering validates and prioritizes new input even if the observed turn has
1128
1359
  finished. A matching active turn finishes its current model response and tool
@@ -1224,6 +1455,47 @@ enter later model prompts. Raw progress remains in the log. Available inputs,
1224
1455
  approval-waiting calls, and completed results retain their existing terminal
1225
1456
  handling.
1226
1457
 
1458
+ ### `reasoningLevels`
1459
+
1460
+ ```ts
1461
+ type ReasoningLevel = 'provider-default' | 'none' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh'
1462
+ const reasoningLevels: readonly ReasoningLevel[]
1463
+ ```
1464
+
1465
+ Every `ReasoningLevel`, in order, for pickers. A `model` option returns one in
1466
+ [`ModelSettings`](#modelsettings).
1467
+
1468
+ ### `isCompactionPart(part)`
1469
+
1470
+ ```ts
1471
+ isCompactionPart(part: { type: string }): part is CompactionPart
1472
+
1473
+ type CompactionPart = {
1474
+ type: 'data-a2-compaction'
1475
+ id: string // the compacting generation's ID
1476
+ data: {
1477
+ status: 'running' | 'completed' | 'failed' | 'interrupted'
1478
+ inputTokens?: number
1479
+ thresholdTokens?: number
1480
+ summary?: string
1481
+ usage?: LanguageModelUsage
1482
+ }
1483
+ }
1484
+ ```
1485
+
1486
+ The transcript part that marks a compaction. When `ai.compaction.requested`
1487
+ folds, the reducer appends it to the active response, between the steps
1488
+ before it and the model step it compacted for; a compaction before the first
1489
+ step creates the response with this part. The part counts toward that step's
1490
+ recovery base, so Stop cutoffs, failures, and retries of the step keep it.
1491
+ `ai.compaction.completed` settles it in place with the summary and usage. A
1492
+ running compaction whose generation ends first becomes `interrupted` (Stop,
1493
+ Pause now, or session close) or `failed` (failure). A pending Stop previews it
1494
+ as `interrupted` on the client. A superseded worker attempt's part is removed,
1495
+ and its replacement compacts again if it needs to. AI SDK
1496
+ `convertToModelMessages()` skips data parts by default, and model context never
1497
+ contains it.
1498
+
1227
1499
  ### `events` and `createEvents(options?)`
1228
1500
 
1229
1501
  The built-in Standard Schema definitions. Use `events` for the default AI SDK
@@ -1232,9 +1504,10 @@ message type. Both are isomorphic.
1232
1504
 
1233
1505
  ### Optimistic AI client state
1234
1506
 
1235
- Clients using `agent().reducer` or `createReducer()` expose `state.messages`,
1236
- `state.inbox`, and `state.starting` with pending send, edit, move, remove,
1237
- Send now, steer, pause, and resume commands applied for display. Idle, unpaused
1507
+ Every client folding an AI reducer exposes `state.messages`, `state.inbox`, and
1508
+ `state.starting` with pending send, edit, move, remove, Send now, steer, pause,
1509
+ resume, and Stop commands applied for display. This includes contracts with
1510
+ application state from `agentContract({ reducer })`. Idle, unpaused
1238
1511
  sends appear in the conversation immediately. A generating input occupies the
1239
1512
  optimistic turn, so subsequent sends queue. Busy or paused sessions queue new
1240
1513
  sends. `starting` indicates a speculative turn awaiting acceptance; it does not
@@ -1245,7 +1518,12 @@ inbox. This display survives receipt and hydration, including while paused.
1245
1518
  They remain pending server inputs until admission and do not enter the model
1246
1519
  prompt early. Multiple steering inputs follow inbox admission order.
1247
1520
 
1248
- `state.active` and model execution stay confirmed.
1521
+ New active turn identities come from accepted control decisions. A pending Stop
1522
+ for that turn clears the displayed `state.active`, generation, and pending
1523
+ approvals/inputs, and previews an idle status (or paused when the inbox is paused). With
1524
+ `lastSeenIndex`, its retained generation projection freezes at the observed
1525
+ frontier. The preview lasts through the request echo until the controller's
1526
+ decision; rejected requests restore the confirmed active turn and output.
1249
1527
  Server reads and reducer folds retain confirmed messages and inbox entries;
1250
1528
  `state.starting` is false there. Custom reducers that call `reduceAIState()` or
1251
1529
  nest the AI fold do not inherit the client display behavior. Repeat editing and
@@ -1257,30 +1535,17 @@ The pending-command ledger survives request echo and retires on the matching
1257
1535
  the client's existing optimistic overlay. This display adds no I/O and does not
1258
1536
  read raw history.
1259
1537
 
1260
- ### `createReducer(options)`
1538
+ ### `AIState`
1261
1539
 
1262
- ```ts
1263
- createReducer({ contract, name?, fold?, stateSchema? }): AIStoredReducer
1264
- ```
1540
+ The AI reducer's `initialState`, `fold`, server `session.state()`, and client
1541
+ snapshots all use `AIState`. Keep state and its log index together when
1542
+ hydrating; do not fold already-seen history twice. `messages` holds
1543
+ [`TranscriptMessage<M>`](#transcriptmessagem) values. Model context has its own
1544
+ built-in fold and schema, which carries the contract's application state.
1265
1545
 
1266
- Builds a normalized AI reducer for a compatible contract. Its `initialState`,
1267
- `fold`, and server `session.state(reducer)` use `AIStoredState`. Client and
1268
- React snapshots expose the inferred `AIState` view automatically.
1269
-
1270
- `reducer.view(storedState)` derives confirmed `AIState` for server code.
1271
- `reducer.fromView(state)` converts a confirmed view into stored state, including
1272
- when migrating an application-owned hydration value. Keep the stored state and
1273
- its log index together when hydrating; do not fold already-seen history twice.
1274
- `fold` can customize the confirmed `AIState` transition, and `stateSchema`
1275
- validates that view after restoration. Both apply only to the transcript.
1276
- Model context has its own built-in fold and schema. Changed custom fold semantics
1277
- still require a new `name`.
1278
-
1279
- Stored state is versioned, serializable data. Shared payload records and
1280
- inverse changes replace repeated tool values and complete recovery copies.
1281
- The reducer caches its live view and builds the records when the stored fields
1282
- are read or serialized. Treat state and views as immutable. No event or tool
1283
- payload is truncated. `AIState`
1546
+ Persistence encoding is internal. Tool inputs, outputs, and provider metadata
1547
+ live in message parts; the tool activity list retains only identity and status.
1548
+ No event or tool payload is truncated. `AIState`
1284
1549
  contains `inbox`, `active`, `starting`, the latest command `receipt`, session lifecycle,
1285
1550
  messages, generation status, pending approvals
1286
1551
  and input, tool activity, compaction, usage, the last error, and
@@ -1313,22 +1578,23 @@ Checkpoint size and serialization cost still grow with retained progress.
1313
1578
  Tool-input deltas update `input-streaming` parts with best-effort partial JSON
1314
1579
  for display. The state stays `input-streaming` until a completed input arrives;
1315
1580
  partial parsing does not authorize execution or change interruption handling.
1316
- Extension events are ignored. The default reducer name is `a2.ai.state.v21:records-v2`.
1581
+ Application events are ignored by the AI fold.
1317
1582
 
1318
1583
  ### `deriveUIMessages(history)` and `reduceAIState(state, event)`
1319
1584
 
1320
- `deriveUIMessages()` folds a raw A2 event history directly into AI SDK
1321
- `UIMessage[]`. `reduceAIState()` is the event-at-a-time form used by the
1585
+ `deriveUIMessages()` folds a raw A2 event history directly into
1586
+ `TranscriptMessage<M>[]`, AI SDK messages that include compaction parts. `reduceAIState()` is the event-at-a-time form used by the
1322
1587
  standard reducer. Both synchronously project the same chunk-only protocol.
1323
1588
 
1324
1589
  ## `experimental-a2/ai/server`
1325
1590
 
1326
- ### `createAgentServer(options)`
1591
+ ### `agent(options)`
1327
1592
 
1328
1593
  ```ts
1329
- createAgentServer({
1330
- agent: AgentDefinition,
1331
- model: LanguageModel | (context => LanguageModel),
1594
+ agent({
1595
+ name: string, // or contract: agentContract(...), never both
1596
+ events?, messageSchema?, // with name: server-only application events
1597
+ model: LanguageModel | ModelSettings | (context => LanguageModel | ModelSettings),
1332
1598
  tools?,
1333
1599
  instructions?,
1334
1600
  generation?,
@@ -1337,17 +1603,49 @@ createAgentServer({
1337
1603
  generate?,
1338
1604
  compaction?,
1339
1605
  progress?,
1340
- store?, scheduler?, telemetry?, handlers?,
1341
- }): A2Server
1606
+ toolLane?: string, // run local tool executions one at a time on this lane
1607
+ handlers?, // application event handlers
1608
+ store?, scheduler?, telemetry?,
1609
+ }): Agent<Events, State, Name> // an A2Server
1342
1610
  ```
1343
1611
 
1344
- Creates the standard server for an agent. A2 runs one AI SDK `streamText()`
1345
- model step per durable generation, persists each `UIMessageChunk` once in a
1346
- durable progress batch, projects messages synchronously, extracts tool and
1347
- approval lifecycle events, executes local tools, and records completion,
1348
- usage, interruption, and failure. `model` accepts an AI SDK model string or
1349
- provider model. `model` and `instructions` can be values or per-generation
1350
- async resolvers.
1612
+ Serves an AI agent as one ordinary A2 server: the built-in protocol, its model
1613
+ and tool handlers, and A2's store, scheduler, and telemetry. Pass `name` for the
1614
+ built-in protocol (with optional server-only `events` and `messageSchema`), or a
1615
+ shared `agentContract()` as `contract` when clients fold application state.
1616
+ Any other contract throws a `TypeError` and fails to typecheck; a fully custom
1617
+ contract is core A2, served by `createServer()`. Messages are typed by the
1618
+ contract's `ai.message.created` schema through
1619
+ [`AIMessageOf`](#aimessageofevents).
1620
+ The result is an `A2Server` with `fetch`, `session(id)`, `drain()`, and
1621
+ `contract`. `typeof` it types `useAgent()` and
1622
+ `createAgentClient()` through a type-only import; `Name` is its reducer name,
1623
+ which decides whether the contract-free client accepts it.
1624
+
1625
+ A2 runs one AI SDK `streamText()` model step per durable generation, persists
1626
+ its `UIMessageChunk`s once in durable progress batches, projects messages
1627
+ synchronously, extracts tool and approval lifecycle events, executes local
1628
+ tools, and records completion, usage, interruption, and failure. `model`
1629
+ accepts an AI SDK model string or provider model, or
1630
+ [`ModelSettings`](#modelsettings) to choose its reasoning level and speed with
1631
+ it. `model`, `instructions`, and `generation` can be values or per-generation
1632
+ async resolvers. A resolver that throws, or returns an invalid value, fails the
1633
+ turn once with its message, like a provider error, instead of retrying; the
1634
+ attempt's `ai.generation.started` omits `model` when the model never resolved.
1635
+ `retry()` runs the step again. Store failures (`A2Error`) still retry durably.
1636
+
1637
+ `fetch` accepts reads and pushes of public AI controls:
1638
+ `ai.control.requested` commands other than the server-authored
1639
+ `request-input` and `tool-result`, with
1640
+ `send`, `edit`, and `steer` limited to user messages. With a `contract`, it also
1641
+ accepts the contract's application events, like any A2 server; `events` passed
1642
+ with `name` stay server-only, since no client can push them. Every other pushed
1643
+ event answers `400`. `fetch(request, { authorize })` adds per-operation policy on
1644
+ top, such as who may push which application event. `handlers` handles application events; naming an
1645
+ event the agent handles itself throws a `TypeError` at construction.
1646
+
1647
+ The agent's `telemetry` instruments both Core and AI operations in the same
1648
+ active trace. See the [span catalogue](#the-span-catalogue).
1351
1649
 
1352
1650
  `generation` contains per-step settings such as `temperature`,
1353
1651
  `maxOutputTokens`, `topP`, provider options, and tool approval policy. A2 owns
@@ -1361,18 +1659,30 @@ untouched. A local tool reads its durable context with `handlerContext()` when
1361
1659
  its handler executes. Model-step timeouts remain available; tool-execution
1362
1660
  timeouts are not.
1363
1661
 
1662
+ A `generation` resolver receives `{ event, messages, state, session, signal }`
1663
+ and returns `AgentGenerationSettings` or a promise. It runs once per attempt
1664
+ before compaction and model dispatch; its settings also govern tool approvals
1665
+ and automatic-compaction compatibility. Static settings are validated at
1666
+ construction, resolved settings before use. Failures and cancellation follow
1667
+ model resolution.
1668
+
1364
1669
  `messageMetadata(context)` synchronously maps each default AI SDK stream part
1365
- to the metadata type of the agent's `UIMessage`. The context includes the
1366
- generation state and the current `TextStreamPart`. Metadata returned for
1367
- standard `start` and `finish` parts is recorded with generation progress and
1368
- shallow-merged onto the projected assistant message. Use the `finish` part's
1369
- `totalUsage` to retain final token usage. A custom `generate` function owns its
1370
- metadata chunks and does not use this mapper.
1670
+ to the metadata type of the agent's `UIMessage`. It receives the same context
1671
+ as `generate(context)`, plus the current `TextStreamPart` as `part`; its
1672
+ `generation` is the settings the default generator sends, with A2's Gateway
1673
+ defaults applied. Metadata returned for standard `start` and `finish` parts is
1674
+ recorded with generation progress and shallow-merged onto the projected
1675
+ assistant message. Use the `finish` part's `totalUsage` to retain final token
1676
+ usage. A custom `generate` function owns its metadata chunks and does not use
1677
+ this mapper.
1371
1678
 
1372
1679
  `generate(context)` optionally replaces the default AI SDK generation. It
1373
- receives messages, the resolved model and instructions, tools, generation
1374
- settings, request state, and the abort signal. It returns exactly one model
1375
- step as a `ReadableStream<UIMessageChunk>`. A2 continues to own tool execution,
1680
+ receives the resolver context, `{ event, messages, state, session, signal }`,
1681
+ plus the step's `generationId`, `modelMessages`, and the resolved `model`,
1682
+ `instructions`, `tools`, and `generation` settings. `messages` and
1683
+ `modelMessages` reflect this step's compaction. Its stream writes the assistant
1684
+ message `event.payload.responseMessageId`. It returns exactly one model step as
1685
+ a `ReadableStream<UIMessageChunk>`. A2 continues to own tool execution,
1376
1686
  continuation, and the durable lifecycle around that stream.
1377
1687
 
1378
1688
  Generation requests use one durable lane, so model calls for a session do not
@@ -1384,7 +1694,8 @@ failure keeps later messages queued until the failed response is explicitly
1384
1694
  retried or interrupted.
1385
1695
 
1386
1696
  Independent authorized tool handlers run concurrently without a fixed
1387
- concurrency limit. A private coordinator reducer tracks generation closure,
1697
+ concurrency limit. `toolLane` names a lane for every local tool execution, so
1698
+ they run one at a time in admission order; model steps keep their own lane. A private coordinator reducer tracks generation closure,
1388
1699
  cancellation, calls, approvals, and terminal results for the active response.
1389
1700
  Completed responses do not accumulate in its state. Its
1390
1701
  `ctx.session.state(coordinator, { through: 'latest' })` reads the durable
@@ -1405,7 +1716,7 @@ require their own idempotency. Approved provider-executed calls may satisfy the
1405
1716
  join from the approval response. Provider tools with declared deferred-result
1406
1717
  support wait for their provider result instead of a local executor. An
1407
1718
  authenticated provider callback appends the terminal `ai.tool.result` through
1408
- the trusted server session API; the browser push allowlist rejects it.
1719
+ the trusted server session API; `fetch` rejects it from browsers.
1409
1720
 
1410
1721
  `compaction` defaults to automatic for supported Gateway requests. It accepts
1411
1722
  `false` to disable compaction and discovery, `{ thresholdTokens?, instructions? }`
@@ -1416,16 +1727,15 @@ The resolver runs once per generation and returns a valid policy or `false`.
1416
1727
  It can await application reducers through `context.session.state(reducer)`, whose
1417
1728
  default boundary is the captured prompt frontier. Model, instruction, compaction,
1418
1729
  metadata, and custom-generation contexts expose state access and omit raw history.
1419
- Callbacks receive prompt messages and request identity, without `state` or
1420
- `execution` arguments. Model and instruction resolvers receive
1421
- `{ event, messages, session, signal }`. Compaction and generation callbacks also
1422
- receive `modelMessages`, including committed summaries. Custom transcript folds
1423
- and schemas apply only to the transcript. `agent.reducer` and `createReducer()`
1424
- continue to expose the full
1425
- transcript view. To read it explicitly, load
1426
- `context.session.state(agent.reducer)` and pass its `state` to
1427
- `agent.reducer.view()`. The stored state also remains suitable for client
1428
- hydration.
1730
+ Callbacks receive prompt messages and the triggering `event`, without an
1731
+ `execution` argument. Model, instruction, and generation-setting resolvers receive
1732
+ `{ event, messages, state, session, signal }`, where `state` is the contract's
1733
+ application state (from `agentContract({ reducer })`, `{}` without one) as of the
1734
+ captured snapshot; it rides in the model-context checkpoint, so it costs no
1735
+ read. Compaction, metadata, and custom-generation callbacks receive the same
1736
+ context plus `modelMessages`, including committed summaries. The contract's reducer continues to expose the full
1737
+ transcript. `context.session.state()` returns its `AIState` directly, paired
1738
+ with its log index for hydration.
1429
1739
  Static options are validated at construction; resolved options are validated
1430
1740
  before that generation starts. Each session uses its own resolved value. `thresholdTokens` is an
1431
1741
  optional positive safe integer. `instructions` adds to A2's internal summary
@@ -1464,53 +1774,149 @@ caching. It does not invoke application generation callbacks, transforms, or too
1464
1774
  hooks. Both custom policy callbacks and `generate` receive `modelMessages`, the
1465
1775
  model-ready prompt including summaries, alongside typed UI `messages`.
1466
1776
 
1467
- Built-in summaries remain separate from application-typed messages. Render
1468
- `state.compaction?.status === 'running'` as current activity, and the durable
1469
- `ai.compaction.*` events as historical timeline markers. The conversation in
1470
- `state.messages` from the transcript reducer remains intact. Model execution
1777
+ Built-in summaries remain separate from application-typed messages. The
1778
+ transcript marks each compaction with a
1779
+ [`data-a2-compaction` part](#iscompactionpartpart), and
1780
+ `state.compaction?.status === 'running'` reports current activity. The
1781
+ conversation in `state.messages` remains intact. Model execution
1471
1782
  uses a separate context checkpoint; existing compactions do not require a
1472
1783
  transcript read. Successful compaction is permanent, including across failure,
1473
1784
  interruption, retry, and recovery. Retry reruns the failed step from the latest
1474
1785
  committed context. Compaction defers while tool exchanges or A2-owned tool work
1475
1786
  remain unsettled, checked with one fresh coordinator state read on each candidate.
1476
1787
  Unresolved dead-lettered tool work also defers compaction until repair and
1477
- settlement or session closure. `progress` controls durable batching with
1478
- `maxChunks` and `maxDelayMs`.
1788
+ settlement or session closure. `progress` controls durable batching. By
1789
+ default each batch is written as soon as the previous one finishes, so a slow
1790
+ model's chunks are written one at a time and chunks that arrive during a write
1791
+ share the next batch, up to `maxChunks` (128). `maxDelayMs` (default 0) lets a
1792
+ batch keep collecting chunks for up to that long after the previous write;
1793
+ boundary chunks and `maxChunks` write sooner. Raising it trades token latency
1794
+ for fewer progress events.
1479
1795
 
1480
1796
  This entry point is server-only and resolves to a throwing browser stub.
1481
1797
 
1798
+ ### `ModelSettings`
1799
+
1800
+ ```ts
1801
+ type ModelSettings = {
1802
+ model: LanguageModel
1803
+ reasoning?: ReasoningLevel
1804
+ speed?: 'fast' | 'standard'
1805
+ }
1806
+ ```
1807
+
1808
+ A model with its reasoning level and speed, which a `model` option or resolver
1809
+ returns to choose all three together. `reasoning` becomes the AI SDK
1810
+ `reasoning` call option. `speed: 'fast'` sets `providerOptions.gateway.speed`
1811
+ to prefer AI Gateway's faster tier when the model offers one, and `'standard'`
1812
+ removes a configured speed. Both override the same settings in `generation`.
1813
+ [`reasoningLevels`](#reasoninglevels) lists the levels.
1814
+
1482
1815
  ### `runWithHandlerContext(ctx, fn)`
1483
1816
 
1484
1817
  The testing seam for context-reading tools: runs `fn` with `ctx`
1485
1818
  installed as the ambient handler context, so a tool's `execute` can be
1486
1819
  called directly in tests. Inside `fn`, `handlerContext()` returns `ctx`
1487
- for any agent argument.
1820
+ with or without a contract argument.
1821
+
1822
+ See [Durable AI agents](/guides/ai-agents) for the protocol and complete
1823
+ examples.
1488
1824
 
1489
- ### `createHandlers(options)`
1825
+ ## `experimental-a2/ai/client`
1490
1826
 
1491
- Returns the built-in A2 handler table without constructing a server. Spread
1492
- it into `createServer({ handlers })` beside application handlers when you need
1493
- a custom assembly. The table handles input facts, generation requests, model
1494
- step completion, tool calls, approval responses, and terminal tool results.
1495
- Application handlers spread later can deliberately replace a built-in
1496
- handler. Use `handler` from `experimental-a2/server` to normalize an
1497
- existing entry before wrapping its `handler` and spreading its execution
1498
- policy. Entries in this table's type are optional; narrow an entry before
1499
- passing it to `handler`.
1827
+ ### `createAgentClient(options)`
1500
1828
 
1501
- A custom assembly owns its browser ingress policy. Use
1502
- `server.fetch(request, { authorize })` to reject server-authored AI event names
1503
- before they reach the server. `createAgentServer()` installs the built-in
1504
- browser allowlist automatically.
1829
+ ```ts
1830
+ createAgentClient<typeof agent>(options: {
1831
+ api: ClientApi
1832
+ gcTime?: number
1833
+ participant?: string
1834
+ reconnect?, fetch?, webSocket?, // as createClient
1835
+ }): AgentClient // an A2Client over the built-in AI protocol
1836
+ ```
1505
1837
 
1506
- See [Durable AI agents](/guides/ai-agents) for the protocol and complete
1507
- examples.
1838
+ A contract-free client for an `agent()` route. It accepts only agents whose
1839
+ reducer carries the built-in name (`typeof AI_STATE_REDUCER`): `agent({ name })`
1840
+ or `agent({ contract })` with an `agentContract()` that has no `reducer`. The AI protocol and its
1841
+ `AIState` reducer are library code, and the type argument, taken from a
1842
+ type-only import of the server module, types messages. Use it for pushes
1843
+ outside React, such as a first message sent before navigating, and pass it to
1844
+ `useAgent({ client })` so the hook shares its sessions. It pushes the built-in
1845
+ AI controls only. Agents built from an extended `agentContract()` use
1846
+ `createClient({ contract })` instead, so the client folds the same state.
1847
+
1848
+ ## `experimental-a2/ai/react`
1849
+
1850
+ ### `useAgent(options)`
1851
+
1852
+ ```ts
1853
+ useAgent<typeof agent>(options: {
1854
+ id: string
1855
+ initial?: InitialSnapshot<AIState>
1856
+ participant?: string
1857
+ } & ({ api: string } | { client: AgentClient })): UseAgentResult
1858
+
1859
+ useAgent(options: {
1860
+ id: string
1861
+ initial?: InitialSnapshot<State>
1862
+ participant?: string
1863
+ } & (
1864
+ | { api: string; contract: Contract } // a shared agentContract(), imported for real
1865
+ | { client: A2Client } // createClient({ contract, api })
1866
+ )): UseAgentResult
1867
+ ```
1868
+
1869
+ One agent session in React: its live optimistic `AIState` and actions that
1870
+ fill in protocol identities. With `api`, the hook keeps one shared client per
1871
+ route for the page's lifetime, reading `globalThis.fetch` on each request; with
1872
+ `client`, it uses yours, for a custom `fetch`, headers, or `gcTime`. Both forms
1873
+ accept only agents whose reducer carries the built-in name, like
1874
+ `createAgentClient()`. With `contract`, it folds the contract's application
1875
+ state too, keeping one client per contract and route; `state` is
1876
+ `AIState & X` and `push` accepts the contract's events. Pass that contract's
1877
+ `createClient({ contract, api })` as `client` instead to share it with pushes
1878
+ outside React, such as a first message sent before navigating. A custom
1879
+ contract, one not built by `agentContract()`, uses core A2's `useSession()` and
1880
+ `inputs` directly. Keep one options form per call site;
1881
+ the form must not change between renders.
1882
+
1883
+ The result is the session result (`state`, `index`, `connection`, `push`,
1884
+ `events`, `hydrated`, `reconnect`, `loadHistory`, `history`) plus:
1885
+
1886
+ | Member | Does |
1887
+ | --- | --- |
1888
+ | `messages` | `state.messages` |
1889
+ | `status` | `state.status` |
1890
+ | `indicator` | `'starting'`, `'thinking'`, `'compacting'`, or `null` |
1891
+ | `sendMessage(message, { generate? })` | queues a string (one text part) or a `UIMessage` |
1892
+ | `steer(message)` | steers the active turn after its current step, or sends when idle |
1893
+ | `stop()` | stops the active turn at the rendered frontier |
1894
+ | `approve(approval)`, `deny(approval, { reason? })` | answers a pending approval |
1895
+ | `respond(input, value)` | answers a pending application input |
1896
+ | `retry()` | retries the failed response with a fresh `retryId` |
1897
+ | `pause({ when })`, `resume()` | pauses and resumes |
1898
+ | `queue.edit(message)`, `queue.move({ inputId, beforeId })`, `queue.remove(inputId)`, `queue.sendNow(inputId)` | edits the inbox |
1899
+
1900
+ Every action returns a `PushResult`, like `push()`: it resolves to the
1901
+ persisted command events and rejects when the push fails, and `.confirmed`
1902
+ resolves when the stream delivers them back. Each event's `id` is the
1903
+ `commandId` of its `ai.control.decided` receipt. Actions read identities from
1904
+ the state of the render that created them, so `stop()` and `queue.sendNow()`
1905
+ carry the frontier the user saw. An action with nothing to address, such as
1906
+ `stop()` without an active turn or `retry()` without a failed one, resolves to
1907
+ an empty array. Actions are rebuilt on every render; their identity is not
1908
+ stable.
1909
+
1910
+ `indicator` is `starting` for an optimistic turn awaiting acceptance,
1911
+ `compacting` while a summary runs, `thinking` while the current model step has
1912
+ no visible part (reasoning and step boundaries do not count), and `null`
1913
+ otherwise.
1508
1914
 
1509
1915
  ## `experimental-a2/client`
1510
1916
 
1511
1917
  ```ts
1512
1918
  createClient(options: {
1513
- reducer: Reducer
1919
+ contract: Contract // requires a public reducer
1514
1920
  api: ClientApi
1515
1921
  gcTime?: number // idle session lifetime; 5 minutes by default
1516
1922
  participant?: string // default presence identity for every session
@@ -1550,8 +1956,13 @@ Call `session.reconnect()` to resume immediately with a fresh attempt count.
1550
1956
  Releasing the last lease still closes the stream. A later first lease starts
1551
1957
  a new connection. Push retries are independent of this policy; a push does
1552
1958
  not resume a paused stream. An acknowledged push's `confirmed` promise waits
1553
- until streamed events or a newer hydrated snapshot cover the acknowledged
1554
- state index. Resumption preserves the stream's own position, even when
1959
+ until the stream delivers through the acknowledged index. A bootstrap
1960
+ snapshot, or a stream that starts after a supplied initial index, delivers
1961
+ through that index. Hydration does not otherwise confirm an unobserved
1962
+ acknowledgement. A push never waits for bootstrap. An HTTP push does not
1963
+ wait for a pending initial Promise either; a WebSocket push rides its
1964
+ connection, which opens once that Promise settles.
1965
+ Resumption preserves the stream's own position, even when
1555
1966
  hydration has advanced state while paused.
1556
1967
 
1557
1968
  A `SessionUrl` function runs for each HTTP request or WebSocket connection.
@@ -1564,7 +1975,7 @@ a string because its connection carries more than one session.
1564
1975
  The framework-agnostic session client `experimental-a2/react` is built on: the SSE
1565
1976
  subscription with frontier resume and reconnection, the optimistic push
1566
1977
  queue with ack/rollback, and the local fold. `client.session(id, {
1567
- initialState?, initialIndex?, initialEvents?, participant? })` returns a
1978
+ initial?, initialEvents?, participant? })` returns a
1568
1979
  handle with `getSnapshot()`/`subscribe()` (the `useSyncExternalStore`
1569
1980
  contract), `push()`, `loadHistory()`, `connect()`, `reconnect()`, and `close()`.
1570
1981
  `connect()` takes a lease on the live stream and returns its release:
@@ -1591,6 +2002,16 @@ frontier. Idle handles are evicted after `gcTime`. This memory layer is
1591
2002
  separate from `experimental-a2/cache-indexeddb`: memory preserves identity
1592
2003
  across route transitions, while IndexedDB preserves the replica across reloads.
1593
2004
 
2005
+ `initial` accepts the same `InitialSnapshot<S>` as React. A pending Promise
2006
+ delays cold connection startup, including a connection acquired by `push`.
2007
+ An HTTP push is sent without waiting; a WebSocket push waits for that
2008
+ connection. It does not start another request. Replacing the source or calling `close()`
2009
+ invalidates its pending result and any deferred adoption behind a write.
2010
+ Releasing the last connection lease does not cancel the caller's Promise;
2011
+ it can still seed cached data without reopening transport. Idle eviction
2012
+ detaches A2's listener. Repeating the same Promise within that cached runtime
2013
+ does not restart cancelled work.
2014
+
1594
2015
  ## `experimental-a2/cache-indexeddb`
1595
2016
 
1596
2017
  ```ts
@@ -1617,15 +2038,31 @@ each handler → the events those handlers append.
1617
2038
 
1618
2039
  ### The span catalogue
1619
2040
 
1620
- Five spans, all carrying `a2.contract` and `a2.session_id`:
1621
-
1622
- | Span | Wraps |
1623
- | ----------- | ------------------------------------------------------ |
1624
- | `a2.append` | validation + the store write (one batch) |
1625
- | `a2.drain` | one drain pass over a session's backlog |
1626
- | `a2.event` | one claimed dispatch of one event |
1627
- | `a2.state` | one `state()` read: snapshot-plus-tail load + fold |
1628
- | `a2.snapshot` | one background batch of checkpoint writes |
2041
+ All spans carry `a2.contract` and `a2.session_id`. Core spans describe the
2042
+ durable runtime; `a2.ai.*` spans describe the built-in AI handlers:
2043
+
2044
+ | Layer | Span | Wraps |
2045
+ | ----- | ---- | ----- |
2046
+ | Core | `a2.append` | validation + the store write (one batch) |
2047
+ | Core | `a2.drain` | one drain pass, including concurrent handlers |
2048
+ | Core | `a2.event` | one claimed dispatch, including its handler and completion |
2049
+ | Core | `a2.claim` | one `claimAvailable` store operation |
2050
+ | Core | `a2.commit` | one `completeAttempt` or `failAttempt` store operation |
2051
+ | Core | `a2.state` | one `state()` read: snapshot-plus-tail load + reducer fold |
2052
+ | Core | `a2.snapshot` | one background batch of checkpoint writes |
2053
+ | AI | `a2.ai.control` | applying an input/work report/session-close decision and preparing its returned events |
2054
+ | AI | `a2.ai.generate` | one generation handler, including Core calls and model/SDK work |
2055
+ | AI | `a2.ai.context` | constructing model messages, including tool prompt reconstruction when uncached |
2056
+ | AI | `a2.ai.compaction` | automatic summarization or the custom compaction callback |
2057
+ | AI | `a2.ai.tool` | one durable tool handler, including application execution |
2058
+ | AI | `a2.ai.ack` | waiting for a persisted work report's canonical events and control receipt |
2059
+
2060
+ Durations measure elapsed time, including nested work: `a2.event` contains its AI
2061
+ handler and later commit, AI handlers call Core, and `a2.state` includes reducer code.
2062
+ Acknowledgements overlap control processing; claims can cover several events,
2063
+ and snapshots run in the background. Summing spans or subtracting summed children
2064
+ does not yield exclusive Core/AI overhead. Model and application I/O is included;
2065
+ inspect its child spans for detail.
1629
2066
 
1630
2067
  Attributes arrive in two waves: **at start** (passed to `span()`) and
1631
2068
  **mid-span** (via `handle.setAttribute`; outcomes and counts aren't
@@ -1641,6 +2078,12 @@ alert on are all mid-span.
1641
2078
  | `a2.append.armed` | `a2.append` | mid | `false` when the scheduler arm failed and this append degraded to append-driven healing |
1642
2079
  | `a2.drain.outcome` | `a2.drain` | mid | `settled` \| `busy` \| `stalled` |
1643
2080
  | `a2.drain.processed` | `a2.drain` | mid | events processed this pass |
2081
+ | `a2.claim.outcome` | `a2.claim` | mid | `claimed` \| `busy` \| `settled` |
2082
+ | `a2.claim.count` | `a2.claim` | mid | number of claimed events, or zero |
2083
+ | `a2.commit.operation` | `a2.commit` | start | `complete` \| `fail` |
2084
+ | `a2.commit.index` | `a2.commit` | start | event log position |
2085
+ | `a2.commit.attempt` | `a2.commit` | start | durable 1-based attempt ordinal |
2086
+ | `a2.commit.outcome` | `a2.commit` | mid | `completed` \| `failed` \| `dead_lettered` \| `superseded` |
1644
2087
  | `a2.event.type` | `a2.event` | start | the event's type |
1645
2088
  | `a2.event.index` | `a2.event` | start | log position |
1646
2089
  | `a2.event.id` | `a2.event` | start | event id |
@@ -1650,12 +2093,28 @@ alert on are all mid-span.
1650
2093
  | `a2.event.outcome` | `a2.event` | mid | `processed` \| `failed` \| `dead_lettered` \| `superseded` \| `surrendered` |
1651
2094
  | `a2.event.aborted` | `a2.event` | mid | `true` when `abortOn` fired during the run |
1652
2095
  | `a2.state.reducer` | `a2.state` | start | the reducer's name |
1653
- | `a2.state.snapshot` | `a2.state` | mid | `hit` \| `miss` \| `rejected` (schema guard discarded it) |
2096
+ | `a2.state.snapshot` | `a2.state` | mid | `hit` \| `miss` \| `rejected` (schema guard discarded it) \| `memory` (served from the server's in-memory state) |
1654
2097
  | `a2.state.folded` | `a2.state` | mid | events folded past the snapshot |
1655
2098
  | `a2.state.index` | `a2.state` | mid | the frontier the returned state reflects |
1656
2099
  | `a2.snapshot.reducer` | `a2.snapshot` | start | the reducer's name |
1657
2100
  | `a2.snapshot.index` | `a2.snapshot` | start | greatest checkpoint index in the write batch |
1658
2101
  | `a2.snapshot.count` | `a2.snapshot` | start | number of checkpoints in the write batch |
2102
+ | `a2.ai.control.type` | `a2.ai.control` | start | `ai.control.requested` \| `ai.work.reported` \| `ai.session.closed` |
2103
+ | `a2.ai.control.id` | `a2.ai.control` | start | triggering event id |
2104
+ | `a2.ai.control.attempt` | `a2.ai.control` | start | durable 1-based attempt ordinal |
2105
+ | `a2.ai.generate.request_id` | `a2.ai.generate` | start | generation request event id |
2106
+ | `a2.ai.generate.attempt` | `a2.ai.generate` | start | durable 1-based attempt ordinal |
2107
+ | `a2.ai.context.request_id` | `a2.ai.context` | start | generation request event id |
2108
+ | `a2.ai.context.phase` | `a2.ai.context` | start | `generation` \| `refresh` \| `tool` |
2109
+ | `a2.ai.compaction.request_id` | `a2.ai.compaction` | start | generation request event id |
2110
+ | `a2.ai.compaction.generation_id` | `a2.ai.compaction` | start | generation attempt id |
2111
+ | `a2.ai.compaction.policy` | `a2.ai.compaction` | start | `automatic` \| `custom` |
2112
+ | `a2.ai.tool.request_id` | `a2.ai.tool` | start | originating generation request event id |
2113
+ | `a2.ai.tool.call_id` | `a2.ai.tool` | start | tool call id |
2114
+ | `a2.ai.tool.name` | `a2.ai.tool` | start | tool name |
2115
+ | `a2.ai.tool.attempt` | `a2.ai.tool` | start | durable 1-based attempt ordinal |
2116
+ | `a2.ai.ack.request_id` | `a2.ai.ack` | start | reporting worker event id |
2117
+ | `a2.ai.ack.report_id` | `a2.ai.ack` | start | work report event id, matching the control span's id |
1659
2118
 
1660
2119
  Drain outcomes: `settled` means nothing actionable is left; `busy` means live
1661
2120
  per-event claims remain; `stalled` means a caught failure needs a later retry.
@@ -1685,24 +2144,29 @@ export const scheduler = vercelQueues()
1685
2144
  | Option | Type | Default |
1686
2145
  | --- | --- | --- |
1687
2146
  | `topic` | `string` matching `[A-Za-z0-9_-]+` | `'a2'` |
1688
- | `scheduledAppends` | `'deployment' \| 'deploymentless'` | `'deployment'` |
2147
+ | `deploymentId` | `string \| null` | `VERCEL_DEPLOYMENT_ID` |
1689
2148
  | `transport` | `QueueTransport` | lazy `@vercel/queue` client |
1690
2149
  | `clock` | `{ now(): Date }` | system clock |
1691
2150
 
1692
- The default pins recovery watchdogs and scheduled appends to the deployment
1693
- that publishes them. Recovery always remains pinned. Set `scheduledAppends` to
1694
- `'deploymentless'` only when every eligible deploymentless consumer for the
1695
- topic can decode pending tasks and validate their events. This option removes
1696
- deployment affinity. It does not select the latest production deployment.
2151
+ `deploymentId` is passed to the Queue SDK unchanged and applies to every task,
2152
+ including recovery watchdogs. Omit it to use `VERCEL_DEPLOYMENT_ID`, pass a
2153
+ deployment ID to target that deployment, or pass `null` for deploymentless
2154
+ delivery. Publishing and consuming clients must use the same deployment scope.
2155
+ The same configured client handles sends, payload fetches, acknowledgements,
2156
+ and visibility changes. Callback contents never choose the client.
2157
+
2158
+ Pinned tasks stay with their target deployment after another deployment is
2159
+ promoted. Deploymentless delivery lets Vercel choose an eligible consumer; it
2160
+ does not select the latest production deployment. Every eligible consumer must
2161
+ use `deploymentId: null` and remain compatible with pending tasks and session
2162
+ work.
1697
2163
 
1698
2164
  The adapter retains every task for seven days and accepts delays through six
1699
2165
  days, leaving the final day for delivery and retries. `transport` and `clock`
1700
2166
  are advanced injection seams for tests and custom delivery. An injected
1701
- transport receives the selected deployment affinity as an optional fourth send
1702
- argument while its message remains an exact `SchedulerTask`. It must honor that
1703
- hint for `scheduledAppends: 'deploymentless'` to affect its provider-specific
1704
- routing. The built-in transport adds its callback-routing metadata only after
1705
- this injection seam.
2167
+ transport receives the topic, an exact `SchedulerTask`, and send options.
2168
+ `deploymentId` configures the built-in Queue SDK client only; an injected
2169
+ transport owns its provider configuration.
1706
2170
 
1707
2171
  ## `experimental-a2/scheduler-qstash`
1708
2172
 
@@ -1797,8 +2261,8 @@ scheduler work. A signed malformed envelope returns 489 with
1797
2261
  `Upstash-NonRetryable-Error: true`. The same non-retryable response applies
1798
2262
  when the declared or streamed body exceeds `maxBodyBytes`. Before an initial
1799
2263
  publish, the adapter checks the normalized task with enough reserved room for
1800
- its largest possible private store-outage lineage. Append tasks reserve a
1801
- maximum safe-integer generation. Drain tasks also reserve their actual root
2264
+ its largest possible private store-outage lineage. Append and clear tasks
2265
+ reserve a maximum safe-integer generation. Drain tasks also reserve their actual root
1802
2266
  `dueAt`. Store-outage successors check their actual body. The adapter therefore
1803
2267
  cannot initially accept a task that would exceed the same limit solely when
1804
2268
  lineage is added. Raise the option to match a larger paid-plan payload limit
@@ -1830,15 +2294,16 @@ edge-access check.
1830
2294
  Settled deliveries return 200. Busy deliveries await a durable successor
1831
2295
  watchdog before returning 200. Stalled deliveries publish a fresh successor
1832
2296
  before returning 200, which resets the per-message retry budget. A retryable
1833
- store outage also hands both drain and append tasks to a fresh successor before
1834
- acknowledging. Store-outage successors wait 30 seconds, then multiply the delay
2297
+ store outage also hands drain, append, and clear tasks to a fresh successor
2298
+ before acknowledging. Store-outage successors wait 30 seconds, then multiply the delay
1835
2299
  by four for each signed generation, capped at one day. Their generation keeps
1836
2300
  increasing after the cap so each handoff has a new deduplication id. Drain
1837
- lineage includes the first failing task's `dueAt`, contract, and session. Append
1838
- lineage includes its task id, contract, and session. Duplicate delivery of one
2301
+ lineage includes the first failing task's `dueAt`, contract, and session.
2302
+ Append and clear lineage includes the task id, contract, and session. Duplicate delivery of one
1839
2303
  generation therefore publishes the same successor key without merging a later
1840
2304
  drain episode. Append tasks otherwise return 200 after their ordinary fixed-id
1841
2305
  append succeeds and any recovery watchdog for pending handler work is durable.
2306
+ Clear tasks return 200 after their conditional clear and its deletion finish.
1842
2307
  Unknown contracts, missing scheduler configuration, schema failures, and
1843
2308
  successor publish failures keep the current delivery retryable. Delivery is at
1844
2309
  least once, so handler side effects still need idempotency keys.
@@ -1874,8 +2339,10 @@ the original error object.
1874
2339
  | `experimental-a2/server` | `createServer`: implement a contract | none |
1875
2340
  | `experimental-a2/client` | `createClient`: framework-agnostic session client | none |
1876
2341
  | `experimental-a2/react` | `createReact` | `react` |
1877
- | `experimental-a2/ai` | agent contract, `handlerContext`, reducer, and append builders: isomorphic | `ai` |
1878
- | `experimental-a2/ai/server` | AI SDK runner and built-in handlers | `ai` |
2342
+ | `experimental-a2/ai` | `agentContract`, `handlerContext`, `inputs`, and the AI reducer: isomorphic | `ai` |
2343
+ | `experimental-a2/ai/server` | `agent`: the AI SDK runner as an A2 server | `ai` |
2344
+ | `experimental-a2/ai/client` | `createAgentClient`: contract-free agent client | `ai` |
2345
+ | `experimental-a2/ai/react` | `useAgent` | `ai`, `react` |
1879
2346
  | `experimental-a2/store-postgres` | `postgres`: Postgres store backend | `pg` (or inject a client) |
1880
2347
  | `experimental-a2/store-redis` | `redis`: Redis Streams store backend, push-native streaming | `ioredis` (or inject a client) |
1881
2348
  | `experimental-a2/store-redis-http` | `redisHttp`: the same Redis store over provider REST APIs (Upstash) | none |