dsh-scribe
DSH 的「书记官」:跨会话记忆 + 本地知识库。数据默认只存在你这台机器上,云端同步是可选项,数据迁移必须你点头。
会话结束,它把这次定下的东西记下来;下次开会话,它替你翻出来;工作区里的文档,它顺手编成目录供检索。记什么、存在哪、发给谁,全部由你决定。
Cross-session memory for DeepSeek Harness — local-first by default, cloud optional, migration always user-approved.
它解决什么问题
AI 编程助手没有记忆:每个新会话都从零开始,你昨天定下的架构决策、踩过的坑、约定的代码规范,今天要重新讲一遍。
把记忆放在云端能解决这个问题,但代价是你的对话内容会离开这台机器——包括密钥路径、客户名、内部接口。很多托管方案还是默认开启的,你不一定意识到它在传。
这个插件的立场很简单:
- 默认不联网。 记忆是
~/.dsh/memory/memories.jsonl里的纯文本,零依赖、零服务端,拔网线也能用。 - 出网是你决定的。 只有你配置了云端后端或嵌入端点,数据才会离开本机;每个出网动作都在下面「数据流向」里写清楚了。
- 迁移不自动。 换存储后端不会偷偷搬数据,必须你在审批框里点同意,而且可回滚。
它做什么
| 时机 | 行为 | 预算 |
|---|---|---|
会话开始(agent/created) |
检索同项目 + 最近的记忆,注入 system prompt 的 memory:context section |
5 条 / ≤1200 字符 |
| 会话进行中 | 模型按需调用 scribe_memory_search / scribe_memory_save / scribe_memory_list / scribe_memory_forget |
按需 |
会话结束(agent/disposed) |
把这次会话压成一条记忆写回 | 1 条 |
| 知识库(可选) | 索引工作区 kb/ 与全局资料(文本、代码、Word/Excel/PPT);会话开始注入目录摘要,模型按需 scribe_kb_search / scribe_kb_read |
目录摘要 ≤1100 字符 |
| 你想换后端 | scribe_memory_migrate → 审批 → 写日志 → scribe_memory_migrate_rollback |
一次 |
会话结束时的压缩是有区分的:少于 10 轮的对话直接保留最后几段原文(便宜、不丢细节);更长的对话交给会话本来就在用的模型,压成「项目在做什么、定了什么、为什么、还欠什么」的一段话。
注入量与会话长度无关,不会越用越贵。
30 秒看它长什么样
它没有自己的界面,记忆就是磁盘上的纯文本:
$ cat ~/.dsh/memory/memories.jsonl
{"id":"mem_mvqil36h_7c9ac9b6","content":"决定:默认本地 JSONL 存储,会话开始注入 5 条记忆到 system prompt,迁移必须经审批","createdAt":1793095200089,"source":"session-summary","tags":["project:dsh-memory","decision"],"schemaVersion":1}
查询和状态走斜杠命令或模型工具:
> /scribe status
后端: local (local)
记忆条数: 1
可用性: 正常
嵌入: lexical fallback
自动注入: 开启
时间范围: 2026-10-27 至 2026-10-27
数据目录: /Users/you/.dsh/memory
模型在会话里也能自己翻:
scribe_memory_search("上次部署脚本怎么写的")
→ 1. [2026-10-25] (project:infra) 部署走 deploy.sh,先用 staging 验证再 promote…
左侧菜单栏入口(可选)
设置 → 记忆与知识库 → 概览里有一个开关:在主页左侧菜单栏显示「记忆与知识库」。
- 打开后,左下角菜单会多一行入口,点开是一个独立页面(和「插件」「任务看板」同级,容器、高亮、折叠都由外壳提供);
- 关掉只是隐藏入口,记忆和知识库照常工作;
- 改完立即生效,不用重启 —— 同一个模块同时持有入口和页面,开关是就地挂载/卸载。
知识库:资料是资料,记忆是记忆
两件事分开存,因为它们的真相来源不同:
| 记忆 | 知识库 | |
|---|---|---|
| 谁写入 | 插件自动(会话结束) | 你自己维护的文档,插件只读 |
| 形态 | 一段结论 | 按标题切块,带原文行号 |
| 回答什么 | 「上次定了什么」 | 「这条规则原文在哪」 |
| 同步 | 插件自己的后端 | Git —— 文档本来就该进版本控制 |
两个作用域,工作区优先:
- 工作区:当前项目目录下的
kb/(默认kb,可改)。沿用你在AGENTS.md里定的约定:INDEX.md一行一条、排除原始/和inbox/、引用写成kb/路径.md#L行号。 - 全局:
~/.dsh/memory/kb/global/,跨项目共享的资料。往目录里放.md就会进索引。
两处目录都能换:
- 工作区行上的「更换目录」→ 选一个文件夹,只对那一个工作区生效(写进
knowledgeBase.workspace.overrides),可「恢复默认」; - 全局卡片的「更换目录」→ 写进
knowledgeBase.global.root。
上传文件:知识库分页顶部是一块拖拽投放区(也可以点击选择文件),先选目标(全局或某个工作区),再把文件拖进去或点选。文件写进目录后会立刻重建索引。
二进制文档会抽正文:.docx .xlsx .pptx 是 Office Open XML(一个装满 XML 的 ZIP),插件用内置解析器读出正文再切块索引——零依赖、跨平台,不需要你机器上装 Word、LibreOffice 或任何命令行工具。.pdf 是唯一真正需要解析器的格式,作为可选依赖 pdfjs-dist 处理:装了就用,没装就跳过并给出原因(不会偷偷索引空内容)。
直接索引的类型:.md .markdown .mdx .txt .rst .csv .tsv .json .jsonl .yaml .yml .toml .ini .conf .sql .html .css .js .mjs .ts .tsx .jsx .vue .py .go .rs .java .kt .rb .php .sh;需要抽正文的类型:.docx .xlsx .pptx(内置)、.pdf(可选依赖)。默认排除 原始/、inbox/、node_modules/、dist/、build/ 以及各语言锁文件。
文件名会去掉路径部分,../ 这类穿越会被削成纯文件名,且落点一定在该知识库目录内。
会话开始时注入的是目录摘要(优先读 INDEX.md,没有就退回文件列表),不是全文;模型需要细节时用 scribe_kb_search,拿到 kb/设计规范.md#L12-L40 这样的引用后,用 scribe_kb_read 读原文行号再引用。
索引是派生数据,放在 ~/.dsh/memory/kb/indexes/,不会改你的文档;过期自动重建(默认 TTL 300 秒),也可以在设置页「知识库」分页手动重建。
只用 Markdown / 纯文本就能工作,不需要任何额外服务。Word / Excel / PowerPoint 的正文抽取也是内置的;只有 PDF 需要可选依赖 pdfjs-dist。
数据默认留在本机
这是本插件与其他记忆方案的核心差别。默认配置下,没有任何数据离开这台机器——下面是完整的出网清单,没有隐藏项:
| 动作 | 出网? | 发到哪里 |
|---|---|---|
保存记忆(默认 local) |
❌ 否 | 仅 ~/.dsh/memory/memories.jsonl |
| 会话开始注入 | ❌ 否 | 从本地文件读 |
scribe_memory_search(未配嵌入端点) |
❌ 否 | 本机词法打分 |
scribe_memory_search(配了 embedding.endpoint) |
⚠️ 是 | 只发送待检索的记忆文本,到你自己填的 /v1/embeddings 地址 |
| 检索重排(默认开) | ⚠️ 是 | 只把查询 + 候选记忆摘要发给当前会话模型,8 秒超时,失败退回原序 |
| 长对话摘要(默认开) | ⚠️ 是 | 把对话发给当前会话模型(本来就在用它,没有新增接收方) |
迁移到 bailian |
⚠️ 是 | 把选中的记忆发到阿里云百炼记忆库 |
| ChromaDB 引擎 | ⚠️ 是 | 发到你自建的 Chroma 服务地址 |
想彻底离线:把 retrieval.llmRerank 和 summarization.shortTurns 调成「不触发模型」,并不要配 embedding.endpoint。
安装
三种方式,任选其一。装完返回 application: applied 即生效(改的是 Host 行为,不用重启页面),
卸载用 plugin_manager · action: remove_bundle · target: dsh-scribe。
1) 从 GitHub 直接装(现在就能用;已实测装出来是 dsh-scribe@1.0.0):
plugin_manager · action: install_bundle · target: github:zcd0818/dsh-scribe#v1.0.0
#v1.0.0 是可选的 ref:写 tag 或 commit 便于复现,省略则取默认分支的最新提交。
这条路径需要能访问 github.com——装之前 DSH 会先 git ls-remote 探一次,网络不通会直接报
spec-host 失败,正是你现在的代理要放行的那一步。
2) 从 npm(已发布:https://www.npmjs.com/package/dsh-scribe):
plugin_manager · action: install_bundle · target: dsh-scribe@1.0.0
npm i dsh-scribe 也能装,装出来的目录结构与 GitHub 那条路径一致(35 个文件,含 cordis.patch.yml)。
3) 本机已有 checkout(开发用,改完即生效):
plugin_manager · action: install_bundle · target: /绝对路径/dsh-scribe
三种方式是同一个包名
dsh-scribe,所以后装的会替换先装的:从 GitHub/npm 装过之后再想要「改完即生效」, 得重新装一次第 3 种。
改名说明:本插件原名
dsh-memory-plugin(行 iddsh-memory),现为dsh-scribe(行 iddsh-scribe)。 数据目录~/.dsh/memory与记忆文件格式都没变,已有记忆不需要迁移; 但如果你之前装过旧名版本,请先remove_bundle掉旧的dsh-memory行再装新的,否则会挂载两次。
开发:本插件是纯 ESM、零运行时依赖,node --test 就能跑,不需要构建步骤。
配置页面(推荐)
安装后打开 DSH 设置,左侧会多出 记忆与知识库 一页,四个分页:
| 分页 | 能做什么 |
|---|---|
| 概览 | 运行状态(后端、条数、时间范围、可用性、数据目录)、界面入口开关、数据边界说明 |
| 记忆 | 按关注点分五组:记忆后端(写入位置、注入开关与上限)、本机存储(目录、引擎)、阿里云百炼记忆库(凭据与隔离键)、检索(重排开关、检索策略、语义嵌入两个子组)、会话摘要与留存;底部统一保存与连通性测试 |
| 知识库 | 索引状态(每个工作区与全局共用一张表:目录、文件数/块数、更新时间、重建/更换目录)、上传文件、使用方式 |
| 数据迁移 | 目标后端 → 生成计划(条数/大小/时间范围/样例)→ 确认执行 → 进度 → 迁移记录与撤销 |
后端的无关字段不会出现:选 local 时不显示百炼凭据,选 bailian 时不显示本机目录——减少每页的干扰项。
页面写的是 profile 配置,保存后插件立即重新挂载,不必手改 YAML。几条它坚持的规则:
- API Key 只写不读:页面只显示「已保存」,输入框留空表示保持不变。
- 迁移必须先看计划再确认:确认按钮用的是后端签发的一次性 token(5 分钟有效、用后作废),跳过页面直接调接口也绕不过去。
- 接口只认同源请求:外来
Origin或sec-fetch-site: cross-site一律 403。
页面走插件自己的本地接口(前缀 /dsh-scribe/api),不经过任何第三方。
配置(YAML)
不想用页面也可以直接写在 DSH profile 的 cordis.patch.yml(或本包的 cordis.patch.yml):
- insert:
- id: dsh-scribe
name: 'dsh-scribe'
config:
defaultProvider: local # 新记忆写到哪
autoInject: true # 会话开始是否注入
autoSave: true # 会话结束是否写回
injectLimit: 5
injectMaxChars: 1200
providers:
local:
type: local
path: '~/.dsh/memory'
engine: file # file(默认,零依赖)| chromadb(需自建服务端)
bailian: # 可选:阿里云百炼记忆库
type: bailian
apiKey: 'env:BAILIAN_API_KEY'
workspaceId: 'llm-xxxxxxxxxxxx'
userId: dsh
planVersion: pro
hybrid: # 可选:云端主 + 本地镜像
type: hybrid
primary: bailian
mirror: local
embedding: # 可选:配了才有语义检索
endpoint: 'https://api.deepseek.com/v1'
apiKey: 'env:EMBEDDING_API_KEY'
model: text-embedding-v3
retrieval:
mode: hybrid # recent | lexical | vector | hybrid
minScore: 0.18
llmRerank: true
summarization:
shortTurns: 10 # 少于这么多轮 → 抽取式
maxChars: 320
retention:
maxMemories: 5000
ttlDays: 0 # >0 时过期即删
| 字段 | 默认 | 说明 |
|---|---|---|
defaultProvider |
local |
新记忆写入的后端 |
autoInject / autoSave |
true / true |
两个自动化开关,可单独关 |
includeSubagents |
false |
子代理是否也注入/记录 |
dataDir |
~/.dsh/memory |
向量缓存与迁移日志所在目录 |
retrieval.mode |
hybrid |
无向量时自动等价于 lexical |
retrieval.llmRerank |
true |
候选多于结果数时用会话模型重排 |
retention.maxMemories |
5000 |
超出按时间淘汰最旧 |
ui.sidebar |
true |
是否在左侧菜单栏放入口 |
knowledgeBase.enabled |
true |
知识库总开关 |
knowledgeBase.workspace.overrides |
{} |
按工作区路径覆盖知识库目录 |
knowledgeBase.workspace.root |
kb |
相对会话工作目录 |
knowledgeBase.workspace.include |
30 种文本/代码扩展名 | 只索引 UTF-8 文本,二进制格式跳过 |
knowledgeBase.workspace.exclude |
原始/、inbox/、锁文件等 |
投放区与原始资料默认不进知识库 |
knowledgeBase.extract.enabled |
true |
是否抽取二进制文档正文 |
knowledgeBase.extract.documents |
.docx .xlsx .pptx .pdf |
需要抽取正文的扩展名 |
knowledgeBase.global.root |
<dataDir>/kb/global |
留空即默认位置 |
knowledgeBase.chunk.maxChars |
1200 |
单块上限,超出按空行切分 |
knowledgeBase.refreshSeconds |
300 |
索引 TTL,过期自动重建 |
apiKey 支持四种写法,避免把密钥写进仓库:字面量、$VAR、${VAR}、env:VAR。
使用
斜杠命令是
/scribe,工具统一带scribe_前缀(scribe_memory_*/scribe_kb_*)。原因是 DSH 的注册表遇到重名会直接抛错——memory_search、kb_search、/memory这些通用名市场上都有别的插件在用,撞上会让后装的插件整体装不上。1.0.0 的旧名/memory仍会在未被占用时自动注册为别名。
什么都不用做:新会话自动带上记忆。
要改设置:设置 → 记忆与知识库(见上)。
让它记住:说「记住这个」,或
/scribe save 提交前必须跑 node --test。主动查:
/scribe search 部署脚本,或直接问模型。看状态:
/scribe status/list/providers。换后端:
/scribe migrate bailian # 只打印计划,不动数据 /scribe migrate bailian --yes # 执行,写可回滚日志 /scribe rollback mig_xxxxxx # 撤销这次迁移新增的记录模型自己调
scribe_memory_migrate时会弹审批框;你拒绝 = 零写入。
后端选择
| 后端 | 数据在哪 | 需要什么 | 适合谁 |
|---|---|---|---|
local + file |
本机 JSONL | 无 | 默认,绝大多数人 |
local + chromadb |
本机 Chroma 服务 | 自建 Chroma + chromadb 包 |
已有向量基础设施 |
bailian |
阿里云百炼记忆库 | 业务空间 ID + API Key | 要跨设备、信得过阿里云 |
hybrid |
云端主 + 本地镜像 | 上面两者 | 要跨设备但怕断网丢上下文 |
换后端不需要改代码,也不需要重开会话。
数据迁移的安全设计
迁移是这个插件里唯一会「批量搬运你的数据」的操作,所以它被设计成四道闸:
- 先看后动:
scribe_memory_migrate第一步只生成计划(多少条、时间范围、目标现状),不写入。 - 审批门:真正执行前走 DSH 的 approval 服务弹框,拒绝即零写入;没有审批服务时直接失败(fail-closed,不会悄悄继续)。
- 只增不删:迁移只往目标端新增,源端一条不动。
- 可回滚:日志记下目标端真实写入的 id(百炼会自己生成 node id),
scribe_memory_migrate_rollback只删这些新增记录,不碰目标端原有数据。
隐私与权限
- 每条记忆就是一小段你可以直接读的文字,没有隐藏字段、没有埋点。
- 插件不收集任何遥测;不请求网络权限之外的系统权限。
- 记忆写回发生在会话销毁时,内容是「这次会话的结论」,不是整段聊天记录。
- 不内置任何托管记忆服务作为默认路径;要用云端,你得自己填凭据。
兼容性
插件声明
| 项目 | 要求 |
|---|---|
| DSH | 需要提供 systemPrompt 与 tools 两个服务(核心行,所有组合都有);其余服务全部可选 |
| Node | ≥ 20(只用到 node: 内置模块;无原生模块、无构建步骤) |
| 平台 | macOS / Linux / Windows 保持一致行为:不调用任何系统命令,不探测本机安装 |
可选服务与降级行为
| 服务 | 缺失时 |
|---|---|
webServer |
设置页没有数据接口;工具、注入、命令照常工作 |
configEditor |
设置页只读(不能保存),仍可用 YAML 配置 |
workspaceRegistry |
知识库分页只显示全局作用域;会话仍按自己的工作目录索引 |
approval |
scribe_memory_migrate 拒绝执行(fail-closed),不会静默迁移 |
llm |
长对话摘要退回抽取式;检索重排退回词法顺序 |
commands |
没有 /scribe 命令,工具仍可用 |
webServer 的 webServer.register 迟到 |
路由等待服务出现再注册;页面刷新后可用 |
客户端侧只依赖 slots;uiWorkspace 缺失时「更换目录」退回手动输入路径。
已知限制
- 首条消息的注入有竞态:section 的文本提供者是同步的,为不让第一步等磁盘,注入靠
agent/created时的异步预热。预热没赶在第一次 prompt 组装前完成,第一条消息就看不到记忆(第二条起正常),此时仍可用scribe_memory_search。 - 百炼适配器未用真实凭据联调:按官方文档契约实现(
/add、/memory_nodes/search、GET /memory_nodes、DELETE /memory_nodes/{id}),有 stub 单测覆盖,但真实环境的行为请以你的账号为准;scribe_memory_status会显示可达性。 - ChromaDB 是可选依赖:需要另装
chromadb并自建服务端,本插件不捆绑;未安装时报错明确,不影响文件引擎。 - 部分失败时日志只记条数:后端只回报失败条数时,
failedIds可能为空(failedCount仍准确);已写入的 id 一定精确,所以回滚可靠。 - 没有 schemastery 配置表单:插件详情页不会自动生成表单(那样需要引入
schemastery);所有配置在「记忆与知识库」页面里图形化编辑,或直接写 YAML。 - PDF 抽取需可选依赖:
pdfjs-dist未安装时 PDF 会被跳过并在页面说明原因;.docx/.xlsx/.pptx不受影响。 - 界面文案是中文:设置页与提示目前只有中文,没有走 Client 的 locale 服务;插件标题/描述有中英两份。英文界面欢迎 PR。
- 首次索引有一次性成本:工作区文件多时第一次索引需要几秒;之后按 mtime 增量,只有改动过的文件会重新切块。
开发
git clone https://github.com/zcd0818/dsh-scribe.git && cd dsh-scribe
node --test test/*.test.js # 零依赖,无需 npm install
CI 在 GitHub Actions 上跑三个平台 × 两个 Node 版本(ubuntu / macos / windows × Node 20 / 22),矩阵配置见 .github/workflows/test.yml
117 个测试覆盖:配置校验、中文分词与词法打分、JSONL 持久化与墓碑删除、按 tag 过滤与分页、去重与检索、prompt 长度约束、迁移计划(只读)、执行与日志、回滚只删本次新增、两种摘要策略与降级、挂载冒烟、工具清单与已发布数量一致、百炼 wire 契约(stub fetch)、本地接口(同源校验、计划 token、单次使用、回滚、延迟注册)、知识库(标题切块与行号、glob 排除、增量复用与删除、引用格式、作用域排序、按行读取、目录摘要、peek 不构建、全局目录自动创建、上传落盘与路径穿越削减、目录切换与恢复默认、默认支持的文件类型边界)、文档抽取(内置 ZIP 读取、docx 段落、xlsx 共享字符串、pdf 缺失依赖时跳过、scribe_kb_read 读抽取正文)、发布前不变量(无本机路径、无调试残留、导入可解析、样式类齐全、清单导出完整)、健壮性(缺服务可挂载、坏索引可重建、不可读/超大文件跳过、并发索引共享一次构建)、界面结构(列表栅格列数恒定、空态与超长路径不破版)、以及四个分页的无浏览器渲染冒烟。
index.js 插件入口(apply:装配 config → service → 会话层/工具/命令)
src/config.js 配置解析与校验(~ 和 $ENV 展开)
src/memory-service.js 门面:去重、嵌入、保留、模型重排、prompt 渲染
src/store.js JSONL 追加存储(墓碑删除、原子重写、写串行化)
src/embeddings.js 可选 /v1/embeddings 客户端 + 磁盘向量缓存
src/providers/ base(契约+统一排序)/ local / chroma / bailian / hybrid / factory
src/session/ lifecycle(注入与回写)/ summarize(两种摘要策略)
src/tools/ memory-tools(模型工具,含迁移审批)/ commands(/scribe,/memory 为可用时自动注册的别名)
src/migration/ migrate(计划→执行→日志→回滚)
src/kb/ chunk(按标题切块+引用)/ indexer(增量索引)/ service(双作用域检索)/ tools(kb_*)
src/web/ api(设置页本地接口:同源校验 + 计划 token)/ config-store(读写 profile 配置)
client.js 设置页(React,注入 settings.section,三个分页)
路线图
- 首次使用引导:检测到已有记忆时,交互式提示选择后端,而不是让你先改 YAML
- 记忆「为什么被记住」溯源:
scribe_memory_list里回链到来源会话 - 冲突解决:同一主题的新旧记忆并存时,标出可能过期的条目
- 记忆浏览:在设置页里直接看/搜/删单条记忆(目前只有
scribe_memory_list工具与/scribe list) - PDF 抽取开箱可用(目前需可选依赖
pdfjs-dist;Word/Excel/PPT 已内置) - 知识库向量检索(当前是词法打分;嵌入端点复用记忆那套)
- 更多后端:自建 OpenAI 兼容向量库、本地 Ollama 嵌入
有需求或踩到坑,欢迎开 issue —— 尤其是你所在环境的 DSH 版本和 scribe_memory_status 的输出,那能省掉一半排查。
贡献
git clone https://github.com/zcd0818/dsh-scribe.git && cd dsh-scribe
node --test test/*.test.js # 零依赖,无需 npm install
CI 在 GitHub Actions 上跑三个平台 × 两个 Node 版本(ubuntu / macos / windows × Node 20 / 22),矩阵配置见 .github/workflows/test.yml
改后端只需实现 src/providers/base.js 的方法并把 type 注册到 src/providers/factory.js;改动请带上对应的单测(现有测试里有 stub fetch 的写法可以直接抄)。
No comments yet. Be the first to write one.