DeepSeek Harness 插件

dsh-markdown-memory

Markdown folder long-term memory for DeepSeek Harness: one file per fact, an index line per memory, mounted into every request as a system prompt section(英文原文)

跳到安装方式

来源信息

GitHub 仓库
SYMlp/dsh-markdown-memory
最近更新
2026年8月20日
分类
工具与能力
GitHub stars
0
载体类型
plugin
目录证据
上游声明已找到 dsh.bundle
证据路径
package.json#dsh.bundle
核对版本
0.1.0-rc.8
上游核对日期
2026-08-20

该证据由上游目录提供。本站没有安装、运行或安全审核这个插件。

安装

默认先复制一段 Prompt,让 Agent 读 GitHub 仓库和源码;需要自己装时再切到命令。

复制这段 Prompt,发给 DSH、Codex 或其他 Agent,让它先读 GitHub 仓库和源码。

请先不要安装或执行任何命令。阅读这个插件的 GitHub 仓库、README 和关键源码,然后用清楚、直接的方式回答以下问题,帮助我判断它是否适合我的需求:

1. 这个插件是什么,解决什么问题;
2. 适合哪些用户和典型使用场景;
3. 安装后如何使用,并给出一个最小使用示例;
4. 有哪些已知限制,以及隐私、安全、兼容性或维护风险;
5. 给出“推荐 / 有条件推荐 / 不推荐”的明确建议和理由。

请区分仓库明确说明、根据源码推断和未知信息。证据不足时请明确说明,不要猜测或照抄 README。

GitHub:https://github.com/SYMlp/dsh-markdown-memory
插件名:dsh-markdown-memory
作者:SYMlp

检查来源文件

安装前先看这个插件目录里的 README 和其他文件。

文件资源管理器3 个文件
README.md来源说明 · 只读预览

dsh-markdown-memory

Your memory is a folder. Every agent already knows how to read a folder.

Markdown folder long-term memory for DeepSeek Harness.

A memory directory holds one markdown file per durable fact, each with YAML frontmatter (name, description, optional metadata) followed by the fact body. An index file (default MEMORY.md) carries one line per memory. This plugin mounts the index and a recursive file listing into every request as a system prompt section; the model reads individual memory files on demand with its file tools.

Why this one

  • Zero infrastructure — no daemon, no database, no account, no embedding pipeline. Point at a folder.
  • Agent-agnostic by format, not by bridge — the same folder a Claude Code auto-memory already writes, the same vault Obsidian already manages. dsh joins your memory; nothing migrates, nothing syncs, because there is only one copy.
  • Human-owned — read it, edit it, git diff it, delete a line of it. When the agent is gone, your memory is still yours.

The position is deliberate: cross-agent memory does not need a mechanism. It needs a format every agent can already read, and a markdown folder is that format.

Compared to other memory approaches

ApproachWhere memory livesCross-agentYou can edit a memory by hand
Graph / vector memory enginesEngine's own store (binary/DB)Via that engine everywhereNo — through the engine
Hosted memory servicesTheir cloudVia their connectorsThrough their UI/API
Session-distillation pluginsGenerated files per harnessUsually single-harnessYes, but the pipeline rewrites
dsh-markdown-memoryA folder you already ownAny agent that reads filesYes — it's just a file

Each row is a legitimate trade. Pick an engine when you want automatic extraction and semantic search out of the box; pick a service when you want fleet-wide memory without owning storage. Pick this plugin when you want your memory to outlive every agent that reads it.

Install

dsh plugin --profile <name> add dsh-markdown-memory

Then point path at your memory directory in the profile's cordis.patch.yml:

- id: markdown-memory
  name: dsh-markdown-memory
  config:
    path: /absolute/path/to/your/memory
    # indexFile: MEMORY.md
    # maxBytes: 32768
    # sectionOrder: 120

Configuration

FieldDefaultMeaning
path(required)Absolute path of the memory directory. Load fails when it is missing or not a directory.
indexFileMEMORY.mdIndex file name inside the directory. Injected in full.
maxBytes32768Byte bound for the injected section. Longer content is truncated with a notice.
sectionOrder120Prompt section order, inside the 100–199 tool-guidance band.
recursivetrueWhether subdirectories are listed.
ignore.obsidian, .git, .trash, node_modulesDirectory names skipped at every level. Setting this replaces the default list.
maxDepth8Directory levels walked below the memory directory; 1 lists the top level only.
maxFiles500How many paths the listing carries. Further matches are counted in a notice, not dropped silently.
seedMaxBytes16384Byte budget for the bodies seeded at session start. A memory that does not fit is skipped whole, never truncated.
recalltrueWhether trigger-matched memories are recalled at each step.
maxRecallPerStep3How many memories one step may recall.
caseSensitiveTriggersfalseWhether trigger matching is case sensitive.

Misconfiguration fails at load with a message naming the field.

Mount your Obsidian vault

An Obsidian vault is a markdown folder, so it mounts directly:

- id: markdown-memory
  name: dsh-markdown-memory
  config:
    path: /absolute/path/to/YourVault
    indexFile: Home.md   # or whatever your map-of-content note is

What the model then gets: your map-of-content note verbatim, plus a listing of every note in the vault as a path relative to the vault root (projects/alpha.md), and it reads any note on demand. [[wikilink]] guidance is already part of the injected section, so vault-style links resolve naturally when the model follows them.

Vault housekeeping folders are skipped by default — .obsidian, .git, .trash, node_modules. Setting ignore replaces that list rather than extending it, so keep the entries you still want skipped.

Current constraints for vaults (honest ones): the walk stops at maxDepth levels and the listing carries at most maxFiles paths, and when a vault exceeds either bound the section says how many notes it left out rather than trimming quietly. Large vaults should raise maxFiles and maxBytes together — the listing is part of the byte-bounded section. Attachments and non-.md files are never listed.

Seeded memories

Most memories are pointers: the model reads the index, decides one is relevant, and opens the file. A few are facts the model should hold before it is asked anything. Mark those in the memory's frontmatter:

---
name: deploy-ritual
description: the release steps that must not be reordered
metadata:
  seed: true
---

Never reorder the migration step ahead of the backup step.

At each session start, every seeded memory the model cannot currently read is injected into the conversation as a user/message carrying this plugin's source and the recall context form. Only metadata.seed selects a memory; a top-level seed key is ignored.

Injection is deduplicated against the durable session log, not against an in-process variable, so it survives restarts and resumes. Three states are distinguished:

State of a seeded memoryWhat happens at session start
Never injected in this sessionInjected
Injected and still model-visibleNot injected again
Injected, then hidden by compactionInjected again — the model can no longer read the earlier copy

Messages already queued for the next step count as injected, which closes the window between agent.inject() and the step that commits the message to the log.

Seeded bodies share the seedMaxBytes budget in listing order. A memory that would exceed the remaining budget is skipped whole; a later, smaller one may still fit.

On-demand recall

A memory that is not seeded still enters the conversation when the current step is about it. Declare what recalls it:

---
name: deploy-ritual
metadata:
  triggers:
    - deploy
    - release checklist
---

Never reorder the migration step ahead of the backup step.

At each step, the text entering that step is matched against every memory's triggers. Matching is literal substring matching — case-insensitive by default, and non-ASCII triggers work as-is. A memory with no declared triggers falls back to two strings it already has: its frontmatter name and its file name without the extension.

Recalled memories are appended after the step's own messages, so the prompt being answered comes first. They are deduplicated exactly as seeds are, through the same three states and the same key space: a memory that was seeded at session start is not recalled again while it is still visible.

Text this plugin injected itself is excluded from matching, so one memory cannot drag in another with no user intent behind it.

Refresh semantics

The directory is read once at plugin load, then re-read at each agent/session-start. That snapshot is what the injected section and the seeded bodies come from, so edits to the index, to the file set, or to a seeded memory become visible at the next session start. A failed refresh keeps serving the last good snapshot and logs a warning.

Recall differs on purpose: which memories are recallable comes from the snapshot, but a recalled body is read from disk at the moment it is recalled. Editing a memory mid-session therefore changes what a later recall carries, while a copy already injected stays as it was — the log records what the model actually read, not what the file says now. A memory deleted after the snapshot is simply not recalled; the step proceeds without it.

Model Experience

Memory index section

#### What the model sees

One system prompt section named markdown-memory:index at order 120:

# Long-term memory

A persistent memory directory is mounted at: /home/user/memory
Each memory is one markdown file with YAML frontmatter (`name`, `description`) followed by the fact.
The index below lists one line per memory. When an index line is relevant to the current task,
read that memory file with your file tools before relying on it. `[[name]]` references link to
the memory file whose frontmatter `name` matches.

## Index (MEMORY.md)

- [Project layout](project-layout.md) — where each subsystem lives
- [Deploy ritual](deploy-ritual.md) — the release steps that must not be reordered
- [Alpha](projects/alpha.md) — the one project with a non-standard release

## Memory files (3)

Paths are relative to the memory directory above.

deploy-ritual.md, project-layout.md, projects/alpha.md

#### Token effect

The section costs the index file size plus a fixed header and the file listing, on every request, bounded by maxBytes (default 32 KiB ≈ 8k tokens; a typical index is far smaller). The listing grows with the vault: at the maxFiles default of 500, paths alone run roughly 1–2k tokens, so a large vault should be budgeted through maxFiles rather than left to maxBytes truncation. Memory file bodies cost nothing until the model chooses to read one.

#### KV Cache effect

The section lives in the system prompt and its text is stable across requests within a session, so it extends the reusable request prefix rather than breaking it. When a session-start refresh picks up changed content, the prefix diverges once at the next request and is stable again afterwards.

Seeded memories

#### What the model sees

One user-role message per seeded memory, before the first turn:

Memory: deploy-ritual.md

Never reorder the migration step ahead of the backup step.

The first line is the memory's identity and is deliberately model-visible: the durable message source carries no per-message fields, so the log records which memory a message holds only through the text the model reads.

#### Token effect

Each seeded body enters the conversation once and is re-sent with every later request in the session, as all conversation history is. It costs nothing when no memory is marked. seedMaxBytes (default 16 KiB ≈ 4k tokens) bounds the total, and the bound drops whole memories rather than half of one.

#### KV Cache effect

Seeds append to the conversation rather than rewriting it, so they extend the reusable prefix instead of breaking it. A seed injected before the first request is part of the prefix for the whole session. After a compaction rewrites the history, a re-injected seed appends to the new tail; the prefix break belongs to the compaction, not to this plugin.

Recalled memories

#### What the model sees

The same shape a seed uses, appended to the messages of the step that triggered it:

Memory: deploy-ritual.md

Never reorder the migration step ahead of the backup step.

#### Token effect

A recalled body enters the request that triggered it and is re-sent with every later request in the session. At most maxRecallPerStep memories enter one step, so the worst case per step is that many bodies. Steps whose text matches no trigger cost nothing.

#### KV Cache effect

Recall appends to the conversation, so the prefix built by earlier requests stays reusable and the recalled body extends it. Because recall happens at pre-step, the memory is part of the request that needed it rather than of the system prompt, which keeps unmatched sessions from carrying it at all.

Testing

pnpm test        # builds lib/, then runs unit tests and the Loader smoke
pnpm test:unit   # unit tests only

The smoke boots the built lib/ through a real cordis.yml and a real Loader in a separate process, drives two turns against a deterministic adapter, and asserts against the persisted JSONL session log: that the seed and the recall each appear exactly once with this plugin's source, that the seed precedes the first model request, and that the module exposes no default export. That last check is not cosmetic — a default export makes the Loader's unwrapExports treat it as the module body and drop inject, which fails the boot with cannot get property "systemPrompt" without inject.

Known Limitations and Deferred Work

  • Host filesystem only — reads use node:fs directly, not the ctx.fs provider seam; remote or sandboxed filesystem products are not covered.
  • No live watching — changes are picked up at session start, not mid-session; there is no fs.watch integration.
  • Triggers are literal, not semantic — recall matches declared strings as substrings; a paraphrase that shares no trigger string does not recall the memory. There is no embedding or synonym expansion.
  • Untriggered memories rarely recall themselves — the fallback is the memory's own two names, which a natural question seldom contains verbatim. Declaring metadata.triggers is what makes recall work.
  • The per-step recall bound is silent — when more memories match than maxRecallPerStep allows, the extras are skipped without telling the model, because a notice would cost tokens in the step it is trying to protect.
  • Seeding is all-or-nothing per memory — a memory is either injected at every session start or never; there is no per-task or per-agent seed set.
  • No write path — the model cannot create or update memories yet; a guarded write tool is planned after recall.

License

MIT