Skip to content

fix: stop retrying oversized requests and recover with one forced compaction - #460

Merged
hetaoBackend merged 2 commits into
mainfrom
fix/413-no-retry-compact-once
Oct 9, 2026
Merged

hetaoBackend merged 2 commits into
mainfrom
fix/413-no-retry-compact-once

Conversation

@hetaoBackend

@hetaoBackend hetaoBackend commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

A request the provider rejects as too large fails the same way on every resend, but:

  • The classifier mapped HTTP 413 to a generic upstream error. The API-key (minimax_api) and custom_provider:* paths retry every pre-output failure, so the same oversized body went out 6 times (~15 s of backoff). This also applied to request_too_large, the local pre-send check (Request body too large: … exceeds client limit …), and MiniMax invalid params, context window exceeds limit (2013).
  • After that kind of failure the main turn never compacted and resent; it just failed.

Fix

  • shared/llm-error-classifier: new request_too_large / context_overflow signals and isLLMRequestOversized(). HTTP 413, byte-limit rejections and unambiguous context-overflow messages are now deterministic rejections (sent once on every path). Transient shapes (timeouts, network errors, 408/429/5xx) are still excluded. A bare MiniMax 2013 without overflow wording is not treated as an overflow.
  • agent-core: new PiTurnHooks.contextOverflowRecoveryHook. When a main-agent request is rejected as too large before any output (pi-ai isContextOverflow or isLLMRequestOversized), the runner runs only these hooks, once per turn, with trigger: 'context_overflow_recovery'. It then persists the replacement through the normal history path and resends once. A second rejection, or a recovery that changes nothing, surfaces the original error. The rejected attempt never reaches history, and ordinary before-LLM hooks (reminders and so on) do not run twice. The wrapper sits inside the logical-call metrics and capture.
  • local-runtime-v2: the executor registers the automatic compaction hook (with its plugin lifecycle) as the recovery hook. The trigger forces compaction regardless of the local estimate and caps the result at 50% of the rejected request's measured tokens/bytes, because the estimate had admitted a request the provider rejected.
  • No image-specific recovery is added; image normalization is handled separately.

Tests

  • llm-retry.test.ts: oversized shapes (413 variants, request_too_large, local body check, 2013 overflow, prompt too long) are sent once on minimax_api, custom_provider, and the managed path. Added classifier positive/negative cases, including the BYOK-attributed form.
  • New context-overflow-recovery.test.ts (PiTurnRunner end to end):
    • a 413 leads to one compaction and one resend, and the turn completes;
    • if the resend also fails, the turn fails with the original error;
    • a recovery hook that changes nothing means no resend;
    • non-size errors don't trigger recovery;
    • ordinary hooks are not re-run;
    • with no hook installed, behavior is unchanged.
  • automatic-context-compactor.test.ts: the forced trigger starts compaction below the thresholds, and the hook maps the runner trigger to force.
  • pnpm typecheck, node scripts/run-vitest-suite.mjs capability (215 files, 5406 passed), and check:source all pass.

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

…paction

A request the provider rejects as too large (HTTP 413, request_too_large,
the local request-body check, or a context-window overflow such as MiniMax
'context window exceeds limit (2013)') fails identically on every resend.
The API-key and custom-provider paths retried it five times.

- Classify these rejections as deterministic (isLLMRequestOversized), so
  withLLMRetry sends them once on every path.
- Add contextOverflowRecoveryHook to PiTurnHooks. When the main-agent
  request is rejected as too large before any output, the runner runs only
  these hooks once per turn with trigger 'context_overflow_recovery',
  persists the replacement and resends once; a second rejection is final.
- local-runtime-v2 registers the automatic compaction hook there; the
  trigger forces compaction and targets at most half of the rejected
  request, since the local estimate had admitted it.
@hetaoBackend
hetaoBackend marked this pull request as ready for review October 9, 2026 06:07

@yujiachen-y yujiachen-y left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

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