fix(api): prune superseded read_letter snapshots

SNAPSHOT_TOOL_NAMES lacked read_letter, and the snapshot check looked for the resume key on every result other than read_resume's, so each cover letter read stayed in the model context. The superseded note now names both read tools.
This commit is contained in:
Amruth Pillai
2026-09-29 22:10:14 +02:00
parent 2622eeff12
commit bc1aaedf1f
2 changed files with 20 additions and 7 deletions
@@ -4,11 +4,11 @@ import { estimateTokens, pruneAgentModelContext } from "./context";
const BIG_RESUME = { basics: { name: "Alice" }, sections: { summary: { content: "x".repeat(2_000) } } };
function readResumeExchange(callId: string): ModelMessage[] {
function readResumeExchange(callId: string, toolName = "read_resume"): ModelMessage[] {
return [
{
role: "assistant",
content: [{ type: "tool-call", toolCallId: callId, toolName: "read_resume", input: {} }],
content: [{ type: "tool-call", toolCallId: callId, toolName, input: {} }],
},
{
role: "tool",
@@ -16,7 +16,7 @@ function readResumeExchange(callId: string): ModelMessage[] {
{
type: "tool-result",
toolCallId: callId,
toolName: "read_resume",
toolName,
output: { type: "json", value: { id: "resume-1", data: BIG_RESUME } },
},
],
@@ -64,10 +64,23 @@ describe("pruneAgentModelContext — tier 0 (snapshot supersession)", () => {
const pruned = pruneAgentModelContext(messages, 1_000_000);
expect(snapshotValue(pruned[2])).not.toHaveProperty("data");
expect(snapshotValue(pruned[2]).note).toContain("Superseded resume snapshot");
expect(snapshotValue(pruned[2]).note).toContain("Superseded document snapshot");
expect(snapshotValue(pruned[4])).toHaveProperty("resume");
});
it("keeps only the last cover letter snapshot too", () => {
const messages = [
user("hi"),
...readResumeExchange("call-1", "read_letter"),
...readResumeExchange("call-2", "read_letter"),
];
const pruned = pruneAgentModelContext(messages, 1_000_000);
expect(snapshotValue(pruned[2])).not.toHaveProperty("data");
expect(snapshotValue(pruned[4])).toHaveProperty("data");
});
it("returns the same array reference when there is at most one snapshot", () => {
const messages = [user("hi"), ...readResumeExchange("call-1")];
+3 -3
View File
@@ -8,9 +8,9 @@ import { estimateTokenCount as estimateTextTokenCount } from "tokenx";
const AGENT_CONTEXT_TOKEN_BUDGET = 40_000;
const SNAPSHOT_TOOL_NAMES = new Set(["read_resume", "apply_resume_patch"]);
const SNAPSHOT_TOOL_NAMES = new Set(["read_resume", "read_letter", "apply_resume_patch"]);
const SUPERSEDED_SNAPSHOT_NOTE =
"Superseded resume snapshot removed. Base further edits on the resume state in the latest read_resume or apply_resume_patch result.";
"Superseded document snapshot removed. Base further edits on the latest read_resume, read_letter or apply_resume_patch result.";
const PRUNED_TOOL_RESULT_NOTE = "Older tool result pruned to fit the context budget.";
type LoosePart = Record<string, unknown> & { type: string };
@@ -69,7 +69,7 @@ function isSnapshotResultPart(part: LoosePart): boolean {
const value = unwrapToolOutput(part.output);
if (!value || typeof value !== "object") return false;
return part.toolName === "read_resume" ? "data" in value : "resume" in value;
return part.toolName === "apply_resume_patch" ? "resume" in value : "data" in value;
}
function supersedeSnapshotPart(part: LoosePart): LoosePart {