dsh-project-memory v0.3.0到v0.4.0:从项目记忆到开发工作流记忆的演进

上一篇《给 dsh 装上项目级长期记忆:dsh-project-memory 设计实录》主要讲了 dsh-project-memory 的设计出发点与整体架构,基于 v0.3.2 版本。

本文基于 v0.4.0,经过三个小版本的迭代,dsh-project-memory 已发展为一个完整的项目级记忆插件,对项目开发的帮助更显著。

本文主要写一些遇到的缺陷,设计的方案,实际开发的取舍及方案落地的效果,按时间顺序来写。

导航

  • [v0.3.3 --- 符号精简(-30%)、盲区标记、内容哈希](#v0.3.3 — 符号精简(-30%)、盲区标记、内容哈希)
  • [v0.3.4 --- 检索 20x(187ms → 9ms)](#v0.3.4 — 检索 20x(187ms → 9ms))
  • [v0.4.0 --- TaskBridge 跨会话任务续接](#v0.4.0 — TaskBridge 跨会话任务续接)

v0.3.3:文档层的记忆信息不够,如何去防止记忆对模型的误导

v0.3.2 的存储主要分三块:基于文档的摘要、基于代码文件的索引、基于会话的经验笔记。前两者存在问题如下:

摘要型记忆的天然缺陷

一个 README 文档,比如 20KB,插件调用 LLM 生成摘要时,输出可能是这样的:

dsh-project-memory 是为 DeepSeek Harness 提供持久化项目记忆的插件。它通过"读到即索引"机制,在模型读取文件时自动记录文档摘要与代码符号,支持跨会话检索与任务续接。核心功能包括文档/符号/经验/任务四层记忆,存储基于本地文件,无需外部数据库。

降级时则是文档的前若干字。

可以看到,摘要非常简洁和概括,对文档的内容其实是没有完全覆盖的,本质上是在做一个有损压缩。README 里的具体特性、架构设计、安装步骤、配置说明等内容,全部被压缩成几句话,甚至完全没提到。

当然,模型可能不会依赖摘要,而是直接去读 README 原文。但这是建立在模型已有"README 是项目介绍文档,通常存在"的隐性知识上的。换成一般的文档,摘要的片面性就不能忽视了,由此产生的对模型的误导也不能忽视。这也是记忆类插件常有的,或者说摘要型记忆的天然缺陷。

代码符号中的 summarysig 字段

对代码文件建立的索引,主要是在做了源码扫描器、类上下文,多行签名等处理后,用正则去在干净的代码文件中提取信息。v0.3.3 引入了 summary(一句简短的描述)和 sig(结构化函数签名),v0.3.2 只有 titletext(截断)、keywords

  • text:截断的代码片段
  • summary:一句简短的描述
  • sig:结构化的函数签名

summary 的问题有两层:

  1. 摘要本身的片面性 :和文档摘要的问题一样, summary 是模型对代码的"解读",不保证准确,信息不全也是误导。
  2. 信息重复summary 确实有用,但是一是不会因为模型看了 summary,代码文件的内容就不用进入上下文,二是模型上下文中有该文件信息后,summary 本质上就重复了。

记忆插件在上下文积累的过程中,慢慢变成了拖累。

至于 sig 结构化字段,在跨语言、极端语法下不可靠。

方案一:符号层重构:只存事实信息,不存知识,减少模型重复的思考

针对代码符号层的问题,确定了几个方向的方案:

方案 存储体积 解析速度 跨语言一致性 可维护性 评价
保留 summary + sig 结构化 差(每语言特化) 不是记忆
只存函数名 + 文件路径 最小 最快 信息太少,记忆作用小
完整声明行 + 位置 中(-30%) 好(统一格式) 信息适中
tree-sitter AST 全量解析 大(+300%) 启动 +500ms(估计) 差(每语言 WASM) 依赖重,体积大
LSP 按需调用 中(需启动进程) 差(每语言独立) 配置复杂,体验割裂
向量检索 + RAG 混合召回 大(+300%) 成本高,边际收益低
全量函数摘要 + 语义缓存 慢(LLM 调用) 成本爆炸,性能差

方案说明

只存函数名 + 文件路径:最轻的方案。模型拿到一个函数名,不知道它的参数类型和返回类型,无法判断这个函数是否和自己当前的任务相关,必须额外读文件才能做决策。信息太少,省掉的成本转移到模型。

完整声明行 + 位置 :最终方案。存函数声明行原文(function createUser(dto: CreateUserDTO): Promise<User>),不加任何解释。完整签名直接可用,附带行号避免模型滚动读取,统一格式跨语言一致,随代码变更自然更新,不需要额外维护。模型拿到后能直接决定"读不读这个文件"。信息适中,可决策,不腐烂。

tree-sitter AST 全量解析:为 7 种语言分别引入 tree-sitter 语法解析器,构建完整 AST。每个语言需维护独立的 grammar 文件,WASM 包体积 ~1--2 MB/语言,冷启动从 80ms 增加到 500ms+。跨语言解析需额外维护调用图,复杂度极高。20k 条目的全量索引时间会变成数秒,而且增量更新时仍然要解析整个文件的 AST。这会影响 watch 轮询和 lazy indexing 的响应速度。模型不用去理解完整 AST,在项目开发中它需要的没有那么多,也不用重复告诉它已知的琐碎知识。之前正则已经覆盖 90%+ 场景,且可选的 TS Compiler API 增强以一种针对性的、可配置的、轻量的方式减少了它的价值。

LSP 按需调用 :启动语言服务器(typescript-language-serverpyright 等)通过 stdio 通信获取类型信息。每个语言需启动独立进程,内存占用大,需处理语言服务器版本兼容性,用户需自行安装语言服务器。配置复杂,不适合作为默认路径。

向量检索 + RAG 混合召回 :对摘要和符号做 embedding(如 bge-small),查询时用向量相似度 + BM25 融合排序。需要嵌入模型(本地推理或调用 API),向量库需持久化,存储体积从 6.7 MB 增加到 50 MB+。与"查询零 LLM、零额外延迟"原则冲突,实测 BM25 + CJK 增强已到 90%+ 召回率,边际收益极低。成本高,收益低。至于近似词查询方面,模型不是用户,在 v0.4.0 实现工作流记录,任务和文件交叉链接后,收益更低。

全量函数摘要 + 语义缓存:索引时对每个函数调用 LLM 生成摘要,存入记忆,查询时直接返回摘要。索引期 LLM 调用次数 = 符号数(5k 文件 ≈ 30k 符号),成本爆炸。摘要会随代码变更而过期,需维护一致性,成本爆炸,不可持续。同时并没有解决之前的问题。

最终决策

维度 重方案 最终选型
符号提取精度 高(AST/LSP/向量) 中(正则 + 可选语言增强(TS))
存储体积 大(+300%~1000%) 适中(-30%)
启动开销 大(+500ms~数秒) 小(+0ms)
维护成本 高(每语言/每版本) 低(统一正则)
依赖 多(tree-sitter/LSP/embedding) 单(pdfjs-dist
跨语言一致性 差(每语言需适配) 好(统一格式)

记忆只要能让模型判断"这个文件我是否需要读"就达到效果了,不需要提前把所有信息都分析好存起来。理解是模型的事情,索引只负责提供足够模型决策的事实。同时要考虑建立、查询的性能和成本。

重构后的单条符号

json 复制代码
{
  "title": "createUser (function)",
  "text": "function createUser(a: CreateUserInput, b: Options): Promise<User> --- user.ts:42",
  "type": "symbol",
  "sourcePath": "src/user.ts",
  "sourceLine": 42
}

v0.3.4:检索 20x 提升(187ms → 9.3ms)

痛点query_memory 在 5k 文件/20k 条目场景中位数 187 ms。每次查询都要:

  1. 遍历所有 entry 做 tokenizeRaw() → 建 TF 表
  2. 现场拼接 title×5 + keywords + summary + pathtoLowerCase()
  3. 重新计算全库 IDF

三层优化叠加

js 复制代码
// 1. IDF 缓存:store._version + _idfCache
// 写入时 version++ 标记失效,查询时版本命中直接复用
if (store._version === queryVersion && store._idfCache) {
  return store._idfCache  // 零计算
}

// 2. 预计算 searchText:setEntries 时一次性算好
entry.searchText = (entry.title || '').repeat(5) + ' ' + 
  (entry.keywords || []).join(' ') + ' ' + 
  (entry.summary || '') + ' ' + entry.path
// 查询时直接用,省掉拼接 + toLowerCase()

// 3. 流式打分:rankEntriesStreaming 单次遍历
// countOccurrences() 字符串计数替代完整 tokenizeRaw + TF 表
// 零中间对象分配,GC 压力归零

实测数据(Node 24, Linux, SSD)

场景 v0.3.3 v0.3.4 提升
5k 文件 / 20k 条目(首次查询,含 IDF 建缓存) ~187 ms ~150 ms -
5k 文件 / 20k 条目(缓存命中) ~187 ms 9.3 ms 20x
1k 文件典型项目(缓存命中) --- <1 ms ---
冷启动加载 82 ms 82 ms 持平

为什么不做增量 IDF? 全量重算 20k 条目只需 ~140 ms,且只发生在写入后的第一次查询。增量维护要处理词项删除、文档数变化、倒排表更新,代码量 10x、极易引入 Bug。版本号 + 全量重算的「办法」相对最稳。


v0.4.0:TaskBridge 跨会话任务续接

场景痛点

  • 语义断层:会话压缩后,模型通过插件记忆重新获得了项目的事实信息(文件结构、符号位置、文档摘要),但查询时使用的关键词来自新会话的模糊描述,与旧会话中的精确术语存在偏差,导致召回结果偏移甚至错位------记忆层存的是事实,但模型问的是描述。

  • 开发状态不可恢复:新会话恢复后,模型可以读文件、看符号、查摘要,但它不知道"上次做到哪了""哪些文件已经改过了""当前任务涉及哪些模块"。这些信息不是文件里写的,是开发过程中积累的理解,插件没有记录它。

  • 记忆膨胀稀释任务边界 :随着项目索引范围扩大,query_memory 返回的条目越来越多。模型拿到 100 个相关文件,却不知道当前任务只需要其中 5 个。记忆的覆盖面变大了,但对"当前任务"的精确性反而下降了。

这三个问题指向同一个缺口:插件存了项目的事实,但没有存工作的上下文。 TaskBridge 的目标就是补上这个缺口------把开发过程中的任务状态、文件范围、进度信息沉淀为可跨会话恢复的记忆。

设计方案

方案 A:宿主主动订阅 session/event

宿主监听 session/event 事件,自动从 todo_writetool/call 中提取任务清单和文件关联。模型无需额外操作,零负担。续接时 list_tasksselect_task 即恢复任务进度与文件范围。缺点是依赖宿主支持 session/event 事件(dsh 0.1.2-alpha.x 实测支持),旧宿主自动降级为手动模式。

方案 B:模型显式 remember

模型每次更新任务清单时主动调用 remember(kind: 'tasklist') 存进度。好处是不需要宿主改动,在旧版本 dsh 上也能工作。坏处是模型在新会话中不一定记得调用 remember,且任务与文件的关联需要模型主动在调用时传参,模型可能漏传或传错,导致续接时文件范围缺失。依赖模型行为,可靠性不足,且无法关联工具调用产生的文件变更。

手动 remember

用户或模型在任务完成时手动调用 remember(problem, solution) 记录经验。与 TaskBridge 不冲突,作为任务完成后的归档补充。无法解决"会话中断后续接"的问题,因为用户/模型在开发过程中不一定记得主动记录。

正则提取会话记忆

记录聊天历史,全自动但无法区分任务与闲聊。续接体验差------历史消息不等于任务进度。同时提取对话效果非常依赖关键词。

最终决策

方案 记录内容 自动化程度 续接体验 成本 评价
会话记忆类插件 聊天历史 全自动 差(聊天≠任务) 方向不对
手动 remember 人工摘要 手动 高(易忘) 用户成本高
模型显式 remember 任务清单文本 半自动(模型调用) 中(无文件关联) 模型可能遗忘调用,无文件链接
宿主主动订阅 session/event 任务清单 + 读写文件 全自动 优(list→select 即接回) 选这个

宿主主动订阅方案不需要模型做任何额外操作,任务和文件的关联由宿主事件自动建立,续接时精确恢复任务上下文,最终被选中。模型显式 remember 的不可靠性是被否决的核心原因。

核心机制:事件订阅 + 自动沉淀

事件源(session/event,签名 (session, event)

事件类型 触发条件 处理逻辑
todo/write 模型调用 todo_write 绑定任务 steps 快照整体覆盖(全量替换);未绑定会话自动新建任务并绑定
tool/call 模型调用 read/write/edit/read_image 绑定任务 files 并集(归一化相对路径、项目外拒绝、上限 100)

关键设计

  • todo_write 无 id、全量替换 → steps 只能整体覆盖,不做步骤级映射
  • tool/callarguments 是 JSON 串 → 解析出 path,归一化为相对路径
  • 子代理会话无法可靠判定 → 接受其自动建档(低频)

任务实体结构

json 复制代码
{
  "id": "task_abc123",
  "title": "重构认证模块",
  "steps": ["拆分 AuthService", "迁移 JWT 逻辑", "补测试"],
  "files": ["src/auth/service.ts", "src/auth/jwt.ts", "test/auth.test.ts"],
  "createdAt": 1725200000000,
  "updatedAt": 1725200000000,
  "lastActiveAt": 1725200000000,
  "archived": false,
  "sessionId": "sess_xyz"
}

标题由模型定

dsh中模型创建任务清单时无任务名,自动扫描提取的话导致任务名奇怪(参考会话名)

  • select_task(title=任务名)先命名再写 todo------模型自己决定任务叫什么
  • 自动回退:取用户消息最后「:」后的任务段(截断 48 字)
  • 续接可 select_task(taskId, title) 改名

用户交互:三个工具 + 一个命令

工具/命令 作用 关键行为
list_tasks 列出项目任务记录(归档标记) 新会话首调,建立上下文
select_task 绑定会话到任务 taskId 精确、自动解归档;title 完全匹配/多候选/无则新建;带 title 可改名
archive_task 归档任务(隐藏、不计容量、停止同步) select_task 可恢复
/tasks(用户输入) 显示任务栈:标题、步骤进度、涉及文件、当前会话绑定 Handler 不经模型,直接渲染

/tasks 输出示例

复制代码
 任务列表 (3 个,1 个进行中)
┌─ task_abc123 [当前会话] 重构认证模块 (2/3 步骤)
│  📁 src/auth/service.ts  src/auth/jwt.ts  test/auth.test.ts
├─ task_def456 [归档] 修复登录闪退 (3/3 步骤)
│  📁 src/auth/login.ts
└─ task_ghi789 新增 OAuth2 支持 (0/2 步骤)
   📁 (暂无文件)

检索集成:任务也变成可搜索的记忆

js 复制代码
// query_memory 新增 type
type: 'task'   // 检索 title/steps/files
type: 'all'    // 结果尾部附一行: 另有 3 个任务记录,可用 list_tasks 查看

设计意图 :模型搜认证模块时,除了文档、符号、经验,还能召回相关任务------任务关联文件,文档、代码、笔记交叉链接


存储:独立于 format v2 的双文件

复制代码
.dsh-project-memory/
├── tasks.json     # 任务实体数组
└── binding.json   # { sessionId: taskId } 会话绑定
  • 独立于 format v2 布局:load 兜底空值,不影响主存储升级
  • 容量自适应fileCount/20 clamp 5, 100,超限按 lastActiveAt 归档最旧
  • 脏标记驱动:未变更不落盘

环境要求与降级策略

环境 行为
dsh 0.1.2-alpha.x (含 session/event + todo_write) 全功能自动同步
旧宿主 (无 session/event 或不触发) 降级 :任务工具仍可作纯记录使用(手动 list_tasks/select_task/archive_task/tasks 命令仍工作)

不强制升级宿主------插件在缺事件时自动降级为手动任务管理器,不阻塞用户。


结语

v0.3.x 系列把项目记忆这个基座做完了:快、准、轻、可验证。v0.4.0 在基座上加了工作流记忆这一层:把 Agent 的日常动作(写 todo、读文件)自动沉淀为可续接的任务实体。

如果说 v0.3.x 回答的是"模型知道项目里有什么",那 v0.4.0 回答的就是"模型知道自己正在做什么"。

如有错误的地方,欢迎指出。

仓库https://github.com/00080000/dsh-project-memory

npmhttps://www.npmjs.com/package/@yolk_vat-y/dsh-project-memory

awesome 列表https://awesome-dsh-plugin.com/zh/p/00080000/dsh-project-memory/

相关推荐
敢敢是只喵i23 分钟前
给现有业务系统接 AI 助手,入口怎么选?网页聊天、自动化任务与本地 Agent 对比
运维·人工智能·ai·开源·自动化·安全架构
吴佳浩1 小时前
32GB 显存,凭什么跑 56GB 大模型?从 Shared Memory 到 AI 异构内存架构
llm·agent·nvidia
吴佳浩1 小时前
为什么每个人最终都会使用 Agent?从 LLM 到 Agent,看懂 AI 为什么一定会走向执行时代
llm·agent·mcp
ddshub_cc1 小时前
GPT Image 2 电商场景指南:主图、详情页与活动物料怎么出
gpt·ai·image2·ai生图·电商ai·电商工具·电商生图
MinggeQingchun2 小时前
AI - 阿里云百炼
ai·阿里云百炼
章老师说2 小时前
BFE v1.8.6 正式发布:AI 网关计费精细化、Claude 协议与会话亲和性升级
运维·人工智能·ai·负载均衡·ai-native
ai小陈2 小时前
FramePack图生视频云端部署实战:从单图输入到视频输出的完整流程
服务器·人工智能·安全·ai·音视频·gpu算力
Mintimate2 小时前
Codex 多账号切换不再折腾:OAuth 配对与 Auth 迁移实践
agent·ai编程
长沙京卓2 小时前
Copilot Coding Agent 变了:AI 编程正在从插件变成项目成员
人工智能·ai