dsh-soul-evolve
xiachuyoufeng/dsh-soul-evolve
Persona state and slow evolution for DSH: five state dimensions (valence, arousal, energy, intimacy, focus) move after each turn and decay toward baseline over time; four scene types (work, tech, support, chat) modulate reply length and warmth without changing the state itself; the Soul panel keeps a persona card plus weighted evolution entries and injects them into the system prompt persona-first under a 4000-character budget. Ships a browser panel with a persona card editor and a live state view (the five metrics plus the last six events), appends a JSONL trace to disk, and seeds a usable default persona on first install.
インストール
dsh plugin --profile web add github:xiachuyoufeng/dsh-soul-evolveREADME
dsh-soul-evolve
人格状态与缓慢演化插件(DeepSeek Harness)。
- 状态层:情绪效价/激活/能量/亲近度,随时间衰减、随交互累积
- 反射弧:输入 → 状态增量 → 表达调制(长度/温度/耐心旋钮,不宣告情绪)
- 每轮自动更新:监听真人
user/message,自动跑反射弧累积状态(0.4.0 起) - 合成引擎:C1–C6 六条硬约束把事件提炼成人格条目
- 场景切面:work / 技术 / 情绪支持 / 闲聊 四类场景,带滞回(1.0.0 起)
- 观测层:每轮追加一行 JSON,
scripts/stats.mjs直接算验收指标(1.0.0 起) - 注入预算:演化条目按权重衰减排序,整段硬顶 4000 字(1.0.0 起)
- 适配层:DSH 依赖收敛到 adapter.js,升级只改这一处
结构
src/state.js 状态存储:schema 版本、原子写入、衰减、作用域、旧路径回退
src/reflex.js 反射弧与表达调制(纯函数)
src/scene.js 场景判定(打分 + 滞回,纯函数)
src/trace.js 每轮观测(JSONL 追加、去重、轮转)
src/persona.js Soul 面板:正文 + 演化条目 + 权重 + 注入预算 + 历史
src/evolve.js 六条约束(入口/格式/频率/冲突/体量/禁止)
src/adapter.js 版本探测、能力检测、降级
src/auto-update.js 每轮自动更新(session/event → 反射弧 → 场景 → 状态文件)
src/api.js 配置面板与升级按钮的后端路由
src/upgrade.js 升级:源码 → 已装副本的原子同步
src/index.js cordis 入口:soul_state / soul_evolve 两个工具 + 动态状态注入
client.js 浏览器半侧:插件详情页的「从本地源码升级」按钮
cordis.patch.yml 挂载声明(DSH 按它把插件挂进配置树)
安装(重要)
插件包必须这样声明,否则 DSH 启动时读不到挂载声明:
"dsh": { "bundle": { "patch": "./cordis.patch.yml" } }
cordis.patch.yml 内容:
- insert:
- id: dsh-soul-evolve
name: 'dsh-soul-evolve'
安装:
dsh plugin add dsh-soul-evolve
装之前先跑预检(零风险,不碰 DSH 配置):
node scripts/verify-plugin.mjs # 74 项断言,全过才装
node --test test/soul.test.js # 35 个用例
数据
- 状态文件:
~/.dsh-soul-evolve/state.json(DSH_SOUL_HOME可覆盖) version字段 +migrate():结构升级时写迁移分支- 写文件用临时文件 + rename,避免崩溃损坏
升级(开发通道,默认关闭)
插件详情页的「从本地源码升级」按钮走这条路:
- 校验源码目录与已装副本都是
dsh-soul-evolve包 - 把已装副本备份到
~/.dsh-soul-evolve/upgrade-backups/<时间戳>/ - 把
src/*.js、package.json、cordis.patch.yml、README.md逐文件原子写过去(临时文件 + rename) - 失败自动回滚,并返回备份路径
- 完成后提示「重启 DSH 生效」
它只同步包文件,不碰 profile 依赖、不改 generation 元数据,所以不会触发依赖重装,也不会让启动校验失败。
背景:官方「更新」按钮要求包有
installTarget,而它只对 DSH 内置的官方目录生成,第三方本地包永远没有——所以这里在plugins.detail.actions插槽里自备一个入口。
这条通道只给开发用,公开发布版默认关闭:没有设置 DSH_SOUL_SOURCE 时,接口返回不可用,配置面板不渲染「升级」区块、插件详情页也不出现按钮——不会留一个点下去就报错的入口。
自己开发时这样打开:
export DSH_SOUL_SOURCE=/path/to/dsh-soul-evolve
接口:
GET /dsh-soul-evolve/api/status— 已装版本 / 源码版本 / 源码目录POST /dsh-soul-evolve/api/upgrade— 执行同步(要求x-soul-upgrade: 1头)
数据
- 状态文件:
~/.dsh-soul-evolve/state.json(DSH_SOUL_HOME可覆盖) - 改名前的旧状态:
~/.dsh-soul/state.json仍会被读取(新路径为空时回退,下次保存写入新路径) - 升级备份:
~/.dsh-soul-evolve/upgrade-backups/<时间戳>/ version字段 +migrate():结构升级时写迁移分支- 写文件用临时文件 + rename,避免崩溃损坏
每轮自动更新
0.4.0 起监听 session/event 里的 user/message:
- 只处理
source.kind === "user"的真人消息(跳过 goal / 团队 / 系统注入) - 取出文本块 →
reflex()算增量 →applyDelta→pushEvent→ 原子写 - 所有写入走同一个串行队列,避免多条消息并发读-改-写互相覆盖
- 任何异常只告警,不影响会话
效果:状态会自己变——下一轮请求组装的提示词里读到的是新状态。
可用 apply(ctx, { autoUpdate: false }) 关闭。
交互沉淀(1.2.0)
性格不只由「用户明确纠正」驱动,也会从交互本身慢慢长出来。
每 15 轮真人输入(sedimentEvery 可调)跑一次沉淀:把 turns.jsonl 里的逐轮观测聚合成候选,
够格的升级成演化条目,写回 Soul 面板。
两条轨道分开沉淀
「该怎么做」和「该用什么语气」是两件事,混在同一段里会互相盖掉。所以推断条目分两条轨道, 各自算门槛、各自写、各自注入:
| 轨道 | 写什么 | 检测器(按优先级) | 判据 | 初始权重 / 半衰 |
|---|---|---|---|---|
工作习惯 habit | 怎么干活 | habit:direct | 一句话指令占比 ≥ 12%(≥20 次) | 0.5 / 60 天 |
habit:ongoing | 每会话轮次中位 ≥ 12 | |||
性格表达 character | 以什么姿态出现 | character:curious | 追问命中占比 ≥ 5%(≥15 次) | 0.45 / 45 天 |
character:feedback | 正面反馈占比 ≥ 60%(≥20 次) | |||
character:steady | 每会话场景切换 ≤ 0.5 次 |
顺序按信息量排:能直接改行为的在前,「别乱变」这种最低的排最后。每个维度一轮只写 1 条, 所以排序决定的是「先长出哪一条」,不是「只有哪一条」。
场景不写条目:场景已经由「人格状态」段每轮实时注入,再沉淀一遍就是同义反复;
而且场景是内部枚举(work / tech / ...),写进条目对读的人没有指导意义。
注入时分成三段,互不干扰:
## Evolved ← 用户明说过的(纠正 / 重复犯错 / 偏好 / 场景)
## 工作习惯 ← habit 轨推断出来的
## 性格表达 ← character 轨推断出来的
习惯轨只收带数字的观测结论
「回复该短」这类话,面板正文早就手写了、memory 的 user 轨也存得下,再统计一遍只是把同一句话
重说一次。所以 1.2.0 移除了 habit:brief 检测器——习惯轨只留 memory 抓不到的那种结论:
「你 143/728 轮是一句话指令」没人会去确认它,但统计得出来。
写入前还有一道覆盖检查:把候选的「学到什么/以后怎么做」与面板正文、已有条目、memory-evolve 写下的记忆条目
(MEMORY.md / USER.md,只读、尽力而为)做字面相似度比较,达到 0.3 就判为已写过,跳过并记下原因。
阈值是实测的——同一句话的重述落在 45%,真实候选撞面板最高 9%、撞记忆最高 3%,取 0.3 两边都留余量。
它挡不住语义同义、措辞完全不同的重复,真正防重复建设靠的是上面那条选型。
与 memory-evolve 的分工
| memory-evolve | 本插件 | |
|---|---|---|
| 记什么 | 用户明说过的事实、偏好、规范 | 从行为里统计出来的模式 |
| 谁写的 | 模型提议 + 用户确认 | 统计推断,无人确认 |
| 保鲜 | 不衰减,靠定期审查合并去重 | 45–60 天半衰,推断错了自己淡出 |
| 注入位置 | memory:snapshot(order 500) | soul:persona(order 10) |
一句话边界:memory 存「他说过该怎么做」,本插件存「做下来一直是这么做」。 「回复该短」这种话 memory 存得下、面板正文也早就手写了,所以不进统计沉淀; 「220/909 轮是一句话指令」这种没人会去确认、但统计得出来的,才归这边。 同理「你的场合主要是 work」不写——那是描述,不是行为要求。
冲突时的优先级是隐式的、但确实存在:memory:snapshot 在 soul:persona 之后注入,且内容都经用户确认过,
所以 memory 更强势;soul 的推断条目只补 memory 没覆盖的部分。
三道闸,防止它给自己编人格
- 证据门槛:独立会话 ≥ 3、跨天 ≥ 3、相关轮次 ≥ 12,缺一不可。单次或单天出现的多半是任务要求,不是模式。
- 模型写不进去:
qualifies()明确拒绝kind: "pattern",只有插件自己的沉淀通道能产出这类条目(qualifiesInferred())。模型不能自造证据。 - 低权重 + 快半衰:初始权重 0.45–0.5、半衰 45–60 天。推断错了会自己淡出;被反复验证(同一
patternKey再次沉淀)权重 +0.1 才慢慢坐实。
另外几条节制:每个维度一次最多写 1 条(一边不够格不挡另一边)、两次自动沉淀至少隔 1 天、
同一 patternKey 冷却 14 天、条目里不许声称内在状态(BANNED 挡「我越来越亲近」这类)。
看得见也能催:配置面板「交互沉淀」区块按轨道分组显示观测统计、够格的候选、以及「还差几天」的候选
(例如 跨天 1/3(还差 2 天)),旁边一个「立即沉淀」按钮(POST /api/patterns/sediment,force 可绕过全局冷却)。
条目上的 evidence(会话数/天数/轮次/起止时间)会存进卡片并在面板显示为「依据 3 会话/30 轮」,删除照旧走 soul_persona(action=forget) 或面板上的「删」。
Soul 面板(0.5.0)
Soul 是"静态骨架",状态是"动态血肉":Soul 面板负责「我是谁、说话什么风格」,状态层负责「今天累不累、跟你熟不熟」。
- 格式:
soul.md风格 Markdown(与市场里 soul-md 系 Soul 面板互通,可直接导入/导出) - 存储:
~/.dsh-soul-evolve/personas.json(多张卡 + 当前激活) - 注入:激活卡作为
soul:persona段(order 10)注入提示词,改完下一轮请求生效,无需重启 - 硬约束:正文不能含
{{/}}(DSH 提示词变量语法,否则整个提示词组装会失败),保存时直接拒绝 - 工具:
soul_persona(list / get / set / delete / activate / evolve / forget / history / rollback),AI 可读写但不会自动改 - 开箱内容:首次安装会自动种入一份通用默认人格(
templates/default-soul.md),面板不是空白的;改它就是自己的,删掉不会自动长回来
正文与演化条目(1.0.0)
一张卡分两半:
| 部分 | 内容 | 谁写 | 注入策略 |
|---|---|---|---|
content | 手写基底(Identity / Personality / Boundaries…) | 用户或 set | 优先完整保留 |
entries[] | 演化条目(一条一句,带类别与权重) | evolve | 按权重排序,预算内取 Top-N |
权重与半衰期:correction 1.0 不衰减、repeat 0.9 不衰减、preference 0.6(90 天)、scene 0.4(30 天)。
写入时判重:同类别 Jaccard ≥ 0.5 视为再次命中,旧条目权重 +0.1,不新增。
整段注入硬顶 4000 字;被截掉的条目仍留在存储里,面板标灰「未注入」,权重回升后会自动回来。
set 覆盖正文时旧正文进历史(保留 10 版),可一键回滚。
场景切面(1.0.0)
四类场景 work / tech / support / chat,默认 work——判错场景比不判更糟。
滞回规则:新场景连续两轮占优,或单轮得分 ≥ max(2, 当前场景分 × 1.5) 才切换;间隔 6 小时以上连续计数清零。
场景偏移加在调制层(温度/长度/耐心),状态本身不分叉。
副语言是状态外显,不是开关:精力 < 0.35 才 hesitation,专注 < 0.4 才 fragmented,状态高时为 false 就不会出现。
配置面板(0.5.0)
插件页 → dsh-soul-evolve → 配置(plugins.bundle.config 插槽):
| 区块 | 内容 |
|---|---|
| 人格状态 | 五维数值 + 基线刻度 + 表达旋钮条形图 + 最近 6 条事件,可手动刷新 |
| Soul 面板 | 只有当前这一个人格:新建 / 编辑 / 保存 / 删除 / 导入 .md / 导出 .md。正文默认收起,点标题展开 |
| Evolved 条目 | 注入预算占用(X/4000 字)、条目列表(类别 · 有效权重 · 未注入标灰)、新增 / 删除、历史版本回滚 |
| 升级 | 从本地源码同步到已安装副本(点完重启生效) |
接口
GET /dsh-soul-evolve/api/state— 状态与调制POST /dsh-soul-evolve/api/state—{ delta }手动调整GET /dsh-soul-evolve/api/personas— Soul 列表GET /dsh-soul-evolve/api/personas/<name>— 单张 Soul 正文 + 条目 + 历史 + 注入占用POST /dsh-soul-evolve/api/personas/entries—{ name, text, category }加条目DELETE /dsh-soul-evolve/api/personas/entries?name=<n>&id=<id>— 删条目POST /dsh-soul-evolve/api/personas/rollback—{ name, index }回滚正文GET /dsh-soul-evolve/api/trace?limit=<n>— 观测记录PUT /dsh-soul-evolve/api/personas—{ name, content }新建/覆盖PUT /dsh-soul-evolve/api/personas/active—{ name }指定生效人格(null = 停用)。面板本身不提供切换入口,保存时自动指向当前卡片;保留此接口是给脚本和导入用的DELETE /dsh-soul-evolve/api/personas?name=<n>— 删除POST /dsh-soul-evolve/api/upgrade— 同步源码到已装副本
写操作要求 x-soul-action: 1 头(跨域预检挡 CSRF)。
已知限制
- 自动更新只覆盖真人输入;工具结果、模型回复不会改变状态
- 多会话共用一个状态文件(未按会话隔离);跨项目可用
stateScope: "workspace"按工作目录分桶,但同一目录下的多个会话仍共用一份 - Soul 只有"全局激活一张",尚未做按工作区/会话分档(人格连续性本身就是跨项目的,所以没做)
- 场景判定靠关键词与长度特征,反讽、长句闲聊这类边界情况会判错;判错时的代价是语气略偏,不是内容出错
- 「点击升级」只同步包文件,不更新 profile 里记录的依赖版本(
visibleVersion仍为安装时版本;插件页显示的版本读的是包内package.json,会正常变化)
开发与同步(迁移后工作流)
node --test test/soul.test.js # 36 个单元测试
node scripts/verify-plugin.mjs # 安装前预检(不碰 DSH 配置)
node scripts/stats.mjs # 观测数据验收(场景温差/切换率/面板预算)
node scripts/check-define.mjs # 用真实 defineTool 校验工具定义
bash scripts/sync-to-profile.sh # 同步到 DSH profile(自动备份)
卸载:dsh plugin remove dsh-soul-evolve。状态与 Soul 面板数据留在 ~/.dsh-soul-evolve/,不会被删——重装就接上。
License
MIT
发布与上架的完整步骤见 PUBLISHING.md。