DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

ffyfox /

ffyfox/dsh-zcode-project-memory

Verified

ZCode 项目记忆的 DeepSeek Harness 非官方社区移植:项目级长效记忆插件。Unofficial community port of ZCode's project memory to DeepSeek Harness: project-scoped long-term memory plugin

★ 0 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@88196a57

dsh-zcode-project-memory

English | 中文

DeepSeek Harness 的项目级长效记忆插件。模型在一次会话里沉淀下的项目教训,会在之后的新会话里被自动读回。

一行 Cordis 插件,做三件事:

  1. 注册 save_project_memory 工具 —— 写一条记忆(独立文件 + 维护索引)。
  2. 注入记忆指引到 system prompt(ctx.systemPrompt.section())—— 告诉模型何时写、写什么格式、怎么删。
  3. 注入记忆索引到运行时上下文(ctx.systemPrompt.context())—— 新会话开场就带着既有记忆的目录。

记忆按项目维度隔离:目录名由项目根目录的规范绝对路径经 sha256 派生,同一项目在任何进程、任何会话里命中同一份记忆,不同项目互不可见。

机制以 zai-org/ZCode 的实现为准复刻,DSH 侧接口以 DeepSeek Harness 官方文档为准。

非官方项目。 本项目是社区作品,与 Z.AI Co., Ltd(ZCode 的开发者)及 DeepSeek 均无隶属关系,未获其授权、赞助或背书。包名中的 zcode 仅用于说明实现来源(见上文与 NOTICE.md)。"ZCode" 是其各自权利人的商标,本项目对该名称的使用仅为描述来源。

安装

从 npm 安装:

dsh plugin --profile <你的 profile> add dsh-zcode-project-memory

dsh plugin add 把参数转发给 profile 目录下的 pnpm(registry 按你自己的 pnpm 配置);package.json 声明了 dsh.bundle.patch,插件会自动加入 profile 的 bundle 层。

从源码安装(改动即时生效,pnpm 建符号链接,须用绝对路径):

dsh plugin --profile <你的 profile> add /绝对路径/dsh-zcode-project-memory

插件 inject 了 tools 与 systemPrompt 两个宿主服务,两者由 dsh-base 提供,因此 web / headless / tui 等继承 dsh-base 的 profile 都能生效;缺少这两个服务的部署里插件停留在 PENDING,不产生副作用。

使用

插件装好后无需任何配置,它就是模型的一个工具加两段自动注入。日常用法就是正常对话 —— 不需要记命令、不需要手动整理文件。

让模型记住

直接说,或者让模型自己判断:

记住:这个项目的 migration 必须先在 staging 跑满 24 小时才能上生产。

模型会调用 save_project_memory,写入一条记忆 + 更新索引。它也被告知在遇到非显而易见的项目约定、架构决策、调试时踩到的坑时主动保存 —— 那些「未来的人读代码也便宜地推不出来」的知识。

不想让它记的别让它记:临时的任务状态、仓库里已经写着的事实(代码结构、git 历史、AGENTS.md 的内容),指引明确排除了这两类。

让它回忆

新会话里直接问就行,记忆索引在开场就已在上下文里:

这个项目的长效记忆里有什么?
上次说的那个 migration 约束是什么?

模型看到索引行后,会按需读取对应的记忆文件拿细节。跨会话、跨进程都有效 —— 记忆落在磁盘上,不依赖某个会话还活着。

让它忘记

没有删除工具,所以直接说:

忘掉关于 migration 的那条记忆。

模型会删掉记忆文件并从 MEMORY.md 移除那一行。也可以手工删(见下文「删除记忆」)。

检查它记住了什么

记忆是纯 Markdown,随时可读可改:

M=~/.dsh/memories/projects/<项目基名>-<哈希>/memory
cat "$M/MEMORY.md"     # 这行是索引:一条记忆一行
ls "$M"                # 每条记忆一个 .md 文件

想清空某个项目的记忆,直接删掉整个 memory/ 目录即可,插件对不存在的目录安静降级。

命令

本包是纯 JavaScript(ESM),没有转译或构建步骤 —— src/ 下的源码就是发布产物,因此只有安装与测试两条命令:

命令 说明
npm install 安装唯一运行时依赖 @deepseek-ai/schemastery
npm test 全量测试(129 项检查)
npm run test:subsystems 只跑三个子系统的最小可执行用例

从零复现:

git clone https://github.com/ffyfox/dsh-zcode-project-memory.git && cd dsh-zcode-project-memory
npm ci        # 或 npm install
npm test      # 退出码 0 即通过

要求 Node.js >=20(见 package.json 的 engines)。CI 在 Node 20 与 22 上跑同一条命令,见 .github/workflows/ci.yml。

记忆放在哪

<DSH_HOME>/memories/projects/<项目基名>-<路径哈希前16位>/memory/
├── MEMORY.md          # 索引:一行一条,永远不放正文
└── <slug>.md          # 每条记忆一个文件,带 frontmatter

DSH_HOME 默认 ~/.dsh。记忆不写进项目仓库,项目目录保持干净,也不受 git 操作、清理或只读挂载影响。

记忆文件形态

---
name: "auth-token-refresh-gotcha"
description: "Expired tokens return 401, not 403."
metadata:
  type: project
  node_type: memory
  originSessionId: "session-…"
---

The refresh endpoint returns 401 (not 403) when the token is merely expired.

**Why:** retry logic keyed on 403 silently drops expired-token retries.

MEMORY.md 只放指针,一行一条:

- [Auth token refresh gotcha](auth-token-refresh-gotcha.md) — Expired tokens return 401, not 403.

type 取四种:user(用户是谁)、feedback(工作方式上的指导)、project(进行中的工作与约束)、reference(外部资源指针)。同一条记忆重复保存会替换自己的文件与索引行,不重复堆叠。

索引超过 200 行或 25000 字符会被截断,注入时带提示。

文件名怎么来

文件名由 key 净化得到:

key 文件名
Auth token refresh gotcha auth-token-refresh-gotcha.md
部署规则 memory-9c31a6.md

净化的字符类是 [a-z0-9._-]。非 ASCII 字符会被整体丢弃,因此当 key 含此字符类之外的字符时,slug 后追加原 key 的 6 位哈希 —— 否则 部署规则 / 发布前检查 / 你的哨兵 会全部塌缩成同一个 memory.md 并互相覆盖。纯 ASCII key 的文件名不受影响,已有记忆不会被改名。

删除记忆

没有删除工具。 删除是两半:删掉该记忆的文件,并且从 MEMORY.md 移除它那一行 —— 只删文件会留下悬空索引指针。注入的指引明确告诉了模型这一点,触发条件是「用户要求忘记某事」或「某条记忆被证明错误/过时」。

手工删除同理:

M=~/.dsh/memories/projects/<项目名>-<哈希>/memory
rm -f "$M/<slug>.md"
# 再从 $M/MEMORY.md 移除对应那一行

架构

src/index.js            Cordis 插件入口:工具注册 + 两处注入
├── src/store.js        记忆读写:路径派生、原子写、稳定读、并发锁
├── src/memory-format.js 记忆与索引的渲染 / 解析、slug 净化
├── src/prompt.js       两个注入面的文本渲染
└── src/subsystems/     三个可选子系统,可单独导入调用(见下)

两个注入面

内容 注入 API 位置
记忆指引(何时写、格式、怎么删) ctx.systemPrompt.section() system prompt
记忆索引(MEMORY.md 文本) ctx.systemPrompt.context() user-role 运行时快照

拆成两处与 ZCode 一致:指引是稳定的规则,索引是随内容变化的数据。

子系统

src/subsystems/ 下三个模块复刻自 ZCode core/src/memory/。三者互不 import、可单独导入调用,需要彼此时通过参数注入(formatManifest / execute 回调)而非 import。

它们不被 apply() 自动启动 —— 插件的运行时行为就是「工具 + 两处注入」这三件事。

子系统 入口 产出
manifest 扫描 scanMemoryManifest({fileSystem?, rootDir, signal?})、formatMemoryManifest(manifest) 记忆文件清单(条目数组 / 单行清单文本)
自动抽取 createMemoryExtractionScheduler(execute)、evaluateMemoryExtraction(snapshot, cursor)、buildMemoryExtractionPrompt(…) 抽取决策 + 增量范围 + 抽取 Prompt
后台总结 runMemoryAgentLoop({model, tools, executeTool, …})、evaluateMemoryAgentToolPolicy(input) 总结文本 + 轮次 + 工具调用轨迹
import { scanMemoryManifest, formatMemoryManifest } from 'dsh-zcode-project-memory/src/subsystems/manifest.js'

const manifest = await scanMemoryManifest({ rootDir: memoryRoot })
console.log(formatMemoryManifest(manifest))
// - [project] auth-token-refresh-gotcha.md (2026-09-22T05:05:00.000Z): Expired tokens return 401…

files 白名单包含整个 src,src/subsystems/ 随包发布。每个子系统都有独立的最小可执行用例,在 stdout 打印一行可解析 JSON({"subsystem": "manifest", …})并以退出码 0 结束。

配置

插件导出 Schemastery Config。三个字段都直接影响注入体量或单条容量,属于不同部署可能想设成不同值的量:

- id: project-memory
  config:
    indexLineLimit: 200        # 索引注入的行数上限
    indexCharacterLimit: 25000 # 索引注入的字符数上限
    maxMemoryChars: 8000       # 单条记忆正文长度上限

默认值与内置常量一致,不配置时行为不变。非法值(如 maxMemoryChars: 0)在插件加载期即失败,不静默降级。

失败行为

记忆是增强项,不能成为会话的失败源:

  • 记忆不存在或不可读 → 注入空串,不在上下文里留空占位。
  • 拿不到可解析的项目目录 → 工具返回 saved: false,两处注入返回空串。
  • 读取一律拒绝符号链接与非普通文件,5 MiB 体积上限。

写入侧:拒绝写穿符号链接、保留既有权限位、O_EXCL|O_NOFOLLOW 独占创建临时文件、fsync 后 rename;索引的读-改-写有进程内排队 + 跨进程文件锁,并发保存不丢条目。

测试

npm test
测试 覆盖
test/injection.mjs 注入面为 section + context;工具参数与输出用真实契约
test/config.mjs 导出 Config、Standard Schema 接口、默认值在 schema 上、非法配置加载期失败
test/roundtrip.mjs 写入 → 另起进程读回,逐字符相等;文件形态;重复保存替换;项目隔离
test/isolation.mjs 路径写法归一;同名不同父目录不碰撞;写 A 不影响 B
test/robustness.mjs 符号链接拒绝、权限保留、原子替换、稳定读、路径安全、体积上限、并发不丢条目、exec.signal 传播、raw 工具自校验
test/subsystem-manifest.mjs 递归收集、跳过索引/非 .md/失效 symlink、mtime 倒序、字段集、渲染格式、端口可注入
test/subsystem-extraction.mjs 游标语义、两条跳过规则及优先级、Prompt 要素、调度器 coalescing / 不推进游标 / 关机
test/subsystem-summarization.mjs 拒绝文案、工具白名单、网络/Agent/mcp 拒绝、记忆目录内写删边界、循环轮次与并行 toolCall
test/packaging.mjs 打包产物符合预期、源码不读取仓库内 .md、入口闭包不含开发期文件

端到端手工验证(需要可用的模型凭据):

cd /your/project
dsh --profile headless "把这条教训存进项目记忆:<...>,然后回复 DONE"
dsh --profile headless "这个项目的长效记忆里说了什么?引用索引行原文。"

与 ZCode 的差异

复刻以行为一致为目标,以下差异是刻意的:

差异 说明
保存入口 ZCode 无记忆工具,模型直接用 write 写文件;本插件提供 save_project_memory,指引相应改为引导调用该工具
索引维护 ZCode 由模型手工更新 MEMORY.md;本插件在保存时由代码同步更新,两者不会不一致
索引并发 ZCode 用 expectedRevision 乐观并发,冲突即失败;本插件用排队 + 文件锁,一次保存不会因为别人同时在存另一条记忆而失败
# agentsMd 标题 ZCode 把索引与 AGENTS.md 指令包在同一标题下;DSH 的指令由 dsh-agent-instructions 拥有,本插件不生成该标题
HTML 注释剥离 不引入 marked 依赖,改用围栏感知的保守近似(宁可多留,不可误删正文)
段落排序值 官方 getSectionOrder() 只接受仓库登记的具名项,仓库外插件拿不到,只能自选数值(section 950 / context 100)
工具命名 子系统的工具白名单用 DSH 实际注册的小写名(read/write/edit/bash),ZCode 的大写名在 DSH 上不会命中

未复刻的部分

项 原因
每轮自动抽取 + 后台总结的自动触发 子系统已实现且可用,但默认不挂载。每轮无条件触发会带来 token 成本叠加、并发限流压力与记忆熵增
既有文件的 frontmatter 修复 仅在新建记忆时写入 node_type / originSessionId,不改写既有文件

规范符合性检查报告见 docs/conformance-report.md。

贡献

欢迎提交 Issue 与 Pull Request。

变更记录见 CHANGELOG.md。

License

本项目自身代码采用 MIT。部分实现移植自 zai-org/ZCode(Apache-2.0,Copyright 2026 Z.AI Co., Ltd),因此整体为 MIT AND Apache-2.0。

逐文件的移植对应关系见 NOTICE.md;Apache-2.0 全文见 LICENSE-APACHE-2.0,上游 NOTICE 见 NOTICE-ZCode.md。

—/ 5

No ratings yet

Verified DSH bundle

Commit 88196a573e71

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout