告别单兵作战!Hermes 多 Agent 协作架构设计全解析
多 Agent 协作不是把几个模型连在一起就结束。真正难的地方在主 Agent:它要把目标拆成任务板,决定哪些信息可以共享,哪些信息只能进入某个子 Agent 的私有上下文,还要把结果回收到一个可复盘的全局状态里。
主从 Agent 协作的架构责任
单 Agent 长任务里,很多失败看起来像“上下文不够”或“模型不够强”。换成主从 Agent 以后,问题没有消失,只是变成了更清楚的工程责任:谁保留全局状态,谁看到哪些背景,谁判断结果可不可以进入下一步。
单 Agent 长任务的问题不只是上下文长短
一个 Agent 一直循环,最常见的不是“完全不会做”,而是中途开始混乱:一段上下文里有用户目标、工具日志、失败尝试、半成品结论、模型自我解释。后续步骤看到这些内容,很难判断哪些是事实,哪些只是临时推理。
单Agent 一轮对话的数据流程图:
单Agent,所有中间过程都进入同一条会话:
| 问题 | 单 Agent 里的表现 | 主从调度里的处理方式 |
|---|---|---|
| 上下文污染 | 所有中间过程都进入同一条会话 | 子 Agent 只返回公开结果,私有过程不回流 |
| 全局状态发散 | 任务做到哪一步主要靠模型“感觉” | 主 Agent 把状态写进任务板 |
| 完成证据不足 | 子任务说完成,但没有会话、结果和验收记录 | 回收 session_id、结果文本和状态变化 |
| 失败边界模糊 | 一个子问题失败后,主会话继续堆信息 | 失败留在具体任务卡上,主 Agent 决定重试或人工确认 |
主 Agent 调度多 Agent 的责任边界
共享的是公共任务状态和结果摘要;进入子 Agent 之前,主 Agent 会把任务卡裁剪成私有任务包。

主Agent: 全局任务信息管理,任务拆解,选择合适的承接子Agent分发任务信息,回收任务信息,决定下一步推进动作;
子Agent: 承接由主Agent输入的任务 + 任务信息用“恰当”的方式进行任务处理,确保对任务的输出能够反馈给主Agent。
两个公开案例给出的边界:
- OpenAI Codex:后台任务需要独立环境和可追踪证据。 OpenAI 在 Codex 发布说明里把 Codex 描述为可以并行处理多个任务的云端软件工程 Agent;每个任务独立运行在隔离环境里,完成后向用户提供变更、终端日志和测试输出等证据。这个案例说明:多任务不是共享一条巨大上下文,而是把任务、环境和证据分开管理。
- Hermes:子 Agent 隔离上下文,Kanban 承载跨 Agent 状态。 Hermes 的 Subagent Delegation 文档写得很直接:
- 子 Agent 从全新 conversation 开始,只知道父 Agent 放进
goal和context的内容; - Kanban 文档则把多 Agent 协作放到持久任务板里,任务、依赖、评论和结果都成为外部状态。
- 子 Agent 从全新 conversation 开始,只知道父 Agent 放进
这些案例不能推出“所有任务都应该多 Agent”。它们共同说明的是:一旦拆成多个执行者,系统就要明确状态、上下文、权限和证据的归属。
主从 Agent 方案
本文的任务板由主 Agent 根据业务目标动态生成:主 Agent 决定几张卡、每张卡叫什么、交给哪个 Codex worker、依赖关系怎么连。
宿主代码不补默认任务,也不替模型硬排三段流程;
它只做真实系统需要的护栏校验: 任务数量范围、子 Agent 白名单、task_id 格式、依赖存在性、DAG 无环和至少一条协作依赖边。
| 层 | 负责什么 | 不负责什么 |
|---|---|---|
| 主 Agent | 理解目标、动态生成任务板、回收结果、综合输出 | 不直接执行子任务,也不绕过结构护栏 |
| TriggerFlow | 保存 board、暴露阶段事件、关闭执行 |
不替模型做语义规划,也不保存私有推理过程 |
| 公共任务板 | 保存任务状态、依赖、会话证据和最终结果 | 不保存子 Agent 的完整私有过程 |
| 私有任务包 | 给某个子 Agent 的 goal + context + upstream_results |
不携带完整主会话和其他无关卡片细节 |
| ACP 边界 | 连接两个官方 Codex ACP worker,收流式 update | 不拆任务、不验收业务结果 |
| 子 Agent | 完成一张任务卡,返回可回收摘要 | 不管理全局计划 |
杭州三日游团建:从业务输⼊到可回收结果
杭州三日游团建案例里,主 Agent 先生成任务板,再把每张卡片裁剪成私有任务包,最后回收结果并综合给组织者。

ACP 简介:把本地 Agent 接成标准会话
主从 Agent 方案里,主 Agent 不应该知道 Codex、Claude Agent SDK、Gemini CLI 这些运行时各自怎么启动、怎么流式输出、怎么申请权限。它需要的是一个更窄的连接形状:能握手、能建会话、能发 prompt、能收到 update、能保留 session_id。
ACP 连接本地 Codex:主 Agent 只需要⾯对标准会话
ACP 在这里像一段标准连接线。Codex 仍然在本机运行,认证、模型请求和工具事件仍由 Codex 自己处理;主 Agent 侧只面对 ACP 的 initialize -> new_session(新会话) -> prompt(单次请求) -> session/update(对会话进行补充) 生命周期。

ACP 只统一连接,不替代主 Agent 编排
换成工程视角,ACP 解决的是“怎么和一个 Agent 说话”,不是“怎么决定让谁做什么”。这点一旦分清,后面的代码就不会混乱:
| 层次 | ACP 负责 | 主 Agent / Harness 负责 |
|---|---|---|
| 连接 | 启动 Agent 进程,完成 initialize |
选择要调用哪个 Agent |
| 会话 | 创建 session_id,承载一轮或多轮 prompt |
决定一个任务卡是否复用同一会话 |
| 输出 | 接收 session/update、文本增量、停止原因 |
把公开结果写回任务板 |
| 权限 | 把工具权限请求转给 Client | 采用允许、拒绝、人工审批等策略 |
| 编排 | 不负责 DAG、重试、验收和综合 | 管任务板、依赖、失败边界和最终交付 |
这也是为什么完整实战里主 Agent 仍然要维护 TaskCard、WorkerTaskInput 和 TriggerFlow state。ACP 只是把本地 Codex 接进来,不会替主 Agent 生成任务板。
连接本地 Codex 的最简案例
先看一段最小代码。它只做一件事:启动官方 Codex ACP adapter,建立一个 session,发一轮很短的 prompt,然后打印回收文本和 session_id。这段代码不引入任务板,也不做上下文裁剪,目的只是把 ACP 生命周期跑清楚。
# ===== ACP 最简连接:启动官方 Codex adapter,发送一轮 prompt =====
import json
import os
import shutil
from pathlib import Path
from typing import Any, cast
from acp import PROTOCOL_VERSION, Client, spawn_agent_process, text_block
from acp.schema import (
ClientCapabilities,
DeniedOutcome,
Implementation,
PermissionOption,
ReadTextFileResponse,
RequestPermissionResponse,
ToolCallUpdate,
)
# Notebook kernel 的 PATH 不一定包含 Codex.app 内置命令。
# 这里把 Codex.app 的资源目录补进子进程环境,npx 启动 adapter 时也能找到本地 Codex。
INTRO_LESSON_DIR = Path.cwd()
if not (INTRO_LESSON_DIR / "materials" / "product_goal.txt").is_file():
INTRO_LESSON_DIR = Path("harness_class/lessons/Multi_Agent_Orchestration").resolve()
INTRO_ACP_ENV = os.environ.copy()
INTRO_CODEX_BIN_DIR = Path("/Applications/Codex.app/Contents/Resources")
if INTRO_CODEX_BIN_DIR.is_dir():
INTRO_ACP_ENV["PATH"] = f"{INTRO_CODEX_BIN_DIR}:{INTRO_ACP_ENV.get('PATH', '')}"
# 像MCP一样进行连接,这里直接连接codex的ACP
INTRO_CODEX_SPEC = {
"name": "codex",
"command": os.getenv("ACP_NPX_COMMAND", "npx"),
"args": ("-y", "@agentclientprotocol/codex-acp@1.1.0"),
}
if shutil.which(str(INTRO_CODEX_SPEC["command"]), path=INTRO_ACP_ENV.get("PATH")) is None:
raise RuntimeError("当前 Notebook kernel 找不到 npx,请从已配置 Node 的 shell 启动 Jupyter kernel。")
print(INTRO_CODEX_SPEC)
最小 Client 只做三件事:收文本、拒绝副作用权限、暴露最终文本。这里故意不写复杂封装,后面的完整实战会在这个基础上增加流式转发、任务板回写和多轮会话。
这里必须按照这样写,要不然会与codex的ACP Adapter对不上,导致没办法正常使用。
# ===== ACP 最小 Client:收文本,拒绝副作用权限 =====
class IntroCodexClient:
def __init__(self) -> None:
self.text_chunks: list[str] = []
self.update_kinds: list[str] = []
# 会话的消息更新
async def session_update(self, session_id: str, update: Any, **kwargs: Any) -> None:
update_kind = str(getattr(update, "session_update", ""))
self.update_kinds.append(update_kind)
content = getattr(update, "content", None)
text = getattr(content, "text", None)
if update_kind == "agent_message_chunk" and getattr(content, "type", None) == "text" and isinstance(text, str):
self.text_chunks.append(text)
# 是否请求的权限
async def request_permission(
self,
options: list[PermissionOption],
session_id: str,
tool_call: ToolCallUpdate,
**kwargs: Any,
) -> RequestPermissionResponse:
# 这个最简案例只验证连接,不允许子 Agent 读写文件或执行命令。
return RequestPermissionResponse(outcome=DeniedOutcome(outcome="cancelled"))
async def write_text_file(self, **kwargs: Any) -> None:
raise RuntimeError("最简 ACP Client 没有声明文件写入能力")
async def read_text_file(self, **kwargs: Any) -> ReadTextFileResponse:
raise RuntimeError("最简 ACP Client 没有声明文件读取能力")
@property
def text(self) -> str:
return "".join(self.text_chunks).strip()
运行这段代码时,spawn_agent_process() 会启动 npx -y @agentclientprotocol/codex-acp@1.1.0。initialize() 交换协议版本和客户端信息;new_session() 生成会话;prompt() 把文本任务发给同一个会话。
# ===== ACP 最小运行:initialize -> new_session -> prompt =====
async def run_intro_codex_prompt(prompt_text: str) -> dict[str, object]:
# 这是第二节的client代码
client = IntroCodexClient()
# 这是第一节的代码,连接codex的acp
command = str(INTRO_CODEX_SPEC["command"])
args = cast(tuple[str, ...], INTRO_CODEX_SPEC["args"])
# 创建一个agent连接进程
async with spawn_agent_process(
cast(Client, client),
command,
*args,
cwd=INTRO_LESSON_DIR,
env=INTRO_ACP_ENV,
) as (connection, _process):
# 初始化连接
init = await connection.initialize(
protocol_version=PROTOCOL_VERSION,
client_capabilities=ClientCapabilities(),
client_info=Implementation(name="course-acp-intro", title="ACP 简介最小 Client", version="0.1.0"),
)
# 从连接中创建一个新的session
session = await connection.new_session(cwd=str(INTRO_LESSON_DIR), mcp_servers=[])
# 向指定的session id发送新的指令
response = await connection.prompt(
prompt=[text_block(prompt_text)],
session_id=session.session_id,
)
agent_info = init.agent_info.name if init.agent_info is not None else "(未提供 agentInfo)"
return {
"agent_info": agent_info,
"session_id": session.session_id,
"stop_reason": str(response.stop_reason),
"update_kinds": sorted(set(client.update_kinds)),
"text": client.text,
}
intro_codex_result = await run_intro_codex_prompt(
"只回复 ACP_CODEX_OK。不要读取文件,不要运行命令,不要修改文件。"
)
print(json.dumps(intro_codex_result, ensure_ascii=False, indent=2))
PS:这段最小代码已经具备 ACP 的核心动作,但它还不是多 Agent 协作。它没有任务拆解,没有依赖顺序,没有上下文裁剪,也没有结果验收。后面的完整实战会把这段连接能力放进主 Agent 的任务板调度里。
Spec dict 示例与验证状态
ACP Registry 里登记了很多可用 Agent,但“有启动形状”不等于“本机已经可用”。AgentSpec 只描述主 Agent 怎么启动一个外部执行单元;真实可用还取决于 CLI 是否安装、是否完成登录、是否有模型额度。
先看官方 adapter 或 Registry 里有明确启动形状的 Agent。这类可以写成比较稳定的 Spec dict 示例:
| Agent | 来源 | Spec dict 示例 |
|---|---|---|
| Codex | ACP Registry / Codex adapter | {"name": "codex", "command": "npx", "args": ("-y", "@agentclientprotocol/codex-acp@1.1.0")} |
| Claude Agent | ACP Registry / Claude adapter | {"name": "claude_agent", "command": "npx", "args": ("-y", "@agentclientprotocol/claude-agent-acp@0.55.0")} |
| Gemini CLI | ACP Registry | {"name": "gemini", "command": "npx", "args": ("-y", "@google/gemini-cli@0.49.0", "--acp")} |
| Qoder CLI | ACP Registry | {"name": "qoder", "command": "npx", "args": ("-y", "@qoder-ai/qodercli@0.2.14", "--acp")} |
还有一些 Agent 支持 ACP,但启动参数依赖具体网关、配置文件、安装方式或运行环境。这里不写成可复制 Spec,只给官方文档入口:
| Agent | 官方文档 | 课程口径 |
|---|---|---|
| OpenClaw | OpenClaw ACP 文档 | Gateway-backed bridge,通常需要配置网关地址、token、session 等参数 |
| Hermes Agent | Hermes ACP 文档 | 支持 hermes acp、hermes-acp、python -m acp_adapter 等方式,按安装方式选择 |
| Cursor | Cursor ACP 文档 | 需要本机安装 Cursor CLI |
本文完整实战只使用两个 Codex profile,是为了让课堂案例可以真实跑通,同时保留“两个独立子 Agent 执行域”的效果。其它 Agent 适合课后按文档逐个接入和验证。
ACP 和 A2A:服务内调用和跨边界协作不是一回事
同样是“Agent 之间通信”,工程场景其实差很多。
主 Agent 在同一个服务模块里维护任务板,知道全局目标、依赖关系、上下文裁剪规则和最终验收方式。它只是需要把某张任务卡交给本地 Codex worker 执行,再把结果写回任务板。
这个 worker 不需要主动发现别的 Agent,也不需要和 HR、财务、供应商系统协商任务;它只要接收 prompt、输出文本、返回 session/update。
这类场景更像“服务内部的主从调度”。主 Agent 是调度中心,子 Agent 是执行单元。通信要轻、要直接、要容易观察。普通函数、内部 API、消息队列都可以承担服务内模块通信;如果执行单元本身是一个 coding agent 进程,ACP 就很贴合:启动进程、建 session、发 prompt、收流式 update。
A2A 面向的是另一类场景。比如团建任务继续往后推进,可能会出现这些独立系统:
- HR Agent:确认人数、部门、员工偏好和审批规则;
- 财务 Agent:确认预算、报销标准和付款限制;
- 供应商 Agent:提供酒店、餐饮、车辆报价;
- 法务 Agent:检查合同条款和风险边界。
这些 Agent 可能来自不同团队、不同 SaaS、不同供应商,彼此不共享进程、数据库、记忆和工具。它们需要先知道“对方能做什么”,再交换任务、产物和状态,同时保留各自系统边界。A2A 的设计重点更接近这种跨服务、跨团队,甚至公开市场里的 Agent 发现与协作。
用手机APP来说,就像美团点外卖付款,会跳转到支付宝APP进行支付,支付完成后,支付宝会把结果告知美团。
| 问题 | 服务模块内主从调度 | 跨服务 / 跨团队 Agent 协作 |
|---|---|---|
| 谁掌握全局任务状态 | 主 Agent / Harness | 多个独立 Agent 各管一部分 |
| 子 Agent 是否需要被公开发现 | 不需要,主 Agent 已经知道调用谁 | 需要,外部 Agent 要能看到能力描述 |
| 通信对象 | 本地 worker、coding agent、内部执行单元 | SaaS Agent、企业系统 Agent、供应商 Agent |
| 关注重点 | 轻量连接、上下文裁剪、流式输出、结果回收 | 能力发现、身份边界、任务协商、产物交换 |
| 更贴近的协议 | ACP 或内部 API / Queue | A2A |
所以不用 A2A,不是因为 A2A 没价值,而是因为它不是为这种服务模块内的轻量通讯设计的。
把 A2A 放到项目内,需要引入 agent discovery、capability card、远程服务边界、任务协商等概念,反而遮住主线:主 Agent 怎样拆任务、裁剪上下文、调度本地 worker、回收结果。
海外实际落地也基本沿着这个分工展开。
- ACP 更集中在开发者工具和 coding agent 接入:Zed、JetBrains 通过 Registry 发现和安装 coding agents,Registry 里能看到 Claude Agent、Codex CLI、Gemini CLI、GitHub Copilot、Goose、OpenCode、Qwen Code 等代表项目。
- A2A 更偏企业协作网络:Google ADK / A2A samples、LangGraph 或 BeeAI agent 暴露成 A2A server,以及 Salesforce、SAP、ServiceNow、Workday、UiPath 这类企业软件或自动化平台围绕跨系统 Agent 协作做互操作。
简而言之,主 Agent 调本地 Codex worker,用 ACP;未来如果要让 HR Agent、财务 Agent、供应商 Agent、法务 Agent 跨系统互相发现能力并交换任务,就用 A2A。
实战:用主 Agent 骨架跑通调度 Loop
步骤1. 先创建主 Agent 骨架
主 Agent 在这节课里由两部分组成:Agently 负责模型请求,TriggerFlow 负责流程编排和执行状态。先把骨架搭出来,后面的任务板、ACP 和综合报告都挂到这两个对象上。
业务输入只保留用户目标
业务目标只描述杭州三日游团建本身;调度规则属于主 Agent 代码,不写进用户需求。

下面代码,还没有 ACP,也没有子 Agent。主 Agent 的第一项责任是拥有一个稳定的“模型请求入口”和一个稳定的“流程状态入口”。

步骤 2. 定义任务板、私有输入和主 Agent 决策
主 Agent 要管理的不是一段聊天记录,而是一张公共任务板。每张卡片只保存可共享状态;真正发给子 Agent 的内容会裁剪成私有任务包。
三类数据契约划清协作边界TaskCard 是公共状态,WorkerTaskInput 是私有输入,WorkerResult 是回收结果。
先定义三类数据。TaskCard 留在公共任务板上,WorkerTaskInput 发给单个子 Agent,WorkerResult 是回收到主 Agent 的公开结果。

decide_board_action() 是主 Agent 判断任务是否完成、是否继续推进、是否重试、是否阻塞的⼊⼝。它只读公共任务板,不依赖 ACP,也不依赖⼦ Agent的私有过程。

有了数据结构以后,再定义主 Agent 怎么“读任务板”。这一步还不调用模型,也不调用 ACP,只做可解释的状态判断。

调度判断之外,还需要几种状态更新动作。它们都只改当前任务卡,不碰其它卡片的私有信息。

步骤 3. 主 Agent 动态生成公共任务板
杭州三日游团建不是固定三步流程。不同组织者可能更关注预算、交通、活动、天气或安全,主 Agent 应该根据业务目标自己决定拆成几张卡、哪些卡先做、哪些卡依赖上游结果。
主 Agent 动态⽣成任务板
模型负责真实拆解;宿主代码只负责把越界输出挡在执行边界之外。

任务板生成分三步: 先确认主 Agent 能调用哪些 worker,再让模型返回动态任务板,最后做结构护栏校验。
这里没有备用任务板;模型没有给出可执行任务板时,程序应该失败并暴露原因,而不是悄悄换成预置流程。

边界确认后,再让主 Agent 真实拆解任务。输出契约仍然保持简单:一组 TaskCard 建议:每张卡说明局部目标、可见上下文、负责的 worker 和依赖关系。

模型给出的动态任务板可以直接体现业务判断,但进入执行前还要过一层护栏。
注意这里的校验只处理结构问题: 有没有字段、worker 是否存在、依赖是否可执行;
它不把业务拆解替换成预设答案。

现在真正跑一次建板。观察重点不是模型写得多漂亮,而是公共任务板是否能支持后续调度。

此时还没有调用任何子 Agent。主 Agent 先把杭州三日游团建目标变成可跟踪的公共状态,再判断第一张 ready 卡片。
步骤 4. 引入 ACP,把私有任务包发给官方子 Agent
现在主 Agent 已经知道下一张卡片是谁、依赖是否满足、失败后是否该重试。接下来才需要 ACP:把一张裁剪后的 WorkerTaskInput 发给本地 Codex 官方连接器,并回收公开文本结果。
ACP的位置:连接外部子Agent,不接管业务编排
ACP 负责连接、会话和流式 update;任务拆解和完成判断仍属于主 Agent。

进入 ACP 前,先把“主 Agent 可以调谁”写清楚。这里两个执行者都走官方 Codex ACP 连接器,但主 Agent 用不同 profile 承担不同任务。

注册表解决“能调谁”,私有任务包解决“给它看什么”。 这一步最能体现上下文隔离。

先不急着调用 ACP。把第一张 ready 卡片裁剪出来,直接看子 Agent 会收到哪些信息。

任务包确认以后,再引入 ACP。
ACP 只负责连接和会话事件,不负责判断任务是否该派发。

ACP 的时序从 Client 开始。这个 Client 不自建子 Agent,只负责接收官方连接器推来的 update,并把公开文本整理出来。

Client 负责接 update;
Session 负责完整连接生命周期:启动连接器、初始化、建会话、发送 prompt。

正式派发业务任务前,先用最小 prompt 做一次连接 smoke。这样能把“连接问题”和“业务调度问题”分开排查。

ACP 会话可以多轮,但主 Agent 不会把完整主会话倒给子 Agent。是否追问、重试或收尾,仍由任务板状态和 decide_board_action() 决定。
步骤 5. 用 TriggerFlow 串起主 Agent Loop
到这里,主 Agent 已经具备四个能力:生成任务板、观察任务板、调用外部子 Agent、更新任务状态。现在把它们放进 TriggerFlow 执行里,让完整过程可观察、可关闭、可复盘。
TriggerFlow的chunk/when/execution结构:
这段代码按 TriggerFlow 的原生结构展开:
@master_flow.chunk(...)定义阶段;master_flow.when(...).to(...)说明阶段关系;create_execution()启动一次可观察的执行。
长任务的等待体验不靠设置更长的等待上限来解决。
主 Agent 要把每个子 Agent 的文本增量转成 worker_delta 事件,让外部观察者持续看到“哪张卡正在推进、子 Agent 已经说了什么”。

TriggerFlow 接管的是执行过程里的公共状态。先定义三个状态工具函数:读任务板、写任务板、追加 worker 输入/结果记录。后面的 chunk 都只通过这些函数读写 state["board"],避免每个代码块各自拼装状态结构。

第一个 chunk 只做建板。它把 ProjectBrief 交给主 Agent,让模型真实决定任务数量、任务内容、worker 分配和依赖关系,然后把公共任务板写入 TriggerFlow state。

第二个 chunk 是主 Agent Loop 的核心:读任务板、选 ready 卡、裁剪私有任务包、调用 worker、回写结果。这里的循环是业务调度循环,TriggerFlow 负责承载它的状态、事件和执行边界。

第三个 chunk 不再调用子 Agent。它只读取已完成任务板,把多个子 Agent 的公开结果综合成组织者可读的报告。这样最终交付仍由主 Agent 负责,而不是把某个子 Agent 的局部回答直接交出去。

三个 chunk 定义完以后,再用 TriggerFlow 的 to() 和 when() 装配流程。这里不把编排藏进 Python 控制流里:
master_flow.to(plan_board):execution 启动后先建任务板;master_flow.when(plan_board).to(dispatch_tasks):建板完成后进入**调度;master_flow.when(dispatch_tasks).to(synthesize_report):所有任务回收后综合报告。

最后运行完整案例。这里会同时看到 TriggerFlow 阶段事件和 Codex worker 的文本增量。TriggerFlow 的 runtime stream 不是模型输出流;它输出的是本次执行里的业务事件,所以我们用 get_async_runtime_stream(timeout=None) 持续观察到 execution close。

TriggerFlow 的结构现在分成四层:
@master_flow.chunk(...)声明阶段;state["board"]保存跨阶段事实;master_flow.when(...).to(...)描述阶段触发关系;execution.get_async_runtime_stream(timeout=None)暴露可观察事件。
dispatch_tasks 进入时读取一次 board,循环内用当前局部 board 推进任务,并在每次 running / done / failed 后写回 state。runtime stream 会持续输出 board_created、task_started、worker_delta、task_done、dispatch_done、final_report_ready。
这里的重点不是给长任务设一个更久的等待时间,而是让执行过程持续产生日志级别的业务事件。
步骤 6. 检查子 Agent 实际收到的信息
完整运行结束后,检查 worker_inputs 最能说明上下文隔离。下游卡片只能看到自己显式依赖的上游公开结果摘要,看不到完整主会话,也看不到其它卡片的私有过程。
派发前裁剪上下文:
公共任务板里的全局信息不会原样传给子 Agent;主 Agent 只发当前任务需要的子集。

完整运行之后再回看 worker_inputs,能验证上下文隔离是不是实际发生了,而不是只停留在设计图上。

最后把 Notebook 的运行结果保存成 JSON。这个文件用于对照观察,不是主流程依赖。

主从 Agent 协作总结
主从 Agent 协作的重点不是“多几个 Agent”。真正的工程收益来自几条清楚的边界:
- 主 Agent 管全局任务状态,不靠对话里的感觉记进度;
- 子 Agent 只拿私有任务包,不继承完整主会话;
decide_board_action()让继续、重试、阻塞、结束都有状态依据;- ACP 负责连接和会话,不承担业务编排;
- TriggerFlow state 承载主 Agent Loop,让阶段、事件和最终状态可观察;
- 最终交付由主 Agent 读任务板后综合,而不是把某个子 Agent 的回答直接当最终答案。
更多推荐


所有评论(0)