Skip to content

[EPIC-W][LOU-W3] Summarize compaction strategy with pinned messages - #84

Merged
LinuxDevil merged 2 commits into
mainfrom
lou-w3-compaction-summarize
Oct 1, 2026
Merged

LinuxDevil merged 2 commits into
mainfrom
lou-w3-compaction-summarize

Conversation

@LinuxDevil

Copy link
Copy Markdown
Owner

What

  • summarizeStrategy({ model, protectedTokens?, prompt?, maxSummaryTokens? }): replaces the messages before the protected tail with one user message, "[Conversation summary]\n<summary>", written by model (an LLMProvider, or a "provider/model" spec resolved lazily with resolveProvider()). System and pinned messages stay where they are. An assistant turn is folded or kept together with all its tool results, and the tail always starts on a group boundary and holds the last message, so every tool call keeps its result. The summary call uses provider.generate() with a focused system instruction (DEFAULT_SUMMARY_PROMPT, which prompt overrides) and the run's abort signal. If it fails or returns an empty summary, the strategy falls back to the prune result and returns error. It never throws.
  • twoPhaseStrategy(options): prunes first and only summarizes, on the pruned conversation, when it is still above the threshold. The docs give it as the recommended strategy. The hook's default stays pruneToolResultsStrategy() because two-phase needs a summarizer model.
  • Pinning: pinMessage(message) / isPinned(message) set and read metadata.pinned. There is a new optional Message.metadata?: Record<string, unknown> in src/providers/llm.ts. The provider converters (toCoreMessages in aiSdkProvider) build fresh objects from named fields, so metadata is never sent. Pruning skips pinned results, and summarizing keeps pinned messages along with their whole assistant turn.
  • Async strategies: CompactionStrategy.compact() may return CompactionResult | Promise<CompactionResult>. The hook awaits it, and if a strategy throws, the hook leaves the run alone and reports error to onCompaction. CompactionResult and CompactionInfo gain summary? and error?. CompactionInput gains thresholdTokens (so two-phase knows its target) and signal. thresholdPercent moved from the hook options to CompactMessagesOptions, and the hook options still inherit it.
  • compactMessages() now returns a Promise. This changes code from LOU-W2 ([EPIC-W][LOU-W2] Context compaction: prune old tool results behind a pluggable strategy #80), which has not been released yet; the CHANGELOG entry notes it.
  • Docs: docs/compaction.md gains summarize, two-phase and pinning sections, and its snippets are updated for the async compactMessages. docs/api-overview.md, CHANGELOG (Added) and the regenerated llms-full.txt are updated too.

Not in this PR: the typed compaction.* stream events and createAgent({ compaction }) belong to LOU-W3.2. onCompaction (now with summary and error) is where they will plug in.

Why

Parity with open-harness, which compacts in two phases (prune, then an LLM summary), and with eve, which compacts at a threshold. Pruning alone cannot shrink a run whose size is in user and assistant turns, or one with too many small results.

Acceptance criteria

  • CompactionStrategy.compact may return CompactionResult | Promise<CompactionResult>; the hook awaits either; CompactionResult gains summary?: string
  • summarizeStrategy({ model: LLMProvider | string, protectedTokens?, prompt?, maxSummaryTokens? }): replaces the compactable prefix (excluding system and pinned) with one [Conversation summary] user message; summarizes whole assistant-turn + tool-result groups (no orphaned call or result); uses generate() with a focused instruction; failures fall back to the prune result with onCompaction({ ..., error }) and never throw into the run
  • twoPhaseStrategy() prunes first and summarizes if still above the threshold; documented as the recommended strategy
  • pinMessage() and the metadata.pinned marker (new optional Message.metadata, ignored by the provider converters), honored by every built-in strategy
  • Tests (src/context/compaction.summarize.test.ts, using mockModel as the summarizer): the summary replaces the prefix and the tail stays intact (identity-checked); groups stay whole; pinned messages survive prune, summarize and two-phase, and are not sent to the summarizer; transcripts stay provider-valid (every call has a result and every result has a call); summarizer failure, an empty summary and an unresolvable model spec fall back to prune with error; two-phase skips the summarizer when pruning is enough; a throwing strategy is reported, not thrown; an async strategy runs end to end through AgentExecutor.execute({ hooks })
  • Docs: docs/compaction.md summarize, two-phase and pinning sections; llms files regenerated; CHANGELOG entry (Added)
  • Scope: only src/context/*, src/providers/llm.ts (metadata) and docs

How verified

  • npx tsc --noEmit: no errors
  • npm run lint: 0 errors, 433 warnings. The changed files add none; the 4 warnings ESLint reports for src/providers/llm.ts are on existing any lines.
  • npm run test:types: no type errors
  • npx vitest run (after the build): 141 files passed, 2070 tests passed, 3 skipped
  • npm run build && npm run build --workspace=packages/create-loushy-agent: both exit 0
  • npm run docs:verify-snippets -- --skip-build: all 96 snippets type-check against src/; 6 also run cleanly
  • npm run docs:llms:check: exit 0
  • npm run test:coverage: 141 files passed, 2070 tests passed, 3 skipped
  • npm run fallow: exit 0, "No issues found". The one existing clone group is between AnthropicProvider and OpenAIProvider and is not from this change.

🤖 Generated with Claude Code

https://claude.ai/code/session_016bY29ibQgv3MDdjAbUT4YR


Generated by Claude Code

claude added 2 commits October 1, 2026 14:07
- summarizeStrategy({ model, protectedTokens?, prompt?, maxSummaryTokens? })
  replaces the turns before the protected tail with one
  "[Conversation summary]" user message written by the summarizer model,
  keeping system/pinned messages and whole assistant+tool-result groups so
  the transcript stays provider-valid; on failure it falls back to pruning
  and reports `error`.
- twoPhaseStrategy() (recommended): prune first, summarize only if still
  above the threshold.
- pinMessage()/isPinned() and optional Message.metadata (never sent to
  providers); every built-in strategy skips pinned messages.
- CompactionStrategy.compact() may be async; compactMessages() returns a
  Promise; the hook awaits the strategy and reports a thrown error to
  onCompaction instead of failing the run. CompactionResult/CompactionInfo
  gain summary/error; CompactionInput gains thresholdTokens/signal.
- Docs: docs/compaction.md summarize/two-phase/pinning sections,
  api-overview, CHANGELOG, regenerated llms-full.txt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bY29ibQgv3MDdjAbUT4YR
@LinuxDevil
LinuxDevil merged commit 30ca585 into main Oct 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants