feat: inject compression summaries as tool-result recaps (acp_context_recap)#95
Merged
Merged
Conversation
…_recap) Replace dual-path injection (Bug 36 merge-into-user + Bug 37 standalone- assistant) with single-path tool-result injection. Each active compression block is now emitted as a synthetic AssistantMessage containing a completed ToolPart for the acp_context_recap tool. At the API level the model sees role:tool output — semantically neutral data that is neither user instruction (fixes #78 goal drift) nor own prior voice (fixes #20 verbatim echo). Works across OpenAI (role:tool) and Anthropic (tool_result content type) without provider-specific handling. Does not break prefix caching: injection is mid-stream at compression anchor positions, system prompt stays stable between compressions. - lib/messages/utils.ts: add createSyntheticToolRecap + ACP_RECAP_TOOL_NAME - lib/messages/prune.ts: simplify filterCompressedRanges to single path - lib/prompts/system.ts: update recap description for tool format - tests: 9 tests updated to assert tool-result format - 599/599 tests pass, typecheck clean
…onSummary These were used by the old user-merge injection path (Bug 36) which is now replaced by the tool-result recap injection. No remaining callers. Verified: bundle has 0 occurrences of 'ACP SYSTEM METADATA' or 'MERGED_SUMMARY' or 'prependCompressionSummary'.
The wrapper text was added to prevent the model from confusing notifications with user input. Now redundant — notifications use noReply:true + ignored:true flags.
… fix + multi-block test Review fixes from dual-agent code review (PR #95 / #22): - isSyntheticMessage now recognizes msg_acp_recap_ prefix (prevents recap messages from being treated as real messages in nudge anchor selection) - system.ts tool count: four → five - New test: multiple active blocks in one filterCompressedRanges pass
This was referenced Jul 10, 2026
Closed
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.
Summary
Compression summaries are now injected as synthetic tool-result pairs (
acp_context_recap) instead of text-based user/assistant messages. This fixes two long-standing bugs:role:assistanttext — the model treated them as its own prior output and continued/echoed them.role:usertext — the model chased old topics as if the user asked about them.Root cause
Compression summaries are system metadata (neither user nor assistant), but opencode's
Messagetype only allowsrole: "user" | "assistant". Both roles misled the model:user→ model treats as instruction → goal drift ([Bug]: 上下文压缩后目标漂移 #78)assistant→ model treats as own voice → echo (chore: release v1.4.0 #20)Solution
Inject summaries as synthetic tool-call + tool-result pairs. At the API level, the model sees
role: "tool"— a neutral third role that means "data from a tool", neither instruction nor own voice.Why this works
Commits
feat: inject compression summaries as tool-result recaps (acp_context_recap)— core implementationrefactor: remove dead MERGED_SUMMARY header/footer + prependCompressionSummary— dead code cleanuprefactor: remove [ACP system message] wrapper from notifications— redundant wrapper removalVerification