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:
- Account-level failures are retried.
AUTH,QUOTA,ACCOUNT_QUOTA,ACCOUNT_TOKEN_INVALID,INVALID_CREDENTIALare all in the default retry list. - 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. - 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:
- 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. - 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. - The tail is truly unlimited. When the upstream never recovers, the turn will stay in the “retrying” state until manually stopped.
- 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.
- The executor itself does not rotate accounts; it only re-initiates requests, so the relay must select a different upstream for the next attempt.
- 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.