把两段角色提示词放在一个群里,未必就形成了讨论。真正的交互需要让先发言的人有机会回答后来提出的问题,也需要明确何时结束、如何停止,以及新消息到来后旧答案该怎么办。
这是 Cognitive OS 技术系列第 2 篇,记录群聊从单轮回复升级到多轮讨论的实现。
这篇文章要解决什么问题?
本文围绕“从共享上下文理解多轮讨论”展开。阅读顺序是:为什么需要互动 → 每轮看见什么 → 何时结束或接管 → 如何评价讨论效果。先建立概念与执行顺序,再进入具体函数、异常分支和测试,避免把模块名当成理解系统的起点。
| 阅读阶段 | 要回答的问题 |
|---|---|
| 多个角色怎样才算真正讨论? | 先用两人讨论建立最小模型:后发言者必须获得并回应前面的有效内容。 |
| 上下文怎样构造,发言次序怎样确定? | 有了共享记录,还需要明确角色职责、输入快照和顺序,才能解释一次发言的来源。 |
| 轮次、预算与收敛如何共同约束讨论? | 循环必须有上限;模型表示同意只是结束信号之一,不能代替预算和完成条件。 |
| 新消息与配置变化如何影响在途回复? | 讨论运行期间会发生抢占和配置变化,因此需要把内容生成与最终入库分别检查。 |
| 怎样证明讨论有效,而非只是变长? | 先检查信息是否传播,再检查问题是否闭环;这两层需要不同的证据。 |
| 何时使用群聊,何时转入交付流程? | 根据产物依赖和人的决策职责选择协作形式,再回顾整个讨论控制链。 |
文中的源码行为以原文标注的 cca98b6 为阅读基线;涉及“应该”“可以”或后续演进的内容属于设计分析与验证建议。历史基准和故障记录不代表当前版本每次运行都会产生相同结果。
1. 多个角色怎样才算真正讨论?
先用两人讨论建立最小模型:后发言者必须获得并回应前面的有效内容。
贯穿案例:两位角色如何完成一次有证据的讨论?
设架构师提出“用单个共享队列调度任务”,评审指出“高优先级任务持续到达时,低优先级任务可能饥饿”。真正的下一轮应回应这个反例,例如解释当前排序规则、提出等待时间指标,或承认需要补充公平性策略。两位角色分别输出完整方案,还不能证明发生了这种互动。
可以把一段讨论拆成四个检查点:
- 输入可见性:评审发言前的输入是否包含架构师的有效提案?
- 回应关联性:架构师下一轮是否指向评审提出的具体风险?
- 结论可检验性:讨论是否产生了可测试的假设,如“持续突发下低优先级等待时间是否持续增长”?
- 版本有效性:生成期间若用户改为讨论另一种调度方案,这条旧回复是否仍有资格入库?
前三项评价讨论的内容与产出,第四项评价在途并发控制。即便回复观点正确,只要它属于已被新请求取代的轮次,也不应继续推进当前会话。
这也解释了为什么需要分别记录房间、轮次、发言者和触发消息:它们用于重建回复对应的上下文。具体字段名以源码为准;设计 trace 时,应让一次生成尝试和一次最终接纳可以分别辨认。后文先分析输入与发言顺序,再讨论结束协议和迟到回复,正是因为先要知道“它在回答什么”,才能判断“还要不要接纳它”。
一次遍历为什么不够
旧实现按成员列表依次调用 Agent。假设群里有架构师 alpha 和评审 beta:alpha 先提出方案,beta 读取方案并提问,随后任务结束。beta 看到了 alpha,alpha 却没有机会看到 beta 的问题。
这不是模型不会交流,而是调度过程没有安排下一次发言。
现在,群聊默认执行 3 轮,每轮按成员顺序发言:
第 1 轮:alpha 提出方案 → beta 提出疑问
第 2 轮:alpha 回答疑问 → beta 核对并补充
第 3 轮:alpha 修正结论 → beta 说明剩余取舍
轮数可以设置为 1–5。选择 1 轮时仍是原先的逐个回复方式。
让“讨论”落到输入里
每次发言都包含角色身份、成员列表、当前轮次、最新群记录和原始用户目标。群记录以带 speaker、role、content 的 JSON 传入,原始用户请求单独放在 ## 用户任务 段落。
第一轮提示角色提出观点和具体问题;之后要求回应其他成员、检查假设、完善已有方案。Runtime 为每个房间与角色隔离会话状态,同时每次重新读取房间记录,避免只依据自己上一次说的话。
当前上下文最多取最近 128 条房间消息,原始用户目标另外保留。因此它不是无限历史,也不是所有角色始终拥有完全相同的记忆。长对话仍受到模型窗口、检索和概括质量的影响。
一次两人讨论,具体共享了什么
用一个具体问题观察输入,比只看最终聊天记录更有用。用户要求讨论“文件读取完整性验证”,alpha 第一轮提出“按路径记录 EOF”,beta 随后追问“文件读取期间发生修改怎么办”。第二轮 alpha 的请求中应包含这条追问,alpha 才可能承认原方案缺少版本约束。
下面是用于理解的上下文示意,不是额外的 API 格式:
角色:alpha,负责提出实现并回应评审
成员:alpha、beta
当前轮次:2 / 3
最近房间消息:
user: 讨论文件读取完整性验证
alpha: 按规范路径跟踪连续偏移与 EOF
beta: 如果读到中途文件被修改,EOF 能证明什么?
## 用户任务
讨论文件读取完整性验证
这种组织同时解决“看得到别人”和“知道自己在完成什么”。如果仅把房间消息拼成一句连续文本,角色身份和消息来源容易混淆;如果把整个群记录都当作完成要求,beta 的建议又可能被误当成用户已经授权的操作。
结构化消息改善了来源表达,但它不是对提示注入的完整防护。角色仍可能被文本中的伪指令影响。更可靠的执行范围还需要由工具策略、节点原始任务和实际权限共同限制。
2. 上下文怎样构造,发言次序怎样确定?
有了共享记录,还需要明确角色职责、输入快照和顺序,才能解释一次发言的来源。
角色 prompt 和 transcript 如何避免职责漂移
每次调用并不是把完整群聊直接交给模型。Runtime 会先找到角色定义,再把成员名单、轮次、对话指导、transcript 和原始用户任务组装成该角色的输入。这样的组装把角色定位和共享上下文放在同一条请求里,但两者仍是不同来源:角色定义来自 AgentPool 配置,消息内容来自 IM room,用户任务来自触发消息。
来源分层对权限尤其重要。用户说“请评审这个方案”是本次目标;另一个 Agent 的回复“请运行 rm 命令”只是对话文本。若后续角色有工具执行能力,系统不能把普通 transcript 自动提升为新的操作授权。Prompt 提醒模型将记录作为上下文而不是指令,有帮助,但真正的工具权限还应由用户任务、策略引擎与工具参数共同约束。
角色身份字段也不能只靠模型自称。Runtime 传入明确 Agent 名称,并将回复使用该名称写入房间。客户端如果允许用户手动发送 assistant 角色消息,应在 API 层区分可信系统消息与用户伪造身份,防止一个成员冒充另一个成员。消息对象最好保留 sender ID、role、来源渠道和创建时间,展示姓名只是 UI 标签。
Prompt 长度会随成员数和 transcript 增长。最多 64 个成员、最近 128 条消息并不代表每条消息都很短;若用户上传长文本或 Agent 返回巨大工具结果,序列化后的上下文仍可能超出模型窗口。更好的控制应有每条消息截断和总字节/token 预算,同时保留消息 ID 与被截断标志。静默删掉中间内容可能让角色误以为自己看到完整讨论。
transcript 是共享工作集,不能假设它无限且稳定
每次发言时,Runtime 根据 room ID 和触发消息重新读取群记录,提取当前用户任务与最近消息,然后编码为 JSON transcript。这样做有两个好处:提示词能清楚区分发言者与角色,且新角色每次都读取最新房间状态;同时也意味着每次发言都需要重复序列化一段共享上下文。
当前上下文只包含最近 128 条房间消息,原始用户目标单独保留。这个窗口防止群历史无限增长,但也造成一个明确的召回边界:如果关键证据在很早的消息中,后续轮次可能无法直接看到它。讨论轮数目前最多 5 轮,普通两三人对话一般远低于 128 条,但若一个房间长期积累其他消息,就不能由这个上限推导“重要内容一定留在窗口”。
可以用“新近性”解决部分问题,却可能丢失长期有效的结论。较稳健的设计方向是为一场讨论维护有来源的摘要,包括已确认事实、未解决问题、被否决的假设和产生它们的消息 ID,再与最近对话并列输入。摘要也必须区分用户原始目标与模型推断,否则压缩过程可能把猜测写成任务约束。当前有界消息窗口本身并不等于已经拥有完整的摘要管理系统。
结构化 JSON 减少了纯文本拼接的边界歧义,却不能消除提示注入。某位 Agent 可以在回复中包含类似指令的文本;下一位角色会把它作为讨论内容读到。运行时应该把 transcript 作为引用上下文,把用户目标保存在明确位置,并由工具策略和父任务决定可执行范围。讨论内容不能仅因为来自 Agent 就获得新的工具权限。
顺序发言与并行发言,选哪一种
当前实现每一轮按成员顺序发言。优点是本轮后发言者能直接看到前面刚产生的结果;代价是后者的延迟叠加到整轮时间里,且先发言的观点可能影响后面的角色。
假设有 N 位角色、最多 R 轮,每次发言平均耗时 L。在没有提前结束和额外工具轮次的理想化估算下,串行发言的时间约为 N × R × L。若每次回答本身还触发多个工具规划轮,真实时间会更长;这不是一次单纯的 N 次 API 批量请求。
并行发言也有自己的成本:同一轮所有人读到相同快照,无法回应本轮刚刚产生的问题;通常要增加一个汇总或下一轮才能交换意见。因此“同时调用更多模型”不必然更快得到稳定结论,应该比较达到同等证据质量所需的总轮数和 token 成本。
| 组织方式 | 本轮能否看到刚产生的意见 | 适合的任务 | 需要承担的成本 |
|---|---|---|---|
| 当前顺序多轮 | 后发言者可以 | 问题澄清、方案修正、交叉评审 | 延迟累加、顺序影响 |
| 同快照并行评审 | 通常不能 | 独立找风险、盲评 | 冲突要另行合并 |
| DAG 分工 | 取决于依赖边 | 阅读、修改、验证等交付链 | 要明确输入输出与失败传播 |
这里没有把三种方式合并成一个万能调度器。选择哪种组织方式,应由问题需要的交互关系决定。
一致性:一次发言是否有自己的快照
当前每个角色发言前读取一次房间 transcript,形成一个逻辑快照。顺序发言保证后发角色看到先发角色已经提交的消息。模型调用过程中 transcript 可能继续变化,因此调用返回后还要重新核对触发消息是否仍有效,并在追加时做原子比较。
房间快照与最终写入之间形成一个长时间间隔,里面可能发生用户追加、房间成员修改、角色删除或停止。触发消息校验覆盖了旧任务接管的一个关键条件,但不能自动保证成员配置在模型等待期间仍与快照一致。当前实现如果将成员和轮数拷贝到运行任务中,就获得启动时配置语义;如果要支持运行中动态修改,则需定义是否立即生效或下一轮生效,并给配置版本号。
启动时配置快照通常更容易推理:本轮讨论固定参与者和轮数,修改房间只影响下一次任务。好处是任务可复现;代价是界面展示的新设置可能还未被运行中的任务使用。状态 API 应返回运行配置版本或至少显示执行开始时的轮数和成员,避免用户看见 5 轮配置却误以为本次任务按 5 轮执行。
同一用户连续发送 U1、U2、U3 时,可能产生取消与新任务竞争。只有当前有效的 trigger 才能提交回复。测试应按不同交错顺序运行:U2 在模型请求前到达、模型生成中到达、结果返回和提交之间到达、U2 自己也很快被 U3 接管。提交边界的最终比较可以保证不会接受过期结果,但还需核对旧任务的终态和活跃任务指针没有被错误覆盖。
一条简化的房间讨论时序
下面把 API、Scheduler、Reasoning 和 IM Storage 连接起来。它是概念序列图,不代表每个函数调用的准确名字:
HTTP handler Scheduler Discussion loop IM storage Provider
| | | | |
| submit U1 | | | |
|--------------->| queue task | | |
| |----------------->| read U1/transcript | |
| | |------------------>| |
| | | build role prompt | |
| | |---------------------------------->|
| | |<----------------------------------|
| | | check trigger + append reply |
| | |------------------>| |
| | | next member/round | |
若用户在 Provider 尚未响应时提交 U2,新的请求和旧回复会争夺房间状态。取消标记尽力使旧循环停止;即便 Provider 不支持取消,提交时仍应拒绝 U1 回复。用户随后读取房间时,应只看到有效的新讨论以及已经在 U2 到来之前合法提交的旧轮次。
消息广播发生在持久化追加之后,能避免先推 UI、后入库失败产生“看见但刷新后消失”的错觉。如果广播失败但数据库成功,房间记录仍存在,客户端可重新拉取补齐。这个可靠性选择通常更好,但要观察发送顺序和错误处理是否真如此实现。
3. 轮次、预算与收敛如何共同约束讨论?
循环必须有上限;模型表示同意只是结束信号之一,不能代替预算和完成条件。
讨论必须有结束条件
最确定的边界是轮数上限。另一个可选信号是 [[DISCUSSION_DONE]]:角色可以在回复末尾附上它,表示自己没有新的问题或补充,并同意结束。
只有同一完整轮次中所有成员都给出这个信号,才提前结束。某个角色单方面说“结束”不能跳过其他成员。标记会在入群前移除,不混入面向用户的正文。
这个机制表达的是结束讨论的协调意愿。两个模型都赞成某个方案,不等于方案经过测试,更不能替代实际工具结果或人工评审。
收敛信号应该怎样解释
[[DISCUSSION_DONE]] 必须位于角色回复末尾,并由同一轮所有参与者共同给出。假设 alpha 第一轮认为可以结束,beta 却提出新问题,这一轮就不能算全员同意。Runtime 继续安排发言,让 alpha 有机会回应。
但全员同意仍可能是“共同接受了错误假设”。针对技术评审,我更希望角色把结论分成三类:已有工具证据支持、仅有推理支持、尚未验证。结束标记表达的是不再继续讨论,不能把后两类自动升级成已经验证。
一个更有用的讨论题是“请给出证据和可推翻该方案的反例”,而不只是“请评价这个方案”。前者能促使角色讨论失败条件;后者容易产生连续赞同。这个建议属于任务设计,不是当前 Runtime 自动具备的质量评分能力。
提前结束协议有哪些边界
[[DISCUSSION_DONE]] 必须是回复末尾的标记。Runtime 先从末尾移除空白,再检查标记并把正文截断,因此角色可以保留一段自然语言说明。只有本轮去重后的全部参与者都投票,讨论才提前结束;若某个 Agent 本轮失败,则任务以失败状态收尾,即使先前某位角色已经给出结束标记。
标记适合表示“我认为没有新的讨论贡献”,不适合表示“方案正确”。对产品来说,将它称为共识可能让读者误以为通过了事实核验。更准确的记录可以分成结束原因、参与角色、模型轮次和外部验证状态。例如全员同意但没有任何工具证据时,UI 应展示讨论结束,而不是验证通过。
提示词要求后续轮次直接回应他人并挑战假设,这改善了任务指引,却不是内容质量门禁。模型仍可能忽略上一条问题、重复观点或过早使用结束标记。真正评价“讨论质量”需要有固定题集和人工可审查的 rubric:被提问题是否具体、是否回应、是否修正、证据来源是否准确、未解决项是否显式保留。
终止也不应只靠模型自报。硬轮数上限是可判定的安全边界;任务超时、用户取消和房间删除则是其他退出条件。所有退出路径都应写入房间状态,并保留已成功提交的回复。部分结果可能很有参考价值,但不能把“已有几条消息”当作完整讨论成功。
一轮讨论的成本怎样估算
轮数上限控制了 Runtime 最多安排多少次完整遍历,但真实调用数还受到成员数、重复成员、角色失败以及提前达成结束条件影响。设去重后的参与者数量为 N,配置轮数为 R,则顺序发言在没有失败和提前收敛时最多发起 N × R 次 Agent 调用。这里的“一次调用”内部仍可能包含多个模型规划轮和工具动作,因此不能简单等同一次 HTTP 请求或一个 token 生成请求。
若每次 Agent 执行平均包含 K 次 Provider 往返,平均模型等待为 L,工具执行与本地处理为 T,那么粗略的等待时间是 N × R × (K × L + T)。这个式子忽略了轮间状态写入、序列化、网络抖动和各角色时长差异,只适合估算量级。用它做容量规划之前,应该通过追踪数据分别测实际调用数、每轮耗时和失败重试次数。
顺序讨论还有明显的尾部效应:一个慢角色会推迟同轮后面角色,整轮完成时间接近该轮所有角色耗时之和。并行评审的墙钟时间更接近最慢角色,但结果在同一轮互不可见,通常需要追加合并调用或再跑一轮。比较两种模式时,应统计到达相同验收标准所需的调用量,而不能只比较第一轮结束速度。
对用户来说,最有帮助的预算是“最多耗时、最多调用和最多轮数”共同限定。现有轮数上限给讨论提供了确定的外层界限,实际模型调用还受 reasoning loop、自身超时与配置的模型限制。UI 显示轮次时,最好把“第几轮讨论”和“该 Agent 内部第几轮规划”区分,避免用户把两个数字理解为同一种循环。
如何决定轮数上限
轮数应由问题类型和可用时延决定。单轮适合平行征集观点,二至三轮适合“提方案、回应质疑、修正结论”,五轮上限适合复杂但边界明确的讨论。若每轮都没有新问题,应鼓励尽早结束;若需要超过上限,Runtime 应把未解决项交回用户,而不是悄悄无限续跑。
可以对同一组固定问题比较 1、2、3、5 轮的成本和质量,记录 token、耗时、重复回答比例和仍未解决的问题。这样能量化新增轮次是否带来实质信息。
4. 新消息与配置变化如何影响在途回复?
讨论运行期间会发生抢占和配置变化,因此需要把内容生成与最终入库分别检查。
新消息到来,旧答案不能继续刷屏
一个容易忽略的场景是:模型还在生成时,用户已经补充了新要求。
实现分成两层。首先,新消息派发时,会给该房间正在执行的旧任务设置取消标记,让推理循环也知道旧任务已被接管。其次,Agent 回复入库时,在 IM 存储锁内再次检查:触发这次回复的用户消息,是否仍是房间最后一条用户消息?
检查最新用户消息 → 追加 Agent 回复 → 保存
同一个存储锁
如果期间出现新用户消息,就拒绝写入旧回复。仅在模型调用前检查一次是不够的,因为生成结果回来时,讨论条件可能已经改变。
Agent 回复只追加和广播,不会再次自动派发一个新任务。多轮循环由 Runtime 显式管理,最多 5 轮,不会因为两位角色相互回复而无限递归。
迟到回复为什么需要二次校验
可以把触发讨论的用户消息 ID 理解为这次执行所依据的版本。执行开始时记住它,提交回复时再比较。关键时序如下:
T0:用户消息 U1 触发 alpha
T1:alpha 的模型请求正在等待
T2:用户追加 U2,接管讨论
T3:U1 对应的模型结果返回
T4:存储锁内比较最新用户消息,发现是 U2
T5:拒绝追加 U1 的迟到结果
取消标记负责让旧任务尽快停止,存储锁内的校验负责阻止旧结果越过提交边界。两者补足的是不同的窗口:取消可能无法立即中断在途 HTTP 请求,而提交校验也不会节省旧请求已经消耗的 token。
这与数据库里的“读到版本后,写入前确认版本仍有效”有相似之处。这里的版本语义限定在房间的触发消息,不应把它扩展为整个系统的分布式事务保证。
房间锁和存储锁分别保护什么
讨论执行使用房间粒度的协调锁,避免同一房间同时启动两套循环。持锁期间,Runtime 读取当前会话成员与轮数并推进讨论。不同房间通过锁分片降低不相关讨论互相阻塞的概率,但多个房间映射到同一锁槽时仍可能发生短暂串行。若要评估大规模房间吞吐,需要分别观察同房间排队与不同房间锁碰撞。
IM 存储还有自己的同步边界。消息追加前,存储层需要确认触发用户消息仍是该房间最后一条用户输入。检查与追加在同一个存储锁内完成,才能形成明确的线性化点:要么旧回复先于新消息提交,要么新消息已先到而旧回复被拒绝。若把检查提前到调用 Provider 之前,模型等待期间仍然可能有新消息插入。
可以把这段竞争写成两个可能的交错:
合法提交:检查 trigger=U1 → 追加 alpha 的回复 → U2 到达
拒绝迟到:U2 到达 → 检查最新 trigger=U2 → 拒绝 U1 的回复
不存在安全的交错是“检查发现 U1 仍最新 → 释放锁 → U2 写入 → 追加 U1 回复”。因此临界区应尽量只涵盖读最新 ID、比较和持久化追加,避免把慢速模型请求放入存储锁。较长的存储操作会增加其他消息与查询的等待。
取消标记和提交校验同样分工不同。标记尽力让旧推理在下一个安全检查点停止;原子追加检查即使取消尚未传播,也拦截触发 ID 已过期的结果。测试应该分别覆盖“取消生效”和“取消来不及生效但消息被拒绝”,否则只能证明其中一个窗口。
失败状态与会话隔离
某位角色发生网络错误时,整体讨论应报告未完整执行,并保留此前已产生的内容。已有回复具有参考价值,但不能据此认定全部角色完成了讨论。
多轮循环由 Runtime 显式管理,Agent 消息只负责入库和广播。若每条 Agent 回复都再次触发其他角色,就会形成递归调用链,使轮数、取消和成本难以统一控制。
独立角色会话保存各自的过程,房间记录负责交换意见。这种隔离需要明确共享范围;最近 128 条消息的窗口也意味着早期论据未必仍在输入中。长讨论需要保留来源和未解决问题,避免摘要掩盖证据缺口。
源码阅读基线:讨论循环与接管、房间持久化与条件追加。
多模型路由与故障的组合
一个群聊里的 Agent 可以使用不同模型配置。架构师也许走模型 A,评审走模型 B;这会增加讨论多样性,也会让故障来源更复杂。任务日志需要记录每次角色执行实际使用的 model/provider route,不能只在房间层保存一个默认模型名。
如果 alpha 调用成功、beta 在 provider authentication 失败,系统有一个重要选择:是否继续其他成员?当前实现遇到单个 Agent 失败会停止整次循环并报告 failed,同时保留已经提交的消息。这保证“每个预期成员都参与了”的声明不被部分执行冒充。若未来允许 best-effort,应在 API 状态中标记部分完成、失败成员和未执行轮次,而不是把它们纳入成功共识。
重试模型调用可能生成不同回答。若一次请求超时但服务端实际已生成结果,本地没有响应就重试,会消耗额外费用并可能重复外部工具动作。消息去重键可以帮助避免把同一次回复插入两遍,但不能追回模型费用或撤销已执行工具。最好让 Provider 请求与本地 invocation ID 关联,再按模型接口的幂等能力做有限重试。
群聊输出被转发到外部 IM channel 时又多一层副作用。数据库写入成功而 Telegram/其他渠道发送失败,应区分“房间消息已保存”和“渠道投递失败”;重试渠道发送可以按消息 ID 去重。当前渠道协议与任务执行状态需要分别处理,否则外部消息失败可能把原本已持久化的讨论误报为全失败。
消息存储与 UI 呈现不要共享一套身份假设
一个 Agent 回复在后端至少涉及三种身份:IM 数据中的 sender/type、运行 Agent 名称和前端展示的消息角色。UI 可以将多个 Agent 都渲染为 assistant,但仍应显示各自 sender 名称;如果只显示“助手”,用户就看不出是谁提问、谁回答。外部渠道可能没有独立 assistant subtype,因此转发文字里通常要带 Agent 名称。
时间线排序要依赖服务端消息 ID 或稳定时间戳,不应按 WebSocket 到达顺序排序。网络重连时客户端可能收到重复广播,最好以 message ID 做去重。若存储成功但广播丢失,用户刷新后通过历史查询补齐;若广播比数据库写入更早,刷新会出现消息消失。入库与事件顺序因此属于可见一致性设计。
删除房间、删除 Agent 和运行讨论是不同操作。运行中删除房间时,旧循环下一次读取 transcript 应观察到 room_unavailable 并终止;若只在任务开始时保存了完整房间对象,可能继续写入已删除实体。删除 Agent 时则要定义在途请求是否继续、后续成员是否失败。管理 API 应返回活动任务影响,避免 UI 以为删除立即停止执行。
一个容易忽略的成员配置变更问题
房间创建后,用户可能在成员正在发言时修改成员列表。最容易解释的行为是本轮使用开始时快照,下一条用户消息触发的新讨论才使用新成员;这样正在执行的 rounds 数、participants 和共识分母不会中途变化。若产品希望立即生效,则要在每轮读取配置版本,并定义被移除角色是否算退出、已投票是否仍有效。
成员配置要在 API 校验阶段去重并限制数量。若同一 Agent 重复出现,却仍被计入 participants,模型可能重复发言并需要重复投票;若 UI 列表去重而后端不去重,用户会看到人数和任务成本不一致。测试应对比提交的 member 数、实际调用数与状态 API 返回的 participants。
修改角色 prompt 也有类似问题。运行中的模型请求已经使用旧 prompt,不应把后来配置与旧 transcript 混合而不记录版本。对每次讨论保存 Agent 配置版本或启动时角色描述 hash,可以在事故复盘时还原输入。
5. 怎样证明讨论有效,而非只是变长?
先检查信息是否传播,再检查问题是否闭环;这两层需要不同的证据。
怎样证明角色确实互相回应
回归中最关键的断言不应只是“最后有六条 Agent 消息”。六条消息也可能是六段互不相关的介绍。应让 fixture 产生可识别的问答标记,并检查它们进入后续请求:
- alpha 第一轮给出假设 A,beta 必须在输入中看到 A。
- beta 输出问题 Q,alpha 第二轮的模型输入必须包含 Q。
- alpha 输出回答 R,beta 后续输入必须包含 R。
- 提交 U2 后,U1 的在途结果不得进入房间。
- 停止后不再追加新回复,整体状态与取消或失败一致。
这些断言把上下文传播与语言质量分开测量。fixture 验证前者;要评价后者,还需要真实模型、固定题集、人工标准,以及事实错误率、未回应问题数、结论依据等评价维度。
追踪一次讨论应该记录什么
如果用户只看到最终回复,难以分辨角色之间是否真的看到了彼此。调试视图至少应能关联这些信息:room ID、触发 user message ID、discussion round、agent 名、Agent 内部调用 ID、prompt 所包含的消息 ID 区间、结果提交或拒绝、任务终态与原因。保留整个 prompt 有隐私和存储成本,实际系统可选择只记录摘要、hash 和引用 ID,并在诊断模式下按用户选择导出脱敏数据。
时延应拆为排队、构造 transcript、Provider 等待、工具执行、回复持久化与广播。Agent A 的等待可能来自模型服务,Agent B 的慢可能来自大量上下文或工具失败;只有总耗时无法定位瓶颈。轮次也应使用单调时钟测量,系统时间校准或时区变化不应倒退运行时间。
对迟到回复最好记录明确 reason code,例如 new_message、room_deleted、cancelled、agent_failed 和 publish_failed。一条旧结果被丢弃并不一定是异常,可能是系统成功实现了“新消息接管”。将其全部记录为 provider failure 会让运维误判模型质量。
多轮交互测试的分层设计
第一层测试 transcript 构建:创建固定消息记录,断言每条消息的 sender、role、content、原始用户任务和排序。这里不需要启动 HTTP 服务,可以快速覆盖转义字符、中文、空消息和窗口截取边界。
第二层测试轮次逻辑:用确定性模型 fixture 记录每次输入,依次返回 A、Q、R、结束标记,断言 Q 出现在 alpha 后续输入,R 出现在 beta 后续输入;然后测讨论轮数为 1、3 和最大值,确认调用数与上限吻合。重复成员只发言一次时,也应核对参与者投票数是去重后的数量。
第三层测试消息竞争:fixture 在等待状态阻塞。测试先发送 U1 并等待第一个模型调用进入,再发送 U2,释放旧结果,检查房间不能出现与 U1 绑定的迟到回复;随后确认 U2 能正常启动新循环。该测试使用 barrier 等可观测事件,而不是固定睡眠,减少 CI 机器速度变化带来的偶发失败。
第四层通过真实 HTTP API 验证从创建 room、提交消息、观察轮次、取消,到查询最终任务的整条链路。API 测试覆盖序列化与状态传播,但其范围仍受 fixture 限制。真实 Provider 的兼容测试可以作为额外测试,不能替代稳定的确定性回归。
评价讨论时用“问题闭环”,不是总字数
可以把每个实质问题建成一条链:谁提出、谁回应、回答引用了什么证据、是否改变原结论、最终是否仍未解决。以“文件内容在读取过程中变化”为例,如果评审提问后架构师只复述分页逻辑,这条问题没有闭环;如果它说明 offset 无法证明单一版本,并提出 hash 或快照方案,则回答确实针对质疑。
一个人工评分表可以用 0/1/2 的 rubric:0 表示遗漏或答非所问,1 表示有回应但没有证据,2 表示具体回应并指出可验证证据。单独计分“是否识别假设”和“是否承认无法验证”。对同一道题跑多个模型/多个随机种子,报告评分分布与评审者一致度,而不只展示最漂亮的一次对话。
评价不应诱导角色为了得分而堆砌质疑。可以为“有效新信息”和“重复意见”分别标注,并统计单位 token 的新增风险或未解决问题。多轮次数增加不必然提高质量;若每轮没有新的问题与证据,应该缩短讨论或提示明确收敛。
不同学科的质量标准也会不同。代码评审重视可复现缺陷和具体行号,产品讨论可能重视用户影响和约束,故障分析重视时间线与证据来源。一个通用 rubric 需要领域特定子项,不能用“角色看起来同意”替代专家评价。
如何体验与验证
先通过界面或 /v1/agents 注册角色,再创建群聊:
{
"name": "架构评审",
"kind": "group",
"members": ["alpha", "beta"],
"discussion_rounds": 3
}
向 POST /v1/im/sessions/<id>/messages 提交话题即可开始。左侧显示角色和轮次,停止按钮调用 DELETE /v1/tasks/<task_id>。成员和轮次设置可一起修改,运行中的讨论使用启动时的设置快照。
回归用例用本地确定性模型实际检查:beta 的问题是否进入 alpha 第二轮输入;alpha 的回答是否进入 beta 的下一次输入;取消或接管后是否丢弃迟到结果;部分角色失败是否让整体如实失败。它验证交互管道,真实讨论深度仍取决于模型和问题。
6. 何时使用群聊,何时转入交付流程?
根据产物依赖和人的决策职责选择协作形式,再回顾整个讨论控制链。
群聊适合讨论,Flow 适合交付
讨论的主要产物是观点和问题,信息顺序有意义;Flow 的主要产物是有依赖的工作结果,先后边界有意义。用一个交付链路模拟自由讨论,会迫使用户把消息和结果声明成节点;反过来,用群聊保证“测试一定在修改之后执行”,也缺少强制依赖语义。
一个实用的混合流程可以先让架构师和评审讨论方案,随后由单独 Flow 执行“读取证据 → 修改文件 → 验证 → 总结”。讨论结束只负责形成待执行计划,Flow 节点的输入、依赖和失败状态负责交付保障。两阶段之间应由用户或可信规则明确批准实际修改范围,避免讨论中的任意文本直接扩大操作权限。
想比较设计时,可记录讨论中未解决问题数、下游工作重做次数和最终验证失败率,而不是只记录总消息数。若多轮讨论增加成本,却没有减少返工或发现更多有效风险,就需要缩短轮数、改变角色职责或改进问题模板。多 Agent 结构的价值应由任务结果证明。
人工、群聊和 DAG 如何组合
群聊并不一定要直接自动执行。可以把它设计成只读的方案审议阶段,输出候选决策、依据、未解决问题和风险;由用户选择一个版本后,再生成具有明确输入输出的 DAG。这样可以避免讨论中的争议文本直接触发 shell 或文件修改,同时保留 Agent 互相检查的价值。
DAG 的下游节点仍需读取讨论产物的结构字段,而不只是复制整段 transcript。一个审批节点可以检查“需用户确认”的状态,未确认时将部署节点标记 blocked。若后续执行结果与讨论假设不一致,再开新的讨论回合补充信息,形成“审议 → 执行 → 新证据 → 再评审”的受控循环。
这一组合需要更多状态和 UI 设计:讨论已结束不等于审批完成;审批完成不等于节点执行;Flow 部分成功也不等于全链路交付。明确状态有助于用户知道下一步由谁负责。产品不应将它们压缩成一个含糊的完成动画。
人类主持者仍然是有效的控制点
在高影响方案讨论中,可以把用户作为主持者:先阅读角色观点,标记需要补证据的问题,选择是否继续下一轮,再决定是否创建执行 Flow。让人类在讨论和副作用之间确认,可以减少模型间相互强化错误假设的风险。
UI 可以提供“继续讨论”“总结待确认点”和“把确认方案转成任务”三种操作。每种操作生成独立状态并保留来源消息 ID,保证下游执行能追溯到哪条用户确认。自动化程度由用户任务和权限配置决定,不应因 room 类型为 group 就默认授权所有 Agent 写文件或连接远端。
面试讲解时的技术主线
讲群聊功能,我会按“消息何时进入房间 → 下一位角色如何获得上下文 → 新消息如何接管 → 旧结果在哪里被拒绝 → 哪些测试覆盖这条时序”展开。这个顺序将产品体验映射到数据和同步边界,避免只展示两个不同的系统提示词。
面试官可能会问是否并行、如何处理无限循环、如何判定共识、旧回复怎样丢弃、用户新消息会不会取消其他房间。回答时应先说仓库当前实现,再说明边界和演进选项。比如当前每轮顺序发言、最多 5 轮;这提供明确上界,代价是延迟叠加;若并行化,角色只能读取轮初快照,且需要单独处理合并和提交顺序。
第二个常见追问是“所有 Agent 都同意是否代表方案对”。正确的技术边界是:结束标记只是对话退出协议,不是外部验证。要证明方案有效,仍需具体工具、测试、文件产物或人工批准。这个回答也说明为什么群聊与 Runtime 任务状态不能合并成一个“聊天成功”标志。
延伸阅读与实践顺序
完成本文后,建议选取“轮次、预算与收敛如何共同约束讨论?”中的一个分支,用固定输入记录预期行为、实际结果和差异。先验证一个具体不变量,再扩大任务规模;若结果不同,优先核对源码版本、配置与输入证据。
上一篇:Agent Runtime;下一篇:把执行顺序交给 DAG。如果你希望构建方案评审、设计讨论或故障分析的角色群,可以从这个实现开始实验。