agentfootprint 9.81.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 +149 -0
- package/CLAUDE.md +2 -1
- 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/core/runbook/verdicts.js +30 -6
- package/dist/core/runbook/verdicts.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/core/runbook/types.d.ts +8 -0
- package/dist/esm/core/runbook/verdicts.d.ts +18 -5
- package/dist/esm/core/runbook/verdicts.js +30 -6
- package/dist/esm/core/runbook/verdicts.js.map +1 -1
- 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/core/runbook/types.d.ts +8 -0
- package/dist/types/core/runbook/types.d.ts.map +1 -1
- package/dist/types/core/runbook/verdicts.d.ts +18 -5
- package/dist/types/core/runbook/verdicts.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 +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,155 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [9.83.0] - 2026-09-03
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **The evidence gate claimed a boundary it did not measure.** Both of its
|
|
15
|
+
user-facing sentences — the correction it sends the model
|
|
16
|
+
(`buildEvidenceCorrection`) and the warning it prints an operator
|
|
17
|
+
(`evidenceRefusalSentence`) — said the flagged values *"appear in NO tool
|
|
18
|
+
result **from this turn**"*. The index behind them has never been turn-scoped:
|
|
19
|
+
it walks every `role: 'tool'` turn in the history. The library was asserting a
|
|
20
|
+
scope it could not honour, in the two places that assertion is read.
|
|
21
|
+
|
|
22
|
+
Both now say what the check really reaches — *"appear in no tool result this
|
|
23
|
+
run read"* — which is both true and the stronger claim, and the operator
|
|
24
|
+
sentence adds the two facts a reader needs: that the corpus is the LIVE
|
|
25
|
+
WINDOW (a window strategy rewrites `scope.history` in place, so a dropped
|
|
26
|
+
result is not in it), and that `noticePriorTurnEvidence` is what answers the
|
|
27
|
+
recency question. The frame PREFIX is unchanged, so
|
|
28
|
+
`isLibraryAuthoredTurn` and every consumer matching on it are untouched.
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
|
|
32
|
+
- **`noticePriorTurnEvidence` — the answer is grounded, and nothing this turn
|
|
33
|
+
fetched grounds it.** Default off.
|
|
34
|
+
|
|
35
|
+
The measured failure: a consumer's agent answered a data question with **zero
|
|
36
|
+
tool calls**, and the gate approved it — `LLM calls 1 · Tool calls 0 ·
|
|
37
|
+
Iterations 1`, then *"All 7 values in the answer were found in what the tools
|
|
38
|
+
returned — the answer stands."* They were found: in an inventory result from
|
|
39
|
+
four turns earlier, fetched for a different question. The user had asked about
|
|
40
|
+
array performance; the answer recommended enabling a collector that had been
|
|
41
|
+
running for months. Two turns did it back to back. Every rail passed honestly
|
|
42
|
+
— the gate measures GROUNDEDNESS and had no notion of WHEN a value was
|
|
43
|
+
grounded.
|
|
44
|
+
|
|
45
|
+
Every indexed form now carries the turn that last served it — one number,
|
|
46
|
+
stamped during the walk the index was already doing (`EvidenceCorpus.values`
|
|
47
|
+
became a `Map<form, turn>`; a TURN starts at each `role: 'user'` message the
|
|
48
|
+
library did not author). When at least one value in the answer is grounded and
|
|
49
|
+
**not one of them** came from the turn being answered, one `advisory` finding
|
|
50
|
+
is filed at the claim seam:
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
const agent = Agent.create({ provider, model, noticePriorTurnEvidence: true })
|
|
54
|
+
.tool(arrayInventory)
|
|
55
|
+
.namesAndNumbersFromEvidence() // ← the other half: it owns the extractor
|
|
56
|
+
.build();
|
|
57
|
+
|
|
58
|
+
await agent.run('what arrays are there?'); // fetches, answers, files nothing
|
|
59
|
+
await agent.followUp('how is array performance?'); // no tool call, answers from turn 1
|
|
60
|
+
// → prior-turn-evidence: 3 grounded value(s), all last served in turn 1,
|
|
61
|
+
// and this turn called no tool at all.
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**The corpus is deliberately NOT narrowed to this turn.** That would have made
|
|
65
|
+
the old sentence true and been the wrong fix: *"and what about that disk?"*
|
|
66
|
+
leans on the previous turn's rows legitimately, and a check that cries wolf is
|
|
67
|
+
a check somebody switches off. ONE grounded value from this turn's own results
|
|
68
|
+
files nothing — not a threshold to tune, but the falsification of the claim
|
|
69
|
+
being tested. A follow-up that calls a tool usually gets that for free,
|
|
70
|
+
because a lookup keyed on an earlier identifier echoes it back.
|
|
71
|
+
|
|
72
|
+
A turn that served no tool results at all is the SAME finding with a stronger
|
|
73
|
+
witness, not a second kind: it is a cheaper proof of the identical fact.
|
|
74
|
+
|
|
75
|
+
**The ceiling** ships as `PRIOR_TURN_EVIDENCE_CEILING`, exported and quoted
|
|
76
|
+
verbatim into every message: referring back is indistinguishable, by evidence
|
|
77
|
+
alone, from going stale; the ordinals count only the turns still in the live
|
|
78
|
+
window, so the distance is a FLOOR (the boundary itself is exact — the
|
|
79
|
+
current request is un-droppable); and values that reached the model through
|
|
80
|
+
`.memory()` recall or RAG are exempt from grounding and invisible to it, so it
|
|
81
|
+
can under-report and never over-report.
|
|
82
|
+
|
|
83
|
+
**Two halves arm it**, and the second is structural rather than a policy
|
|
84
|
+
companion: the dial AND `.namesAndNumbersFromEvidence()`, whose extractor
|
|
85
|
+
decides which tokens in an answer are values at all. It REPORTS — whether an
|
|
86
|
+
answer is advised or refused stays the gate's own `posture` decision, and
|
|
87
|
+
nothing here blocks, revises or rewrites anything. Absent, a run is
|
|
88
|
+
byte-identical save the registered `prior-turn-evidence` row filed
|
|
89
|
+
`not-applicable`, which is the family's law rather than an exception to it.
|
|
90
|
+
|
|
91
|
+
Three terminal exits reach a caller without the gate ever producing a
|
|
92
|
+
grounding reading — an empty answer, a middleware denial, and an answer the
|
|
93
|
+
output schema rejected — and each files its disposition rather than leaving
|
|
94
|
+
the armed row untouched. An untouched armed row is what `assertAlive` reads
|
|
95
|
+
as wiring rot, so without this an empty answer under
|
|
96
|
+
`integrityPosture: 'dev'` would have failed a healthy run with
|
|
97
|
+
`CheckerDeadError`.
|
|
98
|
+
|
|
99
|
+
Docs: [Prior-turn evidence](https://agentfootprint.dev/docs/monitor/prior-turn-evidence).
|
|
100
|
+
|
|
101
|
+
## [9.82.0] - 2026-08-30
|
|
102
|
+
|
|
103
|
+
### Added
|
|
104
|
+
|
|
105
|
+
- **A runbook can finally name the verdict no rule chose.** `verdict_meanings`
|
|
106
|
+
is generated from what the run itself said: the branch descriptions the chart
|
|
107
|
+
declared, and the rule labels this run's `decide()` evidence carried. For one
|
|
108
|
+
branch, both sources are silent by construction — the DEFAULT. It is the
|
|
109
|
+
branch chosen by *no rule* (it fires exactly when every rule failed, so no
|
|
110
|
+
`label` describes it), and when the decider lives inside a dynamically
|
|
111
|
+
generated fan-out branch the branch chart does not exist at build time either,
|
|
112
|
+
so there is no declared description to fall back on.
|
|
113
|
+
|
|
114
|
+
The library shipped visible proof of the gap: this repo's own worked example
|
|
115
|
+
and the published docs page showed a `"verdict": "protected"` row beside a
|
|
116
|
+
`verdict_meanings` map with no `protected` key.
|
|
117
|
+
|
|
118
|
+
The meaning is now declared where the rules are declared — one line at the
|
|
119
|
+
`decide()` call, on `footprintjs` ≥ 9.16.0:
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
// before
|
|
123
|
+
decide(scope, POSTURE_RULES, 'protected');
|
|
124
|
+
// after
|
|
125
|
+
decide(scope, POSTURE_RULES, {
|
|
126
|
+
branch: 'protected',
|
|
127
|
+
label: 'no rule fired — last backup within the 7-day threshold',
|
|
128
|
+
});
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
It rides `DecisionEvidence.defaultLabel` and is harvested exactly like a rule
|
|
132
|
+
label — on every decision, including runs where a rule won, so a published
|
|
133
|
+
meanings map does not gain and lose a key with the day's data.
|
|
134
|
+
|
|
135
|
+
What deliberately did NOT change: there is **no caller-supplied meanings map**
|
|
136
|
+
at the tool boundary. A map a caller can hand in is a map that can describe
|
|
137
|
+
rules that never ran, and it would be indistinguishable in the answer from
|
|
138
|
+
meanings the run produced. Declare nothing and `verdict_meanings` stays
|
|
139
|
+
honestly silent about that branch — the bridge never invents a sentence from a
|
|
140
|
+
branch id. A blank label (`''`) is recorded as no meaning at all, for a rule or
|
|
141
|
+
for the default.
|
|
142
|
+
|
|
143
|
+
Example: `examples/features/68-runbook-as-tool.ts` — its `verdict_meanings`
|
|
144
|
+
now explains every verdict its own rowset shows.
|
|
145
|
+
|
|
146
|
+
### Changed
|
|
147
|
+
|
|
148
|
+
- **`footprintjs` peer dependency: `^9.15.0` → `^9.16.1`** — 9.16.0 carries
|
|
149
|
+
`DefaultBranch` / `DecisionEvidence.defaultLabel`; 9.16.1 is the floor because
|
|
150
|
+
9.16.0 threw on a `decide()` call that omits its default (this repo's own
|
|
151
|
+
suite caught it).
|
|
152
|
+
- **The engine version stamp on a recording envelope is real again.** footprintjs
|
|
153
|
+
9.15.1 added `'./package.json'` to its `exports` map, so `engineVersion()` can
|
|
154
|
+
resolve the manifest it always tried to read: `producer.footprintjsVersion` and
|
|
155
|
+
a bug report's `environment.footprintjs` now carry the installed version
|
|
156
|
+
instead of the honest-but-useless `'unknown'`. The test that pinned the defect
|
|
157
|
+
(and asked to be tripped when it was fixed) now pins the version instead.
|
|
158
|
+
|
|
10
159
|
## [9.81.0] - 2026-08-30
|
|
11
160
|
|
|
12
161
|
### Added
|
package/CLAUDE.md
CHANGED
|
@@ -17,7 +17,7 @@ not in this table, search `src/index.ts` for the nearest noun before writing cod
|
|
|
17
17
|
|
|
18
18
|
| If you are about to build… | It is | Where | Since |
|
|
19
19
|
|---|---|---|---|
|
|
20
|
-
| turning a written operational procedure (a runbook, a triage playbook) into ONE agent tool whose every answer is EVIDENCE — coverage folded up from the inner tools it calls, rule name+version, verdict rows with GENERATED meanings, and the recorded walk as an artifact ticket, never bytes | `runbookAsTool` — dials: `procedure` (factory, invoked per call with `ctx.tools`) + `resultKind` (`'verdict/*'` arms the rowset projection; anything else ships spine + the chart's `report`) + `rules {name, version}` (default absent ⇒ `rule_version: 'undeclared'`) + `verdicts {decider, maxRows}` (default 50) + `presentation` (`'prose'` default = ship `table` + `VERDICT_RENDER_NOTE` "output it VERBATIM"; `'panel'` = the HOST renders the rowset, so NO `table` key at all + `PANEL_RENDER_NOTE` "the rows are already on the reader's screen — do not reproduce them"; the rowset half is byte-identical across modes, `table` stays RESERVED in both, an unknown value THROWS at definition) + `walk {cap, recording}` (cap default 500; over-cap ⇒ control-flow projection, declared · `recording: true` or `{label, maxBytes}`, default OFF — ALSO files the inner chart's own `{snapshot, events, structure}` under `recording/run` and puts its ref on the spine as `walk.recording_ref`, because the ROW projection cannot be drawn; snapshot read from the REDACTED mirror so one `redact` means the same for both; over `maxBytes` (`DEFAULT_RECORDING_MAX_BYTES` = 5,000,000) it is REFUSED not truncated; every absence — no store / over size / unserializable / store threw — is STATED in `walk.recording_note`, and the four `recording_*` fields are absent entirely when the dial is off) + `composedOf` (drift-checked at agent BUILD) + kept `recorders`/`keepRecord`/`keepRecordLimit`/`redact`; reserved state keys `verdicts`/`coverage`/`report` (the `report` bag lands BESIDE the spine, never over it — a report field spelling `af_coverage`/`af_provenance`/`rule_version`/`walk`/`report_note` or a live projection key is discarded and NAMED in `result.report_note`; precedence is explicit in `report.ts`, never spread order); inner `absent()` passes through VERBATIM unless the call said `allowAbsent`; walk kind `recording/chart-walk` + `walk_segment` discriminant | `src/core/runbook/` | 9.76.0, recording 9.79.0 |
|
|
20
|
+
| turning a written operational procedure (a runbook, a triage playbook) into ONE agent tool whose every answer is EVIDENCE — coverage folded up from the inner tools it calls, rule name+version, verdict rows with GENERATED meanings, and the recorded walk as an artifact ticket, never bytes | `runbookAsTool` — dials: `procedure` (factory, invoked per call with `ctx.tools`) + `resultKind` (`'verdict/*'` arms the rowset projection; anything else ships spine + the chart's `report`) + `rules {name, version}` (default absent ⇒ `rule_version: 'undeclared'`) + `verdicts {decider, maxRows}` (default 50; `verdict_meanings` = statically declared branches + this run's rule labels + the DEFAULT branch's label, which reaches evidence only when the chart calls `decide(s, rules, {branch, label})` — fp ≥9.16.1, the default is chosen by NO rule so nothing else can name it, and inside a generated fan-out branch the static walk is blind too; an undeclared or blank label stays ABSENT from the map, never invented from a branch id, and there is deliberately NO caller-supplied meanings map) + `presentation` (`'prose'` default = ship `table` + `VERDICT_RENDER_NOTE` "output it VERBATIM"; `'panel'` = the HOST renders the rowset, so NO `table` key at all + `PANEL_RENDER_NOTE` "the rows are already on the reader's screen — do not reproduce them"; the rowset half is byte-identical across modes, `table` stays RESERVED in both, an unknown value THROWS at definition) + `walk {cap, recording}` (cap default 500; over-cap ⇒ control-flow projection, declared · `recording: true` or `{label, maxBytes}`, default OFF — ALSO files the inner chart's own `{snapshot, events, structure}` under `recording/run` and puts its ref on the spine as `walk.recording_ref`, because the ROW projection cannot be drawn; snapshot read from the REDACTED mirror so one `redact` means the same for both; over `maxBytes` (`DEFAULT_RECORDING_MAX_BYTES` = 5,000,000) it is REFUSED not truncated; every absence — no store / over size / unserializable / store threw — is STATED in `walk.recording_note`, and the four `recording_*` fields are absent entirely when the dial is off) + `composedOf` (drift-checked at agent BUILD) + kept `recorders`/`keepRecord`/`keepRecordLimit`/`redact`; reserved state keys `verdicts`/`coverage`/`report` (the `report` bag lands BESIDE the spine, never over it — a report field spelling `af_coverage`/`af_provenance`/`rule_version`/`walk`/`report_note` or a live projection key is discarded and NAMED in `result.report_note`; precedence is explicit in `report.ts`, never spread order); inner `absent()` passes through VERBATIM unless the call said `allowAbsent`; walk kind `recording/chart-walk` + `walk_segment` discriminant | `src/core/runbook/` | 9.76.0, recording 9.79.0 |
|
|
21
21
|
| speaking MCP from a BROWSER — the SDK is browser-clean (its client + streamableHttp bundle at `platform:'browser'` with zero `node:` edges and never pull in `client/stdio.js`); the one barrier was that `lazyRequire` gets CALLED there, so the fix is to let the caller supply what the loader would have found | `mcpClient({ sdk })` (`McpSdk` = the two SDK modules you imported statically; the library STILL builds the transport, so headers/fetch/gateway vending/`retryOnThrottle`/`_meta` all keep working) · `mcpClient({ connection })` (`McpConnection` = listTools/callTool/close, deliberately NO `connect` — you already did; the only arm that reaches the SDK's `jsonSchemaValidator`, i.e. CSP) + `McpConnectionOptions`; `refuseConflictingOptions` REFUSES at construction every option a transport would have consumed, naming where it moved (a knob naming a behaviour that no longer happens is the defect class); `transportUrl` resolves a relative `url` against `globalThis.location.href` (absolute takes the identical branch, Node refuses by name); `sdkLoadFailure` classifies the seven load sites so a browser is never told to install a package it has (resolution failure ⇒ byte-identical historical message); `retryingFetch` + `ThrottleFetch` promoted out of `@internal` so the connection arm keeps its 429 handling. stdio keeps `lazyRequire` FOREVER — it spawns a subprocess. ZERO packaging change: no subpath, no `browser` condition (TypeScript is blind to it), peer stays optional (no literal dynamic `import()`). Fenced by `test/lib/mcp/browserGraph.test.ts` | `src/lib/mcp/` | 9.81.0 |
|
|
22
22
|
| calling ANOTHER registered tool from inside a tool's `execute` — composition over the agent's own dispatch map instead of importing the module and building a second query stack | `agentToolDispatch` + `ctx.tools` (ToolDispatch has/call) — sees static and skill-carried tools, NEVER ToolProvider-delivered ones (no build-time list, the 9.72.0 caveat); inner calls get the outer facts with hasArtifacts false and a derived toolCallId, `needs` resolved fail-closed non-interactively, `checkIn` and `wants` tools refused by name, no nested dispatch; declare ingredients via `composedOf` (and `gates` for a pausing procedure) — both travel MCP `_meta` | `src/core/agent/toolDispatch.ts` | 9.76.0 |
|
|
23
23
|
| a tool returning numbers WITH the caveats that make them honest — interval/aggregation grain, is-it-a-counter, when the world was measured, which ground was NOT covered — as typed data the model reads compactly and the record keeps whole | `semantic()` + `tools.semantics_declared` (model sees `semanticsForModel` projection; `coverage` field absorbed by the coverage()/absent() channel) | `src/lib/semantics/` | 9.53.0 |
|
|
@@ -53,6 +53,7 @@ not in this table, search `src/index.ts` for the nearest noun before writing cod
|
|
|
53
53
|
| the model inventing an id or a reading that no tool ever returned — a deterministic fabrication detector on the final answer, with a posture for how hard it pushes back | `.namesAndNumbersFromEvidence()` + `posture` (`'assist'` record+flag, the default / `'guard'` one revision then ship flagged / `'rails'` refuse instead) + `shapes` + `exempt` + `minDigits` (default 4) + `nudge` (default off) | `src/core/agent/evidence/` | 9.35.0 |
|
|
54
54
|
| counting a HUMAN-VERIFIED value as ground for the choice-seam check — the person clicked a row, the app verified the cells against the artifact, and the id the model takes from that selection is not fabricated; the source label travels onto the record | `externalGrounds` + `ExternalGround` + `external_ground_used` | `src/integrity/unsupported-argument/check.ts` | 9.72.0 |
|
|
55
55
|
| a lookup tool that answers "nothing found" for EVERY id because a filter broke, and an agent reporting that absence as fact — the run itself produced the id, the lookup came back empty, and that PAIR is worth a look | `noticeEmptyLookups` + `readLookupResult` + `EMPTY_LOOKUP_CEILING` (dial default OFF and needs a tool declaring `argumentsFrom` too — two halves; kind `empty-lookup` at seam `write`, always `advisory: true`; empty = a zero-length ARRAY or an `absent()` envelope, every other shape files `not-applicable` and no finding; the ceiling is quoted verbatim into every message) | `src/integrity/empty-lookup/` | 9.77.0 |
|
|
56
|
+
| an agent answering a NEW question out of an OLD turn's tool results — grounded, four turns stale, and the evidence gate approving it (`Tool calls 0` and "all 7 values were found in what the tools returned"). ALSO the bug half: the gate's two sentences claimed the flagged values "appear in no tool result FROM THIS TURN" while its index walked every `role:'tool'` turn — a boundary asserted and never measured; both now say "no tool result this run read" | `noticePriorTurnEvidence` + `PRIOR_TURN_EVIDENCE_CEILING` + `priorTurnEvidenceOf` + `AnswerGroundingReading` (dial default OFF and needs `.namesAndNumbersFromEvidence()` too — two halves, and the second is structural: the gate owns the extractor that decides which tokens are values; kind `prior-turn-evidence` at seam `claim`, always `advisory: true`; `EvidenceCorpus.values` became a `Map<form, turn>` stamped in the walk that was already happening, a TURN = each `role:'user'` message `isLibraryAuthoredTurn` did NOT write (counting the gate's own correction would file against every revised answer); fires when ≥1 value is grounded and NOT ONE came from the turn being answered — ONE current-turn value files nothing, which is what keeps an honest follow-up quiet and is the design's falsification test; the zero-tool-call turn is the SAME kind with a stronger witness, not a second kind; corpus is the LIVE WINDOW so ordinals are window-relative and the distance is a FLOOR, while the boundary stays exact (`'current-request'` is un-droppable); memory/RAG values are exempt from grounding and invisible — it can under-report, never over-report; the ceiling is quoted verbatim into every message) | `src/integrity/prior-turn-evidence/` | 9.83.0 |
|
|
56
57
|
| a tool's ROWS quietly disagreeing with what the tool promised — a LUN 0 stored as `""` because it is falsy, a numeric column arriving as quoted strings, a declared column present in no row at all | `Tool.resultColumns` + `checkColumnTypes` + `COLUMN_TYPE_CEILING` + `readRowset` (dial `'off'` (default) / `'warn'` (file findings, model reads the rows unchanged) / `'enforce'` (refuse the rows, `resultCeiling`'s teaching-sentence idiom, delivered status `'invalid'`) — the `toolArgsValidation` trio, this seam's mirror; needs a tool declaring `resultColumns` too — two halves; types `number`/`string`/`boolean`/`date`, bare word or `{type, nullable}`, deliberately NO `'unknown'`; OPEN — unlisted columns allowed and unjudged; TWO kinds at seam `write` — `column-type-mismatch` (there, wrong type) vs `missing-column` (declared, in no row); rowset = an ARRAY OF PLAIN OBJECTS with ≥1 row, everything else incl. the ZERO-ROW result files `not-applicable` (empty is `empty-lookup`'s subject); travels MCP `_meta`; the ceiling is quoted verbatim into every message) | `src/integrity/column-types/` | 9.78.0 |
|
|
57
58
|
| a model head-mathing a total from tool-result numbers while a compute tool sits unused on the wire — a LATE line each iteration naming the staged refs and the `wants` tool that spends them (recency working FOR the instruction), plus the revise correction naming the same route | `nudge: true` (default off) + `stagedRefsNudgeLine` + `findStagedRefs` + `grounding_nudged` | `src/core/agent/stagedRefs.ts` | 9.75.0 |
|
|
58
59
|
| a tool answering "I looked and found nothing", routably | `absent` + `looked_for` + `checked`/`not_checked`/`cannot_cover` + `tryInstead` (delivered status `'absent'`, routable by `onToolStatus`) | `src/core/agent/coverage/absent.ts` | 9.43.0 |
|
package/README.md
CHANGED
|
@@ -121,7 +121,7 @@ This is the step that makes "the test run and the production run are the same co
|
|
|
121
121
|
|
|
122
122
|
`ollama()` talks Ollama's native API directly, so there's nothing to install on this side, streamed calls report real token counts (so `.compaction()` and cost budgets work), and when it can't work it says why in words that contain the fix — `ollama serve` when nothing is listening, `ollama pull <model>` when the model isn't there, never a raw connection error and never a hang.
|
|
123
123
|
|
|
124
|
-
For llama.cpp's `llama-server`, vLLM, Together or Groq, use `openai({ baseURL: 'http://localhost:8080/v1', apiKey: 'not-needed', defaultModel: '…' })` — any server speaking the OpenAI Chat Completions API, same `Agent` code either way. Full recipes: [Ollama guide](https://
|
|
124
|
+
For llama.cpp's `llama-server`, vLLM, Together or Groq, use `openai({ baseURL: 'http://localhost:8080/v1', apiKey: 'not-needed', defaultModel: '…' })` — any server speaking the OpenAI Chat Completions API, same `Agent` code either way. Full recipes: [Ollama guide](https://agentfootprint.dev/docs/build/ollama/) · [OpenAI-compatible endpoints](https://agentfootprint.dev/docs/build/openai/#openai-compatible-endpoints-ollama-llamacpp-vllm-together-groq-lm-studio).
|
|
125
125
|
|
|
126
126
|
### Then add context
|
|
127
127
|
|
|
@@ -591,7 +591,7 @@ const reflexion = Loop.create()
|
|
|
591
591
|
| 2 | 12 tools shown | **5 tools** (skill activated) |
|
|
592
592
|
| 3 | 12 tools shown | 5 tools |
|
|
593
593
|
|
|
594
|
-
> 📖 [Dynamic ReAct guide](https://
|
|
594
|
+
> 📖 [Dynamic ReAct guide](https://agentfootprint.dev/docs/build/dynamic-react/) · [Key concepts](https://agentfootprint.dev/docs/getting-started/key-concepts/)
|
|
595
595
|
|
|
596
596
|
### Multi-agent — compose with the alphabet
|
|
597
597
|
|
|
@@ -635,7 +635,7 @@ The patterns the field knows reduce to the same alphabet:
|
|
|
635
635
|
|
|
636
636
|
Same trick as the injection model: instead of N libraries for N patterns, we found the M building blocks all N patterns are made of.
|
|
637
637
|
|
|
638
|
-
> 📖 Compare: [hand-rolled vs declarative](https://
|
|
638
|
+
> 📖 Compare: [hand-rolled vs declarative](https://agentfootprint.dev/docs/getting-started/why/) · [migration from LangChain / CrewAI / LangGraph](https://agentfootprint.dev/docs/getting-started/vs/)
|
|
639
639
|
|
|
640
640
|
### Check in with the receipts — human-in-the-loop consent for consequential actions
|
|
641
641
|
|
|
@@ -663,7 +663,7 @@ Run the flagship demo — an AI coworker that drafts a weekly status doc and che
|
|
|
663
663
|
npm run example examples/features/34-checkin-coworker.ts -- --decline
|
|
664
664
|
```
|
|
665
665
|
|
|
666
|
-
See the [Check-in guide](https://
|
|
666
|
+
See the [Check-in guide](https://agentfootprint.dev/docs/monitor/checkin/).
|
|
667
667
|
|
|
668
668
|
### Act — everything your agent does about its own loop, in one block
|
|
669
669
|
|
|
@@ -697,7 +697,7 @@ It is pure sugar over the five individual doors, pinned byte-equivalent per key
|
|
|
697
697
|
npm run example examples/features/38-act.ts
|
|
698
698
|
```
|
|
699
699
|
|
|
700
|
-
See [The moments of the loop](https://
|
|
700
|
+
See [The moments of the loop](https://agentfootprint.dev/docs/build/loop-moments/).
|
|
701
701
|
|
|
702
702
|
### Watch — who is looking while it does
|
|
703
703
|
|
|
@@ -774,7 +774,7 @@ answers at the cheap model's price inside the expensive conversation.
|
|
|
774
774
|
[`08`](examples/observability/08-self-explain.ts) · the doors walk the
|
|
775
775
|
[**same evidence the board visualizes ▶**](https://footprintjs.github.io/agentThinkingUI/demo/backtrack.html).
|
|
776
776
|
|
|
777
|
-
> 📖 Powered by [footprintjs `causalChain()`](https://footprintjs.github.io/footPrint/blog/backward-causal-chain/) — backward thin-slicing on the commit log. [Causal memory deep dive](https://
|
|
777
|
+
> 📖 Powered by [footprintjs `causalChain()`](https://footprintjs.github.io/footPrint/blog/backward-causal-chain/) — backward thin-slicing on the commit log. [Causal memory deep dive](https://agentfootprint.dev/docs/debug/causal-deep-dive/) · [Explainability & compliance](https://footprintjs.github.io/footPrint/blog/explainability-compliance/)
|
|
778
778
|
|
|
779
779
|
**One recording. Two lenses. Three consumers. Zero extra instrumentation.**
|
|
780
780
|
|
|
@@ -898,7 +898,7 @@ The flowchart, recorders, and tests don't change between dev and prod.
|
|
|
898
898
|
| `anthropic` | Claude (Sonnet, Opus, Haiku) via `@anthropic-ai/sdk` |
|
|
899
899
|
| `openai` | GPT-4o, GPT-4-turbo via `openai` SDK |
|
|
900
900
|
| `bedrock` | Claude / Titan / Mistral via AWS Bedrock runtime |
|
|
901
|
-
| `gemini` | Gemini via `@google/genai` — two doors, **Vertex** (project + ADC) or the Gemini API (one key). Not every door/model pair works: read the [door/model matrix](https://
|
|
901
|
+
| `gemini` | Gemini via `@google/genai` — two doors, **Vertex** (project + ADC) or the Gemini API (one key). Not every door/model pair works: read the [door/model matrix](https://agentfootprint.dev/docs/infrastructure/google-cloud/#the-doormodel-matrix) first |
|
|
902
902
|
| `ollama` | Local models, over Ollama's native API — no SDK, no key, real token counts, refusals that name `ollama serve` / `ollama pull` · `openai({ baseURL })` reaches llama.cpp, vLLM, and any other OpenAI-compatible endpoint |
|
|
903
903
|
| `browserAnthropic` | Browser-side Claude calls (no proxy server) |
|
|
904
904
|
| `browserOpenai` | Browser-side OpenAI calls (no proxy server) |
|
|
@@ -916,7 +916,7 @@ The flowchart, recorders, and tests don't change between dev and prod.
|
|
|
916
916
|
- Skill artifact vocabularies — a skill or a step declares `produces` / `consumes` (artifact kinds), and `graph.checkup()` warns `artifact-kind-unsatisfied` when nothing on the agent claims to make what a consumer needs. Honest by construction: a warning, never an error, because it reads declarations only and artifacts outlive the turn that made them
|
|
917
917
|
- Human-in-the-loop pause / resume — a tool calls `pauseHere(...)` (or `askHuman(...)`); `isPaused(result)` hands you a JSON-serializable checkpoint, and `agent.resume(checkpoint, input)` continues hours later on a different server
|
|
918
918
|
- Resilience primitives — `withRetry`, `withFallback`, `withCircuitBreaker`, `.outputFallback`, `agent.resumeOnError`
|
|
919
|
-
- Context Integrity — deterministic checks at the seams where a run contradicts ITSELF: a tool parked but still on the wire, a tool offered after the results grounding it were evicted, an answer field that disagrees with the fact it claims to report (`.claims()`, requires `.outputSchema()`). Nothing is blocked or rewritten — each defect is one typed finding, and every run files a disposition ledger so "no findings" and "no check ran" stay different states. `integrityPosture: 'dev'` adds the liveness proofs (a start-of-run canary; `CheckerDeadError` instead of a green report from a checker that never ran). Read it back with `find_context_errors` over a recording — [Context Integrity](https://
|
|
919
|
+
- Context Integrity — deterministic checks at the seams where a run contradicts ITSELF: a tool parked but still on the wire, a tool offered after the results grounding it were evicted, an answer field that disagrees with the fact it claims to report (`.claims()`, requires `.outputSchema()`). Nothing is blocked or rewritten — each defect is one typed finding, and every run files a disposition ledger so "no findings" and "no check ran" stay different states. `integrityPosture: 'dev'` adds the liveness proofs (a start-of-run canary; `CheckerDeadError` instead of a green report from a checker that never ran). Read it back with `find_context_errors` over a recording — [Context Integrity](https://agentfootprint.dev/docs/monitor/context-integrity/)
|
|
920
920
|
- 60+ typed observability events — `agent` · `composition` · `context` · `stream` · `tools` · `skill` · `memory` · `cache` · `cost` · `permission` · `eval` · `embedding` · `pause` · `error` · `fallback` · `resilience` · `reliability` · `risk`
|
|
921
921
|
|
|
922
922
|
**Debugging & compliance** (`agentfootprint/observe`)
|
|
@@ -934,7 +934,7 @@ The flowchart, recorders, and tests don't change between dev and prod.
|
|
|
934
934
|
|
|
935
935
|
</details>
|
|
936
936
|
|
|
937
|
-
> 📖 [Agent API reference](https://
|
|
937
|
+
> 📖 [Agent API reference](https://agentfootprint.dev/docs/api/classes/Agent/) · [CHANGELOG](./CHANGELOG.md)
|
|
938
938
|
|
|
939
939
|
---
|
|
940
940
|
|
|
@@ -942,12 +942,12 @@ The flowchart, recorders, and tests don't change between dev and prod.
|
|
|
942
942
|
|
|
943
943
|
| If you are... | Go here |
|
|
944
944
|
|---|---|
|
|
945
|
-
| New to agents | [5-minute quick start](https://
|
|
946
|
-
| Coming from LangChain / CrewAI / LangGraph | [Migration guide](https://
|
|
947
|
-
| Architecting an enterprise rollout | [Production guide](https://
|
|
948
|
-
| Doing due diligence | [Architecture overview](https://
|
|
949
|
-
| Researcher / academic background | [Citations & prior art](https://
|
|
950
|
-
| Curious about design | [Inspiration docs](https://
|
|
945
|
+
| New to agents | [5-minute quick start](https://agentfootprint.dev/docs/getting-started/quick-start/) |
|
|
946
|
+
| Coming from LangChain / CrewAI / LangGraph | [Migration guide](https://agentfootprint.dev/docs/getting-started/vs/) |
|
|
947
|
+
| Architecting an enterprise rollout | [Production guide](https://agentfootprint.dev/docs/monitor/deployment/) |
|
|
948
|
+
| Doing due diligence | [Architecture overview](https://agentfootprint.dev/docs/reference/dependency-graph/) |
|
|
949
|
+
| Researcher / academic background | [Citations & prior art](https://agentfootprint.dev/docs/reference/citations/) |
|
|
950
|
+
| Curious about design | [Inspiration docs](https://agentfootprint.dev/docs/reference/inspiration/) |
|
|
951
951
|
|
|
952
952
|
Or jump into the [examples gallery](https://github.com/footprintjs/agentfootprint/tree/main/examples) — every example is also an end-to-end CI test.
|
|
953
953
|
|
|
@@ -309,7 +309,7 @@ if (!report.ok) throw new Error(formatCheckup(report));
|
|
|
309
309
|
The full architecture of the skill graph — the three surfaces, the authority rule, the
|
|
310
310
|
nine cursor causes, the three-way `read_skill`, and a worked refusal taken from a real
|
|
311
311
|
run — is published as **Skill graph architecture**:
|
|
312
|
-
<https://
|
|
312
|
+
<https://agentfootprint.dev/docs/build/skill-graph-architecture/>.
|
|
313
313
|
Every capability claim there carries a status — `shipped` / `opt-in` /
|
|
314
314
|
`application-provided` / `planned` — and every code block is type-checked against the
|
|
315
315
|
shipped types at build. Read it rather than this file when the question is "how does
|
package/dist/core/Agent.js
CHANGED
|
@@ -315,6 +315,10 @@ class Agent extends RunnerBase_js_1.RunnerBase {
|
|
|
315
315
|
/** See AgentOptions.noticeEmptyLookups (9.77.0). Default false — absent is
|
|
316
316
|
* byte-identical, save for the registered not-applicable ledger row. */
|
|
317
317
|
noticeEmptyLookups = false;
|
|
318
|
+
/** See AgentOptions.noticePriorTurnEvidence (9.83.0). Default false —
|
|
319
|
+
* absent is byte-identical, save for the registered not-applicable ledger
|
|
320
|
+
* row. */
|
|
321
|
+
noticePriorTurnEvidence = false;
|
|
318
322
|
/** Set at chart build: whether any tool in the FULL declared catalog
|
|
319
323
|
* declared `resultColumns` (9.78.0) — the other half of the column-type
|
|
320
324
|
* contract's arming. */
|
|
@@ -682,6 +686,22 @@ class Agent extends RunnerBase_js_1.RunnerBase {
|
|
|
682
686
|
}
|
|
683
687
|
this.noticeEmptyLookups = opts.noticeEmptyLookups;
|
|
684
688
|
}
|
|
689
|
+
// The claim seam's recency dial (9.83.0) — refused at construction for
|
|
690
|
+
// the same reason as the write-seam one above: a truthy non-boolean here
|
|
691
|
+
// would silently arm a check the author only half asked for, and the
|
|
692
|
+
// arming is what decides whether a run is byte-identical to the one
|
|
693
|
+
// before it.
|
|
694
|
+
if (opts.noticePriorTurnEvidence !== undefined) {
|
|
695
|
+
if (typeof opts.noticePriorTurnEvidence !== 'boolean') {
|
|
696
|
+
throw new Error(`Agent: noticePriorTurnEvidence must be a boolean, got ` +
|
|
697
|
+
`${JSON.stringify(opts.noticePriorTurnEvidence)}. It arms the claim-seam ` +
|
|
698
|
+
`'prior-turn-evidence' advisory — a final answer whose every value was last served ` +
|
|
699
|
+
`before this turn — and it needs \`.namesAndNumbersFromEvidence()\` armed beside ` +
|
|
700
|
+
`it, because that gate owns the extractor that decides which tokens are values. ` +
|
|
701
|
+
`Omit it (or pass false) and no such advisory is ever filed.`);
|
|
702
|
+
}
|
|
703
|
+
this.noticePriorTurnEvidence = opts.noticePriorTurnEvidence;
|
|
704
|
+
}
|
|
685
705
|
// The column-type contract's dial (9.78.0) — refused at construction for
|
|
686
706
|
// the same reason as the postures above, and with one more: `'enforce'`
|
|
687
707
|
// REFUSES tool results, so a misspelling silently downgraded to off would
|
|
@@ -2389,6 +2409,12 @@ class Agent extends RunnerBase_js_1.RunnerBase {
|
|
|
2389
2409
|
// AND a tool declaring `resultColumns`. Either alone leaves two
|
|
2390
2410
|
// registered `not-applicable` rows.
|
|
2391
2411
|
columnTypes: this.checkColumnTypes !== 'off' && this.integrityColumnsPresent,
|
|
2412
|
+
// TWO HALVES (9.83.0), and here the second is structural rather than
|
|
2413
|
+
// a policy choice: the evidence gate owns the extractor that decides
|
|
2414
|
+
// which tokens in an answer are values, so a dial with no gate has
|
|
2415
|
+
// nothing whose provenance it could read. Either alone leaves a
|
|
2416
|
+
// registered `not-applicable` row.
|
|
2417
|
+
priorTurnEvidence: this.noticePriorTurnEvidence && this.evidenceGate !== undefined,
|
|
2392
2418
|
}, this.integrityPosture);
|
|
2393
2419
|
}
|
|
2394
2420
|
/**
|
|
@@ -3267,7 +3293,13 @@ class Agent extends RunnerBase_js_1.RunnerBase {
|
|
|
3267
3293
|
// question a build-time mount can answer.
|
|
3268
3294
|
const canCallTools = registryByName.size > 0 || this.externalToolProvider !== undefined;
|
|
3269
3295
|
const hasWrapUp = canCallTools && this.wrapUpAtMaxIterations !== false;
|
|
3270
|
-
const routeDecider = (0, route_js_1.buildRouteDeciderStage)(this.messageMiddleware, this.outputEnforcement, stepPlanFor, this.evidenceGate, hasWrapUp, this.claimContract, this.integrityLedgerHolder
|
|
3296
|
+
const routeDecider = (0, route_js_1.buildRouteDeciderStage)(this.messageMiddleware, this.outputEnforcement, stepPlanFor, this.evidenceGate, hasWrapUp, this.claimContract, this.integrityLedgerHolder,
|
|
3297
|
+
// THE CLAIM SEAM'S RECENCY READ (9.83.0). Value-conditional on both
|
|
3298
|
+
// halves, so an agent that armed neither hands the decider builder
|
|
3299
|
+
// exactly the arguments it always did — and `buildRouteDeciderStage`'s
|
|
3300
|
+
// no-judge fast path still returns the very function reference every
|
|
3301
|
+
// pre-9.83.0 chart was given.
|
|
3302
|
+
this.noticePriorTurnEvidence && this.evidenceGate !== undefined ? true : undefined);
|
|
3271
3303
|
// toolCallsHandler extracted to ./agent/stages/toolCalls.ts (v2.11.2).
|
|
3272
3304
|
const toolCallsHandler = (0, toolCalls_js_1.buildToolCallsHandler)({
|
|
3273
3305
|
registryByName,
|