agentfootprint 6.23.0 → 6.25.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 (111) hide show
  1. package/README.md +31 -0
  2. package/bin/agentfootprint-lint-tools.mjs +14 -0
  3. package/dist/esm/lib/influence-core/cache.js +149 -0
  4. package/dist/esm/lib/influence-core/cache.js.map +1 -0
  5. package/dist/esm/lib/influence-core/index.js +32 -0
  6. package/dist/esm/lib/influence-core/index.js.map +1 -0
  7. package/dist/esm/lib/influence-core/margin.js +110 -0
  8. package/dist/esm/lib/influence-core/margin.js.map +1 -0
  9. package/dist/esm/lib/influence-core/signals.js +232 -0
  10. package/dist/esm/lib/influence-core/signals.js.map +1 -0
  11. package/dist/esm/lib/influence-core/similarity.js +79 -0
  12. package/dist/esm/lib/influence-core/similarity.js.map +1 -0
  13. package/dist/esm/lib/influence-core/types.js +35 -0
  14. package/dist/esm/lib/influence-core/types.js.map +1 -0
  15. package/dist/esm/lib/tool-lint/analyze.js +235 -0
  16. package/dist/esm/lib/tool-lint/analyze.js.map +1 -0
  17. package/dist/esm/lib/tool-lint/cli.js +198 -0
  18. package/dist/esm/lib/tool-lint/cli.js.map +1 -0
  19. package/dist/esm/lib/tool-lint/format.js +61 -0
  20. package/dist/esm/lib/tool-lint/format.js.map +1 -0
  21. package/dist/esm/lib/tool-lint/index.js +23 -0
  22. package/dist/esm/lib/tool-lint/index.js.map +1 -0
  23. package/dist/esm/lib/tool-lint/rules.js +249 -0
  24. package/dist/esm/lib/tool-lint/rules.js.map +1 -0
  25. package/dist/esm/lib/tool-lint/types.js +25 -0
  26. package/dist/esm/lib/tool-lint/types.js.map +1 -0
  27. package/dist/esm/lib/trace-toolpack/bounded.js +76 -0
  28. package/dist/esm/lib/trace-toolpack/bounded.js.map +1 -0
  29. package/dist/esm/lib/trace-toolpack/index.js +10 -0
  30. package/dist/esm/lib/trace-toolpack/index.js.map +1 -0
  31. package/dist/esm/lib/trace-toolpack/traceToolpack.js +699 -0
  32. package/dist/esm/lib/trace-toolpack/traceToolpack.js.map +1 -0
  33. package/dist/esm/lib/trace-toolpack/types.js +24 -0
  34. package/dist/esm/lib/trace-toolpack/types.js.map +1 -0
  35. package/dist/esm/observe.js +25 -0
  36. package/dist/esm/observe.js.map +1 -1
  37. package/dist/esm/recorders/observability/ToolChoiceRecorder.js +261 -0
  38. package/dist/esm/recorders/observability/ToolChoiceRecorder.js.map +1 -0
  39. package/dist/lib/influence-core/cache.js +155 -0
  40. package/dist/lib/influence-core/cache.js.map +1 -0
  41. package/dist/lib/influence-core/index.js +50 -0
  42. package/dist/lib/influence-core/index.js.map +1 -0
  43. package/dist/lib/influence-core/margin.js +114 -0
  44. package/dist/lib/influence-core/margin.js.map +1 -0
  45. package/dist/lib/influence-core/signals.js +242 -0
  46. package/dist/lib/influence-core/signals.js.map +1 -0
  47. package/dist/lib/influence-core/similarity.js +83 -0
  48. package/dist/lib/influence-core/similarity.js.map +1 -0
  49. package/dist/lib/influence-core/types.js +38 -0
  50. package/dist/lib/influence-core/types.js.map +1 -0
  51. package/dist/lib/tool-lint/analyze.js +242 -0
  52. package/dist/lib/tool-lint/analyze.js.map +1 -0
  53. package/dist/lib/tool-lint/cli.js +203 -0
  54. package/dist/lib/tool-lint/cli.js.map +1 -0
  55. package/dist/lib/tool-lint/format.js +65 -0
  56. package/dist/lib/tool-lint/format.js.map +1 -0
  57. package/dist/lib/tool-lint/index.js +43 -0
  58. package/dist/lib/tool-lint/index.js.map +1 -0
  59. package/dist/lib/tool-lint/rules.js +256 -0
  60. package/dist/lib/tool-lint/rules.js.map +1 -0
  61. package/dist/lib/tool-lint/types.js +26 -0
  62. package/dist/lib/tool-lint/types.js.map +1 -0
  63. package/dist/lib/trace-toolpack/bounded.js +86 -0
  64. package/dist/lib/trace-toolpack/bounded.js.map +1 -0
  65. package/dist/lib/trace-toolpack/index.js +16 -0
  66. package/dist/lib/trace-toolpack/index.js.map +1 -0
  67. package/dist/lib/trace-toolpack/traceToolpack.js +704 -0
  68. package/dist/lib/trace-toolpack/traceToolpack.js.map +1 -0
  69. package/dist/lib/trace-toolpack/types.js +28 -0
  70. package/dist/lib/trace-toolpack/types.js.map +1 -0
  71. package/dist/observe.js +64 -1
  72. package/dist/observe.js.map +1 -1
  73. package/dist/recorders/observability/ToolChoiceRecorder.js +266 -0
  74. package/dist/recorders/observability/ToolChoiceRecorder.js.map +1 -0
  75. package/dist/types/lib/influence-core/cache.d.ts +95 -0
  76. package/dist/types/lib/influence-core/cache.d.ts.map +1 -0
  77. package/dist/types/lib/influence-core/index.d.ts +33 -0
  78. package/dist/types/lib/influence-core/index.d.ts.map +1 -0
  79. package/dist/types/lib/influence-core/margin.d.ts +34 -0
  80. package/dist/types/lib/influence-core/margin.d.ts.map +1 -0
  81. package/dist/types/lib/influence-core/signals.d.ts +104 -0
  82. package/dist/types/lib/influence-core/signals.d.ts.map +1 -0
  83. package/dist/types/lib/influence-core/similarity.d.ts +26 -0
  84. package/dist/types/lib/influence-core/similarity.d.ts.map +1 -0
  85. package/dist/types/lib/influence-core/types.d.ts +158 -0
  86. package/dist/types/lib/influence-core/types.d.ts.map +1 -0
  87. package/dist/types/lib/tool-lint/analyze.d.ts +84 -0
  88. package/dist/types/lib/tool-lint/analyze.d.ts.map +1 -0
  89. package/dist/types/lib/tool-lint/cli.d.ts +44 -0
  90. package/dist/types/lib/tool-lint/cli.d.ts.map +1 -0
  91. package/dist/types/lib/tool-lint/format.d.ts +19 -0
  92. package/dist/types/lib/tool-lint/format.d.ts.map +1 -0
  93. package/dist/types/lib/tool-lint/index.d.ts +24 -0
  94. package/dist/types/lib/tool-lint/index.d.ts.map +1 -0
  95. package/dist/types/lib/tool-lint/rules.d.ts +86 -0
  96. package/dist/types/lib/tool-lint/rules.d.ts.map +1 -0
  97. package/dist/types/lib/tool-lint/types.d.ts +156 -0
  98. package/dist/types/lib/tool-lint/types.d.ts.map +1 -0
  99. package/dist/types/lib/trace-toolpack/bounded.d.ts +48 -0
  100. package/dist/types/lib/trace-toolpack/bounded.d.ts.map +1 -0
  101. package/dist/types/lib/trace-toolpack/index.d.ts +10 -0
  102. package/dist/types/lib/trace-toolpack/index.d.ts.map +1 -0
  103. package/dist/types/lib/trace-toolpack/traceToolpack.d.ts +70 -0
  104. package/dist/types/lib/trace-toolpack/traceToolpack.d.ts.map +1 -0
  105. package/dist/types/lib/trace-toolpack/types.d.ts +60 -0
  106. package/dist/types/lib/trace-toolpack/types.d.ts.map +1 -0
  107. package/dist/types/observe.d.ts +4 -0
  108. package/dist/types/observe.d.ts.map +1 -1
  109. package/dist/types/recorders/observability/ToolChoiceRecorder.d.ts +165 -0
  110. package/dist/types/recorders/observability/ToolChoiceRecorder.d.ts.map +1 -0
  111. package/package.json +6 -4
package/README.md CHANGED
@@ -394,6 +394,37 @@ allocates no queue.
394
394
  > 📖 Full semantics (capture policies, backpressure, `'block'` overflow):
395
395
  > [footprintjs deferred-observers guide](https://github.com/footprintjs/footPrint/blob/main/docs/guides/observers-deferred.md)
396
396
 
397
+ ### Lint your tool catalog — before the model picks the wrong twin
398
+
399
+ Tool routing is an LLM decision driven by names + descriptions — so lint the
400
+ catalog like code and gate it in CI. **Zero stack buy-in**: works on any
401
+ OpenAI / Anthropic / MCP / plain tool list, no agentfootprint runtime needed.
402
+
403
+ ```bash
404
+ npx agentfootprint-lint-tools tools.json --threshold 0.94 --strict
405
+ ```
406
+
407
+ ```
408
+ ✗ CONFUSABLE 0.9445 get_fcns_database <> influx_get_fcns_database
409
+ hint: names differ only by 'influx' — make the descriptions say WHEN to choose each
410
+ ~ warn [enum-in-prose] influx_get_port_ranking.metric
411
+ suggest: "enum": ["avg_iops","peak_iops","mbps"]
412
+ ```
413
+
414
+ Pairwise confusability over what the model reads (embedder pluggable,
415
+ content-hash cached) plus a pluggable structural rule pack
416
+ (missing/short descriptions, says-WHAT-not-WHEN, enums hiding in prose,
417
+ undocumented optional params). The runtime counterpart, `toolChoiceRecorder`
418
+ (`agentfootprint/observe`), scores each live LLM call's tool choice against
419
+ the same geometry and flags narrow margins and proxy disagreements — lazily,
420
+ off the hot path.
421
+
422
+ > 📖 **[Tool-catalog lint guide](docs/guides/tool-catalog-lint.md)** — 5 minutes
423
+ > from a tools.json to a gated CI check ·
424
+ > [`examples/observability/02`](examples/observability/02-lint-confusable-catalog.ts) ·
425
+ > [`03`](examples/observability/03-lint-fix-and-pass.ts) ·
426
+ > [`04`](examples/observability/04-tool-choice-margins.ts)
427
+
397
428
  ---
398
429
 
399
430
  ## Quick start — runs offline, no API key
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * agentfootprint-lint-tools — CI gate for tool-catalog confusability
4
+ * (RFC-002 C3). Humble shell: all behavior lives in src/lib/tool-lint/cli.ts
5
+ * (unit-tested there); this wrapper only resolves the built module and maps
6
+ * the returned code onto process.exitCode.
7
+ *
8
+ * npx agentfootprint-lint-tools tools.json
9
+ * npx agentfootprint-lint-tools tools.json --threshold 0.94 --strict
10
+ *
11
+ * Guide: docs/guides/tool-catalog-lint.md
12
+ */
13
+ const { runToolLintCli } = await import('../dist/esm/lib/tool-lint/cli.js');
14
+ process.exitCode = await runToolLintCli(process.argv.slice(2));
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Fast, deterministic, browser-safe content hash (FNV-1a, two 32-bit
3
+ * lanes + length qualifier → "len-xxxxxxxxyyyyyyyy"). Non-cryptographic
4
+ * — cache keying only.
5
+ */
6
+ export function contentHash(text) {
7
+ let h1 = 0x811c9dc5; // FNV offset basis
8
+ let h2 = 0xcbf29ce4; // second lane, different seed
9
+ for (let i = 0; i < text.length; i++) {
10
+ const c = text.charCodeAt(i);
11
+ h1 = Math.imul(h1 ^ c, 0x01000193); // FNV prime
12
+ h2 = Math.imul(h2 ^ c, 0x01000197); // distinct odd prime
13
+ }
14
+ const lane1 = (h1 >>> 0).toString(16).padStart(8, '0');
15
+ const lane2 = (h2 >>> 0).toString(16).padStart(8, '0');
16
+ return `${text.length.toString(36)}-${lane1}${lane2}`;
17
+ }
18
+ const DEFAULT_MAX_ENTRIES = 1024;
19
+ /**
20
+ * Wrap an embedder with a bounded, content-hash-keyed LRU cache.
21
+ * See module docs for keying, bounds, and concurrency semantics.
22
+ */
23
+ export class EmbeddingCache {
24
+ dimensions;
25
+ inner;
26
+ maxEntries;
27
+ /** LRU store — Map iteration order is recency (refreshed on hit). */
28
+ vectors = new Map();
29
+ /** Single-flight joins — promises live here until settled. */
30
+ inflight = new Map();
31
+ hits = 0;
32
+ misses = 0;
33
+ evictions = 0;
34
+ constructor(inner, options = {}) {
35
+ const maxEntries = options.maxEntries ?? DEFAULT_MAX_ENTRIES;
36
+ if (!Number.isInteger(maxEntries) || maxEntries <= 0) {
37
+ throw new Error(`EmbeddingCache: maxEntries must be a positive integer (got ${maxEntries})`);
38
+ }
39
+ this.inner = inner;
40
+ this.maxEntries = maxEntries;
41
+ this.dimensions = inner.dimensions;
42
+ }
43
+ async embed(args) {
44
+ const key = contentHash(args.text);
45
+ const cached = this.vectors.get(key);
46
+ if (cached !== undefined) {
47
+ this.hits += 1;
48
+ this.refresh(key, cached);
49
+ return cached.slice();
50
+ }
51
+ const joined = this.inflight.get(key);
52
+ if (joined !== undefined) {
53
+ this.hits += 1; // coalesced — no extra inner call
54
+ return (await joined).slice();
55
+ }
56
+ this.misses += 1;
57
+ const promise = this.inner.embed(args);
58
+ this.inflight.set(key, promise);
59
+ try {
60
+ const vector = await promise;
61
+ this.store(key, vector);
62
+ return vector.slice();
63
+ }
64
+ finally {
65
+ // Settled either way; rejections are never cached.
66
+ this.inflight.delete(key);
67
+ }
68
+ }
69
+ async embedBatch(args) {
70
+ const { texts, signal } = args;
71
+ const out = new Array(texts.length);
72
+ // Partition into cache hits and misses (deduplicating within the
73
+ // batch — the same text twice embeds once).
74
+ const missTexts = [];
75
+ const missSlots = new Map(); // key → output indices
76
+ for (let i = 0; i < texts.length; i++) {
77
+ const key = contentHash(texts[i]);
78
+ const cached = this.vectors.get(key);
79
+ if (cached !== undefined) {
80
+ this.hits += 1;
81
+ this.refresh(key, cached);
82
+ out[i] = cached.slice();
83
+ continue;
84
+ }
85
+ const slots = missSlots.get(key);
86
+ if (slots !== undefined) {
87
+ this.hits += 1; // in-batch duplicate — one inner embed serves both
88
+ slots.push(i);
89
+ continue;
90
+ }
91
+ this.misses += 1;
92
+ missSlots.set(key, [i]);
93
+ missTexts.push(texts[i]);
94
+ }
95
+ if (missTexts.length > 0) {
96
+ const vectors = this.inner.embedBatch
97
+ ? await this.inner.embedBatch({ texts: missTexts, ...(signal ? { signal } : {}) })
98
+ : await this.embedSequential(missTexts, signal);
99
+ let v = 0;
100
+ for (const [key, slots] of missSlots) {
101
+ const vector = vectors[v++];
102
+ this.store(key, vector);
103
+ for (const slot of slots)
104
+ out[slot] = vector.slice();
105
+ }
106
+ }
107
+ return out;
108
+ }
109
+ /** Visible cache health (bounded honesty — see module docs). */
110
+ stats() {
111
+ return {
112
+ size: this.vectors.size,
113
+ maxEntries: this.maxEntries,
114
+ hits: this.hits,
115
+ misses: this.misses,
116
+ evictions: this.evictions,
117
+ };
118
+ }
119
+ /** Drop all cached vectors. Stats counters are preserved. */
120
+ clear() {
121
+ this.vectors.clear();
122
+ }
123
+ refresh(key, vector) {
124
+ // Map insertion order doubles as the LRU order.
125
+ this.vectors.delete(key);
126
+ this.vectors.set(key, vector);
127
+ }
128
+ store(key, vector) {
129
+ // Defensive copy in — callers can't mutate the cached vector.
130
+ this.vectors.set(key, vector.slice());
131
+ while (this.vectors.size > this.maxEntries) {
132
+ const oldest = this.vectors.keys().next().value;
133
+ this.vectors.delete(oldest);
134
+ this.evictions += 1;
135
+ }
136
+ }
137
+ async embedSequential(texts, signal) {
138
+ const vectors = [];
139
+ for (const text of texts) {
140
+ vectors.push(await this.inner.embed({ text, ...(signal ? { signal } : {}) }));
141
+ }
142
+ return vectors;
143
+ }
144
+ }
145
+ /** Factory sugar — `embeddingCache(embedder)` reads like the built-ins. */
146
+ export function embeddingCache(inner, options = {}) {
147
+ return new EmbeddingCache(inner, options);
148
+ }
149
+ //# sourceMappingURL=cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.js","sourceRoot":"","sources":["../../../../src/lib/influence-core/cache.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * influence-core — the ONE embedding-based scoring engine
3
+ * (RFC-002/003 block D6).
4
+ *
5
+ * Extracted from the Visible Reasoning paper's FDL influence pipeline
6
+ * (Eq. 1–6: four signals + adaptive weighted composite) so that three
7
+ * consumers share one engine and one embedding cache:
8
+ *
9
+ * a) RFC-002 — tool-catalog lint (C1 ← `pairwiseSimilarity`) and the
10
+ * margin recorder (C4/C5 ← `scoreMargin`),
11
+ * b) RFC-003 Part B — the LLM-edge weigher (D7 ← `scoreInfluence` /
12
+ * the signal scorers),
13
+ * c) the FDL paper pipeline itself (stages 4–6 ← `EmbeddingCache` +
14
+ * `scoreInfluence`).
15
+ *
16
+ * Leaf module: zero agent/runtime imports — the only dependency is the
17
+ * `Embedder` interface (re-exported from memory/embedding, the one
18
+ * existing contract) and the shared `cosineSimilarity`.
19
+ *
20
+ * Plug-and-play: the frame and formulas are the library's; the
21
+ * embedder, weights, and thresholds are consumer-injected.
22
+ *
23
+ * Honest claim (RFC-002 §2): every score here is a deterministic
24
+ * embedding-geometry PROXY — semantic alignment, never model internals
25
+ * and never causal attribution.
26
+ */
27
+ export { DEFAULT_INFLUENCE_WEIGHTS, DEFAULT_MARGIN_THRESHOLD, DEFAULT_PERSISTENCE_THRESHOLD, } from './types.js';
28
+ export { contentHash, EmbeddingCache, embeddingCache, } from './cache.js';
29
+ export { adaptWeights, averageRelevancy, compositeScore, finalAnswerSimilarity, persistence, scoreInfluence, structuralProximity, } from './signals.js';
30
+ export { pairwiseSimilarity } from './similarity.js';
31
+ export { scoreMargin } from './margin.js';
32
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/lib/influence-core/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAiBH,OAAO,EACL,yBAAyB,EACzB,wBAAwB,EACxB,6BAA6B,GAC9B,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,WAAW,EACX,cAAc,EACd,cAAc,GAGf,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,cAAc,EACd,qBAAqB,EACrB,WAAW,EACX,cAAc,EACd,mBAAmB,GAEpB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,kBAAkB,EAA+B,MAAM,iBAAiB,CAAC;AAElF,OAAO,EAAE,WAAW,EAAwB,MAAM,aAAa,CAAC"}
@@ -0,0 +1,110 @@
1
+ /**
2
+ * scoreMargin — choice-margin scoring over a candidate set
3
+ * (RFC-002 C4's core: (candidates, contextText, chosen) → scores +
4
+ * margin + flags).
5
+ *
6
+ * Pattern: pure async function, embedder-injected. NO recorder wiring
7
+ * — `toolChoiceRecorder` (C5) owns event ingestion and a
8
+ * KeyedStore, and calls this for the math.
9
+ * Role: `src/lib/influence-core/` leaf. No agent/runtime imports.
10
+ *
11
+ * The competition model (RFC-002 §4): embed the choice context (what
12
+ * the model saw — user message + latest reasoning), embed each offered
13
+ * candidate's text, rank candidates by similarity to the context.
14
+ * margin = score(best chosen) − score(best non-chosen)
15
+ * Small margin = fragile choice (`narrow`); top-scored candidate not
16
+ * among the chosen = `proxyDisagreement` (always worth surfacing:
17
+ * either a proxy miss or a genuinely surprising model choice).
18
+ *
19
+ * Honest claim: the scores are embedding geometry between context and
20
+ * descriptions — a proxy for the model's selection function, never
21
+ * "the model chose because". Margin is EVIDENCE of decisiveness, not
22
+ * proof; tier 3 (choice-entropy sampling) validates the proxy.
23
+ */
24
+ import { cosineSimilarity } from '../../memory/embedding/cosine.js';
25
+ import { DEFAULT_MARGIN_THRESHOLD } from './types.js';
26
+ /**
27
+ * Rank candidates by cosine similarity to the choice context and
28
+ * measure how decisively the chosen one(s) won.
29
+ *
30
+ * Returns ranked `scores` (descending; ties keep candidate input
31
+ * order), the `topScored` name, the `margin` (undefined when every
32
+ * candidate was chosen — no competition to measure; `narrow` is false
33
+ * in that case), and the two flags.
34
+ *
35
+ * Fail-loud validation: empty candidates/chosen, duplicate candidate
36
+ * names, or a chosen name missing from the candidates throw — those
37
+ * are wiring bugs in the caller, not runtime conditions.
38
+ */
39
+ export async function scoreMargin(args) {
40
+ const { candidates, chosen, embedder } = args;
41
+ const marginThreshold = args.marginThreshold ?? DEFAULT_MARGIN_THRESHOLD;
42
+ validate(candidates, chosen);
43
+ // One deduplicated embedding pass: context + distinct candidate texts.
44
+ const distinct = [...new Set([args.contextText, ...candidates.map((c) => c.text)])];
45
+ const vectors = embedder.embedBatch
46
+ ? await embedder.embedBatch({
47
+ texts: distinct,
48
+ ...(args.signal ? { signal: args.signal } : {}),
49
+ })
50
+ : await sequentialEmbed(embedder, distinct, args.signal);
51
+ const vectorByText = new Map();
52
+ for (let i = 0; i < distinct.length; i++)
53
+ vectorByText.set(distinct[i], vectors[i]);
54
+ const contextVec = vectorByText.get(args.contextText);
55
+ const scores = candidates.map((candidate) => ({
56
+ name: candidate.name,
57
+ score: cosineSimilarity(contextVec, vectorByText.get(candidate.text)),
58
+ }));
59
+ // Stable sort — ties keep candidate input order.
60
+ scores.sort((a, b) => b.score - a.score);
61
+ const chosenSet = new Set(chosen);
62
+ let bestChosen = -Infinity;
63
+ let bestOther = -Infinity;
64
+ for (const { name, score } of scores) {
65
+ if (chosenSet.has(name))
66
+ bestChosen = Math.max(bestChosen, score);
67
+ else
68
+ bestOther = Math.max(bestOther, score);
69
+ }
70
+ const margin = bestOther === -Infinity ? undefined : bestChosen - bestOther;
71
+ const topScored = scores[0].name;
72
+ return {
73
+ scores,
74
+ chosen: [...chosen],
75
+ topScored,
76
+ margin,
77
+ flags: {
78
+ narrow: margin !== undefined && margin < marginThreshold,
79
+ proxyDisagreement: !chosenSet.has(topScored),
80
+ },
81
+ };
82
+ }
83
+ async function sequentialEmbed(embedder, texts, signal) {
84
+ const out = [];
85
+ for (const text of texts) {
86
+ out.push(await embedder.embed({ text, ...(signal ? { signal } : {}) }));
87
+ }
88
+ return out;
89
+ }
90
+ function validate(candidates, chosen) {
91
+ if (candidates.length === 0) {
92
+ throw new Error('scoreMargin: candidates must be non-empty');
93
+ }
94
+ const names = new Set();
95
+ for (const candidate of candidates) {
96
+ if (names.has(candidate.name)) {
97
+ throw new Error(`scoreMargin: duplicate candidate name '${candidate.name}' — names must be unique`);
98
+ }
99
+ names.add(candidate.name);
100
+ }
101
+ if (chosen.length === 0) {
102
+ throw new Error('scoreMargin: chosen must be non-empty — calls that chose nothing have no margin to score');
103
+ }
104
+ for (const name of chosen) {
105
+ if (!names.has(name)) {
106
+ throw new Error(`scoreMargin: chosen '${name}' is not among the candidates`);
107
+ }
108
+ }
109
+ }
110
+ //# sourceMappingURL=margin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"margin.js","sourceRoot":"","sources":["../../../../src/lib/influence-core/margin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,232 @@
1
+ /**
2
+ * FDL influence signals — the four-signal composite from the Visible
3
+ * Reasoning paper (Eq. 1–6), extracted verbatim as RFC-003 block D6.
4
+ *
5
+ * Pattern: pure scorer functions + one async orchestrator. Vector-level
6
+ * functions are deterministic and embedder-free; only
7
+ * `scoreInfluence` touches the injected `Embedder`.
8
+ * Role: `src/lib/influence-core/` leaf. Consumers: the FDL paper
9
+ * pipeline (stage 5, computeInfluenceScores), RFC-003 D7's
10
+ * LLM-edge weigher, and — one level up — RFC-002's margin
11
+ * scoring shares the same geometry via `margin.ts`.
12
+ *
13
+ * ## Honest claim per signal (RFC-002 §2 discipline)
14
+ *
15
+ * Every signal is embedding GEOMETRY — a deterministic proxy, not a
16
+ * window into the model:
17
+ *
18
+ * - FA "the tool's output is semantically close to the final
19
+ * answer" — NOT "the answer was derived from it".
20
+ * - AVG "the tool's output stayed semantically close to the
21
+ * reasoning steps" — NOT "the model kept consulting it".
22
+ * - PERSIST "many reasoning steps are similar to it above T" — breadth
23
+ * of apparent reference, NOT counted citations.
24
+ * - DEPTH pure structure (1/(1+ancestors)) — directness of position
25
+ * in the trace, knows nothing about content at all.
26
+ *
27
+ * The composite S(d) means "high semantic alignment with the answer",
28
+ * never "this source contributed X% of the answer" (paper §5.2: scores
29
+ * are per-item, not additive, not causal attribution). Same inputs →
30
+ * same scores, unlike LLM-as-judge.
31
+ */
32
+ import { cosineSimilarity } from '../../memory/embedding/cosine.js';
33
+ import { DEFAULT_INFLUENCE_WEIGHTS, DEFAULT_PERSISTENCE_THRESHOLD } from './types.js';
34
+ /**
35
+ * FA — Final Answer Similarity (paper Eq. 1).
36
+ *
37
+ * `FA(d) = sim(e_d, e_f)` — cosine between the evidence embedding and
38
+ * the final-answer embedding. The strongest prior: verbatim or
39
+ * paraphrased reuse of a tool result scores high. Proxy: semantic
40
+ * overlap, not provenance.
41
+ */
42
+ export function finalAnswerSimilarity(evidenceVec, finalAnswerVec) {
43
+ return cosineSimilarity(evidenceVec, finalAnswerVec);
44
+ }
45
+ /**
46
+ * AVG — Average Relevancy (paper Eq. 2).
47
+ *
48
+ * Mean cosine between the evidence and each LLM reasoning ancestor;
49
+ * 0 when there are no ancestors (structurally zero — see
50
+ * `adaptWeights`). Proxy: consistent semantic closeness across the
51
+ * chain, not actual consultation.
52
+ */
53
+ export function averageRelevancy(evidenceVec, ancestorVecs) {
54
+ const n = ancestorVecs.length;
55
+ if (n === 0)
56
+ return 0;
57
+ let sum = 0;
58
+ for (const ancestorVec of ancestorVecs) {
59
+ sum += cosineSimilarity(evidenceVec, ancestorVec);
60
+ }
61
+ return sum / n;
62
+ }
63
+ /**
64
+ * PERSIST — Persistence (paper Eq. 3).
65
+ *
66
+ * Fraction of ancestors whose similarity to the evidence EXCEEDS the
67
+ * threshold T (strict `>`, default 0.30); 0 when there are no
68
+ * ancestors. Unlike AVG it measures BREADTH: referenced in 4 of 5
69
+ * steps (0.8) beats referenced intensely in 1. Proxy: similarity
70
+ * above a tunable bar, not counted citations.
71
+ */
72
+ export function persistence(evidenceVec, ancestorVecs, threshold = DEFAULT_PERSISTENCE_THRESHOLD) {
73
+ const n = ancestorVecs.length;
74
+ if (n === 0)
75
+ return 0;
76
+ let above = 0;
77
+ for (const ancestorVec of ancestorVecs) {
78
+ if (cosineSimilarity(evidenceVec, ancestorVec) > threshold)
79
+ above += 1;
80
+ }
81
+ return above / n;
82
+ }
83
+ /**
84
+ * DEPTH — Structural Proximity (paper Eq. 4).
85
+ *
86
+ * `DEPTH(d) = 1 / (1 + n)` where n counts LLM reasoning ancestors
87
+ * ONLY (not pipeline plumbing — callers decide what counts as an
88
+ * ancestor when building `EvidenceInput.ancestorTexts`). Direct
89
+ * evidence with no intermediaries gets exactly 1.0. The only
90
+ * content-blind signal: pure trace structure.
91
+ */
92
+ export function structuralProximity(ancestorCount) {
93
+ if (!Number.isInteger(ancestorCount) || ancestorCount < 0) {
94
+ throw new Error(`structuralProximity: ancestorCount must be a non-negative integer (got ${ancestorCount})`);
95
+ }
96
+ return 1 / (1 + ancestorCount);
97
+ }
98
+ /**
99
+ * Adaptive weight redistribution (paper Eq. 6, §5.3).
100
+ *
101
+ * When an item has NO LLM ancestors, AVG and PERSIST are structurally
102
+ * zero — not because the evidence was uninfluential, but because there
103
+ * is nothing to measure against. Without adaptation its score is
104
+ * capped at α+δ (≈0.50 under defaults). Eq. 6 moves the β+γ mass onto
105
+ * FA and DEPTH preserving their ratio:
106
+ *
107
+ * α′ = α + (β+γ)·α/(α+δ), δ′ = δ + (β+γ)·δ/(α+δ), β′ = γ′ = 0
108
+ *
109
+ * Defaults → α′=0.80, δ′=0.20 (the 4:1 FA:DEPTH ratio kept).
110
+ * Per-evidence-item: in a multi-tool pipeline some items adapt while
111
+ * others keep standard weights; `adapted` says which (surface it — the
112
+ * paper's UI marks adapted items).
113
+ *
114
+ * Degenerate guard: if α+δ = 0 there is no defined ratio to preserve —
115
+ * weights return unchanged with `adapted: false`, and the composite is
116
+ * honestly 0 for a no-ancestor item.
117
+ */
118
+ export function adaptWeights(weights, ancestorCount) {
119
+ if (ancestorCount > 0)
120
+ return { weights, adapted: false };
121
+ const base = weights.fa + weights.depth;
122
+ if (base === 0)
123
+ return { weights, adapted: false };
124
+ const mass = weights.avg + weights.persist;
125
+ return {
126
+ weights: {
127
+ fa: weights.fa + (mass * weights.fa) / base,
128
+ avg: 0,
129
+ persist: 0,
130
+ depth: weights.depth + (mass * weights.depth) / base,
131
+ },
132
+ adapted: mass > 0,
133
+ };
134
+ }
135
+ /**
136
+ * Composite score S(d) (paper Eq. 5).
137
+ *
138
+ * `S = α·FA + β·AVG + γ·PERSIST + δ·DEPTH` under the given weights —
139
+ * pass the EFFECTIVE weights from `adaptWeights` for no-ancestor
140
+ * items. With weights summing to 1, S ∈ [−(α+β), 1] (FA/AVG are
141
+ * cosines and may go negative; PERSIST/DEPTH are non-negative).
142
+ */
143
+ export function compositeScore(signals, weights) {
144
+ return (weights.fa * signals.fa +
145
+ weights.avg * signals.avg +
146
+ weights.persist * signals.persist +
147
+ weights.depth * signals.depth);
148
+ }
149
+ /**
150
+ * Score every evidence item on the four FDL signals and rank by
151
+ * composite, descending (paper pipeline stages 4–6 in one call:
152
+ * embed → score → rank). Ties keep input order (stable sort).
153
+ *
154
+ * Deterministic for a deterministic embedder: same inputs → same
155
+ * scores. All texts are embedded in ONE deduplicated batch — with an
156
+ * `EmbeddingCache` injected, repeat calls embed nothing.
157
+ *
158
+ * Honest claim: ranked semantic-alignment proxies. NOT causal
159
+ * attribution — see module docs.
160
+ */
161
+ export async function scoreInfluence(args) {
162
+ const weights = args.weights ?? DEFAULT_INFLUENCE_WEIGHTS;
163
+ assertValidWeights(weights);
164
+ const threshold = args.persistenceThreshold ?? DEFAULT_PERSISTENCE_THRESHOLD;
165
+ assertUniqueIds(args.evidence);
166
+ // ONE deduplicated embedding pass over every distinct text.
167
+ const texts = new Set([args.finalAnswerText]);
168
+ for (const item of args.evidence) {
169
+ texts.add(item.text);
170
+ for (const ancestor of item.ancestorTexts)
171
+ texts.add(ancestor);
172
+ }
173
+ const vectorByText = await embedAll(args.embedder, [...texts], args.signal);
174
+ const finalVec = vectorByText.get(args.finalAnswerText);
175
+ const scored = args.evidence.map((item) => {
176
+ const evidenceVec = vectorByText.get(item.text);
177
+ const ancestorVecs = item.ancestorTexts.map((t) => vectorByText.get(t));
178
+ const signals = {
179
+ fa: finalAnswerSimilarity(evidenceVec, finalVec),
180
+ avg: averageRelevancy(evidenceVec, ancestorVecs),
181
+ persist: persistence(evidenceVec, ancestorVecs, threshold),
182
+ depth: structuralProximity(ancestorVecs.length),
183
+ };
184
+ const effective = adaptWeights(weights, ancestorVecs.length);
185
+ return {
186
+ id: item.id,
187
+ signals,
188
+ weights: effective.weights,
189
+ adapted: effective.adapted,
190
+ score: compositeScore(signals, effective.weights),
191
+ };
192
+ });
193
+ // Stable sort — equal scores keep evidence input order.
194
+ return scored.sort((a, b) => b.score - a.score);
195
+ }
196
+ /** Embed distinct texts via batch API when available, else sequentially. */
197
+ async function embedAll(embedder, texts, signal) {
198
+ const vectors = embedder.embedBatch
199
+ ? await embedder.embedBatch({ texts, ...(signal ? { signal } : {}) })
200
+ : await sequentialEmbed(embedder, texts, signal);
201
+ const byText = new Map();
202
+ for (let i = 0; i < texts.length; i++)
203
+ byText.set(texts[i], vectors[i]);
204
+ return byText;
205
+ }
206
+ async function sequentialEmbed(embedder, texts, signal) {
207
+ const out = [];
208
+ for (const text of texts) {
209
+ out.push(await embedder.embed({ text, ...(signal ? { signal } : {}) }));
210
+ }
211
+ return out;
212
+ }
213
+ function assertValidWeights(weights) {
214
+ for (const [name, value] of Object.entries(weights)) {
215
+ if (!Number.isFinite(value) || value < 0) {
216
+ throw new Error(`scoreInfluence: weight '${name}' must be a finite non-negative number (got ${value})`);
217
+ }
218
+ }
219
+ if (weights.fa + weights.avg + weights.persist + weights.depth === 0) {
220
+ throw new Error('scoreInfluence: all weights are zero — the composite would always be 0');
221
+ }
222
+ }
223
+ function assertUniqueIds(evidence) {
224
+ const seen = new Set();
225
+ for (const item of evidence) {
226
+ if (seen.has(item.id)) {
227
+ throw new Error(`scoreInfluence: duplicate evidence id '${item.id}' — ids must be unique`);
228
+ }
229
+ seen.add(item.id);
230
+ }
231
+ }
232
+ //# sourceMappingURL=signals.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signals.js","sourceRoot":"","sources":["../../../../src/lib/influence-core/signals.ts"],"names":[],"mappings":"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"}