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.
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +343 -0
- package/CLAUDE.md +1 -1
- package/README.md +1 -0
- package/ai-instructions/claude-code/SKILL.md +1 -1
- package/dist/core/Agent.js +207 -2
- package/dist/core/Agent.js.map +1 -1
- package/dist/core/agent/AgentBuilder.js +118 -2
- package/dist/core/agent/AgentBuilder.js.map +1 -1
- package/dist/core/agent/buildAgentChart.js +11 -0
- package/dist/core/agent/buildAgentChart.js.map +1 -1
- package/dist/core/agent/buildDynamicAgentChart.js +22 -0
- package/dist/core/agent/buildDynamicAgentChart.js.map +1 -1
- package/dist/core/agent/buildToolRegistry.js +38 -0
- package/dist/core/agent/buildToolRegistry.js.map +1 -1
- package/dist/core/agent/findings/ledger.js +261 -0
- package/dist/core/agent/findings/ledger.js.map +1 -0
- package/dist/core/agent/findings/offer.js +179 -0
- package/dist/core/agent/findings/offer.js.map +1 -0
- package/dist/core/agent/findings/reserved.js +465 -0
- package/dist/core/agent/findings/reserved.js.map +1 -0
- package/dist/core/agent/findings/serve.js +400 -0
- package/dist/core/agent/findings/serve.js.map +1 -0
- package/dist/core/agent/findings/types.js +56 -0
- package/dist/core/agent/findings/types.js.map +1 -0
- package/dist/core/agent/stages/callLLM.js +78 -10
- package/dist/core/agent/stages/callLLM.js.map +1 -1
- package/dist/core/agent/stages/outputRetry.js +5 -0
- package/dist/core/agent/stages/outputRetry.js.map +1 -1
- package/dist/core/agent/stages/route.js +71 -12
- package/dist/core/agent/stages/route.js.map +1 -1
- package/dist/core/agent/stages/seed.js +33 -1
- package/dist/core/agent/stages/seed.js.map +1 -1
- package/dist/core/agent/stages/toolCalls.js +84 -11
- package/dist/core/agent/stages/toolCalls.js.map +1 -1
- package/dist/core/agent/stages/window.js +110 -10
- package/dist/core/agent/stages/window.js.map +1 -1
- package/dist/core/agent/window/index.js +3 -1
- package/dist/core/agent/window/index.js.map +1 -1
- package/dist/core/agent/window/ledgerFactPins.js +200 -0
- package/dist/core/agent/window/ledgerFactPins.js.map +1 -0
- package/dist/core/agent/window/turns.js +45 -11
- package/dist/core/agent/window/turns.js.map +1 -1
- package/dist/core/runCheckpoint.js +64 -1
- package/dist/core/runCheckpoint.js.map +1 -1
- package/dist/core/slots/buildToolsSlot.js +35 -1
- package/dist/core/slots/buildToolsSlot.js.map +1 -1
- package/dist/esm/core/Agent.d.ts +55 -0
- package/dist/esm/core/Agent.js +207 -2
- package/dist/esm/core/Agent.js.map +1 -1
- package/dist/esm/core/agent/AgentBuilder.d.ts +65 -0
- package/dist/esm/core/agent/AgentBuilder.js +118 -2
- package/dist/esm/core/agent/AgentBuilder.js.map +1 -1
- package/dist/esm/core/agent/buildAgentChart.d.ts +14 -0
- package/dist/esm/core/agent/buildAgentChart.js +11 -0
- package/dist/esm/core/agent/buildAgentChart.js.map +1 -1
- package/dist/esm/core/agent/buildDynamicAgentChart.js +22 -0
- package/dist/esm/core/agent/buildDynamicAgentChart.js.map +1 -1
- package/dist/esm/core/agent/buildToolRegistry.d.ts +7 -0
- package/dist/esm/core/agent/buildToolRegistry.js +38 -0
- package/dist/esm/core/agent/buildToolRegistry.js.map +1 -1
- package/dist/esm/core/agent/findings/ledger.d.ts +105 -0
- package/dist/esm/core/agent/findings/ledger.js +254 -0
- package/dist/esm/core/agent/findings/ledger.js.map +1 -0
- package/dist/esm/core/agent/findings/offer.d.ts +112 -0
- package/dist/esm/core/agent/findings/offer.js +171 -0
- package/dist/esm/core/agent/findings/offer.js.map +1 -0
- package/dist/esm/core/agent/findings/reserved.d.ts +127 -0
- package/dist/esm/core/agent/findings/reserved.js +457 -0
- package/dist/esm/core/agent/findings/reserved.js.map +1 -0
- package/dist/esm/core/agent/findings/serve.d.ts +149 -0
- package/dist/esm/core/agent/findings/serve.js +397 -0
- package/dist/esm/core/agent/findings/serve.js.map +1 -0
- package/dist/esm/core/agent/findings/types.d.ts +179 -0
- package/dist/esm/core/agent/findings/types.js +53 -0
- package/dist/esm/core/agent/findings/types.js.map +1 -0
- package/dist/esm/core/agent/stages/callLLM.d.ts +32 -0
- package/dist/esm/core/agent/stages/callLLM.js +78 -10
- package/dist/esm/core/agent/stages/callLLM.js.map +1 -1
- package/dist/esm/core/agent/stages/outputRetry.js +5 -0
- package/dist/esm/core/agent/stages/outputRetry.js.map +1 -1
- package/dist/esm/core/agent/stages/route.d.ts +8 -1
- package/dist/esm/core/agent/stages/route.js +71 -12
- package/dist/esm/core/agent/stages/route.js.map +1 -1
- package/dist/esm/core/agent/stages/seed.d.ts +28 -0
- package/dist/esm/core/agent/stages/seed.js +33 -1
- package/dist/esm/core/agent/stages/seed.js.map +1 -1
- package/dist/esm/core/agent/stages/toolCalls.d.ts +17 -0
- package/dist/esm/core/agent/stages/toolCalls.js +82 -9
- package/dist/esm/core/agent/stages/toolCalls.js.map +1 -1
- package/dist/esm/core/agent/stages/window.d.ts +22 -0
- package/dist/esm/core/agent/stages/window.js +111 -11
- package/dist/esm/core/agent/stages/window.js.map +1 -1
- package/dist/esm/core/agent/types.d.ts +113 -0
- package/dist/esm/core/agent/window/index.d.ts +1 -0
- package/dist/esm/core/agent/window/index.js +1 -0
- package/dist/esm/core/agent/window/index.js.map +1 -1
- package/dist/esm/core/agent/window/ledgerFactPins.d.ts +147 -0
- package/dist/esm/core/agent/window/ledgerFactPins.js +194 -0
- package/dist/esm/core/agent/window/ledgerFactPins.js.map +1 -0
- package/dist/esm/core/agent/window/strategy.d.ts +25 -0
- package/dist/esm/core/agent/window/turns.d.ts +25 -0
- package/dist/esm/core/agent/window/turns.js +45 -11
- package/dist/esm/core/agent/window/turns.js.map +1 -1
- package/dist/esm/core/agent/window/types.d.ts +64 -0
- package/dist/esm/core/runCheckpoint.d.ts +27 -1
- package/dist/esm/core/runCheckpoint.js +64 -1
- package/dist/esm/core/runCheckpoint.js.map +1 -1
- package/dist/esm/core/slots/buildToolsSlot.d.ts +20 -0
- package/dist/esm/core/slots/buildToolsSlot.js +35 -1
- package/dist/esm/core/slots/buildToolsSlot.js.map +1 -1
- package/dist/esm/events/payloads.d.ts +65 -0
- package/dist/esm/events/registry.d.ts +7 -1
- package/dist/esm/events/registry.js +6 -0
- package/dist/esm/events/registry.js.map +1 -1
- package/dist/esm/events/types.d.ts +3 -1
- package/dist/esm/index.d.ts +2 -1
- package/dist/esm/index.js +6 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/lib/time-travel/servedView.js +54 -10
- package/dist/esm/lib/time-travel/servedView.js.map +1 -1
- package/dist/events/registry.js +6 -0
- package/dist/events/registry.js.map +1 -1
- package/dist/index.js +11 -4
- package/dist/index.js.map +1 -1
- package/dist/lib/time-travel/servedView.js +54 -10
- package/dist/lib/time-travel/servedView.js.map +1 -1
- package/dist/types/core/Agent.d.ts +55 -0
- package/dist/types/core/Agent.d.ts.map +1 -1
- package/dist/types/core/agent/AgentBuilder.d.ts +65 -0
- package/dist/types/core/agent/AgentBuilder.d.ts.map +1 -1
- package/dist/types/core/agent/buildAgentChart.d.ts +14 -0
- package/dist/types/core/agent/buildAgentChart.d.ts.map +1 -1
- package/dist/types/core/agent/buildDynamicAgentChart.d.ts.map +1 -1
- package/dist/types/core/agent/buildToolRegistry.d.ts +7 -0
- package/dist/types/core/agent/buildToolRegistry.d.ts.map +1 -1
- package/dist/types/core/agent/findings/ledger.d.ts +106 -0
- package/dist/types/core/agent/findings/ledger.d.ts.map +1 -0
- package/dist/types/core/agent/findings/offer.d.ts +113 -0
- package/dist/types/core/agent/findings/offer.d.ts.map +1 -0
- package/dist/types/core/agent/findings/reserved.d.ts +128 -0
- package/dist/types/core/agent/findings/reserved.d.ts.map +1 -0
- package/dist/types/core/agent/findings/serve.d.ts +150 -0
- package/dist/types/core/agent/findings/serve.d.ts.map +1 -0
- package/dist/types/core/agent/findings/types.d.ts +180 -0
- package/dist/types/core/agent/findings/types.d.ts.map +1 -0
- package/dist/types/core/agent/stages/callLLM.d.ts +32 -0
- package/dist/types/core/agent/stages/callLLM.d.ts.map +1 -1
- package/dist/types/core/agent/stages/outputRetry.d.ts.map +1 -1
- package/dist/types/core/agent/stages/route.d.ts +8 -1
- package/dist/types/core/agent/stages/route.d.ts.map +1 -1
- package/dist/types/core/agent/stages/seed.d.ts +28 -0
- package/dist/types/core/agent/stages/seed.d.ts.map +1 -1
- package/dist/types/core/agent/stages/toolCalls.d.ts +17 -0
- package/dist/types/core/agent/stages/toolCalls.d.ts.map +1 -1
- package/dist/types/core/agent/stages/window.d.ts +22 -0
- package/dist/types/core/agent/stages/window.d.ts.map +1 -1
- package/dist/types/core/agent/types.d.ts +113 -0
- package/dist/types/core/agent/types.d.ts.map +1 -1
- package/dist/types/core/agent/window/index.d.ts +1 -0
- package/dist/types/core/agent/window/index.d.ts.map +1 -1
- package/dist/types/core/agent/window/ledgerFactPins.d.ts +148 -0
- package/dist/types/core/agent/window/ledgerFactPins.d.ts.map +1 -0
- package/dist/types/core/agent/window/strategy.d.ts +25 -0
- package/dist/types/core/agent/window/strategy.d.ts.map +1 -1
- package/dist/types/core/agent/window/turns.d.ts +25 -0
- package/dist/types/core/agent/window/turns.d.ts.map +1 -1
- package/dist/types/core/agent/window/types.d.ts +64 -0
- package/dist/types/core/agent/window/types.d.ts.map +1 -1
- package/dist/types/core/runCheckpoint.d.ts +27 -1
- package/dist/types/core/runCheckpoint.d.ts.map +1 -1
- package/dist/types/core/slots/buildToolsSlot.d.ts +20 -0
- package/dist/types/core/slots/buildToolsSlot.d.ts.map +1 -1
- package/dist/types/events/payloads.d.ts +65 -0
- package/dist/types/events/payloads.d.ts.map +1 -1
- package/dist/types/events/registry.d.ts +7 -1
- package/dist/types/events/registry.d.ts.map +1 -1
- package/dist/types/events/types.d.ts +3 -1
- package/dist/types/events/types.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/lib/time-travel/servedView.d.ts.map +1 -1
- 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 —
|
|
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/** →
|
|
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
|
-
**
|
|
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
|
|