@adeildo/pi-compact 5.7.1 → 5.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -22,7 +22,9 @@ Output hidden to save context, about 4.2k tokens. It came from: read src/view.ts
22
22
 
23
23
  Long arguments go the same way, like a script sent to bash or a file sent to write. Your messages, the agent's own words, errors and the last 10 turns stay whole. After a prune the agent gets the session's [tasks](../tasks) at the end of its context, so it keeps its goals.
24
24
 
25
- Each prune leaves a line in the chat, with what the next request pays to rewrite the prompt cache and when that pays back. `Ctrl+O` lists the calls it hid.
25
+ On Anthropic models that think, a prune also costs the agent its earlier reasoning. Anthropic signs each thinking block together with the conversation before it, and drops the blocks whose earlier conversation changed. pi then shows "Anthropic dropped N thinking blocks". The visible words stay.
26
+
27
+ Each prune leaves a line in the chat, with the most the next request pays to rewrite the prompt cache and when that pays back. `Ctrl+O` lists the calls it hid.
26
28
 
27
29
  <!-- docs:compact/prune -->
28
30
 
@@ -42,10 +44,10 @@ On six recorded sessions, an agent that only had the summary answered 48 questio
42
44
 
43
45
  - Hiding old tool output works as well as summarizing it and costs less. Summaries made agents run 13 to 15% longer ([JetBrains, NeurIPS 2025](https://arxiv.org/abs/2508.21433)).
44
46
  - A model that picks what to keep cost 94% more with no gain in quality ([OpenHands, 2026](https://arxiv.org/abs/2605.18854)). The rules here are plain code.
45
- - Every edit rewrites the prompt cache ([Anthropic](https://platform.claude.com/docs/en/build-with-claude/context-editing)), so a prune waits until it frees at least a fifth of the context at once. Without that, the same sessions cost 12% more.
47
+ - After a prune, the next request writes the whole conversation to the prompt cache again. Anthropic reuses cache only where an earlier request wrote it ([Anthropic](https://platform.claude.com/docs/en/build-with-claude/context-editing)), and the writes near old outputs have expired. So a prune waits until it frees at least two fifths of the context at once. Pruning a little every turn would cost 3.4 times as much.
46
48
  - Errors stay and every placeholder keeps its call, after [Manus](https://manus.im/blog/Context-Engineering-for-AI-Agents-Lessons-from-Building-Manus).
47
49
 
48
- Replaying a week of real sessions through it, mostly Opus with a 1M context, costs 34% less, the cache rewrites included. `bun packages/compact/eval/replay.ts` replays yours.
50
+ Replaying a week of real sessions through it, mostly Opus with a 1M context, costs 32% less. The replay has each prune's next request write all of the context again, so the real saving is at least that. `bun packages/compact/eval/replay.ts` replays yours.
49
51
 
50
52
  ## With pi's compaction
51
53
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adeildo/pi-compact",
3
- "version": "5.7.1",
3
+ "version": "5.7.2",
4
4
  "description": "Keeps a long pi session's context small: hides old tool output, keeping each call so the agent can run it again, and writes pi's compaction summary from the session's tasks.",
5
5
  "keywords": [
6
6
  "compaction",
@@ -37,7 +37,7 @@
37
37
  "access": "public"
38
38
  },
39
39
  "dependencies": {
40
- "@adeildo/pi-kit": "5.7.1"
40
+ "@adeildo/pi-kit": "5.7.2"
41
41
  },
42
42
  "peerDependencies": {
43
43
  "@earendil-works/pi-ai": "*",
package/src/index.ts CHANGED
@@ -15,7 +15,7 @@ import {
15
15
  type ExtensionContext,
16
16
  } from "@earendil-works/pi-coding-agent";
17
17
 
18
- import { describeCall, KEEP_TURNS, pruneNow, rewrittenAfter, sizeAfter } from "./prune.ts";
18
+ import { describeCall, KEEP_TURNS, pruneNow, sizeAfter } from "./prune.ts";
19
19
  import {
20
20
  hiddenCalls,
21
21
  NOTE_MESSAGE,
@@ -86,7 +86,8 @@ export const compact = defineFeature({
86
86
  message.usage,
87
87
  ctx.model?.cost.cacheRead ?? 0,
88
88
  before - after,
89
- rewrittenAfter(plan, before),
89
+ // Only the system prompt stays cached, so the next request writes all of this again.
90
+ after,
90
91
  );
91
92
  const record: PruneRecord = {
92
93
  before,
package/src/prune.ts CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  // 10 turns did best on SWE-bench: arxiv.org/abs/2508.21433
8
8
  export const KEEP_TURNS = 10;
9
9
  // Each prune rewrites the prompt cache, so it waits to free this much at once.
10
- export const MIN_FREED_SHARE = 0.2;
10
+ export const MIN_FREED_SHARE = 0.4;
11
11
  const MIN_OUTPUT_TOKENS = 250;
12
12
  const MIN_ARGUMENT_CHARS = 1500;
13
13
  const LONG_STRING = 200;
@@ -26,8 +26,6 @@ export interface PrunePlan {
26
26
  /** Estimated, like `total`: compare them, never `freed` with tokens the API reported. */
27
27
  freed: number;
28
28
  total: number;
29
- /** Estimated tokens before the first edit, which the prompt cache keeps. */
30
- cachedPrefix: number;
31
29
  }
32
30
 
33
31
  export type AgentMessage = ProjectedSessionEntry["messages"][number];
@@ -43,7 +41,7 @@ interface Visible {
43
41
  }
44
42
 
45
43
  export function planPrune(entries: readonly ProjectedSessionEntry[]): PrunePlan {
46
- const plan: PrunePlan = { edits: [], hidden: [], freed: 0, total: 0, cachedPrefix: 0 };
44
+ const plan: PrunePlan = { edits: [], hidden: [], freed: 0, total: 0 };
47
45
  const visible = entries.flatMap(({ sourceEntry, messages }): Visible[] =>
48
46
  sourceEntry.type === "message" && messages.length === 1 && messages[0] !== undefined
49
47
  ? [{ id: sourceEntry.id, message: messages[0] }]
@@ -69,11 +67,6 @@ export function planPrune(entries: readonly ProjectedSessionEntry[]): PrunePlan
69
67
  if (message.role === "toolResult") hideOutput(plan, id, message, calls.get(message.toolCallId));
70
68
  if (message.role === "assistant") cutArguments(plan, id, message);
71
69
  }
72
- const edited = new Set(plan.edits.map((edit) => edit.targetId));
73
- const first = visible.findIndex(({ id }) => edited.has(id));
74
- plan.cachedPrefix = visible
75
- .slice(0, Math.max(0, first))
76
- .reduce((sum, { message }) => sum + estimateTokens(message), 0);
77
70
  return plan;
78
71
  }
79
72
 
@@ -95,12 +88,6 @@ export function sizeAfter(plan: PrunePlan, contextTokens: number): number {
95
88
  return Math.round(contextTokens - plan.freed * scale(plan, contextTokens));
96
89
  }
97
90
 
98
- /** What the next request writes to the cache again: everything after the first edit. */
99
- export function rewrittenAfter(plan: PrunePlan, contextTokens: number): number {
100
- const kept = plan.cachedPrefix * scale(plan, contextTokens);
101
- return Math.max(0, Math.round(sizeAfter(plan, contextTokens) - kept));
102
- }
103
-
104
91
  function scale(plan: PrunePlan, contextTokens: number): number {
105
92
  return plan.total === 0 ? 0 : contextTokens / plan.total;
106
93
  }
package/src/record.ts CHANGED
@@ -113,7 +113,7 @@ export function costRows(record: PruneRecord): [string, string][] {
113
113
  const requests = Math.ceil(rewrite / saving);
114
114
  const back = requests === 1 ? "back in 1 request" : `back in ${requests} requests`;
115
115
  return [
116
- ["cache rewrite on the next request", money(rewrite)],
116
+ ["cache rewrite on the next request, at most", money(rewrite)],
117
117
  [`saved on each request after, ${back}`, money(saving)],
118
118
  ];
119
119
  }
package/src/settings.ts CHANGED
@@ -23,7 +23,7 @@ export const pruneAbove = setting({
23
23
  section: PRUNE_SECTION,
24
24
  label: "Above",
25
25
  description:
26
- "Context size that starts a prune. Each prune rewrites the prompt cache once, so it waits until it can free at least a fifth of the context.",
26
+ "Context size that starts a prune. Each prune rewrites the prompt cache once, so it waits until it can free at least two fifths of the context.",
27
27
  control: { type: "number", min: 20_000, max: 2_000_000, step: 10_000, unit: " tokens" },
28
28
  },
29
29
  });