Files
deepseek-harness/packages/llm/llm-retry
_Kerman af3311e1f6 test: close per-file coverage gaps opened by the message-machine refactor
Downstream packages lost the tests that exercised their agent-loop-facing
edges when the loop was rewritten. Restore 100% per-file coverage with
behavior tests through public seams: llm-retry config validation and
cancellation races, goal replay drift/staleness/teardown edges, plan-mode
disposed-flush, workspace-context empty-change commits, api-proxy
synchronous send failures, acp-snapshot spill-path extraction and refresh
write-back, ACP injection-triggered turns, cli-demo and tui inbox
lifecycle edges, and agent-loop retry/settlement/lifecycle branches.
The only source changes are narrowly-justified v8 ignore annotations on
invariant guards and one redundant-guard removal (workspace-context).
2026-07-26 11:49:19 +08:00
..

@deepseek-ai/dsh-llm-retry

Function plugin that retries selected transient model-request failures through the agent/request-error waterfall. It does not wrap ctx.llm.stream(): every adapter call remains one provider attempt, and every retry opens a fresh numbered turn.

The default policy permits two retries for RATE_LIMIT, SERVER, TIMEOUT, and TRANSPORT, using bounded exponential backoff from 500 ms to 10 seconds with 10 percent jitter. Delay bounds must fit Node's supported timer range. A valid providerRetryAfterMs replaces local backoff when it is within the configured cap; an over-cap instruction delegates to the next recovery policy instead.

The recovery listener appends a non-surface llm/retry event after the failed step, waits for the backoff while the failed turn's signal remains live, then calls agent.retry(). The loop closes that failed turn and opens a retry turn over the same durable history. The policy keeps its own retry count across that uninterrupted recovery chain and clears it at terminal agent/idle. Turn cancellation and plugin disposal abort the wait.

The separately published ./invariant companion checks that every retry record appears inside an open turn after its failed step, matches its position in the current retry chain, and carries a positive bounded retry budget and non-negative bounded timer delay. Full jitter may schedule zero milliseconds at its lower boundary.

- name: '@deepseek-ai/dsh-llm-retry'
  config:
    maxTransientRetries: 2
    initialDelayMs: 500
    maxDelayMs: 10000
    jitterRatio: 0.1
    retryableCodes: [RATE_LIMIT, SERVER, TIMEOUT, TRANSPORT]

Model Experience

Transient request recovery

What the model sees

No retry event, delay, or failure prose is model-visible. The retry turn reconstructs the same explicit provider/model request from durable session history; failed chunks never enter derived messages.

Token effect

Each retry is a new provider request and may repeat input-token billing. The finite budget caps attempts; llm/retry itself contributes no tokens.

KV Cache effect

The reconstructed request preserves the prior prefix and is eligible for provider cache reuse under that provider's rules. The non-surface status event does not change cache identity.

Known Limitations and Deferred Work

  • Agent turns are the only retry boundary — direct ctx.llm.stream() consumers remain single-attempt because a raw stream cannot separate already-emitted chunks durably.
  • Finite plugin budgets add — this policy counts only configured transient codes; context-overflow compaction counts only its own code. A future policy with overlapping codes must document and test registration-order behavior.
  • llm/retry records completed backoff, not request completion — later step and turn events establish success, exhaustion, or cancellation.