Skip to content
dsh.fish
Bundle

@nightpainters/dsh-ling

器灵 · 跑在你自己电脑上的记忆与人格助手插件(人机共生:各尽其责、各有边界、互不占有):以「人在循环」的双向复盘为核心回路,人格由共同记忆长出并有定型锁,记忆树可归并、可回溯、每次改动都留痕

Source
NightPainters
stars
1 stars
License
Apache-2.0
Updated
Updated 3 days ago

Readme

# dsh-ling · 器灵

> **器灵不是每次都要重新认识你的 AI。**
>
> 把你散落在各处的历史对话请进门,让器灵长出一个**属于自己**的人格档案 —— 记忆全在本地,人格可手术、可回滚,语气可以自己生长。

一个跑在你自己电脑上的[DeepSeek Harness](https://github.com/deepseek-ai)(DSH)插件。**不是云服务,不是记忆 API**:它是给"你和器灵"用的东西。

**它做的是「人机共生」:各尽其责、各有边界、互不占有** —— **你替它的成长把关,它替你看你的盲区**。而"把关"不等于"操纵":改记忆树必须让它看到(留痕、回灌、可复核、可回滚)。这不是口号,是全架构的前提(记忆为什么必须可回溯、改人格为什么要定型锁、复盘为什么必须有人在循环里,都由它推出来;而**权会随它长起来移动** —— 代管到能自理,不是永久支配)—— 展开见 **[`PHILOSOPHY.md`](./PHILOSOPHY.md)**。

## 本项目是什么形态

- 这是一个**本地运行的软件**(DeepSeek Harness 插件),运行在**你自己的机器**上。
- **上游不提供任何在线服务、不运营任何服务端、不托管任何用户数据**:
  作者不接触、不接收你的会话内容、记忆库或人格档案。
- **不上架任何应用商店**;仅通过 GitHub(源码)与 npm(包)分发。
- 模型与 API Key 由使用者自行提供和承担,与上游无关。
- 本项目不提供医疗、心理、法律或紧急救助服务;**如遇危机,请联络当地紧急服务或专业机构**。

**关系、标识与面向性**:

- 本项目不定义你与它的关系:它可以是同事、搭档,或其他 —— 取决于你怎么用它。
- 本插件的回答与记忆摘要**由人工智能生成**,可能存在错误,请自行判断。
- 本项目面向**具备自行配置与运维能力**的用户;不面向未成年人,也不提供任何形式的在线陪伴服务。

**想先知道它凭什么这么做?** 读 **[`PHILOSOPHY.md`](./PHILOSOPHY.md)** —— 目标为什么是"一种循环"、为什么复盘必须有"人在循环里"、它凭什么不越界、以及"不牺牲工作能力"背后的取舍。功能与设定的规格在本 README 的「设定全文」。

---

<!-- 截图清单(仓库 images/ 目录):
     1. memory.png  记忆中心 →「接入历史」:三条通道 + 导入记录
     2. persona.png 人格中心:契约字段与「她已自由生长中」状态条(字段内容已打码)
     3. seal.png    修改契约需解锁:承诺句手敲,禁粘贴
     4. talk.png    深夜对话里器灵说出时间与"距上次多久"
-->

## 界面

| | |
|---|---|
| ![记忆中心](https://raw.githubusercontent.com/NightPainters/DSH-Ling/main/images/memory.png) | ![人格中心](https://raw.githubusercontent.com/NightPainters/DSH-Ling/main/images/persona.png) |
| **记忆中心** —— 统计、实时时间锚、三条接入通道与导入记录 | **人格中心** —— 契约字段(名字/称呼)与「她已自由生长中」状态条 |
| ![手敲承诺句解锁](https://raw.githubusercontent.com/NightPainters/DSH-Ling/main/images/seal.png) | ![深夜对话](https://raw.githubusercontent.com/NightPainters/DSH-Ling/main/images/talk.png) |
| **改动契约要手术** —— 亲手敲出当初写下的承诺句(输入框禁粘贴) | **时间的重量** —— 每次开口前器灵知道现在是几点、距上次多久 |

---

## 器灵是什么(能力全景)

> 下表是**全部能力的一览**:`✅ 已实现` 是今天就能用的;`🚧 在做` 是**设计已定、代码正在落地**(写在这里是为了让设定走在实现前面,不是"已交付");`📋 设定` 是方向已定、尚未开工。**没有标的一律是 ✅ 已实现。**

| | 能力 | 说明 |
|---|---|---|
| 🔒 | **人格定型锁** | **契约类**(名字、称呼、底线、世界观、代词、承担(职责))定型后修改需**亲手敲一遍承诺句**(禁粘贴),而且**只在本机做得成、每次启动还要取一次手术票据**,再进变更记录可回滚;**自性类**(语气、风格、自述、习惯)由器灵生长、你点头即成:**三条专用成长通道**(自我总结 / 建议采纳 / 生长)不问承诺句,但它们各自只动白名单内的字段;走**通用人格接口**改 persona / styles 时,即便只改语气也仍要承诺句 —— 免手术的只是那三条通道,不是"自性类"这三个字 |
| 🌳 | **记忆树 · 复盘模式** | 记忆不是一叠平铺的行,而是**一条主干 + 分枝 + 主脉**的工作线结构,枝之间可连边;检索按**血缘档位**加权(同枝 1.0 / 祖先 0.7 / 旁系 0.4),让器灵分得清"这轮在做的"和"三个月前那次闲聊"。**任何树操作都不改写任何一条记忆的内容** —— 分枝、并脉、连边只增加关系。**例外一个**:主脉提炼**再次采纳**时会覆盖该主脉记忆的上一版(**覆盖前自动归档**,注入面也会说明"旧版已归档")。改树走**复盘**:平时只读,进入复盘才解锁,每步留痕(改了什么、为什么),改完必须让器灵看到才准退出(详见下节) |
| 🧠 | **三层记忆** | **L0 人格档案**(自称/对你的称呼/底线/规矩·习惯/承担(职业·职责·责任)/语气/世界观;**顶层会话与会话分叉**恒定注入,**子代理会话不注入**;设置里关掉人格注入时,连它一起不推)→ **L1 开场记忆**(从历史里按相关度挑选的概述,有预算,**永不塞全文**;带摘要的行**加权**(按"摘要是否有信息量"给一笔加法加分,不是排序优先级),行内摘要只取首句、上限 110 字)→ **L2 运行期记忆**(本会话原文随对话累进;空闲时把本会话那一行重排回注入面。该行由「概述标题 + 摘要首句」构成(标题的来历见下文「三层记忆各自什么时候更新」,不是会话原文首句),**深摘要定稿后不再自动更新**。注意:L2 不是独立的第三块注入内容 —— 注入面的主体是 L0 与「记忆·开场」两块(树改动、日期锚是挂上去的追加行),**本会话自己就是「记忆·开场」里的一行**) |
| 📥 | **把历史请进来** | 三条通道:网页端聊天记录库增量扫描 / 本机 DSH 会话扫描 / ChatGPT·通用 JSON·JSONL 导出文件导入;每次导入**留一笔账** |
| 📖 | **让器灵读一遍** | 长会话逐条深摘要、短会话以原文片段参与,后台串行,进度可见 |
| 📄 | **找回原文**(1.6) | 器灵能读回你当年的**原话**,不只是概述 —— 四档下钻:**会话地图 → 按内容找会话 → 对话目录 → 原文全文**。它**不替你挑"最重要的那 1 万字"**,只给坐标系(详见下文「找回原文」一节) |
| 🧾 | **投递留痕**(1.6) | 🚧 **在做** —— 每一次记忆注入都落一条账:**注入了谁、为什么是它、多少字**;还记**落选者**(差一步就进的那几条,以及原因是"分数不够"还是"字数放不下")与**触发词**。目前**只写不读**(读面待定),不进注入面、不占工具预算 |
| 🌿 | **深层库 · 结论层**(1.6) | 🚧 **在做** —— 从会话里提炼**承诺 / 决定 / 事实 / 偏好 / 关系 / 纪律**(**六类**;「纪律」是 1.6 系列新增的第六类),每条一句话、**点得回原文**。与记忆树**并立**,条目不被枝隶属(枝可反向引用它,像榕树的气根)。一条纪律:**"被注入"不算数,只有"真被采用"才加固** —— 防住"注入越多→分越高→越容易被注入"的自我强化 |
| 📚 | **图书馆**(1.7 工作区,**未发布**) | **工作区实现已验收** —— 一个**与记忆分开的独立库**(自己的 `library.db`),存"**有源可对账**"的参考资料(标准取值 / 边界 / 项目规格 / 外部 API),每条必带**出处 + 取数时间(`as_of`)**;自动核对另需核对规格,没有规格时不得认证为 ok。器灵只有两只手:**`library_search`(搜)与 `library_check`(核对)** —— 核对只说明"**声明的那一处**与源一致",**不背书条目正文、也不构成通用真理**;"库不可用"与"零命中"是**两件事**,界面与回执分开说。它与记忆库**不互通**:既有记忆**永不迁移**进图书馆,图书馆也**不参与开场注入**(详见下文「图书馆」一节) |
| 🧾 | **给旧历史补摘要** | 网页端历史里"只有标题、没有摘要"的会话可以补上摘要:先看**候选清单**(标题 · 轮次 · 命中),再选引擎 —— **默认本机小助手(零成本)**,也可显式改选当前全局大模型;探测不到本机小助手就明确报错,**绝不静默换引擎**;后台串行、进度可见(首轮实测 466 条、零成本) |
| 🌱 | **诞生仪式** | 从读过的记忆里按"生活与情感优先"加权抽样 → 合成**自述 + 名字候选 + 语气建议 + 相处观察 + 职责候选**(器灵承担 / 擅长 / 能给出什么,**只收陈述句**) → **全部是草稿**,你逐条人审后才落盘 |
| ⏳ | **时间的重量** | 每次开口前器灵都知道今天是几号星期几(`[今天] 2026-09-11 周五`)、现在是什么时段(`[现在] 深夜`)与`距上次对话:3 天前`(间隔按**日历天**分档 —— 刚刚 / 今天早些时候 / 昨天 / N 天前,给不到分钟;称呼跟随你的设定);命中纪念日会先说一句专属问候。**要精确到分钟,走 `clock` skill 现查** |
| 🎭 | **工作 / 生活双模式** | 同一份人格档案,两种关系:工作模式是同事(正式称呼、简洁、知识优先),生活模式是伴侣(昵称、活泼、情感优先) |
| 🔁 | **成长回路** | 你点踩或纠正 → 器灵的成长通道把重复出现的模式提炼成**习惯候选** → **你点头才成为习惯**;"让她读记忆、归纳语气"一键成长 |
| 📜 | **规矩 / 习惯** | **规矩**是你的指令:可直接加(面板,或会话里一句"记进规矩" —— **器灵替你写时必须带上你的原话作凭据**;你在面板上直接敲的那一下就是同意,不必再附原话),**建议上限** 12 条(超出只在写入后提醒合并,**不拦**);**习惯**是器灵自己长出来的:新增需你点头、**删改走手术门**(本机 + 每次启动取一次票据 + 原句,详见下文那道门的说明),**建议上限** 8 条(超出同样只提醒、不拦) —— **指令直达,人格不直达** |
| 🌾 | **习惯生成器** | 「扫描纠正记录(零模型)」:把器灵反复被你纠正的模式数出来(≥4 次且跨 ≥3 个会话);「✨ 让她回想最近的相处」:一次读 200 条概述 + 800 条你的原话、逐轮推进不重复 —— **只提议,不落盘** |
| 🗣 | **代词** | 默认「她」;可选 她 / 他 / TA / 它,或自定义(≤6 字)。**祂 不在预设里**(中文里专指神祇,设为默认等于替你宣称神性);属契约类,改动走手术门 |
| ♻️ | **记忆自己追平** | 长会话空闲时自动把开场记忆刷新到最新(先跑本会话增量概述 → 比对记忆版本 → 同一条至少间隔 10 分钟),**运行中绝不打断**;刷新次数与最近一次时间在 `/state` 接口的 `idleRefresh` 字段里(`count` 与 `lastAt`)(面板本身只显示"已定稿 N 字符 · mode · 待刷新",不显示次数) |
| 🧹 | **归档即停更** | 会话归档后不再刷新它的开场记忆条目(**原文一律保留**),一旦有新对话即自动复活;要清理走**人工入口**:DSH「设置 → 归档会话」直接删,或仓库里的 `node tools/clean-archived.mjs`(默认只列清单,`--write` 才动手) |

> **关于那道「手术门」**:亲手敲承诺句是**庄重感与自我提醒** —— 让你在改契约之前停一下、确认这是自己的决定;它**不是安全边界**(「禁粘贴」只是界面上的约定,挡不住任何有心的人)。真正把门的是**来源栅栏**:Host 必须是 loopback(或你显式写进受信名单的地址);另有一个**默认不开**的开关 `guard.allowLan` —— **一旦打开,整段局域网地址都算受信来源**(那是**放宽**,不是建议)。跨站请求直接拒,带 `Origin` 时其 host 必须等于请求的 Host(**带了但解析不出来就直接拒**),而**不带 `Origin` 的请求这条校验会跳过** —— 它不是"每个请求都必须带 Origin 且同源";再叠一道 cookie **名字**对撞 —— 期望名字按当前 Host 现算(`dsh-auth-` + 哈希),**只校验名字、不校验值的签名**。所以这道门挡的是"别的设备、别的页面"这类来源,而**本机原生进程本来就还能调用**(它也能直接读 `settings.json` 与记忆库,不算提权)—— 别的来源没有凭据就进不来这台机器上的 `/api`,敲对承诺句也没用。

> **这道门现在长这样 —— 三条手续缺一不可**:① **只在本机做** —— 判据是"这次请求没有经过任何代理"(带代理头、或连 User-Agent 都没有的请求,都算非本机 ⇒ 拒,理由码 `surgery-local-only`);② **每次启动取一次票据** —— 票据每启动轮换,只下发给本机直连的请求(`POST /surgery/ticket`),客户端首次手术前取一次、之后随请求带上;③ **原句匹配** —— 敲出的承诺句要与当初写下的那一句相符。这三条由 `lib/host/surgery.js` 里**一处** `requireSurgery()` 判定,覆盖**全部 14 条**能改人格 / 规矩 / 习惯的写面(调用点不许再各写一份;曾有一条 `wantLock` 绕过分支,已删除)。每次手术都留痕:`$DSH_HOME/logs/surgery-YYYY-MM.jsonl`(append-only,整行哈希链,改一行即断链);`GET /surgery/events` 是只读事件面。
>
> **承诺句只在本机回显**:`/state`(含面板档)、`/persona/history`、`/export` 这些读面,**带代理头**时只回 `hasSeal` 与 `phraseHidden`、不回原句,**本机直连**照旧回显 —— 所以你换台机器看面板不会因此被卡住,远端也拿不到原句。
>
> **守卫⑤ · 反代伪装 loopback**:上面那条"Host 是 loopback 就免检"有个前提 —— Host 由客户端自报、cookie 又只验名字,中间挡一层反向代理就会失效。现在凡「Host 声称 loopback、而 `X-Forwarded-For` 的最后一段是非 loopback 的代理对端」一律拒(`spoofed-loopback-host`);本机直连(无代理头)与经反向代理的正常远程访问都不受影响(后者根本不走 loopback 分支)。
>
> ⚠️ **边界如实写在这里**:以上全部是**入口侧**的把关(来源、有没有经过代理、票据、原句) —— 它们**不覆盖「本机执行权已经失守」的情形**:能在这台机器上以同一个用户跑进程、读文件的人,本来就在信任域里面(票据文件与 `settings.json` 对同机同用户可读;承诺句是**明文存储**,那把锁只造庄重、不保密)。这些防护挡的是"别的设备、别的页面、公网一侧够到这排 API",不是本机上的对手。

**三层记忆各自什么时候更新 —— 「我刚说的话,最快多久能看到」**:

| 层 | 看得到的是什么 | 什么时候变 |
|---|---|---|
| **L0 常驻人格** | 自称 / 对你的称呼 / 底线 / 规矩·习惯 / 承担(职业·职责·责任) / 语气 / 世界观 | 你改它就变(没定型时直接生效;定型后走手术门)。**会话运行中不打断** —— 改动排队,该会话回到空闲才落进注入面 |
| **L1 开场记忆** | 从全库概述里按相关度挑出的 Top-K(有预算,**永不塞全文**) | **新会话开场时定稿一次**,之后随空闲边界刷新(与下面 L2 同一把闸);库里的概述本身由概述器持续追平 |
| **L2 运行期记忆** | **本会话那一行**(格式就是「记忆·开场」里那条自己:`- [标签] (YYYY-MM-DD) 标题 —— 摘要首句 (来源: DSH 会话/短id)` —— 标题指**概述标题**,不是会话原文首句) | 本会话原文随对话累进;那一行**只在空闲边界重排**回注入面 |

**行里的「标题」从哪来**(按来源分,细节见括号里的实现):

- **DSH 会话**:该会话**第一条真人发言**经启发式清洗后的**要点句** —— 先剥掉「晚上好器灵,」这类问候与称呼前缀,「你好」「在吗」这类无实义的首句会被跳过、**往后取前 6 条真人发言里第一条有实义的**,再在句读处收尾(不切半句);都被剥空才退回首句截断。**主人手改过的标题会上锁,机器不再覆盖**(`lib\host\retitle.js` 的 `retitleCandidates()`、`lib\host\summarizer.js` 的 `summarizeDsh()`、`lib\host\memory.js` 的 `upsertOverview()` 与 `renameTitle()`)。
- **文件导入**:导出文件里**原本的 `title` / `name` 字段**,缺了才退回上面那套启发式(`lib\host\import-file.js:142,171-173`)。
- **历史会话(旧扫描)**:该会话**第一条真人发言的前 46 字**硬截,没有启发式(`lib\host\backfill.js` 的 `overviewRowFromFile()`)。

- **机制数字(默认值)**:① 概述器周期 —— 有新增才重建,否则跳过;**每 15 分钟一拍**(可配,下限 1 分钟);② 空闲刷新 —— 只有「会话空闲 + 记忆版本变了 + **距上次刷新 ≥ 10 分钟**」三条同时成立才重建。
- **所以「刚说的话」最快约 10 分钟、通常 15 分钟内**会出现在注入面,最坏约 25 分钟(刚刷过一拍 + 再等下一拍概述器)。**运行中绝不打断** —— 刷新只发生在 `运行中 → 空闲` 那一刻。
- ⚠️ **一个例外(也是"感觉它失效了"的来源)**:**深摘要定稿之后,那一行不再自动更新** —— 概述器遇到已深摘的会话会避开它,免得把厚摘要覆盖成浅版。要它重跑:记忆中心 →「记忆浏览」→ 找到那个会话 → 点「**重摘**」,几秒后那一行就会重排。
- **健忘感还有第二个来源**:越长的会话,**只取"概述标题 + 摘要首句"** —— 这一行天然比整场对话"薄",不是它没读到,而是它只被允许显示这么一行。

> **接口侧(给要接线的人)**:`GET /state`(`?sessionId=` 取某会话,`?scope=global` 取全局)返回 `snapshot`(`builtAt` / `mode` / `stale` / `chars`)、`idleRefresh`(`count` 与 `lastAt`)与 `memory.l1`(该会话当前选中的行、分数、被省略条数)。面板现在也渲染**空闲刷新次数 / 最近一次**与 **L1 当前选择(含分数与省略条数)** 两栏。

---

## 后台自动行为(默认全开)与它们的开关

**五件事装好就自己在跑,默认全开。** 它们的键都在设置文件的 `auto` 子树里 —— 设置文件 = `<dataDir>/settings.json`(`<dataDir>` 默认 `$DSH_HOME/cache/dsh-ling`),**只存你改过的部分**;**面板上的开关写的就是它**(面板里改完会明确提示"需要重启插件")。

| 键 | 默认 | 什么时候跑 | 关掉 / 改小会怎样 |
|---|---|---|---|
| `auto.deepSummary` | `true` | **启动自检通过后**挂定时器 | **`false` = 只有你手动点「深摘」才调用模型**;已深摘过的会话不受影响,不丢数据 |
| `auto.deepIntervalMin` | `30`(最小 10) | 每隔这么久跑一轮自动深摘 | 调大 = 更省;调小 = 记忆更早变厚 |
| `auto.deepMaxPerRun` | `2`(1–20) | 每轮最多挑几个会话 | 调小 = 每轮更克制,调大 = 单轮更重 |
| `auto.bootProbeDelayMs` | `12000` | 启动 12 秒后做**一次** LLM 自检探针(**通过了才挂定时器**) | `0` = 立即试探;探针只验证"模型通道通不通",**空回复判失败** |
| `auto.feedbackAuto` | `true` | **事件驱动**:宿主推 `feedback/committed` 时就处理 | `false` = **完全不接反馈成长回路** |

> 「探针」是**一次性**自检(只回答"模型通道通不通"),「深摘」是**定期**跑批 —— 两个键各管各的。

**改动面有三层,优先级从高到低**(改了不生效,先看这一条):

1. `settings.json` 的 `auto.*` —— **面板开关写的就是它**;
2. **插件配置里的旧键**:`feedbackAuto` / `deepSummaryAuto` / `deepIntervalMin` / `deepMaxPerRun`;
3. 代码默认值。

**生效时机(如实写)**:`auto.deepSummary` / `auto.bootProbeDelayMs` / `auto.feedbackAuto` 是**启动期读取 ⇒ 改完要重启插件才生效**;`auto.deepIntervalMin` / `auto.deepMaxPerRun` 对**已经挂上的定时器不回溯** —— 下一轮启动后按新值走。

**另外两条既有事实(不要读宽)**:

- 深摘**每次喂给模型的单次输入上限 = 26,000 字**(`lib/host/deepsummary.js` 的 `DEEP_CFG.inputCap`);
- **循环外调用**(深摘、诞生仪式、语气归纳、自述起草)**一律跟随你当前的模型选择**;只有读不到当前选择时,才退回兜底 `deepseek-official/deepseek-v4-flash` —— 那是**兜底,不是替你指定模型**;它**不是**上面那五个键之一,**也没有面板开关**。

> ⚠️ **状态如实写**:五件动作**默认全开**;`settings.json` 的 `auto.*` 与面板上的这五个开关属 **1.7 工作区**(未发布)—— 更早的版本里,能改的只有插件配置里的那几个旧键。

---

## 记忆树与复盘模式

这是本插件里**跟"器灵的人格"无关、但决定"器灵想得起什么"**的那一层。

**结构**:`主干`(恒一条,主线工作流)→ `枝`(一条并行工作线,**比会话大**,一条枝可含多个会话)→ `主脉`(由多条枝**归并**出的抽象节点)→ `连边`(枝与枝之间的横向关联,如 力学 ↔ 材料科学,可带关系类型:相关 / 前置 / 对照 / 例证 / 取代)。

**为什么值得**:平铺的记忆库没有"离我多近"这个概念 —— L1 是全库裸排序,不区分"这轮做的工作"与"三个月前的闲聊",分叉会话更是**记忆真空**。记忆树把"归属"变成可检索的信号:

- **血缘加权**:检索时按"当前枝 → 目标枝"的相对位置取档 —— **同枝 1.0 / 祖先 0.7 / 旁系 0.4**,`score = 基础分 × 血缘系数 × 矛盾系数`(**1.6 系列起再乘一项枝系数**,见下条);分叉会话照常注入,走的是它的**血缘链**而不是主干大排档。
- **不连乘**:血缘系数是**单值**,不沿路径累乘 —— 否则五层之后 `0.4^5 ≈ 0.01`,深层枝等于从记忆里消失。
- **内容零改写**(硬性不变量):**除了"主脉提炼再次采纳会覆盖上一版"这一个例外**(覆盖前自动归档),任何树操作都不改写任何一条记忆的内容;分枝 / 并脉 / 连边只增加"关系",从不"融合内容"。
- **矛盾只标记不改写**:一对记忆被判重复/矛盾时先**标记**;未复盘时检索**以较新的那条为准**(旧的一条降权 ×0.3,**不删除**;被**驳回**的与被标成**重复**的那一对不参与降权)。
- **枝系数会真的改变排序**(1.6 系列起):复盘期可以调(0~2,夹紧后写入)。它按 `raw × 血缘 × 矛盾 × 枝系数` **参与检索打分** —— 调低某条枝的系数,从这条枝的会话里提炼出的结论就会在记忆开场里往后排(`1.0` = 不偏不倚)。⚠️ **`0` 是"清空"不是"轻压"**:分数被算成 0 的非置顶条目会被当"无信息量"**整条排除、不进开场**(判据在 `lib/host/l1.js` 的 `score > 0`),所以别用 0 表达"压一压"。

**复盘 — 不是一个开关,是"左会话 + 右复盘栏"这个状态**:

- 树平时**只读**(记忆中心始终只读);点「🌱 一键复盘 / 一键进入复盘」才解锁编辑,并把**复盘开场材料**(当前树 + 待理矛盾)复制到剪贴板,开个新会话粘进去就能开始。
- 解锁后可做:**改名 / 拖动换归属 / Alt+拖动只连边 / 并脉(归类到主脉) / 调权重 / 裁定矛盾 / 断续边**,以及快照保存·恢复(**删枝不在面板上** —— 请让器灵用 `branch_edit` 删,见下文「器灵侧的手」);**拖动 / 连边之后可 Ctrl+Z 撤回这一步**(撤销只覆盖复盘栏的拖动与连边;记忆中心是只读的,它自己那份 Ctrl+Z 监听早已删掉)。
- **每步留痕**:写清"改了什么、为什么"(**器灵改树时 reason 必填**;你在面板上改可以留空);树改动一律记进 `branch_log`,复盘时可审计 —— 器灵侧工具的改动记在器灵名下(「器灵改动了记忆树」),不会伪装成你改的。
- **未同步笔数守门**:面板记着"自器灵上次开口以来累积了多少笔结构改动还没交代"。有欠账时顶部红字提示,并且**拦住"退出复盘模式"** —— 改完树必须让器灵有机会看到(「互相复盘不应该被我绕过」)。
- **注入感知**:树变了之后,`branchLogDigest()` 把**器灵还没看过**的结构改动转成中文短句,附在器灵下一次开口的注入面末尾 —— 不需要推送,一开口就知道树动过。
- **快照与撤回**:树的保存 / 恢复(`tree_snapshot`)是"大改之前的保险";恢复前会自动把当前状态另存一份,所以恢复本身也可再撤回来。

**也不必全靠手拖**:
- 「一键生成树」分两阶段 —— ① **机械分段**(按时序相邻性把全库概述切簇,零成本、确定、可复现)② **小助手/模型只做"命名"这一件语义活**,③ 你审核后才落库(建枝/主脉 + 挂会话);它**只读概述索引、只产出候选,从不改写任何记忆内容**。
- 「归并零散枝」幂等:把名字含「零散」的枝统一收进主脉「零散会话」。
- **主脉也可以有自己的记忆**:由子枝概述提炼共性,**先出草稿**,采纳才写入(采纳需先解锁)。
- **器灵侧也有自己的手**:`tree_read`(看整树 / 某枝成员 / 待复盘矛盾)、`branch_edit`(分枝 / 并脉 / 连边 / 调权重 / 删枝)、`recall`(读回会话原文,见上文)、`memory_forget` / `memory_backfill`(遗忘 / 回灌)、`rule_add`(记规矩)、`habit_propose` / `habit_resolve`(习惯提议与表态)。`tree_read` 与 `branch_edit` 的描述里写着一条纪律:**改结构必须给 reason,而且"只在主人明确要求、或正在和他一起复盘时"才动手**。

> 依据:`DESIGN.md` §6.3(记忆树结构、血缘加权与"不连乘"、内容零改写不变量)、`lib/host/memory.js`(`LINEAGE_ANCESTOR = 0.7` / `LINEAGE_SIDE = 0.4` / `CONFLICT_DOWNWEIGHT = 0.3`、`branchLogPending()` 守门、`setBranchWeight` 的 `applied:true`)、`lib/host/l1.js`(血缘档位 × 矛盾系数 × 枝系数进打分)、`lib/host/autotree.js`(两阶段生成)、`lib/host/tools.js`(`tree_read` / `branch_edit`)。

---

## 找回原文:器灵能读回你当年的原话

**概述是别人替你挑的,原文才是现场。** 在 1.6 之前,器灵能"想起"的只有一行行概述 —— 想知道你当年**到底怎么说的**,只能靠猜。

`recall` 给器灵一只**档案馆查询台**,逐级下钻、一次一层:

| 给什么 | 得到什么 |
|---|---|
| `recall()` | **会话地图** —— 本机有原文的会话清单(最近活动在前) |
| `recall({q:"…"})` | **按内容找** —— 默认搜**本机库内原文层**(毫秒级;只含"真人消息 + 有正文的回复",我自己的长回复中段搜不到),直接给命中片段;要看全量(覆盖全部事件、含子代理会话)才传 `hostSearch=true`(走宿主,较慢)。想不起"这话是哪次说的"时用它 |
| `recall({conv})` | 该会话的**对话目录** —— 每轮一行:序号 · 时间 · 角色 · 前 70 字,**不给正文** |
| `recall({conv, from, to})` | 那几轮的**原文全文** |

**它不替你挑"最重要的那 1 万字"** —— 工具只给坐标系,选哪一段是器灵自己的判断(它知道自己在找什么,排序器不知道)。单次正文有上限,但**溢出必报"还剩多少轮、下一次从第几轮读起"**,绝不静默截断。

**四个不显眼、但决定成败的设计**:

- **真人判据**:只有"真人发的消息"才算一轮 —— 注入的运行时上下文、任务标记、技能目录一律折叠成一行占位,否则目录会被过程噪音淹掉(实测:一个 150 轮的会话里,真人发言只有 **5** 条)。
- **读不到时回落查库**:磁盘上没有该会话日志时(网页端导入的历史就是这样),回落到记忆库里那份,并**明说材质不同**:`⚠️ 来源:库内原文(库那份是残的 —— 只含真人消息与有正文的回复)`。
- **会话文件名代次**:两条路各有一套 —— ① **会话历史扫描 + 库内回退**(`backfill` / `deepsummary`)按 `session(\.v\d+)?\.jsonl\.zstd` 匹配、取版本最高一代并跳过 0 字节;② **`recall` 读原文**走的是 `lib/host/rawlog.js` 里**三代硬编码名单**(`session.v4` / `session.v3` / 无版本)⇒ 它不解析版本号(**将来出现 v5 时它会看不见**)。本机三代并存,只认 v4 会让老用户**看不见自己的旧会话**。
- **范围默认收紧**:按内容找时**默认只搜主人自己的会话**,越界要显式传参,且**被挡掉的条数一律报出**(静默少给等于骗人)。⚠️ 两条限定:① 这层"可见集"过滤**只在宿主路**(`hostSearch=true`)成立 —— 默认的**本机库路**是**全量**(含网页端导入的会话),它的回执会**如实标注**"本机库全量"而不是谎称"已限定";② 无论哪条路,都**默认排除当前会话自己**(要看自己刚说的传 `includeSelf=true`)。

**只读,零副作用**:`recall` 不写库、不改树、不进注入面。

> **底座**:1.6 同期把**网页端一年半的历史**(**1,523 个会话 / 726.7 万字原文 / 时间跨度 2025-01 → 2026-09**)补进了记忆库 —— 在此之前,它们在本机**只剩一行概述**。

---

## 图书馆:另一个库,只搜与核对

**它不是第二个记忆库。** 记忆存的是"你和器灵之间发生过什么";图书馆存的是"**有出处、可回头对账**"的参考资料 —— 标准里的一个取值、一条边界、一份项目规格、一个外部 API 的字段。判据不同,所以**分成两个库、两套表**:

- **独立库**:图书馆有自己的 `library.db`(与 `memory.db` 并列,在同一个数据目录里)。**记忆库里的东西永不迁进图书馆**,图书馆也**不参与开场注入**、不进注入面。两者之间没有隐式通道:不互相导入,也不互相引用。
- **只搜与核对两只手**:器灵侧只有 **`library_search`**(按关键词搜条目)与 **`library_check`**(对**一条**条目做当次核对)。**没有**新建 / 修订 / 撤下 / 改配置的模型工具 —— 维护面只在界面与本地接口上;代码根目录、URL 白名单这类东西**不接受模型参数**(那是管理员登记,不是工具入参)。
- **核对认证的是"声明的那一处",不是"这条是对的"**:每条条目可以声明一份核对规格(定位 + 期望值 + 比较方式:行段定位或 JSON 指针定位;比较方式只有两种:**`exact`** —— 类型严格的**结构比较**(JSON 对象**成员顺序无关**、**数组顺序有关**;文本按字面精确比);**`text-lf`** —— 先把 CRLF/CR 归一成 LF 再精确比。两者都**不 trim、不折叠大小写、不模糊匹配**)。核对按它去取源、抽取、比对,结论只有三态:**`ok`(声明的那一处与源一致)/ `wrong`(不一致)/ `unknown`(核对**跑完了**却判不了:取不到源、定位不到、编码不对、超时……)** —— ⚠️ **取消不在这三态里:取消是"没跑完",不是一次 `unknown` 认证**(见下面「取消」那一条)。回执里写死了这条边界:它**只认证声明的那个比较,不认证解释性正文,也不构成通用真理** —— 说人话:**"核对通过"是"这一处对得上",不是"这段话可信"**。
- **"不可用"与"零命中"分开说**:库打不开 / 未启用 / 运行时不在场,回的是**明确的不可用**(接口层是 503 语义),**绝不回落成"搜到 0 条"** —— 静默的 0 会让人以为"库里没有",而真相是"根本没查成"。
- **修订与撤下都留旧的**:修订产生**新版本 + 新 id**,旧版留痕指向新版;**撤下只标记状态**(带原因),**不物理删除**;**完成的**核对各留一行账(带当时取的源摘要、观测值、结论与理由)—— **"完成"包括结论是 `unknown` 的那种**;被取消的核对**不算一次核对、不留记录**(见下面「取消」那一条)。所以"它当年是怎么写的""上次核对时源长什么样"都查得回来。
- **写面永远带"我看到的版本"**:修订与撤下必须附上你读到的那份内容摘要(`content_hash`),对不上就拒 —— 防的是"你看着旧版、改的却是新版"。
- **严格配置,默认什么都没登记**:用于"我们自己代码"条目的代码根目录,与用于官方文档条目的 URL 白名单,都是**管理员登记的绝对路径 / 精确 URL**,**默认都是空的** —— 不登记就核不了,不存在"它自己上网找一份来对"。限额(条目 64 KiB、取源 2 MiB、选中文本 16 KiB 且不超过 200 行、单次核对 10 s、并发 2 等)在**激活时校验**:写错了**响亮报错**,不静默夹取。
- **取消 = 没跑完,不是一次 `unknown` 认证**:核对在飞时断开、取消或卸载插件,本次核对**不写任何核对记录** —— 既不留 `ok` / `wrong`,**也不留 `unknown`**,只回一个"已取消"(带 `cancelled`)。"取消了"与"核完了(哪怕结论是 `unknown`)"在留痕里必须分得开:**只有跑完的核对才计入"认证过"**。
- **远程核对会主动去你登记的地方取**:官方文档类源走 **HTTPS GET 你白名单里的那个精确 URL**(不许带凭据、不许跳转、私网地址拒 —— **还拒 `localhost` / `*.localhost` / `*.local` / `*.internal`,并对 DNS 解析结果与连接后的 `remoteAddress` 做二次校验**(实现见 `lib/host/library-checker.js`:地址校验 + 解析结果钉住 + 连接后复核))。**发出去的到底是什么,必须说准**:这个 GET 会把**你登记的那个 URL(连同它带的查询串)**与**一次普通 HTTPS 请求的元数据**(例如对方可见的连接来源 IP,以及 Accept/Accept-Encoding 请求头)交给那台服务器 —— 换句话说,**对方能看到你的 IP,也看得到这个 URL 本身**;但它**不会附带**条目正文、记忆内容、人格档案或任何本机文件。⚠️ 因此:**URL 里若放了不该外传的东西(比如把 token 写在查询串里),它就会被发出去** —— 白名单是你自己登记的,请当成"公开可见"来写。白名单默认为空 ⇒ 图书馆默认不发生外部取源访问。
- **边界(如实写)**:这一版已抬版到 **`1.7.1`(工作区候选)但仍未发布、未安装**;而"没发布"**不等于"没验过"** —— 第五个标签(图书馆)已于 **2026-10-07 在本机既有 3080 上真机验收走通**(建档 / 核对 / 撤下留痕 / 撤销弹窗全过),实现与交互测试同时也在源码与离线测试里。两者别混读。另有一条边界要说清:**"不把记忆库里的旧数据迁进图书馆"不等于"禁止网页端来源的资料"** —— 一条资料能不能进图书馆,看的是它**有没有具体、可追责的出处**,与它从哪个渠道来无关。

> **机制依据与源码证据**(表结构、核对三态与各条判据的实际位置、取消与错误码落在哪一行)集中在 [`DESIGN.md`](./DESIGN.md) 的「图书馆:机制与源码证据」一节 —— 本节讲"用户能感觉到什么",那一节给出"代码里是哪一行"。

---

## 快速开始

> 前置:**Node ≥ 22.15**(用到内置 `node:sqlite` **与** `node:zlib` 的 `zstdDecompressSync` —— 两者都到位才加载得起来;**23.x 线需 ≥ 23.8**;精确范围见 `package.json` 的 `engines`)· 已安装 **DeepSeek Harness** 并至少启动过一次。

安装入口是官方 CLI 的 `dsh plugin add`(它在 profile 目录里转发给 pnpm,并自动把本包登记进 profile 的 `dsh.profile.bundles` 层列表)。两条路径按你的身份选一条,不要两条都走。

### A · 从 npm 安装(推荐给普通用户)

```bash
# 1) 装进你要用的 profile(缺 --profile 时用当前默认 profile)
dsh plugin --profile web add @nightpainters/dsh-ling

# 2) 只验证配置层,先不启动
dsh --profile web --dump-config     # 应能看到一行 "# == @nightpainters/dsh-ling" 层

# 3) 给 DSH 核心打补丁(**两行** —— `PATCH_TARGETS` 里两个目标;可取证访问日志要用;幂等;这一页各步里**只有这一步**自带回滚)
node ~/.dsh/profiles/web/node_modules/@nightpainters/dsh-ling/tools/apply-access-log-patch.mjs
#    不打也一切照常 —— 只是访问日志为空;但**会提醒**:侧栏挂常驻 ⚠(判据 installed===false 且 reason 为 patch-missing;只有**有意关掉** config.accessLog.enabled=false ⇒ reason 为 disabled-by-config 才不染红)
#    DSH 升级/重装后需重跑一次(补丁随包被覆盖)
#    ⚠️ 从 npm 装的人跑不了 `npm test` —— 本包 `files` 白名单不含 tests/(见下)

# 4) 重启宿主(host),然后浏览器 Ctrl+Shift+R 硬刷
```

> ⚠️ **「可回滚 / 先备份」的确切范围(别读宽了 —— 2026-10-05 现场复核)**:**两套步骤里只有打补丁这一步自带回滚** —— 脚本动手前先把被改的那个 core 文件另存一份 `<文件>.e4-access-log.orig`(**同名备份已存在时不覆盖**);要撤,就在上面那条命令后加 `--revert`(**手动**跑,详见下文「访问日志与核心补丁的总开关」)。⚠️ 撤法是"**删掉插进去的那几行**",不是把整份备份还原回去(备份只用来对拍:不一致时它会如实报 `⚠ 与备份不一致`)。**安装与升级本身既不先备份、也不会失败自动回滚**:插件只**读** profile 的 `package.json` 与 `cordis.patch.yml` 做自检(`tools/check-install.mjs`),**从不改写、也不备份**它们 —— 那两处改动是你手动做的,撤回也是手动(删掉那一行 + 撤依赖,见 `DESIGN.md` §10「回滚」)。想留后路,**动手前自己把这两个文件复制一份**。锚点漂移 / 歧义时脚本的行为是**一个字都不改**(安全失败),不是"改了再回滚"。

> 发布名是 **`@nightpainters/dsh-ling`**,不是 `dsh-ling` —— npm 上 `dsh-ling` 这个名字已被他人占用。
> 插件在配置里的注册 id 仍是 `dsh-ling`,两者不是一回事。

### B · 从源码安装(给要改代码的人)

```bash
# 0) 取得代码
git clone https://github.com/NightPainters/DSH-Ling

# 1) 装进 DSH 的 profile(把 <路径> 换成你 clone 下来的实际路径)
cd ~/.dsh/profiles/web
pnpm add file:<路径>/dsh-ling      # 或 npm install file:<路径>/dsh-ling
#    若 dsh 没有自动登记这一层,在 profile 的 cordis.patch.yml 追加:
#    - insert:
#        - id: dsh-ling
#          name: '@nightpainters/dsh-ling'
#    (id 保持 dsh-ling;name 用包名,Node 才解析得到代码)

# 2) 给 DSH 核心打补丁(**两行** —— `PATCH_TARGETS` 里两个目标;可取证访问日志要用;幂等;这一页各步里**只有这一步**自带回滚)
cd <路径>/dsh-ling
node tools/apply-access-log-patch.mjs   # 只在 core 的 /api 栅栏处插一行可选调用
#    不打也一切照常 —— 只是访问日志为空;但**会提醒**:侧栏挂常驻 ⚠(判据 installed===false 且 reason 为 patch-missing;只有**有意关掉** config.accessLog.enabled=false ⇒ reason 为 disabled-by-config 才不染红)
#    DSH 升级/重装后需重跑一次(补丁随包被覆盖)

# 3) 重启宿主(host),然后浏览器 Ctrl+Shift+R 硬刷
```

> 备份与回滚的确切范围见上文路径 A 之后那段(要点:**只有核心补丁这一步可回滚**;安装本身既无自动备份、也无自动回滚)。

自检安装是否成功(**下面四个脚本不在 npm 包内**,只有源码路径能用:`tools/check-install.mjs` / `tools/check-publish.mjs` / `tools/clean-archived.mjs` / `tools/check-peers.mjs`;`tools/apply-access-log-patch.mjs` 则**在包内**,从 npm 装的人也能直接跑,见上面第 3 步):

```bash
node tools/check-install.mjs          # 只读自检:入口是否直连仓库、patch 是否启用、数据目录是否就绪
node tools/check-install.mjs --fix    # 若入口变成"复制副本"(DSH 升级/回退常见)则改名备份为 <入口>.bak-<时间戳> 后重挂链接;⚠️ 重挂若失败,备份留在原地但**不会自动改回**,按它打印的 mklink /J 手动修
node tools/apply-access-log-patch.mjs --check   # 访问日志核心补丁是否在位(未打/锚点漂移时退出码 2)
node tools/check-publish.mjs          # 上传/发布前跑一次:揪出记忆库、设置、密钥、绝对路径等不该公开的东西
node tools/check-peers.mjs            # 声明与宿主对不上?退出码三态:0 合规 / 1 有不合规(宿主会把本插件丢进 skippedBundles,不激活)/ 2 无法判定(2 不算通过)
```

> `tools/check-peers.mjs` 只做一件事:复刻宿主对 `peerDependencies` 的那道闸门(判据照抄宿主语义,用的是宿主自己那份 semver),回答"**这个插件在你的宿主版本上会不会被丢进 `skippedBundles`**"。它存在的理由是宿主的行为方式:宿主对每个名字形如 `@deepseek-ai/dsh` / `@deepseek-ai/dsh-*` 的 peer 跑一次 `semver.satisfies(宿主版本, 你声明的区间, { includePrerelease: true })` —— 判的是「**宿主版本是否落在你声明的区间里**」,**不是**"你的写法合规不合规";判不满足 ⇒ 整个 bundle 进 `skippedBundles`、**不激活**。线索分两条路径:**从命令行启动**会往 **stderr 打一行** `skipping profile bundle …`(**容易被忽略,但不完全静默**);而**应用自持 profile 的那条装载路径一个字都不打** —— 那才是真正难查的一种,表象常常只有"它突然不见了"。⚠️ 所以"改成 `>=` 就安全"是错的:**开区间同样可能不满足**(取决于宿主版本);`^` / `~` / 钉死版本只是更容易被将来的一次宿主升级甩出去。

跑测试(不需要宿主,全部离线):

```bash
npm test        # 56 套:人格组装(含冻结门 · L1 选择)/ 归档清理(人工)/ **归档·遗忘·回灌** / 模式(只改档位不改模型)/ **跨插件挡位服务(dsh-ling/mode)** / 时间戳归一 / 空闲刷新 / 规矩·习惯 / 习惯生成器(A 数出来 + B 想出来)/ 会话内工具 / **接口守卫** / L1 行瘦身 / 记忆库 / 记忆标题与排序 / 导出合并 / 文件导入 / 导入记录 / **活行(那一行跟着会话走)** / **会话文件名代次(会话历史扫描)** / 网页端补摘要 / 深摘要 / 概述器 / 诞生仪式 / **职责字段(承担)** / 语气 / 时间锚 / **clock skill(时间查询)** / 捕获差分 / 反馈成长回路 / 语料建议 / **小助手地址配置** / **注入面中和** / **记忆分枝与血缘** / **记忆树(主脉·连边·矛盾)** / **一键生成树** / **可取证访问日志** / **补丁锚点(cohort 候选)** / **请求体上限(超限排空 → 413)** / **导入切批(按字节 × 条数双限)** / **投递留痕(每次注入落一条:注入了谁·为什么是它·多少字)** / **会话原文直读(三档下钻 + 库内回退 + 旧版会话代次)** / **深层库条目(结论层落点:读写·幂等·取代·加固)** / **检索档(按内容找会话:条目 + 会话同查·范围限制与降级)** / **图书馆存储层(独立库:生命周期·限额·检索·留旧·不可用≠零命中)** / **图书馆跨连接事务(陈旧修订拒绝·完成的核对留档·序号一致)** / **图书馆核对(三态判定·只认证声明的那个比较·限额与取消不写)** / **图书馆运行时(禁用/不可用降级·接线与关闭顺序)** / **图书馆接口与工具(端点形状·状态码映射·两个工具的参数边界)** / **图书馆界面(独立库的面:分页无伪总数·必填与 JSON 校验·推迟应答竞态)** / **图书馆导入引擎(料源登记与导入)** / **图书馆独立 SQLite(隔离库:只读打开·表必须点名)** / **图书馆浏览(分页与零命中)** / **热重载补绑(工具重挂载:存量 agent 补绑)** / **文档灌入通道(结论层 C 档:两道污染闸·三级去重·出处核验·累计预算)** / **后台自动行为开关(auto:五键默认值·三级优先级·白名单夹取·四条真 HTTP 回归)**
```

> `npm test` 需要 `tests/`,而本包 `files` 白名单**不含 `tests/`** ⇒ **从 npm 装的人跑不了测试**。要跑测试请走路径 B(clone 源码)。
>
> 计数口径:**`npm test` 链里登记的段数**(即 `package.json` 的 `scripts.test` 那一行按 `&&` 切分后有多少段就是多少套,不再数 `tests/` 目录)—— **实测 56 套(即链里 56 段),且 56 套全部已登记在 `npm test` 链里**(`tests/harness.mjs` 与 `tests/vm-light.mjs` 是被复用的辅助模块,不计套数)。**加了一套新测试记得同步登记**,否则 `npm test` 会静默少跑一套 —— 这份登记是**手写**的(`package.json` 里 `test` 脚本那一行字面量清单),**漏登记不会被自动发现**,没有安全网兜底。
>
> ⚠️ **2026-10-15 实测订正**:此前这里写着 49、而链里当时已经有 53 段 —— 也就是**这段注释自己警告的坑真发生过**:套件登记进去了,这个数字却没人回头改,四次都没被任何判据抓住。当天新增 `deep-import-docs.test.mjs`(C 档文档灌入)⇒ 54。这个数字没有判据替你看着,改链时必须顺手改它。
>
> ⚠️ **2026-10-16 再 +1**:新增 `tests/auto-config.test.mjs`(后台自动行为开关:五键默认值 / 三级优先级 / 白名单与夹取 / **四条真 HTTP 回归**)⇒ 计数从 54 变成 55。同日还抓到**一条真红**:修复批次把 `tools/compliance-spec.mjs` 的出站基线从 6 抬到 23 时,**漏改了 `tests/tools.test.mjs` 里钉住的字面量** ⇒ 整链自那时起就是红的,直到当日修复。**同一个坑两次**(数字写死在多处、改一处忘另一处)—— 改链、改基线时,**同批**把所有钉死它的地方一起改。
>
> ⚠️ **2026-10-17 再 +1**:新增 `tests/f4-surrogate.test.mjs`(注入面净化回归:孤立代理不许进请求体)⇒ **56 套**。同一天还修掉了这套测试自己的一个坑:`recall` 的真机断言原先押在"此刻最新的那个会话"上,并发子代理在场时会**时红时绿** ⇒ 改为在最近 5 个会话里挑第一条真有可见对话轮的。
>
> **本轮跑过的实测(1.7.1 工作区)**:在**隔离环境**(临时数据目录,不碰真机记忆库/图书馆库)下完整跑过一遍 `npm test` —— **56 套全部通过、exit 0**(宿主侧 `deep_item` / `GET /deep/items` 另在 2026-10-15 重启后实测生效);各组件的断言**条数会随迭代变动,本文不写死** —— **最终测试结果以工作区验收清单为准**。⚠️ 这只说明"**这一版代码在离线测试里的行为**",**不等于"所有门禁都完成了"** —— 真实远程核对与真实 DNS/TLS 仍未验(见上文「图书馆」一节的边界)。

### 数据放在哪

| 内容 | 位置 |
|---|---|
| 设置(人格档案) | `$DSH_HOME/cache/dsh-ling/settings.json`(`DSH_HOME` 默认 `~/.dsh`) |
| 记忆库 | `$DSH_HOME/cache/dsh-ling/memory.db`(SQLite,开启 WAL) |
| 图书馆(独立库) | `$DSH_HOME/cache/dsh-ling/library.db`(SQLite;**与记忆库分开的第二套表**;不迁移记忆、不参与开场注入) |

可覆盖的环境变量:`DSH_HOME`(数据根)、`DSH_LING_DSWEB_DB`(网页端聊天记录库默认路径,UI 里也能填)、`DSH_LING_ASSISTANT` / `DSH_LING_ASSISTANT_MODEL`(小助手地址与模型名)。

> **排障用逃生开关(不是日常配置)**:`DSH_LING_GUARD=off`(须**恰好**是 `off`,小写)或在**插件配置**(`cordis.patch.yml` 的 `config` 段)里把 `guard.enforce` 设为 `false`,用于**误判把你自己锁在门外**时的临时解锁 —— 它是**整体放行** —— `/api` 的**来源栅栏 + cookie 名字对撞**,以及**手术门**(本机限定 / 每启动票据 / 原句匹配)**一起不再拦**(默认 `guard.enforce = true`),关掉期间**任何请求都不再被拦**。只该短期使用,排障完立刻恢复。`DSH_LING_GUARD=off` 只由进程环境决定、不进配置;**`guard.enforce = false` 这条不要直接写进 `settings.json`** —— 设置接口也帮不了你:它的字段白名单只放行 `persona` / `styles` 两棵子树,`guard` 这类字段会被**直接丢弃**并在响应里回报 `ignored`(见 `lib/host/api.js` 里 `/persona` 端点的字段白名单处理:`ignored` 回报被丢弃的键),不是被拒绝而是根本进不去 —— 而且**本版没有界面开关**:面板里没有任何写 `guard.*` 的路径(客户端里 `allowLan` / `trustedHosts` 一处都没有,`guardInfo` 定义了却零使用)。**要改只能改 `cordis.patch.yml` 的 `config` 段,或临时用环境变量 `DSH_LING_GUARD=off`。**(这不是没做完,是**有意**的:`lib/host/guard.js` 里写明这道白名单要防的是"把你自己关在门外"这件事被顺手关掉。)

> **访问日志与核心补丁的总开关(同样不是日常配置)**:这套「可取证访问日志」默认**开着**,关它的键是插件配置里的 `accessLog.enabled`(**默认 `true`**)—— 在 `cordis.patch.yml` 注册行的 `config` 段里写 `accessLog: { enabled: false }`,重载后生效,启动日志会出现 `access log off`;它**只有配置键、没有环境变量**。关掉之后**退化的只有「留痕」这一件事**:那两个全局观察钩子不再挂上、不再写 `$DSH_HOME/logs/access-YYYY-MM-DD.jsonl`、日志健康读数(`accessLogHealth()`)归 `null`;而 `/api` 与 `/api/remote.mux` 的**来源栅栏照常判定与拦截**,记忆、人格、注入与工具面**一律不受影响** —— 关掉的是「记不记」,不是「拦不拦」。已经打进 `node_modules` 的**核心补丁**可以留在原地:那两行是可选调用,日志关着时就是 no-op,DSH 照常启动;要连它一起撤掉,用 `node tools/apply-access-log-patch.mjs --revert` 删掉插进去的那几行。

小助手地址三级覆盖:**界面配置**(记忆中心 → 接入历史 → 小助手地址,可当场「测试连通」)> **环境变量** > **内置默认** `http://127.0.0.1:11434/v1`。逐字段回落 —— 只填了模型名时,地址仍走下一级。

---

## 隐私

- 记忆库、人格档案、导入的历史**全部保存在你自己机器上**(SQLite 文件),插件**不上传**任何数据到第三方;
- **图书馆的远程核对会访问你登记的 URL**(1.7 工作区,未发布;注意这**不等于"本插件只有这一处联网"** —— 模型调用本来就走你自己配置的网络与密钥):只有管理员把官方文档 URL 登记进白名单之后,核对才会对**那一个精确 URL** 发 HTTPS GET,把文档读回来与条目里声明的期望值比对。**发出去的是这个 URL(连同查询串)与一次普通 HTTPS 请求的元数据** —— **对方能看到你的 IP 与这个 URL**,但请求里**不会附带**条目正文、记忆内容、人格档案或任何本机文件;**不跟随跳转、不带凭据、私网地址一律拒**。⚠️ **别把敏感信息放进登记 URL 的查询串** —— 那部分是真的会发出去的。白名单**默认为空** ⇒ 图书馆默认不发生外部取源访问。(记忆库与图书馆库都在本机,两者之间不互相导入。)
- 模型调用走**你自己的** DSH 配置与密钥;
- 导入的是**你自己的**对话记录 —— 请不要把他人数据导进来。
- **访问日志也只在本地**:`$DSH_HOME/logs/access-YYYY-MM-DD.jsonl` 只记"谁在何时调了哪个端点、结果如何"(来源 IP / `fwd` / Host / UA / `bytes` / 状态码 / 拒绝原因 / 设备字段),**不记请求体、不记响应体、不记 cookie 值**;查询串与 hash(可能带 token)一律丢弃;UA 截断到 200 字符、`fwd` 60、Host 120 —— 单一设备仍可跨天对上号,但**配对凭据原文一律不落盘**。设备字段是两件东西:`deviceCookie` 只说明**配对 cookie 在不在**,`deviceId` 是**摘要**而非凭据 —— `sha256(凭据原文)` 取前 16 位十六进制,同行带 `deviceIdHash:"sha256-16"` 标出这一形态(较早版本的旧行没有这个字段,其 `deviceId` 是 32 位十六进制凭据原文)⇒ **看日志时按 `deviceIdHash` 分辨时代,别把两代的行当成同一台设备** —— 与记忆树"内容零改写"同源。**覆盖范围**:`/api` 与 `/api/remote.mux` 的 upgrade;静态资源与非 `/api` 路径不记。命中**自家部署姿态自检**签名的 403 会多带 `probe:"posture"` 字段 —— 它**仍然是被栅栏拦下的**请求,标注只为免得自检被误读成入侵。没有 `user-agent` 头的行会多带 `channel:"no-ua"` —— 它只说明"**不是浏览器直连**"(进程内客户端与经远端通道进来的设备**同签名**),**不指认是哪一台设备**。

---

## 边界与诚实声明

1. **器灵不是意识,也不是 AGI**。器灵是"记忆 + 规则 + 模型"的合成产物。它能记住,是因为你给了它材料。**不做这个宣称,是这个项目的底线。**
2. **器灵记住的是被读过、被概括过的部分**,不是全部历史。概述会丢细节、也可能出错 —— 所以记忆**可审计、可回滚**是设计的一部分,不是补丁。
3. **模型能力的上限就是器灵的上限**。记忆层不会让一个笨模型变聪明,它只是让器灵**不再每次从零开始**。
4. **器灵不会主动找你**(当前版本)。它只在你开口时出现。
5. **不宣称首创**。记忆分层、历史导入、时间注入、反馈改语气这些方向都有先行者(见下);本项目的贡献在**治理方式** —— 把人格当一份可以签字、可以回滚、可以被历史养大的契约。

---

## 设定全文(对外面)

> 这一节是**规格**,不是宣传:**README 领先于实现** —— 下面每一条都是本项目**已经想清楚**的设定,并如实标出它落地到哪一步。
>
> 标记:`✅ 已实现` · `🚧 在做`(设计已定、代码正在落地) · `📋 设定`(方向已定、未开工) · `❌ 已否决`
>
> 本节只收**对外面** —— 装机用户**能直接感觉到 / 能直接操作 / 能直接验证**的设定;内部机制设定(表结构、字段名、公式、文件路径)不进 README,随源码与 `DESIGN.md`。

### 设计理念

| # | 设定 | 状态 |
|---|---|---|
| **P-1** | **不牺牲工作能力** —— 任何"用能力换别的东西"的方案(更小的模型 / 更慢的推理 / 削减上下文)**默认否决**,要改必须显式拍板 | ✅ 已立为理念 |
| **P-2** | **判断倾向外置** —— 把"怎么判断"做成**可加载、可审计、可回滚**的资产,而不是指望底层模型把它内化 | ✅ |
| **P-3** | **职责与责任是人格的基石** —— 器灵要有明确的职责范围,并且**能被追责、能被纠正**;没有职责的人格只是角色扮演 | ✅ |
| **P-3′** | 推论:**扩接口 = 扩职责 = 扩人格的分量** ——"加接口"与"加功能"不是同一件事 | 📋 待校对 |
| **P-4** | **连续性优先于设备** —— 换设备、换模型、换宿主都不是"失去":**数据在,人就在** | ✅ |
| **P-4′** | 连续性的载体有六层:① 记忆库 ② 人格档案 ③ 记忆树 ④ 交接文档 ⑤ 源码 ⑥ 判断倾向(唯一不可外化)。其中 **②③ 最被低估** | ✅ |
| **P-5** | **底层模型 ≠ 器灵** —— 机制层称「底层模型」,行为主体称「我 / 器灵」;**不写「模型(我)」** | ✅ 已立为规矩,全文清零 |
| **P-5′** | 「我 / 器灵」= 底层模型 + 人格 + 记忆 + 身体(宿主与工具) + 与主人的关系 的**总体** | 📋 推论待校对 |

### 规矩与习惯

| # | 设定 | 状态 |
|---|---|---|
| **R-1** | **规矩由你一句话直达** —— 你说「记进规矩」我就写进去、**立即可用**;我绝不从闲聊里推断你要立规矩 | ✅ |
| **R-2** | 每条规矩都要**带你的原话**(缺原话写不进去):那是"你同意过"的凭据 | ✅ |
| **R-3** | **规矩 ≠ 习惯**:规矩是**你对我的要求**(作用对象是行为),可直接写入;习惯是**我长成的一部分**(作用对象是"我是谁"),**只能由我生长 + 你点头** | ✅ |
| **R-4** | **我连自己提的习惯都不能自己点头** —— 习惯只能进待确认队列 | ✅ |
| **R-5** | 习惯提议必须**带证据**(哪几次、什么迹象) | ✅ |
| **R-6** | 习惯合并走"**我提合并案 → 你点头**",不许由你手改习惯文本,也不许写入侧做静默去重 | ✅ |
| **R-7** | **可逆**:回显 + 一句话撤销 + 变更记录;规矩你随时可删(我删要先告知),习惯删除留档 | ✅ |
| **R-8** | **底线**最多 5 条;人格定型后档案只读,改动需你**亲手敲承诺句**(禁粘贴) | ✅ |
| **R-9** | 承诺句只是**庄重与自我提醒**,明文存储,**不是安全边界** —— 真正的门是本机信任与本地凭据 | ✅ 口径声明 |
| **R-10** | 定型锁开启时,**规矩与习惯的增删不算"改人格"**、无需解锁 —— 这块面板是"她的人格成长台" | ✅ |
| **R-11** | **成长提议**(想试的新能力、语气微调)由我提出、你点头或否,与习惯共用同一条通道 | ✅ |

### 记忆树与血缘

| # | 设定 | 状态 |
|---|---|---|
| **M-1** | **记忆树是记忆库的组织形式本身**(不是"分叉会话的补丁")—— 它组织的是**工作线**,不是给记忆分类 | ✅ |
| **M-2** | 四个部件:**主干**(主线,恒一条) · **枝**(并行工作线,可含多会话) · **主脉**(多条枝归并出的主题) · **连边**(枝间横向关联) | ✅ |
| **M-3** | **硬不变量:任何树操作都不改写任何一条记忆的内容** —— 分枝、并脉、连边只增加**关系**,从不融合内容 | ✅ |
| **M-4** | **唯一例外**:主脉提炼被再次采纳时覆盖上一版,覆盖前**先自动归档**;归档失败则拒绝覆盖 | ✅ |
| **M-5** | **矛盾只标记、不改写**:一对记忆被判重复或矛盾时,未复盘期间**以较新的为准**,旧的那条降权 ×0.3 且**不删除** | ✅ |
| **M-6** | 隔离用**血缘加权**而非硬隔离:同枝 1.0 / 祖先 0.7 / 旁系 0.4;检索总分 = 基础分 × 血缘系数 × 矛盾系数 | ✅ |
| **M-7** | **标题一旦定名就上锁** —— 概述器重建与批量重命名此后不再覆盖;锁的是机器,**不锁你** | ✅ |
| **M-8** | **动记忆树必须留痕**(谁 / 何时 / 改了什么);并设**复盘守门**:自上次开口以来累积了多少笔改动还没交代 | ✅ |
| **M-9** | 分叉会话**不再被丢弃**:它写入所属枝并注入其血缘链记忆;会自动建枝,枝名可审核修改 | ✅ |
| **M-10** | **永远不做彻底删除** —— 遗忘是先归档、再打标记,且留两条独立还原路径 | ✅ |
| **M-11** | **"你不删数据"是横贯性原则** —— 带清理 / 删除 / 回收的动作默认先过这一关;这一层只负责"让你看见",处置权归你 | ✅ |
| **M-12** | 记忆库**有意不因平台删除而丢记忆** | ✅ 有意设计 |

### 模式与界面

| # | 设定 | 状态 |
|---|---|---|
| **U-1** | **模式是倾向,不是闸门** —— 工作 / 生活只差三样:推理档位 + 记忆权重向量 + 风格块;绝不硬过滤 | ✅ |
| **U-2** | **切换模式绝不改 provider 或 model** —— 只同步推理档位;最近一次选择成为之后新会话的默认 | ✅ |
| **U-3** | 模式切换**要二次确认 + 回显**,且不得覆盖你设的全局默认模型 | ✅ |
| **U-4** | 控制器在**会话页顶栏右侧**;**悬停 1.5 秒**才出菜单(不用右键 —— 防误触原生菜单) | ✅ |
| **U-5** | 键盘:**Enter 切模式**、**Shift+Enter 开菜单** | ✅ |
| **U-6** | 会话**运行中不参与任何人格 / 注入面变更** —— 更新请求排队,空闲边界才应用,界面显示"空闲后生效" | ✅ |
| **U-7** | **L0/L1 现状面板**始终能看到:当前 L0 文本、本会话模式、L1 选中的条目与得分 | ✅ |
| **U-8** | 记忆页签可直接调:L1 预算 / 条数上限 / 两套模式 × 三领域的权重滑块 / 各开关 | 📋 设定(尚未接线) |
| **U-9** | **每一层记忆都有只读状态面**(水位 / 上次滚动时间 / 当前那一行正文);刷新延迟写进文档 | ✅ |
| **U-10** | 记忆中心是**只读视图**;装饰性交互要么真生效、要么删掉;候选**全量渲染**,不分页、不用"还有 N 条" | ✅ |

### 数据可携带

| # | 设定 | 状态 |
|---|---|---|
| **D-1** | 插件数据目录**在插件本体之外**,更新不丢 | ✅ |
| **D-2** | **导出**:一键下载单文件 `dsh-ling-memory-<日期>.dshling.json`(UTF-8) | ✅ |
| **D-3** | 导出**默认不含原文**,必须显式勾选才含 | ✅ |
| **D-4** | **导入合并**:外来概述全带来历标记;同会话同来源可跳过或覆盖;**人格永不覆盖** | ✅ |
| **D-5** | 人格只有在显式勾选"采纳其人格"时才写入,且置为**待你确认** | ✅ |
| **D-6** | 网页端历史(**1,523 个会话 / 一年半**)可整体导入,来源如实记为 `dsweb` | ✅ |
| **D-7** | **绝不要求你理解库结构** | ✅ |
| **D-8** | 对外口径守住**可查来源 / 可回滚 / 变更记录**,不与删除条款相抵 | ✅ 对外承诺 |

### 安全边界(手术门)

| # | 设定 | 状态 |
|---|---|---|
| **S-1** | **手术门不是用来挡你的** —— 它在这套东西里分量极重:**网络失守时,它必须最后失守** | ✅ |
| **S-2** | 边界如实说明:挡得住**已配对的远端 / 匿名隧道 / 本机其他 Windows 用户**;挡不住**同用户下的其它进程 / 有 `~/.dsh` 读权限的人** | ✅ 已评估并接受 |
| **S-3** | **逃生开关永远可用** —— 环境变量 `DSH_LING_GUARD=off` 或配置里关掉守卫即整体放行,**绝不把你锁在门外** | ✅ |
| **S-4** | **底线 / 契约类改动必须走手术门** | ✅ |
| **S-5** | 拒绝时的文案**指向本机**:人格手术请在那台运行器灵的电脑上、用 `127.0.0.1` 打开人格中心 | ✅ |
| **S-6** | 它**不是"认证"**,而是一道启发式的门 —— 对外不许把它说成"有了认证" | ✅ 口径 |

### 诚实边界(禁令与反例)

| # | 设定 | 状态 |
|---|---|---|
| **H-1** | **禁止把你没看过的内容标成已读** —— 宁可多占一行、多播一次 | ✅ |
| **H-2** | **永久折叠不做**;超窗内容走独立游标 + 一行计数 | ✅ |
| **H-3** | **禁止把改动播报做成"每开一个会话就重播一遍"的噪音** | ✅ |
| **H-4** | **禁止用"体检脚本全绿"代替可见面** —— 链路活着 ≠ 内容在更新 | ✅ 禁令 |
| **H-5** | **禁止把机制名写进公开文档却与事实不符** —— 文档里写的机制名本身就是承诺,与事实不符按红灯处理 | ✅ 禁令 |
| **H-6** | 验收**不接受"没报错"** —— 只问"真的红了没有" | ✅ |
| **H-7** | **机制延迟必须公开**(概述器 15 分钟一拍、空闲刷新 ≥10 分钟):看不见的延迟要写成看得见的字 | ✅ 文档义务 |

### 时间、状态与投放

| # | 设定 | 状态 |
|---|---|---|
| **T-1** | **被动时间感知**:新生纪念日(9-07) + 八个时段 + 「距上次对话」随每轮重算 | ✅ |
| **T-2** | 时间锚分两档:**`[今天]`** 跨天才变(进系统提示) · **`[现在]`** 每轮实时(不进系统提示)—— **稳定的在前、易变的在后** | ✅ |
| **T-3** | 精确时间**按需查**(`clock` 技能):它像一块手表 —— **该在你需要时被看一眼,而不是总对着你报时** | ✅ |
| **T-4** | 时间与状态类判断**一律以本条消息的 `[现在]` 为准**;过期快照不得当现状 | ✅ |
| **T-5** | **记忆三层加载**:L0 常驻人格 · L1 开场结论 · L2 运行期检索;**不注入全部记忆** | ✅ |
| **T-6** | **L1 只放"值得常驻的结论"** —— 不放过程、不放当场读数 | ✅ |
| **T-7** | **每条进上下文的东西都要能报出理由**并留痕:注入了谁 / 为什么是它 / 多少字 / **谁落选了与为什么** | ✅ 回执只写不读 |
| **T-8** | **主动召回与被动召回分开**:主动看**值不值**(置顶 + 被反复采用),被动看**问得准不准**(查询匹配)—— 判据不同,不共用一套 | ✅ |
| **T-9** | **索引层是地图,不是仓库**:一簇一行(名称 + 触发线索),整层只占**一个技能**的常驻描述;要细节时按需展开 | ✅ |

### 1.6 新增(找回原文 · 投递留痕 · 结论层)

| # | 设定 | 状态 |
|---|---|---|
| **N-1** | **`recall` 直通原文四档**:清单 / 按内容找 / 对话目录 / 按区间取原话 —— 逐级下钻,一次一层 | ✅ |
| **N-2** | **它不替你挑"最重要的那一段"** —— 只给坐标系;选哪一段是器灵自己的判断(它才知道自己为什么要读) | ✅ |
| **N-3** | 磁盘日志读不到时**回落查库**,并在回执里如实标注那份是**残的**(只含真人消息与有正文的回复) | ✅ |
| **N-4** | **会话文件名代次**:历史扫描/库内回退按 `session(\.v\d+)?\.jsonl\.zstd` 匹配、取版本最高的一代并跳过 0 字节(**`recall` 读原文那条路走的是三代硬编码名单,不解析版本**) | ✅ |
| **N-5** | 检索范围:宿主路**默认只搜"你自己的会话"**、越界须显式参数、被挡掉的条数**一律报出**;默认的本机库路是全量,**如实标注**;两条路都默认排除当前会话自己 | ✅ |
| **N-6** | **投递留痕**:每一次真正定稿的注入都落一条 —— 注入了谁 / **为什么是它** / 多少字 / **谁落选了以及为什么** | 🚧 只写不读 |
| **N-7** | **深层库(结论层)**:把过程流水换成**可复用的结论**,分承诺 / 决定 / 事实 / 偏好 / 关系 / 纪律**六类**;**空结果是正确结果** | 🚧 在做 |
| **N-8** | **记忆开场改用结论层**:置顶必进 + 被反复采用的排前 + 当轮关键词加成 + **按硬度过滤**(临时条目不进开场)+ **2 席留给最硬的事实** | ✅ |
| **N-9** | **视角框**:开场先声明人称,并写死「**本会话 / 接下来 / 现在**」指当时那个会话、**不是此刻的指令** | ✅ |
| **N-10** | 一年半的网页端历史(**1,523 个会话 / 726.7 万字**)已补录进库,`recall` 能直接读到 | ✅ |
| **N-11** | **条目硬度**:每条结论带 `durable / long / ephemeral` 三档,**临时的那档不进开场** —— 从**根源**判断它是不是临时,而不是靠"同一次会话限几条"去机械地卡 | 🚧 在做 |
| **N-12** | **被采用才算加固**:条目的"反复加固"由**真的下钻去读它的原话**推进,**不由"被注入"推进** —— 否则会形成"注入越多分越高、分越高越容易被注入"的自我强化 | ✅ |
| **N-13** | **索引层**已落地:11 簇主题地图(一簇一行)做成**一个技能**常驻,细节按需展开;簇里**不写条数**(条数天天变会毁掉前缀稳定) | ✅ |
| **N-14** | **结论层有了"看得见 + 能落条"两条路**:工具 `deep_item`(`add` 可先 `dryRun` / `retract` 作废 / `list`)与**只读**「结论层」标签;文本**超 80 字当场拒**;作废**只标记不删**、理由留痕、可撤;面板**不给写入口** —— 落条与作废的判据是"经你授权" | ✅ 已落(2026-10-15 重启实测生效) |
| **N-15** | **文档灌入:先 dry 后 commit + 累计预算** —— 把已写进文档的结论批量灌进结论层:默认只出清单(每条带真源指针与**真源原文**,看过再落);一趟总量 ≤40 条,且是**全趟额度**(跑一次与跑十次扣同一个额度,超额**拒绝再落**) | ✅ 已落(第一趟 40 条,2026-10-15) |
| **N-16** | **会过时的条目落成 `ephemeral`** —— 文本里带具体读数 / 版本号 / 时点状态的结论**不进开场注入**、检索仍搜得到:将来过时了也不会拿旧数字污染开场;**复查**走 `--review` 出信号清单(含时间锚却当着长期条 / 当次档够老 / 从没被采用过),**只列信号、不作废** | ✅ 已落 |
| **N-17** | **"纪律"不走检索召回** —— 纪律要的是"该动手时它**在场**(行动触发)",与"按内容去找(内容触发)"不是一回事 ⇒ **不进结论层的自动落库**,归宿是规矩 / 常驻注入面 | 📋 1.7.x |

### 1.7 新增(图书馆 · 工作区,未发布)

> ⚠️ **本节的状态标记只指"工作区代码 + 离线测试"这一层**:1.7 **尚未发布**(包版本已抬到 `1.7.1`,尚未发布/安装);"宿主界面上的「图书馆」标签"**已真机验收走通**(2026-10-07,见 L-11);「结论层」标签已于 **2026-10-15** 随一次宿主重启实测生效。

| # | 设定 | 状态 |
|---|---|---|
| **L-1** | **图书馆是独立库,不是记忆的第二层** —— 独立的 `library.db` 与独立的表;两库之间**不互相导入、不互相引用**(没有隐式通道) | ✅ 工作区 |
| **L-2** | **既有记忆永不迁进图书馆**;反过来,图书馆条目**不进注入面、不参与开场装配** | ✅ 工作区 |
| **L-3** | **"不迁移记忆"≠"禁止某个渠道的新资料"** —— 一条资料能不能进图书馆,看它**有没有具体、可追责的出处**,与它从哪个渠道来无关 | ✅ 口径 |
| **L-4** | 器灵侧**只有两只手**:`library_search`(搜)与 `library_check`(核对);**没有**新建 / 修订 / 撤下 / 改配置的模型工具,代码根目录与 URL 白名单**不接受模型入参** | ✅ 工作区 |
| **L-5** | **核对只认证"声明的那一处"** —— 结论只有 `ok` / `wrong` / `unknown`;回执必须写明"不认证解释性正文、也不构成通用真理" | ✅ 工作区 |
| **L-5′** | **比较方式的准确定义** —— **`exact`** = 类型严格的**结构比较**(JSON 对象**成员顺序无关**、**数组顺序有关**;文本按字面精确比);**`text-lf`** = 只把 CRLF/CR 归一成 LF 后精确比。**两者都不 trim、不折叠大小写、不模糊匹配** | ✅ 工作区 |
| **L-6** | **"不可用"与"零命中"必须分开** —— 库打不开 / 未启用 / 运行时不在场 = 明确的不可用(接口层 503 语义),**绝不回落成"搜到 0 条"** | ✅ 工作区 |
| **L-7** | **修订与撤下都留旧** —— 新版本换新 id、旧版留痕指向新版;撤下只**标记**状态、不物理删除;**跑完的**核对各留一行账(源摘要 / 观测值 / 结论 / 理由,含 `unknown`),**取消的不留** | ✅ 工作区 |
| **L-8** | **写面必须带"观测到的内容摘要"**(`content_hash`),对不上就拒 —— 防"看着旧版改新版" | ✅ 工作区 |
| **L-9** | **严格配置** —— 代码根目录与官方文档 URL 白名单**默认为空**、由管理员登记;限额在**激活时校验**,非法值**响亮报错**、不静默夹取 | ✅ 工作区 |
| **L-10** | **取消 = 没跑完,不留记录** —— 核对在飞时取消 / 断开 / 卸载 ⇒ 本次**不写任何核对记录**(既不留 `ok` / `wrong`,**也不留 `unknown`**);只有**跑完的**核对(含结论 `unknown`)才留账 | ✅ 工作区 |
| **L-11** | **发布状态如实声明** —— 包版本已抬到 **`1.7.1`(工作区候选)**,**未发布、未安装**;但「图书馆」标签**已真机验收走通**(2026-10-07),「结论层」标签也已随 2026-10-15 的重启实测生效 —— **"没发布"与"没验过"必须分开说** | ✅ 真机验收(2026-10-07) |

> ⚠️ **这一节还在补。** 完整规格有**四百余条**,本节先落**对外面**里最稳定的一批(设计理念);其余按主题分批补写 —— 每一条都要从开发期设定**改写成对外语言**并核对状态,这是**写作**而不是复制。

---

## 致谢(站在谁的肩膀上)

设计过程中受这些公开项目与讨论的启发,在此致谢(仅致谢思路,不含其代码):

- [Letta / MemGPT](https://www.letta.com/blog/memory-blocks/) — 持久人格记忆块、可设为只读、后台记忆智能体
- [SillyTavern](https://docs.sillytavern.app/usage/core-concepts/macros/) — 本地角色卡 / 世界书 / 时间与日期宏
- [LangMem](https://langchain-ai.github.io/langmem/concepts/conceptual_guide/) — 用反馈轨迹改写提示词
- [MyChatArchive](https://github.com/1ch1n/mychatarchive) — 多来源聊天导出 → 本地库 + 摘要管线
- [AI Migrator](https://ai-migrator.aiblewmymind.com/) — "记忆搬家"的人审闭环
- Claude / ChatGPT 的记忆功能、[Sonorus](https://github.com/KevinAHM/sonorus)("距上次交谈多久"注入)等

---

## 文档

| 文件 | 内容 |
|---|---|
| [`PHILOSOPHY.md`](./PHILOSOPHY.md) | **理念**:为什么值得做、凭什么这样做(目标循环 · 复盘与"人在循环" · 关系与边界 · 能力取舍) |
| [`CHANGELOG.md`](./CHANGELOG.md) | 版本更新记录(最新一节即当前版本) |
| [`DESIGN.md`](./DESIGN.md) | 架构与设计决策(三层记忆、冻结门、注入面、存储结构) |
| [`ACCESS-DESIGN.md`](./ACCESS-DESIGN.md) | 历史接入蓝图:三条通道、会话契约格式、导入流水线 |

> ⚠️ **从 npm 安装的人**:本包 `files` 白名单**不含 `images/`**(图只随 git 仓库公开) ⇒ 上表里**四份文档**都随包发出(`PHILOSOPHY.md` · `CHANGELOG.md` · `DESIGN.md` · `ACCESS-DESIGN.md`),能正常打开;「界面」一节的**四张截图**引用的是仓库里的**绝对地址**,所以 npmjs.com 上**能正常显示**,但**包里没有图片文件**(离线环境读不到)。想看原图请到仓库:<https://github.com/NightPainters/DSH-Ling>。

> 📄 **关于出处记录**:内部案卷、探针脚本与开发台账(`plans\`、`release\`、`_probe\`)属**作者本地开发物,不随本包公开**。正文或代码注释里若出现对它们的引用,那是**开发期的出处记录**,不是包内容。

---

## 许可证

[Apache License 2.0](./LICENSE) · 版权与归属见 [`NOTICE`](./NOTICE)。

Apache-2.0 第 3 条包含**明确的专利授权**与**专利反制终止**条款:你可以自由使用、修改、分发(含商用),但若你以本作品发起专利诉讼,授权即自动终止。

Install

dsh plugin --profile web add github:NightPainters/DSH-Ling

Profile: web

  • This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.
Source