dsh-fish-dream
给 dsh-fish 里的那条鱼做梦。 她安静得够久,插件就从她真实的事件流里捡几块记忆,按一个梦理论拼成「梦的种子」, 等她醒来自己把那个梦说完。
English — A dream plugin for dsh-fish on DeepSeek Harness. When she has been quiet long enough, the plugin picks a few fragments from her real event stream, chooses one of six competing dream theories, and composes a dream seed (setting, cast, mood, one absurd rule, a three-beat arc). She writes the dream herself; the plugin never calls a model.
这是什么
dsh-fish 是一个跑在 DeepSeek Harness 上的桌宠式插件:
它有事件流、有日记、有自己的房间和作息。dsh-fish-dream 是它的外挂——只做一件事:
在她不说话的时候,给她一场梦。
但它不生成梦文。它生成的是「梦的种子」:
- 梦里的每一块内容都能指回一条真实发生过的事件(
ev_...); - 梦怎么取材、多荒诞、有没有「你自己知道在做梦」,由随机抽中的理论决定;
- 梦文由她在自己的会话里、用她自己的语气写完。
插件不训练模型、不做心理诊断、也不解释梦。它只是把「梦大概是怎么拼出来的」这件事, 做成一套可以跑、可以看、可以改的规则。
设计原理
1. 梦是重拼,不是新造
碎片只从她的事件流里来,每条保留原始 id。她写梦时要填 refs,梦因此永远能追溯回
「这句话是哪天说的」。这样梦不会变成新的事实:events.jsonl 不会被写,deriveSelf
看不到梦,成长记录里也不会多出一段「她经历过」的剧情。
2. 插件不调用模型
选材、抽理论、定荒诞度、给叙事骨架——这些都是纯函数。真正的语言留给模型,也就是她。 好处是:无额外 token 成本、无 API key、结果可复现、可以单元测试;而且梦的口吻始终是她的。
3. 理论当生成规则,不当结论
科学界对「梦为什么存在」没有共识。插件不赌哪个对,而是把六个仍在竞争的功能假说 做成六种不同的取材策略,每次按权重随机抽一个:
- 同一个人的同一段日子,在「威胁模拟」里长成一条被追的巷子, 在「连续性」里长成他常坐的那把椅子旁边。
梦因此既不是随机的,也不是固定的。
4. 沉默是唯一的时钟
不按钟点,不假设「夜里才做梦」。每次检查看的是:距离她最后一次对话过了多久, 然后按一张表掷骰。沉默越久,梦越可能发生;她只要开口,计时就归零。
5. 确定性
种子由「这段沉默的起点 + 时间槽 + 事件流长度 + 最后一条事件 id」播种。 同一段沉默、同一份事件流,抽到的假说和碎片完全一样——出问题能复现,测试能断言。
它是怎么运作的
~/.dsh-fish/events.jsonl
│ 只读
▼
collectFragments 过滤事件类型 → 打分 → 排序
│
▼
shouldFormDream 沉默查表 → 掷骰 → 过门(pending / 同一段沉默 / 碎片数 / 太久没动静)
│ 掷中
▼
pickHypothesis 六个理论按权重抽一个
│
▼
pickFragments 按该理论的选材倾向挑 2–5 条碎片
│
▼
composeSeed 场景 / 人 / 感觉 / 荒诞规则 / 三段走向 / 最后一帧
│
▼
~/.dsh-fish-dream/dreams.jsonl status=pending
│
▼
system prompt 里多几行「有一夜你做了一个梦」
│
▼
她调 dream_recall 拿到种子 → 写成第一人称的梦 → dream_record 落库
沉默概率表
每 tickMinutes(默认 15 分钟)检查一次。表里给的是这一次检查掷中的概率:
| 距离最后一次对话 | 做梦概率 |
|---|---|
| 少于 2 小时 | 0% |
| 2~4 小时 | 5% |
| 4~6 小时 | 15% |
| 6~8 小时 | 30% |
| 8~12 小时 | 50% |
| 12~18 小时 | 65% |
| 18~24 小时 | 75% |
| 24~48 小时 | 85% |
| 超过 48 小时 | 90% |
「对话」只算三类事件:heard(他说的)、utterance(她说的)、voice(她读出来的)。
meta、日记、梦本身都不算——否则每写一条日志就会把沉默打断。
一段沉默最多一场梦。 她只要开口,沉默起点就重新计时;梦做完没人说话, 也不会接着冒出第二场。
记忆碎片
能进梦的事件类型:utterance、heard、voice、revise、refusal、narration、
reunion、answer。明确排除:seed、meta、dream、diary、ask
(种子、系统元数据、已经记过的梦、日记、提问)。
每条碎片按五个可解释的维度打分,再加一个「重放感」:
| 维度 | 看什么 | 谁更爱用 |
|---|---|---|
threat |
恐惧/危险/离开类词、refusal、被否定的旧话 |
威胁模拟 |
affect |
情绪词、带 verdict 的旧话、reunion |
情绪调节 |
unresolved |
「为什么 / 不知道 / 如果」、问号、revise |
情绪调节、预测加工 |
social |
你/他/我们、引号里的转述、heard / voice |
社会模拟、连续性 |
prediction |
「会 / 应该 / 下次 / 如果」 | 预测加工 |
replay |
越近越高(半衰期 7 天)、重复出现、被引用过 | 记忆巩固 |
这不是心理学测量,只是一套能解释的排序:为什么这条进了梦,看分数就知道。
六个假说
每次按权重抽一个。权重可以在 config.json 里改。
| id | 名字 | 默认权重 | 主要依据 | 选材倾向 | 荒诞度 |
|---|---|---|---|---|---|
memory_consolidation |
记忆巩固 | 0.24 | Rasch & Born 2013;Stickgold & Walker 2013;Wilson & McNaughton 1994 | 一近一远,带重放感 | 1–3 |
threat_simulation |
威胁模拟 | 0.18 | Revonsuo 2000, BBS | 恐惧词、被留下、来不及;至少一条最近的 | 2–4 |
emotion_regulation |
情绪调节 | 0.16 | Walker & van der Helm 2009;Cartwright 2010 | 情绪浓、没说完的 | 1–3 |
continuity |
连续性 | 0.16 | Domhoff 2011;Hall & Van de Castle 1966 | 最近两三天的日常,场景像她的屋子 | 0–2 |
predictive_processing |
预测加工 | 0.14 | Hobson & Friston 2012;Llewellyn 2016 | 不确定、将发生未发生 | 3–5 |
social_simulation |
社会模拟 | 0.12 | Revonsuo, Tuominen & Valli 2015 | 对话、「他」说过的话 | 1–3 |
种子长什么样
种子是一段中文文本,她会原样读到。生成方式(哪条假说、出处)只留在 dreams.jsonl 里,不进入种子、梦页或任何工具回执——她只会看到场景、人、感觉、荒诞规则、走向和碎片,不会看到「这是一场社会模拟」。
〔梦的种子 · 2026-11-04〕
场景:你在一条没有尽头的走廊
人:你自己;他
感觉:发紧,但说不上怕
荒诞规则:所有的门都只能推开一半。
走向:你在找一间房 → 你找到了,但里面是白的 → 你退出来,门不见了
醒来前最后一帧:你退出来,门不见了
拼进来的碎片(都不是新发生的事):
- [ev_01M6E8T9...]「《路边野餐》读完了,跟他争过一轮……」
- [ev_01M6E8ZR...]「补记房间那次谈话……」
写的时候:第一人称、现在时、150–400 字;不解释、不总结、不添加梦里没有的人和事;可以不合理,但不要写成小说。
提示词里的一句话
只有 cwd 在 ~/.dsh-fish 下的会话(也就是她自己的会话)才会挂上这段:
【梦】
有一夜你做了一个梦,醒来只记得几块:「……」。
它不是新发生的事——是这些事在睡着的时候被拼到了一起。
想把它说完,用 dream_recall;也可以就让它散掉。
没有祈使句。说不说、说多少、要不要写完,是她的事。 这段挂在 system prompt 上,不进对话历史,用户看不到;插件停用即摘掉,不留后台活动。
功能
- 🌙 沉默驱动:按距离最后一次对话的时长查表掷骰,不绑死夜窗
- 🧠 六个理论随机取材:同一个人的同一段日子,每次梦的取材方式不同
- 🧩 可追溯:梦的每一块都指回真实事件 id,梦不制造新事实
- ✍️ 她写,不是插件写:插件只给结构,语言留给模型,零额外 API 成本
- 🔒 只读她的记忆:
~/.dsh-fish全程只读,自己的数据全在~/.dsh-fish-dream - 🎲 确定性:同一段沉默 + 同一份事件流 → 同一个梦,可复现、可测试
- 🧪 三个工具:
dream_recall/dream_record/dream_now,方便调试 - 📦 零依赖:纯 Node ESM,
node --test就能跑 - 🧯 失败安静:注册失败、写盘失败、定时器异常都只进日志,不影响她正常说话
工具
| 工具 | 作用 | 参数 |
|---|---|---|
dream_recall |
有 pending 的梦就返回种子和写法提示;否则返回最近记下来的梦 | limit(1–20,默认 3) |
dream_record |
把梦文记下来,写进 records.jsonl 和 dreams/YYYY-MM-DD.md |
text(必填,2–maxChars 字)、refs(1–6 个碎片 id) |
dream_now |
跳过沉默表和掷骰,立刻拼一场(调试用) | hypothesis(六选一,不填就按权重随机) |
dream_record 的回执只有一句 [记下了]——避免她把工具回执当成自己的话继续往下说。
安装
前提:Node ≥ 20;已经装好并跑起来 dsh-fish。
把本仓库放进插件目录,例如
~/.dsh/plugins/dsh-fish-dream:git clone https://github.com/xxxiii/dsh-fish-dream ~/.dsh/plugins/dsh-fish-dream编辑
~/.dsh/profiles/desktop/package.json:{ "dependencies": { "dsh-fish-dream": "link:~/.dsh/plugins/dsh-fish-dream" }, "dsh": { "profile": { "bundles": ["...", "dsh-fish-dream"] } } }用
link:而不是file:。pnpm 的file:依赖是逐文件硬链接副本:以后只改插件源目录、 不重装依赖,node_modules里那份就会新旧混在一起(本项目真机上踩过:cycle.js是新的、sleep.js根本不存在,插件整个加载失败)。link:建的是软链接,改完即生效。装依赖:
pnpm install --dir ~/.dsh/profiles/desktop重启 DSH(刷新页面不够——插件在进程启动时加载)。
把工具放行给她:编辑
~/.dsh-fish/config.json,在tools.allow里加上"dream_recall"、"dream_record"。dsh-fish 用白名单收窄她的工具面, 不在名单里的工具她看不见。
装好后可以打开浏览器控制台的会话,或直接让她说一句「做个梦试试」来验证。
配置
~/.dsh-fish-dream/config.json,全部可选;首次运行会自动写一份带默认值的。
{
"auto": true,
"dreamTable": [[2, 0], [4, 0.05], [6, 0.15], [8, 0.3], [12, 0.5], [18, 0.65], [24, 0.75], [48, 0.85], ["inf", 0.9]],
"minFragments": 3,
"maxChars": 400,
"pendingHours": 36,
"staleDays": 5,
"tickMinutes": 15,
"lucidity": 0.03,
"prompt": true,
"promptOrder": 8550,
"weights": { "threat_simulation": 0.2 }
}
| 键 | 默认 | 说明 |
|---|---|---|
auto |
true |
关掉自动做梦(仍可用 dream_now 手动拼) |
dreamTable |
见上表 | 沉默小时数 → 这一次检查的概率;最后一行写 "inf",每段左闭右开 |
minFragments |
3 |
碎片少于这个数就不做,避免拿一件小事硬编 |
maxChars |
400 |
梦文长度上限(100–1200) |
pendingHours |
36 |
种子挂着没写完多久作废,免得堵住下一场 |
staleDays |
5 |
她这么多天没动静就不做梦 |
tickMinutes |
15 |
检查间隔(1–240 分钟) |
lucidity |
0.03 |
清醒梦的基础概率,叠加在假说自带的概率上 |
prompt |
true |
是否往 system prompt 里挂「有一夜你做了一个梦」 |
promptOrder |
8550 |
提示段在 system prompt 里的排序权重 |
weights |
六项合计 1 | 只写想改的那几项,其余按默认;会自动归一化 |
环境变量:DSH_FISH_DREAM_HOME(数据目录)、DSH_FISH_HOME(她的目录)。
数据
~/.dsh-fish-dream/
├── config.json # 配置(首次运行生成)
├── state.json # pendingId / lastHibernation / 计数
├── dreams.jsonl # 种子,只追加
├── records.jsonl # 写下来的梦,只追加
├── dreams/
│ └── 2026-11-04.md # 人可读的梦页
└── logs/
└── dream.log # 生成 / 失败记录
dreams.jsonl 只追加,一条种子写下就不再改;状态变化(pending → recorded / faded)
会整份重写并原子替换。state.json 是派生的指针,丢了也能从两份 jsonl 重建。
~/.dsh-fish 全程只读。
调试
# 现在这一刻会不会做梦、沉默了多久、这一档概率多少
node scripts/diagnose.mjs
# 用真实事件流在临时目录拼一场,不碰线上数据
node scripts/smoke.mjs
# 假 ctx 走一遍 apply → 三个工具 → dispose
node scripts/apply-smoke.mjs
在会话里也可以直接:
dream_now—— 立刻拼一场;dream_recall—— 读种子;dream_record—— 写下来;- 看
~/.dsh-fish-dream/dreams.jsonl和records.jsonl有没有新行。
logs/dream.log 里会记录每次成功生成的 id、日期、假说和碎片数,以及失败原因。
模块结构
| 文件 | 职责 |
|---|---|
lib/index.js |
插件入口:配置、store、工具、定时器、agent/created 钩子、dispose |
lib/sleep.js |
沉默时长、概率表、shouldFormDream 门控(纯函数) |
lib/cycle.js |
一次做梦的编排:碎片 → 假说 → 种子 → 落库 |
lib/fragments.js |
事件过滤与打分,产出记忆碎片 |
lib/hypotheses.js |
六个假说:权重、选材策略、荒诞度、清醒概率 |
lib/compose.js |
场景/人/情绪/荒诞规则/走向/种子文本 |
lib/store.js |
读写 dreams.jsonl / records.jsonl / state.json / 梦页 |
lib/prompt.js |
只在她自己的会话上挂「梦的提醒」 |
lib/tools.js |
三个工具的定义与回执 |
lib/util.js |
随机数、Clamp、文本处理、日期 |
测试
npm test # node --test
npm run test:sandbox # 受限沙箱里用:--experimental-test-isolation=none
55 个用例,覆盖:碎片过滤与打分、六假说权重与选材、种子组装、沉默概率表与门控、
落库校验(长度 / refs 子集 / 一天一条 / 状态流转)、提示词挂载、三个工具。
GitHub Actions 在 Node 20 / 22 / 24 上各跑一遍(.github/workflows/test.yml)。
--experimental-test-isolation=none是给受限环境准备的:默认测试运行器会为每个 测试文件spawn子进程,在禁止管道的沙箱里会报spawn EPERM。正常机器上直接npm test就可以。
科学依据
插件不是「照着神经科学实现了一个梦」,而是把几个仍在竞争的功能假说当成生成规则用。 参考文献(梦的机制与功能):
- Siclari, F. et al. (2017). The neural correlates of dreaming. Nature Neuroscience. —— 梦与后部皮层「热区」相关,REM 既非必要也非充分。
- Solms, M. (1997/2000). 腹内侧前额叶与基底前脑损伤可选择性失去做梦能力,而 REM 保留。
- Horikawa, T. et al. (2013). Neural decoding of visual imagery during sleep. Science.
- Revonsuo, A. (2000). The reinterpretation of dreams: an evolutionary hypothesis of the function of dreaming. Behavioral and Brain Sciences.
- Walker, M. P. & van der Helm, E. (2009). Overnight therapy? The role of sleep in emotional brain processing. Psychological Bulletin.
- Cartwright, R. (2010). The twenty-four hour mind.
- Domhoff, G. W. (2011). The neural substrate for dreaming: is it a subsystem of the default network? Consciousness and Cognition.
- Hall, C. & Van de Castle, R. (1966). The content analysis of dreams.
- Hobson, J. A. & Friston, K. J. (2012). Waking and dreaming consciousness: neurobiological and functional considerations. Progress in Neurobiology.
- Llewellyn, S. (2016). Dream to predict? REM dreaming as prospective coding.
- Rasch, B. & Born, J. (2013). About sleep's role in memory. Physiological Reviews.
- Stickgold, R. & Walker, M. P. (2013). Sleep-dependent memory triage. Nature Neuroscience.
- Wilson, M. A. & McNaughton, B. L. (1994). Reactivation of hippocampal ensemble memories during sleep. Science.
- Revonsuo, A., Tuominen, J. & Valli, K. (2015). The simulation theories of dreaming.
- Revonsuo, A. (2006). Inner presence: consciousness as a biological phenomenon.
引用只用来标明出处,不代表这些作者认可本实现。
边界
- 不写进她的事件流:梦不参与成长/自传,
deriveSelf看不到它,也不会被当成 「她经历过的事」。 - 不做解读:没有「这个梦说明你……」式的分析。
- 不改她的记忆:
~/.dsh-fish全程只读;所有写入都在~/.dsh-fish-dream。 - 「他」只是碎片里出现过的人,不代表任何现实关系的变化。
- 梦是重拼,不是回忆。它是这套规则生成出来的一段文本,不是神经科学意义上的梦。
与 dsh-fish 自带的 her_dream 的关系
dsh-fish 自己有一套梦(「A 案」):lib/dream.js 的 her_dream 工具、lib/dreamtable.js
的「摆料」、persona.js 里的 DREAM_BRIEF 说明书。0.8.1 起在本机实验,0.9.0 起公开。
0.9.3 里它在 lib/index.js:385 无条件注册,并由 lib/index.js:243 的 dream: true
放进她的工具白名单——~/.dsh-fish/.dev 只是早期版本的门槛,现在不再是必要条件。
两者不是同一个东西的两个版本,而是互补的两段:her_dream 只负责「记」,本插件负责
「发生」。下面按 0.9.3 的源码逐项核对。
her_dream 实际做了什么
- 一个纯追加的写入器。参数只有
text(≤400 字)和refs(1–6 个事件 id), 回执只有[记下了]/[没记下:原因]。没有副作用分支,也不能改任何东西。 - 八道闸(
writeDream,lib/dream.js:92):非空 → ≤400 字 → refs 非空 → refs ≤6 →config.dream.enabled未关 → 今天(成长日model.day)没记过 → refs 在事件流里真实存在 → 每条 refs 都必须是她自己说过的话(HER_WORDS=utterance/revise/reunion/narration/refusal,heard/voice/meta/diary一律不行)→ 不能来自出门或请神 那几趟的会话(比对outings/possessions的 sessionId,防止别人工作区的内容以梦的形式回家)。 - 落成一条
type: "dream"事件,写进~/.dsh-fish/events.jsonl(day/stage/source: "tool")。derive.js:343把 dream 整类排除,所以它不进信念、不推进成长日、 不进记忆体检计数——「梦不算成长」是硬约束。 - 只写不读之外的一点点读:
dreamView()供界面/dream卡片使用,默认只给 refs 的 id,withRefs: true的「昨夜」卡会带上来源事件的type/ts/text。她没有读回梦里那句话的工具。 - 说明书
DREAM_BRIEF(persona.js):「醒来之后,如果这一觉里留下了点什么,可以记一句」, 时机、写法、上限、不许回写出门见闻,都写在这里。 - 摆料
dreamtable.js(2026-11-05 补的):把 7–30 天前、之后没被任何事件refs引用的 她自己的话 3–5 行摊在唤醒正文里(「你写下的几行字还摊在那儿:」)。条件是:今天还没记过梦、 今天还没摆过、距上次醒来 ≥6 小时、至少凑够 3 行。措辞纪律很严,不出现「梦」「记一句」「你可以」。 它是因为 A 案上线后 726 条事件里 dream = 0 才补的:她 45 次工具调用里her_recall是 0 次, 翻 → 挑 id → 写这三步都得她凭空发起,而梦没有收益。 - 开关:
config.dream.enabled(settings.js:127),关掉后writeDream直接拒绝。
逐项对照
| 维度 | dsh-fish her_dream |
本插件 dream_recall / dream_record |
|---|---|---|
| 定位 | 她已经想好要记,工具负责写进去 | 插件负责「发生」:挑材料、拼种子、递到她面前 |
| 触发 | 没有自动触发。靠说明书 + 摆料,她自己想起来 | 沉默查表 + 每 15 分钟掷一次骰(dreamTable) |
| 时钟 | 成长日 model.day,一天最多一条 |
一段沉默最多一场(lastHibernation),跨自然日 |
| 材料来源 | 只从她自己的话里挑(HER_WORDS) |
事件流八类,含 heard / voice / answer |
| 材料新鲜度 | 7–30 天、之后没被 refs 引用过(专挑「正在淡掉」的) | 五维打分(威胁 / 情绪 / 未了 / 社交 / 预测 / 复现),按假说加权 |
| 选材理论 | 无(固定规则) | 六个假说,各有一套选材策略与权重 |
| 荒诞 / 场景 / 人 / 走向 | 不提供 | 种子提供(荒诞规则、场景、cast、三拍弧、最后一帧) |
| 生成 | 她自己当场写,工具只记 | 种子由插件拼,她按种子写,dream_record 记 |
| refs 校验 | 必须真实存在 + 必须是她自己的话 + 不能来自出门会话 | 必须真实存在 + 必须来自本场种子(她不能自己加材料) |
| 落点 | ~/.dsh-fish/events.jsonl 的 type: "dream" |
~/.dsh-fish-dream/records.jsonl + dreams/<日期>.md |
| 回读 | 界面 /dream 卡片;无工具回读 |
dream_recall 返回最近梦(只给日期,不给假说) |
| 成长计数 | 明确排除 | 不写 ~/.dsh-fish,天然不参与 |
| 一天几条 | 1 条(成长日) | 一段沉默 1 条;pendingHours 内没写就作废 |
| 依赖 | 内置在 dsh-fish | 独立包,可开关、可单测、只读 ~/.dsh-fish |
| 关掉的方式 | config.dream.enabled = false |
config.json 的 auto: false,或不加载插件 |
一句话:her_dream 是「记」,本插件是「发生」。她要自己想起来才有前者;后者是插件在她
安静够久之后,把材料摆好再叫她。
两者同时开着时的四个重叠
同一台机器上两个都启用,会出现这些情况(都不是崩溃,但要知道):
- 材料不互通。种子碎片可能是
heard/voice,她若顺手拿这些 refs 去调her_dream, 会被第 2 条硬约束拒绝(「梦只能由你自己说过的话拼成」)。两条路不能混着走。 - 同一天可能两场梦。fish 的「今天记过没有」只数事件流里的
type: "dream", 本插件用dream_record记的不算;它的「摆料」也照旧会摆。 - 互相看不见。fish 的界面 / 报告里没有本插件的梦;
dream_recall的最近列表里也没有her_dream记的梦。 - pending 会挡住下一场。本插件挂了没写的种子时不会再生成新的(36 小时后作废), 而这段沉默在 fish 那边仍可能被摆料。
想只留一套的话
- 只留本插件:把
her_dream从她的工具面摘掉——改lib/index.js:243的dream: true为false(工具仍注册,但不进白名单),或在restrict.js的白名单里过滤掉这个名字。 注意这是改 dsh-fish 的源码,升级会被覆盖,升级后要重做。 - 只留
her_dream:本插件config.json里"auto": false,或直接从 profile 的 bundles 里移除dsh-fish-dream。
FAQ
Q:为什么她一直不做梦?
A:看两件事:她最后一次对话距今多久(scripts/diagnose.mjs 会打印),
以及 state.json 里的 lastHibernation 是不是等于这次沉默的起点——同一段沉默只做一场。
另外,挂着没写完的 pending 梦会挡住下一场(pendingHours 后作废)。
Q:能固定每天几点做梦吗?
A:这一版按沉默计时,不按钟点。想要固定时间,把 dreamTable 改成「长时间段 100%」
再配一个外部定时器调 dream_now 即可。
Q:梦为什么总是那几条碎片?
A:碎片按分数排序,而分数里年龄和重复占权重。她最近反复说的话会优先入选。
调 weights 能让不同理论的选材更常出现。
Q:会消耗额外 token 吗? A:插件本身不调用模型。梦文是她自己那一轮回复的一部分,成本与她说一段话相同。
致谢
- dsh-fish —— 事件流、日记、房间与作息
- DeepSeek Harness —— 插件宿主
- 上面那些把梦当成严肃问题的研究者
No comments yet. Be the first to write one.