agentfootprint 9.104.0 → 9.106.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.
Files changed (156) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +132 -0
  3. package/CLAUDE.md +5 -2
  4. package/README.md +2 -0
  5. package/ai-instructions/claude-code/SKILL.md +1 -1
  6. package/dist/core/Agent.js +101 -0
  7. package/dist/core/Agent.js.map +1 -1
  8. package/dist/core/agent/AgentBuilder.js +196 -1
  9. package/dist/core/agent/AgentBuilder.js.map +1 -1
  10. package/dist/core/agent/buildAgentChart.js +13 -0
  11. package/dist/core/agent/buildAgentChart.js.map +1 -1
  12. package/dist/core/agent/buildDynamicAgentChart.js +35 -0
  13. package/dist/core/agent/buildDynamicAgentChart.js.map +1 -1
  14. package/dist/core/agent/stages/callLLM.js +79 -9
  15. package/dist/core/agent/stages/callLLM.js.map +1 -1
  16. package/dist/core/agent/stages/seed.js +9 -0
  17. package/dist/core/agent/stages/seed.js.map +1 -1
  18. package/dist/core/agent/toolChoice/compose.js +92 -0
  19. package/dist/core/agent/toolChoice/compose.js.map +1 -0
  20. package/dist/core/agent/toolChoice/pick.js +165 -0
  21. package/dist/core/agent/toolChoice/pick.js.map +1 -0
  22. package/dist/core/agent/toolChoice/record.js +108 -0
  23. package/dist/core/agent/toolChoice/record.js.map +1 -0
  24. package/dist/core/agent/toolChoice/types.js +46 -0
  25. package/dist/core/agent/toolChoice/types.js.map +1 -0
  26. package/dist/core/slots/buildToolsSlot.js +200 -120
  27. package/dist/core/slots/buildToolsSlot.js.map +1 -1
  28. package/dist/doors/ontology.js +52 -0
  29. package/dist/doors/ontology.js.map +1 -0
  30. package/dist/esm/core/Agent.d.ts +15 -0
  31. package/dist/esm/core/Agent.js +101 -0
  32. package/dist/esm/core/Agent.js.map +1 -1
  33. package/dist/esm/core/agent/AgentBuilder.d.ts +106 -0
  34. package/dist/esm/core/agent/AgentBuilder.js +196 -1
  35. package/dist/esm/core/agent/AgentBuilder.js.map +1 -1
  36. package/dist/esm/core/agent/buildAgentChart.d.ts +22 -0
  37. package/dist/esm/core/agent/buildAgentChart.js +13 -0
  38. package/dist/esm/core/agent/buildAgentChart.js.map +1 -1
  39. package/dist/esm/core/agent/buildDynamicAgentChart.js +35 -0
  40. package/dist/esm/core/agent/buildDynamicAgentChart.js.map +1 -1
  41. package/dist/esm/core/agent/stages/callLLM.d.ts +23 -0
  42. package/dist/esm/core/agent/stages/callLLM.js +56 -9
  43. package/dist/esm/core/agent/stages/callLLM.js.map +1 -1
  44. package/dist/esm/core/agent/stages/seed.d.ts +11 -0
  45. package/dist/esm/core/agent/stages/seed.js +9 -0
  46. package/dist/esm/core/agent/stages/seed.js.map +1 -1
  47. package/dist/esm/core/agent/toolChoice/compose.d.ts +53 -0
  48. package/dist/esm/core/agent/toolChoice/compose.js +88 -0
  49. package/dist/esm/core/agent/toolChoice/compose.js.map +1 -0
  50. package/dist/esm/core/agent/toolChoice/pick.d.ts +104 -0
  51. package/dist/esm/core/agent/toolChoice/pick.js +158 -0
  52. package/dist/esm/core/agent/toolChoice/pick.js.map +1 -0
  53. package/dist/esm/core/agent/toolChoice/record.d.ts +49 -0
  54. package/dist/esm/core/agent/toolChoice/record.js +101 -0
  55. package/dist/esm/core/agent/toolChoice/record.js.map +1 -0
  56. package/dist/esm/core/agent/toolChoice/types.d.ts +125 -0
  57. package/dist/esm/core/agent/toolChoice/types.js +43 -0
  58. package/dist/esm/core/agent/toolChoice/types.js.map +1 -0
  59. package/dist/esm/core/agent/types.d.ts +100 -0
  60. package/dist/esm/core/slots/buildToolsSlot.d.ts +21 -0
  61. package/dist/esm/core/slots/buildToolsSlot.js +177 -120
  62. package/dist/esm/core/slots/buildToolsSlot.js.map +1 -1
  63. package/dist/esm/doors/ontology.d.ts +35 -0
  64. package/dist/esm/doors/ontology.js +36 -0
  65. package/dist/esm/doors/ontology.js.map +1 -0
  66. package/dist/esm/events/dispatcher.d.ts +1 -1
  67. package/dist/esm/events/dispatcher.js.map +1 -1
  68. package/dist/esm/events/payloads.d.ts +65 -0
  69. package/dist/esm/events/registry.d.ts +13 -1
  70. package/dist/esm/events/registry.js +12 -0
  71. package/dist/esm/events/registry.js.map +1 -1
  72. package/dist/esm/events/types.d.ts +1 -1
  73. package/dist/esm/index.d.ts +1 -0
  74. package/dist/esm/index.js +7 -0
  75. package/dist/esm/index.js.map +1 -1
  76. package/dist/esm/lib/time-travel/servedView.js +36 -4
  77. package/dist/esm/lib/time-travel/servedView.js.map +1 -1
  78. package/dist/esm/ontology/define.d.ts +60 -0
  79. package/dist/esm/ontology/define.js +268 -0
  80. package/dist/esm/ontology/define.js.map +1 -0
  81. package/dist/esm/ontology/index.d.ts +14 -0
  82. package/dist/esm/ontology/index.js +14 -0
  83. package/dist/esm/ontology/index.js.map +1 -0
  84. package/dist/esm/ontology/instruction.d.ts +23 -0
  85. package/dist/esm/ontology/instruction.js +34 -0
  86. package/dist/esm/ontology/instruction.js.map +1 -0
  87. package/dist/esm/ontology/serve.d.ts +63 -0
  88. package/dist/esm/ontology/serve.js +147 -0
  89. package/dist/esm/ontology/serve.js.map +1 -0
  90. package/dist/esm/ontology/types.d.ts +83 -0
  91. package/dist/esm/ontology/types.js +20 -0
  92. package/dist/esm/ontology/types.js.map +1 -0
  93. package/dist/events/dispatcher.js.map +1 -1
  94. package/dist/events/registry.js +12 -0
  95. package/dist/events/registry.js.map +1 -1
  96. package/dist/index.js +12 -3
  97. package/dist/index.js.map +1 -1
  98. package/dist/lib/time-travel/servedView.js +36 -4
  99. package/dist/lib/time-travel/servedView.js.map +1 -1
  100. package/dist/ontology/define.js +273 -0
  101. package/dist/ontology/define.js.map +1 -0
  102. package/dist/ontology/index.js +24 -0
  103. package/dist/ontology/index.js.map +1 -0
  104. package/dist/ontology/instruction.js +37 -0
  105. package/dist/ontology/instruction.js.map +1 -0
  106. package/dist/ontology/serve.js +151 -0
  107. package/dist/ontology/serve.js.map +1 -0
  108. package/dist/ontology/types.js +21 -0
  109. package/dist/ontology/types.js.map +1 -0
  110. package/dist/types/core/Agent.d.ts +15 -0
  111. package/dist/types/core/Agent.d.ts.map +1 -1
  112. package/dist/types/core/agent/AgentBuilder.d.ts +106 -0
  113. package/dist/types/core/agent/AgentBuilder.d.ts.map +1 -1
  114. package/dist/types/core/agent/buildAgentChart.d.ts +22 -0
  115. package/dist/types/core/agent/buildAgentChart.d.ts.map +1 -1
  116. package/dist/types/core/agent/buildDynamicAgentChart.d.ts.map +1 -1
  117. package/dist/types/core/agent/stages/callLLM.d.ts +23 -0
  118. package/dist/types/core/agent/stages/callLLM.d.ts.map +1 -1
  119. package/dist/types/core/agent/stages/seed.d.ts +11 -0
  120. package/dist/types/core/agent/stages/seed.d.ts.map +1 -1
  121. package/dist/types/core/agent/toolChoice/compose.d.ts +54 -0
  122. package/dist/types/core/agent/toolChoice/compose.d.ts.map +1 -0
  123. package/dist/types/core/agent/toolChoice/pick.d.ts +105 -0
  124. package/dist/types/core/agent/toolChoice/pick.d.ts.map +1 -0
  125. package/dist/types/core/agent/toolChoice/record.d.ts +50 -0
  126. package/dist/types/core/agent/toolChoice/record.d.ts.map +1 -0
  127. package/dist/types/core/agent/toolChoice/types.d.ts +126 -0
  128. package/dist/types/core/agent/toolChoice/types.d.ts.map +1 -0
  129. package/dist/types/core/agent/types.d.ts +100 -0
  130. package/dist/types/core/agent/types.d.ts.map +1 -1
  131. package/dist/types/core/slots/buildToolsSlot.d.ts +21 -0
  132. package/dist/types/core/slots/buildToolsSlot.d.ts.map +1 -1
  133. package/dist/types/doors/ontology.d.ts +36 -0
  134. package/dist/types/doors/ontology.d.ts.map +1 -0
  135. package/dist/types/events/dispatcher.d.ts +1 -1
  136. package/dist/types/events/dispatcher.d.ts.map +1 -1
  137. package/dist/types/events/payloads.d.ts +65 -0
  138. package/dist/types/events/payloads.d.ts.map +1 -1
  139. package/dist/types/events/registry.d.ts +13 -1
  140. package/dist/types/events/registry.d.ts.map +1 -1
  141. package/dist/types/events/types.d.ts +1 -1
  142. package/dist/types/events/types.d.ts.map +1 -1
  143. package/dist/types/index.d.ts +1 -0
  144. package/dist/types/index.d.ts.map +1 -1
  145. package/dist/types/lib/time-travel/servedView.d.ts.map +1 -1
  146. package/dist/types/ontology/define.d.ts +61 -0
  147. package/dist/types/ontology/define.d.ts.map +1 -0
  148. package/dist/types/ontology/index.d.ts +15 -0
  149. package/dist/types/ontology/index.d.ts.map +1 -0
  150. package/dist/types/ontology/instruction.d.ts +24 -0
  151. package/dist/types/ontology/instruction.d.ts.map +1 -0
  152. package/dist/types/ontology/serve.d.ts +64 -0
  153. package/dist/types/ontology/serve.d.ts.map +1 -0
  154. package/dist/types/ontology/types.d.ts +84 -0
  155. package/dist/types/ontology/types.d.ts.map +1 -0
  156. package/package.json +15 -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 — 115 typed events across 25 domains
547
+ ### Observability — 119 typed events across 27 domains
548
548
 
549
549
  ```typescript
550
550
  agent.on('agentfootprint.context.injected', (e) =>
package/CHANGELOG.md CHANGED
@@ -5,6 +5,138 @@ 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.106.0] - 2026-09-17
9
+
10
+ ### Added — the ontology: a declared map of what exists and where, never a way to fetch it
11
+
12
+ - **`agentfootprint/ontology`** — a new door (the seventeenth): `defineOntology(spec)`
13
+ takes an `OntologySpec` — `nodes` (an `OntologyNode` per term: `meaning`,
14
+ `unit`, `aliases`, and the `sources` that hold it, each an
15
+ `OntologyNodeSource` with the registered tools that read it from there
16
+ (`via`) and the author's `coverage` sentence), `sources` (an
17
+ `OntologySource` per place data is held: `meaning`, `coverage`,
18
+ `configured` — a boolean the author wrote, or absent, which means unknown
19
+ and is never assumed) and `edges` (an `OntologyEdge`: `from`, `to`, the
20
+ author's `relation` word, `meaning`) — and returns an `Ontology`:
21
+ validated, detached, deep-frozen, with a `hash`. Every fault is refused
22
+ by name (an id that is not identifier-safe, an edge to a node nobody
23
+ declared, a node held by a source nobody declared, an empty or over-long
24
+ text, a repeated alias / `via` / edge, a count past `ONTOLOGY_LIMITS`). A
25
+ node with no source is legal: known, nowhere collected here.
26
+ `ontologyHash` fingerprints the five declared fields through the
27
+ receipt's own `stableJson` (key-order independent; edge order is part of
28
+ the identity).
29
+ - **`.ontology(map)`** (`AgentOptions.ontology`, `AgentBuilder.ontology`;
30
+ once per agent, the option form goes through the same door). At
31
+ `.build()` every `via` tool name is checked against the agent's tool
32
+ registry — a name no registry carries is refused, naming the ontology,
33
+ the tool, the node and the source. At run `seed` writes the whole map
34
+ ONCE as the run constant `AgentState.ontology` (`OntologyRecord`: `id`,
35
+ `version`, `hash`, `spec`), so the wire, the rebuild and a lens need
36
+ nothing but the record.
37
+ - **The served piece.** Every model call is served ONE request-only system
38
+ piece composed by the pure `ontologyPiece` (`OntologyPiece`, `source:
39
+ 'ontology'` — a new `ContextSource`): a constant header quoting the
40
+ context contract's `domainDefinitions`, `limitations` and `evidenceRefs`
41
+ meanings, then `nodes:`, `sources:`, `held by:`, `relations:` and `known,
42
+ not held here:` — every line the declaration's, nodes and sources sorted
43
+ by id, edges in declaration order, `ONTOLOGY_PIECE_LIMITS` lines per
44
+ section with the overflow stated, an empty section omitted, no per-call
45
+ byte (an unchanged map reuses the cached system prefix). Joined after the
46
+ recovery piece and before the findings piece — injections → recovery →
47
+ ontology → findings, fixed — never as an injection; hashed on the receipt
48
+ and rebuilt byte-equal by `servedAt` in both chart shapes (the grouped
49
+ chart crosses the key into `sf-llm-call` under the arm).
50
+ - **The ask.** `ONTOLOGY_INSTRUCTION` (`ONTOLOGY_INSTRUCTION_ID`), an
51
+ always-on instruction the `outputSchema()` way: say which declared
52
+ source, tool or neighbouring node the map names for a need the results
53
+ did not meet — as a proposal, never as a claim that data exists there;
54
+ never invent a value from the map; report a node listed as known but not
55
+ held here as declared. Judged by `unprovable` in the model-facing
56
+ inventory, beside the piece.
57
+ - **`agentfootprint.ontology.served`** `{ iteration, id, version, hash,
58
+ nodes, sources, edges }` — once per call that served the piece;
59
+ identities and numbers only. A new domain, `ontology`, with its
60
+ wildcard: 26 → 27 domains, 118 → 119 events.
61
+ - **The law.** The library never decides that a node "has no data": absence
62
+ is the model's or the tool's to report; the map only lets the model SAY
63
+ where a need would be met. Nothing is executed or fetched through it;
64
+ nothing is inferred from it. An agent without `.ontology()` is
65
+ byte-identical to one built before the map existed — no key, no piece,
66
+ no instruction, no event (the 19 byte-identity references untouched; one
67
+ new reference `agent-ontology`). What the map is measured on — tool calls
68
+ before the honest answer on questions whose data is not collected, and
69
+ whether that answer names the source — is a bench on the first host after
70
+ the release, not here (`docs/design/2026-09-ontology.md`).
71
+
72
+ ## [9.105.0] - 2026-09-17
73
+
74
+ ### Added — tool choice by classifier: a second reading beside the model's call, and a narrowing dial
75
+
76
+ - **`.toolChoice({ classifier, serve, alwaysServe })`** (`AgentOptions.toolChoice`,
77
+ `AgentBuilder.toolChoice`). At every model call the tools slot asks the
78
+ classifier ONE `choice` question — id `TOOL_CHOICE_QUESTION` (`'tool'`),
79
+ criteria the tools about to be served by name with their own descriptions
80
+ (the merged wire minus the always-served doors), state the user's message
81
+ plus the active skill id — and files a `ToolChoiceRow` under
82
+ `AgentState.toolChoices` BEFORE the call: `offered`, `ranked` (the
83
+ provider's distribution as sent, highest first, an unscored tool absent —
84
+ never padded, never renormalised), `chosen` (the provider's own pick,
85
+ absent when it named nothing offered), `confidence`, `usage`, `latencyMs`,
86
+ `served` (the names the slot committed) and `narrowed`. After the reply
87
+ `callLLM` files a `ToolChoiceOutcomeRow`: `called` (the model's tool calls
88
+ in order, empty on an answer), `firstAgrees` (`chosen === called[0]`,
89
+ absent when either is absent) and `miss` (the names called outside a
90
+ narrowed served list). A failed classifier call is a `ToolChoiceErrorRow`
91
+ (status, message, latency) and the full set is served — fail open, never
92
+ fail narrow. The model's call is the emission; the pick is a second
93
+ reading marked `source: 'classifier'`, never substituted, never merged.
94
+ - **Advisory by default** (`serve: 'all'`): the wire is byte for byte the
95
+ unarmed agent's — every request equal, every receipt equal; the record
96
+ gains the rows and nothing else.
97
+ - **The narrowing dial** (`serve: { top: N }`): the slot commits the
98
+ classifier's top-N plus the doors — `read_skill`, `list_skills`,
99
+ `skip_step`, `present` (`ALWAYS_SERVED_TOOLS`) and the app's `alwaysServe`
100
+ — in the merged wire's order, at the ONE decoration site, so
101
+ `dynamicToolSchemas`, the receipt's `tools.schemaHashes` and
102
+ `servedAt(k).tools.schemas` are the narrowed list by construction (no new
103
+ `SERVED_GAPS` kind; `toolsInjections` follows the served set). The full
104
+ wire is served with the reason on the row (`narrowedSkipped`:
105
+ `NarrowSkipReason`) when the classifier failed or scored fewer than N
106
+ (`unavailable`), fewer than N + 1 candidates were offered (`too-few`), the
107
+ previous call's outcome carried a miss (`after-miss`) or the call is the
108
+ out-of-budget wrap-up (`wrap-up`). A miss — the model naming a
109
+ narrowed-away tool — is recorded on the outcome row and answered by the
110
+ dispatcher's off-wire path exactly as before (`tools.answered_off_wire`);
111
+ the next call serves the full wire. `reactMode: 'classic'` is refused at
112
+ build (the `.findings()` precedent).
113
+ - **Three events**, `agentfootprint.tool_choice.picked` (`chosen`,
114
+ `confidence`, `offered` and `served` as counts, `narrowed`,
115
+ `narrowedSkipped`, `latencyMs`, tokens), `agentfootprint.tool_choice.outcome`
116
+ (`called`, `firstAgrees`, `missed`) and `agentfootprint.tool_choice.failed`
117
+ (`status`, `latencyMs`) — identities, enums, numbers and a boolean only
118
+ (payloads `ToolChoicePickedPayload`, `ToolChoiceOutcomePayload`,
119
+ `ToolChoiceFailedPayload`); 115 → 118 typed events, 25 → 26 domains.
120
+ - **Root exports**: `ToolChoiceRow`, `ToolChoiceErrorRow`,
121
+ `ToolChoiceOutcomeRow`, `ToolChoiceEntry`, `ToolChoiceLedger`,
122
+ `ToolChoiceScore`, `NarrowSkipReason`, `TOOL_CHOICE_QUESTION`,
123
+ `ALWAYS_SERVED_TOOLS`. The asker (`src/core/agent/toolChoice/pick.ts`) is
124
+ loaded through `import()` by the slot and is deliberately not exported.
125
+ - **`npm run bench:tool-choice`** — the mock provider, one skill of eight
126
+ tools, six scripted steps, a scripted classifier ranking the right tool
127
+ first / second / a wrong pair, under `advisory` and `top-2`: `first-agrees`,
128
+ `misses`, `extra-calls`, `tools-slot-bytes` (from the receipt's
129
+ `requestMeasurement`), `pick-tokens`, `pick-latency-ms`; the cost line
130
+ first; exits non-zero if the unarmed twin's tools-slot bytes move or an
131
+ advisory row's differ from the twin's. On 2026-09-17: 1745 bytes unarmed
132
+ and on every advisory row, 743 under `top-2` with a right ranking, 1172
133
+ under a wrong pair (three misses, the full wire on the call after each,
134
+ zero extra calls).
135
+ - Byte identity: the 18 unarmed references untouched; one new reference
136
+ `agent-tool-choice` (`test/core/tools/byte-identity.test.ts`), generated
137
+ alone. Design: `docs/design/2026-09-scored-choice.md` § Step 4; README:
138
+ `src/core/agent/toolChoice/README.md`.
139
+
8
140
  ## [9.104.0] - 2026-09-17
9
141
 
10
142
  ### Added — a calibrated judge beside the model's own standings
package/CLAUDE.md CHANGED
@@ -68,6 +68,8 @@ 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 |
72
+ | a MAP of the domain the model can reason over when a result holds nothing — what each term IS (meaning, unit), how terms RELATE, which SOURCE holds a term (with the author's coverage sentence) and which registered TOOL reads it from there; declared once, validated against the tool registry at build, on the record as one run constant, served as one request-only system piece per call, never a fetch path and never inferred from | `defineOntology` + `.ontology(map)` (`AgentOptions.ontology`) + `AgentState.ontology` + `ontologyPiece` / `ONTOLOGY_INSTRUCTION` (`agentfootprint/ontology`) + `agentfootprint.ontology.served` | `src/ontology/` | 9.106.0 |
71
73
 
72
74
  **One law before you add a mapping.** Any function turning caller data into a KEY,
73
75
  a namespace, a filename or an index entry must be injective, and its collision
@@ -84,7 +86,7 @@ does not exist, so the table can go out of date by OMISSION but can never lie
84
86
  about what it names.
85
87
 
86
88
  ## Module map
87
- Entry points — SIXTEEN doors, and `package.json` `exports` is exhaustive, so a path not on this list does not resolve at all: `.` core API · `/providers` everything you plug a backend into (mock/anthropic/openai/bedrock/browser*, embedders, staticTools/gatedTools/skillScopedTools/mcpClient, thinking handlers, code runners) · `/memory` (defineMemory, MEMORY_TYPES, InMemoryStore, mockEmbedder, redis/agentcore/bedrockAgentMemory stores) · `/rag` index-time loaders/splitters/indexCorpus · `/cache` prompt caching (+ registerCacheStrategy; side-effectful, hence its own door) · `/observe` the whole watching story (recorders, recordRun, strategies + attach*, vendor sinks, run-autopsy finders, toSSE, status, locales) · `/events` typed event system · `/context` the injection engine (defineInjection/Skill/Fact/Instruction/Steering, decideSkill) · `/resilience` provider decorators + the reliability RULES · `/hosting` nodeHost/httpHost/standingAgent + session stores · `/security` permissions + tool credentials (it absorbed the old identity door) · `/reliability` the retained alias, kept ONLY because it is the sole home of the gate's `CircuitOpenError` · `/skill-graph` the framework-neutral routing layer · `/recipes` the declared unit of agent CONFIGURATION (defineAgentRecipe → `.recipe()`; authoring-time vocabulary, so it stays off the main barrel) · `/maps` the mount kernel's vocabulary (9.58.0: Claim<T> + the engagement lease machine + its renewal feed — pure data and pure functions, no run entry point; mounted via `.maps()`) · `/classify` the calibrated-classifier port (9.104.0: `Classifier`, `typesafe`, `mockClassifier`, `ClassifierError` — a backend that SCORES declared candidates; spent by `.findings({ judge })` and by `classifierScorer` on `/skill-graph`). 9.0.0 removed sixteen older paths (`/llm-providers`, `/injection-engine`, `/tool-providers`, `/strategies`, `/observability-providers`, `/identity`, `/stream`, `/thinking`, `/status`, `/locales`, `/debug`, `/debug/finders`, `/memory-providers`, `/embedders`, `/hosting-providers`, `/observability/contextError/finders`) — it removed PATHS, not code; each name still ships through the door that absorbed it. This list is pinned against `package.json` by test/api-conformance/documented-doors.test.ts, and the removed sixteen by subpath-exports.test.ts. **Main barrel does NOT export** mock/browser*/defineMemory/defineSkill/skillGraph/mcpClient/InMemoryStore — import from the doors above.
89
+ Entry points — SEVENTEEN doors, and `package.json` `exports` is exhaustive, so a path not on this list does not resolve at all: `.` core API · `/providers` everything you plug a backend into (mock/anthropic/openai/bedrock/browser*, embedders, staticTools/gatedTools/skillScopedTools/mcpClient, thinking handlers, code runners) · `/memory` (defineMemory, MEMORY_TYPES, InMemoryStore, mockEmbedder, redis/agentcore/bedrockAgentMemory stores) · `/rag` index-time loaders/splitters/indexCorpus · `/cache` prompt caching (+ registerCacheStrategy; side-effectful, hence its own door) · `/observe` the whole watching story (recorders, recordRun, strategies + attach*, vendor sinks, run-autopsy finders, toSSE, status, locales) · `/events` typed event system · `/context` the injection engine (defineInjection/Skill/Fact/Instruction/Steering, decideSkill) · `/resilience` provider decorators + the reliability RULES · `/hosting` nodeHost/httpHost/standingAgent + session stores · `/security` permissions + tool credentials (it absorbed the old identity door) · `/reliability` the retained alias, kept ONLY because it is the sole home of the gate's `CircuitOpenError` · `/skill-graph` the framework-neutral routing layer · `/recipes` the declared unit of agent CONFIGURATION (defineAgentRecipe → `.recipe()`; authoring-time vocabulary, so it stays off the main barrel) · `/maps` the mount kernel's vocabulary (9.58.0: Claim<T> + the engagement lease machine + its renewal feed — pure data and pure functions, no run entry point; mounted via `.maps()`) · `/classify` the calibrated-classifier port (9.104.0: `Classifier`, `typesafe`, `mockClassifier`, `ClassifierError` — a backend that SCORES declared candidates; spent by `.findings({ judge })` and by `classifierScorer` on `/skill-graph`) · `/ontology` the declared map (9.106.0: `defineOntology`, `ontologyHash`, `ontologyPiece`, `ONTOLOGY_INSTRUCTION`, the shapes — what each term IS, how terms RELATE, which SOURCE holds a term and which registered TOOL reads it; pure data and pure functions, no run entry point, no fetch path; mounted via `.ontology()`, served as one request-only system piece per call). 9.0.0 removed sixteen older paths (`/llm-providers`, `/injection-engine`, `/tool-providers`, `/strategies`, `/observability-providers`, `/identity`, `/stream`, `/thinking`, `/status`, `/locales`, `/debug`, `/debug/finders`, `/memory-providers`, `/embedders`, `/hosting-providers`, `/observability/contextError/finders`) — it removed PATHS, not code; each name still ships through the door that absorbed it. This list is pinned against `package.json` by test/api-conformance/documented-doors.test.ts, and the removed sixteen by subpath-exports.test.ts. **Main barrel does NOT export** mock/browser*/defineMemory/defineSkill/skillGraph/mcpClient/InMemoryStore — import from the doors above.
88
90
 
89
91
  | src/ | one job |
90
92
  |---|---|
@@ -100,6 +102,7 @@ Entry points — SIXTEEN doors, and `package.json` `exports` is exhaustive, so a
100
102
  | recorders/core/ | bridges footprintjs events → typed EventDispatcher (ContextRecorder, EmitBridge, typedEmit) — auto-attached by Agent.createExecutor; most factories also exported via `/observe` for manual wiring (EmitBridge itself stays internal) |
101
103
  | recorders/observability/ | consumer recorders over the typed stream (RunStepRecorder, FlowchartRecorder, Status, Trace replay) + `recordRun` — THE producer of a recording `{snapshot, events, structure}` (the shape lens's `observeRecording` consumes; `structure` = `getSpec().buildTimeStructure`, which no snapshot carries). Anything that saves a run goes through it |
102
104
  | rag/ | (8.10.0, door `/rag`) index-TIME: `DocumentLoader` adapters (text/markdown/html zero-dep, pdf via lazy `unpdf`) + `Splitter` factories + `indexCorpus` — a REAL footprintjs chart whose commit log IS the indexing report. `defineRAG` deliberately stays on the MAIN barrel (run-time wiring); this door is the half that runs once, before any agent exists |
105
+ | ontology/ | (9.106.0) the declared map — `defineOntology` (validate, detach, freeze, `ontologyHash` through the receipt's `stableJson`) + `serve.ts · ontologyPiece` (the ONE composer, called by `callLLM` and by `servedView.viewOf`; every line quotes the declaration under a constant header that quotes the context contract's three meanings) + `instruction.ts` (the always-on ask). Behind the `/ontology` door; `Agent.buildChart` checks every `via` tool name against `registryByName` at build; `seed` writes `AgentState.ontology` once; the grouped chart crosses the key into `sf-llm-call` under `hasOntology` |
103
106
  | lib/ | first-party sub-libraries: injection-engine/, context-bisect/ (localizeContextBug, toBacktrackTrace + sliceToBacktrackTrace — the atui board serializers), influence-core/, trace-toolpack/ (selfExplain; 6 tools incl. variable-first `backtrack(variable, element?)`), context-ledger/ (which pieces EARNED their tokens — post-run offers/uses/outcomes bookkeeping + demote-never-starve gates `ledgerToolGate`/`ledgerEntryScorer`/`ledgerGated`; grouped-mode folds sf-llm-call inner logs, unmeterable runs → undefined; /observe), mcp/, rag/, tool-lint/ |
104
107
  | memory/ | store/ (MemoryStore port) + pipeline presets + stages + beats/facts + causal/ (dev-only, TOP_K+search()-only) + wire/mountMemoryPipeline + retrieval/ (8.8.0: the `RetrievalStrategy` seam + `RetrievalEvidence`, the record a retrieval leaves — `topK()` is what every earlier release did unnamed) |
105
108
  | maps/ | (9.58.0, door `/maps`) the mount kernel: claim/ (`Claim<T>` honesty primitive) + engagement/ (lease machine `advanceEngagement` + renewal feed `renewalEvidenceOf` + vocabulary). Pure data/functions; the ONE framework hook is `ctx.parkedIds` in the evaluator (the `leaseActiveIds` mirror), fed by the Evaluate stage; state rides `AgentState.mapEngagement` as a TOP-LEVEL array (the StepPointerCarrier law) |
@@ -158,7 +161,7 @@ Traps: `src/observability/` holds the finder IMPLEMENTATIONS (canonical home; `d
158
161
  - **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
162
  - **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
163
  - **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/** → 115 typed events across 25 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).
164
+ - **events/** → 119 typed events across 27 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
165
  - **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
166
  - **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
167
  - **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,8 @@ 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
936
+ - The ontology — `.ontology(map)` mounts a declared map of the domain (`defineOntology` on `agentfootprint/ontology`): what each term IS (meaning, unit), how terms RELATE, which SOURCE holds a term with the author's coverage sentence, and which registered TOOL reads it from there. A map, not a door — it provides no way to get data, nothing is fetched through it and the library infers nothing from it; it is on the record once as `AgentState.ontology`, served as one request-only system piece per call (every line the declaration's; a node nobody holds is `known, not held here`), hashed on the receipt and rebuilt by `servedAt`, and the model is asked to name the source or neighbouring node a need points at — as a proposal. `via` tool names are checked against the registry at build. An agent without `.ontology()` is byte-identical
935
937
  - 60+ typed observability events — `agent` · `composition` · `context` · `stream` · `tools` · `skill` · `memory` · `cache` · `cost` · `permission` · `eval` · `embedding` · `pause` · `error` · `fallback` · `resilience` · `reliability` · `risk`
936
938
 
937
939
  **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
- **115 typed events across 25 domains.** Two subscription shapes and no third:
255
+ **119 typed events across 27 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
 
@@ -287,6 +287,21 @@ 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;
297
+ /** The declared ontology (9.106.0, `.ontology()`): the frozen map. Threaded
298
+ * to seed (the whole map, written once as the run constant `ontology`),
299
+ * to call-llm (`ontology: true`, the gate on every read of that key) and
300
+ * to both chart builders (`hasOntology`, the grouped boundary's crossing)
301
+ * on an armed agent only — an unarmed agent hands each stage exactly the
302
+ * deps it always did. The tool names its `via` edges name are checked
303
+ * against the registry in `buildChart`, beside `buildToolRegistry`. */
304
+ ontology;
290
305
  /** The opt-in tool-result ceiling in characters (9.11.0). Absent → results
291
306
  * are never measured. See {@link AgentOptions.maxToolResultChars}. */
292
307
  maxToolResultChars;
@@ -661,6 +676,10 @@ class Agent extends RunnerBase_js_1.RunnerBase {
661
676
  this.toolArgValidation = opts.toolArgValidation;
662
677
  if (opts.findings !== undefined)
663
678
  this.findingsOptions = opts.findings;
679
+ if (opts.toolChoice !== undefined)
680
+ this.toolChoiceOptions = opts.toolChoice;
681
+ if (opts.ontology !== undefined)
682
+ this.ontology = opts.ontology;
664
683
  // The tool-result ceiling (9.11.0). Refused HERE, naming the value, rather
665
684
  // than at the first tool call of the first run — a dial that cannot cap
666
685
  // anything is a configuration mistake, not a runtime condition.
@@ -757,6 +776,16 @@ class Agent extends RunnerBase_js_1.RunnerBase {
757
776
  }
758
777
  this.keepLedgerFacts = resolveKeepLedgerFacts(this.findingsOptions.keepLedgerFacts ?? opts.keepLedgerFacts);
759
778
  }
779
+ // Tool choice by classifier (9.105.0) needs the same per-call recomposition
780
+ // the ledger does — the pick is made where the served list is built, and
781
+ // a narrowed list committed on turn 1 would be served on every later call
782
+ // with no pick behind it. The same refusal, at the same place.
783
+ if (this.toolChoiceOptions !== undefined && this.reactMode === 'classic') {
784
+ throw new Error("Agent: .toolChoice() requires per-iteration slot recomposition — reactMode 'classic' " +
785
+ 'caches the tools slot on turn 1, so the classifier would be asked once and a narrowed ' +
786
+ "list would be served on every later call. Use the default 'dynamic' mode (or " +
787
+ "'dynamic-grouped').");
788
+ }
760
789
  // Refused at construction, never mid-run — a misspelled posture that was
761
790
  // ignored would leave the liveness theorems switched off in an agent
762
791
  // that believes they are on (the concurrency-mode precedent).
@@ -2585,6 +2614,28 @@ class Agent extends RunnerBase_js_1.RunnerBase {
2585
2614
  getRunContext: getRunCtx,
2586
2615
  }));
2587
2616
  }
2617
+ // Same wiring for `agentfootprint.tool_choice.*` (9.105.0) — the three
2618
+ // events `recordToolChoice` files on scope. Attached only under
2619
+ // `.toolChoice()`, for the same reason.
2620
+ if (this.toolChoiceOptions !== undefined) {
2621
+ attachObserver(new EmitBridge_js_1.EmitBridge({
2622
+ id: 'agentfootprint.tool-choice-bridge',
2623
+ prefix: 'agentfootprint.tool_choice.',
2624
+ dispatcher,
2625
+ getRunContext: getRunCtx,
2626
+ }));
2627
+ }
2628
+ // Same wiring for `agentfootprint.ontology.*` (9.106.0) — the one event
2629
+ // `callLLM` emits when the declared map is served. Attached only under
2630
+ // `.ontology()`, for the same reason.
2631
+ if (this.ontology !== undefined) {
2632
+ attachObserver(new EmitBridge_js_1.EmitBridge({
2633
+ id: 'agentfootprint.ontology-bridge',
2634
+ prefix: 'agentfootprint.ontology.',
2635
+ dispatcher,
2636
+ getRunContext: getRunCtx,
2637
+ }));
2638
+ }
2588
2639
  for (const r of this.attachedRecorders) {
2589
2640
  // A recorder's OWN `delivery` field is more specific than the
2590
2641
  // agent-level default — footprintjs's options bag would override the
@@ -3221,6 +3272,11 @@ class Agent extends RunnerBase_js_1.RunnerBase {
3221
3272
  return l;
3222
3273
  },
3223
3274
  }),
3275
+ // The declared ontology (9.106.0): the whole frozen map, which seed
3276
+ // writes ONCE as the run constant `ontology` — the `findingsServe`
3277
+ // precedent, a build-time fact the rebuild reads from the RECORD. An
3278
+ // unarmed agent hands seed exactly the deps object it always did.
3279
+ ...(this.ontology !== undefined && { ontology: this.ontology }),
3224
3280
  // The conversation's inherited skill cursor (SG-C). Consumed (cleared)
3225
3281
  // on every run; HONORED only when the mounted graph declared
3226
3282
  // `continuity: 'conversation'` — the same one-option-one-behavior gate
@@ -3285,6 +3341,26 @@ class Agent extends RunnerBase_js_1.RunnerBase {
3285
3341
  // armed may a registry tool's own `_findings` be refused.
3286
3342
  ...(this.findingsOptions !== undefined && { findings: true }),
3287
3343
  });
3344
+ // The declared ontology's tool edges (9.106.0): every name a node's `via`
3345
+ // names must be a tool this registry can dispatch — the static registry,
3346
+ // the framework's doors and every skill's tools, `registryByName` being
3347
+ // the ONE map that holds all of them. Refused at BUILD, naming the
3348
+ // ontology and the tool: a map that points a need at a tool nobody can
3349
+ // call is a map that lies, and only the operator can fix it. Provider-
3350
+ // served tools are only met at dispatch and cannot be named here.
3351
+ if (this.ontology !== undefined) {
3352
+ for (const [nodeId, node] of Object.entries(this.ontology.nodes)) {
3353
+ for (const held of node.sources ?? []) {
3354
+ for (const toolName of held.via ?? []) {
3355
+ if (!registryByName.has(toolName)) {
3356
+ throw new Error(`Agent: ontology '${this.ontology.id}' names tool '${toolName}' that is not ` +
3357
+ `registered (node '${nodeId}', source '${held.source}'). Register the tool ` +
3358
+ `with .tool() or on a skill, or drop it from the node's \`via\`.`);
3359
+ }
3360
+ }
3361
+ }
3362
+ }
3363
+ }
3288
3364
  // A statically registered tool that declares `wants` on an agent with no
3289
3365
  // store is configuration that lies: every call would be refused at
3290
3366
  // dispatch for a gap only the operator can close. Refused at BUILD,
@@ -3549,6 +3625,19 @@ class Agent extends RunnerBase_js_1.RunnerBase {
3549
3625
  // The findings ledger (9.101.0): the ONE decoration site is inside this
3550
3626
  // slot; the gate rides in value-conditionally.
3551
3627
  ...(this.findingsOptions !== undefined && { findings: true }),
3628
+ // Tool choice by classifier (9.105.0): the pick and the narrowing live
3629
+ // inside this slot too, at the same site; value-conditional.
3630
+ ...(this.toolChoiceOptions !== undefined && {
3631
+ toolChoice: {
3632
+ classifier: this.toolChoiceOptions.classifier,
3633
+ ...(typeof this.toolChoiceOptions.serve === 'object' && {
3634
+ top: this.toolChoiceOptions.serve.top,
3635
+ }),
3636
+ ...(this.toolChoiceOptions.alwaysServe !== undefined && {
3637
+ alwaysServe: this.toolChoiceOptions.alwaysServe,
3638
+ }),
3639
+ },
3640
+ }),
3552
3641
  });
3553
3642
  // callLLM extracted to ./agent/stages/callLLM.ts (v2.11.2). Same
3554
3643
  // late-binding pattern as seed for toolSchemas (computed below).
@@ -3575,6 +3664,12 @@ class Agent extends RunnerBase_js_1.RunnerBase {
3575
3664
  }),
3576
3665
  }),
3577
3666
  ...(this.answerValidationConfig !== undefined && { suppressDraftTokens: true }),
3667
+ // Tool choice by classifier (9.105.0): the outcome row after the reply.
3668
+ ...(this.toolChoiceOptions !== undefined && { toolChoice: true }),
3669
+ // The declared ontology (9.106.0): the piece served from the run
3670
+ // constant on every call, under the one gate — an unarmed agent reads
3671
+ // no new key.
3672
+ ...(this.ontology !== undefined && { ontology: true }),
3578
3673
  // The receipt's salt (9.88.0) — read per call, like seed's own accessor.
3579
3674
  getRunId: () => this.currentRunContext?.runId,
3580
3675
  // …and its off switch. Value-conditional, so an agent on the default
@@ -3941,6 +4036,12 @@ class Agent extends RunnerBase_js_1.RunnerBase {
3941
4036
  // grammar as the slot's `findings` and callLLM's `hasFindingsLedger`,
3942
4037
  // so the three can never disagree about whether the ledger is armed.
3943
4038
  ...(this.findingsOptions !== undefined && { hasFindingsLedger: true }),
4039
+ // Tool choice by classifier (9.105.0): the mount args on the Tools
4040
+ // branch and the key across the sf-llm-call boundary, under the arm.
4041
+ ...(this.toolChoiceOptions !== undefined && { hasToolChoice: true }),
4042
+ // The declared ontology (9.106.0): the grouped chart carries the run
4043
+ // constant across the sf-llm-call boundary, under the arm.
4044
+ ...(this.ontology !== undefined && { hasOntology: true }),
3944
4045
  // `.limitsTravelWithTheAnswer()` (this release) — value-conditional, the
3945
4046
  // `resolvedModel` precedent: absent from the deps object entirely for an
3946
4047
  // agent that did not ask, so both builders mount the final-branch stage