Skip to content

Commit 8ca97f1

Browse files
CL-9007: ship shared summary-plus-small-tail pipeline for automatic and manual compaction (#1177)
* test(session): cover budgeted tail pipeline for compaction folds Red state for the shared summary-plus-small-tail pipeline: tail budget with shortened tool outputs, pair-safe cuts, whole newest user messages, repeat folds updating the prior summary, and archive retrievability. These fail against the last-N-verbatim keep window. * feat(session): fold a structured summary plus a small budgeted tail Route every pruning-compactor fold through one budgeted tail selector: the tail holds at least the last keepRecentTurns turns and extends older while the next whole pick fits tailBudgetTokens. Tail tool outputs ride head/tail excerpted with a sentinel; cuts never split a tool pair; newest user messages stay whole; prior spines fold into the next handoff while the summary updates them via priorSummary. * fix(session): emit excerpted tail copies when nothing is summarized A keep-set that covers every foldable turn still has to ship the excerpted live copies the tail selector already paid for. Returning the unexcerpted turns keeps the occupancy that armed the compact. Skip re-excerpting only on the structured marker so a body that merely mentions the prefix is still shortened.
1 parent 5cf8c62 commit 8ca97f1

12 files changed

Lines changed: 1070 additions & 53 deletions

‎src/context-compactor.test.ts‎

Lines changed: 92 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ function hasConsecutiveSameRole(turns: ConversationTurn[]): boolean {
5959
describe("createPruningCompactor", () => {
6060
test("returns turns unchanged when under the keep threshold", async () => {
6161
const compactor = createPruningCompactor({
62+
// CL-9007: pin a tiny tail budget so the fold covers the same older
63+
// region the old keepRecentTurns cut folded.
64+
compactionShape: { tailBudgetTokens: 10 },
6265
keepRecentTurns: 5,
6366
summaryMaxChars: 500,
6467
});
@@ -78,16 +81,28 @@ describe("createPruningCompactor", () => {
7881
// compactorNoOpFloor accordingly breaks that guarantee silently.
7982
const keepRecentTurns = 3;
8083
const compactor = createPruningCompactor({
84+
// CL-9007: pin a tiny tail budget so the fold covers the same older
85+
// region the old keepRecentTurns cut folded.
86+
compactionShape: { tailBudgetTokens: 10 },
8187
keepRecentTurns,
8288
summaryMaxChars: 500,
8389
});
8490
const floor = compactorNoOpFloor(keepRecentTurns);
8591

92+
// Bodies exceed the pinned tail budget, so past-floor always has a folded
93+
// region while at-floor still no-ops on the count check alone.
94+
const body = (i: number): string => `body ${i} ` + "x".repeat(100);
8695
const atFloor = Array.from({ length: floor }, (_, i) =>
87-
makeTurn({ role: i % 2 === 0 ? "user" : "assistant" }),
96+
makeTurn({
97+
role: i % 2 === 0 ? "user" : "assistant",
98+
content: [{ type: "text", text: body(i) }],
99+
}),
88100
);
89101
const pastFloor = Array.from({ length: floor + 1 }, (_, i) =>
90-
makeTurn({ role: i % 2 === 0 ? "user" : "assistant" }),
102+
makeTurn({
103+
role: i % 2 === 0 ? "user" : "assistant",
104+
content: [{ type: "text", text: body(i) }],
105+
}),
91106
);
92107

93108
expect(
@@ -100,6 +115,9 @@ describe("createPruningCompactor", () => {
100115

101116
test("compacts old turns and preserves recent ones", async () => {
102117
const compactor = createPruningCompactor({
118+
// CL-9007: pin a tiny tail budget so the fold covers the same older
119+
// region the old keepRecentTurns cut folded.
120+
compactionShape: { tailBudgetTokens: 10 },
103121
keepRecentTurns: 2,
104122
summaryMaxChars: 500,
105123
});
@@ -150,6 +168,9 @@ describe("createPruningCompactor", () => {
150168

151169
test("preserves tool_call and tool_result blocks in recent turns", async () => {
152170
const compactor = createPruningCompactor({
171+
// CL-9007: pin a tiny tail budget so the fold covers the same older
172+
// region the old keepRecentTurns cut folded.
173+
compactionShape: { tailBudgetTokens: 10 },
153174
keepRecentTurns: 1,
154175
summaryMaxChars: 500,
155176
});
@@ -183,6 +204,9 @@ describe("createPruningCompactor", () => {
183204
describe("createPruningCompactor — initiating task preservation", () => {
184205
test("keeps the initiating task verbatim even when it is far outside the recent window", async () => {
185206
const compactor = createPruningCompactor({
207+
// CL-9007: pin a tiny tail budget so the fold covers the same older
208+
// region the old keepRecentTurns cut folded.
209+
compactionShape: { tailBudgetTokens: 10 },
186210
keepRecentTurns: 2,
187211
maxAnchorTurns: 1,
188212
summaryMaxChars: 500,
@@ -232,6 +256,9 @@ describe("createPruningCompactor — initiating task preservation", () => {
232256

233257
test("emits the compaction summary as a user turn, never system", async () => {
234258
const compactor = createPruningCompactor({
259+
// CL-9007: pin a one-token tail budget — only the mandatory floor stays
260+
// live, so this tiny fixture still folds the same older region.
261+
compactionShape: { tailBudgetTokens: 1 },
235262
keepRecentTurns: 1,
236263
summaryMaxChars: 500,
237264
});
@@ -247,6 +274,9 @@ describe("createPruningCompactor — initiating task preservation", () => {
247274

248275
test("never emits consecutive same-role turns, even with adjacent user anchors", async () => {
249276
const compactor = createPruningCompactor({
277+
// CL-9007: pin a one-token tail budget — only the mandatory floor stays
278+
// live, so this tiny fixture still folds the same older region.
279+
compactionShape: { tailBudgetTokens: 1 },
250280
keepRecentTurns: 2,
251281
summaryMaxChars: 500,
252282
});
@@ -270,6 +300,9 @@ describe("createPruningCompactor — initiating task preservation", () => {
270300

271301
test("keeps alternating roles when a tool_result user turn abuts a plain user turn", async () => {
272302
const compactor = createPruningCompactor({
303+
// CL-9007: pin a tiny tail budget so the fold covers the same older
304+
// region the old keepRecentTurns cut folded.
305+
compactionShape: { tailBudgetTokens: 10 },
273306
keepRecentTurns: 1,
274307
maxAnchorTurns: 3,
275308
summaryMaxChars: 500,
@@ -338,6 +371,9 @@ describe("createPruningCompactor — image aging", () => {
338371

339372
test("strips image bytes from an anchored (aged) turn but keeps its text", async () => {
340373
const compactor = createPruningCompactor({
374+
// CL-9007: pin a tiny tail budget so the fold covers the same older
375+
// region the old keepRecentTurns cut folded.
376+
compactionShape: { tailBudgetTokens: 10 },
341377
keepRecentTurns: 2,
342378
maxAnchorTurns: 1,
343379
summaryMaxChars: 500,
@@ -405,6 +441,9 @@ describe("createPruningCompactor — image aging", () => {
405441

406442
test("keeps an image intact when its turn is still within the recent window", async () => {
407443
const compactor = createPruningCompactor({
444+
// CL-9007: pin a tiny tail budget so the fold covers the same older
445+
// region the old keepRecentTurns cut folded.
446+
compactionShape: { tailBudgetTokens: 10 },
408447
keepRecentTurns: 3,
409448
summaryMaxChars: 500,
410449
});
@@ -437,6 +476,9 @@ describe("createPruningCompactor — image aging", () => {
437476
// With few turns, full pruning is a no-op, but images outside keepRecentTurns
438477
// must still spill so they are not resent as base64 forever.
439478
const compactor = createPruningCompactor({
479+
// CL-9007: pin a tiny tail budget so the fold covers the same older
480+
// region the old keepRecentTurns cut folded.
481+
compactionShape: { tailBudgetTokens: 10 },
440482
keepRecentTurns: 2,
441483
summaryMaxChars: 500,
442484
});
@@ -478,6 +520,9 @@ describe("createPruningCompactor — image aging", () => {
478520

479521
test("records the number of turns aged out in the transform record", async () => {
480522
const compactor = createPruningCompactor({
523+
// CL-9007: pin a tiny tail budget so the fold covers the same older
524+
// region the old keepRecentTurns cut folded.
525+
compactionShape: { tailBudgetTokens: 10 },
481526
keepRecentTurns: 1,
482527
maxAnchorTurns: 1,
483528
summaryMaxChars: 500,
@@ -540,6 +585,9 @@ describe("createPruningCompactor — error anchoring (CL-6906)", () => {
540585
...padding(8, "after"),
541586
];
542587
const compactor = createPruningCompactor({
588+
// CL-9007: pin a tiny tail budget so the fold covers the same older
589+
// region the old keepRecentTurns cut folded.
590+
compactionShape: { tailBudgetTokens: 10 },
543591
keepRecentTurns: 6,
544592
maxAnchorTurns: 8,
545593
summaryMaxChars: 2000,
@@ -587,6 +635,9 @@ describe("createPruningCompactor — error anchoring (CL-6906)", () => {
587635
...padding(8, "after"),
588636
];
589637
const compactor = createPruningCompactor({
638+
// CL-9007: pin a tiny tail budget so the fold covers the same older
639+
// region the old keepRecentTurns cut folded.
640+
compactionShape: { tailBudgetTokens: 10 },
590641
keepRecentTurns: 6,
591642
maxAnchorTurns: 8,
592643
summaryMaxChars: 2000,
@@ -648,6 +699,9 @@ describe("createPruningCompactor — error anchoring (CL-6906)", () => {
648699
...padding(8, "after"),
649700
];
650701
const compactor = createPruningCompactor({
702+
// CL-9007: pin a tiny tail budget so the fold covers the same older
703+
// region the old keepRecentTurns cut folded.
704+
compactionShape: { tailBudgetTokens: 10 },
651705
keepRecentTurns: 6,
652706
maxAnchorTurns: 8,
653707
summaryMaxChars: 2000,
@@ -734,6 +788,9 @@ describe("createPruningCompactor — maxAnchorTurns caps pairing pulls (CL-6906)
734788

735789
const maxAnchorTurns = 4;
736790
const compactor = createPruningCompactor({
791+
// CL-9007: pin a tiny tail budget so the fold covers the same older
792+
// region the old keepRecentTurns cut folded.
793+
compactionShape: { tailBudgetTokens: 10 },
737794
keepRecentTurns: 6,
738795
maxAnchorTurns,
739796
summaryMaxChars: 2000,
@@ -755,6 +812,9 @@ describe("createPruningCompactor — summarize receives the workflow context (CL
755812
let capturedCtx: unknown = "not called";
756813
const workflowCtx = { workflow: { name: "build", stepIndex: 2, total: 7 } };
757814
const compactor = createPruningCompactor({
815+
// CL-9007: pin a one-token tail budget — only the mandatory floor stays
816+
// live, so this tiny fixture still folds the same older region.
817+
compactionShape: { tailBudgetTokens: 1 },
758818
keepRecentTurns: 1,
759819
summaryMaxChars: 500,
760820
summaryContext: () => workflowCtx,
@@ -776,6 +836,9 @@ describe("createPruningCompactor — summarize receives the workflow context (CL
776836
describe("createPruningCompactor — operator extra instructions", () => {
777837
test("stores extra instructions on the compact record", async () => {
778838
const compactor = createPruningCompactor({
839+
// CL-9007: pin a tiny tail budget so the fold covers the same older
840+
// region the old keepRecentTurns cut folded.
841+
compactionShape: { tailBudgetTokens: 10 },
779842
keepRecentTurns: 1,
780843
summaryMaxChars: 500,
781844
summaryContext: () => ({ extraInstructions: "keep the auth discussion" }),
@@ -799,6 +862,9 @@ describe("createPruningCompactor — operator extra instructions", () => {
799862
makeTurn({ role: "user", content: [{ type: "text", text: "recent" }] }),
800863
];
801864
const written = await createPruningCompactor({
865+
// CL-9007: pin a one-token tail budget — only the mandatory floor stays
866+
// live, so this tiny fixture still folds the same older region.
867+
compactionShape: { tailBudgetTokens: 1 },
802868
keepRecentTurns: 1,
803869
summaryMaxChars: 500,
804870
summaryContext: () => ({ extraInstructions: "keep the auth discussion" }),
@@ -812,6 +878,9 @@ describe("createPruningCompactor — operator extra instructions", () => {
812878

813879
let captured: { extraInstructions?: string } | undefined;
814880
const next = await createPruningCompactor({
881+
// CL-9007: pin a one-token tail budget — only the mandatory floor stays
882+
// live, so this tiny fixture still folds the same older region.
883+
compactionShape: { tailBudgetTokens: 1 },
815884
keepRecentTurns: 1,
816885
summaryMaxChars: 500,
817886
summaryContext: () => {
@@ -860,6 +929,9 @@ describe("createPruningCompactor — consolidated handoff (CL-7521)", () => {
860929

861930
test("second apply replaces the prior summary instead of accumulating", async () => {
862931
const compactor = createPruningCompactor({
932+
// CL-9007: pin a tiny tail budget so the fold covers the same older
933+
// region the old keepRecentTurns cut folded.
934+
compactionShape: { tailBudgetTokens: 10 },
863935
keepRecentTurns: 2,
864936
summaryMaxChars: 500,
865937
});
@@ -880,6 +952,9 @@ describe("createPruningCompactor — consolidated handoff (CL-7521)", () => {
880952

881953
test("second apply keeps the initiating task as its own user turn", async () => {
882954
const compactor = createPruningCompactor({
955+
// CL-9007: pin a tiny tail budget so the fold covers the same older
956+
// region the old keepRecentTurns cut folded.
957+
compactionShape: { tailBudgetTokens: 10 },
883958
keepRecentTurns: 2,
884959
maxAnchorTurns: 1,
885960
summaryMaxChars: 500,
@@ -941,6 +1016,9 @@ describe("createPruningCompactor — consolidated handoff (CL-7521)", () => {
9411016

9421017
test("harness spacer is stamped with the reserved producer id and a visible sentinel", async () => {
9431018
const compactor = createPruningCompactor({
1019+
// CL-9007: pin a tiny tail budget so the fold covers the same older
1020+
// region the old keepRecentTurns cut folded.
1021+
compactionShape: { tailBudgetTokens: 10 },
9441022
keepRecentTurns: 2,
9451023
summaryMaxChars: 500,
9461024
});
@@ -983,6 +1061,9 @@ describe("createPruningCompactor — consolidated handoff (CL-7521)", () => {
9831061

9841062
test("empty-fold keep-set returns the input unchanged", async () => {
9851063
const compactor = createPruningCompactor({
1064+
// CL-9007: pin a tiny tail budget so the fold covers the same older
1065+
// region the old keepRecentTurns cut folded.
1066+
compactionShape: { tailBudgetTokens: 10 },
9861067
keepRecentTurns: 1,
9871068
maxAnchorTurns: 8,
9881069
summaryMaxChars: 500,
@@ -1028,6 +1109,9 @@ describe("createPruningCompactor — consolidated handoff (CL-7521)", () => {
10281109
},
10291110
});
10301111
const compactor = createPruningCompactor({
1112+
// CL-9007: pin a tiny tail budget so the fold covers the same older
1113+
// region the old keepRecentTurns cut folded.
1114+
compactionShape: { tailBudgetTokens: 10 },
10311115
keepRecentTurns: 2,
10321116
summaryMaxChars: 500,
10331117
summarize,
@@ -1248,6 +1332,9 @@ describe("buildLLMTurnSummary", () => {
12481332
describe("buildTurnSummary via createPruningCompactor", () => {
12491333
test("summarizes tool_call and tool_result blocks in compacted turns", async () => {
12501334
const compactor = createPruningCompactor({
1335+
// CL-9007: pin a tiny tail budget so the fold covers the same older
1336+
// region the old keepRecentTurns cut folded.
1337+
compactionShape: { tailBudgetTokens: 10 },
12511338
keepRecentTurns: 1,
12521339
summaryMaxChars: 2000,
12531340
});
@@ -1319,6 +1406,9 @@ describe("buildTurnSummary via createPruningCompactor", () => {
13191406
test("a truncated lying spine aborts instead of shipping", async () => {
13201407
const maxChars = 20;
13211408
const compactor = createPruningCompactor({
1409+
// CL-9007: pin a tiny tail budget so the fold covers the same older
1410+
// region the old keepRecentTurns cut folded.
1411+
compactionShape: { tailBudgetTokens: 10 },
13221412
keepRecentTurns: 1,
13231413
summaryMaxChars: maxChars,
13241414
});

0 commit comments

Comments
 (0)