Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Builtin compaction extension changes

## Summarization must not forward agent tools (2026-08-21)

### What changed
- `getSummarizationTools()` now returns `[]` via `compactionSummarizationTools()` instead of copying `pi.getActiveTools()`.
- The summarizer remains a prose-only LLM call; `snapshot.tools` is empty so models cannot end the summary with a bare `toolUse`.

### Why
- Forwarding the live tool list made tool-preferring models (observed: openai-codex/gpt-5.6-sol) finish summarization with `stopReason: toolUse` and no text.
- `getSummaryText()` only joins `content.type === "text"`, so that becomes `SummaryGenerationError("empty-summary")` and compaction is rejected while tokens stay above the threshold.
- Observed on session `01a01fae` at 257k tokens: auto, retry, and manual `/compact` all rejected with empty-summary `toolUse`. The 2026-07-21 note already documented this failure mode.

## Skip Cursor compaction while the session is not idle (2026-08-19)

Blocking and generated apply refuse `cursor` / `cursor-cli-oauth` when `!ctx.isIdle()`. Mid-run Cursor compact poisons `conversationId`. Idle `agent_end` / `pre_prompt` still compact.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type {
} from "../../types.ts";
import * as checkpointState from "./checkpoint-state.ts";
import * as breaker from "./circuit-breaker.ts";
import { compactionSummarizationTools } from "./summarization-tools.ts";
import {
BUILTIN_CONTEXT_REDUCTION_OPTIONS,
reduceContextMessages,
Expand Down Expand Up @@ -211,16 +212,7 @@ export default function compactionExtension(
const getLogger = (ctx: ExtensionContext): CompactionLogger => (logger ??= createCompactionLogger(ctx.agentDir));

function getSummarizationTools(): Tool[] {
if (typeof pi.getAllTools !== "function" || typeof pi.getActiveTools !== "function") return [];
try {
const definitionsByName = new Map(pi.getAllTools().map((tool) => [tool.name, tool]));
return pi.getActiveTools().flatMap((name) => {
const tool = definitionsByName.get(name);
return tool ? [{ name: tool.name, description: tool.description, parameters: tool.parameters }] : [];
});
} catch {
return [];
}
return compactionSummarizationTools();
}

let idleWarmupTimer: ReturnType<typeof setTimeout> | undefined;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { Tool } from "@earendil-works/pi-ai";

/**
* Summarization is a prose-only LLM call. Forwarding the live agent tool list
* lets tool-preferring models (observed: openai-codex/gpt-5.6-sol) end the
* summary with `stopReason: toolUse` and no text, which senpi rejects as
* `empty-summary` and leaves the session above the compaction threshold.
*/
export function compactionSummarizationTools(_liveTools?: Tool[]): Tool[] {
return [];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { describe, expect, it } from "vitest";
import type { Tool } from "@earendil-works/pi-ai";
import { compactionSummarizationTools } from "../../src/core/extensions/builtin/compaction/summarization-tools.ts";

describe("compactionSummarizationTools", () => {
it("returns no tools even when the live agent list is non-empty", () => {
const live = [
{
name: "bash",
description: "run a command",
parameters: { type: "object", properties: {} },
},
] as Tool[];
expect(compactionSummarizationTools(live)).toEqual([]);
expect(compactionSummarizationTools()).toEqual([]);
});
});
Loading