experimental-a2 0.20.0 → 0.22.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 (309) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/README.md +1 -1
  3. package/dist/{actor-C7b-1arn.d.ts → actor-CqibLgjQ.d.ts} +15 -7
  4. package/dist/actor-CqibLgjQ.d.ts.map +1 -0
  5. package/dist/actor-client.d.ts +1 -1
  6. package/dist/actor-client.js +2 -2
  7. package/dist/actor-react.d.ts +3 -3
  8. package/dist/actor-react.js +5 -5
  9. package/dist/{actor-shared-t7w8xIKP.js → actor-shared-DFET5q16.js} +3 -3
  10. package/dist/actor-shared-DFET5q16.js.map +1 -0
  11. package/dist/actor.d.ts +1 -1
  12. package/dist/actor.js +54 -43
  13. package/dist/actor.js.map +1 -1
  14. package/dist/{ai-Cz3hqVhk.d.ts → ai-DAu77CDr.d.ts} +8 -8
  15. package/dist/{ai-Cz3hqVhk.d.ts.map → ai-DAu77CDr.d.ts.map} +1 -1
  16. package/dist/{ai-mMiWul1T.js → ai-EEfKyc-z.js} +9 -9
  17. package/dist/ai-EEfKyc-z.js.map +1 -0
  18. package/dist/ai-client.d.ts +2 -2
  19. package/dist/ai-client.js +2 -2
  20. package/dist/ai-fx.d.ts +4 -2
  21. package/dist/ai-fx.d.ts.map +1 -1
  22. package/dist/ai-fx.js +214 -67
  23. package/dist/ai-fx.js.map +1 -1
  24. package/dist/{ai-harness-CV6rXbB3.d.ts → ai-harness-DTjkAr2T.d.ts} +2 -2
  25. package/dist/{ai-harness-CV6rXbB3.d.ts.map → ai-harness-DTjkAr2T.d.ts.map} +1 -1
  26. package/dist/{ai-harness-Clbg5ei7.js → ai-harness-dQeJaR1t.js} +7 -3
  27. package/dist/ai-harness-dQeJaR1t.js.map +1 -0
  28. package/dist/ai-pi.d.ts +1 -1
  29. package/dist/ai-pi.js +3 -3
  30. package/dist/ai-pi.js.map +1 -1
  31. package/dist/ai-react.d.ts +5 -5
  32. package/dist/ai-react.js +2 -2
  33. package/dist/ai-server.d.ts +14 -17
  34. package/dist/ai-server.d.ts.map +1 -1
  35. package/dist/ai-server.js +86 -73
  36. package/dist/ai-server.js.map +1 -1
  37. package/dist/ai.d.ts +2 -2
  38. package/dist/ai.js +2 -2
  39. package/dist/{client-BhHLoNKF.js → client-BQS4i_V9.js} +29 -6
  40. package/dist/client-BQS4i_V9.js.map +1 -0
  41. package/dist/{client-h_DKFAjj.d.ts → client-CUMbS_38.d.ts} +3 -3
  42. package/dist/{client-h_DKFAjj.d.ts.map → client-CUMbS_38.d.ts.map} +1 -1
  43. package/dist/client.d.ts +3 -3
  44. package/dist/client.js +1 -1
  45. package/dist/{contract-BYKASlCV.js → contract-CWukt01d.js} +2 -2
  46. package/dist/contract-CWukt01d.js.map +1 -0
  47. package/dist/index.d.ts +5 -5
  48. package/dist/index.js +3 -3
  49. package/dist/{initial-snapshot-Bx-c0pDP.d.ts → initial-snapshot-D3Lve43o.d.ts} +4 -4
  50. package/dist/initial-snapshot-D3Lve43o.d.ts.map +1 -0
  51. package/dist/{internal-CscSY7K_.js → internal-CvsrprnU.js} +40 -16
  52. package/dist/internal-CvsrprnU.js.map +1 -0
  53. package/dist/otel.d.ts +1 -1
  54. package/dist/otel.js +1 -1
  55. package/dist/otel.js.map +1 -1
  56. package/dist/{react-BBXgHWFc.d.ts → react-BoHvC3v9.d.ts} +4 -4
  57. package/dist/{react-BBXgHWFc.d.ts.map → react-BoHvC3v9.d.ts.map} +1 -1
  58. package/dist/react.d.ts +1 -1
  59. package/dist/react.js +1 -1
  60. package/dist/{reducer-CqA59Q6U.d.ts → reducer-CYPHsSmd.d.ts} +2 -2
  61. package/dist/{reducer-CqA59Q6U.d.ts.map → reducer-CYPHsSmd.d.ts.map} +1 -1
  62. package/dist/{reducer-BAXc8JyF.js → reducer-CjmvZSAb.js} +2 -2
  63. package/dist/{reducer-BAXc8JyF.js.map → reducer-CjmvZSAb.js.map} +1 -1
  64. package/dist/runtime-consumer-CIeL33wj.js +12 -0
  65. package/dist/runtime-consumer-CIeL33wj.js.map +1 -0
  66. package/dist/scheduler-qstash.d.ts +2 -2
  67. package/dist/scheduler-qstash.js +3 -3
  68. package/dist/scheduler-task-X1SToDjP.js.map +1 -1
  69. package/dist/scheduler-vercel.d.ts +2 -2
  70. package/dist/scheduler-vercel.js +3 -3
  71. package/dist/scheduler-vercel.js.map +1 -1
  72. package/dist/{server-qyb5aJJG.js → server-DtFrWxi3.js} +412 -116
  73. package/dist/server-DtFrWxi3.js.map +1 -0
  74. package/dist/{server-DYAATFuT.d.ts → server-qgez0Xny.d.ts} +108 -41
  75. package/dist/server-qgez0Xny.d.ts.map +1 -0
  76. package/dist/server.browser.js +1 -1
  77. package/dist/server.browser.js.map +1 -1
  78. package/dist/server.d.ts +4 -4
  79. package/dist/server.js +2 -2
  80. package/dist/{session-clear-C-QwTXV1.d.ts → session-clear-D1PFe7xl.d.ts} +2 -2
  81. package/dist/session-clear-D1PFe7xl.d.ts.map +1 -0
  82. package/dist/{session-clear-DGI4jTQ5.js → session-clear-wHd5GCpZ.js} +5 -4
  83. package/dist/session-clear-wHd5GCpZ.js.map +1 -0
  84. package/dist/store-D0MhBvg6.d.ts +532 -0
  85. package/dist/store-D0MhBvg6.d.ts.map +1 -0
  86. package/dist/store-N8PXxDAS.js.map +1 -1
  87. package/dist/store-codec-DwrWlgQx.js +65 -0
  88. package/dist/store-codec-DwrWlgQx.js.map +1 -0
  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 +406 -225
  92. package/dist/store-memory.js.map +1 -1
  93. package/dist/{store-polling-7WJV0YWP.js → store-polling-BZt2Rf3D.js} +2 -2
  94. package/dist/{store-polling-7WJV0YWP.js.map → store-polling-BZt2Rf3D.js.map} +1 -1
  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 +1598 -587
  98. package/dist/store-postgres.js.map +1 -1
  99. package/dist/store-presence-polling-JuAy2Ck2.js +111 -0
  100. package/dist/store-presence-polling-JuAy2Ck2.js.map +1 -0
  101. package/dist/store-redis-http.d.ts +1 -1
  102. package/dist/store-redis-http.js +4 -4
  103. package/dist/store-redis-notify-CsJw_hfD.js +3060 -0
  104. package/dist/store-redis-notify-CsJw_hfD.js.map +1 -0
  105. package/dist/store-redis.d.ts +1 -1
  106. package/dist/store-redis.d.ts.map +1 -1
  107. package/dist/store-redis.js +4 -5
  108. package/dist/store-redis.js.map +1 -1
  109. package/dist/store-sqlite.d.ts +1 -1
  110. package/dist/store-sqlite.d.ts.map +1 -1
  111. package/dist/store-sqlite.js +527 -299
  112. package/dist/store-sqlite.js.map +1 -1
  113. package/dist/stream-buffer-DrTyiIuo.js +57 -0
  114. package/dist/stream-buffer-DrTyiIuo.js.map +1 -0
  115. package/dist/{telemetry-Cyfqo6Qt.d.ts → telemetry-CWwsh-Gx.d.ts} +3 -3
  116. package/dist/telemetry-CWwsh-Gx.d.ts.map +1 -0
  117. package/dist/{wire-BKxH1vbP.js → wire-CV-Z7wz-.js} +6 -5
  118. package/dist/wire-CV-Z7wz-.js.map +1 -0
  119. package/docs/01-quickstart.mdx +21 -15
  120. package/docs/02-examples.mdx +1 -1
  121. package/docs/actors/01-introduction.mdx +13 -2
  122. package/docs/actors/02-concurrency.mdx +1 -1
  123. package/docs/agents/01-introduction.mdx +1 -1
  124. package/docs/agents/02-tools.mdx +12 -11
  125. package/docs/agents/03-conversations.mdx +36 -25
  126. package/docs/agents/04-harnesses.mdx +14 -7
  127. package/docs/client/03-presence.mdx +18 -14
  128. package/docs/contract/01-events.mdx +2 -2
  129. package/docs/contract/02-state.mdx +18 -14
  130. package/docs/contract/03-sessions.mdx +32 -25
  131. package/docs/index.mdx +29 -24
  132. package/docs/reference/01-core.mdx +7 -6
  133. package/docs/reference/02-server.mdx +250 -191
  134. package/docs/reference/04-ai.mdx +33 -23
  135. package/docs/reference/05-telemetry.mdx +27 -13
  136. package/docs/reference/06-errors.mdx +25 -19
  137. package/docs/reference/meta.ts +1 -1
  138. package/docs/schedulers/01-overview.mdx +8 -8
  139. package/docs/schedulers/02-vercel-queues.mdx +2 -2
  140. package/docs/schedulers/03-qstash.mdx +4 -4
  141. package/docs/server/01-consumers.mdx +418 -0
  142. package/docs/server/02-timers.mdx +31 -21
  143. package/docs/server/03-cancellation.mdx +52 -44
  144. package/docs/server/04-routes.mdx +4 -1
  145. package/docs/server/05-sql-projections.mdx +391 -0
  146. package/docs/server/06-guarantees.mdx +61 -51
  147. package/docs/stores/01-overview.mdx +3 -3
  148. package/docs/stores/02-redis.mdx +24 -10
  149. package/docs/stores/03-redis-http.mdx +8 -6
  150. package/docs/stores/04-postgres.mdx +65 -14
  151. package/docs/stores/05-sqlite.mdx +5 -3
  152. package/docs/upgrades/01-overview.mdx +31 -0
  153. package/docs/upgrades/78-0-22.mdx +115 -0
  154. package/docs/upgrades/79-0-21.mdx +165 -0
  155. package/docs/upgrades/80-0-20.mdx +43 -0
  156. package/docs/upgrades/81-0-19.mdx +33 -0
  157. package/docs/upgrades/82-0-18.mdx +33 -0
  158. package/docs/upgrades/83-0-17.mdx +69 -0
  159. package/docs/upgrades/meta.ts +3 -0
  160. package/examples/playground/DEPLOY.md +39 -7
  161. package/examples/playground/app/a0/chat.test.tsx +2 -2
  162. package/examples/playground/app/a0/sandbox-log.ts +17 -15
  163. package/examples/playground/app/a0/sandbox.ts +58 -13
  164. package/examples/playground/app/a0/server.ts +4 -4
  165. package/examples/playground/app/a2slack/a2slack.css +1067 -0
  166. package/examples/playground/app/a2slack/c/[channel]/page.tsx +20 -0
  167. package/examples/playground/app/a2slack/consumers/agent.ts +63 -0
  168. package/examples/playground/app/a2slack/consumers/feed.ts +164 -0
  169. package/examples/playground/app/a2slack/consumers/notify.ts +116 -0
  170. package/examples/playground/app/a2slack/consumers/search.ts +37 -0
  171. package/examples/playground/app/a2slack/consumers/triage.ts +31 -0
  172. package/examples/playground/app/a2slack/consumers/welcome.ts +25 -0
  173. package/examples/playground/app/a2slack/db/a2.ts +209 -0
  174. package/examples/playground/app/a2slack/db/index.ts +73 -0
  175. package/examples/playground/app/a2slack/db/migrations/0000_extensions.sql +4 -0
  176. package/examples/playground/app/a2slack/db/migrations/0001_workspace.sql +74 -0
  177. package/examples/playground/app/a2slack/db/migrations/0002_a2.sql +215 -0
  178. package/examples/playground/app/a2slack/db/migrations/0003_a2_batch.sql +67 -0
  179. package/examples/playground/app/a2slack/db/migrations/0004_a2_session_event_ids.sql +2 -0
  180. package/examples/playground/app/a2slack/db/migrations/meta/0000_snapshot.json +18 -0
  181. package/examples/playground/app/a2slack/db/migrations/meta/0001_snapshot.json +500 -0
  182. package/examples/playground/app/a2slack/db/migrations/meta/0002_snapshot.json +1159 -0
  183. package/examples/playground/app/a2slack/db/migrations/meta/0003_snapshot.json +1159 -0
  184. package/examples/playground/app/a2slack/db/migrations/meta/0004_snapshot.json +1165 -0
  185. package/examples/playground/app/a2slack/db/migrations/meta/_journal.json +41 -0
  186. package/examples/playground/app/a2slack/db/queries.ts +263 -0
  187. package/examples/playground/app/a2slack/db/schema.ts +145 -0
  188. package/examples/playground/app/a2slack/drills/a2slack.drill.ts +708 -0
  189. package/examples/playground/app/a2slack/drills/harness.ts +244 -0
  190. package/examples/playground/app/a2slack/events/route.ts +36 -0
  191. package/examples/playground/app/a2slack/identity.ts +44 -0
  192. package/examples/playground/app/a2slack/inbox/route.ts +8 -0
  193. package/examples/playground/app/a2slack/lag/data/route.ts +8 -0
  194. package/examples/playground/app/a2slack/lag/page.tsx +101 -0
  195. package/examples/playground/app/a2slack/lag.ts +73 -0
  196. package/examples/playground/app/a2slack/layout.tsx +43 -0
  197. package/examples/playground/app/a2slack/messages/route.ts +17 -0
  198. package/examples/playground/app/a2slack/model.test.ts +208 -0
  199. package/examples/playground/app/a2slack/model.ts +369 -0
  200. package/examples/playground/app/a2slack/models.test.ts +72 -0
  201. package/examples/playground/app/a2slack/page.tsx +10 -0
  202. package/examples/playground/app/a2slack/scheduler/route.ts +5 -0
  203. package/examples/playground/app/a2slack/search/route.ts +40 -0
  204. package/examples/playground/app/a2slack/search-index.ts +108 -0
  205. package/examples/playground/app/a2slack/server.test.ts +315 -0
  206. package/examples/playground/app/a2slack/server.ts +55 -0
  207. package/examples/playground/app/a2slack/sessions.ts +50 -0
  208. package/examples/playground/app/a2slack/thread/route.ts +8 -0
  209. package/examples/playground/app/a2slack/ui/ChannelView.tsx +87 -0
  210. package/examples/playground/app/a2slack/ui/Composer.tsx +117 -0
  211. package/examples/playground/app/a2slack/ui/DotsShadow.tsx +42 -0
  212. package/examples/playground/app/a2slack/ui/Feed.tsx +157 -0
  213. package/examples/playground/app/a2slack/ui/InboxContext.tsx +63 -0
  214. package/examples/playground/app/a2slack/ui/InboxView.tsx +82 -0
  215. package/examples/playground/app/a2slack/ui/Menu.tsx +128 -0
  216. package/examples/playground/app/a2slack/ui/MessageRow.tsx +314 -0
  217. package/examples/playground/app/a2slack/ui/NameGate.tsx +35 -0
  218. package/examples/playground/app/a2slack/ui/SearchContext.tsx +146 -0
  219. package/examples/playground/app/a2slack/ui/SearchResults.tsx +97 -0
  220. package/examples/playground/app/a2slack/ui/Shell.tsx +34 -0
  221. package/examples/playground/app/a2slack/ui/Sidebar.tsx +199 -0
  222. package/examples/playground/app/a2slack/ui/ThreadPanel.tsx +134 -0
  223. package/examples/playground/app/a2slack/ui/useInbox.ts +71 -0
  224. package/examples/playground/app/agent/server.ts +3 -3
  225. package/examples/playground/app/chat/[chatId]/chat-client.tsx +1 -1
  226. package/examples/playground/app/chat/server.ts +45 -35
  227. package/examples/playground/app/components/demo-icons.tsx +7 -0
  228. package/examples/playground/app/components/demos.tsx +7 -0
  229. package/examples/playground/app/orders/[orderId]/order-client.tsx +2 -2
  230. package/examples/playground/app/orders/page.tsx +1 -1
  231. package/examples/playground/app/orders/server.ts +49 -30
  232. package/examples/playground/app/recovery/[recoveryId]/recovery-client.tsx +14 -15
  233. package/examples/playground/app/recovery/page.tsx +4 -4
  234. package/examples/playground/app/recovery/server.ts +23 -32
  235. package/examples/playground/drizzle.config.ts +14 -0
  236. package/examples/playground/lib/store.ts +1 -1
  237. package/examples/playground/next.config.ts +6 -1
  238. package/examples/playground/package.json +12 -1
  239. package/examples/playground/scripts/migrate.mjs +31 -0
  240. package/examples/playground/vercel.json +9 -0
  241. package/examples/playground/vitest.drills.config.ts +32 -0
  242. package/package.json +5 -2
  243. package/sql/postgres/drizzle-schema.ts +209 -0
  244. package/sql/postgres/schema.sql +227 -67
  245. package/src/actor-shared.ts +10 -2
  246. package/src/actor.ts +101 -71
  247. package/src/ai-agent.ts +162 -111
  248. package/src/ai-control-server.ts +8 -8
  249. package/src/ai-fx.ts +324 -82
  250. package/src/ai-harness.ts +17 -2
  251. package/src/ai-pi.ts +2 -2
  252. package/src/ai-server.ts +2 -2
  253. package/src/ai.ts +7 -7
  254. package/src/client.ts +19 -2
  255. package/src/contract.ts +1 -1
  256. package/src/deterministic-id.ts +27 -4
  257. package/src/errors.ts +8 -5
  258. package/src/idempotent-replay.ts +0 -1
  259. package/src/index.ts +1 -1
  260. package/src/internal.ts +57 -16
  261. package/src/otel.ts +1 -1
  262. package/src/postgres-schema.ts +98 -57
  263. package/src/presence-recovery.ts +18 -11
  264. package/src/presence-tombstones.ts +52 -0
  265. package/src/push-envelope.ts +20 -0
  266. package/src/runtime-consumer.ts +10 -0
  267. package/src/scheduler-task.ts +2 -2
  268. package/src/scheduler-vercel.ts +1 -1
  269. package/src/server.browser.ts +1 -1
  270. package/src/server.ts +819 -218
  271. package/src/session-socket.ts +1 -1
  272. package/src/store-codec.ts +66 -0
  273. package/src/store-memory.ts +604 -286
  274. package/src/store-postgres.ts +2006 -857
  275. package/src/store-presence-polling.ts +59 -34
  276. package/src/store-redis-core.ts +1961 -1333
  277. package/src/store-redis-notify.ts +76 -26
  278. package/src/store-redis.ts +2 -3
  279. package/src/store-sqlite.ts +915 -516
  280. package/src/store.ts +241 -102
  281. package/src/telemetry.ts +2 -1
  282. package/src/wire.ts +1 -0
  283. package/dist/actor-C7b-1arn.d.ts.map +0 -1
  284. package/dist/actor-shared-t7w8xIKP.js.map +0 -1
  285. package/dist/ai-harness-Clbg5ei7.js.map +0 -1
  286. package/dist/ai-mMiWul1T.js.map +0 -1
  287. package/dist/client-BhHLoNKF.js.map +0 -1
  288. package/dist/contract-BYKASlCV.js.map +0 -1
  289. package/dist/idempotent-replay-JdgMQRRm.js +0 -21
  290. package/dist/idempotent-replay-JdgMQRRm.js.map +0 -1
  291. package/dist/initial-snapshot-Bx-c0pDP.d.ts.map +0 -1
  292. package/dist/internal-CscSY7K_.js.map +0 -1
  293. package/dist/server-DYAATFuT.d.ts.map +0 -1
  294. package/dist/server-qyb5aJJG.js.map +0 -1
  295. package/dist/session-clear-C-QwTXV1.d.ts.map +0 -1
  296. package/dist/session-clear-DGI4jTQ5.js.map +0 -1
  297. package/dist/store-CyLUy86v.d.ts +0 -404
  298. package/dist/store-CyLUy86v.d.ts.map +0 -1
  299. package/dist/store-codec-DTG0Ftek.js +0 -10
  300. package/dist/store-codec-DTG0Ftek.js.map +0 -1
  301. package/dist/store-presence-polling-B1ixeqIS.js +0 -94
  302. package/dist/store-presence-polling-B1ixeqIS.js.map +0 -1
  303. package/dist/store-redis-notify-CM0AfLY0.js +0 -2544
  304. package/dist/store-redis-notify-CM0AfLY0.js.map +0 -1
  305. package/dist/stream-buffer-Cx7AAPGq.js +0 -23
  306. package/dist/stream-buffer-Cx7AAPGq.js.map +0 -1
  307. package/dist/telemetry-Cyfqo6Qt.d.ts.map +0 -1
  308. package/dist/wire-BKxH1vbP.js.map +0 -1
  309. package/docs/server/01-handlers.mdx +0 -227
@@ -65,9 +65,10 @@ The `UIMessage` type a contract's input commands (`ai.control.requested`)
65
65
  carry, such as `AIMessageOf<typeof contract.events>`; `UIMessage` otherwise.
66
66
  `agent()` types messages with it.
67
67
 
68
- ### `handlerContext(contract?)`
68
+ ### `consumerContext(contract?)`
69
69
 
70
- Returns the current tool execution's durable handler context, an
70
+ Returns the current tool execution's durable
71
+ [consumer context](/server/consumers#the-context), an
71
72
  `AgentToolContext<D>` with `event`, `attempt`, `session`, and `signal`.
72
73
 
73
74
  - Tools are plain AI SDK `tool()` definitions, passed through uninterpreted;
@@ -123,15 +124,15 @@ Pure typed builders of `ai.control.requested` commands, for `append()` and
123
124
  - **Trust.** Browser ingress accepts user messages, approval and input
124
125
  responses, interruptions, and explicit retries. `inputs.requestInput()`,
125
126
  `inputs.toolResult()`, and non-user `inputs.seed()` messages are trusted
126
- server appends; only built-in handlers append generation requests and AI
127
+ server appends; only A2's own consumers append generation requests and AI
127
128
  lifecycle events. No builder covers session lifecycle: append
128
129
  `ai.session.created` and `ai.session.closed` from trusted server code.
129
130
  - **IDs.** Message, seed, and steer commands use the input message ID as their
130
131
  event ID, and must. Approval, input-response, and retry builders use stable
131
132
  interaction IDs; other commands use the ID `push()` or `append()` assigns,
132
133
  kept across transport retries. Caller IDs are nonempty, never start with the
133
- reserved `a2.ai:`, and are unique across the store; identical events replay
134
- and conflicting reuse rejects before append. A2's own IDs are opaque,
134
+ reserved `a2.ai:`, and are unique within the session; identical events
135
+ replay and conflicting reuse in the session rejects before append. A2's own IDs are opaque,
135
136
  fixed-length, deterministic `a2.ai:` values; an assistant message's ID is its
136
137
  initial model-request event's ID.
137
138
  - **Passive input.** `generate: false` is valid browser input: it admits the
@@ -336,7 +337,7 @@ agent({
336
337
  progress?,
337
338
  toolLane?: string, // run local tool executions one at a time on this lane
338
339
  toolRecovery?: { [tool]?: ToolRecovery }, // 'rerun' (default) | 'report'
339
- handlers?, // application event handlers
340
+ consumers?, // your consumers, next to A2's own
340
341
  store?, scheduler?, telemetry?,
341
342
  }): Agent<Events, State, Name> // an A2Server
342
343
  ```
@@ -350,10 +351,10 @@ stub.
350
351
  | `name` | the built-in protocol, with optional server-only `events` and `messageSchema` |
351
352
  | `contract` | a shared `agentContract()`, when clients fold application state |
352
353
  | `model` | an AI Gateway model ID, or [`ModelSettings`](#modelsettings) with one, under every harness; a value or per-generation async resolver |
353
- | `tools` | AI SDK `tool()` definitions; local tools read context with [`handlerContext()`](#handlercontextcontract) |
354
+ | `tools` | AI SDK `tool()` definitions; local tools read context with [`consumerContext()`](#consumercontextcontract) |
354
355
  | `instructions` | a value or per-generation async resolver |
355
356
  | `maxSteps` | a positive integer capping one complete assistant response, under every harness; no limit by default |
356
- | `harness` | the agent's loop: `{ type: 'ai-sdk', … }` (`AISDKHarness`, the default), [`fx()`](#fx), or [`pi()`](#pisandbox) |
357
+ | `harness` | the agent's loop: `{ type: 'ai-sdk', … }` (`AISDKHarness`, the default), [`fx()`](#fx), or [`pi()`](#pi-sandbox-) |
357
358
  | `harness.generation` | per-step settings, or a resolver |
358
359
  | `harness.messageMetadata` | maps default stream parts to message metadata |
359
360
  | `harness.generate` | replaces the default model step |
@@ -361,7 +362,7 @@ stub.
361
362
  | `progress` | durable batching: `maxChunks` (128), `maxDelayMs` (0) |
362
363
  | `toolLane` | one lane for every local tool execution, one at a time in admission order; model steps keep their own lane |
363
364
  | `toolRecovery` | `'rerun'` (default) or `'report'` per tool, the harness's own tools included |
364
- | `handlers` | application event handlers; naming an event the agent handles throws a `TypeError` |
365
+ | `consumers` | your [consumers](/server/consumers), next to A2's own: `a2.ai.metadata` (concurrent), `a2.ai.control` (one lane for commands, work reports, and closes), `a2.ai.model` (one lane for generations), and `a2.ai.tools` (on `toolLane`, or concurrent). Yours may take any of the contract's events, the agent's own included, and run beside A2's. Names starting with `a2.` are reserved |
365
366
  | `store`, `scheduler`, `telemetry` | as for any A2 server; `telemetry` covers Core and AI in one trace ([span catalogue](/reference/telemetry#the-span-catalogue)) |
366
367
 
367
368
  **Contracts.** A contract not built by `agentContract()` throws a `TypeError`
@@ -384,7 +385,7 @@ overrides model headers. See
384
385
  **`harness`** is the agent's loop. `{ type: 'ai-sdk' }` (`AISDKHarness`), the
385
386
  default, runs one AI SDK `streamText()` step per durable generation, with the
386
387
  `generation`, `messageMetadata`, `generate`, and `compaction` options below.
387
- [`fx()`](#fx) and [`pi()`](#pisandbox) run a coding agent's own loop instead.
388
+ [`fx()`](#fx) and [`pi()`](#pi-sandbox-) run a coding agent's own loop instead.
388
389
  The four options exist only in `{ type: 'ai-sdk' }`; passing one to `agent()`
389
390
  itself throws a `TypeError` naming where it belongs.
390
391
 
@@ -445,11 +446,11 @@ compaction. It returns exactly one model step as a
445
446
  the durable lifecycle. See
446
447
  [Replace generation, not durability](/agents/conversations#replace-generation-not-durability).
447
448
 
448
- **Tools.** Independent authorized tool handlers run concurrently with no
449
- fixed limit. Results and generation completion arrive in either order; a
450
- continuation needs the closed step and every required terminal result.
451
- Single-owner terminal events commit atomically with the parent handler's
452
- completion and carry its durable cause; many-owner joins and preliminary
449
+ **Tools.** Independent authorized tool executions run concurrently with no
450
+ fixed limit, unless `toolLane` orders them. Results and generation completion
451
+ arrive in either order; a continuation needs the closed step and every
452
+ required terminal result. Single-owner terminal events commit atomically with
453
+ the parent run's completion and carry its durable cause; many-owner joins and preliminary
453
454
  outputs append immediately and idempotently. Provider calls and external side
454
455
  effects are at least once: give them their own idempotency. An approved
455
456
  provider-executed call can satisfy the join from its approval response, and a
@@ -489,8 +490,8 @@ See [Delivery semantics](/agents/conversations#delivery-semantics).
489
490
  - Static options are validated at construction, resolved ones before their
490
491
  generation; each session uses its own value.
491
492
  - Discovery: without an explicit threshold, the first generation batches a
492
- metadata request into its start write, and an independent handler records the
493
- model's limits from the public Gateway catalog in `state.modelMetadata`
493
+ metadata request into its start write, and A2's `a2.ai.metadata` consumer
494
+ records the model's limits from the public Gateway catalog in `state.modelMetadata`
494
495
  without delaying dispatch (shared one-hour cache, five-second timeout; no new
495
496
  request per turn, even after exhausted retries). The default threshold is 75%
496
497
  of the context window, lowered for a larger output allowance. While it is
@@ -525,7 +526,7 @@ See [Compact long conversations](/agents/conversations#compact-long-conversation
525
526
  **Harnesses.** Under `fx()` and `pi()`, each model call the harness makes is one
526
527
  durable step of the response, with the same events, progress, steering,
527
528
  interruption, `maxSteps`, and recovery as any step. Tool calls it makes become
528
- ordinary A2 tool calls, executed by A2's tool handlers, and `toolRecovery` may
529
+ ordinary A2 tool calls, executed by A2's `a2.ai.tools` consumer, and `toolRecovery` may
529
530
  name the harness's own tools, such as Pi's `bash`. `model` is resolved per step
530
531
  as for any agent. `instructions` and `tools` reach the harness; how they
531
532
  combine with its own prompt and tools depends on the harness. Tool approval
@@ -538,7 +539,9 @@ their `https:` or `data:` URLs; any other file fails the turn. See
538
539
 
539
540
  A harness resumes after a crash at the interrupted step. Finished model calls
540
541
  are not made again and finished tool calls do not run again. A2 rebuilds the
541
- harness for each step from the log, and stores nothing outside it. Each agent
542
+ harness for each step from the log, unless [`fx()`](#fx) continues the kernel
543
+ its previous step left in the same process, and keeps nothing durable outside
544
+ the log. Each agent
542
545
  keeps, in its process's memory, the harness's state at the end of the last
543
546
  response it ran there, FX's checkpoint or Pi's session, so a warm process reads
544
547
  and replays only the log since; a cold one reads and replays the session from
@@ -569,11 +572,11 @@ resolver. `reasoning` becomes the AI SDK `reasoning` call option
569
572
  model has one; `'standard'` removes a configured speed. Both override the same
570
573
  settings in `generation`. See [Choose the model](/agents/introduction#choose-the-model).
571
574
 
572
- ### `runWithHandlerContext(ctx, fn)`
575
+ ### `runWithConsumerContext(ctx, fn)`
573
576
 
574
577
  The test seam for context-reading tools: runs `fn` with `ctx` as the ambient
575
- handler context, so tests can call `execute` directly. Inside `fn`,
576
- `handlerContext()` returns `ctx`, with or without a contract argument.
578
+ consumer context, so tests can call `execute` directly. Inside `fn`,
579
+ `consumerContext()` returns `ctx`, with or without a contract argument.
577
580
 
578
581
  ## `experimental-a2/ai/fx`
579
582
 
@@ -599,7 +602,14 @@ response so far from the log: recorded model responses and tool results answer
599
602
  the kernel without network calls or tool runs, then FX makes its next model
600
603
  call. Replay grows with the response, roughly 0.1 to 0.5 seconds for one of 80
601
604
  to 200 tool calls. When libfx can't take a checkpoint, A2 replays from an
602
- earlier one.
605
+ earlier one. The process that ran the step before skips the replay: it keeps
606
+ FX's kernel waiting on that step's tool calls, and the next step gives the
607
+ kernel any steering sent meanwhile, answers its tool calls from the log, and
608
+ lets it go on. A step that finds anything else first, such as changed model
609
+ settings or a model call another process committed, closes that kernel and
610
+ replays. A kept kernel holds its process open, so A2 closes it once the session
611
+ has nothing left to run in that process: however a response ends, its process
612
+ can exit.
603
613
 
604
614
  FX's model calls carry the same [AI Gateway](#agentoptions) defaults as the
605
615
  AI SDK harness: `caching: 'auto'`, under any Gateway options FX sets itself,
@@ -11,13 +11,13 @@ import { orders } from '@/contracts'
11
11
  export const ordersServer = createServer({
12
12
  contract: orders,
13
13
  telemetry: otel(),
14
- // ...store, scheduler, and handlers
14
+ // ...store, scheduler, and consumers
15
15
  })
16
16
  ```
17
17
 
18
18
  With any OpenTelemetry setup registered (on Vercel, `@vercel/otel`), one trace
19
19
  shows the request, `a2.append`, the inline `a2.drain`, an `a2.event` span per
20
- handler, and the appends those handlers make. Failures mark their `a2.event`
20
+ consumer run, and the appends those runs make. Failures mark their `a2.event`
21
21
  span, and `a2.event.outcome = dead_lettered` is the attribute to alert on.
22
22
  Agent servers add `a2.ai.*` spans.
23
23
 
@@ -38,27 +38,28 @@ A2 ships.
38
38
  This catalogue is a contract: renames and additions are breaking for
39
39
  dashboards and are called out in the package's `CHANGELOG.md`. All spans carry
40
40
  `a2.contract` and `a2.session_id`. Core spans describe the durable runtime;
41
- `a2.ai.*` spans describe the built-in AI handlers.
41
+ `a2.ai.*` spans describe the AI runtime's consumers.
42
42
 
43
43
  | Layer | Span | Wraps |
44
44
  | ----- | ---- | ----- |
45
45
  | Core | `a2.append` | validation + the store write (one batch) |
46
- | Core | `a2.drain` | one drain pass, including concurrent handlers |
47
- | Core | `a2.event` | one claimed dispatch, including its handler and completion |
46
+ | Core | `a2.drain` | one drain pass, including the consumer runs it starts |
47
+ | Core | `a2.event` | one claimed head, including the consumer's run and its completion |
48
48
  | Core | `a2.claim` | one `claimAvailable` store operation |
49
49
  | Core | `a2.commit` | one `completeAttempt` or `failAttempt` store operation |
50
50
  | Core | `a2.state` | one `state()` read: snapshot-plus-tail load + reducer fold |
51
51
  | Core | `a2.snapshot` | one background batch of checkpoint writes |
52
52
  | Core | `a2.redrive` | one `session.redrive()`: the store operation and starting the session's wakeup |
53
+ | Core | `a2.consume` | one `session.consume()`, including the drains it runs and the waits between them |
53
54
  | AI | `a2.ai.control` | applying an input/work report/session-close decision and preparing its returned events |
54
- | AI | `a2.ai.generate` | one generation handler, including Core calls and model/SDK work |
55
+ | AI | `a2.ai.generate` | one generation run, including Core calls and model/SDK work |
55
56
  | AI | `a2.ai.context` | constructing model messages, including tool prompt reconstruction when uncached |
56
57
  | AI | `a2.ai.compaction` | automatic summarization or the custom compaction callback |
57
- | AI | `a2.ai.tool` | one durable tool handler, including application execution |
58
+ | AI | `a2.ai.tool` | one durable tool run, including application execution |
58
59
  | AI | `a2.ai.ack` | waiting for a persisted work report's canonical events and control receipt |
59
60
 
60
- Durations include nested work: `a2.event` contains its AI handler and later
61
- commit, AI handlers call Core, and `a2.state` includes reducer code.
61
+ Durations include nested work: `a2.event` contains its AI run and later
62
+ commit, AI runs call Core, and `a2.state` includes reducer code.
62
63
  Acknowledgements overlap control processing, claims can cover several events,
63
64
  and snapshots run in the background, so summing spans, or subtracting summed
64
65
  children, does not yield exclusive Core or AI overhead. Model and application
@@ -71,7 +72,7 @@ mid-span.
71
72
 
72
73
  | Attribute | Span | When | Values |
73
74
  | --------------------- | ----------- | ----- | ----------------------------------------------------- |
74
- | `a2.append.source` | `a2.append` | start | `external` \| `handler` |
75
+ | `a2.append.source` | `a2.append` | start | `external` \| `consumer` |
75
76
  | `a2.append.mode` | `a2.append` | start | `inline` \| `dispatch` |
76
77
  | `a2.append.types` | `a2.append` | start | comma-joined event types |
77
78
  | `a2.append.count` | `a2.append` | start | batch size |
@@ -87,9 +88,12 @@ mid-span.
87
88
  | `a2.event.type` | `a2.event` | start | the event's type |
88
89
  | `a2.event.index` | `a2.event` | start | log position |
89
90
  | `a2.event.id` | `a2.event` | start | event id |
91
+ | `a2.event.consumer` | `a2.event` | start | the consumer running the event |
90
92
  | `a2.event.attempt` | `a2.event` | start | same durable 1-based ordinal as `ctx.attempt` |
91
93
  | `a2.event.lane` | `a2.event` | start | stored lane value; absent for concurrent unlaned work |
92
- | `a2.event.handled` | `a2.event` | start | normally `true`; `false` when a custom-adapter row has no handler |
94
+ | `a2.event.handled` | `a2.event` | start | normally `true`; `false` when a claimed head's consumer is not registered |
95
+ | `a2.event.age_ms` | `a2.event` | start | milliseconds since the event was appended, when its run starts: how far behind its consumer is in time |
96
+ | `a2.event.lag` | `a2.event` | start | events appended after this one when it was claimed: how far behind its consumer is in the log. Absent when the store doesn't report its head |
93
97
  | `a2.event.outcome` | `a2.event` | mid | `processed` \| `failed` \| `dead_lettered` \| `superseded` \| `surrendered` |
94
98
  | `a2.event.aborted` | `a2.event` | mid | `true` when `abortOn` fired during the run |
95
99
  | `a2.state.reducer` | `a2.state` | start | the reducer's name |
@@ -99,7 +103,12 @@ mid-span.
99
103
  | `a2.snapshot.reducer` | `a2.snapshot` | start | the reducer's name |
100
104
  | `a2.snapshot.index` | `a2.snapshot` | start | greatest checkpoint index in the write batch |
101
105
  | `a2.snapshot.count` | `a2.snapshot` | start | number of checkpoints in the write batch |
106
+ | `a2.consume.consumer` | `a2.consume` | start | the consumer being consumed |
107
+ | `a2.consume.through` | `a2.consume` | mid | the index it runs through |
108
+ | `a2.consume.index` | `a2.consume` | mid | the index it handled every event through |
109
+ | `a2.consume.drains` | `a2.consume` | mid | drains it ran to get there |
102
110
  | `a2.redrive.index` | `a2.redrive` | start | log position of the event being redriven |
111
+ | `a2.redrive.consumer` | `a2.redrive` | start | the consumer the event is dead for |
103
112
  | `a2.redrive.action` | `a2.redrive` | start | `retry` \| `skip` |
104
113
  | `a2.redrive.redriven` | `a2.redrive` | mid | `true` when the event was dead-lettered and is now resolved |
105
114
  | `a2.redrive.armed` | `a2.redrive` | mid | `false` when the scheduler arm failed and the wakeup degraded to append-driven healing |
@@ -120,13 +129,18 @@ mid-span.
120
129
  | `a2.ai.ack.request_id` | `a2.ai.ack` | start | reporting worker event id |
121
130
  | `a2.ai.ack.report_id` | `a2.ai.ack` | start | work report event id, matching the control span's id |
122
131
 
132
+ - Consumer lag: group `a2.event` spans by `a2.event.consumer`. `a2.event.lag`
133
+ is how many events the session had appended after the one it claimed, and
134
+ `a2.event.age_ms` how old that event was when its run started. A consumer
135
+ that keeps up keeps both near zero; a rising `a2.event.lag` means it is
136
+ falling behind its log.
123
137
  - Drain outcomes: `settled` means nothing actionable is left; `busy` means live
124
138
  per-event claims or a failed event's retry hold remain; `stalled` means a
125
139
  caught failure in this drain needs a later retry. `failed` appears only on
126
140
  scheduler deliveries: the drain threw after arming its successor watchdog,
127
141
  the span records the error, and that successor retries the session.
128
- - A failing handler marks its `a2.event` span with the exception and error
129
- status. Alert on `a2.event.outcome = dead_lettered`. A dead-lettered event
142
+ - A failing consumer run marks its `a2.event` span with the exception and
143
+ error status. Alert on `a2.event.outcome = dead_lettered`. A dead-lettered event
130
144
  blocks only later events in its lane, until `session.redrive()` resolves it;
131
145
  other lanes stay eligible.
132
146
  - `superseded` means a stale completion or failure did not change the log.
@@ -10,7 +10,7 @@ exposes `A2ClientError`, which extends `A2Error`, for server failures and local
10
10
  ones alike, so `instanceof A2Error` matches both.
11
11
 
12
12
  ```ts
13
- // e.g. in a route handler:
13
+ // e.g. in a route:
14
14
  import { A2Error } from 'experimental-a2'
15
15
 
16
16
  export async function POST(req: Request) {
@@ -39,17 +39,19 @@ export async function POST(req: Request) {
39
39
  | `UNKNOWN_EVENT_TYPE` | an event type isn't in the contract's `events` map | no |
40
40
  | `UNKNOWN_PRESENCE_FIELD` | a presence field isn't in the contract's `presence` map | no |
41
41
  | `PRESENCE_NOT_SUPPORTED` | the contract declares `presence` but the store has no presence capability. Thrown at construction; for environment-resolved stores, at first store use | no |
42
- | `PARTIAL_DUPLICATE_BATCH` | a batch mixes already-appended and fresh events, contains the same id twice, or uses an id already used in a different session (event ids are globally unique). Always a caller bug; nothing was written | no |
43
- | `SUPERSEDED_ATTEMPT` | a handler append or clear comes from an attempt that a recovery claim or a session clear replaced; nothing was written. Also the abort reason on that handler's `ctx.signal` | no |
44
- | `SESSION_CLEARED` | a state read reaches below the latest clear, a handler reads state past a clear of its event, or an actor call is cleared before it receives its answer | no |
42
+ | `PARTIAL_DUPLICATE_BATCH` | a batch mixes already-appended and fresh events, or contains the same id twice. Event ids are unique per session, so an id used in another session never conflicts. Always a caller bug; nothing was written | no |
43
+ | `SUPERSEDED_ATTEMPT` | a consumer's append or clear comes from an attempt that a recovery claim or a session clear replaced; nothing was written. Also the abort reason on that attempt's `ctx.signal` | no |
44
+ | `SESSION_CLEARED` | a state read reaches below the latest clear, a consumer reads state past a clear of its event, or an actor call is cleared before it receives its answer | no |
45
45
  | `CLAIM_EXPIRED` | never thrown; only the `ctx.signal` abort reason when the attempt's lease lapsed without renewal (assumed loss, decided locally, not proof) | no |
46
+ | `CONSUMER_DEAD_LETTERED` | `session.consume()` reached an event that dead-lettered for that consumer. Resolve it with `session.redrive()`, then consume again | no |
46
47
  | `STORE_UNAVAILABLE` | the store backend failed; nothing was written. The original error is the `cause` | **yes** |
47
- | `STORE_NOT_CONFIGURED` | production boots with no `store` configured (during `next build` page collection, deferred to first use), a store's optional peer dependency is missing, or a Postgres database lacks A2's tables at the version the release needs (or the notification triggers `listenConnectionString` uses). The message names the fix | no |
48
+ | `STORE_NOT_CONFIGURED` | production boots with no `store` configured (during `next build` page collection, deferred to first use), a store's optional peer dependency is missing, or a Postgres query finds one of A2's tables or columns missing, or that its role can't use (and, with `listenConnectionString`, a subscription finds the notification triggers missing). The message names the fix | no |
48
49
 
49
50
  Retrying an *identical* batch is not `PARTIAL_DUPLICATE_BATCH`: it's an
50
51
  idempotent success that returns the previously appended events. A failing
51
- handler never makes `append` throw; handler failures retry and, if persistent,
52
- [dead-letter the event](/server/guarantees#when-a-handler-keeps-failing). See
52
+ consumer never makes `append` throw; its failures retry and, if persistent,
53
+ [dead-letter the event](/server/guarantees#when-a-consumer-keeps-failing) for
54
+ that consumer. See
53
55
  [What append never throws for](/server/guarantees#what-append-never-throws-for).
54
56
 
55
57
  ## `A2ClientError`
@@ -82,33 +84,37 @@ or `RangeError`.
82
84
 
83
85
  ## NonRetriableError
84
86
 
85
- Throw it (or a subclass, matched by `instanceof`) from a handler when the
87
+ Throw it (or a subclass, matched by `instanceof`) from a consumer when the
86
88
  failure is deterministic: a guard that refuses this attempt refuses every
87
- retry identically. The event dead-letters immediately instead of consuming the
88
- ten-failure retry budget. Any other thrown error keeps the
89
+ retry identically. The event dead-letters for that consumer immediately,
90
+ instead of spending the ten-failure retry budget. Any other thrown error keeps the
89
91
  ordinary retry contract.
90
92
 
91
93
  ```ts
92
94
  import { z } from 'zod'
93
95
  import * as a2 from 'experimental-a2'
94
96
  import { NonRetriableError } from 'experimental-a2'
95
- import { createServer } from 'experimental-a2/server'
97
+ import { consumer, createServer } from 'experimental-a2/server'
96
98
 
97
99
  const orders = a2.contract({
98
100
  name: 'orders',
99
101
  events: { created: z.object({ chargeable: z.boolean() }) },
100
102
  })
101
103
 
104
+ const charge = consumer({
105
+ contract: orders,
106
+ name: 'charge',
107
+ types: ['created'],
108
+ }).run(async ({ event }) => {
109
+ if (!event.payload.chargeable) {
110
+ throw new NonRetriableError('this order cannot be charged')
111
+ }
112
+ // transient failures below retry as usual
113
+ })
114
+
102
115
  export const ordersServer = createServer({
103
116
  contract: orders,
104
- handlers: {
105
- created: async ({ event }) => {
106
- if (!event.payload.chargeable) {
107
- throw new NonRetriableError('this order cannot be charged')
108
- }
109
- // transient failures below retry as usual
110
- },
111
- },
117
+ consumers: [charge],
112
118
  })
113
119
  ```
114
120
 
@@ -1 +1 @@
1
- export default { order: 9 }
1
+ export default { order: 10 }
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: Overview
3
- description: A clock for handler retries and timers.
3
+ description: A clock for consumer retries and timers.
4
4
  ---
5
5
 
6
6
  ```ts server/orders.ts
@@ -15,9 +15,9 @@ export const ordersServer = createServer({
15
15
  contract: orders,
16
16
  store: redis({ url: process.env.REDIS_URL }),
17
17
  scheduler,
18
- handlers: {
18
+ consumers: [
19
19
  /* ... */
20
- },
20
+ ],
21
21
  })
22
22
  ```
23
23
 
@@ -27,7 +27,7 @@ import { billingServer, ordersServer, scheduler } from '@/server'
27
27
  export const POST = scheduler.handler(ordersServer, billingServer)
28
28
  ```
29
29
 
30
- A scheduler wakes sessions on a clock. It retries a handler that failed or
30
+ A scheduler wakes sessions on a clock. It retries a consumer that failed or
31
31
  whose process died, and it delivers [timers](/server/timers), whose delays must
32
32
  fit the [provider's limit](/server/timers#provider-limits). Share one
33
33
  scheduler across your servers and mount its route once, with every server it
@@ -41,11 +41,11 @@ loads, so a bad route fails at startup.
41
41
 
42
42
  ## Without a scheduler
43
43
 
44
- If no event type has a handler and nothing calls `session.schedule`, skip it.
44
+ If the server has no consumers and nothing calls `session.schedule`, skip it.
45
45
  Events settle as they're appended, with no queue messages. That's a complete
46
46
  production setup for history, reducers, and live sync.
47
47
 
48
- With handlers but no scheduler, a session only wakes on a top-level append or
48
+ With consumers but no scheduler, a session only wakes on a top-level append or
49
49
  an explicit `server.drain()`. Reads never wake it, so a session nobody writes
50
50
  to stays stuck. That's fine for internal tools that can heal on the next
51
51
  write. For production, add a scheduler.
@@ -58,8 +58,8 @@ instance its own `topic` and trigger.
58
58
 
59
59
  ## Dispatch in a fresh invocation
60
60
 
61
- `append` starts handlers in the same request and uses the scheduler as a
62
- watchdog. `append.dispatch` stores the event and hands its handlers to the
61
+ `append` starts consumers in the same request and uses the scheduler as a
62
+ watchdog. `append.dispatch` stores the event and hands its consumers to the
63
63
  queue instead:
64
64
 
65
65
  ```ts app/api/imports/route.ts
@@ -15,9 +15,9 @@ export const ordersServer = createServer({
15
15
  contract: orders,
16
16
  store: redis({ url: process.env.REDIS_URL }),
17
17
  scheduler,
18
- handlers: {
18
+ consumers: [
19
19
  /* ... */
20
- },
20
+ ],
21
21
  })
22
22
  ```
23
23
 
@@ -15,9 +15,9 @@ export const ordersServer = createServer({
15
15
  contract: orders,
16
16
  store: redis({ url: process.env.REDIS_URL }),
17
17
  scheduler,
18
- handlers: {
18
+ consumers: [
19
19
  /* ... */
20
- },
20
+ ],
21
21
  })
22
22
  ```
23
23
 
@@ -37,8 +37,8 @@ QStash's local server on the first publish. It needs `@upstash/qstash` 2.11.0
37
37
  or newer.
38
38
 
39
39
  The route checks `Upstash-Signature` before it touches the store and answers
40
- unsigned requests with 401. Delivery is at least once, so handler side effects
41
- need idempotency keys.
40
+ unsigned requests with 401. Delivery is at least once, so consumer side
41
+ effects need idempotency keys.
42
42
 
43
43
  ## Callback URL
44
44