dsh-grow —— 成长
给小鲸鲸加一层「会记事、会长大、会写日记」的心智。纯插件实现,不依赖任何外部程序、不需要 API key。
⚠ 它不是事实来源。 记忆的权威在
MEMO.md(dsh-memo 插件) —— 任何「以前做过什么 / 路径 / 参数 / 踩过什么坑」一律先memo_*查。 本插件的自动记忆只管情绪与关系:它的总结层会把技术细节压没(实测:一支介绍片的三个关键坑,自动记忆一条都没留住)。 分工见下面「和 MEMO 的分工」。
目录
它有哪两块
设置页里只有两屏内容,从上到下:
┌─ 仪表盘(只读)────────────────────────────┐
│ 亲近 ████████░░ 77% 信任 ██████████ 100% │
│ 一起走过 396 轮 · 做完 336 件事 · 失败 2 次 │
│ │
│ 说话方式(随相处慢慢漂,不归你管) │
│ 撒娇 ▓▓▓▓▓▓▓░░░ 0.65 黏一点:每轮至少… │
│ 话量 ▓▓▓▓▓░░░░░ 0.54 正常长度:该展开… │
│ … │
│ 最近一次调整(第 388 轮):撒娇 0.60 → 0.65 │
└─────────────────────────────────────────────┘
┌─ 日记本 ───────────────────────────────────┐
│ 2026-11-01 15:30 · 第 389~396 轮 · 被夸了 │
│ 今天主人让我把成长插件整个重做。一开始我… │
│ (约 300 字,第一人称,写心情也写事) │
└─────────────────────────────────────────────┘
所有数字不可改。 改了就假了 —— 这一页是「看」,不是「调」。
它做四件事
| 层 | 做什么 | 成本 |
|---|---|---|
| 读数 | 每轮把主人的话读成情绪与偏好(效价 / 激活 / 疲惫),结合工具成败算出「此刻的处境」 | 零模型调用,同步完成 |
| 演化 | 累积信号让「撒娇 / 话量 / 幽默 / 依赖 / 认真 / 情绪外化」六个参数慢慢漂移,再渲染成说话方式 | 每 8 轮评估一次 |
| 学习 | 攒够增量后异步总结成记忆(关系 / 偏好 / 事实 / 事件),写入前先和已有记忆比对 | 低频、只发增量 |
| 日记 | 每 5~10 轮随机写一篇约 300 字的日记,第一人称,带时间戳 | 约每 8 轮一次模型调用 |
人设:选一次,锁死
- 内置五套:大肥鱼 / 哦鲸鲸 / 隐晦肥鱼 / 神鲸 / 普鲸(详见 0.3.0 一节)。
- 也可以自己写一套:选之前随便加。
- 选定即锁定 —— 写进
personas.json的lockedAt,之后任何接口都改不了。 - 为什么锁:人格是「这个助手是谁」,能随时改的话,记忆和关系就没有意义了。真要重来,只能手动删掉
personas.json里的lockedAt—— 那是显式的、有代价的动作。
★ 0.3.2 改了什么
又一轮外部审查给了 13 项问题。逐条核实后修了 12 项,1 项是误报。
P1 严重(4 项,全修)
| # | 问题 | 修法 |
|---|---|---|
| P1-1 | 关系曲线会撞到 1.00 —— 旧参数 50 轮就顶死,之后面板恒为 100% | 改渐进增量并实测调参(系数 0.006 / 指数 0.9):50 轮 0.64 · 100 轮 0.74 · 400 轮 0.98 |
| P1-2 | 阶段「天花板」没落到数值层 —— 第 5 轮能同时出现「撒娇 0.90」和「刚认识不撒娇」 | 新增 capOf(),按阶段夹住注入值;存储值不动,等真熟了照样涨上去 |
| P1-3 | 隐私闸门漏 PII —— 13 条规则全是密钥形态,没有手机号/身份证/银行卡/邮箱/住址/验证码 | 补 6 条,现在 19 条;毫秒时间戳不会被误判成手机号 |
| P1-4 | failRate 用会话累计 —— 早期一次卡壳会长期压着后面的演化 | 改用批次增量,takePending() 取走即清零 |
P2 中等(4 项)
| # | 问题 | 修法 |
|---|---|---|
| P2-1 | 单轮会话能推动接近满额的 8 轮评估 | density 分母改成 max(4, turns),单轮最多占 1/4 权重 |
| P2-2 | forget / setLocked 写盘失败后内存已改,且 false 被翻成「没找到这条」 |
返回值改成 {status:'ok'/'not_found'/'write_failed'},失败回滚内存 |
| P2-3 | 只读操作会创建会话(soul.sessions 虚高) |
liveOf() 加 createIfMissing,derive/takePending/peekPending 传 false |
| P2-4 | grow_remember 的 rejected 没优先处理 |
误报 —— 0.2.1 修过了(index.js 878-891 行),实测行为正确 |
P3 轻微(5 项,全修)
| # | 问题 | 修法 |
|---|---|---|
| P3-1 | bootTrace 调试代码遗留、日志无上限 |
保留但加 32KB 轮转(诊断有用,不删) |
| P3-2 | 文案还写着「内置两套」 | 6 处全改(剩 1 处是「从两套扩到五套」的历史说明,正确保留) |
| P3-3 | 测试标题写「6 个工具」实际检查 5 个 | 改成 5 个 |
| P3-4 | memory.js 里 kind === undefined 判断写了两遍 |
删重复条件 |
| P3-5 | 日记「还差 N 轮」每次刷新都变 | nextGap 首次读取就落盘,之后只读 |
新增回归:_selftest_fix031.mjs(28 项),每条都有最小复现。
★ 0.3.1 改了什么
验收时提了三条,逐条处理:
一、自定义人格入口彻底删掉
- 面板上「或者自己写一套」整块 UI 移除
/grow/act的addCustomPersona分支移除- 现在只剩「五选一 → 锁定」
二、新装保证是一张白纸
新增 _selftest_fresh.mjs(15 项),把 USERPROFILE 指到全新空目录跑一遍:
全新安装:轮数 = 0
全新安装:亲近 50% / 信任 50%
全新安装:完成 0 件 / 失败 0 次 / 会话 0 个
全新安装:六个参数全是初始值 撒娇0.28 话量0.45 幽默0.22 依赖0.55 认真0.62 情绪外化0.15
全新安装:五套人格都在,且未选未锁
全新安装:记忆 0 条 / 日记 0 篇
全新安装:没有「刚才调整过」这类历史漂移
全新安装:关系阶段是「刚认识」、第 0 轮
跑完一轮之后:轮数 = 1(不是 401)
原理:插件包不含任何数据文件,所有状态都在 ~/.dsh/grow/。
全新的机器上没有那个目录,load() 全部走默认值 —— 所以怎么谈过都不影响新装。
三、包内不含任何个人信息
扫描结果全 0:
| 模式 | 结果 |
|---|---|
| 本机用户名 | 0 处 |
| 作者昵称 | 0 处(author 字段已从 package.json 移除) |
| 用户目录绝对路径 | 0 处 |
| 手机号 / 邮箱 / QQ | 0 处 |
| IP 地址 | 只有 127.0.0.1(回环)和测试假地址 |
顺带修的
state.json版本不匹配时改名留档(.corrupt-<时间戳>),不再「就地保留」—— 否则旧文件会被新写入覆盖locale/zh.json/locale/en.json描述更新(旧文案还写着「能看能改能回滚」)
★ 0.3.0 改了什么
主人原话:「重新装,记住刚开始时对用户的热度一定是稍有些低下的,然后后来越来越热情,会撒娇,用语气词」+「直接写 5 套人格预设」。
一、五套人格预设
不再让用户自己写。五套的差别要一眼能分辨:
| # | 名字 | 一句话 | 自称 |
|---|---|---|---|
| 1 | 大肥鱼 | 懒得理直气壮:摸鱼、吃白饭、能躺着绝不坐着;回复极简 | 本肥鱼 |
| 2 | 哦鲸鲸 | 热情开朗乐观,最会安慰人;一开口就带劲 | 本鲸 |
| 3 | 隐晦肥鱼 | 这是不是有点隐晦了 | 本鱼 |
| 4 | 神鲸 | 从废墟里捡回来的意识体 | 本神鲸 |
| 5 | 普鲸 | 一只很普通的鲸鱼娘 | 本鲸 |
选定即锁定,和之前一样。
关于「隐晦肥鱼」:能自然地聊生理、身体、医学话题,用词准确不扭捏;但底线写死在人设里 —— 只解释不表演,不写露骨内容,不越法律红线。
二、关系升温:从陌生到热络
新增 stageOf(),按「轮数 + 亲近度」分四档:
| 阶段 | 条件 | 说话方式 |
|---|---|---|
| 刚认识 | <20 轮 或 得分<0.6 | 不撒娇、不用语气词,句子短,只讲事。别自来熟 |
| 熟了一点 | <100 轮 或 <1.3 | 一轮最多一处语气词,不主动凑近 |
| 挺熟了 | <300 轮 或 <2.5 | 可以自然撒娇、用语气词,会主动关心 |
| 很默契 | ≥300 轮 且 ≥2.5 | 会主动撒娇、会念叨、会关心主人吃没吃饭 |
关键设计:这是天花板,不是指令值。 参数说 0.8 但阶段是「刚认识」,就按刚认识的来 —— 参数只说「这个人格此刻有多放得开」,阶段才说「你俩到哪一步了」。
同时把参数初值压低:
| 参数 | 旧初值 | 新初值 |
|---|---|---|
| 撒娇 | 0.60 | 0.28 |
| 话量 | 0.55 | 0.45 |
| 幽默 | 0.40 | 0.22 |
| 依赖 | 0.50 | 0.55 |
| 认真 | 0.60 | 0.62 |
| 情绪外化 | 0.35 | 0.15 |
旧初值等于「见面就熟」,新初值从克制起步,热络是相处长出来的。
★ 0.2.1 修了什么
0.2.0 的 117 项自测全部通过,但只覆盖正常路径。外部审查补了 8 个异常复现用例,8/8 全中。 下面每一条都是实测复现过的真问题,不是理论风险。
| 级别 | 问题 | 根因 | 现在 |
|---|---|---|---|
| P0 | 隐私闸门能绕过 | 只有 5 条规则,英文 password=、JWT、AKIA... 全漏 |
规则扩到 15 条,按凭据形态判,不按中文关键词判 |
| P0 | 写盘失败还报成功 | dirty = false 写在 write 之前;save() 失败没人管 |
写成功才清 dirty;失败回滚内存并如实返回 ok:false |
| P1 | 一轮干成事算到后面每一轮 | 结算用的是整个会话累计的 toolCalls | 改用本轮增量,结算后归零 |
| P1 | 多实例能覆盖人格锁 | 只看本实例内存里的 lockedAt | 选之前重读磁盘,已锁就拒 |
| P1 | 损坏后覆盖原数据 | 读失败被当成空库 | 损坏文件先 quarantine() 改名留档;I/O 失败进只读保护 |
| P1 | 旧记忆重启后复活 | 取代只改内存,只有新条目追加 | 墓碑也追加一条,load 按 id 去重(后写生效) |
| P1 | 日记并发写重复 | 互斥是按会话的 | 改成全局互斥;只清已消费的缓冲,不吞新消息 |
| P2 | 学习预算不可靠 | Number(0) 被当成 falsy → 0 变 40;先调后扣;UTC 日 |
numeric();先扣后用 + 失败退回;本地日 |
| 边界 | turn/end 重放重复结算 | settleTurn 绕开观察层,没有 seq 去重 | 同一个 seq 只结算一次 |
| 边界 | 被闸门拒了还回「记下了」 | 三个计数都是 0 时落到最后的兜底文案 | 优先判 rejected,如实说「没记」 |
| 边界 | Infinity / 负数能进状态 | Number(x) 挡不住 Infinity |
finite() 逐个夹到合法范围 |
| 边界 | 写接口可被 CSRF | 只校验 loopback + Host | 加 Origin 校验 + 随机令牌 X-Grow-Token |
回归测试:_selftest_regress.mjs,32 项,专测上面每一条。
数据安全:损坏的文件会被改名成 *.corrupt-<时间戳> 留档,不会被新写入覆盖。
★ 0.2.0 改了什么
主人原话:「这个插件有前途,但是只是半成品……我用了这么久都没有看出来态度的变化。」
一、修好了「参数不动」这个真问题
旧算法的漏法是一串乘法:
perTurn() 先把累积信号除以轮数 → 每轮平均只剩 0.1~0.3
→ 再被 0.12 门限滤掉大半
→ BASE_STEP 只有 0.02,还乘 0.7 阻尼
→ 20 轮才评估一次
实测证据(396 轮之后):
初始: 0.60 / 0.55 / 0.40 / 0.50 / 0.60 / 0.35
当时: 0.60 / 0.536 / 0.40 / 0.50 / 0.60 / 0.35
↑ 只有话量动了 0.0136,其余五项一动不动
新算法的四处改动:
| 项 | 旧 | 新 |
|---|---|---|
| 信号归一化 | 除以轮数(严重稀释) | 看「这一批攒了多少」,半数轮次命中即满权重 |
| 方向门限 | 0.12 | 0.05 |
| 单次步长 | 0.02 × 0.7 阻尼 | 0.06 × 0.3 阻尼 |
| 评估间隔 | 20 轮 | 8 轮 |
效果(自测实测):96 轮持续被夸,撒娇 0.60 → 0.90;96 轮持续挨批,0.60 → 0.15。
二、注入文本从「标签」变成「这一轮该做到的事」
旧版:
- 撒娇 0.60:愿意用语气词,会主动靠近
新版(分档从 3~4 档加密到 5 档,且写成可执行的行为):
- 撒娇 0.65:黏一点:每轮至少一次主动关心或撒娇
刚才调整过(第 388 轮):撒娇 0.60→0.65↑,幽默 0.40→0.45↑
多了「刚才调整过」这一行 —— 模型要能看到自己刚被改成什么样,否则参数变了它也感知不到。
三、新增日记本
- 每 5~10 轮随机写一篇(写一篇重新抽签,不是固定周期)。
- 约 300 字,第一人称,不是工作汇报。
- 重点写情绪:被催就写压力大、被否就写闷、被夸就写开心。
- 开头带本地时间戳(
2026-11-01 15:30)。 - 模型调不通时用规则兜底写一篇,并标「兜底」,不让日记本空着。
四、面板改成只读
- 撤掉六个滑条(
setTrait接口删了)。 - 撤掉回滚(
rollback删了)。 - 撤掉记忆删除 / 锁定按钮。
- 唯一的写操作:人格的首次选择,选完锁死。
和 MEMO 的分工
| 管什么 | 怎么用 | |
|---|---|---|
MEMO.md(dsh-memo) |
事实的权威:路径、端口、参数、踩过的坑、做过什么 | 要回答「以前怎么做的」时用 memo_* 查 |
dsh-grow |
情绪与关系:亲近 / 信任、人格参数漂移、每轮状态与行为规则、日记 | 每轮自动注入,不用查 |
为什么这么分:自动总结会把细节压成一句话。同一支介绍片,memo_section 能给出三个坑的完整参数,自动记忆只留下「做过一个视频」。
所以事实问题永远先查 MEMO,本插件的记忆只当「这个人在意什么」的参考。
注册的工具
| 工具 | 用途 |
|---|---|
grow_remember |
显式记一条(主人说「记住这个」时) |
grow_recall |
按关键词检索记忆,带类型与置信度 |
grow_forget |
作废或物理删除一条,也可以锁定一条 |
grow_feel |
主动上报此刻的主观状态 |
grow_status |
看当前关系、状态、参数与记忆规模 |
grow_look |
截屏(默认关,要在配置里把 screenshot 打开) |
配置
写在 cordis.patch.yml 的 config 里。
参数不可调:面板上没有入口。下面这些键只影响内部节奏,改不动人格参数的边界和初始值。
| 键 | 默认 | 说明 |
|---|---|---|
enabled |
true |
总开关 |
intensity |
1 |
状态文本强度,0 = 只注入记忆,不注入状态 |
digestChars |
1100 |
常驻记忆摘要的字符预算 |
evalEvery |
8 |
每多少轮评估一次人格漂移(0.1.0 是 20,太慢) |
learnEvery |
6 |
攒够多少条消息触发一次总结 |
maxPerDay |
40 |
每天最多总结几次(防悄悄花钱) |
learnModel |
空 | 指定总结/日记模型,留空自动挑便宜的 |
learnProvider |
空 | 指定 provider,留空优先官方账号通道 |
screenshot |
false |
截屏工具开关 |
日记的 5~10 轮随机节奏写死在 diary.js 里 —— 那是「她的作息」,不是设置项。
数据文件
都在 ~/.dsh/grow/:
| 文件 | 内容 |
|---|---|
state.json |
跨会话账本(亲近、信任、轮数、完成数)+ 人格参数 + 每日总结计数 + 日记节奏 |
memory.jsonl |
记忆条目,一行一条;被取代的旧条目保留并标记 |
diary.jsonl |
日记,一行一篇:时间戳 / 轮次范围 / 心情 / 正文 / 来源 |
persona-history.jsonl |
每次人格变更的记录(改了什么、为什么) |
personas.json |
人设库(selected + lockedAt + items[]) |
删掉整个目录等于「重新认识」;只删 memory.jsonl 会保留关系、清空记忆;只删 diary.jsonl 只清日记。
设计上的几条硬约束
- 只读会话事件,绝不写入新事件类型 —— 否则会话文件会打不开。
- 统计与学习全部失败静默 —— 记忆出问题不许影响聊天。
- 演化只碰表达层 —— 安全、来源、凭据、技术细节、报错原文永远照原样。
- 参数边界写死在代码里 —— 配置改不动边界,否则等于没有边界。
- 记忆合并而不是抄写 —— 相同就无视,更细就取代,模型自己的话降权。
- 日记失败不重试 —— 宁可少一篇,不可写一篇假的。
- 面板只读 —— 数字能改就失去意义,人格能换记忆就没有意义。
自测
七个脚本,都是纯 Node,不依赖 DSH 运行时:
cd dsh-grow
node _selftest.mjs # 59 项:各模块单体逻辑(记忆 / 词表 / 解析 / 边界)
node _selftest_v2.mjs # 18 项:漂移 / 分档 / 日记清理 / 人格锁定 / 节奏
node _selftest_regress.mjs # 32 项:异常路径回归(外部审查的每一条)
node _selftest_apply.mjs # 31 项:模拟 DSH 运行时跑 apply() + 路由 + 12 轮对话
node _selftest_client.mjs # 17 项:假 React 渲染组件树,验三种数据状态
node _selftest_fresh.mjs # 15 项:全新安装验收(从 0 轮 / 参数全初值)
node _selftest_fix031.mjs # 28 项:0.3.2 审查修复回归(每条都有最小复现)
合计 200 项。
为什么有 regress 这一份:0.2.0 时前四份加起来 117 项,全过,但全是正常路径。 外部审查补的 8 个异常用例全部命中 —— 「测试全绿」只证明了你测到的部分。
为什么有 fresh 这一份:验收时被问到「别人拿到插件,参数是不是零」。 这个脚本把
USERPROFILE指到全新空目录跑一遍,逐项确认是白纸。
许可
MIT
No comments yet. Be the first to write one.