多 Agent 系统真实问题 · 首期 · 阅读系列导读 →
2026 年 8 月,我们调查 Flowness 的能力接入情况时,遇到一个很难用“功能有没有完成”回答的问题:一个检查服务时限的命令已经写好,调用它的代码也已经进入仓库,负责告警的后台服务正在运行,可是这项检查没有生效。
沿着部署副本往下查,原因才清楚。仓库里的告警脚本已经增加了调用,服务器实际运行的脚本却是更新前的版本。那份部署文件比相关代码提交早了约 15 分钟,缺少新增检查。服务的“正在运行”是真的,命令的“已经实现”也是真的,两条事实合起来,仍不能证明这项能力正在工作。
这是我们在多 Agent 协作中遇到的一类基础问题:系统能列出自己拥有的组件,却不一定能说明某个角色在当前环境里究竟能完成什么。新来的 Agent 据此做计划,维护者据此判断系统健康,都会受到影响。
Flowness 是我们自己开发的 Harness 项目,用来组织多个 AI Agent 持续协作,管理任务、上下文、工具调用与工作记录。这篇文章从当时的能力调查出发,讨论这种“自我认识”缺了哪些信息,以及为什么再写一份功能清单仍不够。
功能说明容易准确,接入关系更容易失真
在上述案例中,命令的帮助文字把告警程序列为消费方。仅阅读这段说明,Agent 很容易形成一个合理预期:告警程序会周期性调用命令,发现超时后报告。
实际调查要查清的是另一组事实:调用代码在哪个版本里,部署到哪里,哪个进程正在运行它,以及它读到的数据是不是最近的。原调查发现,相关状态记录已经满足当时的超时条件,但自动告警链没有部署到位;状态文件此后也没有继续更新。维护者看到一个活着的服务,无法从服务状态判断其中这条检查是否真的在推进。
这类差异在抽查中并非只有一处。调查挑选了 20 项能力,对照说明与实现、接入点和运行痕迹,发现 8 项存在部分偏移。其中 4 项都涉及集成关系:说明写着某个工作流会调用它、某段代码会填写字段,实际检查却没有找到相应接入,或发现部署副本没有带上这段代码。这个有目的的抽查不能用来估算全系统的偏移率,却提示我们应当把“谁使用它”单独作为调查对象。
写一个命令的 Agent,通常容易描述这个命令收到输入后做什么。它未必同时控制调用者、发布流程与后续会话的上下文。如果把预期集成写成现状,下一位 Agent 又把说明当成环境事实,尚未落实的关系就会被继承为已经具备的能力。
因此,我们这里说的能力自我模型,并不涉及模型是否有主观上的自我意识。它指的是系统对自身能力的一份可查询表征:实现是什么,谁能到达它,在哪个环境和版本下可用,需要什么条件,可以用什么结果核对。它必须能回答一次具体行动的问题。
盘点先暴露了一个更基本的问题:我们在数什么
当时的旧清单记录了 72 个命令入口。调查沿源码解析命令结构,得到 31 个独立的顶层命令,以及 41 个命令组;继续展开命令组,里面还有 209 个子命令。最终可执行的末端命令共有 240 个。
两个总数分别回答不同的问题。72 是顶层导航入口数,240 是具体动作的入口数。真正影响调查的是,之前的孤立能力筛查停在顶层,没有逐一检查组内的动作。一个命令组出现在工作流中,不能证明组内每个命令都接入了工作。
调查随后把末端命令与生产入口、历史会话记录交叉比较。初筛中,197 项找到了生产面的接入点,34 项只有会话中的手动调用痕迹,9 项在两侧都没有找到。生产入口包括技能说明、钩子、脚本和系统服务;历史调用则需要分辨真实执行、帮助输出和讨论中的文字提及。
这组分类提供了继续核查的顺序,并没有把 240 个命令逐个运行一遍。后续深查还修正了初筛:前面那个服务时限命令被分到“只有手动调用”,进一步查出仓库其实已有调用代码,只是没有进入部署。这种中间状态无法用“有”或“没有”稳定表达。
还有一些工具本来就应该由人按需调用,恢复功能也可能长期没有触发机会。频繁调用并不是能力完整的标准。需要核对的是,系统声称它应当生效的场景出现时,是否存在一条可执行的路径。
调查 Agent 也不知道能力从哪里进入系统
为了找出缺少激活入口的技能,调查者先使用关键词搜索。但“设计”“修复”“审查”这样的通用词会出现在许多无关文本中,搜索命中无法说明技能被调用。收紧匹配规则后,调查得到了“68 个技能中,39 个没有激活通道”的结果。
这个判断很快被调查者自己派出的核查 Agent 推翻。39 项里包含一项独立核验技能,而启动核验会话的代码映射已经明确登记了它。搜索检查了文档引用,却没有检查这类代码注册。
加入代码映射后,待核数量降到 16。接着,核查者又发现性能诊断技能已经被工作命令文件点名,只是原句把技能名写在“skill”一词前面,搜索正则要求的顺序相反。补查这类入口后,数量降到 14。原调查仍将 14 保留为上界,因为其他自然语言表述还可能漏检。
这段过程说明,能力表征的难点也存在于维护它的工具里。系统实际通过多种方式激活技能:文档之间的引用、命令源码、项目说明、会话派发映射,以及斜杠命令文件。调查者没有掌握入口的种类,就会把“检查器没有识别”写成“系统没有接入”。
增加一个盘点 Agent,并不能自动获得完整的系统视角。盘点本身需要可检查的覆盖范围,还需要保留能推翻结论的路径。否则,一份看起来更全面的新清单,只会更有把握地重复旧清单的遗漏。
事件能证明发生过什么,但先要证明它在记录什么
与分散的调用入口相比,统一事件账本似乎提供了更简单的核对办法。代码声明了事件类型,历史账本记录了运行中发出的事件,两者可以取差集。
这次调查一度出现过“有 47 类从未发射”和“有 20 类从未发射”两个结果。独立复核发现,前一个扫描没有覆盖最早一段账本;后一个计算混入了不同命名空间的名字,不能与当前事件枚举直接相减。统一历史范围并核对类型后,订正结果是:150 种定义中,有 45 种没有在本次完整历史核查中找到出现痕迹。
这里仍需要继续问,每一种事件是否本应被触发,以及相应能力是否一定会产生这种事件。原调查还发现,一个高频调用的咨询命令并不写自己的专属账本事件,结果留在终端输出、文件或短期缓存中。只看事件账本,便不能重建它的使用情况。反过来,一条“已登记某能力”的事件,也不能充当该能力已经运行的证据。
同一轮调查还找到另一种漂移:事件枚举的注释写着 71 种,实际定义已有 150 种,相关测试只检查定义数量不少于 127。这个下界检查允许继续增加类型,不会因注释过期而失败。它检验的是定义数量,完全没有检验这些定义是否被使用。
这些细节会决定系统怎样理解自己。计数、类型名、事件身份与时间范围只要有一处错位,一个看似精确的数字就可能回答了另一个问题。给能力建立运行证据,首先要建立能力与证据之间明确的对应关系。
能力查询应当返回一条可以执行和核对的关系
这些案例使我们更清楚地看到,一份能力清单至少要区分几个状态:实现已存在,某个角色有入口,当前部署带有这条入口,在特定条件下已经成功执行。一个状态可以为另一个提供线索,却不能代替它。
例如,Agent 查询服务时限检查时,真正有用的结果应当说明:检查的对象是什么,由谁触发,当前部署是否包含调用,最近一次检查发生在什么时候。如果某一环还没有证据,查询需要保留这项缺口,而不是把整个能力压缩成一个绿色状态。
这也带来一个维护问题。能力表不能只是另一份靠人记得修改的文档。定义变更、调用者迁移、角色权限变化、部署更新,都可能使原来的答案过期。哪些变化能自动检测,哪些需要人判断,哪些证据值得持续保存,应当与能力的风险和使用方式一起设计。把所有历史记录塞给每个 Agent,会把发现能力的成本转交给执行者,也不能保证它找到了正确关系。
这轮调查完成了末端命令枚举、接入点与调用痕迹交叉检查,以及事件口径订正。它让我们对当时系统的认识更准确;持续维护能力表征的效果,还需要另外验证。
一个有区分力的验证,可以从新接手的 Agent 开始:给它一个需要使用既有能力的任务,观察它能否找到正确入口、识别前置条件,并用返回结果判断动作是否完成。再改变部署版本或撤掉一条入口,看能力查询是否随之失效或提示变化。这样测到的才是能力表征对实际决策的帮助,而不只是表格是否填满。
还有一个尚未解决的取舍:越全面的核对,维护成本越高;核对越少,系统越可能把过期关系交给下一位执行者。我们希望进一步找到的是,在持续变化的多 Agent 系统里,哪些关系必须保持新鲜,才能让已有能力被可靠地使用。