Faithful port of the hermes-agent MEMORY.md / USER.md mechanism for DSH: bounded dual-bank memory (2200/1375 chars), a single `memory` tool (add/replace/remove/batch), frozen-snapshot injection per session, nudge reminders, DSH-native JSON storage — zero external dependencies, zero LLM cost.
Install
# from npm (prebuilt)
dsh plugin --profile web add dsh-hermes-memory
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:isheng-eqi/dsh-hermes-memory
Any plugin you install runs third-party code with your own permissions — it can read your files, use your credentials, and reach the network, and tool approvals don’t sandbox it. GitHub-sourced plugins also run build scripts at install time — pnpm blocks those until you allow them, so an install can stop with ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED or ERR_PNPM_IGNORED_BUILDS; dsh prints the exact key to add under allowBuilds in your profile’s pnpm-workspace.yaml, and the install works on the next run. Allowing a build is a trust decision: only install sources you trust, and pin a commit (github:owner/repo#sha).
README
An adaptation of Hermes built-in memory for DeepSeek Harness, with two bounded memory banks, frozen session snapshots, atomic curation, and periodic background review.
Before installing — model usage: the v2.0.0 static bundle enables independent background review by default. Reviews make additional LLM requests and consume model quota; paid models may incur additional charges.
- By default, a review follows a qualifying response at each multiple of 10 genuine user turns and inherits the triggering turn's model. Each review allows at most 16 additional model requests, with a default model-loop time budget of 120 seconds. These are limits and a budget, not fixed request counts or prices.
- To disable independent background review, set
backgroundReview.enabled: falsein the plugin configuration; see the nested YAML example in Configure background review. Main-agent curation and injected memory still consume tokens, so disabling review does not eliminate model usage.- Cost depends on the model, conversation length, and actual request count. The bank character budgets do not cap total tokens or cost; see Resource boundaries.
A review may save grounded, durable facts or decide that nothing is worth saving; the number of entries need not grow over time. Background changes to existing entries and deletions become pending proposals that the owner can approve or discard in the Memory panel.
The implementation is based on Hermes commit c0c4357744d1a6994d332b826aea4c77fcccaf38 (2026-11-11). The primary installation is the static bundle. The dynamic sandbox has no native request cancellation controller, so the dynamic deployment provides memory tools, snapshots, and statistics while explicitly reporting background review as unavailable. The plugin uses DSH storage and lifecycle services; platform differences are documented below.
Install and use
Install or enable the bundle in the DSH plugin manager, refresh the existing DSH page, and click Memory at the bottom of the sidebar. A source installation uses the package directory. For the published package:
dsh plugin --profile web add dsh-hermes-memory
The plugin requires tools, timer, sessionProjections, and a JSON storage backend. The Web profile already includes the storage components; do not declare duplicate storage rows. If storage is unavailable, the plugin returns an error instead of replacing existing data with an empty store.
The implementation verification record covers DSH 0.2.2-alpha.2, Node 24.22.2, and the current v4 session format. The package's peer dependency ranges do not guarantee runtime compatibility with every matching version.
Observe automatic memory
- State a durable fact during a normal conversation, such as “Use Chinese for all future conversations.” A one-off task requirement does not automatically become a lasting preference.
- The main model can save memory at any time. By default, background review also runs when the genuine user-turn count reaches a multiple of 10 and that turn completes normally. Tool calls and automatic continuation messages do not advance the user-turn count.
- Refresh the Memory panel to inspect background status, both banks, and pending changes. Background additions can be saved directly; changes to existing entries and deletions require approval.
- The current session retains the frozen snapshot captured on its first read. New sessions receive updated memory. Use
memory_listto read the current store during an existing session.
A turn counts at most one genuine user message. A review also requires a genuine user message in the triggering turn, successful turn completion, and a nonempty final text response without tool calls. Cancelled or failed turns, empty responses, and responses with unfinished tool calls do not trigger review; any genuine user message already received in those turns still counts toward the interval. Ordinary subagents do not trigger automatic review. Only one review runs per session at a time; later triggers encountered while it is running are skipped rather than queued for replay.
The review receives only memory and memory_list. It has no file, network, command execution, or proposal approval tools. A model response without tool calls ends the review, including a decision that nothing is worth saving. The review also stops at the request limit, deadline, model error, or cancellation. Tool calls in a response that hits the output token limit are not executed. Unloading the plugin cancels requests and waits for writes that have already started to settle.
The two memory banks
| Target | Purpose | Default capacity |
|---|---|---|
user |
User identity, lasting preferences, communication style, and explicit standing expectations | 1375 characters |
memory |
Environment facts, tool conventions, paths, and stable configuration that apply across tasks | 2200 characters |
Each fact belongs in one bank. Transient progress, completion logs, and TODOs belong in session history. Procedures, techniques, and task-specific preferences belong in skills. This plugin does not automatically modify skill files.
Budgets count Unicode code points, including the \n§\n separator between entries; a single 😀 counts as one character. Exceeding the limit does not silently truncate existing entries.
Call the tools
The model-facing memory interface has two required top-level fields: target and operations. Each call writes one bank. The entire batch and its final character budget are validated before a single durable commit.
{"target":"user","operations":[{"action":"add","content":"The user prefers Chinese for conversations."}]}
{"target":"memory","operations":[{"action":"patch","old_text":"Node 24","content":"Node 26"}]}
{"target":"memory","operations":[{"action":"remove","old_text":"The complete obsolete entry"},{"action":"add","content":"An updated, stable environment fact."}]}
addcreates an entry and deduplicates identical text. Leading and trailing whitespace is trimmed.patchreplaces one unique, exactold_textspan while preserving other facts and whitespace in the same entry. To replace an entire entry, use its complete old text asold_text.removelocates and deletes an entire entry. Exact full-entry matches take precedence, followed by unique substring matches. Entry lookup tolerates common differences in quotes, dashes, and whitespace, but apatchreplacement still requires an exact, unique stored span.- A consolidation batch cannot empty a previously nonempty bank. One explicit
removemay delete the final entry. Removal operations can progressively repair a bank that is already over budget. - Legacy top-level
action,oldText, andnew_textinputs remain supported by an internal compatibility layer. The model interface no longer exposesreplace; the panel retains whole-entry editing.
memory_list reads the current banks. memory_search performs deterministic keyword matching within the two banks; it does not search chat history. memory_stats reports capacity, enabled banks, and the pending proposal count. memory_debug exposes storage and background diagnostics.
When a write fails, the tool returns an error and, where applicable, the current entries and usage so the model can correct the batch. The first three consolidation failures return the original error; from the fourth failure onward, the result includes done: true and an instruction to stop retrying. Main-model failure counts are per session and reset each turn; background review uses a separate count for that review. This instruction does not forcibly reject subsequent tool calls. The background loop remains bounded by its 16-request cap and deadline. A successful commit clears the failure count and explicitly instructs the model not to repeat the saved update. DSH controls any further main-model requests after a tool result.
memory_pending only allows the model to list proposals. Approval and discard require the authenticated owner to act in the Memory panel. The model cannot approve its own changes, and the background review has no approval tool. Approval revalidates the complete matched entry and its identity; a stale proposal cannot overwrite an entry that changed after the proposal was created.
Configure background review
Edit the hermes-memory entry through the DSH configuration UI or configuration editing service. To disable independent background review while retaining memory tools and snapshots, merge this configuration into that entry:
backgroundReview:
enabled: false
Full default configuration example (enabled: true enables background review):
memoryCharLimit: 2200
userCharLimit: 1375
memoryEnabled: true
userEnabled: true
nudgeInterval: 10
contentGuard: true
writeApproval: false
backgroundReview:
enabled: true
maxIterations: 16
timeoutMs: 120000
| Field | Behavior |
|---|---|
memoryEnabled / userEnabled |
Disable model reads, snapshots, and writes for the corresponding bank while retaining its stored data for administration |
nudgeInterval |
Legacy name for the background review interval in user turns; default 10. 0 disables periodic triggers unless a separate interval overrides it |
backgroundReview.interval |
Overrides nudgeInterval; 0 disables periodic triggers |
backgroundReview.enabled |
Default true; false disables automatic review while leaving main-model memory writes available |
backgroundReview.provider / model |
Set both to select a separate review model; omit both to inherit the actual model and parameters of the triggering turn |
backgroundReview.maxIterations |
Default 16, clamped to 1–16; maximum model requests per review, not a tool-call limit |
backgroundReview.timeoutMs |
Total background model-loop budget in milliseconds; default 120000 (120 seconds). Cancellation still waits for writes that have already started to settle |
writeApproval |
When true, main-model writes also become pending proposals. Direct owner edits in the panel can still be saved |
contentGuard |
Applies a bounded local scan to new content. Existing entries that match the guard are withheld from the injected prompt |
When a different provider or model is selected, the previous model's reasoning effort and output token limit are not imposed on it; the target adapter's defaults apply. The 16-request limit bounds the plugin's model loop. Internal network retries in the host or model adapter are controlled by their own settings and are not counted by this loop. Each review sends the conversation text captured at the trigger and the currently enabled memory banks. Token usage and cost depend on context length, model, and the actual number of requests.
A background error does not fail normal chat. Failure status remains visible. The same claimed trigger is not automatically replayed after reload; a later interval can try again. A successful earlier write is not rolled back if a later review request fails.
Resource boundaries for extension authors
“Bounded memory” describes the two curated banks, not a bound on total process memory, disk usage, tokens, or cost. Keep these separate when integrating the plugin:
| Resource | Plugin boundary | Outside that boundary |
|---|---|---|
| Curated bank content | Default memoryCharLimit: 2200 and userCharLimit: 1375, measured in Unicode code points including entry separators; final batches are validated before commit |
Prompt framing, tool schemas, conversation evidence, and runtime metadata are not charged to these budgets. Lowering a limit does not automatically truncate existing data; removal-only repairs remain possible |
| Review input | The scheduler captures text from all derived user, assistant, and tool messages available at the trigger; it does not impose an input-size cap or truncate that evidence | runReview has an optional reject-only maxInputChars argument, but the automatic scheduler does not supply it and it is not a deployment configuration field. It measures serialized input with JavaScript string length (UTF-16 code units), not the banks' Unicode code-point count |
| Concurrent reviews | At most one active review per session; overlapping triggers for that session are skipped | No plugin-wide concurrency cap across different sessions. The serialized write queue does not serialize model requests |
| Requests and time | At most 16 model requests per review, configurable downward; a default 120-second model-loop cancellation budget | Not a tool-call, token, cost, or hard end-to-end shutdown limit. Already-started writes are awaited; host/adapter network retries have their own controls |
| Model context and output | Uses the selected model's effective request configuration | Context-window admission, output-token limits, and provider quotas belong to the host/model adapter/provider; they are not guaranteed by bank budgets or the request-count limit |
For integrations needing a strict resource envelope, define input admission, aggregate concurrency, and token/cost policies at the appropriate host boundary rather than assuming the bank budgets provide them. See lib/scheduler.js, lib/review.js, and lib/core.js for the respective contracts. No additional caps or automatic truncation are enabled by this documentation.
Storage and recovery
DSH's JSON KvUnit stores memory in ~/.dsh/storages/hermes_memory.json. The first mutation of v1 records writes an atomic v2 global snapshot:
global: { format: 2, tables: { memory, user } }
The original v1 tables remain as a recovery copy; subsequent reads use the v2 snapshot. Back up data before downgrading. Older versions cannot read later updates stored in v2.
A separate ~/.dsh/storages/hermes_memory_runtime.json stores review claims, outcomes, and pending proposals without adding that metadata to the main session. On reload, unfinished reviews are marked interrupted. An approval with an uncertain commit outcome is not automatically replayed. Any durable publication error stops further writes; reload the plugin and reread storage before deciding how to recover.
Static and dynamic installations share the same storage names and must have only one writer instance. The write queue and storage hub serialize writes within one host. Multiple DSH processes writing to the same directory, or external edits to the files while the plugin is running, are unsupported. This follows DSH's current storage contract and differs from Hermes' native file locking.
Runtime metadata retention
The bank character budgets apply only to the rendered memory and user entries. They do not limit the runtime metadata file, its in-memory copy, or copies made while publishing an update. The current implementation has no automatic expiration, record-count cap, or pruning for runtime metadata:
- Review records: the latest claimed turn and its outcome are retained for each distinct session ID. A later review in the same session replaces that session's record, rather than appending every review. Records for other sessions remain, including after reload; their turn markers prevent replay of already claimed triggers.
- Proposal records: each staged proposal retains its operations, pinned entry identities/content, provenance, and status. Approving or discarding a proposal changes its status; it does not delete the record. Completed proposals disappear from the pending list but remain in storage. Interrupted approvals also remain available for inspection.
- Reload: recovery updates unfinished statuses, not retention. Reloading does not compact or reset the runtime file. The plugin loads the runtime global document in full and publishes a new global document when updating it, so metadata growth can increase storage and memory costs even when both banks are below their budgets.
For long-running installations, monitor the runtime file separately from bank usage. There is currently no supported cleanup command. Do not delete or hand-prune review claims or proposal records to reclaim space: doing so can remove replay protection or the evidence needed to resolve an uncertain approval. Before any recovery or future migration, stop the single writer and back up both storage files together; retain the originals. Follow a migration procedure that preserves claim and approval semantics, then reload and inspect the stored state before resuming writes. Discarding a proposal in the panel is a workflow decision, not a storage cleanup operation.
Implementation references: ensureRuntime, claimReview, and finishReview, and pendingAction in the same module. A future bounded-retention policy needs to preserve replay protection and uncertain approval records; this documentation does not introduce automatic deletion.
The panel API uses DSH owner authentication and Host/Origin checks. Responses disable caching:
| Path | Method | Purpose |
|---|---|---|
/hermes-memory/stats |
GET | Readiness, capacity, and background status |
/hermes-memory/ops |
POST | Read both banks or perform direct edits; list results include pending proposals and background status |
/hermes-memory/pending |
GET / POST | List proposals, or approve or discard a specific proposal ID |
For HTTP 401, reopen the page through the DSH login entry. HTTP 503 indicates that authentication or storage services are unavailable. Failed panel operations preserve drafts, the last successful snapshot, and pending proposals instead of displaying an empty store or a false success state.
Scope of the adaptation
| Hermes mechanism | DSH implementation |
|---|---|
| Two banks, default budgets, independent enable switches, required target, atomic operations | Adapted |
| Frozen snapshots, Unicode code point budgets, unique-span patching, protection against emptying a bank | Adapted with v4 producer-owned message sources |
| Background model review after a qualifying response at each multiple of 10 user turns by default | Adapted as an independent, restricted model loop with at most 16 requests |
| Grounded facts, no inference of user traits from conversation topics, skills take priority for procedural knowledge | Main and background prompts follow the pinned upstream policy |
| Approval for background changes and deletions, with complete original-entry pins | Adapted; only the authenticated owner can approve or discard through the DSH panel |
External providers, automatic skill maintenance, native session_search |
Outside this plugin's dual-bank scope; chat retrieval uses host capabilities |
| Native threat scanner | Replaced with a bounded local heuristic adaptation; not claimed to be equivalent, and false positives or negatives are possible |
| File locks and cross-process rereads | Replaced with a single DSH writer instance, serialized writes, and atomic KvUnit publication |
| Full background agent fork | Replaced with an isolated llm.prepareCall loop; no new main session and no recursive agents |
| Review counters after a main-session fork | DSH rebuilds the count from inherited history, retaining the parent's accumulated user turns instead of starting from zero for a new agent |
| Background requests in the dynamic VM | Unavailable because the current host exposes no native cancellation controller; use the static bundle |
The 2.0.0 implementation and verification record documents validation and platform boundaries in Chinese. The historical 1.2.0 comparison record describes that earlier release and is not a statement of current capabilities.
Develop and verify
The source separates pure rules, prompts, the background model loop, scheduling, and host integration. host.js is generated from those modules; do not maintain a second implementation by hand.
npm run build:host
npm test
npm pack --dry-run
Tests against real DSH services require a built checkout. Those tests are skipped when no checkout is specified:
$env:DSH_CLIENT_TEST_CHECKOUT=Join-Path $env:USERPROFILE 'deepseek-harness'
node --test tests/*.test.mjs
The tests use temporary directories and synthetic data. They cover real v4 encoding, closing and reopening plain JSONL and Zstandard sessions, real JSON KvUnits, native and dynamic VM integration, model adapters, review triggers, concurrency, approvals, and storage failures. Synthetic facts are not written into the actual user memory banks.
Before release, use the strict pinned-host check, which requires the specified revision, clean sources, rebuilt artifacts, and every test passing with zero skips. Ordinary npm test success does not replace this evidence. The generator accepts LF and Windows CRLF checkouts while still rejecting stale dynamic mirrors.
Run these development tools from a source checkout of this repository; they are not included in the installed plugin package:
- Pinned offline Hermes cases: ten traceable scenarios compare selected behaviors. DSH JSON and Hermes Markdown remain different disk formats, and offline tests do not monitor newer upstream changes.
- Synthetic memory quality scorer: scores existing outputs; scorer regression success does not establish model quality.
- Synthetic resource characterization: observes scheduler, review, storage calls and disposal. Its two-million UTF-16-unit cap covers cumulative user evidence across phases, not complete requests or production resource limits.
The main-chat guidance contains 964 Unicode code points. The memory tool description and parameters add approximately 51 characters compared with the previous version. The background-specific review prompt contains 1477 code points and is sent only in review requests. These are character counts; actual token usage and cost depend on the model tokenizer, conversation length, and request count.
License
MIT. Attribution for the original Hermes design, prompts, and rules is recorded in THIRD_PARTY_NOTICES.md. The SVG icon is original to this project.
Links
More in this category
vectorize-io/hindsight#coding-agents★ 60916
Hindsight, agent memory that learns: long-term project memory with auto recall and retain, knowledge pages, deep reflection, and per-repo memory banks.
volcengine/OpenViking#examples/dsh-memory-plugin★ 42209
OpenViking memory and context bundle for DeepSeek Harness: pre-step auto-recall and profile injection, session capture, `viking://` URI guarding, and recall/write memory tools backed by an OpenViking server.
MemTensor/MemOS#memos-local-plugin★ 12392
Local-first memory plugin for DeepSeek Harness: layered L1/L2/L3 memory with automatic capture, manual memos_* retrieval tools, and a built-in memory viewer.
agentscope-ai/ReMe#dsh★ 3799
Connects DeepSeek Harness to ReMe's local-first, self-evolving personal knowledge base: automatically captures completed main-agent conversations as user-owned Markdown memory, searches conversations and source material through reme_search with BM25, optional embeddings, and wikilink expansion, and schedules daily memory consolidation.
zilliztech/memsearch#MemSearch★ 2873
Shared Markdown memory for DSH and other coding agents, with automatic capture, pre-step context injection, searchable recall, and memory-to-skill self-evolution through a review panel.
vshulcz/deja-vu#extensions/dsh★ 1544
Cross-session memory and context recovery across forty-five other coding agents (45+) on this machine, reading session files they already wrote before install (Claude Code, Codex, Cursor, Windsurf, Trae, Antigravity, VS Code Copilot Chat, opencode, OpenClaw, Hermes, Kimi, Qoder, Cline, Zed, Magpie and more): six tools (deja_recall, deja_session, deja_blame, deja_fix, deja_how, deja_remember), a /deja command, and optional automatic recall injected into runtime context. Local BM25 CLI/MCP index, no LLM, no embeddings, no network (dsh plugin --profile web add dsh-deja).
Community comments
Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.