dsh-task-memory
Manifest validDeepSeek Harness Plugin: Workspace-level task memory — store completed tasks as memory cards so you don't have to re-analyze them in the next session.
dsh-task-memory
DeepSeek Harness 插件:工作区级任务记忆。
解决的问题:相似的活会反复出现,而每次会话都从零重新分析一遍。这个插件把"做过的任务"存成一人一张的记忆卡,并且让卡片以两段式被检索——先看索引,命中后才读那一张的正文,所以记忆再长也不需要每次全读。
两段式检索
| 阶段 | 机制 | 成本 |
|---|---|---|
| 索引 | 一张有界的卡片摘要清单,注入到会话(一行一卡:名字 + 描述 + 触发词) | 常驻,且按使用频率排序、有硬上限 |
| 正文 | 命中后用 task_memory_load 取那一张卡的正文 | 一次调用,只读一张 |
索引不进对话历史,也不按轮次重复;其余卡片用 task_memory_index / task_memory_search 发现。记忆再长也不需要每次全读。
上架过的卡片(见下)会额外出现在 Harness 的技能目录里,可以用内置 skill 工具加载——但那是可选的,不是索引机制本身。
数据布局
所有工作区共用一个 SQLite 数据库:$DSH_HOME/task-memory/memory.db(默认 ~/.dsh/task-memory/memory.db)。表内用 workspace 列隔离各工作区。
meta (key, value) ← schema 版本等
cards (workspace, name, description, when_to_use, triggers, tags,
status, published, revision, created, updated, body, hits, last_used)
assets (workspace, card, path, bytes) ← 外键级联,随卡片一起删除
用 node:sqlite(Node 内置)实现,零第三方依赖、无需编译。本机 dsh-memento 已在用同一机制,所以 Electron 宿主也支持。
数据库相对文件存储买到的三个性质:
| 性质 | 说明 |
|---|---|
| 原子性 | 卡片与附件在同一事务里写入,崩溃不会留下"有附件没卡片" |
| 唯一身份 | 卡名是主键列,"一件事只留一张卡"是 schema 保证,不是靠代码反复检查 |
| 计数不漂移 | hits/last_used 是卡片行上的列,不可能与卡片失联 |
索引仍然永远从行数据现算,不存索引表;## 变更记录 段落承担版本历史,所以也不需要 journal。
工具
| 工具 | 作用 |
|---|---|
task_memory_index | 完整索引,可按 query / status / tag / tier 过滤。索引被上限截断时兜底 |
task_memory_load | 按名字取一张卡的正文、元数据与附件清单 |
task_memory_asset | 读取某张卡的某个附件(代码片段、补丁等,存于数据库) |
task_memory_save | 落卡。默认 auto 模式会拒绝为已记录的任务再建一张卡,并返回候选卡 |
task_memory_search | 跨卡片正文全文检索(带片段),触发词都没命中时兜底 |
新鲜度分档
记忆库有两种"旧":内容过时(status: verified/draft/stale,人工判断)和久未使用(档位,自动按时间算)。两者正交——一张卡可以既"已确认"又"遗忘"(内容正确但一年没用了)。合成一个字段就再也说不清用户指的是哪种。
五档(天数可配置)
| 档位 | 默认距离 | 在注入索引里的呈现 |
|---|---|---|
| 近期 | ≤ 7 天 | 名字 + 描述 + 触发词 + 年龄 + 使用次数 |
| 之前 | 8–30 天 | 同上 |
| 很久之前 | 31–90 天 | 名字 + 描述(去掉触发词)+ 年龄 |
| 远古 | 91–365 天 | 只有名字,合并成一行 |
| 遗忘 | > 365 天 | 不列出,只报一行数量 |
这就是"检索频率不同":越旧的卡越不容易出现在模型眼前,但一次也没丢。
时间基准:max(updated, lastUsed)
- 刚改写过的卡 = 知识新鲜 → 算活跃
- 刚被检索过的卡 = 仍被需要 → 算活跃
取较新者。只用 updated 会让"写完再没碰过"的卡一直假装新鲜;只用 lastUsed 会把刚创建、还没被检索过的卡判成遗忘。两者都是 cards 表上的列,所以不可能与卡片失联。
遗忘 ≠ 删除
遗忘档只做三件事:不注入、面板可见并标记、工具仍能检索到。行数据一个字段都不动——一个会自己删记忆的记忆插件比一个拥挤的更糟。
想清理时用面板上的「清理遗忘」按钮:它先列出所有遗忘档卡片让你确认,再删除指定名字的那些。没有自动清理。
档位边界写进配置(tierDays),随时可改;改完不需要迁移任何数据,因为档位永远是算出来的,不存进文件。
筛选
面板顶部有两个筛选器:
| 控件 | 作用 |
|---|---|
| 档位下拉 | 默认(按配置,通常只看近期)/ 全部档位 / 仅近期 / 仅之前 / 仅很久之前 / 仅远古 / 仅遗忘 |
| 起止日期 | 按最后活动日期过滤;范围默认取真实数据的最早~最晚,可改 |
| 日期条 | 每个「有卡片的日期」一个可点标签(显示月-日与张数),点一下就把范围收到那一天 |
日期条是必需的补充:原生 <input type="date"> 的日历弹层同样由 Chromium 绘制、CSS 碰不到,而用日期选择器排错时最关键的信息是"哪天有记忆"。所以那个信息放在选择器旁边,而不是指望日历里能标出来。
档位筛选在宿主侧执行(不是浏览器里过滤):列表本身有上限,本地过滤会把本该被查询捞出来的卡藏掉。
工具 task_memory_index 也接受 tier 参数,含义与面板一致。
设置页
「设置 → 任务记忆」区块,改完保存立即生效(不需要重启):
| 设置 | 说明 |
|---|---|
| 面板默认显示哪些档位 | 打开面板时默认勾选的档位;默认只看近期 |
| 档位边界(天) | 四个边界,改完立刻影响分档 |
| 注入索引的卡片上限 | 索引里最多列多少张 |
| 单张卡正文上限 | 超出截断并给出数据库位置 |
| 回合结束自动落卡 | 关掉后只能由你明确要求才记录 |
设置存在哪里:$DSH_HOME/task-memory/settings.json(默认 ~/.dsh/task-memory/settings.json)。
优先顺序是 设置文件 > cordis.patch.yml 里的 config > 内置默认。设置文件赢,因为它代表你最后一次在界面上做的选择;loader config 仍然是新安装的种子值。
打开设置页会补齐缺失的参数。 页面上每个参数都显示它的有效值,而一个"没有记录"的参数是无法在界面里改的——所以读取设置时会把本地文件缺的字段按当前生效的值写进去(不是写死默认值:通过 loader config 设过 maxCatalogCards: 99 的部署,文件里也该是 99,否则文件与运行中的插件说的不是一回事)。已经写过的值永远不会被覆盖。
为什么不用宿主自己的设置服务:dsh-settings 只渲染插件用 schemastery 声明了 .volatile() 字段的表单,而本插件零依赖、没有 schema。手写 YAML 去改 profile 的 patch 文件则有损坏整个 profile 启动配置的风险——为了一个设置面板不值得。所以设置写进自己的 JSON 文件,原子写(临时文件 + rename),坏文件退化成"没有覆盖"而不是启动失败。
兼容旧版记忆
早期版本把记忆存成 <工作区>/.dsh/task-memory/notes/<卡名>/SKILL.md 加一个 stats.json。那些文件里的知识仍然有效,所以面板上有一个「兼容旧版记忆」按钮,按当前选择的工作区把它搬进数据库。
点击后是三步,每步都有确认:
- 预演(不写任何东西)—— 告诉你将导入多少张、其中多少是新建、多少与库里已有卡片同名、多少附件、多少张无法解析。
- 导入 —— 已存在的同名卡片不会被覆盖(导入永远不破坏库里更新的版本),然后报告实际写入了什么。
- 删除旧文件 —— 这一步单独确认,且在导入成功之后才提出。
导入用 importCard 原样写回:原始日期、revision、hits、last_used 都保留,而不是当作新卡重新推导。删除只动 notes/ 与 stats.json 两项,同目录下别的插件数据不会被碰。
自动落卡
默认不需要你开口。 每个回合结束时,如果这一轮确实做了实事(工具调用 ≥ 3 次)而且还没有存过卡,插件会自动追问模型一次:
任务记忆自动检查:本轮工作已结束。回顾本轮,判断是否产生了值得留存的东西……
模型据此判断本轮是否有非显然结论/踩坑/可复用流程:有就调 task_memory_save,没有就回一句"本轮无需记录"。
为什么要这么做:一段提示词只能"请求"模型记录,而刚干完活的模型不会可靠地执行又一条指令。之前的实测就是——你不主动说"记住这个",卡片永远是零。所以这里用的是真实触发点而不是请求:宿主在关闭回合前会 await agent/turn-stopping,监听器调 agent.steer(...) 就能让机器多跑一步。
为什么是"追问"而不是"直接写":只有模型分得清"可复用的根因"和"一次性的查询结果"。缺的不是判断力,而是提醒自己去判断这件事。
三个防呆:
| 条件 | 行为 |
|---|---|
| 本轮工具调用 < 3 次 | 不提醒(查询、闲聊、小改动) |
| 本轮已经存过卡 | 不提醒(记忆已是最新) |
| 本轮本身就是提醒引发的复查 | 不提醒(防死循环的关键) |
模型说"不值得存"时不会硬写。关闭方式:把 autoCapture 设为 false。
上架与不上架
卡片默认只留在本工作区的任务记忆里,不进技能目录。 需要时用 publish: true 单独上架。
为什么默认不上架:本插件的卡片注册成 runtime 技能后,会出现在 Harness 的技能目录里,而技能中心(Skill Center)页面列的就是同一份目录。技能中心是给人挑通用技能的地方,不该被某个工作区的排查记录塞满。而且技能中心虽然能编辑技能,却无法操作 runtime 条目(它列出的 runtime 技能没有文件路径),所以这些卡片在那里只是一排点了没反应的噪声。
| 状态 | 出现在任务记忆索引 | 出现在技能目录/技能中心 | 加载方式 |
|---|---|---|---|
| 未上架(默认) | ✅ | ❌ | task_memory_load / task_memory_search |
已上架(publish: true) | ✅ | ✅ | 上面两种,外加内置 skill 工具 |
上架与否不影响可检索性:未上架的卡一样会被注入索引、一样能被工具检索和加载,只是不会污染面向人的技能列表。上架状态是粘性的——一次 update 不会把它悄悄取消,只有显式传 publish: false 才撤下。
判断标准:跨任务复用的通用方法论才值得上架;某个项目的排查记录、一次性结论留在工作区即可。
上架后立即生效,不用等
技能的收集结果在注册表里是按 revision 缓存的,所以"改了卡片"本身不会让目录刷新。本插件在每次写入后主动调 control.invalidate() —— 它把 revision 加一、清掉缓存并通知观察者,于是下一次收集必然重新读取。
关键在于宿主侧的消费方式:@deepseek-ai/dsh-tool-skill 在每一步(agent/pre-step)都会 snapshot() 一次并比对摘要,摘要变了才推送新目录。所以 invalidate 之后的下一个步骤就会看到新卡片。
这对模型侧的技能列表是立即的。技能中心页面是浏览器里的列表,点一下刷新即可看到;它读的是同一份已失效的缓存,不会再拿到旧数据。
只记一份
task_memory_save 的三种模式:
auto(默认):先用本地相似度(字符 trigram + 触发词重合 + 标签重合)比对已有卡片。判定为同一件事时不创建,直接返回那张卡的name与匹配理由,让模型改用update。update:合并进指定卡。合并按##段落进行——模型这次写到的段落被替换,没提到的段落原样保留,因此后一次保存不会悄悄抹掉先前的结论。revision递增,triggers/tags取并集。force-create:确实是一个不同任务时才用。
索引有硬上限(默认 50 张,按加载次数→更新时间排序)。被截断的卡仍然可以通过 task_memory_index / task_memory_search 找到——这是"记忆可以很长,但每次不必全读"的落地方式。
记忆面板(P3)
Harness 侧边栏里的「任务记忆」入口,中间列打开面板。左侧是卡片列表、右侧是详情/编辑区,两列各自滚动——详情不会因为你记忆变多而被推得越来越远。
| 操作 | 说明 |
|---|---|
| 浏览 | 顶部选工作区(带卡片数),左栏列表:名字、状态、描述、更新时间、revision、加载次数、触发词 |
| 查看 | 点左栏卡片,右栏显示正文与元数据 |
| 新建 | 点右上角「新建卡片」,在右栏填写;勾选**「作为技能上架」**(默认不勾) |
| 编辑 | 右栏直接改字段与正文后保存;上架开关也在这里 |
| 删除 | 二次确认,删掉卡片及其全部附件(外键级联) |
| 检索 | 按正文全文过滤左栏列表 |
状态显示中文:「已确认 / 草稿 / 可能过时」。存储里仍然是 verified / draft / stale 标识符 —— 面板只翻译显示,因为那三个词是工具 schema 和数据库列使用的值,就地改写会破坏与模型的契约。
人工编辑与模型写入的语义不同:模型 update 是按 ## 段落合并,而面板保存是逐字替换——你在编辑器里写的就是最终内容,删掉的段落和触发词不会自己回来。
实现要点:Host 侧用 ctx.webServer.register 挂 /api/task-memory/* 六条路由,客户端用 fetch 调用,两个 slot(sidebar.panellist + main)提供入口和页面。样式只用主题 token,明暗主题都跟随宿主。
界面里的下拉框是自绘的,不是原生 <select>:后者的展开弹层由 Chromium/OS 绘制、CSS 碰不到,在暗色主题下会是白底黑字,与其余控件格格不入。自绘版直接采用宿主菜单的配方(--dsw-menu-surface-fill + --dsw-menu-backdrop-filter + --dsw-elevation-prominent),并保留原生控件的全部键盘与无障碍行为(方向键、Home/End、Enter/Space、Escape、点外部关闭、焦点回到触发器)。
安装
本插件不使用 npm 安装、没有 node_modules、不依赖任何 @deepseek-ai/* 包——link: 安装的插件是从真实路径加载的,Node 的解析走不到 profile 的 node_modules,所以裸包名会失败。lib/schema.js 因此自己实现了注册契约。
- 加依赖(
link:指向本目录):"dependencies": { "dsh-task-memory": "link:D:/AI/dsh-task-memory" } - 加进 bundles:
"bundles": [ ..., "dsh-task-memory" ] - 建 junction,让 Node 能解析到它:
New-Item -ItemType Junction ` -Path "$env:USERPROFILE\.dsh\profiles\<profile>\node_modules\dsh-task-memory" ` -Target "D:\AI\dsh-task-memory"
配置
- id: dsh-task-memory
name: dsh-task-memory
config:
maxCatalogCards: 50 # 注入索引的卡片上限
maxBodyChars: 24000 # 单张卡正文的读取上限,超出截断并给出数据库位置
includeSystemPrompt: true
autoCapture: true # 回合结束自动追问是否落卡;设 false 关闭
defaultTiers: [recent] # 面板默认显示哪些档位
tierDays: # 新鲜度档位的天数边界,必须递增
past: 7 # ≤7 天算「近期」
old: 30 # ≤30 天算「之前」
ancient: 90 # ≤90 天算「很久之前」
forgotten: 365 # ≤365 天算「远古」,超过则「遗忘」
这些都可以在「设置 → 任务记忆」里改,改完立即生效并存进设置文件(优先于这里的 loader config)。
测试
node test/all.test.js
node --test 会为每个文件 spawn 子进程(在受限沙箱下会被拒),test/all.test.js 把十组用例导入同一进程执行,断言完全相同。
test/setup.js 在任何测试运行前把 DSH_HOME 指向临时目录,所以跑测试绝不会创建或改动你真实的 ~/.dsh/task-memory/。每个用例还各自开 :memory: 库,互不干扰。每个测试文件都单独引入它,这样一个套件被单独运行时同样安全。
另有一个真实 Cordis 运行时的集成测试(test/integration.cordis.mjs:装配、通过真实注册表读写、上架语义、写入触发目录重扫、面板路由、回合结束追问及其防循环、卸载清理)。它必须在 profile 目录下运行——插件是 junction 安装的,从 D:\AI\dsh-task-memory 里跑会解析不到 @deepseek-ai/*。运行方式见该文件头部注释。
限制
- 卡片按会话工作目录隔离(数据库里是
workspace列),不跨工作区共享。面板顶部的选择器可以在工作区间切换查看。 - 去重是启发式:它宁可拒绝新建也不轻易产生第二张卡,但最终判断在模型;工具会给出匹配理由,模型可以据此改用
force-create。 - 删除卡片不提供模型工具——模型能自行删除的记忆就是会消失的记忆。删除只在面板里由人操作。
- 自动落卡是追问而非直接写入:模型判断不值得存就不会存。这保住了判断质量,代价是它偶尔会漏存——想确保记下来时,直接说"记住这个"。
- 改用数据库后,卡片不再能直接用
read/edit工具改;附件也用task_memory_asset读,而不是给一个文件路径。 - 改动插件的 JS 代码后需要重启 Harness 才会加载(
patchReload: live只覆盖新增插件与配置改动)。
Comments
Loading…
Similar plugins
by zhaoxuejie
让 DeepSeek Harness agent 把本地 Obsidian 知识库当作长期记忆与工作台:全文/语义检索、会话记忆注入、一键捕获、巡检管家(只建议不擅改)
★ 1
MIT
JavaScript
Sep 7, 2026
dsh plugin --profile web add dsh-plugin-vault-memoryby Scorp1o117
Agent memory for DeepSeek Harness | DeepSeek Harness 记忆插件
★ 7
↓ 753/wk
MIT
JavaScript
Oct 4, 2026
dsh plugin --profile web add dsh-tdai-memoryby sikwoxy
DeepSeek Harness 插件:跨会话持久记忆(Hermes 式)
★ 4
↓ 130/wk
MIT
TypeScript
Aug 14, 2026
dsh plugin --profile web add dsh-tool-memoryby 1304836815
DSH 会话级记忆插件:收尾提醒 + MEMORY.md 记忆索引维护 + 实时对话日志 + LLM 摘要压缩,配置面板在 设置→插件。Session memory for DeepSeek Harness.
★ 0
MIT
JavaScript
Aug 21, 2026
dsh plugin --profile web add @dsh-external/dsh-auto-memoryby rebron1900
Mnemosyne 记忆层在 DeepSeek Harness 中的插件 — 本地优先、SQLite 支持的跨会话记忆。
★ 1
↓ 97/wk
MIT
JavaScript
Sep 30, 2026
dsh plugin --profile web add dsh-mnemosyneLayered file memory for DeepSeek Harness with workspace-scoped USER/MEMORY notes, background consolidation, and hybrid session recall.
★ 0
↓ 55/wk
dsh plugin --profile web add dsh-file-memory