stitchkit 0.118.1 → 0.120.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 (180) hide show
  1. package/CHANGELOG.md +92 -0
  2. package/README.md +10 -7
  3. package/dist/browser/http.d.ts.map +1 -1
  4. package/dist/contract/client-types.d.ts +6 -3
  5. package/dist/contract/client-types.d.ts.map +1 -1
  6. package/dist/contract/define-validation.d.ts.map +1 -1
  7. package/dist/contract/define.d.ts +18 -12
  8. package/dist/contract/define.d.ts.map +1 -1
  9. package/dist/contract/endpoint-capabilities.d.ts +28 -0
  10. package/dist/contract/endpoint-capabilities.d.ts.map +1 -0
  11. package/dist/contract/factory.d.ts +49 -19
  12. package/dist/contract/factory.d.ts.map +1 -1
  13. package/dist/contract/tool-view.d.ts +1 -1
  14. package/dist/contract/tool-view.d.ts.map +1 -1
  15. package/dist/contract/transports.d.ts +5 -0
  16. package/dist/contract/transports.d.ts.map +1 -0
  17. package/dist/entrypoints/agent-runtime/coding-tools.js +9 -8
  18. package/dist/entrypoints/agent-runtime/harness.js +9 -8
  19. package/dist/entrypoints/agent-runtime/realtime.js +3 -3
  20. package/dist/entrypoints/agent-runtime/sandbox.js +10 -9
  21. package/dist/entrypoints/agent-runtime/sqlite/bun.js +12 -11
  22. package/dist/entrypoints/agent-runtime/sqlite/node.js +12 -11
  23. package/dist/entrypoints/agent-runtime/testing.js +12 -11
  24. package/dist/entrypoints/agent-runtime.js +22 -19
  25. package/dist/entrypoints/application/diagnostic-journal.js +3 -3
  26. package/dist/entrypoints/application/directory-inbox.js +4 -4
  27. package/dist/entrypoints/application/opentelemetry.js +1 -1
  28. package/dist/entrypoints/application.js +10 -10
  29. package/dist/entrypoints/cli/publish.js +4 -4
  30. package/dist/entrypoints/cli.js +13 -12
  31. package/dist/entrypoints/contract.d.ts +1 -1
  32. package/dist/entrypoints/contract.d.ts.map +1 -1
  33. package/dist/entrypoints/contract.js +6 -4
  34. package/dist/entrypoints/files.js +3 -3
  35. package/dist/entrypoints/geo/contract.d.ts +7 -0
  36. package/dist/entrypoints/geo/contract.d.ts.map +1 -0
  37. package/dist/entrypoints/geo/contract.js +6 -0
  38. package/dist/entrypoints/geo.js +60 -23
  39. package/dist/entrypoints/http.js +3 -3
  40. package/dist/entrypoints/index.js +9 -7
  41. package/dist/entrypoints/live.js +1 -1
  42. package/dist/entrypoints/node.js +7 -6
  43. package/dist/entrypoints/primitives.js +1 -1
  44. package/dist/entrypoints/process/owner-loss.js +1 -1
  45. package/dist/entrypoints/process.js +3 -3
  46. package/dist/entrypoints/react.js +1 -1
  47. package/dist/entrypoints/remote.js +4 -3
  48. package/dist/entrypoints/server.js +16 -15
  49. package/dist/entrypoints/telegram/screens.js +5 -5
  50. package/dist/entrypoints/telegram.js +7 -7
  51. package/dist/entrypoints/testing.js +7 -6
  52. package/dist/entrypoints/tools/agent.d.ts +9 -0
  53. package/dist/entrypoints/tools/agent.d.ts.map +1 -0
  54. package/dist/entrypoints/tools/agent.js +16 -0
  55. package/dist/entrypoints/tools/connections.js +7 -6
  56. package/dist/entrypoints/tools/contract.js +1 -1
  57. package/dist/entrypoints/tools/invoker.js +5 -4
  58. package/dist/entrypoints/tools/mcp.js +110 -9
  59. package/dist/entrypoints/tools/toolkit.d.ts +9 -0
  60. package/dist/entrypoints/tools/toolkit.d.ts.map +1 -0
  61. package/dist/entrypoints/tools/toolkit.js +40 -0
  62. package/dist/entrypoints/tools.d.ts +0 -10
  63. package/dist/entrypoints/tools.d.ts.map +1 -1
  64. package/dist/entrypoints/tools.js +38 -80
  65. package/dist/entrypoints/tracking/contract.js +5 -3
  66. package/dist/entrypoints/tracking/performance.js +3 -3
  67. package/dist/entrypoints/tracking.d.ts +1 -1
  68. package/dist/entrypoints/tracking.d.ts.map +1 -1
  69. package/dist/entrypoints/tracking.js +63 -83
  70. package/dist/entrypoints/xlsx.js +1 -1
  71. package/dist/geo/index.d.ts +2 -1
  72. package/dist/geo/index.d.ts.map +1 -1
  73. package/dist/geo/record.d.ts +8 -0
  74. package/dist/geo/record.d.ts.map +1 -0
  75. package/dist/geo/resolver.d.ts +1 -3
  76. package/dist/geo/resolver.d.ts.map +1 -1
  77. package/dist/geo/schema.d.ts +44 -0
  78. package/dist/geo/schema.d.ts.map +1 -0
  79. package/dist/geo/types.d.ts +2 -13
  80. package/dist/geo/types.d.ts.map +1 -1
  81. package/dist/{index-n5sv4z0w.js → index-0qyrcjfs.js} +16 -18
  82. package/dist/index-1psbyyhj.js +21 -0
  83. package/dist/{index-tsngj2b5.js → index-2fc1k9k2.js} +206 -206
  84. package/dist/index-3prvb42w.js +30 -0
  85. package/dist/{index-073nw9qr.js → index-3yb5kft5.js} +524 -522
  86. package/dist/{index-d9hq291n.js → index-4jpykzvw.js} +70 -81
  87. package/dist/index-568w258r.js +76 -0
  88. package/dist/index-69t14s4a.js +39 -0
  89. package/dist/{index-dky0dcmf.js → index-72r9k0av.js} +6 -2
  90. package/dist/{index-6p3frkv5.js → index-7zwtbak8.js} +9 -50
  91. package/dist/{index-90p34rts.js → index-8ryydawp.js} +7 -5
  92. package/dist/{index-vv1sch8a.js → index-8vzh5wpv.js} +13 -8
  93. package/dist/{index-75ns2ybj.js → index-8xyf8h2k.js} +1 -1
  94. package/dist/{index-xx1j310k.js → index-8yxgcyt6.js} +171 -169
  95. package/dist/{index-p6apg0jg.js → index-9frf01t9.js} +16 -9
  96. package/dist/{index-q0th8q0z.js → index-9mjrnqxs.js} +1 -3
  97. package/dist/{index-6f49krb5.js → index-aa50qwv4.js} +1 -1
  98. package/dist/{index-ega3yz4c.js → index-aqwg2dm5.js} +1 -1
  99. package/dist/{index-p0rf7sq9.js → index-bgcwxhxh.js} +225 -223
  100. package/dist/index-bph35vpy.js +77 -0
  101. package/dist/{index-tdpmfks8.js → index-bz479f0x.js} +1 -1
  102. package/dist/index-d3xdyjt6.js +38 -0
  103. package/dist/{index-q968hy6y.js → index-e452syyh.js} +1 -1
  104. package/dist/{index-86nbfysb.js → index-ff39f9dr.js} +3 -3
  105. package/dist/index-h1j71dr1.js +48 -0
  106. package/dist/{index-mgb7r8b8.js → index-jswst0h3.js} +1 -1
  107. package/dist/{index-095ya5hm.js → index-knmnj11j.js} +22 -191
  108. package/dist/{index-vwhrnp67.js → index-kz9734jc.js} +3 -3
  109. package/dist/{index-txg1npmg.js → index-mtwh9hyp.js} +1 -1
  110. package/dist/{index-dr3t5n1v.js → index-nn9qvkww.js} +46 -46
  111. package/dist/{index-ghtpfz4z.js → index-nyyam9ce.js} +2 -2
  112. package/dist/{index-qezhmp51.js → index-nz06qw5a.js} +3 -1
  113. package/dist/{index-1bx1xv1k.js → index-pfvde2fz.js} +5 -2
  114. package/dist/{index-w7jq3fz9.js → index-pq2m7t46.js} +1142 -1212
  115. package/dist/{index-yk4g8805.js → index-pq7rkwkk.js} +1 -30
  116. package/dist/{index-s80awwsw.js → index-qch1vcaa.js} +3 -1
  117. package/dist/{index-hdjmdwsj.js → index-qctbzmg9.js} +521 -511
  118. package/dist/{index-n8z3rvzw.js → index-qevqn3d3.js} +3 -5
  119. package/dist/{index-nqxrm176.js → index-r3db01kh.js} +1 -1
  120. package/dist/{index-v6vb5qx8.js → index-s3yxw6kt.js} +1 -1
  121. package/dist/{index-cqmqyjax.js → index-sa99p98w.js} +1 -1
  122. package/dist/index-sb9dr3tq.js +14 -0
  123. package/dist/{index-0gxvns6s.js → index-sba3e6m4.js} +1 -0
  124. package/dist/{index-f24bdt13.js → index-v1swr26p.js} +3 -1
  125. package/dist/{index-rhzkndr5.js → index-x4p3kw1p.js} +533 -530
  126. package/dist/{index-cycp3r3e.js → index-yb9n4pgj.js} +2 -2
  127. package/dist/{index-htxh46ej.js → index-ycqhyars.js} +2 -2
  128. package/dist/{index-9bwcthxw.js → index-yj6saztf.js} +31 -13
  129. package/dist/{index-f6m4c545.js → index-zrdcmg6j.js} +4 -6
  130. package/dist/{index-tvtacc9a.js → index-zyc64nbh.js} +1 -34
  131. package/dist/process/bun-child.d.ts +3 -2
  132. package/dist/process/bun-child.d.ts.map +1 -1
  133. package/dist/server/contract-method.d.ts.map +1 -1
  134. package/dist/tools/internal/surface-projector.d.ts.map +1 -1
  135. package/dist/tools/operations/async-operation-canonical-types.d.ts +1 -1
  136. package/dist/tracking/attribution.d.ts +21 -25
  137. package/dist/tracking/attribution.d.ts.map +1 -1
  138. package/dist/tracking/client-visit.d.ts +5 -7
  139. package/dist/tracking/client-visit.d.ts.map +1 -1
  140. package/dist/tracking/client.d.ts +2 -2
  141. package/dist/tracking/client.d.ts.map +1 -1
  142. package/dist/tracking/contract.d.ts +1 -1
  143. package/dist/tracking/host.d.ts +0 -1
  144. package/dist/tracking/host.d.ts.map +1 -1
  145. package/dist/tracking/privacy.d.ts +6 -0
  146. package/dist/tracking/privacy.d.ts.map +1 -1
  147. package/dist/tracking/schemas.d.ts +2 -2
  148. package/dist/tracking/schemas.d.ts.map +1 -1
  149. package/llms/agent-runtime-harness.txt +4 -3
  150. package/llms/agent-runtime-sandbox.txt +1 -1
  151. package/llms/agent-runtime.2.txt +3 -2
  152. package/llms/agent-runtime.3.txt +4 -46
  153. package/llms/agent-runtime.4.txt +113 -0
  154. package/llms/agent-runtime.txt +11 -13
  155. package/llms/cli.txt +1 -1
  156. package/llms/contract.txt +46 -24
  157. package/llms/geo-contract.txt +23 -0
  158. package/llms/geo.txt +40 -4
  159. package/llms/stitchkit.txt +16 -9
  160. package/llms/testing.txt +4 -6
  161. package/llms/tools-agent.txt +24 -0
  162. package/llms/tools-mcp.txt +71 -6
  163. package/llms/tools-toolkit.txt +23 -0
  164. package/llms/tools.2.txt +8 -5
  165. package/llms/tools.3.txt +9 -78
  166. package/llms/tools.txt +8 -8
  167. package/llms/tracking-contract.txt +1 -1
  168. package/llms/tracking.txt +27 -5
  169. package/llms/{upgrading-0.105.0-0.118.0.txt → upgrading-0.106.0-0.120.0.txt} +152 -410
  170. package/llms/upgrading-0.37.0-0.53.0.txt +969 -0
  171. package/llms/{upgrading-0.44.0-0.62.0.txt → upgrading-0.55.0-0.68.0.txt} +509 -528
  172. package/llms/{upgrading-0.63.0-0.83.0.txt → upgrading-0.69.0-0.95.0.txt} +506 -509
  173. package/llms/{upgrading-0.84.0-0.104.0.txt → upgrading-0.96.0-0.105.0.txt} +410 -506
  174. package/llms/upgrading.txt +5 -5
  175. package/llms-full.txt +475 -154
  176. package/llms.txt +17 -14
  177. package/package.json +32 -20
  178. package/llms/upgrading-0.37.0-0.44.0.txt +0 -444
  179. package/dist/{index-arjmb6de.js → index-1jwsfkfz.js} +0 -0
  180. package/dist/{index-qndsr7f0.js → index-5efrpfmj.js} +3 -3
@@ -1,14 +1,517 @@
1
- # Upgrading 0.63.0 → 0.83.0 — stitchkit docs slice
1
+ # Upgrading 0.69.0 → 0.95.0 — stitchkit docs slice
2
2
 
3
3
  > Generated from the `docs/` tree by `bun run gen:llms`. The index of all slices is
4
4
  > `llms.txt` in this package.
5
5
 
6
- Contains: Guide: Upgrading, 0.63.0 → 0.83.0 (docs/guide/upgrading.md)
6
+ Contains: Guide: Upgrading, 0.69.0 → 0.95.0 (docs/guide/upgrading.md)
7
7
 
8
8
  ==============================================================================
9
- # Guide: Upgrading, 0.63.0 → 0.83.0 (docs/guide/upgrading.md)
9
+ # Guide: Upgrading, 0.69.0 → 0.95.0 (docs/guide/upgrading.md)
10
10
  ==============================================================================
11
11
 
12
+ ## Released migration: 0.95.0
13
+
14
+ One breaking change in `stitchkit/application`: the process lifecycle ledger
15
+ counts downtime as the window nobody answered in, and gains the `draining`
16
+ phase and the `startup-failed` outcome. A ledger file written by an earlier
17
+ version loads unchanged — the new `unavailableAt` reads as `null`.
18
+
19
+ 1. **Read downtime from the ready fact.** **Who must act:** a project that
20
+ reads `downtimeMs` from a `started` fact. It is gone from `StartFact`; the
21
+ number it carried is `processGapMs`. The downtime now arrives with the new
22
+ run's readiness:
23
+
24
+ ```ts
25
+ // before
26
+ ledger.subscribe((fact) => {
27
+ if (fact.type === 'started') report(fact.previousExit, fact.downtimeMs);
28
+ });
29
+ // after
30
+ ledger.subscribe((fact) => {
31
+ if (fact.type === 'started') previousExit = fact.previousExit;
32
+ if (fact.type === 'ready') report(previousExit, fact.downtimeMs);
33
+ });
34
+ ```
35
+
36
+ 2. **Handle the new values.** **Who must act:** a project that switches
37
+ exhaustively over `LifecycleTermination` (new: `draining`,
38
+ `startup-failed`), `PreviousExit` (new: `startup-failed`) or
39
+ `ProcessLifecycleFact` (new: `DrainingFact`, `type: 'draining'`). The type
40
+ checker names each switch. An open run is `active` or `draining`; a check
41
+ written as `termination === 'active'` to mean "still running" becomes
42
+ `termination === 'active' || termination === 'draining'`.
43
+ `TransitionReadyInput` is renamed `TransitionRunInput` — the same fields,
44
+ now also taken by `transitionProcessDraining`; rename the import.
45
+
46
+ 3. **Record the drain and the readiness when driving the ledger by hand.**
47
+ **Who must act:** only a project that calls `createProcessLifecycleLedger`
48
+ methods itself instead of mounting `lifecycleLedgerResource`. Call
49
+ `recordDraining()` where the application stops admitting work, before it
50
+ drains; without it downtime is counted from the recorded stop. Call
51
+ `recordReady()` when the run serves; a run stopped without it now ends
52
+ `startup-failed`, not `clean`.
53
+
54
+ ## Released migration: 0.94.0
55
+
56
+ One breaking minor that consolidates: tool options move into one group, and
57
+ names that were exported from two entrypoints keep only their owner. Every step
58
+ is an edit a type checker or `defineContract` points at; the few behaviour
59
+ changes that need no edit are under Changed and Fixed in the changelog.
60
+
61
+ 1. **Move endpoint tool options into `tool`.** **Who must act:** every project
62
+ with an endpoint that sets `toolName`, `ui`, `annotations` or `mcp`. Run the
63
+ codemod from a checkout of this repository, then typecheck:
64
+
65
+ ```bash
66
+ bun packages/core/scripts/codemod-tool-group.ts --check path/to/your/src # list
67
+ bun packages/core/scripts/codemod-tool-group.ts path/to/your/src # rewrite
68
+ ```
69
+
70
+ ```ts
71
+ // before
72
+ { method: 'GET', path: '/', desc: 'List', toolName: 'user_list', mcp: { inputRequired } }
73
+ // after
74
+ { method: 'GET', path: '/', desc: 'List', tool: { name: 'user_list', mcp: { inputRequired } } }
75
+ ```
76
+
77
+ The codemod leaves two kinds of endpoint for a person and prints where: one
78
+ built with a spread (`{ ...base, toolName }`), and one exposed on HTTP only,
79
+ whose tool options never reached a tool — delete them there. Anything left
80
+ over fails at startup, by name — a key moved into the group under its old
81
+ name (`tool: { toolName }`) included:
82
+
83
+ ```text
84
+ Contract "users": endpoint "list" sets `toolName` — tool options live in `tool` since 0.94.0: use tool.name
85
+ ```
86
+
87
+ `expose` does not move. `MethodDef` — what hooks and runtime tools see — keeps
88
+ `toolName`, `ui`, `annotations` and `mcp` at the top level, so hook code does
89
+ not change. A tool view is declared only with `withToolView`, which now puts
90
+ it in `tool.view`.
91
+
92
+ 2. **Change imports of moved names.** **Who must act:** projects importing any
93
+ of these; the compiler lists every site.
94
+
95
+ | Name | From | To |
96
+ |---|---|---|
97
+ | `RuntimeToolTransport`, `ToolSurfaceTransport`, `ToolInvokerTransport` | `stitchkit/tools`, `stitchkit/tools/invoker`, `stitchkit/tools/connections` | `ToolTransport` from `stitchkit/contract` |
98
+ | `createCli`, `defineCliCommand`, `CliConfig`, `CliSurfaceSource`, `CliPresentationPolicyConfig`, `CliWaitConfig`, `ExitCodeMap`, `CliCommand*` | `stitchkit/tools` | `stitchkit/cli` |
99
+ | `parseSSE`, `ParseSSEOptions` | `stitchkit/server` | `stitchkit` |
100
+ | `runAgentStoreConformance`, `AgentStoreConformance*`, `createAgentRace*`, `AgentRace*` | `stitchkit/testing` | `stitchkit/agent-runtime/testing` |
101
+ | `createLocalStepDurability`, `LocalStepDurability`, `LocalStepDurabilityOptions`, `StepDurabilityLedger` | `stitchkit/agent-runtime` | `stitchkit/tools` |
102
+
103
+ 3. **Regenerate a surface snapshot only if it reports drift.** **Who must act:**
104
+ a project whose committed snapshot has an object with integer-like keys
105
+ (`"9"`, `"10"`) — they are now sorted like every other key. Every other
106
+ snapshot, CLI manifest signature and agent-store archive is byte-identical.
107
+
108
+ ## Released migration: 0.93.0
109
+
110
+ 1. **Regenerate every committed surface snapshot, once.** `manifestVersion` is
111
+ `3` and each operation row gained `mcp` — the rounds it declares before it
112
+ runs. Most rows get `null`, so the first diff is large and mechanical.
113
+ **Who must act:** anyone with a committed snapshot. A snapshot on the older
114
+ version is refused by name rather than failing a schema literal:
115
+
116
+ ```text
117
+ Surface snapshot is manifestVersion 2; this build writes 3. Regenerate it
118
+ and review the diff — the format changed, so the first regeneration is
119
+ expected to be large.
120
+ ```
121
+
122
+ ```ts
123
+ // before — a declared round left no trace, so declaring or removing one
124
+ // passed review invisibly
125
+ manifest.operations[0]
126
+ // after
127
+ manifest.operations[0].mcp
128
+ // null | { inputRequired: [{ key, message, schema }] }
129
+ // | { inputRequired: 'resolved-per-call' }
130
+ ```
131
+
132
+ A project that kept its own test for "declared `inputRequired` implies a
133
+ configured state key" can drop it: the snapshot holds that link now.
134
+
135
+ 2. **Nothing else to do.** Native commands that declare `output` became
136
+ invokable in process; a name that answered `NOT_FOUND` from
137
+ `createCliInvoker` in 0.92.0 may now resolve. If an application relied on
138
+ native commands being unreachable there, stop passing them to the invoker.
139
+
140
+ ## Released migration: 0.92.0
141
+
142
+ 1. **`RequestEvent.method`, `path` and `statusCode` are optional.** Work that did
143
+ not arrive over a transport now records their **absence** instead of inventing
144
+ values — a background loop used to write `method: 'AGENT'`, which is not a
145
+ verb. **Who must act:** anyone whose `RequestEventSinkConfig.write` puts these
146
+ into NOT NULL columns, or whose filter reads `event.method`.
147
+
148
+ ```ts
149
+ // before
150
+ write: (event) => db.audit.create({ data: { method: event.method, path: event.path, status: event.statusCode } })
151
+ // after — a job has none of the three; `kind` says which kind of row it is
152
+ write: (event) => db.audit.create({ data: {
153
+ kind: event.kind,
154
+ name: event.name ?? null,
155
+ method: event.method ?? null,
156
+ path: event.path ?? null,
157
+ status: event.statusCode ?? null,
158
+ } })
159
+ ```
160
+
161
+ A filter written as `event.method !== 'GET'` keeps jobs (`undefined !== 'GET'`),
162
+ which is usually what you want. To exclude them deliberately:
163
+ `event.kind === 'request' && event.method !== 'GET'`.
164
+
165
+ 2. **`applyCliUpdate` needs the manifest when given a trust root.** The signature
166
+ covers the manifest and every asset digest together, so an asset alone cannot
167
+ be verified. **Who must act:** only callers adopting signature verification;
168
+ the unsigned call is unchanged.
169
+
170
+ ```ts
171
+ // before
172
+ await applyCliUpdate({ asset })
173
+ // after
174
+ await applyCliUpdate({ asset, manifest: check.manifest, trust })
175
+ ```
176
+
177
+ 3. **An MCP continuation minted before this release is refused once.** The signed
178
+ round state now carries a fingerprint of the question plan, checked on every
179
+ round, so a plan that moved between rounds is a refusal rather than a
180
+ different question asked under the previous one's key. A conversation already
181
+ in flight at deploy time has no fingerprint and gets
182
+ `INVALID_REQUEST_STATE`; the host re-asks and the second attempt succeeds.
183
+ **Who must act:** nobody — but expect a small number of these in the minutes
184
+ after a deploy, and do not read them as tampering.
185
+
186
+ ## Released migration: 0.91.0
187
+
188
+ 1. `STITCH_ERROR_STATUS` gained `NOT_IMPLEMENTED` (501). An **exhaustive** error
189
+ vocabulary must name it — without the row `defineErrors({ mapping: { exhaustive:
190
+ true } })` stops compiling and then throws at startup. Nothing else in this
191
+ release requires action.
192
+
193
+ ```ts
194
+ // before
195
+ map: { …, WAIT_TIMEOUT: 'timeout' }
196
+ // after
197
+ map: { …, NOT_IMPLEMENTED: 'not_implemented', WAIT_TIMEOUT: 'timeout' }
198
+ ```
199
+
200
+ The code is thrown only by the new dev-only `onMissingHandler: 'stub'` policy;
201
+ a vocabulary that is not exhaustive is unaffected.
202
+
203
+ ## Released migration: 0.90.0
204
+
205
+ 1. `stitchkit.watch.value` is a discriminated union on `kind` — `full`, `delta`,
206
+ `unchanged` — and every frame carries `fingerprint`. **Both ends must come from
207
+ the same major.** A client older than this release reads a `delta` frame as a
208
+ value of `undefined`, silently; upgrade the hub and the pages that talk to it
209
+ together, or set `deltaMemoryBytes: 0` on the hub until they are.
210
+
211
+ Applications using `createWatchClient` need no code change: it rebuilds the
212
+ value and hands the listener the whole answer as before. Code that reads
213
+ `WatchValueFrame.value` directly — a hand-written subscriber, a test double —
214
+ narrows on `kind` first.
215
+
216
+ ```ts
217
+ // before: frame.value
218
+ // after:
219
+ if (frame.kind === 'full') hold(frame.value);
220
+ else if (frame.kind === 'delta') hold(applyWatchDelta(held, frame.delta));
221
+ // 'unchanged' leaves what you hold standing
222
+ ```
223
+
224
+ 2. `AttachedWatcher.open` takes an optional third argument, `have`. A custom
225
+ server binding that forwards `stitchkit.watch.open` should pass the payload's
226
+ `have` through; omitting it costs a whole value on every reconnection and is
227
+ otherwise harmless.
228
+
229
+ ## Released migration: 0.89.0
230
+
231
+ 1. Custom `AgentRuntimeStore` adapters implement `seedConversationInput`; custom
232
+ `AgentRuntimeStoreDriver` adapters implement `seeds.load` and `seeds.create` together
233
+ with atomic, ordered history insertion. Use the maintained Prisma example and run
234
+ `runAgentStoreConformance` against the adapter. Built-in memory and SQLite
235
+ stores already implement the contract; SQLite migrates to schema version 3.
236
+ 2. OpenRouter usage normalization consumes each completed SDK step's `usage`, including
237
+ `raw`, rather than aggregate usage. Missing counters stay `unavailable`.
238
+
239
+ ```ts
240
+ // before: normalizeOpenRouterUsage(await result.usage, metadata)
241
+ // after:
242
+ for (const step of await result.steps) {
243
+ const usage = normalizeOpenRouterUsage(step.usage, step.providerMetadata);
244
+ // Persist the step's measured usage with its provenance.
245
+ }
246
+ ```
247
+
248
+ 3. Durable step bodies return lossless JSON. Convert dates explicitly, reject invalid
249
+ numbers upstream, and return `null` after an effect-only operation.
250
+
251
+ ```ts
252
+ await context.step('send', async () => {
253
+ await sendIdempotently();
254
+ return null;
255
+ });
256
+ ```
257
+
258
+ First execution, replay and concurrent callers receive detached values. Persistence
259
+ does not make external effects transactional; the host retains idempotency and recovery.
260
+
261
+
262
+ How to move a consuming project from one stitchkit version to another — including
263
+ across many versions at once (a project frozen on an old version, then jumped
264
+ forward). The process is mechanical: stitchkit marks every breaking change in one
265
+ place and one format, so you can recover the full migration from the version diff.
266
+
267
+ ## Released migration: 0.88.0
268
+
269
+ Two mechanical edits, and only if your project builds these values rather than only reading them.
270
+
271
+ A completed step event now carries the provider's response identity, and the field is required:
272
+
273
+ ```ts
274
+ // before
275
+ { type: 'step-finished', step: 0, usage }
276
+
277
+ // after
278
+ { type: 'step-finished', step: 0, usage, response: { id: generationId } }
279
+ ```
280
+
281
+ Every event the runtime emits already has it, so a sink that only reads them needs no change. A
282
+ fixture, a test double or a pipeline that re-validates events persisted by an earlier version does:
283
+ give the older records a response identity, or keep parsing them with the schema they were written
284
+ under.
285
+
286
+ If you implement the whole `AgentRuntime` interface — usually a mock — add the new method:
287
+
288
+ ```ts
289
+ // before
290
+ const fake: AgentRuntime = { submit, interrupt, recover, close }
291
+
292
+ // after
293
+ const fake: AgentRuntime = { submit, interrupt, abandon, recover, close }
294
+ ```
295
+
296
+ No data migration is involved, and nothing in the store changes shape.
297
+
298
+ ## Released migration: 0.87.0
299
+
300
+ Only if your project implements `AgentConversationReader` itself. A message page
301
+ now names which of its messages compaction removed:
302
+
303
+ ```ts
304
+ // before
305
+ return { items, ...(nextCursor && { nextCursor }) }
306
+
307
+ // after
308
+ return { items, compacted: [], ...(nextCursor && { nextCursor }) }
309
+ ```
310
+
311
+ An empty array is the honest answer for a reader that pages only the active
312
+ history; it is what the SQLite reader returns unless the caller asks for
313
+ `includeCompacted`. Calling the reader needs no change, and no data migration is
314
+ involved.
315
+
316
+ ## Released migration: 0.86.0
317
+
318
+ Only if you branch on `AgentTerminalReason` or show it to a person. Three
319
+ failures that used to arrive as `provider_failure` now arrive as themselves:
320
+
321
+ ```ts
322
+ // before
323
+ if (run.terminalReason === 'provider_failure') showOutage()
324
+
325
+ // after
326
+ switch (run.terminalReason) {
327
+ case 'provider_failure':
328
+ showOutage()
329
+ break
330
+ case 'storage_conflict':
331
+ // Another writer took this run's revision, or the store refused the write.
332
+ retryOrReport()
333
+ break
334
+ case 'output_rejected':
335
+ // Your own `protocol.acceptTerminal` refused the finished message.
336
+ reportRejectedAnswer()
337
+ break
338
+ case 'runtime_failure':
339
+ // The runtime, or a check the SDK ran on what it was handed, failed
340
+ // before the provider was reached.
341
+ reportInternal()
342
+ break
343
+ }
344
+ ```
345
+
346
+ A run that never reached the provider can no longer report `provider_failure`,
347
+ so an alert keyed on that value stops firing for causes that were never the
348
+ provider's.
349
+
350
+ If you implement `AgentRuntimeStore` directly, add the event-ledger surface:
351
+
352
+ ```ts
353
+ const store: AgentRuntimeStore = {
354
+ // existing normalized operations
355
+ appendEvent,
356
+ readEvents,
357
+ exportConversation,
358
+ importConversation,
359
+ }
360
+ ```
361
+
362
+ If you implement an `AgentRuntimeStoreDriver` (Prisma, Postgres, any external
363
+ database), the ledger is yours to keep: `events` is a required member, and it
364
+ needs a table.
365
+
366
+ ```ts
367
+ // before
368
+ const driver: AgentRuntimeStoreDriver<Tx> = { transaction, head, runs, admissions, history, scanRecoverable }
369
+
370
+ // after
371
+ const driver: AgentRuntimeStoreDriver<Tx> = {
372
+ transaction, head, runs, admissions, history, scanRecoverable,
373
+ events: {
374
+ // One row per event; `seq` is per conversation, starts at 1, and is
375
+ // assigned inside the caller's transaction so concurrent appends cannot
376
+ // share or skip a number.
377
+ append: async (tx, draft) => {
378
+ // Two transactions that both read max(seq) and both insert max + 1
379
+ // collide on the primary key; under SERIALIZABLE the snapshot predates
380
+ // any lock you take here. Treat a unique violation on this table as a
381
+ // serialization conflict and re-run the whole transaction — see
382
+ // `runTransaction` in examples/agent-store-prisma/adapter.ts.
383
+ const seq = (await tx.event.aggregate({ _max: { seq: true }, where: { conversationId: draft.conversationId } }))._max.seq ?? 0
384
+ await tx.event.create({ data: { ...draft, seq: seq + 1, payload: JSON.stringify(draft.payload) } })
385
+ return { ...draft, seq: seq + 1 }
386
+ },
387
+ // Ordered by `seq`, bounded by `fromSeq`/`toSeq`/`limit`; `nextSeq` when
388
+ // more remain.
389
+ list: async (tx, input) => { /* see examples/agent-store-prisma/adapter.ts */ },
390
+ },
391
+ }
392
+ ```
393
+
394
+ The table carries `eventId` (unique), `conversationId`, `seq`, `schemaVersion`,
395
+ `kind`, `occurredAt`, `ignorable` and `payload`. `runAgentStoreConformance`
396
+ from `stitchkit/testing` now exercises it — contiguous `seq` under twenty
397
+ concurrent appends, bounded reads, the `ignorable` flag, a byte-stable export
398
+ — so run it against your driver before deploying. Expect one more insert per
399
+ run operation (`runtime/transition`) and one per model step
400
+ (`provider/request`) on the hot path; there is no opt-out.
401
+
402
+ If you operate a built-in SQLite store file:
403
+
404
+ 1. Back the file up. The first open by 0.86.0 migrates it to schema 2 in one
405
+ transaction and there is no way back: a 0.85.x package refuses a migrated
406
+ file with `Unsupported Stitchkit agent-runtime SQLite schema version 2`.
407
+ 2. Check the build for FTS5 before deploying:
408
+
409
+ ```sql
410
+ SELECT sqlite_compileoption_used('ENABLE_FTS5');
411
+ ```
412
+
413
+ A build without it is refused at open with `requires FTS5 support`. On the
414
+ machine this was verified, Bun 1.3 `bun:sqlite` and Node 24.18 `node:sqlite`
415
+ both carry it; verify your own build rather than assuming — the table of
416
+ supported runtimes says Node ≥ 22.5, and FTS5 there is not verified here.
417
+ 3. Know what the migration wrote. Each existing conversation becomes one
418
+ `runtime/baseline` event at `seq 1`, dated when the migration ran, with
419
+ `asOf` inside the payload naming the last message it describes. Event
420
+ search and projections address that conversation's pre-migration history as
421
+ one snapshot at `seq 1`; event-level precision begins with the first event
422
+ after the migration.
423
+
424
+ If you subscribe to `AgentRuntimeEvent` and switch over `event.type`
425
+ exhaustively, add the transient `attempt-reset` case — drop the run's
426
+ transient text — or the reducer stops compiling; a reducer that ignores it
427
+ keeps a failed attempt's partial text on screen.
428
+
429
+ New in this release, not a migration: child runs cascade only when the manager
430
+ is given to the runtime —
431
+ `createAgentRuntime({ ...config, children: createSqliteAgentChildManager({ sqlite, spawn }) })`
432
+ — and the SQLite companions take the store handle (`{ sqlite }`), whose
433
+ `database` and `transaction` they share.
434
+
435
+ ## Released migration: 0.85.0
436
+
437
+ Only if you implement `AgentRuntimeStore` directly. Add the new durable
438
+ operation mutation beside the existing run mutations:
439
+
440
+ ```ts
441
+ // before
442
+ const store: AgentRuntimeStore = {
443
+ checkpointRunAssistant,
444
+ commitRunTerminal,
445
+ // ...
446
+ }
447
+
448
+ // after
449
+ const store: AgentRuntimeStore = {
450
+ checkpointRunAssistant,
451
+ recordRunOperation,
452
+ commitRunTerminal,
453
+ // ...
454
+ }
455
+ ```
456
+
457
+ It must atomically replace `AgentRun.lastOperation`, increment the run revision
458
+ and conversation snapshot version, and enforce the supplied owner, fencing
459
+ token and expected revision. Adapters built with `createAgentRuntimeStore`
460
+ already receive the reference implementation; no driver method is added.
461
+
462
+ ## Released migration: 0.84.0
463
+
464
+ Only if you call `createAgentCodingTools`.
465
+
466
+ ```bash
467
+ rg -n "createAgentCodingTools"
468
+ ```
469
+
470
+ **1. Backslashes are refused.** If anything in your system hands the tools a Windows-style path,
471
+ it now gets `FORBIDDEN`. Convert to `/` at the boundary:
472
+
473
+ ```ts
474
+ // before: read_file({ path: 'src\\index.ts' }) // reached openat as two segments
475
+ // after: read_file({ path: 'src/index.ts' })
476
+ ```
477
+
478
+ This is the half that matters most: until now a backslash bypassed your **required**
479
+ `authorize({ operation, path })` callback, not just `authorizePath`. A rule denying
480
+ `credentials/token.txt` refused that spelling and served — and overwrote — the same file spelled
481
+ `credentials\token.txt`. If your policy denies specific files, re-read your audit logs for
482
+ backslash spellings.
483
+
484
+ **2. Denying a directory now denies everything under it — and a policy must admit every directory
485
+ on the way to a file**, the way POSIX needs `+x` on each directory in a path.
486
+
487
+ If your policy is a DENY-list, it now does what it read as:
488
+
489
+ ```ts
490
+ authorizePath: ({ path }) => path !== 'credentials'
491
+ // before: hid `credentials` from glob, served `credentials/token.txt` to read_file
492
+ // after: refuses `credentials/token.txt`, `credentials/nested/deep.txt` and writes under it
493
+ ```
494
+
495
+ If you relied on the old behaviour — a directory hidden from discovery but readable directly — you
496
+ were relying on a defect, and the two halves of one callback disagreeing. Name the paths you
497
+ actually want reachable instead.
498
+
499
+ If your policy is an ALLOW-list, widen it to the directories it leads through, or nothing under
500
+ them is reachable:
501
+
502
+ ```ts
503
+ // before: allowed src/index.ts, because only the leaf was ever asked
504
+ authorizePath: ({ path }) => path.startsWith('src/')
505
+ // after: `src` itself is asked first and refused, so the file is refused too
506
+ authorizePath: ({ path }) => path === 'src' || path.startsWith('src/')
507
+ ```
508
+
509
+ The workspace root is not asked about — it is the boundary the tools already own.
510
+
511
+ **3. The policy is called more often.** A path of depth N costs N+1 calls (`.`, then each ancestor,
512
+ then the leaf), outermost first, stopping at the first refusal. A policy that logs or meters will
513
+ see a different sequence, and will no longer see the leaf once an ancestor has refused.
514
+
12
515
  ## Released migration: 0.83.0
13
516
 
14
517
  Two things, both mechanical, and only if you touch an observability sink.
@@ -646,509 +1149,3 @@ else renderExistingRole(message)
646
1149
 
647
1150
  No data migration is needed. The schema still accepts all earlier records, and applications that
648
1151
  do not exhaustively branch over these unions compile unchanged.
649
-
650
- ## Released migration: 0.68.0
651
-
652
- ### Make Unix transport selection explicit outside Bun
653
-
654
- The legacy `unix` option remains a Bun convenience, but on Node or another
655
- runtime it now fails before dispatch. Replace it with the owned adapter and close
656
- that adapter with the application:
657
-
658
- ```ts
659
- // before — unsafe outside Bun: an unsupported fetch could dial baseUrl over TCP
660
- const http = createHttpClient({ baseUrl, unix: '/run/service.sock' })
661
-
662
- // after — Bun and Node; every dispatch and redirect stays on the socket
663
- import { createUnixClientTransport } from 'stitchkit/server' // or stitchkit/node
664
- const transport = createUnixClientTransport({ socketPath: '/run/service.sock' })
665
- const http = createHttpClient({ baseUrl, fetch: transport.fetch })
666
- // during shutdown
667
- await transport.close()
668
- ```
669
-
670
- Do not automatically replay `possibly-dispatched`: a timeout or connection loss
671
- after bytes left the process does not prove that a write did not happen.
672
-
673
- ### Choose tolerant stream parsing explicitly
674
-
675
- `parseNDJSON` and `parseSSE` now throw on malformed JSON, invalid UTF-8 and an
676
- over-limit line. The default line ceiling is 1 MiB. If a feed deliberately skips
677
- bad records, retain that policy explicitly:
678
-
679
- ```ts
680
- // before — malformed input disappeared implicitly
681
- parseNDJSON(response)
682
-
683
- // after — ordinary fail-closed path
684
- parseNDJSON(response, { maxLineBytes: 256 * 1024 })
685
-
686
- // after — deliberately tolerant path
687
- parseNDJSON(response, { onParseError: (raw, error) => report(raw, error) })
688
- ```
689
-
690
- ### Extend exhaustive framework-error handling
691
-
692
- If a switch makes `StitchErrorCode` exhaustive, add
693
- `STREAM_ITEM_INVALID`, `STREAM_FRAME_TOO_LARGE`,
694
- `STREAM_TERMINAL_MISSING` and `STREAM_LIFETIME_EXCEEDED`. A partial application
695
- status map needs no change (ADR 0105).
696
-
697
- The admission/channel APIs and endpoint `stream` descriptor are additive; raw
698
- responses and raw `streamingRoute` remain supported.
699
-
700
- ## Released migration: 0.67.0
701
-
702
- Three application-kernel changes. Two of them fix silent failures, so the most
703
- important part of this migration is not what the compiler points at — it is the
704
- two things that change with no compile error at all: **when your server is
705
- created**, and **which shutdown budget a signal spends**.
706
-
707
- ### If you gave `managedServerResource` a thunk
708
-
709
- It is now called during `start`, after the resource's dependencies are ready —
710
- the reading the type always suggested. Before, it was called on the way *down*,
711
- so `app.start()` resolved, the snapshot said `healthy` and `ready`, and nothing
712
- was bound to the port.
713
-
714
- If your application looked like this, it was never listening:
715
-
716
- ```ts
717
- // before — resolves clean, listens on nothing
718
- managedServerResource({ id: 'http', dependsOn: ['database'], server: () => createServer(config) })
719
- ```
720
-
721
- Nothing to change: the same code now binds the port. Check your startup logs for
722
- a healthy report you never actually verified with a request.
723
-
724
- If you used the spread workaround — this resource's phases over your own
725
- `start` — it keeps working, and you can now delete it:
726
-
727
- ```ts
728
- // before
729
- let handle: ManagedServerHandle<T> | null = null
730
- const shutdown = managedServerResource({ id: 'http', server: () => handle! })
731
- const http = defineManagedResource({
732
- ...shutdown,
733
- dependsOn: ['database', 'socket-io'],
734
- start: () => { handle = createServer(config) },
735
- })
736
-
737
- // after
738
- const http = managedServerResource({
739
- id: 'http',
740
- dependsOn: [database, socketIo],
741
- server: () => createServer(config),
742
- })
743
- ```
744
-
745
- An already-created handle is adopted exactly as before.
746
-
747
- ### If you passed `shutdown` to `bindProcessSignals`
748
-
749
- `bindProcessSignals` used to fill in the schema's defaults for every budget you
750
- omitted, which made `createApplication({ shutdown })` unreachable on the signal
751
- path — the one path production stops through. An application declaring five
752
- seconds took thirty-five.
753
-
754
- Declare the budget once, on the application, and delete the repetition:
755
-
756
- ```ts
757
- // before — the same two numbers in two places, and only one of them applied
758
- const app = createApplication({ id: 'svc', resources, shutdown: SHUTDOWN_BUDGET })
759
- bindProcessSignals(app, { shutdown: SHUTDOWN_BUDGET })
760
-
761
- // after
762
- const app = createApplication({ id: 'svc', resources, shutdown: SHUTDOWN_BUDGET })
763
- bindProcessSignals(app)
764
- ```
765
-
766
- **Check this even if you change nothing.** If you declared a budget on the
767
- application and did not repeat it on the binding, your process has been stopping
768
- on 30 s / 5 s and will now stop on what you declared. Compare it against the
769
- supervisor timeout that watches it — `kill_timeout`, `TimeoutStopSec` — because
770
- that number was probably calculated from the declaration.
771
-
772
- Passing `shutdown` to the binding still works and still wins, key by key: one
773
- key overrides that key alone and leaves the other at the declaration.
774
-
775
- ### If you read `resource.dependsOn`
776
-
777
- Its type widened from `readonly string[]` to `readonly ManagedResourceDependency[]`
778
- — `string | ManagedResource` — so a dependency can be declared as the resource
779
- itself. Declaring stays compatible; reading needs one call:
780
-
781
- ```ts
782
- // before
783
- const ids: readonly string[] = resource.dependsOn ?? []
784
-
785
- // after
786
- import { managedResourceDependencyId } from 'stitchkit/application'
787
- const ids = (resource.dependsOn ?? []).map(managedResourceDependencyId)
788
- ```
789
-
790
- ### If you thread a handle through a module-local
791
-
792
- This is the pattern the change exists to remove, and it is opt-in — nothing
793
- breaks if you keep yours:
794
-
795
- ```ts
796
- // before
797
- let socket: SocketHandle | null = null
798
- const socketIo = defineManagedResource({
799
- id: 'socket-io',
800
- start: async () => { socket = await createSocketServer(config) },
801
- })
802
- const http = defineManagedResource({
803
- id: 'http',
804
- dependsOn: ['socket-io'],
805
- start: () => {
806
- if (!socket) throw new Error('socket is not initialized') // unreachable
807
- server = createServer({ socket })
808
- },
809
- })
810
-
811
- // after
812
- const socketIo = defineManagedResource({
813
- id: 'socket-io',
814
- start: async () => ({ value: await createSocketServer(config) }),
815
- })
816
- const http = defineManagedResource({
817
- id: 'http',
818
- dependsOn: [socketIo],
819
- start: (context) => {
820
- server = createServer({ socket: context.use(socketIo) }) // SocketHandle
821
- },
822
- })
823
- ```
824
-
825
- Declare the dependency with the **resource** when you intend to read from it —
826
- that is the form `use` can type. `use` refuses a resource missing from
827
- `dependsOn`, and refuses one that published no value; the second refusal is a
828
- compile error too.
829
-
830
- ## Released migration: 0.66.0
831
-
832
- Three changes, and only one of them is a feature. The other two are shapes that
833
- would have had to break later: a vocabulary that described one fact with two
834
- words, and a store whose only read was the whole conversation.
835
-
836
- The compiler points at most of it. Two things change with no compile error —
837
- what a **total** says about its own provenance, and the fact that token counts
838
- are now validated where they were not.
839
-
840
- ### An input that joins a run in flight
841
-
842
- #### If you matched exhaustively on `AgentTerminalReason`
843
-
844
- `'absorbed'` is new. A run ends this way when a run already in flight took its
845
- input on and answered it; its state is `'superseded'`, and `absorbedIntoRunId`
846
- names the run that has the answer.
847
-
848
- ```ts
849
- // after
850
- switch (run.terminalReason) {
851
- // …
852
- case 'absorbed':
853
- // no assistant message of its own — follow run.absorbedIntoRunId
854
- break
855
- }
856
- ```
857
-
858
- A run record with `terminalReason: 'absorbed'` and no `absorbedIntoRunId` is
859
- refused at parse time, and so is `absorbedIntoRunId` on any other reason.
860
-
861
- #### If you render or export runs
862
-
863
- An absorbed run has **no assistant message**. Anything that assumes "every
864
- terminal run has one" needs the `absorbed` case — the answer is on the run
865
- `absorbedIntoRunId` names, and `store.loadRun` resolves it.
866
-
867
- #### If you want the policy
868
-
869
- ```ts
870
- // after
871
- runs: { inputPolicy: 'inject' }
872
- ```
873
-
874
- It was withdrawn in 0.65.0 and is back with the ordering corrected: the
875
- absorption commits with the terminal record, not at the step boundary. Read
876
- *An input that joins a run in flight* in the agent-runtime guide before turning
877
- it on — in particular what happens when the absorbing run does not complete, and
878
- how it composes with `coalescePending`.
879
-
880
- #### If you implement a store driver
881
-
882
- Nothing to do, but know what changed underneath: one terminal commit can now
883
- save **two** run records, and they must land in the same transaction. A driver
884
- that persists one of the pair fails `runAgentStoreConformance`.
885
-
886
- ### A run read without its conversation
887
-
888
- #### If you implement `AgentRuntimeStore` by hand
889
-
890
- Two members to add. **If your adapter is an `AgentRuntimeStoreDriver` passed to
891
- `createAgentRuntimeStore`, there is nothing to do** — the driver already had
892
- everything both need.
893
-
894
- ```ts
895
- // after
896
- loadRun(input: { conversationId: string; runId: string }): Promise<AgentRunView | undefined>
897
- listActiveRuns(conversationId: string): Promise<readonly AgentRun[]>
898
- ```
899
-
900
- `AgentRunView` is `{ snapshotVersion, run, assistant? }`. `assistant` is the
901
- retained terminal answer, so it is present exactly when the run has ended and
902
- absent while it is live — a store that returns a live run's draft here lets the
903
- terminal path resolve a run that has not finished. `listActiveRuns` orders by
904
- `createdAt` then `id`, and must not report a run that has ended.
905
- `runAgentStoreConformance` covers both, including the boundary cases.
906
-
907
- #### If you have a store **double** in your tests
908
-
909
- The runtime now reads `loadRun` where it used to read `loadSnapshot`. A double
910
- that simulates a condition — a run drifting to another owner, a stale fencing
911
- token — must apply it to both reads, or the code under test will not see it.
912
- This is not hypothetical: it turned one of this repository's own fixtures into
913
- an infinite retry loop, which is how the bounded retry below was found.
914
-
915
- #### Nothing else changes
916
-
917
- `loadSnapshot` behaves exactly as before, and still returns the whole
918
- conversation — as does every mutation result. What it costs, and what bounds it,
919
- is now written down in *Reading a conversation* in the agent-runtime guide.
920
-
921
- ### One provenance vocabulary, and integral tokens
922
-
923
- #### If you match on `'measured'`
924
-
925
- A **total** now says `computed`, because it is a sum this code performed rather
926
- than a count it took — the same rule `AgentUsage` has always applied to a run's
927
- spend. Two values change with no compile error:
928
-
929
- ```ts
930
- // before
931
- if (result.totalTokens.provenance === 'measured') { /* exact */ }
932
- // after
933
- if (result.totalTokens.provenance === 'computed') { /* exact, and derived */ }
934
- ```
935
-
936
- It affects `AgentHistoryBudgetResult.totalTokens` and
937
- `ComposedAgentPrompt.instructionTokens`. A per-message or per-section count is
938
- still `measured` — only the totals moved. When any part was estimated the total
939
- is still `estimated`: an estimate survives arithmetic, and that is the weaker
940
- claim, so it wins.
941
-
942
- #### If you produce token counts
943
-
944
- They are validated now, in the places they were not. A fractional count throws
945
- where it used to flow into the context-window arithmetic:
946
-
947
- ```ts
948
- // refused from this release on
949
- estimateTokens: (text) => ({ value: text.length / 4, provenance: 'estimated' })
950
- // after
951
- estimateTokens: (text) => ({ value: Math.ceil(text.length / 4), provenance: 'estimated' })
952
- ```
953
-
954
- The same applies to `ComposeAgentPromptOptions.estimateFallback` and
955
- `.historyTokens`, to `AgentPromptBudget.toolSchemas` / `.attachments` /
956
- `.providerOverhead`, and to `AgentPromptBudget.contextWindow` /
957
- `.reservedOutput`, which must now be non-negative safe integers.
958
-
959
- `AgentUsageValue.value` is `z.int()` too, which matters if you build usage
960
- records by hand or in a store double. A figure arriving from a **provider** is
961
- not thrown — `normalizeSdkUsage` and the OpenRouter adapter turn a non-integer
962
- into `{ provenance: 'unavailable' }`, so a run that already answered is not
963
- failed over its own bookkeeping.
964
-
965
- #### The new export
966
-
967
- `AgentProvenanceSchema` / `AgentProvenance` is the whole vocabulary:
968
- `provider-reported`, `measured`, `computed`, `estimated`, `unavailable`. Each
969
- surface declares its subset, so nothing widened — `AgentUsageValue` still refuses
970
- `measured` and `AgentTokenCount` still refuses `provider-reported`. Use it when
971
- you want one switch over the question instead of two.
972
-
973
- ## Released migration: 0.65.0
974
-
975
- The largest migration of the pre-1.0 line, and most of it is the compiler
976
- pointing at things. One item is a feature withdrawal and two change behaviour
977
- with no compile error at all.
978
-
979
- ### A projection a provider accepts, and a policy withdrawn
980
-
981
- ### If you compact, or use `system-note`, you were broken and now are not
982
-
983
- No code change to adopt the fix — but **check your own code if you call
984
- `projectAgentHistory` yourself.** System and summary records no longer appear in
985
- `messages`; they come back in `system` and belong in the provider's instructions
986
- channel, which is the only place `ai` accepts them:
987
-
988
- ```ts
989
- // before — the provider refuses this outright
990
- const messages = await projectAgentHistory(snapshot.messages)
991
- streamText({ model, messages })
992
-
993
- // after
994
- const { messages, system } = await projectAgentHistoryDetailed(snapshot.messages)
995
- streamText({
996
- model,
997
- instructions: system.map((content) => ({ role: 'system', content })),
998
- messages,
999
- })
1000
- ```
1001
-
1002
- If you use `createAgentRuntime`, this is handled for you.
1003
-
1004
- ### `inputPolicy: 'inject'` is withdrawn
1005
-
1006
- ```ts
1007
- // before
1008
- runs: { inputPolicy: 'inject' }
1009
- // after
1010
- runs: { inputPolicy: 'queue' }
1011
- ```
1012
-
1013
- `'absorbed'`, `AgentRun.absorbedIntoRunId` and `AgentRuntimeStore.absorbQueuedRun`
1014
- go with it. A driver built on `AgentRuntimeStoreDriver` needs no change.
1015
-
1016
- ### Records must agree with themselves
1017
-
1018
- If you construct `AgentRun` values — a store double, a fixture, a migration —
1019
- derive the state instead of setting it:
1020
-
1021
- ```ts
1022
- // after
1023
- import { runStateForTerminalReason } from 'stitchkit/agent-runtime'
1024
- AgentRunSchema.parse({ …run, terminalReason: reason, state: runStateForTerminalReason(reason) })
1025
- ```
1026
-
1027
- A terminal state with no reason, a queued run carrying one, and `policy_stop`
1028
- without a `terminalPolicyName` are now all refused at parse time.
1029
-
1030
- ### Enum and field changes the compiler will point at
1031
-
1032
- - `AgentTerminalReason`: `'tool_failure'` removed (never produced),
1033
- `'context_overflow'` added — this runtime's own refusal when the prompt does
1034
- not fit, which used to report `provider_failure`.
1035
- - `AgentRunState`: `'absorbed'` removed.
1036
- - `AgentRunMetrics.usage` is required.
1037
- - `AgentHistoryBudgetDecision['reason']`: `'superseded'` → `'unspeakable'`.
1038
-
1039
- ### Two behaviour changes with no compile error
1040
-
1041
- - **`loop.idleTimeoutMs` now defaults to 60 000.** A run whose provider stream
1042
- goes quiet for a minute ends as `timeout` instead of holding the lane forever.
1043
- Pass `null` for the old behaviour, and think about why you want it.
1044
- - **`advanceAgentRuntimeEventCursor` stops returning `gap` for durable events.**
1045
- If you reload a conversation on `gap`, you were reloading after essentially
1046
- every run. Transient events still report it, and there it is real.
1047
-
1048
- ## Released migration: 0.64.0
1049
-
1050
- Two changes, and only one of them can break a build. Nothing was removed.
1051
-
1052
- ### An event that says which kind it is
1053
-
1054
- ### Narrow `AgentRunEvent` on `type`
1055
-
1056
- ```ts
1057
- // before
1058
- for (const event of events) record(event.usage?.cost?.value ?? 0)
1059
-
1060
- // after — and the compiler will point at every site
1061
- for (const event of events) {
1062
- if (event.type !== 'run-terminal') continue
1063
- record(event.usage.cost.value ?? null) // `usage` is present; unknown says so
1064
- }
1065
- ```
1066
-
1067
- `step` is now required on `step-finished`, `terminalReason` on `run-terminal`,
1068
- and `usage` on both. `queueWaitMs` exists only on `run-started`. Nothing was
1069
- removed — the fields that were optional because a *different* kind of event
1070
- lacked them are now simply on the kinds that have them.
1071
-
1072
- Import `AgentRunTerminalEventSchema` (or the sibling schemas) if you construct
1073
- events in tests.
1074
-
1075
- ### If you implement `AgentRuntimeStore` directly, move to the driver
1076
-
1077
- Not urgent and nothing breaks today — but the aggregate is no longer the
1078
- supported target, so its future growth will not be announced as breaking
1079
- (→ ADR 0111). The supported shape is one line:
1080
-
1081
- ```ts
1082
- const driver: AgentRuntimeStoreDriver<TransactionClient> = { /* six primitives */ }
1083
- export const store = createAgentRuntimeStore(driver)
1084
- ```
1085
-
1086
- Run `runAgentStoreConformance` against your adapter before and after any bump —
1087
- see *Before you bump, if you implement an agent store* above.
1088
-
1089
- ## Released migration: 0.63.0
1090
-
1091
- Four changes to what a running system reports and how an input reaches a run
1092
- in flight. Nothing moves an export except one added store member, and only an
1093
- application implementing `AgentRuntimeStore` directly has to touch code.
1094
-
1095
- ### A spend figure that survives, and an input that joins
1096
-
1097
- ### `AgentRuntimeStore` has a ninth member
1098
-
1099
- Only if you implement the aggregate interface directly — an adapter built on
1100
- `AgentRuntimeStoreDriver` needs no change, and the public conformance kit covers
1101
- the new operation.
1102
-
1103
- ```ts
1104
- // after
1105
- absorbQueuedRun(input: AbsorbQueuedRun): Promise<AgentStoreMutationResult>
1106
- ```
1107
-
1108
- It moves a queued successor's inputs into the run already answering, in one
1109
- mutation, and marks the successor `absorbed`. Two run records change in one
1110
- transaction — if your driver persists them one at a time, persist both.
1111
-
1112
- ### A provider failure says so
1113
-
1114
- ```ts
1115
- // before — an upstream error arrived as a policy stop with no policy
1116
- if (terminal.reason === 'policy_stop') retryLater()
1117
-
1118
- // after
1119
- if (terminal.reason === 'provider_failure') retryLater()
1120
- if (terminal.reason === 'provider_stop') { /* a length cap or content filter */ }
1121
- ```
1122
-
1123
- `policy_stop` now always carries the `policyName` that caused it. If you switch
1124
- exhaustively on `AgentTerminalReason` or `AgentRunState`, add `'provider_stop'`
1125
- and `'absorbed'`.
1126
-
1127
- ### `partial` changed meaning
1128
-
1129
- It used to tell you which event kind you were holding. It now tells you whether
1130
- the figure beside it is a confirmed total:
1131
-
1132
- ```ts
1133
- // after — true when the provider never reported the run finished
1134
- if (metrics.partial) treatAsFloor(metrics.usage)
1135
- ```
1136
-
1137
- Terminal events for runs that were superseded, interrupted, timed out or failed
1138
- before the provider finished now report `partial: true` where they reported
1139
- `false`.
1140
-
1141
- ### Checkpoint metrics are a running total
1142
-
1143
- ```ts
1144
- // wrong, and now wrong by more than it used to be
1145
- const spent = checkpoints.reduce((total, c) => total + (c.metrics.usage?.cost?.value ?? 0), 0)
1146
- // right
1147
- const spent = checkpoints.at(-1)?.metrics.usage?.cost?.value
1148
- ```
1149
-
1150
- ### You can read a run's spend back from the store
1151
-
1152
- `AgentRun.usage` is written at every checkpoint and with the terminal record, so
1153
- a dropped observability event no longer loses the number, and a process that dies
1154
- mid-stream leaves behind what it had already spent.