メインコンテンツへスキップ
M

dsh-tag-memory

marsma-101/dsh-tag-memory

DSH 插件:让智能体有一份自己的记忆库——一张只追加的 CSV,稳定读写,闲时能整理。每个智能体每轮自动拿到标签字典(systemPrompt.section,函数值),由模型判断该不该记、每维取什么值,并提供 memory_write / memory_recall 两个工具。界面上有条目表格、维度筛选、搜索导出,以及备份 / 恢复 / 合并。零依赖,全部落本地文件。安装时会探测各智能体的 MEMORY.md 并写入一段受控说明(标记块、幂等、块外一个字节不碰、挂载自愈)。

インストール

dsh plugin --profile web add github:marsma-101/dsh-tag-memory

README

dsh-tag-memory

DSH 插件:让智能体有一份自己的记忆库——存成 CSV,稳定读写,闲时还能整理。

每轮自动交给模型两样东西:一份标签字典(怎么判——这条该不该记、每个维度取什么值) 和一份记忆快照(现在能用上的条目直接摆在眼前)。写和查交给两个工具; 条目在界面上看得见、搜得到、导得出;备份 / 恢复 / 合并都在那一页上。

解决什么问题

  • 换个会话就失忆:上一轮学到的东西,这一轮不在了。
  • 记忆看不见:谁记了什么、记到哪一步,没有一处能翻。
  • 不敢让它自己记:模型自由发挥写进去的东西格式五花八门,回头查不动也改不动。

答案:记忆只有一种形态——一张只追加的 CSV。模型只管判断,程序只管落笔。 于是它同时是一张随时能翻的表格、一份随时能导出的 CSV、一个随时能备份和回滚的文件。

是什么形式

一个插件,三块:

文件挂在哪干什么
preset.js每个智能体systemPrompt.section 每轮注入标签字典 + 记忆快照;注册 memory_write / memory_recall 两个工具
index.jsDSH 宿主记忆文件托管:探测各智能体的 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你自己的。插件升级 / 重装 / 卸载都不碰它

为什么要分开:库放插件目录里,插件一升级、一重装,记忆就跟着没了。所以库必须搬出去。 引擎反过来必须跟着插件走——它要是留在技能目录,插件就没法独立安装。

数据目录怎么定(从宽到窄):

  1. 配置里的 dataDir(Host 行与 preset 行都认;旧字段名 libraryDir 仍然等价)
  2. 环境变量 DSH_TAG_MEMORY_DATA_DIR
  3. (兼容旧名)环境变量 DSH_TAG_MEMORY_SKILL_DIR —— 语义没变,当年指的就是「库在哪」
  4. <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 里就只剩一段说明。三条规矩:

  1. 只认标记:只动 <!-- BEGIN dsh-tag-memory notice --> 之间的内容,块外一个字节不碰; 文件没有标记就当用户自己写的,只追加、不重写。
  2. 只追加 / 原位替换:有标记就替换块内,没有就追加到末尾;旧的手工字典块会被顺手接管替换掉。
  3. 不能轻易丢:首次落笔先留 .bak-<时间戳>;插件每次挂载(以及新智能体出现时)核对标记,不在就补回。

探测哪些文件算「记忆文件」由 config.memoryFileNames 决定,默认只认 MEMORY.md(大小写不敏感)。 目录不存在、文件不存在 → 跳过。

接口

端点方法返回
/tag-memory-api/overviewGET是否就绪、库/字典路径、维度取值与计数、记忆文件托管状态;health.ragged 点名「格子数跟表头对不上」的行
/tag-memory-api/entriesGET?cond=<JSON>&q=<关键词>&sort=<排序>&limit=<n>&offset=<n> → 命中的条目(含 total / shown / hasMore)
/tag-memory-api/dictGET标签字典(只读):四个维度 / 辅助列 / 通用规则 / 写入路由 / 召回口径,连同文件路径、改动时辰、version、stale(盘上与在用的是否一致)。不写文件、不改字典。
/tag-memory-api/healPOST立即重跑一次记忆文件自愈,返回 summary + 新状态
/tag-memory-api/backupGET备份现状:地址是否已设、上次时间/条数、条数差、份数/占用/盘上余量、最近几份、到点没
/tag-memory-api/backupPOST{label?} → 立即备份。没设地址时返回 code:'NO_DIR',界面据此切进「设置备份地址」状态
/tag-memory-api/backup/dirPOST{dir} → 设置备份地址(绝对路径;不存在就建;建不了/不可写就报错不落配置)
/tag-memory-api/restorePOST{stamp} → 整体替换成那一版;先强制留档当前版,换完复核记录数
/tag-memory-api/mergePOST{path|text, apply?} → 合并外来 memory.csv;不传 apply 就是预演(不落笔)
/tag-memory-api/pickerGET这台 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 之后,照这三步核对,每步都有一个不含糊的读数:

  1. 宿主半(界面与记忆文件托管)——侧边栏应出现「记忆」按钮,点进去有数据。 命令行等价物:GET /tag-memory-api/overview 返回 ready: true,条数与你库里一致。
  2. 智能体半(字典注入 + 两个工具)——新开一个会话再查(旧会话保留着装载那一刻的绑定, 代表不了新会话):工具清单里应有 memory_write / memory_recall, 系统提示词里应有「记忆字典」段与「记忆快照」段。 只有界面能用、新会话里却没有这两样 → 跑 node scripts/install.mjs 看输出; 正常应显示 PASS 智能体半已接线(bundle patch、profile 平面全局)。
  3. 真写一条——让智能体 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

関連プラグイン