dsh-tag-memory
marsma-101/dsh-tag-memory
本地CSV追加记忆:标签字典、memory_write/memory_recall,筛选搜索导出、备份恢复合并;MEMORY.md仅改受控块,零依赖。
安装
dsh plugin --profile web add github:marsma-101/dsh-tag-memoryREADME
dsh-tag-memory
DSH 插件:让智能体有一份自己的记忆库——存成 CSV,稳定读写,闲时还能整理。
每轮自动交给模型两样东西:一份标签字典(怎么判——这条该不该记、每个维度取什么值) 和一份记忆快照(现在能用上的条目直接摆在眼前)。写和查交给两个工具; 条目在界面上看得见、搜得到、导得出;备份 / 恢复 / 合并都在那一页上。
解决什么问题
- 换个会话就失忆:上一轮学到的东西,这一轮不在了。
- 记忆看不见:谁记了什么、记到哪一步,没有一处能翻。
- 不敢让它自己记:模型自由发挥写进去的东西格式五花八门,回头查不动也改不动。
答案:记忆只有一种形态——一张只追加的 CSV。模型只管判断,程序只管落笔。 于是它同时是一张随时能翻的表格、一份随时能导出的 CSV、一个随时能备份和回滚的文件。
是什么形式
一个插件,三块:
| 文件 | 挂在哪 | 干什么 |
|---|---|---|
preset.js | 每个智能体 | systemPrompt.section 每轮注入标签字典 + 记忆快照;注册 memory_write / memory_recall 两个工具 |
index.js | DSH 宿主 | 记忆文件托管:探测各智能体的 MEMORY.md,写一段受控的说明块;挂 /tag-memory-api 给界面取数 |
client.js | 浏览器 | 「记忆」页:条目表格 + 维度筛选 + 搜索;标题行上带 备份 / 恢复 / 合并 |
零依赖:不引入向量库、外部 API 或服务,全部落在本地文件上。
两段式:模型判断,程序执行
字典里写着每个维度能取什么值、有什么规则(稳定性 只有 稳定 / 易变;深度 只在
类型=记忆 时才有值……)。模型读字典、填字段、调工具;编号、校验、追加、去重、筛选、返回,
全是程序干的。
好处是可回溯:库里每条都带着当时「为什么记、凭什么这么判」。 判定规则以后改了,历史条目也不会被悄悄改写。
写不进去的时候,话得说清。 校验不通过不是甩一句「不能为空」——
缺 环境 / 依据,说的是「这条在什么条件下成立、凭什么」,缺了换一个项目就会被当成通用做法误用;
取值不在字典里,说的是「按这一维筛的时候永远筛不到它」。拒绝要能教会人怎么改,
不然写的人只会随手填一个值糊过去。
同一句话写第二遍会被拦住。 库里已经有一条「内容 + 项目 + 类型」都一样的生效条目时,
再写一条会被拒,并指名道姓说清库里那条是谁、正确做法是取代(把旧的那条改成 状态=被取代 退场)。
理由很实在:两条一模一样的条目,读的时候不知道该信哪条,而且它们会各自演进,越往后越说不清。
项目 不同不算重复(同一个做法在两个项目下各自成立);已经退场(被取代 / 已归档)的条目
不挡新的一条。
一行错位,整库跟着串。 CSV 的列是按位置读的:哪一行少写一格,它后面每一格都会往前串一位—— 「时间」那格里显示的其实是「环境」,界面照样有值,只是值是错的。这种错不报错、不丢行,最难发现, 所以两头都盯:
- 看得见:
GET /tag-memory-api/overview的health.ragged点名「第几条、差几格」, 记忆页顶上直接把这条挑明——表里这些行显示的值不可信。 - 写不进:库里有错位的行时,
memory_write停下并说明(而不是照写不误,让它继续长), 因为判重也是按下标比字段的,拿错位行去比「内容」比的根本不是内容。 - 有退路:把那一行补齐到与表头同样多的格子就能接着写;拿不准就用「恢复」退回上一份备份—— 备份是整份替换,不看列数,所以带着错位行的旧备份也一定救得回来。
注入:字典 + 记忆快照
只给字典是不够的——模型知道有本手册,但它得先想起去查;忘了查就等于没记忆。 所以每轮除了字典,还按「能不能直接用」把条目分三档摆出来:
| 档 | 谁进这一档 | 给什么 |
|---|---|---|
| 常驻 | 距离=常驻 且生效 | 全文(内容 + 环境 + 原因)——标了常驻就是「每轮必读」 |
| 本项目 / 通用 | 项目=通用,或 项目 正是当前工作区文件夹名 | 全文,受字数预算约束 |
| 其他项目 / 深藏 | 其余生效条目(含 距离=深藏,原文 条一律是深藏) | 只有一行索引:正文不进上下文 |
| — | 状态=被取代 / 已归档 | 不出现 |
第三档是结构性闸门,不是提示词劝阻:看得到的东西一定会被顺手用,所以别的项目的做法
只留一行线索(- [M-…] 在「那个项目」:…),要用就得先 memory_recall 把正文取来。
原文 条平时不进常规召回,也是同一个道理。
三档都是每轮要付的 token 账,所以字数与条数上限全部可配(见「配置」)。超了按 id 倒序截断,
并在段里明说截了多少——宁可让模型知道「还有没放进来的」,也不要让它以为库里就这么多。
库为空或读不到时这一段整段不出现,退回「只注入字典」。
记忆库
一条 21 列的 CSV(17 列老库在启动时自动迁移,迁移前强制快照、数据行逐字节不动):
id 内容 时间 环境 原因 备注 稳定性 成熟度 距离 角色 项目 状态 关键词 工具 版本 依据 类型 强度 最后强化 已验证项目 使用次数
引擎内置 + 数据独立
这个插件不依赖任何外部技能目录,两样东西分开存放:
| 什么 | 放哪 | 谁管 |
|---|---|---|
引擎(读写核心 core.mjs、备份核心 backup.mjs、字典样张 tags.schema.json、出厂模板 templates/*) | 插件包内 lib/engine/ | 随插件走、随插件升级。用户不用管,也不该去改 |
数据(你的记忆库 store/memory.csv、索引、备份配置,以及这份库正在用的字典 references/tags.schema.json) | 独立数据目录,默认 <DSH_HOME>/tag-memory | 你自己的。插件升级 / 重装 / 卸载都不碰它 |
为什么要分开:库放插件目录里,插件一升级、一重装,记忆就跟着没了。所以库必须搬出去。 引擎反过来必须跟着插件走——它要是留在技能目录,插件就没法独立安装。
数据目录怎么定(从宽到窄):
- 配置里的
dataDir(Host 行与 preset 行都认;旧字段名libraryDir仍然等价) - 环境变量
DSH_TAG_MEMORY_DATA_DIR - (兼容旧名)环境变量
DSH_TAG_MEMORY_SKILL_DIR—— 语义没变,当年指的就是「库在哪」 <DSH_HOME>/tag-memory
首次启动自动建库:数据目录不存在、或存在但是空库时,插件会用自带的模板建出
store/memory.csv(表头)、store/index.md、store/backup.json,并把字典落一份到
references/tags.schema.json。已有库一个字节不动——只有「新目录 + 空库」才会建。
从旧版本迁移(0.1.x → 0.2.0)
0.1.x 把库放在 <DSH_HOME>/skills/marsma101-tag-memory。0.2.0 起默认位置改成
<DSH_HOME>/tag-memory。插件不会自动搬库——发现旧库时它只在界面上提示,不替你动文件。
两条路,选一条:
甲:把旧库拷到新位置(推荐)
# 先备份一份,再拷(把旧目录整个搬过去,包含 store/ 与 references/)
Copy-Item -Recurse "$env:USERPROFILE\.dsh\skills\marsma101-tag-memory\*" "$env:USERPROFILE\.dsh\tag-memory\"
拷完重启 DSH。旧目录可以留着当备份,确认新位置读写正常后再自己删。
乙:让插件继续用旧位置
在配置里把数据目录指过去(dataDir,或设环境变量 DSH_TAG_MEMORY_DATA_DIR):
# profile 补丁里,Host 行与 preset 行各写一次
config:
dataDir: 'C:\Users\你\.dsh\skills\marsma101-tag-memory'
这样不用搬文件,但库仍然留在技能目录里——将来那个目录被清理,库就跟着没了。 所以只建议过渡期用。
装完可以用
node scripts/install.mjs自查:它会把「插件内置引擎是否齐」「数据目录在哪、 有没有库」「有没有检测到旧库」逐条报出来。
只追加:新记录追加到末尾,旧条目靠 状态 退场(被取代 / 已归档),不删行。
所以库只有一个方向长——任何一版都能原样备份、原样回滚。
归档返回体的 ok 与 allOk
批量归档(memory_archive 工具 / core.archiveBatch)的返回体有两个字段,语义不同:
| 字段 | 含义 | 什么时候 false |
|---|---|---|
ok | 这一批跑完了 | 只有「整批没跑」时(如批前留档失败),此时一条都没动 |
allOk | 每一条都成功 | 部分条目被四判据拒绝时 |
ok=true, allOk=false 是正常结果——建议清单里混进非候选很常见。
调用方要按 results 逐条判成败,不要拿 ok 当「全部成功」用(那样会误判成整批失败、白重试一轮)。
逐条结果里有 id / ok / error / code / reason / snapshot。
「记忆」页
条目页挂两处(sidebar.panellist 的按钮 + main 的主区页面),设置栏另挂一处只读字典
(见下节)。数据全走宿主的只读接口。
| 槽 | 作用 | 位置 |
|---|---|---|
sidebar.panellist | 侧边栏按钮(图标 + 「记忆」) | order: 5,紧排在官方插件按钮(order: 0)下面 |
main | 侧边栏按钮切过去的主区页面(条目:浏览 / 备份 / 恢复 / 合并都在这) | key: 'tag-memory';shell 的 .centerCol 是 overflow:hidden 且高度确定,面板得自己当滚动容器(.tm-panel = height:100% + overflow:auto),顶部再让出 --dsh-frame-top-clearance |
settings.section | 设置里的**「记忆」**一栏:标签字典(只读) | order: 60 |
浏览能力:
- 表格视图(默认):库就是 CSV,所以一行一条、一列一个字段。列名与列序都照库自己的表头走;
默认只摆十列(
id 内容 时间 稳定性 成熟度 距离 角色 项目 状态 关键词),环境/原因/备注/工具/版本/依据 这些长文本或常空的留在「详情」里。表头与勾选列常驻,列多时横向滚动,长文本截断(悬浮看全文)。 设置页窄,默认给卡片视图;右上角随时切换,选择记在localStorage。 - 维度下拉:每个维度一个下拉(选项带计数、已选打 ✓),横向排一行;选一个取值叠加一条条件,同一个再选一次即取消。
- 排序
sort:id_desc(默认——最新写进去的排最上面,一眼看到新加的是什么) /file_desc(按文件顺序,后加的在前)/time_desc/time_asc/id_asc,按自然序比较(M-20260101-002这类也排得对)。 并列时(库里同一天写进去的条目时间全一样)用「在文件里的先后」当第二把尺——没有它,时间 ↓看着像没排。 界面只摆宿主overview.sorts报出来的档位,旧宿主上不会出现点了没反应的选项。 - 分页
limit+offset,页尾「加载更多」;hasMore指示是否还有。 - 含已归档:默认排除
被取代/已归档;开关打开时把状态全集 +留白显式写进条件。 - 详情展开:每条可就地展开全部非空字段;正文超过 120 字先折叠。
- 多维选择:同一维度可多选(维度内 = 或,维度间 = 与);当前条件以面包屑显示,可逐个撤掉。
- 勾选带走:勾选条目(含全选本页 / 清空)→「复制 Markdown」或「导出 CSV」;每条也有单独「复制」。
- 本地记忆:条件 / 排序 / 每页数存
localStorage(带存档版本号,改了默认值才顶得动老存档),重开设置页仍在。 - 键盘:
/聚焦搜索框,Esc清空全部条件与关键词。
条目本身只读:写入仍由智能体经 memory_write 完成。
标签字典(设置 →「记忆」)
字典是三份合同:注入给模型的说明文本、写入时的校验判据、筛选语义的来源。它在哪、写了什么, 以前只有翻开文件才知道。设置里那一栏把这本手册原样摆出来:
- 四个维度(写入时必判)各一张卡:这个维度是选值还是写句子、每个取值什么意思、
落入哪一列(
深度 → 成熟度)、有没有上限(距离最多 10 条)、必填与否、 什么时候判 / 怎么判 / 什么能升降它 / 受谁管。 - 辅助列、通用规则(留白怎么填、必填哪几列、筛选支持哪些算子)、 写入路由(什么情形写成原文、什么时候算「被取代」)、召回口径(排序、先报total 再取前 N 条)。
- 顶上写明字典文件的路径、改动的时辰、
version。盘上的字典和进程里正在用的那份对不上时, 这一栏会当场点出来并提示「重启 DSH 才会生效」——不是等注入用着旧字典、你还以为改好了。
这一栏是只读的。 改字典=改规则,落到库里每一条的判法上,所以不在这页做编辑器: 要改就直接编辑那个文件(改前自己留一份),改完重启 DSH。页尾写明了这一条。
版式:主位通则
用户要看的、要点的放主位;说明、状态、设置退到次位并默认收起。 判据一句话: 这东西用户「每次都要看」还是「出了事才看」?
| 位置 | 内容 |
|---|---|
| 标题行(右对齐) | 立即备份 / 恢复… / 合并… / 设置备份地址… 四颗按钮——要点的,跟标题同一行,不另占一张卡 |
| 标题下面一行 | 备份现状:地址 · 上次时间 · 库条数与条数差 · 份数/占用 · 盘上余量 · 「该备份了」 |
| 正文(主位) | 记忆条目:维度下拉一行 + 搜索/排序/每页/含已归档 + 勾选工具条 + 表格/卡片 |
| 页尾(默认收起) | 维护与状态:记忆文件托管明细 + 手上的备份列表(都是「出事了才看」的) |
备份 / 恢复 / 合并
- 没有默认备份地址。 地址存在
store/backup.json的backupDir,空就是没设。 点「立即备份」而地址为空 → 直接展开「设置备份地址」面板,不是弹一句提醒。 - 选一次就记住。 点「设置备份地址」会弹 DSH 的系统文件夹选择框——就是开新会话选工作区时
弹的同一个框(
ctx.uiWorkspace.pickDirectory(),native / 内建浏览两种后端它自己适配)。 选中即落笔:面板顶上那行「当前地址」立刻跟着变,刷新 / 重开界面都还是它,直到你重选。 手填绝对路径的走右边「保存这个地址」。存地址与立刻备份是两件事——备份归标题行的「立即备份」。 - 地址只有一个落笔点:
commitDir()→POST /backup/dir。选文件夹、内建浏览的「就选这个目录」、 手填后保存,全汇到它;没有第二条路能偷偷改地址。 - 两级兜底:拿不到
uiWorkspace(或它没有原生选择能力)→ 退回内建目录浏览 (面包屑 + 子目录 + 「就选这个目录」);连这个也没有 → 输入框还在,手填绝对路径。 ⚠️ 不自己 spawn 进程弹框——试过 PowerShell 的FolderBrowserDialog:框开在别的窗口后面, 等于弹不出来(Windows 只把前台激活权给前台进程)。用 DSH 那道门就没这问题。 - 立即备份:备
memory.csv/index.md/backup.json三件套外加标签字典 (references/tags.schema.json,落成schema-<戳>.json),文件名带戳YYYYMMDD-HHMMSS; 逐项校验(字节数 / SHA-256 / 解析器数的记录数 / 无 BOM)全过才更新水位; 戳上已有内容不同的同名文件就往后挪一秒——备份从不覆盖已有内容;超过keepCopies份轮转最旧的。 字典是解释这份库的那本说明书,所以跟着库一起走:库换回旧的一版,判法也得跟着回到那一版。 - 恢复 = 整体替换(不是补缺失):先验它能读并把内容读进内存 → 强制先留档当前这一版
(失败就中止,一个字节不动)→ 写回
memory.csv+index.md(backup.json不还原,那是设置不是记忆) → 复核记录数,对不上就报错并指出用哪份回退。 - 恢复时字典怎么办,明说、不静默:那份备份里有字典就一起换回去(换完当场生效,不必重启); 与现在这份逐字节一样就说「没动」;老备份里没有字典(加字典之前留的)→ 字典原地不动, 并说明「这是加字典之前的备份」。三种情形都会在恢复结果里写出来。
- 字典改过了、备份还是旧的这个状态会在备份卡上说出口(「字典改了,库还没重合」)—— 库与字典分家过一次,不靠人记得。
- 合并:给一份外来
memory.csv的绝对路径 → 先预演(一个字节不落),把 「新增 / 重复跳过 / id 撞号改新号 / 加不进去的 / 内容一样但标签不一样」逐条交代清楚 → 看过再「确认合并」。守的铁律是:先留档、只追加、不重写、不删任何条目、id 撞号按内容判。 预演自己就做校验,不合规的行当场挑出来(连原因一起),不会等真合并才一条条失败。 来源库没有类型列(迁移前的老库)时,并进来的条目按记忆落——不让它们留空, 留空会被筛选当成「未判」而漏掉。
记忆文件托管(index.js / lib/notice.mjs)
插件的字典注入接管之后,各智能体的 MEMORY.md 里就只剩一段说明。三条规矩:
- 只认标记:只动
<!-- BEGIN dsh-tag-memory notice -->之间的内容,块外一个字节不碰; 文件没有标记就当用户自己写的,只追加、不重写。 - 只追加 / 原位替换:有标记就替换块内,没有就追加到末尾;旧的手工字典块会被顺手接管替换掉。
- 不能轻易丢:首次落笔先留
.bak-<时间戳>;插件每次挂载(以及新智能体出现时)核对标记,不在就补回。
探测哪些文件算「记忆文件」由 config.memoryFileNames 决定,默认只认 MEMORY.md(大小写不敏感)。
目录不存在、文件不存在 → 跳过。
接口
| 端点 | 方法 | 返回 |
|---|---|---|
/tag-memory-api/overview | GET | 是否就绪、库/字典路径、维度取值与计数、记忆文件托管状态;health.ragged 点名「格子数跟表头对不上」的行 |
/tag-memory-api/entries | GET | ?cond=<JSON>&q=<关键词>&sort=<排序>&limit=<n>&offset=<n> → 命中的条目(含 total / shown / hasMore) |
/tag-memory-api/dict | GET | 标签字典(只读):四个维度 / 辅助列 / 通用规则 / 写入路由 / 召回口径,连同文件路径、改动时辰、version、stale(盘上与在用的是否一致)。不写文件、不改字典。 |
/tag-memory-api/heal | POST | 立即重跑一次记忆文件自愈,返回 summary + 新状态 |
/tag-memory-api/backup | GET | 备份现状:地址是否已设、上次时间/条数、条数差、份数/占用/盘上余量、最近几份、到点没 |
/tag-memory-api/backup | POST | {label?} → 立即备份。没设地址时返回 code:'NO_DIR',界面据此切进「设置备份地址」状态 |
/tag-memory-api/backup/dir | POST | {dir} → 设置备份地址(绝对路径;不存在就建;建不了/不可写就报错不落配置) |
/tag-memory-api/restore | POST | {stamp} → 整体替换成那一版;先强制留档当前版,换完复核记录数 |
/tag-memory-api/merge | POST | {path|text, apply?} → 合并外来 memory.csv;不传 apply 就是预演(不落笔) |
/tag-memory-api/picker | GET | 这台 DSH 的目录选择后端是 native(弹系统框)还是 browse(列目录) |
自检
node scripts/selftest.mjs # 235 项断言;全程沙箱(临时复制一份记忆库),不碰任何 live 数据
# (「字典随备份」「写入去重 / 拒绝话术」「列数校验」三节要记忆库侧支持
# 才跑得动,不支持会**点名 SKIP** 并写清是哪一份库,不假装通过)
node scripts/install.mjs # 只读校验:包结构 + profile 清单 + 预览会写哪些记忆文件
交付/测试用命令:selftest.mjs 全过 + install.mjs(check)全 PASS 即可视为可安装。
跑这两条不需要先装插件,随时可以在源码目录里复验。
在还没装记忆库的机器上,
selftest.mjs会把 A–K 整节报成SKIP并说清缺哪几个文件 (pass=1 fail=0 skip=11,退出码 0)——那是「还没装」,不是「装坏了」,所以不报 FAIL。
安装
官方 DSH 的 bundle 安装方式(推荐):
dsh plugin --profile <profile> add <本仓库路径或 GitHub 地址>
# 若本机 pnpm 报 ERR_PNPM_ADDING_TO_ROOT,按它提示加 -w:
dsh plugin --profile <profile> add -w <本仓库路径或 GitHub 地址>
本包的 cordis.patch.yml 同时插入 Host 半(记忆文件托管 + 设置页 API)和
智能体半(字典/快照注入 + memory_write / memory_recall),
所以按官方 bundle 方式安装后两半都会挂上,不需要再手工改 profile 补丁。
也保留了仓库内的离线安装脚本,便于冷启动、迁移和检查:
node scripts/install.mjs # 只读校验 + 预览会写哪些记忆文件
node scripts/install.mjs --apply # 安装(链接 + profile 清单 + 写记忆块)
node scripts/install.mjs --apply --seed # 顺带播初始记忆(**只在该库 0 条时**)
node scripts/install.mjs --uninstall # 预览要摘掉哪些(只读,一个字节不动)
node scripts/install.mjs --uninstall --apply # 卸干净:逐项摘回原样
node scripts/install.mjs --apply --dest <路径> # 先把本体拷到 <路径>,再从那儿发链接
node scripts/install.mjs --dsh-home <路径> --profile <名> # 装到别的 DSH / 别的 profile
--wire-presets 仍保留用于兼容老版安装产物;新版 bundle patch 已自带智能体半,
正常情况下不需要再传。
装完需重启 DSH 才生效。所有被改的文件,脚本都会先留 .bak-<时间戳>。
本体别留在会话目录里。 链接默认直接指向源码目录。源码要是在
downloads\这类 会话目录里,那里被清理=挂载本体凭空消失——界面上什么都不报,只是「本来有的东西不见了」。--dest <路径>先把本体拷过去、再从那儿发链接;只在本机、不该跟着搬的东西 (.git/node_modules/ 开发记录 / 运行时的库与备份)不拷。不填--dest则与从前完全一样。 check 会发现「本体在会话目录里」或「链接指着别处」,都会报出来;apply 时链接指着我们自己的 另一份,会重指到本次要用的本体。
两半都在 bundle patch 里。 宿主半(
index.js:设置页 API、记忆文件托管)和 智能体半(preset.js:字典/快照注入 + 两个工具)都由cordis.patch.yml的 profile 平面声明接线。安装后应同时看到侧边栏「记忆」页和新会话里的memory_write/memory_recall,不需要分别装两次。
--uninstall摘的是安装时写下的那几处:node_modules里的链接、profile 清单里的依赖与 bundle 条目、老版本可能留下的 profile 补丁声明、各智能体MEMORY.md里的说明块——摘回原样,逐条校验, 有一项摘不干净就整批不落盘(宁可不卸,也不留半截)。 记忆库(store/memory.csv与字典)不在清单里:卸插件不删记忆。 链接只解「当初是我们拉的那一条」(--dest装的话,认的是清单里记的那个落点); 别人写在别处的引用(比如某个 preset 自己的plugins里挂的那一行)是它的配置,我们不碰。
客户端那半(
client.js)DSH 会自己热更,改完刷新浏览器即可; 宿主那半(index.js)只在进程启动时装载,改了必须重启。
初始记忆(--seed)
全新装好、库里一条都没有时,智能体第一次面对的是一个空表格:它不知道该记什么,
最容易先记进来的是流水账。--seed 往空库里播 3 条起步记忆:
| 条 | 内容 | 为什么是它 |
|---|---|---|
| 1 | 只记「下次还用得上」的东西(规矩、纠正、决策、实测做法);一次性的进度与闲聊不记 | 先立「什么不记」,库才不会被流水账淹掉 |
| 2 | 写「项目=通用」之前先问:换一个项目还成立吗? | 误标成通用的话会被带到每个项目里,且最像规矩、最不被怀疑 |
| 3 | 这台机器的库在哪个目录、备份地址设没设 | 换机器 / 换目录时,「库在哪」是最先要问清的事;由装机程序按实情生成 |
- 默认不写,要加
--seed;且只在该库 0 条时写。库里已经有条目就一条都不写 ——重复安装、升级插件都不会把种子塞回去(不会凭空多出你没用过的记忆,也不会和已有条目重复)。 - 3 条都走和智能体写入同一套校验(不是塞进去的例外);
时间按装机当天填。 install.mjs(只读 check)会照实说这一次会不会写:「库是空的 → 加--seed会写入 3 条」 或「库里已有条目 →--seed不会写任何东西」。- 只播已经存在的空库;库里连文件都没有时,先按记忆库的说明把库装好再跑。
安装后自验
装完并重启 DSH 之后,照这三步核对,每步都有一个不含糊的读数:
- 宿主半(界面与记忆文件托管)——侧边栏应出现「记忆」按钮,点进去有数据。
命令行等价物:
GET /tag-memory-api/overview返回ready: true,条数与你库里一致。 - 智能体半(字典注入 + 两个工具)——新开一个会话再查(旧会话保留着装载那一刻的绑定,
代表不了新会话):工具清单里应有
memory_write/memory_recall, 系统提示词里应有「记忆字典」段与「记忆快照」段。 只有界面能用、新会话里却没有这两样 → 跑node scripts/install.mjs看输出; 正常应显示PASS 智能体半已接线(bundle patch、profile 平面全局)。 - 真写一条——让智能体
memory_write一条,回「记忆」页刷新,新条目应该在。
三条里最容易漏第 2 条:宿主半与智能体半是两码事。如果用了很老的安装产物或 手工覆盖了 bundle 配置,可能只挂上宿主半——界面照样能用,但没有任何智能体拿得到字典。
卸载回滚
node scripts/install.mjs --uninstall # 先看要摘什么(只读,一个字节不动)
node scripts/install.mjs --uninstall --apply # 真摘
摘的是装的时候动的那几处——node_modules 里的链接、profile 清单里的依赖与 bundle 条目、
老版本可能留下的 profile 补丁声明、各智能体 MEMORY.md 里的说明块。逐条校验,有一项摘不干净就整批不落盘
(宁可不卸,也不留半截)。每个文件动之前都留 .bak-<时间戳>,想回退就把 .bak 改回原名。
记忆库不在摘除清单里:卸插件不删记忆。 数据目录里的 store/memory.csv、字典、备份目录一个字节都不碰;
链接只解「当初是我们拉的那一条」,别人写在别处的引用(比如某个 preset 自己的 plugins 里挂的那行)不碰。
摘完同样要重启 DSH 才生效。
配置
index.js(profile 行):
- id: tag-memory
name: dsh-tag-memory
config:
dataDir: 'D:\我的记忆库' # 数据目录(库放哪);不填见下。旧名 libraryDir 仍认
memoryFileNames: ['MEMORY.md'] # 探测哪些文件算「记忆文件」
notice: true # 关掉则不写记忆块
migrateLegacyDict: true # 是否接管旧的手工字典块
presetRoot: 'D:\…\.agent-presets' # 找不到记忆文件时才填
preset.js(智能体半;bundle patch 已默认挂好,下面只是配置项说明):
- insert:
- id: tag-memory-preset
name: 'dsh-tag-memory/preset'
config:
agent: '某某' # 写进 `角色` 列;不填则依次取 preset 目录名、智能体 id
dataDir: 'D:\我的记忆库' # 数据目录(库放哪);不填见下。旧名 libraryDir 仍认
snapshot: # 每轮注入「记忆快照」的预算;不填用缺省值
enabled: true # false = 退回「只注入字典」
maxPinned: 6 # 常驻条目最多放几条全文
maxBodyChars: 4000 # 全部全文条目的字数上限(常驻优先占)
maxIndexLines: 40 # 索引行上限
快照的预算是每轮都要付的 token 账,按库的大小调。以一份约 150 条生效条目的库为例:
| 配置 | 注入总长 | 约合 |
|---|---|---|
6 / 4000 / 40(缺省) | 约 9.7k 字符 | ≈ 6.1k tokens |
5 / 2000 / 20 | 约 6.6k 字符 | ≈ 4.1k tokens |
10 / 6000 / 60 | 约 13k 字符 | ≈ 8.1k tokens |
嫌贵往下调,嫌记不住往上调;maxIndexLines: 0 就是「只放常驻,其余全靠 memory_recall」。
数据目录在哪,按这个次序找:dataDir(旧名 libraryDir 等价)→ 环境变量 DSH_TAG_MEMORY_DATA_DIR
→(兼容旧名)DSH_TAG_MEMORY_SKILL_DIR → <DSH_HOME>/tag-memory。
两半都挂了时,谁先装载谁说了算——两处都写、且写成不一样,是配置错误。
引擎(core.mjs / backup.mjs / 字典样张 / 模板)不看这一串:它永远在插件包内的 lib/engine/。
许可证
Apache-2.0