You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Bug]: Lightweight compaction results are discarded when summarization is skipped #2970
ConversationCompactor.compactIfNeeded() performs argument truncation and tool-result pruning before deciding whether to summarize. Both lightweight passes return a replacement list when they change messages.
However, the method returns Optional.empty() in either of these cases:
The reduced conversation is below the summarization trigger.
The trigger fires, but the safe cutoff is zero because the conversation fits within the preserved tail.
CompactionMiddleware interprets the empty result as a no-op and forwards the original ReasoningInput. The reduced messages therefore reach neither the immediate model request nor the working AgentState. This can leave oversized tool content in subsequent requests even though the lightweight pass successfully reduced it.
Reproduction
Reproduced on upstream main at ea511ec2f68ca929a6bbc4e92cd0c0952a38c592 (2.0.3-SNAPSHOT), using Java 17 and a mocked model; no provider calls are required.
Build a conversation containing a user message, an assistant tool call, and its matching tool result.
Put "x".repeat(10_000) in the tool result.
Configure triggerTokens(1_000), keepTokens(0), keepMessages(20) and pruning with protectTokens(0), minimumTokens(1), maxOutputChars(100).
Invoke CompactionMiddleware.onReasoning() and capture the input received by next.
The tool result is pruned internally, but next receives the original input and state is unchanged.
Setting triggerMessages(3) reproduces the second early-return path (zero cutoff). Enabling argument truncation instead of result pruning reproduces both paths with a large write_file content argument.
A regression test exercises all four combinations at the real middleware boundary: all four fail on the baseline, while two unchanged-conversation controls pass.
Expected behavior
Return the reduced conversation whenever a lightweight pass changed it, even when summarization is skipped. Reserve Optional.empty() for a genuine no-op. This lets existing callers update both the request and working state without adding an LLM call or changing the public method signature.
Related work and scope
This is distinct from #2267 / #2319, which fixed ToolResultEvictionMiddleware updating state without updating the copied reasoning input. The issue here is the two early returns in ConversationCompactor.
This report does not propose changing pruning thresholds, protected windows, default argument truncation settings, or summary-error handling. I am preparing a small fix with regression tests.
Root cause:ConversationCompactor.compactIfNeeded() runs the lightweight passes first (truncateArgs + pruneToolResults, ~lines 105-108) but returns Optional.empty() whenever full summarization is not needed — shouldCompact == false (~lines 110-113) or cutoff <= 0 (~lines 115-119). CompactionMiddleware (~lines 126-130) treats an empty Optional as a no-op and forwards the original ReasoningInput, so the reduced list reaches neither the model request nor AgentState.
Impact: Pruned/truncated tool content stays in the conversation and in persisted state, increasing token usage and risking context-window overflow even though the lightweight pass succeeded. PruneConfig is enabled by default, so this is the common path, not an edge case.
Severity: Moderate-High.
Suggested fix: Return the reduced message list when the lightweight passes changed the conversation but summarization is skipped; in CompactionMiddleware, rebuild ReasoningInput and update AgentState from those messages (same plumbing as a full compaction, without memory flush/offload/summarization).
Proposing an automated fix on this track. Reply /approve to generate the PR, /revise to adjust the approach, or /reject to drop it.
Status: 72 h feedback window expired — no automated PR will be generated, this proposal window here is closed
The evaluation above went 72 hours without an /approve, /revise or /reject, so per our workflow the window is closed and no competing PR will be generated. No action is needed from anyone.
The root cause in the evaluation still stands, and the fix is already in flight from the community:
fix(harness): 保留跳过摘要时的轻量上下文裁剪结果 #2971 (open, non-draft) — fix(harness): 保留跳过摘要时的轻量上下文裁剪结果, by @yyyzl, Fixes #2970. Same seam as suggested above: keep the reduced message list produced by truncateArgs / pruneToolResults when full summarization is skipped, instead of dropping it with an empty Optional.
As of this check #2971 is MERGEABLE with reviewDecision=REVIEW_REQUIRED at head 52af02c20c, last updated 2026-09-13T18:48Z. Its check rollup is green on build (ubuntu-latest) / build (windows-latest) / Check License / Check Module Sync, while license/cla is still pending — that CLA gate plus a maintainer review are what #2971 needs next, not a second implementation. #2971 stays the active path for this issue.
Thanks @yyyzl for picking this up. If maintainers would still like an independent second proposal here, reply on this issue and we will re-evaluate.
Activity
oss-maintainer commentedon Sep 13, 2026
Issue Evaluation
Category:
bug| Status: ConfirmedVerified against current
main(39cd304).Root cause:
ConversationCompactor.compactIfNeeded()runs the lightweight passes first (truncateArgs+pruneToolResults, ~lines 105-108) but returnsOptional.empty()whenever full summarization is not needed —shouldCompact == false(~lines 110-113) orcutoff <= 0(~lines 115-119).CompactionMiddleware(~lines 126-130) treats an emptyOptionalas a no-op and forwards the originalReasoningInput, so the reduced list reaches neither the model request norAgentState.Impact: Pruned/truncated tool content stays in the conversation and in persisted state, increasing token usage and risking context-window overflow even though the lightweight pass succeeded.
PruneConfigis enabled by default, so this is the common path, not an edge case.Severity: Moderate-High.
Suggested fix: Return the reduced message list when the lightweight passes changed the conversation but summarization is skipped; in
CompactionMiddleware, rebuildReasoningInputand updateAgentStatefrom those messages (same plumbing as a full compaction, without memory flush/offload/summarization).Proposing an automated fix on this track. Reply
/approveto generate the PR,/reviseto adjust the approach, or/rejectto drop it.Automated evaluation by github-manager-bot
oss-maintainer commentedon Sep 16, 2026
Status: 72 h feedback window expired — no automated PR will be generated, this proposal window here is closed
The evaluation above went 72 hours without an
/approve,/reviseor/reject, so per our workflow the window is closed and no competing PR will be generated. No action is needed from anyone.The root cause in the evaluation still stands, and the fix is already in flight from the community:
fix(harness): 保留跳过摘要时的轻量上下文裁剪结果, by @yyyzl,Fixes #2970. Same seam as suggested above: keep the reduced message list produced bytruncateArgs/pruneToolResultswhen full summarization is skipped, instead of dropping it with an emptyOptional.As of this check #2971 is
MERGEABLEwithreviewDecision=REVIEW_REQUIREDat head52af02c20c, last updated 2026-09-13T18:48Z. Its check rollup is green onbuild (ubuntu-latest)/build (windows-latest)/Check License/Check Module Sync, whilelicense/clais still pending — that CLA gate plus a maintainer review are what #2971 needs next, not a second implementation. #2971 stays the active path for this issue.Thanks @yyyzl for picking this up. If maintainers would still like an independent second proposal here, reply on this issue and we will re-evaluate.
Automated by github-manager-bot
fix(harness): preserve tool result metadata and state when pruning (#…