Per-project memory for DeepSeek Harness. Three Markdown files per workspace — PROJECT.md, DECISIONS.md and SESSIONS.md — are auto-created on the first step of any session in that workspace, re-read from disk and re-injected only when they change (and re-injected if compaction dropped them), and written back by classification through a memory_checkpoint tool, with a bounded end-of-turn nudge when a turn did work but recorded nothing. memory_read and memory_search read the live log and the archive, the latter over a zero-dependency BM25 index. The session log is capped at 200 entries with the overflow moved to SESSIONS-archive.md — never injected, but visible, readable and searchable, and restorable one entry at a time. Settings page: a workspace list with a path filter, read-only tabs for the three memory files plus the archive, reload, export/import of one workspace's memory as a JSON bundle, a two-step clear, a panel that rewrites or removes the AGENTS.md instruction block, and a PROJECT.md staleness reminder. A composer status chip shows that workspace's sync state and creates the files on click. Zero dependencies — no vector store, no embeddings, and no model calls.
Install
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:TodayJin/dsh-trilogy
Any plugin you install runs third-party code with your own permissions — it can read your files, use your credentials, and reach the network, and tool approvals don’t sandbox it. GitHub-sourced plugins also run build scripts at install time — pnpm blocks those until you allow them, so an install can stop with ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED or ERR_PNPM_IGNORED_BUILDS; dsh prints the exact key to add under allowBuilds in your profile’s pnpm-workspace.yaml, and the install works on the next run. Allowing a build is a trust decision: only install sources you trust, and pin a commit (github:owner/repo#sha).
README
This plugin publishes its README in Chinese only.
给 DeepSeek Harness 的项目记忆插件 —— 为每个工作区维护三份 Markdown 记忆文件:自动创建、自动加载、自动记录。
记忆不该靠模型自觉。这些行为由 host 插件保证,而不是靠提示词提醒模型:
| 能力 | 只靠提示词/技能 | 本插件 |
|---|---|---|
| 新建项目自动创建三个文件 | ❌ 要人工触发 | ✅ 会话启动时自动 scaffold |
| 每个会话开始加载三个文件 | ❌ 靠模型记得读 | ✅ 自动注入 + 内容摘要去重 |
| 进展分类记录进三个文件 | ⚠️ 靠模型自觉 | ✅ 收尾兜底提醒 + memory_checkpoint 工具 |
「会话冷启动」与「锁定决策」是原生实现,不依赖模型自觉。
三个文件
全部在 <项目根>/memory/ 下。项目根的判定规则:
项目根 = 会话工作目录本身。 插件完全不看版本控制 —— 你打开哪个目录,记忆就写在那个目录的
memory/ 里。工作区有没有 .git 都不影响判定。
若你想要「一个仓库一份记忆、不要每个子包各一份」,把 projectRootStrategy 设成 "marker",
插件才会向上找 projectRootMarkers(默认 .git)。
| 文件 | 职责 | 写入方式 |
|---|---|---|
PROJECT.md |
项目现在是什么 | 就地编辑,控制在一屏内 |
DECISIONS.md |
为什么是这样 | 只追加,最新在最上 |
SESSIONS.md |
发生了什么、什么时候 | 只追加,最新在最上 |
PROJECT.md 的固定五节:这是什么 / 怎么跑和怎么测 / 东西都在哪 / 现状 / 坑。
空节写 暂无。
旧版本写过英文标题(What this is 等)的工作区,下一次被会话打开时会就地改名 —— 只动标题行,正文一个字不动;工具也仍然接受旧的英文节名。
什么才配占一行
对每条候选只问一句:
没有这条,未来的会话会不会浪费时间、或者重犯同一个错误?
不合格的候选是丢弃,不是删短。删除是受限操作:只允许「刚写入的这行直接取代了某一行」的配对替换, 其他看着陈旧的内容会被要求写进报告交给你决定,而不是被静默删掉。
路由表
| 内容性质 | 去向 |
|---|---|
| 改变「项目是什么」或「怎么跑」 | PROJECT.md(就地编辑) |
| 定下来的选择 + 被否决的替代 | DECISIONS.md(顶部追加) |
| 本次会话做了什么、怎么验证的 | SESSIONS.md(顶部追加) |
安装
前置:pnpm 在 PATH 上,DSH 0.1.7-rc.2。
# 从本地目录安装(link,改代码即时生效,适合开发)
dsh plugin --profile web add link:D:/path/to/dsh-trilogy
# 重启 dsh web 生效
卸载:
dsh plugin --profile web remove dsh-trilogy
dsh plugin add 会自动写入 profile 依赖并追加到 dsh.profile.bundles,无需手工编辑。
memory/ 目录属于你的项目,卸载插件不会删除它。
使用
装好后不需要任何操作:
- 在任意项目里开一个会话 →
memory/三个文件和AGENTS.md的 boot block 被自动创建; - 项目还没被描述过时(
PROJECT.md五节全是暂无)→ 注入一条"去调研这个项目并填上"的指令, 模型会读 README / 构建与测试配置 / 入口点 / 目录结构,并真的跑一遍测试命令,然后填PROJECT.md。 填完就不再提; - 之后每个会话开始 → 三个文件自动注入上下文(内容没变则不重复注入,KV cache 友好;
内容变了不重发整块,只在末尾追加一行「记忆已更新,需要时用
memory_read」—— 改写历史中段会让 prompt cache 从那里到结尾全部失效,实测贵 93 倍)。 注入块开头永远带一行账目:注入了什么、没注入什么、三个文件实际多大 —— 成熟工作区会先撞到天花板,账目不能等到丢东西才出现; - 一个工作区像一个抽屉,里面往往塞着好几件事 —— 所以日志条目可以带一个区域标签(
memory_checkpoint的area),注入的名额就按区域分:每个区域保底 1 条(聊什么都能看到一点),其余按最近活跃度加权(Σ 0.5^(条目年龄/14天),「最近干得多」的多拿),但单个区域不超过公平份额的 2 倍(热门区域不能把别的挤没)。区域是粗粒度闭集:最多 4 个,第 5 个和留空的一起归入通用—— 宁可粗,不要碎; - 会话干了实事却没记录 → 插件先欠着,这条提醒会在你的下一条消息那一步随它一起出现,由主模型自己判断该不该记。它不会在当前回合末尾多逼出一条回复、也不会自己开一个回合(那会把「记下了」变成一回合的输出,真正的回答被折叠到中间)。
初次填充(bootstrap)
模板不等于项目说明。插件把"先调研再写"这一步也自动化了 ——
scaffold 出空模板后,只要 PROJECT.md 还是空的,就注入一条 bootstrap 指令,要求:
- 读 README、构建与测试配置、入口点、目录结构;有测试命令就真的跑一遍并记录是否通过;
- 用
memory_checkpoint填PROJECT.md五节 —— 每条论断都必须来自读过的文件或跑过的命令; - 补一条真实的
SESSIONS.md记录和已定的DECISIONS.md条目。
填完后 bootstrap 自动消失(靠内容判断,不需要额外状态)。用 bootstrapWhenEmpty: false 关掉。
手写了自己标题格式的
PROJECT.md不会被判定为"空",因此不会被反复催。
输入框状态图标
对话框左下角(conversation.input.left 座位)常驻一个小指示器:
[图标] 正在记录 · 刚刚
| 阶段 | 显示 | 触发时机 |
|---|---|---|
recording |
正在记录 | memory_checkpoint 正在写文件 |
updating |
正在更新 | 长时间写入进行中 |
done |
更新完毕 | 刚写完(约 8 秒后自动回落为「已同步」) |
idle |
已同步 | 无进行中的写入 |
后面跟的是最近的同步时间(刚刚 / N 秒前 / N 分钟前 / N 小时前 / N 天前)。
悬停显示完整信息:状态、最近同步、工作区路径、写入的文件。
数据来自宿主时钟 —— 响应里带一个 now 字段,所以浏览器不需要相信自己的时钟。
客户端每 4 秒轮询一次 GET /trilogy/status;宿主不可达时保留最后一次读数。
图标用 UI primitives 的
IconLoadingOutline16/IconRefreshOutline14/IconCheckOutline14/IconDatabaseOutline16;若该模块缺少对应图标, 自动回退成一个会随状态变色的圆点。
图形设置界面
插件带一个浏览器半边,在 设置 → 项目记忆 里:
| 功能 | 说明 |
|---|---|
| 看记录 | 左栏选工作区(可按路径筛选);右栏顶部写明选中的是哪个、什么状态、最近什么时候动过 |
| 看内容 | 页签按文件的用途命名:现状 / 决策 / 日志 / 归档,右侧标出对应的文件名与字节数 |
| 清除 | 删掉该工作区的全部记忆文件(含 SESSIONS-archive.md),并撤回 AGENTS.md 里的 boot block。注册表条目保留 —— 下次在该工作区开新会话会重新创建空文件 |
| 忘记 | 只在「已清除」的行上出现:把这条工作区从面板列表里去掉。只动注册表这一条索引,磁盘上什么都不碰 —— 之后在该工作区开一次会话,它会被重新登记。注册表只是索引,所以列表会一直记住曾经有记忆的工作区;这个按钮就是唯一的出口 |
| 只读查看 | 四个记忆文件在面板里都只读:写入交给会话里的 memory_checkpoint(或导入记忆包),界面里唯一能编辑的是下面的指令文件 |
| 重新读取 | 放在页签条里、紧挨 归档 左边 —— 它重新读取的正是这些页签选中的那个文件 |
| 搜索筛选 | 工作区多时按路径过滤 |
| 指令文件 | 显示 AGENTS.md 里 Memory 段的状态(已写入 / 旧版本 / 未写入 / 文件不存在)。可重写或移除这一整段 —— 只摘这一段,文件其余内容原样保留;也可以编辑整份文件,整份替换,Memory 段也在里面。注意:移除(或在整份编辑里删掉)这一段后,该工作区下一个新会话会把它写回;要永久关闭请设 writeBootBlock: false |
| 陈旧提醒 | PROJECT.md 落后于其余记忆文件超过 projectStaleDays(默认 14 天)时,在面板顶部给出横幅,并说明这期间追加了多少条日志 |
| 归档与恢复 | 归档页签逐条列出被搬走的会话记录,点「恢复这条」把它搬回活日志顶部。归档条目只读,要改就先恢复回日志,再让会话改写。归档和活日志一样最新在最上 —— 每一轮搬出去的批次都头插到归档正文最前 |
| 导出 / 导入 | 把一个工作区的 memory/ 导出成单个 JSON 记忆包,在另一台机器或另一个工作区导入。导入会覆盖同名文件,且只接受本插件导出的包(按 kind 校验) |
| 危险操作单独一行 | 「清除记忆文件」被移到最底部、与常用按钮分开,并且要连点两次(第二次是「确认清除」,旁边写明会删掉什么)才真的执行 |
工作区列表在打开设置页时从活会话回填,所以历史项目(在新功能之前建的 memory/)也会出现。
写入受限:清除、初始化、保存都是写操作,而裸 webServer 路由本身没有鉴权。保存 的
白名单按工作区解析 —— 只允许那三个记忆文件,加上该工作区自己注册的指令文件,别的一律拒绝
(面板只在指令文件上提供编辑器,但宿主这层白名单没变)。因此
整个 /trilogy 前缀被限制为本机访问(非 loopback 返回 403)。
插件完全不调用模型 —— 没有向量检索、没有 embedding、没有后台蒸馏。
PROJECT.md的初次内容由会话里的模型自己去读文件、跑测试命令后写入。
模型侧工具
| 工具 | 作用 |
|---|---|
memory_checkpoint |
按路由表分类写入。参数:sessions[](每条可带 area 区域标签,见上)/ decisions[] / project[] / notes |
memory_read |
按需读取某个记忆文件(三个文件默认已自动加载;也含 SESSIONS-archive.md) |
memory_search |
零依赖 BM25 检索,连归档一起搜 —— 注入预算之外的内容也找得回来 |
日期由插件从系统时钟盖戳,不靠模型记 —— 让模型自己读系统日期是不可靠的。
收尾兜底(nudge)
- 不额外调用模型 —— 复用当前会话里已在跑的主模型,token 开销只是一条短提醒
- 只在「这轮干了实事」且「这轮没记录任何东西」时触发
- 欠着,等你的下一条消息再一起送出 —— 不在回合末尾投递、也不会自己开一个回合;提醒会和你的下一条消息在同一个步骤里被模型看到,所以本回合的收尾仍然是你真正的回答,产物行、回合导航这类「以回合末条为准」的界面不会被那句「记下了」抢走
- 有冷却时间与每会话次数上限,无事发生的轮次不打扰
- 「值不值得记」由主模型判断(它上下文最全),插件只负责保证它一定会被问一次
配置
在 profile 的 cordis.patch.yml 里按 id 覆盖,例如:
- id: trilogy
config:
injectBudgetBytes: 24000
nudgeMaxPerSession: 5
| 键 | 默认 | 含义 |
|---|---|---|
enabled |
true |
总开关 |
memoryDirName |
"memory" |
记忆目录名(相对项目根) |
projectRootMarkers |
[".git"] |
项目根标识 |
autoScaffold |
true |
缺文件时自动创建 |
writeBootBlock |
true |
往 AGENTS.md 追加 Memory 段 |
bootBlockFile |
"AGENTS.md" |
boot block 写进哪个文件 |
injectOnSessionStart |
true |
会话开始自动注入 |
bootstrapWhenEmpty |
true |
PROJECT.md 还空着时,注入"去调研并填上"的指令 |
sessionsMaxEntries |
200 |
SESSIONS.md 超过这个条数才把最旧的搬到归档(阈值定得高,避免过早压缩) |
projectRootStrategy |
"workspace" |
workspace = 工作区即项目;marker = 向上找 .git |
injectBudgetBytes |
160000 |
注入总字节预算。是「停止降级的天花板」,不是配额 —— 装得下就立刻返回。实际生效值还会按路由窗口封顶:min(配置值, 窗口 × 16%),所以小窗口的模型不会被塞一个吃掉它上下文的块(128K 窗口 → 约 20 KB)。实际注入量 = PROJECT.md 全文 + DECISIONS.md 全文 + 最近 sessionEntriesInjected 条日志;SESSIONS.md 全文永远不注入 |
sessionEntriesInjected |
15 |
注入最近几条 SESSIONS 条目 —— 这个键才决定有多少日志进上下文(SESSIONS.md 全文永远不注入)。15 条约占 45 KB ≈ 11k token |
nudgeOnTurnEnd |
true |
收尾智能判断兜底 |
nudgeCooldownMs |
600000 |
兜底提醒冷却(10 分钟) |
nudgeMaxPerSession |
3 |
每会话兜底提醒上限(成功写入一次就清零,长会话不会因为额度用完而沉默) |
projectStaleDays |
14 |
PROJECT.md 落后其余记忆文件多少天才提示陈旧;0 关闭提示 |
模板可改:三个文件和 boot block 的模板在 templates/,运行时直接读取;目录缺失时回退到内置副本。
注入预算的取舍顺序
超预算时按此顺序降级(PROJECT.md 是「当前状态」,优先级最高):
- 完整注入三个文件;
- 日志条目逐档减半(15 → 7 → 3 → 1),而不是一次丢掉整份日志;
- 连一条也装不下时,才丢掉
SESSIONS.md的全部条目; - 再不行只保留
DECISIONS.md头部; - 最后才硬截断
PROJECT.md(并标注[已截断以适配注入预算])。
每一步都会往注入块末尾追加一句说明,写明这次省掉了什么 —— 模型知道自己看到的不是全貌。
开发与测试
node test/smoke.mjs # 74 项,宿主半边
node test/client-render.mjs # 9 项,浏览器半边:每个组件都真的渲染一次,并检查注入的样式表
node test/client-interact.mjs # 29 项,浏览器半边:点击 → 请求 → 状态 → 重渲染
smoke.mjs 用假 ctx 驱动真实的 apply()。两个浏览器套件用 vm 沙箱跑 bundle:
渲染套件用一个 React 桩同步调用每个注册的组件(组件引用未声明的变量会被
SlotErrorBoundary 静默吞成空白页,渲染套件把那片沉默变成一条失败测试);
交互套件换成一个会真正重渲染的迷你 React 运行时,配一个会把每个请求体当 JSON 解析
的假宿主 —— fetch 会把普通对象悄悄变成 "[object Object]",只有真解析才拦得住。
改了
lib/client.js就必须跑两个客户端套件,并重启dsh web。 客户端 bundle 在 启动时快照,base bundle 里hmr是disabled: true。
test/与node_modules/不在发布文件清单里。本地跑测试需要node_modules/@deepseek-ai能解析到 DSH 的包(开发时用 junction 指向 DSH 安装目录即可)。
已知边界
- 不做:向量检索、embedding、网络请求、后台服务、修改 DSH 核心
- 写入用
node:fs直连,不走 harness 的ctx.fsseam —— scaffold 必须在任何 provider 下都一致工作; seam 的 resolve/write 契约是为沙箱化的工具执行设计的 - 与
AGENTS.md共存:AGENTS.md管「该守什么规矩」,三个文件管「项目是什么、发生了什么」 DECISIONS.md允许模型写入,但要求在条目里写明被否决的替代与约束(没有这些就不该写)
参考
- 设计说明:
DESIGN.md
Links
More in this category
vectorize-io/hindsight#coding-agents★ 58107
Hindsight, agent memory that learns: long-term project memory with auto recall and retain, knowledge pages, deep reflection, and per-repo memory banks.
volcengine/OpenViking#examples/dsh-memory-plugin★ 41402
OpenViking memory and context bundle for DeepSeek Harness: pre-step auto-recall and profile injection, session capture, `viking://` URI guarding, and recall/write memory tools backed by an OpenViking server.
MemTensor/MemOS#memos-local-plugin★ 12229
Local-first memory plugin for DeepSeek Harness: layered L1/L2/L3 memory with automatic capture, manual memos_* retrieval tools, and a built-in memory viewer.
agentscope-ai/ReMe#dsh★ 3742
Connects DeepSeek Harness to ReMe's local-first, self-evolving personal knowledge base: automatically captures completed main-agent conversations as user-owned Markdown memory, searches conversations and source material through reme_search with BM25, optional embeddings, and wikilink expansion, and schedules daily memory consolidation.
zilliztech/memsearch#MemSearch★ 2816
Shared Markdown memory for DSH and other coding agents, with automatic capture, pre-step context injection, searchable recall, and memory-to-skill self-evolution through a review panel.
vshulcz/deja-vu#extensions/dsh★ 1375
Reads the session files forty-two other coding agents on this machine already wrote (Claude Code, Codex, Cursor, VS Code Copilot Chat, opencode, OpenClaw, Hermes, Kimi, Cline, Zed and more), including sessions from before it was installed: six tools (deja_recall, deja_session, deja_blame, deja_fix, deja_how, deja_remember), a /deja command, and optional automatic recall added to the runtime context. Local BM25 index, no LLM, no embeddings, no network (dsh plugin --profile web add dsh-deja).
Community comments
Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.