Skip to content

fix(agent-runtime): retry compaction summary before retained-tail fallback - #554

Closed
ZxlDragonDoctor wants to merge 2 commits into
vastsa:mainfrom
ZxlDragonDoctor:fix/compaction-retry-before-fallback
Closed

ZxlDragonDoctor wants to merge 2 commits into
vastsa:mainfrom
ZxlDragonDoctor:fix/compaction-retry-before-fallback

Conversation

@ZxlDragonDoctor

Copy link
Copy Markdown
Contributor

Problem

Automatic context compaction often fails to produce a real summary. On failure the runtime immediately writes a retained_tail fallback checkpoint (~112 tokens of recovery prose) and drops older messages from the next model request. There is no retry-until-a-real-summary-exists loop (#543).

Two concrete gaps in packages/agent-runtime/src/runtime.ts:

  1. buildCheckpoint calls generateCompaction once; transient provider failures go straight to recoverCompactionFailure.
  2. compactionSummaryWouldExceedBudget returns recoverable: true without calling the model, so oversized history never attempts a reduced payload.

Solution

  • Retry loop in performCompaction: up to COMPACTION_SUMMARY_MAX_ATTEMPTS = 3 with backoff (2s → 4s → 8s). Only after retries are exhausted does retained-tail recovery run.
  • Reduced payload via shrinkPreparationForSummary: when the budget guard trips, drop oldest summarize-eligible messages until the input fits, then still call the model. Keep at least one message.
  • Honest failure label: fallback checkpoint summary now says "Summary generation failed after retries" instead of implying a successful tiny summary.

Classification, provider-retry backoff, abort/cancel paths, and the fresh_window rollover strategy are unchanged.

Testing

  • New unit file packages/agent-runtime/src/compaction-retry.test.ts:
    • retry cap = 3
    • backoff schedule 2s/4s/8s
    • shrink until budget clears
    • undefined when even minimal payload cannot fit
    • never shrink below one message
  • Full vitest / desktop suite not run in this environment (fork behind upstream main; rebasing after review).

Issue alignment

Addresses #543. Claimed on the issue before implementation. Distinct from #224 / PR #249 (fallback recoverability on a later compaction) — this PR retries the failing attempt.

Non-goals

Core value: Long sessions keep a real model summary when the provider flaps, instead of silently dropping history behind a 112-token stub.

…lback

Issue vastsa#543. Transient summary failures retry up to 3 times with backoff;
budget-exceeded attempts shrink the payload and still call the model.
Only after retries are exhausted does retained-tail recovery write a
failure checkpoint (wording updated to say summary generation failed).
@vastsa

vastsa commented Sep 17, 2026

Copy link
Copy Markdown
Owner

谢谢,方向是对的:先重试、再缩减、最后回退。这个 PR 与当前 main 冲突,且有几处我没法直接合:

  • 外层循环对所有 recoverable: true 的失败都重试,包括「无新内容可压缩」、401、配额耗尽这类确定性失败,每次白等 2s+4s。pi-ai 的 retryAssistantCall 已经自带瞬时错误分类和 abort 归一化,compact()retry 参数就是为这个留的。
  • shrinkPreparationForSummary 直接丢掉最旧的消息,但 checkpoint 的 throughMessageId 仍覆盖它们,等于摘要静默宣称了它没看过的范围。
  • compaction-retry.test.ts 在测试文件里重新声明常量和一个「镜像」shrink 函数,没有 import runtime,对真实接线没有覆盖。
  • 缺 ADR/spec:ADR 0049 第 1 条决策(预检守卫)被改了。

另外根因还多一层:预检守卫对原始消息求和,而 pi 序列化时每条 tool result 已截到 2 000 字符,所以它高估了好几倍——这是「不调模型就失败」的主要来源。

#563 保留了你的三段式方向,用 RetryPolicy + 按序列化 prompt 预检 + 只截工具输出不删消息 + UI 失败标签 + ADR 0282 落地,已在 issue 里 credit 这个 PR。这个 PR 我会在 #563 合入后关闭。


Thanks — the direction (retry, then reduce, then fall back) is right and is kept in #563. What blocked landing this as-is: the outer loop retries deterministic failures too; shrinking by dropping the oldest messages leaves the checkpoint claiming a range the summary never saw; the test file mirrors the constants instead of importing the runtime; the ADR 0049 preflight decision changes without an ADR; and the branch conflicts with main. The bigger root cause was also missing: the guard summed raw messages while pi caps tool results at 2 000 chars when serializing, so it overstated tool-heavy inputs several-fold. #563 credits this PR and will close it when merged.

@vastsa

vastsa commented Sep 17, 2026

Copy link
Copy Markdown
Owner

Superseded by #563, now merged into main (c36d246). Thanks for the direction — credited in the ADR 0282 record and on #543.

@vastsa vastsa closed this Sep 17, 2026
pull Bot pushed a commit to mengzhisuoliu/PI-Desktop that referenced this pull request Sep 17, 2026
…e falling back

Issue vastsa#543: on real long sessions most automatic compactions ended as the
~112-token retained-tail recovery notice, and the transcript row labelled
each one "summary ≈112 tokens". Three things made the ADR 0049 fallback
the common outcome instead of the exception:

- compact() was called without a RetryPolicy, so pi-ai returned the first
  failed response as-is. The main turn retries transient failures through
  the streamFn wrapper, but the summary goes through Models.completeSimple
  and never reaches it; one dropped stream or 503 discarded the summary.
- The preflight guard summed estimateTokens over the raw messages, while
  pi-agent-core serializes the conversation to text and caps every tool
  result at 2 000 characters. Tool-heavy sessions looked several times
  larger than the prompt that would be sent and skipped the model for
  summaries that would have fit.
- ContextCompactionMark could not tell a fallback from a summary, so the
  row rendered the recovery notice as a successful summary of N tokens.

The summary request now carries a bounded pi-ai RetryPolicy (3 retries,
2s/4s/8s, abort-aware; pi-ai's classifier decides what is transient, so
quota and auth failures still return at once). The guard sizes the prompt
pi actually serializes, and when that still exceeds the window it tries
exactly one reduced input (tool results cut to a 500-char prefix, thinking
dropped, no message removed) before the fallback runs. The mark gains an
additive `fallback?: "retained_tail"` derived from persisted details, and
the row labels such checkpoints as a failed summary in all locales.

Supersedes PR vastsa#554, which retried every recoverable failure including
deterministic ones and shrank the input by dropping the oldest messages
while the checkpoint still claimed to cover them. Records ADR 0282 /
D445 and updates E2E-084, the runtime spec, and the IPC mark shape.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants