MemoryHub (mh) integration for DeepSeek Harness: loads .memoryhub checkpoint memory on session start, saves dsh sessions back into checkpoints, and registers the mh workflow skill and tools
MemoryHub keeps project memory as purified sessions in git-versioned
checkpoints under .memoryhub/. This plugin wires that into dsh:
Auto-load on session start — runs mh load in the session workspace and
injects the checkpoint memory as durable plugin context. No prompt, no tool
call; the model simply starts with its memory back.
mh_save bridges dsh sessions to mh — dsh session files are not among
the transcript formats mh discovers (Claude Code / pi / Codex), so the
plugin renders the live session's durable event log as a pi-format JSONL
transcript (in the temp dir) and saves through mh's existing
--transcript path. Purified saves and agent-written compact saves both
work, under a stable per-session identity: re-saving replaces, never
duplicates.
Six tools — mh_load, mh_save, mh_status, mh_list, mh_search,
mh_checkpoint wrap the CLI; the model never has to shell out for the
common operations. Rarer operations (mh link, mh back, mh import,
mh ui) stay in the shell, as the skill explains.
A "Memory" tab in the web UI — beside chat and trajectory: the mh ui
checkpoint map of the session's workspace, embedded live (see
The Memory tab).
The mh workflow skill — registered at runtime, it teaches the model
when to load, when and how to save (including writing compact summaries),
and the hub rules (no HANDOFF.md files, git -C .memoryhub for anything
mh does not cover).
Prerequisites
mh installed and on PATH (uv tool install git+https://github.com/solknight48/memoryhub;
needs git ≥ 2.32, Python ≥ 3.12). The plugin shells out to it — the hub
format, git commits, and error wording stay owned by mh.
dsh-memoryhub · DSH Hub
Install
dsh plugin --profile web add github:solknight48/dsh-memoryhub
The package declares dsh.bundle, so the install appends its patch layer to
the profile. For a checkout instead: dsh plugin --profile web add ./dsh-memoryhub.
Git installs fetch sources, and pnpm ≥ 10 asks before running this package's
prepare build the first time; allowlist dsh-memoryhub in the profile's
pnpm-workspace.yaml as dsh instructs, then re-run the add.
Configure
Every field is optional; these are the defaults:
# $DSH_HOME/profiles//cordis.patch.yml — restates the whole row.
- insert:
- id: memoryhub
name: dsh-memoryhub
config:
mhBin: mh # mh executable (PATH-resolved)
autoLoad: true # mh load + inject on every session start
# loadBudget: 6000 # token budget for auto-load; omit for mh's default
timeoutMs: 20000 # kill any mh invocation after this
registerTools: true # the six mh_* tools
registerSkill: true # the mh workflow skill
noHubHint: false # inject a one-line hint when no hub is found
uiTab: true # serve the mh-ui URL route the web Memory tab fetches
uiReadOnly: false # spawn mh ui --read-only for the Memory tab
uiBudget: none # the Memory tab map's initial budget ('none' = no over-budget badges)
contextWindowTokens: 128000 # fallback window for the context-% estimate
With noHubHint: false, a workspace without .memoryhub/ stays completely
silent: no injection, no log spam. mh itself decides what "the project" is by
walking up from the session's recorded cwd — the same rule as in a shell.
Loading: no budget by default, with a context-% receipt
mh_load loads every session in the selected checkpoints — the
over-budget filtering of stock mh load is off by deployment choice. Pass
the tool's budget argument when you do want only the newest sessions that
fit. (Auto-load, if you re-enable it, keeps its budgeted semantics via
loadBudget: it injects into every session silently, so it stays opt-in per
token.)
Every successful mh_load ends with a receipt line, e.g.:
[memoryhub] memory ≈ 31,240 tokens ≈ 12.2% of the 256,000-token context window (adapter-reported); session total after load ≈ 18.6%
The window comes from the model adapter's own report (the session's newest
request/context event) when available, otherwise from
contextWindowTokens. The "session total" adds the last request's measured
input size, so it reads as "where this load leaves the conversation". Token
counting uses mh's own ~4 chars/token heuristic, so the number agrees with
the Memory tab's sizes.
The Memory tab (web UI)
The package is a dual-face dsh plugin: the same memoryhub row that loads
the host half also puts the browser half (dsh.client in package.json)
into the web boot graph. The browser half registers one entry into the
conversation.view slot ring — a Memory tab next to chat and trajectory.
What the tab shows is not a reimplementation: it is mh ui itself, the
checkpoint map (timeline, token budget, per-turn editing), embedded in an
iframe so every mh feature and fix shows up unchanged. The wiring:
The tab asks the host half for the map URL of its session:
GET /plugins/memoryhub/mh-ui?session=.
The host half resolves the session's workspace (session.header.cwd) and
lazily spawns mh ui --no-browser --port 0 there — one server per
workspace, pooled, killed when the plugin unloads. mh keeps its own hub
discovery (walk up for .memoryhub/, MH_HUB override).
The token-bearing URL (http://127.0.0.1: /?t=…) is parsed from the
child's stdout and returned to the tab, which iframes it. mh's own
security model (loopback bind, one-shot token, Host check) applies
unchanged; uiReadOnly: true serves the map without editing.
A workspace without a hub renders an empty state with a retry button instead
of a server. The route is registered only when a web server exists, so
headless compositions never notice the feature.
The map is spawned with mh ui --budget (default none, which
needs mh from the memoryhub repo with mh ui --budget support — 2026-08-14
or later): the map's budget box starts empty, so its over-budget preview
badges stay off, matching mh_load's load-everything default. Set uiBudget
to a number to restore the budgeted preview.
How saving works
mh save identifies a session through its transcript. mh knows Claude Code,
pi, and Codex transcript formats; dsh is not one of them (yet). So on
mh_save, the plugin:
Walks the session's durable event log and keeps exactly what mh's own
purifier keeps: genuine user input (source.kind === 'user') and
assistant text. Plugin-injected context (including this plugin's own
auto-load snapshot), tool calls/results, and reasoning stay out.
Writes it as a pi-format transcript at
$TMPDIR/dsh-memoryhub/dsh_.jsonl.
Runs mh save --transcript (or
mh save --compact --file --transcript with the
summary the model wrote into the tool call).
Consequences worth knowing:
The checkpoint filename key follows mh's pi rule (pi-) even though
the session came from dsh. Cosmetic only; identity is stable per session.
The bridge is rebuilt at every save, so a later save sees the whole session
up to that point and replaces the earlier representation (mh keeps one
representation per session).
Steering messages and file attachments are not bridged (v1); a compact
save's summary can still carry anything important.
mh import backfills Claude Code / pi / Codex history only — dsh history
enters checkpoints by being saved through this plugin.
Verify
npm install
npm run build
npm test # e2e against the real mh CLI in a temp HOME
The test suite exercises the exact paths the plugin uses: bridge JSONL →
mh save --transcript → mh load, compact-replaces-purified,
save-twice-replaces, and the mh-ui pool against a real mh ui server
(spawn, token URL, page 200, guard 403, no-hub mapping). Plugin
registration (six tools + mh(runtime) skill + the Memory tab's client
bundle in the boot graph and the /plugins/memoryhub/mh-ui route branches)
was verified by booting a real dsh web profile with the bundle installed.
The agent/session-start → mh load → agent.inject path uses the same
extension points as dsh's own hook bridges; it has not been exercised
against a live model session yet (needs an API key), and neither has the
Memory tab's in-browser render.
Layout
src/index.ts plugin: config schema, session-start auto-load, tools, skill,
mh-ui route (webServer soft dependency)
src/bridge.ts dsh session events -> pi-format JSONL transcript
src/mh.ts execFile runner for the mh CLI (non-zero exits are data)
src/mh-ui.ts mh ui process pool (one map server per workspace)
src/estimate.ts context-share estimate for mh_load (adapter window, mh's heuristic)
src/skill.ts the mh workflow skill, adapted for dsh
src/client/ browser half: the Memory conversation-view tab (iframe of mh ui)
tests/ e2e against the real mh binary