agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
95 lines • 4.32 kB
JavaScript
;
/**
* tokenBudget — compaction's trigger discipline, without the summarizer.
*
* Pattern: WindowStrategy implementation. Its own module, registering
* nothing at import.
* Role: core/ layer.
* Emits: N/A — the stage emits, records and costs; this file decides.
*
* The market's other familiar policy: cap the window at a token budget
* (Mastra's `TokenLimiter` is the closest sibling). What is different here is
* the same thing that is different about compaction — the number is COUNTED,
* never guessed:
*
* • the trigger reads the input tokens the PROVIDER reported for the last
* call, not a character estimate and not a divide-by-four heuristic;
* • a provider that reports no usage gets `CompactionUnmeasurableError` by
* name, the same refusal `.compaction()` makes, rather than an invented
* window size or a configured budget that silently never applies.
*
* When over budget it drops the oldest contiguous removable span — the same
* span compaction would have folded, chosen by the same refusal engine — and
* writes no summary at all. Nothing is claimed about what left, because
* nothing was read: the record and the eviction events name it, and the
* commit log still has it verbatim.
*
* Use it over `summarizeOldest` when you would rather lose the old turns than
* pay a summarizer to paraphrase them, and over `slidingWindow` when the
* thing you are actually defending is a token bill rather than a turn depth.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.tokenBudget = exports.TOKEN_BUDGET = void 0;
const drop_js_1 = require("./drop.js");
const errors_js_1 = require("../errors.js");
const options_js_1 = require("../options.js");
/** `WindowRecord.strategy` written by every record this strategy files. */
exports.TOKEN_BUDGET = 'token-budget';
/**
* Drop the oldest turns whenever the last call's adapter-reported input
* tokens exceed `thresholdTokens`.
*
* @example
* ```ts
* import { Agent, tokenBudget } from 'agentfootprint';
*
* const agent = Agent.create({ provider: anthropic(), model: 'claude-sonnet-4-5' })
* .window(tokenBudget({ thresholdTokens: 120_000 }))
* .build();
* ```
*/
function tokenBudget(options) {
const config = (0, options_js_1.resolveTokenBudgetOptions)(options, 'tokenBudget');
return {
name: exports.TOKEN_BUDGET,
async plan(input) {
const { measured } = input;
// Iteration 1: nothing has been sent, so nothing has been counted.
if (measured === undefined)
return undefined;
// Counted, not guessed — the same wall compaction hits, by the same name.
if (measured.input === 0 && measured.output === 0) {
throw new errors_js_1.CompactionUnmeasurableError(input.providerName);
}
if (measured.input <= config.thresholdTokens)
return undefined;
const outcome = (0, drop_js_1.dropOldestSpan)(input, config.keepRecentTurns, exports.TOKEN_BUDGET);
const record = {
strategy: exports.TOKEN_BUDGET,
iteration: input.iteration,
measuredTokens: measured.input,
thresholdTokens: config.thresholdTokens,
overBudget: true,
keepRecentTurns: config.keepRecentTurns,
removedStageIds: outcome.removedStageIds,
removedMessageCount: outcome.removedMessageCount,
windowCharsBefore: outcome.windowCharsBefore,
windowCharsAfter: outcome.windowCharsAfter,
refusals: outcome.refusals,
};
return {
...(outcome.window !== undefined && { window: outcome.window }),
...(outcome.rebase !== undefined && { rebase: outcome.rebase }),
record,
evictions: outcome.evictions,
budgetPressure: {
capTokens: config.thresholdTokens,
projectedTokens: measured.input,
planAction: outcome.removedMessageCount > 0 ? 'evict' : 'none',
},
};
},
};
}
exports.tokenBudget = tokenBudget;
//# sourceMappingURL=tokenBudget.js.map