
Security News
Deno Goes the Way of the Dinosaur
Cloudflare is acquiring Deno’s team, which will provide another year of bug fixes and security updates before ending support for the runtime.
dsh-engram
Advanced tools
Minimalist long-term memory for DeepSeek Harness, distilling symbolic-index + pi-esr ideas: zero-LLM auto-capture, a symbolic [ENGRAM] index with progressive disclosure, and an ESR-lite evidence-closure protocol (esr_task / esr_node / esr_close / esr_link
Minimalist long-term memory for DeepSeek Harness, distilled from the symbolic-index and pi-esr ideas — with one goal: save tokens.
-m commit message, edits to
key files, repeated errors), plus an explicit engram_store. Nothing on the hot
path calls a model, and pure plumbing — git push / git stash / silent
commits — is deliberately never recorded (see Auto-capture policy below).
Every write is run through a deterministic secret redactor first — API
keys, JWTs, Bearer tokens, private keys, AWS/Stripe/Slack/GitHub tokens and
key=value secret shapes are replaced with <REDACTED:…> markers before the
dedup hash, the char cap, and storage, so sensitive text can never land on
disk. Failing test runs are recognized by pure patterns (npm test,
node --test, vitest/pytest/jest, failure lines) and captured as
tags:["error","test"] entries with a raised signal. DSH goal-domain
integration: a completed or blocked goal (goal/change session event)
auto-sediments as a handoff/error memory (tag:goal), so the outcome of
a goal survives its session (see the GET /api/dsh-engram/goals read surface).autoCapturePreferences, default on,
zero LLM) — the lesson that "when the AI makes a mistake and gets corrected,
that lesson should persist" (correx,
PAHF, Mem0 preference extraction): a
session/event listener watches user/message and turns deterministic
correction/preference cues into a dedicated preference memory kind —
Chinese (不要用X, 改用X, 以后统一用X…) and English (don't use X,
switch to X, prefer X, never use X…) patterns, three directions
(avoid / switch-to / prefer), with interrogative/one-shot-instruction/
memory-tool-meta-talk filters to keep false positives out. preference
memories outrank every other kind in the [ENGRAM] block ([R] prefix) so a
standing user rule is the first line the model sees each session; per-session
cap + recent-window dedup mirror the auto-capture discipline. Zero LLM, zero
embeddings, deterministic and pure — extracted clauses are bounded, and every
write still goes through the secret redactor.[ENGRAM] block (default
budget 700 chars ≈ 175 tokens; one line per memory) is injected at prompt
assembly and frozen per session, keeping the request prefix byte-stable for
KV-cache reuse. The agent drills down with engram_recall / engram_detail instead
of dumping raw hits into context; recall ranks the in-domain pool with an
in-process BM25 pass (TF·IDF with label/phrase boosts plus a gentle time-decay
factor — deterministic, zero deps), appends a compact entity-neighborhood
table (reusing esr_link: node --rel--> node · conf%) for entity-anchored
hits, re-warms the old error entry when a new failure is near-identical
(repeat-failure revival: recency + hit climb toward promoteHits so the
failure resurfaces instead of piling up), and on zero local hits falls back
to DSH's own cross-session full-text index (ctx.sessionQuery, filtered by
cwd) instead of building a parallel SQLite index; when the FTS fallback also
comes back empty and the query contains CJK characters (Chinese FTS5
tokenization), recall runs a bounded substring scan over the newest session
logs (zstd-decompressed, LRU-cached) and appends matched past sessions —
deterministic # past sessions lines, capped files and bytes.autoRecallOnStart, default on) — the pull-only
design had a gap proven in real use: recall tools relied on model initiative,
and in practice the model almost never calls engram_recall on its own (19
real sessions, thousands of tool calls, ~5 recall calls total). So at the FIRST
assembly of a new session the host runs one deterministic BM25 recall keyed on
the session's first user message and injects the top ≤ autoRecallLimit hits
as a [RECALL] block (default 700-char budget, "few and precise" — agentmemory
candidate ①). Relevant memory is in context even without a single recall call.
Pure rule, zero LLM, pure read (never bumps hits); superseded stale truth does
not occupy an injection slot (still fetchable via engram_detail); frozen per
session together with [ENGRAM] for prefix stability.… marker and the block's trailing guidance names that
concrete id: #<id> truncated — engram_detail <id> (full) · engram_recall <query>.
So the model knows the full record is one deterministic engram_detail call
away instead of assuming the preview is the whole memory (proven gap in real
use: 66 sessions, zero engram_detail calls — the detail path was invisible).engram_store accepts optional
supersedes / contradicts memory ids (validated against the same
workspace). A superseded ("stale truth") memory is demoted to the tail of
recall and excluded from the [ENGRAM] block, while the superseding
statement keeps the line; a contradicted memory stays ranked but is flagged
· contradicted by <id>. Old rows are never deleted — re-fetchable, just
honestly ranked. Opt-in autoSupersede config (default off) auto-marks
entity-anchored replacement updates ("改用 / no longer / switched…") as
superseding their matching older row; explicit supersedes always wins.cmd: signature
tag; when that same command later succeeds, engram auto-sediments a
procedure memory (fixed: <cmd> — N earlier failing runs now succeed),
tags the earlier error rows resolved (never re-ranking them), and recall
then surfaces the fix instead of the stale failure.esr_task / esr_close / esr_link give tasks
a draft → active → stable lifecycle where stable requires real evidence
(artifact / evaluation / memory_ref), surfacing closure gaps instead of
letting the agent declare victory without proof. With verifyArtifact on
(default), a non-URL artifact is resolved against the workspace (= the session
cwd) and must exist on disk — otherwise the task stays ACTIVE with the reason;
force:true (or disabling the toggle) skips the disk check, the three gates
are still mandatory. The tool and the web forms share one gate
(store.evidenceGate), so the two surfaces can never drift.
Tasks are never isolated graph nodes: esr_task(entity=…) one-call wiring
auto-creates the ent_<slug> node and hangs the task on it
(task --relates_to--> entity, idempotent), so a task created with a domain
anchor immediately shows a relation; and esr_dep writes the dependency edge
into both the task's deps (blocker logic) and the shared links table, making
every task dependency a first-class visible edge in the graph.[ESR] block is a
deterministic per-session frozen snapshot (set at session start,
deterministically sorted, timestamps excluded, explicitly "WILL NOT
auto-refresh") plus the session's monotonically accumulated actionables:
the promote: / root-cause: / close: / stale: / escalate: lines are
APPENDED exactly once, the moment each matures, and stay frozen for the rest
of the session (never removed) — so the prefix changes only when genuinely
new decision-point info appears, never per-turn, while the timely nudges
(narrow funnel / recurring failure / work done / stale / balance) stay in
context. Live state is still pulled via esr_status (full deterministic
view + since_revision short "unchanged" response + derived actionables).
The tools/result recorder feeds the actionables' data sources; P4
conversion metering stays (#suggest-* tags + 10-minute attribution +
GET /api/dsh-engram/triggerstats). Pure rules, zero LLM.autoSinkTodosOnEnd default
on) — during a session the native todo stays cheap, per-session working
memory that dies with the session. But when a session closes at its teardown
edge (session/disposed) while its plan still has pending todos, they are
auto-landed as ESR draft tasks (name = the todo text, description
marked 「源自会话计划」, deduped against existing task names, capped by
maxTasksPerWorkspace), so the plan never silently evaporates. Sinking to
draft — not active — keeps them out of the [ESR] active rows and creates
no evidence obligation; an explicit esr_claim / esr_task step is still
what promotes a draft to active. The "nothing auto-written during the
session" trade-off is unchanged; only the end-of-session data-loss gap is
closed. Disable via profile patch or the settings card.[ESR] surface, and dangling link edges are dropped. The working set (active
task refs, task memories, indexed hits) is never touched, and nothing is
hard-deleted — every archived entry keeps its id and stays re-fetchable.compaction service and swaps DSH's lossy
LLM-summary compression for mechanical eviction + re-fetch pointers:
evicted turns are scanned for engram/ESR anchors (#memory-id, tsk_*,
ent_*, file_path) and replaced with one line "this detail lives at
engram_detail / engram_recall / the [ESR] block"; only un-provenanced
turns get a scoped LLM narrative (gcNarrative, default on, off = fully
mechanical). All six GC constraints hold (working-set-protected,
pointer-salience, no-provenance-no-evict…); any error falls back to the
default compressor — compaction is never broken.MIT · node >= 22.19 · host-half + browser-half in one package
Surveys of the existing DSH plugin ecosystem show the recall-bridge, approval-gate, LLM-distillation and vector/graph niches are already crowded. dsh-engram fills the three gaps that matter for token discipline:
DSH already provides cross-session FTS (ctx.sessionQuery), storage
(ctx.storageDomain), prompt-injection hooks and settings slots; dsh-engram is a
thin composition layer over them, not a re-implementation.
The DSH plugin ecosystem is young and unvetted — there is no official directory, no signature check, and the harness's read/write permission tiers do not constrain what a plugin's own code can do. So the trust bar for a memory plugin is higher than for a tool: a memory plugin sits on your prompt prefix and writes to disk on your behalf. dsh-engram's model, stated plainly:
~/.dsh/storages/dsh_engram.json (plus the session-log
reads it makes through DSH's own ctx.sessionQuery).engram_store, auto-capture, and the import/restore route — runs text through
the deterministic redactText rule engine (API keys, JWTs, Bearer tokens,
private keys, key=value secret shapes) before the dedup hash, the char
cap, and storage. Nothing sensitive is ever persisted, so nothing sensitive
can be recalled later.danger-full-access — if you
keep your profile's defaults, engram inherits exactly those bounds./api/dsh-engram reject any non-loopback caller unless you explicitly
whitelist a hostname via trustedHosts; even then the same-origin fence holds.npm run dsh-engram -- doctor (/scripts/dsh-engram.mjs doctor) reports what the plugin is configured to touch; the whole store is
inspectable, nothing is opaque. If you ever doubt a claim in this section, the
code to check is lib/redact.js, lib/store.js and this plugin's
cordis.patch.yml.What this deliberately does not claim: it is not an air-gap. If your profile
grants the agent danger-full-access or a shell, the agent can use those —
engram is a memory layer, not a sandbox. The point above is that dsh-engram
itself adds no surface of its own.
Memory should not be a hostage. The four tables (memories / tasks / links / entities) can be dumped and restored through a stable, versioned, machine-readable shape — so the corpus survives a reinstall, a machine move, or (when a thin adapter is written) a move to another harness, MCP end-point or Claude Code skill. The contract is plain JSON with a self-describing header:
GET /api/dsh-engram/export?workspace=/path/to/project # one workspace
GET /api/dsh-engram/export # every workspace
{
"meta": { "format": "dsh-engram/export", "version": 1, "exportedAt": 1724…, "workspace": "/path/to/project" },
"memories": [ /* all rows, archived included — a backup never loses provenance */ ],
"tasks": [ /* draft/active/stable + archived */ ],
"links": [ /* typed edges */ ],
"entities": [ /* graph nodes */ ]
}
Restore is idempotent by default and gated when destructive:
POST /api/dsh-engram/import # body: { payload, mode, dryRun?, workspace?, confirm? }
mode: "merge" (default) — writes only rows whose id is absent, so a
backup can be re-applied any number of times without duplicating anything. Each
row keeps its own workspace.mode: "replace" — wipes the target workspace from all four tables, then
restores only its rows. Destructive, so it additionally requires
confirm: "restore" in the body.dryRun: true — computes the identical plan (what would be written /
skipped) without writing a single row.storeMemory: memory text passes the
secret redactor before touching disk, and rows that would break the contract
(missing id/workspace, empty text, over maxMemoryChars, id already present
in merge, over the workspace memory cap) are skipped and listed in the
response report instead of aborting the batch.A restore round-trip for one workspace is then:
curl -s "http://127.0.0.1:3080/api/dsh-engram/export?workspace=$PWD" -o engram-backup.json
curl -s -X POST http://127.0.0.1:3080/api/dsh-engram/import \
-H "content-type: application/json" \
-d "{\"payload\": $(cat engram-backup.json), \"mode\": \"merge\"}"
>=0.1.2-alpha.2 <0.3.0-0 (本版 0.5.0; 已发布的 0.3.7、0.4.0 仍声明旧的 0.1.x 范围 >=0.1.2-alpha.2 <0.2.0-0 — 见下方矩阵)0.2.x)落地,npm test 329/329 通过;真实 dsh 0.2.0-rc.2 隔离 profile 运行烟测通过(见下方“当前发布”)dsh plugin --profile web add dsh-engramShort version. Every published release (
0.2.0…0.3.6) supports the old DSH API graph (0.1.0-rc.7/0.1.1, peers^0.1.0-rc.7) only. DSH0.1.2-alpha.2and newer are not compatible with those releases — the settings/client API family broke there. New-DSH support starts with0.3.7, the first release of the new API generation (described below).
| dsh-engram release | Status | DSH support | Declared via |
|---|---|---|---|
0.2.0 … 0.3.6 (npm, GitHub v0.3.3–v0.3.6) | published | old DSH only — 0.1.0-rc.7 / 0.1.0-rc.8 / 0.1.1-rc.1 / 0.1.1-rc.2 | peers @deepseek-ai/dsh-*@^0.1.0-rc.7 |
0.3.7 / 0.4.0 (npm) | published | new DSH, settings installSection — >=0.1.2-alpha.2 <0.2.0-0 | engines.dsh + peers >=0.1.2-alpha.2 <0.2.0-0 |
0.5.0 (本期 / this release) | pre-release | DSH 0.1.2-alpha.2 … <0.3.0-0 — 设置卡迁移到 entry-id volatile Config(0.1.7+/0.2.x),≤0.1.5 自动回退 installSection | engines.dsh + peers >=0.1.2-alpha.2 <0.3.0-0 |
0.1.2-alpha.20.1.2-alpha.2 is the first version of the new API family and the first that
breaks the released plugin versions:
@deepseek-ai/dsh-settings removed settingsNamespace() and
installSettingsSection() — the exact imports every release ≤ 0.3.6 uses —
and replaced them with settings.installSection. The old host half fails to
load on 0.1.2-alpha.2+.@deepseek-ai/dsh-client-runtime
no longer exists in the new family (replaced by dsh-api-remotes plus
dsh-client-locale / -conversation / -renderer / -ui-settings-plugins),
and the settings card used to talk through ctx.remote.settings.@deepseek-ai/cordis ^4.0.1 → ^4.0.2,
@deepseek-ai/schemastery ^3.18.1 → ^3.18.2, and @deepseek-ai/dsh stopped
shipping config/agent-presets in the package (moved to dsh.configTrees).The web config card no longer depends on any of this: since 0.3.7 it reads
and writes the dsh-engram settings namespace through the plugin's own
HTTP route (/api/dsh-engram/settings, same-origin fetch like the memory
viewer), so the card is generation-agnostic on both sides of the
0.1.2-alpha.2 boundary.
One more settings break, at 0.1.7 / 0.2.0 — and the reason 0.4.0 was
not actually loadable on the current DSH: @deepseek-ai/dsh-settings removed
settings.installSection again in 0.1.7-alpha.2 (and keeps it removed in
0.2.x). The new settings model is entry-keyed: editable fields are
.volatile() fields of the plugin's own Config (validated + hot-committed by
the loader), and the settings form is keyed by the profile entry id (engram),
so nothing needs "mounting". 0.5.0 feature-detects the two generations in
installEngramSettings:
≥0.1.7 / 0.2.x — the 27 GUI knobs are the volatile Config fields
(lib/settings.js makeEngineConfigSchema, exported as Config from
lib/index.js); the card reads/writes entry engram through the same
/api/dsh-engram/settings HTTP route, and the service.settings seam
re-syncs resolved on loader/volatile-update so card edits land live on
new sessions.≤0.1.5 — the legacy settings.installSection namespace (dsh-engram)
is registered as before; nothing changes.The declared range >=0.1.2-alpha.2 <0.3.0-0 covers both, and the HTTP wire
namespace stays dsh-engram on both generations (the seam's active namespace
drives mutations).
So: keep DSH on 0.1.1-rc.2 or older while running a release ≤ 0.3.6, and
upgrade the plugin to >=0.3.7 before moving DSH to 0.1.2-alpha.2.
0.5.0The published 0.4.0 declared >=0.1.2-alpha.2 <0.2.0-0 — which the semver
gate rejects on every DSH 0.2.0-0 prerelease (<0.2.0-0 excludes
0.2.0-rc.x), and its host settings half still called the installSection
API that dsh-settings 0.1.7+ removed. 0.5.0 fixes both: the range
opens to <0.3.0-0 (verified against DSH's own includePrerelease check, so
0.2.0-rc.2 and the whole 0.2.x line pass), and the settings card is
migrated onto the entry-id volatile Config model described above (with a
feature-detected legacy fallback).
| Component | Supported range |
|---|---|
| DSH runtime / host / client package family | >=0.1.2-alpha.2 <0.3.0-0 |
@deepseek-ai/cordis | ^4.0.2 |
@deepseek-ai/schemastery | ^3.18.2 |
| Node.js | >=22.19.0 |
0.1.2-alpha.2 is the minimum version for this API family: host settings are
feature-detected — on DSH ≤0.1.5 they register the dsh-engram
namespace via settings.installSection; on ≥0.1.7 / 0.2.x the form is the
plugin's Config with the 27 GUI knobs marked .volatile() (entry id
engram). The browser settings card reads and writes through the plugin's own
HTTP route (/api/dsh-engram/settings, see Route B above) on both. The full
regression suite (npm test 329/329, incl. the new settings-v2 coverage for the
volatile-Config model) passes against the DSH 0.2.0-rc.2 dependency set
(cordis 4.0.4 + schemastery 3.18.4 + cosmokit, matching the global DSH CLI).
The .volatile()/loader hot-commit path and the describe-side projection were
verified against the real dsh-settings 0.2.0-rc.2 utilities, and a full
real-machine boot smoke on an isolated dsh 0.2.0-rc.2 profile passed:
the plugin boots with no version-range denial, the settings card form is
served for entry engram (all 27 GUI knobs, incl. trustedHosts), and a
GET → PUT (mutate ops, revision 0→1) → unset round trip on
/api/dsh-engram/settings lands in the profile row and reads back. The older
0.1.0-rc.7 and 0.1.1 API graphs are outside this release's compatibility
range.
To install the DSH version used for validation:
npm install --global @deepseek-ai/dsh@0.2.0-rc.2
dsh --version
# from GitHub (this repo)
dsh plugin --profile web add github:skepsun/dsh-engram
# once published to npm
dsh plugin --profile web add dsh-engram
# local development (symlink — edits apply immediately)
dsh plugin --profile web add link:/path/to/dsh-engram
Then restart dsh web. Data persists in ~/.dsh/storages/dsh_engram.json.
The npm and GitHub installs below need no manual dependency step: pnpm installs
zodand vendors the optional@deepseek-ai/*peers into the plugin's ownnode_modules, and the CLI auto-registers the plugin into the profile'sdsh.profile.bundles. Thesetup-linksstep below is only for thelink:development workflow, where pnpm deliberately does not install a symlinked directory's dependencies.
A fresh session is required to see the injected
[ENGRAM]/[ESR]blocks and the tools; both prompts and the tools registry are assembled per session.
link: installsA symlinked plugin resolves its imports from its own node_modules, so the
host-side dependencies must be present next to the checkout — they are not
tracked by git:
cd /path/to/dsh-engram
node scripts/setup-links.mjs # one command: links the @deepseek-ai
# workspace packages into node_modules AND
# installs zod (reused from the harness
# pnpm store, or via `npm install`)
The script auto-locates the harness checkout at ../deepseek-harness (also
works when it sits next to the repo's parent, e.g. E:\deepseek-harness +
E:\kototoro_demo\dsh-engram); override with DSH_HARNESS_DIR. Without this
step, dsh web boot fails with ERR_MODULE_NOT_FOUND: Cannot find package 'zod' (and would fail on the @deepseek-ai/* peers next). node scripts/setup-links.mjs --check prints the state without writing anything.
After restart, inside the native DSH settings surface:
Sidebar "ESR 看板" entry + full-screen kanban — one more row under
New Session with a live active-task badge (polled from /overview every
30s across all workspaces). Clicking it opens a full-screen board in the
center column: 草稿 / 进行中(gaps) / 就绪(evidence ready) / 已闭环 columns,
workspace filter + search, an inline create form, and per-card
"补齐证据 → 关闭" closure forms sharing the esr_close gates (artifact +
evaluation + memory_refs). A 看板 / 图谱 (board/graph) toggle in the
header reuses the full relation graph (esr_node/esr_link force-directed SVG:
entity circles + task check badges, drag/zoom/select for relation details),
following the workspace filter and refreshed by the same 20s polling.
Following the task-board precedent, the entry and
the board are DOM-mounted and self-heal (MutationObserver re-inserts on shell
re-renders), with cross-panel exclusivity against task-board/ssh (opening one
evicts the others; clicking a sidebar session/workspace row hands the center
column back to the conversation). The conversation subtree stays mounted
underneath and is hidden by html[data-dsh-engram-board-active], so toggling loses
no state.
Unified task strip above the composer — the conversation dock that DSH
ships for its built-in todo tool is taken over (same conversation.input.dock
cell / id: todo at a lower priority) and merged into one modern control:
the session's current plan (todo_write's todos projection) plus the
workspace's persistent ESR tasks (with evidence-gap badges and an inline
"补齐证据 → close" form) plus the relation graph rendered as
node → relation → node chips with entity/task names resolved. It only shows
while there is something to show, stays live with 15s polling, and the built-in
plan still renders (without the ESR parts) if the loopback-fenced API is
unreachable. A workspace-switcher chip leads the strip: it defaults to
following the current session (the tooltip says so), and its dropdown pins the
ESR task/relation source to any workspace (✓ marks the active pin; the × or
"follow session" entry reverts). Switching refetches immediately and is a pure
UI focus change — the model's session context and the per-session frozen
injection blocks are untouched.
Settings → Engram Memory — a standalone first-class settings section
(right after the Plugins section, not a child tab of it). Default
"All workspaces" view shows every workspace's memories/tasks/links
grouped by workspace (dropdown + prev/next workspace pager; the memory
table additionally pages 10 rows per page with a jump dropdown, fixed
column widths, 3-line clamped content ellipsis and full text on hover).
Overview
stat cards (counts by workspace/kind, auto-capture totals, per-workspace
[ENGRAM] index token estimate, cumulative GC totals), a searchable /
filterable memory table with archive + delete actions, an ESR task board
with an inline "new task" form and a per-task "fill evidence to close"
(artifact / evaluation / memory_ref → STABLE, same gates as esr_close),
a node + relation list (nodes are domain objects the model registers via
esr_node — package/service/repo/concept; relations via esr_link), a separate
relation-graph tab (hand-rolled force-directed SVG, no chart library so
the bundle stays pure: entities as circles, tasks as check badges, relations
colored per type with direction arrows; drag nodes, pan, wheel-zoom, re-layout,
hover highlights the neighborhood, clicking a node pops a floating panel with
its incident relations and linked objects; dangling links are counted and
warned about), an injection-preview tab that renders the exact
[ENGRAM] index block (order 40) and [ESR] task/closure block (order 41)
the model sees each session — same pure functions as the system-prompt
sections — as two terminal-style panes with per-line coloring (block
headers, task lines, drill hint, and the data-driven escalate: reminder
highlighted), line/char/~token cost chips plus memory/task/link/node count
chips, 20s auto-refresh and one-click copy of the raw block text (backed by
the new GET /api/dsh-engram/preview?workspace=… route). Every ESR task
card (board and ESR tab) carries an evidence-progress ring — a small
three-arc SVG donut mapping artifact · evaluation · memory_ref, all green
when closure-ready, amber while gapped, gray with no evidence yet; the
board header adds an aggregate ring showing overall evidence
completeness (%) plus how many in-progress tasks are closure-ready. Pure
SVG, no chart library, bundle stays clean. A telemetry dashboard tab
turns the /stats usage rollup (workspace × day) into a pure-SVG dashboard:
three gauges for ESR proactivity (benchmarked against the 0.34 escalate
threshold, amber + hint when low), recall hit rate and detail
follow-through, five stat cards (total / esr / memory calls, avg hits per
query, failures), a 14-day mem-vs-esr stacked bar chart and a Top-8 tool
breakdown (mem blue / esr purple), 20s auto-refresh with an automatic
small-sample (<10 calls) warning. A details sidebar (master–detail)
opens on the right when you click any task / memory / node / relation
row: task cards show state badge, evidence ring, full id + timeline,
gap list, clickable memory references and an inline "fill evidence to
close" form that refreshes the lists on success; memory cards show full
text, tags, signal/hits/TTL and provenance metadata; node cards list all
incident relations (typed, colored, with direction + confidence); clicking
a task's memory reference jumps straight to that memory, with a hint when
it is not in the loaded set. And a
memory-GC panel (dry-run toggle + run button + pointer report). The GUI
create/close use the host's new POST /api/dsh-engram/tasks and
POST /api/dsh-engram/tasks/close routes. Model-side proactivity is driven
by the [ENGRAM]/[ESR] injected blocks: multi-step work gets a task now,
recurring domain objects get a node, related tasks/nodes get a link.
Real behaviour telemetry (agent observability) — the ESR page opens
with an "agent behaviour" panel fed by a new usage table (per workspace
× day rollup) + GET /api/dsh-engram/stats: every engram_*/esr_* tool call
is recorded (counts, failures, recall mechanics). Reported ratios:
ESR proactivity = esr calls / (memory + esr calls); recall hit rate
= recalls returning ≥1 hit / total recalls; mean hits per query;
detail conversion = a engram_detail following a hit recall within 8
session events. Per-tool counts + a 14-day daily rollup are shown too.
Numbers are real, from real sessions — lift ESR proactivity by watching
this panel and tuning the injected prompt.
Settings → Plugins → Plugin configuration → dsh-engram — a collapsible config card in the same style as the built-in "Shell / Agent loop / Web search" cards: title + one-line description + chevron, collapsed by default, click to expand/collapse. Open, its ~12 options render under four groups — Capture & Search / Index / Lifecycle & GC / Security. Changes apply to new sessions (frozen blocks stay stable); Discard / Save with an "unsaved" badge on the header. The card drives the namespace through the connection's own settings RPCs (not the isLoopback-gated scope), so it stays editable even when the GUI is reached through an operator-authorized tunnel.
The browser half is served by DSH's client-module loader directly from this
package (dsh.client + exports["./client"], no web-application rebuild); the
data comes from the loopback-fenced /api/dsh-engram/* route family. The fence
stays closed by default; to reach the memory viewer from an authorized tunnel
hostname, list it in the plugin's trustedHosts config (e.g. via the registry
or a profile patch):
// patch/engram.json
{ "engram": { "trustedHosts": ["cream-club-fragrances-caught.trycloudflare.com"] } }
If you change client/src, rebuild the bundle with:
npm run build:client
| Tool | Purpose | Kind |
|---|---|---|
engram_store | Explicitly store one memory (kind, tags, optional entity anchor, optional supersedes/contradicts ids, optional source/conditions) | write |
engram_recall | Deterministic keyword recall over workspace memories; optional search_sessions FTS over past sessions; opt-in scope=global/recallScope cross-workspace recall with [W:<ws>] origin markers | read |
engram_detail | Full record of one memory id (provenance, tags, hits) | read |
esr_task | Create a task entity (draft → active) | write |
esr_close | Close a task via the evidence protocol (artifact + evaluation + memory_ref) | write |
esr_link | Add a typed relation between two entities (mini graph) | write |
esr_dep | Add a dependency edge (blocks / relates-to / parent-of) between tasks | write |
esr_claim | Atomically claim a task (assignee + claimedAt, draft → active) | write |
esr_unclaim | Release a claimed task's assignee | write |
esr_ready | List claimable tasks (no open blocker, unclaimed) | read |
esr_status | Pull live ESR state + derived hints (since_revision short "unchanged" response) | read |
esr_node | Create/update an entity node (stable symbol) | write |
esr_gc | Run the memory GC for the workspace (dry_run:true previews) | write |
esr_model | Precomputed mental model of the workspace (brief/full, max_chars) | read |
The [ESR] block is a frozen per-session snapshot and never auto-refreshes —
call esr_status for the live truth.
A scheduled sweep (gcIntervalHours, default 24h) plus a manual esr_gc /
GUI button keeps the store bounded the pi-esr way — mechanical, working-set
protected, archive-only:
gcStableRetentionDays become archived and leave [ESR];GC never touches the working set: memories referenced by an active task
(memory_refs), task-kind memories, and already-indexed hits
(hits >= promoteHits). Run esr_gc with dry_run: true to preview. Nothing
is hard-deleted — the report ends with re-fetch pointers for everything it
archived, so archives are recoverable, not lost.
DSH's default context compression is a lossy LLM full summary
(compaction-basic): evicted history is crushed into prose — un-queryable, and
the summary itself burns tokens. dsh-engram's auto-GC replaces it:
ContextGcEngine extends BasicCompactionEngine overrides the single
summarize() hook and swaps the summary body for:
engram_store/engram_recall/engram_detail, tsk_*/ent_*
touched by esr_*, and file_path anchors (esr_gc and other management
calls are NOT anchors);engram_detail(id: "…") / engram_recall(query) / the [ESR] block /
esr_ready); the active working set is restated, never evicted;gcNarrative, default on; off = the whole path is
zero-LLM and un-provenanced turns are kept as truncated verbatim).Triggers follow DSH's own compaction timing (step pressure / context-overflow /
/compact); the lock, replay validation, tool-call/result pairing and token
pricing are all reused from the basic engine. Any error falls back to the
default compressor — compaction is never broken; with gcReplacesCompaction: false (or a host without dsh-compaction-basic) a bare BasicCompactionEngine
is mounted instead, so the compaction service never disappears while
dsh-engram is installed. Mounting registers the compaction service on the
plugin's fiber, so unloading/reloading engram restores the default engine.
Shrink gate: the harness refuses any checkpoint that is not strictly smaller than the evicted span (
summary is not smaller than the shadowed content) and rolls the compaction back — otherwise the context never shrinks and the session eventually hits the model-side overflow. Context GC self-predicts that gate: it mirrors the harness framing with the hosttokenMeter(verified token-for-token identical) and truncates the pointer/narrative body to the evicted span's token budget (the pointer head and working set survive; the trimmed tail stays recoverable in the session log), so compactions actually commit. Full from-scratch usage:docs/CONTEXT-GC-GUIDE.zh.md.
Assembly happens on two planes — and web is now fully automatic, zero config:
lib/index.js) mounts the engine inside ctx.effect via
mountCompactionEngine(ctx, resolved, { readWorkspace }) → new Engine(),
registering the compaction service on its own fiber; the plugin patch
disables the base compaction-basic row, making engram the sole host-plane
provider. gcReplacesCompaction: true (default) → ContextGcEngine;
false → a bare BasicCompactionEngine. Tune via profile patch
(config: { gcReplacesCompaction: false }) or the settings card's
gcNarrative knob (narrative off = fully mechanical).compaction-basic), unreachable from a
host-plane row — and unreachable from a profile patch too (mountPreset
builds its Include.Config with no patches; verified against the harness
source). So on boot, once the agentPresets service is ready, the plugin
auto-edits every preset that still has the stock layout — the default
preset plus the whole roster (shipped root + ~/.dsh/.agent-presets user
root: standard, code, cordis, and your own presets): it swaps the
compaction-basic row inside the compaction group for
dsh-engram/compaction. That auto-wiring is:
autoWebCompaction: true, toggle in the settings card's
memory-GC section), idempotent — an already-wired preset is left alone;
what "off" means: the plugin stops auto-wiring on later boots (the
headless/TUI/base host plane is unaffected) — it does NOT un-wire presets
that were already wired; use npm run web-compaction:revert for that;minimal
preset), is never written, only logged;agent.cordis.yml.engram.bak
holds each original next to its file, every result is re-read and verified
after writing, and any doubt rolls that file back; a failure only warns and
that session keeps DSH's default summarizer — the host plane is never
affected.gcReplacesCompaction / gcNarrative) — with
"already wired but with a different config" treated as a refresh, so
changing the settings knobs re-provisions the web rows on the next boot
instead of being silently ignored.--file <path> targets one):
npx dsh-engram status # stock = untouched · wired = active · custom = hands-off
npx dsh-engram doctor # status + ranked next steps for every gap found
npx dsh-engram enable # same as the boot-time auto-wiring (usually a no-op)
npx dsh-engram revert # restore the stock row everywhere — run BEFORE uninstalling
(in the repo, npm run web-compaction:* is an alias of the same CLI.)
Either way the compaction group becomes:- id: compaction
name: cordis:group
group: true
isolate:
compaction: true
toolResultPruner: true
config:
- id: engram-compaction # replaces the original compaction-basic
name: dsh-engram/compaction
config:
gcReplacesCompaction: true # from settings; false = default LLM summary for this session
gcNarrative: true # from settings; false = fully mechanical
- id: command-compact
name: '@deepseek-ai/dsh-command-compact'
- id: tool-result-pruner
name: '@deepseek-ai/dsh-compaction-tool-result-pruner'
# keep the original config
⚠️ Uninstall = revert first: once a preset references
dsh-engram/compaction, removing dsh-engram leaves every such row dangling
and web sessions hang on load — so run npx dsh-engram revert
(restores every preset) BEFORE uninstalling. A harness upgrade that ships a
fresh standard preset overwrites the swap and self-heals the dangling
reference.mountCompactionEngine, and the web plane by autoWebCompaction
(default on) rewriting every stock preset at boot. Zero manual setup.$DSH_HOME/engram/context-gc.status.json (host plane mode + the per-preset
web results + effective config). At any time run npx dsh-engram status or
npx dsh-engram doctor; the web memory board (ESR kanban) also shows a
header chip ("Context GC·主机 ·N 预设") fed by the overview API. Two startup
log lines confirm it too: engram context-gc: compaction = Context GC … and
engram web-provision: wired Context GC into preset ….autoWebCompaction (web auto-wiring on/off), gcReplacesCompaction
(Context GC vs default summarizer), gcNarrative (narrative safety net).
Changes take effect after restarting dsh web, and the web rows are
refreshed to match automatically.Verify it took: after restarting dsh web (or the profile) —
engram context-gc: compaction = Context GC (mechanical eviction + re-fetch pointers);engram web-provision: wired Context GC into preset "standard" (…); restart dsh web so sessions pick it up,
or npx dsh-engram status reports wired for each; sessions composed
from ANY preset then compact via mechanical eviction + re-fetch pointers.
A line like … keeping the existing compaction service means another provider
already owns compaction in that realm (the swap didn't take); web-provision … skipped (custom)
means that preset was customized (or has no compaction group) and was left untouched;
dsh-compaction-basic unavailable means the backend is missing and everything
falls back to the default compressor.Context GC owns what a compaction summary says; BoundaryPrune owns when a
deterministic prune fires: at todo-completion progress and goal terminal
events, it calls DSH's own toolResultPruner (deterministic surface-replace,
zero LLM) so long-stale over-threshold tool results stop replaying in every
later request. The two switches are independent.
boundaryPruneMinRemaining (default 50) more requests — expectation is a
workspace-history prior with ×2 growth projection past it, so long sessions
are never starved.boundaryPruneIdleCadence, default 0/off) — the "step idle" source that
covers the ~62% of sessions which never touch todo/goal (recon:
eval/recon-idle-boundary.mjs; +85% saved chars at cadence 50 in the
counterfactual eval/simulate-idle-cadence.mjs), still repayment-gated.boundaryPruneThresholdChars (default 0 → host's
8192). Set e.g. 2048 with smaller retained edges
(boundaryPruneHeadChars/boundaryPruneTailChars) to prune earlier —
T2048+small-edges lands 2.4× the first-prune savings of the host default on
the real corpus (10.14M vs 4.23M chars). Lowering the threshold is safe with
resultPack on, which archives the removed middles.boundaryPrune: false. When on, the log shows
engram boundary-prune (todo @N reqs): pruned ….result_recall, default OFF)BoundaryPrune makes pruning lossless: the removed middle of every pruned
tool result is redacted (same rules as the memory write path) and archived to
~/.dsh/engram/resultpack/<callId>.json, and the pruned surface node gets a
tail handle — [middle archived: result_recall(<callId>) 可取] — so the model
knows the full result is one step away. result_recall(callId, offset?)
returns the archived middle in paged text blocks (progressive disclosure, the
same philosophy as [ENGRAM] → engram_detail, and the complete
ObservationPack pattern from SoL-Pi mechanism ③).
resultPackRetentionDays, default 7) riding the same GC cadence.boundaryPrune has nothing to archive, so it
registers no tool and emits one warning.resultPack: false (must be on together with
boundaryPrune: true). When active the log shows
engram result-pack enabled: result_recall(callId, offset) registered.Mechanisms must be provably activating, and dormant ones are off by default.
/api/dsh-engram/stats (and the GUI overview) now answer
activation: { boundaryPrune, recall }:
boundaryPrune — cumulative, durable record of real BoundaryPrune fires
(fires / prunedResults / charsRemoved / byKind todo|goal|step / lastFire),
written to ~/.dsh/engram/boundary-prune.status.json on every landed prune,
independent of resultPack.recall — the injection leg of the recall funnel: how many [RECALL]
blocks actually made it into sessions (recall-injections.json), closing
the loop already measured from session logs (engram_recall queries → hits →
engram_detail follows) so you can see injection→query→detail activation.Capture is deterministic and offline — it only sees tool results, never the conversation. Exactly what earns a memory record:
| Tool result | Action | Signal |
|---|---|---|
git commit … -m "subject" | record — the written subject is the memory | 0.55 |
git merge / rebase / cherry-pick / tag / checkout -b | record (milestone) | 0.5 |
git push / git stash / commit without -m | skip — plumbing echo, not a decision | — |
| write/edit of a significant config & doc path | record | 0.3 |
| read of a config path | record | 0.3 |
| repeated tool error | record (deduped by message) | 0.25 |
Explicit engram_store writes are always recorded regardless of these rules
(rate-limited per session).
Who earns a [ENGRAM] index line (this is what actually touches the prompt):
signal >= minIndexSignal or hits >= promoteHits or kind === "task",
then capped by indexMaxLines / indexMaxChars. One extra guard keeps the pipe
clean: auto-captured git command echoes — text that embeds a shell chain
(git push: cd … && …) — stay out of the index even when above the signal
threshold, until recall hits have promoted them. Everything else sits quietly in
storage, reachable on demand via engram_recall / engram_detail — "retrieved ≠
injected".
What the model actually sees (rendered once per session, then frozen):
ESR operating protocol (static, byte-identical every turn)
1. Before starting: esr_ready to see claimable work; esr_status for live state.
2. Multi-step work → esr_task (draft first); claim → esr_claim; finish → esr_close (all three gates).
state is the single source of truth: unsure → call esr_status.
[ENGRAM] workspace: symbolic-index · 2 memories · 1 task(s) active · 0 links
[D] 06-18 Decided: use sqlite-vec for retrieval #a2331d87
[T] 06-18 Retrieval upgrade — ACTIVE · gap: artifact, evaluation, memory_ref #tsk_8b26
drill: use [RECALL] below · engram_detail <id> (full record) · engram_recall <query> (more) · esr_task/esr_node/esr_link (work)
[RECALL] recall · 1 hit(s) · first msg: retrieval
- [D] 06-18 Decided: use sqlite-vec for retrieval upgrade #a2331d87 ×2
context: use above · engram_detail <id> · engram_recall <query>
[ESR] tasks: 1 active / 1 stable
- tsk_0d: Retrieval upgrade — ACTIVE · gap: artifact, evaluation, memory_ref
- closed: tsk_9a (RAG eval) · +1
snapshot from session start — WILL NOT auto-refresh; call esr_status for live state
# this-session actionables (frozen)
promote: 2 pending todo(s) vs 1 ESR task(s) — esr_task(name="…") #suggest-promote
Prefixes: [D] decision · [E] error · [P] procedure · [F] fact ·
[I] insight · [H] handoff · [T] task. A procedure re-used enough times
(hits >= promoteHits) becomes a "proven experience" — its prefix upgrades to
[P✓] and it is stably ranked first in the block (newest-first within each
group, keeping the block deterministic): our zero-LLM counterpart to TencentDB
Agent Memory's "Skill = validated, executable experience". Membership follows
the Auto-capture policy (signal threshold / recall promotion / git-echo guard),
bounded by the configured line and character budgets. # ids address the full
records via engram_detail. When a workspace has no tasks, [ESR] still renders
one line naming esr_task/esr_close so the mechanism stays visible to the
model instead of vanishing.
[RECALL] (session-start auto recall, autoRecallOnStart) runs one deterministic
BM25 pass over the workspace keyed on the session's first user message and injects
the top autoRecallLimit hits, bounded by the autoRecallMaxChars character
budget; nothing renders on zero hits or an empty workspace. It is a pointer — the
model should treat it as "this is relevant; engram_detail for the full record" —
rather than full-text dumping. It is a pure read (never bumps hits), superseded
stale truth never occupies a slot, and it freezes per session with [ENGRAM].
Defaults are token-conscious; override any key via the profile patch
(~/.dsh/profiles/web/cordis.patch.yml) or the web config card:
- id: engram
config:
autoCapture: true # zero-LLM tool-result capture
autoCapturePreferences: true # zero-LLM user-correction/preference capture (prefer/avoid/switch-to cues)
preferenceCapturePerSession: 8 # per-session preference memory cap
boundaryPrune: false # ResultPack L1: prune stale over-threshold tool results at todo/goal boundaries (default OFF)
boundaryPruneThresholdChars: 0 # threshold override (0 = host 8192; e.g. 2048 prunes earlier)
boundaryPruneHeadChars: 0 # retained head under an override (0 = host 4096)
boundaryPruneTailChars: 0 # retained tail under an override (0 = host 1024)
boundaryPruneIdleCadence: 0 # request-count boundary for todo/goal-less sessions (0 = event-only)
resultPack: false # ResultPack L2: archive pruned middles + result_recall paged recall (default OFF; needs L1 on)
resultPackRetentionDays: 7 # archive retention days (mtime sweep on the GC cadence)
sessionSearch: true # engram_recall may also FTS past sessions
recallScope: workspace # recall scope: "workspace" (strict isolation, default) | "global" (opt-in cross-workspace recall with [W:<ws>] origin markers)
autoRecallOnStart: true # session-start auto recall (false = back to pure pull)
autoRecallLimit: 3 # [RECALL] max injected hits (few and precise)
autoRecallMaxChars: 700 # [RECALL] char budget
autoCapturePerSession: 40
indexMaxLines: 12 # [ENGRAM] line cap
indexMaxChars: 700 # [ENGRAM] char cap (token budget)
minIndexSignal: 0.4 # auto-captures below this stay out of the index
# (git command echoes are excluded regardless,
# until promoted by recall hits)
promoteHits: 3 # ...until recalled this many times
expireDays: 180 # memory TTL (0 = never)
maxMemoriesPerWorkspace: 2000
gcEnabled: true # scheduled memory GC
gcIntervalHours: 24 # sweep cadence
gcStableRetentionDays: 120 # stable tasks leave [ESR] after this
gcReplacesCompaction: true # Context GC: take over DSH auto-compact (false = keep the default LLM summarizer)
gcNarrative: true # scoped LLM narrative for un-provenanced turns; false = fully mechanical (zero LLM)
engramIndexOrder: 40 # systemPrompt section order (before tools band)
esrOrder: 41
npm test # 291 tests: core + web API + GC + usage-observability + Context GC + ESR triggers + BoundaryPrune + preference learning (node:test)
npm run eval # offline recall + structure benchmark (deterministic)
npm run build:client
npm run eval (eval/recall-bench.mjs) is the deterministic layer — LongMemEval-style: a controlled corpus (ASCII + CJK, known tags/entities/ timestamps) measured through the real store/recall path (openEngramDomain + domain.recall), reporting Precision@k / Recall@k / MRR / Hit@1 per probe (exact tag, substring, multi-term, CJK, phrase-single, ordering, negative) plus StructMemEval-flavoured structure metrics (exact-duplicate dedup rate, entity anchoring coverage, dangling-link hygiene). Honest, reproducible numbers — everyone gets the same output. Current run: AVG P@k 0.770 / AVG R@k 1.000 / MRR 0.889 / hit@1 0.889 / 0 negative false-positives / dedup 1.0.
/api/dsh-engram/stats + the observability panel is the real-session layer —
it answers how the model actually uses the memory in production (ESR
proactivity ratio, recall hit rate, detail conversion), while the eval
answers how good the retrieval layer itself is. The stats read the
concatenated multi-frame session.jsonl.zstd logs with Node's built-in
node:zlib (frame scan + one reused decoder handle) — no system zstd CLI
is required, so the telemetry works out-of-the-box on Windows and other
hosts without an external codec.
Repo layout: lib/ (host half: store / capture / index-block / tools / api /
settings), client/ (browser half, TSX + build.mjs), test/ (node:test).
The web GUI opens and immediately shows “Failed to load plugins”, with a loader error like:
failed to apply loader entry … (@linxin666/dsh-client-ui-web-ui-settings):
keyed slot "settings.plugin.item" requires options.key
Cause: DSH hosts since 0.1.0-rc.7 declare the config-card slot
settings.plugin.item as keyed by the settings namespace a card edits —
which is exactly how dsh-engram's own config card registers (under
key: "dsh-engram"). The @linxin666/dsh-web-ui-all family before 0.2.0
(dsh-client-ui-web-ui-settings) registered its group card into that slot
without a key, and because a single failed loader entry aborts the whole
boot, the GUI stays stuck on the failure page.
Fixes:
@linxin666/dsh-web-ui-all@^0.2.x.
The 0.2 line moved its settings surface out of the keyed slot into a
first-level settings.section (the upstream fix for exactly this error).key: "web-ui-plugins" to that one
settings.plugin.item registration in the installed
node_modules/@linxin666/dsh-client-ui-web-ui-settings/lib/client.js, then
restart dsh web. (Under namespace-keyed dispatch the group card simply
stays hidden; nothing else on the page is affected.)MIT
FAQs
Minimalist long-term memory for DeepSeek Harness, distilling symbolic-index + pi-esr ideas: zero-LLM auto-capture, a symbolic [ENGRAM] index with progressive disclosure, and an ESR-lite evidence-closure protocol (esr_task / esr_node / esr_close / esr_link
The npm package dsh-engram receives a total of 0 weekly downloads. As such, dsh-engram popularity was classified as not popular.
We found that dsh-engram demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Security News
Cloudflare is acquiring Deno’s team, which will provide another year of bug fixes and security updates before ending support for the runtime.

Security News
Anthropic’s free OSS Scanner uses Mythos and other Claude models to find open source vulnerabilities and send maintainers reports with proposed fixes.

Security News
On Front Lines’ Early Adopters: CISO podcast, Andrew Becherer shares lessons from Datadog, weighs the long-term costs of AI-built tools, and explains how founders can actually gain a CISO’s trust.