agentfootprint 9.87.1 → 9.89.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +946 -0
- package/dist/core/Agent.js +32 -0
- package/dist/core/Agent.js.map +1 -1
- package/dist/core/LLMCall.js +7 -10
- package/dist/core/LLMCall.js.map +1 -1
- package/dist/core/agent/buildAgentMessageApiChart.js +2 -4
- package/dist/core/agent/buildAgentMessageApiChart.js.map +1 -1
- package/dist/core/agent/buildDynamicAgentChart.js +10 -0
- package/dist/core/agent/buildDynamicAgentChart.js.map +1 -1
- package/dist/core/agent/buildMessageApiChart.js +2 -4
- package/dist/core/agent/buildMessageApiChart.js.map +1 -1
- package/dist/core/agent/composeRequest.js +134 -0
- package/dist/core/agent/composeRequest.js.map +1 -0
- package/dist/core/agent/stages/callLLM.js +62 -25
- package/dist/core/agent/stages/callLLM.js.map +1 -1
- package/dist/core/agent/stages/seed.js +12 -0
- package/dist/core/agent/stages/seed.js.map +1 -1
- package/dist/core/agent/stages/toolCalls.js +2 -4
- package/dist/core/agent/stages/toolCalls.js.map +1 -1
- package/dist/core/flowchartAsTool.js.map +1 -1
- package/dist/esm/core/Agent.d.ts +3 -0
- package/dist/esm/core/Agent.js +32 -0
- package/dist/esm/core/Agent.js.map +1 -1
- package/dist/esm/core/LLMCall.js +7 -10
- package/dist/esm/core/LLMCall.js.map +1 -1
- package/dist/esm/core/agent/buildAgentMessageApiChart.js +2 -4
- package/dist/esm/core/agent/buildAgentMessageApiChart.js.map +1 -1
- package/dist/esm/core/agent/buildDynamicAgentChart.js +10 -0
- package/dist/esm/core/agent/buildDynamicAgentChart.js.map +1 -1
- package/dist/esm/core/agent/buildMessageApiChart.js +2 -4
- package/dist/esm/core/agent/buildMessageApiChart.js.map +1 -1
- package/dist/esm/core/agent/composeRequest.d.ts +121 -0
- package/dist/esm/core/agent/composeRequest.js +127 -0
- package/dist/esm/core/agent/composeRequest.js.map +1 -0
- package/dist/esm/core/agent/stages/callLLM.d.ts +28 -0
- package/dist/esm/core/agent/stages/callLLM.js +61 -24
- package/dist/esm/core/agent/stages/callLLM.js.map +1 -1
- package/dist/esm/core/agent/stages/seed.d.ts +26 -0
- package/dist/esm/core/agent/stages/seed.js +12 -0
- package/dist/esm/core/agent/stages/seed.js.map +1 -1
- package/dist/esm/core/agent/stages/toolCalls.js +2 -4
- package/dist/esm/core/agent/stages/toolCalls.js.map +1 -1
- package/dist/esm/core/agent/types.d.ts +64 -0
- package/dist/esm/core/flowchartAsTool.d.ts +15 -7
- package/dist/esm/core/flowchartAsTool.js.map +1 -1
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +9 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/lib/context-bisect/llmEdgeWeigher.d.ts +8 -0
- package/dist/esm/lib/context-bisect/llmEdgeWeigher.js +0 -0
- package/dist/esm/lib/context-bisect/llmEdgeWeigher.js.map +1 -1
- package/dist/esm/lib/context-bisect/trajectory.js +3 -7
- package/dist/esm/lib/context-bisect/trajectory.js.map +1 -1
- package/dist/esm/lib/context-ledger/contextLedger.js +5 -12
- package/dist/esm/lib/context-ledger/contextLedger.js.map +1 -1
- package/dist/esm/lib/time-travel/epochs.d.ts +210 -0
- package/dist/esm/lib/time-travel/epochs.js +274 -0
- package/dist/esm/lib/time-travel/epochs.js.map +1 -0
- package/dist/esm/lib/time-travel/index.d.ts +10 -4
- package/dist/esm/lib/time-travel/index.js +10 -4
- package/dist/esm/lib/time-travel/index.js.map +1 -1
- package/dist/esm/lib/time-travel/keyedFold.d.ts +112 -0
- package/dist/esm/lib/time-travel/keyedFold.js +286 -0
- package/dist/esm/lib/time-travel/keyedFold.js.map +1 -0
- package/dist/esm/lib/time-travel/milestoneStops.d.ts +57 -36
- package/dist/esm/lib/time-travel/milestoneStops.js +75 -82
- package/dist/esm/lib/time-travel/milestoneStops.js.map +1 -1
- package/dist/esm/lib/time-travel/receipt.d.ts +496 -0
- package/dist/esm/lib/time-travel/receipt.js +434 -0
- package/dist/esm/lib/time-travel/receipt.js.map +1 -0
- package/dist/esm/lib/time-travel/servedView.d.ts +525 -0
- package/dist/esm/lib/time-travel/servedView.js +820 -0
- package/dist/esm/lib/time-travel/servedView.js.map +1 -0
- package/dist/esm/lib/time-travel/sha256.d.ts +24 -0
- package/dist/esm/lib/time-travel/sha256.js +103 -0
- package/dist/esm/lib/time-travel/sha256.js.map +1 -0
- package/dist/esm/recorders/observability/BoundaryRecorder.d.ts +21 -0
- package/dist/esm/recorders/observability/BoundaryRecorder.js +6 -0
- package/dist/esm/recorders/observability/BoundaryRecorder.js.map +1 -1
- package/dist/index.js +124 -103
- package/dist/index.js.map +1 -1
- package/dist/lib/context-bisect/llmEdgeWeigher.js +0 -0
- package/dist/lib/context-bisect/llmEdgeWeigher.js.map +1 -1
- package/dist/lib/context-bisect/trajectory.js +3 -7
- package/dist/lib/context-bisect/trajectory.js.map +1 -1
- package/dist/lib/context-ledger/contextLedger.js +5 -12
- package/dist/lib/context-ledger/contextLedger.js.map +1 -1
- package/dist/lib/time-travel/epochs.js +284 -0
- package/dist/lib/time-travel/epochs.js.map +1 -0
- package/dist/lib/time-travel/index.js +35 -5
- package/dist/lib/time-travel/index.js.map +1 -1
- package/dist/lib/time-travel/keyedFold.js +290 -0
- package/dist/lib/time-travel/keyedFold.js.map +1 -0
- package/dist/lib/time-travel/milestoneStops.js +74 -81
- package/dist/lib/time-travel/milestoneStops.js.map +1 -1
- package/dist/lib/time-travel/receipt.js +442 -0
- package/dist/lib/time-travel/receipt.js.map +1 -0
- package/dist/lib/time-travel/servedView.js +826 -0
- package/dist/lib/time-travel/servedView.js.map +1 -0
- package/dist/lib/time-travel/sha256.js +107 -0
- package/dist/lib/time-travel/sha256.js.map +1 -0
- package/dist/recorders/observability/BoundaryRecorder.js +6 -0
- package/dist/recorders/observability/BoundaryRecorder.js.map +1 -1
- package/dist/types/core/Agent.d.ts +3 -0
- package/dist/types/core/Agent.d.ts.map +1 -1
- package/dist/types/core/LLMCall.d.ts.map +1 -1
- package/dist/types/core/agent/buildAgentMessageApiChart.d.ts.map +1 -1
- package/dist/types/core/agent/buildDynamicAgentChart.d.ts.map +1 -1
- package/dist/types/core/agent/buildMessageApiChart.d.ts.map +1 -1
- package/dist/types/core/agent/composeRequest.d.ts +122 -0
- package/dist/types/core/agent/composeRequest.d.ts.map +1 -0
- package/dist/types/core/agent/stages/callLLM.d.ts +28 -0
- package/dist/types/core/agent/stages/callLLM.d.ts.map +1 -1
- package/dist/types/core/agent/stages/seed.d.ts +26 -0
- package/dist/types/core/agent/stages/seed.d.ts.map +1 -1
- package/dist/types/core/agent/stages/toolCalls.d.ts.map +1 -1
- package/dist/types/core/agent/types.d.ts +64 -0
- package/dist/types/core/agent/types.d.ts.map +1 -1
- package/dist/types/core/flowchartAsTool.d.ts +15 -7
- package/dist/types/core/flowchartAsTool.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/lib/context-bisect/llmEdgeWeigher.d.ts +8 -0
- package/dist/types/lib/context-bisect/llmEdgeWeigher.d.ts.map +1 -1
- package/dist/types/lib/context-bisect/trajectory.d.ts.map +1 -1
- package/dist/types/lib/context-ledger/contextLedger.d.ts.map +1 -1
- package/dist/types/lib/time-travel/epochs.d.ts +211 -0
- package/dist/types/lib/time-travel/epochs.d.ts.map +1 -0
- package/dist/types/lib/time-travel/index.d.ts +10 -4
- package/dist/types/lib/time-travel/index.d.ts.map +1 -1
- package/dist/types/lib/time-travel/keyedFold.d.ts +113 -0
- package/dist/types/lib/time-travel/keyedFold.d.ts.map +1 -0
- package/dist/types/lib/time-travel/milestoneStops.d.ts +57 -36
- package/dist/types/lib/time-travel/milestoneStops.d.ts.map +1 -1
- package/dist/types/lib/time-travel/receipt.d.ts +497 -0
- package/dist/types/lib/time-travel/receipt.d.ts.map +1 -0
- package/dist/types/lib/time-travel/servedView.d.ts +526 -0
- package/dist/types/lib/time-travel/servedView.d.ts.map +1 -0
- package/dist/types/lib/time-travel/sha256.d.ts +25 -0
- package/dist/types/lib/time-travel/sha256.d.ts.map +1 -0
- package/dist/types/recorders/observability/BoundaryRecorder.d.ts +21 -0
- package/dist/types/recorders/observability/BoundaryRecorder.d.ts.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* keyedFold — the value of ONE state key at one commit, folded from the run's
|
|
4
|
+
* own fold base.
|
|
5
|
+
*
|
|
6
|
+
* Role: Fold. It reads a finished run's commit log plus the base that log
|
|
7
|
+
* was recorded against, and answers "what was `key` at commit `idx`?"
|
|
8
|
+
* It executes nothing, records nothing and stores nothing beside the
|
|
9
|
+
* log. Its answers are DETACHED — deep-frozen before they are cached,
|
|
10
|
+
* because a memo that hands out live references is a fold that lets a
|
|
11
|
+
* reader rewrite the run (`keyedFold.ts` · `freezeDeep`).
|
|
12
|
+
* Reads: a `FoldSource` — a snapshot (`commitLog` + `initialState`) or a
|
|
13
|
+
* subflow subtree (`history` + `initialState`), through footprintjs's
|
|
14
|
+
* own `stateAt` (for the base) and `applySmartMerge` (for the replay).
|
|
15
|
+
* Emits: N/A.
|
|
16
|
+
*
|
|
17
|
+
* ── WHY THIS FILE EXISTS ───────────────────────────────────────────────────
|
|
18
|
+
* A commit log stores DIFFS. `commitValueAt` folds them for one key and says
|
|
19
|
+
* so in its own docstring: a value that came from the run's INITIAL state and
|
|
20
|
+
* was never `set` again folds to ABSENT, because the log alone cannot see the
|
|
21
|
+
* pre-run base.
|
|
22
|
+
*
|
|
23
|
+
* That is not an edge case here. A RESUMED agent run is a fresh executor
|
|
24
|
+
* seeded from `checkpoint.sharedState`, so the entire pre-pause world —
|
|
25
|
+
* `systemPromptInjections`, `history`, `dynamicToolSchemas`, every build-time
|
|
26
|
+
* constant `seed` wrote before the pause — is base, not log. Folding the
|
|
27
|
+
* resumed run with `commitValueAt` returns an empty system prompt and a
|
|
28
|
+
* truncated conversation and declares no gap: a confident falsehood about the
|
|
29
|
+
* one question this family exists to answer. footprintjs 9.17 shipped
|
|
30
|
+
* `RuntimeSnapshot.initialState` and `stateAt` for exactly this, and every
|
|
31
|
+
* read in `epochs.ts` goes through here so it lands on that base.
|
|
32
|
+
*
|
|
33
|
+
* ── WHY NOT JUST CALL `stateAt` PER READ ───────────────────────────────────
|
|
34
|
+
* `stateAt` folds the WHOLE state, and it clones that state once per bundle
|
|
35
|
+
* (`applySmartMerge` opens with a `structuredClone` of its base). One fold is
|
|
36
|
+
* therefore O(commits × state size) — measured on a real 100-turn agent
|
|
37
|
+
* (1,719 commits, 101 epochs): 323 ms for a single fold, and 11.7 s to fold
|
|
38
|
+
* once per epoch, against 78 ms for the whole per-epoch scrub before this
|
|
39
|
+
* release. A per-epoch reader cannot pay that.
|
|
40
|
+
*
|
|
41
|
+
* So `stateAt` is used for the ONE thing only it can give — the fold base and
|
|
42
|
+
* the honest `basis` verdict, at `commitIdx: -1`, where it folds nothing and
|
|
43
|
+
* clones the base once — and the forward replay is done per key, through
|
|
44
|
+
* footprintjs's own `applySmartMerge`. There is deliberately no second
|
|
45
|
+
* implementation of the trace verbs here: every `set` / `append` / `merge` /
|
|
46
|
+
* `delete` is applied by the same function the live commit uses.
|
|
47
|
+
* `keyed-fold-equivalence.test.ts` pins the result against `stateAt` itself on
|
|
48
|
+
* real runs, including a resumed one and a merge with no `set` anchor — the
|
|
49
|
+
* one shape that cannot be folded at all without the base.
|
|
50
|
+
*/
|
|
51
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
52
|
+
exports.keyedFold = void 0;
|
|
53
|
+
const advanced_1 = require("footprintjs/advanced");
|
|
54
|
+
const trace_1 = require("footprintjs/trace");
|
|
55
|
+
/**
|
|
56
|
+
* The delimiter footprintjs joins nested trace paths with — the engine's own
|
|
57
|
+
* `DELIM`. Mirrored rather than imported because it is not on any public
|
|
58
|
+
* barrel.
|
|
59
|
+
*
|
|
60
|
+
* Today's scope facade records a nested write (`scope.profile.city = …`) as a
|
|
61
|
+
* MERGE on the top-level key rather than as a delimited path, so this is
|
|
62
|
+
* DEFENCE and not a hot path. It is still load-bearing: a delimited path must
|
|
63
|
+
* be attributed to its ROOT key, or a write to `history<DELIM>0<DELIM>content`
|
|
64
|
+
* would be filed under a key nobody asks for and dropped from every fold.
|
|
65
|
+
*/
|
|
66
|
+
const PATH_DELIM = '\u001F';
|
|
67
|
+
/** The top-level state key a trace path belongs to. */
|
|
68
|
+
function rootKeyOf(path) {
|
|
69
|
+
const cut = path.indexOf(PATH_DELIM);
|
|
70
|
+
return cut < 0 ? path : path.slice(0, cut);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Deep-freeze in place, skipping subtrees that are frozen already.
|
|
74
|
+
*
|
|
75
|
+
* ── A FOLD RESULT IS DETACHED, OR IT IS NOT A FOLD ─────────────────────────
|
|
76
|
+
* This module memoizes: `valueAt` hands the SAME object back on every call for
|
|
77
|
+
* the same question, and the forward cursor seeds the replay of every LATER
|
|
78
|
+
* index from that very object. Handing out a live reference therefore does not
|
|
79
|
+
* merely risk a surprise — a consumer that edits what it was handed silently
|
|
80
|
+
* rewrites what every later epoch reports was served, which is the one thing
|
|
81
|
+
* this family exists to be trusted about.
|
|
82
|
+
*
|
|
83
|
+
* It was not a hazard in the reader this module replaced. footprintjs's
|
|
84
|
+
* `commitValueAt` clones per call, so the same edit was harmless there; the
|
|
85
|
+
* memo is what introduced it, so the memo is where it is closed.
|
|
86
|
+
*
|
|
87
|
+
* FREEZING RATHER THAN CLONING PER READ, for two reasons. Cloning per read is
|
|
88
|
+
* exactly the cost the forward cursor exists to avoid — it puts an O(value)
|
|
89
|
+
* copy back on every question, on the hot path of a scrub. And freezing is the
|
|
90
|
+
* answer footprintjs itself already gives: `stateAt` returns
|
|
91
|
+
* `freezeDeep(structuredClone(out))`, so a caller that moves between the two
|
|
92
|
+
* folds meets one contract instead of two.
|
|
93
|
+
*
|
|
94
|
+
* The forward replay is unaffected: `applySmartMerge` opens with a
|
|
95
|
+
* `structuredClone` of its base, and a clone of a frozen value is not frozen.
|
|
96
|
+
*
|
|
97
|
+
* MEASURED, so the choice is not a guess, and reported because it is not free.
|
|
98
|
+
* On a 601-epoch run (10,219 commits) the whole per-epoch scrub — every
|
|
99
|
+
* epoch's system pieces, conversation and tool list, folded and rebuilt —
|
|
100
|
+
* costs 1,141 ms with the freeze and 930 ms without it: +211 ms, +23%, on a
|
|
101
|
+
* scrub that cost 20.8 s two fixes ago. The batch form moves 919 ms to
|
|
102
|
+
* 1,116 ms. The alternative is DEARER, not cheaper: on the same structure
|
|
103
|
+
* (1,200 messages) `structuredClone` costs 3.6x what this walk does — 0.94 ms
|
|
104
|
+
* against 0.26 ms — and a copy would have to run once per READ where the
|
|
105
|
+
* freeze runs once per memoized answer. A mutable answer is not worth 23%, and
|
|
106
|
+
* buying safety by copying would cost more than 23%.
|
|
107
|
+
*/
|
|
108
|
+
function freezeDeep(value) {
|
|
109
|
+
if (value === null || typeof value !== 'object')
|
|
110
|
+
return value;
|
|
111
|
+
Object.freeze(value);
|
|
112
|
+
for (const key of Object.getOwnPropertyNames(value)) {
|
|
113
|
+
const inner = value[key];
|
|
114
|
+
if (inner !== null && typeof inner === 'object' && !Object.isFrozen(inner))
|
|
115
|
+
freezeDeep(inner);
|
|
116
|
+
}
|
|
117
|
+
return value;
|
|
118
|
+
}
|
|
119
|
+
/** The bundles, from whichever field this source spells them in. */
|
|
120
|
+
function commitsOf(source) {
|
|
121
|
+
if (!source)
|
|
122
|
+
return [];
|
|
123
|
+
if (Array.isArray(source.commitLog))
|
|
124
|
+
return source.commitLog;
|
|
125
|
+
if (Array.isArray(source.history))
|
|
126
|
+
return source.history;
|
|
127
|
+
return [];
|
|
128
|
+
}
|
|
129
|
+
/** Build the per-key touch index in ONE pass over the log. */
|
|
130
|
+
function indexTouches(log) {
|
|
131
|
+
const index = new Map();
|
|
132
|
+
for (let at = 0; at < log.length; at++) {
|
|
133
|
+
const bundle = log[at];
|
|
134
|
+
if (!bundle)
|
|
135
|
+
continue;
|
|
136
|
+
for (const entry of bundle.trace ?? []) {
|
|
137
|
+
const path = entry.path;
|
|
138
|
+
if (typeof path !== 'string')
|
|
139
|
+
continue;
|
|
140
|
+
const key = rootKeyOf(path);
|
|
141
|
+
const verb = entry.verb;
|
|
142
|
+
const touch = {
|
|
143
|
+
at,
|
|
144
|
+
bundle,
|
|
145
|
+
entry,
|
|
146
|
+
anchors: path === key && (verb === 'set' || verb === 'delete'),
|
|
147
|
+
};
|
|
148
|
+
const list = index.get(key);
|
|
149
|
+
if (list === undefined)
|
|
150
|
+
index.set(key, [touch]);
|
|
151
|
+
else
|
|
152
|
+
list.push(touch);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return index;
|
|
156
|
+
}
|
|
157
|
+
/** The last position in `touches` whose bundle index is `<= idx`, or `-1`. */
|
|
158
|
+
function lastTouchAt(touches, idx) {
|
|
159
|
+
let lo = 0;
|
|
160
|
+
let hi = touches.length - 1;
|
|
161
|
+
let found = -1;
|
|
162
|
+
while (lo <= hi) {
|
|
163
|
+
const mid = (lo + hi) >> 1;
|
|
164
|
+
if (touches[mid].at <= idx) {
|
|
165
|
+
found = mid;
|
|
166
|
+
lo = mid + 1;
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
hi = mid - 1;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return found;
|
|
173
|
+
}
|
|
174
|
+
/** Memoized folds, one per source object. */
|
|
175
|
+
const FOLDS = new WeakMap();
|
|
176
|
+
/**
|
|
177
|
+
* The keyed fold for one source, memoized on the source object.
|
|
178
|
+
*
|
|
179
|
+
* Memoized because a per-epoch reader asks the same source for the same keys
|
|
180
|
+
* at many different commits, and the index is the expensive half: building it
|
|
181
|
+
* is one pass over the log, and answering a read afterwards is a binary search
|
|
182
|
+
* plus a replay from the nearest full-value write (normally exactly one).
|
|
183
|
+
*
|
|
184
|
+
* @example
|
|
185
|
+
* ```ts
|
|
186
|
+
* import { keyedFold } from 'agentfootprint';
|
|
187
|
+
*
|
|
188
|
+
* const fold = keyedFold(agent.getSnapshot());
|
|
189
|
+
* fold.basis; // 'initial+log' — the base travelled
|
|
190
|
+
* fold.valueAt('history', 12); // the conversation after the 13th commit
|
|
191
|
+
* fold.valueAt('history', -1); // …and what the run STARTED from
|
|
192
|
+
* ```
|
|
193
|
+
*/
|
|
194
|
+
function keyedFold(source) {
|
|
195
|
+
if (source !== null && typeof source === 'object') {
|
|
196
|
+
const memo = FOLDS.get(source);
|
|
197
|
+
if (memo !== undefined)
|
|
198
|
+
return memo;
|
|
199
|
+
}
|
|
200
|
+
const log = commitsOf(source);
|
|
201
|
+
// `stateAt` at -1 folds NOTHING: it clones the base once and reports whether
|
|
202
|
+
// there was one. That is the whole reason to call the engine's own fold here
|
|
203
|
+
// rather than read `initialState` directly — the `basis` verdict is its
|
|
204
|
+
// answer to give, and a recording that travelled without its base must be
|
|
205
|
+
// able to say so.
|
|
206
|
+
const seed = (0, trace_1.stateAt)(source, -1);
|
|
207
|
+
const index = indexTouches(log);
|
|
208
|
+
const answers = new Map();
|
|
209
|
+
/** Per key, the furthest replay position reached and the value there — see
|
|
210
|
+
* the forward-cursor note in `compute`. */
|
|
211
|
+
const cursor = new Map();
|
|
212
|
+
/** One touch applied, through the ENGINE's verb switch, over a state
|
|
213
|
+
* projected to this key alone. `applySmartMerge` navigates from the root,
|
|
214
|
+
* so a delimited path lands exactly where it did in the real fold. */
|
|
215
|
+
const step = (key, value, touch) => (0, advanced_1.applySmartMerge)({ [key]: value }, touch.bundle.updates, touch.bundle.overwrite, [
|
|
216
|
+
touch.entry,
|
|
217
|
+
])[key];
|
|
218
|
+
const compute = (key, idx) => {
|
|
219
|
+
const touches = index.get(key);
|
|
220
|
+
const last = touches === undefined ? -1 : lastTouchAt(touches, idx);
|
|
221
|
+
// The base's own value. `stateAt` already deep-freezes what it returns, so
|
|
222
|
+
// this is a no-op today — asserted rather than assumed, because the law
|
|
223
|
+
// this module states about its answers must not rest on another module
|
|
224
|
+
// continuing to keep it.
|
|
225
|
+
if (touches === undefined || last < 0)
|
|
226
|
+
return freezeDeep(seed.state[key]);
|
|
227
|
+
// Anchor at the latest write that fully determines the key on its own;
|
|
228
|
+
// everything before it — the base included — cannot be read back through
|
|
229
|
+
// it. With no anchor the base IS the starting value, which is the whole
|
|
230
|
+
// fix: `commitValueAt` starts from `undefined` here and folds a fiction.
|
|
231
|
+
let start = 0;
|
|
232
|
+
let anchored = false;
|
|
233
|
+
for (let i = last; i >= 0; i--) {
|
|
234
|
+
if (touches[i].anchors) {
|
|
235
|
+
start = i;
|
|
236
|
+
anchored = true;
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
let from = start;
|
|
241
|
+
let value = anchored ? undefined : seed.state[key];
|
|
242
|
+
// THE FORWARD CURSOR, and why this module would be slower than the reader
|
|
243
|
+
// it replaces without it. An agent's `history` is `set` ONCE and appended
|
|
244
|
+
// to every turn, so a query at turn k has to replay k appends from the
|
|
245
|
+
// anchor — and a per-epoch scrub asks E times, which is O(E²) replays of a
|
|
246
|
+
// growing array. Resuming from the furthest position already reached makes
|
|
247
|
+
// the whole scrub O(E) replays instead, because a scrub walks forward.
|
|
248
|
+
// (Measured on a 96-turn run: 201 ms per scrub without this, 40 ms with.)
|
|
249
|
+
// A backward or out-of-order query simply falls back to the anchor, which
|
|
250
|
+
// is correct and no worse than never having cached anything.
|
|
251
|
+
const held = cursor.get(key);
|
|
252
|
+
if (held !== undefined && held.position >= start && held.position <= last) {
|
|
253
|
+
from = held.position + 1;
|
|
254
|
+
value = held.value;
|
|
255
|
+
}
|
|
256
|
+
for (let i = from; i <= last; i++)
|
|
257
|
+
value = step(key, value, touches[i]);
|
|
258
|
+
// Frozen HERE, before anything else can hold it: the cursor keeps this very
|
|
259
|
+
// object as the seed for every later index, and `valueAt` memoizes it. One
|
|
260
|
+
// freeze covers both — `keyedFold.ts` · `freezeDeep`.
|
|
261
|
+
freezeDeep(value);
|
|
262
|
+
cursor.set(key, { position: last, value });
|
|
263
|
+
return value;
|
|
264
|
+
};
|
|
265
|
+
const fold = {
|
|
266
|
+
basis: seed.basis,
|
|
267
|
+
length: log.length,
|
|
268
|
+
valueAt(key, idx) {
|
|
269
|
+
const at = Math.min(Math.floor(idx), log.length - 1);
|
|
270
|
+
let perKey = answers.get(key);
|
|
271
|
+
if (perKey === undefined) {
|
|
272
|
+
perKey = new Map();
|
|
273
|
+
answers.set(key, perKey);
|
|
274
|
+
}
|
|
275
|
+
if (perKey.has(at))
|
|
276
|
+
return perKey.get(at);
|
|
277
|
+
const value = compute(key, at);
|
|
278
|
+
perKey.set(at, value);
|
|
279
|
+
return value;
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
// The fold OBJECT is memoized too — the same one every caller of `keyedFold`
|
|
283
|
+
// gets for this source — so it is frozen for the same reason its answers are.
|
|
284
|
+
Object.freeze(fold);
|
|
285
|
+
if (source !== null && typeof source === 'object')
|
|
286
|
+
FOLDS.set(source, fold);
|
|
287
|
+
return fold;
|
|
288
|
+
}
|
|
289
|
+
exports.keyedFold = keyedFold;
|
|
290
|
+
//# sourceMappingURL=keyedFold.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keyedFold.js","sourceRoot":"","sources":["../../../src/lib/time-travel/keyedFold.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,16 +9,26 @@
|
|
|
9
9
|
* it), and `conventions.ts` · `milestoneFor` for the domain vocabulary.
|
|
10
10
|
* Emits: N/A.
|
|
11
11
|
*
|
|
12
|
-
* WHY THIS FILE EXISTS. footprintjs
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
12
|
+
* WHY THIS FILE EXISTS. footprintjs ships the reader's cursor (`timeTravel`)
|
|
13
|
+
* and one stop grammar — `commitStops`, one stop per executed stage — because
|
|
14
|
+
* that is the only grammar the substrate itself knows. It does NOT know what an
|
|
15
|
+
* LLM turn is. agentfootprint does: `milestoneFor` has classified a local stage
|
|
16
|
+
* id into an iteration / slot / llm-turn / tool-call / decision since 9.x, and
|
|
17
|
+
* until 9.87 every consumer that wanted a milestone slider mapped that
|
|
18
|
+
* classifier onto commits by hand. This is that mapping, once, on the strategy
|
|
19
|
+
* seam the port opened — so the agent SUPPLIES the stops for its own runs and
|
|
20
|
+
* every reader gets the same axis.
|
|
21
|
+
*
|
|
22
|
+
* WHAT IT IS (9.89.0). A FILTER over the port's own axis. footprintjs 9.18
|
|
23
|
+
* shipped `filterStops(stops, keep)` — the bookend guard, the re-partition and
|
|
24
|
+
* a `meta` slot on the stop — because two consumers had each re-derived all
|
|
25
|
+
* three by hand against 9.17, this file among them. The hand-rolled copies are
|
|
26
|
+
* gone: the one owner of the `[start, …stages, end]` contract is the library
|
|
27
|
+
* that returns it, and this strategy is one expression over it. The axis is
|
|
28
|
+
* byte-for-byte the 9.88.0 axis (`test/lib/time-travel/milestone-stops-equivalence.test.ts` pins
|
|
29
|
+
* that against a verbatim copy of the 9.88.0 implementation); what is new is that the milestone now RIDES on the
|
|
30
|
+
* stop as `meta`, and that a start which absorbed pre-milestone stages says so
|
|
31
|
+
* with `prologue: true`.
|
|
22
32
|
*
|
|
23
33
|
* WHAT IT IS NOT. Not a second cursor. A strategy only says where the one
|
|
24
34
|
* cursor may rest; `timeTravel` still owns the position, the folds and the
|
|
@@ -30,19 +40,32 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
30
40
|
exports.milestoneStopsStrategy = exports.milestoneStops = exports.milestoneOf = void 0;
|
|
31
41
|
const trace_1 = require("footprintjs/trace");
|
|
32
42
|
const conventions_js_1 = require("../../conventions.js");
|
|
43
|
+
const MILESTONE_KINDS = [
|
|
44
|
+
'iteration',
|
|
45
|
+
'slot',
|
|
46
|
+
'llm-turn',
|
|
47
|
+
'tool-call',
|
|
48
|
+
'decision',
|
|
49
|
+
];
|
|
50
|
+
/** Is this `meta` a {@link Milestone} — ours, not another strategy's? */
|
|
51
|
+
function isMilestone(meta) {
|
|
52
|
+
if (meta === null || typeof meta !== 'object')
|
|
53
|
+
return false;
|
|
54
|
+
const { kind, label } = meta;
|
|
55
|
+
return typeof label === 'string' && MILESTONE_KINDS.includes(kind);
|
|
56
|
+
}
|
|
33
57
|
/**
|
|
34
58
|
* The milestone a stop stands for, or `null` when it stands for none (the
|
|
35
59
|
* `'start'` / `'end'` bookends).
|
|
36
60
|
*
|
|
37
|
-
*
|
|
38
|
-
* `
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* truth here, only a second reading of the one there is.
|
|
61
|
+
* A stop that {@link milestoneStops} made carries its milestone as
|
|
62
|
+
* `stop.meta`, and that is what is read — the classification the strategy
|
|
63
|
+
* made when it put the stop on the axis, not a second run of the classifier.
|
|
64
|
+
* A stop from ANOTHER strategy — the port's own `commitStops`, or a consumer's
|
|
65
|
+
* filter that kept the stop without a milestone meta — has none, so the answer
|
|
66
|
+
* falls back to where it always came from: `milestoneFor` over the stop's
|
|
67
|
+
* `runtimeStageId`. Same classifier, same answer; a `meta` of some other
|
|
68
|
+
* vocabulary is not mistaken for ours.
|
|
46
69
|
*
|
|
47
70
|
* @example
|
|
48
71
|
* ```ts
|
|
@@ -51,6 +74,8 @@ const conventions_js_1 = require("../../conventions.js");
|
|
|
51
74
|
* ```
|
|
52
75
|
*/
|
|
53
76
|
function milestoneOf(stop) {
|
|
77
|
+
if (isMilestone(stop.meta))
|
|
78
|
+
return stop.meta;
|
|
54
79
|
return stop.runtimeStageId ? (0, conventions_js_1.milestoneFor)(stop.runtimeStageId) : null;
|
|
55
80
|
}
|
|
56
81
|
exports.milestoneOf = milestoneOf;
|
|
@@ -66,9 +91,11 @@ exports.milestoneOf = milestoneOf;
|
|
|
66
91
|
* parallel fork child commits twice and siblings interleave — every repeat is
|
|
67
92
|
* an empty bundle), the authoritative mount set read off the execution tree,
|
|
68
93
|
* the `'start'` / `'end'` bookends, and the id-less leading commit that carries
|
|
69
|
-
* a subflow's `inputMapper` seed.
|
|
70
|
-
*
|
|
71
|
-
*
|
|
94
|
+
* a subflow's `inputMapper` seed. And since 9.89.0 the COMPOSITION itself is
|
|
95
|
+
* the port's too: `filterStops` checks the `[start, …stages, end]` shape,
|
|
96
|
+
* keeps what `keep` keeps, and re-partitions the log. A second implementation
|
|
97
|
+
* of any of that would be a second chance to disagree with the library about
|
|
98
|
+
* what a stage is.
|
|
72
99
|
*
|
|
73
100
|
* **The survivors re-partition the log.** A stage that classifies `null` is not
|
|
74
101
|
* a stop, but its commits still happened, so they are folded into the stop that
|
|
@@ -79,14 +106,21 @@ exports.milestoneOf = milestoneOf;
|
|
|
79
106
|
* so on a drilled cursor `'start'` reads as "what this subflow began with,
|
|
80
107
|
* after its plumbing ran".
|
|
81
108
|
*
|
|
82
|
-
* **What that costs `'start'
|
|
83
|
-
* base: the state before ANY stage ran. Here it
|
|
84
|
-
* before the first milestone, so `stateAt(start)`
|
|
85
|
-
* milestone READ, not the run's raw base — a real agent
|
|
86
|
-
* keys in `seed` before anything a reader would scrub
|
|
87
|
-
* answer for this axis (the stops must still partition
|
|
88
|
-
*
|
|
89
|
-
*
|
|
109
|
+
* **What that costs `'start'`, and the flag that says so.** On footprintjs's
|
|
110
|
+
* own axis `'start'` is the fold base: the state before ANY stage ran. Here it
|
|
111
|
+
* absorbs every stage that ran before the first milestone, so `stateAt(start)`
|
|
112
|
+
* is the state the first milestone READ, not the run's raw base — a real agent
|
|
113
|
+
* seeds a couple of dozen keys in `seed` before anything a reader would scrub
|
|
114
|
+
* to. That is the right answer for this axis (the stops must still partition
|
|
115
|
+
* the log), and since 9.18 the port marks it: the returned start carries
|
|
116
|
+
* `prologue: true` whenever it absorbed a stage, so a renderer that means
|
|
117
|
+
* "before anything ran" checks `kind === 'start' && !prologue` instead of
|
|
118
|
+
* assuming it from the kind.
|
|
119
|
+
*
|
|
120
|
+
* **The milestone rides on the stop.** Every kept stop carries its
|
|
121
|
+
* classification as `meta` — `Stop<Milestone>` — so a reader asks
|
|
122
|
+
* `stop.meta?.kind` (or {@link milestoneOf}, which reads the same slot) rather
|
|
123
|
+
* than re-running the classifier over the id. The bookends carry none.
|
|
90
124
|
*
|
|
91
125
|
* **A log with no milestones in it at all.** A non-empty log the classifier
|
|
92
126
|
* recognises nothing in — a non-agent chart handed this strategy — yields the
|
|
@@ -114,71 +148,30 @@ exports.milestoneOf = milestoneOf;
|
|
|
114
148
|
* @example
|
|
115
149
|
* ```ts
|
|
116
150
|
* import { timeTravel } from 'footprintjs/trace';
|
|
117
|
-
* import { milestoneStopsStrategy
|
|
151
|
+
* import { milestoneStopsStrategy } from 'agentfootprint';
|
|
118
152
|
*
|
|
119
153
|
* const cursor = timeTravel(agent.getSnapshot()!, { strategy: milestoneStopsStrategy });
|
|
120
|
-
* cursor.stops.map((s) => [s.label,
|
|
154
|
+
* cursor.stops.map((s) => [s.label, s.meta?.kind]);
|
|
121
155
|
* // measured on a two-turn `dynamic` run — 42 commits, 15 stops:
|
|
122
156
|
* // [['Run start', undefined], ['Iteration', 'iteration'],
|
|
123
157
|
* // ['System prompt', 'slot'], ['Messages', 'slot'], ['Tools', 'slot'],
|
|
124
158
|
* // ['LLM turn', 'llm-turn'], ['Route', 'decision'], ['Tool call', 'tool-call'],
|
|
125
159
|
* // … the second turn …, ['Run end', undefined]]
|
|
160
|
+
* cursor.stops[0].prologue; // true — `seed` and the plumbing ran before the first Iteration
|
|
126
161
|
* ```
|
|
127
162
|
*/
|
|
128
163
|
function milestoneStops(commitLog, executionTree) {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
return
|
|
132
|
-
|
|
133
|
-
// log. The bookends are kept verbatim in KIND and re-partitioned below; only
|
|
134
|
-
// the stages in between are filtered.
|
|
135
|
-
//
|
|
136
|
-
// CHECK THE PROPERTY, NOT MERE PRESENCE. `Stop[]` does not pin that shape in
|
|
137
|
-
// the type, and everything below rests on it: `'start'` is the only stop that
|
|
138
|
-
// may open at `-1` (the fold base no commit index reaches) and `'end'` is the
|
|
139
|
-
// only one that folds the whole log. If a future `commitStops` returned some
|
|
140
|
-
// other shape, a presence check would take the first and last STAGE stops for
|
|
141
|
-
// bookends — the first would silently inherit start's `-1` arithmetic and keep
|
|
142
|
-
// its raw stage label, and the last milestone would lose its milestone label.
|
|
143
|
-
// Refusing loudly is the honest answer: an empty axis would be
|
|
144
|
-
// indistinguishable from "this run committed nothing", which is a different
|
|
145
|
-
// fact about a different run.
|
|
146
|
-
const [start, ...rest] = perStage;
|
|
147
|
-
const end = rest.pop();
|
|
148
|
-
if (!start || start.kind !== 'start' || !end || end.kind !== 'end') {
|
|
149
|
-
throw new Error('milestoneStops: commitStops returned an unexpected shape — expected ' +
|
|
150
|
-
`[start, …stages, end], got kinds [${perStage.map((s) => s.kind).join(', ')}]. ` +
|
|
151
|
-
'This is a footprintjs contract change, not something a run can cause.');
|
|
152
|
-
}
|
|
153
|
-
const kept = rest
|
|
154
|
-
.map((stop) => ({ stop, milestone: (0, conventions_js_1.milestoneFor)(stop.runtimeStageId) }))
|
|
155
|
-
.filter((row) => row.milestone !== null);
|
|
156
|
-
const first = kept[0];
|
|
157
|
-
const stops = [
|
|
158
|
-
{
|
|
159
|
-
...start,
|
|
160
|
-
step: 0,
|
|
161
|
-
// Everything before the first milestone folds into `'start'`.
|
|
162
|
-
lastCommitIdx: (first ? first.stop.commitIdx : commitLog.length) - 1,
|
|
163
|
-
},
|
|
164
|
-
];
|
|
165
|
-
for (const [i, { stop, milestone }] of kept.entries()) {
|
|
166
|
-
const next = kept[i + 1];
|
|
167
|
-
stops.push({
|
|
168
|
-
...stop,
|
|
169
|
-
step: stops.length,
|
|
170
|
-
// Absorb the non-milestone stages that ran after this one.
|
|
171
|
-
lastCommitIdx: next ? next.stop.commitIdx - 1 : commitLog.length - 1,
|
|
172
|
-
label: milestone.label,
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
stops.push({ ...end, step: stops.length });
|
|
176
|
-
return stops;
|
|
164
|
+
return (0, trace_1.filterStops)((0, trace_1.commitStops)(commitLog, executionTree), (stop) => {
|
|
165
|
+
const milestone = (0, conventions_js_1.milestoneFor)(stop.runtimeStageId);
|
|
166
|
+
return milestone ? { label: milestone.label, meta: milestone } : null;
|
|
167
|
+
});
|
|
177
168
|
}
|
|
178
169
|
exports.milestoneStops = milestoneStops;
|
|
179
170
|
/**
|
|
180
171
|
* `milestoneStops` as a footprintjs `TimeTravelStrategy` — what you hand
|
|
181
|
-
* `timeTravel(snapshot, { strategy })`.
|
|
172
|
+
* `timeTravel(snapshot, { strategy })`. Typed over {@link Milestone}, so the
|
|
173
|
+
* cursor's stops are `Stop<Milestone>` and `cursor.at()?.meta?.kind` is typed;
|
|
174
|
+
* it is still assignable wherever a bare `TimeTravelStrategy` is expected.
|
|
182
175
|
*/
|
|
183
176
|
exports.milestoneStopsStrategy = { stopsFor: milestoneStops };
|
|
184
177
|
//# sourceMappingURL=milestoneStops.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"milestoneStops.js","sourceRoot":"","sources":["../../../src/lib/time-travel/milestoneStops.ts"],"names":[],"mappings":";AAAA
|
|
1
|
+
{"version":3,"file":"milestoneStops.js","sourceRoot":"","sources":["../../../src/lib/time-travel/milestoneStops.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;;;AAGH,6CAA6D;AAE7D,yDAAwF;AAExF,MAAM,eAAe,GAA6B;IAChD,WAAW;IACX,MAAM;IACN,UAAU;IACV,WAAW;IACX,UAAU;CACX,CAAC;AAEF,yEAAyE;AACzE,SAAS,WAAW,CAAC,IAAa;IAChC,IAAI,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5D,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,IAA2C,CAAC;IACpE,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAK,eAAsC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AAC7F,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,SAAgB,WAAW,CAAC,IAAmB;IAC7C,IAAI,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC,IAAI,CAAC;IAC7C,OAAO,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,IAAA,6BAAY,EAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACxE,CAAC;AAHD,kCAGC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgFG;AACH,SAAgB,cAAc,CAC5B,SAAkC,EAClC,aAA6B;IAE7B,OAAO,IAAA,mBAAW,EAAY,IAAA,mBAAW,EAAC,SAAS,EAAE,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE;QAC5E,MAAM,SAAS,GAAG,IAAA,6BAAY,EAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QACpD,OAAO,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IACxE,CAAC,CAAC,CAAC;AACL,CAAC;AARD,wCAQC;AAED;;;;;GAKG;AACU,QAAA,sBAAsB,GAAkC,EAAE,QAAQ,EAAE,cAAc,EAAE,CAAC"}
|