omp-memory-dsh
Manifest validDSH 侧适配 omp 的项目级记忆:跨 agent 共享 lessons,零删除红线
omp-memory-dsh
把 omp(oh-my-pi coding agent)的项目级记忆接入 DeepSeek Harness,让两个 agent 在同一个项目里 共用同一份教训沉淀。
- DSH 侧注入
memory_summary.md+learned.md的一个窗口 - DSH 侧提供
learn工具,把新教训写回同一个learned.md - omp 侧的
learn/ autolearn 完全不受影响,两边看到的内容一致
🔴 安全承诺(先看这一节)
这个插件会写你的文件系统,而且那份文件是另一个 agent 的。所以它的默认行为是保守的:
| 承诺 | 实现方式 | 可验证性 |
|---|---|---|
| 绝不删除任何东西 | 插件源码里不存在 rm / unlink / truncate / rmdir 调用,由测试静态扫描强制 | V19 |
| 唯一可写目标 | join(root, <项目键>, "learned.md") 一个文件 | V24 |
| 不碰 omp 的其它产物 | MEMORY.md / memory_summary.md / raw_memories.md / rollout_summaries/ / skills/ / config.yml 一律只读或不碰 | V19 / V24 |
| 封顶淘汰默认拒绝 | omp 的教训上限是 100 条,写入会淘汰最旧的。DSH 默认 evictionPolicy: "refuse":会淘汰就拒绝写入并如实回报,文件零变化 | V21 |
| 字节保真 | 不改 BOM、不归一化换行、不整文件 trim,只在第一个 bullet 段前插入一行 | V20 |
| 并发写不覆盖 | 写前 CAS 指纹复核(size + mtime + sha256);检出并发改动就重读合并,连续冲突就放弃写入并回报 | V22 |
| 只读模式零写 | writeEnabled: false ⇒ 对记忆目录零文件系统写操作 | V23 |
| 一键刹车 | enabled: false ⇒ 插件不碰文件系统、不注册任何东西 | 配置 |
| 真实目录零污染 | 测试全程以真实记忆目录做只读校验,比对 (size, mtimeMs, sha256) | V24 |
不残留 {{...}} 占位符 | 记忆里真实存在 {{var}};DSH 渲染时插值失败会抛异常、整轮请求报废 | integration.test.ts(调 DSH 真实的 renderPrompt) |
| 不盲写 | 指纹读失败(Windows EPERM/EBUSY)不等于「文件没变」;读不到就是冲突,绝不覆盖 | V26 |
| 不认撕裂文件 | 0 字节的 learned.md(omp 用 Bun.write 中途截断)一律判为冲突,不写 | V25 |
| 不改坏字节 | 文件不是合法 UTF-8 ⇒ 拒绝写回(否则非法字节会永久变成乱码) | V32 |
| 不注入指令 | 读侧过滤指令性内容并如实上报拦截条数;写侧拒收投毒与凭据形状 | V28 / V29 / V31 / V33 |
| 不静默建目录 | 项目记忆目录不存在时拒绝写入并说明原因,不替 omp 创建 | 写路径闸门 |
为什么默认拒绝淘汰? omp 的教训上限是 100 条,超了就静默淘汰最旧的。在真实项目里,DSH 每写一条都会 加速这个淘汰,而 omp 自己毫不知情。默认
refuse把这个决定权交回给人: 要不要用历史沉淀换一条新教训,你说了算。 确实需要与 omp 完全一致时,配置evictionPolicy: "omp-parity"即可(仍会如实回报淘汰条数)。
🔴 100 条封顶是 omp 的规则,不是本插件的
这个上限由 omp 强制。本插件只能控制自己这一侧(默认 refuse ⇒ DSH 绝不触发淘汰),
omp 自己继续写就会淘汰掉它最早的沉淀。
本插件对此的处理:
learn撞顶时拒绝写入,并报出真实总数与剩余额度(例:「当前已有 100 条教训…距上限还差 0 条」)。- 成功写入时也回报当前条数(例:「当前 84/100 条,距上限还差 16 条」)。
- 注入文本的
Project:行标明这是哪个项目的记忆。
⚠️ 注意:这条风险不因装了这个插件而消失。插件只能保证「DSH 不加速它」。 要真正控制它,得改 omp 侧的上限(那是另一个话题,本插件不碰 omp 的配置)。
兼容性
| 组件 | 要求 | 备注 |
|---|---|---|
| DeepSeek Harness | >=0.2.1-rc.1 <0.3.0-0 | peer dependency 声明了显式预发布分支,避免 npm/semver 静默排除预发布版 |
| Node.js | >=22.6.0 | |
| omp (oh-my-pi) | v18.12.2 及以后 | 脱敏与写入语义对齐 v18.12.2;v18.12.3 的相关上游文件逐字节相同,行为等价 |
| 平台 | Windows / Linux / macOS | 平台相关的路径归一化在测试中显式覆盖 |
DSH 的官方包一律声明在 peerDependencies(不作为生产依赖打包),插件本身只引入
@deepseek-ai/schemastery 一个运行时依赖。
安装
推荐:用预构建 tarball(不需要本地构建)
dsh plugin --profile web add https://github.com/meisijiya/omp-memory-dsh/releases/download/v0.2.0/omp-memory-dsh-0.2.0.tgz
或者:从 GitHub 源码安装(会跑 prepare 自动构建)
dsh plugin --profile web add github:meisijiya/omp-memory-dsh
或者:手动挂到 profile
本包是一个 DSH bundle:package.json 里声明了 dsh.bundle.patch,
所以 dsh plugin add 装上之后会自动生效,不需要额外写 patch。
如果你要手动挂载,把插件加进 profile 的 package.json 依赖,然后在
cordis.patch.yml 里写包名(不是绝对路径):
- insert:
- id: omp-memory
name: 'omp-memory-dsh'
config:
enabled: true
evictionPolicy: 'refuse'
🔴 为什么是包名而不是绝对路径? DSH 的 loader 用
import(name)加载插件。Node 的 ESM 在 Windows 上不接受裸绝对路径 (会报ERR_UNSUPPORTED_ESM_URL_SCHEME),而且绝对路径换台机器就断。 包名由 profile 的node_modules解析,是唯一可分发的写法。 (本仓库里cordis.patch.yml就是这个文件本身。)
装完需要重启 DSH(插件在进程启动时装载)。
配置
所有配置项都有默认值,不配置也能跑。
| 配置项 | 类型 | 默认 | 说明 |
|---|---|---|---|
enabled | boolean | true | 总开关。false ⇒ 不碰文件系统、不注册任何东西 |
inject | boolean | true | 是否向 system prompt 注入记忆 |
root | string | "" ⇒ ~/.omp/agent/memories | 记忆根目录。Linux 上 omp 用 $XDG_STATE_HOME/omp/memories,此时必须显式指定 |
writeEnabled | boolean | true | 写闸门。false ⇒ 零写操作,只保留只读注入 |
evictionPolicy | "refuse" | "omp-parity" | "refuse" | 触发 100 条封顶时是否允许淘汰最旧的教训 |
sectionOrder | number | 950 | 注入 section 的排序位(落在 FILE_REFERENCE 900 与 TOOL_BASH 1000 之间) |
lockTimeoutMs | number | 2000 | 写锁等待超时。超时报错,绝不静默阻塞 |
requireApproval | boolean | false | 是否让 learn 走审批通道。只有在部署里确实装了审批服务时才开,否则 ask 会降级为拒绝,learn 全部失败 |
ledger | boolean | true | 是否写 sidecar 溯源账本(写在 omp 记忆目录之外:~/.dsh/omp-memory-dsh/ledger/) |
maxPageLines | number | 200 | memory_read 单次最多返回的行数 |
工作原理
读路径
- 会话首次装配 system prompt 时,读取
<root>/<项目键>/memory_summary.md与learned.md。 - 摘要先占预算:共享预算是 5000 token(≈ 20 000 字符),
learnedBudget = 5000 - ceil(摘要字符数 / 4)。 learned.md只读窗口,不整读:读文件头部(60%)与尾部(40%),中间省略。- 切点吸附行边界 ⇒ 绝不产出「半条教训」。省略处会插入截断标记与一句提示。
- 快照在整个会话内冻结(与 omp 一致),只有 DSH 自己刚写过一次才会刷新。 这样 system prompt 不会在一轮对话中途漂移。
- 内容注入到一个
interpolate: false的 section 里,外面包着<omp-memory-data>标签, 并附上 omp 原版的 6 条 Rules。
项目键在会话开始时冻结。 DSH 的工作目录在会话中可变,但项目键不变 —— 否则
learn会写到另一个项目的目录里去。
写路径
learn 工具的完整闸门顺序(任何一步不过,都不写文件):
内容检查(拒绝指令式内容)
→ 格式与长度归一(脱敏 + 中和注入 + 上限 2000/400 字符)
→ writeEnabled 闸门
→ 目录存在性检查(绝不静默新建目录)
→ 文件锁(withFileLock,超时即报错)
→ 撕裂守卫(无尾换行 ⇒ 复读一次比对)
→ 计算新内容(去重 + 插入第一个 bullet 段 + 封顶处理)
→ 封顶闸门(refuse 模式下会淘汰就拒绝)
→ 写前 CAS 指纹复核(size + mtime + sha256)
→ 原子写(writeFileAtomic)
→ 写后校验
→ 写 sidecar 溯源账本
目录必须先存在。 如果这个项目在 omp 那边还没有记忆目录,
learn会拒绝写入并说明原因, 而不是替 omp 建一个 —— 静默建目录会让两端从此分叉。需要先让 omp 打开过该项目。
工具面
learn
把一条持久教训写进本项目的共享记忆。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
memory | string | 是 | 教训本身。一条独立的陈述句。 |
context | string | 否 | 简短来源(文件路径、命令、报错),供后来者判断可信度。 |
写入失败时抛错并说明原因(例如「拒绝写入:本次写入将淘汰最旧的 1 条既有教训」)。
memory_read
按行分页读取本项目的完整记忆,只读。用于读到被注入窗口省略掉的教训,或审计记忆实际内容。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
offset | integer | 否 | 行偏移,默认 0 |
limit | integer | 否 | 返回行数,默认 50,上限 maxPageLines |
与 omp 的关系
| omp | 本插件 | |
|---|---|---|
| 记忆文件 | ~/.omp/agent/memories/<项目键>/learned.md | 同一个文件 |
| 写入方式 | Bun.write(非原子) | writeFileAtomic(原子) |
| 淘汰策略 | 静默淘汰最旧的 | 默认拒绝并回报(可切到 omp-parity) |
| 注入预算 | 5000 token | 5000 token(一致) |
| 脱敏规则 | 上游实现 | 逐字移植,差分测试保证字节一致 |
omp 的 config.yml、autolearn、managed-skills 一律不碰。
一致性是怎么验证的
单元测试能证明「实现符合我们写下的规则」,但不能证明「两端真的看到同一份东西」。 所以另有一组黑盒对跑:同一句提示词,分别喂给 omp 的无头模式与 DSH 的一次性任务, 再用一个没有记忆的目录做对照。
| 有记忆的项目 | 没有记忆的目录 | |
|---|---|---|
omp(omp -p) | 答出记忆里的正确答案 | 明确回答「没有这条记忆」 |
DSH(dsh --profile headless) | 答出同一句正确答案 | 明确回答「没有这条记忆」 |
反方向也验证过:DSH 的 learn 写进去一条带唯一标记的教训后,
omp 在同一个项目里能读到它。
对照组的意义:正确答案只有在两边都拿到记忆时才出现 ⇒ 它来自记忆,不是模型猜的。
已知限制(诚实清单)
- 只做项目级记忆。 不读 omp 的
managed-skills/,不做auto-learn的自进化。 - Linux 上需要显式配
root。 omp 在 Linux 上用$XDG_STATE_HOME/omp/memories, 本插件的默认是~/.omp/agent/memories,两者不一致时需要手工指定。 - 不是 100% 原子。 CAS 复核与 rename 之间仍有极小竞态窗口。真正兜底的是 原子写 + 撕裂守卫 + 写后校验三件套。
- omp 的非原子写(
Bun.write)在写入瞬间可能让 DSH 读到半截文件。 撕裂守卫会检出并重试,但极端情况下仍可能读到不完整内容。 requireApproval默认关闭。 DSH 没有工具级审批字段,审批只能通过tools/pre-execute的 waterfall 模拟,且必须有审批服务在场,否则ask降级为拒绝。- 脱敏规则是上游
v18.12.2的快照。 上游改进后需要重新对齐(重跑npm run test:fixture会报漂移)。 - DSH 侧比 omp 多一层护栏 ⇒ 两端行为有分叉。 DSH 拒收指令式内容与明文凭据, omp 不拒收(omp 那边只是中和注入载体、不做语义判断)。所以同一条内容, DSH 可能写不进去而 omp 写得进去。护栏命中时会在报错里说明原因。
- 读侧不拦明文凭据。 写侧会拒收凭据形状,但读侧只过滤指令性内容 ——
omp 那边写进去的
password=...仍会被原样注入。收紧读侧会破坏与 omp 的字节一致性, 这是一个已知的、刻意保留的不对称。 - 注入的是窗口,不是全部。5000 token 共享预算下,大项目只有开头与结尾的教训进入注入,
中间的要用
memory_read主动读。(读取预算是按字符算的,实际 I/O 按字节算,中文约 ×2.7。)
开发
npm install
npm run check # typecheck + vitest + fixture 三连
npm run build # 产出 lib/(逐文件编译,不是一个 bundle)
npm run test # 只跑 vitest
npm run test:fixture # 上游漂移检查 / oracle 自检 / 安全红线脚本
npm run test:upstream # 在线拉取 GitHub 上的 omp 固定 tag 比对 sha256
npm run verify:build # 把构建产物装进真实 cordis 容器并加载
npm run verify:package # 打包 → 解包 → 逐个解析相对 import → 真的 import 一次
verify:package不是形式检查:它会真的npm pack,解包,验证产物里每个相对 import 都能解析(漏发一个.js文件就会在这里炸),然后真的 import 一次产物入口。
上游 oracle
test/fixtures/omp-v18.12.2/ 里是 oh-my-pi v18.12.2 的上游源码切片
(memories/index.ts、memories/storage.ts、memory-backend/redact.ts、
prompts/memories/read-path.md)以及一份 manifest.json(逐文件 sha256)。
check.mjs—— 校验本地切片与 manifest 一致,并把pure.ts重新抽取后逐字节比对(oracle 手改即漂移)selftest.mjs—— oracle 自身的行为自检,并全量扫描真实记忆目录验不变式safety.mjs—— C6 安全红线的可执行判据verify-upstream.mjs—— 在线拉取 GitHub 上的 omp 固定 tag 比对 sha256pure.ts—— 从上游切片抽出的纯逻辑,测试专用
npm run test:fixture 全绿是放行的前提之一。
重新生成 oracle
上游升级后:
node test/fixtures/omp-v18.12.2/regen.mjs
License
MIT
上游 oh-my-pi 的相关代码遵循其自身许可证;本仓库只包含为差分测试而保留的源码切片。
Comments
Loading…
Similar plugins
by luyy9apples
Approval-gated workspace instructions and shared project memory for DeepSeek Harness
★ 0
MIT
TypeScript
Sep 2, 2026
dsh plugin --profile web add dsh-workspace-memoryby jonah791
Agent-driven long-term memory for DeepSeek Harness: scoped memory (global + per-workspace), layered entries (fact/knowle
★ 3
↓ 81/wk
MIT
TypeScript
Sep 7, 2026
dsh plugin --profile web add dsh-agent-memoryby huangjua
🧠 Persistent cross-session local memory for DSH agents (Markdown SSOT + self-healing SQLite mirror)
★ 0
BSD-3-Clause
TypeScript
Oct 3, 2026
dsh plugin --profile web add @dsh-external/dsh-local-memoryby zhaoxuejie
让 DeepSeek Harness agent 把本地 Obsidian 知识库当作长期记忆与工作台:全文/语义检索、会话记忆注入、一键捕获、巡检管家(只建议不擅改)
★ 1
MIT
JavaScript
Sep 7, 2026
dsh plugin --profile web add dsh-plugin-vault-memoryby liu-zhengdong
把 Markdown 记忆目录接入 DSH:每轮常驻索引,正文按需读取
★ 0
MIT
TypeScript
Oct 14, 2026
dsh plugin --profile web add dsh-simple-memoryby guyuefangyuanl
Cross-session persistent memory for the DeepSeek Harness: a model-facing memory tool, an always-on index section, and a bundled maintenance skill.
★ 3
↓ 434/wk
MIT
JavaScript
Aug 21, 2026
dsh plugin --profile web add dsh-memory