Skip to content

[Bug]: Lightweight compaction results are discarded when summarization is skipped #2970

Description

@yyyzl

Describe the bug

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:

  1. The reduced conversation is below the summarization trigger.
  2. 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.

Activity

  1. oss-maintainer commented on Sep 13, 2026

    @oss-maintainer
    Collaborator

    Issue Evaluation

    Category: bug | Status: Confirmed

    Verified against current main (39cd304).

    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.


    Automated evaluation by github-manager-bot

  2. oss-maintainer commented on Sep 16, 2026

    @oss-maintainer
    Collaborator

    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:

    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.

    Automated by github-manager-bot

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions