Skip to content

Commit 3e2a05f

Browse files
Takhoffmansteipete
authored andcommitted
Restore reserve-based overflow precheck
1 parent ceb6860 commit 3e2a05f

3 files changed

Lines changed: 134 additions & 1 deletion

File tree

src/agents/pi-embedded-runner/run/attempt.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,10 @@ import {
120120
} from "../prompt-cache-observability.js";
121121
import { resolveCacheRetention } from "../prompt-cache-retention.js";
122122
import { sanitizeSessionHistory, validateReplayTurns } from "../replay-history.js";
123+
import {
124+
PREEMPTIVE_OVERFLOW_ERROR_TEXT,
125+
shouldPreemptivelyCompactBeforePrompt,
126+
} from "./preemptive-compaction.js";
123127
import {
124128
clearActiveEmbeddedRun,
125129
type EmbeddedPiQueueHandle,
@@ -1521,7 +1525,7 @@ export async function runEmbeddedAttempt(
15211525
const hookAgentId = sessionAgentId;
15221526

15231527
let promptError: unknown = null;
1524-
let promptErrorSource: "prompt" | "compaction" | null = null;
1528+
let promptErrorSource: "prompt" | "compaction" | "precheck" | null = null;
15251529
let prePromptMessageCount = activeSession.messages.length;
15261530
try {
15271531
const promptStartedAt = Date.now();
@@ -1761,6 +1765,27 @@ export async function runEmbeddedAttempt(
17611765
});
17621766
}
17631767

1768+
const reserveTokens = settingsManager.getCompactionReserveTokens();
1769+
const preemptiveCompaction = shouldPreemptivelyCompactBeforePrompt({
1770+
messages: activeSession.messages,
1771+
systemPrompt: systemPromptText,
1772+
prompt: effectivePrompt,
1773+
contextTokenBudget: params.contextTokenBudget,
1774+
reserveTokens,
1775+
});
1776+
if (preemptiveCompaction.shouldCompact) {
1777+
promptError = new Error(PREEMPTIVE_OVERFLOW_ERROR_TEXT);
1778+
promptErrorSource = "precheck";
1779+
log.warn(
1780+
`[context-overflow-precheck] sessionKey=${params.sessionKey ?? params.sessionId} ` +
1781+
`provider=${params.provider}/${params.modelId} ` +
1782+
`estimatedPromptTokens=${preemptiveCompaction.estimatedPromptTokens} ` +
1783+
`promptBudgetBeforeReserve=${preemptiveCompaction.promptBudgetBeforeReserve} ` +
1784+
`reserveTokens=${reserveTokens} sessionFile=${params.sessionFile}`,
1785+
);
1786+
return;
1787+
}
1788+
17641789
const btwSnapshotMessages = activeSession.messages.slice(-MAX_BTW_SNAPSHOT_MESSAGES);
17651790
updateActiveEmbeddedRunSnapshot(params.sessionId, {
17661791
transcriptLeafId,
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { describe, expect, it } from "vitest";
2+
import {
3+
PREEMPTIVE_OVERFLOW_ERROR_TEXT,
4+
estimatePrePromptTokens,
5+
shouldPreemptivelyCompactBeforePrompt,
6+
} from "./preemptive-compaction.js";
7+
8+
describe("preemptive-compaction", () => {
9+
const verboseHistory =
10+
"alpha beta gamma delta epsilon zeta eta theta iota kappa lambda mu ".repeat(40);
11+
const verboseSystem =
12+
"system guidance with multiple distinct words to avoid tokenizer overcompression ".repeat(25);
13+
const verbosePrompt =
14+
"user request with distinct content asking for a detailed answer and more context ".repeat(25);
15+
16+
it("exports a context-overflow-compatible precheck error text", () => {
17+
expect(PREEMPTIVE_OVERFLOW_ERROR_TEXT).toContain("Context overflow:");
18+
expect(PREEMPTIVE_OVERFLOW_ERROR_TEXT).toContain("(precheck)");
19+
});
20+
21+
it("raises the estimate as prompt-side content grows", () => {
22+
const smaller = estimatePrePromptTokens({
23+
messages: [{ role: "assistant", content: verboseHistory }],
24+
systemPrompt: "sys",
25+
prompt: "hello",
26+
});
27+
const larger = estimatePrePromptTokens({
28+
messages: [{ role: "assistant", content: verboseHistory }],
29+
systemPrompt: verboseSystem,
30+
prompt: verbosePrompt,
31+
});
32+
33+
expect(larger).toBeGreaterThan(smaller);
34+
});
35+
36+
it("requests preemptive compaction when the reserve-based prompt budget would be exceeded", () => {
37+
const result = shouldPreemptivelyCompactBeforePrompt({
38+
messages: [{ role: "assistant", content: verboseHistory }],
39+
systemPrompt: verboseSystem,
40+
prompt: verbosePrompt,
41+
contextTokenBudget: 500,
42+
reserveTokens: 50,
43+
});
44+
45+
expect(result.shouldCompact).toBe(true);
46+
expect(result.estimatedPromptTokens).toBeGreaterThan(result.promptBudgetBeforeReserve);
47+
});
48+
49+
it("does not request preemptive compaction when the reserve-based prompt budget still fits", () => {
50+
const result = shouldPreemptivelyCompactBeforePrompt({
51+
messages: [{ role: "assistant", content: "short history" }],
52+
systemPrompt: "sys",
53+
prompt: "hello",
54+
contextTokenBudget: 10_000,
55+
reserveTokens: 1_000,
56+
});
57+
58+
expect(result.shouldCompact).toBe(false);
59+
expect(result.estimatedPromptTokens).toBeLessThan(result.promptBudgetBeforeReserve);
60+
});
61+
});
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import type { AgentMessage } from "@mariozechner/pi-agent-core";
2+
import { estimateTokens } from "@mariozechner/pi-coding-agent";
3+
import { SAFETY_MARGIN, estimateMessagesTokens } from "../../compaction.js";
4+
5+
export const PREEMPTIVE_OVERFLOW_ERROR_TEXT =
6+
"Context overflow: prompt too large for the model (precheck).";
7+
8+
export function estimatePrePromptTokens(params: {
9+
messages: AgentMessage[];
10+
systemPrompt?: string;
11+
prompt: string;
12+
}): number {
13+
const { messages, systemPrompt, prompt } = params;
14+
const syntheticMessages: AgentMessage[] = [];
15+
if (typeof systemPrompt === "string" && systemPrompt.trim().length > 0) {
16+
syntheticMessages.push({ role: "system", content: systemPrompt } as AgentMessage);
17+
}
18+
syntheticMessages.push({ role: "user", content: prompt } as AgentMessage);
19+
20+
const estimated =
21+
estimateMessagesTokens(messages) +
22+
syntheticMessages.reduce((sum, message) => sum + estimateTokens(message), 0);
23+
return Math.max(0, Math.ceil(estimated * SAFETY_MARGIN));
24+
}
25+
26+
export function shouldPreemptivelyCompactBeforePrompt(params: {
27+
messages: AgentMessage[];
28+
systemPrompt?: string;
29+
prompt: string;
30+
contextTokenBudget: number;
31+
reserveTokens: number;
32+
}): {
33+
shouldCompact: boolean;
34+
estimatedPromptTokens: number;
35+
promptBudgetBeforeReserve: number;
36+
} {
37+
const estimatedPromptTokens = estimatePrePromptTokens(params);
38+
const promptBudgetBeforeReserve = Math.max(
39+
1,
40+
Math.floor(params.contextTokenBudget) - Math.max(0, Math.floor(params.reserveTokens)),
41+
);
42+
return {
43+
shouldCompact: estimatedPromptTokens > promptBudgetBeforeReserve,
44+
estimatedPromptTokens,
45+
promptBudgetBeforeReserve,
46+
};
47+
}

0 commit comments

Comments
 (0)