DeepSeek Harness Hub
← 返回列表

Markdown 记忆夹SYMlp/dsh-markdown-memory

DeepSeek Harnessspec-screened在 GitHub 查看 ↗
未验证

用 Markdown 文件夹为 agent 保存可编辑的长期记忆

尚未跑自动兼容性验证,可查看页面内的依赖与入口分析。 · 最近上游提交 2026/8/20 · 已提供中文文档

DeepSeek Harness (dsh) 的 Markdown 文件夹长期记忆插件:每个事实一个文件,可人工编辑,可用 git 进行版本管理

综合分
28
GitHub 分
28
用户评分
★ Stars
1
周下载量
安装插件(需先安装 dsh CLI 引擎:npm install -g @deepseek-ai/dsh)
dsh plugin --profile web add SYMlp/dsh-markdown-memory
该插件未发布到 npm,走 GitHub 源安装(pnpm 若拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑)
数据截至 2026/9/16(元数据每日更新 · 实装验证按队列轮转,单条结论的验证时间见上方)
依赖的 DSH / Cordis 模块
@deepseek-ai/schemastery@deepseek-ai/cordis@deepseek-ai/dsh-agent@deepseek-ai/dsh-agent-spine-demo@deepseek-ai/dsh-app-boot@deepseek-ai/dsh-llm@deepseek-ai/dsh-loader-smoke@deepseek-ai/dsh-session@deepseek-ai/dsh-session-checkpoint-policy@deepseek-ai/dsh-session-persistence-jsonl@deepseek-ai/dsh-system-prompt
用户评分
还没有人投票,来当第一个
订阅周报,不错过优质插件更新
每周一封 · 高评分插件 + 新用户活动

README

dsh-markdown-memory

你的记忆就是一个文件夹。每个 agent 都已经知道如何读取文件夹。

为 DeepSeek Harness 提供的 Markdown 文件夹长期记忆。

一个记忆目录为每条持久事实保存一个 markdown 文件,每个文件都带有 YAML frontmatter(name、description、可选的 metadata),其后是事实正文。一个索引文件(默认为 MEMORY.md)为每条记忆保存一行。该插件将索引和递归文件列表作为系统提示词的一部分挂载到每个请求中;模型按需使用其文件工具读取单个记忆文件。

为什么选择这个

- 零基础设施 —— 没有守护进程、没有数据库、没有账户、没有嵌入流水线。指向一个文件夹即可。
- 格式上 agent 无关,而非通过桥接 —— 与 Claude Code 自动记忆已经写入的是同一个文件夹,与 Obsidian 已经管理的是同一个 vault。dsh 加入你的记忆;无需迁移,无需同步,因为只有一份副本。
- 归人类所有 —— 阅读它、编辑它、对它执行 git diff、删除其中一行。当 agent 消失时,你的记忆仍然属于你。

这一立场是刻意的:跨 agent 记忆不需要某种机制。它需要一种每个 agent 都已经能读取的格式,而 markdown 文件夹就是这种格式。

与其他记忆方法的比较

| 方法 | 记忆存放位置 | 跨 agent | 你可以手动编辑一条记忆 |
| --- | --- | --- | --- |
| 图 / 向量记忆引擎 | 引擎自己的存储(二进制/数据库) | 通过该引擎在各处使用 | 否 —— 需通过引擎 |
| 托管记忆服务 | 它们的云 | 通过它们的连接器 | 通过它们的 UI/API |
| 会话蒸馏插件 | 每个 harness 生成的文件 | 通常仅限单一 harness | 可以,但流水线会重写 |
| dsh-markdown-memory | 一个你已经拥有的文件夹 | 任何能读取文件的 agent | 可以 —— 它只是一个文件 |

每一行都是一种合理的取舍。当你想要开箱即用的自动提取和语义搜索时,选择引擎;当你想要无需自己拥有存储的全舰队记忆时,选择服务。当你想要你的记忆比每一个读取它的 agent 都更长久时,选择这个插件。

安装

dsh plugin --profile  add dsh-markdown-memory

然后在 profile 的 cordis.patch.yml 中将 path 指向你的记忆目录:

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

配置

| 字段 | 默认值 | 含义 |
| --- | --- | --- |
| path | (必填) | 记忆目录的绝对路径。当它缺失或不是目录时,加载失败。 |
| indexFile | MEMORY.md | 目录内的索引文件名。会被完整注入。 |
| maxBytes | 32768 | 注入部分的字节上限。更长的内容会被截断并附上提示。 |
| sectionOrder | 120 | 提示词部分顺序,位于 100–199 工具指导区间内。 |
| recursive | true | 是否列出子目录。 |
| ignore | .obsidian、.git、.trash、node_modules | 在每一层级都跳过的目录名。设置此项会替换默认列表。 |
| maxDepth | 8 | 在记忆目录之下遍历的目录层级数;1 仅列出顶层。 |
| maxFiles | 500 | 列表携带的路径数量。更多匹配项会在通知中计数,而不会被静默丢弃。 |
| seedMaxBytes | 16384 | 会话开始时播种正文的字节预算。放不下的记忆会整体跳过,绝不截断。 |
| recall | true | 是否在每一步召回触发器匹配的记忆。 |
| maxRecallPerStep | 3 | 一步最多可召回多少条记忆。 |
| caseSensitiveTriggers | false | 触发器匹配是否区分大小写。 |

配置错误会在加载时失败,并给出指明字段的消息。

挂载你的 Obsidian 仓库

Obsidian 仓库就是一个 markdown 文件夹,因此可以直接挂载:

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

模型随后得到的是:你的内容地图笔记原文,加上仓库中每一条笔记相对于仓库根目录的路径列表(projects/alpha.md),并且它可以按需读取任意笔记。[[wikilink]] 指引已经是注入段落的一部分,因此当模型跟随仓库风格的链接时,它们会自然解析。

仓库的日常管理文件夹默认会被跳过——.obsidian、.git、.trash、node_modules。设置 ignore 会替换该列表,而不是扩展它,因此请保留你仍希望跳过的条目。

仓库当前的限制(如实说明):遍历会在 maxDepth 层停止,列表最多携带 maxFiles 条路径,当仓库超出任一上限时,该段落会说明它遗漏了多少条笔记,而不是悄悄裁剪。大型仓库应同时提高 maxFiles 和 maxBytes——列表是受字节限制段落的一部分。附件和非 .md 文件永远不会被列出。

播种记忆

大多数记忆是指针:模型读取索引,判断某一条相关,然后打开该文件。少数是模型在被提问之前就应掌握的事实。在记忆的 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.

在每次会话开始时,模型当前无法读取的每一条播种记忆都会作为 user/message 注入对话,携带此插件的来源和 recall 上下文形式。只有 metadata.seed 会选择一条记忆;顶层的 seed 键会被忽略。

注入会针对持久会话日志去重,而不是针对进程内变量,因此它能在重启和恢复后存续。区分三种状态:

| 播种记忆的状态 | 会话开始时会发生什么 |
| --- | --- |
| 本会话中从未注入 | 注入 |
| 已注入且模型仍可见 | 不再注入 |
| 已注入,随后被压缩隐藏 | 再次注入——模型已无法读取先前的副本 |

已排队等待下一步的消息视为已注入,这关闭了 agent.inject() 与将该消息提交到日志的步骤之间的窗口。

种子正文按列表顺序共享 seedMaxBytes 预算。超出剩余预算的记忆会被整体跳过;之后较小的记忆仍可能放入。

按需召回

未被种子化的记忆在当前步骤涉及它时仍会进入对话。声明什么会召回它:

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

Never reorder the migration step ahead of the backup step.

在每一步,进入该步骤的文本会与每条记忆的触发器进行匹配。匹配是字面子串匹配——默认不区分大小写,非 ASCII 触发器按原样工作。未声明触发器的记忆会回退到它已有的两个字符串:其 frontmatter 中的 name 和不带扩展名的文件名。

被召回的记忆会追加在该步骤自身消息之后,因此正在被回答的提示排在最前。它们与种子完全一样去重,通过相同的三种状态和相同的键空间:在会话开始时被种子化的记忆,只要仍然可见,就不会再次被召回。

此插件自身注入的文本会被排除在匹配之外,因此一条记忆无法在无用户意图的情况下拖入另一条。

刷新语义

该目录在插件加载时读取一次,然后在每次 agent/session-start 时重新读取。注入部分和种子正文都来自该快照,因此对索引、文件集或某条种子化记忆的编辑会在下一次会话开始时生效。刷新失败会继续使用上一个良好快照并记录警告。

召回则有意不同:哪些记忆可被召回来自快照,但被召回的正文会在召回的那一刻从磁盘读取。因此,在会话中途编辑一条记忆会改变后续召回所携带的内容,而已经注入的副本保持原样——日志记录的是模型实际读取的内容,而不是文件现在所说的内容。快照之后被删除的记忆只是不会被召回;该步骤会在没有它的情况下继续。

模型体验

记忆索引部分

模型看到的内容

一个名为 markdown-memory:index 的系统提示部分,顺序为 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)
- 项目布局 — 每个子系统所在的位置
- 部署仪式 — 不可重新排序的发布步骤
- Alpha — 唯一一个采用非标准发布流程的项目

记忆文件(3)

路径相对于上方的记忆目录。

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

Token 影响

该部分在每次请求时都会产生索引文件大小加上固定头部和文件列表的开销,受 maxBytes 限制(默认 32 KiB ≈ 8k tokens;典型索引远小于此)。列表随仓库增长:在 maxFiles 默认值 500 时,仅路径就大约消耗 1–2k tokens,因此大型仓库应通过 maxFiles 进行预算控制,而不是任由 maxBytes 截断。记忆文件正文在模型选择读取之前不产生任何开销。

KV 缓存影响

该部分位于系统提示中,其文本在会话内的各次请求之间保持稳定,因此它扩展了可复用的请求前缀,而不是破坏它。当会话启动时的刷新捕获到变更内容时,前缀会在下一次请求时发生一次分歧,之后再次保持稳定。

预置记忆

模型看到的内容

每条预置记忆对应一条用户角色消息,位于第一轮之前:
text
Memory: deploy-ritual.md

Never reorder the migration step ahead of the backup step.

第一行是记忆的身份标识,并且是刻意对模型可见的:持久化消息源不携带逐消息字段,因此日志只能通过模型读取的文本来记录一条消息持有哪条记忆。

Token 影响

每个预置正文进入对话一次,并随会话中之后的每次请求重新发送,正如所有对话历史一样。当没有记忆被标记时,它不产生任何开销。seedMaxBytes(默认 16 KiB ≈ 4k tokens)限制总量,且该限制会丢弃整条记忆,而不是丢弃半条。

KV 缓存影响

预置内容追加到对话中,而不是重写对话,因此它们扩展了可复用的前缀,而不是破坏它。在第一次请求之前注入的预置内容在整个会话中都是前缀的一部分。在压缩重写历史之后,重新注入的预置内容会追加到新的尾部;前缀断裂属于压缩操作,而不属于此插件。

召回记忆

模型看到的内容

与预置记忆使用的形状相同,追加到触发它的那个步骤的消息中:
text
Memory: deploy-ritual.md

Never reorder the migration step ahead of the backup step.

Token 影响

召回的正文进入触发它的那次请求,并随会话中之后的每次请求重新发送。每个步骤最多有 maxRecallPerStep 条记忆进入,因此每个步骤的最坏情况就是那么多条正文。文本不匹配任何触发条件的步骤不产生任何开销。

KV 缓存影响
Recall 会追加到对话中,因此早期请求构建的前缀保持可复用,而召回的内容会对其进行扩展。由于召回发生在 pre-step 阶段,记忆是所需请求的一部分,而不是系统提示的一部分,这使未匹配的会话完全不会携带它。

测试
sh
pnpm test        # 构建 lib/,然后运行单元测试和 Loader 冒烟测试
pnpm test:unit   # 仅运行单元测试

冒烟测试在独立进程中通过真实的 cordis.yml 和真实的 Loader 启动构建后的 lib/,针对确定性适配器驱动两轮对话,并对持久化的 JSONL 会话日志进行断言:种子和召回各自恰好出现一次且来源为此插件,种子先于第一次模型请求,以及该模块不暴露默认导出。最后一项检查并非表面功夫——默认导出会让 Loader 的 unwrapExports 将其视为模块主体并丢弃 inject,从而导致启动失败并报错 cannot get property "systemPrompt" without inject。

已知限制与推迟的工作

- 仅限宿主文件系统 —— 读取直接使用 node:fs,而非 ctx.fs 提供者接缝;远程或沙箱文件系统产品不在覆盖范围内。
- 无实时监视 —— 更改在会话开始时被拾取,而非会话中途;没有 fs.watch 集成。
- 触发器是字面匹配,而非语义匹配 —— 召回将声明的字符串作为子串进行匹配;不共享任何触发字符串的转述不会召回该记忆。没有嵌入或同义词扩展。
- 未触发的记忆很少能自行召回 —— 回退方案是记忆自身的两个名称,而自然提问很少逐字包含它们。声明 metadata.triggers 才是让召回生效的关键。
- 每步召回上限是静默的 —— 当匹配的记忆多于 maxRecallPerStep 所允许的数量时,多余的会被跳过而不告知模型,因为通知会在它试图保护的步骤中消耗 token。
- 种子注入对每条记忆是全有或全无 —— 一条记忆要么在每次会话开始时都被注入,要么从不注入;没有按任务或按代理的种子集。
- 无写入路径 —— 模型尚不能创建或更新记忆;一个受保护的写入工具计划在召回之后实现。

许可证

MIT

上游仓库有新提交时邮件通知你(每天最多一封,无更新不打扰),随时一键退订。

💬 加入 DPharness 群聊

插件用法、部署报错、新插件第一时间同步——群里问,比一个人翻文档快。

点击加入 QQ 群
DPharness 群聊二维码,手机 QQ 扫码进群
扫码进群