dshbase

插件目录 / Knowledge / dsh-engram

dsh-engram

已验证 · 实测可装 skepsun

✓ 持续维护 基于 7 个官方 DSH 包

查看 GitHub ↗ ← 返回插件目录

1Stars
1Forks
0未关闭 issue
—语言
2026-08-24最近推送
跨平台平台

功能简介

极简·省 token 的 DSH 长期记忆插件:零 LLM 自动捕获、符号索引渐进披露、ESR 证据闭环。Minimalist token-saving memory for DeepSeek Harness: zero-LLM auto-capture, symbolic index, ESR-lite evidence closure.

✅
我们的评价
可用 — 实测通过,早期项目

极简·省 token 的 DSH 长期记忆插件:零 LLM 自动捕获、符号索引渐进披露、ESR 证据闭环。Minimalist token-saving memory for DeepSeek Harness: zero-LLM auto-capture, symbolic index, ESR-lite evidence closure. 实测能干净安装、正常启动。早期项目,但功能可用。

「已验证」表示我们的自动化 CI 在干净 profile 里实际执行了 dsh plugin add 并启动成功——仅此而已。功能描述与版本兼容性均为作者声明。这不是安全审计,也不代表对第三方代码的背书。

README

dsh-engram

English · 中文

CI

Minimalist long-term memory for DeepSeek Harness, distilled from the
symbolic-index and pi-esr ideas —
with one goal: save tokens.

  • Zero-LLM intake — auto-captures meaningful events from tool results by pure
    pattern matching (git milestones with a written -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).
  • Symbolic index + progressive disclosure — a compact [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.
  • Memory-to-memory semantics — 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.
  • Failure→fix closure (zero LLM) — error memories carry a 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-lite closure protocol — 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 trigger mechanism (pi-esr-aligned, hybrid: static protocol + frozen
    snapshot + monotonically accumulated actionables + pull)
    — prefix-cache
    stability is the point. Teaching the model WHEN to use ESR is a static
    methodology
    section (byte-identical every turn); the [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.
  • Session-end todo auto-sink (draft safety net, 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.
  • Memory GC (pi-esr constraints) — a scheduled, mechanical, archive-only
    sweep: TTL-expired memories are archived, over-cap workspaces evict the
    lowest-value entries, stable tasks past their retention window leave the
    [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.
  • Context GC (auto-GC = replaces DSH's auto-compact) — NOT the memory-panel
    GC. It takes over the host's 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.
  • Web viewer — a memory browser with benchmark-ish stats and a config card,
    built entirely on DSH's native settings slots (no third-party UI package).
MIT   ·   node >= 22.19   ·   host-half + browser-half in one package

Why another memory plugin?

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:

  1. No model in the write path — capture is deterministic pattern matching.
  2. No raw text in the prompt — a bounded symbolic index is injected, retrieval
    stays on demand ("retrieved ≠ injected").
  3. Honest task closure — STABLE cannot be declared without evidence.

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.

Install

# 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 zod and vendors the optional @deepseek-ai/* peers into the
plugin's own node_modules, and the CLI auto-registers the plugin into the
profile's dsh.profile.bundles. The setup-links step below is only for
the link: 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.

Dependencies for link: installs

A 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.

What you get in the GUI

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

Tools

Tool Purpose Kind
engram_store Explicitly store one memory (kind, tags, optional entity anchor, optional supersedes/contradicts memory ids) write
engram_recall Deterministic keyword recall over workspace memories; optional search_sessions FTS over past sessions 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.

GC: two planes — memory-panel reclamation + Context GC (replacing auto-compact)

Memory-panel reclamation (store maintenance)

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
:

  • TTL-expired memories are archived (soft; the id stays, retrievable via the
    GUI's archived filter);
  • over-cap workspaces evict the lowest-value non-protected memories;
  • stable tasks past gcStableRetentionDays become archived and leave [ESR];
  • links whose both endpoints are gone are dropped (dangling edges).

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.

Context GC (replaces DSH's auto-compact)

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:

  1. Scan — the evicted messages are scanned for provenance anchors: memory
    ids echoed by engram_store/engram_recall/engram_detail, tsk_*/ent_*
    touched by esr_*, and file_path anchors (esr_gc and other management
    calls are NOT anchors);
  2. Pointer summary — every evicted category carries an explicit re-fetch
    call (engram_detail(id: "…") / engram_recall(query) / the [ESR] block /
    esr_ready); the active working set is restated, never evicted;
  3. Narrative safety net — only un-provenanced turns (pure chat / reasoning)
    get a scoped LLM summary (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 host
tokenMeter (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.

Where it is enabled (the entry point)

Assembly happens on two planes — and web is now fully automatic, zero config:

  • Host plane (headless / TUI / base profiles) — on by default, zero config:
    the main plugin (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).
  • Preset plane (web profile) — fully automatic, zero config, covers every
    preset
    : web keeps compaction inside each agent preset's isolated realm
    (the shipped presets mount their own 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:
    • on by default (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;
    • per-preset: only stock-layout presets are touched — a preset with a
      custom compaction group, or none at all (e.g. the shipped minimal
      preset), is never written, only logged;
    • backed up + validated: a create-only 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.
    • config actually propagates: the engine config written into each row
      comes from the live settings (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.
    • A fully equivalent manual CLI, shipped in the npm package
      (scans both the shipped and the user root; --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.

First impressions after an npm install (perceive / guide / configure)
  • Automatic: install into a profile and boot once — the host plane is taken
    over by mountCompactionEngine, and the web plane by autoWebCompaction
    (default on) rewriting every stock preset at boot. Zero manual setup.
  • Perceivable: at boot the plugin writes an authoritative snapshot to
    $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 ….
  • Configurable: one settings card (memory-GC section) governs everything:
    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) —

  • host plane (headless/TUI/base): the startup log shows
    engram context-gc: compaction = Context GC (mechanical eviction + re-fetch pointers);
  • web plane: the log shows one line per wired preset —
    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.

Auto-capture policy

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".

Injected blocks

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: engram_store (user asks to remember) | engram_recall <query> | engram_detail <id> | esr_task / esr_close / esr_link

[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.

Config

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
    sessionSearch: true      # engram_recall may also FTS past sessions
    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

Development

npm test            # 152 tests: core + web API + GC + usage-observability + Context GC + ESR triggers (node:test)
npm run eval        # offline recall + structure benchmark (deterministic)
npm run build:client

Testing & evaluation (two layers of real testing)

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.

Repo layout: lib/ (host half: store / capture / index-block / tools / api /
settings), client/ (browser half, TSX + build.mjs), test/ (node:test).

Troubleshooting

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:

  • Proper fix — upgrade the family: @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).
  • Immediate unblock: add 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.)

Related

  • symbolic-index — the original cross-session
    memory plugin (5-signal RRF fusion, sqlite-vec, Dream Engine).
  • pi-esr — project-lifetime evidence-driven
    task states; the closure protocol here is its lite form.

License

MIT

安装

🧩 让 Agent 自动装(推荐)

装一次目录插件,之后本站所有插件都能让 DeepSeek Harness 自动找、自动装:

dsh plugin add dshbase-catalog

然后对 agent 说「帮我装 dsh-engram」,它会在目录里找到并自动安装。文档:dshbase-catalog · 已验证场景包。

该插件是 GitHub 源码(未发 npm)——直接从仓库装:

Web profile:

dsh plugin --profile web add github:skepsun/dsh-engram

Headless(CLI)profile:

dsh plugin --profile headless add github:skepsun/dsh-engram

实测报告

验证通过:从 GitHub 源码完成 L1 安装 + L2 加载 + L3 运行(dsh 0.1.0-rc.6)。

安全:尚未扫描——我们的每日静态扫描将很快覆盖它。

分享徽章

Knowledge 里更多

浏览全部 7819 个插件 →