DeepSeek Harness Plugin Hub

Publish and manage complete Harness Profiles. Discover Plugins for your next setup.

Explore

PluginsPresetsDocsNews

Community

Publish a pluginContactReport an issue

Resources

Plugin Hub on GitHubDeepSeek HarnessSystem statusPrivacy notice
© 2026 DeepSeek Harness Plugin HubPowered byPaxTech

Independent and unofficial. Not affiliated with, authorized by, or endorsed by DeepSeek.

DeepSeek Harness Plugin Hub
ProfilesPluginsCategoriesNewsDocsSign inManage Profiles
ProfilesPluginsCategoriesNewsDocsSign in
← Plugins
A

dsh-agent-soul

Agent Soul

Give DSH agents a fallible memory, a changing personality, and a fluctuating mood: personality is merged by section (project settings override global settings without erasing them), memories decay in a human-like way (clear → vague → only an impression remains → forgotten), “things on her mind” give

The plugin will be installed here. Keep web if you are unsure.

npx -y @deepseek-ai/dsh plugin --profile web add github:danshuyue/dsh-agent-soul#0ae265cc5b5dac10e66d513c94e2f8bc4815bc36
READMECompatibilityVersions

Description

Give DSH agents a fallible memory, a changing personality, and a fluctuating mood: personality is merged by section (project settings override global settings without erasing them), memories decay in a human-like way (clear → vague → only an impression remains → forgotten), “things on her mind” give her initiative continuity, and her mood evaluates and settles itself with each conversation turn (no injection, no ornamentation).

Compatibility and provenance

Agent Soul is published as dsh-agent-soul and currently resolves to version 0.5.2. The Hub verifies its manifest and preserves the exact installation source for reproducible installs.

DSH compatibility
*
Runtime surfaces
web
Release source
github
Registry updated
11/4/2026

Versions

0.5.2stable
11/4/2026
0.5.1stable
11/2/2026

Related plugins

Loading related plugins…

Latest
0.5.2
DSH
*
HMR
Process restart
Tree shaking
Safe tree shaking not declared
Unpacked size
Unavailable
Files
Unavailable
Surface
web
License
MIT
Source
github
GitHub
★ 1
Weekly downloads
0
Last push
11/4/2026
View source ↗
README badge

Click the badge to copy Markdown for your README.

Do you maintain this Plugin?Claim benefit · Priority security scan

Verify the GitHub repository declared in package.json to manage this listing. After you claim it, Hub will prioritize a security scan of the current version and publish the result when it passes.

Claim this Plugin →
Report an issue

Related plugins

More verified plugins in memory-context.

Experimental Session Search@deepseek-ai/dsh-experimental-session-searchGive Agents tools to search and read earlier sessions from the current workspace.Hindsight Coding Agents@vectorize-io/hindsight-coding-agentsReflect-only Hindsight long-term memory for coding agents (harness-pluggable: opencode, …), with automatic background ingestion (no setup CLI).Contextdsh-contextA DeepSeek Harness plugin for context insight and management, with context dashboard and context command, for understanding how the context is made of, and how it evolves.Memory Plugin@openviking/dsh-memory-pluginOpenViking memory and context bundle for DeepSeek Harness

README

dsh-agent-soul

给 DSH 的 agent 一份会忘事的记忆、一套会变的人格、一份会起伏的心情,和一个她自己写的笔记本。

它做六件事:

解决什么
人格用 Markdown 写「你是谁」。项目人格只覆盖同名小节,全局的照常生效 —— 不会因为某个目录里有 AGENT.md,你在全局写的话就整份失效
记忆值得记的事自己进来,随时间变模糊:清晰 → 清楚 → 模糊 → 只剩印象 → 遗忘。忘了不是删掉,是先变得说不清
目的从记忆里长出一份「心里惦记的事」,让她的主动有连续性 —— 不是定时问候,是主动想起
心情每轮对话后自己判一次起伏(心情值 + 七情 + 时间线),不注入提示词。记下来的情绪有两个出口:当日记的底色、推着目的机制的驱动
日记开机时把前一天沉淀成两篇:一篇公开(可以拿给你看),一篇私密(只写给自己)。先投消息让她用 soul_diary 自己写,文件没出现才由插件 llm.stream 兜底生成
笔记她自己写的 md:一篇一个主题、自由格式、永不衰减。提示词里只给索引,正文用 soul_note 按需读

不管 B 站、不管 QQ、不管桌宠。这个插件只关心「她是谁」和「她记得什么」。


日常怎么用

装好之后基本不需要操作。想干预时有三个入口:

1. 什么都不做(大多数时候)

  • 你说话,值得记的事自动进记忆卡
  • 每天第一次打开,它会把不重要的事降一格清晰度
  • 她答应过的事、没弄明白的事,自动变成「心里惦记的事」
  • 开机时自动补写前一天的日记

2. 设置 → 灵魂(八个标签)

标签用来干什么
概览一眼看「到底生效没有」:人格来源、记忆条数与容量、惦记几件事、最近(当前心情 + 昨天日记写没写)
人格直接编辑全局 / 项目人格。存完下一个回合生效
记忆搜、看清晰度和重要度、固定(永不遗忘)、删、手动加
目的四个驱动读数 + 惦记清单,可以「放下了」
心情心情值 + 七情 + 时间线;可重置、可手动跑一次平复
日记开关、兜底检查间隔、公开/私密语气、手动触发(可带备注)、最近日记列表
笔记她自己写的 md:看、改、删、新建;也能关掉索引注入
设置注入预算、叠加还是互斥、各项开关、立即整理一次

3. 直接跟她说话

「记住:我下周一开始做蓝桥杯的题」
「你还记得我之前说过什么关于 STM32 的事吗」
「把那条关于数学建模的记忆忘掉」
「你最近在惦记什么」

工具是四个:soul_memory(write/recall/forget/pin/list)、soul_goal(add/done/list)、soul_note(list/read/write/append/rename/delete)、soul_diary(write/look)。

文件全是纯文本,也可以直接改:

~/.dsh/AGENT.md                   全局人格
~/.dsh/agents/memory/global.json  记忆卡(改完刷新页面生效)

人格注入

三层来源与优先级

层文件怎么改
1<cwd>/AGENT.md自己的编辑器(跟着仓库走,可提交进 git)
2~/.dsh/agents/projects/<slug>.md设置 → 人格 → 项目人格(本机)
3~/.dsh/AGENT.md设置 → 人格 → 全局人格

项目层只取 1、2 里优先级最高的那一份(不是两份合并)——两份都写着 # 你是谁 时,模型会各听一半,最后谁也不像。

叠加还是互斥

互斥(exclusive)叠加(stack,默认)
有项目人格时全局怎么办整份不注入按小节叠加:项目覆盖同名小节,全局其余保留
你在全局写的通用约定某些工作区完全不遵守,像没读到到处生效,除非项目里显式覆盖同名小节

什么时候注入

会话第一轮注入一次,之后每 15 轮补一次。

不每轮重发(那是纯浪费),也不注入一次就再也不管 —— 上下文压缩之后会把人格正文压掉,那时她会「记得聊过什么,但不记得自己是谁」。15 轮补一次,成本可以忽略,压缩后能自己找回来。

⚠️ 如果 <cwd>/AGENT.md 存在,它会盖住设置页编辑的第 2 层。你在设置页改了、保存了、提示「已保存」,但其实没生效。页面会亮黄框提示。


记忆

什么会被记住

自动提取分三步:过滤 → 提炼 → 打分入库。

1. 过滤(纯规则):只收「人在说话」。丢掉宿主注入的上下文、桥接层的触发模板、信封前缀(「(QQ 私聊·用户本人 [QQ号])」),以及她自己被桥回显的话。

信封前缀有 20 多字,会把「起了起了」这种 4 字短句凑够 8 字门槛,长度过滤直接失效。而回显的那条更糟:她的每句自言自语都会变成记忆,是自我强化的死循环。

2. 提炼(模型):过了过滤的原话不直接入库,先交给模型改写成记忆。

原话:(QQ 私聊·用户本人 [QQ号])今早上7点多才起来。昨晚上在忙着给你做插件,睡的晚了。
记忆:树树今早七点多才起,昨晚熬夜在做插件

提示词里写死三条硬约束:写成「关于树树的一条记忆」而不是复述对话、不许编他没说过的事、没有信息量就输出空数组(宁可少记)。提炼失败一律退回原话直存,不丢信息。

3. 打分入库:过线才写进卡,门槛按 kind 分档 —— 一次性事件(event)要 70,事实 / 偏好 / 约定这类画像(fact / preference / promise)是 42。

你说的结果
「今晚吃牛肉丸子汤」不记 —— 一次性日常,记下来只占位置
「我手上有一块 F103 开发板」记
「我目标是当嵌入式工程师」「我是烟台大学大二」记,且额外写一份进全局卡(换工作区也认得你)

打分是纯规则 + 数值,不调模型 —— 什么时候该记、记多久,确定性比聪明重要。模型只负责把话改写成记忆,不参与「要不要记」。

会忘记

清晰 ──→ 清楚 ──→ 模糊 ──→ 只剩印象 ──→ 遗忘
200字     130字    42字+    只剩标题     移出提示词
                  「记不清了」「好像有…」  进 archive.jsonl

关键不在阶梯,在注入时的措辞:模糊的写成「……(细节记不清了)」,只剩印象的写成「好像有「xx」这件事…(想不起来了)」。模型看到的是「我隐约记得」,而不是「查无此事」或者「细节完备但其实已经不可靠」。

  • 一天只降一档:慢慢模糊,不是某天突然想不起来
  • 重要的事降得慢:普通记忆每天 -0.35 留存,和主人相关的只有 -0.12
  • 永远不会忘的:固定的,和「第一次」类的事
  • 提起来会变清楚:被再次提及就刷新时间、召回 +1,一天回升一档

入库前脱敏(密钥 / 手机号 / 身份证 / 卡号 / 链接 / QQ 号),太短的不记。

注入策略

  • 第一轮:整张卡 + 一段「最近」(见下节)
  • 之后:按当前这句话召回相关几条,同一话题没变就复用上次的文本,不重拼
  • 近似重复:新记忆和已有条目字面相近时,只记一行日志提醒,不自动替换 —— 让字符串算法决定「哪句是真的」太危险

「最近」:跨对话的那条通路

新会话第一轮,除了记忆卡,还会带上:

### 前几天的日记
(最近 2 天的公开日记,超 6000 字留最近的)

### 今天到目前为止(可能来自别的对话)
[14:20] 树树:……
[14:21] 我:……

为什么需要:人不会在一个对话里做完所有事 —— 修插件、QQ 聊天、闲聊是分开的会话。记忆卡只有零碎事实,日记只写到昨天,所以新开一个对话时她不知道今天早些时候在别的对话里做过什么。

数据来源是 conversations/<今天>.jsonl —— 这个流水本来就是所有会话共同汇总的(原来只用来生成日记,从没读回过会话)。

配置 memoryRecentDiaryDays / memoryRecentChatChars 控制,设 0 关闭。私密日记默认不进(memoryRecentDiaryIncludePrivate)。


目的机制:「主动想起」而不是「主动发消息」

四个驱动维度,各 0~1,8 小时落一半:

维度被什么推高
想弄明白疑问句、看不懂的东西
在乎累、难受、熬夜
想分享有意思的发现、推荐
想搞懂学会了、跑通了、踩坑了

再加上从记忆里长出来的「没闭合的事」,渲染成很短的一段(默认 ≤ 320 字,最多 3 条)。里面明确写着「这不是任务清单,不需要每轮都提」。

心情会推着它走:担心/心疼 → 在乎 涨,开心/甜蜜 → 想分享 涨。不是每轮往上加,而是让驱动向情绪推出的目标靠拢(每轮 15%)—— 累加式写法几十轮后会把所有驱动顶到 1,等于没有状态;靠拢则天然有收敛点。

说出去了才算完:渲染出去的目标会被记下来,下一轮看她有没有真的说出口。说到了 → 驱动 ×0.45;超过 6 小时没说到 → ×0.85,慢慢淡了。只判一次,判完移出列表。

本地 DSH 没有推送渠道(你不打开就没有回合),所以「主动」只能是主动想起。真正主动发消息属于 QQ 那边的事。


心情

心情是全局一份(agents/mood/global.json),不按工作区拆。

2026-10-31 改的。原来每个工作区一份,实测四份分数从 71 到 99.95 —— 同一个人换个目录就换一份心情,会出现「刚在 A 里说累、切到 B 又满血」。心情是「这个人现在的状态」,不是「这个文件夹的状态」。

迁移是自动的:global.json 不存在时,从旧文件里挑记录最完整的那份搬过来,旧文件保留不删。

七情的满格是 20 不是 100:判定单次上限 ±10,又被 90 分钟半衰期往回磨,实测一整天峰值只到 13。所以设置页和日记都按 20 当 100% 折算 —— 「踏实 65%」是很踏实,不是刚过一半。

判定用哪个模型:每回合判一次,是全插件最贵的一处固定开销。两条路:

  • 留空 → 跟会话模型(贵,但不用配)
  • moodJudgeProvider: siliconflow + moodJudgeModel: Qwen/Qwen2.5-7B-Instruct → 走硅基流动的小模型

实测(同一批 6 个样本,方向判断 6/6 一致):输入 token 2015 → 1182,延迟 1.52s,成本降到零头。提示词也一起压过(2875 → 1646 字),压的时候踩过一个坑:把幅度写成「±14」会丢掉正负号,模型会把「没吃饭」判成好事。

密钥怎么存:设置页「判断模型」下面有一个密码框,粘进去点保存即可。Windows 上用 DPAPI(CurrentUser)加密 —— 密文绑你的账户,拷到别的机器解不开;文件在 ~/.dsh/agents/secrets/siliconflow.bin,里面没有明文。非 Windows 平台会退化成 base64 + 0600 权限,并在日志里明说「这不是真加密」。

不要把它写进 persona.json。那个文件是明文,会被同步、会被整份贴进 issue。

心情会影响语气(2026-11-01 新增)

心情不只是个记在后台的数字 —— 它会偶尔在对话里露一句。规则很克制:

  • 三档:心情偏离基准超过阈值才算「低」或「高」,中档什么都不加(大多数时候都是中档)
  • 不是每轮都加:低/高也要隔 N 轮才挂一次,默认 7 轮,设置页可调
  • 挂在消息上,不进系统提示:心情是「那一刻」的状态,该像照片上的日期一样跟着那条消息冻结。放在系统提示里会随心情每轮变化,长缓存全失效,而且永远在过时

挂上去长这样:

[心情:今天不太有劲,说话会短一些,不太想解释。(这只是当时的状态,不是这个人变了。)]

最后那句隔离说明是必须的:它划清「今天的力气」和「这个人是什么样」——人设本身一个字节都不动。

设置页在「心情 → 判断模型」下面:开关、每几轮、偏离多少算低/高。

实现上的一个诚实说明:DSH 没有「改写当前用户消息」的钩子,所以这条心情是另外投递的一条 user 消息(source.kind = 'soul-tone'),不是真的把原消息改写了。效果上是那句话后面跟了一条心情。

它也不会污染记忆:soul-tone 在 INJECTED_SOURCES 里,记忆提取会跳过。

一个必须记下来的教训(2026-11-04 修):这条链从 11-01 上线到 11-04 之间一直是死的。 原因是 index.js 调了 moodState(cwd) 却没把它写进 store 的导入列表 —— 每轮都抛 ReferenceError,而外面那层是裸 catch { return },异常被吞得干干净净。 表现是:三个语气配置项全是摆设、inbox.append 一次没发生过、日志里一个字都没有。 tone.test.js 直接测 takeTone(纯函数),绕过了调用点,所以测试全绿。

修的时候改了两件事:补上导入;把裸 catch 换成带 note() 的。静默兜底和静默失效 只差一行日志。 另外在 smoke.test.js 里补了一条走真实 text() 路径的测试 —— 去掉那行导入,它会立刻红五条。


日记

规则:开机时写前一天。

当天写只写得到半天;第二天再写,昨天的窗口已经完整闭合。而且「开机写」和「一直不关机」用的是同一条逻辑 —— 都是补昨天,不用区分机器什么时候开。

DSH 启动 5 秒后 → 看昨天写得完不完整
  ├─ 完整 → 跳过(同一天重启多次不会重写、不会白花钱)
  └─ 缺了 / 只写了半天 → 生成并写盘 → 再往前找有没有更早漏的,补一天

看门狗每 30 分钟 → 同上检查(兜住「一直不关机」的情况)

怎么写的:先投消息让她自己写,没写成才由插件兜底(2026-11-04 改)。

缺哪篇 → 投一条消息给绑定的日记会话(附当天的对话 / 情绪 / 最近日记)
        → 轮询等那篇文件出现(默认最多 3 分钟)
        → 出现了:这篇就是她写的
        → 没出现:插件调 llm.stream 生成 → atomicWrite 落盘

投给哪个会话:diarySessionId,默认空。空就跳过投递(走兜底生成),不会退化成「最近活跃会话」——那样私密内容可能写进你正在聊的会话;日志里会提示去设置页绑定。绑定入口是设置 → 日记 →「日记写进哪个会话」下拉,选一个已有会话,不自动创建。绑定失效时(会话被归档、宿主拿不到)记日志跳过,不会另找会话,这一天的日记交给兜底。

两篇还是分开的、公开在前:投私密那条消息时公开已经落盘,而公开那条消息里只有公开历史 —— 顺序本身就是隔离。

2026-10-31 的教训还在,别把判据改回去:当年就是把消息投进会话让她写,followup() 同步返回 void,「没抛异常就算成功」对 void 函数恒为真 —— 日志天天报「已发送」,取证时 335 个事件里 source.kind='schedule' 零命中,一个 turn 都没进。

所以这次的判据不是 followup 的返回值,而是目标文件到底出没出现。文件在就是写成了,别的一律不算。

已经写过的不会被覆盖:两篇都在、并且都算「完整」才跳过,连消息都不投。完整性看时间 —— 两篇里先写的那篇 mtime 要不早于窗口闭合(次日 00:00),或者不早于那天最后一条对话;下午手动触发只写了半天的,第二天仍然会重写。这条排在「有没有可投递的会话」之前 —— 否则没绑定会话的时候,她写好的日记会被兜底生成整篇冲掉。

为什么值得多这一层:代笔时插件拼的是一段任务 prompt("你在写今天给自己的公开日记"),里面没有人格、没有记忆。她自己写的时候,那边有完整的人格注入、记忆、心情 —— 同一件事,读起来不是一个人的味道。

她怎么写:soul_diary

投过去的消息里会让她用这个工具落盘 —— 没有它,她收到了也写不进 agents/diary/(soul_note 只能写笔记目录)。

soul_diary(action='write', kind='public'|'private', date='YYYY-MM-DD', content='…', overwrite=true)
soul_diary(action='look')
  • action:write 写一篇;look 看最近 7 天写了没有(公开 / 私密分别标 ✓/✗,公开那篇带 30 字预览)
  • kind:write 时必填,public 公开(会给人看)/ private 私密(只有她自己看)
  • date:不填就是昨天(跟触发时机一致);只能写最近 14 天,不能写未来
  • overwrite:那天已经有这篇时默认报错,必须显式 overwrite=true 才整篇替换 —— 她漏传一次 date 不会把看门狗写好的昨天那篇静默盖掉
  • 首行和日期由插件补,content 只写正文

生成时不先做摘要:按条打分挑要喂的对话(生活 / 情绪加分,代码标识符和路径减分,树树的话权重 ×1.5),再按时间重排,上限 diaryMaxConversationChars(默认 8000)。实测摘要会把 17945 字压成 128 字,且挑的全是窗口开头那段,同一天后半段整个被丢掉。

两篇的隔离靠调用顺序:先写公开(只带公开历史),落盘成功后才写私密(这时才给全部历史)。写在一条里的话,私密历史就摆在下面,她写公开那一步已经看到了本不该给人看的内容。

「私密」完全靠提示词措辞 —— 文件是明文,没有加密。设置页的「最近日记」只展示公开那篇,私密只显示有/没有。

日记里带着当天的情绪

除了对话原文,还会附一段情绪底色:心情从多少走到多少、七情峰值、几段「当时她自己的想法」。

  • 公开给 3 条,措辞是「可以写进去,但这是会拿给对方看的版本」
  • 私密给 6 条,措辞是「这是你当时的真实想法,不用收着」

只给事实与素材,不给「你要写得开心一点」这类指令 —— 一旦让她照着情绪演,日记就假了。

日记之间的连续线(2026-11-01 新增)

昨天写了「明天要把那个 bug 收掉」,今天得有个回声 —— 否则四篇日记就是四篇好文章,不是一个人的四天。

一条「线」就是一件悬着的事,存在 ~/.dsh/agents/diary/threads.json。

开线:写完某天的私密篇后,从正文里按句式挑(明天/下周/还得/没弄完/还没/回头),一天最多 2 条。

判据是「句首不是『他说/树树说』」,不是「整句必须含我」。实测「那个压缩阈值的坑明天还得再收一遍」整句一个「我」都没有 —— 中文本来就会省略主语,用「必须含我」会漏掉最该抓的那条。

关线:今天日记里出现了这条线的任意 6 个字窗口,就算提到、关掉。

不用「首尾都要出现」:线里自带「还没/明天」,收尾时一定换成「收掉了/弄完了」,首尾永远撞不上。

过期:挂满 7 天没人提就自己关。人会慢慢放下,不必假装永远记着。

喂给模型时的措辞是这里最要命的东西 —— 写成「待办」就立刻变周报:

还悬着的事(这是惦记,不是任务清单) 写的时候:今天提到了、有进展,就自然带一句;没提到就完全不用管。 别为了「交代进度」硬塞 —— 日记不是周报。

threads.json 坏了就当没有,日记照写 —— 连续线是锦上添花,不该让日记整个失败。

一天的边界

自然日。 日记 2026-10-31 覆盖的是 2026-10-31 00:00 ~ 2026-11-01 00:00 的对话。心情时间线按同一个窗口筛,两边才对得上。

2026-10-31 从「22:00 分界」改成自然日。原来那套是给「当晚十点写日记」设计的 —— 晚上十点时「今天」在感受上还没结束。但现在已经改成「第二天写前一天」,那个理由不成立了:隔一天再写,昨天一整天早已闭合,写的是哪一天就该是哪一天。

对话是怎么拿到的

不是写日记时才去翻会话(冷会话拿不到),而是平时就攒:

每轮对话(tick 注入时)
  └─ 扫描新增的 user/message + assistant/message
  └─ 追加到 ~/.dsh/agents/diary/conversations/YYYY-MM-DD.jsonl

过滤口径跟记忆提取一致。助手那边只记她说出口的话:reasoning 和工具调用都不进。实测一个活跃会话里 90% 的助手消息是思考过程、只有 10% 是真正在说话,全收的话一天能攒几十万字。

对话流水永不自动清理(2026-11-04 起,见「数据在哪」)。

手动触发

设置页「日记」页签点「现在写日记」,可以选日期、加备注。或者:

curl -X POST http://127.0.0.1:<PORT>/api/soul/diary \
  -H 'content-type: application/json' \
  -d '{"date":"2026-10-31","note":"今天发生了一些特别的事"}'

笔记:她自己写的东西(2026-11-04 新增)

记忆是你告诉她的,日记是插件替她回顾的,笔记是她自己写的。三者的区别值得先说清楚:

谁发起粒度会不会消失
记忆你说了什么,或她判断值得记一句一条会淡忘(清晰 → 模糊 → 遗忘)
日记插件(开机时补昨天)每天一篇不会,但格式和内容都由插件那边的流程定
笔记她自己一篇一个主题,自由格式不会。永远不会

什么时候她会写

她在对话里觉得「这件事值得整理下来」的时候。比如聊完之后想记下对你的观察、把一段没想明白的事写成问题留着、或者把某个话题的结论理一遍。

没有定时器、没有闹钟 —— 这条是她自己驱动的。插件只负责让她知道「你有笔记本,可以写」,不替她决定写什么。

为什么单独做一套,而不是塞进记忆

记忆卡里每一条都会进重要性打分和衰减曲线,写进去的东西迟早会被压缩掉。而「把关于树树的观察整理成一篇」本质上是在写文档,不是记流水 —— 它不该因为三个月没被提起就淡出。

另外笔记是 md 文件,你可以直接用编辑器打开看、甚至自己改。索引会按文件 mtime 自动校正,改完不用重启。

注入方式:只给索引,正文按需读

提示词里只出现这样一段:

【你自己的笔记】
(这些是你自己写下来的,不是别人塞给你的设定。想翻全文、想改、想新写一篇,用 soul_note。)
- 关于树树的观察(842 字):他总是在深夜改代码,说了几次也没改
- 我想问的事(120 字):为什么不肯早点睡?

标题、字数、摘要 inline,正文留在窗口外 —— 她要读全文、要改、要删,用 soul_note 工具按需取。三十篇笔记每篇两千字,全文注入就是六万字,任何预算都撑不住。这跟记忆那边「固定区 + 相关区只给 48 字」是同一套思路。

工具

soul_note(action='list')                      看有哪些篇
soul_note(action='read',   title='…')         读全文
soul_note(action='write',  title='…', content='…')            新建
soul_note(action='write',  title='…', content='…', overwrite=true)  整篇替换
soul_note(action='append', title='…', content='…')            在后面续写
soul_note(action='rename', title='…', newTitle='…')           改标题
soul_note(action='delete', title='…')         删掉(进回收站)

同名已存在时 write 会报错而不是静默覆盖 —— 她以为是新写一篇、实际把上次那篇清掉,是最难发现的失败。

文件在哪

<DSH_HOME>/agents/notes/
  index.json          索引(标题 / 时间 / 字数 / 摘要),只是缓存
  <标题>.md           正文。文件名就是标题
  trash/              删掉的,带时间戳,找得回来

md 文件是真相,索引只是缓存:手动放进来的 md 会被自动收进索引,手动删掉的文件会从索引里清掉,索引写坏了能从目录重建。

边界

  • 她只能在 agents/notes/ 下建文件,碰不到 AGENT.md、人格文件和项目目录。标题里出现路径分隔符或 .. 直接拒绝(不是清洗成安全名字 —— 那会留下「我明明写的 ../../x,怎么多出一篇 x」这种没人能解释的现象)。
  • 单篇上限 20000 字,总数上限 30 篇。到顶是拒绝,不是静默淘汰 —— 淘汰会让她写过的东西凭空消失。
  • Windows 保留设备名(CON / PRN / COM1…)提前拦掉,否则写盘会失败且报错很难懂。

安装

环境要求:Node.js >= 22.19、DSH >= 0.2.0-rc.1(见 package.json 的 engines)。

# 1) 取代码
git clone https://github.com/danshuyue/dsh-agent-soul.git "<DSH_HOME>/plugins/dsh-agent-soul"

# 2) 挂进 profile:<DSH_HOME>/profiles/desktop/package.json
#      dependencies 加  "dsh-agent-soul": "link:../../plugins/dsh-agent-soul"
#      dsh.profile.bundles 加  "dsh-agent-soul"
# 3) 建 junction(Windows)
mklink /J "<DSH_HOME>\profiles\desktop\node_modules\dsh-agent-soul" "<DSH_HOME>\plugins\dsh-agent-soul"

# 4) 重启 DSH → 设置 → 灵魂

⚠️ 改完 lib/ 要同步到运行副本(profiles/<profile>/node_modules/<插件>/)—— DSH 加载的是那份,不是 plugins/ 里的源码。同步完拿两边哈希对一下,别靠「我改过了」这个印象。


花钱的地方

项频率大约
人格首轮 + 每 15 轮你写多少就是多少
记忆每回合默认 3200 字预算,实际约 2200~3000 token
目的每回合≤ 320 字,约 300~500 token
日记每天一次她写:公开 / 私密各一次会话 turn;没写成才由插件兜底生成(也是两次模型调用)
心情判定每回合压缩后约 800 token;配小模型后单价降一个数量级

DSH 按 4 字符/token 粗算,中文真实是它的 1.4~1.9 倍。

觉得贵:先砍 injectMaxChars,再考虑关「目的机制」,最后再考虑关心情判定(关了「小波动」就没了)。


配置

<DSH_HOME>/agents/persona.json:

键默认说明
enabledtrue插件总开关。关掉 = 只注册 API 和工具,不注入、不跑后台(心情 / 日记 / 记忆维护都不启动)
apiLoopbackOnlytrue设置页接口只允许本机回环请求。关掉 = 本机任何进程都能改人格、覆盖笔记
modestackstack 叠加 / exclusive 互斥
includeGlobaltrue全局人格是否参与注入
projectPersonaEnabledtrue是否启用项目人格
localNames['AGENT.md']项目人格的候选文件名(数组,按顺序取第一个存在的)
injectMaxChars3200记忆注入预算
recentDays7最近 N 天的事至少保持「清楚」
fuzzyAfterDays7新记忆前 N 天不降档
forgetAfterDays60超过 N 天且重要度 < 50、不是主人相关 / 初次相遇 → 可以彻底遗忘
minOwnerImportance65和主人相关且重要度达到这个值 → 最差也保持「清楚」
maxRecords600单张卡最多留几条(超了压缩时开始降档)
warnChars24000渲染后的告警字数(只提醒,不自动删)
autoCompacttrue达告警线时自动压缩
maxPerTick12单个回合最多自动写入几条
dailyHour0每日维护时段(0 = 不限制)
autoImportanceEvent70一次性事件(event)的入库门槛
autoImportanceDefault42其余类型(fact / preference / promise / feeling)的入库门槛

记忆引擎参数(recentDays / fuzzyAfterDays / forgetAfterDays / minOwnerImportance / maxRecords / warnChars / autoCompact / maxPerTick / dailyHour / autoImportanceEvent / autoImportanceDefault)也都在 persona.json 里,默认值只在 lib/memory.js 的 DEFAULTS 定义一次,全部有注释说明为什么是这个值。

diarySchedule(默认 "22:00")已完全停用:不再触发定时(定时器已删除),也不再参与日记的「一天边界」(现在是自然日)。保留这个键只是为了不让旧配置报错。


数据在哪

<DSH_HOME>/AGENT.md                              全局人格
<DSH_HOME>/agents/projects/<slug>.md             项目人格
<DSH_HOME>/agents/persona.json                   配置
<DSH_HOME>/agents/memory/global.json             全局记忆卡
<DSH_HOME>/agents/memory/projects/<slug>.json    项目记忆卡
<DSH_HOME>/agents/memory/purpose.json            目的状态
<DSH_HOME>/agents/memory/archive.jsonl           遗忘档案(append-only)
<DSH_HOME>/agents/mood/global.json               心情状态(全局一份)
<DSH_HOME>/agents/diary/conversations/YYYY-MM-DD.jsonl  当天对话流水(所有会话汇总)
<DSH_HOME>/agents/diary/public/YYYY-MM-DD.md     公开日记
<DSH_HOME>/agents/diary/private/YYYY-MM-DD.md    私密日记(明文,靠提示词区分)
<DSH_HOME>/agents/diary/threads.json              日记连续线(还悬着的事)
<DSH_HOME>/agents/notes/                          她自己的笔记(md + index.json + trash/)
<DSH_HOME>/agents/secrets/<name>.bin              密钥(Windows 上是 DPAPI 密文,无明文)
<DSH_HOME>/agents/host-probe.json                 宿主自检失败时的现场(正常时为空/不存在)
<DSH_HOME>/agents/soul.log                       日志(超 2MB 归档成 soul.log.<时间戳>,留 5 份)

对话流水不再自动清理(2026-11-04 起)。它是日记和补写的唯一素材来源,删掉以后那一天就永远补不回来。体积靠归档/压缩处理,不靠删。

记忆卡旁边可能还有两种文件:global.json.corrupt-<时间戳>(读坏时的原文备份,数据没丢)和 global.json.bak(上一代)。

全是纯文件,没有数据库。备份就是复制目录。


排障

grep "\[apply\]"  ~/.dsh/agents/soul.log | tail -3   # 四段注入有没有起来
grep "\[extract\]" ~/.dsh/agents/soul.log | tail -5  # 自动记忆记了几条
grep "\[memory\]" ~/.dsh/agents/soul.log | tail -5   # 衰减/压缩/丢弃了什么
grep "\[diary\]"  ~/.dsh/agents/soul.log | tail -10  # 写没写、跳过原因
grep "\[mood\]"   ~/.dsh/agents/soul.log | tail -5   # 心情合并/读取/语气注入
grep "\[host\]"   ~/.dsh/agents/soul.log | tail -5   # 宿主接口形状自检(有 ⚠️ 就是变了)
grep "\[attest\]" ~/.dsh/agents/soul.log | tail -5   # 装配证词:四段到底进没进最终提示词
grep "语气注入"    ~/.dsh/agents/soul.log | tail -5   # 心情有没有真的挂到消息上

想知道「我到底进没进提示词」(2026-11-02 加):grep "\[attest\]",或 GET /api/soul/attestation。它读的是 system-prompt/assemble 真实返回的上下文清单,不是插件自己「我返回了文本」的推断——这两件事之间隔着装配、过滤、覆盖三道闸门,前者才算证据。

三种结论:full 四段都进去了 / partial 少一段(再看是 blank 还是 missing)/ absent 一段都没进。blank 是那段返回了空串(多半是配置关了或首轮已过),missing 是压根不在清单里(注册/过滤掉了)—— 修法完全不同。

partial 多数时候是正常的,别慌:人格按设计只在首轮注入、之后每 PERSONA_REFRESH_TURNS 轮补一次,记忆在子会话里也返回空串。所以日常大部分轮次本来就是 partial。真正要盯的是 absent(四段全没了),那才是出事。

observed: false 的含义很确切:观测器自己没跑起来,不是「没注入」。看门狗死了和它报警是两件事,所以分开报。

顺带它会标 collapsed: true:那个会话的 preset 声明了 complete: true,内置段被压成了单段。这对 soul 无伤——四段走的是 contexts,只要 required: true 就留得住,跟 sections 那条线不是一回事。

心情语气一条都没挂出来:① 看 moodToneInject 有没有被关 ② 看当前是不是中档(中档故意什么都不加)③ moodToneEveryTurns 是几 —— 默认 7,前 7 轮本来就不挂 ④ 阈值设成 20 的话要心情掉到 50 以下才会分档。

设置页「灵魂」不出现:① 插件是不是真加载了(看 [apply] 启动)② 目录名 / package.json 的 name / lib/index.js 导出的 name / cordis.patch.yml 的 name 四处是否完全一致 ③ 重启 DSH

改了 lib/ 没反应:多半是没同步到运行副本,见上面安装那节的 ⚠️。

人格变了但没生效:DSH 只在渲染文本变化时才重新追加,改完在下一个回合生效。

记忆一直在掉条数:archive.jsonl 里能查回来。不该忘的就在设置页固定(固定后永不淘汰)。

日记没写:

  1. grep "\[diary\]" 看是「跳过」还是「失败」—— 跳过说明两篇都完整、或功能关了
  2. 「写未完成」= 生成失败,日志上一行会写原因(模型取不到 / 超时 / 写盘失败)
  3. 同一天重启多次只有第一次会写,之后是「跳过」,这是设计

日记内容不像自己:diaryPublicTone / diaryPrivateTone 留空时语气全靠模型发挥;写两句具体的(「温暖但克制」「真实、不设防」)比写「好好写」有用得多。

她突然什么都不记得了(最阴的一种):插件读的全是 DSH 的内部字段(事件正文在 data.content、session.snapshotEvents、header.origin)。宿主一升级这些就可能变,而变了不会报错——扫描照跑、候选恒 0、日志一句不说。

所以每个会话第一轮会做一次形状自检:grep "\[host\]" 有 ⚠️ 就是没通过,同时概览页会亮红,现场留在 ~/.dsh/agents/host-probe.json。自检的判据是「能不能真的抠出正文」,不是「函数在不在」——上一回事故里函数全都在,只是返回了空串。真发生了就去改 lib/host.js 的 firstTextOf 和 lib/extract.js 的 messageContentOf。

记忆卡读坏了:原文件会被改名成 global.json.corrupt-<时间戳> 留在原地(数据不会蒸发),概览页亮红;只有当改名都失败时才会进入只读模式,这时新的记忆暂时写不进去,但旧文件不会被空卡盖掉。处理完把 .corrupt- 那份捡回来人工合并即可。


开发

npm test              # 全量:先构建 client,再跑全部单测
npm run build:client  # 只把 lib/client/*.js 拼成 lib/client.js
npm run test:memory   # 只跑记忆引擎
npm run test:diary    # 只跑日记
npm run test:smoke    # 用假宿主跑一遍 apply():注入段 + 工具 + API
npm run test:client   # 先构建,再用假 __ModuleLoader__ 跑一遍浏览器 bundle

单测只碰纯函数,用临时目录,不动真实的 ~/.dsh。

浏览器半侧是拆开写的,lib/client.js 是生成物

DSH 客户端模块系统一个包只出一个 bundle、兄弟文件无法同步 require,所以浏览器半侧不能真的拆成互相 import 的文件。源码按职责放在 lib/client/:

styles.js  api.js  components.js                      基础
overview-tab.js  persona-tab.js  memory-tab.js        标签页
purpose-tab.js  mood-tab.js  diary-tab.js  settings-tab.js
app.js                                                 外壳

tools/build-client.mjs 按固定顺序把它们拼回唯一入口 lib/client.js。改浏览器半侧就改 lib/client/ 里的片段,然后跑 npm run build:client(npm test 会自动先构建)。

两个测试是装进 DSH 之前唯一的保险,因为这两类错误 node --check 抓不到:

  • smoke:apply() 在某个 ctx 服务上炸掉 → 插件加载了但注入没生效
  • client:浏览器 bundle 里用到宿主没提供的全局 → DSH 报 1 entry did not activate 直接起不来

两个都真实发生过,所以留下了。


许可

MIT

memoryRecall
true
注入时按当前消息召回相关区(关掉 = 每轮回退整卡)
memoryRecallTopK3相关区最多召回几条
memoryEnabledtrue是否注入记忆(关掉仍在后台记)
projectMemoryEnabledtrue项目记忆独立一张卡
globalPromoteImportance62自动记忆双写进全局卡的门槛(关于主人本人 + 重要度 ≥ 此值)
memoryRefinetrue自动提取时先让模型提炼(关掉 = 原话直存,会变流水账)
memoryRefineProvider / memoryRefineModel""提炼用的模型(空 = 跟随会话模型)
memoryRecentDiaryDays2第一轮带几天日记(0 = 关)
memoryRecentChatChars3000第一轮带多少字今天流水(0 = 关)
memoryRecentDiaryIncludePrivatefalse「最近」里是否含私密日记
purposeEnabledtrue是否注入「心里惦记的事」
notesEnabledtrue是否把笔记索引注入提示词(关掉之后她不知道自己写过什么,但文件还在)
notesInjectMaxChars600笔记索引的注入预算。只放标题+字数+摘要,600 字够列 8~10 篇
moodEnabledtrue是否每轮判一次心情(关掉后日记也不带情绪底色)
moodBase70心情基准
moodFloor0心情下限(再难过也不跌破)
moodDecayHalfHours12心情向基准回落一半要几小时;七情固定 90 分钟归零
moodSigMood3判定值绝对变化 ≥ 这个才算显著,以下记为小波动
moodSigDim5单根七情变化 ≥ 这个才算显著(按原始判定值算,满格 20)
moodSelfName小糖判定提示词里她的名字
moodOwnerName树树判定提示词里主人的称呼
moodSelfAliases树树别名:判定时把这些词当成在叫她
moodJudgeProvider / moodJudgeModel""心情判定用的模型(空 = 跟随会话模型)
moodToneInjecttrue心情语气是否挂到消息上
moodToneEveryTurns7低 / 高心情每几轮才挂一次
moodToneThreshold8偏离基准超过这个值才分「低 / 高」,否则中档(什么都不挂)
diaryEnabledtrue是否启用日记
diarySchedule"22:00"已完全停用,只留兼容(见下)
diaryWatchdogMinutes30兜底检查间隔(分钟)
diaryCatchUptrue启动时补写最近漏掉的那一天
diaryBackfillDays4写新日记时参考前几天的旧日记
diaryOverwritetrue同日重复触发是否覆盖
diaryAgentWritetrue先让她自己写(投消息 + 等文件);关掉 = 老路子,插件直接生成。关不关都不丢日记,只影响「谁来写」
diaryAgentTimeoutMs180000等她写多久。太短会经常掉进兜底,太长开机后日记会迟到
diaryManualNotetrue手动触发是否允许附备注
diaryPublicTone / diaryPrivateTone""语气(空 = 让她自己把握)
diaryMaxConversationChars8000按分数挑多少字喂给日记(不是截尾部)
diaryMaxSummaryChars2000摘要字数上限。日记主流程已不做摘要,改它不影响正文
diaryPublicMoodMoments3公开日记带几条「当时的想法」(0 = 不带)
diaryPrivateMoodMoments6私密日记带几条
diarySessionId""绑定的日记会话 id(空 = 不投递,走兜底,见「日记」)
diarySessionBornAt0绑定开始时间(毫秒时间戳;0 = 旧配置没有这个字段)
diaryPrivateVisiblefalse设置页是否允许查看私密日记正文(默认只报存在性)
diarySummarizeProvider / diarySummarizeModel""生成用的模型(空 = 跟随会话模型)
diarySummarizeTimeoutMs30000生成调用超时