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