Passer au contenu principal
F

dsh-topics-memory

fan56/dsh-topics-memory

dsh-plugin : mémoire thématique privilégiant la qualité. Un sujet conserve la question initiale, la conclusion, l’impact et les dépendances, pas le processus. Bundle OKF local, suivi par git, injecté sans LLM dans les limites du budget.

Installer

dsh plugin --profile web add github:fan56/dsh-topics-memory

README

dsh-topics-memory

English | 中文

A dsh plugin: maintains "working topic memory" as an OKF (Open Knowledge Format v0.2) knowledge bundle, persisted in a local git repository (optionally synced to a private GitHub repo), with conclusions traceable through git history, sessions automatically observed and distilled into knowledge, and relevant topics injected to the model before every turn.

Requires dsh >= 0.1.2-rc.1 — this plugin targets the dsh RC/stable line only (CI and releases resolve the newest of the latest/next dist-tags at runtime). The alpha line is no longer supported.

Demo

The full flow in about four minutes: topics captured, distilled, and injected in a real session.

https://github.com/user-attachments/assets/8c06cc98-b1ed-402b-9110-4f9a93eb15bc

The problem it solves

Long sessions forget. Cross-session, even more so. This plugin maintains structured topic memory: each Topic records a matter's name, dependencies, open questions, current conclusion, impact, and recommendations. When a conclusion changes, edit the file and commit — git log directly answers "when, by whom, and why did this conclusion change".

Why this plugin exists: memory is edited, not accumulated

The short version: more memory is not better memory.

Most memory tools assume accumulation — record everything, retrieve broadly. That may work for humans; for LLMs it backfires twice over. Model attention is a finite resource, so a giant memory bank means every turn is spent digging for signal in noise. Worse, process memories hoard intermediate judgments that were right once and wrong later — and they will confidently steer the model into bad decisions.

So this plugin takes a hard editorial line on what deserves to be remembered: a topic records exactly four things — the question that started it, the conclusion it reached, what it impacts, and what it depends on. Everything in between — the discussion, the dead ends, the wrong turns — is deliberately not memory. Process belongs to the session; when the session ends, it goes. Only conclusions that survive distillation make it into the bundle.

Short-term memory is the session's own job — the conversation context already is one, and a plugin that feeds it back is noise. Long-term memory belongs to topics: small, structured, git-traceable, injected in budgeted slices with zero hits meaning zero injection. Every turn hands the model the minimum high-value context, not the biggest warehouse.

This plugin is not trying to be the model's notebook. It is trying to be the model's editor: deciding what is worth keeping — and, more importantly, what should be forgotten.

Core features

  • Strict OKF v0.2 compliance: each Topic is a markdown + YAML frontmatter concept document (type: Topic) that the whole OKF ecosystem (Obsidian, OKF validators) can consume directly; ships with the provenance (sources), trust (generated/verified), and lifecycle (status/stale_after) field families.
  • Git-traceable: one conclusion change = one commit (write-through); the topic_history tool and /topics history make change history first-class.
  • Local-first: local-only mode by default (~/.dsh/topics/), zero config, zero credentials; setting repo enables GitHub sync (single repo, single bundle, single main, write-through + debounced push; rebase conflicts are demoted and flagged for a human — no automatic smart-merge).
  • LLM-free hot-path injection: per-turn lexical matching (CJK bigrams + words + weighted tags + depends graph walk), millisecond-scale; zero matches = zero injection; per-topic digest ≤300 tokens, top-K ≤4, total budget ≤1.5k tokens — all configurable.
  • Observable, tunable injection: every turn writes an Injection Log (hits, scores, near-misses, budget usage); /topics stats reports hit rate, top-N, near-miss distribution, and tuning suggestions — tune from evidence, not vibes.
  • Knowledge as a graph: depends (machine-readable directed edges) plus body [[wikilinks]] and markdown links (human-written edges) form one graph; retrieval walks it in both directions (per-level decay, configurable depth) so a single hit pulls in a knowledge subgraph; every write rebuilds the meta/backlinks.json reverse index, and /topics show lists "who references me, and how" — check the blast radius before changing a conclusion.
  • Two-stage observer (M2): the main model jots atomic observations with topic_observe; a background distill lane (session end + every N turns, model configurable) distills them into formal Topics in batches; when the model itself deems something worth keeping, it topic_saves directly.

Quick start

  1. Install (command below), restart dsh;
  2. Run /topics onboard — native dsh ask-user panels walk you through the five decisions: mode / repo / distill model / injection tier / auto-observe — nothing is written until the final confirm;
  3. Work as usual: relevant conclusions are injected every turn; say "remember…" to have the model topic_save; /topics status for health, /topics stats for injection stats.

Tools & commands

Model toolsPurpose
topic_saveSave/revise a Topic (name / dependencies / open questions / conclusion / impact / recommendations)
topic_observeJot an atomic observation (decision/finding/constraint/question), pending distill
topic_searchLLM-free keyword search over memory
topic_historyA topic's conclusion change history (git log as a tool)
CommandPurpose
/topics onboardInteractive setup wizard on dsh-native ask-user panels (mode / repo / distill model / injection tier / auto-observe); typed fallback where no ask-user UI exists
/topics statusBundle health: topic count, observation backlog, conflicts, last distill outcome, sync status
/topics distillManually trigger one distill run over the current observation pool (same lane, same in-flight guard; summary mirrors the distill-state fields)
/topics statsInjection stats: hit rate, top-N, near-miss distribution, tuning advice
/topics list / show / historyBrowse topics, backlinks, and change history
/topics graphGenerate a relationship-graph web page (force-directed, draggable/zoomable, hover for conclusions) and open it in the browser
/topics sync [pull|push]GitHub mode: manual pull/push (automatic by default)
/topics config / set <key> <value>View and edit config (thresholds, budgets, distill model, …)

Install

dsh plugin --profile <your profile> add @aiwayds/dsh-topics-memory

First thing after installing: run /topics onboard. The bundle lives at ~/.dsh/topics/ by default ($DSH_TOPICS_HOME overrides). GitHub sync: /topics set repo <owner/name> (suggested repo name dsh-topics-data, to keep it distinct from the plugin's own source repo); credentials come from $GITHUB_TOKEN or a logged-in gh CLI (login is not this plugin's job).

Upgrading from 0.5.x (rename)

0.6.0 renames the plugin: @aiwayds/dsh-llmwiki-memory → @aiwayds/dsh-topics-memory, the /wiki command family → /topics, and the settings namespace llmwiki → topics. Install the new package (and remove the old one from your profile) — on first start the plugin migrates everything automatically: the data directory ~/.dsh/llmwiki is renamed to ~/.dsh/topics, and user-tuned values in the old llmwiki settings namespace are copied into topics. No manual steps; if a migration step fails the plugin falls back to the old locations and keeps working.

Uninstall

Remove the plugin from a profile:

dsh plugin --profile <name> remove @aiwayds/dsh-topics-memory

The host reconciles the profile automatically: the dsh.profile.bundles entry is spliced and the patch layer is dropped.

What stays on disk (kept on purpose — this is your memory):

  • ~/.dsh/topics/ — the whole topic bundle: topic markdown, meta/, and the embedded .git repo (the full history; it may carry an origin remote — GitHub sync stops with the plugin). To archive the bundle elsewhere, copy or clone this directory as-is.
  • ~/.dsh/settings.yaml topics: section — user overrides. Reinstalling silently reactivates sync including any configured repo; delete the section first if you want a clean start.
  • The legacy llmwiki: settings section left by the 0.5.x → 0.6.x migration is never auto-deleted; remove it by hand once nothing needs it.

Purge everything: back up ~/.dsh/topics first, then rm -rf ~/.dsh/topics.

Configuration

First-time setup belongs to /topics onboard; day-to-day tuning is /topics set <key> <value> (writes the topics namespace in settings.yaml, effective from the next session). All keys and defaults:

KeyDefaultMeaning
repoempty (local-only)GitHub sync repo owner/name; suggested dsh-topics-data; empty = back to local-only
autoInjecttruePer-turn injection master switch
injectDeduptrueSession-level injection dedup: topics already injected in this session are not re-injected (registry cleared at session end; budget-dropped topics stay injectable; deduped topK slots are NOT backfilled) — ADR 0012
suppressEchotrueDistill-echo suppression: topics distilled from the CURRENT session's own turns are not injected back into it (provenance rides the observations log sessionId → distilledInto)
topK4Max topics injected per turn
perTopicBudget300Per-topic digest token budget
totalBudget1500Total injection budget per turn
matchThreshold0.3Hit threshold; tune from /topics stats near-miss evidence
tagBoost0.15Additive boost per tag hit (total cap across hits equals this value)
injectModepointerInjection shape: pointer (light pointers, ≤80 tok each, topic_open pulls the full text; total budget capped at 600) / digest (full digest rendering, per-topic 300 / total 1500)
qualityLanesampledSlow quality lane: off / sampled (1/3 of turns) / always; produced at turn/end, consumed by the next injection (consume-once), never for subagent sessions
graphDepth2depends graph walk depth (0 disables)
recencyWindowDays7Recency bonus window (+0.2)
autoObservetrueCapture atomic observations every turn
includeSubagentsfalseWhether injection and observation also engage subagent sessions (ADR 0011; off by default since 0.7.0); off skips them entirely
observationMaxChars2000Per-side per-turn observation truncation
distillProvider / distillModelempty (distill off)Distill lane model route; both must be set to enable. With a UI, /topics set distill-provider / distill-model without a value opens a picker panel (provider list → that provider's model catalog); a mixed provider model / provider/model value for distill-model splits into both keys
distillEveryTurns5Distill every N turns of a long session
distillOnSessionEndtrueDistill once when a session ends
distillBatchSize40Observations per distill model call. On an output-limit (max-tokens) failure the batch halves automatically (floor 5) and retries — a failing batch can no longer livelock the backlog; the shrink persists until reload or a config change. Note: /topics set distillBatchSize back to the same value does not reset the shrink — set a different value or reload the plugin
distillMaxModelCalls8Max model calls per distill run, including the one corrective retry for ops echoing no valid observed_ids (the run stalls when the budget can't fit it). Batches already distilled keep their marks when the budget stops the run (partial progress), recorded as partial: … in the distill state
pushDebounceSeconds45GitHub-mode debounced push interval

Acknowledgements

This project's shape is directly inspired and supported by:

  • zosmaai/pi-llm-wiki — a native OKF v0.2 knowledge extension for pi and this project's direct inspiration; its two-stage observation (cheap atomic observations + background distill), cache-safe injection (volatile content never enters the system prompt), and layered vault & ownership model are all absorbed here.
  • GoogleCloudPlatform/open-knowledge-format — the Open Knowledge Format (OKF) v0.2 spec this bundle format strictly follows.
  • Karpathy's LLM Wiki pattern — the starting point of the whole "an LLM maintains a personal knowledge base" methodology.
  • fan56/pi-topic-memory — the same author's predecessor: a working topic ledger with silent injection for pi; its LLM-free hot-path matching and injection-timing experience is this project's direct technical ancestor.
  • chancelu/dsh-llmwiki — a fellow dsh-ecosystem precedent; this project's same-turn injection seam (agent/inbox/spliced + systemPrompt.context()) follows the mechanism it validated on real dsh.

Known boundaries

  • Subagents are out of memory by default — one switch to opt in: by default (include-subagents off since 0.7.0) delegated sessions are skipped entirely — no injection, no observation, no distill triggers; /topics set include-subagents on applies injection and observation to them too. The topic tools stay on the global layer, so an explicit topic_save from a child still lands. Out-of-process subagents (claude-code/codex providers) never load this plugin anyway.
  • Exit is local-only (0.10.0): the plugin's disposer makes one local git commit of the meta sidecars (observations / injections / distill state) and never waits on the network — no pull, no push, no model call, so host exit no longer pays a git round-trip or a bounded distill wait (the old 90s cap is gone; only a 10s guard against a pathological git stall remains). The exit distill trigger is fired fire-and-forget and is skipped entirely while a session-end run is still in flight (the same pool head would otherwise be fed to the model twice). Nothing is lost by skipping: observations are write-through on disk, the deferred push is replayed by the next boot's pull, and the skipped distill is replayed there too (boot-replay). meta/distill-state.json records each lane's outcome, checkable via /topics status.
  • Observation GC (three strikes): an observation the model actually evaluated (parseable answer, however useless) but no op consumed accrues one failed attempt; the third failed attempt physically deletes it — explicitly authorized cleanup of raw data the lane demonstrably cannot process. Runs that never evaluated the batch never count: infrastructure failures (network errors, unconfigured distill route → readable no-model short-circuit) and unparseable output (invalid-output) are exempt, and a batch still mid-shrink on output-limit retries is only counted once a verdict is reached (success, floor stop, stall, or an explicit skip). Deletions are committed immediately (data destruction stays git-traceable); pure attempt counters follow the usual flush cadence.
  • Config read timing: /topics set and settings.yaml edits take effect most reliably from the next session start.
  • Picking a distill model: /topics onboard splits the distill decision into two dependent questions (provider first, then that provider's model catalog), pre-validated with resolveModelInfo — a provider with no live route blocks and re-asks, an off-catalog model (a non-NO_ADAPTER failure: outside the advisory catalog, possibly still usable) warns but is allowed; hosts without an ask UI or a usable model route fall back to typed input. The same validation backs the /topics set picker panels.

Design docs

  • CONTEXT.md — domain glossary
  • docs/adr/ — 0001–0013: OKF compliance, remote shape, sync strategy, two-stage observer, bundle layout, injection defaults, observability & tunables, dual-mode persistence, onboarding wizard, subagent isolation, the include-subagents switch, injection dedup default-on, the rename & migration

License

MIT

Plugins associés