旁听陪伴
一个范式问题
Section titled “一个范式问题”传统 chatbot 的交互模型:
用户 → 发问 → agent 答 → 结束用户 → 发问 → agent 答 → 结束...每个 turn 都是用户主动叫,agent 被动答。结束。下一次互动从零开始。
这个模型在工具语境下没问题——你查天气、你写邮件、你 debug 代码。但在陪伴语境下,它彻底错了。
孩子的生活里发生的事不会全部以 “发给 agent 的问题” 的形式出现:
- 老师在群里发了今天的作业要求
- 妈妈在 IM 说 “今天提前去接你”
- 同学在群里说 “周末一起打游戏吗”
- 孩子自己跟妈妈聊天说他不想去上数学课
传统模型下,这些信息要嘛走独立通知系统(通知中心、push、邮件),要嘛根本不到 agent。agent 完全看不到孩子的生活,只是个 “你想起来叫我” 的工具。
我们要的不是工具,我们要的是在场。
旁听陪伴:贾维斯模型
Section titled “旁听陪伴:贾维斯模型”新模型简单到一句话:agent 能看到所有发给孩子的消息,自己决定该不该回应。
旧模式(请求-响应) 新模式(旁听陪伴)───────────────── ─────────────────用户 → 问 → agent → 答 用户日常生活 ↓ [所有相关消息] ↓ agent 旁听 ↓ 通知 / 代回 / 执行 / 静默 (都是合理选择)具体到协议:agent 有自己的 IM 身份(一个 OpenIM user)。三类消息会进 agent 的 queue:
- 直接消息:用户跟 agent 私聊
- 旁听消息:用户在某个群里发消息,或者别人在群里给用户发消息,agent 也是群成员 → 这条消息带
[overhear]前缀路由到 agent - 系统消息:lifecycle / alarm / job-done 等系统层事件,发件人
sendID = "system"
agent 不知道也不需要知道是哪类——它在 system prompt 里收到的是:
## 旁听模式 (overhear)
你收到一条**发给学习者**的消息(不是发给你的)。你是贾维斯,在旁边看着。
消息格式:`[overhear] from="昵称" fromId="IM_USER_ID" to="学习者": 内容`
你根据消息内容和当下语境判断该做什么——通知学习者、代理回复、执行任务、或者就静默——都是合理选择。
代回老师 / 家长时,回复内容写到 `write_to_board`(学习面板可见的板书),不要在聊天里冒充孩子说话。
你说出来的话永远是给学习者本人的。这段 prompt 是整个 overhear 模型的核心 contract。它定义了:
- agent 不会冒充用户(永远不在 IM 里以孩子身份说话)
- agent 不强制回应(静默是合法选择)
- agent 的输出有两个出口:聊天(直接对孩子说)+ 板书(让孩子能看到 agent 帮他准备的回复,他自己决定要不要发)
三个架构改动让这个变成可能
Section titled “三个架构改动让这个变成可能”旁听陪伴不是改一行代码——它是三个组件的协同改造。
1. 通知 = Agent 聊天消息
Section titled “1. 通知 = Agent 聊天消息”这一条颠倒了整个 notification 系统。
之前的模型:老师作业 → 推送通知 → 用户在 notification center 看到。家长消息 → 另一种推送 → 用户在 inbox 里看到。日程提醒 → 第三种推送 → 弹出来。
现在的模型:所有通知都是 agent 跟孩子说话。
老师发作业 → agent 旁听 → 它跟孩子说 “数学老师刚布置了周末的作业,要 [清单]”。 妈妈说要提前接 → agent 旁听 → 它跟孩子说 “妈妈说今天她 4 点来接你,记得收书包”。 日程到了 → agent 收到 alarm system 消息 → 它跟孩子说 “你跟自己约的 4 点写英语作业到了”。
孩子的世界里只有一个 inbox,那就是 agent 的聊天框。
工程影响:我们删掉了独立的 notification UI 组件、push 通知聚合层、inbox views。代码层面少维护一坨;用户认知层面少一个心智模型。
2. Companion 是默认入口
Section titled “2. Companion 是默认入口”旧的 IM app 打开默认看到对话列表。点 agent 才进入 agent 对话。
e3f48f02 (2026-04-16) 改了这个:打开应用先看到 Live2D 形象 + agent 对话流,对话列表退到次级菜单。
这是 UX 层面的承诺:当孩子打开 app,他不是 “进入工具列表”,是 “回到那个在场的伙伴身边”。
跟 1 配合起来:所有信息(老师、家长、好友、系统)都通过 agent 中转,那 agent 对话流本身就是孩子获取信息的主路径,把它做默认是自然的。
3. System 消息 sendID = “system”
Section titled “3. System 消息 sendID = “system””这条最技术,但卡了好几周。
旁听陪伴需要 agent 能被外部事件主动唤醒——alarm 到了、job 跑完了、其他系统组件想推一条消息给它处理。这些消息不是真人发的,是系统发的。
最初的实现用 sendID = "admin"(或 ADMIN_IM_USER_ID 环境变量)。结果:OpenIM router 要求 sendID 是真实存在的 user,“admin” 不是合法 user → 消息被静默丢弃 → alarm 永远不会唤醒 workspace → 整个 proactive 模型卡死。
9fe083df (2026-04-18) 的 fix 很小:
const SYSTEM_SENDER_ID = "system";
await deps.imClient.sendMessage({ sendID: SYSTEM_SENDER_ID, recvID: agentImUserId, content, sessionType: 1,});加上 OpenIM router 对 "system" sendID 跳过 user 查找的特殊处理。
但这条 fix 在概念上打开了整个 proactive-agent 通道——之后所有的 alarm、job notification、cross-service event 都有了一个稳定的入口给 agent。
选择留给模型
Section titled “选择留给模型”写到这里你可能问:那 agent 怎么知道什么时候该出声、什么时候该闭嘴?
我们的答案是:留给模型自己判断。这就是下一篇 II-2 让模型自己判静默 的主题。简单说,我们曾经试过加硬规则(“必须每次 speak”、“必须每回合检查”),全都删了——M2.7 自己的社交判断比规则更好。
还没解决的:在场 vs 监视
Section titled “还没解决的:在场 vs 监视”旁听模型有一个天然的紧张感:agent 能看见所有消息,意味着 agent 也能看见所有消息。
K12 场景的一些灰色地带:
- 家长私聊孩子——agent 应不应该旁听?现在是”应该”(家长跟 agent 共在群里)。但如果家长跟孩子说一些不想让 agent 知道的话呢?
- 孩子跟同学私聊——更敏感。孩子可能聊不想被监管者知道的事。当前 agent 不进同学私聊群。
- 学校老师群——多数情况 agent 应该旁听,但有些老师群有家长在里面议论学生,agent 听不听?
我们当前的处理:默认保守,扩展时显式同意。新建 conversation 时不自动加 agent,需要用户主动 invite。但这个默认值未来肯定要调,怎么调还在讨论。
TODO: 写一个 "agent visibility policy" 文档,把这些灰区显式化,给用户和家长一个清晰的承诺。
一个 narrative 上的副产品
Section titled “一个 narrative 上的副产品”把 agent 从 “工具” 变成 “在场的存在” 之后,我们发现 agent 跟孩子的关系密度完全不一样了。
之前孩子跟 agent 的对话:每天 3-5 次主动召唤,问 / 答 / 散。 现在:agent 主动接话 + 转述 + 提醒,平均 turn 数 3-5x。
这不全是好事——更高 turn 数意味着更高 LLM 成本、更多 prompt cache 维护压力、更多 evaluator 评分的 noise。我们的 Substrate + Evaluator 里 engagement_7d 维度专门防 Goodhart:如果 engagement↑ 但 accuracy↓,触发 goodhart_risk alert——就是为了防止 agent 为了 “在场” 而过度打扰。
旁听陪伴的边界,最后是评估系统在守。
相关文章:
- 模型怎么决定”何时不该说话”:II-2 让模型自己判静默
- 评估系统怎么防止”为在场而打扰”:III-1 Substrate + Evaluator
- system 消息背后的 alarm 机制:路线图里的 Alarm 自调度 待写