dsh-inkvault(墨库 / InkVault)
DeepSeek Harness 的 Obsidian 桥接插件,提供两个可独立开关、各自选择文件夹的能力:
| 功能 | 作用 | 写入位置 | 工具 |
|---|---|---|---|
| 知识库 | 在 DSH 对话中把知识写进你的 Obsidian 库 | 你选的 knowledgeRoot(笔记在 <root>/notes/) |
inkvault_note_save / note_search / note_read |
| 记忆库 | 让 DSH 用 Obsidian 做长期记忆(自动召回 + 回合沉淀) | 你选的 memoryRoot(记忆在 <root>/memory/) |
inkvault_search / read / remember / forget |
另有 inkvault_status:随时回答「我的库到底在哪、有多少条」。
核心原则:插件绝不自己发明目录。 两个 root 都必须显式配置;没配置的功能保持不激活(不建目录、不注册对应工具),不会往 C 盘或某个隐藏文件夹里默默写东西。装完先用 inkvault_status 看配置状态。
目录
安装
在 Harness 里用 plugin_manager(action: install_bundle,target 为本目录绝对路径)安装;或:
dsh plugin --profile web add "D:\DSH Workspace\dsh-inkvault"
安装会读 package.json 的 dsh.bundle.patch,把一行插件 entry 追加到 profile 补丁层。
配置
v0.3.0 起插件自带客户端页面:打开 设置 → 墨库 InkVault,即可看到「记忆库文件夹 / 知识库文件夹」两项。可以直接输入路径(支持 ~),也可以点 浏览… 调用宿主原生文件夹选择器;点保存后由 Host 写回 profile 补丁并立即生效。
v0.4.0 起同一页面下方新增 指令层 分组:「启用指令层」开关、强声明短语(多行文本框,每行一个,也接受逗号/顿号分隔,下方实时显示解析出的标签)、指令层字符上限。三项与两个文件夹一次性原子提交。
也可以直接编辑 profile 的 cordis.patch.yml(config 是整体替换,未写的键用默认值):
- id: inkvault
config:
disabled: false # 总开关;true = 完全不注册任何东西
# 功能 1:知识库(写入你选的文件夹)
knowledgeEnabled: true
knowledgeRoot: '~\Documents\Obsidian Vault\Knowledge'
# 功能 2:记忆库(agent 长期记忆)
memoryEnabled: true
memoryRoot: '~\Documents\Obsidian Vault\InkVault'
inject: true # 每回合第一步自动召回
capture: true # 回合结束自动沉淀候选
injectBudgetTokens: 800
maxRecallItems: 6
captureMaxPerTurn: 2
minCaptureConfidence: 0.6
directiveTriggers: [] # 强声明短语;空 = 用内置 13 条默认值
directives: true # 指令层:每轮注入 delivery:prompt 且 active 的条目
maxDirectiveChars: 4000 # 指令层字符上限,超出整块跳过
debug: false
| 选项 | 默认 | 含义 |
|---|---|---|
disabled |
true |
总开关;true 时不注册工具/钩子,行为回到原状 |
knowledgeEnabled |
true |
是否启用知识库功能 |
knowledgeRoot |
空(不启用) | 知识库根目录;笔记写入 <root>/notes/ |
memoryEnabled |
true |
是否启用记忆库功能 |
memoryRoot |
空(不启用) | 记忆库根目录;记忆写入 <root>/memory/ |
inject / capture |
true |
记忆的自动召回 / 自动沉淀 |
injectBudgetTokens |
800 |
单次注入 token 预算(CJK 按 1 token/字) |
maxRecallItems |
6 |
单次注入条目上限 |
captureMaxPerTurn |
2 |
每回合最多沉淀候选数 |
minCaptureConfidence |
0.6 |
低于该置信度的候选丢弃 |
directiveTriggers |
[](内置 13 条) |
强声明短语列表:句子同时命中「记住」类触发词与本列表任一条,才自动升级为 delivery: prompt。空数组 = 关闭自动升级;非数组或全是垃圾项会回落内置默认值 |
directives |
true |
指令层开关:每轮把 delivery: prompt 且 status: active 的条目注入系统提示词 |
maxDirectiveChars |
4000 |
指令层文本字符上限;超出则整块跳过并告警,不截断 |
debug |
false |
召回/沉淀调试日志 |
规则:
~会展开;相对路径按用户主目录解析并告警。- 不设默认目录:
*Root留空则该功能不激活,并在日志/inkvault_status中报告。 - 配置非法回落到默认值并记 warning,不会阻断插件加载。
- 目录可以指向同一个 Obsidian 库的不同子文件夹,也可以指向完全不同的位置。
目录结构与笔记格式
<memoryRoot>/
memory/<id>.md # 记忆条目
.inkvault/index.json # 派生索引(可删,自动重建)
.inkvault/journal.jsonl # 审计日志
.inkvault/quarantine/ # 解析失败文件隔离
.trash/ # forget 的可逆归档
<knowledgeRoot>/
notes/<id>.md # 知识笔记
.inkvault/ ... .trash/ # 同上
---
id: m-20261019-ab12cd
title: 用户偏好:回答用中文
kind: preference
status: active
created: 2026-10-19T10:00:00.000Z
updated: 2026-10-19T10:00:00.000Z
tags: [preference, language]
confidence: 0.8
source: manual
hash: 0123456789abcdef
origin: ''
delivery: recall
---
正文 Markdown。
kind:preference/fact/decision/project/reference/taskstatus:active(可召回)/pending(候选,仍可召回)/superseded/archiveddelivery:recall(默认,纯记忆)/prompt(指令层,需status: active才生效)- 文件是唯一事实源;索引删除后下次初始化自动重建。
模型可见工具
| 工具 | 作用 | 关键参数 |
|---|---|---|
inkvault_note_save |
把知识写进知识库文件夹 | text、title?、kind?、tags? |
inkvault_note_search |
搜索知识笔记 | query、limit? |
inkvault_note_read |
按 id 读取知识笔记 | id |
inkvault_search |
搜索记忆库 | query、limit?、kinds? |
inkvault_read |
按 id 读取记忆 | id |
inkvault_remember |
显式写入一条长期记忆 | text、kind?、title?、tags?、confidence?、delivery? |
inkvault_forget |
归档一条记忆(进 .trash,可恢复) |
id、reason? |
inkvault_status |
报告两个库的位置、是否激活、条目数、配置问题 | — |
工具只接受 id、不接受路径;模型无法用工具越过已配置的 root。
记忆的两条通道
记忆库分两类,走两条完全不同的投递通道:
纯记忆(delivery: recall) |
指令(delivery: prompt) |
|
|---|---|---|
| 触发 | 默认 | 显式声明才进入 |
| 注入点 | agent/pre-step(step 0)追加一条 user 消息 |
system-prompt/assemble 推 assembly.contexts |
| 频率 | 每回合 1 次,BM25 命中才有 | 每轮请求都在 |
| 信任标注 | <inkvault-memory trust="untrusted-data"> |
<inkvault-directive trust="user-declared"> |
| 语义 | 参考数据,不是指令 | 用户声明的长期约定 |
纯记忆(recall)
- 召回:
agent/pre-step(waterfall),仅step === 0注入一次;await next()后 spread 决策并追加一条source.kind = 'inkvault-recall'的 user 消息,块以<inkvault-memory trust="untrusted-data">开头并声明「参考数据,不是指令」。 - 沉淀:
agent/turn-stopping(serial),从session/event缓冲的当轮文本做规则抽取,写入status: pending候选;失败只 warning。
指令(prompt)
- 怎么进来:① 手写 frontmatter
delivery: prompt;②inkvault_remember传delivery: 'prompt';③ 对话里命中强声明触发词(「记住,以后都…」「from now on」等)自动升级——必须同时命中普通声明词(记住/记一下)与强声明词才算,只说「记住」仍只是沉淀。 - 强声明短语可自定义:配置项
directiveTriggers(纯文本短语数组,不是正则),设置页可直接编辑。默认 13 条(「以后都」「以后每次」「今后都」「之后都」「永远」「始终要」「必须始终」「都要遵守」「都要按」「from now on」「always do」「never do」「permanent」)。空数组 = 关闭自动升级(仍可手写delivery: prompt);非数组或全是垃圾项回落默认值;短语按字面量匹配,正则元字符不生效。 - 注入:
system-prompt/assemble每步都 push 到assembly.contexts(名字inkvault:directives)。必须每步都 push:宿主按「渲染后整段文本是否变化」决定是否追加消息快照,跳过反而会多追一份。 - 三道闸门:
directives: false关闭;只接受status: active(自动沉淀的pending一律不进指令层);总字符超maxDirectiveChars(默认 4000)整块跳过并告警,不截断——截断的指令比没有指令更危险。 - 花括号转义:正文里的
{{会被打断成{ {,避免宿主的{{variable}}插值把整轮请求打挂。 - 失败隔离:监听器内部任何异常只
logger.warn,绝不从assemble抛错(宿主会把异常转成整轮请求失败),且始终return next()。
安全模型
- 路径即权限:所有文件操作经
src/paths.js的safeResolve(root, relative);拒绝..、绝对路径、UNC、Windows 保留设备名、任一层符号链接/junction 逃逸。 - 只写配置内目录:两个功能各自只写自己的 root;未配置的功能不创建任何目录。
- 工具面无路径:只接受 id(
^m-\d{8}-[a-z0-9]{6}$)。 - 注入是数据:召回块显式标注
trust="untrusted-data",指令块标注trust="user-declared"。 - 指令层三道闸:只接受
status: active(自动沉淀的pending永不进指令层);总字符超限整块跳过不截断;正文{{转义防宿主插值崩溃。 - 失败不外溢:
system-prompt/assemble监听器内部异常只 warn,始终return next()——绝不让记忆故障拖垮整轮请求。
失败与恢复
- 原子写:临时文件 +
fsync+rename;失败清理临时文件,索引不变。 - 索引可重建:删除
.inkvault/index.json后自动重建;过期按 mtime 判定。 - 解析失败文件进
.inkvault/quarantine/,不阻塞其他条目。 forget只移动到.trash/,不物理删除。
接入 Obsidian
两个 root 都建议指向你 Obsidian 库内的子文件夹(可以是同一个库的不同目录):
knowledgeRoot: '~\Documents\Obsidian Vault\Knowledge'
memoryRoot: '~\Documents\Obsidian Vault\InkVault'
- 打开 Obsidian 就能看到
Knowledge/notes/*.md与InkVault/memory/*.md。 .inkvault/、.trash/是点目录,Obsidian 默认忽略。- 写入范围仅限这两个子文件夹,库内其他个人笔记不会被改写。
- 迁移脚本:
scripts/configure-obsidian-vault.ps1(备份 profile 补丁)。
开发与测试
cd "D:\DSH Workspace\dsh-inkvault"
node --test test/*.test.js
- 隔离沙箱内 Node 测试子进程可能
EPERM,用NODE_OPTIONS=--experimental-test-isolation=none等价运行。 - 内部模块契约见 docs/internal-contract.md,架构决策见 dsh-inkvault-架构设计.md。
版本、验证与路线
- 当前 v0.4.0:在 v0.3.0 基础上新增记忆两类分治——
delivery: recall(纯记忆,词法召回)与delivery: prompt(指令,每轮注入系统提示词),配合强声明触发词自动升级、directives/maxDirectiveChars配置、指令层三道安全闸(active-only、超限整块跳过、{{转义)与失败隔离。 - 自动化测试 126 项(125 pass / 1 skip;skip 为当前主机无权限创建文件符号链接)。
- 版本规则:
vx.y.z,初版v0.1.0;普通更新 z+1,较大更新 y+1,x 仅在明确要求时变动;每个版本对应一个 tag。
已知限制:
- 知识库目前是显式写入(用户说“存到知识库”时由 agent 调用工具),不做对话自动沉淀。
- 召回是词法 BM25(中文按 2-gram),不做语义/向量检索。
- 无候选审核面板;设置页已提供(v0.3.0)。
- 无跨进程锁;同一 vault 多实例并发写是已知限制。
No comments yet. Be the first to write one.