experimental-a2 0.17.0 → 0.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (221) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +1 -1
  3. package/dist/{actor-DBbe5xeE.d.ts → actor-C7b-1arn.d.ts} +5 -5
  4. package/dist/{actor-DBbe5xeE.d.ts.map → actor-C7b-1arn.d.ts.map} +1 -1
  5. package/dist/actor-client.d.ts +1 -1
  6. package/dist/actor-react.d.ts +3 -3
  7. package/dist/actor-react.js +1 -1
  8. package/dist/actor.d.ts +1 -1
  9. package/dist/actor.js +2 -2
  10. package/dist/{ai-vpGtcaCB.d.ts → ai-BMLlI2Ff.d.ts} +3 -3
  11. package/dist/{ai-vpGtcaCB.d.ts.map → ai-BMLlI2Ff.d.ts.map} +1 -1
  12. package/dist/{ai-UrWfKeuv.js → ai-D6mGlO8j.js} +2 -2
  13. package/dist/{ai-UrWfKeuv.js.map → ai-D6mGlO8j.js.map} +1 -1
  14. package/dist/ai-client.d.ts +2 -2
  15. package/dist/ai-client.js +2 -2
  16. package/dist/ai-fx.browser.d.ts +1 -0
  17. package/dist/ai-fx.browser.js +6 -0
  18. package/dist/ai-fx.browser.js.map +1 -0
  19. package/dist/ai-fx.d.ts +10 -0
  20. package/dist/ai-fx.d.ts.map +1 -0
  21. package/dist/ai-fx.js +692 -0
  22. package/dist/ai-fx.js.map +1 -0
  23. package/dist/ai-harness-B2suP2Qh.d.ts +26 -0
  24. package/dist/ai-harness-B2suP2Qh.d.ts.map +1 -0
  25. package/dist/ai-harness-BuLh_EYi.js +273 -0
  26. package/dist/ai-harness-BuLh_EYi.js.map +1 -0
  27. package/dist/ai-pi.browser.d.ts +1 -0
  28. package/dist/ai-pi.browser.js +6 -0
  29. package/dist/ai-pi.browser.js.map +1 -0
  30. package/dist/ai-pi.d.ts +17 -0
  31. package/dist/ai-pi.d.ts.map +1 -0
  32. package/dist/ai-pi.js +950 -0
  33. package/dist/ai-pi.js.map +1 -0
  34. package/dist/ai-react.d.ts +5 -5
  35. package/dist/ai-react.js +2 -2
  36. package/dist/ai-server.d.ts +47 -25
  37. package/dist/ai-server.d.ts.map +1 -1
  38. package/dist/ai-server.js +113 -40
  39. package/dist/ai-server.js.map +1 -1
  40. package/dist/ai-tool-input-DmSqMUQi.js +40 -0
  41. package/dist/ai-tool-input-DmSqMUQi.js.map +1 -0
  42. package/dist/ai.d.ts +1 -1
  43. package/dist/ai.js +1 -1
  44. package/dist/bounded-cache-CPHWF-8h.js +41 -0
  45. package/dist/bounded-cache-CPHWF-8h.js.map +1 -0
  46. package/dist/{client-COaiaDub.js → client-BhHLoNKF.js} +3 -3
  47. package/dist/{client-COaiaDub.js.map → client-BhHLoNKF.js.map} +1 -1
  48. package/dist/{client-qRnRUzBS.d.ts → client-h_DKFAjj.d.ts} +3 -3
  49. package/dist/{client-qRnRUzBS.d.ts.map → client-h_DKFAjj.d.ts.map} +1 -1
  50. package/dist/client.d.ts +3 -3
  51. package/dist/client.js +1 -1
  52. package/dist/index.d.ts +5 -5
  53. package/dist/{initial-snapshot-BrLso5x_.d.ts → initial-snapshot-Bx-c0pDP.d.ts} +2 -2
  54. package/dist/{initial-snapshot-BrLso5x_.d.ts.map → initial-snapshot-Bx-c0pDP.d.ts.map} +1 -1
  55. package/dist/{internal-CbVvEwad.js → internal-CscSY7K_.js} +11 -3
  56. package/dist/internal-CscSY7K_.js.map +1 -0
  57. package/dist/otel.d.ts +1 -1
  58. package/dist/{react-BbJ-g6Uu.d.ts → react-BBXgHWFc.d.ts} +4 -4
  59. package/dist/{react-BbJ-g6Uu.d.ts.map → react-BBXgHWFc.d.ts.map} +1 -1
  60. package/dist/react.d.ts +1 -1
  61. package/dist/react.js +1 -1
  62. package/dist/reducer-BAXc8JyF.js.map +1 -1
  63. package/dist/{reducer-BYu6DT7h.d.ts → reducer-CqA59Q6U.d.ts} +2 -2
  64. package/dist/{reducer-BYu6DT7h.d.ts.map → reducer-CqA59Q6U.d.ts.map} +1 -1
  65. package/dist/scheduler-qstash.d.ts +2 -2
  66. package/dist/scheduler-qstash.js +3 -3
  67. package/dist/scheduler-qstash.js.map +1 -1
  68. package/dist/scheduler-vercel.d.ts +7 -7
  69. package/dist/scheduler-vercel.d.ts.map +1 -1
  70. package/dist/scheduler-vercel.js +9 -10
  71. package/dist/scheduler-vercel.js.map +1 -1
  72. package/dist/{server-Rj64fNvX.js → server-D-Y4pqwu.js} +126 -67
  73. package/dist/server-D-Y4pqwu.js.map +1 -0
  74. package/dist/{server-CGM8WzRG.d.ts → server-DYAATFuT.d.ts} +42 -7
  75. package/dist/server-DYAATFuT.d.ts.map +1 -0
  76. package/dist/server.d.ts +4 -4
  77. package/dist/server.js +1 -1
  78. package/dist/{session-clear-Dshpuhff.d.ts → session-clear-C-QwTXV1.d.ts} +2 -2
  79. package/dist/session-clear-C-QwTXV1.d.ts.map +1 -0
  80. package/dist/{store-Gpkq7WBo.d.ts → store-CyLUy86v.d.ts} +53 -5
  81. package/dist/store-CyLUy86v.d.ts.map +1 -0
  82. package/dist/store-N8PXxDAS.js.map +1 -1
  83. package/dist/store-memory.d.ts +1 -1
  84. package/dist/store-memory.js +56 -11
  85. package/dist/store-memory.js.map +1 -1
  86. package/dist/{store-polling-4_y8lETm.js → store-polling-7WJV0YWP.js} +2 -2
  87. package/dist/{store-polling-4_y8lETm.js.map → store-polling-7WJV0YWP.js.map} +1 -1
  88. package/dist/store-postgres.d.ts +1 -1
  89. package/dist/store-postgres.d.ts.map +1 -1
  90. package/dist/store-postgres.js +106 -25
  91. package/dist/store-postgres.js.map +1 -1
  92. package/dist/{store-presence-polling-Q1C-k2dd.js → store-presence-polling-B1ixeqIS.js} +3 -3
  93. package/dist/{store-presence-polling-Q1C-k2dd.js.map → store-presence-polling-B1ixeqIS.js.map} +1 -1
  94. package/dist/store-redis-http.d.ts +1 -1
  95. package/dist/store-redis-http.js +3 -3
  96. package/dist/{store-redis-notify-tqWNnMRu.js → store-redis-notify-CM0AfLY0.js} +271 -101
  97. package/dist/store-redis-notify-CM0AfLY0.js.map +1 -0
  98. package/dist/store-redis.d.ts +1 -1
  99. package/dist/store-redis.js +1 -1
  100. package/dist/store-sqlite.d.ts +1 -1
  101. package/dist/store-sqlite.js +66 -21
  102. package/dist/store-sqlite.js.map +1 -1
  103. package/dist/{telemetry-cZH-cibj.d.ts → telemetry-Cyfqo6Qt.d.ts} +2 -2
  104. package/dist/telemetry-Cyfqo6Qt.d.ts.map +1 -0
  105. package/dist/{wire-DjfHOvdW.js → wire-BKxH1vbP.js} +2 -2
  106. package/dist/{wire-DjfHOvdW.js.map → wire-BKxH1vbP.js.map} +1 -1
  107. package/docs/01-quickstart.mdx +49 -83
  108. package/docs/{guides/07-examples.mdx → 02-examples.mdx} +10 -20
  109. package/docs/actors/01-introduction.mdx +48 -86
  110. package/docs/actors/02-concurrency.mdx +43 -70
  111. package/docs/actors/03-timers.mdx +28 -42
  112. package/docs/actors/04-routes.mdx +110 -150
  113. package/docs/actors/meta.ts +1 -1
  114. package/docs/agents/01-introduction.mdx +257 -0
  115. package/docs/agents/02-tools.mdx +247 -0
  116. package/docs/agents/03-conversations.mdx +538 -0
  117. package/docs/agents/04-harnesses.mdx +136 -0
  118. package/docs/agents/meta.ts +1 -0
  119. package/docs/client/01-react.mdx +294 -0
  120. package/docs/client/02-transports.mdx +86 -0
  121. package/docs/client/03-presence.mdx +224 -0
  122. package/docs/client/meta.ts +1 -0
  123. package/docs/contract/01-events.mdx +57 -0
  124. package/docs/contract/02-state.mdx +142 -0
  125. package/docs/contract/03-sessions.mdx +175 -0
  126. package/docs/index.mdx +109 -145
  127. package/docs/reference/01-core.mdx +139 -0
  128. package/docs/reference/02-server.mdx +860 -0
  129. package/docs/reference/03-client.mdx +301 -0
  130. package/docs/reference/04-ai.mdx +731 -0
  131. package/docs/reference/05-adapters.mdx +351 -0
  132. package/docs/reference/06-errors.mdx +138 -0
  133. package/docs/reference/meta.ts +1 -1
  134. package/docs/server/01-handlers.mdx +200 -0
  135. package/docs/server/02-timers.mdx +130 -0
  136. package/docs/server/03-cancellation.mdx +142 -0
  137. package/docs/server/04-routes.mdx +243 -0
  138. package/docs/server/05-production.mdx +323 -0
  139. package/docs/server/06-guarantees.mdx +132 -0
  140. package/examples/playground/DEPLOY.md +3 -5
  141. package/examples/playground/app/a0/[appId]/page.tsx +12 -13
  142. package/examples/playground/app/a0/app-list.tsx +9 -2
  143. package/examples/playground/app/a0/app-view.tsx +9 -1
  144. package/examples/playground/app/a0/apps/[appId]/route.ts +10 -5
  145. package/examples/playground/app/a0/apps.test.ts +12 -0
  146. package/examples/playground/app/a0/apps.ts +22 -7
  147. package/examples/playground/app/a0/chat.tsx +47 -1
  148. package/examples/playground/app/a0/devtools.tsx +2 -8
  149. package/examples/playground/app/a0/fx/events/route.ts +9 -0
  150. package/examples/playground/app/a0/fx/scheduler/route.ts +7 -0
  151. package/examples/playground/app/a0/fx/short/events/route.ts +11 -0
  152. package/examples/playground/app/a0/model.test.ts +18 -1
  153. package/examples/playground/app/a0/model.ts +47 -10
  154. package/examples/playground/app/a0/no-photon.cjs +4 -0
  155. package/examples/playground/app/a0/no-quickjs.cjs +3 -0
  156. package/examples/playground/app/a0/pi/events/route.ts +9 -0
  157. package/examples/playground/app/a0/pi/scheduler/route.ts +7 -0
  158. package/examples/playground/app/a0/pi/short/events/route.ts +11 -0
  159. package/examples/playground/app/a0/sandbox-log.ts +10 -4
  160. package/examples/playground/app/a0/sandbox-session.ts +152 -0
  161. package/examples/playground/app/a0/scheduler/route.ts +2 -2
  162. package/examples/playground/app/a0/server.ts +98 -54
  163. package/examples/playground/app/a0/session.ts +17 -5
  164. package/examples/playground/app/a0/shell.tsx +11 -3
  165. package/examples/playground/app/agent/ingress.test.ts +6 -3
  166. package/examples/playground/app/agent/server.ts +9 -6
  167. package/examples/playground/app/agent/telemetry.test.ts +71 -50
  168. package/examples/playground/app/canvas/[canvasId]/canvas-client.tsx +1 -1
  169. package/examples/playground/app/chat/server.test.ts +30 -26
  170. package/examples/playground/app/chat/server.ts +46 -46
  171. package/examples/playground/next.config.ts +17 -5
  172. package/examples/playground/package.json +4 -1
  173. package/examples/playground/vercel.json +18 -0
  174. package/package.json +27 -3
  175. package/src/ai-agent.ts +271 -90
  176. package/src/ai-fx.browser.ts +5 -0
  177. package/src/ai-fx.ts +912 -0
  178. package/src/ai-harness.ts +518 -0
  179. package/src/ai-model-metadata.ts +11 -6
  180. package/src/ai-pi.browser.ts +5 -0
  181. package/src/ai-pi.ts +1322 -0
  182. package/src/ai-sdk-step.ts +2 -2
  183. package/src/ai-server.ts +3 -1
  184. package/src/ai-tool-input.ts +45 -0
  185. package/src/internal.ts +13 -0
  186. package/src/libfx.d.ts +34 -0
  187. package/src/reducer.ts +1 -1
  188. package/src/scheduler-qstash.ts +5 -5
  189. package/src/scheduler-vercel.ts +14 -15
  190. package/src/server.ts +233 -29
  191. package/src/store-memory.ts +92 -16
  192. package/src/store-postgres.ts +156 -25
  193. package/src/store-redis-core.ts +322 -110
  194. package/src/store-redis-notify.ts +2 -0
  195. package/src/store-sqlite.ts +121 -28
  196. package/src/store.ts +55 -3
  197. package/src/telemetry.ts +1 -0
  198. package/dist/internal-CbVvEwad.js.map +0 -1
  199. package/dist/server-CGM8WzRG.d.ts.map +0 -1
  200. package/dist/server-Rj64fNvX.js.map +0 -1
  201. package/dist/session-clear-Dshpuhff.d.ts.map +0 -1
  202. package/dist/store-Gpkq7WBo.d.ts.map +0 -1
  203. package/dist/store-redis-notify-tqWNnMRu.js.map +0 -1
  204. package/dist/telemetry-cZH-cibj.d.ts.map +0 -1
  205. package/docs/concepts/01-contracts.mdx +0 -237
  206. package/docs/concepts/02-handlers.mdx +0 -284
  207. package/docs/concepts/03-durability.mdx +0 -314
  208. package/docs/concepts/04-state.mdx +0 -209
  209. package/docs/guides/01-timers.mdx +0 -193
  210. package/docs/guides/02-cancellation.mdx +0 -161
  211. package/docs/guides/03-react.mdx +0 -324
  212. package/docs/guides/04-local-first.mdx +0 -47
  213. package/docs/guides/05-production.mdx +0 -534
  214. package/docs/guides/06-ai-agents.mdx +0 -1408
  215. package/docs/guides/08-application-data.mdx +0 -113
  216. package/docs/guides/09-presence.mdx +0 -262
  217. package/docs/guides/10-transports.mdx +0 -221
  218. package/docs/reference/01-api.mdx +0 -2358
  219. package/docs/reference/02-errors.mdx +0 -150
  220. /package/docs/{concepts → contract}/meta.ts +0 -0
  221. /package/docs/{guides → server}/meta.ts +0 -0
package/docs/index.mdx CHANGED
@@ -1,27 +1,20 @@
1
1
  ---
2
2
  title: Introduction
3
- description: A2 is durable sync and reactions for things with a lifecycle. Append or schedule events, run handlers, and keep one live view from server to browser.
3
+ description: A2 syncs your immutable event log, from server to browser.
4
4
  ---
5
5
 
6
6
  ```bash
7
7
  pnpm add experimental-a2
8
8
  ```
9
9
 
10
- A2 is durable sync and reactions for things with a lifecycle.
11
-
12
- An order, agent run, approval, or import gets a session. You append events.
13
- Handlers react to them, and usually return the next one. The browser follows
14
- the same log and folds the same state as the server. That's the whole model;
15
- the rest of this page is it happening.
16
-
17
- Events record facts, reducers compute the current view, and handlers perform
18
- the reactions.
10
+ ```bash
11
+ npx skills add vercel-labs/a2
12
+ ```
19
13
 
20
- ## Define a contract
14
+ ## Define a shared contract
21
15
 
22
- Events are the vocabulary. A contract names them once, as a plain
23
- importable value. The server implements it, and the browser types itself
24
- off it later.
16
+ Name the events once. The server implements the contract, and the browser
17
+ types itself off it.
25
18
 
26
19
  ```ts contracts.ts
27
20
  import { z } from 'zod'
@@ -30,144 +23,143 @@ import * as a2 from 'experimental-a2'
30
23
  export const orders = a2.contract({
31
24
  name: 'orders',
32
25
  events: {
33
- created: z.object({ items: z.array(z.string()) }),
34
- 'shop.notified': z.object({}),
35
- cancelled: z.object({}),
26
+ 'order.created': z.object({
27
+ shopId: z.string(),
28
+ items: z.array(z.string()),
29
+ }),
30
+ 'shop.notified': z.object({ shopId: z.string() }),
31
+ 'courier.assigned': z.object({ courierId: z.string() }),
32
+ 'order.delivered': z.object({ signedBy: z.string() }),
33
+ 'order.cancelled': z.object({ reason: z.string() }),
36
34
  },
37
35
  reducer: {
38
36
  name: 'order-status',
39
37
  initialState: { status: 'new' },
40
- fold: (state, event) => ({ status: event.type === 'shop.notified' ? 'notified' : event.type }),
38
+ fold: (state, event) => ({ status: event.type }),
41
39
  },
42
40
  })
43
41
  ```
44
42
 
45
- ## React to events
43
+ ## Implement the server
46
44
 
47
- Handlers are plain async functions. Each one reacts to a fact and usually
48
- returns the next one.
45
+ Bind the contract to a store, a scheduler, and the handlers that react to its
46
+ events.
49
47
 
50
48
  ```ts server/orders.ts
51
49
  import { createServer } from 'experimental-a2/server'
50
+ import { vercelQueues } from 'experimental-a2/scheduler-vercel'
51
+ import { redis } from 'experimental-a2/store-redis'
52
52
  import { orders } from '@/contracts'
53
53
 
54
54
  export const ordersServer = createServer({
55
55
  contract: orders,
56
+ store: redis({ url: process.env.REDIS_URL }),
57
+ scheduler: vercelQueues(),
56
58
  handlers: {
57
- created: async ({ event }) => {
59
+ 'order.created': async ({ event }) => {
58
60
  // your side effect; event.id makes a stable idempotency key:
59
61
  // await notifyShop(event.payload, { idempotencyKey: event.id })
60
- return { type: 'shop.notified', payload: {} }
62
+ const { shopId } = event.payload
63
+ return { type: 'shop.notified', payload: { shopId } }
61
64
  },
62
65
  },
63
66
  })
64
67
  ```
65
68
 
66
- ## Open a session
67
-
68
- A session is one instance of the contract, with its own ordered log. It is the
69
- thing A2 keeps durable and live. The id is yours:
70
-
71
- - `session('order-8194')`: an order moving through fulfillment
72
- - `session('chat-a8c1')`: an agent chat streaming its reply
73
- - `session('cart-julian')`: a shopping cart
74
- - `session('doc-roadmap')`: a document with three people typing in it
75
-
76
- Anything whose story spans more than one request. Put events in the same
77
- session when they form one history or view. Handlers are concurrent unless
78
- they share a lane. Any server code can open
79
- one and append: a route handler, a webhook, a cron job.
69
+ One route serves every session to the browser:
80
70
 
81
71
  ```ts app/api/orders/route.ts
82
72
  import { ordersServer } from '@/server/orders'
83
73
 
84
- export async function POST(req: Request) {
85
- const body = await req.json()
86
- await ordersServer.session(body.orderId).append({
87
- type: 'created',
88
- payload: { items: body.items },
89
- })
90
- return Response.json({ ok: true })
91
- }
74
+ export const GET = ordersServer.fetch
75
+ export const POST = ordersServer.fetch
92
76
  ```
93
77
 
94
- A2 commits the event before its handler can start. The handler may begin before
95
- `append` returns; the return confirms durability, not handler completion.
96
- A queue-backed scheduler wakes pending work after a crash or timeout. The same
97
- scheduler lets a session append a typed event later with
98
- `session.schedule(name, { delay: '30s' }, event)`.
99
-
100
- ## State is a fold
78
+ ## Implement the client
101
79
 
102
- There is no separate state record for the session to keep in sync. Fold its log
103
- through a reducer, a pure function derived from the contract. It imports
104
- nothing server-only, which is about to matter.
80
+ The browser follows a session over SSE or a
81
+ [WebSocket](/client/transports) and folds the same state. `push` applies
82
+ instantly and rolls back if the server says no.
105
83
 
106
- ```ts reducer.ts
84
+ ```tsx app/orders/[orderId]/order.tsx
85
+ 'use client'
86
+ import { createClient } from 'experimental-a2/client'
87
+ import { useSession } from 'experimental-a2/react'
107
88
  import { orders } from '@/contracts'
108
89
 
109
- export const orderStatus = orders.reducer
110
- ```
111
-
112
- ```ts
113
- // anywhere on the server:
114
- import { ordersServer } from '@/server/orders'
115
- import { orders } from '@/contracts'
90
+ const ordersClient = createClient({
91
+ contract: orders,
92
+ // SSE; for a WebSocket: { type: 'ws', url: '/api/orders' }
93
+ api: '/api/orders',
94
+ })
116
95
 
117
- const { state } = await ordersServer.session(orderId).state()
118
- // state → { status: 'notified' }
96
+ export function Order({ orderId }: { orderId: string }) {
97
+ const { state, push } = useSession(ordersClient, orderId)
98
+ return (
99
+ <button
100
+ onClick={() =>
101
+ void push({ type: 'order.cancelled', payload: { reason: 'customer' } })
102
+ }
103
+ >
104
+ {state.status} · cancel
105
+ </button>
106
+ )
107
+ }
119
108
  ```
120
109
 
121
- ## The browser joins the session
110
+ ## It's just an event log
122
111
 
123
- The reducer is isomorphic, so the browser runs the same fold the server
124
- does. A session streams to the client live and takes optimistic writes,
125
- all typed by the one contract:
126
-
127
- ```ts app/orders/[orderId]/session.ts
128
- 'use client'
129
- import { createReact } from 'experimental-a2/react'
130
- import { orders } from '@/contracts'
112
+ ```ts
113
+ // anywhere on the server: a route, a webhook, a cron job
114
+ import { ordersServer } from '@/server/orders'
131
115
 
132
- export const { SessionProvider, useSession } = createReact({
133
- contract: orders,
116
+ const order = ordersServer.session('order-8194')
117
+ await order.append({
118
+ type: 'courier.assigned',
119
+ payload: { courierId: 'courier-7' },
134
120
  })
121
+ const events = await order.history()
122
+ // order.created, shop.notified, courier.assigned
135
123
  ```
136
124
 
137
- ```tsx app/orders/[orderId]/order-client.tsx
138
- 'use client'
139
- import { useSession } from './session'
125
+ A session is the ordered list of what happened. State is a fold over it, on
126
+ both sides of the wire. Everything else A2 keeps is a cache it can rebuild
127
+ from the log.
140
128
 
141
- export function OrderStatus() {
142
- const { state, connection, push } = useSession()
143
- return (
144
- <div>
145
- {state.status}
146
- {connection.status === 'live' ? ' · live' : ''}
147
- <button onClick={() => void push({ type: 'cancelled', payload: {} })}>
148
- Cancel order
149
- </button>
150
- </div>
151
- )
152
- }
153
- ```
129
+ ## With strong guarantees
130
+
131
+ - Appends are effectively-once.
132
+ - The log stays ordered.
133
+ - Handlers are at-least-once.
134
+ - Returned events commit with the handler's completion.
135
+ - Handlers run concurrently, unless they share a lane.
136
+ - Failures stay contained.
137
+ - A failing handler never makes `append` throw.
154
138
 
155
- `push` applies instantly, validates against the contract before anything
156
- leaves the browser, and rolls back if the server says no. Mounting
157
- `SessionProvider` and the small stream route it talks to takes about
158
- twenty lines; [Live UI](/guides/react) wires it end to end.
139
+ [Guarantees](/server/guarantees) has the details and the limits.
159
140
 
160
- ## The properties
141
+ ## Go further...
161
142
 
162
- - **Handlers are plain async functions.** They run concurrently by default.
163
- Use a lane for selective FIFO execution.
164
- - **Handlers are optional per event type.** A history-only event settles during
165
- append and creates no reaction or scheduler work.
166
- - **History is real.** [`history()`](/concepts/state) returns what
167
- actually happened, in order. Debugging is reading, not reconstructing.
168
- - **Nothing inside a session is sacred except its log.** Snapshots, claims, queue
169
- messages: all disposable caches. Delete them and A2 rebuilds
170
- everything from the log.
143
+ <CardGroup cols={2}>
144
+ <Card title="Handlers" href="/server/handlers" icon="zap">
145
+ Retries, lanes, and idempotency.
146
+ </Card>
147
+ <Card title="State" href="/contract/state" icon="layers">
148
+ Reducers and the current view.
149
+ </Card>
150
+ <Card title="History" href="/contract/state#history" icon="history">
151
+ The raw log, oldest first.
152
+ </Card>
153
+ <Card title="AI" href="/agents/introduction" icon="bot">
154
+ Agents with tools and approvals.
155
+ </Card>
156
+ <Card title="Timers" href="/server/timers" icon="clock">
157
+ Schedule an event for later.
158
+ </Card>
159
+ <Card title="Production" href="/server/production" icon="rocket">
160
+ A store and a scheduler.
161
+ </Card>
162
+ </CardGroup>
171
163
 
172
164
  ## FAQ
173
165
 
@@ -179,7 +171,7 @@ a durable event log. Events settle in their append transaction, with no drain,
179
171
  claim, or queue message. A contract can also mix handled and history-only event
180
172
  types. A2 decides per event type.
181
173
 
182
- See [Events without handlers](/concepts/handlers#events-without-handlers).
174
+ See [Events without handlers](/server/handlers#events-without-handlers).
183
175
 
184
176
  </details>
185
177
 
@@ -189,7 +181,7 @@ See [Events without handlers](/concepts/handlers#events-without-handlers).
189
181
  Configure Vercel Queues or QStash, then call
190
182
  `session.schedule(name, { delay: '30s' }, event)` or use `{ at: Date }`. The
191
183
  same scheduler route handles recovery and the delayed append. Names and omitted
192
- event ids stay stable across retries. See [Timers](/guides/timers).
184
+ event ids stay stable across retries. See [Timers](/server/timers).
193
185
 
194
186
  </details>
195
187
 
@@ -198,16 +190,17 @@ event ids stay stable across retries. See [Timers](/guides/timers).
198
190
 
199
191
  Cancellation is an event too: same route, same auth, same log. Handlers
200
192
  opt in with `abortOn` and receive a live `AbortSignal`; reducers cut the
201
- derived view exactly. See [Cancellation](/guides/cancellation).
193
+ derived view exactly. See [Cancellation](/server/cancellation).
202
194
 
203
195
  </details>
204
196
 
205
197
  <details>
206
198
  <summary>What happens when a handler keeps failing?</summary>
207
199
 
208
- It retries with backoff, then dead-letters after ten caught failures. Later
209
- events in the same lane wait; unlaned events and other lanes continue.
210
- [Durability](/concepts/durability) has the exact guarantees and the
200
+ It retries with backoff, then dead-letters after ten failed attempts, whether
201
+ the handler threw or its process ended. Later events in the same lane wait
202
+ until you retry or skip it; unlaned events and other lanes continue.
203
+ [Guarantees](/server/guarantees) has the exact guarantees and the
211
204
  recovery story behind them.
212
205
 
213
206
  </details>
@@ -219,7 +212,7 @@ A durable store backend, such as Postgres or Redis. Add a queue-backed scheduler
219
212
  when the contract has handlers or calls `session.schedule`. A history-only
220
213
  contract with no timers needs no reaction infrastructure. Development needs
221
214
  neither: SQLite appears under `.a2/` and state survives restarts. See
222
- [Going to production](/guides/production).
215
+ [Production](/server/production).
223
216
 
224
217
  </details>
225
218
 
@@ -235,10 +228,10 @@ does not replicate database tables or subscribe to queries across sessions.
235
228
  <details>
236
229
  <summary>Where does my application database fit?</summary>
237
230
 
238
- A2 does not replace it. Use A2 for things with a lifecycle. Keep collections,
231
+ A2 does not replace it. Keep collections,
239
232
  relationships, constraints, search, and reporting in your application
240
233
  database. Split ownership instead of writing the same fact through both paths.
241
- See [A2 and your database](/guides/application-data).
234
+ See [Sessions and your database](/contract/sessions#sessions-and-your-database).
242
235
 
243
236
  </details>
244
237
 
@@ -248,35 +241,6 @@ See [A2 and your database](/guides/application-data).
248
241
  Yes. Every installation includes a standalone Next.js playground at
249
242
  `node_modules/experimental-a2/examples/playground`. It contains complete
250
243
  contracts, reducers, servers, route handlers, clients, and schedulers using only
251
- public package entry points. See [Complete examples](/guides/examples).
244
+ public package entry points. See [Complete examples](/examples).
252
245
 
253
246
  </details>
254
-
255
- ## Where to next
256
-
257
- <CardGroup cols={2}>
258
- <Card title="Quickstart" href="/quickstart" icon="rocket">
259
- A contract, a handler, and a durable append in five minutes.
260
- </Card>
261
- <Card title="Contracts and sessions" href="/concepts/contracts" icon="folder">
262
- The two nouns you'll use everywhere.
263
- </Card>
264
- <Card title="Durability" href="/concepts/durability" icon="wrench">
265
- What A2 guarantees, and exactly how.
266
- </Card>
267
- <Card title="Live UI with React" href="/guides/react" icon="lightbulb">
268
- Stream a session to the browser with optimistic writes.
269
- </Card>
270
- <Card title="Durable AI agents" href="/guides/ai-agents" icon="bot">
271
- Run AI SDK agents with durable messages, progress, tools, and approvals.
272
- </Card>
273
- <Card title="A2 and your database" href="/guides/application-data" icon="database">
274
- Decide what belongs in a session and what belongs in ordinary tables.
275
- </Card>
276
- <Card title="Actors" href="/actors/introduction" icon="boxes">
277
- Durable objects on the log: typed state, one message at a time.
278
- </Card>
279
- <Card title="Complete examples" href="/guides/examples" icon="code">
280
- Read or copy the standalone Next.js playground included with the package.
281
- </Card>
282
- </CardGroup>
@@ -0,0 +1,139 @@
1
+ ---
2
+ title: Core
3
+ description: "Every entry point, and the isomorphic `experimental-a2` core: contracts, reducers, IDs, and the event shape."
4
+ ---
5
+
6
+ ## Entry points
7
+
8
+ | Entry point | Ships | Peer dependency |
9
+ | --- | --- | --- |
10
+ | `experimental-a2` | `contract`, `A2Error`: isomorphic core | none |
11
+ | `experimental-a2/server` | `createServer`: implement a contract | none |
12
+ | `experimental-a2/client` | `createClient`: framework-agnostic session client | none |
13
+ | `experimental-a2/react` | `createReact` | `react` |
14
+ | `experimental-a2/ai` | `agentContract`, `handlerContext`, `inputs`, and the AI reducer: isomorphic | `ai` |
15
+ | `experimental-a2/ai/server` | `agent`: an agent's loop as an A2 server | `ai` |
16
+ | `experimental-a2/ai/fx` | `fx`: the FX harness for `agent()` | `ai`, `libfx` |
17
+ | `experimental-a2/ai/pi` | `pi`: the Pi harness for `agent()` | `ai`, `@earendil-works/pi-coding-agent`, `@earendil-works/pi-ai` |
18
+ | `experimental-a2/ai/client` | `createAgentClient`: contract-free agent client | `ai` |
19
+ | `experimental-a2/ai/react` | `useAgent` | `ai`, `react` |
20
+ | [`experimental-a2/store-postgres`](/reference/adapters#experimental-a2store-postgres) | `postgres`: Postgres store backend | `pg` (or inject a client) |
21
+ | [`experimental-a2/store-redis`](/reference/adapters#experimental-a2store-redis) | `redis`: Redis Streams store backend, push-native streaming | `ioredis` (or inject a client) |
22
+ | [`experimental-a2/store-redis-http`](/reference/adapters#experimental-a2store-redis-http) | `redisHttp`: the same Redis store over provider REST APIs (Upstash) | none |
23
+ | `experimental-a2/store-sqlite` | SQLite store backend | none |
24
+ | `experimental-a2/store-memory` | in-memory store backend | none |
25
+ | `experimental-a2/scheduler-vercel` | `vercelQueues`: recovery and timers | `@vercel/queue` |
26
+ | `experimental-a2/scheduler-qstash` | `qstash`: signed recovery and timers | `@upstash/qstash` |
27
+ | `experimental-a2/cache-indexeddb` | `indexedDb` browser cache | none |
28
+ | `experimental-a2/otel` | `otel` telemetry adapter | `@opentelemetry/api` |
29
+
30
+ `experimental-a2` imports none of the backends, and a browser-bundle test in CI
31
+ enforces it. Importing a backend's entry point, such as
32
+ `experimental-a2/store-postgres`, is what pulls that backend in; core never puts
33
+ one in your client bundle.
34
+
35
+ ## `experimental-a2`
36
+
37
+ ### `a2.contract(options)`
38
+
39
+ ```ts
40
+ a2.contract(options: {
41
+ name: string
42
+ events: Record<string, StandardSchemaV1>
43
+ presence?: Record<string, StandardSchemaV1>
44
+ reducer?: { name: string; initialState: S; stateSchema?: StandardSchemaV1; fold: (state: S, event: Event) => S }
45
+ }): Contract
46
+ ```
47
+
48
+ Defines one kind of lifecycle. The result is a plain, importable,
49
+ **isomorphic** value: the server implements it, reducers derive from it, and
50
+ the browser types its pushes off it. See [Events](/contract/events).
51
+
52
+ | Option | Meaning |
53
+ | --- | --- |
54
+ | `name` | Unique per app; identifies the contract in storage and queue messages. May not contain the control character U+001F. |
55
+ | `events` | Event type to payload schema. Types may not start with `a2.`, which A2 reserves for its own events, such as `a2.cleared`. |
56
+ | `presence` | Optional. Field to schema for one participant's ephemeral state. Replicated last-writer-wins, never stored in the log, invisible to reducers. See [Presence](/client/presence). |
57
+ | `reducer` | Optional. The one reducer the contract publishes to clients; an inline definition includes `fold`. See [`reducer(options)`](#reduceroptions). |
58
+
59
+ Schemas are [Standard Schema](https://standardschema.dev): Zod, Valibot,
60
+ ArkType, anything that implements it. Validators must be synchronous; async
61
+ ones are rejected at definition time. The validated output is what's stored.
62
+
63
+ ### `contract.batch(...events)`
64
+
65
+ Returns the same events as a readonly tuple, preserving each literal type in a
66
+ heterogeneous handler-returned array. A2 validates them when the handler
67
+ completes. See [Handlers](/server/handlers#return-what-happens-after-success).
68
+
69
+ ### `reducer(options)`
70
+
71
+ ```ts
72
+ reducer(options: {
73
+ contract: Contract
74
+ name: string // identity; keys cached snapshots
75
+ initialState: S // the seed (schema input, when stateSchema is set)
76
+ stateSchema?: StandardSchemaV1 // declares S, checks initialState, guards cached folds
77
+ }): { fold(fn: (state: S, event: Event) => S): Reducer<S> }
78
+ ```
79
+
80
+ Builds a standalone reducer for server reads or for the contract's `reducer`
81
+ field. See [State](/contract/state#reducers).
82
+
83
+ - The first call fixes the state and event types, so `.fold()` receives
84
+ concrete ones. Literal unions in your state survive with zero annotations.
85
+ - `name` keys cached snapshots, so two reducers never fight over a cache
86
+ entry. Rename it when the fold's logic changes: a rename (or a `stateSchema`
87
+ mismatch, when present) invalidates, and the next read refolds from raw
88
+ events.
89
+
90
+ ### `A2Error`
91
+
92
+ The shared base for A2 failures, discriminated by `code`. The session client
93
+ exposes its `A2ClientError` subclass. See [Errors](/reference/errors).
94
+
95
+ ### `idempotentId(...parts)`
96
+
97
+ `idempotentId(...parts: string[]): Promise<string>` derives a stable UUIDv8
98
+ from an ordered tuple of strings. It performs no I/O and runs on the server and
99
+ in the browser.
100
+
101
+ ```ts shared/request-id.ts
102
+ import { idempotentId } from 'experimental-a2'
103
+
104
+ export const id = await idempotentId('orders', 'shop-7', 'request-42')
105
+ ```
106
+
107
+ - Pass the result as an event's `id` when retries should converge on the same
108
+ event.
109
+ - The same tuple produces the same ID across runtimes and A2 versions.
110
+ - String boundaries, order, and case matter; strings are not normalized. Empty
111
+ strings and an empty tuple are valid.
112
+ - Every argument has the same role; a prefix such as `'orders'` is a namespace
113
+ by convention. Include enough context to distinguish operations across
114
+ contracts and sessions: event IDs are globally unique within a store.
115
+ - A2 preserves explicit IDs and does not change its automatic ID generation.
116
+ Changing an existing recipe changes retry identity, so retries of already
117
+ committed requests must keep their original IDs.
118
+
119
+ ### `SessionCleared`
120
+
121
+ An `Event` whose `type` is `'a2.cleared'` and whose `payload` is `null`: the
122
+ marker a [clear](/reference/server#sessionclearoptions) leaves in
123
+ `session.stream()`. It never reaches handlers or `history()`. Comparing
124
+ `event.type` with that string narrows a stream item to `SessionCleared`. The
125
+ session client handles the marker itself, so it matters only when you read the
126
+ server stream yourself.
127
+
128
+ ## Event
129
+
130
+ ```ts
131
+ type Event = {
132
+ id: string
133
+ type: string
134
+ payload: unknown // typed by the contract's schema for `type`
135
+ index: number // position in the session's log, from 1
136
+ sessionId: string
137
+ createdAt: Date
138
+ }
139
+ ```