Skip to content

[Bug]: auto-compaction sends an empty conversation at the LIVE call site — full.length > summary.messageCount is always false there (works on resume) #878

Description

@jrmvii

Summary

When --compact-threshold fires, the compacted transcript handed to the provider is [system, summary] with no live turn at all, so the model has nothing to answer and the run dies with provider returned an empty completion (no assistant text or tool call), exit 1.

Please read the call-site distinction below before testing. The tail-preservation logic in buildCompactedTranscript is correct on the resume path and unreachable on the live path. Testing with --continue will show it working.

Reproduced on main @ 8dce012, v0.1.0, built from source.

Repro

oh-my-cli --compact-threshold 1400 "read the four .txt files in this directory and report each sentinel value"

Observed, 3/3 identical runs, to the token (3 701 prompt tokens, 2 requests):

Context compacted: summarized 6 message(s) (2 completed-action receipt(s)) after 2442 prompt tokens
... 3 attempts ...
provider returned an empty completion (no assistant text or tool call)
exit 1

Negative control — the same task, same backend, without --compact-threshold: succeeds, 5 requests, 18 125 prompt tokens, all four sentinels returned correctly. So this is not the provider.

Root cause

compactMessages stamps the summary with the length of the array it was given (src/compaction.ts:258):

    messageCount: messages.length,

buildCompactedTranscript preserves the tail only when the full transcript is longer than that (src/compaction.ts:324):

  if (full.length > summary.messageCount) {
    out.push(...full.slice(summary.messageCount));
  }

That condition depends on which array full is:

  • Resume pathloadSessionMessages (src/compaction.ts:437): full is the on-disk transcript, which has grown past messageCount. Condition true, tail preserved, works as intended.

  • Live pathsrc/agent.ts:618-619:

          const { summary } = compactMessages(messages);
          const compacted = buildCompactedTranscript(messages, summary);

    full is the very array that produced messageCount. The condition evaluates to full.length > full.lengthalways false. The branch is unreachable here.

Only the live path runs during auto-compaction. So the compacted transcript is always [system, renderSummaryMessage(summary)] — and renderSummaryMessage emits role system too. The provider receives two system messages and zero user/assistant turns.

The comment directly above buildCompactedTranscript states the opposite intent — "Preserving the tail is what keeps continuation honest (Issue #719)" — which is accurate for resume and not for the live call site.

Suggested fix

Give the live path a messageCount that reflects what was summarized rather than the whole array, or have buildCompactedTranscript take the summarized slice explicitly instead of inferring it from a length comparison that only holds on one of its two callers.

Secondary observation (same flag, much smaller)

--compact-threshold can never fire on the first provider call of a process: lastPromptTokens is initialized to 0 (src/agent.ts:492) and the guard tests lastPromptTokens >= compactThreshold before the call (src/agent.ts:616). A session resumed with --continue therefore sends its full history at least once before compaction is even considered.

Verified: a 1 941-token session resumed with --compact-threshold 1500 issued a 1 984-token request with no compaction.

Context

Found while benchmarking terminal agents against a local MLX server whose usable history ceiling is ~24k tokens. oh-my-cli's own overhead is excellent for that constraint — 1 231 tokens fixed, ~237 per turn — which is why the compaction path mattered enough to chase down.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingpriority:p1High priority: address before normal work

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions