agentfootprint 9.102.0 → 9.104.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 (154) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +210 -88
  3. package/CLAUDE.md +3 -2
  4. package/ai-instructions/claude-code/SKILL.md +1 -1
  5. package/dist/classify/index.js +20 -0
  6. package/dist/classify/index.js.map +1 -0
  7. package/dist/classify/mock.js +44 -0
  8. package/dist/classify/mock.js.map +1 -0
  9. package/dist/classify/types.js +47 -0
  10. package/dist/classify/types.js.map +1 -0
  11. package/dist/classify/typesafe.js +249 -0
  12. package/dist/classify/typesafe.js.map +1 -0
  13. package/dist/core/Agent.js +18 -1
  14. package/dist/core/Agent.js.map +1 -1
  15. package/dist/core/agent/AgentBuilder.js +33 -2
  16. package/dist/core/agent/AgentBuilder.js.map +1 -1
  17. package/dist/core/agent/findings/judge.js +173 -0
  18. package/dist/core/agent/findings/judge.js.map +1 -0
  19. package/dist/core/agent/findings/ledger.js +50 -6
  20. package/dist/core/agent/findings/ledger.js.map +1 -1
  21. package/dist/core/agent/findings/reserved.js +23 -1
  22. package/dist/core/agent/findings/reserved.js.map +1 -1
  23. package/dist/core/agent/findings/serve.js +15 -4
  24. package/dist/core/agent/findings/serve.js.map +1 -1
  25. package/dist/core/agent/findings/types.js +8 -1
  26. package/dist/core/agent/findings/types.js.map +1 -1
  27. package/dist/core/agent/stages/callLLM.js +1 -1
  28. package/dist/core/agent/stages/callLLM.js.map +1 -1
  29. package/dist/core/agent/stages/seed.js +9 -0
  30. package/dist/core/agent/stages/seed.js.map +1 -1
  31. package/dist/core/agent/stages/toolCalls.js +65 -0
  32. package/dist/core/agent/stages/toolCalls.js.map +1 -1
  33. package/dist/doors/classify.js +52 -0
  34. package/dist/doors/classify.js.map +1 -0
  35. package/dist/doors/skill-graph.js +9 -1
  36. package/dist/doors/skill-graph.js.map +1 -1
  37. package/dist/esm/classify/index.d.ts +13 -0
  38. package/dist/esm/classify/index.js +14 -0
  39. package/dist/esm/classify/index.js.map +1 -0
  40. package/dist/esm/classify/mock.d.ts +26 -0
  41. package/dist/esm/classify/mock.js +40 -0
  42. package/dist/esm/classify/mock.js.map +1 -0
  43. package/dist/esm/classify/types.d.ts +111 -0
  44. package/dist/esm/classify/types.js +43 -0
  45. package/dist/esm/classify/types.js.map +1 -0
  46. package/dist/esm/classify/typesafe.d.ts +58 -0
  47. package/dist/esm/classify/typesafe.js +245 -0
  48. package/dist/esm/classify/typesafe.js.map +1 -0
  49. package/dist/esm/core/Agent.d.ts +3 -1
  50. package/dist/esm/core/Agent.js +18 -1
  51. package/dist/esm/core/Agent.js.map +1 -1
  52. package/dist/esm/core/agent/AgentBuilder.d.ts +6 -1
  53. package/dist/esm/core/agent/AgentBuilder.js +33 -2
  54. package/dist/esm/core/agent/AgentBuilder.js.map +1 -1
  55. package/dist/esm/core/agent/findings/judge.d.ts +84 -0
  56. package/dist/esm/core/agent/findings/judge.js +168 -0
  57. package/dist/esm/core/agent/findings/judge.js.map +1 -0
  58. package/dist/esm/core/agent/findings/ledger.d.ts +16 -6
  59. package/dist/esm/core/agent/findings/ledger.js +50 -6
  60. package/dist/esm/core/agent/findings/ledger.js.map +1 -1
  61. package/dist/esm/core/agent/findings/reserved.d.ts +14 -0
  62. package/dist/esm/core/agent/findings/reserved.js +22 -0
  63. package/dist/esm/core/agent/findings/reserved.js.map +1 -1
  64. package/dist/esm/core/agent/findings/serve.d.ts +19 -4
  65. package/dist/esm/core/agent/findings/serve.js +15 -4
  66. package/dist/esm/core/agent/findings/serve.js.map +1 -1
  67. package/dist/esm/core/agent/findings/types.d.ts +72 -1
  68. package/dist/esm/core/agent/findings/types.js +7 -0
  69. package/dist/esm/core/agent/findings/types.js.map +1 -1
  70. package/dist/esm/core/agent/stages/callLLM.d.ts +9 -1
  71. package/dist/esm/core/agent/stages/callLLM.js +1 -1
  72. package/dist/esm/core/agent/stages/callLLM.js.map +1 -1
  73. package/dist/esm/core/agent/stages/seed.d.ts +10 -0
  74. package/dist/esm/core/agent/stages/seed.js +9 -0
  75. package/dist/esm/core/agent/stages/seed.js.map +1 -1
  76. package/dist/esm/core/agent/stages/toolCalls.d.ts +15 -0
  77. package/dist/esm/core/agent/stages/toolCalls.js +42 -0
  78. package/dist/esm/core/agent/stages/toolCalls.js.map +1 -1
  79. package/dist/esm/core/agent/types.d.ts +48 -0
  80. package/dist/esm/doors/classify.d.ts +35 -0
  81. package/dist/esm/doors/classify.js +36 -0
  82. package/dist/esm/doors/classify.js.map +1 -0
  83. package/dist/esm/doors/skill-graph.d.ts +2 -0
  84. package/dist/esm/doors/skill-graph.js +5 -0
  85. package/dist/esm/doors/skill-graph.js.map +1 -1
  86. package/dist/esm/events/payloads.d.ts +50 -0
  87. package/dist/esm/events/registry.d.ts +5 -1
  88. package/dist/esm/events/registry.js +4 -0
  89. package/dist/esm/events/registry.js.map +1 -1
  90. package/dist/esm/index.d.ts +1 -1
  91. package/dist/esm/index.js.map +1 -1
  92. package/dist/esm/lib/injection-engine/classifierScorer.d.ts +44 -0
  93. package/dist/esm/lib/injection-engine/classifierScorer.js +91 -0
  94. package/dist/esm/lib/injection-engine/classifierScorer.js.map +1 -0
  95. package/dist/esm/lib/injection-engine/index.d.ts +1 -0
  96. package/dist/esm/lib/injection-engine/index.js +1 -0
  97. package/dist/esm/lib/injection-engine/index.js.map +1 -1
  98. package/dist/esm/lib/time-travel/servedView.js +15 -3
  99. package/dist/esm/lib/time-travel/servedView.js.map +1 -1
  100. package/dist/events/registry.js +4 -0
  101. package/dist/events/registry.js.map +1 -1
  102. package/dist/index.js.map +1 -1
  103. package/dist/lib/injection-engine/classifierScorer.js +95 -0
  104. package/dist/lib/injection-engine/classifierScorer.js.map +1 -0
  105. package/dist/lib/injection-engine/index.js +6 -2
  106. package/dist/lib/injection-engine/index.js.map +1 -1
  107. package/dist/lib/time-travel/servedView.js +15 -3
  108. package/dist/lib/time-travel/servedView.js.map +1 -1
  109. package/dist/types/classify/index.d.ts +14 -0
  110. package/dist/types/classify/index.d.ts.map +1 -0
  111. package/dist/types/classify/mock.d.ts +27 -0
  112. package/dist/types/classify/mock.d.ts.map +1 -0
  113. package/dist/types/classify/types.d.ts +112 -0
  114. package/dist/types/classify/types.d.ts.map +1 -0
  115. package/dist/types/classify/typesafe.d.ts +59 -0
  116. package/dist/types/classify/typesafe.d.ts.map +1 -0
  117. package/dist/types/core/Agent.d.ts +3 -1
  118. package/dist/types/core/Agent.d.ts.map +1 -1
  119. package/dist/types/core/agent/AgentBuilder.d.ts +6 -1
  120. package/dist/types/core/agent/AgentBuilder.d.ts.map +1 -1
  121. package/dist/types/core/agent/findings/judge.d.ts +85 -0
  122. package/dist/types/core/agent/findings/judge.d.ts.map +1 -0
  123. package/dist/types/core/agent/findings/ledger.d.ts +16 -6
  124. package/dist/types/core/agent/findings/ledger.d.ts.map +1 -1
  125. package/dist/types/core/agent/findings/reserved.d.ts +14 -0
  126. package/dist/types/core/agent/findings/reserved.d.ts.map +1 -1
  127. package/dist/types/core/agent/findings/serve.d.ts +19 -4
  128. package/dist/types/core/agent/findings/serve.d.ts.map +1 -1
  129. package/dist/types/core/agent/findings/types.d.ts +72 -1
  130. package/dist/types/core/agent/findings/types.d.ts.map +1 -1
  131. package/dist/types/core/agent/stages/callLLM.d.ts +9 -1
  132. package/dist/types/core/agent/stages/callLLM.d.ts.map +1 -1
  133. package/dist/types/core/agent/stages/seed.d.ts +10 -0
  134. package/dist/types/core/agent/stages/seed.d.ts.map +1 -1
  135. package/dist/types/core/agent/stages/toolCalls.d.ts +15 -0
  136. package/dist/types/core/agent/stages/toolCalls.d.ts.map +1 -1
  137. package/dist/types/core/agent/types.d.ts +48 -0
  138. package/dist/types/core/agent/types.d.ts.map +1 -1
  139. package/dist/types/doors/classify.d.ts +36 -0
  140. package/dist/types/doors/classify.d.ts.map +1 -0
  141. package/dist/types/doors/skill-graph.d.ts +2 -0
  142. package/dist/types/doors/skill-graph.d.ts.map +1 -1
  143. package/dist/types/events/payloads.d.ts +50 -0
  144. package/dist/types/events/payloads.d.ts.map +1 -1
  145. package/dist/types/events/registry.d.ts +5 -1
  146. package/dist/types/events/registry.d.ts.map +1 -1
  147. package/dist/types/index.d.ts +1 -1
  148. package/dist/types/index.d.ts.map +1 -1
  149. package/dist/types/lib/injection-engine/classifierScorer.d.ts +45 -0
  150. package/dist/types/lib/injection-engine/classifierScorer.d.ts.map +1 -0
  151. package/dist/types/lib/injection-engine/index.d.ts +1 -0
  152. package/dist/types/lib/injection-engine/index.d.ts.map +1 -1
  153. package/dist/types/lib/time-travel/servedView.d.ts.map +1 -1
  154. package/package.json +14 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,122 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [9.104.0] - 2026-09-17
9
+
10
+ ### Added — a calibrated judge beside the model's own standings
11
+
12
+ - **`agentfootprint/classify`** — a new door for a classifier that SCORES
13
+ declared candidates instead of generating text: the `Classifier` port
14
+ (`{ name; classify(request, signal?) }`), the request shapes (`choice` over
15
+ declared options, `noul` as a probability, `score` on a declared scale),
16
+ the answer shapes (the pick, its confidence and the WHOLE distribution),
17
+ `ClassifierError` (`status?`, `retryable`), and two adapters —
18
+ `typesafe()` (TypeSafe "System One", model `jev-latest`; key from
19
+ `TYPESAFE_API_KEY` or `apiKey`, refused at construction when missing; one
20
+ `fetch`, no SDK; exponential backoff on 429/529 up to `maxRetries`;
21
+ `latencyMs` measured around the whole call; the wire mapped field for
22
+ field, probabilities never renormalised, the key never in an error) and
23
+ `mockClassifier(script)` for tests and the bench (every request on
24
+ `calls`). Verified 2026-09-17 by one real call; the suite maps that probe
25
+ against a stubbed `fetch` and never calls the hosted classifier.
26
+ - **`.findings({ judge })`** — a SECOND SOURCE on the findings ledger. After
27
+ every tool result lands and before the next model call, the judge is
28
+ asked what the RESULT is worth for the proposition the model declared on
29
+ the call, or for the user's question when none was declared (never why
30
+ the tool was called), and its answer is filed as a `JudgmentRow` beside
31
+ the model's own `StandingRow`: `source: 'judge'`, the judge's name and
32
+ the provider's model string, `against: 'proposition' | 'question'`, the
33
+ standing, the distribution, the confidence, `testsSubject`, usage,
34
+ latency, `clipped` when the result was cut at 4000 characters, the
35
+ iteration. A failed call is a `JudgmentErrorRow` (status, the provider's
36
+ message, latency) — never a guessed standing — and the run continues.
37
+ `foldLedger` gains `judgments` (last wins) and leaves `standingOf` the
38
+ model's; nothing is served from a judgment in this release (policy A —
39
+ the design page names B–D and leaves them to the bench). The probe's
40
+ disagreement — the model said `ruled-out`, the judge said `noise` at
41
+ 0.69 — is the first recorded fact of the second source. Root exports
42
+ `JudgmentRow` / `JudgmentErrorRow`; `agent.findings()` returns the rows
43
+ as before.
44
+ - **Events** `agentfootprint.findings.judged` (`toolCallId`, `toolName`,
45
+ `iteration`, `against`, `standing`, `confidence`, `latencyMs`,
46
+ `inputTokens?`, `outputTokens?`) and `agentfootprint.findings.judge_failed`
47
+ (`toolCallId`, `toolName`, `iteration`, `status?`, `latencyMs`) —
48
+ identities, enums and numbers, never the state; and
49
+ `agentfootprint.findings.standing` gains `agrees?: boolean`, the model's
50
+ standing against the judge's current judgment of the same result, present
51
+ exactly when a judgment row exists (the judge files before the model call
52
+ that declares, so the standing event is where both readings exist). 115
53
+ typed events across 25 domains.
54
+ - Only a result the tool PRODUCED is judged: a permission denial, a halt, a
55
+ fail-closed refusal or a declined check-in files no judgment row and
56
+ spends no classifier call — a denial text is not a tool result.
57
+ - `typesafe()` refuses a reply without the provider's `model` string or an
58
+ answer whose numeric field (`confidence`, `score`, `noul`) is missing or
59
+ not a finite number, as a `ClassifierError` — never `'unknown'` or `NaN`
60
+ on the record; a per-attempt `timeout` ends the call after one attempt,
61
+ not retryable.
62
+ - **`classifierScorer(classifier)`** on `agentfootprint/skill-graph` — an
63
+ `EntryScorer` whose ranking IS the provider's distribution (the scored
64
+ choice, design step 2): one `choice` question over the entry candidates
65
+ with `{ id: description }` as criteria, `score` and `relevance` both the
66
+ probability as sent (a candidate the provider did not score is absent
67
+ from the ranking, never a padded 0), `chosen` the provider's own pick,
68
+ `scorer: 'classifier:<model>'`. A provider failure returns `scorer:
69
+ 'classifier:unavailable'` with an empty ranking and no `chosen`, and the
70
+ existing cold-start entry pick takes over — never a guess.
71
+ - **Bench** `bench/findings-shuffle.mjs`: `AF_SHUFFLE_JUDGE=mock|typesafe`
72
+ arms every armed condition with a judge and adds `judge-accuracy`,
73
+ `judge-agrees`, `judge-tokens` and `judge-latency-ms`, with the classifier
74
+ calls stated on the cost line before the first is made; the mock judge
75
+ scripts the planted truth at a fixed confidence and the smoke laws pin
76
+ it. Unset, the bench prints the 9.103.0 table byte for byte.
77
+ - Byte identity: the 17 references pass untouched; `agent-findings-judge`
78
+ is the one new reference, generated alone with a scripted judge — its
79
+ delta over `agent-findings` is the two judgment rows and the declared
80
+ proposition, and nothing under `served.*`.
81
+
82
+ ## [9.103.0] - 2026-09-17
83
+
84
+ ### Added — an answer-turn ask on the served ledger piece, as a dial
85
+
86
+ - `findings({ answerAsk: 'quote-facts' })` appends a second model-facing ask
87
+ to the served ledger piece — `FINDINGS_ANSWER_ASK`, its last section after
88
+ a blank line — telling the model HOW to answer from it: answer from the
89
+ `facts` lines and copy each value as written; an `evidenceRefs` or
90
+ `nextSteps` result is unsettled, say so; a `{"collapsed":true,…}` ticket
91
+ carries no data (judged noise or ruled out, or its fact already listed),
92
+ do not draw on it; an undeclared result is
93
+ served in full and may be used; never invent a value. It says what the
94
+ model may do and promises nothing (judged by `unprovable` at the strictest
95
+ lifetime, registered in the model-facing inventory), and it is a constant,
96
+ so the piece's cache law holds with it. The default is `'none'` and the
97
+ dial is BENCH-GATED like `'ledger-only'`: it ships so
98
+ `bench/findings-shuffle.mjs`'s new fourth condition, `ledger+ask`, can
99
+ score it on a real model on `facts-in-answer` against `ledger-and-facts` —
100
+ the design page's fourth run measured one fact value in twelve restated
101
+ when the piece is served, and asked for this instruction variant next. On
102
+ the mock that row equals `ledger-and-facts` to the digit (the scripted
103
+ model ignores prose; the header says so). The dial rides the
104
+ `findingsServe` thread exactly — `Agent.ts` to seed and to call-llm,
105
+ value-conditionally — and lands on the record as the run constant
106
+ `AgentState.findingsAnswerAsk`, written ONLY under `'quote-facts'`, so an
107
+ armed agent on the default commits the key set it committed in 9.102.0,
108
+ every unarmed agent is byte-identical, and `servedAt` re-appends the ask
109
+ from the record by construction. A bad value is refused at build at either
110
+ door. Pinned: `findings/serve.test.ts` (the ask appended only under the
111
+ dial, exact bytes, absent by default, never alone), `findings-served.test.ts`
112
+ (both chart shapes end the served system text with the ask; without the
113
+ dial byte-identical), `findings-declarations.test.ts` (the option, the run
114
+ constant only when armed and on, the refusal), `receipt-conformance.test.ts`
115
+ (the rebuild byte-equal under the dial); all 18 byte-identity references
116
+ pass untouched.
117
+
118
+ ### Measured — the ask on two hosted models (`bench/findings-shuffle.mjs`, noise 4 at the end of the order, 1,000-token payloads, ten runs per condition)
119
+
120
+ - Claude Sonnet 5: facts in the answer 0.883 with the ledger alone → 0.950 with the ask (1.000 without the ledger); noise cited 0.100 with either → 0.300 without the ledger; drift 0.70 → 0.40.
121
+ - Claude Haiku 4.5: facts 0.983 → 1.000 with the ask; noise cited 0.000 (0.100 without the ledger); drift 0.30 → 0.20.
122
+ - Collapsing facts too (`serve: 'ledger-only'`) was the worst row on both models and stays bench-gated. Full tables: docs/design/2026-09-findings-ledger-real-model.md.
123
+
8
124
  ## [9.102.0] - 2026-09-17
9
125
 
10
126
  ### Added — a declared fact stays in the window; noise leaves first
@@ -28,7 +144,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
28
144
  the refusal engine saw a declared fact and a declared noise result as the
29
145
  same bytes. By the model's claim only: a turn is the removal unit and its
30
146
  standing is its most valuable result's (`fact > open > undeclared >
31
- ruled-out > noise`); a result the model never named is undeclared and is not
147
+ ruled-out > noise`); a result the model never named is undeclared and is not
32
148
  held; the library reads no result's text to decide otherwise.
33
149
  - A fact hold never exists without its ceiling and its stand-down. The
34
150
  ceiling is spent newest first and a held turn already inside
@@ -96,7 +212,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
96
212
  So from the second call on, the reserved `_findings` property on every
97
213
  served schema binds the ids the model may name: `previous[].toolCallId`
98
214
  carries `enum: <the tool results on the wire with no standing yet, newest
99
- first, at most 32>` and says "one of the ids listed; a result not listed
215
+ first, at most 32>` and says "one of the ids listed; a result not listed
100
216
  cannot be named here"; a clipped list states the cap. The list is what the
101
217
  model can still READ: a result with no standing, a `fact` (stood on in the
102
218
  piece, served verbatim under the default mode) and an `open` result (served
@@ -119,7 +235,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
119
235
  `receipt.tools.schemaHashes` moves when the offer does. The law holds at
120
236
  the schema AND at the row: the offer is what the model may COPY, never what
121
237
  the library resolves — an id outside it still files as written, `unknownId:
122
- true`, never mapped to a position or a tool name — and every id INSIDE it
238
+ true`, never mapped to a position or a tool name — and every id INSIDE it
123
239
  resolves, because a standing is identified against the same served history
124
240
  the offer was read from (`findings/offer.ts · knownResults`: the served
125
241
  `role: 'tool'` messages plus the previous batch), so an id copied from the
@@ -172,7 +288,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
172
288
 
173
289
  - Since 9.101.0, `withoutFindingsArgument` recognised the library's
174
290
  decoration by REFERENCE only (`properties._findings ===
175
- FINDINGS_ARGUMENT_SCHEMA`), and the reference never holds on the live path:
291
+ FINDINGS_ARGUMENT_SCHEMA`), and the reference never holds on the live path:
176
292
  the served list `callLLM` reads is the committed `dynamicToolSchemas`, a
177
293
  `structuredClone` of what the slot planted. So the choice seam's enum fence
178
294
  (`declaredEnumValuesOf(withoutFindingsArgument(schema))`, the
@@ -187,7 +303,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
187
303
  written. An armed agent may therefore file an `unsupported-argument`
188
304
  finding it previously suppressed. Its unit test had passed because it
189
305
  handed the function the live reference; `test/core/agent/findings/
190
- reserved.test.ts` now pins the clone.
306
+ reserved.test.ts` now pins the clone.
191
307
 
192
308
  ### Unchanged — the unarmed agent, and the two armed references regenerated alone
193
309
 
@@ -284,7 +400,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
284
400
  `open`. Bounded, every overflow stated, an empty bucket omitted. On the
285
401
  wire, and only on the wire, a tool result the model judged `noise` or
286
402
  `ruled-out` is served as a ticket, `{"collapsed":true,"standing":…,
287
- "toolCallId":…}`, in place of its content; `open` and undeclared results
403
+ "toolCallId":…}`, in place of its content; `open` and undeclared results
288
404
  stay verbatim; nothing is dropped or reordered, and `toolName` /
289
405
  `toolCallId` are untouched, so the tool_use/tool_result pair stays
290
406
  wire-valid. `history` never changes — the window stage stays its only
@@ -578,7 +694,7 @@ footprintjs the range admits (`^9.21.1`).
578
694
  touched before); `cache-transform`'s "that gap is the stronger claim" is
579
695
  measured for every (gap, shared field) pairing that occurs beside it on
580
696
  every fixture view, intact and damaged, against that gap's own claim (an
581
- unmeasured kind fails by name; "stronger" is *may be short*, ≤ — on a fresh
697
+ unmeasured kind fails by name; "stronger" is _may be short_, ≤ — on a fresh
582
698
  run with its base stripped the rebuild recovers everything);
583
699
  `forced-tool-schema`'s "the tool list is complete" runs on both chart
584
700
  shapes. The design page marks the three built; the general statement — a
@@ -635,7 +751,7 @@ footprintjs the range admits (`^9.21.1`).
635
751
  (`servedView.ts` · `readReceipt`), which checks `basis.epoch` and nothing
636
752
  else. A stored receipt with a `basis` and no `cache` container therefore
637
753
  made `servedAt(snapshot, k)` throw `Cannot read properties of undefined
638
- (reading 'strategy')` where 9.92 built a view. WHY it matters: a recording is
754
+ (reading 'strategy')` where 9.92 built a view. WHY it matters: a recording is
639
755
  older than the reader that opens it, and every recording a consumer already
640
756
  held was minted by the release that minted it — the lens found this the day
641
757
  after 9.93.0 shipped, on a recording it already had. That breaks the family's
@@ -646,7 +762,7 @@ footprintjs the range admits (`^9.21.1`).
646
762
  the check does not verify declared optional (`cache?`, and `strategy?`
647
763
  within it) — and `receiptAt` answers that type, so the compiler holds every
648
764
  reader to it; `viewOf` reads the strategy through it (`receipt?.cache?.
649
- strategy !== null`). A receipt with no `cache` yields a view, its
765
+ strategy !== null`). A receipt with no `cache` yields a view, its
650
766
  `cache-transform` gap IS raised (the 9.93 rule: absence still raises; only a
651
767
  receipt SAYING `null` lifts it), and its `basis` and every hash row it
652
768
  carries still verify. Nothing is repaired: no `cache: {}` is fabricated and
@@ -656,9 +772,9 @@ footprintjs the range admits (`^9.21.1`).
656
772
  `omittedForAttention`) sits behind the same narrowing and the same type; no
657
773
  second reader existed in the library. `Receipt` itself is unchanged — it is
658
774
  the MINTED shape and a mint still writes every container.
659
- THE VINTAGE LAW, now in `src/lib/time-travel/README.md`: *a reader reads
775
+ THE VINTAGE LAW, now in `src/lib/time-travel/README.md`: _a reader reads
660
776
  the receipt it is handed; a missing container is a fact about the vintage,
661
- never a throw.* `test/lib/time-travel/receipt-vintage.test.ts` ages real
777
+ never a throw._ `test/lib/time-travel/receipt-vintage.test.ts` ages real
662
778
  recordings three ways — no `cache` (the shape the lens hit), `cache` without
663
779
  `strategy` (the 9.88.0–9.92.1 mint, byte for byte), `strategy: null` (the
664
780
  9.93.0 mint on a chart with no strategy) — and requires a view, the gap, the
@@ -669,8 +785,8 @@ footprintjs the range admits (`^9.21.1`).
669
785
  `receiptAt(s, k)!.cache.transform` unguarded must guard the container — the
670
786
  compiler now says on the page what the runtime said with a throw.
671
787
  ```ts
672
- const receipt = receiptAt(olderRecording, 1)!; // minted by 9.92
673
- receipt.cache?.strategy; // undefined — not null, not repaired
788
+ const receipt = receiptAt(olderRecording, 1)!; // minted by 9.92
789
+ receipt.cache?.strategy; // undefined — not null, not repaired
674
790
  servedAt(olderRecording, 1)!.gaps.map((g) => g.gap); // ['cache-transform', 'provider-defaults']
675
791
  ```
676
792
 
@@ -717,7 +833,7 @@ one iteration (below), and because every bundled consumer's output changes.
717
833
  catalogs and answers.
718
834
  ```ts
719
835
  const agent = Agent.create({ provider, model }).tool(lookup).selfExplain().build();
720
- await agent.run({ message: 'Refund order A-1001?' }); // pack not loaded
836
+ await agent.run({ message: 'Refund order A-1001?' }); // pack not loaded
721
837
  await agent.run({ message: 'Why did you approve it?' }); // loads on the iteration read_skill opened it
722
838
  ```
723
839
  - **The ESM build now carries `sideEffects`.** A bundler reads that flag from
@@ -785,7 +901,7 @@ window and no cache strategy records a different byte except the one new key.
785
901
  release has no key, and a reader treats that as "cannot say", never as
786
902
  `null`.
787
903
  ```ts
788
- receiptAt(agent.getSnapshot()!, 1)!.cache.strategy; // '*' — an agent on the mock provider
904
+ receiptAt(agent.getSnapshot()!, 1)!.cache.strategy; // '*' — an agent on the mock provider
789
905
  receiptAt(llmCall.getSnapshot()!, 1)!.cache.strategy; // null — nothing between assembly and the port
790
906
  ```
791
907
  - **`Receipt.omittedForAttention` is written** — by the agent chart's window
@@ -805,8 +921,11 @@ window and no cache strategy records a different byte except the one new key.
805
921
  `no-receipt-on-chart`, the one gap that can lose it. An agent without a
806
922
  window hands both stages the deps they always had.
807
923
  ```ts
808
- const agent = Agent.create({ provider, model }).system('bot').tool(lookup)
809
- .window(slidingWindow({ keepRecentTurns: 1 })).build();
924
+ const agent = Agent.create({ provider, model })
925
+ .system('bot')
926
+ .tool(lookup)
927
+ .window(slidingWindow({ keepRecentTurns: 1 }))
928
+ .build();
810
929
  await agent.run({ message: 'go' });
811
930
  receiptAt(agent.getSnapshot()!, 3)!.omittedForAttention; // { count: 2, hashes: [...] } — the pair that left at this head
812
931
  ```
@@ -924,7 +1043,7 @@ consequences, one example each:
924
1043
  (`ServedToolParties.lastServed`) or the name's only holder when it was never
925
1044
  served; every such dispatch is on the record as the new
926
1045
  `agentfootprint.tools.answered_off_wire` (`{ toolName, toolCallId,
927
- iteration, answeredBy, answeredById? }`). A pause that re-dispatches on
1046
+ iteration, answeredBy, answeredById? }`). A pause that re-dispatches on
928
1047
  resume (a middleware ask, a check-in, a credential consent) carries the
929
1048
  served party on its checkpoint (`pausedToolParty`, pause-path-only), so a
930
1049
  resume in a FRESH Agent instance — empty closure, no Compose — cannot fall
@@ -938,7 +1057,7 @@ consequences, one example each:
938
1057
  is named as itself.
939
1058
  - **A dead claim is reported.** New `agentfootprint.tools.claim_swallowed`
940
1059
  (`ToolsClaimSwallowedPayload`: `{ toolName, iteration, lostBy, lostById?,
941
- wonBy, wonById? }`, names only) fires once per iteration for every party
1060
+ wonBy, wonById? }`, names only) fires once per iteration for every party
942
1061
  whose claim to a name is held by somebody else — whether its contract
943
1062
  competed (an active skill against a provider) or never reached the merge
944
1063
  (the same skill while inactive; a provider tool whose name a static
@@ -1008,8 +1127,8 @@ Two reasons were on the record for that. **Neither survived.** The first — tha
1008
1127
  `Receipt.cache.transform` has no honest value on a chart running no cache
1009
1128
  strategy — was already refuted in 9.88.0 and left refuted in the source: an
1010
1129
  `Agent` with a pass-through strategy records `'unchanged'` today, and the value
1011
- is true for the same reason where no strategy exists at all, because *the
1012
- request that went out IS the request that was assembled*. **No fourth enum value
1130
+ is true for the same reason where no strategy exists at all, because _the
1131
+ request that went out IS the request that was assembled_. **No fourth enum value
1013
1132
  was added.** The second — the salt — never applied to `LLMCall`, which owns its
1014
1133
  executor and mints a run id exactly as `Agent` does. It DOES apply to the two
1015
1134
  messageAPI charts, and that was verified rather than assumed: footprintjs stamps
@@ -1052,8 +1171,8 @@ than salting every hash with an empty string.
1052
1171
  unnoticed for as long as no receipt existed to contradict it. The read is now
1053
1172
  a fallback chain (the agent key first, so no agent recording changes), the
1054
1173
  same shape the conversation has had since 9.88.0. Red before, on
1055
- `test/lib/time-travel/receipt-conformance.test.ts` · *the tool the model was
1056
- served is hashed by the receipt AND rebuilt from the log*.
1174
+ `test/lib/time-travel/receipt-conformance.test.ts` · _the tool the model was
1175
+ served is hashed by the receipt AND rebuilt from the log_.
1057
1176
 
1058
1177
  ### Changed
1059
1178
 
@@ -1061,7 +1180,7 @@ than salting every hash with an empty string.
1061
1180
  printed sentence never named a chart, so nothing in it went false; the
1062
1181
  MECHANISM comment beside it now names the shapes that still reach it — a
1063
1182
  messageAPI chart handed no run id, a run that declined with `recordReceipt:
1064
- false`, a recording made before 9.88.0, and a consumer's own `call-llm` stage,
1183
+ false`, a recording made before 9.88.0, and a consumer's own `call-llm` stage,
1065
1184
  which this library does not mint for. The cause `'no-receipt-committed'` is
1066
1185
  still driven by a REAL run in both walks; the driver moved from an `LLMCall`
1067
1186
  (which now mints) to a messageAPI chart with no run id.
@@ -1151,12 +1270,12 @@ import { milestoneTag, milestoneStopsStrategy } from 'agentfootprint';
1151
1270
 
1152
1271
  // A reader with NO agent id conventions — footprintjs's own strategy, our word:
1153
1272
  const turns = timeTravel(agent.getSnapshot()!, { strategy: tagStops([milestoneTag('llm-turn')]) });
1154
- turns.stops.map((s) => s.label); // ['Run start', 'CallLLM', 'CallLLM', 'Run end']
1155
- turns.stops[1].meta; // ['milestone:llm-turn', 'milestone-label:LLM turn']
1273
+ turns.stops.map((s) => s.label); // ['Run start', 'CallLLM', 'CallLLM', 'Run end']
1274
+ turns.stops[1].meta; // ['milestone:llm-turn', 'milestone-label:LLM turn']
1156
1275
 
1157
1276
  // The agent's own axis, unchanged in shape — tag first, id fallback:
1158
1277
  const all = timeTravel(agent.getSnapshot()!, { strategy: milestoneStopsStrategy });
1159
- all.stops[5].meta; // { kind: 'llm-turn', label: 'LLM turn' } — read off the bundle
1278
+ all.stops[5].meta; // { kind: 'llm-turn', label: 'LLM turn' } — read off the bundle
1160
1279
  ```
1161
1280
 
1162
1281
  ## [9.89.3] - 2026-09-10
@@ -1241,13 +1360,13 @@ for byte as before.
1241
1360
  const tool = flowchartAsTool({
1242
1361
  name: 'seeded',
1243
1362
  description: 'Seeds a subflow with the key and reads it there.',
1244
- flowchart: chart, // parent writes apiKey; the subflow is seeded with it
1363
+ flowchart: chart, // parent writes apiKey; the subflow is seeded with it
1245
1364
  keepRecord: true,
1246
1365
  redact: { keys: ['apiKey'] },
1247
1366
  });
1248
- await tool.execute({}, ctx); // → '{"apiKey":"REDACTED","seen":26}' — the subflow computed on the real 26-byte key
1367
+ await tool.execute({}, ctx); // → '{"apiKey":"REDACTED","seen":26}' — the subflow computed on the real 26-byte key
1249
1368
  const record = JSON.stringify(innerRunsOf(tool)!.get(ctx.toolCallId)!);
1250
- record.includes('sk-live-'); // false — result, log, mirror, stageReads, narrative Input: line, history[0], parent log
1369
+ record.includes('sk-live-'); // false — result, log, mirror, stageReads, narrative Input: line, history[0], parent log
1251
1370
  ```
1252
1371
 
1253
1372
  The law's other half is unchanged and now pinned in the same file: the live
@@ -1278,7 +1397,7 @@ changes, and a tool without `redact` behaves byte for byte as before.
1278
1397
  policy-redacted key never entered the inner commit log — and the option's
1279
1398
  own documentation drew the wrong conclusion from that. The live state view
1280
1399
  is not a commit: it is the run's raw heap, and its scrubbed twin (the
1281
- *redacted mirror* footprintjs maintains beside it) is served only by
1400
+ _redacted mirror_ footprintjs maintains beside it) is served only by
1282
1401
  `getSnapshot({ redact: true })`. Both tools called `getSnapshot()` bare, so
1283
1402
  the string the model read (`JSON.stringify(snapshot.values)`, or whatever a
1284
1403
  `resultMapper` built from it), the envelope's state, and a kept record's
@@ -1299,13 +1418,13 @@ changes, and a tool without `redact` behaves byte for byte as before.
1299
1418
  const tool = flowchartAsTool({
1300
1419
  name: 'weather_advice',
1301
1420
  description: 'Forecast tomorrow and advise on biking.',
1302
- flowchart: adviceChart, // writes scope.apiKey = 'sk-…'
1421
+ flowchart: adviceChart, // writes scope.apiKey = 'sk-…'
1303
1422
  keepRecord: true,
1304
1423
  redact: { keys: ['apiKey'] },
1305
1424
  });
1306
- await tool.execute({}, ctx); // → '{"apiKey":"REDACTED","advice":"bike"}'
1425
+ await tool.execute({}, ctx); // → '{"apiKey":"REDACTED","advice":"bike"}'
1307
1426
  const { snapshot } = innerRunsOf(tool)!.get(ctx.toolCallId)!.recording!;
1308
- snapshot.sharedState.apiKey; // 'REDACTED' — was the plaintext
1427
+ snapshot.sharedState.apiKey; // 'REDACTED' — was the plaintext
1309
1428
  JSON.stringify(snapshot).includes('sk-'); // false — in every field the log scrubs
1310
1429
  ```
1311
1430
 
@@ -1343,7 +1462,8 @@ both additive: a 9.88.0 consumer compiles and behaves identically.
1343
1462
  object a consumer already holds:
1344
1463
 
1345
1464
  ```ts
1346
- receiptHash(runId, toolDigestInput(servedAt(k).tools.schemas[i])) === receiptAt(k).tools.schemaHashes[name]
1465
+ receiptHash(runId, toolDigestInput(servedAt(k).tools.schemas[i])) ===
1466
+ receiptAt(k).tools.schemaHashes[name];
1347
1467
  ```
1348
1468
 
1349
1469
  It takes an `LLMToolSchema` — the tool as handed to the provider port, which
@@ -1371,7 +1491,7 @@ both additive: a 9.88.0 consumer compiles and behaves identically.
1371
1491
  all of it by hand against 9.17. This was one of them. The hand-rolled guard,
1372
1492
  the re-partition loop and `milestoneOf`'s re-derivation are gone;
1373
1493
  `milestoneStops` is now one expression, `filterStops(commitStops(log, tree),
1374
- keep)`, and the one owner of the `[start, …stages, end]` contract is the
1494
+ keep)`, and the one owner of the `[start, …stages, end]` contract is the
1375
1495
  library that returns it. Public names and signatures are unchanged
1376
1496
  (`milestoneOf`, `milestoneStops`, `milestoneStopsStrategy`); the stops are
1377
1497
  now typed `Stop<Milestone>`, so `cursor.at()?.meta?.kind` is typed, and
@@ -1501,14 +1621,14 @@ that file now exists. All of it is closed below, at the root, additively.
1501
1621
  strategy actually applied, three scalars each (`field`, `boundaryIndex`,
1502
1622
  `ttl`). `transformHash` is a digest over the whole prepared request: inside a
1503
1623
  run it says only "something changed", and across epochs it is not comparable
1504
- at all — so it could not answer *did the breakpoints move between call 3 and
1505
- call 4?*, which is the question that decides an Anthropic bill. Two receipts'
1624
+ at all — so it could not answer _did the breakpoints move between call 3 and
1625
+ call 4?_, which is the question that decides an Anthropic bill. Two receipts'
1506
1626
  `markersApplied` answer it by inspection.
1507
1627
  - **`receipt.cache.transform`** — `'unchanged' | 'rewritten' | 'unknown'`.
1508
1628
  Branch on this, never on `transformHash === null`.
1509
1629
  - **`RECEIPT_BOUNDARY`** — the one sentence every receipt field is true at, so
1510
- a renderer prints the library's own wording: *a receipt describes the request
1511
- as this library last saw it.* Where that is — the provider port — is in the
1630
+ a renderer prints the library's own wording: _a receipt describes the request
1631
+ as this library last saw it._ Where that is — the provider port — is in the
1512
1632
  comment beside the constant, because the sentence is printed and the sixth
1513
1633
  round's rule is that a printed sentence names no mechanism.
1514
1634
  - **`ServedView.basis`** — `{ model, provider, runId }`, read off the receipt.
@@ -1647,7 +1767,7 @@ that file now exists. All of it is closed below, at the root, additively.
1647
1767
  terminal, in a diff and in review, and one `sed` away from being eaten. Same
1648
1768
  bytes, same hashes; they can now be read.
1649
1769
  - **`test/lib/time-travel/receipt-conformance.test.ts` · `describe('a redacted
1650
- run')` was a NO-OP, and two READMEs documented what it pretended to prove.**
1770
+ run')` was a NO-OP, and two READMEs documented what it pretended to prove.**
1651
1771
  It passed `redact: [...]` to `Agent.create`, which has no such option;
1652
1772
  `tsconfig.json` excludes `test/`, so the unknown key was never typechecked and
1653
1773
  was silently dropped. The run was not redacted, and the READMEs' "honest edge"
@@ -1820,6 +1940,7 @@ a sentence is TRUE, and three of them were not.
1820
1940
  conditionally instead would mean inferring "no strategy ran" from a recording,
1821
1941
  which is the absence-of-evidence reading this whole feature refuses; recorded
1822
1942
  as entry 8 of `docs/design/2026-09-recorded-not-built.md`.
1943
+
1823
1944
  #### The fifth and sixth rounds — a printed gap sentence stops describing code
1824
1945
 
1825
1946
  Five review rounds, and each one found NEW false prose in the sentences the
@@ -1829,7 +1950,7 @@ composed once and read many times is a PREDICTION** — one surface over: compos
1829
1950
  once, and read against every later version of the code it describes.
1830
1951
 
1831
1952
  The reproduction is one shipped string. `SERVED_GAPS['no-receipt-on-chart'].why`
1832
- said *"THREE causes and none of them is a hole in this view: …"*. A fourth path
1953
+ said _"THREE causes and none of them is a hole in this view: …"_. A fourth path
1833
1954
  was then added — a value under the receipt key refused because it carries no
1834
1955
  basis — and BOTH halves went false at once: four causes, and that one IS a hole.
1835
1956
  Nobody edited the string. Nobody had to.
@@ -1841,12 +1962,12 @@ one, and the reason is the whole story of this release.
1841
1962
 
1842
1963
  Five rounds tried to write TRUE mechanism sentences and the rate of new
1843
1964
  falsehoods held constant. So the sixth put one question to all ten printed
1844
- sentences — *could this become false without anyone editing it?* — and NINE
1965
+ sentences — _could this become false without anyone editing it?_ — and NINE
1845
1966
  could, two of them being false the day they shipped. Exactly one could not:
1846
1967
 
1847
- > `UNGAPPED_FIELDS.gaps` — *"The account itself rather than a fact about the
1968
+ > `UNGAPPED_FIELDS.gaps` — _"The account itself rather than a fact about the
1848
1969
  > request: a gap naming this list would be the account excusing its own
1849
- > absence."*
1970
+ > absence."_
1850
1971
 
1851
1972
  It survives because it makes **no claim about code**. It says what the field
1852
1973
  means inside the account, and nothing outside the sentence can falsify it. Every
@@ -1881,8 +2002,8 @@ of them still make a claim a code edit falsifies.** Exactly one does not —
1881
2002
  `UNGAPPED_FIELDS.gaps` — and it does not because it is SELF-REFERENTIAL: it says
1882
2003
  what its field is inside the account, not anything about the request. The other
1883
2004
  ten cannot copy that shape, because a sentence that tells a reader something
1884
- USEFUL — *may be SHORT*, *absent means unknown*, *the tool list is complete and
1885
- the schemas are one short* — is a claim about how the rebuild behaves, and the
2005
+ USEFUL — _may be SHORT_, _absent means unknown_, _the tool list is complete and
2006
+ the schemas are one short_ — is a claim about how the rebuild behaves, and the
1886
2007
  rebuild is code. **The reduction changed the VOCABULARY of the claims, not their
1887
2008
  CLASS.**
1888
2009
 
@@ -1926,20 +2047,20 @@ spot than six rounds of rewriting produced, and it is the whole of it.
1926
2047
  a doc is FOR; the other three do not, because a cause count goes stale in a
1927
2048
  doc exactly as it does in a constant.
1928
2049
  - **Every printed gap sentence is one or two sentences and names nothing.**
1929
- `no-receipt-on-chart` now reads in full: *"Nothing on this view has been
2050
+ `no-receipt-on-chart` now reads in full: _"Nothing on this view has been
1930
2051
  checked against what went out. Every field below is missing as a whole, and an
1931
2052
  absence among them says nothing about the call — not even that a dial was left
1932
- unset."* `forced-tool-schema` is 26 words. `callRuntimeStageId`'s excuse is 12.
2053
+ unset."_ `forced-tool-schema` is 26 words. `callRuntimeStageId`'s excuse is 12.
1933
2054
  - **TWO SENTENCES WERE FALSE THE DAY THEY SHIPPED, and both were mechanism
1934
2055
  claims, so the rule deletes the category rather than the instances.**
1935
- `cache-transform` said *"only its INPUTS are on the record"* while three of
2056
+ `cache-transform` said _"only its INPUTS are on the record"_ while three of
1936
2057
  the fields it covers are OUTPUTS that are on the record — `cache.transform`
1937
2058
  (the verdict of comparing what the strategy was given against what it handed
1938
2059
  back), `cache.transformHash` (the digest of the result, when they differed)
1939
2060
  and `cache.markersApplied` (the breakpoints actually applied, as against the
1940
2061
  candidates in `scope.cacheMarkers`, which are the inputs). And
1941
- `no-receipt-on-chart` opened *"No receipt was found for this epoch"* and
1942
- closed *"absent here means unrecorded"* — both false under
2062
+ `no-receipt-on-chart` opened _"No receipt was found for this epoch"_ and
2063
+ closed _"absent here means unrecorded"_ — both false under
1943
2064
  `'receipt-shape-rejected'`, where a receipt WAS written and the read refused
1944
2065
  it, which is to say the sentence asserted which cause applied and was wrong
1945
2066
  for one of two. Neither can be written under the new rule. Both are corrected
@@ -1948,9 +2069,9 @@ spot than six rounds of rewriting produced, and it is the whole of it.
1948
2069
  - **`RECEIPT_BOUNDARY` was the one printed sentence exempted from the rule, and
1949
2070
  the exemption is gone.** It named `LLMProvider.complete` and `complete()` — a
1950
2071
  module and a call, printed to a reader who cannot open either. It now reads
1951
- *"A receipt describes the request as this library last saw it. Whatever
2072
+ _"A receipt describes the request as this library last saw it. Whatever
1952
2073
  handled it after that could have changed it, and nothing on the receipt would
1953
- show that."* The port, the decorated provider, the vendor adapter and the
2074
+ show that."_ The port, the decorated provider, the vendor adapter and the
1954
2075
  vendor's own defaults are in the comment above the constant. One string, one
1955
2076
  rule set: the walk no longer strips it before judging, and asserts it on its
1956
2077
  own as well as inside the two entries that quote it.
@@ -1985,8 +2106,8 @@ spot than six rounds of rewriting produced, and it is the whole of it.
1985
2106
  measurement is unchanged and still re-taken by the walk on every run; it reads
1986
2107
  the claim from the COMMENT beside the entry now, so a chart that starts
1987
2108
  supplying one still fails the suite instead of aging the sentence. What a
1988
- reader is shown is what the field means: *"Absent means nobody recorded a
1989
- drop, never that nothing was dropped."*
2109
+ reader is shown is what the field means: _"Absent means nobody recorded a
2110
+ drop, never that nothing was dropped."_
1990
2111
  - **`examples/observability/24-receipt-at-the-stop.ts` clipped a printed reason
1991
2112
  mid-version-number.** It printed `reason.split('.')[0]`, so "measured on
1992
2113
  9.88.0" reached a reader as "measured on 9." — a fragment that reads as a
@@ -1996,8 +2117,7 @@ spot than six rounds of rewriting produced, and it is the whole of it.
1996
2117
  coverage.** The header claimed the vacuous-row guard was closed "by driving
1997
2118
  each row on a run that reaches its own fields". Measured, that clause is
1998
2119
  false: `no-fold-base` moves 7 of the 11 fields it names, `no-run-log` 1 of 4,
1999
- `no-receipt-on-chart` 3 of 8, and only `no-conversation-on-record` reaches all
2000
- 3. The reasons are structural — a receipt-only field never appears on a view,
2120
+ `no-receipt-on-chart` 3 of 8, and only `no-conversation-on-record` reaches all 3. The reasons are structural — a receipt-only field never appears on a view,
2001
2121
  so removing the receipt cannot MOVE it — so the header names the numbers per
2002
2122
  row, a test pins them so the table cannot go stale, and it says plainly that a
2003
2123
  field claim divergence does not reach is carried by the coverage half and by
@@ -2021,56 +2141,57 @@ each of them, which is how the library closes everything else, and it found a
2021
2141
  different class of defect: not prose that names a mechanism, but prose that is
2022
2142
  plain, short, rule-abiding and UNTRUE OF THE VIEW IT IS PRINTED BESIDE.
2023
2143
 
2024
- - **`no-run-log` claimed a loss that a run says did not happen.** It read *"The
2025
- fields below could not be fully recovered here"* — an assertion that recovery
2144
+ - **`no-run-log` claimed a loss that a run says did not happen.** It read _"The
2145
+ fields below could not be fully recovered here"_ — an assertion that recovery
2026
2146
  DID fail. Measured on the ordinary view that raises it, a
2027
2147
  `'dynamic-grouped'` agent with one plain tool and its `commitLog` emptied:
2028
2148
  `tools.names`, `tools.schemas`, `tools.forced` and `messages.requestOnly` all
2029
2149
  come back BYTE-IDENTICAL to the intact view. The gap fires and costs nothing,
2030
2150
  because that run has no forced tool name and no `wants` to lose. It now reads
2031
- *"The fields below may be SHORT: a name can be missing from the tool list, and
2151
+ _"The fields below may be SHORT: a name can be missing from the tool list, and
2032
2152
  a line that went out with the request can be missing too. An absence below is
2033
2153
  not evidence that there was nothing there — read the whole recording rather
2034
- than a piece of it."* **The repair is the sentence and not the condition**,
2154
+ than a piece of it."_ **The repair is the sentence and not the condition**,
2035
2155
  and the reason is that the condition cannot be narrowed by anything the read
2036
2156
  can see: whether the run had a constant to lose is recorded in the log whose
2037
2157
  absence raises the gap. Both directions are asserted — emptying the run log
2038
2158
  takes a forced-output run's tool list from one name to none and a staged-refs
2039
2159
  run's request-only line to nothing, and takes nothing at all from the plain
2040
2160
  run. Its second clause was loose as well: it said a line could go missing
2041
- *"from the conversation"*, and a request-only line is by construction in no
2161
+ _"from the conversation"_, and a request-only line is by construction in no
2042
2162
  conversation — asserted now against the wire, which carries it as the last
2043
2163
  message of the request while the rebuilt `messages.asSent` does not contain it.
2044
2164
  - **Three sentences were true where they were composed and misleading where they
2045
2165
  were PRINTED.** One sentence, several contexts: the library's own Honest
2046
2166
  Sentence law says it has to hold in all of them.
2047
- - `cache-transform` ended by quoting `RECEIPT_BOUNDARY` — *"A receipt
2048
- describes the request as this library last saw it"* — and it is raised on
2167
+
2168
+ - `cache-transform` ended by quoting `RECEIPT_BOUNDARY` — _"A receipt
2169
+ describes the request as this library last saw it"_ — and it is raised on
2049
2170
  EVERY view, including a receipt-less one. Measured: an `LLMCall` view
2050
2171
  carries exactly `no-receipt-on-chart` and `cache-transform`, so the reader
2051
2172
  was told what a receipt describes beside a view that has none. The quote is
2052
2173
  gone from it and the claim survives in the entry's own words, in the
2053
- vocabulary of a view: *"…and nothing on this view would show it."*
2174
+ vocabulary of a view: _"…and nothing on this view would show it."_
2054
2175
  `provider-defaults` keeps the quote and is the only entry that may have it —
2055
2176
  it is pushed inside `if (receipt !== undefined)`, so a view carrying it
2056
2177
  always has a receipt for the sentence to be about. Asserted across every run
2057
2178
  in the new file: a gap whose `why` includes the boundary appears only on a
2058
2179
  view whose `basis` is defined.
2059
- - `no-fold-base` said the view's number *"may differ from the one the receipt
2060
- for this turn carries"* — printed on views that carry no receipt (a
2180
+ - `no-fold-base` said the view's number _"may differ from the one the receipt
2181
+ for this turn carries"_ — printed on views that carry no receipt (a
2061
2182
  base-less `LLMCall` recording raises both gaps at once). It now says what
2062
- the NUMBER means: *"The turn number below may be this turn's place in run
2063
- order rather than the number the run itself gave it."* Asserted on the
2183
+ the NUMBER means: _"The turn number below may be this turn's place in run
2184
+ order rather than the number the run itself gave it."_ Asserted on the
2064
2185
  resumed run whose base and `iteration` writes are gone: the view calls the
2065
2186
  second turn 1 while the run's own count for it was 2.
2066
- - `no-receipt-on-chart` closed *"their absence here is a gap in the record,
2067
- never a call made without them"*. True of each field AS A WHOLE and false
2187
+ - `no-receipt-on-chart` closed _"their absence here is a gap in the record,
2188
+ never a call made without them"_. True of each field AS A WHOLE and false
2068
2189
  one level down, which is the level a reader reads at: a receipt always
2069
2190
  carries `params` and always carries a `cache.transform` verdict, and an
2070
2191
  absence INSIDE `params` — measured `{}` on an agent that set no dials — IS a
2071
2192
  call made without one. It now claims nothing about what is inside a field it
2072
- cannot see: *"…an absence among them says nothing about the call — not even
2073
- that a dial was left unset."*
2193
+ cannot see: _"…an absence among them says nothing about the call — not even
2194
+ that a dial was left unset."_
2074
2195
 
2075
2196
  - **`test/lib/time-travel/gap-sentences.test.ts` — the assertion, beside the
2076
2197
  sentence.** One real run per catalogue entry and per `UNGAPPED_FIELDS` key,
@@ -2159,6 +2280,7 @@ plain, short, rule-abiding and UNTRUE OF THE VIEW IT IS PRINTED BESIDE.
2159
2280
  hand-listed, so a gap whose damage nobody wrote down still gets only the
2160
2281
  account half. Neither half reads a `why`. That is a person's job, and it is
2161
2282
  where this release's last three defects came from.
2283
+
2162
2284
  - `test/lib/time-travel/keyed-fold-equivalence.test.ts` — 9 tests: every key at
2163
2285
  every commit of a real run, checked against `stateAt` itself, including a
2164
2286
  merge with no `set` anchor (which cannot be folded at all without the base),
@@ -2208,9 +2330,9 @@ the examples it pulls in — a repair of its own, not a line item in this releas
2208
2330
  The hole is closed instead in `test/type-regressions/`, which already compiles
2209
2331
  under `npm run test:types`.
2210
2332
 
2211
- `ServedGap.fields` carries TWO relations on one list. Most entries mean *the
2212
- rebuild cannot produce this field*; `cache-transform`'s composition fields mean
2213
- *it can, and both sides agree, but only up to the cache strategy*. A checker
2333
+ `ServedGap.fields` carries TWO relations on one list. Most entries mean _the
2334
+ rebuild cannot produce this field_; `cache-transform`'s composition fields mean
2335
+ _it can, and both sides agree, but only up to the cache strategy_. A checker
2214
2336
  that granted the second as an excuse would stop checking fields the record
2215
2337
  proves perfectly well — which is exactly what happened when the composition
2216
2338
  fields were added, and it silently disabled one clause of the law. The clause
@@ -2354,7 +2476,7 @@ Iteration → … → Run end.
2354
2476
  becomes a one-line reader of it.
2355
2477
 
2356
2478
  - **Which keys are visible where, in the grouped shape.** The settled skill
2357
- cursor for turn *k* is on the OUTER axis, at iteration *k* (`currentSkillId`).
2479
+ cursor for turn _k_ is on the OUTER axis, at iteration _k_ (`currentSkillId`).
2358
2480
  Inside the drill, `currentSkillId` is the value the turn STARTED from — it
2359
2481
  crosses the mount as a read-only input — and the move the turn made is
2360
2482
  `nextSkillCursor`, merged back out by the outputMapper. Both logs are truthful
@@ -2400,7 +2522,7 @@ Iteration → … → Run end.
2400
2522
  - **17 tests over real runs**, not fixtures: the axis in both chart shapes, the
2401
2523
  partition property (every commit belongs to exactly one stop), a jump that
2402
2524
  lands and a miss that names a nearest without moving, the skill graph read
2403
- along the commits with the wire as witness (the skill whose body rode turn *k*
2525
+ along the commits with the wire as witness (the skill whose body rode turn _k_
2404
2526
  is the one that iteration's own log settled on), marks that survive jumps and
2405
2527
  never appear in the recording, what `'start'` folds on this axis versus the
2406
2528
  port's, a resumed run's axis, and a log with no milestones in it. One more
@@ -2435,7 +2557,7 @@ Everything else here is one review of 9.86.0, taken finding by finding.
2435
2557
  `TurnRoute.by`'s six values, so the clause was composed for the other four too:
2436
2558
  false for `'continuity'` (the cursor was carried over from the previous turn and
2437
2559
  nothing moved it — the verdict every follow-up produces under `{ strictness:
2438
- 'guard', continuity: 'conversation' }`), false for `'menu'` resolved by the
2560
+ 'guard', continuity: 'conversation' }`), false for `'menu'` resolved by the
2439
2561
  model's own pick, and unprovable for `'none'`. The composer now takes
2440
2562
  `turnStartedBy: TurnRoute['by']` and says one past fact per value — "the turn's
2441
2563
  start had already been resolved decisively", "the cursor had been carried over
@@ -2528,7 +2650,7 @@ Everything else here is one review of 9.86.0, taken finding by finding.
2528
2650
  The ten 9.86.0 rows cited `buildToolRegistry.ts` line ranges from the 9.85.0 tree
2529
2651
  that the same release had moved by about twenty-nine lines, beside a
2530
2652
  `buildToolsSlot.ts` line from HEAD. They name the checks now (`holders.includes
2531
- (PRESENT_TOOL_NAME)`, the `seenNames` loop, the `sharedSkillTools` backfill), and
2653
+ (PRESENT_TOOL_NAME)`, the `seenNames` loop, the `sharedSkillTools` backfill), and
2532
2654
  the baseline's `note` says so. The `present-vs-mcp` row no longer claims the MCP
2533
2655
  cell proves the blind spot is the provider channel: both claimants mount through
2534
2656
  `staticTools()`, so the cell shows an MCP catalog inherits that seam unchanged and
@@ -2643,11 +2765,11 @@ the cursor?", and one scope key answers "which skill ids may this role see?".
2643
2765
  export inside the tool-calls stage, not on any package door — _corrected in
2644
2766
  9.86.1_), which
2645
2767
  names the dispatch roster: `Unknown tool 'X' on that call. Tool names that
2646
- resolved to an implementation on that call: …`, or, with an empty roster, that
2768
+ resolved to an implementation on that call: …`, or, with an empty roster, that
2647
2769
  none did. The leading `Unknown tool` token is preserved, so every matcher on it
2648
2770
  is untouched.
2649
2771
 
2650
- It says *resolved*, not *could be dispatched*, because two gates sit between
2772
+ It says _resolved_, not _could be dispatched_, because two gates sit between
2651
2773
  resolution and a tool running — the `tool_call` permission check and the
2652
2774
  middleware chain — and neither is asked to phrase an error. And the roster is
2653
2775
  role-filtered before it is named: it used to read the dispatch map raw and could
@@ -2661,10 +2783,10 @@ the cursor?", and one scope key answers "which skill ids may this role see?".
2661
2783
  Fold holds.** Three sentences branched on `length > 0` over an already-filtered
2662
2784
  array, so "the graph held nothing" and "the role filter emptied it" composed the
2663
2785
  same words. A cursor whose only declared hop was hidden answered `read_skill`
2664
- with *"No skill was reachable from 'alpha' when that call was made."* while the
2786
+ with _"No skill was reachable from 'alpha' when that call was made."_ while the
2665
2787
  graph was routing `alpha`; a `'guard'` menu whose every id had been hidden since
2666
- the turn started said *"no menu was outstanding when that call was made.
2667
- Declared routes moved the cursor instead."* — two false clauses in one breath.
2788
+ the turn started said _"no menu was outstanding when that call was made.
2789
+ Declared routes moved the cursor instead."_ — two false clauses in one breath.
2668
2790
 
2669
2791
  A model told the map is a dead end stops asking for the door it may not be
2670
2792
  shown, and the checker cannot see it: every one of those sentences passes
@@ -2677,8 +2799,8 @@ the cursor?", and one scope key answers "which skill ids may this role see?".
2677
2799
 
2678
2800
  The fourth sentence was the one the model reads to CHOOSE. `describeOffer`
2679
2801
  computed its columns from an already-filtered catalog, so a cursor whose only
2680
- declared hop is hidden was told *"Nothing is reachable from here — answer with
2681
- the skill you are in, or finish."* while the graph held that edge. It classifies
2802
+ declared hop is hidden was told _"Nothing is reachable from here — answer with
2803
+ the skill you are in, or finish."_ while the graph held that edge. It classifies
2682
2804
  the hop set over the unfiltered catalog now and drops the clause when the filter
2683
2805
  is what emptied it; with nothing wired out at all the sentence still stands,
2684
2806
  because that absence is one the description has evidence for. Reaching it meant