摘要:任务分完只是开始,执行中镇民会遇阻、退出、误判,团队散伙往往就在这些瞬间。联合意图理论给出答案:共同目标上的承诺必须互知(相互信念),且终止承诺必须通知队友。本节拆解 Joint Intentions 与 SharedPlans 两大流派,给出联合承诺状态机与团队重组的代码实验。
三个镇民说好一起修桥:木匠备料、铁匠打钉、石匠砌墩。开工次日石匠病倒,却谁也没告诉——木匠铁匠照计划运料施工,月底桥墩荒在那里,木料泡了雨。这场事故里没有人偷懒、没有人违约私约,坏的只是承诺没有对齐。上一节解决了"谁干什么",这一节解决更隐蔽的一层:一群各自有 BDI 的镇民,凭什么算一个"团队"?本节同时收编旧教程里"团队协作模型"一讲的全部内容:角色分配、相互信念、失败重组,都在联合意图的框架下讲。
给每个镇民塞一个"修桥"的个体意图,不够吗?不够,麻烦出在四处。搭便车:反正别人会干,我歇会儿。协调错拍:都以为对方会先动,结果都在等。承诺不可见:我不知道你是否还在坚持,也不敢贸然退出或加码。终止不同步:一方判定目标无望默默散场,另一方还在投入。个体意图是私人状态,团队却需要公共的承诺——这就是联合意图理论要造的东西。
科恩与莱韦斯克 用"意图"这支笔给团队下了定义:一个团队是这样一个集体——成员对共同目标持有相互信念(mutual belief,"我知道你知道我们都知道"的递归式公共知识),并对联合行动做出联合承诺:承诺维持到目标达成、被判不可行、或被判不再值得,三者之一出现为止。联合承诺的核心条款是终止通知义务:一旦某成员相信目标已达成、无望或已无意义,他有义务让全队形成"我们知道目标状态变了"的相互信念,然后才能脱身。开头的修桥悲剧,坏就坏在石匠违反了通知义务。
状态机的关键是"重新考虑"与"终止通知"两个阀门:前者在状态变化时停下来对表,后者保证没有人在队友不知情时离场。
格罗斯与克劳斯嫌联合意图"太整块":真实团队的计划常常是残缺的——知道要修桥,还没定谁备料。SharedPlans 的答案是部分计划:团队对行动持有部分共享计划,配两种承诺——对"怎么做"的部分(选配方子计划,比如确定备料方式)与对"做的时候好好做"的全组承诺。计划像拼图一样逐层细化,不需要开工前就集齐;这更贴近真实团队的渐进式组队。两大流派后来在 STEAM 这类系统里合流:用联合意图管承诺与通知,用分层计划管任务结构,机器人足球与空战模拟里都用过这套内核。
相互信念听着玄,工程上就是"让 everybody 知道 everybody 知道"。最朴素的造法是广播加确认:发起者广播提案,每个成员回执,发起者再广播"全员已回执"——三步之后相互信念落成,消息量与人数成线性关系。STEAM 的贡献是算了这笔账并给出选择性通信:只有当队友的决策"依赖"这条信息时才广播(他不知道就会做错,才值得花邮票),否则省下。团队协作的通信预算思维由此而来。
把状态机写成代码。两个镇民组队运木料,中途一个发现木料被洪水冲走(目标无望),按协议触发终止通知;对照组则"默默退出",看损失。
class TeamMember: """团队成员:持有个体信念与对联合目标的承诺。""" def __init__(self, name): self.name = name self.believes = {} # 私有信念 self.committed = False self.inbox = [] def send(self, peer, msg): peer.inbox.append(msg) def perceive(self, fact): self.believes.update(fact) # 关键规则:状态变化触及联合目标 -> 必须通知 if self.committed and ("goal_done" in fact or "goal_hopeless" in fact): return ("notify_status", fact) return None class JointTeam: def __init__(self, members, goal): self.members = members self.goal = goal self.state = "forming" def form_commitment(self): """公开认领:相互信念的最小实现(全员在场喊一嗓子)。""" for m in self.members: m.committed = True self.state = "committed" return f"全员认领目标 {self.goal}" def run(self, events): log = [] for actor_name, fact in events: actor = next(m for m in self.members if m.name == actor_name) reaction = actor.perceive(fact) if reaction and reaction[0] == "notify_status": # 终止通知义务:让全员都知道状态变了 for peer in self.members: if peer is not actor: actor.send(peer, ("goal_status", fact)) self.state = "terminated_notified" log.append(f"{actor.name} 通知全队: {list(fact)[0]}") else: log.append(f"{actor.name} 继续执行") return log team = JointTeam([TeamMember("carpenter"), TeamMember("smith")], "运木料") print(team.form_commitment()) for line in team.run([("carpenter", {"rain": True}), ("smith", {"goal_hopeless": "木料被冲走"})]): print(" ", line) print("团队状态:", team.state)
对照组只要把 notify_status 分支删掉——史密斯默默回家,木匠在雨里白等,正是修桥事故的翻版。联合意图的全部工程价值,浓缩在"状态变了必须喊"这一条上。
通知到位只是止损,团队还要重组。角色表挂在团队上(不挂在个人身上,这是第 3 章角色概念的复用),成员失效时把它的角色拍卖给余下成员。
ROLES = {"scout": "侦察火点", "pumper": "接管水泵", "relay": "传话补水"} class FireTeam: def __init__(self, assignment): self.assignment = assignment # 角色 -> 成员名 self.alive = set(assignment.values()) def member_down(self, who): self.alive.discard(who) orphan = [r for r, m in self.assignment.items() if m == who] for r in orphan: # 失效成员的角色重新竞标 cands = sorted(self.alive) if not cands: return f"无人可接 {r},请求外援" self.assignment[r] = cands[0] # 简化:空闲序最前者接任 return self.assignment team = FireTeam({"scout": "alice", "pumper": "bob", "relay": "cara"}) print("初始:", team.assignment) print("bob 倒下 ->", team.member_down("bob")) print("cara 也倒下 ->", team.member_down("cara"))
角色重组让团队"人走茶不凉"。注意第二次重组后 alice 身兼侦察与传话——现实中要查负载与技能匹配,那正是第 4.1 节派活机制的回头客:团队模型的运行时,就是一部不断重跑分配算法的历史。
⚠️ 常见坑:把联合意图实现成"全员心跳广播"——每人每轮向全队报告全部状态。相互信念的造价随人数平方膨胀,团队规模一大通信就被心跳吃光。正确姿势是 STEAM 式的选择性通信:只在"队友不知道会做错"时才发。
💡 关键直觉:联合意图不是让镇民变得高尚,而是让退出有成本、散场有秩序。经济学的话讲,它把"随时可以静默跑路"改成"跑路要打招呼";博弈论的话讲(第 6 章),它把一次性博弈拉长成有声誉记录的重复博弈。
联合意图家族的收益:团队承诺可见(敢投入)、终止有序(不空等)、失败可重组(不散伙)。代价:相互信念的广播开销、承诺刚性(环境剧变时要走"重新考虑"流程,反应变慢)、以及规模瓶颈——成员太多时"互知"的通信与维护成本失控,分层团队(小队内互知、小队间代表互知)是工程上的解。学到第 7 章会发现,多智能体强化学习里的"团队信用分配"问题,本质上就是联合意图的学习版:怎么让每个镇民从团队回报里看见自己的贡献。
修桥队重整旗鼓:立了公开承诺板,谁退出先喊一嗓子;角色表挂在队部,病倒的石匠把砌墩角色传给了学徒。木料保住了,桥在月底通了。市政厅的三大问题还剩最后一个:任务没法分给个人——排一张全镇的集日表,每个摊位的选择都牵着别人的选择,这种"牵一发动全身"的难题,下一节请出分布式约束优化来解。