dsh-memoryhub
Agent 与会话 活跃维护

dsh-memoryhub

solknight48/dsh-memoryhub

MemoryHub (mh) plugin for DeepSeek Harness (dsh): auto-loads checkpoint memory on session start, adds mh_* tools and the mh skill, and a Memory tab in the web UI

3
Stars 标星
0
Forks 分支
3
Watchers 关注
0
Open Issues
TypeScript
主要语言
MIT
开源协议
1.3 MB
仓库大小
1 个月前
最后推送
一键安装扩展 / 插件指令
dsh plugin --profile web add github:solknight48/dsh-memoryhub
git clone https://github.com/solknight48/dsh-memoryhub.git
git clone git@github.com:solknight48/dsh-memoryhub.git
README.md main

dsh-memoryhub

English | 简体中文

MemoryHub (mh) integration for
DeepSeek Harness (dsh).

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 toolsmh_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).

Memory tab in the dsh web UI

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.

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/<name>/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:

  1. The tab asks the host half for the map URL of its session:
    GET /plugins/memoryhub/mh-ui?session=<id>.
  2. 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).
  3. The token-bearing URL (http://127.0.0.1:<port>/?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 <uiBudget> (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:

  1. 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.
    Assistant text that follows an mh load is dropped too, until the next
    genuine user message: that reply is the load receipt plus a digest of the
    memory that was just loaded — content that already lives in the
    checkpoints. Saving it would re-embed old memory into every new session
    and snowball. The load is detected whether it went through the mh_load
    tool directly or through a run_code block (the web GUI's path); the
    user's own request line stays. Detection is two-key: the call must
    have a load's shape (a direct mh_load call, tools.mh_load(…) in code,
    or mh load as a shell command — text that merely mentions mh load in a
    grep or edit payload does not count) AND the call's result must carry
    mh's own load receipt (<!-- mh | loaded: and friends); a failed load
    arms nothing.
  2. Writes it as a pi-format transcript at
    $TMPDIR/dsh-memoryhub/dsh_<session-id>.jsonl.
  3. Runs mh save --transcript <that file> (or
    mh save --compact --file <summary.md> --transcript <that file> with the
    summary the model wrote into the tool call).

Consequences worth knowing:

  • The checkpoint filename key follows mh's pi rule (pi-<id12>) 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 --transcriptmh 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-startmh loadagent.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

License

MIT