src / budget.ts
src / budget.ts
import type { PromptVariant } from "./summarizer";
/**
* Small-context clamping: on a small-context model, the configured
* verbatim/chunk/reserve/summary sizes can each exceed what the window can
* hold at all — e.g. keepRecentTokens 6000 on a 4k window leaves no room
* for compaction to ever cut, right where it's needed most. Each field is
* capped to max(floor, ctx * fraction) of the real context length; the
* effective value only ever reduces a configured one, never raises it.
*/
export interface SizeConfig {
contextLength: number | undefined;
keepRecentTokens: number;
chunkTokens: number;
reservedOutputTokens: number;
summarizerMaxTokens: number;
}
export interface EffectiveSizes {
keepRecentTokens: number;
chunkTokens: number;
reservedOutputTokens: number;
summarizerMaxTokens: number;
/** Names of fields that were reduced, for the one-time warning. */
clamped: string[];
}
export function effectiveSizes(cfg: SizeConfig): EffectiveSizes {
const ctx = cfg.contextLength;
if (ctx === undefined || ctx <= 0) {
return {
keepRecentTokens: cfg.keepRecentTokens,
chunkTokens: cfg.chunkTokens,
reservedOutputTokens: cfg.reservedOutputTokens,
summarizerMaxTokens: cfg.summarizerMaxTokens,
clamped: [],
};
}
const clamped: string[] = [];
const clamp = (
name: string,
configured: number,
floor: number,
fraction: number,
): number => {
const cap = Math.max(floor, Math.floor(ctx * fraction));
if (configured > cap) {
clamped.push(name);
return cap;
}
return configured;
};
const keepRecentTokens = clamp(
"keepRecentTokens",
cfg.keepRecentTokens,
512,
0.25,
);
const chunkTokens = clamp("chunkTokens", cfg.chunkTokens, 1024, 0.3);
const reservedOutputTokens = clamp(
"reservedOutputTokens",
cfg.reservedOutputTokens,
256,
0.25,
);
const summarizerMaxTokens = clamp(
"summarizerMaxTokens",
cfg.summarizerMaxTokens,
200,
0.15,
);
return {
keepRecentTokens,
chunkTokens,
reservedOutputTokens,
summarizerMaxTokens,
clamped,
};
}
/**
* Which summarizer system-prompt variant to use this turn. Forced modes
* ("full"/"compact") always win; "auto" always resolves to "full" — bench
* 2026-08-24: full beat compact on recall at 3B and 18B; auto stays full —
* compact remains the rescue prompt and a manual option.
*/
export function pickPromptVariant(
mode: "auto" | "full" | "compact",
): PromptVariant {
if (mode !== "auto") return mode;
return "full";
}
import type { PromptVariant } from "./summarizer";
/**
* Small-context clamping: on a small-context model, the configured
* verbatim/chunk/reserve/summary sizes can each exceed what the window can
* hold at all — e.g. keepRecentTokens 6000 on a 4k window leaves no room
* for compaction to ever cut, right where it's needed most. Each field is
* capped to max(floor, ctx * fraction) of the real context length; the
* effective value only ever reduces a configured one, never raises it.
*/
export interface SizeConfig {
contextLength: number | undefined;
keepRecentTokens: number;
chunkTokens: number;
reservedOutputTokens: number;
summarizerMaxTokens: number;
}
export interface EffectiveSizes {
keepRecentTokens: number;
chunkTokens: number;
reservedOutputTokens: number;
summarizerMaxTokens: number;
/** Names of fields that were reduced, for the one-time warning. */
clamped: string[];
}
export function effectiveSizes(cfg: SizeConfig): EffectiveSizes {
const ctx = cfg.contextLength;
if (ctx === undefined || ctx <= 0) {
return {
keepRecentTokens: cfg.keepRecentTokens,
chunkTokens: cfg.chunkTokens,
reservedOutputTokens: cfg.reservedOutputTokens,
summarizerMaxTokens: cfg.summarizerMaxTokens,
clamped: [],
};
}
const clamped: string[] = [];
const clamp = (
name: string,
configured: number,
floor: number,
fraction: number,
): number => {
const cap = Math.max(floor, Math.floor(ctx * fraction));
if (configured > cap) {
clamped.push(name);
return cap;
}
return configured;
};
const keepRecentTokens = clamp(
"keepRecentTokens",
cfg.keepRecentTokens,
512,
0.25,
);
const chunkTokens = clamp("chunkTokens", cfg.chunkTokens, 1024, 0.3);
const reservedOutputTokens = clamp(
"reservedOutputTokens",
cfg.reservedOutputTokens,
256,
0.25,
);
const summarizerMaxTokens = clamp(
"summarizerMaxTokens",
cfg.summarizerMaxTokens,
200,
0.15,
);
return {
keepRecentTokens,
chunkTokens,
reservedOutputTokens,
summarizerMaxTokens,
clamped,
};
}
/**
* Which summarizer system-prompt variant to use this turn. Forced modes
* ("full"/"compact") always win; "auto" always resolves to "full" — bench
* 2026-08-24: full beat compact on recall at 3B and 18B; auto stays full —
* compact remains the rescue prompt and a manual option.
*/
export function pickPromptVariant(
mode: "auto" | "full" | "compact",
): PromptVariant {
if (mode !== "auto") return mode;
return "full";
}