agentfootprint 9.100.0 → 9.102.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 (183) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +343 -0
  3. package/CLAUDE.md +1 -1
  4. package/README.md +1 -0
  5. package/ai-instructions/claude-code/SKILL.md +1 -1
  6. package/dist/core/Agent.js +207 -2
  7. package/dist/core/Agent.js.map +1 -1
  8. package/dist/core/agent/AgentBuilder.js +118 -2
  9. package/dist/core/agent/AgentBuilder.js.map +1 -1
  10. package/dist/core/agent/buildAgentChart.js +11 -0
  11. package/dist/core/agent/buildAgentChart.js.map +1 -1
  12. package/dist/core/agent/buildDynamicAgentChart.js +22 -0
  13. package/dist/core/agent/buildDynamicAgentChart.js.map +1 -1
  14. package/dist/core/agent/buildToolRegistry.js +38 -0
  15. package/dist/core/agent/buildToolRegistry.js.map +1 -1
  16. package/dist/core/agent/findings/ledger.js +261 -0
  17. package/dist/core/agent/findings/ledger.js.map +1 -0
  18. package/dist/core/agent/findings/offer.js +179 -0
  19. package/dist/core/agent/findings/offer.js.map +1 -0
  20. package/dist/core/agent/findings/reserved.js +465 -0
  21. package/dist/core/agent/findings/reserved.js.map +1 -0
  22. package/dist/core/agent/findings/serve.js +400 -0
  23. package/dist/core/agent/findings/serve.js.map +1 -0
  24. package/dist/core/agent/findings/types.js +56 -0
  25. package/dist/core/agent/findings/types.js.map +1 -0
  26. package/dist/core/agent/stages/callLLM.js +78 -10
  27. package/dist/core/agent/stages/callLLM.js.map +1 -1
  28. package/dist/core/agent/stages/outputRetry.js +5 -0
  29. package/dist/core/agent/stages/outputRetry.js.map +1 -1
  30. package/dist/core/agent/stages/route.js +71 -12
  31. package/dist/core/agent/stages/route.js.map +1 -1
  32. package/dist/core/agent/stages/seed.js +33 -1
  33. package/dist/core/agent/stages/seed.js.map +1 -1
  34. package/dist/core/agent/stages/toolCalls.js +84 -11
  35. package/dist/core/agent/stages/toolCalls.js.map +1 -1
  36. package/dist/core/agent/stages/window.js +110 -10
  37. package/dist/core/agent/stages/window.js.map +1 -1
  38. package/dist/core/agent/window/index.js +3 -1
  39. package/dist/core/agent/window/index.js.map +1 -1
  40. package/dist/core/agent/window/ledgerFactPins.js +200 -0
  41. package/dist/core/agent/window/ledgerFactPins.js.map +1 -0
  42. package/dist/core/agent/window/turns.js +45 -11
  43. package/dist/core/agent/window/turns.js.map +1 -1
  44. package/dist/core/runCheckpoint.js +64 -1
  45. package/dist/core/runCheckpoint.js.map +1 -1
  46. package/dist/core/slots/buildToolsSlot.js +35 -1
  47. package/dist/core/slots/buildToolsSlot.js.map +1 -1
  48. package/dist/esm/core/Agent.d.ts +55 -0
  49. package/dist/esm/core/Agent.js +207 -2
  50. package/dist/esm/core/Agent.js.map +1 -1
  51. package/dist/esm/core/agent/AgentBuilder.d.ts +65 -0
  52. package/dist/esm/core/agent/AgentBuilder.js +118 -2
  53. package/dist/esm/core/agent/AgentBuilder.js.map +1 -1
  54. package/dist/esm/core/agent/buildAgentChart.d.ts +14 -0
  55. package/dist/esm/core/agent/buildAgentChart.js +11 -0
  56. package/dist/esm/core/agent/buildAgentChart.js.map +1 -1
  57. package/dist/esm/core/agent/buildDynamicAgentChart.js +22 -0
  58. package/dist/esm/core/agent/buildDynamicAgentChart.js.map +1 -1
  59. package/dist/esm/core/agent/buildToolRegistry.d.ts +7 -0
  60. package/dist/esm/core/agent/buildToolRegistry.js +38 -0
  61. package/dist/esm/core/agent/buildToolRegistry.js.map +1 -1
  62. package/dist/esm/core/agent/findings/ledger.d.ts +105 -0
  63. package/dist/esm/core/agent/findings/ledger.js +254 -0
  64. package/dist/esm/core/agent/findings/ledger.js.map +1 -0
  65. package/dist/esm/core/agent/findings/offer.d.ts +112 -0
  66. package/dist/esm/core/agent/findings/offer.js +171 -0
  67. package/dist/esm/core/agent/findings/offer.js.map +1 -0
  68. package/dist/esm/core/agent/findings/reserved.d.ts +127 -0
  69. package/dist/esm/core/agent/findings/reserved.js +457 -0
  70. package/dist/esm/core/agent/findings/reserved.js.map +1 -0
  71. package/dist/esm/core/agent/findings/serve.d.ts +149 -0
  72. package/dist/esm/core/agent/findings/serve.js +397 -0
  73. package/dist/esm/core/agent/findings/serve.js.map +1 -0
  74. package/dist/esm/core/agent/findings/types.d.ts +179 -0
  75. package/dist/esm/core/agent/findings/types.js +53 -0
  76. package/dist/esm/core/agent/findings/types.js.map +1 -0
  77. package/dist/esm/core/agent/stages/callLLM.d.ts +32 -0
  78. package/dist/esm/core/agent/stages/callLLM.js +78 -10
  79. package/dist/esm/core/agent/stages/callLLM.js.map +1 -1
  80. package/dist/esm/core/agent/stages/outputRetry.js +5 -0
  81. package/dist/esm/core/agent/stages/outputRetry.js.map +1 -1
  82. package/dist/esm/core/agent/stages/route.d.ts +8 -1
  83. package/dist/esm/core/agent/stages/route.js +71 -12
  84. package/dist/esm/core/agent/stages/route.js.map +1 -1
  85. package/dist/esm/core/agent/stages/seed.d.ts +28 -0
  86. package/dist/esm/core/agent/stages/seed.js +33 -1
  87. package/dist/esm/core/agent/stages/seed.js.map +1 -1
  88. package/dist/esm/core/agent/stages/toolCalls.d.ts +17 -0
  89. package/dist/esm/core/agent/stages/toolCalls.js +82 -9
  90. package/dist/esm/core/agent/stages/toolCalls.js.map +1 -1
  91. package/dist/esm/core/agent/stages/window.d.ts +22 -0
  92. package/dist/esm/core/agent/stages/window.js +111 -11
  93. package/dist/esm/core/agent/stages/window.js.map +1 -1
  94. package/dist/esm/core/agent/types.d.ts +113 -0
  95. package/dist/esm/core/agent/window/index.d.ts +1 -0
  96. package/dist/esm/core/agent/window/index.js +1 -0
  97. package/dist/esm/core/agent/window/index.js.map +1 -1
  98. package/dist/esm/core/agent/window/ledgerFactPins.d.ts +147 -0
  99. package/dist/esm/core/agent/window/ledgerFactPins.js +194 -0
  100. package/dist/esm/core/agent/window/ledgerFactPins.js.map +1 -0
  101. package/dist/esm/core/agent/window/strategy.d.ts +25 -0
  102. package/dist/esm/core/agent/window/turns.d.ts +25 -0
  103. package/dist/esm/core/agent/window/turns.js +45 -11
  104. package/dist/esm/core/agent/window/turns.js.map +1 -1
  105. package/dist/esm/core/agent/window/types.d.ts +64 -0
  106. package/dist/esm/core/runCheckpoint.d.ts +27 -1
  107. package/dist/esm/core/runCheckpoint.js +64 -1
  108. package/dist/esm/core/runCheckpoint.js.map +1 -1
  109. package/dist/esm/core/slots/buildToolsSlot.d.ts +20 -0
  110. package/dist/esm/core/slots/buildToolsSlot.js +35 -1
  111. package/dist/esm/core/slots/buildToolsSlot.js.map +1 -1
  112. package/dist/esm/events/payloads.d.ts +65 -0
  113. package/dist/esm/events/registry.d.ts +7 -1
  114. package/dist/esm/events/registry.js +6 -0
  115. package/dist/esm/events/registry.js.map +1 -1
  116. package/dist/esm/events/types.d.ts +3 -1
  117. package/dist/esm/index.d.ts +2 -1
  118. package/dist/esm/index.js +6 -0
  119. package/dist/esm/index.js.map +1 -1
  120. package/dist/esm/lib/time-travel/servedView.js +54 -10
  121. package/dist/esm/lib/time-travel/servedView.js.map +1 -1
  122. package/dist/events/registry.js +6 -0
  123. package/dist/events/registry.js.map +1 -1
  124. package/dist/index.js +11 -4
  125. package/dist/index.js.map +1 -1
  126. package/dist/lib/time-travel/servedView.js +54 -10
  127. package/dist/lib/time-travel/servedView.js.map +1 -1
  128. package/dist/types/core/Agent.d.ts +55 -0
  129. package/dist/types/core/Agent.d.ts.map +1 -1
  130. package/dist/types/core/agent/AgentBuilder.d.ts +65 -0
  131. package/dist/types/core/agent/AgentBuilder.d.ts.map +1 -1
  132. package/dist/types/core/agent/buildAgentChart.d.ts +14 -0
  133. package/dist/types/core/agent/buildAgentChart.d.ts.map +1 -1
  134. package/dist/types/core/agent/buildDynamicAgentChart.d.ts.map +1 -1
  135. package/dist/types/core/agent/buildToolRegistry.d.ts +7 -0
  136. package/dist/types/core/agent/buildToolRegistry.d.ts.map +1 -1
  137. package/dist/types/core/agent/findings/ledger.d.ts +106 -0
  138. package/dist/types/core/agent/findings/ledger.d.ts.map +1 -0
  139. package/dist/types/core/agent/findings/offer.d.ts +113 -0
  140. package/dist/types/core/agent/findings/offer.d.ts.map +1 -0
  141. package/dist/types/core/agent/findings/reserved.d.ts +128 -0
  142. package/dist/types/core/agent/findings/reserved.d.ts.map +1 -0
  143. package/dist/types/core/agent/findings/serve.d.ts +150 -0
  144. package/dist/types/core/agent/findings/serve.d.ts.map +1 -0
  145. package/dist/types/core/agent/findings/types.d.ts +180 -0
  146. package/dist/types/core/agent/findings/types.d.ts.map +1 -0
  147. package/dist/types/core/agent/stages/callLLM.d.ts +32 -0
  148. package/dist/types/core/agent/stages/callLLM.d.ts.map +1 -1
  149. package/dist/types/core/agent/stages/outputRetry.d.ts.map +1 -1
  150. package/dist/types/core/agent/stages/route.d.ts +8 -1
  151. package/dist/types/core/agent/stages/route.d.ts.map +1 -1
  152. package/dist/types/core/agent/stages/seed.d.ts +28 -0
  153. package/dist/types/core/agent/stages/seed.d.ts.map +1 -1
  154. package/dist/types/core/agent/stages/toolCalls.d.ts +17 -0
  155. package/dist/types/core/agent/stages/toolCalls.d.ts.map +1 -1
  156. package/dist/types/core/agent/stages/window.d.ts +22 -0
  157. package/dist/types/core/agent/stages/window.d.ts.map +1 -1
  158. package/dist/types/core/agent/types.d.ts +113 -0
  159. package/dist/types/core/agent/types.d.ts.map +1 -1
  160. package/dist/types/core/agent/window/index.d.ts +1 -0
  161. package/dist/types/core/agent/window/index.d.ts.map +1 -1
  162. package/dist/types/core/agent/window/ledgerFactPins.d.ts +148 -0
  163. package/dist/types/core/agent/window/ledgerFactPins.d.ts.map +1 -0
  164. package/dist/types/core/agent/window/strategy.d.ts +25 -0
  165. package/dist/types/core/agent/window/strategy.d.ts.map +1 -1
  166. package/dist/types/core/agent/window/turns.d.ts +25 -0
  167. package/dist/types/core/agent/window/turns.d.ts.map +1 -1
  168. package/dist/types/core/agent/window/types.d.ts +64 -0
  169. package/dist/types/core/agent/window/types.d.ts.map +1 -1
  170. package/dist/types/core/runCheckpoint.d.ts +27 -1
  171. package/dist/types/core/runCheckpoint.d.ts.map +1 -1
  172. package/dist/types/core/slots/buildToolsSlot.d.ts +20 -0
  173. package/dist/types/core/slots/buildToolsSlot.d.ts.map +1 -1
  174. package/dist/types/events/payloads.d.ts +65 -0
  175. package/dist/types/events/payloads.d.ts.map +1 -1
  176. package/dist/types/events/registry.d.ts +7 -1
  177. package/dist/types/events/registry.d.ts.map +1 -1
  178. package/dist/types/events/types.d.ts +3 -1
  179. package/dist/types/events/types.d.ts.map +1 -1
  180. package/dist/types/index.d.ts +2 -1
  181. package/dist/types/index.d.ts.map +1 -1
  182. package/dist/types/lib/time-travel/servedView.d.ts.map +1 -1
  183. package/package.json +2 -1
package/AGENTS.md CHANGED
@@ -544,7 +544,7 @@ files `agentfootprint.tools.coverage_declared`; with
544
544
  to the final answer, which is how a limit survives a model that would rather not
545
545
  mention it.
546
546
 
547
- ### Observability — 111 typed events across 24 domains
547
+ ### Observability — 113 typed events across 25 domains
548
548
 
549
549
  ```typescript
550
550
  agent.on('agentfootprint.context.injected', (e) =>
package/CHANGELOG.md CHANGED
@@ -5,6 +5,349 @@ 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.102.0] - 2026-09-17
9
+
10
+ ### Added — a declared fact stays in the window; noise leaves first
11
+
12
+ - The window reads the findings ledger. On an agent with `.findings()` and a
13
+ window strategy, a turn whose tool result the model declared a `fact` is
14
+ HELD beyond `keepRecentTurns` — newest first, up to `keepLedgerFacts`
15
+ (default 4; `false` or `0` for no hold) — and the refusal is on the record
16
+ by name: `WindowRefusalReason` gains `'ledger-fact'`. It is a bounded hold
17
+ in the refusal engine, the content-aware sibling of the last-tool-result
18
+ pin (`ledgerFactPinsOf` beside `toolResultPinsOf`, admitted by the same
19
+ ceiling-spender in `planRemoval`), so `slidingWindow`, `tokenBudget`,
20
+ `summarizeOldest` and a consumer-written strategy all inherit it through
21
+ `planRemoval` — no new strategy file, none of the three shipped ones changed
22
+ a byte, the contiguous span, the drop ladder and the meter's single-seam
23
+ rebase untouched. 'Noise first' follows with no second mechanism: noise,
24
+ ruled-out, open and undeclared turns are unpinned and leave oldest-first as
25
+ they always did, and a judged noise turn that outlives a fact is already a
26
+ ticket on the wire. Why: the served piece restored a declared fact to the
27
+ answer turn, but under a window the fact's RESULT still left by recency —
28
+ the refusal engine saw a declared fact and a declared noise result as the
29
+ same bytes. By the model's claim only: a turn is the removal unit and its
30
+ 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
32
+ held; the library reads no result's text to decide otherwise.
33
+ - A fact hold never exists without its ceiling and its stand-down. The
34
+ ceiling is spent newest first and a held turn already inside
35
+ `keepRecentTurns` spends no slot (the free-pin law); the turns it turned away
36
+ are `yielded` on the record; nothing at or before the current request is
37
+ pinnable. The stand-down is the pin's: when the two previous visits removed
38
+ nothing and named only pins, the fact pins release for one visit and the
39
+ record says so. It reads BOTH pin names (`'last-tool-result'` and/or
40
+ `'ledger-fact'`), because a turn held by both pins is reported under the
41
+ recency pin's name — a fact stand-down reading only its own would never see
42
+ that turn blocking, and the two pins would alternate under each other's
43
+ name with the window never shrinking (derived, pinned by test, never
44
+ shipped). The recency pin's own stand-down reads only its own name, so its
45
+ 9.57.0 rule is unchanged.
46
+ - The record says what was held and whose standing left. `WindowRecord`
47
+ gains two optional keys, present only on an armed agent and filed by the
48
+ STAGE so a consumer-written strategy's record carries them too:
49
+ `ledgerFacts` (the `WindowObservations` shape — `pinned`, `yielded`,
50
+ `limit` = `keepLedgerFacts`, `standDown: true` on the visit it released)
51
+ and `droppedStandings` (`{ toolCallId, standing? }` for every tool result
52
+ that left; `standing` absent is undeclared, never a verdict the library
53
+ inferred). `WindowStrategyInput.standingOf?` hands a strategy a turn's
54
+ declared standing, bound by the stage from ONE read of the ledger and
55
+ absent on an unarmed agent — a strategy never reads scope for it; it is for
56
+ ordering or reporting among what the engine left removable, never for
57
+ inferring. What the model is TOLD about a drop is unchanged: the notice
58
+ names tools and counts, never a standing, never the model's own line.
59
+ - `keepLedgerFacts` is live: `.findings({ keepLedgerFacts })` and
60
+ `Agent.create({ keepLedgerFacts })` (the `.findings()` door wins when both
61
+ are given), resolved once at build and validated there — a negative or
62
+ non-integer value is refused, never mid-run. Without `.findings()` the
63
+ option is accepted and does nothing (the keepLastToolResults-without-a-window
64
+ precedent). Exported from the root: `LedgerFactPin` (the candidate a hold is
65
+ built from: `toolCallIds`, `toolName`, `turnIndex`, `messageIndex`, `chars`),
66
+ beside the widened `WindowRefusalReason` and the `WindowRecord` additions.
67
+ - Measured (`npm run bench:findings`, mock provider, 30 tool calls, a planted
68
+ fact every third, a sliding window keeping 6 turns): the results of 2 of 10
69
+ planted facts reached the answer turn verbatim by recency alone, 6 under the
70
+ default ceiling and 8 under a ceiling of 6, while the piece carried 9 of 10
71
+ on every armed row (the last batch is undeclared by the no-outputSchema law)
72
+ and the noise share of tool-result bytes on the wire stayed at the collapsed
73
+ level; every noise result left on every armed row, so the hold changed WHICH
74
+ facts left, not whether noise did. The design page has the print and its
75
+ reading; no real-model number exists yet.
76
+
77
+ ### Unchanged — an agent without `.findings()` plans, records and sends the bytes it did before
78
+
79
+ - The hold, the ledger read, `standingOf`, `ledgerFacts` and `droppedStandings`
80
+ are all gated on the door: an unarmed window stage never reads
81
+ `findingsLedger` (pinned by a getter counting reads), hands its strategy the
82
+ exact input it always did, files the exact record it always did, and sends
83
+ the same request bytes — even when the model emits `_findings` on its own.
84
+ The 16 byte-identity references under `test/core/tools/reference/` pass
85
+ untouched; ONE new reference, `agent-findings-window`, was generated alone,
86
+ with what it holds read back from its bytes on the test file's header.
87
+ - `keepLedgerFacts: false` plans exactly as the unarmed window (the bench
88
+ checks it: same facts verbatim, same tool messages, same receipt count, no
89
+ fact held), so the hold is an addition to the plan, never a rewrite of it.
90
+
91
+ ### Added — the ids the model may name are in the schema; the proposition before the call; a bench that can measure
92
+
93
+ - The offer. On a hosted model the ask "by its tool_result id" produced
94
+ standings named by ORDINAL (`"0"`, `"1"`), recorded honestly as `unknownId`
95
+ and settling nothing (`docs/design/2026-09-findings-ledger-real-model.md`).
96
+ So from the second call on, the reserved `_findings` property on every
97
+ served schema binds the ids the model may name: `previous[].toolCallId`
98
+ 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
100
+ cannot be named here"; a clipped list states the cap. The list is what the
101
+ model can still READ: a result with no standing, a `fact` (stood on in the
102
+ piece, served verbatim under the default mode) and an `open` result (served
103
+ verbatim, carrying what would settle it) stay listed, so a later call can
104
+ REVISE a standing — `open` → `fact` when a call settles it, `fact` →
105
+ `ruled-out` when a conflict resolves; the fold's last-wins law is reachable
106
+ through the enum. A result the model declared `noise` or `ruled-out` leaves
107
+ the list: it is a ticket on the wire under every serve mode and the piece
108
+ carries a count or one line, so there is nothing left to re-judge (a wrong
109
+ `ruled-out` is answered by a new call). The instruction asks the model to
110
+ name a result again only to change its standing. An evicted result is not
111
+ on the list, and the first call — with nothing to name — serves the base
112
+ property by reference, byte-identical to 9.101.0. ONE owner of the two sets
113
+ (`findings/offer.ts`: `offeredResultIds` for the enum, `undeclaredIds` for
114
+ the piece's `undeclared:` line — the honest absence, a subset of the offer),
115
+ computed at the Tools mount, where the served history and the ledger meet
116
+ (the slot is an isolated subflow), under the same arm that decorates; bound
117
+ at the ONE decoration site and committed with the tool list, so
118
+ `servedAt(k).tools.schemas` holds exactly what was offered and
119
+ `receipt.tools.schemaHashes` moves when the offer does. The law holds at
120
+ the schema AND at the row: the offer is what the model may COPY, never what
121
+ 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
123
+ resolves, because a standing is identified against the same served history
124
+ the offer was read from (`findings/offer.ts · knownResults`: the served
125
+ `role: 'tool'` messages plus the previous batch), so an id copied from the
126
+ offer files with its tool name whichever batch the result came from (the
127
+ first cut resolved against the last batch only, and an offered older id
128
+ filed as `unknownId` — caught in review, never released). The instruction
129
+ asks the JSON answer, which has no schema to bind, for the id exactly as
130
+ the schema listed it.
131
+ - Named, not measured: from the second call on, an armed agent's tool schemas
132
+ vary per call (the enum), so a `'tools'` cache breakpoint cannot hit on such
133
+ a run and, on a prefix-cached wire, every breakpoint after it misses with
134
+ it — the system piece already moved the block on every declaring call
135
+ (step 3's recorded cost); the offer moves the tools prefix ahead of it. No
136
+ bench in the tree counts cache tokens; the lever not taken (the offer in
137
+ the request-only system piece, below the tools breakpoint — prose, which is
138
+ what failed on the real model) is on the design page.
139
+ - The proposition. `FindingsDeclaration` gains `proposition?: string` (what
140
+ the call tests; the schema recommends it when `basis` is `'exploratory'`)
141
+ and `predicts?: string` (what the result should show if it holds), declared
142
+ on the call before its result exists; both land on the `BasisRow`, each cut
143
+ at 240 chars with the cut stated in the text. `FindingsDeclaredPayload`
144
+ gains `hasProposition?: true` — a flag, never the text. The served piece
145
+ quotes the judged call's own proposition on `open` and `ruled-out` lines
146
+ (`… — tested: <proposition>`), never on a fact line; `predicts` is
147
+ record-only.
148
+ - The shuffle bench can measure (`bench/findings-shuffle.mjs`): `NOISE_AT`
149
+ (end / start / spread), `NOISE_SIZE` (about 250 / 1000 / 4000 tokens of
150
+ padding per noise record), noise values within 5% of a fact and never equal,
151
+ a `standing-accuracy` column (the share of the actor's standings on known
152
+ ids that agree with the planted truth — the harness knows the truth, so no
153
+ judge), and `--matrix` (24 cells for one model per invocation, the cost line
154
+ printed before the first call). Default mode prints the pre-packet numbers
155
+ to the digit and the mock smoke stays green. No hosted run was made in
156
+ this release; the real-model page holds the pre-offer tables and names the
157
+ number that must move.
158
+
159
+ ### Changed — `.findings()` refuses `reactMode: 'classic'`
160
+
161
+ - The offer needs the tools slot recomposed every call, so `.findings()` is
162
+ REFUSED at build under `reactMode: 'classic'` — through both doors
163
+ (`.findings()` and `Agent.create({ findings })`), the `selfExplain` twin:
164
+ classic selects the Tools branch on turn 1 only, so an armed classic agent
165
+ would have served the offer-less base on every call and filed every
166
+ standing as `unknownId`, the number this packet exists to move silently
167
+ stuck at zero. 9.101.x accepted the combination and degraded it silently;
168
+ the message names the fix. `'dynamic'` (the default) and `'dynamic-grouped'`
169
+ are unchanged.
170
+
171
+ ### Fixed — the choice seam's enum fence read the decorated schema
172
+
173
+ - Since 9.101.0, `withoutFindingsArgument` recognised the library's
174
+ decoration by REFERENCE only (`properties._findings ===
175
+ FINDINGS_ARGUMENT_SCHEMA`), and the reference never holds on the live path:
176
+ the served list `callLLM` reads is the committed `dynamicToolSchemas`, a
177
+ `structuredClone` of what the slot planted. So the choice seam's enum fence
178
+ (`declaredEnumValuesOf(withoutFindingsArgument(schema))`, the
179
+ `unsupported-argument` check) judged the model's argument values against a
180
+ schema that still carried `_findings`, and a value equal to one of the
181
+ findings vocabulary words — `direct`, `exploratory`, `low`, `medium`,
182
+ `high`, `fact`, `open`, `noise`, `ruled-out` — was excused as a declared
183
+ enum value the model was entitled to. The decoration is recognised by its
184
+ versioned marker now (the first sentence of the reserved property's
185
+ description), so the frozen base, an offer copy and a committed clone of
186
+ either are all peeled, and an author's own `_findings` is still read as
187
+ written. An armed agent may therefore file an `unsupported-argument`
188
+ finding it previously suppressed. Its unit test had passed because it
189
+ handed the function the live reference; `test/core/agent/findings/
190
+ reserved.test.ts` now pins the clone.
191
+
192
+ ### Unchanged — the unarmed agent, and the two armed references regenerated alone
193
+
194
+ - An agent without `.findings()` maps no new key on the Tools mount, reads
195
+ neither `history` nor `findingsLedger` there, and serves and records the
196
+ bytes it did before: the 16 unarmed byte-identity references pass
197
+ untouched. `agent-findings` and `agent-findings-window` were regenerated
198
+ each alone (the rest copied aside and `cmp`-equal after) and their delta is
199
+ on the test file's header: the enum on every epoch after the first, a
200
+ `dynamicToolSchemas` write on each such epoch where an unchanged list used
201
+ to be an empty commit, the two new properties and the instruction's two new
202
+ lines, and the sizes that follow; regenerated once more, each alone, in the
203
+ second review for the revisable offer (a declared fact stays listed) and
204
+ the instruction's revised line. No message, ticket, ledger row, window
205
+ record or gap moved.
206
+
207
+ ## [9.101.1] - 2026-09-16
208
+
209
+ ### Fixed — the docs site's export-file ceiling
210
+
211
+ - 9.101.0 never reached npm: its publish workflow stopped at the docs site's
212
+ performance budget (`docs-next/scripts/check-site-budget.mjs`), where the
213
+ generated API-reference routes for the findings ledger's new exports took the
214
+ static export past the file-count ceiling. The ceiling is raised to the
215
+ measured count plus the same thin headroom; no byte ceiling moved. The library
216
+ is byte-identical to 9.101.0; the entry below is what ships.
217
+
218
+ ## [9.101.0] - 2026-09-16
219
+
220
+ ### Added — the model's own findings, on the record, at zero extra calls
221
+
222
+ - `.findings()` on the agent builder (`AgentBuilder.findings`) — every
223
+ SERVED tool schema gains one reserved optional argument, `_findings`
224
+ (`RESERVED_ARGUMENT`): the model declares a `basis` for each call before
225
+ the result exists (`direct` when it expects the answer, `exploratory` when
226
+ it is looking; optional `expect`: `low` / `medium` / `high`) and, on its
227
+ next tool call or as a top-level `_findings.previous` on a JSON answer,
228
+ the STANDING of each earlier tool result by its tool_result id — `fact`
229
+ with the assertions it stands on, `open` with what would settle it,
230
+ `ruled-out` with one line, `noise` with nothing. The library peels the
231
+ argument off before the tool, the middleware chain, the argument
232
+ validator, the permission gate and every pause carrier see the call, files
233
+ the rows through ONE writer as an append-only `AgentState.findingsLedger`
234
+ (`basis` / `standing` / `conflict` rows — a conflict is `conflictsOf`'s
235
+ fact about two stood-on readings that disagree, witnesses by identity),
236
+ and leaves the assistant turn in history verbatim. Read it back with
237
+ `Agent.findings()` (detached; `undefined` when unarmed or when the model
238
+ declared nothing — never an empty array standing in for "no findings").
239
+ Why: a long tool loop serves every result back in full on every call and
240
+ what the model already judged is nowhere but in its head; asking a second
241
+ model would cost a call per result and put a second voice on the record.
242
+ Nothing is inferred (a call with no declaration files no row; a result
243
+ nobody names has no standing — undeclared, never `open`); what the answer
244
+ turn is then SERVED from the record is the next block; and the always-on
245
+ `findings-ledger` instruction is a system piece hashed on every receipt,
246
+ so a reworded ask is a different hash a bench can name. `keepLedgerFacts`
247
+ is accepted now so no public name changes later and is inert until
248
+ standing-aware eviction lands.
249
+ - Two typed events — `agentfootprint.findings.declared` (one per basis row)
250
+ and `agentfootprint.findings.standing` (one per standing row) — carry
251
+ identities, enums and counts only; assertion values, `settles` and `line`
252
+ live in the committed key under whatever redaction the run configured.
253
+ - The checkpoint carries `findingsLedger` only when present, and
254
+ `continueFrom` re-seeds it, so a continued conversation never reports its
255
+ earlier declarations as undeclared. `validateCheckpoint` checks each row's
256
+ shape per kind — shape only, never the values.
257
+ - A registry tool that declares its own `_findings` property is refused at
258
+ build, naming the tool, and only when `.findings()` is armed; a provider-
259
+ or MCP-ingested schema that carries the name is left undecorated (the
260
+ author's property wins, recorded by the committed schema itself), and a
261
+ call to that tool is not peeled — the value runs as the author's argument
262
+ and files no row.
263
+ - The answer that stands is the peeled JSON; a re-ask (`output-retry`,
264
+ `step-nudge`, `evidence-recheck`) quotes the emission — the string the
265
+ provider returned — into the conversation, never the peeled form. A policy
266
+ halt hands the app the peeled args (`PolicyHaltError.proposed.args`), the
267
+ same carrier law every pause carrier follows.
268
+ - Exported from the root: `RESERVED_ARGUMENT` and the row types
269
+ (`FindingsLedger`, `FindingsRow`, `BasisRow`, `StandingRow`,
270
+ `ConflictRow`, `ConflictWitness`, `FindingsDeclaration`, `Basis`,
271
+ `Expect`, `Standing`).
272
+
273
+ ### Added — served from the ledger
274
+
275
+ - The answer turn reads the ledger. Once the model has declared at least one
276
+ standing, every later call on an armed agent is served a request-only
277
+ system piece composed from the folded ledger — `source: 'findings'` on the
278
+ receipt's `system.pieces` and on `servedAt(snapshot, k).system.pieces` —
279
+ headed by the context contract's own field meanings and holding `facts`,
280
+ `limitations`, `evidenceRefs` and `nextSteps`, each bucket marked "declared
281
+ by the model" and quoting the declaration (a conflict names both witnesses
282
+ and no verdict; a ruled-out branch is its one line), then `noise` as a count
283
+ and `undeclared` for the results nobody named — the honest absence, never
284
+ `open`. Bounded, every overflow stated, an empty bucket omitted. On the
285
+ wire, and only on the wire, a tool result the model judged `noise` or
286
+ `ruled-out` is served as a ticket, `{"collapsed":true,"standing":…,
287
+ "toolCallId":…}`, in place of its content; `open` and undeclared results
288
+ stay verbatim; nothing is dropped or reordered, and `toolName` /
289
+ `toolCallId` are untouched, so the tool_use/tool_result pair stays
290
+ wire-valid. `history` never changes — the window stage stays its only
291
+ writer — and `servedAt` rebuilds the piece and the collapse with the same
292
+ functions in the same order, so `receiptAt(k)` agrees by construction on
293
+ an armed run with a collapsed entry; no new served gap, `withheld`
294
+ untouched. Why a piece and a ticket rather than a rewritten history: the
295
+ record must keep the emission, and a judged result's bytes should not be
296
+ read again at full size on every call after the model said what it was.
297
+ - `.findings({ serve })` chooses how much of the pile stays:
298
+ `'ledger-and-facts'` (the default — facts, open and undeclared results
299
+ verbatim beside the piece; noise and ruled-out as tickets) or
300
+ `'ledger-only'` (fact results as tickets too; the model answers from the
301
+ piece, its own paraphrase). The second is BENCH-GATED: shipped so
302
+ `bench/findings-shuffle.mjs` can measure it on a real model, not a
303
+ recommendation, and never a default until that run shows the answer does
304
+ not drift when the same evidence arrives in a different order. The run
305
+ constant `findingsServe` is committed on every armed run so a served view
306
+ knows which dial produced the wire.
307
+ - The piece carries no per-call byte — it is a function of the folded
308
+ ledger and the wire's tool ids and nothing else — because it joins the ONE
309
+ system block the cache marker covers (`systemPromptCachePolicy` is
310
+ `'always'` by default, and the Anthropic adapters mark the whole joined
311
+ system prompt as one block). A re-ask is served the same system bytes and
312
+ the same system hash as the call before it. What that does NOT save: a
313
+ model that declares on every call moves the ledger on every call, so from
314
+ the first standing on each such call writes a new system cache entry and
315
+ reads none, and on Anthropic's wire the message breakpoints behind it miss
316
+ too. The feature's claim is wire bytes, not cache reads; no bench in the
317
+ tree counts cache tokens yet, and the design page names the trade.
318
+ - Measured (`npm run bench:findings`, mock provider, 20 tool calls, a
319
+ planted fact every 3rd): the piece carries all 6 planted facts at the
320
+ answer turn on every armed row — including under a sliding window that had
321
+ evicted 4 of them — and 13 of the 14 noise results are tickets, the noise
322
+ share of tool-result bytes on the wire falling from 93.5% to 15.2% with no
323
+ window; the fourteenth is the last batch's, undeclared by the
324
+ no-outputSchema law and served in full. The design page has the printed
325
+ table and its reading; no real-model number exists yet.
326
+ - Grounding is unchanged: the evidence gate keeps indexing raw history, so a
327
+ faithful ledger fact grounds through the result it cites and an invented
328
+ value is flagged. The choice seam no longer credits the whole system prompt
329
+ while a request-only piece is joined, so a subject id invented in a `fact`
330
+ assertion cannot excuse an argument equal to it; an unarmed agent takes the
331
+ branch it always did. The grouped chart carries `findingsLedger` across the
332
+ call-llm boundary, so both chart shapes serve byte-equal text.
333
+
334
+ ### Unchanged — an agent without `.findings()` records the bytes it recorded before
335
+
336
+ - Every decoration, peel, write, piece and event is gated on the door. The
337
+ 15 byte-identity references under `test/core/tools/reference/` were run on
338
+ this tree first and pass untouched; ONE new reference, `agent-findings`,
339
+ was generated alone — and regenerated alone for the served piece, its
340
+ delta (the run constant, the piece on one receipt, nothing else) on the
341
+ test file's header. `npm run bench:findings` runs each configuration
342
+ unarmed, armed with the mock declaring, and armed with the mock declaring
343
+ NOTHING, and exits non-zero if any of the six baseline columns moves under
344
+ that silent arm — they do not: an armed agent whose model declares no
345
+ standing is served the bytes it always was, plus the instruction.
346
+ - The name was proved before it shipped: `_findings` survives every
347
+ provider's `inputSchema` mapping byte-for-byte, `required` untouched —
348
+ Anthropic, OpenAI, Gemini, Bedrock, Ollama, Foundry (hosted and local) and
349
+ both browser providers (`test/adapters/reservedArgumentSurvives.test.ts`).
350
+
8
351
  ## [9.100.0] - 2026-09-16
9
352
 
10
353
  ### Added — the delivered answer names its shape guarantee
package/CLAUDE.md CHANGED
@@ -157,7 +157,7 @@ Traps: `src/observability/` holds the finder IMPLEMENTATIONS (canonical home; `d
157
157
  - **Embedder fingerprint** (8.9.0) → `Embedder.id` (optional; every shipped embedder sets one, and NONE include dims — the store appends `@<dims>` itself, so an id carrying its own size double-stamps) + `indexDocuments` defaulting `embedderId` to it + `SqliteVectorStore.reconcileFingerprint` (the only comparison site). Rule: dimensions ALWAYS decide, model ids decide only when BOTH sides named themselves — refusing on an absent name would block the majority of callers who never pass `embedderId`.
158
158
  - **Retrieval record** (8.8.0) → FOUR stages write one object in sequence: `loadRelevant` (candidates+scores+threshold verdicts) → `pickByBudget` (re-marks admitted→over-budget/over-max-entries) → `formatDefault` (`promptFragment` + `promptPosition`) → the read mount's outputMapper lifts it to root as `retrievalEvidence_<id>`. `memoryRecallInjections` then splits ONE recall into one ActiveInjection PER CHUNK — guarded by a byte-equality check (`fragments.join('\n\n') === systemContent`) that falls back to the single injection rather than change the prompt. `rank` (score order) and `promptPosition` (picker order) are DIFFERENT and both load-bearing: joining fragments in rank order reproduces the right bytes in a sequence the model never saw.
159
159
  - **AgentState** → all 8 stages/ files, both builders' mappers, memory-wire STRING-TYPED keys ('runIdentity'/'turnNumber'/… buildAgentChart.ts:177-180 — not refactor-safe), finalizeResult's `reliabilityFail*`/`policyHalt*` reads (rename silently kills the typed errors).
160
- - **events/** → 111 typed events across 24 domains (counts anti-drift-tested against this file — update BOTH when adding events): ALL_EVENT_TYPES exhaustiveness tests, DomainWildcard hand-list, ~42 importers (recorders, strategies, stream, commentary).
160
+ - **events/** → 113 typed events across 25 domains (counts anti-drift-tested against this file — update BOTH when adding events): ALL_EVENT_TYPES exhaustiveness tests, DomainWildcard hand-list, ~42 importers (recorders, strategies, stream, commentary).
161
161
  - **Run-configuration manifest (9.41.0)** → `agentfootprint.agent.run_configured`, the JOIN KEY that turns N runs into N labelled ARMS: one event naming the adapters/strategies in play (provider+model, reactMode, each memory's declared strategy/retrieval/embedder, window, graph posture+classifier, evidence posture, artifacts-present). Composed by the PURE `core/agent/runManifest.ts`, dispatched from `Agent.emitRunManifest()` at the END of `createExecutor` — the ONE funnel `run()` AND `resume()` share, both of which mint a fresh runId. Direct `dispatcher.dispatch` with a STATED pseudo-stage (`run-configured#0`), the `emitToolSessionReport` precedent — there is no stage yet, and `minimalMeta()` would make the one joinable-by-design event unjoinable. TWO laws, both tested: NAMES ONLY (a store is reported PRESENT and unnamed rather than identified by a directory/endpoint — `MemoryStore` and `ArtifactStore` declare no id), and ABSENT means "not configured", never a guessed `'default'`. Graph presence is read off `skillGraphNextSkill`, NOT `skillGraphCascade` (a 9.16-style mount sets no cascade and would read as "no graph"). `MemoryDefinition` gained `strategy`/`retrieval`/`embedderId` for it — declared names the compiled pipeline had closed over, the `store`-in-the-open precedent.
162
162
  - **adapters/types.ts LLMMessage/LLMRequest** → 62 importers: tool_use round-trip (toolCalls.ts:115-135), wire assembly (callLLM.ts:150-160), providers, cache strategies, security/extractSequence, reliability loop.
163
163
  - **Cache** → strategy registration is a MODULE SIDE EFFECT (src/index.ts:15-17); an entry point skipping that import silently falls back to NoOp. Resolved once per Agent at construction (Agent.ts:347).
package/README.md CHANGED
@@ -931,6 +931,7 @@ The flowchart, recorders, and tests don't change between dev and prod.
931
931
  - Human-in-the-loop pause / resume — a tool calls `pauseHere(...)` (or `askHuman(...)`); `isPaused(result)` hands you a JSON-serializable checkpoint, and `agent.resume(checkpoint, input)` continues hours later on a different server
932
932
  - Resilience primitives — `withRetry`, `withFallback`, `withCircuitBreaker`, `.outputFallback`, `agent.resumeOnError`
933
933
  - Context Integrity — deterministic checks at the seams where a run contradicts ITSELF: a tool parked but still on the wire, a tool offered after the results grounding it were evicted, an answer field that disagrees with the fact it claims to report (`.claims()`, requires `.outputSchema()`). Nothing is blocked or rewritten — each defect is one typed finding, and every run files a disposition ledger so "no findings" and "no check ran" stay different states. `integrityPosture: 'dev'` adds the liveness proofs (a start-of-run canary; `CheckerDeadError` instead of a green report from a checker that never ran). Read it back with `find_context_errors` over a recording — [Context Integrity](https://agentfootprint.dev/docs/monitor/context-integrity/)
934
+ - Findings ledger — `.findings()` adds one reserved optional argument, `_findings`, to every served tool schema: the model declares why it makes each call (`basis`: `direct` or `exploratory`) and, on its next call or as a top-level key of a JSON answer, the standing of each earlier result by its tool_result id (`fact` with the assertions it stands on, `open`, `ruled-out`, `noise`). Zero extra model calls. The argument is peeled off before the tool, the middleware or the validator sees the call; the rows land as one append-only `findingsLedger` on the run's state (`agent.findings()`), with a `conflict` row whenever two stood-on readings disagree. Nothing is inferred — a call that declares nothing files nothing — and nothing is served differently yet: the ledger is a record. An agent that never calls `.findings()` is byte-identical to one built before it existed
934
935
  - 60+ typed observability events — `agent` · `composition` · `context` · `stream` · `tools` · `skill` · `memory` · `cache` · `cost` · `permission` · `eval` · `embedding` · `pause` · `error` · `fallback` · `resilience` · `reliability` · `risk`
935
936
 
936
937
  **Debugging & compliance** (`agentfootprint/observe`)
@@ -252,7 +252,7 @@ const agent = Agent.create({ provider, model })
252
252
  agent.on('agentfootprint.context.evaluated', (e) => console.log(e.payload.activeIds));
253
253
  ```
254
254
 
255
- **111 typed events across 24 domains.** Two subscription shapes and no third:
255
+ **113 typed events across 25 domains.** Two subscription shapes and no third:
256
256
  `'*'` (every event) and `'agentfootprint.<domain>.*'` (one domain). **`'agentfootprint.*'`
257
257
  is not a pattern** — TypeScript rejects it, and at runtime it would match nothing.
258
258