Skip to content

[bug] Post-compression sent-view undercounts prompt by ~70–80K → false idle → host-floor emergency re-nudge loop #325

Description

@ranxianglei

Summary

After a successful compression, ACP's own token accounting drops far below the provider-reported prompt size (a roughly constant ~70–80K offset), so the nudge state flips to idle. On the next turn the host-floor anchor (#257) snaps tokenCount back up to the real prompt size (~88–95% of the effective limit) and re-injects an EMERGENCY "compress now" nudge. The model compresses again, accounting drops again, and the loop repeats — the agent stops working every few turns just to compress.

Environment

  • pi + billion-context-pi v0.1.56
  • Model: local vLLM qwen3.8-27b, contextWindow=262144, maxTokens=131072
  • Every turn logs [overflow-selfheal] event=output-headroom before=262144 after=131072 → effective limit = 131,072

Repro session

  • Session id: 01a07b3c-ab19-7b19-8290-f7967a8221a6
  • Log: ~/.pi/acp.log from line 10163 onward
  • Compression points: 14:29:55 UTC (blocks b13+b14), 14:51:08 (b15+b16), 14:56:49 (b17)

Evidence

1. Pre-compression estimates match the provider exactly

On nearly every non-compression turn, [turn] tok == hostTokens, e.g. @14:27:14 tok=124726 hostTokens=124726. The estimator is correct while the conversation is uncompressed.

2. Post-compression: constant ~70–80K undercount

time (UTC) [turn] tok provider input+cacheRead gap
14:31:30 31,745 79,788 + 22,528 = 102,316 70,571
14:56:15 34,220 110,570 (cacheR=0) 76,350
14:56:49 4,153 + 115,200 = 119,353

The #289 view-recount does not catch this: at 14:56:20 viewTokens=36,069 vs actual 116,261.

The gap stays in the same ~70–80K band across different compression sizes, which suggests a fixed component is missing from the post-compression sent-view rather than per-block summary miscounting (the 10 active tier-1 block summaries total only ~8–12K tokens).

3. Host-floor anchor then re-triggers EMERGENCY

Next turn after each compression, tokenCount = max(ownEstimate, lastProviderUsage) snaps back to 106,669 (81%) / 112,658 (86%) / 115,696 (88%) / 118,184 (90%) / 124,879 (95%) of the 131,072 limit → nudge escalates active→emergency and injects:

⚠️ Context limit reached — compress now. Prioritize consumed tool outputs.

(acp.log lines 13504-05 @14:27:14, 13848-49 @14:37:21, 13922-23 @14:48:06, 14122 @14:56:20; voice=emergency emergency=false reInject=false)

4. Loop cycle observed

emergency nudge → model compresses → accounting drops to 21–34K (16–26%, nudge=idle) → 1–2 work turns add tool outputs → host anchor snaps back to ~110–125K (88–95%) → emergency again.

User-visible: agent stopped every few turns to compress instead of working; user interrupted twice (stopReason=aborted @14:52:55, @14:57:08) and typed "继续" twice.

Secondary symptoms in the looped tail

  • Duplicate compress of the identical range m00103..m00149 (second call failed: "Requested range(s) already compressed … Run acp_status")
  • One malformed empty compress {} call (@14:52:55)
  • Model thinking opens with "Need to compress the consumed ranges" on successive turns

These look like a weak local model (qwen3.8-27b) reacting to repeated emergency nudges; the root trigger is the accounting mismatch above.

Contributing factor

The effective window was halved by output-headroom (maxTokens=131072 reserved out of 262144), putting all nudge bands below the real post-compression prompt floor (~102–135K). Lowering maxTokens to 49152 removes the pressure, but the undercount itself should be fixed.

Suggested fix direction

Audit what the post-compression sent-view includes (estimateTokens(coreMessages, coveredIds, imageTokens) in src/index.ts): something fixed (~70–80K) is present in the actual request but absent from the view only after a compression has occurred. The #289 view-recount compares against the post-processTurn view, so it inherits the same blind spot.

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

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions