agentfootprint 9.104.0 → 9.105.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +68 -0
- package/CLAUDE.md +2 -1
- package/README.md +1 -0
- package/ai-instructions/claude-code/SKILL.md +1 -1
- package/dist/core/Agent.js +48 -0
- package/dist/core/Agent.js.map +1 -1
- package/dist/core/agent/AgentBuilder.js +97 -1
- package/dist/core/agent/AgentBuilder.js.map +1 -1
- package/dist/core/agent/buildAgentChart.js +13 -0
- package/dist/core/agent/buildAgentChart.js.map +1 -1
- package/dist/core/agent/buildDynamicAgentChart.js +28 -0
- package/dist/core/agent/buildDynamicAgentChart.js.map +1 -1
- package/dist/core/agent/stages/callLLM.js +42 -0
- package/dist/core/agent/stages/callLLM.js.map +1 -1
- package/dist/core/agent/toolChoice/compose.js +92 -0
- package/dist/core/agent/toolChoice/compose.js.map +1 -0
- package/dist/core/agent/toolChoice/pick.js +165 -0
- package/dist/core/agent/toolChoice/pick.js.map +1 -0
- package/dist/core/agent/toolChoice/record.js +108 -0
- package/dist/core/agent/toolChoice/record.js.map +1 -0
- package/dist/core/agent/toolChoice/types.js +46 -0
- package/dist/core/agent/toolChoice/types.js.map +1 -0
- package/dist/core/slots/buildToolsSlot.js +200 -120
- package/dist/core/slots/buildToolsSlot.js.map +1 -1
- package/dist/esm/core/Agent.d.ts +7 -0
- package/dist/esm/core/Agent.js +48 -0
- package/dist/esm/core/Agent.js.map +1 -1
- package/dist/esm/core/agent/AgentBuilder.d.ts +49 -0
- package/dist/esm/core/agent/AgentBuilder.js +97 -1
- package/dist/esm/core/agent/AgentBuilder.js.map +1 -1
- package/dist/esm/core/agent/buildAgentChart.d.ts +13 -0
- package/dist/esm/core/agent/buildAgentChart.js +13 -0
- package/dist/esm/core/agent/buildAgentChart.js.map +1 -1
- package/dist/esm/core/agent/buildDynamicAgentChart.js +28 -0
- package/dist/esm/core/agent/buildDynamicAgentChart.js.map +1 -1
- package/dist/esm/core/agent/stages/callLLM.d.ts +11 -0
- package/dist/esm/core/agent/stages/callLLM.js +19 -0
- package/dist/esm/core/agent/stages/callLLM.js.map +1 -1
- package/dist/esm/core/agent/toolChoice/compose.d.ts +53 -0
- package/dist/esm/core/agent/toolChoice/compose.js +88 -0
- package/dist/esm/core/agent/toolChoice/compose.js.map +1 -0
- package/dist/esm/core/agent/toolChoice/pick.d.ts +104 -0
- package/dist/esm/core/agent/toolChoice/pick.js +158 -0
- package/dist/esm/core/agent/toolChoice/pick.js.map +1 -0
- package/dist/esm/core/agent/toolChoice/record.d.ts +49 -0
- package/dist/esm/core/agent/toolChoice/record.js +101 -0
- package/dist/esm/core/agent/toolChoice/record.js.map +1 -0
- package/dist/esm/core/agent/toolChoice/types.d.ts +125 -0
- package/dist/esm/core/agent/toolChoice/types.js +43 -0
- package/dist/esm/core/agent/toolChoice/types.js.map +1 -0
- package/dist/esm/core/agent/types.d.ts +59 -0
- package/dist/esm/core/slots/buildToolsSlot.d.ts +21 -0
- package/dist/esm/core/slots/buildToolsSlot.js +177 -120
- package/dist/esm/core/slots/buildToolsSlot.js.map +1 -1
- package/dist/esm/events/payloads.d.ts +47 -0
- package/dist/esm/events/registry.d.ts +9 -1
- package/dist/esm/events/registry.js +8 -0
- package/dist/esm/events/registry.js.map +1 -1
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/events/registry.js +8 -0
- package/dist/events/registry.js.map +1 -1
- package/dist/index.js +12 -3
- package/dist/index.js.map +1 -1
- package/dist/types/core/Agent.d.ts +7 -0
- package/dist/types/core/Agent.d.ts.map +1 -1
- package/dist/types/core/agent/AgentBuilder.d.ts +49 -0
- package/dist/types/core/agent/AgentBuilder.d.ts.map +1 -1
- package/dist/types/core/agent/buildAgentChart.d.ts +13 -0
- package/dist/types/core/agent/buildAgentChart.d.ts.map +1 -1
- package/dist/types/core/agent/buildDynamicAgentChart.d.ts.map +1 -1
- package/dist/types/core/agent/stages/callLLM.d.ts +11 -0
- package/dist/types/core/agent/stages/callLLM.d.ts.map +1 -1
- package/dist/types/core/agent/toolChoice/compose.d.ts +54 -0
- package/dist/types/core/agent/toolChoice/compose.d.ts.map +1 -0
- package/dist/types/core/agent/toolChoice/pick.d.ts +105 -0
- package/dist/types/core/agent/toolChoice/pick.d.ts.map +1 -0
- package/dist/types/core/agent/toolChoice/record.d.ts +50 -0
- package/dist/types/core/agent/toolChoice/record.d.ts.map +1 -0
- package/dist/types/core/agent/toolChoice/types.d.ts +126 -0
- package/dist/types/core/agent/toolChoice/types.d.ts.map +1 -0
- package/dist/types/core/agent/types.d.ts +59 -0
- package/dist/types/core/agent/types.d.ts.map +1 -1
- package/dist/types/core/slots/buildToolsSlot.d.ts +21 -0
- package/dist/types/core/slots/buildToolsSlot.d.ts.map +1 -1
- package/dist/types/events/payloads.d.ts +47 -0
- package/dist/types/events/payloads.d.ts.map +1 -1
- package/dist/types/events/registry.d.ts +9 -1
- package/dist/types/events/registry.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +2 -1
package/AGENTS.md
CHANGED
|
@@ -544,7 +544,7 @@ files `agentfootprint.tools.coverage_declared`; with
|
|
|
544
544
|
to the final answer, which is how a limit survives a model that would rather not
|
|
545
545
|
mention it.
|
|
546
546
|
|
|
547
|
-
### Observability —
|
|
547
|
+
### Observability — 118 typed events across 26 domains
|
|
548
548
|
|
|
549
549
|
```typescript
|
|
550
550
|
agent.on('agentfootprint.context.injected', (e) =>
|
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,74 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [9.105.0] - 2026-09-17
|
|
9
|
+
|
|
10
|
+
### Added — tool choice by classifier: a second reading beside the model's call, and a narrowing dial
|
|
11
|
+
|
|
12
|
+
- **`.toolChoice({ classifier, serve, alwaysServe })`** (`AgentOptions.toolChoice`,
|
|
13
|
+
`AgentBuilder.toolChoice`). At every model call the tools slot asks the
|
|
14
|
+
classifier ONE `choice` question — id `TOOL_CHOICE_QUESTION` (`'tool'`),
|
|
15
|
+
criteria the tools about to be served by name with their own descriptions
|
|
16
|
+
(the merged wire minus the always-served doors), state the user's message
|
|
17
|
+
plus the active skill id — and files a `ToolChoiceRow` under
|
|
18
|
+
`AgentState.toolChoices` BEFORE the call: `offered`, `ranked` (the
|
|
19
|
+
provider's distribution as sent, highest first, an unscored tool absent —
|
|
20
|
+
never padded, never renormalised), `chosen` (the provider's own pick,
|
|
21
|
+
absent when it named nothing offered), `confidence`, `usage`, `latencyMs`,
|
|
22
|
+
`served` (the names the slot committed) and `narrowed`. After the reply
|
|
23
|
+
`callLLM` files a `ToolChoiceOutcomeRow`: `called` (the model's tool calls
|
|
24
|
+
in order, empty on an answer), `firstAgrees` (`chosen === called[0]`,
|
|
25
|
+
absent when either is absent) and `miss` (the names called outside a
|
|
26
|
+
narrowed served list). A failed classifier call is a `ToolChoiceErrorRow`
|
|
27
|
+
(status, message, latency) and the full set is served — fail open, never
|
|
28
|
+
fail narrow. The model's call is the emission; the pick is a second
|
|
29
|
+
reading marked `source: 'classifier'`, never substituted, never merged.
|
|
30
|
+
- **Advisory by default** (`serve: 'all'`): the wire is byte for byte the
|
|
31
|
+
unarmed agent's — every request equal, every receipt equal; the record
|
|
32
|
+
gains the rows and nothing else.
|
|
33
|
+
- **The narrowing dial** (`serve: { top: N }`): the slot commits the
|
|
34
|
+
classifier's top-N plus the doors — `read_skill`, `list_skills`,
|
|
35
|
+
`skip_step`, `present` (`ALWAYS_SERVED_TOOLS`) and the app's `alwaysServe`
|
|
36
|
+
— in the merged wire's order, at the ONE decoration site, so
|
|
37
|
+
`dynamicToolSchemas`, the receipt's `tools.schemaHashes` and
|
|
38
|
+
`servedAt(k).tools.schemas` are the narrowed list by construction (no new
|
|
39
|
+
`SERVED_GAPS` kind; `toolsInjections` follows the served set). The full
|
|
40
|
+
wire is served with the reason on the row (`narrowedSkipped`:
|
|
41
|
+
`NarrowSkipReason`) when the classifier failed or scored fewer than N
|
|
42
|
+
(`unavailable`), fewer than N + 1 candidates were offered (`too-few`), the
|
|
43
|
+
previous call's outcome carried a miss (`after-miss`) or the call is the
|
|
44
|
+
out-of-budget wrap-up (`wrap-up`). A miss — the model naming a
|
|
45
|
+
narrowed-away tool — is recorded on the outcome row and answered by the
|
|
46
|
+
dispatcher's off-wire path exactly as before (`tools.answered_off_wire`);
|
|
47
|
+
the next call serves the full wire. `reactMode: 'classic'` is refused at
|
|
48
|
+
build (the `.findings()` precedent).
|
|
49
|
+
- **Three events**, `agentfootprint.tool_choice.picked` (`chosen`,
|
|
50
|
+
`confidence`, `offered` and `served` as counts, `narrowed`,
|
|
51
|
+
`narrowedSkipped`, `latencyMs`, tokens), `agentfootprint.tool_choice.outcome`
|
|
52
|
+
(`called`, `firstAgrees`, `missed`) and `agentfootprint.tool_choice.failed`
|
|
53
|
+
(`status`, `latencyMs`) — identities, enums, numbers and a boolean only
|
|
54
|
+
(payloads `ToolChoicePickedPayload`, `ToolChoiceOutcomePayload`,
|
|
55
|
+
`ToolChoiceFailedPayload`); 115 → 118 typed events, 25 → 26 domains.
|
|
56
|
+
- **Root exports**: `ToolChoiceRow`, `ToolChoiceErrorRow`,
|
|
57
|
+
`ToolChoiceOutcomeRow`, `ToolChoiceEntry`, `ToolChoiceLedger`,
|
|
58
|
+
`ToolChoiceScore`, `NarrowSkipReason`, `TOOL_CHOICE_QUESTION`,
|
|
59
|
+
`ALWAYS_SERVED_TOOLS`. The asker (`src/core/agent/toolChoice/pick.ts`) is
|
|
60
|
+
loaded through `import()` by the slot and is deliberately not exported.
|
|
61
|
+
- **`npm run bench:tool-choice`** — the mock provider, one skill of eight
|
|
62
|
+
tools, six scripted steps, a scripted classifier ranking the right tool
|
|
63
|
+
first / second / a wrong pair, under `advisory` and `top-2`: `first-agrees`,
|
|
64
|
+
`misses`, `extra-calls`, `tools-slot-bytes` (from the receipt's
|
|
65
|
+
`requestMeasurement`), `pick-tokens`, `pick-latency-ms`; the cost line
|
|
66
|
+
first; exits non-zero if the unarmed twin's tools-slot bytes move or an
|
|
67
|
+
advisory row's differ from the twin's. On 2026-09-17: 1745 bytes unarmed
|
|
68
|
+
and on every advisory row, 743 under `top-2` with a right ranking, 1172
|
|
69
|
+
under a wrong pair (three misses, the full wire on the call after each,
|
|
70
|
+
zero extra calls).
|
|
71
|
+
- Byte identity: the 18 unarmed references untouched; one new reference
|
|
72
|
+
`agent-tool-choice` (`test/core/tools/byte-identity.test.ts`), generated
|
|
73
|
+
alone. Design: `docs/design/2026-09-scored-choice.md` § Step 4; README:
|
|
74
|
+
`src/core/agent/toolChoice/README.md`.
|
|
75
|
+
|
|
8
76
|
## [9.104.0] - 2026-09-17
|
|
9
77
|
|
|
10
78
|
### Added — a calibrated judge beside the model's own standings
|
package/CLAUDE.md
CHANGED
|
@@ -68,6 +68,7 @@ not in this table, search `src/index.ts` for the nearest noun before writing cod
|
|
|
68
68
|
| subscribing to every event in one domain at once | `DomainWildcard` | `src/events/dispatcher.ts` | 9.4.0 |
|
|
69
69
|
| pausing a run for a person and resuming it later | `checkInApproved` / `checkInDeclined` (`{ by, note?, value? }`; a decline is NOT an abort — the model gets a "declined by human" tool result and adapts in-loop) | `src/core/checkin.ts` | 7.5.0 |
|
|
70
70
|
| a SECOND source judging every tool result — a calibrated classifier asked what a result is worth for the declared proposition (never why the tool was called), its distribution and cost on the record beside the model's own standing, never merged, never served in its place (policy A) | `.findings({ judge })` + `Classifier` / `typesafe` / `mockClassifier` (`agentfootprint/classify`) + `JudgmentRow` / `JudgmentErrorRow` + `classifierScorer` (the scored entry choice, `agentfootprint/skill-graph`) | `src/classify/` | 9.104.0 |
|
|
71
|
+
| a classifier's reading of WHICH TOOL beside every model call — the ranking, the pick and the cost on the record before the call, the model's call and the agreement after it; optionally the served list narrowed to the top-N plus the doors, on the committed record the receipt hashes and the served view rebuilds; a miss recorded and the full wire served next call, never a refusal, never an override | `.toolChoice({ classifier, serve, alwaysServe })` + `ToolChoiceRow` + `ToolChoiceOutcomeRow` + `ToolChoiceErrorRow` + `ALWAYS_SERVED_TOOLS` + `recordToolChoice` + `narrowServed` (events `agentfootprint.tool_choice.*`, bench `npm run bench:tool-choice`) | `src/core/agent/toolChoice/` | 9.105.0 |
|
|
71
72
|
|
|
72
73
|
**One law before you add a mapping.** Any function turning caller data into a KEY,
|
|
73
74
|
a namespace, a filename or an index entry must be injective, and its collision
|
|
@@ -158,7 +159,7 @@ Traps: `src/observability/` holds the finder IMPLEMENTATIONS (canonical home; `d
|
|
|
158
159
|
- **Embedder fingerprint** (8.9.0) → `Embedder.id` (optional; every shipped embedder sets one, and NONE include dims — the store appends `@<dims>` itself, so an id carrying its own size double-stamps) + `indexDocuments` defaulting `embedderId` to it + `SqliteVectorStore.reconcileFingerprint` (the only comparison site). Rule: dimensions ALWAYS decide, model ids decide only when BOTH sides named themselves — refusing on an absent name would block the majority of callers who never pass `embedderId`.
|
|
159
160
|
- **Retrieval record** (8.8.0) → FOUR stages write one object in sequence: `loadRelevant` (candidates+scores+threshold verdicts) → `pickByBudget` (re-marks admitted→over-budget/over-max-entries) → `formatDefault` (`promptFragment` + `promptPosition`) → the read mount's outputMapper lifts it to root as `retrievalEvidence_<id>`. `memoryRecallInjections` then splits ONE recall into one ActiveInjection PER CHUNK — guarded by a byte-equality check (`fragments.join('\n\n') === systemContent`) that falls back to the single injection rather than change the prompt. `rank` (score order) and `promptPosition` (picker order) are DIFFERENT and both load-bearing: joining fragments in rank order reproduces the right bytes in a sequence the model never saw.
|
|
160
161
|
- **AgentState** → all 8 stages/ files, both builders' mappers, memory-wire STRING-TYPED keys ('runIdentity'/'turnNumber'/… buildAgentChart.ts:177-180 — not refactor-safe), finalizeResult's `reliabilityFail*`/`policyHalt*` reads (rename silently kills the typed errors).
|
|
161
|
-
- **events/** →
|
|
162
|
+
- **events/** → 118 typed events across 26 domains (counts anti-drift-tested against this file — update BOTH when adding events): ALL_EVENT_TYPES exhaustiveness tests, DomainWildcard hand-list, ~42 importers (recorders, strategies, stream, commentary).
|
|
162
163
|
- **Run-configuration manifest (9.41.0)** → `agentfootprint.agent.run_configured`, the JOIN KEY that turns N runs into N labelled ARMS: one event naming the adapters/strategies in play (provider+model, reactMode, each memory's declared strategy/retrieval/embedder, window, graph posture+classifier, evidence posture, artifacts-present). Composed by the PURE `core/agent/runManifest.ts`, dispatched from `Agent.emitRunManifest()` at the END of `createExecutor` — the ONE funnel `run()` AND `resume()` share, both of which mint a fresh runId. Direct `dispatcher.dispatch` with a STATED pseudo-stage (`run-configured#0`), the `emitToolSessionReport` precedent — there is no stage yet, and `minimalMeta()` would make the one joinable-by-design event unjoinable. TWO laws, both tested: NAMES ONLY (a store is reported PRESENT and unnamed rather than identified by a directory/endpoint — `MemoryStore` and `ArtifactStore` declare no id), and ABSENT means "not configured", never a guessed `'default'`. Graph presence is read off `skillGraphNextSkill`, NOT `skillGraphCascade` (a 9.16-style mount sets no cascade and would read as "no graph"). `MemoryDefinition` gained `strategy`/`retrieval`/`embedderId` for it — declared names the compiled pipeline had closed over, the `store`-in-the-open precedent.
|
|
163
164
|
- **adapters/types.ts LLMMessage/LLMRequest** → 62 importers: tool_use round-trip (toolCalls.ts:115-135), wire assembly (callLLM.ts:150-160), providers, cache strategies, security/extractSequence, reliability loop.
|
|
164
165
|
- **Cache** → strategy registration is a MODULE SIDE EFFECT (src/index.ts:15-17); an entry point skipping that import silently falls back to NoOp. Resolved once per Agent at construction (Agent.ts:347).
|
package/README.md
CHANGED
|
@@ -932,6 +932,7 @@ The flowchart, recorders, and tests don't change between dev and prod.
|
|
|
932
932
|
- Resilience primitives — `withRetry`, `withFallback`, `withCircuitBreaker`, `.outputFallback`, `agent.resumeOnError`
|
|
933
933
|
- Context Integrity — deterministic checks at the seams where a run contradicts ITSELF: a tool parked but still on the wire, a tool offered after the results grounding it were evicted, an answer field that disagrees with the fact it claims to report (`.claims()`, requires `.outputSchema()`). Nothing is blocked or rewritten — each defect is one typed finding, and every run files a disposition ledger so "no findings" and "no check ran" stay different states. `integrityPosture: 'dev'` adds the liveness proofs (a start-of-run canary; `CheckerDeadError` instead of a green report from a checker that never ran). Read it back with `find_context_errors` over a recording — [Context Integrity](https://agentfootprint.dev/docs/monitor/context-integrity/)
|
|
934
934
|
- Findings ledger — `.findings()` adds one reserved optional argument, `_findings`, to every served tool schema: the model declares why it makes each call (`basis`: `direct` or `exploratory`) and, on its next call or as a top-level key of a JSON answer, the standing of each earlier result by its tool_result id (`fact` with the assertions it stands on, `open`, `ruled-out`, `noise`). Zero extra model calls. The argument is peeled off before the tool, the middleware or the validator sees the call; the rows land as one append-only `findingsLedger` on the run's state (`agent.findings()`), with a `conflict` row whenever two stood-on readings disagree. Nothing is inferred — a call that declares nothing files nothing — and nothing is served differently yet: the ledger is a record. An agent that never calls `.findings()` is byte-identical to one built before it existed
|
|
935
|
+
- Tool choice by classifier — `.toolChoice({ classifier, serve })` asks a calibrated classifier (`agentfootprint/classify`) at every model call which of the tools about to be served answers the current step, and files its ranking, its pick and its cost as a `pick` row beside the model's own call, then an `outcome` row after the reply (`firstAgrees`, any `miss`). The model's call is never overridden. `serve: { top: N }` narrows the served list to the classifier's top-N plus the doors (`read_skill`, `skip_step`, `present`, your `alwaysServe`), on the committed record the receipt hashes and `servedAt` rebuilds; a failed classifier serves the full set, a miss serves the full set on the next call. `npm run bench:tool-choice` measures agreement, misses and tools-slot bytes from the record. An agent without `.toolChoice()` is byte-identical
|
|
935
936
|
- 60+ typed observability events — `agent` · `composition` · `context` · `stream` · `tools` · `skill` · `memory` · `cache` · `cost` · `permission` · `eval` · `embedding` · `pause` · `error` · `fallback` · `resilience` · `reliability` · `risk`
|
|
936
937
|
|
|
937
938
|
**Debugging & compliance** (`agentfootprint/observe`)
|
|
@@ -252,7 +252,7 @@ const agent = Agent.create({ provider, model })
|
|
|
252
252
|
agent.on('agentfootprint.context.evaluated', (e) => console.log(e.payload.activeIds));
|
|
253
253
|
```
|
|
254
254
|
|
|
255
|
-
**
|
|
255
|
+
**118 typed events across 26 domains.** Two subscription shapes and no third:
|
|
256
256
|
`'*'` (every event) and `'agentfootprint.<domain>.*'` (one domain). **`'agentfootprint.*'`
|
|
257
257
|
is not a pattern** — TypeScript rejects it, and at runtime it would match nothing.
|
|
258
258
|
|
package/dist/core/Agent.js
CHANGED
|
@@ -287,6 +287,13 @@ class Agent extends RunnerBase_js_1.RunnerBase {
|
|
|
287
287
|
* armed agent with a window, where `stages/window.ts · buildWindowStage`
|
|
288
288
|
* spends it as the `'ledger-fact'` pin ceiling. */
|
|
289
289
|
findingsOptions;
|
|
290
|
+
/** Tool choice by classifier (9.105.0, `.toolChoice()`): the classifier,
|
|
291
|
+
* the serve dial and the app's own doors. Threaded to the tools slot (the
|
|
292
|
+
* pick and the narrowing), to call-llm (`toolChoice: true`, the outcome
|
|
293
|
+
* row) and to both chart builders (`hasToolChoice`, the mount args) on an
|
|
294
|
+
* armed agent only — an unarmed agent hands each stage exactly the deps it
|
|
295
|
+
* always did. */
|
|
296
|
+
toolChoiceOptions;
|
|
290
297
|
/** The opt-in tool-result ceiling in characters (9.11.0). Absent → results
|
|
291
298
|
* are never measured. See {@link AgentOptions.maxToolResultChars}. */
|
|
292
299
|
maxToolResultChars;
|
|
@@ -661,6 +668,8 @@ class Agent extends RunnerBase_js_1.RunnerBase {
|
|
|
661
668
|
this.toolArgValidation = opts.toolArgValidation;
|
|
662
669
|
if (opts.findings !== undefined)
|
|
663
670
|
this.findingsOptions = opts.findings;
|
|
671
|
+
if (opts.toolChoice !== undefined)
|
|
672
|
+
this.toolChoiceOptions = opts.toolChoice;
|
|
664
673
|
// The tool-result ceiling (9.11.0). Refused HERE, naming the value, rather
|
|
665
674
|
// than at the first tool call of the first run — a dial that cannot cap
|
|
666
675
|
// anything is a configuration mistake, not a runtime condition.
|
|
@@ -757,6 +766,16 @@ class Agent extends RunnerBase_js_1.RunnerBase {
|
|
|
757
766
|
}
|
|
758
767
|
this.keepLedgerFacts = resolveKeepLedgerFacts(this.findingsOptions.keepLedgerFacts ?? opts.keepLedgerFacts);
|
|
759
768
|
}
|
|
769
|
+
// Tool choice by classifier (9.105.0) needs the same per-call recomposition
|
|
770
|
+
// the ledger does — the pick is made where the served list is built, and
|
|
771
|
+
// a narrowed list committed on turn 1 would be served on every later call
|
|
772
|
+
// with no pick behind it. The same refusal, at the same place.
|
|
773
|
+
if (this.toolChoiceOptions !== undefined && this.reactMode === 'classic') {
|
|
774
|
+
throw new Error("Agent: .toolChoice() requires per-iteration slot recomposition — reactMode 'classic' " +
|
|
775
|
+
'caches the tools slot on turn 1, so the classifier would be asked once and a narrowed ' +
|
|
776
|
+
"list would be served on every later call. Use the default 'dynamic' mode (or " +
|
|
777
|
+
"'dynamic-grouped').");
|
|
778
|
+
}
|
|
760
779
|
// Refused at construction, never mid-run — a misspelled posture that was
|
|
761
780
|
// ignored would leave the liveness theorems switched off in an agent
|
|
762
781
|
// that believes they are on (the concurrency-mode precedent).
|
|
@@ -2585,6 +2604,17 @@ class Agent extends RunnerBase_js_1.RunnerBase {
|
|
|
2585
2604
|
getRunContext: getRunCtx,
|
|
2586
2605
|
}));
|
|
2587
2606
|
}
|
|
2607
|
+
// Same wiring for `agentfootprint.tool_choice.*` (9.105.0) — the three
|
|
2608
|
+
// events `recordToolChoice` files on scope. Attached only under
|
|
2609
|
+
// `.toolChoice()`, for the same reason.
|
|
2610
|
+
if (this.toolChoiceOptions !== undefined) {
|
|
2611
|
+
attachObserver(new EmitBridge_js_1.EmitBridge({
|
|
2612
|
+
id: 'agentfootprint.tool-choice-bridge',
|
|
2613
|
+
prefix: 'agentfootprint.tool_choice.',
|
|
2614
|
+
dispatcher,
|
|
2615
|
+
getRunContext: getRunCtx,
|
|
2616
|
+
}));
|
|
2617
|
+
}
|
|
2588
2618
|
for (const r of this.attachedRecorders) {
|
|
2589
2619
|
// A recorder's OWN `delivery` field is more specific than the
|
|
2590
2620
|
// agent-level default — footprintjs's options bag would override the
|
|
@@ -3549,6 +3579,19 @@ class Agent extends RunnerBase_js_1.RunnerBase {
|
|
|
3549
3579
|
// The findings ledger (9.101.0): the ONE decoration site is inside this
|
|
3550
3580
|
// slot; the gate rides in value-conditionally.
|
|
3551
3581
|
...(this.findingsOptions !== undefined && { findings: true }),
|
|
3582
|
+
// Tool choice by classifier (9.105.0): the pick and the narrowing live
|
|
3583
|
+
// inside this slot too, at the same site; value-conditional.
|
|
3584
|
+
...(this.toolChoiceOptions !== undefined && {
|
|
3585
|
+
toolChoice: {
|
|
3586
|
+
classifier: this.toolChoiceOptions.classifier,
|
|
3587
|
+
...(typeof this.toolChoiceOptions.serve === 'object' && {
|
|
3588
|
+
top: this.toolChoiceOptions.serve.top,
|
|
3589
|
+
}),
|
|
3590
|
+
...(this.toolChoiceOptions.alwaysServe !== undefined && {
|
|
3591
|
+
alwaysServe: this.toolChoiceOptions.alwaysServe,
|
|
3592
|
+
}),
|
|
3593
|
+
},
|
|
3594
|
+
}),
|
|
3552
3595
|
});
|
|
3553
3596
|
// callLLM extracted to ./agent/stages/callLLM.ts (v2.11.2). Same
|
|
3554
3597
|
// late-binding pattern as seed for toolSchemas (computed below).
|
|
@@ -3575,6 +3618,8 @@ class Agent extends RunnerBase_js_1.RunnerBase {
|
|
|
3575
3618
|
}),
|
|
3576
3619
|
}),
|
|
3577
3620
|
...(this.answerValidationConfig !== undefined && { suppressDraftTokens: true }),
|
|
3621
|
+
// Tool choice by classifier (9.105.0): the outcome row after the reply.
|
|
3622
|
+
...(this.toolChoiceOptions !== undefined && { toolChoice: true }),
|
|
3578
3623
|
// The receipt's salt (9.88.0) — read per call, like seed's own accessor.
|
|
3579
3624
|
getRunId: () => this.currentRunContext?.runId,
|
|
3580
3625
|
// …and its off switch. Value-conditional, so an agent on the default
|
|
@@ -3941,6 +3986,9 @@ class Agent extends RunnerBase_js_1.RunnerBase {
|
|
|
3941
3986
|
// grammar as the slot's `findings` and callLLM's `hasFindingsLedger`,
|
|
3942
3987
|
// so the three can never disagree about whether the ledger is armed.
|
|
3943
3988
|
...(this.findingsOptions !== undefined && { hasFindingsLedger: true }),
|
|
3989
|
+
// Tool choice by classifier (9.105.0): the mount args on the Tools
|
|
3990
|
+
// branch and the key across the sf-llm-call boundary, under the arm.
|
|
3991
|
+
...(this.toolChoiceOptions !== undefined && { hasToolChoice: true }),
|
|
3944
3992
|
// `.limitsTravelWithTheAnswer()` (this release) — value-conditional, the
|
|
3945
3993
|
// `resolvedModel` precedent: absent from the deps object entirely for an
|
|
3946
3994
|
// agent that did not ask, so both builders mount the final-branch stage
|