dsh-plugin-immune: Preventing Agents from Repeating Past Mistakes

Introduction

Almost everyone who writes prompts for Agents encounters this pattern: the same pitfall, stepped on in this session, fixed, and then stepped in the exact same way in the next session. Writing it in documentation nobody maintains, or writing it into system prompts is too long and too generic. The idea behind dsh-plugin-immune is to turn the “scene of the pitfall” directly into rules—when a tool call fails and then succeeds, the plugin automatically distills it into an antibody and saves it in SQLite. Next time the relevant filename or command is asked, the antibody is injected into the context first.

It is maintained by meat-daemon-404, categorized as “Memory”, 3 stars on GitHub, npm package name dsh-immune-system (currently 0.9.5), License MIT. It supports two hosts simultaneously: DSH (DeepSeek Harness) and ZCode, sharing the same core/.

What is it

In one sentence: An automatic distillation and recall plugin for Agent failure-repair experience.

The workflow is “Pairing → Distillation → Antibody → Injection”:

 Tool Failure ─┐
               ├─► Pairing ─► Distillation ─► Antibody (SQLite + FTS5, isolated by project)
 Tool Success ─┘                          │
                                          ▼
         Next Session Question ─► <immune_memory> injected into model context

Compared to static document solutions, the difference lies in the source of the rules. Instead of a person writing “vite client environment variables need loadEnv”, the Agent distills it itself after actually stepping in the pit once, so it appears first in the relevant context next time.

Installation

The simplest way on the DSH side is via npm:

dsh plugin --profile <profile> add dsh-immune-system

<profile> is the one actually being used under ~/.dsh/profiles/, usually desktop on the desktop. After installation, DSH needs to be restarted; the bundle is assembled at startup.

There are two equivalent ways that don’t require an npm account:

cd dsh-immune-system && npm pack          # → dsh-immune-system-0.9.0.tgz
dsh plugin --profile <profile> add ./dsh-immune-system-0.9.0.tgz
dsh plugin --profile <profile> add github:meat-daemon-404/dsh-plugin-immune

Note that git: installation runs the prepare build during installation. pnpm will intercept this by default; you need to add the key printed by pnpm to the profile’s pnpm-workspace.yaml → allowBuilds. The author’s suggestion is unless you specifically need this form, use the first two methods.

After installation, use /immune for a self-check; if it returns “No antibodies currently,” it means it’s connected.

The package engine requires ^22.19.0 || >=24.0.0, which is written as node >=22.5 on the README badges.

How to use

Just work normally; no extra operations are needed. Step into a pit → fix it → automatically distill in the next round. Afterwards, when a question containing the same filename or command is asked, <immune_memory>…</immune_memory> will appear in the context.

Slash commands on the DSH side:

Command Effect
/immune List all antibodies (including retired ones with --all)
/immune-export Export verified antibodies into SKILL.md
/immune-forget <id> Delete a specified antibody
/immune-forget --last Undo the one just learned in the previous round
/immune-forget --prune Retire antibodies that have never hit and are older than 14 days (recoverable, no longer injected)
/immune-forget --purge Physically delete the above antibodies (irreversible)

The command prefix on the ZCode side changes to /dsh-immune-system:, with the addition of /dsh-immune-system:immune-learn (manually teach a rule) and /dsh-immune-system:immune-ui (start a local dashboard).

Common CLI for both:

cli.js ui [--port N]                            # Local dashboard, default 7788, port occupied auto-extends
cli.js learn <trigger> [symptom] <prescription> # Manually teach a rule
cli.js search <query>                           # Read-only search, same channel as dashboard/MCP, not counted
cli.js export-json <path> / import-json <path>  # Library snapshot export/import
cli.js doctor                                   # Installation health check: node version, FTS5, database, marker, MCP script

If doctor fails at any step, the exit code is non-zero, which can be directly integrated into CI.

Storage and Sync

The antibody library is a single SQLite file with FTS5 trigram indexing, matching Chinese characters by 3-character window. DSH default path:

<DSH_HOME>/storages/dsh-immune-system/<sha1(current directory)[0..16]>.sqlite

Isolated by project; different workspaces don’t cross-contaminate, and no files are dropped into the project directory. IMMUNE_DB_PATH takes precedence when explicitly specified.

Cross-machine sync uses JSON snapshots:

node "<plugin>/dist/zcode/src/cli.js" export-json library.json   # Snapshot
node "<plugin>/dist/zcode/src/cli.js" import-json library.json   # Merge into current library

The merge semantics are conservative: prescriptions are deduplicated by identity; for rules known on both sides, take the max of hit/invalid counts; status is active if active on either side. Commit library.json to the repository, and team members can import-json once to inherit all lessons.

A Few Notable Designs

Failure determination doesn’t rely on a single event. ZCode’s PostToolUseFailure only triggers when the tool handler throws an exception; a non-zero exit code is a normal return. DSH’s result.isError only covers infrastructure failures. Therefore, the plugin sorts and judges by authority: isError → sandbox rejection mark (judged as failure but not stored for learning) → kill/timeout → structured value.exitCode (if present, authoritative, 0 success, non-0 failure) → structured value exists but no exit status (payload is the conclusion, body not scanned) → line ending [exit code: N] → narrow-gauge failure mark regex. The last two steps deliberately yield to structured signals because it is common for commands exiting 0 to print command not found in grep output and should not be recorded as failures.

Antibodies will be retired. After an antibody is injected but the tool still fails, deduct points. After 3 failures, it is automatically retired and re-distilled with new evidence. Deduction includes relevance judgment: the failed content must share words with the antibody’s trigger words, excluding the tool name itself.

Generic pits are only learned once. Symptoms like File has been modified since read are unrelated to specific files; testing showed learning a duplicate rule on three different files. The refined pattern table in core/lessons.ts normalizes these symptoms into generic trigger words and prescriptions, followed by deduplication at the prescription level.

Human correction takes priority. The common real-world chain is: tool fails → Agent asks human → human says “this repo uses pnpm” → retry succeeds. That human sentence is the best distillation input; UserPromptSubmit will hang the human message of the new round on the unfinished failure of the previous round, serving as the optimal source of the prescription during distillation. Vague words like “continue” or “ok” are blocked by isSubstantiveHint.

Pre-checks are disabled by default. IMMUNE_GUARD provides deterministic checks with three levels: off (default) / warn / deny. Three rules that are wrong in any environment (strongly pushing main, rm -rf /, git reset --hard) are written in code, not in the database—distilled products are model outputs, and compiling them into regex to intercept tool calls is like granting stopping power to unreviewed strings. deny is the only destructive mode, so it is not the default value.

Injection hygiene. Injected content comes only from fields distilled by the plugin itself; original tool outputs are not included; angle brackets in antibody text are stripped; there is a length limit; explicitly marked as “for reference only”. A round injects at most once, and only at the first step of the round.

Distillation hangs at the turn boundary. drain() may send HTTP requests to the configured LLM endpoint. DSH’s tools/post-execute listener is awaited on the tool result path; putting it there would block every “success after failure” tool call until the HTTP returns. So it hangs at agent/turn-stopping, and unflushed queues are covered by requeueStuckJobs() in the next session-start.

Serial connections instead of ATTACH DATABASE. SQLite explicitly states that transactions across attached databases are not atomic in WAL mode. Since the plugin uses short connections per process and concurrent hooks, project libraries and shared libraries use two independent connections. The cost is just that recall becomes two queries.

Applicable Scenarios and Notes

Suitable for scenarios where the Agent is fixed in the same workspace, repeatedly doing the same tech stack, and after falling into a pit, quickly returning to a similar context. When differences between projects are large, the project-isolated library prevents cross-contamination; cross-project general experience is supplemented by the shared library (global.sqlite).

Before installation, it is recommended to do two things:

  1. Read the source code. The permissions declared by the plugin are “read tool call results and slash command inputs; write SQLite antibody library locally; only initiate requests to that endpoint when IMMUNE_LLM_BASE_URL and IMMUNE_LLM_MODEL are explicitly configured; do not read other plugins’ config, do not execute arbitrary commands.” This is the declaration in the README; actual behavior should still be based on the source code.
  2. Confirm the license. The repository is under the MIT License.

The security boundary of the local dashboard is deliberately narrowed: only binds to 127.0.0.1, generates a random token on every startup, and all /api requests must carry it, strict CSP, and the page does not load any external resources.

It should be noted that the author explicitly states in the README: the client and routing contracts for the “Immunity” tab in the host web native settings page have been tested, but have not been run end-to-end in a real web application; whether the tab appears as expected needs to be confirmed by restarting DSH yourself.

Conclusion

The value of this plugin lies in turning “pitfall experience” into a persistent, searchable, and retirement-capable asset, rather than a set of static documentation that requires human maintenance. 3 stars mean it is still an early project, and the interface and default paths may change.

Directory page: https://www.skillhub.cn/plugins/meat-daemon-404/dsh-plugin-immune

GitHub: https://github.com/meat-daemon-404/dsh-plugin-immune

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

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

Xiaoye