agentfootprint 9.82.0 → 9.83.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 +91 -0
- package/CLAUDE.md +1 -0
- package/README.md +15 -15
- package/ai-instructions/claude-code/SKILL.md +1 -1
- package/dist/core/Agent.js +33 -1
- package/dist/core/Agent.js.map +1 -1
- package/dist/core/agent/evidence/evidenceIndex.js +96 -11
- package/dist/core/agent/evidence/evidenceIndex.js.map +1 -1
- package/dist/core/agent/evidence/gate.js +66 -6
- package/dist/core/agent/evidence/gate.js.map +1 -1
- package/dist/core/agent/stages/route.js +71 -12
- package/dist/core/agent/stages/route.js.map +1 -1
- package/dist/esm/core/Agent.d.ts +4 -0
- package/dist/esm/core/Agent.js +33 -1
- package/dist/esm/core/Agent.js.map +1 -1
- package/dist/esm/core/agent/evidence/evidenceIndex.d.ts +91 -5
- package/dist/esm/core/agent/evidence/evidenceIndex.js +96 -11
- package/dist/esm/core/agent/evidence/evidenceIndex.js.map +1 -1
- package/dist/esm/core/agent/evidence/gate.d.ts +27 -0
- package/dist/esm/core/agent/evidence/gate.js +66 -6
- package/dist/esm/core/agent/evidence/gate.js.map +1 -1
- package/dist/esm/core/agent/evidence/types.d.ts +12 -0
- package/dist/esm/core/agent/stages/route.d.ts +6 -1
- package/dist/esm/core/agent/stages/route.js +69 -10
- package/dist/esm/core/agent/stages/route.js.map +1 -1
- package/dist/esm/core/agent/types.d.ts +66 -0
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +8 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/integrity/disposition/lifecycle.d.ts +17 -1
- package/dist/esm/integrity/disposition/lifecycle.js +26 -1
- package/dist/esm/integrity/disposition/lifecycle.js.map +1 -1
- package/dist/esm/integrity/finding/types.d.ts +12 -2
- package/dist/esm/integrity/finding/types.js.map +1 -1
- package/dist/esm/integrity/prior-turn-evidence/check.d.ts +145 -0
- package/dist/esm/integrity/prior-turn-evidence/check.js +200 -0
- package/dist/esm/integrity/prior-turn-evidence/check.js.map +1 -0
- package/dist/esm/lib/trace-toolpack/traceToolpack.js +3 -2
- package/dist/esm/lib/trace-toolpack/traceToolpack.js.map +1 -1
- package/dist/index.js +12 -3
- package/dist/index.js.map +1 -1
- package/dist/integrity/disposition/lifecycle.js +26 -1
- package/dist/integrity/disposition/lifecycle.js.map +1 -1
- package/dist/integrity/finding/types.js.map +1 -1
- package/dist/integrity/prior-turn-evidence/check.js +204 -0
- package/dist/integrity/prior-turn-evidence/check.js.map +1 -0
- package/dist/lib/trace-toolpack/traceToolpack.js +3 -2
- package/dist/lib/trace-toolpack/traceToolpack.js.map +1 -1
- package/dist/types/core/Agent.d.ts +4 -0
- package/dist/types/core/Agent.d.ts.map +1 -1
- package/dist/types/core/agent/evidence/evidenceIndex.d.ts +91 -5
- package/dist/types/core/agent/evidence/evidenceIndex.d.ts.map +1 -1
- package/dist/types/core/agent/evidence/gate.d.ts +27 -0
- package/dist/types/core/agent/evidence/gate.d.ts.map +1 -1
- package/dist/types/core/agent/evidence/types.d.ts +12 -0
- package/dist/types/core/agent/evidence/types.d.ts.map +1 -1
- package/dist/types/core/agent/stages/route.d.ts +6 -1
- package/dist/types/core/agent/stages/route.d.ts.map +1 -1
- package/dist/types/core/agent/types.d.ts +66 -0
- package/dist/types/core/agent/types.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/integrity/disposition/lifecycle.d.ts +17 -1
- package/dist/types/integrity/disposition/lifecycle.d.ts.map +1 -1
- package/dist/types/integrity/finding/types.d.ts +12 -2
- package/dist/types/integrity/finding/types.d.ts.map +1 -1
- package/dist/types/integrity/prior-turn-evidence/check.d.ts +146 -0
- package/dist/types/integrity/prior-turn-evidence/check.d.ts.map +1 -0
- package/dist/types/lib/trace-toolpack/traceToolpack.d.ts.map +1 -1
- package/package.json +2 -2
|
@@ -18,8 +18,18 @@
|
|
|
18
18
|
*
|
|
19
19
|
* ## What counts as evidence, and what deliberately does not
|
|
20
20
|
*
|
|
21
|
-
* • **Tool results** — the `role: 'tool'` turns in
|
|
22
|
-
*
|
|
21
|
+
* • **Tool results** — the `role: 'tool'` turns in `scope.history` AS IT
|
|
22
|
+
* STANDS AT JUDGEMENT. A value the model read from a tool is grounded.
|
|
23
|
+
*
|
|
24
|
+
* Say that precisely, because "the whole conversation" is only true for an
|
|
25
|
+
* agent with no window strategy. `.window()` / `.compaction()` /
|
|
26
|
+
* `tokenBudget` REWRITE `scope.history` in place (window.ts, `scope.history
|
|
27
|
+
* = result.window`), so on those agents this corpus is the LIVE WINDOW and
|
|
28
|
+
* a result the window has dropped is not in it — the same value then reads
|
|
29
|
+
* as ungrounded. `keepLastToolResults` (default 2) is what keeps the most
|
|
30
|
+
* recent observations pinned; nothing pins the rest. Anything computed from
|
|
31
|
+
* this index therefore describes what the run can still SEE, never
|
|
32
|
+
* everything it ever read.
|
|
23
33
|
* • **Object KEYS count.** A map keyed by WWN puts real identifiers in key
|
|
24
34
|
* position, and the model saw them exactly as it saw the values.
|
|
25
35
|
* • **Tool call ARGUMENTS do not count.** The model typed those. Grounding a
|
|
@@ -40,6 +50,42 @@
|
|
|
40
50
|
* system-prompt content this turn was built from. A value the user supplied is
|
|
41
51
|
* not a fabrication — the user gave it — and neither is one the app's own
|
|
42
52
|
* prompt or skill body put in front of the model.
|
|
53
|
+
*
|
|
54
|
+
* ## WHEN a value was read, and why the corpus is still conversation-wide
|
|
55
|
+
*
|
|
56
|
+
* The corpus above answers "did the run read this?" and says nothing about
|
|
57
|
+
* WHEN. That gap has a measured cost: an agent answered a fresh question with
|
|
58
|
+
* ZERO tool calls, and the gate approved it because all seven values appeared
|
|
59
|
+
* in an inventory result fetched four turns earlier for a different question.
|
|
60
|
+
* Grounded, and four turns out of date.
|
|
61
|
+
*
|
|
62
|
+
* The fix is NOT to narrow the corpus to this turn. "And what about that
|
|
63
|
+
* disk?" legitimately leans on the previous turn's rows, and flagging those
|
|
64
|
+
* would make the gate cry wolf until somebody switched it off. So the corpus
|
|
65
|
+
* stays whole and every indexed form carries the TURN it was last served in
|
|
66
|
+
* — one number, stamped during the walk that was happening anyway.
|
|
67
|
+
*
|
|
68
|
+
* A TURN starts at each `role: 'user'` message the library did NOT author.
|
|
69
|
+
* `isLibraryAuthoredTurn` (frames.ts) is a statement about AUTHORSHIP, not
|
|
70
|
+
* about time — it exists to keep the exempt corpus from swallowing the gate's
|
|
71
|
+
* own corrections — and it is reused here for a different reason that happens
|
|
72
|
+
* to need the same predicate: a frame the library wrote is not a person asking
|
|
73
|
+
* something new. Excluding them is load-bearing rather than tidy. The evidence
|
|
74
|
+
* recheck appends a `role: 'user'` turn MID-RUN, so counting it as a boundary
|
|
75
|
+
* would push this turn's own tool results into the "earlier" bucket and file a
|
|
76
|
+
* notice against every revised answer.
|
|
77
|
+
*
|
|
78
|
+
* Turn 1 is the first user message still in the window; anything indexed
|
|
79
|
+
* before one (a history with no user turn at all) is turn 0, which is also
|
|
80
|
+
* `currentTurn`, so nothing can read as earlier and no notice can be filed
|
|
81
|
+
* from a conversation with no boundary in it.
|
|
82
|
+
*
|
|
83
|
+
* **The ordinals are WINDOW-RELATIVE**, and that is the honest bound: they
|
|
84
|
+
* count the user turns the run can still see, so under a window strategy
|
|
85
|
+
* "turn 2 of 4" may be the conversation's turn 9 of 13. The BOUNDARY is
|
|
86
|
+
* exact regardless — the current request is un-droppable by every window
|
|
87
|
+
* strategy (`'current-request'`, window/currentRequest.ts) — so "this turn
|
|
88
|
+
* versus earlier" holds; only the DISTANCE is a floor.
|
|
43
89
|
*/
|
|
44
90
|
import { absenceEvidenceProjection } from '../coverage/index.js';
|
|
45
91
|
import { isLibraryAuthoredTurn } from './frames.js';
|
|
@@ -55,11 +101,16 @@ function add(sink, raw) {
|
|
|
55
101
|
if (norm === '')
|
|
56
102
|
return;
|
|
57
103
|
for (const form of lookupForms(norm)) {
|
|
104
|
+
// Re-stamping a form the index already holds costs no entry, so it is
|
|
105
|
+
// done before the budget is consulted: a truncated index should still
|
|
106
|
+
// hold the freshest turn for everything it did manage to index.
|
|
107
|
+
if (sink.values.has(form)) {
|
|
108
|
+
sink.values.set(form, sink.turn);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
58
111
|
if (sink.budget <= 0)
|
|
59
112
|
return;
|
|
60
|
-
|
|
61
|
-
continue;
|
|
62
|
-
sink.values.add(form);
|
|
113
|
+
sink.values.set(form, sink.turn);
|
|
63
114
|
sink.budget -= 1;
|
|
64
115
|
}
|
|
65
116
|
}
|
|
@@ -124,21 +175,38 @@ function indexResult(content, sink) {
|
|
|
124
175
|
addText(sink, content);
|
|
125
176
|
}
|
|
126
177
|
/**
|
|
127
|
-
* Build the evidence corpus from
|
|
178
|
+
* Build the evidence corpus from the history AS IT STANDS: every `role: 'tool'`
|
|
179
|
+
* turn still in it, each indexed form stamped with the turn that served it.
|
|
128
180
|
*
|
|
129
181
|
* In a single-turn run these are exactly this turn's tool results. In a
|
|
130
182
|
* continued conversation the earlier turns' results are in here too, and that
|
|
131
183
|
* is deliberate: the model really did read them, and calling a value from turn
|
|
132
|
-
* one a fabrication in turn two would be false.
|
|
184
|
+
* one a fabrication in turn two would be false. Which earlier turns are still
|
|
185
|
+
* here is the WINDOW's decision, not this function's — see the header.
|
|
133
186
|
*/
|
|
134
187
|
export function evidenceFromHistory(history) {
|
|
135
|
-
const sink = { values: new
|
|
188
|
+
const sink = { values: new Map(), budget: MAX_INDEX_TOKENS, turn: 0 };
|
|
189
|
+
let toolResultsThisTurn = 0;
|
|
136
190
|
for (const msg of history) {
|
|
191
|
+
// A turn boundary — the person asked something new. The library's own
|
|
192
|
+
// corrections are `role: 'user'` too and are NOT boundaries; see the
|
|
193
|
+
// header for the bug that distinction prevents.
|
|
194
|
+
if (msg.role === 'user' && !isLibraryAuthoredTurn(msg.content)) {
|
|
195
|
+
sink.turn += 1;
|
|
196
|
+
toolResultsThisTurn = 0;
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
137
199
|
if (msg.role !== 'tool')
|
|
138
200
|
continue;
|
|
201
|
+
toolResultsThisTurn += 1;
|
|
139
202
|
indexResult(msg.content, sink);
|
|
140
203
|
}
|
|
141
|
-
return {
|
|
204
|
+
return {
|
|
205
|
+
values: sink.values,
|
|
206
|
+
truncated: sink.budget <= 0,
|
|
207
|
+
currentTurn: sink.turn,
|
|
208
|
+
toolResultsThisTurn,
|
|
209
|
+
};
|
|
142
210
|
}
|
|
143
211
|
/**
|
|
144
212
|
* Build the exempt corpus: everything the RUN put in front of the model that
|
|
@@ -150,9 +218,26 @@ export function evidenceFromHistory(history) {
|
|
|
150
218
|
* summarised or redacted contributes what it has. That direction is the safe
|
|
151
219
|
* one: a missing exemption can only cost a false flag on a value the app
|
|
152
220
|
* supplied, and the caller can name it in `exempt`.
|
|
221
|
+
*
|
|
222
|
+
* STATED REACH, because `systemPromptInjections` is broader than "the app's
|
|
223
|
+
* own prompt": it also carries `.memory()` recall and RAG passages. A value
|
|
224
|
+
* that reached the model through memory or retrieval is therefore EXEMPT from
|
|
225
|
+
* grounding — never flagged, and never counted as tool evidence of any turn,
|
|
226
|
+
* so it is invisible to the recency read too. That is the correct call for the
|
|
227
|
+
* fabrication question (the run really did put those words in front of the
|
|
228
|
+
* model) and a real blind spot for the recency one, and it is deliberately NOT
|
|
229
|
+
* fixed here: recall is its own seam, with its own record
|
|
230
|
+
* (`retrievalEvidence_<id>`, `agentfootprint.memory.attached`), and giving a
|
|
231
|
+
* recalled passage a turn stamp means deciding whose turn it belongs to — the
|
|
232
|
+
* one it was written in or the one it was recalled in — which is a question
|
|
233
|
+
* this file has no standing to answer.
|
|
153
234
|
*/
|
|
154
235
|
export function exemptFromRun(args) {
|
|
155
|
-
|
|
236
|
+
// The same accumulator, walked with no turn boundaries: an exemption is a
|
|
237
|
+
// fact about WHO supplied a value, and the turn it arrived in changes
|
|
238
|
+
// nothing about that. The keys are lifted into a Set at the end so the
|
|
239
|
+
// grounding decision keeps the exact membership type it always took.
|
|
240
|
+
const sink = { values: new Map(), budget: MAX_INDEX_TOKENS, turn: 0 };
|
|
156
241
|
if (args.userMessage)
|
|
157
242
|
addText(sink, args.userMessage);
|
|
158
243
|
for (const msg of args.history) {
|
|
@@ -175,6 +260,6 @@ export function exemptFromRun(args) {
|
|
|
175
260
|
else if (rec.contentSummary)
|
|
176
261
|
addText(sink, rec.contentSummary);
|
|
177
262
|
}
|
|
178
|
-
return sink.values;
|
|
263
|
+
return new Set(sink.values.keys());
|
|
179
264
|
}
|
|
180
265
|
//# sourceMappingURL=evidenceIndex.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"evidenceIndex.js","sourceRoot":"","sources":["../../../../../src/core/agent/evidence/evidenceIndex.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"evidenceIndex.js","sourceRoot":"","sources":["../../../../../src/core/agent/evidence/evidenceIndex.ts"],"names":[],"mappings":"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"}
|
|
@@ -42,6 +42,13 @@
|
|
|
42
42
|
* unexamined. A missed fabrication is a miss; a false accusation costs a real
|
|
43
43
|
* turn and can refuse a good answer, so the bias points the way it does.
|
|
44
44
|
*
|
|
45
|
+
* **And it does not ask WHEN.** A value read four turns ago for a different
|
|
46
|
+
* question is as grounded here as one read a second ago — which is a measured
|
|
47
|
+
* failure, not a hypothetical (see `../../../integrity/prior-turn-evidence`).
|
|
48
|
+
* That fact is reported beside this check, never folded into it: the gate's
|
|
49
|
+
* job is "was this read at all", and answering a second question through the
|
|
50
|
+
* same verdict would make one dial control two decisions.
|
|
51
|
+
*
|
|
45
52
|
* ## Why the check is DETERMINISTIC
|
|
46
53
|
*
|
|
47
54
|
* No model call, no embedding, no judge. The library's thesis is that
|
|
@@ -69,6 +76,13 @@ export declare function resolveEvidenceGate(opts?: NamesAndNumbersOptions): Reso
|
|
|
69
76
|
*
|
|
70
77
|
* `exempt` is checked BEFORE the evidence: a value the user supplied is not a
|
|
71
78
|
* fabrication whether or not a tool ever echoed it back.
|
|
79
|
+
*
|
|
80
|
+
* The same pass reports WHEN each grounded value was read (`grounding`) — the
|
|
81
|
+
* time axis the corpus gained in 9.83.0. Three buckets, and an exempt value is
|
|
82
|
+
* in none of them: exemption is a statement about who SUPPLIED a value, and a
|
|
83
|
+
* value the app or the person put in front of the model was never something
|
|
84
|
+
* the run had to go and look up. Counting one as tool evidence — of any turn —
|
|
85
|
+
* would answer a question nobody asked.
|
|
72
86
|
*/
|
|
73
87
|
export declare function checkAnswer(answer: string, args: {
|
|
74
88
|
readonly gate: ResolvedEvidenceGate;
|
|
@@ -92,6 +106,19 @@ export declare function describeValues(values: readonly UnsupportedValue[]): str
|
|
|
92
106
|
* compaction frame and the schema frame follow, and a rule with an exception
|
|
93
107
|
* is not a rule.
|
|
94
108
|
*
|
|
109
|
+
* ## The scope this sentence used to claim, and no longer does (9.83.0)
|
|
110
|
+
*
|
|
111
|
+
* Both this frame and {@link evidenceRefusalSentence} said the flagged values
|
|
112
|
+
* "appear in NO tool result FROM THIS TURN". The index they are written from
|
|
113
|
+
* has never been turn-scoped — it walks every `role: 'tool'` turn in
|
|
114
|
+
* `scope.history` — so the sentence named a boundary the check did not honour,
|
|
115
|
+
* in the two places the claim is read by a model and by an operator. It was
|
|
116
|
+
* also needlessly weak: a flagged value appears in no tool result the run can
|
|
117
|
+
* point at AT ALL, which is a stronger and true thing to say. So the words
|
|
118
|
+
* changed to the check's real reach. The recency fact the old wording gestured
|
|
119
|
+
* at is now reported where it can be measured — `prior-turn-evidence`, off by
|
|
120
|
+
* default — instead of asserted here where it could not be.
|
|
121
|
+
*
|
|
95
122
|
* `stagedRefs` (optional) is the staged-refs join computed by the recheck
|
|
96
123
|
* stage: when this turn holds placed artifact tickets a served `wants` tool
|
|
97
124
|
* can spend, the frame names the refs and the spender by their declared names
|
|
@@ -42,6 +42,13 @@
|
|
|
42
42
|
* unexamined. A missed fabrication is a miss; a false accusation costs a real
|
|
43
43
|
* turn and can refuse a good answer, so the bias points the way it does.
|
|
44
44
|
*
|
|
45
|
+
* **And it does not ask WHEN.** A value read four turns ago for a different
|
|
46
|
+
* question is as grounded here as one read a second ago — which is a measured
|
|
47
|
+
* failure, not a hypothetical (see `../../../integrity/prior-turn-evidence`).
|
|
48
|
+
* That fact is reported beside this check, never folded into it: the gate's
|
|
49
|
+
* job is "was this read at all", and answering a second question through the
|
|
50
|
+
* same verdict would make one dial control two decisions.
|
|
51
|
+
*
|
|
45
52
|
* ## Why the check is DETERMINISTIC
|
|
46
53
|
*
|
|
47
54
|
* No model call, no embedding, no judge. The library's thesis is that
|
|
@@ -148,20 +155,58 @@ function anchor(re) {
|
|
|
148
155
|
*
|
|
149
156
|
* `exempt` is checked BEFORE the evidence: a value the user supplied is not a
|
|
150
157
|
* fabrication whether or not a tool ever echoed it back.
|
|
158
|
+
*
|
|
159
|
+
* The same pass reports WHEN each grounded value was read (`grounding`) — the
|
|
160
|
+
* time axis the corpus gained in 9.83.0. Three buckets, and an exempt value is
|
|
161
|
+
* in none of them: exemption is a statement about who SUPPLIED a value, and a
|
|
162
|
+
* value the app or the person put in front of the model was never something
|
|
163
|
+
* the run had to go and look up. Counting one as tool evidence — of any turn —
|
|
164
|
+
* would answer a question nobody asked.
|
|
151
165
|
*/
|
|
152
166
|
export function checkAnswer(answer, args) {
|
|
153
167
|
const candidates = extractCandidates(answer, args.gate);
|
|
154
168
|
const unsupported = [];
|
|
169
|
+
let fromThisTurn = 0;
|
|
170
|
+
let fromPriorTurns = 0;
|
|
171
|
+
let latestPriorTurn;
|
|
155
172
|
for (const candidate of candidates) {
|
|
156
173
|
const forms = lookupForms(candidate.value);
|
|
157
|
-
|
|
158
|
-
|
|
174
|
+
if (forms.some((f) => args.exempt.has(f)))
|
|
175
|
+
continue;
|
|
176
|
+
// The NEWEST turn any spelling of this value was served in. Newest,
|
|
177
|
+
// because the question is whether this turn could have supplied it — an
|
|
178
|
+
// identifier echoed back by a lookup this turn is this turn's, whatever
|
|
179
|
+
// else served it three turns ago.
|
|
180
|
+
let turn;
|
|
181
|
+
for (const form of forms) {
|
|
182
|
+
const seen = args.evidence.values.get(form);
|
|
183
|
+
if (seen !== undefined && (turn === undefined || seen > turn))
|
|
184
|
+
turn = seen;
|
|
185
|
+
}
|
|
186
|
+
if (turn === undefined) {
|
|
159
187
|
unsupported.push({ value: clip(candidate.value), shape: candidate.shape });
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
if (turn >= args.evidence.currentTurn) {
|
|
191
|
+
fromThisTurn += 1;
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
fromPriorTurns += 1;
|
|
195
|
+
if (latestPriorTurn === undefined || turn > latestPriorTurn)
|
|
196
|
+
latestPriorTurn = turn;
|
|
160
197
|
}
|
|
161
198
|
return {
|
|
162
199
|
unsupported,
|
|
163
200
|
candidates: candidates.length,
|
|
164
201
|
evidenceTruncated: args.evidence.truncated,
|
|
202
|
+
grounding: {
|
|
203
|
+
fromThisTurn,
|
|
204
|
+
fromPriorTurns,
|
|
205
|
+
...(latestPriorTurn !== undefined && { latestPriorTurn }),
|
|
206
|
+
currentTurn: args.evidence.currentTurn,
|
|
207
|
+
toolResultsThisTurn: args.evidence.toolResultsThisTurn,
|
|
208
|
+
indexTruncated: args.evidence.truncated,
|
|
209
|
+
},
|
|
165
210
|
};
|
|
166
211
|
}
|
|
167
212
|
/** Render the flagged values for a human or a model: `` `x` (shape) ``. */
|
|
@@ -186,6 +231,19 @@ export function describeValues(values) {
|
|
|
186
231
|
* compaction frame and the schema frame follow, and a rule with an exception
|
|
187
232
|
* is not a rule.
|
|
188
233
|
*
|
|
234
|
+
* ## The scope this sentence used to claim, and no longer does (9.83.0)
|
|
235
|
+
*
|
|
236
|
+
* Both this frame and {@link evidenceRefusalSentence} said the flagged values
|
|
237
|
+
* "appear in NO tool result FROM THIS TURN". The index they are written from
|
|
238
|
+
* has never been turn-scoped — it walks every `role: 'tool'` turn in
|
|
239
|
+
* `scope.history` — so the sentence named a boundary the check did not honour,
|
|
240
|
+
* in the two places the claim is read by a model and by an operator. It was
|
|
241
|
+
* also needlessly weak: a flagged value appears in no tool result the run can
|
|
242
|
+
* point at AT ALL, which is a stronger and true thing to say. So the words
|
|
243
|
+
* changed to the check's real reach. The recency fact the old wording gestured
|
|
244
|
+
* at is now reported where it can be measured — `prior-turn-evidence`, off by
|
|
245
|
+
* default — instead of asserted here where it could not be.
|
|
246
|
+
*
|
|
189
247
|
* `stagedRefs` (optional) is the staged-refs join computed by the recheck
|
|
190
248
|
* stage: when this turn holds placed artifact tickets a served `wants` tool
|
|
191
249
|
* can spend, the frame names the refs and the spender by their declared names
|
|
@@ -197,7 +255,7 @@ export function describeValues(values) {
|
|
|
197
255
|
*/
|
|
198
256
|
export function buildEvidenceCorrection(failedAnswer, values, stagedRefs) {
|
|
199
257
|
const frame = `${EVIDENCE_CHECK_FRAME_PREFIX} — the answer above states values that appear in NO tool ` +
|
|
200
|
-
`result
|
|
258
|
+
`result this run read, so they were not read from the data. Reply again using only names ` +
|
|
201
259
|
`and numbers a tool actually returned. If you need one of these values, call the tool that ` +
|
|
202
260
|
`provides it.` +
|
|
203
261
|
(stagedRefs === undefined ? '' : stagedRefsTeachingClause(stagedRefs)) +
|
|
@@ -220,14 +278,16 @@ export function buildEvidenceCorrection(failedAnswer, values, stagedRefs) {
|
|
|
220
278
|
*/
|
|
221
279
|
export function evidenceRefusalSentence(values, posture, revised) {
|
|
222
280
|
const head = posture === 'rails'
|
|
223
|
-
? `[agentfootprint] this answer was NOT returned: ${values.length} value(s) in it appear in no tool result
|
|
224
|
-
: `[agentfootprint] this answer states ${values.length} value(s) that appear in no tool result
|
|
281
|
+
? `[agentfootprint] this answer was NOT returned: ${values.length} value(s) in it appear in no tool result this run read`
|
|
282
|
+
: `[agentfootprint] this answer states ${values.length} value(s) that appear in no tool result this run read`;
|
|
225
283
|
return (`${head} — ${describeValues(values)}. ` +
|
|
226
284
|
(revised ? 'The model was asked once to correct them and they survived the revision. ' : '') +
|
|
227
285
|
'What would satisfy the check: every name and number in the answer appears in a tool ' +
|
|
228
286
|
'result (or in the message you sent). Call a tool that returns these values, declare their ' +
|
|
229
287
|
'shape via `shapes` if they are legitimate and the extractor mis-read them, or accept the ' +
|
|
230
288
|
"answer with `posture: 'assist'`. This check catches INVENTED values only — it cannot " +
|
|
231
|
-
'tell you whether a claim built from real values is true
|
|
289
|
+
'tell you whether a claim built from real values is true, and it does not ask WHEN a value ' +
|
|
290
|
+
'was read (`noticePriorTurnEvidence` reports that). What it reads is the live window: a ' +
|
|
291
|
+
'result a window strategy has already dropped is not in it.');
|
|
232
292
|
}
|
|
233
293
|
//# sourceMappingURL=gate.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gate.js","sourceRoot":"","sources":["../../../../../src/core/agent/evidence/gate.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"gate.js","sourceRoot":"","sources":["../../../../../src/core/agent/evidence/gate.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* Role: core/ layer. Nothing here runs; it is the contract.
|
|
7
7
|
* Emits: N/A.
|
|
8
8
|
*/
|
|
9
|
+
import type { AnswerGroundingReading } from '../../../integrity/prior-turn-evidence/check.js';
|
|
9
10
|
/**
|
|
10
11
|
* How hard the check pushes back. **Same three words as the skill-graph
|
|
11
12
|
* routing dial, deliberately** — one posture vocabulary across the library —
|
|
@@ -138,4 +139,15 @@ export interface EvidenceVerdict {
|
|
|
138
139
|
* accusation.
|
|
139
140
|
*/
|
|
140
141
|
readonly evidenceTruncated: boolean;
|
|
142
|
+
/**
|
|
143
|
+
* WHEN the answer's grounded values were read (9.83.0) — the same pass,
|
|
144
|
+
* one time axis.
|
|
145
|
+
*
|
|
146
|
+
* Always computed, because it is three counters over a loop that was
|
|
147
|
+
* already running. It is only READ when the operator armed
|
|
148
|
+
* `AgentOptions.noticePriorTurnEvidence`, which is what turns it into a
|
|
149
|
+
* `prior-turn-evidence` finding; nothing else in the gate consults it and
|
|
150
|
+
* no posture changes because of it.
|
|
151
|
+
*/
|
|
152
|
+
readonly grounding: AnswerGroundingReading;
|
|
141
153
|
}
|
|
@@ -75,4 +75,9 @@ hasWrapUp?: boolean,
|
|
|
75
75
|
* Both absent → the claim seam never runs and not one line changes. */
|
|
76
76
|
claims?: readonly DeclaredClaim[], integrityLedger?: {
|
|
77
77
|
current: DispositionLedger | undefined;
|
|
78
|
-
}
|
|
78
|
+
},
|
|
79
|
+
/** `AgentOptions.noticePriorTurnEvidence` (9.83.0) — the claim seam's
|
|
80
|
+
* recency read. Needs the evidence gate to be armed too (it owns the
|
|
81
|
+
* extractor), so absent OR gate-less → not one row, not one event, and
|
|
82
|
+
* the decider a pre-9.83.0 chart was handed. */
|
|
83
|
+
noticePriorTurnEvidence?: boolean): (scope: TypedScope<AgentState>) => RouteBranch | Promise<RouteBranch>;
|
|
@@ -19,6 +19,8 @@ import { judgeAnswer, recordOutputAttempt, } from '../outputEnforcement.js';
|
|
|
19
19
|
import { pointerOf, remainingStepsOf, stepInProgress, } from '../../../lib/injection-engine/skillSteps.js';
|
|
20
20
|
import { checkAnswer, evidenceRefusalSentence, MAX_REPORTED_VALUES } from '../evidence/gate.js';
|
|
21
21
|
import { evidenceFromHistory, exemptFromRun } from '../evidence/evidenceIndex.js';
|
|
22
|
+
import { priorTurnEvidenceOf } from '../../../integrity/prior-turn-evidence/check.js';
|
|
23
|
+
import { fileIntegrityFindings } from '../integrityFindings.js';
|
|
22
24
|
import { unsupportedClaimsOf } from '../../../integrity/unsupported-claim/check.js';
|
|
23
25
|
import { contextErrorIdentity } from '../../../integrity/finding/types.js';
|
|
24
26
|
/** The base decision, with the sentence that explains it. Split out so the
|
|
@@ -299,7 +301,10 @@ function judgeUnfinishedSteps(scope, stepPlanFor, earlyStop) {
|
|
|
299
301
|
* • otherwise → `'flagged'`: the answer ships unchanged with the fact on the
|
|
300
302
|
* record and one warning naming the values.
|
|
301
303
|
*/
|
|
302
|
-
function judgeEvidence(scope, gate, earlyStop
|
|
304
|
+
function judgeEvidence(scope, gate, earlyStop,
|
|
305
|
+
/** `AgentOptions.noticePriorTurnEvidence` (9.83.0). Absent → the recency
|
|
306
|
+
* read is computed and never looked at, and no row is ever filed. */
|
|
307
|
+
noticePriorTurnEvidence, integrityLedger) {
|
|
303
308
|
if (gate === undefined)
|
|
304
309
|
return undefined;
|
|
305
310
|
const answer = scope.llmLatestContent ?? '';
|
|
@@ -307,8 +312,12 @@ function judgeEvidence(scope, gate, earlyStop) {
|
|
|
307
312
|
const afterRevision = scope.evidenceRevisionSpent === true;
|
|
308
313
|
// An empty answer states nothing. `stoppedEarly` already reports why it is
|
|
309
314
|
// empty; adding "and it cited nothing" would be noise about a non-answer.
|
|
310
|
-
if (answer === '')
|
|
315
|
+
if (answer === '') {
|
|
316
|
+
// …but the recency row still has to say it could not run. An armed check
|
|
317
|
+
// that files nothing is indistinguishable from an unhooked one.
|
|
318
|
+
noteRecency(noticePriorTurnEvidence, integrityLedger, 'unreachable');
|
|
311
319
|
return undefined;
|
|
320
|
+
}
|
|
312
321
|
const history = scope.history;
|
|
313
322
|
const verdict = checkAnswer(answer, {
|
|
314
323
|
gate,
|
|
@@ -319,6 +328,19 @@ function judgeEvidence(scope, gate, earlyStop) {
|
|
|
319
328
|
systemPromptInjections: scope.systemPromptInjections,
|
|
320
329
|
}),
|
|
321
330
|
});
|
|
331
|
+
// ── THE CLAIM SEAM'S RECENCY READ (9.83.0) — `prior-turn-evidence` ──
|
|
332
|
+
// Judged on EVERY outcome, clean or flagged, and before the branch below
|
|
333
|
+
// returns: "grounded" and "grounded, but every value is four turns old"
|
|
334
|
+
// are different facts about the same answer, and an answer that is about
|
|
335
|
+
// to be revised for a fabrication can still be resting on stale evidence
|
|
336
|
+
// for everything else it says. Detection only — it never chooses a branch,
|
|
337
|
+
// so the revise/refuse/flag decision below is byte-for-byte the one every
|
|
338
|
+
// release since 9.35.0 made.
|
|
339
|
+
if (noticePriorTurnEvidence === true) {
|
|
340
|
+
const recency = priorTurnEvidenceOf(verdict.grounding, iteration);
|
|
341
|
+
integrityLedger?.current?.note('prior-turn-evidence', 'claim', recency.disposition, recency.disposition === 'checked-fail' ? Date.now() : undefined);
|
|
342
|
+
fileIntegrityFindings(scope, recency.findings, iteration);
|
|
343
|
+
}
|
|
322
344
|
if (verdict.unsupported.length === 0) {
|
|
323
345
|
typedEmit(scope, 'agentfootprint.agent.evidence_checked', {
|
|
324
346
|
iteration,
|
|
@@ -377,6 +399,25 @@ function judgeEvidence(scope, gate, earlyStop) {
|
|
|
377
399
|
}
|
|
378
400
|
return undefined;
|
|
379
401
|
}
|
|
402
|
+
/**
|
|
403
|
+
* File the recency check's disposition WITHOUT reading anything (9.83.0) —
|
|
404
|
+
* `noteClaims`' precedent, one judge over, and for the identical reason.
|
|
405
|
+
*
|
|
406
|
+
* An armed check that leaves its row UNTOUCHED is what `assertAlive` reads as
|
|
407
|
+
* wiring rot, and in `integrityPosture: 'dev'` that is a `CheckerDeadError` on
|
|
408
|
+
* a perfectly healthy run. Three terminal exits reach a caller without the
|
|
409
|
+
* evidence gate ever producing a grounding reading — an empty answer, a
|
|
410
|
+
* middleware denial, and an answer the output schema rejected — so each says
|
|
411
|
+
* so in the ledger rather than going quiet.
|
|
412
|
+
*
|
|
413
|
+
* Value-conditional on the dial: an unarmed check already carries its
|
|
414
|
+
* `not-applicable` row from registration and must not be noted twice.
|
|
415
|
+
*/
|
|
416
|
+
function noteRecency(armed, ledger, disposition) {
|
|
417
|
+
if (armed !== true)
|
|
418
|
+
return;
|
|
419
|
+
ledger?.current?.note('prior-turn-evidence', 'claim', disposition);
|
|
420
|
+
}
|
|
380
421
|
/** The `route_decided` rationale for an evidence recheck. */
|
|
381
422
|
function evidenceRecheckRationale(scope) {
|
|
382
423
|
const pending = scope.evidenceUnsupported;
|
|
@@ -467,7 +508,12 @@ export function buildRouteDeciderStage(messageMiddleware, enforcement, stepPlanF
|
|
|
467
508
|
hasWrapUp = false,
|
|
468
509
|
/** The declared claim contract (9.61.0) and the run's disposition ledger.
|
|
469
510
|
* Both absent → the claim seam never runs and not one line changes. */
|
|
470
|
-
claims, integrityLedger
|
|
511
|
+
claims, integrityLedger,
|
|
512
|
+
/** `AgentOptions.noticePriorTurnEvidence` (9.83.0) — the claim seam's
|
|
513
|
+
* recency read. Needs the evidence gate to be armed too (it owns the
|
|
514
|
+
* extractor), so absent OR gate-less → not one row, not one event, and
|
|
515
|
+
* the decider a pre-9.83.0 chart was handed. */
|
|
516
|
+
noticePriorTurnEvidence) {
|
|
471
517
|
const chain = messageMiddleware ?? [];
|
|
472
518
|
if (chain.length === 0 &&
|
|
473
519
|
enforcement === undefined &&
|
|
@@ -476,9 +522,9 @@ claims, integrityLedger) {
|
|
|
476
522
|
return hasWrapUp ? buildSimpleDecider(true) : routeDeciderStage;
|
|
477
523
|
}
|
|
478
524
|
if (enforcement !== undefined)
|
|
479
|
-
return buildEnforcingDecider(chain, enforcement, stepPlanFor, evidence, hasWrapUp, claims, integrityLedger);
|
|
525
|
+
return buildEnforcingDecider(chain, enforcement, stepPlanFor, evidence, hasWrapUp, claims, integrityLedger, noticePriorTurnEvidence);
|
|
480
526
|
if (stepPlanFor !== undefined || evidence !== undefined)
|
|
481
|
-
return buildJudgingDecider(chain, stepPlanFor, evidence, hasWrapUp);
|
|
527
|
+
return buildJudgingDecider(chain, stepPlanFor, evidence, hasWrapUp, noticePriorTurnEvidence, integrityLedger);
|
|
482
528
|
return async (scope) => {
|
|
483
529
|
const { chosen, rationale, earlyStop } = decideBranch(scope);
|
|
484
530
|
// ── The out-of-budget wrap-up (9.56.0) ─────────────────────────────
|
|
@@ -622,7 +668,7 @@ function readValidatedAnswer(scope) {
|
|
|
622
668
|
return undefined;
|
|
623
669
|
}
|
|
624
670
|
}
|
|
625
|
-
function buildJudgingDecider(chain, stepPlanFor, evidence, hasWrapUp) {
|
|
671
|
+
function buildJudgingDecider(chain, stepPlanFor, evidence, hasWrapUp, noticePriorTurnEvidence, integrityLedger) {
|
|
626
672
|
return async (scope) => {
|
|
627
673
|
const { chosen, rationale, earlyStop } = decideBranch(scope);
|
|
628
674
|
if (chosen !== 'final') {
|
|
@@ -657,13 +703,19 @@ function buildJudgingDecider(chain, stepPlanFor, evidence, hasWrapUp) {
|
|
|
657
703
|
}
|
|
658
704
|
scope.llmLatestContent = verdict.content;
|
|
659
705
|
}
|
|
706
|
+
// A withheld answer is judged by nothing below, so the recency row says so
|
|
707
|
+
// here rather than sitting untouched (see `noteRecency`).
|
|
708
|
+
if (denied)
|
|
709
|
+
noteRecency(noticePriorTurnEvidence, integrityLedger, 'not-applicable');
|
|
660
710
|
if (!denied && judgeUnfinishedSteps(scope, stepPlanFor, earlyStop) === 'step-nudge') {
|
|
661
711
|
emitRouteDecided(scope, 'step-nudge', stepNudgeRationale(scope));
|
|
662
712
|
return 'step-nudge';
|
|
663
713
|
}
|
|
664
714
|
// AFTER the step judge: an answer about to be nudged is not the final
|
|
665
715
|
// answer, and grounding it would pay for a turn that is being replaced.
|
|
666
|
-
if (!denied &&
|
|
716
|
+
if (!denied &&
|
|
717
|
+
judgeEvidence(scope, evidence, earlyStop, noticePriorTurnEvidence, integrityLedger) ===
|
|
718
|
+
'evidence-recheck') {
|
|
667
719
|
emitRouteDecided(scope, 'evidence-recheck', evidenceRecheckRationale(scope));
|
|
668
720
|
return 'evidence-recheck';
|
|
669
721
|
}
|
|
@@ -678,7 +730,7 @@ function buildJudgingDecider(chain, stepPlanFor, evidence, hasWrapUp) {
|
|
|
678
730
|
* announce the branch — so `route_decided` names the branch the run actually
|
|
679
731
|
* took rather than the one it was heading for.
|
|
680
732
|
*/
|
|
681
|
-
function buildEnforcingDecider(chain, enforcement, stepPlanFor, evidence, hasWrapUp, claims, integrityLedger) {
|
|
733
|
+
function buildEnforcingDecider(chain, enforcement, stepPlanFor, evidence, hasWrapUp, claims, integrityLedger, noticePriorTurnEvidence) {
|
|
682
734
|
return async (scope) => {
|
|
683
735
|
const base = decideBranch(scope);
|
|
684
736
|
if (base.chosen !== 'final') {
|
|
@@ -730,6 +782,10 @@ function buildEnforcingDecider(chain, enforcement, stepPlanFor, evidence, hasWra
|
|
|
730
782
|
// The claim seam never runs on a denied answer — nobody receives it —
|
|
731
783
|
// but the run must still SAY that, per declared claim.
|
|
732
784
|
noteClaims(claims, integrityLedger, 'not-applicable');
|
|
785
|
+
// Same exit, same reasoning, for the recency read: an answer nobody
|
|
786
|
+
// receives has no provenance worth reporting, and that is a rule rather
|
|
787
|
+
// than a failure to reach one.
|
|
788
|
+
noteRecency(noticePriorTurnEvidence, integrityLedger, 'not-applicable');
|
|
733
789
|
emitRouteDecided(scope, 'final', base.rationale);
|
|
734
790
|
settleWrapUp(scope, base.earlyStop, false);
|
|
735
791
|
return 'final';
|
|
@@ -751,7 +807,8 @@ function buildEnforcingDecider(chain, enforcement, stepPlanFor, evidence, hasWra
|
|
|
751
807
|
// The evidence gate is last of the three judges: it grounds the answer
|
|
752
808
|
// the schema accepted and the procedure finished — the one that is
|
|
753
809
|
// really about to be handed back.
|
|
754
|
-
if (judgeEvidence(scope, evidence, base.earlyStop
|
|
810
|
+
if (judgeEvidence(scope, evidence, base.earlyStop, noticePriorTurnEvidence, integrityLedger) ===
|
|
811
|
+
'evidence-recheck') {
|
|
755
812
|
emitRouteDecided(scope, 'evidence-recheck', evidenceRecheckRationale(scope));
|
|
756
813
|
return 'evidence-recheck';
|
|
757
814
|
}
|
|
@@ -822,7 +879,9 @@ function buildEnforcingDecider(chain, enforcement, stepPlanFor, evidence, hasWra
|
|
|
822
879
|
// failed its own contract and the caller is being told so; grounding the
|
|
823
880
|
// values inside a shape the app has declared invalid would file a second
|
|
824
881
|
// verdict about a string nobody is going to use, and under `guard` it
|
|
825
|
-
// would buy a revision for an answer that cannot pass anyway.
|
|
882
|
+
// would buy a revision for an answer that cannot pass anyway. The recency
|
|
883
|
+
// row says exactly that — out of scope by rule, never silence.
|
|
884
|
+
noteRecency(noticePriorTurnEvidence, integrityLedger, 'not-applicable');
|
|
826
885
|
emitRouteDecided(scope, 'final', brokenByChain !== undefined
|
|
827
886
|
? `final answer satisfied the output schema until the output rule '${brokenByChain}' ` +
|
|
828
887
|
`rewrote it (${failure.stage}) — not re-asked, because the model's answer was fine`
|