agentfootprint 7.16.0 → 7.17.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/dist/conventions.js +8 -6
- package/dist/conventions.js.map +1 -1
- package/dist/core/Agent.js +31 -26
- package/dist/core/Agent.js.map +1 -1
- package/dist/core/agent/AgentBuilder.js +93 -43
- package/dist/core/agent/AgentBuilder.js.map +1 -1
- package/dist/core/agent/buildAgentChart.js +8 -8
- package/dist/core/agent/buildAgentChart.js.map +1 -1
- package/dist/core/agent/buildDynamicAgentChart.js +6 -6
- package/dist/core/agent/buildDynamicAgentChart.js.map +1 -1
- package/dist/core/agent/stages/window.js +141 -0
- package/dist/core/agent/stages/window.js.map +1 -0
- package/dist/core/agent/window/errors.js.map +1 -0
- package/dist/core/agent/window/index.js +36 -0
- package/dist/core/agent/window/index.js.map +1 -0
- package/dist/core/agent/window/notice.js +57 -0
- package/dist/core/agent/window/notice.js.map +1 -0
- package/dist/core/agent/window/options.js +93 -0
- package/dist/core/agent/window/options.js.map +1 -0
- package/dist/core/agent/window/removal.js +45 -0
- package/dist/core/agent/window/removal.js.map +1 -0
- package/dist/core/agent/window/strategies/drop.js +106 -0
- package/dist/core/agent/window/strategies/drop.js.map +1 -0
- package/dist/core/agent/window/strategies/slidingWindow.js +87 -0
- package/dist/core/agent/window/strategies/slidingWindow.js.map +1 -0
- package/dist/core/agent/window/strategies/summarizeOldest.js +178 -0
- package/dist/core/agent/window/strategies/summarizeOldest.js.map +1 -0
- package/dist/core/agent/window/strategies/tokenBudget.js +95 -0
- package/dist/core/agent/window/strategies/tokenBudget.js.map +1 -0
- package/dist/core/agent/window/strategy.js +42 -0
- package/dist/core/agent/window/strategy.js.map +1 -0
- package/dist/core/agent/window/summarize.js.map +1 -0
- package/dist/core/agent/{compaction → window}/turns.js +50 -28
- package/dist/core/agent/window/turns.js.map +1 -0
- package/dist/core/agent/window/types.js +23 -0
- package/dist/core/agent/window/types.js.map +1 -0
- package/dist/esm/conventions.d.ts +8 -6
- package/dist/esm/conventions.js +8 -6
- package/dist/esm/conventions.js.map +1 -1
- package/dist/esm/core/Agent.d.ts +6 -6
- package/dist/esm/core/Agent.js +31 -26
- package/dist/esm/core/Agent.js.map +1 -1
- package/dist/esm/core/agent/AgentBuilder.d.ts +60 -4
- package/dist/esm/core/agent/AgentBuilder.js +93 -43
- package/dist/esm/core/agent/AgentBuilder.js.map +1 -1
- package/dist/esm/core/agent/buildAgentChart.d.ts +14 -7
- package/dist/esm/core/agent/buildAgentChart.js +8 -8
- package/dist/esm/core/agent/buildAgentChart.js.map +1 -1
- package/dist/esm/core/agent/buildDynamicAgentChart.js +6 -6
- package/dist/esm/core/agent/buildDynamicAgentChart.js.map +1 -1
- package/dist/esm/core/agent/stages/window.d.ts +63 -0
- package/dist/esm/core/agent/stages/window.js +137 -0
- package/dist/esm/core/agent/stages/window.js.map +1 -0
- package/dist/esm/core/agent/types.d.ts +15 -8
- package/dist/esm/core/agent/window/errors.js.map +1 -0
- package/dist/esm/core/agent/window/index.d.ts +27 -0
- package/dist/esm/core/agent/window/index.js +25 -0
- package/dist/esm/core/agent/window/index.js.map +1 -0
- package/dist/esm/core/agent/window/notice.d.ts +46 -0
- package/dist/esm/core/agent/window/notice.js +52 -0
- package/dist/esm/core/agent/window/notice.js.map +1 -0
- package/dist/esm/core/agent/window/options.d.ts +33 -0
- package/dist/esm/core/agent/window/options.js +87 -0
- package/dist/esm/core/agent/window/options.js.map +1 -0
- package/dist/esm/core/agent/window/removal.d.ts +22 -0
- package/dist/esm/core/agent/window/removal.js +40 -0
- package/dist/esm/core/agent/window/removal.js.map +1 -0
- package/dist/esm/core/agent/window/strategies/drop.d.ts +54 -0
- package/dist/esm/core/agent/window/strategies/drop.js +102 -0
- package/dist/esm/core/agent/window/strategies/drop.js.map +1 -0
- package/dist/esm/core/agent/window/strategies/slidingWindow.d.ts +48 -0
- package/dist/esm/core/agent/window/strategies/slidingWindow.js +83 -0
- package/dist/esm/core/agent/window/strategies/slidingWindow.js.map +1 -0
- package/dist/esm/core/agent/window/strategies/summarizeOldest.d.ts +42 -0
- package/dist/esm/core/agent/window/strategies/summarizeOldest.js +174 -0
- package/dist/esm/core/agent/window/strategies/summarizeOldest.js.map +1 -0
- package/dist/esm/core/agent/window/strategies/tokenBudget.d.ts +47 -0
- package/dist/esm/core/agent/window/strategies/tokenBudget.js +91 -0
- package/dist/esm/core/agent/window/strategies/tokenBudget.js.map +1 -0
- package/dist/esm/core/agent/window/strategy.d.ts +181 -0
- package/dist/esm/core/agent/window/strategy.js +41 -0
- package/dist/esm/core/agent/window/strategy.js.map +1 -0
- package/dist/esm/core/agent/window/summarize.js.map +1 -0
- package/dist/esm/core/agent/window/turns.d.ts +108 -0
- package/dist/esm/core/agent/{compaction → window}/turns.js +48 -26
- package/dist/esm/core/agent/window/turns.js.map +1 -0
- package/dist/esm/core/agent/window/types.d.ts +263 -0
- package/dist/esm/core/agent/window/types.js +22 -0
- package/dist/esm/core/agent/window/types.js.map +1 -0
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +10 -5
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/recorders/core/CompactionMeter.d.ts +25 -19
- package/dist/esm/recorders/core/CompactionMeter.js +27 -19
- package/dist/esm/recorders/core/CompactionMeter.js.map +1 -1
- package/dist/index.js +17 -7
- package/dist/index.js.map +1 -1
- package/dist/recorders/core/CompactionMeter.js +27 -19
- package/dist/recorders/core/CompactionMeter.js.map +1 -1
- package/dist/types/conventions.d.ts +8 -6
- package/dist/types/conventions.d.ts.map +1 -1
- package/dist/types/core/Agent.d.ts +6 -6
- package/dist/types/core/Agent.d.ts.map +1 -1
- package/dist/types/core/agent/AgentBuilder.d.ts +60 -4
- package/dist/types/core/agent/AgentBuilder.d.ts.map +1 -1
- package/dist/types/core/agent/buildAgentChart.d.ts +14 -7
- package/dist/types/core/agent/buildAgentChart.d.ts.map +1 -1
- package/dist/types/core/agent/stages/window.d.ts +64 -0
- package/dist/types/core/agent/stages/window.d.ts.map +1 -0
- package/dist/types/core/agent/types.d.ts +15 -8
- package/dist/types/core/agent/types.d.ts.map +1 -1
- package/dist/types/core/agent/window/errors.d.ts.map +1 -0
- package/dist/types/core/agent/window/index.d.ts +28 -0
- package/dist/types/core/agent/window/index.d.ts.map +1 -0
- package/dist/types/core/agent/window/notice.d.ts +47 -0
- package/dist/types/core/agent/window/notice.d.ts.map +1 -0
- package/dist/types/core/agent/window/options.d.ts +34 -0
- package/dist/types/core/agent/window/options.d.ts.map +1 -0
- package/dist/types/core/agent/window/removal.d.ts +23 -0
- package/dist/types/core/agent/window/removal.d.ts.map +1 -0
- package/dist/types/core/agent/window/strategies/drop.d.ts +55 -0
- package/dist/types/core/agent/window/strategies/drop.d.ts.map +1 -0
- package/dist/types/core/agent/window/strategies/slidingWindow.d.ts +49 -0
- package/dist/types/core/agent/window/strategies/slidingWindow.d.ts.map +1 -0
- package/dist/types/core/agent/window/strategies/summarizeOldest.d.ts +43 -0
- package/dist/types/core/agent/window/strategies/summarizeOldest.d.ts.map +1 -0
- package/dist/types/core/agent/window/strategies/tokenBudget.d.ts +48 -0
- package/dist/types/core/agent/window/strategies/tokenBudget.d.ts.map +1 -0
- package/dist/types/core/agent/window/strategy.d.ts +182 -0
- package/dist/types/core/agent/window/strategy.d.ts.map +1 -0
- package/dist/types/core/agent/window/summarize.d.ts.map +1 -0
- package/dist/types/core/agent/window/turns.d.ts +109 -0
- package/dist/types/core/agent/window/turns.d.ts.map +1 -0
- package/dist/types/core/agent/window/types.d.ts +264 -0
- package/dist/types/core/agent/window/types.d.ts.map +1 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/recorders/core/CompactionMeter.d.ts +25 -19
- package/dist/types/recorders/core/CompactionMeter.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/core/agent/compaction/errors.js.map +0 -1
- package/dist/core/agent/compaction/index.js +0 -24
- package/dist/core/agent/compaction/index.js.map +0 -1
- package/dist/core/agent/compaction/strategy.js +0 -152
- package/dist/core/agent/compaction/strategy.js.map +0 -1
- package/dist/core/agent/compaction/summarize.js.map +0 -1
- package/dist/core/agent/compaction/turns.js.map +0 -1
- package/dist/core/agent/compaction/types.js +0 -21
- package/dist/core/agent/compaction/types.js.map +0 -1
- package/dist/core/agent/stages/compact.js +0 -130
- package/dist/core/agent/stages/compact.js.map +0 -1
- package/dist/esm/core/agent/compaction/errors.js.map +0 -1
- package/dist/esm/core/agent/compaction/index.d.ts +0 -18
- package/dist/esm/core/agent/compaction/index.js +0 -18
- package/dist/esm/core/agent/compaction/index.js.map +0 -1
- package/dist/esm/core/agent/compaction/strategy.d.ts +0 -103
- package/dist/esm/core/agent/compaction/strategy.js +0 -148
- package/dist/esm/core/agent/compaction/strategy.js.map +0 -1
- package/dist/esm/core/agent/compaction/summarize.js.map +0 -1
- package/dist/esm/core/agent/compaction/turns.d.ts +0 -85
- package/dist/esm/core/agent/compaction/turns.js.map +0 -1
- package/dist/esm/core/agent/compaction/types.d.ts +0 -148
- package/dist/esm/core/agent/compaction/types.js +0 -20
- package/dist/esm/core/agent/compaction/types.js.map +0 -1
- package/dist/esm/core/agent/stages/compact.d.ts +0 -59
- package/dist/esm/core/agent/stages/compact.js +0 -126
- package/dist/esm/core/agent/stages/compact.js.map +0 -1
- package/dist/types/core/agent/compaction/errors.d.ts.map +0 -1
- package/dist/types/core/agent/compaction/index.d.ts +0 -19
- package/dist/types/core/agent/compaction/index.d.ts.map +0 -1
- package/dist/types/core/agent/compaction/strategy.d.ts +0 -104
- package/dist/types/core/agent/compaction/strategy.d.ts.map +0 -1
- package/dist/types/core/agent/compaction/summarize.d.ts.map +0 -1
- package/dist/types/core/agent/compaction/turns.d.ts +0 -86
- package/dist/types/core/agent/compaction/turns.d.ts.map +0 -1
- package/dist/types/core/agent/compaction/types.d.ts +0 -149
- package/dist/types/core/agent/compaction/types.d.ts.map +0 -1
- package/dist/types/core/agent/stages/compact.d.ts +0 -60
- package/dist/types/core/agent/stages/compact.d.ts.map +0 -1
- /package/dist/core/agent/{compaction → window}/errors.js +0 -0
- /package/dist/core/agent/{compaction → window}/summarize.js +0 -0
- /package/dist/esm/core/agent/{compaction → window}/errors.d.ts +0 -0
- /package/dist/esm/core/agent/{compaction → window}/errors.js +0 -0
- /package/dist/esm/core/agent/{compaction → window}/summarize.d.ts +0 -0
- /package/dist/esm/core/agent/{compaction → window}/summarize.js +0 -0
- /package/dist/types/core/agent/{compaction → window}/errors.d.ts +0 -0
- /package/dist/types/core/agent/{compaction → window}/summarize.d.ts +0 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* summarizeOldest — fold the oldest foldable turns into one summary message.
|
|
3
|
+
*
|
|
4
|
+
* Pattern: WindowStrategy implementation. Its own module, importing nothing
|
|
5
|
+
* that registers anything, so a bundle that never mentions it never
|
|
6
|
+
* carries it (and never carries the summarizer machinery either).
|
|
7
|
+
* Role: core/ layer.
|
|
8
|
+
* Emits: N/A — the stage emits, records and costs; this file decides.
|
|
9
|
+
*
|
|
10
|
+
* This is what `.compaction({...})` configures, and the market's familiar
|
|
11
|
+
* move (Claude Code / the Claude Agent SDK call it compaction). What is
|
|
12
|
+
* different here is not the fold — it is that the fold is filed as a claim:
|
|
13
|
+
* a summary is a claim ABOUT the past, and the past itself stays in the
|
|
14
|
+
* commit log, byte for byte.
|
|
15
|
+
*
|
|
16
|
+
* Everything it decides, it explains. Every engaged path returns a record —
|
|
17
|
+
* including the paths that change nothing, which are the ones a person
|
|
18
|
+
* debugging an over-budget window actually needs.
|
|
19
|
+
*/
|
|
20
|
+
import { CompactionUnmeasurableError } from '../errors.js';
|
|
21
|
+
import { resolveCompactionOptions } from '../options.js';
|
|
22
|
+
import { indexRange } from '../removal.js';
|
|
23
|
+
import { buildSummaryMessage, isCompactedSummary, runSummarizer } from '../summarize.js';
|
|
24
|
+
import { windowChars } from '../turns.js';
|
|
25
|
+
/** `WindowRecord.strategy` written by every record this strategy files. */
|
|
26
|
+
export const SUMMARIZE_OLDEST = 'summarize-oldest';
|
|
27
|
+
/**
|
|
28
|
+
* Fold the oldest contiguous run of foldable turns into one summary message,
|
|
29
|
+
* keeping the recent turns and stepping over anything unresolved.
|
|
30
|
+
*
|
|
31
|
+
* Triggered by COUNTED tokens: the last call's adapter-reported input tokens
|
|
32
|
+
* against `thresholdTokens`. A provider that reports no usage gets
|
|
33
|
+
* `CompactionUnmeasurableError` rather than an invented number.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* import { Agent, summarizeOldest } from 'agentfootprint';
|
|
38
|
+
*
|
|
39
|
+
* const agent = Agent.create({ provider: anthropic(), model: 'claude-sonnet-4-5' })
|
|
40
|
+
* .window(summarizeOldest({ thresholdTokens: 120_000, summarizer: anthropic() }))
|
|
41
|
+
* .build();
|
|
42
|
+
* // `.compaction({ ... })` is this exact line, spelled shorter.
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export function summarizeOldest(options) {
|
|
46
|
+
const config = resolveCompactionOptions(options, 'summarizeOldest');
|
|
47
|
+
return {
|
|
48
|
+
name: SUMMARIZE_OLDEST,
|
|
49
|
+
async plan(input) {
|
|
50
|
+
const { history, turns, iteration, measured } = input;
|
|
51
|
+
// Iteration 1: nothing has been sent, so nothing has been counted. A
|
|
52
|
+
// compactor that acted here would be guessing.
|
|
53
|
+
if (measured === undefined)
|
|
54
|
+
return undefined;
|
|
55
|
+
if (measured.input === 0 && measured.output === 0) {
|
|
56
|
+
throw new CompactionUnmeasurableError(input.providerName);
|
|
57
|
+
}
|
|
58
|
+
if (measured.input <= config.thresholdTokens)
|
|
59
|
+
return undefined;
|
|
60
|
+
const model = config.model ?? input.agentModel;
|
|
61
|
+
const charsBefore = windowChars(history);
|
|
62
|
+
const base = {
|
|
63
|
+
strategy: SUMMARIZE_OLDEST,
|
|
64
|
+
iteration,
|
|
65
|
+
measuredTokens: measured.input,
|
|
66
|
+
thresholdTokens: config.thresholdTokens,
|
|
67
|
+
overBudget: true,
|
|
68
|
+
windowCharsBefore: charsBefore,
|
|
69
|
+
};
|
|
70
|
+
const unchanged = (refusals, extra = {}) => ({
|
|
71
|
+
record: {
|
|
72
|
+
...base,
|
|
73
|
+
removedStageIds: [],
|
|
74
|
+
removedMessageCount: 0,
|
|
75
|
+
foldedStageIds: [],
|
|
76
|
+
foldedMessageCount: 0,
|
|
77
|
+
windowCharsAfter: charsBefore,
|
|
78
|
+
summaryChars: 0,
|
|
79
|
+
refusals,
|
|
80
|
+
...extra,
|
|
81
|
+
},
|
|
82
|
+
evictions: [],
|
|
83
|
+
budgetPressure: {
|
|
84
|
+
capTokens: config.thresholdTokens,
|
|
85
|
+
projectedTokens: measured.input,
|
|
86
|
+
planAction: 'none',
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
const plan = input.planRemoval(config.keepRecentTurns, (turn) => isCompactedSummary(turn.messages[0]));
|
|
90
|
+
if (plan.from === -1) {
|
|
91
|
+
// Nothing foldable. The window stays over budget and the run proceeds
|
|
92
|
+
// — reported, not silently truncated.
|
|
93
|
+
return unchanged(plan.refusals);
|
|
94
|
+
}
|
|
95
|
+
const spanStart = turns[plan.from].start;
|
|
96
|
+
const spanEnd = turns[plan.to].start + turns[plan.to].length;
|
|
97
|
+
const head = history.slice(0, spanStart);
|
|
98
|
+
const span = history.slice(spanStart, spanEnd);
|
|
99
|
+
const tail = history.slice(spanEnd);
|
|
100
|
+
// A broken summarizer must not take down the run.
|
|
101
|
+
let summary;
|
|
102
|
+
try {
|
|
103
|
+
const result = await runSummarizer(config.summarizer, model, span, input.signal);
|
|
104
|
+
summary = {
|
|
105
|
+
text: result.text,
|
|
106
|
+
usage: { input: result.usage.input, output: result.usage.output },
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
return {
|
|
111
|
+
...unchanged([
|
|
112
|
+
{ reason: 'summarizer-failed', turnIndex: plan.from, messageIndex: spanStart },
|
|
113
|
+
...plan.refusals,
|
|
114
|
+
]),
|
|
115
|
+
warning: `the summarizer threw, so nothing was folded this iteration and the window ` +
|
|
116
|
+
`stays over budget (${measured.input} tokens vs a threshold of ` +
|
|
117
|
+
`${config.thresholdTokens}). The run continues. Cause: ` +
|
|
118
|
+
`${err instanceof Error ? err.message : String(err)}`,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
const summaryMessage = buildSummaryMessage(summary.text, {
|
|
122
|
+
foldedMessageCount: span.length,
|
|
123
|
+
iteration,
|
|
124
|
+
model,
|
|
125
|
+
});
|
|
126
|
+
const spend = { model, usage: summary.usage };
|
|
127
|
+
// Would the fold actually help? A summary plus its authored frame can be
|
|
128
|
+
// LONGER than a handful of short turns; folding then spends a call to
|
|
129
|
+
// grow the window and lose detail at the same time. Both sides are
|
|
130
|
+
// measured in chars — one unit, an exact comparison, not a token guess.
|
|
131
|
+
if (summaryMessage.content.length >= windowChars(span)) {
|
|
132
|
+
return {
|
|
133
|
+
...unchanged([
|
|
134
|
+
{ reason: 'summary-not-smaller', turnIndex: plan.from, messageIndex: spanStart },
|
|
135
|
+
...plan.refusals,
|
|
136
|
+
], { summaryChars: summaryMessage.content.length, summarizerTokens: summary.usage }),
|
|
137
|
+
spend, // the call still happened, so it still counts
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
const foldedAtMs = input.now();
|
|
141
|
+
const facts = input.removalFacts(indexRange(spanStart, spanEnd), foldedAtMs);
|
|
142
|
+
const window = [...head, summaryMessage, ...tail];
|
|
143
|
+
const record = {
|
|
144
|
+
...base,
|
|
145
|
+
removedStageIds: facts.removedStageIds,
|
|
146
|
+
removedMessageCount: span.length,
|
|
147
|
+
// The 7.16 names for the two values above, kept and deprecated.
|
|
148
|
+
foldedStageIds: facts.removedStageIds,
|
|
149
|
+
foldedMessageCount: span.length,
|
|
150
|
+
windowCharsAfter: windowChars(window),
|
|
151
|
+
summaryChars: summaryMessage.content.length,
|
|
152
|
+
summarizerTokens: summary.usage,
|
|
153
|
+
refusals: plan.refusals,
|
|
154
|
+
};
|
|
155
|
+
return {
|
|
156
|
+
window,
|
|
157
|
+
rebase: {
|
|
158
|
+
headCount: head.length,
|
|
159
|
+
keptTailCount: tail.length,
|
|
160
|
+
insertedAtMs: foldedAtMs,
|
|
161
|
+
},
|
|
162
|
+
record,
|
|
163
|
+
evictions: facts.evictions,
|
|
164
|
+
budgetPressure: {
|
|
165
|
+
capTokens: config.thresholdTokens,
|
|
166
|
+
projectedTokens: measured.input,
|
|
167
|
+
planAction: 'summarize',
|
|
168
|
+
},
|
|
169
|
+
spend,
|
|
170
|
+
};
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=summarizeOldest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"summarizeOldest.js","sourceRoot":"","sources":["../../../../../../src/core/agent/window/strategies/summarizeOldest.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tokenBudget — compaction's trigger discipline, without the summarizer.
|
|
3
|
+
*
|
|
4
|
+
* Pattern: WindowStrategy implementation. Its own module, registering
|
|
5
|
+
* nothing at import.
|
|
6
|
+
* Role: core/ layer.
|
|
7
|
+
* Emits: N/A — the stage emits, records and costs; this file decides.
|
|
8
|
+
*
|
|
9
|
+
* The market's other familiar policy: cap the window at a token budget
|
|
10
|
+
* (Mastra's `TokenLimiter` is the closest sibling). What is different here is
|
|
11
|
+
* the same thing that is different about compaction — the number is COUNTED,
|
|
12
|
+
* never guessed:
|
|
13
|
+
*
|
|
14
|
+
* • the trigger reads the input tokens the PROVIDER reported for the last
|
|
15
|
+
* call, not a character estimate and not a divide-by-four heuristic;
|
|
16
|
+
* • a provider that reports no usage gets `CompactionUnmeasurableError` by
|
|
17
|
+
* name, the same refusal `.compaction()` makes, rather than an invented
|
|
18
|
+
* window size or a configured budget that silently never applies.
|
|
19
|
+
*
|
|
20
|
+
* When over budget it drops the oldest contiguous removable span — the same
|
|
21
|
+
* span compaction would have folded, chosen by the same refusal engine — and
|
|
22
|
+
* writes no summary at all. Nothing is claimed about what left, because
|
|
23
|
+
* nothing was read: the record and the eviction events name it, and the
|
|
24
|
+
* commit log still has it verbatim.
|
|
25
|
+
*
|
|
26
|
+
* Use it over `summarizeOldest` when you would rather lose the old turns than
|
|
27
|
+
* pay a summarizer to paraphrase them, and over `slidingWindow` when the
|
|
28
|
+
* thing you are actually defending is a token bill rather than a turn depth.
|
|
29
|
+
*/
|
|
30
|
+
import type { WindowStrategy } from '../strategy.js';
|
|
31
|
+
import type { TokenBudgetOptions } from '../types.js';
|
|
32
|
+
/** `WindowRecord.strategy` written by every record this strategy files. */
|
|
33
|
+
export declare const TOKEN_BUDGET = "token-budget";
|
|
34
|
+
/**
|
|
35
|
+
* Drop the oldest turns whenever the last call's adapter-reported input
|
|
36
|
+
* tokens exceed `thresholdTokens`.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* import { Agent, tokenBudget } from 'agentfootprint';
|
|
41
|
+
*
|
|
42
|
+
* const agent = Agent.create({ provider: anthropic(), model: 'claude-sonnet-4-5' })
|
|
43
|
+
* .window(tokenBudget({ thresholdTokens: 120_000 }))
|
|
44
|
+
* .build();
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export declare function tokenBudget(options: TokenBudgetOptions): WindowStrategy;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tokenBudget — compaction's trigger discipline, without the summarizer.
|
|
3
|
+
*
|
|
4
|
+
* Pattern: WindowStrategy implementation. Its own module, registering
|
|
5
|
+
* nothing at import.
|
|
6
|
+
* Role: core/ layer.
|
|
7
|
+
* Emits: N/A — the stage emits, records and costs; this file decides.
|
|
8
|
+
*
|
|
9
|
+
* The market's other familiar policy: cap the window at a token budget
|
|
10
|
+
* (Mastra's `TokenLimiter` is the closest sibling). What is different here is
|
|
11
|
+
* the same thing that is different about compaction — the number is COUNTED,
|
|
12
|
+
* never guessed:
|
|
13
|
+
*
|
|
14
|
+
* • the trigger reads the input tokens the PROVIDER reported for the last
|
|
15
|
+
* call, not a character estimate and not a divide-by-four heuristic;
|
|
16
|
+
* • a provider that reports no usage gets `CompactionUnmeasurableError` by
|
|
17
|
+
* name, the same refusal `.compaction()` makes, rather than an invented
|
|
18
|
+
* window size or a configured budget that silently never applies.
|
|
19
|
+
*
|
|
20
|
+
* When over budget it drops the oldest contiguous removable span — the same
|
|
21
|
+
* span compaction would have folded, chosen by the same refusal engine — and
|
|
22
|
+
* writes no summary at all. Nothing is claimed about what left, because
|
|
23
|
+
* nothing was read: the record and the eviction events name it, and the
|
|
24
|
+
* commit log still has it verbatim.
|
|
25
|
+
*
|
|
26
|
+
* Use it over `summarizeOldest` when you would rather lose the old turns than
|
|
27
|
+
* pay a summarizer to paraphrase them, and over `slidingWindow` when the
|
|
28
|
+
* thing you are actually defending is a token bill rather than a turn depth.
|
|
29
|
+
*/
|
|
30
|
+
import { dropOldestSpan } from './drop.js';
|
|
31
|
+
import { CompactionUnmeasurableError } from '../errors.js';
|
|
32
|
+
import { resolveTokenBudgetOptions } from '../options.js';
|
|
33
|
+
/** `WindowRecord.strategy` written by every record this strategy files. */
|
|
34
|
+
export const TOKEN_BUDGET = 'token-budget';
|
|
35
|
+
/**
|
|
36
|
+
* Drop the oldest turns whenever the last call's adapter-reported input
|
|
37
|
+
* tokens exceed `thresholdTokens`.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* import { Agent, tokenBudget } from 'agentfootprint';
|
|
42
|
+
*
|
|
43
|
+
* const agent = Agent.create({ provider: anthropic(), model: 'claude-sonnet-4-5' })
|
|
44
|
+
* .window(tokenBudget({ thresholdTokens: 120_000 }))
|
|
45
|
+
* .build();
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export function tokenBudget(options) {
|
|
49
|
+
const config = resolveTokenBudgetOptions(options, 'tokenBudget');
|
|
50
|
+
return {
|
|
51
|
+
name: TOKEN_BUDGET,
|
|
52
|
+
async plan(input) {
|
|
53
|
+
const { measured } = input;
|
|
54
|
+
// Iteration 1: nothing has been sent, so nothing has been counted.
|
|
55
|
+
if (measured === undefined)
|
|
56
|
+
return undefined;
|
|
57
|
+
// Counted, not guessed — the same wall compaction hits, by the same name.
|
|
58
|
+
if (measured.input === 0 && measured.output === 0) {
|
|
59
|
+
throw new CompactionUnmeasurableError(input.providerName);
|
|
60
|
+
}
|
|
61
|
+
if (measured.input <= config.thresholdTokens)
|
|
62
|
+
return undefined;
|
|
63
|
+
const outcome = dropOldestSpan(input, config.keepRecentTurns, TOKEN_BUDGET);
|
|
64
|
+
const record = {
|
|
65
|
+
strategy: TOKEN_BUDGET,
|
|
66
|
+
iteration: input.iteration,
|
|
67
|
+
measuredTokens: measured.input,
|
|
68
|
+
thresholdTokens: config.thresholdTokens,
|
|
69
|
+
overBudget: true,
|
|
70
|
+
keepRecentTurns: config.keepRecentTurns,
|
|
71
|
+
removedStageIds: outcome.removedStageIds,
|
|
72
|
+
removedMessageCount: outcome.removedMessageCount,
|
|
73
|
+
windowCharsBefore: outcome.windowCharsBefore,
|
|
74
|
+
windowCharsAfter: outcome.windowCharsAfter,
|
|
75
|
+
refusals: outcome.refusals,
|
|
76
|
+
};
|
|
77
|
+
return {
|
|
78
|
+
...(outcome.window !== undefined && { window: outcome.window }),
|
|
79
|
+
...(outcome.rebase !== undefined && { rebase: outcome.rebase }),
|
|
80
|
+
record,
|
|
81
|
+
evictions: outcome.evictions,
|
|
82
|
+
budgetPressure: {
|
|
83
|
+
capTokens: config.thresholdTokens,
|
|
84
|
+
projectedTokens: measured.input,
|
|
85
|
+
planAction: outcome.removedMessageCount > 0 ? 'evict' : 'none',
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=tokenBudget.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokenBudget.js","sourceRoot":"","sources":["../../../../../../src/core/agent/window/strategies/tokenBudget.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,2BAA2B,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,EAAE,yBAAyB,EAAE,MAAM,eAAe,CAAC;AAI1D,2EAA2E;AAC3E,MAAM,CAAC,MAAM,YAAY,GAAG,cAAc,CAAC;AAE3C;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,WAAW,CAAC,OAA2B;IACrD,MAAM,MAAM,GAAG,yBAAyB,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;IAEjE,OAAO;QACL,IAAI,EAAE,YAAY;QAElB,KAAK,CAAC,IAAI,CAAC,KAA0B;YACnC,MAAM,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC;YAE3B,mEAAmE;YACnE,IAAI,QAAQ,KAAK,SAAS;gBAAE,OAAO,SAAS,CAAC;YAC7C,0EAA0E;YAC1E,IAAI,QAAQ,CAAC,KAAK,KAAK,CAAC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAClD,MAAM,IAAI,2BAA2B,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;YAC5D,CAAC;YACD,IAAI,QAAQ,CAAC,KAAK,IAAI,MAAM,CAAC,eAAe;gBAAE,OAAO,SAAS,CAAC;YAE/D,MAAM,OAAO,GAAG,cAAc,CAAC,KAAK,EAAE,MAAM,CAAC,eAAe,EAAE,YAAY,CAAC,CAAC;YAC5E,MAAM,MAAM,GAAsB;gBAChC,QAAQ,EAAE,YAAY;gBACtB,SAAS,EAAE,KAAK,CAAC,SAAS;gBAC1B,cAAc,EAAE,QAAQ,CAAC,KAAK;gBAC9B,eAAe,EAAE,MAAM,CAAC,eAAe;gBACvC,UAAU,EAAE,IAAI;gBAChB,eAAe,EAAE,MAAM,CAAC,eAAe;gBACvC,eAAe,EAAE,OAAO,CAAC,eAAe;gBACxC,mBAAmB,EAAE,OAAO,CAAC,mBAAmB;gBAChD,iBAAiB,EAAE,OAAO,CAAC,iBAAiB;gBAC5C,gBAAgB,EAAE,OAAO,CAAC,gBAAgB;gBAC1C,QAAQ,EAAE,OAAO,CAAC,QAAQ;aAC3B,CAAC;YAEF,OAAO;gBACL,GAAG,CAAC,OAAO,CAAC,MAAM,KAAK,SAAS,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC;gBAC/D,GAAG,CAAC,OAAO,CAAC,MAAM,KAAK,SAAS,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC;gBAC/D,MAAM;gBACN,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,cAAc,EAAE;oBACd,SAAS,EAAE,MAAM,CAAC,eAAe;oBACjC,eAAe,EAAE,QAAQ,CAAC,KAAK;oBAC/B,UAAU,EAAE,OAAO,CAAC,mBAAmB,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM;iBAC/D;aACF,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* window/strategy — PUBLIC. How a window strategy is shaped.
|
|
3
|
+
*
|
|
4
|
+
* Pattern: Strategy (GoF), with the dangerous half of the decision handed in
|
|
5
|
+
* pre-bound rather than left to the implementer to re-derive.
|
|
6
|
+
* Role: core/ layer. The window stage does the wiring — read the meter,
|
|
7
|
+
* write the window, emit, record, cost — and delegates the one
|
|
8
|
+
* interesting question to a strategy:
|
|
9
|
+
*
|
|
10
|
+
* given the segmented turns and what the provider actually
|
|
11
|
+
* counted → what should the window become, and what does the
|
|
12
|
+
* ledger need to be told about it?
|
|
13
|
+
*
|
|
14
|
+
* Emits: N/A. A strategy is pure decision + (optionally) its own LLM call;
|
|
15
|
+
* it never touches scope, never emits, and never writes. That is
|
|
16
|
+
* what makes it testable without a chart, and what keeps the
|
|
17
|
+
* "record everything you did" duty in ONE place (the stage) rather
|
|
18
|
+
* than duplicated per strategy.
|
|
19
|
+
*
|
|
20
|
+
* Three strategies ship — `summarizeOldest` (what `.compaction()`
|
|
21
|
+
* configures), `slidingWindow`, `tokenBudget` — and `.window(...)` takes any
|
|
22
|
+
* object that satisfies `WindowStrategy`.
|
|
23
|
+
*
|
|
24
|
+
* Two things are deliberately NOT left to the implementer:
|
|
25
|
+
*
|
|
26
|
+
* 1. **The refusal rules.** `planRemoval` arrives already bound to this
|
|
27
|
+
* iteration's turns and guards. A strategy cannot forget that an
|
|
28
|
+
* unanswered tool call must not leave the window, because it never gets
|
|
29
|
+
* the chance to decide that for itself — it asks, and it is told, with
|
|
30
|
+
* every refusal named. That is safety by construction, not by docs.
|
|
31
|
+
* 2. **Provenance.** `removalFacts` turns "these indices left" into the
|
|
32
|
+
* stage ids that wrote them and how long each lived. A strategy cannot
|
|
33
|
+
* file a removal it cannot name.
|
|
34
|
+
*
|
|
35
|
+
* The TRIGGER, by contrast, is entirely the strategy's own: `plan` is called
|
|
36
|
+
* at every ReAct iteration boundary and answers `undefined` when it did not
|
|
37
|
+
* engage. That is why `slidingWindow` can run on a provider that reports no
|
|
38
|
+
* usage at all while `summarizeOldest` and `tokenBudget` refuse by name.
|
|
39
|
+
*/
|
|
40
|
+
import type { LLMMessage } from '../../../adapters/types.js';
|
|
41
|
+
import type { Turn, RemovalPlan } from './turns.js';
|
|
42
|
+
import type { WindowRecord } from './types.js';
|
|
43
|
+
/** One message leaving the window, with the facts an eviction event needs. */
|
|
44
|
+
export interface WindowEviction {
|
|
45
|
+
/** Index in the PRE-change window — the index the content hash was built on. */
|
|
46
|
+
readonly index: number;
|
|
47
|
+
/** How long it lived in the window. Exact; 0 when its birth is unknown. */
|
|
48
|
+
readonly survivalMs: number;
|
|
49
|
+
}
|
|
50
|
+
/** The provenance of a set of removed messages, as the ledger needs it. */
|
|
51
|
+
export interface RemovalFacts {
|
|
52
|
+
/** `runtimeStageId`s of the stages that appended those messages, in order. */
|
|
53
|
+
readonly removedStageIds: readonly string[];
|
|
54
|
+
/** One eviction per message, with its measured lifetime. */
|
|
55
|
+
readonly evictions: readonly WindowEviction[];
|
|
56
|
+
}
|
|
57
|
+
/** Everything a strategy is allowed to look at. */
|
|
58
|
+
export interface WindowStrategyInput {
|
|
59
|
+
/** The window as it stands, detached. */
|
|
60
|
+
readonly history: readonly LLMMessage[];
|
|
61
|
+
/** The same window, segmented into turns. */
|
|
62
|
+
readonly turns: readonly Turn[];
|
|
63
|
+
/**
|
|
64
|
+
* What the provider REPORTED for the last completed call. Counted, never
|
|
65
|
+
* guessed. `undefined` before the first call of the run — a strategy that
|
|
66
|
+
* acted on that would be guessing, which is the one thing this family
|
|
67
|
+
* refuses to do.
|
|
68
|
+
*
|
|
69
|
+
* `{ input: 0, output: 0 }` is a provider that reported NOTHING, not a call
|
|
70
|
+
* that cost nothing. A token-triggered strategy should throw
|
|
71
|
+
* `CompactionUnmeasurableError` there rather than invent a size.
|
|
72
|
+
*/
|
|
73
|
+
readonly measured: {
|
|
74
|
+
readonly input: number;
|
|
75
|
+
readonly output: number;
|
|
76
|
+
} | undefined;
|
|
77
|
+
/** The ReAct iteration this decision belongs to. */
|
|
78
|
+
readonly iteration: number;
|
|
79
|
+
/** The agent's own model — the sensible default for a strategy that bills. */
|
|
80
|
+
readonly agentModel: string;
|
|
81
|
+
/** `provider.name` of the MAIN provider, for a refusal that names it. */
|
|
82
|
+
readonly providerName: string;
|
|
83
|
+
/** The run's cancellation signal, when there is one. */
|
|
84
|
+
readonly signal: AbortSignal | undefined;
|
|
85
|
+
/** Wall clock, injectable so a caller can pin `survivalMs`. */
|
|
86
|
+
readonly now: () => number;
|
|
87
|
+
/**
|
|
88
|
+
* THE shared refusal engine, bound to this iteration.
|
|
89
|
+
*
|
|
90
|
+
* Answers: which contiguous span of turns may leave, and every turn that
|
|
91
|
+
* refused, named. Never removes the system envelope, the last
|
|
92
|
+
* `keepRecentTurns` turns, an unanswered tool call, the paused tool, or a
|
|
93
|
+
* pending check-in.
|
|
94
|
+
*
|
|
95
|
+
* @param keepRecentTurns how many trailing turns are off-limits
|
|
96
|
+
* @param isExistingSummary optional predicate marking a turn that is a
|
|
97
|
+
* summary a previous fold wrote; when the whole span is one of those, the
|
|
98
|
+
* plan refuses with `only-existing-summary`. Pass it only if your strategy
|
|
99
|
+
* spends an LLM call — a drop has nothing to protect against.
|
|
100
|
+
*/
|
|
101
|
+
readonly planRemoval: (keepRecentTurns: number, isExistingSummary?: (turn: Turn) => boolean) => RemovalPlan;
|
|
102
|
+
/**
|
|
103
|
+
* Turn removed message indices into the facts the ledger needs: which
|
|
104
|
+
* stages wrote them, and how long each lived in the window.
|
|
105
|
+
*
|
|
106
|
+
* @param indices indices in the PRE-change window that are leaving
|
|
107
|
+
* @param atMs the moment they leave (usually `input.now()`)
|
|
108
|
+
*/
|
|
109
|
+
readonly removalFacts: (indices: readonly number[], atMs: number) => RemovalFacts;
|
|
110
|
+
}
|
|
111
|
+
/** What the stage should do next. */
|
|
112
|
+
export interface WindowStrategyResult {
|
|
113
|
+
/** The new window. Absent = leave the window alone. */
|
|
114
|
+
readonly window?: readonly LLMMessage[];
|
|
115
|
+
/**
|
|
116
|
+
* How the meter must re-align its provenance to the new window, which is
|
|
117
|
+
* `[...head, (one new message)?, ...tail]`. Present exactly when `window`
|
|
118
|
+
* is. `insertedAtMs` is the birth of the message the strategy put in the
|
|
119
|
+
* span's place — omit it when the strategy removed messages and inserted
|
|
120
|
+
* nothing.
|
|
121
|
+
*/
|
|
122
|
+
readonly rebase?: {
|
|
123
|
+
readonly headCount: number;
|
|
124
|
+
readonly keptTailCount: number;
|
|
125
|
+
readonly insertedAtMs?: number;
|
|
126
|
+
};
|
|
127
|
+
/** What the ledger is told. Always present — an engaged visit explains itself. */
|
|
128
|
+
readonly record: WindowRecord;
|
|
129
|
+
/** Messages that left the window, for `context.evicted`. */
|
|
130
|
+
readonly evictions: readonly WindowEviction[];
|
|
131
|
+
/**
|
|
132
|
+
* The budget reading to report on `agentfootprint.context.budget_pressure`.
|
|
133
|
+
*
|
|
134
|
+
* OMIT IT when the strategy has no token budget. `slidingWindow` does: it
|
|
135
|
+
* triggers on turn count, and filling `capTokens` with a number nobody
|
|
136
|
+
* configured would be the invented figure this family refuses. No budget,
|
|
137
|
+
* no budget_pressure event.
|
|
138
|
+
*/
|
|
139
|
+
readonly budgetPressure?: {
|
|
140
|
+
readonly capTokens: number;
|
|
141
|
+
readonly projectedTokens: number;
|
|
142
|
+
readonly planAction: 'evict' | 'summarize' | 'none';
|
|
143
|
+
};
|
|
144
|
+
/** A billed call the strategy made, for the cost channel. */
|
|
145
|
+
readonly spend?: {
|
|
146
|
+
readonly model: string;
|
|
147
|
+
readonly usage: {
|
|
148
|
+
readonly input: number;
|
|
149
|
+
readonly output: number;
|
|
150
|
+
};
|
|
151
|
+
};
|
|
152
|
+
/** A one-per-run dev warning the stage should print. */
|
|
153
|
+
readonly warning?: string;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* A window strategy: what the live window should become at this iteration
|
|
157
|
+
* boundary, and what the record must say about the change.
|
|
158
|
+
*
|
|
159
|
+
* Pass one to `AgentBuilder.window(...)`. Exactly one per agent —
|
|
160
|
+
* `.compaction(...)` is the same door with `summarizeOldest` already in it.
|
|
161
|
+
*/
|
|
162
|
+
export interface WindowStrategy {
|
|
163
|
+
/**
|
|
164
|
+
* Stable name — it is written onto every record this strategy files
|
|
165
|
+
* (`WindowRecord.strategy`), so a reader can tell which policy produced a
|
|
166
|
+
* window, and it names the strategy on the chart's `compact` stage.
|
|
167
|
+
*/
|
|
168
|
+
readonly name: string;
|
|
169
|
+
/**
|
|
170
|
+
* Decide. Called at EVERY ReAct iteration boundary.
|
|
171
|
+
*
|
|
172
|
+
* Return `undefined` when this strategy did not engage — nothing was over
|
|
173
|
+
* budget, nothing was old enough, nothing has been counted yet. The ledger
|
|
174
|
+
* stays untouched and the run proceeds.
|
|
175
|
+
*
|
|
176
|
+
* Return a result for anything else, INCLUDING a visit that changed
|
|
177
|
+
* nothing because every candidate refused. Those are the visits a person
|
|
178
|
+
* debugging an oversized window actually needs.
|
|
179
|
+
*/
|
|
180
|
+
plan(input: WindowStrategyInput): Promise<WindowStrategyResult | undefined>;
|
|
181
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* window/strategy — PUBLIC. How a window strategy is shaped.
|
|
3
|
+
*
|
|
4
|
+
* Pattern: Strategy (GoF), with the dangerous half of the decision handed in
|
|
5
|
+
* pre-bound rather than left to the implementer to re-derive.
|
|
6
|
+
* Role: core/ layer. The window stage does the wiring — read the meter,
|
|
7
|
+
* write the window, emit, record, cost — and delegates the one
|
|
8
|
+
* interesting question to a strategy:
|
|
9
|
+
*
|
|
10
|
+
* given the segmented turns and what the provider actually
|
|
11
|
+
* counted → what should the window become, and what does the
|
|
12
|
+
* ledger need to be told about it?
|
|
13
|
+
*
|
|
14
|
+
* Emits: N/A. A strategy is pure decision + (optionally) its own LLM call;
|
|
15
|
+
* it never touches scope, never emits, and never writes. That is
|
|
16
|
+
* what makes it testable without a chart, and what keeps the
|
|
17
|
+
* "record everything you did" duty in ONE place (the stage) rather
|
|
18
|
+
* than duplicated per strategy.
|
|
19
|
+
*
|
|
20
|
+
* Three strategies ship — `summarizeOldest` (what `.compaction()`
|
|
21
|
+
* configures), `slidingWindow`, `tokenBudget` — and `.window(...)` takes any
|
|
22
|
+
* object that satisfies `WindowStrategy`.
|
|
23
|
+
*
|
|
24
|
+
* Two things are deliberately NOT left to the implementer:
|
|
25
|
+
*
|
|
26
|
+
* 1. **The refusal rules.** `planRemoval` arrives already bound to this
|
|
27
|
+
* iteration's turns and guards. A strategy cannot forget that an
|
|
28
|
+
* unanswered tool call must not leave the window, because it never gets
|
|
29
|
+
* the chance to decide that for itself — it asks, and it is told, with
|
|
30
|
+
* every refusal named. That is safety by construction, not by docs.
|
|
31
|
+
* 2. **Provenance.** `removalFacts` turns "these indices left" into the
|
|
32
|
+
* stage ids that wrote them and how long each lived. A strategy cannot
|
|
33
|
+
* file a removal it cannot name.
|
|
34
|
+
*
|
|
35
|
+
* The TRIGGER, by contrast, is entirely the strategy's own: `plan` is called
|
|
36
|
+
* at every ReAct iteration boundary and answers `undefined` when it did not
|
|
37
|
+
* engage. That is why `slidingWindow` can run on a provider that reports no
|
|
38
|
+
* usage at all while `summarizeOldest` and `tokenBudget` refuse by name.
|
|
39
|
+
*/
|
|
40
|
+
export {};
|
|
41
|
+
//# sourceMappingURL=strategy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"strategy.js","sourceRoot":"","sources":["../../../../../src/core/agent/window/strategy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"summarize.js","sourceRoot":"","sources":["../../../../../src/core/agent/window/summarize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAIH,6EAA6E;AAC7E,MAAM,CAAC,MAAM,sBAAsB,GAAG,oBAAoB,CAAC;AAE3D,kFAAkF;AAClF,MAAM,eAAe,GAAG,kBAAkB,CAAC;AAC3C,MAAM,gBAAgB,GAAG,sBAAsB,CAAC;AAEhD;;;;GAIG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG;IACtC,wFAAwF;IACxF,kCAAkC;IAClC,EAAE;IACF,kCAAkC,eAAe,QAAQ,gBAAgB,sBAAsB;IAC/F,8FAA8F;IAC9F,6FAA6F;IAC7F,EAAE;IACF,4FAA4F;IAC5F,6FAA6F;IAC7F,gGAAgG;IAChG,yEAAyE;CAC1E,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAEb,+EAA+E;AAC/E,SAAS,aAAa,CAAC,GAAe;IACpC,MAAM,KAAK,GAAG,CAAC,GAAG,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,EAAE,CAAC,IAAI,IAAI,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC1F,MAAM,IAAI,GACR,GAAG,CAAC,IAAI,KAAK,MAAM;QACjB,CAAC,CAAC,eAAe,GAAG,CAAC,QAAQ,IAAI,SAAS,GAAG;QAC7C,CAAC,CAAC,GAAG,CAAC,IAAI,KAAK,WAAW,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;YAC9C,CAAC,CAAC,qBAAqB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;YAC1C,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC;IACf,OAAO,GAAG,IAAI,KAAK,GAAG,CAAC,OAAO,EAAE,CAAC;AACnC,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,gBAAgB,CAAC,QAA+B;IAC9D,OAAO,CAAC,eAAe,EAAE,GAAG,QAAQ,CAAC,GAAG,CAAC,aAAa,CAAC,EAAE,gBAAgB,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACxF,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,mBAAmB,CACjC,OAAe,EACf,KAIC;IAED,MAAM,KAAK,GACT,GAAG,sBAAsB,MAAM,KAAK,CAAC,kBAAkB,kCAAkC;QACzF,mCAAmC,KAAK,CAAC,SAAS,0CAA0C;QAC5F,cAAc,KAAK,CAAC,KAAK,gEAAgE;QACzF,sEAAsE,CAAC;IACzE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,KAAK,OAAO,OAAO,EAAE,EAAE,CAAC;AAC7D,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,kBAAkB,CAAC,GAA2B;IAC5D,OAAO,GAAG,KAAK,SAAS,IAAI,GAAG,CAAC,IAAI,KAAK,MAAM,IAAI,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,sBAAsB,CAAC,CAAC;AACpG,CAAC;AAOD;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,QAAqB,EACrB,KAAa,EACb,IAA2B,EAC3B,MAA+B;IAE/B,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,QAAQ,CAAC;QACvC,YAAY,EAAE,wBAAwB;QACtC,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7D,KAAK;QACL,GAAG,CAAC,MAAM,KAAK,SAAS,IAAI,EAAE,MAAM,EAAE,CAAC;KACxC,CAAC,CAAC;IACH,OAAO,EAAE,IAAI,EAAE,QAAQ,CAAC,OAAO,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC;AAC3D,CAAC"}
|