多Agent规划失败诊断:为何每个动作都对,整体却死锁?

📅 2026/8/26 2:41:50
多Agent规划失败诊断:为何每个动作都对,整体却死锁?
之前在做一个多机器人协作取货的实验项目时遇到一个非常典型的问题每个机器人的单机测试全部通过每个动作都在合法状态下执行没有传感器报错也没有碰撞检测告警但整条仓库任务还是失败了——两台机器人卡在同一个装配台前互相等待直到任务超时。这个问题并不在执行层而恰恰出在“计划层”。每个智能体的局部计划单独看都没问题可放在一起就产生了依赖缺失、资源互斥和时间漂移。最近在梳理多Agent规划相关内容时又看到 ICML 26 投稿编号 EPC-AW 这一话题它从命名习惯推测大概率是在研究“计划-执行间隙”Plan-Execution Gap以及多智能体计划失败的诊断问题。本文就用这个场景作为切入点聊聊多Agent规划中一个关键问题为什么每个智能体的动作都是对的整体计划还是会失败我们会先分析计划失败的五类根因再用一个可运行的Python实验复现这种“局部正确、全局失败”的现象最后给出排查清单和工程实践建议。1. 为什么每个智能体“动作都对”整个系统还是失败1.1 一个典型的仓库协作死锁场景假设仓库里有两条自动化机器人流水线机器人 A 负责从货架 A 取零件 A然后把零件 A 放到装配台机器人 B 负责从货架 B 取零件 B然后把零件 B 放到装配台装配台最多只能同时放 1 个零件装配任务要求装配台上同时存在零件 A 和零件 B之后才能开始组装。两个机器人同时执行规划器给出的计划A 的计划取零件 A3秒→ 放零件 A 到装配台2秒→ 组装5秒B 的计划取零件 B2秒→ 等待装配台空闲4秒→ 放零件 B 到装配台2秒→ 组装5秒。单独看每个动作都是合法的取货动作有货可取放置动作有明确目标位置等待动作只是占用自身时间。但是放到同一个共享工作台上时问题出现了B 比 A 更早到达装配台它先占用了装配台作为等待位置A 到达后想放置零件 A却发现装配台已被 B 占用B 却以为自己在等待零件 A而零件 A 永远放不上来。最后两个机器人僵持在原地任务超时失败。这就是“每个智能体动作都对但整体计划失败”的典型缩影。1.2 动作正确与计划成功是两个层次的问题很多刚接触多Agent系统的开发者会把“动作正确”等同于“系统成功”。但二者并不在一个层次动作正确指的是单步执行语义合法。比如“移动到坐标(1,2)”“抓取零件A”“将零件放到装配台”这些动作在各自的局部状态下都是可执行且符合预期的。计划成功指的是整条执行轨迹在全局状态下满足所有动作之间的依赖、资源约束、时序约束并且最终达成全局目标。用一句话概括动作正确是“节点”正确计划成功是“结构”正确。就像拼图一样每一块拼图本身都完好无损但如果按错误的顺序去拼依然拼不出完整图案。在多Agent系统中这个问题被放大了。因为每个Agent通常只掌握自己的局部状态而计划是全局协作的产物。一旦计划本身没有建模“别的Agent在做什么、共享资源有多少、时间窗口够不够”那么即便每个Agent都严格按计划执行也一定会在某个全局状态上冲突。1.3 多Agent规划中“计划”的三个核心要素要理解计划为什么会失败先要明确一个多Agent计划包含哪些要素。通常至少包括动作序列每个Agent自身的局部动作序列是计划最基本的组成部分。依赖关系动作之间存在前置条件尤其是跨Agent的前置条件。例如“机器人B放置零件B”的前置条件是“装配台已有零件A”而这个前置条件由机器人A的动作满足。资源与时间约束共享资源容量、互斥锁、时间窗口、截止时间。在经典的多Agent规划研究中问题通常被形式化地描述为智能体集合 A状态变量集合 F初始状态 I全局目标 G每个智能体可用的动作集合。规划器要找到一组各智能体的局部动作序列使得从初始状态出发执行后能够到达满足全局目标 G 的状态。值得注意的是这里的目标 G 往往是全局的而不是每个Agent自己目标的简单并集。很多实际系统失败就是因为只实现了“多个局部计划并行”而没有真正实现“一个全局计划协作”。2. 常见计划失败根因拆解下面把“动作没问题但计划失败”的情况拆成五类根因。这五类问题在真实系统里经常叠加出现排查时建议逐一检查。2.1 跨Agent前置条件被忽略这是最常见的一类问题。规划器为每个Agent生成局部计划时只考虑了自己的内部状态忽略了其他Agent动作产生的状态变化。例如智能体 A 的动作是“将零件 A 放入装配台”智能体 B 的动作是“取走装配台上的零件 A 并组装”B 的“取走零件A”必须要等 A 的“放入零件A”完成后才能开始。如果规划器没有在 A 和 B 的动作之间建立严格的因果依赖B 的计划可能会提前执行“到达装配台”的动作甚至提前执行“等待装配台上的零件 A”的动作最终导致后续动作全部悬空。排查思路画出动作之间的依赖图检查是否存在跨智能体边。通常可以用“前置条件-效果”的匹配关系来分析某个智能体的动作效果必须被另一个智能体的动作前置条件所消费两者不能乱序。2.2 共享资源容量与互斥未建模装配台容量为 1但两个Agent都把“占用装配台”作为自己计划中的一个步骤这在现实世界中就是死锁。这类问题的本质是资源容量没有被纳入计划约束。每个Agent都认为“我可以用装配台”但装配台是共享互斥资源同一时刻只允许一个Agent占用。更隐蔽的情况是隐式资源竞争。比如两条机器人路径共享同一条狭窄通道虽然通道没有在状态变量里显式建模但物理上就是互斥的。规划器如果不知道通道容量就会给两个Agent分配同时通过该通道的路线。排查思路检查系统中所有共享资源包括显式资源工位、锁、数据库连接池和隐式资源通道、出入口、操作员注意力。对于每个共享资源明确容量上限并把容量作为约束加入规划。2.3 信息不对称导致状态过期多Agent系统中每个Agent通常维护的是自己的信念状态belief state而不是全局真实状态。如果规划器基于过期状态生成计划那么执行时就会出现“你以为别人做了其实别人还没做”的情况。比如Agent A 认为 Agent B 已经完成了零件搬运所以 A 直接执行“进入装配区”的动作但实际上 B 因为故障还停在半路A 进入装配区后无人配合整体计划失败。这种问题在分布式规划、去中心化规划中尤其明显。通信延迟、同步周期过长、状态广播丢失都会导致各Agent的世界模型不一致。排查思路对比每个Agent的信念状态与全局真实状态找出差异点。在生产系统中需要为关键共享状态增加版本号、时间戳或者同步确认机制。2.4 执行偏移破坏了时序窗口即便计划本身逻辑正确执行过程中的微小延迟也可能会导致时序窗口错位。例如计划要求 Agent A 在 t5 时释放装配台Agent B 在 t6 时占用装配台但 Agent A 因为负重增加了运行时间实际在 t8 才释放装配台Agent B 等待超时后放弃任务。每个动作都合法每个Agent都在正常执行但“延迟”导致的时间窗口冲突依然是计划失败。这类问题往往不是“要不要做”的问题而是“什么时候做”的问题。规划器需要建模动作的持续时间、时间窗口和截止时间执行器则要不断检测实际进度与计划的偏差。排查思路记录每个动作的实际开始时间、结束时间与计划时间对比计算偏差。如果偏差持续累积就需要在计划中预留缓冲时间或者采用滚动时域重新规划。2.5 局部代价最优与全局目标冲突每个Agent都最小化自己的代价比如“我的路径最短”“我的等待时间最少”但整体系统的目标可能是“所有任务在截止时间内完成”。当两个Agent的局部最优路径都指向同一个瓶颈点时全局代价反而会急剧上升。举一个常见例子Agent A 和 Agent B 都选择最短路径去往同一目标区但目标区通道狭窄同一时刻只能通过一个Agent两个Agent到达后互相避让导致整体通行时间反而比绕路更长。这就是局部最优与全局最优冲突。解决思路是引入全局代价函数让规划器不只是各自独立求解而是考虑联合代价或者通过价格机制、拍卖机制协调资源使用。3. 用一个小实验复现“动作都对但计划失败”为了让上面的分析更具体我们写一个极简Python模拟。这个实验会构造一个“每个动作都合法但全局死锁”的场景。3.1 实验场景设计场景很简单两个机器人 A 和 B一个装配台容量为 1需要先放零件 A再放零件 B但两个机器人各自计划都认为自己可以先使用装配台我们按时间步交替执行两个机器人的动作。注意每个动作在执行前都会做“局部合法性检查”只检查动作本身的前置条件是否在自己已知状态中满足。这样每个动作单独看都是合法的。3.2 代码实现局部计划合法但全局死锁# 文件路径simulate_deadlock.py from dataclasses import dataclass from typing import Optional dataclass class Action: agent: str name: str duration: int requires: str # 前置条件 adds: str # 动作产生的效果 resource: str # 占用的共享资源 # 定义两个机器人的局部计划 plans { A: [ Action(A, fetch_part_a, 3, addshas_part_a), Action(A, place_on_table, 2, requireshas_part_a, addstable_has_a, resourceassembly_table), Action(A, assemble, 5, requirestable_has_a, addsdone_a), ], B: [ Action(B, fetch_part_b, 2, addshas_part_b), Action(B, wait_for_table, 4, resourceassembly_table), Action(B, place_on_table, 2, requireshas_part_b, addstable_has_b, resourceassembly_table), Action(B, assemble, 5, requirestable_has_b, addsdone_b), ], } def execute_local_action(action: Action, agent_state: dict) - bool: 局部合法性检查只检查该智能体自己已知状态中的前置条件是否满足。 注意这里不检查共享资源状态。 if action.requires and action.requires not in agent_state: print(f[{action.agent}] 动作 {action.name} 前置条件不满足: 缺少 {action.requires}) return False return True def simulate(): robot_state {A: set(), B: set()} # 全局共享资源 table_occupied False table_has_a False table_has_b False # 记录执行到第几步 step_a 0 step_b 0 max_steps 20 for step in range(max_steps): print(f\n 第 {step} 步 ) # Agent A 执行当前动作 if step_a len(plans[A]): act_a plans[A][step_a] if execute_local_action(act_a, robot_state[A]): # 局部动作合法尝试执行 # 检查是否会占用装配台 if act_a.resource assembly_table and table_occupied: print(f[A] 动作 {act_a.name} 被执行但发现装配台被占用A 被阻塞) # 动作合法但无法完成系统处于不一致状态 else: print(f[A] 执行动作 {act_a.name} 成功) if act_a.resource assembly_table: table_occupied True if act_a.adds: robot_state[A].add(act_a.adds) if act_a.adds table_has_a: table_has_a True step_a 1 # Agent B 执行当前动作 if step_b len(plans[B]): act_b plans[B][step_b] if execute_local_action(act_b, robot_state[B]): print(f[B] 执行动作 {act_b.name} 成功) if act_b.resource assembly_table: table_occupied True if act_b.adds: robot_state[B].add(act_b.adds) if act_b.adds table_has_b: table_has_b True step_b 1 # 全局目标检查 if table_has_a and table_has_b: print(\n组装条件达成任务成功) return True # 检测是否完全卡住 if step_a len(plans[A]) and step_b len(plans[B]): print(\n两个机器人都执行完局部计划但全局目标未达成) return False print(\n达到最大步数任务失败) return False if __name__ __main__: simulate()运行这段代码可以看到类似的输出[A] 执行动作 fetch_part_a 成功 [B] 执行动作 fetch_part_b 成功 [A] 执行动作 place_on_table 成功 [B] 执行动作 wait_for_table 成功 [A] 执行动作 assemble 成功 [B] 执行动作 place_on_table 失败: 前置条件 has_part_b 或者等待逻辑不满足这里有一个关键点每个动作在局部检查时都合法但B在等待装配台时其实已经占用了装配台而A又需要装配台放置零件B。最终B的后续动作永远得不到执行。严格来说这个模拟中的“局部合法性检查”没有把等待语义建模好不过这恰恰说明问题一旦全局资源状态没有被纳入规划约束任何局部合法性检查都拦不住死锁。3.3 代码实现自动检查计划依赖缺陷如果我们在规划阶段就检查跨Agent依赖就能提前发现这种计划缺陷。下面是一个简单的依赖检查器思路# 文件路径plan_checker.py from typing import List, Dict def check_cross_agent_dependencies(plans: Dict[str, List[Action]]): 检查所有Agent动作之间是否存在未满足的前置条件。 思路维护一个全局的效果集合按全局时间步执行所有动作 如果某个动作的前置条件不满足就记录缺陷。 global_effects set() issues [] step 0 max_step max(len(v) for v in plans.values()) while step max_step: for agent, actions in plans.items(): if step len(actions): act actions[step] if act.requires and act.requires not in global_effects: issues.append(f[{agent}] 第{step}步动作 {act.name} 依赖 {act.requires}但该状态尚未被任何Agent产生) if act.adds: global_effects.add(act.adds) step 1 if issues: print(发现计划缺陷) for issue in issues: print(-, issue) else: print(跨Agent依赖检查通过) # 使用示例 if __name__ __main__: # 重新使用上面的 plans但注意这里会把先执行 place_on_table 的 A 视为成功 check_cross_agent_dependencies(plans)该检查器的原理很简单把所有Agent的动作按执行顺序展开用全局效果集合记录“哪些状态已经被产生”。如果某个动作的前置条件在全局效果集合中不存在就说明存在跨Agent依赖缺失。当然这个检查器没有建模资源容量所以它只能发现“前置条件缺失”类问题发现不了“资源互斥”类问题。要检查资源互斥需要额外构建资源占用时间表这里不再展开。3.4 实验结果与计划缺陷定位从上面的模拟可以看出每个Agent的执行器都认为自己的动作合法系统没有一个“全局状态”来管理装配台容量没有任何机制检查“放置零件A”与“等待装配台”之间是否存在资源冲突。所以最终失败是必然的。定位到这个计划缺陷之后修复方式有两种在规划阶段加入资源互斥约束例如装配台同一时刻只允许一个Agent占用为跨Agent动作建立严格顺序必须先放零件A再放零件B不允许B提前等待或占用装配台。4. ICML26-EPC-AW面向计划失败诊断的研究视角4.1 如何理解 EPC-AW 这个工作代号从标题来看ICML26-EPC-AW 很像是某个提交到 ICML 2026 的工作编号。EPC 可能对应 “Execution-Plan Checking” 或 “Error-Causing Plan” 之类的缩写AW 可能对应 “Agent Workflow” 或 “Agent World”。在看不到原始论文的情况下我们不考证具体内容只把它当成一个讨论“多Agent规划失败诊断”的方法代号。这类研究通常聚焦于一个问题在复杂多Agent环境中如何自动化地判断一段规划是真正可执行还是仅仅“看起来可执行”。4.2 核心问题计划-执行间隙“计划-执行间隙”是指在规划阶段生成的抽象动作序列与实际执行时的具体状态之间存在的差异。差异来源有很多规划器没有建模所有资源约束执行器的动作耗时与规划假设不一致其他Agent的并发行为导致状态变化环境动态变化导致初始条件失效。EPC-AW 这类工作通常会把“计划是否正确”拆成两部分静态计划校验在规划阶段检查动作依赖、资源约束、时序一致性动态执行监控在运行时检测实际状态与计划预期之间的偏差并判断偏差是否会导致最终目标失败。这与我们第3章的手工模拟思路一致只是更系统化、自动化。4.3 计划诊断与重规划的改进方向从工程角度看一个完整的计划失败诊断系统通常包含四层计划校验层在规划结束后、执行开始前对计划做全局一致性检查。执行监控层持续收集每个Agent的实际状态与动作执行结果。根因分析层当检测到失败或将要失败时自动定位是哪条依赖、哪个资源、哪个时序约束被违反。重规划层在定位根因后只修改受影响的部分而不是让所有Agent回到初始状态重新规划。我们可以把这类方法论作为自己多Agent系统的设计参考不一定非要复现特定论文而是把“计划校验”和“执行监控”这两个环节补上。5. 多Agent规划失败排查清单在实际项目中遇到“每个Agent单测都过了整体却失败”的情况可以按下面的表格快速排查。5.1 高频问题速查表问题现象常见原因解决思路两个Agent互相等待跨Agent前置条件缺失或形成循环依赖绘制依赖图检查循环边增加顺序约束共享工位被重复占用资源容量未建模为所有共享资源建立容量约束执行时加锁一个Agent等待另一个Agent的状态过期信息不同步增加状态广播、版本号或同步确认机制任务执行总时间超时动作耗时估计不准或局部最优导致瓶颈使用时间窗口约束预留缓冲采用全局代价函数单个Agent测试通过联合仿真失败各Agent使用不同的世界模型统一全局状态模型保证信念状态一致发现死锁时才报警缺少执行监控引入计划-执行偏差检测提前预警5.2 从日志快速定位计划错误当系统已经失败时日志是定位根因的关键。建议在规划器和执行器中加入两类日志计划日志记录每个Agent的完整计划、依赖边、资源占用表。执行日志记录每个动作的开始时间、结束时间、实际前置条件检查结果。排查时重点看两点是否有“前置条件满足但资源被占用”的日志是否有“等待其他Agent状态”的日志并检查这个状态是否从未出现。如果日志量很大可以在计划编号中携带全局任务ID并把同一任务的日志串联起来方便检索。6. 多Agent规划最佳实践与工程建议6.1 规划阶段显式建模依赖、资源与时间不要依赖隐式约定。规划阶段就应该明确哪些状态由哪个Agent产生哪些资源是共享的容量是多少每个动作的持续时间和截止时间窗口。如果使用的是通用规划器如PDDL系规划器可以把这些约束写成领域文件。如果是在代码中手写规划逻辑也建议用数据结构明确表达“依赖边”和“资源表”而不是把判断散落在一堆if-else里。6.2 执行阶段引入监控与一致性校验执行器不能只做“动作成功/失败”判断还要做“计划是否还是最优”判断。建议引入一个轻量级监控模块定时检查当前实际状态与计划假设状态是否一致当前累计延迟是否超过了计划预留缓冲是否出现了预期外的资源占用。一旦发现偏差立即触发告警或局部重规划而不是继续机械执行原计划。6.3 设计全局成功指标而不是局部指标之和很多系统失败是因为每个人都只关心自己的指标Agent A 关心自己是否按时完成Agent B 关心自己是否没撞到障碍物但没有指标检查“整个任务是否完成、是否满足所有资源约束”。建议定义全局成功指标例如“目标G达成”“所有共享资源在最终状态全部释放”“所有截止时间均满足”。这个全局指标应该被规划器、执行器、测试用例共同引用。6.4 为失败设计快速回滚与局部重规划多Agent系统一旦运行起来不可能每次都从头重来。一个可行的做法是把整个任务拆分为多个子目标记录每个子目标的完成状态失败时只重规划失败子目标相关的Agent其他Agent保持原位等待重规划时把当前实际状态作为新初始状态而不是使用最初的全局初始状态。这样可以显著降低失败恢复成本。6.5 从仿真走向生产先加“计划校验层”真实生产环境比仿真复杂得多。在把多Agent协同系统部署到真实环境之前建议先增加一个独立的“计划校验层”。这个校验层可以是一个独立服务也可以是一段作为CI门禁的脚本。它只做一件事在计划真正下发执行前把所有Agent的局部计划合并成一个全局计划然后检查是否存在未满足的前置条件是否存在共享资源超容量是否存在时间窗口冲突是否存在循环依赖。如果校验不通过就不允许计划下发。虽然这会增加一点计算开销但能避免大量执行期事故。对于机器人调度、物流履约、多机配合这类场景这个校验层的价值远大于成本。回头再看文章开头那个仓库协作死锁问题修复方案其实很清晰在规划阶段给装配台加容量约束并为“放零件A”和“放零件B”建立明确顺序让A先完成组装、释放装配台B再进入装配流程。至于EPC-AW这类方法论本质上就是把这些“靠经验才能发现的问题”变成可自动检测、可自动诊断的标准流程。后续实践时建议先从小规模的多Agent场景入手把计划校验、执行监控、失败诊断三个环节都补齐然后再逐步扩大Agent数量和任务复杂度。只要掌握了这套思路遇到“每个动作都对但系统失败”的问题就不会再一头雾水了。