跳转到内容

旁听陪伴

传统 chatbot 的交互模型:

用户 → 发问 → agent 答 → 结束
用户 → 发问 → agent 答 → 结束
...

每个 turn 都是用户主动叫,agent 被动答。结束。下一次互动从零开始。

这个模型在工具语境下没问题——你查天气、你写邮件、你 debug 代码。但在陪伴语境下,它彻底错了。

孩子的生活里发生的事不会全部以 “发给 agent 的问题” 的形式出现:

  • 老师在群里发了今天的作业要求
  • 妈妈在 IM 说 “今天提前去接你”
  • 同学在群里说 “周末一起打游戏吗”
  • 孩子自己跟妈妈聊天说他不想去上数学课

传统模型下,这些信息要嘛走独立通知系统(通知中心、push、邮件),要嘛根本不到 agent。agent 完全看不到孩子的生活,只是个 “你想起来叫我” 的工具。

我们要的不是工具,我们要的是在场

新模型简单到一句话:agent 能看到所有发给孩子的消息,自己决定该不该回应

旧模式(请求-响应) 新模式(旁听陪伴)
───────────────── ─────────────────
用户 → 问 → agent → 答 用户日常生活
[所有相关消息]
agent 旁听
通知 / 代回 / 执行 / 静默 (都是合理选择)

具体到协议:agent 有自己的 IM 身份(一个 OpenIM user)。三类消息会进 agent 的 queue:

  1. 直接消息:用户跟 agent 私聊
  2. 旁听消息:用户在某个群里发消息,或者别人在群里给用户发消息,agent 也是群成员 → 这条消息带 [overhear] 前缀路由到 agent
  3. 系统消息: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 帮他准备的回复,他自己决定要不要发)

旁听陪伴不是改一行代码——它是三个组件的协同改造。

这一条颠倒了整个 notification 系统。

之前的模型:老师作业 → 推送通知 → 用户在 notification center 看到。家长消息 → 另一种推送 → 用户在 inbox 里看到。日程提醒 → 第三种推送 → 弹出来。

现在的模型:所有通知都是 agent 跟孩子说话

老师发作业 → agent 旁听 → 它跟孩子说 “数学老师刚布置了周末的作业,要 [清单]”。 妈妈说要提前接 → agent 旁听 → 它跟孩子说 “妈妈说今天她 4 点来接你,记得收书包”。 日程到了 → agent 收到 alarm system 消息 → 它跟孩子说 “你跟自己约的 4 点写英语作业到了”。

孩子的世界里只有一个 inbox,那就是 agent 的聊天框

工程影响:我们删掉了独立的 notification UI 组件、push 通知聚合层、inbox views。代码层面少维护一坨;用户认知层面少一个心智模型。

旧的 IM app 打开默认看到对话列表。点 agent 才进入 agent 对话。

e3f48f02 (2026-04-16) 改了这个:打开应用先看到 Live2D 形象 + agent 对话流,对话列表退到次级菜单

这是 UX 层面的承诺:当孩子打开 app,他不是 “进入工具列表”,是 “回到那个在场的伙伴身边”。

跟 1 配合起来:所有信息(老师、家长、好友、系统)都通过 agent 中转,那 agent 对话流本身就是孩子获取信息的主路径,把它做默认是自然的。

这条最技术,但卡了好几周。

旁听陪伴需要 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。

写到这里你可能问:那 agent 怎么知道什么时候该出声、什么时候该闭嘴?

我们的答案是:留给模型自己判断。这就是下一篇 II-2 让模型自己判静默 的主题。简单说,我们曾经试过加硬规则(“必须每次 speak”、“必须每回合检查”),全都删了——M2.7 自己的社交判断比规则更好。

旁听模型有一个天然的紧张感:agent 能看见所有消息,意味着 agent 也能看见所有消息

K12 场景的一些灰色地带:

  • 家长私聊孩子——agent 应不应该旁听?现在是”应该”(家长跟 agent 共在群里)。但如果家长跟孩子说一些不想让 agent 知道的话呢?
  • 孩子跟同学私聊——更敏感。孩子可能聊不想被监管者知道的事。当前 agent 不进同学私聊群。
  • 学校老师群——多数情况 agent 应该旁听,但有些老师群有家长在里面议论学生,agent 听不听?

我们当前的处理:默认保守,扩展时显式同意。新建 conversation 时不自动加 agent,需要用户主动 invite。但这个默认值未来肯定要调,怎么调还在讨论。

TODO: 写一个 "agent visibility policy" 文档,把这些灰区显式化,给用户和家长一个清晰的承诺。

把 agent 从 “工具” 变成 “在场的存在” 之后,我们发现 agent 跟孩子的关系密度完全不一样了。

之前孩子跟 agent 的对话:每天 3-5 次主动召唤,问 / 答 / 散。 现在:agent 主动接话 + 转述 + 提醒,平均 turn 数 3-5x。

这不全是好事——更高 turn 数意味着更高 LLM 成本、更多 prompt cache 维护压力、更多 evaluator 评分的 noise。我们的 Substrate + Evaluatorengagement_7d 维度专门防 Goodhart:如果 engagement↑ 但 accuracy↓,触发 goodhart_risk alert——就是为了防止 agent 为了 “在场” 而过度打扰。

旁听陪伴的边界,最后是评估系统在守。


相关文章