Skip to content

[Bug] Compaction loop: summary system messages are never compacted and can keep the session above the threshold #383

Open

Description

@esecoder

Summary

After the image-payload fix in #381, compaction can enter a self-sustaining loop: each successful compaction appends a new summary system message, but existing summaries are skipped by the next compaction because startIndex is the first non-system message. The main request keeps including all summaries, so activeTokens can remain above autoCompactWindow, and the agent loop compacts again on the next iteration.

This is distinct from the original HTTP 400 in #378 / #382. #381 fixes that failure; this issue tracks the loop it can expose.

Observed behavior

  • @vegamo/deepcode-cli 0.4.3 with the fix(core): omit inline image data from compaction prompt #381 change applied locally.
  • The affected session reached 139 summary system messages, 1,246,916 characters total.
  • A new summary was generated roughly every 30–60 seconds.
  • Later summaries were degenerate ("transcript truncated", "no user turns"), consistent with compacting only a tiny tail plus prior summaries.
  • The session stayed above the threshold at roughly 277K active tokens, so compaction re-triggered. There was no contextWindow or autoCompactWindow override in the user settings; the CLI resolved a window below that value for this session.

Why it loops

Relevant code in packages/core/src/session.ts:

const sessionMessages = this.listSessionMessages(sessionId).filter((message) => !message.compacted);
const startIndex = sessionMessages.findIndex((message) => message.role !== "system");
// ...
const compactPrompt = getCompactPrompt(sessionMessages.slice(startIndex, endIndex));
// ...
sessionMessages.splice(endIndex, 0, summaryMessage);
  • Accumulated summaries are role: "system" with meta.isSummary.
  • startIndex skips every system message, so a summary inserted before the first remaining non-system message is never included in a later summary prompt and never marked compacted.
  • The main request still sends all of those summaries.
  • If their combined size plus the tail stays above the window, the next agent-loop iteration calls compactSession() again.
  • Each call appends another summary, so the summary total can grow faster than the tail shrinks. Once the summaries alone exceed the threshold, the loop is self-sustaining.

The trigger is at session.ts:1774:

if (session.activeTokens > compactPromptTokenThreshold) {
  // ...
  await this.compactSession(sessionId, sessionController.signal);
}

Minimal reproduction

  1. Create a session with a small non-system tail and enough meta.isSummary system messages that the active prompt is above autoCompactWindow.
  2. Set activeTokens above the threshold, or let a real request set it.
  3. Run the agent loop or call compactSession() repeatedly.
  4. Observe a new summary appended each time and activeTokens remaining above the threshold.

A real-world trigger is a session that previously failed compaction because of inline images (#378 / #382). After #381, the first compaction succeeds; if the prompt is still above the window, the loop starts.

Suggested guards

  • After a compaction, do not compact again in the same turn unless the main request's activeTokens actually dropped; or cap compaction attempts per turn.
  • Treat summary system messages as compactable: include them in the compaction slice or merge them into the new summary instead of leaving them permanently at the front.
  • Collapse existing summaries into one before appending a new one.
  • Skip compaction when the slice contains no new non-system messages (only summaries and/or a tiny tail).
  • Optionally cap the number or total size of summary messages.

Workaround

A local data repair can collapse the summaries and reset activeTokens, but that is not a product fix and most users will not know to do it.

Related

Activity

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions