imrcpan/dsh-retry: A fixed-interval retry strategy for model requests

Introduction

This article introduces a DeepSeek Harness plugin called dsh-retry, maintained by imrcpan. It solves a very specific problem in agent development: model requests are not expensive, but unstable, especially when going through a relay station (relay) or a shared upstream pool. A single 401 or rate limit can crash the entire turn.

DeepSeek Harness itself has retry capability at the agent step boundary. Its built-in executor is called @deepseek-ai/dsh-llm-retry, which only exposes exponential backoff: initialDelayMs, maxDelayMs, jitterRatio. This curve is not friendly for relay scenarios—waiting for a 401 might back off to tens of seconds, while the next request might succeed if it switches to another upstream account. dsh-retry replaces this curve with a fixed schedule.

What is this

One-sentence positioning: dsh-retry is a DSH plugin that replaces the system’s built-in retry executor, changes the model request retry interval from exponential backoff to fixed scheduling, and is adapted for relay endpoints, shared account pools, and unstable networks.

Maintainer: imrcpan. Category: Model Inference. License: MIT. Version: 0.1.0. Repository: https://github.com/imrcpan/dsh-retry, Directory page: https://www.skillhub.cn/plugins/imrcpan/dsh-retry.

Here is its retry wait time schedule:

Retry Count 1 2 3 4 5 6 and later
Wait Time 2s 5s 5s 5s 10s 20s, unlimited

The first 5 are controlled by the schedule array, after which it uniformly follows tailDelayMs, every 20s without limit. Compared to exponential backoff, this curve reacts faster to transient jitter and retries permanent upstream failures only once every 20s.

Core Features

Fixed Interval Retry Scheduling

The original executor uses exponential backoff, where wait time doubles until it hits a cap. dsh-retry changes to explicit scheduling: 2s, 5s, 5s, 5s, 10s, then retries every 20s without limit. The reasoning is that relay failures occur per account, not per request: if a backend returns a 401 or quota is exhausted, the next attempt switching to another account is likely to succeed, so the wait should be short and fixed, rather than climbing higher and higher.

Failure Classification for Relay Endpoints and Unstable Upstreams

The README explicitly states that this executor is optimized for relay endpoints and unstable networks, with failure codes covering three categories:

  1. Account-level failures are retried. AUTH, QUOTA, ACCOUNT_QUOTA, ACCOUNT_TOKEN_INVALID, INVALID_CREDENTIAL are all in the default retry list.
  2. Gateway noise is retried. Cloudflare and non-proxy returned 5xx are classified as SERVER, including HTML error pages that are not valid JSON at all.
  3. Stream corruption is retried. Broken SSE frames are classified as MALFORMED_RESPONSE.

Additionally, HTTP_* is a prefix rule covering all unclassified 4xx, so errors like HTTP_404 will be retried instead of ending the turn directly.

Retry Doesn’t Produce Duplicate Output

The retried step re-executes within the same open turn for the same durable history. The model does not see the retry; it only receives the rebuilt request from the retried step, so there will be no duplicated partial answers.

Events and UI

Every planned retry appends a durable llm/retry event, and after the wait ends, llm/retry-started is appended. The Chat UI will render a line like Retrying model request (2/5). 4s, which can be expanded to view the failure reason and delay; the Trajectory panel will list the attempt, error code, and delay. The 6th retry and later are displayed as unlimited. There are no toast popups, and intermediate output of failed attempts is not retained.

Installation and Usage

The plugin is a DSH bundle: package.json declares dsh.bundle.patch, which points to ./cordis.patch.yml. Standard installation automatically mounts the new executor and disables the built-in llm-retry, so no manual patching is required.

Installation command (from README):

dsh plugin --profile web add github:imrcpan/dsh-retry

After installation, you must restart the profile, as the composition is read at startup. After restarting, this line will appear under Plugins, and its config can be edited.

What the Plugin Does After Installation

The mounted layer inserts the dsh-retry executor and disables llm-retry:

- insert:
    - id: dsh-retry
      name: "dsh-retry"
      config:
        schedule: [2000, 5000, 5000, 5000, 10000]
        tailDelayMs: 20000
        retryableCodes: [ ... ]   # See cordis.patch.yml for full list
- id: llm-retry
  disabled: true

Disabling llm-retry is critical: the two mounted executors will fight for the same failure, and each will write retry chain state. If the profile overrides this setting, ensure it remains disabled.

Install from Source Directory

If using a checkout, it needs to be installed as a bundle so the manifest takes effect:

dsh plugin --profile web add /absolute/path/to/dsh-retry

It also accepts a regular file: dependency, but in that case the bundle layer is not applied, and you need to manually copy the patch snippet above into the profile’s cordis.patch.yml:

{
  "dependencies": {
    "dsh-retry": "file:./plugins/dsh-retry"
  }
}

Configuration

There are three keys under config:

key Default Value Meaning
schedule [2000, 5000, 5000, 5000, 10000] Milliseconds to wait before the 1st..nth retry; array length is the capped head
tailDelayMs 20000 Wait time for each retry after the schedule is exhausted
retryableCodes see below Failure codes worth retrying

Entries ending with * in retryableCodes are prefix rules. HTTP_* will match HTTP_404, HTTP_409, HTTP_422, and other unclassified status codes. Bare * is rejected because it would match all failures.

Default Retryable Failure Codes

code Typical Cause
EMPTY_RESPONSE Response completed but no content blocks
RATE_LIMIT HTTP 429 or provider rate limit error
SERVER HTTP 5xx, including gateway and Cloudflare 5xx
TIMEOUT Request or stream timeout
TRANSPORT Connection reset, socket error, stream truncated
PI_AI_ERROR Upstream errors that the pi-ai adapter cannot classify
AUTH HTTP 401 or 403; switching accounts in a shared pool might succeed
QUOTA, ACCOUNT_QUOTA Balance or account quota exhausted
ACCOUNT_TOKEN_INVALID Stored account token rejected by 401
INVALID_CREDENTIAL Malformed credential entry in the pool
MALFORMED_RESPONSE Corrupted SSE frame or mismatched event type
HTTP_* Unclassified 4xx, e.g., HTTP_404, HTTP_409, HTTP_422

Default Non-Retryable Failure Codes

code Reason
CONTEXT_WINDOW_EXCEEDED compaction is responsible for recovery; retries would create a loop
INVALID_REQUEST The request itself is wrong; switching accounts won’t save it
NO_ADAPTER, UNSUPPORTED_REASONING_EFFORT Configuration error
MISSING_CREDENTIAL No credentials configured for this route
ACCOUNT_SIGN_IN_REQUIRED Requires manual login
ABORTED Cancellation must remain in a terminal state
UNKNOWN Fallback code for external errors; unlimited retry is unsafe

If the judgment of your deployment environment differs from the defaults, you can add these codes to retryableCodes.

Applicable Scenarios and Notes

Suitable for: DSH deployments using relay stations, shared account pools, or obviously unstable upstreams. These failures are random noise dispersed per-account, so fixed short-interval retry yields the most benefit. If using a stable direct connection, the built-in executor is sufficient, and there is no need to switch.

Several behaviors you must know:

  1. The plugin runs with the permissions of the current dsh process. It will use whatever credentials it can read within the scope of that process’s authorization. It is recommended to check cordis.patch.yml, index.js, and LICENSE before installing to confirm the source.
  2. The retry chain counter is stored in memory, per session. A host restart ends the turn, nothing is lost. Only requests from the agent loop are retried; places calling ctx.llm.stream() directly remain single attempts.
  3. The tail is truly unlimited. When the upstream never recovers, the turn will stay in the “retrying” state until manually stopped.
  4. For credential and quota failures in the retry pool, the premise is that the pool will rotate on the next attempt. If the pool itself is permanently exhausted, it will loop every 20s.
  5. The executor itself does not rotate accounts; it only re-initiates requests, so the relay must select a different upstream for the next attempt.
  6. Every attempt is a separately billed provider request.

Conclusion

The value of dsh-retry does not lie in inventing a new retry mechanism, but in replacing exponential backoff with a fixed schedule, incorporating the three most common failure types in relay scenarios (account-level, gateway-level, stream corruption) into the retry scope, while guaranteeing no duplicate output. To view the source code and configuration directly, you can open https://github.com/imrcpan/dsh-retry, or view the directory entry at https://www.skillhub.cn/plugins/imrcpan/dsh-retry.

For the ecosystem context: The philosophy of DSH is “everything is a plugin”. The community directory is an independent site and is not officially affiliated with DeepSeek or HF; use the source of the plugin itself as the authority when using it.

羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

Xiaoye