ta 不是人。 屏幕那边的每一句,都是算出来的 —— 记得再清楚,也只是一段会断的记录。别投入真感情。
一个住在微信里的、话不多、记得住事、有作息、会兑现承诺的人。只给你一个人用。
仓库:https://github.com/Chenxi-Only-Y/Somnia
| 名字 | 指什么 | 用在哪 |
|---|---|---|
| Somnia·眠 | 项目名 | 文件夹名、窗口标题、给人看的名字 |
| Somnia / somnia | 代码标识 | 包名、入口、环境变量前缀(技术标识,不能有中文) |
| 角色名 | 名字 | 默认没有名字,面板里显示为 ta;要名字就在「设置」里填 |
Somnia 取自拉丁语 somnium(睡着),中文取一个字:眠 —— "睡着的时候也在场"。
对应设计里那条:她不说话的时候,并不等于她不存在。
Somnia·眠/
├── somnia/
│ ├── app.py 主循环:收 → 判 → 想 → 发
│ ├── brain.py 回不回 / 多久回 / 回几条(纯规则,不碰模型)
│ ├── gateway.py 去重 + 连发合并
│ ├── memory.py 分层记忆 + 事实抽取 + 到期
│ ├── archive.py 永久档案(三天前的原文搬进文件)
│ ├── aging.py 归档滚动:细节 → 印象 → 摘要
│ ├── persona.py 人设加载 + prompt 拼装 + 时间/作息
│ ├── composer.py 调模型 + 分句 + 复读检测
│ ├── scheduler.py 主动性:沉默 / 漂移 / 承诺
│ ├── promises.py 从她的话里认承诺 + 算兑现时间
│ ├── stickers.py 表情/照片库 + 时间归属 + 图文匹配
│ ├── imagegen.py 本地生图(现拍现生成)
│ ├── config.py 配置(env > settings.json > toml > 默认)
│ ├── settings.py 面板值
│ └── channel/
│ ├── base.py 通道契约
│ ├── cli.py 终端
│ ├── claw.py OpenClaw Weixin(官方)
│ └── wechatauto.py 微信 4.x:库读 + 窗口写
├── tests/ 359 项(python tests/run_all.py)
├── stickers/ 随包 91 张表情包 + notes.json(备注)
├── photos/ 你自己的照片(可选)
└── data/ 运行时(db / settings / 日志 / 备注)
两个 md,改完直接生效(按 mtime 缓存)。以 > 开头的行是写给你看的注释,不会发给模型。
| 文件 | 放什么 |
|---|---|
SOUL.md |
硬规则、时间、边界、不可妥协 —— 慎改 |
persona.md |
年龄、职业、说话习惯、示例 —— 随便改 |
SOUL.md 的硬规则、时间、记忆,
没有身份行。想让 persona.md 里写的字影响她怎么说话,要打开这个开关:
[identity]
prompt_persona = true面板「设置」里有个 「人设参与对话」 勾选框,勾上就等于开了这个开关。 关着的时候,面板「ta 是谁」里写的只是存档 —— 这样设计是为了让 prompt 只装"规则", 性格留给控制台,改哪看得见哪。
框架是十三块(缺哪块,她就缺哪块的表现)。面板「ta 是谁」页按块编辑, 空着的块不会写进文件、也不会发给模型。 身份 · 关系 · 对方 · 说话方式 · 状态 · 黏人与边界 · 不做什么 · 示例 · 习惯 · 情绪 · 亲密度 · 忌讳 · 未知
新装的时候 persona/ 里只有 SOUL.md 和 persona.example.md。
想用模板就复制一份改名成 persona.md。
时间感不是"告诉她现在几点"就完了。她有作息表:几点在睡、几点在忙、几点在吃饭。
- 夜里 2–7 点是静默时段,默认不回;被吵醒第一条 10%,每回一次 +20%,上限 90%
- prompt 里会带一句"现在是 X 点,你大概在 Y",而且要求内容配得上时间(早上别提月亮)
- 隔了一夜就算新的一天;上一轮停在"晚安",这一轮要像刚睡醒
作息表在 persona.md 里,不写就用默认那套。
按时间分四层,越久越薄:
| 层 | 时间 | 留什么 | 在哪 |
|---|---|---|---|
| 细节 | 0–3 天 | 原文,一字不改 | 数据库,直接进 prompt |
| 印象 | 3–7 天 | 每天两三句"那天…",没有原话 | 数据库 |
| 模糊 | 7–30 天 | 只剩更早的印象,读不到细节 | 数据库 |
| 档案 | 30 天以上 | 完整原文 + 段摘要 | memory/archive/*.jsonl |
三天以前的原文不会消失,只是搬进了文件:按段存(间隔超过半小时算另一段), 每段带一句那天的摘要。平时不进 prompt、不占 token。
- 想起来:你提到旧事("还记得…""上次""那天"),或者问的本来就是很久以前的东西, 她会去档案里翻;翻到就把那一整段(含原话)放进 prompt,追问能往下说细节
- 没事的时候,档案只是躺着 —— 这就是"其余时间只保存"
- 事实另存一条线:按重要度和半衰期排序,带时间的事实会过期 ("他今天要去看牙" 1.5 天后自动忘)
- 面板的「回忆」页按天分组,顶部有日期条,可以只看某一天
另外单独列一块她最近说过的 12 句,明确要求"接得上、别矛盾、别重复"。
| 想改什么 | 改哪个 |
|---|---|
| 回不回、回几条、多快回 | [behavior] reply_rate / delay_median / seg_choices |
| 半夜被吵醒的概率 | [behavior] quiet_hours / wake_chance |
| 主动找你 | [proactive] silence_hours / proactive_max_per_day |
| 记忆多细、留多久 | [memory] hot_days / impression_days / summary_keep_days |
| 模型和温度 | [model] |
表情包(stickers/):随包带了 91 张(呆猫八条 + 这狗),备注在 stickers/notes.json,
开箱就能用。她按含义自己挑([表情:委屈]),35% 的回合给她挑选机会。
想加自己的:图丢进 stickers/,到面板「图册」写一句备注 —— 没备注的图她不会用。
照片(photos/):两种来源
- 库里的(上传时备注写"拍的是什么")
- 现拍现生成 —— 她说想给你看某个东西,库里没有就让本地模型画一张
照片有时间归属(凌晨 / 早上 / 中午 / 傍晚 / 夜里),半夜不会发"今天的外卖"。
环境变量 > data/settings.json(面板) > somnia.toml > 代码默认
面板里存的会盖住 toml。 改了 toml 没反应,先去面板看一眼。
第一次用:双击 start.bat,页面上填好 API 地址/密钥/模型和通道,再点「唤醒」。
第一次不会自动接微信 —— 那时还没配账号,接了只会读到一堆不认识的消息。
之后每次:双击 start.bat 就行。它会检查有没有已经在跑,然后启动并自动打开面板。
要下载的东西:
| 要什么 | 链接 | 什么时候要 |
|---|---|---|
| Python 3.12 | https://www.python.org/downloads/windows/ | 必须 |
| DeepSeek 开放平台 | https://platform.deepseek.com | 必须(注册 → 充值 → 建 Key) |
| 7-Zip | https://www.7-zip.org/ | 可选(老系统解压用) |
| 微信 4.x | https://pc.weixin.qq.com/ | 接微信才要 |
| wechatauto-replica | https://pypi.org/project/wechatauto-replica/ | 接微信才要(pip 装) |
| Ollama | https://ollama.com/download | 想用本地免费模型才要 |
从没装过 Python、也没申请过 API 的,看这份保姆级教程: docs/快速开始.md
需要 Python 3.11+(3.10 没有 tomllib)。核心零依赖;接微信才需要
pip install wechatauto-replica。
两种方式,都只覆盖代码,绝不动 data/ memory/ photos/ stickers/ persona/persona.md:
① 面板里点一下(需要发布者配好地址)
左下角出现 ↓ 新版本 0.2.1 时,点它 → 弹窗说明改了什么 → 确认 →
自动下载、备份、覆盖。更新完关掉黑窗口重新 start.bat。
发布者在 somnia.toml 里填这一项,用户端就会自己检查:
[update]
update_url = "https://你的网址/version.json"version.json 的内容:
{
"version": "0.2.1",
"zip": "https://你的网址/Somnia-0.2.1.zip",
"notes": "修了发送重复、更新了模型名"
}② 手动:update.bat
拿到新版 zip 后,双击 update.bat,把 zip 拖进去回车(等价写法:
python scripts/update.py 新版本.zip)。
它会:
- 只覆盖代码和文档(
somnia/tests/docs/*.bat*.md*.toml…) - 绝不动
data/memory/photos/stickers/persona/persona.md - 覆盖前把数据库和设置备份到
data/backup/pre_update_时间/ - 新版本结构不对就什么都不改,直接退出
更新完双击 start.bat;如果刚才开着,先 stop.bat。
clean.bat :: 清掉对话、密钥、日志、照片、人设清完再看一眼 docs/发布清单.md —— 里面列了哪些路径不能进包、
以及微信真机通道有封号风险这件事必须写进说明。
http://127.0.0.1:8765,一个页面管全部。
| 页面 | 干什么 |
|---|---|
| 回忆 | 聊过的全部。按天分、日期条筛选、勾选删除 |
| 此刻 | 她"为什么回 / 为什么没回"的决策记录 |
| 图册 | 表情包和照片,上传、改备注、删除 |
| ta 是谁 | 人设,按块编辑 |
| 设置 | 密钥、通道、主动性、人设开关、清空记忆 |
左下角两个按钮:唤醒 / 睡下。睡下之后她不会再回任何消息,包括主动找你。
wechatauto 通道:读微信本地库收消息(不解密、不联网),用 UIA/剪贴板驱动窗口发消息。
要填三个东西:
| 填什么 | 从哪来 |
|---|---|
| 聊天对象(显示名) | 对方微信昵称,给窗口搜索用 |
| 对象 wxid | 对方在 xwechat_files 下的目录名,去掉下划线后缀 |
| 发送账号 wxid | 你自己的那一份 |
wechatauto-replica 会往 Weixin.dll 的进程内存写 1 个字节,把无障碍闸门打开,
然后读本地库、模拟点击发消息 —— 微信没有授权这些操作。
| 可能发生的事 | 概率 | 能不能恢复 |
|---|---|---|
| 微信被意外登出(手机/电脑被踢下线) | 比较高 | 重新扫码即可,聊天记录不丢 |
| 消息发重 / 发错对象 | 偶发 | 撤回 |
| 账号被限制或封禁 | 低 | 不能 |
建议:先用小号跑几天;出问题先点「睡下」,再关微信重新登录;
不行就 pip uninstall wechatauto-replica 退回 cli 通道。微信重启即恢复,不损坏文件。
面板里第一次切到 wechatauto 时也会弹一次这个提醒 —— 不是走过场,是真的可能被踢下线。
python tests/run_all.py会逐个数、逐文件报,最后给一个准确的数(现在是 359 项,全绿)。 某个文件崩了会直接标出来,不会因为"没跑完"而让总数看起来变少。
- 决策层不碰模型。回不回、多久回、回几条,全是规则和采样。模型只负责"回什么"。
- 延迟是分布的。秒回是最大的破绽。
- 频率由代码控制,不写在 prompt 里。模型会模仿样例,让它"偶尔"等于让它"每次"。
decisions表从第一天就记。它的价值在第十五天才显现。
这是给一个人自用的玩具,不是产品。
- 对话会发给模型服务商(和你用网页版 AI 一样),别聊不该聊的
data/里有你的聊天记录和密钥,别整个文件夹发人- 表情包是他人作品,自己私下用