agentfootprint 9.91.0 → 9.93.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 (98) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +236 -0
  3. package/CLAUDE.md +1 -1
  4. package/ai-instructions/claude-code/SKILL.md +1 -1
  5. package/dist/core/Agent.js +38 -7
  6. package/dist/core/Agent.js.map +1 -1
  7. package/dist/core/LLMCall.js +6 -4
  8. package/dist/core/LLMCall.js.map +1 -1
  9. package/dist/core/agent/buildAgentMessageApiChart.js +10 -0
  10. package/dist/core/agent/buildAgentMessageApiChart.js.map +1 -1
  11. package/dist/core/agent/buildToolRegistry.js +90 -33
  12. package/dist/core/agent/buildToolRegistry.js.map +1 -1
  13. package/dist/core/agent/messageApiReceipt.js +1 -0
  14. package/dist/core/agent/messageApiReceipt.js.map +1 -1
  15. package/dist/core/agent/stages/callLLM.js +18 -7
  16. package/dist/core/agent/stages/callLLM.js.map +1 -1
  17. package/dist/core/agent/stages/toolCalls.js +176 -28
  18. package/dist/core/agent/stages/toolCalls.js.map +1 -1
  19. package/dist/core/agent/stages/window.js +12 -2
  20. package/dist/core/agent/stages/window.js.map +1 -1
  21. package/dist/core/agent/window/evictedTurns.js +60 -0
  22. package/dist/core/agent/window/evictedTurns.js.map +1 -0
  23. package/dist/core/slots/buildToolsSlot.js +209 -62
  24. package/dist/core/slots/buildToolsSlot.js.map +1 -1
  25. package/dist/esm/core/Agent.d.ts +4 -0
  26. package/dist/esm/core/Agent.js +39 -8
  27. package/dist/esm/core/Agent.js.map +1 -1
  28. package/dist/esm/core/LLMCall.js +6 -4
  29. package/dist/esm/core/LLMCall.js.map +1 -1
  30. package/dist/esm/core/agent/buildAgentMessageApiChart.js +10 -0
  31. package/dist/esm/core/agent/buildAgentMessageApiChart.js.map +1 -1
  32. package/dist/esm/core/agent/buildToolRegistry.d.ts +80 -31
  33. package/dist/esm/core/agent/buildToolRegistry.js +90 -33
  34. package/dist/esm/core/agent/buildToolRegistry.js.map +1 -1
  35. package/dist/esm/core/agent/messageApiReceipt.d.ts +3 -1
  36. package/dist/esm/core/agent/messageApiReceipt.js +1 -0
  37. package/dist/esm/core/agent/messageApiReceipt.js.map +1 -1
  38. package/dist/esm/core/agent/stages/callLLM.d.ts +9 -0
  39. package/dist/esm/core/agent/stages/callLLM.js +18 -7
  40. package/dist/esm/core/agent/stages/callLLM.js.map +1 -1
  41. package/dist/esm/core/agent/stages/toolCalls.d.ts +43 -5
  42. package/dist/esm/core/agent/stages/toolCalls.js +174 -27
  43. package/dist/esm/core/agent/stages/toolCalls.js.map +1 -1
  44. package/dist/esm/core/agent/stages/window.d.ts +8 -0
  45. package/dist/esm/core/agent/stages/window.js +12 -2
  46. package/dist/esm/core/agent/stages/window.js.map +1 -1
  47. package/dist/esm/core/agent/types.d.ts +15 -0
  48. package/dist/esm/core/agent/window/evictedTurns.d.ts +52 -0
  49. package/dist/esm/core/agent/window/evictedTurns.js +56 -0
  50. package/dist/esm/core/agent/window/evictedTurns.js.map +1 -0
  51. package/dist/esm/core/slots/buildToolsSlot.d.ts +100 -0
  52. package/dist/esm/core/slots/buildToolsSlot.js +207 -61
  53. package/dist/esm/core/slots/buildToolsSlot.js.map +1 -1
  54. package/dist/esm/events/payloads.d.ts +92 -18
  55. package/dist/esm/events/registry.d.ts +5 -1
  56. package/dist/esm/events/registry.js +4 -0
  57. package/dist/esm/events/registry.js.map +1 -1
  58. package/dist/esm/lib/time-travel/receipt.d.ts +71 -28
  59. package/dist/esm/lib/time-travel/receipt.js +33 -17
  60. package/dist/esm/lib/time-travel/receipt.js.map +1 -1
  61. package/dist/esm/lib/time-travel/servedView.d.ts +31 -22
  62. package/dist/esm/lib/time-travel/servedView.js +78 -43
  63. package/dist/esm/lib/time-travel/servedView.js.map +1 -1
  64. package/dist/events/registry.js +4 -0
  65. package/dist/events/registry.js.map +1 -1
  66. package/dist/lib/time-travel/receipt.js +33 -17
  67. package/dist/lib/time-travel/receipt.js.map +1 -1
  68. package/dist/lib/time-travel/servedView.js +78 -43
  69. package/dist/lib/time-travel/servedView.js.map +1 -1
  70. package/dist/types/core/Agent.d.ts +4 -0
  71. package/dist/types/core/Agent.d.ts.map +1 -1
  72. package/dist/types/core/LLMCall.d.ts.map +1 -1
  73. package/dist/types/core/agent/buildAgentMessageApiChart.d.ts.map +1 -1
  74. package/dist/types/core/agent/buildToolRegistry.d.ts +80 -31
  75. package/dist/types/core/agent/buildToolRegistry.d.ts.map +1 -1
  76. package/dist/types/core/agent/messageApiReceipt.d.ts +3 -1
  77. package/dist/types/core/agent/messageApiReceipt.d.ts.map +1 -1
  78. package/dist/types/core/agent/stages/callLLM.d.ts +9 -0
  79. package/dist/types/core/agent/stages/callLLM.d.ts.map +1 -1
  80. package/dist/types/core/agent/stages/toolCalls.d.ts +43 -5
  81. package/dist/types/core/agent/stages/toolCalls.d.ts.map +1 -1
  82. package/dist/types/core/agent/stages/window.d.ts +8 -0
  83. package/dist/types/core/agent/stages/window.d.ts.map +1 -1
  84. package/dist/types/core/agent/types.d.ts +15 -0
  85. package/dist/types/core/agent/types.d.ts.map +1 -1
  86. package/dist/types/core/agent/window/evictedTurns.d.ts +53 -0
  87. package/dist/types/core/agent/window/evictedTurns.d.ts.map +1 -0
  88. package/dist/types/core/slots/buildToolsSlot.d.ts +100 -0
  89. package/dist/types/core/slots/buildToolsSlot.d.ts.map +1 -1
  90. package/dist/types/events/payloads.d.ts +92 -18
  91. package/dist/types/events/payloads.d.ts.map +1 -1
  92. package/dist/types/events/registry.d.ts +5 -1
  93. package/dist/types/events/registry.d.ts.map +1 -1
  94. package/dist/types/lib/time-travel/receipt.d.ts +71 -28
  95. package/dist/types/lib/time-travel/receipt.d.ts.map +1 -1
  96. package/dist/types/lib/time-travel/servedView.d.ts +31 -22
  97. package/dist/types/lib/time-travel/servedView.d.ts.map +1 -1
  98. package/package.json +1 -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 — 109 typed events across 24 domains
547
+ ### Observability — 111 typed events across 24 domains
548
548
 
549
549
  ```typescript
550
550
  agent.on('agentfootprint.context.injected', (e) =>
package/CHANGELOG.md CHANGED
@@ -7,6 +7,242 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [9.93.0] - 2026-09-11
11
+
12
+ **The receipt says which strategy, and what the window dropped.** Four of the
13
+ seven standing recorded-not-built entries close, one is verified rather than
14
+ rebuilt, and one is assessed and half-built. Every item below is a behaviour
15
+ change to a shipped record — a new receipt key on every minting chart, a gap
16
+ that stops firing on three of them, a field that starts being written — which
17
+ is why this is a minor and not a patch. Nothing that had no name collision, no
18
+ window and no cache strategy records a different byte except the one new key.
19
+
20
+ ### Added
21
+
22
+ - **`Receipt.cache.strategy: string | null`** — WHICH cache strategy the
23
+ request went through (its registry `providerName`; `'*'` is the built-in
24
+ pass-through every agent runs), or `null` where none stood between assembly
25
+ and the port. WHY: `cache.transform: 'unchanged'` is the honest verdict both
26
+ when a strategy returned what it was given and when there was no strategy at
27
+ all, so the served view had no way to know a rewrite was impossible and
28
+ raised `cache-transform` on every view — including `LLMCall` and the two
29
+ message-API charts, where a reader was told what a cache strategy may have
30
+ done beside a call none could touch (entry 8). No fourth `transform` value
31
+ was added; the fact is its own field, minted by the one owner
32
+ (`buildReceipt` refuses to mint without it). A receipt from before this
33
+ release has no key, and a reader treats that as "cannot say", never as
34
+ `null`.
35
+ ```ts
36
+ receiptAt(agent.getSnapshot()!, 1)!.cache.strategy; // '*' — an agent on the mock provider
37
+ receiptAt(llmCall.getSnapshot()!, 1)!.cache.strategy; // null — nothing between assembly and the port
38
+ ```
39
+ - **`Receipt.omittedForAttention` is written** — by the agent chart's window
40
+ stage, for every turn it evicts for budget at an iteration's head. WHY: the
41
+ field was declared in 9.88.0 for "why did the model not know that?" and
42
+ excused as "no chart in this library supplies it" (entry 9) — a measurement
43
+ that had looked at the slots, which drop nothing, and not at the window,
44
+ which does (`context.evicted`, `reason: 'budget'`), and never wrote it. The
45
+ window hands what left to the call-llm mint on an in-memory handle
46
+ (`window/evictedTurns.ts`), the seam the compaction meter already crosses —
47
+ never a scope read, which on every windowless run would be a tracked read of
48
+ an absent key and a phantom context source per loop. Each hash is the
49
+ evicted turn's own `messages.entries[].hash`, so a drop on epoch k's receipt
50
+ pairs with the turn as an earlier receipt served it — the pairing law,
51
+ driven on a real sliding window. Absent now means nothing was dropped before
52
+ that call. The field left `UNGAPPED_FIELDS` and is named by
53
+ `no-receipt-on-chart`, the one gap that can lose it. An agent without a
54
+ window hands both stages the deps they always had.
55
+ ```ts
56
+ const agent = Agent.create({ provider, model }).system('bot').tool(lookup)
57
+ .window(slidingWindow({ keepRecentTurns: 1 })).build();
58
+ await agent.run({ message: 'go' });
59
+ receiptAt(agent.getSnapshot()!, 3)!.omittedForAttention; // { count: 2, hashes: [...] } — the pair that left at this head
60
+ ```
61
+
62
+ ### Changed
63
+
64
+ - **`cache-transform` is raised only where a rewrite was possible** — where
65
+ the receipt names a strategy, or where no receipt can say (a receipt-less
66
+ view, a pre-9.93.0 receipt, a refused shape). WHY: entry 8 — a sentence
67
+ about what a strategy may have done was printed where none could run. Only a
68
+ receipt that SAYS `null` lifts it; absence still raises, so this is not the
69
+ inference-from-absence the entry refused. Measured: `LLMCall` and both
70
+ message-API charts with a run id now read `['provider-defaults']`; the
71
+ receipt-less message-API view still reads
72
+ `['no-receipt-on-chart', 'cache-transform']`, honestly. The printed sentence
73
+ is unchanged and every clause of it still holds where it prints.
74
+ ```ts
75
+ servedAt(llmCall.getSnapshot()!, 1)!.gaps.map((g) => g.gap); // ['provider-defaults'] (was ['cache-transform', 'provider-defaults'])
76
+ ```
77
+ - **`cache-transform` names `tools.forced`, `tools.withheld` and
78
+ `cache.strategy`** — fourteen fields, the whole request the strategy holds
79
+ (`params` alone stays off, read past it). WHY: entry 7 — both `tools`
80
+ fields are written from assembly's own decision and never from
81
+ `preparedRequest`, so a strategy that drops the forced answer tool from
82
+ `request.tools` leaves a receipt whose `tools.forced` names a tool the port
83
+ never carried, and the gap did not warn. Driven: an unforcing strategy on a
84
+ `'tool-forced'` agent — wire has no tool and no forcing, receipt says
85
+ `tools.forced: 'respond_with_schema'` and `params.toolChoice` absent, the
86
+ gap names the half that describes the decision. Reading them off the
87
+ prepared request instead was NOT done: that would describe a different fact
88
+ under the same name.
89
+ - **The receipt-conformance law checks the cache verdict outright where no
90
+ strategy ran** — `transform: 'unchanged'`, `transformHash: null`,
91
+ `markersApplied: []`, and no `cache-transform` excuse; where one ran, the gap
92
+ must excuse the unrebuildable fields as before.
93
+ - **Byte-identity references regenerated** (`test/core/tools/reference/`),
94
+ with the whole delta against the 9.92.1 set on record in the test's header:
95
+ the new `cache.strategy` key on every minting fixture, the gap's longer
96
+ field list on every agent view, and the gap leaving the three no-strategy
97
+ views. No message, tool or other key moved on any fixture.
98
+
99
+ ### Verified
100
+
101
+ - **Recorded-not-built entries 4 and 5 hold under the 9.92.0 law** and are
102
+ marked built (verified 2026-09-11 against the 9.92.1 `dist`, before any edit
103
+ of this release). `.selfExplain()`'s `run_overview` against an always-visible
104
+ skill, a never-activated scoped skill and a stepped skill: dispatch follows
105
+ the offer on every epoch (the never-activated skill no longer answers the
106
+ framework's own contract — the framework does), `tools.shadowed` names the
107
+ wire's party and never `provider(skill-scoped:self-explain)`, and the losing
108
+ claim is named by `tools.claim_swallowed` on every epoch it lost. The
109
+ reservation this entry names is deliberately untouched; the measured output
110
+ is pasted on the entries and pinned as `offer-and-answer.test.ts` §7.
111
+
112
+ ### Assessed
113
+
114
+ - **Entry 10 ("an assertion can be weaker than its clause") is split, and the
115
+ mechanical half is built.** Every clause in `gap-sentences.test.ts` declares
116
+ the fields it is about; a seam hands its assertion a view or receipt with
117
+ one declared field altered, then removed; a contract requires the assertion
118
+ to fail under at least one. The first run found one insensitivity and it is
119
+ fixed. What remains is the quantifier half — a clause about "every field" or
120
+ "both chart shapes" asserted on fewer runs — which needs per-clause hand
121
+ work and is named on the entry rather than faked by a mechanism.
122
+
123
+ ## [9.92.1] - 2026-09-11
124
+
125
+ ### Fixed
126
+
127
+ - **9.92.0 never reached npm; this release is the same code.** Its publish job
128
+ failed the docs site's deferred-demo gzip ceiling by 0.4 KB (421.0 KB against
129
+ 420.6 KB): the interactive demo bundles the library itself, and 9.92.0's
130
+ tool-resolution family rides the same main-entry graph as the receipt family
131
+ did in 9.88.0. The ceiling is re-baselined ~2% over the measurement with the
132
+ reasoning beside the number, and the docs-truth report regenerated. No
133
+ library code changes between 9.92.0 and 9.92.1. This is the fourth raise of
134
+ that ceiling for a family the demo never calls; the dynamic-import fix named
135
+ in 9.61.0 is the next move rather than a fifth raise.
136
+
137
+ ## [9.92.0] - 2026-09-11
138
+
139
+ **The offer and the answer are one party.** At every LLM call the model is
140
+ OFFERED a list of tool contracts (the wire; the receipt hashes each one), and
141
+ when it calls a name something ANSWERS. Until this release those two halves
142
+ could come from different parties for one name: the tools slot merges
143
+ `[static, provider, skill, step]` first-occurrence-wins, while dispatch
144
+ consulted the build-time registry first — so a provider's contract on the wire
145
+ was answered by a skill's `execute` (an INACTIVE skill's, even), a provider's
146
+ `skip_step` by the framework's (which then advanced the procedure on a call the
147
+ model made against somebody else's contract), and the report that exists for
148
+ this seam walked `activeInjections` and named the provider on epochs whose wire
149
+ carried the skill's contract. A claimant that lost both the wire and the
150
+ dispatch was simply dead, 22 rows of it in the divergence walk's baseline, with
151
+ nothing on the record. `docs/design/2026-09-the-offer-and-the-answer.md` states
152
+ the law; recorded-not-built entries 1, 2 and 3, the `claim-swallowed` bullet
153
+ under entry 1 and the 9.91.0 tools-slot follow-up are marked built.
154
+
155
+ **The law:** for every tool name on a call, exactly one party owns the OFFER and
156
+ the same party owns the ANSWER — or the record names the disagreement. Three
157
+ consequences, one example each:
158
+
159
+ - **Dispatch follows the offer.** `buildToolsSlot.ts` · `mergeWire` is the one
160
+ pass that produces the wire AND the record of who won each name
161
+ (`ServedToolParties`, closure-shared like `ProviderToolCache`, never scope
162
+ state); `toolCalls.ts` · `lookupTool` reads it first. A provider and a
163
+ never-activated scoped skill both claiming `shared_tool`: the wire carries
164
+ the provider's contract, the provider's tool answers, and the skill's
165
+ `execute` answers no call while the provider holds the name — nor after the
166
+ provider withdraws it, because the skill's contract was never what the
167
+ model read: that call is REFUSED as a recorded tool result (`toolCalls.ts`
168
+ · `notServedResult`). A name NOT on this epoch's wire — a held-out step
169
+ tool, a parked map's tool, a scoped tool named from a restored transcript —
170
+ still dispatches (the capability law's held-out clause, `epoch-laws.test.ts`
171
+ 1(a)–(e)), but only to the party the model LAST read the name under
172
+ (`ServedToolParties.lastServed`) or the name's only holder when it was never
173
+ served; every such dispatch is on the record as the new
174
+ `agentfootprint.tools.answered_off_wire` (`{ toolName, toolCallId,
175
+ iteration, answeredBy, answeredById? }`). A pause that re-dispatches on
176
+ resume (a middleware ask, a check-in, a credential consent) carries the
177
+ served party on its checkpoint (`pausedToolParty`, pause-path-only), so a
178
+ resume in a FRESH Agent instance — empty closure, no Compose — cannot fall
179
+ back to the build-time map's first holder either.
180
+ - **The report's subject is the wire.** `agentfootprint.tools.shadowed` now
181
+ fires once per contested name per iteration when two contracts COMPETED for
182
+ the wire, and `schemaFrom`/`dispatchTo` both name the wire's party — they
183
+ agree by construction. A stepped skill and a provider sharing a name draw
184
+ `skill 'desk-stepped'` in both halves on every epoch, never `'provider'`.
185
+ The vocabulary gained `'framework'` (`ToolNameChannel`), so an auto-attach
186
+ is named as itself.
187
+ - **A dead claim is reported.** New `agentfootprint.tools.claim_swallowed`
188
+ (`ToolsClaimSwallowedPayload`: `{ toolName, iteration, lostBy, lostById?,
189
+ wonBy, wonById? }`, names only) fires once per iteration for every party
190
+ whose claim to a name is held by somebody else — whether its contract
191
+ competed (an active skill against a provider) or never reached the merge
192
+ (the same skill while inactive; a provider tool whose name a static
193
+ `.tool()` owns; the framework's `skip_step` between tenures). Identity is by
194
+ IMPLEMENTATION: two skills sharing one `Tool` reference (documented-legal)
195
+ are one claim and draw nothing. Now 111 typed events across 24 domains.
196
+
197
+ **`skip_step` is a claimant like any other** (`buildToolRegistry.ts` ·
198
+ `toolClaimants`, the registry's new list of every build-time claimant per
199
+ name — a list, not a winner). Its schema still merges last, so it rides the
200
+ wire only when nobody else put the name forward; when a provider serves
201
+ `skip_step`, the provider's tool answers and the step bookkeeping keys on the
202
+ framework's own instance having answered (`toolCalls.ts` ·
203
+ `frameworkSkipStepAnswered`) — the procedure does not advance. The build-time
204
+ refusals (a static `.tool()` or a skill tool named `skip_step`/`present`) stand.
205
+
206
+ **`buildAgentMessageApiChart` hands the model the declared tool set on every
207
+ turn.** Its tools-slot mount now says `arrayMerge: ArrayMergeMode.Replace`, as
208
+ the agent charts always did; turn 2 no longer serves `['weather','weather']`.
209
+
210
+ **Byte-identity for every run with no name collision.**
211
+ `test/core/tools/byte-identity.test.ts` drives fifteen collision-free shapes
212
+ (both agent react modes, a skill-graph hop, a stepped skill, a parked map, the
213
+ wrap-up, a tool-forced output, three provider shapes, two skills sharing one
214
+ `Tool` reference, `LLMCall`, both message-API charts) and compares `commitLog`
215
+ and `servedAt(k)` against references under `test/core/tools/reference/`
216
+ generated on the 9.91.0 tree. All fifteen are identical; the
217
+ receipt-conformance suite is unchanged and green on all four chart shapes.
218
+
219
+ **The divergence walk, re-recorded — and widened.** Every `contract-swap`
220
+ row (12) and every `report-misattributed` row (10) is GONE and ratcheted so
221
+ it cannot reopen quietly; the `claim-swallowed` family is 35 rows, every one
222
+ carrying what `tools.claim_swallowed` said about it. The walk gained the
223
+ cross-epoch × collision cases it never had (walk D: a provider withdrawing a
224
+ name an inactive skill also holds; a fresh-instance resume of a provider's
225
+ call) and a kind for the held-out dispatch the law keeps, `answered-off-wire`
226
+ (4 rows, each carrying the event). The harness moved to
227
+ `test/core/agent/toolDivergenceWalk.harness.ts` so the reproductions in
228
+ `test/core/tools/offer-and-answer.test.ts` — the entries' own, verbatim, each
229
+ red on 9.91.0, plus the review's own shapes — drive the same configurations.
230
+
231
+ **Consumer-facing changes, stated plainly.** (1) A provider tool that shares a
232
+ name with a skill tool now RUNS where the skill's used to — the contract the
233
+ model read is the one that answers. (2) `tools.shadowed` fires on
234
+ configurations that were silent (a provider against a static `.tool()` or a
235
+ framework auto-attach) and no longer names `'provider'` as `schemaFrom` where
236
+ the wire carried the skill's contract; `dispatchTo` now equals `schemaFrom`.
237
+ (3) Two new events, `tools.claim_swallowed` and `tools.answered_off_wire`.
238
+ (4) A call to a name that left the wire, whose last-served party can no longer
239
+ answer, is refused with a recorded result where it used to be answered by
240
+ whichever party the build-time map held first; the same sentence answers a
241
+ provider-served call resumed in a fresh instance (where it used to read
242
+ `Unknown tool`). (5) Two stale comments in `buildToolsSlot.ts` and
243
+ `buildToolRegistry.ts` corrected in the same diff; `Agent.ts`'s "fresh chart
244
+ per run()" comment corrected — the chart is built once at construction.
245
+
10
246
  ## [9.91.0] - 2026-09-10
11
247
 
12
248
  **Every chart that serves a model now mints a receipt.** A receipt is the proof
package/CLAUDE.md CHANGED
@@ -154,7 +154,7 @@ Traps: `src/observability/` holds the finder IMPLEMENTATIONS (canonical home; `d
154
154
  - **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`.
155
155
  - **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.
156
156
  - **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).
157
- - **events/** → 109 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).
157
+ - **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).
158
158
  - **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.
159
159
  - **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.
160
160
  - **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).
@@ -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
- **109 typed events across 24 domains.** Two subscription shapes and no third:
255
+ **111 typed events across 24 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
 
@@ -50,6 +50,7 @@ const ReliabilityRecorder_js_1 = require("../recorders/core/ReliabilityRecorder.
50
50
  const ResilienceRecorder_js_1 = require("../recorders/core/ResilienceRecorder.js");
51
51
  const CheckInRecorder_js_1 = require("../recorders/core/CheckInRecorder.js");
52
52
  const CompactionMeter_js_1 = require("../recorders/core/CompactionMeter.js");
53
+ const evictedTurns_js_1 = require("./agent/window/evictedTurns.js");
53
54
  const durabilityBarrier_js_1 = require("./durabilityBarrier.js");
54
55
  const toolSessions_js_1 = require("./toolSessions.js");
55
56
  const eventMeta_js_1 = require("../bridge/eventMeta.js");
@@ -264,6 +265,10 @@ class Agent extends RunnerBase_js_1.RunnerBase {
264
265
  /** The instrument the window stage reads mid-run (adapter-reported usage +
265
266
  * per-message provenance). Only ever created alongside a strategy. */
266
267
  compactionMeterHandle;
268
+ /** The window stage's seam to the receipt (9.93.0): what left the window at
269
+ * this iteration's head, read by the call-llm mint in the same iteration.
270
+ * Only ever created alongside a strategy, like the meter. */
271
+ evictedTurnsHandle;
267
272
  /** Snapshot read-tracking policy (#18/#14) — forwarded to the internal
268
273
  * executor. Agent default is `'summary'` (cheap markers), NOT
269
274
  * footprintjs's `'full'`. See AgentOptions.readTracking. */
@@ -612,6 +617,7 @@ class Agent extends RunnerBase_js_1.RunnerBase {
612
617
  if (windowStrategy !== undefined) {
613
618
  this.windowStrategy = windowStrategy;
614
619
  this.compactionMeterHandle = (0, CompactionMeter_js_1.compactionMeter)();
620
+ this.evictedTurnsHandle = (0, evictedTurns_js_1.createEvictedTurnsHandle)();
615
621
  }
616
622
  // The two governance chains. Empty arrays (not undefined) so every read
617
623
  // site is a plain `.length > 0` test rather than an optional dance.
@@ -2221,6 +2227,9 @@ class Agent extends RunnerBase_js_1.RunnerBase {
2221
2227
  // `conversationId` is deliberately not carried: it is a thread, not a
2222
2228
  // person, and `sessionId` beside it is the fact the transport delivered.
2223
2229
  const actor = runOptions?.identity ?? this.lastRunIdentity;
2230
+ // A fresh run starts with no evictions filed — the previous run's last
2231
+ // visit must not be read as this run's (`window/evictedTurns.ts`).
2232
+ this.evictedTurnsHandle?.clear();
2224
2233
  this.currentRunContext = {
2225
2234
  runStartMs: Date.now(),
2226
2235
  runId: (0, RunnerBase_js_1.makeRunId)(),
@@ -2980,7 +2989,7 @@ class Agent extends RunnerBase_js_1.RunnerBase {
2980
2989
  // auto-attached `present` tool when a store is attached, 9.22.0) +
2981
2990
  // skill-supplied tools (with autoActivate scoping); validates
2982
2991
  // name uniqueness; produces the dispatch map.
2983
- const { registryByName, toolSchemas, toolDeclaringSkills } = (0, buildToolRegistry_js_1.buildToolRegistry)(registry, this.injections, {
2992
+ const { registryByName, toolSchemas, toolDeclaringSkills, toolClaimants } = (0, buildToolRegistry_js_1.buildToolRegistry)(registry, this.injections, {
2984
2993
  hasArtifactStore: artifactStore !== undefined,
2985
2994
  });
2986
2995
  // A statically registered tool that declares `wants` on an agent with no
@@ -3137,13 +3146,25 @@ class Agent extends RunnerBase_js_1.RunnerBase {
3137
3146
  const messagesSubflow = (0, buildMessagesSlot_js_1.buildMessagesSlot)({
3138
3147
  ...(budget?.messages !== undefined && { budgetCap: budget.messages }),
3139
3148
  });
3140
- // Per-run cache shared between buildToolsSlot (writer, each
3141
- // iteration) and buildToolCallsHandler (reader, same iteration).
3142
- // Holds the resolved Tool[] from `provider.list(ctx)` so dispatch
3143
- // doesn't re-invoke `list()` — vital for async network providers.
3144
- // A fresh chart (and thus fresh cache) is built per `agent.run()`,
3145
- // so concurrent runs don't share state.
3149
+ // Cache shared between buildToolsSlot (writer, each iteration) and
3150
+ // buildToolCallsHandler (reader, same iteration). Holds the resolved
3151
+ // Tool[] from `provider.list(ctx)` so dispatch doesn't re-invoke `list()`
3152
+ // — vital for async network providers.
3153
+ //
3154
+ // LIFETIME, stated correctly (9.92.0): the chart is built ONCE, at
3155
+ // construction (`initChart`), and reused by every `run()` and `resume()`
3156
+ // of this Agent — so this cache and the record below outlive a run. They
3157
+ // are safe because one Agent runs one turn at a time and the slot
3158
+ // overwrites them every iteration before dispatch reads them. What that
3159
+ // does NOT cover is a resume in a FRESH instance, whose closure is empty
3160
+ // and whose Compose never re-runs before the resumed dispatch — which is
3161
+ // why the pause paths carry `pausedToolParty` on the checkpoint.
3146
3162
  const providerToolCache = { current: [] };
3163
+ // Who put each name on the wire (9.92.0) — the same closure-shared shape,
3164
+ // written by the tools slot's merge and read by dispatch so the party
3165
+ // whose contract the model read is the party that answers. `lastServed`
3166
+ // is the run's memory of the same fact, for a name that left the wire.
3167
+ const servedTools = { current: new Map(), lastServed: new Map() };
3147
3168
  const readSkillFor = this.readSkillOfferFor();
3148
3169
  // Per-role skill visibility. The RESOLVER is handed to the tools slot, which
3149
3170
  // is the fact's one owner: it resolves the ids once per iteration and
@@ -3225,6 +3246,8 @@ class Agent extends RunnerBase_js_1.RunnerBase {
3225
3246
  integrityLedger: this.integrityLedgerHolder,
3226
3247
  ...(this.externalToolProvider && { toolProvider: this.externalToolProvider }),
3227
3248
  ...(this.externalToolProvider && { providerToolCache }),
3249
+ toolClaimants,
3250
+ servedTools,
3228
3251
  ...(readSkillFor && { readSkillFor }),
3229
3252
  ...(hiddenSkillIds && { hiddenSkillIds }),
3230
3253
  ...(budget?.tools !== undefined && { budgetCap: budget.tools }),
@@ -3239,6 +3262,9 @@ class Agent extends RunnerBase_js_1.RunnerBase {
3239
3262
  // …and its off switch. Value-conditional, so an agent on the default
3240
3263
  // hands `callLLM` exactly the dep bag it always did.
3241
3264
  ...(this.recordReceiptValue === false && { recordReceipt: false }),
3265
+ // …and the window's evictions for `omittedForAttention` (9.93.0) — only
3266
+ // an agent with a window strategy has any, and only it hands the seam.
3267
+ ...(this.evictedTurnsHandle !== undefined && { evictedTurns: this.evictedTurnsHandle }),
3242
3268
  provider,
3243
3269
  model,
3244
3270
  ...(temperature !== undefined && { temperature }),
@@ -3313,6 +3339,9 @@ class Agent extends RunnerBase_js_1.RunnerBase {
3313
3339
  run: (0, window_js_1.buildWindowStage)({
3314
3340
  strategy: this.windowStrategy,
3315
3341
  meter: this.compactionMeterHandle,
3342
+ ...(this.evictedTurnsHandle !== undefined && {
3343
+ evictedTurns: this.evictedTurnsHandle,
3344
+ }),
3316
3345
  agentModel: model,
3317
3346
  providerName: provider.name,
3318
3347
  getRunId: () => this.currentRunContext?.runId,
@@ -3397,6 +3426,8 @@ class Agent extends RunnerBase_js_1.RunnerBase {
3397
3426
  }),
3398
3427
  ...(this.externalToolProvider && { externalToolProvider: this.externalToolProvider }),
3399
3428
  ...(this.externalToolProvider && { providerToolCache }),
3429
+ servedTools,
3430
+ toolClaimants,
3400
3431
  ...(permissionChecker && { permissionChecker }),
3401
3432
  ...(credentialProvider && { credentialProvider }),
3402
3433
  // The claim-check store (9.21.0). Absent → not one new line runs in