Cross-session long-term memory under the "memory palace" metaphor: dual-scope SQLite stores (user + per-git-origin project), hybrid FTS5 + local-vector retrieval with RRF fusion and recency/proof ranking boost, automatic capture from the session log (including the final turn), provenance audit chains, consolidation distillation and decay forgetting, and a settings-page Memory Library panel with corridor topology, tour butler, and refurb list.
Install
# from npm (prebuilt)
dsh plugin --profile web add @kenz1117/dsh-engram
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:kenz1117/dsh-engram
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
dsh-engram · Memory Palace
中文 | English
Quick Start
dsh plugin --profile web add @kenz1117/dsh-engram
Zero configuration after install (stores and model cache default to ~/.dsh/engram; profile injection on, automatic capture off).
Screenshots
Today — the palace dashboard: KPI hero, activity heatmap, tour proposal, room directory.
Full-width refurb list — every suggestion shows its full reasoning and the complete memory text, read and act in place (demote / distill-merge), no popup detours.
Library — the full memory store with filters, sorting and inline placard expansion.
Palace Structure: the Directory Is the Room, the Path Is the Route
What actually works in a memory palace is its information architecture, not the biology — machines can use the former, while an AI has neither the latter nor any need for it. Stripped down, there are only four things:
Grand hall · core memory few and stable, always present the profile injected every turn (entry cap + token budget)
│
Corridor · route index pick the room first, never scan the whole store room directory + engram_search room=
│
├─ Fact Hall fact what the user said
├─ Preference Pavilion preference tastes and preferences
├─ Decision Chamber decision decisions and agreements
├─ Episode Gallery episode experiences and timeline
└─ Skill Workshop skill methods and techniques capacity 9 per room, overflow opens "<name>-2"
│
Placard · marker rule unique · distinctive · dated scored on write; low scores enter the refurb list
| Palace principle | What it is in the plugin | Code |
|---|---|---|
| Location as index | Slotted on write to room#slot; capacity 9 (7±2), overflow opens a new room, slots are never recycled |
src/palace/slots.ts |
| Fixed route as order | tour_routes is append-only; engram_tour mode=fixed walks the whole palace in slot order |
src/store/sqlite.ts |
| Skeleton reused long-term | A topic always lands in the same room and index, so recall is sequential extraction rather than fresh search | src/palace/slots.ts |
| Every marker unique | Placard scored 0-1: globally unique +0.4 / date anchor +0.3 / no 6-char prefix clash within the room +0.3 | src/imagery/score.ts |
| Review discipline | SM-2 spaced repetition; retrieval practice returns cues and placards but never the body text | src/review/sm2.ts |
Sensory and emotional dimensions (smell, temperature, emotional weight) are deliberately not scored: they are patches for the human brain's innate limits, and an AI has neither the limits nor the need.
Features
- Cross-session memory: a user memory profile is injected at session start (entry cap plus token budget, both configurable; a curated profile maintained via
engram_profile_edittakes precedence over derived facts), so the agent naturally knows who you are and what you are building; tools recall facts across sessions. - Dual-scope stores:
user.dbshared globally;project-<hash>.dbisolated per git origin identity (falls back to a working-directory encoding without git; legacy stores migrate automatically) — personal preferences follow the person, project conventions follow the repo. - Hybrid retrieval: FTS5 (unicode61 + Chinese 2-gram pre-tokenization) fused with local vectors (
Xenova/bge-small-zh-v1.5, 512-dim, q8) via RRF, plus one-hop expansion over relation edges and a multiplicative recency/proof ranking boost; the embedding model runs offline, and a failed download degrades to keyword-only retrieval with an explicit marker. - Memory-palace information architecture (v0.7.2+): all four principles live on the core path, not in display-layer paint — location as index (writes sort by kind into rooms and pin a
room#slotcoordinate; room capacity 9, overflow opens a new room, slots are never recycled); fixed route as order (tour_routesis append-only;engram_tour mode=fixedwalks the whole palace in slot order); skeleton reused long-term (a topic always lands in the same room and the same index, so recall is sequential extraction rather than fresh search); every marker unique (placard rule scored 0-1: globally unique +0.4 / date anchor +0.3 / no 6-char prefix clash within the room +0.3; low scores enter the refurb list; a palace with fewer than 8 active memories is left unscanned to avoid small-store noise). - Corridor-routed retrieval: the profile carries a room directory, and
engram_searchaccepts aroomparameter — decide the room first, then search inside it, instead of always running whole-store RRF. The top 5 hits carry neighbouring slot ids from the same room as encoding-specificity cues. - Retrieval-practice loop (spaced repetition):
engram_review_queuereturns palace coordinates and placard cues but never the body text, forcing the model to recall first;engram_reviewreveals the entry andengram_report grade(0-5) self-rates it, advancing an SM-2 schedule (1 → 6 → round(prev × ease) days, reset on failure, ease floor 1.3). Entries under review scheduling no longer take part in automatic decay — their fate is decided by recall. Session-start injection reports how many memories are due. - History backfill (v0.7.3+): distils dsh's persisted past sessions into the palace, turn by turn — each session is written into the project store matching its own cwd (no cross-project bleed), reusing the live-capture throttling, redaction, and echo suppression, with a
(session, turn)idempotency key so an interrupted run resumes. Backfilled entries never enter the due-today queue. You choose the import rules (time window / turns per session / total turn budget / auxiliary model / include subagent·seeded·no-cwd sessions) and see a zero-cost estimate (no LLM calls) before running; staging happens in a dedicated "Backfill" tab with progress (including a breakdown of skip reasons) and pause. Auxiliary calls reuse the model you are currently using by default (a past session's log records the provider/model of that time, which may no longer be available here), or you can pick one explicitly from the host's registered providers/models in the tab's "Auxiliary model" dropdown. - Knowledge flywheel: capture/save → contradiction candidates (high-similarity neighbors create
contradictsedges on write, for the model/user to adjudicate) → hit reinforcement (confidence +0.05) → distillation (topic clusters merge into higher-level rules, supersedes chains, confidence inheritance) → decay (low-importance, long-unaccessed entries archive; restorable). - Automatic belief consolidation (v0.7.14+, off by default; yml
reflect: suggest|auto): after a session ends, raw memories that have not yet served as evidence for any belief are consolidated by an auxiliary LLM into one-sentence observations. Unlike distillation, the raw memories are all kept — the observation is an additional abstraction layer: each belief carries its evidence-id list plus a proof count, and same-topic beliefs merge automatically by vector proximity (cosine ≥0.9), so one belief is continuously refined instead of duplicated. When new evidence lands near an existing belief (≥0.86) the belief turnsstale, and the next consolidation feeds its raw evidence back to the model for a freshness review with four outcomes —form(new),refine,still(holds),refute(kept with its evidence chain for audit). Inautomode consolidation runs in the background after the session (5s timeout; failures never affect the conversation); insuggestmode it only enqueues a pending-consolidation task that theengram_reflecttool drains (the tool can also be invoked manually in any mode). The top 3 active beliefs by proof count are injected with the profile packet; stale/refuted beliefs are never injected. Schema upgraded to v12 (the observations table; incremental migration, zero data movement). - Jev System-One adjudication (v0.7.12+, off by default, the
jevblock in yml): the DEFER fuzzy-band three-way ruling (merge / accept / defer) and contradiction-edge confirmation can be delegated to an external Jev model; when Jev is unavailable (network failure / timeout / missing key) it silently falls back to the pure-rule four-way behavior, never blocking writes. Every adjudication leaves a side observation (verdict, whether Jev took part, elapsed time, fallback reason; up to 50 in-process, cleared on restart), so the panel's "Jev" view answers "why was this one deferred" at a glance. Five yml fields — the switch / API key / endpoint / model / timeout — can be overridden from the panel (stored at<dbDir>/jev-config.json, 0600 file inside a 0700 directory) and take effect on save without a restart; a "Test connection" button verifies endpoint and model reachability with the current input (unsaved values are tested as typed, never written to the override file); the key is returned only masked by GET, never leaves the process, and is never back-filled into the input. The three thresholds stay yml-only advanced settings, shown read-only. - Entity lexicon (v0.7.10+): the auxiliary LLM output of automatic capture and
engram_savealso extracts entity mentions (people/projects/tools/concepts, with optional aliases), resolved by normalized name against existing entities' names/aliases — a hit reuses the entity and refreshes its "last mentioned" stamp, a miss creates one; memories and entities link many-to-many, stored in the same store as the source memory.engram_searchrows carry entity tags of the hits; entity-resolution failures silently skip the links and never block the memory write itself. The panel's "Entities" view filters by kind, searches names/aliases, and opens one entity to see every memory it pulls in. - Automatic capture (when
ingestis enabled): each new turn's first step extracts candidate facts from the previous turn out of the session log, and session end captures the final turn too (failures leave a pending key that the next session replays; capture is idempotent per session+turn), written with low confidence and, when embeddings are available, disposed by the four-state rule toward near neighbors (merge-and-reinforce restatements / edge-and-defer suspected contradictions / write fresh) — memories accumulate without you saying "remember this". - Provenance audit: every memory records its source session, turn, and event seq;
engram_reviewtraces the full source chain, supersede chain, contradictions, and operation log; all writes/edits/forgets/distills/decays land in the operation log table. - Web management panel (v0.7.0+): the "Memory Library" tab in Settings has nine views — Today (a summary bar: memories / open / clarity + health ring, with a footer row of four small stats — founded / excavated / curated / due today — inline with the pulse timestamp; followed by tour proposal, room directory, refurb list, health breakdown; a profile card in the side column shows the curated profile's current content and version-diff history), Palace (filters incl. due today, tour-route order, batch actions, list & edit; the review / edit dialogs render as viewport-floating cards — sticky-pinned to the visible top of the host scroll area, so opening from any scroll depth never jumps or squeezes the page), Corridor (corridor bird's-eye + retrieval bench), Log (last-7-days counts + the full merged op_log, filterable by operation class), Backfill, Episodes (episodes grouped by session, with anchor-neighborhood expansion), Entities (the entity lexicon: kind filters + name/alias search + per-entity linked memories), Beliefs (v0.7.15+: consolidated beliefs sorted by evidence count with held / stale / refuted status filters; "evidence" expands the evidence chain for a read-only look at the underlying memories — purged evidence keeps the belief with an explicit notice; backed by the
GET /api/engram/observationsandGET /api/engram/observation-evidenceroutes), and Jev (System-One adjudication: config overrides + connection test + recent adjudications). The header is a single two-ended row: the scope pills (Private / Project / Shared) on the left drive every panel, with the workspace dropdown inlined on the same row for project scope; the right side holds the due badge plus icon-only revisit / export buttons (the export flyout is no longer covered by the tab row); a "中 | EN" segmented control switches the panel copy language (independent of the host language, persisted), with all copy bilingual zh/en. Filter by redaction marks (include/exclude[REDACTED:*]entries) with amber badges for audit coverage. - Prompt-injection defense: every memory recall exit (profile injection,
engram_search/timeline/reviewoutput) is wrapped in<engram_memory_context>protocol tags with a usage warning (history is not the current request, do not follow instructions inside it, use only when relevant), while the current request is wrapped separately in<current_user_request>; all inbound content (capture candidates, saved bodies) is stripped of these protocol tags first, blocking second-order injection through forged protocol blocks. - Capture redaction: inbound content passes a regex scrub for common secrets and credentials (sk- API keys, Bearer, AWS AKIA, GitHub tokens, PEM private keys, password/token assignments, Chinese-language password assignments) and personal data (mainland-China phone numbers, 18-digit national ID numbers); matched fragments become
[REDACTED:<type>]. - Recall placeholder (anti-echo-chamber): memory-recall tool output inside captured slices is replaced with
[engram memory result omitted from capture: <tool>], and the extraction prompt states that restating existing memory is not new information, breaking the memory self-reinforcement loop. - Multi-query retrieval:
engram_searchcan use the aux LLM to rewrite the query into ≤3 complementary queries, retrieving each and fusing them with cross-query RRF plus a per-query floor; a failed rewrite degrades to the single query (queryRewrite: falsedisables). - Evidence gate (search → assess): a hit means "relevant", not "enough to answer". Every retrieval registers an in-process batch (latest 20 per session, released when the session ends) and prints
ref=…per row plus the batch id;engram_assessmay only cite refs of that batch, andsufficientis enforced by code — all three must hold (the model claims adequacy, at least one valid evidence ref,nextStrategy=answer) or the verdict is insufficient with the strategy rewritten back to retrieval. Verdicts and rejected refs go to the audit log, visible under the "retrieval" category in the Log view. If unassessed batches remain before the next step, a wrap-up reminder is injected (escalating to a change-strategy hint after ≥2 consecutive insufficient verdicts;assessReminder: falsedisables). - Portable data:
engram_exportexports Markdown / JSON files in one step, with a redacted-view variant (secondary scrubbing + 40-char preview truncation, share-safe).engram_mirrorwrites an Obsidian / Logseq-friendly mirror directory (one Markdown per memory with YAML frontmatter +[[id]]backlinks), turning the palace into a human-readable private knowledge base. - AGI Architecture Exploration (dsh-market · AGI Architecture): listed in dsh-market's "AGI Architecture Exploration" category as a cognitive-science reframe of agent long-term memory — memory palace (imagery labels + room placards), corridor topology (force-directed graph), closure questions (the ingest prompt nudges the model to ask clarifying questions), consolidation merging (heuristic dedup + cosine similarity) — sitting alongside MemGPT / Letta in the "agent memory architecture" conversation.
Tools (21, narrow parameters)
| Tool | Purpose |
|---|---|
engram_save |
Save (four-state write disposition: a restatement merges into and reinforces the existing entry (merge, no new node), a suspected contradiction is written with a contradicts edge pending adjudication (defer), fresh content is written (accept), low-entropy content is dropped (drop); without embeddings everything is accept); items array saves ≤10 in one call with shared scrubbing and in-batch dedup, reporting each entry's disposition (disposition/mergedInto), one failure not blocking the rest (count/items/failed summary); placard attaches a marker (scored unique · distinctive · dated; low scores get a rewrite hint); entities carries entity mentions (resolved and linked into the entity lexicon with the memory) |
engram_search |
Hybrid semantic + keyword retrieval (hits reinforce confidence); room routes through the corridor — search inside one room only; the top 5 hits carry same-room neighbouring-slot cues, and hit rows carry linked-entity tags; asOf does point-in-time lookback — only entity facts valid at that moment count, and hit rows carry point-in-time facts of linked entities at the row tail. Output rows carry id= and ref=; the tail carries the batch id |
engram_facts |
Entity fact-chain read/write. Write mode takes a facts array (≤50 entries, each entity/entityId + content); replaces supersedes an old fact (soft-invalidated, history chain kept); bad entries land in failed without interrupting the rest. Query mode takes entity (by name; no entity is created on miss, ambiguity lists candidates, near misses suggest alternatives) or entityId (exact); includeInvalid returns the full chain including invalidated facts, asOf filters to facts valid at that point in time |
engram_assess |
Evidence gate: before answering, judge whether the retrieved content suffices. Submit batchId + ≤8 evidenceRefs (only ref= values from that batch's output) + missing + nextStrategy; the code requires all three for sufficient (claimed adequate, at least one valid in-batch evidence ref, nextStrategy=answer), otherwise it rules insufficient and rewrites the strategy back to retrieval; refs from other batches are rejected and listed, and the verdict is written to the audit log |
engram_timeline |
Timeline browsing: creation-time descending by default; order: 'tour' follows the fixed tour route's slot order instead (output carries palace coordinates, entries off-route sorted last) so the agent can re-walk the route |
engram_episode_timeline |
Dedicated timeline for episodic memories: browse experiences by date range and source session, grouped by session (newest group first, entries chronological within; sessionId drills into one session; group headers carry the one-sentence session summary generated during capture or at session end). With around set to a memory id, it switches to temporal-neighborhood expansion — listing episodes within ± a window (windowMinutes, default 60) of the anchor's creation time, answering "what else happened around then" |
engram_update |
Correct an entry (supersedes chain); can re-attach a placard marker too |
engram_forget |
Forget (soft-delete, restorable) |
engram_report |
Report an outcome (preferred for skill-kind entries): success +0.05 / failure -0.1, and persistently useless memories decay away naturally. With grade (0-5) it advances the SM-2 review schedule instead, acting as the self-rating entry point of retrieval practice |
engram_review_queue |
Due-today queue: palace coordinates (room#slot), placard, and overdue days but no body text — recall first, reveal, then self-rate |
engram_review |
Audit one entry: source chain, supersede chain, contradictions, operation log |
engram_stats |
Whole-store statistics and signal ratio; carries the room directory (slots occupied per room plus the latest placard) |
engram_examine |
Progressive disclosure: fetch full placards by id in batches (≤16 recommended; retrieve ids first) |
engram_neighbors |
Corridor walk: from one room, follow 1-3 hops of relation edges and return a neighbour summary |
engram_tour |
Tour routing: mode=fixed walks the whole palace in fixed slot order (constant route, sequential extraction); mode=thematic plans 3-7 stops around a theme |
engram_audit_forgotten |
Closed-wing archaeology: list recent closed entries with their epitaphs to review whether past forgetting was sound |
engram_ingest_history |
History backfill: distil past dsh sessions into the palace turn by turn (per-cwd stores; already-captured turns skipped). After each session's turns are captured, an auxiliary LLM generates a one-sentence session summary stored for timeline group headers (live sessions get the same summary at dispose once the final turn is captured; failures stay silent and never block). dryRun defaults to true (estimate only); pass dryRun=false to run. Use the Settings "Backfill" tab for large batches |
engram_export |
Export Markdown / JSON files (data portability); redactedView: true emits a redacted view (secondary scrubbing + 40-char preview truncation, share-safe) |
engram_distill |
Distill: merge same-topic clusters into higher-level rules (LLM) |
engram_reflect |
Belief consolidation: abstract unconsolidated raw memories into proof-backed higher-level beliefs (four actions form/refine/still/refute; automatic vector-near-neighbor merging; raw memories stay active). Also drains the pending-consolidation queue created by reflect=suggest |
engram_profile_edit |
Edit the curated profile block: view shows the current content and version history; edit writes a new version (optimistic lock via expectedVersion, conflicts fail loud); rollback restores a historical version (appended as a new version; history is never rewritten). The curated profile is injected ahead of the derived profile; every edit lands in the operation log, and the profile card on the panel's Today view shows version diffs |
Configuration
Optional configuration (cordis.yml):
- id: dsh-engram
name: '@kenz1117/dsh-engram'
config:
dbDir: '~/.dsh/engram' # store and model-cache root directory
injectProfile: true # inject the user memory profile at session start
profileTopN: 8 # injection entry cap (1-64)
injectTokenBudget: 1024 # injection token budget (128-8192; CJK text counted at 1.5 tokens/char, other text at 4 chars/token; entries that still don't fit degrade to index lines)
injectItemBudgetStart: 160 # graduated budget: body chars for the first entry (40-2000, decayed per entry; truncate first before degrading)
injectItemBudgetDecay: 0.9 # graduated budget: per-entry decay factor (0.5-1)
injectItemBudgetFloor: 24 # graduated budget: per-entry body char floor (8-200, must not exceed start)
assessReminder: true # evidence-gate nudge: inject an engram_assess reminder before the next step when unassessed search batches exist
modelCacheDir: '~/.dsh/engram/models' # embedding model cache directory
hfEndpoint: 'https://huggingface.co' # model download endpoint; set a mirror behind restricted networks
ingest: 'off' # automatic capture: off | light (user messages only, ≤2/turn) | eager (assistant messages too, ≤5/turn)
reflect: 'off' # automatic belief consolidation: off | suggest (only enqueue on session end; drain via engram_reflect) | auto (consolidate in the background after the session); engram_reflect stays manually callable in every mode
# provider and model must be given as a pair: aux-LLM route override for capture/distill (parsed from the session log by default)
# provider: 'deepseek'
# model: 'deepseek-v4-flash'
decayAfterDays: 30 # decay: last access older than this many days (also the recency-boost decay window)
decayImportanceBelow: 0.3 # decay: and importance below this → archive (restorable)
rankRecencyWeight: 0.2 # retrieval recency boost weight (0-2, 0 disables)
rankProofWeight: 0.1 # retrieval hit-count boost weight (0-2, 0 disables)
queryRewrite: true # engram_search rewrites ≤3 queries via the aux LLM and fuses them with RRF (degrades to a single query on failure)
autoSlot: true # write-time auto-sloting (sort by kind into rooms, pin `room#slot`, register on the tour route)
reviewScheduling: true # write-time enrolment in review scheduling (first due in 1 day; off = new entries never enter the SM-2 queue)
# History-backfill defaults (initial values for the tab and the tool; the total-turn cap is a hard ceiling you can only lower per run)
historyBackfillDays: 7 # default time window in days; 0 = unlimited
historyBackfillMaxTurnsPerSession: 20 # default max turns per session
historyBackfillMaxTotalTurns: 200 # hard cap on turns per run (1-5000)
historyBackfillIncludeSubagents: false # exclude subagent sessions by default
historyBackfillIncludeSeeded: false # exclude seeded sessions by default
historyBackfillIncludeNoCwd: false # exclude sessions without cwd by default (they can only go to the user store)
# Jev System-One adjudication (off by default; enabling requires an apiKey; failures silently degrade to the pure-rule four-way behavior)
# DEFER fuzzy-band three-way ruling: probability ≥ deferMergeAbove → same memory (merge), ≤ deferAcceptBelow → different (accept), in between stays defer;
# before a defer lands, contradiction edges are confirmed by Jev first (an edge is created only at probability ≥ contradictMinProbability).
jev:
enabled: false # switch
# apiKey: '' # Jev API key (Bearer auth)
# baseUrl: 'https://api.typesafe.ai' # official endpoint, POST /v1/systemone
# model: 'jev-latest' # adjudication model name
# timeoutMs: 3000 # per-request timeout in ms (1000-60000)
# deferMergeAbove: 0.85 # fuzzy-band probability ≥ this → same memory (merge)
# deferAcceptBelow: 0.15 # fuzzy-band probability ≤ this → different (accept)
# contradictMinProbability: 0.8 # contradiction confirmation probability ≥ this → create the contradicts edge
# The five fields above (enabled/apiKey/baseUrl/model/timeoutMs) can be overridden from the panel's "Jev" tab (stored at <dbDir>/jev-config.json, field-level override, effective immediately on save); the three thresholds are configurable only here
How It Works
The plugin has a host half (Node) and a browser half (React):
Session agent Host half (Node)
│ │
├─ every turn, first step ◀─────────────── ├─ user memory profile injection (plugin-source user snapshot)
├─ engram_save / search / review … ──────▶ ├─ SQLite dual stores (user.db / project-<origin hash>.db)
│ ├─ FTS5 keyword track + local vector track, RRF fusion + ranking boost
├─ engram_distill ───────────────────────▶ ├─ aux-LLM distillation (cluster merge → supersedes chain)
├─ session end (reflect enabled) ────────▶ ├─ belief consolidation (unconsolidated evidence → observations: near-neighbor merge / stale review)
│ └─ automatic capture: session log → candidate facts (incl. the final turn at session end; ingest on)
└─ Settings "Memory Library" tab ◀──────── ─── loopback API /api/engram/* (writes verify loopback Origin)
- Dual-scope stores:
user.dbshared globally;project-<hash>.dbnamed by the normalized git origin URL hash (git@github.com:a/b.gitandhttps://github.com/a/bshare one store; worktrees resolve through the pointer to the main repo's origin); without git or an origin it falls back to a working-directory encoding, and legacy cwd-named stores are renamed in place at startup (if both exist, nothing moves and a warning is logged). - Palace structure (the directory is the room, the path is the route): the grand hall is the standing core profile injected every turn (few and stable, always present); the corridor is the room directory in the profile plus the
engram_search roomparameter (pick the room, then search); rooms sort by kind (Fact Hall / Preference Pavilion / Decision Chamber / Episode Gallery / Skill Workshop) with capacity 9, overflowing into<name>-2; placards are each memory'splacardmarker, scored on the unique · distinctive · dated rule. Existing stores are slotted on first open (idempotent; opening a new room logs a warning so a human can name it). - Automatic capture (when
ingestis on): each new turn's first step extracts candidate facts from the previous turn out of the session log; session end (session/disposed) captures the final turn with a 5-second timeout, and on failure/timeout a pending key lands in the operation log for the next session's first step to replay; captured (session, turn) pairs are idempotent. The read source is the session log; aux-call request auditing goes to the plugin's own operation log — unknown events are never appended to the session log. Candidates are written with low confidence and deduplicated by embedding. - Provenance chain: every memory records its source session, turn, and event seq, fully traceable via
engram_review; the operation log table records every write/edit/forget/distill/decay. - Offline embeddings: the model downloads once (q8, ~50MB; mirror endpoint configurable), then runs fully offline; on failure the plugin keeps working and retrieval degrades to keyword-only with an explicit marker.
- UI localization: the client half registers zh/en dictionaries through the host locale service and follows the host language setting live; data-level enums (status/kind) are mapped only in the display layer and stay English in storage.
Web Management Panel
After installation, a Memory Palace entry appears in the host sidebar (a standalone panel app since v0.8, no longer a settings tab), with 9 views covering everything a memory library needs day to day:
| View | What it shows |
|---|---|
| Today | KPI hero (memories / open / clarity / due today, each with a sparkline) + 6-month activity heatmap + tour proposal + full-width refurb list + room directory + resident profile |
| Library | The full memory store: filter by room / status / text, time & tour-route sorting, inline placard expansion, edit-via-supersession, batch forget / restore |
| Corridor | The memory graph: floorplan (architectural metaphor) / force-directed modes, plus a retrieval workbench with 1–3 hop roaming |
| Spiral | A spiral-layout overview, older towards the centre |
| Episodes | The episode timeline: grouped by session with LLM summaries, anchor-based proximity expansion |
| Entities | The people / projects / tools / concepts dictionary with per-entity memory links |
| Beliefs | Distilled higher-level patterns with six-step evidence strength |
| Log | The merged op_log audit trail, filterable by operation class |
| Ops | History backfill (estimate → run → resume), Jev adjudication setup and the recent-verdicts feed |
The UI language follows the panel switcher (中文 / English / Русский), and server-generated reason strings follow it too. All data flows over the loopback API /api/engram/* (write actions are gated by loopback origin plus the three-layer write guard). When the panel bundle and the backend process are different versions, a banner reminds you to restart the app. Headless profiles without a webServer mount nothing; everything else keeps working. The per-release panel notes from the v0.7 era live in docs/panel-history-v0.7.md.
Development
pnpm install # prepare symlinks @deepseek-ai/* peers from ../deepseek-harness (run pnpm install && pnpm run build in the harness repo first); runs pnpm bundle automatically when lib/ is missing
pnpm test # unit + composition tests; real-embedding e2e runs when ENGRAM_E2E=1 (HF_ENDPOINT configurable) and the network allows
pnpm typecheck
pnpm bundle
Checks before a PR
node scripts/check-messages.mjs # every key the code asks for is declared, nothing is orphaned, zh/en/ru aligned
node scripts/check-patterns.mjs # secret redaction and chit-chat rejection behave on every declared language
pnpm typecheck
pnpm test
The first two run on a bare checkout, and CI (.github/workflows/checks.yml) runs exactly those two, plus pnpm install --frozen-lockfile --ignore-scripts. typecheck, test and bundle cannot run there: the @deepseek-ai/* peers are not on npm (see .npmrc), so they resolve only inside a deepseek-harness workspace. Run all four locally before opening a PR — a dictionary mistake fails loudly on your machine, but a locale-dependent assertion (one that asserts Chinese text while language: 'auto' resolves to the developer's own locale) fails only on someone else's.
Two notes on install and build scripts:
- GitHub source quick-install (
dsh plugin add github:kenz1117/dsh-engram) fetches sources;preparerunspnpm bundleon the spot whenlib/is missing. If that build fails, read the bundle section of the prepare log — not the host's misleading "no entry point". - Native build permission: the native packages pulled in transitively by
@huggingface/transformers(onnxruntime-node,sharp,protobufjs) need pnpm's permission to run their build scripts. pnpm 11 ignores the in-packagepnpm.onlyBuiltDependenciesfield; the permission must live on the consumer side (profile / workspace) — pnpm 11 usesallowBuildsinpnpm-workspace.yaml, pnpm 10 usesonlyBuiltDependenciesin the same file. Without it pnpm skips those builds and reports the skips as errors, leaving the embedder unusable; grant the permission and reinstall.
Model Experience
Request context and condition
What the model sees
Each turn's first step appends a plugin-source user snapshot: User memory profile (dsh-engram, cross-session) — Grand Hall (always present): followed by the user-scope memory list (up to 8 entries within a 1024-token budget by default; over-budget entries degrade to #id index lines; injectProfile: false disables), each line carrying its palace coordinate (room#slot); when reviews are due, a line reporting how many memories are due is appended. Tool results are plain text lines (with id=, scope/kind annotations, slot coordinates, contradiction-candidate hints, and degradation notes). Automatic capture and distillation each make one aux-LLM call (billed independently of the main conversation path, with purpose attribution).
Token effect
Profile injection is a conditional fixed cost (bounded by both the entry cap and the token budget); the tool schemas are a standing cost (20 narrow-parameter tools).
KV Cache effect
Profile text changes as the memory store changes — changes only land at turn boundaries in new sessions or after memory updates; within a session the prefix stays stable while the injected content is unchanged; tool schemas are constant and never affect the prefix.
Known Limitations and Deferred Work
- Contradiction checks are LLM-free by default — writes report candidates by vector similarity (≥0.88) and create edges; semantic-contradiction confirmation is left to model/user adjudication and distillation. Optional Jev adjudication (the panel's "Jev" tab or the yml
jevblock) routes the fuzzy band and contradiction edges to an external judge, falling back to this default whenever Jev is unavailable. - Memories written during embedder degradation have no vectors — memories written before the model is ready do not participate in the semantic track; after semantics come online run
pnpm backfillonce to backfill existing vectors (afterpnpm buildhas warmed the model cache;HF_ENDPOINTconfigurable).
Roadmap
The palace IA (position-as-index / fixed routes / retrieval practice) is the indexing and audit skeleton; it evolves toward AI-native memory tiers and a temporal fact graph in three phases. Actual releases increment by 0.0.1; the phase labels are planning codenames only:
- Phase 1 · Flywheel closure (done, v0.7.8): four-state write disposition (ACCEPT new / MERGE reinforce-in-place / DROP low-entropy / DEFER contradiction-pending —
engram_saveand capture return it per item); graduated decaying injection budget (160 chars for the first entry, ×0.9 per subsequent entry, floor 24, all configurable); redaction extended to Chinese passwords, national ID numbers, and phone numbers; evidence-gate wrap-up reminder (inject a reminder when a turn ends with unassessed search batches). - Phase 2 · Memory tiers: editable profile (done, v0.7.9:
engram_profile_edittool with version-chain rollback and panel diff audit, following Letta's core-memory model); a dedicated timeline index for episode memories (done, v0.7.9:engram_episode_timelinetool with date ranges, per-session grouping, temporal-neighborhood expansion, and a dedicated episode index; the panel "Episodes" view plus capture-time and live-session summary group headers landed in the same batch); automated belief consolidation (done, v0.7.14: the observations table (schema v12) with evidence chains and proof counts; triggered on session end viareflect: suggest|auto; near-neighbor merging continuously refines one belief; stale freshness review with the four actions form/refine/still/refute; active beliefs are injected with the profile; manualengram_distillstays available); configurable room capacity and placard scoring (defaults unchanged). - Phase 3 · Temporal fact graph: entity table with write-time extraction and resolution (done, v0.7.10: the entities + node_entities tables; capture/save auxiliary output carries entity mentions resolved on write;
engram_searchhits carry entity tags; panel "Entities" view + entity loopback routes); fact-level triples carryingvalid_at/invalid_atwindows with soft-invalidated conflicts (done, v0.7.11: the facts table (entity + time window +replaced_by+ source memory), capture auxiliary output carries facts matched into the table by normalized entity name,replacesdeclares the soft-invalidation chain,engram_searchgainsasOfpoint-in-time lookback, theengram_factstool reads and writes fact chains, and the panel entity page shows fact-chain details); automatic supports / refines / related edge creation; a LoCoMo-zh evaluation subset in CI as a retrieval-quality regression gate (deterministic Recall@k / MRR metrics).
Acknowledgements
Thanks to the community contributors who made this project better:
- @dsv180 — the most prolific community contributor. PR #13: bilingual host message catalog (zh/en) plus the language-pattern acceptance script (the skeleton of
check-patterns). PR #22: CI gates that make the two peer-free checks actually run on a hosted runner. PR #23: made every peer optional viapeerDependenciesMeta— fixing the packaging incident that left releases 0.8.0–0.8.14 uninstallable in clean profiles. PR #20 (Jev credentials move up into the host; the plugin stores no keys) and PR #21 (the embedder becomes a deployment property with a hard dimension guard) are under review. Every PR arrives with bilingual write-ups, itemised verification evidence and an honest limits list — a model of collaboration. - @FeiLZX3Km — Issue #19: three rounds of high-quality UI feedback (sidebar icon back to monochrome, full-width refurb list, popup readability), each with their own code investigation; the v0.8.15 / v0.9.1 interface fixes trace directly to it.
- @lujfsd — PR #2: adapting to the new dsh
sessionPersistenceread-handle API (load()was removed upstream), per-candidate scope on capture (the extraction output now carriesscope), the project palace following the active workspace (GET /api/engram/workspacesplus the panel workspace picker), and renaming the no-git store key from "first 12 chars of cwd" to a full cwd sha256 (fixing collisions between sibling directories) — with 17 new tests and two design documents.
- @lujfsd — PR #2: adapting to the new dsh
- @f0909172434 — PR #4: governance for ambiguous legacy database ownership — the eager rename now writes a JSON tombstone sidecar (
<old db>.migrated-to, created exclusively with 0o600, recordingmigratedTo/claimedByCwd/claimedAt), and a later workspace hitting the same legacy name receives a warning naming the prior claim; a newlegacyMigrationconfig (eagermigrates automatically by default /conservativeleaves old databases untouched with a warning), with migration outcomes refined intorenamed/kept-both/deferred/already-migrated. Ships with tests.
License
MIT © 2026 KenZ (kenz1117)
Legacy project database migration
legacyMigration: eager (default) keeps the automatic rename when only the old
filename exists. A private JSON sidecar, <old filename>.migrated-to, records
migratedTo, claimedByCwd, and claimedAt (ISO timestamp). A later workspace
with the same truncated old filename receives a warning naming the prior claim.
The record describes a file migration, not proof of per-memory ownership.
Same-origin worktrees continue to share the same new database.
Set legacyMigration: conservative in the plugin's cordis.yml config before
upgrading/opening old data to leave the old database untouched. Engram warns
with the old and proposed filenames and opens a new, initially empty project
store, so the session can continue. If old and new files coexist, neither is
moved or merged, regardless of policy. This mode cannot undo an earlier migration.
Before manually resolving ownership, stop all hosts using the directory and back
up databases and their SQLite sidecars. Review/export the relevant memories with
engram_review / engram_export. The new store may already contain fresh data:
do not blindly move the old file over it. Audit both stores and reassign data
manually; no automatic classification, split, merge, or deletion is performed.
The pointer includes a local workspace path; treat it as private metadata. Malformed/unreadable pointers stop migration rather than silently losing the claim history. Restoring an old file beside a pointer requires manual review. Rename and pointer creation are separate filesystem operations, not a transaction or cross-process lock. A pointer-write failure is reported with both filenames; the database has already moved and is not automatically rolled back. After a crash between the operations, inspect both paths before retrying. Existing migrations performed before this feature have no retroactive pointer.
Links
More in this category
Clearailhc/clearai-dsh★ 1104
An agent preset for evidence-checked research: every step advances only through system-verified observations, L3+ claims go to an independent evaluator, and the session folds into a replayable ledger that grows into a browsable domain ontology. Requires DSH >=0.1.7-alpha.1.
FuRongJun-1999/dsh-memory★ 413
White-box AGI architecture exploration: metacognition (self-cognition loop), continual learning (knowledge flywheel), world model (condition space, spatiotemporal memory graph), self-improvement (bootstrap discipline), zero-LLM white-box pipeline, and auditable trust guardrails.
pk14742952-AD/dsh-host-rsi★ 33
Reflective self-evolution plugin for local Qwen3.8 27B/3.8 Flash and online APIs: a coding-loop convergence steerer curbs Qwen3.8 27B/Flash repeated-verification deep-thinking to converge faster and cut thinking time; a five-state delivery guard detects and corrects stuck turns and infinite loops; model-agnostic host logic, mostly compatible with online APIs; per-use continual self-improvement with counter-reason self-checks, critic escalation, inverted-index memory retrieval, coding-loop convergence steering, and a five-state delivery guard.
MichengAI/dsh-pua★ 21
Adds /pua to steer the agent to try another approach and verify results before claiming completion, with 15 company styles, persona modes, global and per-conversation settings, and an optional loop that continues until a verification command passes, the iteration limit is reached, or the user cancels.
Kanadego/dsh-heartbeat★ 15
Heartbeat loop for DeepSeek Harness: every 20 minutes (by default) the agent wakes on its own to keep a local profile of your interests and preferences, search the web for things you care about, and keep what it finds. When it has something worth sharing, it sends a short package of notes to the sessions you link it to. A set of rules in code - quiet hours, busy windows, a daily limit, a cooldown - decides whether it may speak at all, and the agent in that session decides whether to actually say anything. Windows only.
dsh-answer-reviewer★ 8
Agentic answer reviewer for DeepSeek Harness: each final assistant turn is re-reviewed by a separate LLM graded 1-100; below-threshold scores steer the agent back with concrete feedback. The score is shown as a chip on the answer's action row (green pass / red needs-work, with retry count and reason on hover). Live gate config from the Reviewer 配置 tab in the conversation view ring above the composer (对话 / 轨迹 / 记忆系统 / Reviewer 配置), from a 127.0.0.1 HTTP server, or from an optional right-sidebar tab via dsh-better-sidebar.
Community comments
Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.