Repository navigation
[EPIC-W][LOU-W3] Summarize compaction strategy with pinned messages - #84
Merged
Merged
Conversation
- 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
…marize # Conflicts: # CHANGELOG.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
summarizeStrategy({ model, protectedTokens?, prompt?, maxSummaryTokens? }): replaces the messages before the protected tail with oneusermessage,"[Conversation summary]\n<summary>", written bymodel(anLLMProvider, or a"provider/model"spec resolved lazily withresolveProvider()). 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 usesprovider.generate()with a focused system instruction (DEFAULT_SUMMARY_PROMPT, whichpromptoverrides) and the run's abort signal. If it fails or returns an empty summary, the strategy falls back to the prune result and returnserror. 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 stayspruneToolResultsStrategy()because two-phase needs a summarizer model.pinMessage(message)/isPinned(message)set and readmetadata.pinned. There is a new optionalMessage.metadata?: Record<string, unknown>insrc/providers/llm.ts. The provider converters (toCoreMessagesin aiSdkProvider) build fresh objects from named fields, sometadatais never sent. Pruning skips pinned results, and summarizing keeps pinned messages along with their whole assistant turn.CompactionStrategy.compact()may returnCompactionResult | Promise<CompactionResult>. The hook awaits it, and if a strategy throws, the hook leaves the run alone and reportserrortoonCompaction.CompactionResultandCompactionInfogainsummary?anderror?.CompactionInputgainsthresholdTokens(so two-phase knows its target) andsignal.thresholdPercentmoved from the hook options toCompactMessagesOptions, 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/compaction.mdgains summarize, two-phase and pinning sections, and its snippets are updated for the asynccompactMessages.docs/api-overview.md, CHANGELOG (Added) and the regeneratedllms-full.txtare updated too.Not in this PR: the typed
compaction.*stream events andcreateAgent({ compaction })belong to LOU-W3.2.onCompaction(now withsummaryanderror) 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.compactmay returnCompactionResult | Promise<CompactionResult>; the hook awaits either;CompactionResultgainssummary?: stringsummarizeStrategy({ 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); usesgenerate()with a focused instruction; failures fall back to the prune result withonCompaction({ ..., error })and never throw into the runtwoPhaseStrategy()prunes first and summarizes if still above the threshold; documented as the recommended strategypinMessage()and themetadata.pinnedmarker (new optionalMessage.metadata, ignored by the provider converters), honored by every built-in strategysrc/context/compaction.summarize.test.ts, usingmockModelas 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 witherror; two-phase skips the summarizer when pruning is enough; a throwing strategy is reported, not thrown; an async strategy runs end to end throughAgentExecutor.execute({ hooks })docs/compaction.mdsummarize, two-phase and pinning sections; llms files regenerated; CHANGELOG entry (Added)src/context/*,src/providers/llm.ts(metadata) and docsHow verified
npx tsc --noEmit: no errorsnpm run lint: 0 errors, 433 warnings. The changed files add none; the 4 warnings ESLint reports forsrc/providers/llm.tsare on existinganylines.npm run test:types: no type errorsnpx vitest run(after the build): 141 files passed, 2070 tests passed, 3 skippednpm run build && npm run build --workspace=packages/create-loushy-agent: both exit 0npm run docs:verify-snippets -- --skip-build: all 96 snippets type-check against src/; 6 also run cleanlynpm run docs:llms:check: exit 0npm run test:coverage: 141 files passed, 2070 tests passed, 3 skippednpm 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