agentfootprint 7.0.0 → 7.2.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 (69) hide show
  1. package/CLAUDE.md +83 -622
  2. package/dist/core/agent/AgentBuilder.js +1 -1
  3. package/dist/core/agent/AgentBuilder.js.map +1 -1
  4. package/dist/debug.js +2 -1
  5. package/dist/debug.js.map +1 -1
  6. package/dist/esm/core/agent/AgentBuilder.js +1 -1
  7. package/dist/esm/core/agent/AgentBuilder.js.map +1 -1
  8. package/dist/esm/debug.d.ts +1 -1
  9. package/dist/esm/debug.js +1 -1
  10. package/dist/esm/debug.js.map +1 -1
  11. package/dist/esm/lib/context-bisect/index.d.ts +1 -0
  12. package/dist/esm/lib/context-bisect/index.js +3 -0
  13. package/dist/esm/lib/context-bisect/index.js.map +1 -1
  14. package/dist/esm/lib/context-bisect/sliceToBacktrackTrace.d.ts +52 -0
  15. package/dist/esm/lib/context-bisect/sliceToBacktrackTrace.js +150 -0
  16. package/dist/esm/lib/context-bisect/sliceToBacktrackTrace.js.map +1 -0
  17. package/dist/esm/lib/context-ledger/contextLedger.d.ts +35 -0
  18. package/dist/esm/lib/context-ledger/contextLedger.js +309 -0
  19. package/dist/esm/lib/context-ledger/contextLedger.js.map +1 -0
  20. package/dist/esm/lib/context-ledger/gates.d.ts +63 -0
  21. package/dist/esm/lib/context-ledger/gates.js +105 -0
  22. package/dist/esm/lib/context-ledger/gates.js.map +1 -0
  23. package/dist/esm/lib/context-ledger/index.d.ts +10 -0
  24. package/dist/esm/lib/context-ledger/index.js +10 -0
  25. package/dist/esm/lib/context-ledger/index.js.map +1 -0
  26. package/dist/esm/lib/context-ledger/types.d.ts +107 -0
  27. package/dist/esm/lib/context-ledger/types.js +28 -0
  28. package/dist/esm/lib/context-ledger/types.js.map +1 -0
  29. package/dist/esm/lib/trace-toolpack/traceToolpack.d.ts +1 -0
  30. package/dist/esm/lib/trace-toolpack/traceToolpack.js +111 -0
  31. package/dist/esm/lib/trace-toolpack/traceToolpack.js.map +1 -1
  32. package/dist/esm/observe.d.ts +2 -0
  33. package/dist/esm/observe.js +4 -0
  34. package/dist/esm/observe.js.map +1 -1
  35. package/dist/lib/context-bisect/index.js +5 -1
  36. package/dist/lib/context-bisect/index.js.map +1 -1
  37. package/dist/lib/context-bisect/sliceToBacktrackTrace.js +154 -0
  38. package/dist/lib/context-bisect/sliceToBacktrackTrace.js.map +1 -0
  39. package/dist/lib/context-ledger/contextLedger.js +313 -0
  40. package/dist/lib/context-ledger/contextLedger.js.map +1 -0
  41. package/dist/lib/context-ledger/gates.js +111 -0
  42. package/dist/lib/context-ledger/gates.js.map +1 -0
  43. package/dist/lib/context-ledger/index.js +17 -0
  44. package/dist/lib/context-ledger/index.js.map +1 -0
  45. package/dist/lib/context-ledger/types.js +29 -0
  46. package/dist/lib/context-ledger/types.js.map +1 -0
  47. package/dist/lib/trace-toolpack/traceToolpack.js +111 -0
  48. package/dist/lib/trace-toolpack/traceToolpack.js.map +1 -1
  49. package/dist/observe.js +9 -1
  50. package/dist/observe.js.map +1 -1
  51. package/dist/types/debug.d.ts +1 -1
  52. package/dist/types/debug.d.ts.map +1 -1
  53. package/dist/types/lib/context-bisect/index.d.ts +1 -0
  54. package/dist/types/lib/context-bisect/index.d.ts.map +1 -1
  55. package/dist/types/lib/context-bisect/sliceToBacktrackTrace.d.ts +53 -0
  56. package/dist/types/lib/context-bisect/sliceToBacktrackTrace.d.ts.map +1 -0
  57. package/dist/types/lib/context-ledger/contextLedger.d.ts +36 -0
  58. package/dist/types/lib/context-ledger/contextLedger.d.ts.map +1 -0
  59. package/dist/types/lib/context-ledger/gates.d.ts +64 -0
  60. package/dist/types/lib/context-ledger/gates.d.ts.map +1 -0
  61. package/dist/types/lib/context-ledger/index.d.ts +11 -0
  62. package/dist/types/lib/context-ledger/index.d.ts.map +1 -0
  63. package/dist/types/lib/context-ledger/types.d.ts +108 -0
  64. package/dist/types/lib/context-ledger/types.d.ts.map +1 -0
  65. package/dist/types/lib/trace-toolpack/traceToolpack.d.ts +1 -0
  66. package/dist/types/lib/trace-toolpack/traceToolpack.d.ts.map +1 -1
  67. package/dist/types/observe.d.ts +2 -0
  68. package/dist/types/observe.d.ts.map +1 -1
  69. package/package.json +4 -4
@@ -0,0 +1,313 @@
1
+ "use strict";
2
+ /**
3
+ * contextLedger — the post-run bookkeeper (see types.ts for the WHY).
4
+ *
5
+ * DESIGN: a pure POST-RUN analyzer over the run's own commit log — no live
6
+ * recorder lifecycle, no event-order coupling. Everything it counts is
7
+ * already durably recorded by the engine (dogfooding: offers come from
8
+ * `commitValueAt` folds, answer attribution from `sliceForKey` — the same
9
+ * canonical queries every triage surface uses):
10
+ *
11
+ * offers — the context IN EFFECT at each LLM call. Call marker: a
12
+ * commit that wrote `totalInputTokens` (monotonic — never
13
+ * net-change-dropped) and did NOT write `userMessage` (which
14
+ * only the seed writes). At each call index, fold
15
+ * `activeInjections` + `dynamicToolSchemas`, and add the
16
+ * STATIC tool registry (duck-read from the runner's public
17
+ * `getUIGroup().extra.toolNames` — static registries live in a
18
+ * closure, never in scope state). NOTE offers are deliberately
19
+ * NOT "per context-key commit": the net-change filter drops
20
+ * identical re-commits, so stable context appears once in the
21
+ * log while still being offered (and paying tokens) per call.
22
+ * Static-registry tools count offers with approxTokens 0 in L1
23
+ * (their schema JSON isn't in the log) — earnRate, the gate
24
+ * signal, is unaffected; dynamicToolSchemas carries real sizes.
25
+ * uses — tool: assistant messages' toolCalls in the final history;
26
+ * skill: `activatedInjectionIds`;
27
+ * injection: its SLOT's write sits on the final answer's
28
+ * backward slice (slot-granular, labeled).
29
+ * outcome — consumer label per run, credited to every offered piece.
30
+ *
31
+ * The slot→slice join needs NO id conventions: for each slot key
32
+ * (INJECTION_KEYS.*), `findLastWriter` names the commit that fed the final
33
+ * LLM call; membership of that writer in the answer slice IS the signal.
34
+ */
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.contextLedger = void 0;
37
+ const trace_1 = require("footprintjs/trace");
38
+ const trace_2 = require("footprintjs/trace");
39
+ const conventions_js_1 = require("../../conventions.js");
40
+ /** chars ÷ 4 — a serialized-length ESTIMATE, deliberately rough and cheap. */
41
+ function approxTokens(value) {
42
+ try {
43
+ return Math.ceil(JSON.stringify(value).length / 4);
44
+ }
45
+ catch {
46
+ return 0;
47
+ }
48
+ }
49
+ /** sf-llm-call mount keys in loop order — grouped-mode iterations live in
50
+ * their own retained inner logs (the same projection context-bisect's
51
+ * assembleGroupedTrajectory uses). */
52
+ function llmCallMountKeys(subflowResults) {
53
+ if (!subflowResults)
54
+ return [];
55
+ return Object.keys(subflowResults)
56
+ .filter((k) => k.includes('#') && (0, trace_2.splitStageId)(k.split('#')[0]).localStageId === conventions_js_1.SUBFLOW_IDS.LLM_CALL)
57
+ .sort((a, b) => (0, trace_2.parseRuntimeStageId)(a).executionIndex - (0, trace_2.parseRuntimeStageId)(b).executionIndex);
58
+ }
59
+ function snapshotOf(source) {
60
+ const maybeRunner = source;
61
+ if (typeof maybeRunner?.getLastSnapshot === 'function') {
62
+ return maybeRunner.getLastSnapshot();
63
+ }
64
+ return source;
65
+ }
66
+ /** Static tool registry names, duck-read from the runner's public UI-group
67
+ * metadata (Agent fills `extra.toolNames`). Empty for non-runner sources. */
68
+ function staticToolNamesOf(source) {
69
+ const maybeRunner = source;
70
+ if (typeof maybeRunner?.getUIGroup !== 'function')
71
+ return [];
72
+ try {
73
+ const names = maybeRunner.getUIGroup()?.extra?.toolNames;
74
+ return Array.isArray(names) ? names.filter((n) => typeof n === 'string') : [];
75
+ }
76
+ catch {
77
+ return [];
78
+ }
79
+ }
80
+ function contextLedger() {
81
+ const table = new Map(); // key: `${kind}:${id}`
82
+ const runOffers = new Map(); // runRef → offered keys
83
+ let runsRecorded = 0;
84
+ let lastRunRef;
85
+ function rowOf(kind, id) {
86
+ const key = `${kind}:${id}`;
87
+ let row = table.get(key);
88
+ if (!row) {
89
+ row = { id, kind, offered: 0, approxTokensSpent: 0, used: 0, usedVia: {}, runsSeen: 0, outcomes: {} };
90
+ table.set(key, row);
91
+ }
92
+ return row;
93
+ }
94
+ function markUsed(kind, id, via) {
95
+ const row = rowOf(kind, id);
96
+ row.used += 1;
97
+ row.usedVia[via] = (row.usedVia[via] ?? 0) + 1;
98
+ }
99
+ function recordRun(source) {
100
+ const snapshot = snapshotOf(source);
101
+ const log = snapshot?.commitLog;
102
+ if (!log?.length)
103
+ return undefined;
104
+ runsRecorded += 1;
105
+ const runRef = `run-${runsRecorded}`;
106
+ const offeredKeys = new Set();
107
+ const offer = (kind, id, tokens) => {
108
+ const row = rowOf(kind, id);
109
+ row.offered += 1;
110
+ row.approxTokensSpent += tokens;
111
+ offeredKeys.add(`${kind}:${id}`);
112
+ };
113
+ // ── OFFERS: the context IN EFFECT at each LLM call ───────────────────
114
+ // Call marker: wrote totalInputTokens (monotonic — survives the
115
+ // net-change filter) and NOT userMessage (the seed's unique write).
116
+ // A SINGLE forward pass per log tracks the latest context values as
117
+ // writes appear (one commitValueAt per actual write — O(N), review
118
+ // finding #3), so each marker reads the context in effect exactly even
119
+ // though identical re-commits are dropped from the log.
120
+ //
121
+ // GROUPED reactMode (review finding #1): call-llm + the slots live
122
+ // INSIDE per-iteration `sf-llm-call#k` subflow logs (their context keys
123
+ // never bubble to the root), so offers fold over each retained inner
124
+ // log — the same projection context-bisect's grouped trajectory uses.
125
+ const staticTools = staticToolNamesOf(source);
126
+ let callMarkers = 0;
127
+ const foldOffersFrom = (bundles) => {
128
+ let injections = [];
129
+ let schemas = [];
130
+ for (let i = 0; i < bundles.length; i++) {
131
+ const paths = new Set(bundles[i].trace.map((t) => t.path));
132
+ if (paths.has('activeInjections')) {
133
+ const v = (0, trace_1.commitValueAt)(bundles, i, 'activeInjections');
134
+ if (Array.isArray(v))
135
+ injections = v;
136
+ }
137
+ if (paths.has('dynamicToolSchemas')) {
138
+ const v = (0, trace_1.commitValueAt)(bundles, i, 'dynamicToolSchemas');
139
+ if (Array.isArray(v))
140
+ schemas = v;
141
+ }
142
+ if (!paths.has('totalInputTokens') || paths.has('userMessage'))
143
+ continue;
144
+ callMarkers += 1;
145
+ for (const inj of injections) {
146
+ if (!inj?.id)
147
+ continue;
148
+ const kind = inj.flavor === 'skill' ? 'skill' : 'injection';
149
+ offer(kind, inj.id, approxTokens(inj));
150
+ }
151
+ // Tools: dynamic schemas (real sizes) + the static registry (offer
152
+ // counted, size unknowable from the log in L1 — earnRate unaffected).
153
+ const offeredToolsThisCall = new Set();
154
+ for (const schema of schemas) {
155
+ if (!schema?.name || offeredToolsThisCall.has(schema.name))
156
+ continue;
157
+ offeredToolsThisCall.add(schema.name);
158
+ offer('tool', schema.name, approxTokens(schema));
159
+ }
160
+ for (const name of staticTools) {
161
+ if (offeredToolsThisCall.has(name))
162
+ continue;
163
+ offeredToolsThisCall.add(name);
164
+ offer('tool', name, 0);
165
+ }
166
+ }
167
+ };
168
+ const mountKeys = llmCallMountKeys(snapshot?.subflowResults);
169
+ if (mountKeys.length > 0) {
170
+ for (const key of mountKeys) {
171
+ const inner = snapshot?.subflowResults?.[key]?.treeContext?.history;
172
+ if (Array.isArray(inner))
173
+ foldOffersFrom(inner);
174
+ }
175
+ }
176
+ else {
177
+ foldOffersFrom(log);
178
+ }
179
+ // A run with NO call markers anywhere is a shape this ledger cannot
180
+ // meter (e.g. the LLMCall runner, which never writes totalInputTokens)
181
+ // — report honestly instead of confidently recording zero offers.
182
+ if (callMarkers === 0) {
183
+ runsRecorded -= 1;
184
+ return undefined;
185
+ }
186
+ // ── USES: tool calls (assistant messages in the final history) ───────
187
+ const lastIdx = log.length - 1;
188
+ const history = (0, trace_1.commitValueAt)(log, lastIdx, 'history');
189
+ if (Array.isArray(history)) {
190
+ for (const msg of history) {
191
+ if (msg?.role !== 'assistant' || !Array.isArray(msg.toolCalls))
192
+ continue;
193
+ for (const call of msg.toolCalls) {
194
+ if (call?.name)
195
+ markUsed('tool', call.name, 'tool-called');
196
+ }
197
+ }
198
+ }
199
+ // ── USES: skill activations ──────────────────────────────────────────
200
+ const activated = (0, trace_1.commitValueAt)(log, lastIdx, 'activatedInjectionIds');
201
+ if (Array.isArray(activated)) {
202
+ for (const id of activated) {
203
+ if (typeof id === 'string' && id.length > 0)
204
+ markUsed('skill', id, 'skill-activated');
205
+ }
206
+ }
207
+ // ── USES: answer-slice membership per slot (slot-granular, honest) ───
208
+ // Slice the final answer; a slot counts as "on the answer's dependency
209
+ // chain" when its key's LAST WRITER is a slice member. Every injection
210
+ // in the FINAL context of that slot gets the (shared) credit.
211
+ let sliceAvailable = false;
212
+ const tree = snapshot?.executionTree;
213
+ if (tree) {
214
+ const reads = (0, trace_1.keysReadFromExecutionTree)(tree);
215
+ const slice = (0, trace_1.sliceForKey)(log, 'finalContent', reads);
216
+ if (slice.root) {
217
+ sliceAvailable = true;
218
+ const memberIds = new Set((0, trace_1.flattenCausalDAG)(slice.root).map((n) => n.runtimeStageId));
219
+ const finalInjections = (0, trace_1.commitValueAt)(log, lastIdx, 'activeInjections');
220
+ const finalBySlotKey = new Map();
221
+ // Which slot carried each injection is projected per-slot into the
222
+ // INJECTION_KEYS records — fold each slot key's final value.
223
+ for (const slotKey of Object.values(conventions_js_1.INJECTION_KEYS)) {
224
+ const writer = (0, trace_1.findLastWriter)(log, slotKey);
225
+ if (!writer || !memberIds.has(writer.runtimeStageId))
226
+ continue;
227
+ const slotRecords = (0, trace_1.commitValueAt)(log, lastIdx, slotKey);
228
+ if (Array.isArray(slotRecords))
229
+ finalBySlotKey.set(slotKey, slotRecords);
230
+ }
231
+ // Credit: injections present in a slice-member slot's final records.
232
+ const activeById = new Map();
233
+ if (Array.isArray(finalInjections)) {
234
+ for (const inj of finalInjections) {
235
+ if (inj?.id)
236
+ activeById.set(inj.id, inj);
237
+ }
238
+ }
239
+ for (const records of finalBySlotKey.values()) {
240
+ for (const rec of records) {
241
+ const id = rec?.id;
242
+ if (!id)
243
+ continue;
244
+ const flavor = activeById.get(id)?.flavor ?? rec.source;
245
+ const kind = flavor === 'skill' ? 'skill' : 'injection';
246
+ markUsed(kind, id, 'answer-slice(slot)');
247
+ }
248
+ }
249
+ }
250
+ }
251
+ // runsSeen: once per run per offered piece.
252
+ for (const key of offeredKeys) {
253
+ const row = table.get(key);
254
+ if (row)
255
+ row.runsSeen += 1;
256
+ }
257
+ runOffers.set(runRef, offeredKeys);
258
+ lastRunRef = runRef;
259
+ return { runRef, offeredPieces: [...offeredKeys], sliceAvailable };
260
+ }
261
+ function recordOutcome(label, runRef) {
262
+ const ref = runRef ?? lastRunRef;
263
+ if (!ref)
264
+ return false;
265
+ const offered = runOffers.get(ref);
266
+ if (!offered)
267
+ return false;
268
+ for (const key of offered) {
269
+ const row = table.get(key);
270
+ if (row)
271
+ row.outcomes[label] = (row.outcomes[label] ?? 0) + 1;
272
+ }
273
+ return true;
274
+ }
275
+ function freeze(row) {
276
+ return {
277
+ ...row,
278
+ usedVia: { ...row.usedVia },
279
+ outcomes: { ...row.outcomes },
280
+ earnRate: row.offered > 0 ? row.used / row.offered : 0,
281
+ };
282
+ }
283
+ return {
284
+ recordRun,
285
+ recordOutcome,
286
+ rows: () => [...table.values()].map(freeze).sort((a, b) => a.earnRate - b.earnRate),
287
+ row: (kind, id) => {
288
+ const row = table.get(`${kind}:${id}`);
289
+ return row ? freeze(row) : undefined;
290
+ },
291
+ exportJSON: () => ({ version: 1, rows: [...table.values()].map(freeze), runsRecorded }),
292
+ importJSON: (json) => {
293
+ if (json?.version !== 1)
294
+ return;
295
+ for (const r of json.rows) {
296
+ const row = rowOf(r.kind, r.id);
297
+ row.offered += r.offered;
298
+ row.approxTokensSpent += r.approxTokensSpent;
299
+ row.used += r.used;
300
+ row.runsSeen += r.runsSeen;
301
+ for (const [via, n] of Object.entries(r.usedVia)) {
302
+ row.usedVia[via] = (row.usedVia[via] ?? 0) + (n ?? 0);
303
+ }
304
+ for (const [label, n] of Object.entries(r.outcomes)) {
305
+ row.outcomes[label] = (row.outcomes[label] ?? 0) + n;
306
+ }
307
+ }
308
+ runsRecorded += json.runsRecorded;
309
+ },
310
+ };
311
+ }
312
+ exports.contextLedger = contextLedger;
313
+ //# sourceMappingURL=contextLedger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contextLedger.js","sourceRoot":"","sources":["../../../src/lib/context-ledger/contextLedger.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;;;AAEH,6CAM2B;AAG3B,6CAAsE;AACtE,yDAAmE;AAUnE,8EAA8E;AAC9E,SAAS,YAAY,CAAC,KAAc;IAClC,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACrD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,CAAC;IACX,CAAC;AACH,CAAC;AAmBD;;uCAEuC;AACvC,SAAS,gBAAgB,CAAC,cAAmD;IAC3E,IAAI,CAAC,cAAc;QAAE,OAAO,EAAE,CAAC;IAC/B,OAAO,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC;SAC/B,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,IAAA,oBAAY,EAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,YAAY,KAAK,4BAAW,CAAC,QAAQ,CAAC;SACrG,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAA,2BAAmB,EAAC,CAAC,CAAC,CAAC,cAAc,GAAG,IAAA,2BAAmB,EAAC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC;AACnG,CAAC;AAED,SAAS,UAAU,CAAC,MAA4B;IAC9C,MAAM,WAAW,GAAG,MAAuC,CAAC;IAC5D,IAAI,OAAO,WAAW,EAAE,eAAe,KAAK,UAAU,EAAE,CAAC;QACvD,OAAQ,WAAW,CAAC,eAAiC,EAA8B,CAAC;IACtF,CAAC;IACD,OAAO,MAAkC,CAAC;AAC5C,CAAC;AAED;8EAC8E;AAC9E,SAAS,iBAAiB,CAAC,MAA4B;IACrD,MAAM,WAAW,GAAG,MAAgF,CAAC;IACrG,IAAI,OAAO,WAAW,EAAE,UAAU,KAAK,UAAU;QAAE,OAAO,EAAE,CAAC;IAC7D,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,WAAW,CAAC,UAAU,EAAE,EAAE,KAAK,EAAE,SAAS,CAAC;QACzD,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC7F,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAeD,SAAgB,aAAa;IAC3B,MAAM,KAAK,GAAG,IAAI,GAAG,EAAsB,CAAC,CAAC,uBAAuB;IACpE,MAAM,SAAS,GAAG,IAAI,GAAG,EAAuB,CAAC,CAAC,wBAAwB;IAC1E,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,IAAI,UAA8B,CAAC;IAEnC,SAAS,KAAK,CAAC,IAAe,EAAE,EAAU;QACxC,MAAM,GAAG,GAAG,GAAG,IAAI,IAAI,EAAE,EAAE,CAAC;QAC5B,IAAI,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACzB,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,GAAG,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,iBAAiB,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;YACtG,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QACtB,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAED,SAAS,QAAQ,CAAC,IAAe,EAAE,EAAU,EAAE,GAAe;QAC5D,MAAM,GAAG,GAAG,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAC5B,GAAG,CAAC,IAAI,IAAI,CAAC,CAAC;QACd,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;IACjD,CAAC;IAED,SAAS,SAAS,CAAC,MAA4B;QAC7C,MAAM,QAAQ,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;QACpC,MAAM,GAAG,GAAG,QAAQ,EAAE,SAAS,CAAC;QAChC,IAAI,CAAC,GAAG,EAAE,MAAM;YAAE,OAAO,SAAS,CAAC;QAEnC,YAAY,IAAI,CAAC,CAAC;QAClB,MAAM,MAAM,GAAG,OAAO,YAAY,EAAE,CAAC;QACrC,MAAM,WAAW,GAAG,IAAI,GAAG,EAAU,CAAC;QAEtC,MAAM,KAAK,GAAG,CAAC,IAAe,EAAE,EAAU,EAAE,MAAc,EAAQ,EAAE;YAClE,MAAM,GAAG,GAAG,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAC5B,GAAG,CAAC,OAAO,IAAI,CAAC,CAAC;YACjB,GAAG,CAAC,iBAAiB,IAAI,MAAM,CAAC;YAChC,WAAW,CAAC,GAAG,CAAC,GAAG,IAAI,IAAI,EAAE,EAAE,CAAC,CAAC;QACnC,CAAC,CAAC;QAEF,wEAAwE;QACxE,gEAAgE;QAChE,oEAAoE;QACpE,oEAAoE;QACpE,mEAAmE;QACnE,uEAAuE;QACvE,wDAAwD;QACxD,EAAE;QACF,mEAAmE;QACnE,wEAAwE;QACxE,qEAAqE;QACrE,sEAAsE;QACtE,MAAM,WAAW,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;QAC9C,IAAI,WAAW,GAAG,CAAC,CAAC;QAEpB,MAAM,cAAc,GAAG,CAAC,OAAuB,EAAQ,EAAE;YACvD,IAAI,UAAU,GAA0B,EAAE,CAAC;YAC3C,IAAI,OAAO,GAAqB,EAAE,CAAC;YACnC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBACxC,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;gBAC3D,IAAI,KAAK,CAAC,GAAG,CAAC,kBAAkB,CAAC,EAAE,CAAC;oBAClC,MAAM,CAAC,GAAG,IAAA,qBAAa,EAAC,OAAO,EAAE,CAAC,EAAE,kBAAkB,CAAC,CAAC;oBACxD,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;wBAAE,UAAU,GAAG,CAA0B,CAAC;gBAChE,CAAC;gBACD,IAAI,KAAK,CAAC,GAAG,CAAC,oBAAoB,CAAC,EAAE,CAAC;oBACpC,MAAM,CAAC,GAAG,IAAA,qBAAa,EAAC,OAAO,EAAE,CAAC,EAAE,oBAAoB,CAAC,CAAC;oBAC1D,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;wBAAE,OAAO,GAAG,CAAqB,CAAC;gBACxD,CAAC;gBACD,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,kBAAkB,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,aAAa,CAAC;oBAAE,SAAS;gBACzE,WAAW,IAAI,CAAC,CAAC;gBAEjB,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;oBAC7B,IAAI,CAAC,GAAG,EAAE,EAAE;wBAAE,SAAS;oBACvB,MAAM,IAAI,GAAc,GAAG,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC;oBACvE,KAAK,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,EAAE,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC;gBACzC,CAAC;gBACD,mEAAmE;gBACnE,sEAAsE;gBACtE,MAAM,oBAAoB,GAAG,IAAI,GAAG,EAAU,CAAC;gBAC/C,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;oBAC7B,IAAI,CAAC,MAAM,EAAE,IAAI,IAAI,oBAAoB,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC;wBAAE,SAAS;oBACrE,oBAAoB,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;oBACtC,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC;gBACnD,CAAC;gBACD,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;oBAC/B,IAAI,oBAAoB,CAAC,GAAG,CAAC,IAAI,CAAC;wBAAE,SAAS;oBAC7C,oBAAoB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;oBAC/B,KAAK,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;gBACzB,CAAC;YACH,CAAC;QACH,CAAC,CAAC;QAEF,MAAM,SAAS,GAAG,gBAAgB,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC;QAC7D,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACzB,KAAK,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;gBAC5B,MAAM,KAAK,GAAG,QAAQ,EAAE,cAAc,EAAE,CAAC,GAAG,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC;gBACpE,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;oBAAE,cAAc,CAAC,KAAK,CAAC,CAAC;YAClD,CAAC;QACH,CAAC;aAAM,CAAC;YACN,cAAc,CAAC,GAAG,CAAC,CAAC;QACtB,CAAC;QAED,oEAAoE;QACpE,uEAAuE;QACvE,kEAAkE;QAClE,IAAI,WAAW,KAAK,CAAC,EAAE,CAAC;YACtB,YAAY,IAAI,CAAC,CAAC;YAClB,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,wEAAwE;QACxE,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC;QAC/B,MAAM,OAAO,GAAG,IAAA,qBAAa,EAAC,GAAG,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC;QACvD,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;YAC3B,KAAK,MAAM,GAAG,IAAI,OAA+B,EAAE,CAAC;gBAClD,IAAI,GAAG,EAAE,IAAI,KAAK,WAAW,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC;oBAAE,SAAS;gBACzE,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,SAAS,EAAE,CAAC;oBACjC,IAAI,IAAI,EAAE,IAAI;wBAAE,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;gBAC7D,CAAC;YACH,CAAC;QACH,CAAC;QAED,wEAAwE;QACxE,MAAM,SAAS,GAAG,IAAA,qBAAa,EAAC,GAAG,EAAE,OAAO,EAAE,uBAAuB,CAAC,CAAC;QACvE,IAAI,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;YAC7B,KAAK,MAAM,EAAE,IAAI,SAAqB,EAAE,CAAC;gBACvC,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC;oBAAE,QAAQ,CAAC,OAAO,EAAE,EAAE,EAAE,iBAAiB,CAAC,CAAC;YACxF,CAAC;QACH,CAAC;QAED,wEAAwE;QACxE,uEAAuE;QACvE,uEAAuE;QACvE,8DAA8D;QAC9D,IAAI,cAAc,GAAG,KAAK,CAAC;QAC3B,MAAM,IAAI,GAAG,QAAQ,EAAE,aAA0C,CAAC;QAClE,IAAI,IAAI,EAAE,CAAC;YACT,MAAM,KAAK,GAAG,IAAA,iCAAyB,EAAC,IAAI,CAAC,CAAC;YAC9C,MAAM,KAAK,GAAG,IAAA,mBAAW,EAAC,GAAG,EAAE,cAAc,EAAE,KAAK,CAAC,CAAC;YACtD,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;gBACf,cAAc,GAAG,IAAI,CAAC;gBACtB,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,IAAA,wBAAgB,EAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC;gBACrF,MAAM,eAAe,GAAG,IAAA,qBAAa,EAAC,GAAG,EAAE,OAAO,EAAE,kBAAkB,CAAC,CAAC;gBACxE,MAAM,cAAc,GAAG,IAAI,GAAG,EAAiC,CAAC;gBAChE,mEAAmE;gBACnE,6DAA6D;gBAC7D,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,MAAM,CAAC,+BAAc,CAAC,EAAE,CAAC;oBACpD,MAAM,MAAM,GAAG,IAAA,sBAAc,EAAC,GAAG,EAAE,OAAO,CAAC,CAAC;oBAC5C,IAAI,CAAC,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,cAAc,CAAC;wBAAE,SAAS;oBAC/D,MAAM,WAAW,GAAG,IAAA,qBAAa,EAAC,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;oBACzD,IAAI,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC;wBAAE,cAAc,CAAC,GAAG,CAAC,OAAO,EAAE,WAAoC,CAAC,CAAC;gBACpG,CAAC;gBACD,qEAAqE;gBACrE,MAAM,UAAU,GAAG,IAAI,GAAG,EAA+B,CAAC;gBAC1D,IAAI,KAAK,CAAC,OAAO,CAAC,eAAe,CAAC,EAAE,CAAC;oBACnC,KAAK,MAAM,GAAG,IAAI,eAAwC,EAAE,CAAC;wBAC3D,IAAI,GAAG,EAAE,EAAE;4BAAE,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;oBAC3C,CAAC;gBACH,CAAC;gBACD,KAAK,MAAM,OAAO,IAAI,cAAc,CAAC,MAAM,EAAE,EAAE,CAAC;oBAC9C,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;wBAC1B,MAAM,EAAE,GAAI,GAAuB,EAAE,EAAE,CAAC;wBACxC,IAAI,CAAC,EAAE;4BAAE,SAAS;wBAClB,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,MAAM,IAAK,GAA2B,CAAC,MAAM,CAAC;wBACjF,MAAM,IAAI,GAAc,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC;wBACnE,QAAQ,CAAC,IAAI,EAAE,EAAE,EAAE,oBAAoB,CAAC,CAAC;oBAC3C,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;QAED,4CAA4C;QAC5C,KAAK,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;YAC9B,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAC3B,IAAI,GAAG;gBAAE,GAAG,CAAC,QAAQ,IAAI,CAAC,CAAC;QAC7B,CAAC;QACD,SAAS,CAAC,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;QACnC,UAAU,GAAG,MAAM,CAAC;QACpB,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC,GAAG,WAAW,CAAC,EAAE,cAAc,EAAE,CAAC;IACrE,CAAC;IAED,SAAS,aAAa,CAAC,KAAa,EAAE,MAAe;QACnD,MAAM,GAAG,GAAG,MAAM,IAAI,UAAU,CAAC;QACjC,IAAI,CAAC,GAAG;YAAE,OAAO,KAAK,CAAC;QACvB,MAAM,OAAO,GAAG,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACnC,IAAI,CAAC,OAAO;YAAE,OAAO,KAAK,CAAC;QAC3B,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;YAC1B,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAC3B,IAAI,GAAG;gBAAE,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;QAChE,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,SAAS,MAAM,CAAC,GAAe;QAC7B,OAAO;YACL,GAAG,GAAG;YACN,OAAO,EAAE,EAAE,GAAG,GAAG,CAAC,OAAO,EAAE;YAC3B,QAAQ,EAAE,EAAE,GAAG,GAAG,CAAC,QAAQ,EAAE;YAC7B,QAAQ,EAAE,GAAG,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;SACvD,CAAC;IACJ,CAAC;IAED,OAAO;QACL,SAAS;QACT,aAAa;QACb,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAC;QACnF,GAAG,EAAE,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE;YAChB,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,IAAI,IAAI,EAAE,EAAE,CAAC,CAAC;YACvC,OAAO,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACvC,CAAC;QACD,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,YAAY,EAAE,CAAC;QACvF,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE;YACnB,IAAI,IAAI,EAAE,OAAO,KAAK,CAAC;gBAAE,OAAO;YAChC,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;gBAC1B,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;gBAChC,GAAG,CAAC,OAAO,IAAI,CAAC,CAAC,OAAO,CAAC;gBACzB,GAAG,CAAC,iBAAiB,IAAI,CAAC,CAAC,iBAAiB,CAAC;gBAC7C,GAAG,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,CAAC;gBACnB,GAAG,CAAC,QAAQ,IAAI,CAAC,CAAC,QAAQ,CAAC;gBAC3B,KAAK,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;oBACjD,GAAG,CAAC,OAAO,CAAC,GAAiB,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,GAAiB,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;gBACpF,CAAC;gBACD,KAAK,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC;oBACpD,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;gBACvD,CAAC;YACH,CAAC;YACD,YAAY,IAAI,IAAI,CAAC,YAAY,CAAC;QACpC,CAAC;KACF,CAAC;AACJ,CAAC;AAnOD,sCAmOC"}
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ /**
3
+ * context-ledger/gates.ts — L2: the ledger's rows feed what gets included.
4
+ *
5
+ * Three gates, one policy, each hooking an EXISTING seam (no new framework
6
+ * surface): `gatedTools(inner, predicate)` for tools, `skillGraph().entryBy`
7
+ * for skills, and a rule-trigger wrapper for injections.
8
+ *
9
+ * THE POLICY PRINCIPLE — demote, never starve: a piece the ledger judges
10
+ * poorly is demoted, but every `refreshEvery`-th decision lets it through
11
+ * anyway ("parole"), so it keeps generating fresh ledger data. Without
12
+ * parole a piece demoted once could never earn again — a self-fulfilling
13
+ * verdict. And the ledger never judges a piece it barely knows: below
14
+ * `minOffers` everything passes.
15
+ */
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.ledgerGated = exports.ledgerEntryScorer = exports.ledgerToolGate = void 0;
18
+ const entryScorer_js_1 = require("../injection-engine/entryScorer.js");
19
+ const DEFAULTS = { minOffers: 5, earnRateFloor: 0.05, refreshEvery: 10 };
20
+ /**
21
+ * The shared verdict: should this piece be included right now?
22
+ * Stateful per gate instance (the parole counter), pure otherwise.
23
+ */
24
+ function makeVerdict(ledger, policy) {
25
+ const p = { ...DEFAULTS, ...policy };
26
+ const demotions = new Map();
27
+ return (kind, id) => {
28
+ const row = ledger.row(kind, id);
29
+ if (!row)
30
+ return true; // unknown to the ledger → always include
31
+ if (row.offered < p.minOffers)
32
+ return true; // not enough data to judge
33
+ if (row.earnRate >= p.earnRateFloor)
34
+ return true; // earning its tokens
35
+ // Demoted — but parole every Nth decision keeps the data flowing.
36
+ const key = `${kind}:${id}`;
37
+ const count = (demotions.get(key) ?? 0) + 1;
38
+ demotions.set(key, count);
39
+ return p.refreshEvery !== Infinity && count % p.refreshEvery === 0;
40
+ };
41
+ }
42
+ /**
43
+ * TOOL GATE — plug into the existing provider combinator:
44
+ *
45
+ * ```ts
46
+ * import { gatedTools, staticTools } from 'agentfootprint/tool-providers';
47
+ * builder.toolProvider(gatedTools(staticTools(tools), ledgerToolGate(ledger)));
48
+ * ```
49
+ *
50
+ * Unused tool schemas are usually the biggest silent token cost — this is
51
+ * the gate to start with.
52
+ */
53
+ function ledgerToolGate(ledger, policy) {
54
+ const verdict = makeVerdict(ledger, policy);
55
+ return (toolName) => verdict('tool', toolName);
56
+ }
57
+ exports.ledgerToolGate = ledgerToolGate;
58
+ /**
59
+ * SKILL GATE — an `EntryScorer` for `skillGraph().entryBy(...)` that wraps
60
+ * another scorer (keyword, embedding, …) and demotes candidates the ledger
61
+ * says never earn. Demotion multiplies the inner score toward the floor —
62
+ * ranking pressure, not exclusion — and parole restores full weight
63
+ * periodically, so a demoted skill can still win when nothing else fits.
64
+ */
65
+ function ledgerEntryScorer(ledger, inner, policy) {
66
+ const verdict = makeVerdict(ledger, policy);
67
+ const DEMOTION_WEIGHT = 0.25;
68
+ return {
69
+ name: `ledger(${inner.name})`,
70
+ async score(input, signal) {
71
+ const base = await inner.score(input, signal);
72
+ // Re-rank through rankEntries so `chosen`, `score` AND `relevance` are
73
+ // recomputed TOGETHER from the demoted scores — preserving the
74
+ // entryScorer contract (argmax-score == argmax-relevance; the "Why
75
+ // this skill?" panel and the pick can never disagree).
76
+ const candidates = base.ranked.map((r) => ({
77
+ id: r.id,
78
+ description: input.candidates.find((c) => c.id === r.id)?.description ?? '',
79
+ }));
80
+ const demotedScores = base.ranked.map((r) => verdict('skill', r.id) ? r.score : r.score * DEMOTION_WEIGHT);
81
+ return (0, entryScorer_js_1.rankEntries)(`ledger(${base.scorer})`, candidates, demotedScores);
82
+ },
83
+ };
84
+ }
85
+ exports.ledgerEntryScorer = ledgerEntryScorer;
86
+ /**
87
+ * INJECTION GATE — wrap ONE injection so the ledger's verdict joins its
88
+ * trigger. `always` becomes a ledger-backed rule; an existing `rule` is
89
+ * AND-ed with the verdict. `on-tool-return` / `llm-activated` are returned
90
+ * UNCHANGED (they are already demand-driven — the ledger has nothing to
91
+ * add). `always` pieces are exempt unless you explicitly wrap them — the
92
+ * safety-first default from the design.
93
+ */
94
+ function ledgerGated(injection, ledger, policy) {
95
+ const verdict = makeVerdict(ledger, policy);
96
+ const kind = injection.flavor === 'skill' ? 'skill' : 'injection';
97
+ const trigger = injection.trigger;
98
+ if (trigger.kind === 'always') {
99
+ return { ...injection, trigger: { kind: 'rule', activeWhen: () => verdict(kind, injection.id) } };
100
+ }
101
+ if (trigger.kind === 'rule') {
102
+ const original = trigger.activeWhen;
103
+ return {
104
+ ...injection,
105
+ trigger: { kind: 'rule', activeWhen: (ctx) => original(ctx) && verdict(kind, injection.id) },
106
+ };
107
+ }
108
+ return injection; // demand-driven triggers pass through untouched
109
+ }
110
+ exports.ledgerGated = ledgerGated;
111
+ //# sourceMappingURL=gates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gates.js","sourceRoot":"","sources":["../../../src/lib/context-ledger/gates.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;GAaG;;;AAEH,uEAAiE;AAuBjE,MAAM,QAAQ,GAA2B,EAAE,SAAS,EAAE,CAAC,EAAE,aAAa,EAAE,IAAI,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC;AAEjG;;;GAGG;AACH,SAAS,WAAW,CAClB,MAAqB,EACrB,MAAgC;IAEhC,MAAM,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,GAAG,MAAM,EAAE,CAAC;IACrC,MAAM,SAAS,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC5C,OAAO,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE;QAClB,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACjC,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAC,CAAC,yCAAyC;QAChE,IAAI,GAAG,CAAC,OAAO,GAAG,CAAC,CAAC,SAAS;YAAE,OAAO,IAAI,CAAC,CAAC,2BAA2B;QACvE,IAAI,GAAG,CAAC,QAAQ,IAAI,CAAC,CAAC,aAAa;YAAE,OAAO,IAAI,CAAC,CAAC,qBAAqB;QACvE,kEAAkE;QAClE,MAAM,GAAG,GAAG,GAAG,IAAI,IAAI,EAAE,EAAE,CAAC;QAC5B,MAAM,KAAK,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;QAC5C,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAC1B,OAAO,CAAC,CAAC,YAAY,KAAK,QAAQ,IAAI,KAAK,GAAG,CAAC,CAAC,YAAY,KAAK,CAAC,CAAC;IACrE,CAAC,CAAC;AACJ,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAgB,cAAc,CAAC,MAAqB,EAAE,MAAqB;IACzE,MAAM,OAAO,GAAG,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;AACjD,CAAC;AAHD,wCAGC;AAED;;;;;;GAMG;AACH,SAAgB,iBAAiB,CAC/B,MAAqB,EACrB,KAAkB,EAClB,MAAqB;IAErB,MAAM,OAAO,GAAG,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,MAAM,eAAe,GAAG,IAAI,CAAC;IAC7B,OAAO;QACL,IAAI,EAAE,UAAU,KAAK,CAAC,IAAI,GAAG;QAC7B,KAAK,CAAC,KAAK,CAAC,KAAuB,EAAE,MAAoB;YACvD,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;YAC9C,uEAAuE;YACvE,+DAA+D;YAC/D,mEAAmE;YACnE,uDAAuD;YACvD,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBACzC,EAAE,EAAE,CAAC,CAAC,EAAE;gBACR,WAAW,EAAE,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,WAAW,IAAI,EAAE;aAC5E,CAAC,CAAC,CAAC;YACJ,MAAM,aAAa,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAC1C,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,eAAe,CAC7D,CAAC;YACF,OAAO,IAAA,4BAAW,EAAC,UAAU,IAAI,CAAC,MAAM,GAAG,EAAE,UAAU,EAAE,aAAa,CAAC,CAAC;QAC1E,CAAC;KACF,CAAC;AACJ,CAAC;AAzBD,8CAyBC;AAED;;;;;;;GAOG;AACH,SAAgB,WAAW,CACzB,SAAoB,EACpB,MAAqB,EACrB,MAAqB;IAErB,MAAM,OAAO,GAAG,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,MAAM,IAAI,GAAc,SAAS,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC;IAC7E,MAAM,OAAO,GAAG,SAAS,CAAC,OAAO,CAAC;IAClC,IAAI,OAAO,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,EAAE,GAAG,SAAS,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;IACpG,CAAC;IACD,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QAC5B,MAAM,QAAQ,GAAG,OAAO,CAAC,UAAU,CAAC;QACpC,OAAO;YACL,GAAG,SAAS;YACZ,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC,EAAE,CAAC,EAAE;SAC7F,CAAC;IACJ,CAAC;IACD,OAAO,SAAS,CAAC,CAAC,gDAAgD;AACpE,CAAC;AAnBD,kCAmBC"}
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ /**
3
+ * context-ledger — which context pieces EARNED their tokens?
4
+ *
5
+ * Post-run bookkeeping over the run's own commit log (offers, structural
6
+ * uses, consumer outcomes) whose rows feed the gating seams so future turns
7
+ * include less. See README.md for the honesty model and the L2 gates.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.ledgerGated = exports.ledgerEntryScorer = exports.ledgerToolGate = exports.contextLedger = void 0;
11
+ var contextLedger_js_1 = require("./contextLedger.js");
12
+ Object.defineProperty(exports, "contextLedger", { enumerable: true, get: function () { return contextLedger_js_1.contextLedger; } });
13
+ var gates_js_1 = require("./gates.js");
14
+ Object.defineProperty(exports, "ledgerToolGate", { enumerable: true, get: function () { return gates_js_1.ledgerToolGate; } });
15
+ Object.defineProperty(exports, "ledgerEntryScorer", { enumerable: true, get: function () { return gates_js_1.ledgerEntryScorer; } });
16
+ Object.defineProperty(exports, "ledgerGated", { enumerable: true, get: function () { return gates_js_1.ledgerGated; } });
17
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/context-ledger/index.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;;AAEH,uDAAmD;AAA1C,iHAAA,aAAa,OAAA;AAStB,uCAA+F;AAAtF,0GAAA,cAAc,OAAA;AAAE,6GAAA,iBAAiB,OAAA;AAAE,uGAAA,WAAW,OAAA"}
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ /**
3
+ * context-ledger/types.ts — the bookkeeper's vocabulary.
4
+ *
5
+ * WHY THIS LIBRARY EXISTS: context engineering fails in one predictable
6
+ * direction — "include everything to be safe". Every injection, skill and
7
+ * tool schema costs tokens EVERY turn, whether or not it ever mattered. The
8
+ * ledger keeps score across runs: which pieces EARNED their tokens (were
9
+ * called, activated, or sat on the answer's dependency slice) and which
10
+ * never did. Its rows feed the L2 gates (gatedTools predicate, skill-graph
11
+ * EntryScorer, injection demotion) so future turns include less — the
12
+ * mechanism that makes lesser models viable.
13
+ *
14
+ * HONESTY (the discipline everything here inherits):
15
+ * - Every counter is a STRUCTURAL fact from the run's own commit log —
16
+ * offers are recorded commits, uses are recorded calls/activations/slice
17
+ * membership. Nothing is inferred from model internals.
18
+ * - Each kind's `used` definition is explicit ({@link UsedSignal}) and rides
19
+ * every count — a consumer can always see WHY a piece counted as used.
20
+ * - Slice membership is slot-granular (all injections sharing a slot share
21
+ * its write) — the signal name says so: `'answer-slice(slot)'`.
22
+ * - `approxTokens*` is a serialized-length estimate (JSON chars ÷ 4), named
23
+ * so nobody mistakes it for a tokenizer count.
24
+ * - The ledger never claims causation. `earnRate` is bookkeeping;
25
+ * ablation (context-bisect) can upgrade individual claims when you pay
26
+ * for it.
27
+ */
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/lib/context-ledger/types.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG"}
@@ -37,6 +37,7 @@
37
37
  Object.defineProperty(exports, "__esModule", { value: true });
38
38
  exports.callTraceTool = exports.traceToolpack = void 0;
39
39
  const trace_1 = require("footprintjs/trace");
40
+ const trace_2 = require("footprintjs/trace");
40
41
  const toolArgsValidation_js_1 = require("../../core/agent/toolArgsValidation.js");
41
42
  const tools_js_1 = require("../../core/tools.js");
42
43
  const types_js_1 = require("../../identity/types.js");
@@ -214,6 +215,7 @@ function truncateText(text, cap) {
214
215
  * | `run_overview` | What happened, broadly? (the entry point) |
215
216
  * | `trace_node` | What did step X read/write, and where did its inputs come from? |
216
217
  * | `trace_slice` | Which chain of steps produced the data at X? (causal slice) |
218
+ * | `backtrack` | Why is VARIABLE K what it is? (+ `element`: who made K[i]?) — variable-first |
217
219
  * | `who_wrote` | Which step last wrote key K? |
218
220
  * | `get_value` | The full value of K as of step X (capped, truncation-marked) |
219
221
  * | `read_narrative` | The human-readable story, paginated (only when narrative provided) |
@@ -235,6 +237,7 @@ function traceToolpack(artifacts, options) {
235
237
  buildRunOverview(artifacts, index),
236
238
  buildTraceNode(artifacts, index, opts),
237
239
  buildTraceSlice(artifacts, index, opts),
240
+ buildBacktrack(artifacts, index, opts),
238
241
  buildWhoWrote(index, opts),
239
242
  buildGetValue(index, opts),
240
243
  ];
@@ -551,6 +554,114 @@ function buildTraceSlice(artifacts, index, opts) {
551
554
  });
552
555
  }
553
556
  // ── who_wrote ──────────────────────────────────────────────────────────────
557
+ // ── backtrack — variable-first triage (the follow-up question shape) ──────
558
+ /**
559
+ * `trace_slice` is STEP-addressed ("what fed step X?"). Follow-up questions
560
+ * arrive VARIABLE-addressed ("why did you say the quote is 11.88?" → "why is
561
+ * `quote` what it is?") — and for agent history, ELEMENT-addressed ("where
562
+ * did message 7 come from?"). This tool is that address translation, built
563
+ * on footprintjs' slice layer (`sliceForKey` / `elementProvenance`): anchor
564
+ * at the variable's last writer, walk backwards; or name one array element's
565
+ * birth step. Same honesty contracts as the rest of the pack — a missing
566
+ * slice states WHY (initial state / frozen args / a closure), element
567
+ * attribution carries its basis (append-verb exact vs prefix-inference),
568
+ * and reads-coverage warns when read tracking was off.
569
+ */
570
+ function buildBacktrack(artifacts, index, opts) {
571
+ return (0, tools_js_1.defineTool)({
572
+ name: 'backtrack',
573
+ description: "Why is a state VARIABLE what it is? Anchors at the variable's last writer and walks the " +
574
+ 'backward dependency chain (variable-first — you do not need a step id). For array ' +
575
+ "variables (e.g. 'history'), pass 'element' to ask which step produced element N instead. " +
576
+ "Optional 'before' (commit index) time-travels: the value as it stood before that point. " +
577
+ `Bounded by maxDepth (default ${opts.sliceMaxDepth}, max ${types_js_2.TOOLPACK_HARD_CAPS.sliceMaxDepth}) ` +
578
+ `and maxNodes (default ${opts.sliceMaxNodes}, max ${types_js_2.TOOLPACK_HARD_CAPS.sliceMaxNodes}).`,
579
+ inputSchema: {
580
+ type: 'object',
581
+ properties: {
582
+ variable: keyProperty(index, "The state key to explain, e.g. 'quote' or 'history'."),
583
+ element: {
584
+ type: 'integer',
585
+ description: 'Optional array index: ask which step PRODUCED this element (birth attribution) ' +
586
+ 'instead of slicing the whole variable.',
587
+ },
588
+ before: {
589
+ type: 'integer',
590
+ description: 'Optional exclusive commit index — explain the value as it stood BEFORE this point ' +
591
+ "(chain from an element birth with: element_birth's commitIdx + 1).",
592
+ },
593
+ maxDepth: {
594
+ type: 'integer',
595
+ description: `Optional slice depth (default ${opts.sliceMaxDepth}, hard cap ${types_js_2.TOOLPACK_HARD_CAPS.sliceMaxDepth}).`,
596
+ },
597
+ maxNodes: {
598
+ type: 'integer',
599
+ description: `Optional node budget (default ${opts.sliceMaxNodes}, hard cap ${types_js_2.TOOLPACK_HARD_CAPS.sliceMaxNodes}).`,
600
+ },
601
+ },
602
+ required: ['variable'],
603
+ additionalProperties: false,
604
+ },
605
+ execute: ({ variable, element, before, maxDepth, maxNodes }) => {
606
+ const key = (0, bounded_js_1.normalizeKey)(variable, index.knownPaths);
607
+ // ── element mode: birth attribution ─────────────────────────────────
608
+ if (element !== undefined) {
609
+ const birth = (0, trace_2.elementProvenance)(index.commitLog, key, element, {
610
+ ...(before !== undefined && { atIdx: before - 1 }),
611
+ });
612
+ if (!birth) {
613
+ const prov = (0, trace_2.arrayProvenance)(index.commitLog, key, {
614
+ ...(before !== undefined && { atIdx: before - 1 }),
615
+ });
616
+ if (prov.missing === 'never-written') {
617
+ return (`'${(0, bounded_js_1.displayKey)(key)}' was never written in range — it may come from run input ` +
618
+ '(args), env, pre-run state, or a closure; the commit log cannot see those.');
619
+ }
620
+ if (prov.missing === 'not-an-array') {
621
+ return (`'${(0, bounded_js_1.displayKey)(key)}' is not an array at that point (scalar, deleted, or degraded) — ` +
622
+ `element attribution does not apply. Call backtrack without 'element' to slice it.`);
623
+ }
624
+ if (prov.missing === 'empty-log')
625
+ return 'the commit log is empty — nothing executed.';
626
+ return (`element ${element} is out of range for '${(0, bounded_js_1.displayKey)(key)}' ` +
627
+ `(length ${prov.length}). Valid indices: 0..${(prov.length ?? 1) - 1}.`);
628
+ }
629
+ return (`'${(0, bounded_js_1.displayKey)(key)}'[${birth.index}] = ${(0, bounded_js_1.renderPreview)((0, bounded_js_1.boundedPreview)(birth.value, opts.previewChars))} — ` +
630
+ `born at ${birth.runtimeStageId} ("${birth.stageName}", verb: ${birth.verb}, ` +
631
+ `attribution: ${birth.basis}${birth.basis === 'append-verb' ? ' — engine-recorded, exact' : ''}). ` +
632
+ `Drill the producer with trace_node('${birth.runtimeStageId}'), or ask why it wrote this ` +
633
+ `with backtrack({variable: '${(0, bounded_js_1.displayKey)(key)}', before: ${birth.commitIdx + 1}}).`);
634
+ }
635
+ // ── slice mode: variable-first backward slice ────────────────────────
636
+ const depth = (0, bounded_js_1.clampParam)(maxDepth, opts.sliceMaxDepth, 1, types_js_2.TOOLPACK_HARD_CAPS.sliceMaxDepth);
637
+ const nodeBudget = (0, bounded_js_1.clampParam)(maxNodes, opts.sliceMaxNodes, 2, types_js_2.TOOLPACK_HARD_CAPS.sliceMaxNodes);
638
+ const keysReadOf = (id) => {
639
+ const node = index.nodes.get(id);
640
+ return node?.stageReads ? Object.keys(node.stageReads) : [];
641
+ };
642
+ const slice = (0, trace_2.sliceForKey)(index.commitLog, key, keysReadOf, {
643
+ maxDepth: depth,
644
+ maxNodes: nodeBudget,
645
+ ...(before !== undefined && { before }),
646
+ ...(artifacts.controlDeps ? { controlDeps: artifacts.controlDeps } : {}),
647
+ });
648
+ const lines = [
649
+ (0, bounded_js_1.displayText)((0, trace_2.formatSlice)(slice)),
650
+ '',
651
+ 'drill any (stepId) with trace_node; fetch values with get_value; array variables take ' +
652
+ "'element' for per-element attribution.",
653
+ ];
654
+ if (slice.root !== undefined && !artifacts.controlDeps) {
655
+ lines.push('⚠ control edges unavailable — artifacts carry no controlDeps lookup; decisions that ' +
656
+ 'routed execution are not shown.');
657
+ }
658
+ if (slice.root !== undefined && !index.hasReadTracking) {
659
+ lines.push('⚠ artifacts carry no per-step read tracking — the slice may show only the writer step.');
660
+ }
661
+ return lines.join('\n');
662
+ },
663
+ });
664
+ }
554
665
  function buildWhoWrote(index, opts) {
555
666
  return (0, tools_js_1.defineTool)({
556
667
  name: 'who_wrote',