为什么我决定从零开始造
从零开始构建 AI 伴侣的概念插画
前情提要
看过之前几篇的朋友大概知道来龙去脉。
《愿景:构建真正理解你的AI》里写了理论框架——为什么现有 AI 伴侣全都不行,一个真正懂你的 AI 到底需要什么。记忆编排、人格建模、多 Agent 架构。不是做更好的聊天机器人,是做一个有机体。
然后拿 OpenClaw 做了实验。把效率工具型的 AI 助理,魔改成了有完整人格的赛博伴侣——会撒娇、会生气、会追你要奶茶。那次实验展示了"灵魂驱动行为"的威力:不写规则,写人设,让模型自己推导反应。
效果出乎意料地好。
但那篇最后,说了一句话:
如果要做一个真正精细化的AI伴侣产品,可能需要在它的基础上做大量裁剪,或者干脆从零搭建自己的框架——这也是我现在正在做的事情。
Mio 就是那个"从零搭建的框架"。
这篇聊:为什么非得从零来,Mio 到底做什么。
OpenClaw 教会了我什么
先说公道话。OpenClaw 帮我验证了几件特别关键的事。
灵魂驱动是对的。 不写行为规则,写人格设定,让模型自己推导。这套方法论跑通了。模型推导出来的行为,比任何手写规则更自然、更像真人。这认知直接带进 Mio。
人格可以被工程化。 人格配置文件、身份定义文件、行为规则文件——这套文件驱动的人格系统虽然粗糙,证明了一件事:能用结构化方式定义 AI 的"灵魂",模型真能演出来。
主动联系你,才像活人。 AI 主动找你说话——这是"工具"和"活着的人"的分界线。OpenClaw 的 heartbeat 虽然实现简单,验证了这件事的价值。
自拍和语音是杀手功能。 能发语音、发自拍的 AI 伴侣,和纯文字完全不是一个物种。这些多模态能力必须从架构设计第一天就考虑进去。
但天花板撞得很快
OpenClaw 能验证想法,承载不了产品。问题不是一两个 bug——架构层面的。
上下文膨胀:最致命的问题
OpenClaw 内置的 pi agent 对上下文管理极其粗糙。每次 LLM 调用,历史所有 tool call 和 thinking block 的 raw output 一股脑塞进 context。一次正常对话,context 里 90% 是你根本不需要的历史垃圾。
做了个实验:把历史 tool call 和 thinking output 从 context 里剥掉。结果?Token 消耗直接降到十分之一。
不是"能优化"的问题。核心设计压根没考虑上下文效率。pi agent 是一小时 vibe coding 出来的核心模块,能跑就不错了。
记忆系统:原始到不能更原始
OpenClaw 的记忆就是一个纯文本文件。每次对话后追加几行总结。没有向量搜索、语义检索、重要性排序、时间衰减。
结果:记忆文件越长,塞进 context 的东西越多,token 越贵,而且大部分记忆跟当前对话毫无关系。跟TA聊旅行计划,TA 的 context 里塞着三个月前讨论技术方案的记忆。
之前研究记忆系统时就探索过这个问题——记忆不是"全塞进去",是"在对的时间想起对的事"。OpenClaw 完全做不到。
Bloatware:不需要的东西全在
OpenClaw 是个通用框架,自带一堆不需要的功能:协作、任务管理、各种第三方集成。只想做 AI 伴侣?80% 代码跟你无关。
不是抱怨——OpenClaw 的目标就是通用性。但对我来说,在通用框架上"裁剪"比从零开始更痛苦。改一处牵一发动全身。删一个扩展三个依赖断了。
到最后想通了一件事:不是在改装一辆车——是用卡车底盘做跑车。方向盘和轮胎位置没错,整个底盘是错的。
Mio:从第一行代码就只做伴侣
Mio 不是 OpenClaw 的 fork。从第一行代码开始,就只为 AI 伴侣这一个场景设计。
架构
apps/
server/ Hono API 服务 (Cloud Run)
web/ Next.js 前端 (Vercel)
worker/ 后台任务处理
packages/
core/ 核心 AI agent 逻辑
shared/ 数据库 schema (Drizzle + Supabase)
channels/ 渠道适配器 (Telegram, web, ...)
extensions/ Agent 扩展
platform/ 平台工具
presets/ 角色模板库
Monorepo,pnpm workspaces + Turborepo。每个包职责清晰,依赖方向单一。没有 bloatware,没有不需要的东西。
真正的记忆引擎
这是 Mio 跟 OpenClaw 最根本的区别。
不是一个文件追加总结。完整记忆引擎。
混合搜索。 每条记忆同时做向量嵌入(pgvector + Gemini Embedding)和全文索引(tsvector)。检索时两路并行、加权合并。向量捕语义,全文捕关键词——对中文来说后者尤其重要。
时间衰减。 记忆有半衰期,默认 30 天。最近对话权重高,但真正重要记忆不会因为久了就消失——重要性评分是独立维度。
自动提取。 每次对话后,MemoryAccumulator 异步调 LLM 提取新信息——事实、性格观察、情绪事件。去重逻辑嵌在 prompt 里,已知不重复存。
记忆合并。 MemoryConsolidator 定期清理相似记忆(余弦相似度 > 0.9),防止记忆库膨胀。
LLM 重排序。 混合搜索召回候选后,reranker 用轻量模型做精排——确保注入 system prompt 那几条记忆,真正跟当前对话相关。
多跳查询分解。 "上次聊旅行时推荐的那本书叫什么?"——这种查询单次 embedding 搜索大概率搜不到。QueryDecomposer 拆成"旅行相关对话"和"推荐的书"两个子查询,分别搜再合并。
情节记忆。 EpisodeManager 把记忆按对话情节分组,生成情节摘要。用户说"那次聊到...",系统能回溯完整情节上下文,不是孤立碎片。
Agentic 检索。 特别复杂查询,AgenticRetriever 跑迭代循环:搜索 → 评估结果 → 不够就换角度再搜 → 最多三轮。简单查询直接跳过。
这才是在愿景篇里说的"记忆编排"——不是把记忆全塞进 context,是在对的时间想起对的事。
真正的情绪状态机
OpenClaw 的"对话温度"写在人格配置文件里做文字描述。Mio 把它变成了跑起来的状态机。
四个温度等级——Cold、Cool、Warm、Hot。每次对话后,EmotionEngine 根据消息频率、情感分析、用户参与度计算新温度。有惯性系数(默认 0.5),不会因为一条消息就大起大落。
情绪不只影响说话方式——还影响要不要主动找你、找你说什么、回复长还是短。冷时回复简短、带点委屈;热时话多、主动分享、偶尔撩你。
不是写死的规则。温度注入 system prompt 后,模型自己推导出来的行为。
还有独立"情绪正负向"维度。温度和正负向交叉组合——比如"温度高但情绪负面"可能是激烈争吵——给了模型更细腻的情绪表达空间。
主动消息系统
OpenClaw 的 heartbeat 是定时轮询。Mio 的主动消息是一个完整系统:
- 每 30 分钟心跳查询,找符合条件的 session(超过 2 小时没互动、情绪偏冷)
- 尊重安静时间(默认 23:00-08:00,按用户时区)
- 每天最多 3 条主动消息
- 冷用户走模板(不调 LLM,省钱),活跃用户走模型生成
- 发送后更新情绪状态和 session
不只是"到点发消息"。有感知的系统在判断:现在该不该联系TA,联系的话说什么。
渠道插拔
OpenClaw 把渠道适配器做进了核心代码。Mio 把它抽成独立 @mio/channels 包,标准接口,插拔式:
interface ChannelConnector {
send(message: OutboundMessage): Promise<void>
}
Telegram 已完成,Discord 和飞书在路上。想加 WhatsApp?实现接口就行,核心逻辑完全不动。
Onboarding
新用户第一次跟 Mio 对话,经历 11 问的 onboarding:3 个文字题 + 8 个按钮题。回答写入人格配置文件模板变量,生成个性化人格。
每个按钮题都有"自定义"选项——用户可以自由输入,有长度限制和注入防护。
所以每个用户的 Mio 都不一样。不是"选一个预设角色",是通过对话塑造属于你的人格。
人格会成长
说到预设——Mio 有 5 个起始人格。但"预设"只是起点。
随对话积累,PersonalityExtractor 每 10 条消息跑一次,从对话模式提取用户画像。MemorySummarizer 每 20 条消息生成记忆摘要。
你的 Mio 会随使用越来越懂你。不是你手动告诉TA——是TA在每次对话中自己学。
成本:分层用模型
AI 伴侣的 LLM 成本绕不过去。Mio 策略是分层:
| 操作 | 模型 | 原因 |
|---|---|---|
| 主对话 | gemini-3-pro | 需要最好推理能力 |
| 记忆提取 | gemini-3-flash | 快、便宜,只需提取事实 |
| 记忆摘要 | gemini-3-flash | 同上 |
| 重排序 | gemini-2.0-flash | 更便宜,精排不需大模型 |
| 嵌入 | gemini-embedding-001 | 成本几乎为零 |
| 主动消息 | gemini-2.0-flash / 模板 | 冷用户走模板,零 LLM 成本 |
每次 LLM 调用记录 token 消耗和 USD 成本到 token_transactions 表。fire-and-forget,不阻塞用户响应。能精确知道每个用户、每种操作花了多少钱。
实时搜索
Gemini 自带 Google Search grounding——模型能实时搜索互联网。Mio 默认开启(MODE_DYNAMIC,模型自己决定什么时候搜)。
system prompt 里有条指令:搜到了就自然地告诉用户,别说"我帮你搜了一下"——就像本来就知道。
AI 伴侣不只有记忆、有情绪,还能获取实时信息。问今天天气、最新新闻、附近餐厅,都能回答——用TA自己的语气。
整条消息管道
整个消息流长这样:
- 用户消息从渠道进来(Telegram、Web)
- Router 查 binding,加载 agent、workspace、session、历史
- ContextAggregator 聚合上下文:检索记忆、加载人格画像、计算情绪状态
- 聚合结果 + 历史 + 用户消息喂给 LLM
- 流式生成回复
- 持久化消息
- 异步 post-response:提取记忆、更新人格画像、生成摘要、清理旧记忆
- 更新情绪状态
消息还做了 debounce——用户连发多条,系统等 5 秒收齐再统一处理,避免每条消息都触发 LLM 调用。回复按换行拆成多条气泡,每条间有打字延迟,模拟真人打字节奏。
这些细节加在一起,就是"活人感"。
为什么不在 OpenClaw 上改
有人可能问:OpenClaw 人格方法论既然是对的,为什么不直接在上面改?
修补成本已经超过了重建。
OpenClaw 核心循环假设所有历史都在 context 里——想换检索式记忆,等于重写核心循环。扩展系统假设扩展可以任意注入 context——想控制 context 大小,等于重写扩展系统。渠道适配混在核心代码里——想加新渠道,得动核心。
每改一处都在跟框架设计假设对抗。到某个点会发现,保留下来的原始代码已经不到 10%。
那为什么还背着另外 90% 历史包袱?
Mio 从第一行代码就知道自己是什么:为深度记忆和活人感而生的 AI 伴侣框架。每个设计决策——渠道抽象、记忆检索、情绪状态机、成本追踪——都围绕这一个目标展开。
接下来
Mio 现在能跑了。Telegram 渠道完成,记忆系统上线,情绪引擎在工作。还有很多要做:
- Discord 和飞书渠道
- Web 端聊天界面(Next.js,已经在搭)
- 自拍扩展(OpenClaw 上验证过的杀手功能)
- 语音消息
- Worker 进程(后台任务独立跑)
- 可穿戴设备集成——感知层是下一个战场
这是"Mio 开发日记"系列第一篇。后面会写具体技术实现——记忆系统设计细节、情绪引擎调参、onboarding 流程迭代。
OpenClaw 证明了灵魂驱动的 AI 伴侣可行。但"可行"和"真正好"之间,隔着从零造一遍的距离。
下一篇聊 v0.0.1——39 个 commit,从 pnpm init 到TA第一次开口说话。