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,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* epochs — where one epoch's committed pieces live, in either chart shape.
|
|
3
|
+
*
|
|
4
|
+
* Role: Fold. It reads a finished run's recording and answers one question:
|
|
5
|
+
* "for iteration k, which commit log holds the call, at which index,
|
|
6
|
+
* and against which fold base?" It executes nothing, records nothing
|
|
7
|
+
* and stores nothing beside the log.
|
|
8
|
+
* Reads: a recording (`commitLog`, `initialState`, `subflowResults`), through
|
|
9
|
+
* footprintjs's own id parsers and this folder's `keyedFold.ts`.
|
|
10
|
+
* Emits: N/A.
|
|
11
|
+
*
|
|
12
|
+
* ── WHY THIS FILE EXISTS ───────────────────────────────────────────────────
|
|
13
|
+
* An agent's iteration — an EPOCH — commits to a different place depending on
|
|
14
|
+
* how the chart was built. Under `reactMode: 'dynamic'` the `call-llm` stage is
|
|
15
|
+
* a parent-level stage and its bundle is in the run's own log. Under
|
|
16
|
+
* `'dynamic-grouped'` each turn runs inside an `sf-llm-call` subflow, which has
|
|
17
|
+
* its own isolated runtime and its own log, retained per iteration under
|
|
18
|
+
* `subflowResults['sf-llm-call#k'].treeContext.history`.
|
|
19
|
+
*
|
|
20
|
+
* That fork was written out THREE times — `contextLedger.ts` had a private
|
|
21
|
+
* `llmCallMountKeys`, `trajectory.ts` had a byte-twin of it, and a rebuild of
|
|
22
|
+
* what the model was served would have been the third. Three copies of "which
|
|
23
|
+
* log is this key in?" is three chances to disagree about which iteration a
|
|
24
|
+
* value belonged to, on the one question the whole family exists to answer.
|
|
25
|
+
* So the fork lives here once, and every reader asks it.
|
|
26
|
+
*
|
|
27
|
+
* ── WHAT AN EPOCH NUMBER IS ────────────────────────────────────────────────
|
|
28
|
+
* The committed `iteration` value in effect at the call — the same number
|
|
29
|
+
* `callLLM` put on the receipt's `basis.epoch` and on every `iteration_start`
|
|
30
|
+
* event. It starts at 1. It is READ, not counted: a run that pauses and
|
|
31
|
+
* resumes, or one whose first turn is not its first stage, still names its
|
|
32
|
+
* epochs the way the run itself named them.
|
|
33
|
+
*
|
|
34
|
+
* ── EVERY READ FOLDS FROM THE BASE ─────────────────────────────────────────
|
|
35
|
+
* A location carries the FOLD SOURCE its log was recorded against, not just
|
|
36
|
+
* the log, and every read below goes through `keyedFold.ts`. A resumed run is
|
|
37
|
+
* a fresh executor seeded from `checkpoint.sharedState`, so most of what the
|
|
38
|
+
* call read is base rather than log; a fold that could not see the base
|
|
39
|
+
* answered "absent" for the whole pre-pause world and said nothing about it.
|
|
40
|
+
* `EpochLocation.basis` is that fold's own verdict, carried out to the reader
|
|
41
|
+
* so a recording that travelled without its base declares a gap instead of a
|
|
42
|
+
* confident empty view.
|
|
43
|
+
*/
|
|
44
|
+
import type { CommitBundle } from 'footprintjs/advanced';
|
|
45
|
+
import { type FoldBasis, type FoldSourceLike } from './keyedFold.js';
|
|
46
|
+
/** The shape a recording has to have for any of this to work — a snapshot, or
|
|
47
|
+
* anything that hands one over. */
|
|
48
|
+
export interface RecordingLike {
|
|
49
|
+
readonly commitLog?: readonly CommitBundle[];
|
|
50
|
+
/** The log's fold base (`RuntimeSnapshot.initialState`, footprintjs 9.17+).
|
|
51
|
+
* Absent on a recording that travelled without it — see
|
|
52
|
+
* {@link EpochLocation.basis}. */
|
|
53
|
+
readonly initialState?: Record<string, unknown>;
|
|
54
|
+
readonly executionTree?: unknown;
|
|
55
|
+
readonly subflowResults?: Record<string, unknown>;
|
|
56
|
+
}
|
|
57
|
+
/** One epoch, located: which log holds its call, where in that log, and what
|
|
58
|
+
* that log folds against. */
|
|
59
|
+
export interface EpochLocation {
|
|
60
|
+
/**
|
|
61
|
+
* The committed `iteration` at the call. 1-based, and READ rather than
|
|
62
|
+
* counted — a run that pauses and resumes still names its epochs the way the
|
|
63
|
+
* run itself named them.
|
|
64
|
+
*
|
|
65
|
+
* ONE fallback, and it is a count: when the fold cannot read `iteration` at
|
|
66
|
+
* all (a recording whose base did not travel, on a run that never re-`set`
|
|
67
|
+
* it), this is the location's position in run order instead. The tell is
|
|
68
|
+
* {@link EpochLocation.basis} / {@link EpochLocation.runBasis} reading
|
|
69
|
+
* `'log-only'`, which is also what makes `servedView.ts` raise
|
|
70
|
+
* `no-fold-base`. A view must have some number to be addressed by; this one
|
|
71
|
+
* is the honest second choice, not a claim about the record.
|
|
72
|
+
*/
|
|
73
|
+
readonly epoch: number;
|
|
74
|
+
/** The `call-llm` stage's `runtimeStageId` — the llm-turn stop's own id. */
|
|
75
|
+
readonly callRuntimeStageId: string;
|
|
76
|
+
/** The log the call committed to: the run's own under `'dynamic'`, the
|
|
77
|
+
* turn's inner history under `'dynamic-grouped'`. */
|
|
78
|
+
readonly log: readonly CommitBundle[];
|
|
79
|
+
/** The call's ARRAY index in {@link EpochLocation.log}. */
|
|
80
|
+
readonly callIdx: number;
|
|
81
|
+
/** The RUN's log, always. Run constants (a build-time fact seeded once) are
|
|
82
|
+
* read here, because a grouped turn's inner log never sees them unless the
|
|
83
|
+
* boundary happens to carry them. */
|
|
84
|
+
readonly runLog: readonly CommitBundle[];
|
|
85
|
+
/** The `sf-llm-call` mount this epoch was projected from — set under
|
|
86
|
+
* `'dynamic-grouped'` only, and the tell that `callIdx` is inner-relative. */
|
|
87
|
+
readonly subflowScope?: string;
|
|
88
|
+
/**
|
|
89
|
+
* The fold source {@link EpochLocation.log} belongs to — the log PLUS the
|
|
90
|
+
* base it was recorded against. Reads go through it, never through the bare
|
|
91
|
+
* array, so a value seeded before the run (every key of a resumed run) folds
|
|
92
|
+
* from where it actually came from.
|
|
93
|
+
*/
|
|
94
|
+
readonly source: FoldSourceLike;
|
|
95
|
+
/** The same, for {@link EpochLocation.runLog}. */
|
|
96
|
+
readonly runSource: FoldSourceLike;
|
|
97
|
+
/**
|
|
98
|
+
* How {@link EpochLocation.source} folds. `'log-only'` means the recording
|
|
99
|
+
* travelled without its fold base, so anything the log never `set` reads as
|
|
100
|
+
* absent — `servedView.ts` turns that into a declared gap rather than an
|
|
101
|
+
* empty view.
|
|
102
|
+
*/
|
|
103
|
+
readonly basis: FoldBasis;
|
|
104
|
+
/** The same, for {@link EpochLocation.runSource}. */
|
|
105
|
+
readonly runBasis: FoldBasis;
|
|
106
|
+
/** `false` when this recording carries no RUN log at all — a subtree handed
|
|
107
|
+
* in on its own. Every run constant is then unreadable, which
|
|
108
|
+
* `servedView.ts` declares rather than reads as absent. */
|
|
109
|
+
readonly hasRunLog: boolean;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* A snapshot from whatever the caller had: a runner (`Agent`, `LLMCall`), or
|
|
113
|
+
* the snapshot itself. Duck-typed on purpose — a recording read back from JSON
|
|
114
|
+
* is a plain object and must work exactly like a live one.
|
|
115
|
+
*/
|
|
116
|
+
export declare function recordingOf(source: unknown): RecordingLike | undefined;
|
|
117
|
+
/**
|
|
118
|
+
* The `sf-llm-call` mount keys in `subflowResults`, in loop order.
|
|
119
|
+
*
|
|
120
|
+
* Non-empty ⇒ the run is GROUPED and every turn's pieces are in its own inner
|
|
121
|
+
* log. Empty ⇒ flat, and the run's own log holds everything.
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* ```ts
|
|
125
|
+
* llmCallMountKeys(snapshot.subflowResults); // ['sf-llm-call#4', 'sf-llm-call#9']
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
128
|
+
export declare function llmCallMountKeys(subflowResults: Record<string, unknown> | undefined): string[];
|
|
129
|
+
/**
|
|
130
|
+
* Every epoch in a recording, in run order — the ONE owner of the flat /
|
|
131
|
+
* grouped fork.
|
|
132
|
+
*
|
|
133
|
+
* Returns `[]` for a recording with no LLM call in it at all (an empty log, a
|
|
134
|
+
* chart with no `call-llm` stage). That is a different fact from "the epoch you
|
|
135
|
+
* asked for is not here", which is what {@link epochAt} says with `undefined`.
|
|
136
|
+
*
|
|
137
|
+
* MEMOIZED on the recording object, because the cost of locating epochs is a
|
|
138
|
+
* pass over the log and a per-epoch reader asks for one epoch at a time: before
|
|
139
|
+
* this, scrubbing a 600-turn run epoch by epoch relocated every epoch on every
|
|
140
|
+
* call and took 20.8 s. Holding the snapshot and asking it repeatedly is the
|
|
141
|
+
* shape a reader's UI actually has, so the memo is keyed on exactly that
|
|
142
|
+
* object; a caller that hands in a runner and gets a fresh snapshot each time
|
|
143
|
+
* pays the pass each time, which is the cost of asking a different question.
|
|
144
|
+
*
|
|
145
|
+
* FROZEN, because it is memoized: the array and each location on it are the
|
|
146
|
+
* very objects the next caller gets, so an edit here would be an edit to
|
|
147
|
+
* everybody's answer. The same law `keyedFold.ts` · `freezeDeep` states for a
|
|
148
|
+
* folded value. What is NOT frozen is what a location POINTS AT — `log`,
|
|
149
|
+
* `source`, `runSource` are the caller's own recording, and this file does not
|
|
150
|
+
* get to lock down an object it was merely handed.
|
|
151
|
+
*
|
|
152
|
+
* @example
|
|
153
|
+
* ```ts
|
|
154
|
+
* import { epochLocations } from 'agentfootprint';
|
|
155
|
+
*
|
|
156
|
+
* epochLocations(agent.getSnapshot()!).map((e) => [e.epoch, e.callRuntimeStageId]);
|
|
157
|
+
* // dynamic: [[1, 'call-llm#12'], [2, 'call-llm#31']]
|
|
158
|
+
* // dynamic-grouped: [[1, 'sf-llm-call/call-llm#9'], [2, 'sf-llm-call/call-llm#28']]
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
161
|
+
export declare function epochLocations(source: unknown): readonly EpochLocation[];
|
|
162
|
+
/**
|
|
163
|
+
* One epoch by number, or `undefined` when the run has no such iteration.
|
|
164
|
+
*
|
|
165
|
+
* @example
|
|
166
|
+
* ```ts
|
|
167
|
+
* epochAt(agent.getSnapshot()!, 2)?.callRuntimeStageId; // 'call-llm#31'
|
|
168
|
+
* ```
|
|
169
|
+
*/
|
|
170
|
+
export declare function epochAt(source: unknown, epoch: number): EpochLocation | undefined;
|
|
171
|
+
/**
|
|
172
|
+
* The value of `key` as the epoch's call READ it — folded to just BEFORE the
|
|
173
|
+
* call's own bundle, so a key the call itself writes back (`history` gains the
|
|
174
|
+
* assistant turn; `receipt` is minted) reads as it was on the way in.
|
|
175
|
+
*
|
|
176
|
+
* Folded from the run's base, not from the log alone: on a resumed run the
|
|
177
|
+
* call read values the log never wrote, and reading those as absent is the
|
|
178
|
+
* fiction `keyedFold.ts` exists to end. A call that is its log's FIRST commit
|
|
179
|
+
* (a grouped turn, or the first stage after a resume) therefore folds to the
|
|
180
|
+
* base rather than to nothing.
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* ```ts
|
|
184
|
+
* const epoch = epochAt(snapshot, 1)!;
|
|
185
|
+
* (readAtCall(epoch, 'history') as unknown[]).length; // the turns that went out
|
|
186
|
+
* ```
|
|
187
|
+
*/
|
|
188
|
+
export declare function readAtCall(location: EpochLocation, key: string): unknown;
|
|
189
|
+
/**
|
|
190
|
+
* The value of `key` the epoch's call itself COMMITTED — folded through the
|
|
191
|
+
* call's own bundle. The receipt is read this way, and only the receipt: it is
|
|
192
|
+
* the one value the call writes that describes the call.
|
|
193
|
+
*/
|
|
194
|
+
export declare function readAfterCall(location: EpochLocation, key: string): unknown;
|
|
195
|
+
/**
|
|
196
|
+
* A RUN CONSTANT: a build-time fact `seed` put on the record once, read from
|
|
197
|
+
* the run's own log.
|
|
198
|
+
*
|
|
199
|
+
* It is read from the run log rather than the epoch's log because that is where
|
|
200
|
+
* `seed` runs in both chart shapes. Under `'dynamic-grouped'` the turn's inner
|
|
201
|
+
* log only ever holds what the boundary's `inputMapper` carried in, and a
|
|
202
|
+
* constant nobody inside the turn reads is not carried — so asking the inner
|
|
203
|
+
* log would answer "absent" for a fact the run plainly recorded.
|
|
204
|
+
*
|
|
205
|
+
* `undefined` has TWO causes and a caller must not blur them: the run never
|
|
206
|
+
* recorded this constant, or this recording carries no run log to read
|
|
207
|
+
* (`EpochLocation.hasRunLog` is `false` — a subtree handed in on its own).
|
|
208
|
+
* `servedView.ts` branches on that flag and declares a gap for the second.
|
|
209
|
+
*/
|
|
210
|
+
export declare function readRunConstant(location: EpochLocation, key: string): unknown;
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* epochs — where one epoch's committed pieces live, in either chart shape.
|
|
3
|
+
*
|
|
4
|
+
* Role: Fold. It reads a finished run's recording and answers one question:
|
|
5
|
+
* "for iteration k, which commit log holds the call, at which index,
|
|
6
|
+
* and against which fold base?" It executes nothing, records nothing
|
|
7
|
+
* and stores nothing beside the log.
|
|
8
|
+
* Reads: a recording (`commitLog`, `initialState`, `subflowResults`), through
|
|
9
|
+
* footprintjs's own id parsers and this folder's `keyedFold.ts`.
|
|
10
|
+
* Emits: N/A.
|
|
11
|
+
*
|
|
12
|
+
* ── WHY THIS FILE EXISTS ───────────────────────────────────────────────────
|
|
13
|
+
* An agent's iteration — an EPOCH — commits to a different place depending on
|
|
14
|
+
* how the chart was built. Under `reactMode: 'dynamic'` the `call-llm` stage is
|
|
15
|
+
* a parent-level stage and its bundle is in the run's own log. Under
|
|
16
|
+
* `'dynamic-grouped'` each turn runs inside an `sf-llm-call` subflow, which has
|
|
17
|
+
* its own isolated runtime and its own log, retained per iteration under
|
|
18
|
+
* `subflowResults['sf-llm-call#k'].treeContext.history`.
|
|
19
|
+
*
|
|
20
|
+
* That fork was written out THREE times — `contextLedger.ts` had a private
|
|
21
|
+
* `llmCallMountKeys`, `trajectory.ts` had a byte-twin of it, and a rebuild of
|
|
22
|
+
* what the model was served would have been the third. Three copies of "which
|
|
23
|
+
* log is this key in?" is three chances to disagree about which iteration a
|
|
24
|
+
* value belonged to, on the one question the whole family exists to answer.
|
|
25
|
+
* So the fork lives here once, and every reader asks it.
|
|
26
|
+
*
|
|
27
|
+
* ── WHAT AN EPOCH NUMBER IS ────────────────────────────────────────────────
|
|
28
|
+
* The committed `iteration` value in effect at the call — the same number
|
|
29
|
+
* `callLLM` put on the receipt's `basis.epoch` and on every `iteration_start`
|
|
30
|
+
* event. It starts at 1. It is READ, not counted: a run that pauses and
|
|
31
|
+
* resumes, or one whose first turn is not its first stage, still names its
|
|
32
|
+
* epochs the way the run itself named them.
|
|
33
|
+
*
|
|
34
|
+
* ── EVERY READ FOLDS FROM THE BASE ─────────────────────────────────────────
|
|
35
|
+
* A location carries the FOLD SOURCE its log was recorded against, not just
|
|
36
|
+
* the log, and every read below goes through `keyedFold.ts`. A resumed run is
|
|
37
|
+
* a fresh executor seeded from `checkpoint.sharedState`, so most of what the
|
|
38
|
+
* call read is base rather than log; a fold that could not see the base
|
|
39
|
+
* answered "absent" for the whole pre-pause world and said nothing about it.
|
|
40
|
+
* `EpochLocation.basis` is that fold's own verdict, carried out to the reader
|
|
41
|
+
* so a recording that travelled without its base declares a gap instead of a
|
|
42
|
+
* confident empty view.
|
|
43
|
+
*/
|
|
44
|
+
import { parseRuntimeStageId, splitStageId } from 'footprintjs/trace';
|
|
45
|
+
import { STAGE_IDS, SUBFLOW_IDS } from '../../conventions.js';
|
|
46
|
+
import { keyedFold } from './keyedFold.js';
|
|
47
|
+
/**
|
|
48
|
+
* A snapshot from whatever the caller had: a runner (`Agent`, `LLMCall`), or
|
|
49
|
+
* the snapshot itself. Duck-typed on purpose — a recording read back from JSON
|
|
50
|
+
* is a plain object and must work exactly like a live one.
|
|
51
|
+
*/
|
|
52
|
+
export function recordingOf(source) {
|
|
53
|
+
const runner = source;
|
|
54
|
+
const accessor = runner?.getLastSnapshot ?? runner?.getSnapshot;
|
|
55
|
+
if (typeof accessor === 'function') {
|
|
56
|
+
return accessor.call(source);
|
|
57
|
+
}
|
|
58
|
+
return source;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The `sf-llm-call` mount keys in `subflowResults`, in loop order.
|
|
62
|
+
*
|
|
63
|
+
* Non-empty ⇒ the run is GROUPED and every turn's pieces are in its own inner
|
|
64
|
+
* log. Empty ⇒ flat, and the run's own log holds everything.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```ts
|
|
68
|
+
* llmCallMountKeys(snapshot.subflowResults); // ['sf-llm-call#4', 'sf-llm-call#9']
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
export function llmCallMountKeys(subflowResults) {
|
|
72
|
+
if (!subflowResults)
|
|
73
|
+
return [];
|
|
74
|
+
return Object.keys(subflowResults)
|
|
75
|
+
.filter((k) => k.includes('#') && splitStageId(k.split('#')[0]).localStageId === SUBFLOW_IDS.LLM_CALL)
|
|
76
|
+
.sort((a, b) => parseRuntimeStageId(a).executionIndex - parseRuntimeStageId(b).executionIndex);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* One `sf-llm-call` mount's own subtree — its log AND the base that log folds
|
|
80
|
+
* against. The subtree, not the array, because the whole point of a location
|
|
81
|
+
* is that a read lands on the base as well as the diffs.
|
|
82
|
+
*/
|
|
83
|
+
function innerSubtreeOf(subflowResults, key) {
|
|
84
|
+
const entry = subflowResults[key];
|
|
85
|
+
const tree = entry?.treeContext;
|
|
86
|
+
return tree !== null && typeof tree === 'object' && Array.isArray(tree.history)
|
|
87
|
+
? tree
|
|
88
|
+
: undefined;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* The FIRST commit of the `call-llm` stage in a log, or `-1`.
|
|
92
|
+
*
|
|
93
|
+
* First, not last, for the reason `commitStops` anchors there: one stage
|
|
94
|
+
* execution can flush more than one bundle under one `runtimeStageId`, and the
|
|
95
|
+
* call's own reads happened at the first.
|
|
96
|
+
*/
|
|
97
|
+
function callIndexIn(log) {
|
|
98
|
+
for (let i = 0; i < log.length; i++) {
|
|
99
|
+
const stageId = log[i]?.stageId;
|
|
100
|
+
if (stageId && splitStageId(stageId).localStageId === STAGE_IDS.CALL_LLM)
|
|
101
|
+
return i;
|
|
102
|
+
}
|
|
103
|
+
return -1;
|
|
104
|
+
}
|
|
105
|
+
/** Located epochs, memoized on the recording object. */
|
|
106
|
+
const LOCATIONS = new WeakMap();
|
|
107
|
+
/**
|
|
108
|
+
* Every epoch in a recording, in run order — the ONE owner of the flat /
|
|
109
|
+
* grouped fork.
|
|
110
|
+
*
|
|
111
|
+
* Returns `[]` for a recording with no LLM call in it at all (an empty log, a
|
|
112
|
+
* chart with no `call-llm` stage). That is a different fact from "the epoch you
|
|
113
|
+
* asked for is not here", which is what {@link epochAt} says with `undefined`.
|
|
114
|
+
*
|
|
115
|
+
* MEMOIZED on the recording object, because the cost of locating epochs is a
|
|
116
|
+
* pass over the log and a per-epoch reader asks for one epoch at a time: before
|
|
117
|
+
* this, scrubbing a 600-turn run epoch by epoch relocated every epoch on every
|
|
118
|
+
* call and took 20.8 s. Holding the snapshot and asking it repeatedly is the
|
|
119
|
+
* shape a reader's UI actually has, so the memo is keyed on exactly that
|
|
120
|
+
* object; a caller that hands in a runner and gets a fresh snapshot each time
|
|
121
|
+
* pays the pass each time, which is the cost of asking a different question.
|
|
122
|
+
*
|
|
123
|
+
* FROZEN, because it is memoized: the array and each location on it are the
|
|
124
|
+
* very objects the next caller gets, so an edit here would be an edit to
|
|
125
|
+
* everybody's answer. The same law `keyedFold.ts` · `freezeDeep` states for a
|
|
126
|
+
* folded value. What is NOT frozen is what a location POINTS AT — `log`,
|
|
127
|
+
* `source`, `runSource` are the caller's own recording, and this file does not
|
|
128
|
+
* get to lock down an object it was merely handed.
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* ```ts
|
|
132
|
+
* import { epochLocations } from 'agentfootprint';
|
|
133
|
+
*
|
|
134
|
+
* epochLocations(agent.getSnapshot()!).map((e) => [e.epoch, e.callRuntimeStageId]);
|
|
135
|
+
* // dynamic: [[1, 'call-llm#12'], [2, 'call-llm#31']]
|
|
136
|
+
* // dynamic-grouped: [[1, 'sf-llm-call/call-llm#9'], [2, 'sf-llm-call/call-llm#28']]
|
|
137
|
+
* ```
|
|
138
|
+
*/
|
|
139
|
+
export function epochLocations(source) {
|
|
140
|
+
const recording = recordingOf(source);
|
|
141
|
+
if (recording !== null && typeof recording === 'object') {
|
|
142
|
+
const memo = LOCATIONS.get(recording);
|
|
143
|
+
if (memo !== undefined)
|
|
144
|
+
return memo;
|
|
145
|
+
}
|
|
146
|
+
const located = Object.freeze(locate(recording).map((e) => Object.freeze(e)));
|
|
147
|
+
if (recording !== null && typeof recording === 'object')
|
|
148
|
+
LOCATIONS.set(recording, located);
|
|
149
|
+
return located;
|
|
150
|
+
}
|
|
151
|
+
/** The uncached pass — one walk of the log, one keyed fold per source. */
|
|
152
|
+
function locate(recording) {
|
|
153
|
+
const runLog = (recording?.commitLog ?? []);
|
|
154
|
+
const runSource = recording ?? {};
|
|
155
|
+
const runFold = keyedFold(runSource);
|
|
156
|
+
const hasRunLog = runLog.length > 0;
|
|
157
|
+
const subflowResults = recording?.subflowResults;
|
|
158
|
+
const mounts = llmCallMountKeys(subflowResults);
|
|
159
|
+
if (mounts.length > 0 && subflowResults) {
|
|
160
|
+
const found = [];
|
|
161
|
+
for (const [ordinal, key] of mounts.entries()) {
|
|
162
|
+
const inner = innerSubtreeOf(subflowResults, key);
|
|
163
|
+
const log = (inner?.history ?? []);
|
|
164
|
+
const callIdx = callIndexIn(log);
|
|
165
|
+
if (inner === undefined || callIdx < 0)
|
|
166
|
+
continue;
|
|
167
|
+
const fold = keyedFold(inner);
|
|
168
|
+
const iteration = fold.valueAt('iteration', callIdx);
|
|
169
|
+
found.push({
|
|
170
|
+
epoch: typeof iteration === 'number' && Number.isFinite(iteration) ? iteration : ordinal + 1,
|
|
171
|
+
callRuntimeStageId: log[callIdx].runtimeStageId,
|
|
172
|
+
log,
|
|
173
|
+
callIdx,
|
|
174
|
+
runLog,
|
|
175
|
+
subflowScope: key,
|
|
176
|
+
source: inner,
|
|
177
|
+
runSource,
|
|
178
|
+
basis: fold.basis,
|
|
179
|
+
runBasis: runFold.basis,
|
|
180
|
+
hasRunLog,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
return found;
|
|
184
|
+
}
|
|
185
|
+
// FLAT: every `call-llm` bundle in the run's own log, at its first commit.
|
|
186
|
+
const found = [];
|
|
187
|
+
const seen = new Set();
|
|
188
|
+
for (let i = 0; i < runLog.length; i++) {
|
|
189
|
+
const bundle = runLog[i];
|
|
190
|
+
if (splitStageId(bundle.stageId).localStageId !== STAGE_IDS.CALL_LLM)
|
|
191
|
+
continue;
|
|
192
|
+
if (seen.has(bundle.runtimeStageId))
|
|
193
|
+
continue;
|
|
194
|
+
seen.add(bundle.runtimeStageId);
|
|
195
|
+
const iteration = runFold.valueAt('iteration', i);
|
|
196
|
+
found.push({
|
|
197
|
+
epoch: typeof iteration === 'number' && Number.isFinite(iteration) ? iteration : found.length + 1,
|
|
198
|
+
callRuntimeStageId: bundle.runtimeStageId,
|
|
199
|
+
log: runLog,
|
|
200
|
+
callIdx: i,
|
|
201
|
+
runLog,
|
|
202
|
+
source: runSource,
|
|
203
|
+
runSource,
|
|
204
|
+
basis: runFold.basis,
|
|
205
|
+
runBasis: runFold.basis,
|
|
206
|
+
hasRunLog,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
return found;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* One epoch by number, or `undefined` when the run has no such iteration.
|
|
213
|
+
*
|
|
214
|
+
* @example
|
|
215
|
+
* ```ts
|
|
216
|
+
* epochAt(agent.getSnapshot()!, 2)?.callRuntimeStageId; // 'call-llm#31'
|
|
217
|
+
* ```
|
|
218
|
+
*/
|
|
219
|
+
export function epochAt(source, epoch) {
|
|
220
|
+
return epochLocations(source).find((e) => e.epoch === epoch);
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* The value of `key` as the epoch's call READ it — folded to just BEFORE the
|
|
224
|
+
* call's own bundle, so a key the call itself writes back (`history` gains the
|
|
225
|
+
* assistant turn; `receipt` is minted) reads as it was on the way in.
|
|
226
|
+
*
|
|
227
|
+
* Folded from the run's base, not from the log alone: on a resumed run the
|
|
228
|
+
* call read values the log never wrote, and reading those as absent is the
|
|
229
|
+
* fiction `keyedFold.ts` exists to end. A call that is its log's FIRST commit
|
|
230
|
+
* (a grouped turn, or the first stage after a resume) therefore folds to the
|
|
231
|
+
* base rather than to nothing.
|
|
232
|
+
*
|
|
233
|
+
* @example
|
|
234
|
+
* ```ts
|
|
235
|
+
* const epoch = epochAt(snapshot, 1)!;
|
|
236
|
+
* (readAtCall(epoch, 'history') as unknown[]).length; // the turns that went out
|
|
237
|
+
* ```
|
|
238
|
+
*/
|
|
239
|
+
export function readAtCall(location, key) {
|
|
240
|
+
if (location.callIdx < 0)
|
|
241
|
+
return undefined;
|
|
242
|
+
return keyedFold(location.source).valueAt(key, location.callIdx - 1);
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* The value of `key` the epoch's call itself COMMITTED — folded through the
|
|
246
|
+
* call's own bundle. The receipt is read this way, and only the receipt: it is
|
|
247
|
+
* the one value the call writes that describes the call.
|
|
248
|
+
*/
|
|
249
|
+
export function readAfterCall(location, key) {
|
|
250
|
+
if (location.callIdx < 0)
|
|
251
|
+
return undefined;
|
|
252
|
+
return keyedFold(location.source).valueAt(key, location.callIdx);
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* A RUN CONSTANT: a build-time fact `seed` put on the record once, read from
|
|
256
|
+
* the run's own log.
|
|
257
|
+
*
|
|
258
|
+
* It is read from the run log rather than the epoch's log because that is where
|
|
259
|
+
* `seed` runs in both chart shapes. Under `'dynamic-grouped'` the turn's inner
|
|
260
|
+
* log only ever holds what the boundary's `inputMapper` carried in, and a
|
|
261
|
+
* constant nobody inside the turn reads is not carried — so asking the inner
|
|
262
|
+
* log would answer "absent" for a fact the run plainly recorded.
|
|
263
|
+
*
|
|
264
|
+
* `undefined` has TWO causes and a caller must not blur them: the run never
|
|
265
|
+
* recorded this constant, or this recording carries no run log to read
|
|
266
|
+
* (`EpochLocation.hasRunLog` is `false` — a subtree handed in on its own).
|
|
267
|
+
* `servedView.ts` branches on that flag and declares a gap for the second.
|
|
268
|
+
*/
|
|
269
|
+
export function readRunConstant(location, key) {
|
|
270
|
+
if (!location.hasRunLog)
|
|
271
|
+
return undefined;
|
|
272
|
+
return keyedFold(location.runSource).valueAt(key, location.runLog.length - 1);
|
|
273
|
+
}
|
|
274
|
+
//# sourceMappingURL=epochs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"epochs.js","sourceRoot":"","sources":["../../../../src/lib/time-travel/epochs.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* time-travel/ — the agent's stops on a reader's cursor
|
|
2
|
+
* time-travel/ — the agent's stops on a reader's cursor, and what the model
|
|
3
|
+
* was handed at each of them.
|
|
3
4
|
*
|
|
4
|
-
* Role: Fold. See README.md for what a milestone stop is,
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Role: Fold. See README.md for what a milestone stop is, what a receipt and a
|
|
6
|
+
* served view are, and how one strategy and one epoch owner serve both chart
|
|
7
|
+
* shapes.
|
|
7
8
|
*/
|
|
8
9
|
export { milestoneOf, milestoneStops, milestoneStopsStrategy } from './milestoneStops.js';
|
|
10
|
+
export { epochAt, epochLocations, llmCallMountKeys, readAfterCall, readAtCall, readRunConstant, recordingOf, type EpochLocation, type RecordingLike, } from './epochs.js';
|
|
11
|
+
export { keyedFold, type FoldBasis, type FoldSourceLike, type KeyedFold } from './keyedFold.js';
|
|
12
|
+
export { buildReceipt, messageDigestInput, toolDigestInput, receiptHash, stableJson, FORCED_OUTPUT_TOOL_KEY, RECEIPT_BOUNDARY, RECEIPT_HASH_CHARS, RECEIPT_KEY, UNSERIALIZABLE, type Receipt, type ReceiptAttentionOmission, type ReceiptCacheMarker, type ReceiptMessage, type ReceiptParams, type ReceiptPiece, type ReceiptRequestOnlyMessage, } from './receipt.js';
|
|
13
|
+
export { sha256Hex } from './sha256.js';
|
|
14
|
+
export { receiptAt, servedAt, servedViews, SERVED_GAPS, UNGAPPED_FIELDS, type ServedGap, type ServedGapCause, type ServedGapKind, type ServedPiece, type ServedRequestOnly, type ServedView, } from './servedView.js';
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* time-travel/ — the agent's stops on a reader's cursor
|
|
2
|
+
* time-travel/ — the agent's stops on a reader's cursor, and what the model
|
|
3
|
+
* was handed at each of them.
|
|
3
4
|
*
|
|
4
|
-
* Role: Fold. See README.md for what a milestone stop is,
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Role: Fold. See README.md for what a milestone stop is, what a receipt and a
|
|
6
|
+
* served view are, and how one strategy and one epoch owner serve both chart
|
|
7
|
+
* shapes.
|
|
7
8
|
*/
|
|
8
9
|
export { milestoneOf, milestoneStops, milestoneStopsStrategy } from './milestoneStops.js';
|
|
10
|
+
export { epochAt, epochLocations, llmCallMountKeys, readAfterCall, readAtCall, readRunConstant, recordingOf, } from './epochs.js';
|
|
11
|
+
export { keyedFold } from './keyedFold.js';
|
|
12
|
+
export { buildReceipt, messageDigestInput, toolDigestInput, receiptHash, stableJson, FORCED_OUTPUT_TOOL_KEY, RECEIPT_BOUNDARY, RECEIPT_HASH_CHARS, RECEIPT_KEY, UNSERIALIZABLE, } from './receipt.js';
|
|
13
|
+
export { sha256Hex } from './sha256.js';
|
|
14
|
+
export { receiptAt, servedAt, servedViews, SERVED_GAPS, UNGAPPED_FIELDS, } from './servedView.js';
|
|
9
15
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/lib/time-travel/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/lib/time-travel/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC1F,OAAO,EACL,OAAO,EACP,cAAc,EACd,gBAAgB,EAChB,aAAa,EACb,UAAU,EACV,eAAe,EACf,WAAW,GAGZ,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,SAAS,EAAuD,MAAM,gBAAgB,CAAC;AAChG,OAAO,EACL,YAAY,EACZ,kBAAkB,EAClB,eAAe,EACf,WAAW,EACX,UAAU,EACV,sBAAsB,EACtB,gBAAgB,EAChB,kBAAkB,EAClB,WAAW,EACX,cAAc,GAQf,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EACL,SAAS,EACT,QAAQ,EACR,WAAW,EACX,WAAW,EACX,eAAe,GAOhB,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* keyedFold — the value of ONE state key at one commit, folded from the run's
|
|
3
|
+
* own fold base.
|
|
4
|
+
*
|
|
5
|
+
* Role: Fold. It reads a finished run's commit log plus the base that log
|
|
6
|
+
* was recorded against, and answers "what was `key` at commit `idx`?"
|
|
7
|
+
* It executes nothing, records nothing and stores nothing beside the
|
|
8
|
+
* log. Its answers are DETACHED — deep-frozen before they are cached,
|
|
9
|
+
* because a memo that hands out live references is a fold that lets a
|
|
10
|
+
* reader rewrite the run (`keyedFold.ts` · `freezeDeep`).
|
|
11
|
+
* Reads: a `FoldSource` — a snapshot (`commitLog` + `initialState`) or a
|
|
12
|
+
* subflow subtree (`history` + `initialState`), through footprintjs's
|
|
13
|
+
* own `stateAt` (for the base) and `applySmartMerge` (for the replay).
|
|
14
|
+
* Emits: N/A.
|
|
15
|
+
*
|
|
16
|
+
* ── WHY THIS FILE EXISTS ───────────────────────────────────────────────────
|
|
17
|
+
* A commit log stores DIFFS. `commitValueAt` folds them for one key and says
|
|
18
|
+
* so in its own docstring: a value that came from the run's INITIAL state and
|
|
19
|
+
* was never `set` again folds to ABSENT, because the log alone cannot see the
|
|
20
|
+
* pre-run base.
|
|
21
|
+
*
|
|
22
|
+
* That is not an edge case here. A RESUMED agent run is a fresh executor
|
|
23
|
+
* seeded from `checkpoint.sharedState`, so the entire pre-pause world —
|
|
24
|
+
* `systemPromptInjections`, `history`, `dynamicToolSchemas`, every build-time
|
|
25
|
+
* constant `seed` wrote before the pause — is base, not log. Folding the
|
|
26
|
+
* resumed run with `commitValueAt` returns an empty system prompt and a
|
|
27
|
+
* truncated conversation and declares no gap: a confident falsehood about the
|
|
28
|
+
* one question this family exists to answer. footprintjs 9.17 shipped
|
|
29
|
+
* `RuntimeSnapshot.initialState` and `stateAt` for exactly this, and every
|
|
30
|
+
* read in `epochs.ts` goes through here so it lands on that base.
|
|
31
|
+
*
|
|
32
|
+
* ── WHY NOT JUST CALL `stateAt` PER READ ───────────────────────────────────
|
|
33
|
+
* `stateAt` folds the WHOLE state, and it clones that state once per bundle
|
|
34
|
+
* (`applySmartMerge` opens with a `structuredClone` of its base). One fold is
|
|
35
|
+
* therefore O(commits × state size) — measured on a real 100-turn agent
|
|
36
|
+
* (1,719 commits, 101 epochs): 323 ms for a single fold, and 11.7 s to fold
|
|
37
|
+
* once per epoch, against 78 ms for the whole per-epoch scrub before this
|
|
38
|
+
* release. A per-epoch reader cannot pay that.
|
|
39
|
+
*
|
|
40
|
+
* So `stateAt` is used for the ONE thing only it can give — the fold base and
|
|
41
|
+
* the honest `basis` verdict, at `commitIdx: -1`, where it folds nothing and
|
|
42
|
+
* clones the base once — and the forward replay is done per key, through
|
|
43
|
+
* footprintjs's own `applySmartMerge`. There is deliberately no second
|
|
44
|
+
* implementation of the trace verbs here: every `set` / `append` / `merge` /
|
|
45
|
+
* `delete` is applied by the same function the live commit uses.
|
|
46
|
+
* `keyed-fold-equivalence.test.ts` pins the result against `stateAt` itself on
|
|
47
|
+
* real runs, including a resumed one and a merge with no `set` anchor — the
|
|
48
|
+
* one shape that cannot be folded at all without the base.
|
|
49
|
+
*/
|
|
50
|
+
import type { CommitBundle } from 'footprintjs/advanced';
|
|
51
|
+
/**
|
|
52
|
+
* How a fold was derived — footprintjs's own two answers, restated here so a
|
|
53
|
+
* caller of this module does not have to import the engine's trace barrel to
|
|
54
|
+
* name them.
|
|
55
|
+
*
|
|
56
|
+
* - `'initial+log'` — the recording carried its fold base, so a value seeded
|
|
57
|
+
* before the run (or before a resume) folds correctly.
|
|
58
|
+
* - `'log-only'` — it did not. Anything the log never `set` reads as absent,
|
|
59
|
+
* and a reader must say so rather than present the hole as a proof.
|
|
60
|
+
*/
|
|
61
|
+
export type FoldBasis = 'initial+log' | 'log-only';
|
|
62
|
+
/**
|
|
63
|
+
* Anything that carries a commit log and, ideally, the base it was recorded
|
|
64
|
+
* against — the two field spellings footprintjs already has: `commitLog` on a
|
|
65
|
+
* run snapshot, `history` on a subflow subtree.
|
|
66
|
+
*/
|
|
67
|
+
export interface FoldSourceLike {
|
|
68
|
+
readonly commitLog?: readonly CommitBundle[];
|
|
69
|
+
readonly history?: readonly unknown[];
|
|
70
|
+
readonly initialState?: Record<string, unknown>;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* One key's value at one commit — the question, and the answers already
|
|
74
|
+
* computed for it.
|
|
75
|
+
*/
|
|
76
|
+
export interface KeyedFold {
|
|
77
|
+
/** How this fold was derived — see {@link FoldBasis}. */
|
|
78
|
+
readonly basis: FoldBasis;
|
|
79
|
+
/** How many bundles the log holds. */
|
|
80
|
+
readonly length: number;
|
|
81
|
+
/**
|
|
82
|
+
* The value of `key` folded through commit ARRAY INDEX `idx`, inclusive.
|
|
83
|
+
* `-1` (or lower) folds nothing and returns the base's value — the state
|
|
84
|
+
* before the log's first commit.
|
|
85
|
+
*
|
|
86
|
+
* DEEP-FROZEN, and the same object on every call for the same question. It
|
|
87
|
+
* is detached from the engine's own bundles AND unmutable, so a caller
|
|
88
|
+
* cannot rewrite what a later index folds to — `keyedFold.ts` · `freezeDeep`
|
|
89
|
+
* says why that is a law here rather than a courtesy. Copy it
|
|
90
|
+
* (`structuredClone`, a spread) if you need something to edit.
|
|
91
|
+
*/
|
|
92
|
+
valueAt(key: string, idx: number): unknown;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* The keyed fold for one source, memoized on the source object.
|
|
96
|
+
*
|
|
97
|
+
* Memoized because a per-epoch reader asks the same source for the same keys
|
|
98
|
+
* at many different commits, and the index is the expensive half: building it
|
|
99
|
+
* is one pass over the log, and answering a read afterwards is a binary search
|
|
100
|
+
* plus a replay from the nearest full-value write (normally exactly one).
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```ts
|
|
104
|
+
* import { keyedFold } from 'agentfootprint';
|
|
105
|
+
*
|
|
106
|
+
* const fold = keyedFold(agent.getSnapshot());
|
|
107
|
+
* fold.basis; // 'initial+log' — the base travelled
|
|
108
|
+
* fold.valueAt('history', 12); // the conversation after the 13th commit
|
|
109
|
+
* fold.valueAt('history', -1); // …and what the run STARTED from
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
export declare function keyedFold(source: FoldSourceLike | undefined): KeyedFold;
|