DeepSeek Harness plugin

dsh-key-rotation

Per-provider API key rotation for DeepSeek Harness: a key pool per provider, auto-created clone routes, and switching to the next key on quota/rate-limit errors. Includes a Settings section (Key

Jump to install

Source facts

Repository
GooDAnDReaDY/dsh-key-rotation
Latest update
Aug 19, 2026
Category
Models & Providers
GitHub stars
0
Format
plugin
Catalog evidence
Upstream dsh.bundle evidence
Evidence path
package.json#dsh.bundle
Checked against
0.1.0-rc.8
Upstream check date
2026-08-20

This evidence comes from the upstream catalog. This site has not installed, run, or security-reviewed the plugin.

Install

Start with a prompt that asks an agent to review the GitHub repository and source. Switch to the command if you want to install it yourself.

Copy this prompt into DSH, Codex, or another agent and ask it to review the GitHub repository and source first.

Do not install or run any commands yet. Read this plugin's GitHub repository, README, and relevant source code. Then answer the questions below clearly and directly so I can decide whether it fits my needs:

1. What is this plugin, and what problem does it solve?
2. Who is it for, and what are its typical use cases?
3. How is it used after installation? Include one minimal example.
4. What known limitations or privacy, security, compatibility, or maintenance risks does it have?
5. Give a clear recommendation: recommend, conditionally recommend, or do not recommend, with reasons.

Distinguish statements documented by the repository, inferences from source code, and unknowns. If evidence is insufficient, say so explicitly. Do not guess or simply repeat the README.

GitHub: https://github.com/GooDAnDReaDY/dsh-key-rotation
Plugin: dsh-key-rotation
Author: GooDAnDReaDY

Check the source files

Read the README and other files from this plugin directory before installing.

File explorer3 files
README.mdSource · read only

dsh-key-rotation

Per-provider API key rotation for DeepSeek Harness (dsh). Instead of failing on a quota/rate-limit error, the plugin transparently retries the request on the next healthy key in a per-provider pool.

> Hermes-style rotation: every configured provider has a key pool; when a key's limit is exhausted, the request is retried on the next key. Exhausted keys stay in cooldown and return to rotation after cooldownMs.

What it does

  • Key pools per provider — list the API keys (as credential/env names) that a provider may rotate through.
  • The provider you picked stays the provider — rotation swaps the key, never the route, so a multi-call turn does not break. Legacy clone routes remain registered but are hidden from the model dropdown.
  • Transparent on-failure rotation — on a switchable error (QUOTA, RATE_LIMIT, AUTH/INVALID…) the request is retried on the next key.
  • Cooldown — an exhausted key is skipped for cooldownMs, then returns.
  • Dead/revoked key handling — an auth/invalid key rotates to the next pool key instead of erroring out.
  • Settings GUI — a Settings → Key Rotation section to manage everything without touching config files:

- add a key in one place — press Add key, paste the value, done. The credential name is generated for you (<PROVIDER>_API_KEY, then _2, _3, …) and shown only on hover; the card lists keys as Key 1, Key 2. - live key status — per key: in use / ready / cooling down with a countdown / no such credential, which is what catches a mistyped name that would otherwise fail silently. - rotation counter — how many times a provider switched key, on which failure, and how long ago. - key order — ↑/↓ buttons; the order of keys is the order they are tried. - switch codes as checkboxes instead of a comma-separated string.

Install

# From npm after publishing:
dsh plugin --profile web add @goodandready/dsh-key-rotation

# From GitHub:
dsh plugin --profile web add github:GooDAnDReaDY/dsh-key-rotation

# Locally from a checkout:
dsh plugin --profile web add /path/to/dsh-key-rotation

Restart the Web UI afterwards.

Configure

Web GUI (recommended)

Open Settings → Key Rotation and, for each provider, list the credential names of its keys. The plugin stores this in the dsh-key-rotation settings namespace (same place as settings.yaml).

settings.yaml

dsh-key-rotation:
  switchCodes: [QUOTA, RATE_LIMIT, SERVER, TIMEOUT, TRANSPORT, EMPTY_RESPONSE, UNKNOWN_MODEL]
  cooldownMs: 60000
  providers:
    # `provider` is the id of a provider registered with dsh, as it appears
    # in Settings -> Models. `keys` are CREDENTIAL NAMES, never key values.
    - provider: my-provider
      keys: [MY_PROVIDER_API_KEY, MY_PROVIDER_API_KEY_2, MY_PROVIDER_API_KEY_3]
    - provider: another-provider
      keys: [ANOTHER_PROVIDER_API_KEY, ANOTHER_PROVIDER_API_KEY_2]
FieldDefaultDescription
switchCodes[QUOTA, RATE_LIMIT, SERVER, TIMEOUT, TRANSPORT, EMPTY_RESPONSE, UNKNOWN_MODEL]Error codes that trigger a key switch.
cooldownMs60000How long an exhausted key stays out of rotation.
providers[{ provider, keys: [envName, ...] }]. keys are credential/env names, not the key values themselves.

How keys are stored

The plugin config only ever references keys by name (e.g. MY_PROVIDER_API_KEY). The values live in the dsh Credentials service or $DSH_HOME/.credentials.yaml — never in the plugin config.

A key typed into the Key Rotation card is written to that same credentials store: the value travels to the host once and is never sent back to the browser. Only its last 5 characters are, so two keys can be told apart in the UI. A key supplied by the launching environment is shown as read-only, because overwriting it here would be shadowed anyway.

How it works

request ──► {provider: rotation} clone route ──► pick next healthy key in pool
        ┌────────┐   on switchable failure retry with next key, stay in cooldown
        └─────────┘
  • The plugin patches ctx.credentials.resolve so a pool reference resolves to the current healthy key (round-robin, skipping keys in cooldown).
  • It intercepts llm/stream to retry the request on the next key after a switchable failure, instead of surfacing the error to the caller. The hook is deliberately not async: the loop iterates its result directly, and returning a promise breaks every turn.
  • The provider identity never changes — only the resolved key does — which keeps the adapter's replay state consistent across a multi-call turn.

Two local-only routes back the card: GET /dsh-key-rotation/status (key state, rotation counters, last 5 characters of each key) and PUT|DELETE /dsh-key-rotation/key (store or drop one key value). Both refuse anything that is not a same-origin request from loopback.

Structure

dsh-key-rotation/
├── package.json            # dsh bundle/plugin metadata + peerDependencies
├── cordis.patch.yml        # bundle layer: registers the virtual route "rotation"
├── lib/index.js            # host: pools, credentials.resolve patch, stream retry
├── lib/client.js           # browser: Settings → Key Rotation panel
└── README.md

Security notes

  • Key values never leave your Credentials store; the plugin config only holds env/credential names.
  • switchCodes are error classification strings, not expressions — no secrets involved.

License

MIT