agentfootprint 9.85.0 → 9.86.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/CHANGELOG.md +376 -24
- package/dist/core/Agent.js +15 -4
- package/dist/core/Agent.js.map +1 -1
- package/dist/core/agent/AgentBuilder.js.map +1 -1
- package/dist/core/agent/buildAgentChart.js +5 -0
- package/dist/core/agent/buildAgentChart.js.map +1 -1
- package/dist/core/agent/buildDynamicAgentChart.js +22 -4
- package/dist/core/agent/buildDynamicAgentChart.js.map +1 -1
- package/dist/core/agent/buildToolRegistry.js +12 -1
- package/dist/core/agent/buildToolRegistry.js.map +1 -1
- package/dist/core/agent/selfCallNotice.js +20 -7
- package/dist/core/agent/selfCallNotice.js.map +1 -1
- package/dist/core/agent/skillBrains.js +3 -2
- package/dist/core/agent/skillBrains.js.map +1 -1
- package/dist/core/agent/stages/stepNudge.js +13 -4
- package/dist/core/agent/stages/stepNudge.js.map +1 -1
- package/dist/core/agent/stages/toolCalls.js +389 -58
- package/dist/core/agent/stages/toolCalls.js.map +1 -1
- package/dist/core/agent/stages/wrapUp.js +43 -6
- package/dist/core/agent/stages/wrapUp.js.map +1 -1
- package/dist/core/agent/window/currentRequest.js +4 -2
- package/dist/core/agent/window/currentRequest.js.map +1 -1
- package/dist/core/slots/buildToolsSlot.js +20 -4
- package/dist/core/slots/buildToolsSlot.js.map +1 -1
- package/dist/doors/skill-graph.js +4 -1
- package/dist/doors/skill-graph.js.map +1 -1
- package/dist/esm/core/Agent.js +15 -4
- package/dist/esm/core/Agent.js.map +1 -1
- package/dist/esm/core/agent/AgentBuilder.d.ts +2 -1
- package/dist/esm/core/agent/AgentBuilder.js.map +1 -1
- package/dist/esm/core/agent/buildAgentChart.js +5 -0
- package/dist/esm/core/agent/buildAgentChart.js.map +1 -1
- package/dist/esm/core/agent/buildDynamicAgentChart.js +22 -4
- package/dist/esm/core/agent/buildDynamicAgentChart.js.map +1 -1
- package/dist/esm/core/agent/buildToolRegistry.d.ts +20 -0
- package/dist/esm/core/agent/buildToolRegistry.js +12 -1
- package/dist/esm/core/agent/buildToolRegistry.js.map +1 -1
- package/dist/esm/core/agent/selfCallNotice.d.ts +20 -7
- package/dist/esm/core/agent/selfCallNotice.js +20 -7
- package/dist/esm/core/agent/selfCallNotice.js.map +1 -1
- package/dist/esm/core/agent/skillBrains.d.ts +12 -4
- package/dist/esm/core/agent/skillBrains.js +3 -2
- package/dist/esm/core/agent/skillBrains.js.map +1 -1
- package/dist/esm/core/agent/stages/stepNudge.d.ts +13 -4
- package/dist/esm/core/agent/stages/stepNudge.js +13 -4
- package/dist/esm/core/agent/stages/stepNudge.js.map +1 -1
- package/dist/esm/core/agent/stages/toolCalls.d.ts +171 -0
- package/dist/esm/core/agent/stages/toolCalls.js +385 -57
- package/dist/esm/core/agent/stages/toolCalls.js.map +1 -1
- package/dist/esm/core/agent/stages/wrapUp.d.ts +33 -2
- package/dist/esm/core/agent/stages/wrapUp.js +41 -5
- package/dist/esm/core/agent/stages/wrapUp.js.map +1 -1
- package/dist/esm/core/agent/types.d.ts +21 -4
- package/dist/esm/core/agent/window/currentRequest.d.ts +4 -2
- package/dist/esm/core/agent/window/currentRequest.js +4 -2
- package/dist/esm/core/agent/window/currentRequest.js.map +1 -1
- package/dist/esm/core/slots/buildToolsSlot.d.ts +1 -1
- package/dist/esm/core/slots/buildToolsSlot.js +20 -4
- package/dist/esm/core/slots/buildToolsSlot.js.map +1 -1
- package/dist/esm/doors/skill-graph.d.ts +1 -1
- package/dist/esm/doors/skill-graph.js +4 -1
- package/dist/esm/doors/skill-graph.js.map +1 -1
- package/dist/esm/events/payloads.d.ts +26 -2
- package/dist/esm/index.js +6 -5
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/lib/injection-engine/index.d.ts +1 -1
- package/dist/esm/lib/injection-engine/index.js +4 -1
- package/dist/esm/lib/injection-engine/index.js.map +1 -1
- package/dist/esm/lib/injection-engine/skillGraph.d.ts +56 -0
- package/dist/esm/lib/injection-engine/skillGraph.js +29 -3
- package/dist/esm/lib/injection-engine/skillGraph.js.map +1 -1
- package/dist/esm/lib/injection-engine/skillSteps.d.ts +20 -1
- package/dist/esm/lib/injection-engine/skillSteps.js +24 -3
- package/dist/esm/lib/injection-engine/skillSteps.js.map +1 -1
- package/dist/esm/lib/injection-engine/skillToolDescriptors.d.ts +18 -0
- package/dist/esm/lib/injection-engine/skillToolDescriptors.js +103 -10
- package/dist/esm/lib/injection-engine/skillToolDescriptors.js.map +1 -1
- package/dist/esm/lib/injection-engine/types.d.ts +12 -10
- package/dist/esm/lib/injection-engine/types.js +5 -4
- package/dist/esm/lib/injection-engine/types.js.map +1 -1
- package/dist/esm/lib/saidByPerson.d.ts +48 -15
- package/dist/esm/lib/saidByPerson.js +52 -17
- package/dist/esm/lib/saidByPerson.js.map +1 -1
- package/dist/esm/lib/spokenIds.d.ts +52 -0
- package/dist/esm/lib/spokenIds.js +11 -0
- package/dist/esm/lib/spokenIds.js.map +1 -0
- package/dist/esm/lib/trace-toolpack/traceToolpack.js +12 -5
- package/dist/esm/lib/trace-toolpack/traceToolpack.js.map +1 -1
- package/dist/index.js +6 -5
- package/dist/index.js.map +1 -1
- package/dist/lib/injection-engine/index.js +5 -2
- package/dist/lib/injection-engine/index.js.map +1 -1
- package/dist/lib/injection-engine/skillGraph.js +31 -4
- package/dist/lib/injection-engine/skillGraph.js.map +1 -1
- package/dist/lib/injection-engine/skillSteps.js +24 -3
- package/dist/lib/injection-engine/skillSteps.js.map +1 -1
- package/dist/lib/injection-engine/skillToolDescriptors.js +103 -10
- package/dist/lib/injection-engine/skillToolDescriptors.js.map +1 -1
- package/dist/lib/injection-engine/types.js +5 -4
- package/dist/lib/injection-engine/types.js.map +1 -1
- package/dist/lib/saidByPerson.js +53 -18
- package/dist/lib/saidByPerson.js.map +1 -1
- package/dist/lib/spokenIds.js +15 -0
- package/dist/lib/spokenIds.js.map +1 -0
- package/dist/lib/trace-toolpack/traceToolpack.js +12 -5
- package/dist/lib/trace-toolpack/traceToolpack.js.map +1 -1
- package/dist/types/core/Agent.d.ts.map +1 -1
- package/dist/types/core/agent/AgentBuilder.d.ts +2 -1
- package/dist/types/core/agent/AgentBuilder.d.ts.map +1 -1
- 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 +20 -0
- package/dist/types/core/agent/buildToolRegistry.d.ts.map +1 -1
- package/dist/types/core/agent/selfCallNotice.d.ts +20 -7
- package/dist/types/core/agent/selfCallNotice.d.ts.map +1 -1
- package/dist/types/core/agent/skillBrains.d.ts +12 -4
- package/dist/types/core/agent/skillBrains.d.ts.map +1 -1
- package/dist/types/core/agent/stages/stepNudge.d.ts +13 -4
- package/dist/types/core/agent/stages/stepNudge.d.ts.map +1 -1
- package/dist/types/core/agent/stages/toolCalls.d.ts +171 -0
- package/dist/types/core/agent/stages/toolCalls.d.ts.map +1 -1
- package/dist/types/core/agent/stages/wrapUp.d.ts +33 -2
- package/dist/types/core/agent/stages/wrapUp.d.ts.map +1 -1
- package/dist/types/core/agent/types.d.ts +21 -4
- package/dist/types/core/agent/types.d.ts.map +1 -1
- package/dist/types/core/agent/window/currentRequest.d.ts +4 -2
- package/dist/types/core/agent/window/currentRequest.d.ts.map +1 -1
- package/dist/types/core/slots/buildToolsSlot.d.ts +1 -1
- package/dist/types/core/slots/buildToolsSlot.d.ts.map +1 -1
- package/dist/types/doors/skill-graph.d.ts +1 -1
- package/dist/types/doors/skill-graph.d.ts.map +1 -1
- package/dist/types/events/payloads.d.ts +26 -2
- package/dist/types/events/payloads.d.ts.map +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/lib/injection-engine/index.d.ts +1 -1
- package/dist/types/lib/injection-engine/index.d.ts.map +1 -1
- package/dist/types/lib/injection-engine/skillGraph.d.ts +56 -0
- package/dist/types/lib/injection-engine/skillGraph.d.ts.map +1 -1
- package/dist/types/lib/injection-engine/skillSteps.d.ts +20 -1
- package/dist/types/lib/injection-engine/skillSteps.d.ts.map +1 -1
- package/dist/types/lib/injection-engine/skillToolDescriptors.d.ts +18 -0
- package/dist/types/lib/injection-engine/skillToolDescriptors.d.ts.map +1 -1
- package/dist/types/lib/injection-engine/types.d.ts +12 -10
- package/dist/types/lib/injection-engine/types.d.ts.map +1 -1
- package/dist/types/lib/saidByPerson.d.ts +48 -15
- package/dist/types/lib/saidByPerson.d.ts.map +1 -1
- package/dist/types/lib/spokenIds.d.ts +53 -0
- package/dist/types/lib/spokenIds.d.ts.map +1 -0
- package/dist/types/lib/trace-toolpack/traceToolpack.d.ts.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,325 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [9.86.0] - 2026-09-05
|
|
11
|
+
|
|
12
|
+
Every hand-counted list in 9.84.0 and 9.85.0 was short by one or two.
|
|
13
|
+
|
|
14
|
+
"Five classes of `role: 'user'` message are authored by this library" — seven
|
|
15
|
+
were. "Reachability OR posture" — three arms refuse. "Sixty configurations,
|
|
16
|
+
crossing every source" — the cross skipped four of the seven sources it named,
|
|
17
|
+
and there are seventy-six. The rules that catch a sentence which outlives its
|
|
18
|
+
moment were a transcript of the wordings that had already escaped: thirteen of
|
|
19
|
+
fifteen plausible forward-looking sentences walked straight through them.
|
|
20
|
+
|
|
21
|
+
None of those was a typo. Each was a fact the library computed in one place and
|
|
22
|
+
re-derived, by hand, wherever a second consumer needed it — and a hand-derived
|
|
23
|
+
list is a list that is short the day after somebody adds the next case. Three
|
|
24
|
+
of them are now WALKS rather than counts: the user-turn producers are parsed out
|
|
25
|
+
of `src/` with the TypeScript compiler, every sentence-shaped literal in `src/`
|
|
26
|
+
is run through the model-facing rules with a file and a line on failure, and the
|
|
27
|
+
offer/dispatch cross iterates its source list whole instead of filtering it.
|
|
28
|
+
Two more are single owners: one function answers "is this `read_skill` target
|
|
29
|
+
the cursor?", and one scope key answers "which skill ids may this role see?".
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
|
|
33
|
+
- **`read_skill` refusals answered for a fact nobody owned.** Five call sites
|
|
34
|
+
needed to know that `makeReachableSkills` filters the cursor out of its own
|
|
35
|
+
successor set — correct for a MOVE, silent about a READ. Three of them wrote
|
|
36
|
+
their own `requested === cursor` line. Two never heard: a tool proposing
|
|
37
|
+
`propose-transition` back to the cursor's own skill was refused as unreachable,
|
|
38
|
+
and the `skill_read` permission gate was asked to grant a capability the model
|
|
39
|
+
was already exercising, then told the model that its own skill was "not
|
|
40
|
+
available in this context".
|
|
41
|
+
|
|
42
|
+
`classifySkillTarget({ cursor, target, hops, open })` now owns it, returning
|
|
43
|
+
`'self' | 'hop' | 'open' | 'unreachable'`. It is a pure function in the
|
|
44
|
+
injection engine, exported through the `agentfootprint/context` barrel and the
|
|
45
|
+
`agentfootprint/skill-graph` door — the same function object through both, so a
|
|
46
|
+
foreign host cannot re-derive it wrongly either. The five consumers switch on
|
|
47
|
+
it: the gate arm, `describeOffer`, the tool-effects judge, the `skill_read`
|
|
48
|
+
permission gate, and the refusal composer. `makeReachableSkills` keeps its own
|
|
49
|
+
exclusion — it is the PRODUCER of the hop set, and excluding the cursor is what
|
|
50
|
+
a move means — and its doc comment now says the exclusion is about movement and
|
|
51
|
+
sends the next reader to `classifySkillTarget`.
|
|
52
|
+
|
|
53
|
+
- **A refusal could name a skill the caller's own policy hides.** Role visibility
|
|
54
|
+
was a property of one builder: `Agent.hiddenSkillIdsNow()` fed
|
|
55
|
+
`buildReadSkillTool` and nothing else. So the description named nothing hidden
|
|
56
|
+
while the gate, one stage downstream, composed its refusals — and filled
|
|
57
|
+
`skill.rejected.allowed` — from the graph's raw sets.
|
|
58
|
+
|
|
59
|
+
The tools slot now resolves the hidden set once per iteration, publishes it on
|
|
60
|
+
`scope.hiddenSkillIds`, and both chart shapes bubble it. The gate keeps two sets
|
|
61
|
+
on purpose: the RAW one it judges with, and the filtered one it speaks with. It
|
|
62
|
+
judges with the raw set because a narrowing may take a schema off the wire and
|
|
63
|
+
may never take a name out of the dispatch map — filtering admission would remove
|
|
64
|
+
a capability, which the monotone rule forbids. In practice a hidden id never
|
|
65
|
+
reaches the gate, because the same checker denies it upstream; the filter is what
|
|
66
|
+
makes that true by construction rather than by coincidence.
|
|
67
|
+
|
|
68
|
+
- **Two refusal composers that contradicted each other forty lines apart are one.**
|
|
69
|
+
`skillRefusal` and `postureRefusal` are replaced by `composeReadSkillRefusal`,
|
|
70
|
+
and every arm of it is a past fact about the one call it names. Gone with them:
|
|
71
|
+
"from here" (deixis — a different place on every re-read), "Pick one of these, or
|
|
72
|
+
finish" (an exhortation in a string that persists for the rest of the run), and a
|
|
73
|
+
posture arm that named a hop the very next arm would have declined. A refusal now
|
|
74
|
+
opens `read_skill("X") was not granted on that call:` and every clause after it
|
|
75
|
+
refers back to that call.
|
|
76
|
+
|
|
77
|
+
- **`Unknown tool: X` told the model it was wrong and never what would have
|
|
78
|
+
worked.** Both dispatch doors now compose one exported `unknownToolResult`, which
|
|
79
|
+
names the dispatch roster: `Unknown tool 'X' on that call. Tool names that
|
|
80
|
+
resolved to an implementation on that call: …`, or, with an empty roster, that
|
|
81
|
+
none did. The leading `Unknown tool` token is preserved, so every matcher on it
|
|
82
|
+
is untouched.
|
|
83
|
+
|
|
84
|
+
It says *resolved*, not *could be dispatched*, because two gates sit between
|
|
85
|
+
resolution and a tool running — the `tool_call` permission check and the
|
|
86
|
+
middleware chain — and neither is asked to phrase an error. And the roster is
|
|
87
|
+
role-filtered before it is named: it used to read the dispatch map raw and could
|
|
88
|
+
name a tool belonging to a skill the caller's own policy hides, which is the
|
|
89
|
+
leak the refusals had just closed, one sentence over. `buildToolRegistry` now
|
|
90
|
+
returns `toolDeclaringSkills` (tool name → the skills that declare it) from the
|
|
91
|
+
walk it was already doing, and a name is withheld only when EVERY declaring
|
|
92
|
+
skill is hidden — a tool two skills share stays named. Dispatch is untouched.
|
|
93
|
+
|
|
94
|
+
- **A filtered-empty list was reported as an empty one — a Lens denying what the
|
|
95
|
+
Fold holds.** Three sentences branched on `length > 0` over an already-filtered
|
|
96
|
+
array, so "the graph held nothing" and "the role filter emptied it" composed the
|
|
97
|
+
same words. A cursor whose only declared hop was hidden answered `read_skill`
|
|
98
|
+
with *"No skill was reachable from 'alpha' when that call was made."* while the
|
|
99
|
+
graph was routing `alpha`; a `'guard'` menu whose every id had been hidden since
|
|
100
|
+
the turn started said *"no menu was outstanding when that call was made.
|
|
101
|
+
Declared routes moved the cursor instead."* — two false clauses in one breath.
|
|
102
|
+
|
|
103
|
+
A model told the map is a dead end stops asking for the door it may not be
|
|
104
|
+
shown, and the checker cannot see it: every one of those sentences passes
|
|
105
|
+
`unprovable()`, because the defect is in what the composer was handed, not in
|
|
106
|
+
how it was worded. So the fact is now a type. `SpokenIds` carries both halves of
|
|
107
|
+
a filtered set — `named`, and `held` for whether the unfiltered set held
|
|
108
|
+
anything — and `held` is required, so the compiler asks every caller the
|
|
109
|
+
question every call site forgot to answer. Where a filter empties a set the
|
|
110
|
+
clause is OMITTED. Omission is free and always true; the negative is a denial.
|
|
111
|
+
|
|
112
|
+
The fourth sentence was the one the model reads to CHOOSE. `describeOffer`
|
|
113
|
+
computed its columns from an already-filtered catalog, so a cursor whose only
|
|
114
|
+
declared hop is hidden was told *"Nothing is reachable from here — answer with
|
|
115
|
+
the skill you are in, or finish."* while the graph held that edge. It classifies
|
|
116
|
+
the hop set over the unfiltered catalog now and drops the clause when the filter
|
|
117
|
+
is what emptied it; with nothing wired out at all the sentence still stands,
|
|
118
|
+
because that absence is one the description has evidence for. Reaching it meant
|
|
119
|
+
moving `SpokenIds`/`spoken` to `src/lib/spokenIds.ts`: it lived in the tool-calls
|
|
120
|
+
stage, on the wrong side of the skill-graph fence, so the description — composed
|
|
121
|
+
inside `src/lib/injection-engine/`, which may not import the agent loop — was the
|
|
122
|
+
one surface that could not use the fact its own refusals were repaired with.
|
|
123
|
+
|
|
124
|
+
- **Under a `.tree()` with nothing open, `read_skill` is no longer offered.** A
|
|
125
|
+
tree routes by predicate on every iteration and keeps no cursor, so the tool had
|
|
126
|
+
nothing it could do and a menu of one refusal is worse than no menu. The schema
|
|
127
|
+
leaves the request; the NAME stays in the dispatch map, which is the same law as
|
|
128
|
+
everywhere else. With open skills present, the description explains the tree and
|
|
129
|
+
lists exactly what a pick can open, instead of printing "Nothing is reachable
|
|
130
|
+
from here".
|
|
131
|
+
|
|
132
|
+
- **Two library-authored user turns were credited to a person.** The out-of-budget
|
|
133
|
+
wrap-up instruction and the stepped-skill nudge both append to `scope.history`
|
|
134
|
+
with `role: 'user'` and took no registered opening, so `isSaidByPerson` said a
|
|
135
|
+
person wrote them. Two things followed. The window's refusal engine could pin
|
|
136
|
+
"the current request" on the framework's own wrap-up instruction and drop the
|
|
137
|
+
real request underneath it. And a routing rule written the documented way —
|
|
138
|
+
`saidByPerson(ctx).some((m) => m.content.includes(…))` — matched on the library's
|
|
139
|
+
own bookkeeping: the wrap-up said "Do not request tools", and the nudge names a
|
|
140
|
+
skill id and every unrun step's tool name.
|
|
141
|
+
|
|
142
|
+
Both are registered now, and `LIBRARY_AUTHORED_PREFIXES` holds all six openings
|
|
143
|
+
frozen, so the writer and the recogniser read one constant. Both sentences were
|
|
144
|
+
also rewritten: each was composed once and re-read on every later call of the
|
|
145
|
+
turn, which made their present-tense clauses predictions.
|
|
146
|
+
|
|
147
|
+
- **Three trace-toolpack results said "this call" and "right now".** They are
|
|
148
|
+
anchored to the call they answer, in the past tense — including the one arm the
|
|
149
|
+
new deictic-container rule caught the first time it was ever composed.
|
|
150
|
+
|
|
151
|
+
- **`escalation` counts three kinds of refusal, and its docs named two.**
|
|
152
|
+
"Reachability OR posture" is wrong in the JSDoc behind `EscalationPolicy` and
|
|
153
|
+
`SkillGraphOptions`, in the `skill.escalated` payload doc and on the skills page:
|
|
154
|
+
the counter fires beside all three `skill.rejected` emit sites, self-call
|
|
155
|
+
included. No behaviour changed — the self-call site has counted since 9.84.0,
|
|
156
|
+
deliberately.
|
|
157
|
+
|
|
158
|
+
### Added
|
|
159
|
+
|
|
160
|
+
- **A WALK over every `role: 'user'` construction site in `src/`.**
|
|
161
|
+
`test/lib/injection-engine/userTurnProducers.test.ts` parses the tree with the
|
|
162
|
+
TypeScript compiler (a `PropertyAssignment` of `role: 'user'`, so type members
|
|
163
|
+
and comments quoting the string are not counted) and requires every site to be
|
|
164
|
+
classified as an authored frame, a person's own words, or never-in-history, each
|
|
165
|
+
with a written reason. **Thirty-five** sites are classified today: six authored
|
|
166
|
+
frames, seven person, twenty-two never-in-history. Sites are keyed by file with
|
|
167
|
+
the per-file COUNT asserted, so a new producer inside an already-listed file
|
|
168
|
+
fails as loudly as one in a new file. One producer the parser cannot see — the
|
|
169
|
+
message an injection delivers, whose role is copied off the `Injection` — is
|
|
170
|
+
named in the header and pinned by its `injectedBy` marker instead.
|
|
171
|
+
|
|
172
|
+
- **The model-facing checker judges SHAPE, not just remembered wordings.** Four
|
|
173
|
+
new rules: a present-tense copula with a capability noun, deictic-present
|
|
174
|
+
adverbs, second-person effect verbs, and a standing imperative at a clause
|
|
175
|
+
start. Fifteen plausible forward-looking sentences were written out and put
|
|
176
|
+
to the rules: "You are currently in 'alpha'", "Calling read_skill switches you
|
|
177
|
+
to beta", "The following tools are available to you: …", "Nothing is live in
|
|
178
|
+
this scope at the moment". All fifteen are caught by the rules as they stand,
|
|
179
|
+
and the suite asserts exactly that. **Thirteen** of them passed against the
|
|
180
|
+
rule list AS IT STOOD BEFORE THIS RELEASE — the number that motivated the
|
|
181
|
+
work, measured once against a list this tree no longer contains, so it is a
|
|
182
|
+
record of why the rules changed rather than something a run here can
|
|
183
|
+
reproduce. `exemptBecause` is now structurally
|
|
184
|
+
required: `BannedClause` is a discriminated union, and because the root
|
|
185
|
+
`tsconfig.json` excludes `test/`, that is proven where it can actually be
|
|
186
|
+
compiled — `test/type-regressions/`.
|
|
187
|
+
|
|
188
|
+
- **A WALK over every sentence-shaped literal in `src/`.** The registry's own
|
|
189
|
+
header used to say the gap it could not close was "a scan of `src/` … and this
|
|
190
|
+
is not that". `test/modelFacingScan.test.ts` is that: it parses every `.ts` file
|
|
191
|
+
under `src/` with the TypeScript compiler, folds `+` chains and template holes,
|
|
192
|
+
runs each literal through the rules at the persistent lifetime, and fails with
|
|
193
|
+
`file:line` unless the file's flagged literals are accounted for in a ledger of
|
|
194
|
+
**eighty-four files / one hundred and sixty-three literals**, every entry naming
|
|
195
|
+
where the string is delivered and how many literals it covers. Per-file counts
|
|
196
|
+
are the guard again. Four things it cannot see are stated in its header rather
|
|
197
|
+
than left to be found: a sentence assembled across statements, text that lives
|
|
198
|
+
in data rather than in `src/`, literals under twenty-five characters, and any
|
|
199
|
+
falsehood that avoids all the shapes.
|
|
200
|
+
|
|
201
|
+
Its ledger carries an `unrepaired` bucket of **thirty-three** literals across
|
|
202
|
+
thirteen entries that are model-facing, persistent and correctly caught, and
|
|
203
|
+
that were left alone because each needs its own tests. They are named with their
|
|
204
|
+
delivery site, so the bucket is a work list rather than a pardon. The bucket's
|
|
205
|
+
arithmetic is asserted by the suite itself — the counts above come from a run,
|
|
206
|
+
not from a report, which is the failure this whole entry is about.
|
|
207
|
+
|
|
208
|
+
- **Five live producers are registered and read.** The `read_skill` refusal
|
|
209
|
+
composer (every arm), `unknownToolResult`, the trace toolpack's inspection
|
|
210
|
+
results, and — closing the gap 9.85.0's registry named — the wrap-up and
|
|
211
|
+
stepped-skill frames at a shared `INJECTED_TURN` surface.
|
|
212
|
+
|
|
213
|
+
- **The offer/dispatch cross iterates its source list whole.** `frameworkCases()`
|
|
214
|
+
re-derived a source list inside the walk —
|
|
215
|
+
`CLAIMANTS.filter((c) => ['static', 'provider', 'skill-active'].includes(c.id))`
|
|
216
|
+
— so a second hand-written list of sources existed with nothing keeping it in
|
|
217
|
+
sync with the first, and 9.85.0's "crosses every source" was false. That mattered
|
|
218
|
+
because the framework's four auto-attach reservations each read a DIFFERENT
|
|
219
|
+
build-time list, so which source holds a contested name is precisely what decides
|
|
220
|
+
whether a reservation can see it. The walk goes from **sixty configurations to
|
|
221
|
+
seventy-six** and from **thirty-six divergence rows to forty-six**; all ten new
|
|
222
|
+
rows carry a hand-written, checked `tolerated`. Three new tests own what was
|
|
223
|
+
previously true only because somebody had typed it: that every claimant is
|
|
224
|
+
crossed against every auto-attach name, that the header's arithmetic equals the
|
|
225
|
+
recorded case count, and that a placeholder `tolerated` is refused (empty,
|
|
226
|
+
`todo`/`tbd`/`fixme`/`xxx` on a word boundary in any case, or under forty
|
|
227
|
+
characters — a floor on effort, not a measure of truth).
|
|
228
|
+
|
|
229
|
+
Eight of the ten are already-recorded seams reached through a source that had
|
|
230
|
+
never been crossed, and say so. Two are defects nobody had recorded and are
|
|
231
|
+
written up as entries 4 and 5 of `docs/design/2026-09-recorded-not-built.md`
|
|
232
|
+
rather than papered over: `.selfExplain()` reserves its trace-tool names against
|
|
233
|
+
`this.registry` and never `this.injectionList`, making it the one auto-attach
|
|
234
|
+
family with no net at all against a skill's `tools: []`; and the misattributed
|
|
235
|
+
shadow report can now name a `skill-scoped:self-explain` provider — one the
|
|
236
|
+
consumer did not write and cannot open — as the file to go look at.
|
|
237
|
+
|
|
238
|
+
- **`report-misattributed` rows carry their attribution in the row id.** A shadow
|
|
239
|
+
event's meaning lives in its `schemaFromId`/`dispatchToId`, and the row was keyed
|
|
240
|
+
on case + tool + epoch, so two reports naming different sources in one epoch — a
|
|
241
|
+
strictly worse fact than one wrong report — collapsed into one `Map` entry and
|
|
242
|
+
vanished.
|
|
243
|
+
|
|
244
|
+
- **`SkillRejectedPayload.allowed` is what the model was actually told.** Role-
|
|
245
|
+
filtered rather than the graph's raw set. Shape unchanged; only agents with a
|
|
246
|
+
`PermissionChecker` governing `'skill_read'` see any difference. The field's own
|
|
247
|
+
JSDoc says so at the call site, which is the doc a consumer actually reads.
|
|
248
|
+
|
|
249
|
+
- **`ToolRegistryArtifacts.toolDeclaringSkills`** — tool name → the ids of the
|
|
250
|
+
skills whose `inject.tools` carry it, recorded on the walk `buildToolRegistry`
|
|
251
|
+
was already doing and thrown away. Empty for an agent whose skills carry no
|
|
252
|
+
tools. Its one consumer is the unknown-tool roster's role filter; it exists so
|
|
253
|
+
that consumer does not walk `Agent.injections` a second time to re-derive what
|
|
254
|
+
this file already knew.
|
|
255
|
+
|
|
256
|
+
- **`ToolEffectPayload.stay?: true`** on `agentfootprint.tools.effect` — a
|
|
257
|
+
`propose-transition` naming the cursor's own skill is accepted as a no-op.
|
|
258
|
+
Deliberately not a fourth `outcome`, so an exhaustive consumer switch keeps
|
|
259
|
+
compiling.
|
|
260
|
+
|
|
261
|
+
- **`AgentState.hiddenSkillIds?: readonly string[]`** — the per-iteration
|
|
262
|
+
role-hidden set, written by the tools slot and read by the `read_skill` gate.
|
|
263
|
+
|
|
264
|
+
- **`ReadSkillOffer.treeRouted?: boolean`** — declares the mounted graph a decision
|
|
265
|
+
tree, which is what lets the descriptor withhold the offer.
|
|
266
|
+
|
|
267
|
+
### Two decisions worth stating plainly
|
|
268
|
+
|
|
269
|
+
- **A self-call at a MOUNTED cursor is answered BEFORE the permission gate**, because
|
|
270
|
+
it exercises no capability. `read_skill` naming the cursor's own skill activates
|
|
271
|
+
nothing and moves nothing, so there is no grant for a `PermissionChecker` to make or
|
|
272
|
+
withhold; asking it produced a denial about the one skill whose body was already in
|
|
273
|
+
that call's system prompt. The skip stops at a PARKED cursor, and deliberately: a
|
|
274
|
+
park suppresses a map's contribution without moving the cursor, so the gate below
|
|
275
|
+
reads the same id as a RE-ENGAGEMENT and puts the body and its tools back on the
|
|
276
|
+
wire — which is a capability, and the policy's question to answer. One predicate,
|
|
277
|
+
`atMountedCursor`, is what both gates ask. Every other id still goes to the policy.
|
|
278
|
+
The refusal BUDGET is
|
|
279
|
+
unchanged and still counts the self-call, including the `surfaceMode: 'both'`
|
|
280
|
+
re-read that returns the body — the 9.84.0 argument stands, and it is about the
|
|
281
|
+
loop rather than about the wording: a model that keeps asking the graph where it
|
|
282
|
+
stands instead of working is exactly the stuck run escalation exists for.
|
|
283
|
+
|
|
284
|
+
- **A `propose-transition` naming the cursor's own skill is a STAY**, accepted as a
|
|
285
|
+
no-op with `stay: true` on the event and no refusal on the result. The tool asked
|
|
286
|
+
for a state the run is already in; there is nothing to move and nothing to refuse.
|
|
287
|
+
|
|
288
|
+
### Deliberately not changed
|
|
289
|
+
|
|
290
|
+
- **The three `STATED:` prose pins** in `src/core/agent/buildToolRegistry.ts` are
|
|
291
|
+
untouched, word for word. `test/core/agent/epoch-laws.test.ts` and
|
|
292
|
+
`test/core/agent/toolDivergenceWalk.test.ts` both read them.
|
|
293
|
+
- **The escalation budget still counts `'both'`-mode self-call re-reads**, per the
|
|
294
|
+
argument above.
|
|
295
|
+
- **The grounding gate** — item 5 of the "Offer, Not Dispatch" review — is a new
|
|
296
|
+
DIAL, not a fix for anything here, and is not in this release.
|
|
297
|
+
- **The flat default is not narrowed.** `scopeTools` stays `false` until 10.0.0.
|
|
298
|
+
- **`isLibraryAuthoredTurn`** (the evidence gate's exempt corpus) is deliberately
|
|
299
|
+
narrower than `isSaidByPerson` and was not widened to the two new frames. It
|
|
300
|
+
decides who SUPPLIED a value, not who wrote a turn; widening it would change which
|
|
301
|
+
values the evidence gate exempts, with no finding behind it.
|
|
302
|
+
|
|
303
|
+
### Changelog corrections
|
|
304
|
+
|
|
305
|
+
A reader auditing this project by its changelog has to be able to trust the older
|
|
306
|
+
entries, so six sentences in 9.84.0 and 9.85.0 are corrected in place, each marked
|
|
307
|
+
where it stands:
|
|
308
|
+
|
|
309
|
+
- **9.84.0** — _"Five classes of `role: 'user'` message"_: seven kinds are
|
|
310
|
+
library-authored; the wrap-up instruction and the stepped-skill nudge went
|
|
311
|
+
unregistered until this release.
|
|
312
|
+
- **9.84.0** — _"The window's own refusal engine has always applied that rule"_: it
|
|
313
|
+
applied a three-class version, and 9.84.0 widened it to five.
|
|
314
|
+
- **9.84.0** — _"a step or park hold-out says the tools were withheld rather than
|
|
315
|
+
naming them"_: the withheld arm names the declared tools, and a parked cursor
|
|
316
|
+
never reaches the notice at all.
|
|
317
|
+
- **9.85.0** — the fifth _"model-facing sentence"_ bullet credited 9.85.0 with a
|
|
318
|
+
`read_skill` description fix that shipped in 9.84.0, and quoted a sentence that
|
|
319
|
+
existed only in a source comment. Removed, with the reason left in its place; the
|
|
320
|
+
count above it is now four.
|
|
321
|
+
- **9.85.0** — _"drives a real run per configuration. Sixty configurations,
|
|
322
|
+
thirty-six divergences"_: forty of seventy-six are driven, twenty-six are refused
|
|
323
|
+
at build and ten are not constructible; the enumeration it replaced was a
|
|
324
|
+
development draft, never a shipped list.
|
|
325
|
+
- **9.85.0** — law 1 was restated unscoped. It is scoped to the tools
|
|
326
|
+
`buildToolRegistry` routes, with the shadow seam and the walk as its recorded
|
|
327
|
+
exceptions.
|
|
328
|
+
|
|
10
329
|
## [9.85.0] - 2026-09-04
|
|
11
330
|
|
|
12
331
|
A sentence composed once and read many times is not a fact — it is a prediction.
|
|
@@ -19,7 +338,7 @@ guard asserting a boundary it cannot verify.
|
|
|
19
338
|
|
|
20
339
|
### Fixed
|
|
21
340
|
|
|
22
|
-
- **
|
|
341
|
+
- **Four model-facing sentences that outlive the moment they were true.** Each is
|
|
23
342
|
now anchored to one named call, in the past tense, after tracing it to its
|
|
24
343
|
delivery point to confirm it really is re-read:
|
|
25
344
|
|
|
@@ -34,12 +353,14 @@ guard asserting a boundary it cannot verify.
|
|
|
34
353
|
sent right now"_, on a card that rides every call while a map is parked. Its
|
|
35
354
|
falsifier is compose order, not staleness: the card is written in the
|
|
36
355
|
injection-engine pass and the tools slot that acts on the park runs after it.
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
356
|
+
|
|
357
|
+
_Corrected in 9.86.0._ A fifth bullet stood here, crediting 9.85.0 with the
|
|
358
|
+
`read_skill` description fix and quoting _"You do not need read_skill to go on
|
|
359
|
+
using it"_ as a wording it had replaced. `skillToolDescriptors.ts` has no
|
|
360
|
+
non-comment change between `v9.84.0` and `v9.85.0`: that fix shipped in 9.84.0,
|
|
361
|
+
where it is also recorded, and the quoted sentence lived only inside a source
|
|
362
|
+
comment — no release ever put it on the wire. What 9.85.0 added to that file is
|
|
363
|
+
the LENS LAW block above `describeOffer`, which is a Documentation change.
|
|
43
364
|
|
|
44
365
|
### Added
|
|
45
366
|
|
|
@@ -59,13 +380,24 @@ guard asserting a boundary it cannot verify.
|
|
|
59
380
|
`test/core/agent/toolDivergenceWalk.test.ts` crosses every source that can put
|
|
60
381
|
a name on the wire or answer to one — static, provider, MCP, always-on skill,
|
|
61
382
|
active skill, inactive skill, stepped skill — against six narrowing states and
|
|
62
|
-
the framework's auto-attach names
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
383
|
+
the framework's auto-attach names. _Corrected in 9.86.0:_ at 9.85.0 the
|
|
384
|
+
auto-attach cross did NOT reach every source — it filtered `CLAIMANTS` down to
|
|
385
|
+
three of the seven — and the walk does not drive a real run per configuration.
|
|
386
|
+
Both are true of the walk as it stands after 9.86.0 widened it, with these
|
|
387
|
+
counts. Of its **seventy-six** configurations, **forty** are driven
|
|
388
|
+
as real runs (thirty-six divergent, four clean), **twenty-six** are refused at
|
|
389
|
+
build — which is the walk exercising a refusal, and its `because` records the
|
|
390
|
+
refusal's first line — and **ten** are not constructible at all, so no run is
|
|
391
|
+
attempted. **Forty-six** divergence rows come out of the forty driven, each
|
|
392
|
+
with a mechanically derived cause and a stated reason it is tolerated. New
|
|
393
|
+
fails. Disappeared fails. Vacuous fails, and is unbaselineable.
|
|
66
394
|
|
|
67
395
|
It replaced a hand-written enumeration that claimed completeness and was
|
|
68
|
-
falsified three rounds running
|
|
396
|
+
falsified three rounds running — _corrected in 9.86.0:_ that enumeration was
|
|
397
|
+
drafted and falsified during this work, and no released version ever carried
|
|
398
|
+
it, so the walk shipped in place of a draft rather than of a shipped list.
|
|
399
|
+
|
|
400
|
+
It then found three classes nobody seeded: a
|
|
69
401
|
provider tool whose name a registry holder already owns is dead in both
|
|
70
402
|
directions and the shadow report cannot see it; the auto-attach names disagree
|
|
71
403
|
about what they refuse; and `selfExplain` is a fourth family whose reservation
|
|
@@ -74,10 +406,17 @@ guard asserting a boundary it cannot verify.
|
|
|
74
406
|
### Documentation
|
|
75
407
|
|
|
76
408
|
- **Three laws stated where the code lives**, epoch-scoped, after two earlier
|
|
77
|
-
phrasings were false in shipped configurations.
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
409
|
+
phrasings were false in shipped configurations. Law 1, as the source states it
|
|
410
|
+
and _corrected here in 9.86.0_, is SCOPED: **among the tools `buildToolRegistry`
|
|
411
|
+
routes**, every offered capability resolves to a dispatchable implementation
|
|
412
|
+
with stable identity for that epoch — same-epoch offer implies same-epoch
|
|
413
|
+
dispatch. It is not a claim about the whole wire, and the source names its
|
|
414
|
+
recorded exceptions rather than implying there are none: the SHADOW SEAM (the
|
|
415
|
+
wire list is merged one layer out in `buildToolsSlot` and carries provider
|
|
416
|
+
schemas these maps never hold), with the full enumeration delegated to
|
|
417
|
+
`test/core/agent/toolDivergenceWalk.test.ts`. The second clause is unscoped and
|
|
418
|
+
unchanged: attention may alter the offer, but omission from the offer must not
|
|
419
|
+
be presented as proof of permanent capability loss. Only static skill-registry
|
|
81
420
|
tools are known to remain dispatchable after leaving the offer.
|
|
82
421
|
|
|
83
422
|
- **`docs/design/2026-09-recorded-not-built.md`** — three real defects with
|
|
@@ -108,9 +447,13 @@ guard asserting a boundary it cannot verify.
|
|
|
108
447
|
stands and which tools it could call, taken from the merged wire list the LLM
|
|
109
448
|
stage actually sent, intersected with the skill's own declared tools — never
|
|
110
449
|
from the declaration alone. Every configuration that would make that false has
|
|
111
|
-
its own wording: a skill declaring no tools says so, a
|
|
112
|
-
|
|
113
|
-
|
|
450
|
+
its own wording: a skill declaring no tools says so, a hold-out names the
|
|
451
|
+
declared tools and states that they were withheld — _corrected in 9.86.0:_ this
|
|
452
|
+
read "a step or park hold-out says the tools were withheld rather than naming
|
|
453
|
+
them", and the withheld arm does name them; a PARK never reaches the notice at
|
|
454
|
+
all, because a self-call at a parked map member is a re-engagement request
|
|
455
|
+
(9.59.0) answered on an earlier arm — and a call whose wire cannot be
|
|
456
|
+
established says nothing about tools at all. Mechanically it is still
|
|
114
457
|
a rejection — no activation, no cursor move, and the refusal budget still
|
|
115
458
|
counts it, because a self-call _loop_ is exactly the stuck model that budget
|
|
116
459
|
exists to escalate.
|
|
@@ -159,12 +502,21 @@ guard asserting a boundary it cannot verify.
|
|
|
159
502
|
### Added
|
|
160
503
|
|
|
161
504
|
- **`saidByPerson(ctx)` / `isSaidByPerson(msg)` — telling what a person said from
|
|
162
|
-
what the library wrote.**
|
|
163
|
-
|
|
505
|
+
what the library wrote.** _Corrected in 9.86.0:_ this said "five classes", and
|
|
506
|
+
**seven** kinds of `role: 'user'` message are authored by this library, not by a
|
|
507
|
+
person. Five are registered here: the compaction frame, the drop notice (whose
|
|
164
508
|
text names tools), the schema-check and evidence-check corrections, and any
|
|
165
|
-
injection-delivered message. The
|
|
166
|
-
|
|
167
|
-
|
|
509
|
+
injection-delivered message. The out-of-budget wrap-up instruction and the
|
|
510
|
+
stepped-skill nudge were library-authored the whole time and went unregistered
|
|
511
|
+
until 9.86.0. _Also corrected:_ the window's own refusal engine had not "always
|
|
512
|
+
applied that rule" — it applied a THREE-class version (drop notice, compaction
|
|
513
|
+
frame, injection-delivered), and 9.84.0 widened it to the five registered here,
|
|
514
|
+
so a schema-check or evidence-check frame can no longer become the protected
|
|
515
|
+
anchor. That widening changes the anchor only when the run's own message text is
|
|
516
|
+
absent from the window, because the anchor is matched by content first and only
|
|
517
|
+
falls back to the last thing a person said. A `when` predicate could apply no
|
|
518
|
+
version of it, because `InjectionContext.history` exposes only
|
|
519
|
+
`{ role, content, toolName? }`. An author writing an entry rule
|
|
168
520
|
that reads history was silently matching on our own bookkeeping. One
|
|
169
521
|
implementation, reused by both — the rule cannot drift between routing and the
|
|
170
522
|
window.
|
package/dist/core/Agent.js
CHANGED
|
@@ -2039,12 +2039,17 @@ class Agent extends RunnerBase_js_1.RunnerBase {
|
|
|
2039
2039
|
}
|
|
2040
2040
|
const open = this.openSkillIds();
|
|
2041
2041
|
const reachable = graphMenu;
|
|
2042
|
+
// A decision `tree()` keeps no cursor, so every routing pick is refused by
|
|
2043
|
+
// construction (9.86.0). The descriptor withholds the tool entirely when
|
|
2044
|
+
// nothing is left to open, and explains the tree when something is.
|
|
2045
|
+
const treeRouted = reachable !== undefined && this.skillGraphIsTree;
|
|
2042
2046
|
return (args) => {
|
|
2043
2047
|
const grantable = reachable
|
|
2044
2048
|
? [...new Set([...reachable(args.currentSkillId), ...open])]
|
|
2045
2049
|
: undefined;
|
|
2046
|
-
//
|
|
2047
|
-
//
|
|
2050
|
+
// `undefined` when the offer is WITHHELD — the tools slot then drops the
|
|
2051
|
+
// schema from the request. (`skills` is non-empty, so an empty catalog is
|
|
2052
|
+
// not one of the ways this can be undefined.)
|
|
2048
2053
|
return (0, skillTools_js_1.buildReadSkillTool)(skills, {
|
|
2049
2054
|
...(grantable !== undefined && { grantable }),
|
|
2050
2055
|
...(args.hiddenSkillIds !== undefined && { hiddenIds: args.hiddenSkillIds }),
|
|
@@ -2054,7 +2059,8 @@ class Agent extends RunnerBase_js_1.RunnerBase {
|
|
|
2054
2059
|
// The turn-start menu (SG-C) — the tools slot passes it only while the
|
|
2055
2060
|
// verdict is outstanding; describeOffer leads with it.
|
|
2056
2061
|
...(args.menu !== undefined && { menu: args.menu }),
|
|
2057
|
-
|
|
2062
|
+
...(treeRouted && { treeRouted: true }),
|
|
2063
|
+
})?.schema;
|
|
2058
2064
|
};
|
|
2059
2065
|
}
|
|
2060
2066
|
/**
|
|
@@ -2945,7 +2951,7 @@ class Agent extends RunnerBase_js_1.RunnerBase {
|
|
|
2945
2951
|
// auto-attached `present` tool when a store is attached, 9.22.0) +
|
|
2946
2952
|
// skill-supplied tools (with autoActivate scoping); validates
|
|
2947
2953
|
// name uniqueness; produces the dispatch map.
|
|
2948
|
-
const { registryByName, toolSchemas } = (0, buildToolRegistry_js_1.buildToolRegistry)(registry, this.injections, {
|
|
2954
|
+
const { registryByName, toolSchemas, toolDeclaringSkills } = (0, buildToolRegistry_js_1.buildToolRegistry)(registry, this.injections, {
|
|
2949
2955
|
hasArtifactStore: artifactStore !== undefined,
|
|
2950
2956
|
});
|
|
2951
2957
|
// A statically registered tool that declares `wants` on an agent with no
|
|
@@ -3313,6 +3319,11 @@ class Agent extends RunnerBase_js_1.RunnerBase {
|
|
|
3313
3319
|
// toolCallsHandler extracted to ./agent/stages/toolCalls.ts (v2.11.2).
|
|
3314
3320
|
const toolCallsHandler = (0, toolCalls_js_1.buildToolCallsHandler)({
|
|
3315
3321
|
registryByName,
|
|
3322
|
+
// WHICH SKILLS DECLARE EACH TOOL NAME (9.86.0) — read by the unknown-tool
|
|
3323
|
+
// roster so a role that may not see a skill is not told the names of the
|
|
3324
|
+
// tools that skill brought. Value-conditional: an agent whose skills carry
|
|
3325
|
+
// no tools hands the handler exactly the deps object it always did.
|
|
3326
|
+
...(toolDeclaringSkills.size > 0 && { toolDeclaringSkills }),
|
|
3316
3327
|
// The claim ledger accumulates only for an agent that declared a
|
|
3317
3328
|
// contract to read it (9.61.0) — value-conditional, so every other
|
|
3318
3329
|
// agent commits exactly what it always did.
|