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.
Files changed (143) hide show
  1. package/CHANGELOG.md +946 -0
  2. package/dist/core/Agent.js +32 -0
  3. package/dist/core/Agent.js.map +1 -1
  4. package/dist/core/LLMCall.js +7 -10
  5. package/dist/core/LLMCall.js.map +1 -1
  6. package/dist/core/agent/buildAgentMessageApiChart.js +2 -4
  7. package/dist/core/agent/buildAgentMessageApiChart.js.map +1 -1
  8. package/dist/core/agent/buildDynamicAgentChart.js +10 -0
  9. package/dist/core/agent/buildDynamicAgentChart.js.map +1 -1
  10. package/dist/core/agent/buildMessageApiChart.js +2 -4
  11. package/dist/core/agent/buildMessageApiChart.js.map +1 -1
  12. package/dist/core/agent/composeRequest.js +134 -0
  13. package/dist/core/agent/composeRequest.js.map +1 -0
  14. package/dist/core/agent/stages/callLLM.js +62 -25
  15. package/dist/core/agent/stages/callLLM.js.map +1 -1
  16. package/dist/core/agent/stages/seed.js +12 -0
  17. package/dist/core/agent/stages/seed.js.map +1 -1
  18. package/dist/core/agent/stages/toolCalls.js +2 -4
  19. package/dist/core/agent/stages/toolCalls.js.map +1 -1
  20. package/dist/core/flowchartAsTool.js.map +1 -1
  21. package/dist/esm/core/Agent.d.ts +3 -0
  22. package/dist/esm/core/Agent.js +32 -0
  23. package/dist/esm/core/Agent.js.map +1 -1
  24. package/dist/esm/core/LLMCall.js +7 -10
  25. package/dist/esm/core/LLMCall.js.map +1 -1
  26. package/dist/esm/core/agent/buildAgentMessageApiChart.js +2 -4
  27. package/dist/esm/core/agent/buildAgentMessageApiChart.js.map +1 -1
  28. package/dist/esm/core/agent/buildDynamicAgentChart.js +10 -0
  29. package/dist/esm/core/agent/buildDynamicAgentChart.js.map +1 -1
  30. package/dist/esm/core/agent/buildMessageApiChart.js +2 -4
  31. package/dist/esm/core/agent/buildMessageApiChart.js.map +1 -1
  32. package/dist/esm/core/agent/composeRequest.d.ts +121 -0
  33. package/dist/esm/core/agent/composeRequest.js +127 -0
  34. package/dist/esm/core/agent/composeRequest.js.map +1 -0
  35. package/dist/esm/core/agent/stages/callLLM.d.ts +28 -0
  36. package/dist/esm/core/agent/stages/callLLM.js +61 -24
  37. package/dist/esm/core/agent/stages/callLLM.js.map +1 -1
  38. package/dist/esm/core/agent/stages/seed.d.ts +26 -0
  39. package/dist/esm/core/agent/stages/seed.js +12 -0
  40. package/dist/esm/core/agent/stages/seed.js.map +1 -1
  41. package/dist/esm/core/agent/stages/toolCalls.js +2 -4
  42. package/dist/esm/core/agent/stages/toolCalls.js.map +1 -1
  43. package/dist/esm/core/agent/types.d.ts +64 -0
  44. package/dist/esm/core/flowchartAsTool.d.ts +15 -7
  45. package/dist/esm/core/flowchartAsTool.js.map +1 -1
  46. package/dist/esm/index.d.ts +1 -0
  47. package/dist/esm/index.js +9 -0
  48. package/dist/esm/index.js.map +1 -1
  49. package/dist/esm/lib/context-bisect/llmEdgeWeigher.d.ts +8 -0
  50. package/dist/esm/lib/context-bisect/llmEdgeWeigher.js +0 -0
  51. package/dist/esm/lib/context-bisect/llmEdgeWeigher.js.map +1 -1
  52. package/dist/esm/lib/context-bisect/trajectory.js +3 -7
  53. package/dist/esm/lib/context-bisect/trajectory.js.map +1 -1
  54. package/dist/esm/lib/context-ledger/contextLedger.js +5 -12
  55. package/dist/esm/lib/context-ledger/contextLedger.js.map +1 -1
  56. package/dist/esm/lib/time-travel/epochs.d.ts +210 -0
  57. package/dist/esm/lib/time-travel/epochs.js +274 -0
  58. package/dist/esm/lib/time-travel/epochs.js.map +1 -0
  59. package/dist/esm/lib/time-travel/index.d.ts +10 -4
  60. package/dist/esm/lib/time-travel/index.js +10 -4
  61. package/dist/esm/lib/time-travel/index.js.map +1 -1
  62. package/dist/esm/lib/time-travel/keyedFold.d.ts +112 -0
  63. package/dist/esm/lib/time-travel/keyedFold.js +286 -0
  64. package/dist/esm/lib/time-travel/keyedFold.js.map +1 -0
  65. package/dist/esm/lib/time-travel/milestoneStops.d.ts +57 -36
  66. package/dist/esm/lib/time-travel/milestoneStops.js +75 -82
  67. package/dist/esm/lib/time-travel/milestoneStops.js.map +1 -1
  68. package/dist/esm/lib/time-travel/receipt.d.ts +496 -0
  69. package/dist/esm/lib/time-travel/receipt.js +434 -0
  70. package/dist/esm/lib/time-travel/receipt.js.map +1 -0
  71. package/dist/esm/lib/time-travel/servedView.d.ts +525 -0
  72. package/dist/esm/lib/time-travel/servedView.js +820 -0
  73. package/dist/esm/lib/time-travel/servedView.js.map +1 -0
  74. package/dist/esm/lib/time-travel/sha256.d.ts +24 -0
  75. package/dist/esm/lib/time-travel/sha256.js +103 -0
  76. package/dist/esm/lib/time-travel/sha256.js.map +1 -0
  77. package/dist/esm/recorders/observability/BoundaryRecorder.d.ts +21 -0
  78. package/dist/esm/recorders/observability/BoundaryRecorder.js +6 -0
  79. package/dist/esm/recorders/observability/BoundaryRecorder.js.map +1 -1
  80. package/dist/index.js +124 -103
  81. package/dist/index.js.map +1 -1
  82. package/dist/lib/context-bisect/llmEdgeWeigher.js +0 -0
  83. package/dist/lib/context-bisect/llmEdgeWeigher.js.map +1 -1
  84. package/dist/lib/context-bisect/trajectory.js +3 -7
  85. package/dist/lib/context-bisect/trajectory.js.map +1 -1
  86. package/dist/lib/context-ledger/contextLedger.js +5 -12
  87. package/dist/lib/context-ledger/contextLedger.js.map +1 -1
  88. package/dist/lib/time-travel/epochs.js +284 -0
  89. package/dist/lib/time-travel/epochs.js.map +1 -0
  90. package/dist/lib/time-travel/index.js +35 -5
  91. package/dist/lib/time-travel/index.js.map +1 -1
  92. package/dist/lib/time-travel/keyedFold.js +290 -0
  93. package/dist/lib/time-travel/keyedFold.js.map +1 -0
  94. package/dist/lib/time-travel/milestoneStops.js +74 -81
  95. package/dist/lib/time-travel/milestoneStops.js.map +1 -1
  96. package/dist/lib/time-travel/receipt.js +442 -0
  97. package/dist/lib/time-travel/receipt.js.map +1 -0
  98. package/dist/lib/time-travel/servedView.js +826 -0
  99. package/dist/lib/time-travel/servedView.js.map +1 -0
  100. package/dist/lib/time-travel/sha256.js +107 -0
  101. package/dist/lib/time-travel/sha256.js.map +1 -0
  102. package/dist/recorders/observability/BoundaryRecorder.js +6 -0
  103. package/dist/recorders/observability/BoundaryRecorder.js.map +1 -1
  104. package/dist/types/core/Agent.d.ts +3 -0
  105. package/dist/types/core/Agent.d.ts.map +1 -1
  106. package/dist/types/core/LLMCall.d.ts.map +1 -1
  107. package/dist/types/core/agent/buildAgentMessageApiChart.d.ts.map +1 -1
  108. package/dist/types/core/agent/buildDynamicAgentChart.d.ts.map +1 -1
  109. package/dist/types/core/agent/buildMessageApiChart.d.ts.map +1 -1
  110. package/dist/types/core/agent/composeRequest.d.ts +122 -0
  111. package/dist/types/core/agent/composeRequest.d.ts.map +1 -0
  112. package/dist/types/core/agent/stages/callLLM.d.ts +28 -0
  113. package/dist/types/core/agent/stages/callLLM.d.ts.map +1 -1
  114. package/dist/types/core/agent/stages/seed.d.ts +26 -0
  115. package/dist/types/core/agent/stages/seed.d.ts.map +1 -1
  116. package/dist/types/core/agent/stages/toolCalls.d.ts.map +1 -1
  117. package/dist/types/core/agent/types.d.ts +64 -0
  118. package/dist/types/core/agent/types.d.ts.map +1 -1
  119. package/dist/types/core/flowchartAsTool.d.ts +15 -7
  120. package/dist/types/core/flowchartAsTool.d.ts.map +1 -1
  121. package/dist/types/index.d.ts +1 -0
  122. package/dist/types/index.d.ts.map +1 -1
  123. package/dist/types/lib/context-bisect/llmEdgeWeigher.d.ts +8 -0
  124. package/dist/types/lib/context-bisect/llmEdgeWeigher.d.ts.map +1 -1
  125. package/dist/types/lib/context-bisect/trajectory.d.ts.map +1 -1
  126. package/dist/types/lib/context-ledger/contextLedger.d.ts.map +1 -1
  127. package/dist/types/lib/time-travel/epochs.d.ts +211 -0
  128. package/dist/types/lib/time-travel/epochs.d.ts.map +1 -0
  129. package/dist/types/lib/time-travel/index.d.ts +10 -4
  130. package/dist/types/lib/time-travel/index.d.ts.map +1 -1
  131. package/dist/types/lib/time-travel/keyedFold.d.ts +113 -0
  132. package/dist/types/lib/time-travel/keyedFold.d.ts.map +1 -0
  133. package/dist/types/lib/time-travel/milestoneStops.d.ts +57 -36
  134. package/dist/types/lib/time-travel/milestoneStops.d.ts.map +1 -1
  135. package/dist/types/lib/time-travel/receipt.d.ts +497 -0
  136. package/dist/types/lib/time-travel/receipt.d.ts.map +1 -0
  137. package/dist/types/lib/time-travel/servedView.d.ts +526 -0
  138. package/dist/types/lib/time-travel/servedView.d.ts.map +1 -0
  139. package/dist/types/lib/time-travel/sha256.d.ts +25 -0
  140. package/dist/types/lib/time-travel/sha256.d.ts.map +1 -0
  141. package/dist/types/recorders/observability/BoundaryRecorder.d.ts +21 -0
  142. package/dist/types/recorders/observability/BoundaryRecorder.d.ts.map +1 -1
  143. 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, why the strategy is
5
- * composed from footprintjs's own per-stage axis rather than re-derived, and
6
- * how the same strategy serves both chart shapes.
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, why the strategy is
5
- * composed from footprintjs's own per-stage axis rather than re-derived, and
6
- * how the same strategy serves both chart shapes.
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;;;;;;GAMG;AAEH,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC"}
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;