Agent协作:工具面
一个agent与别的agent交互的全部工具,按四个域摆开:计划、执行、实体、通讯。
这页说明每个工具做什么、哪个会话能操作哪些任务、两个预算怎么限制一条链的规模。
设计文档:docs/reference/design/runtime/agent-collaboration.md。
八个参考实现在同样八个维度上怎么做,见Agent协作:八家实现对照。
01四域词汇表
四个域各有一个名词。名词分清楚了,工具名就不用记:每个域的工具都在操作它自己那个名词。
计划
todo
写下来的计划
手写的计划清单。只记录打算做什么,不对应任何在跑的东西,改它不会让任何agent运行。
todo_createtodo_updatetodo_list
执行
task
正在运行的任务
派出去正在跑的任务:一个task_id、一个状态、一份结果。它是任务列表里的条目,不是执行任务的那个实体。
list_taskstask_outputtask_stop
实体
agent
执行任务的实体
新建一个、给已有的派任务、列出所有agent、把干完的归档。一个agent就是会话DAG里的一条分支。
agentlist_agentsarchive_agent
通讯
message
发消息,和读历史
发一条消息过去,不产生任务记录;或者直接读任意一条分支的全文,不触发对方运行。
send_messageread_conversation
最容易混的两组:todo和task同是"任务"两个字,但一个是写下来的计划、一个是正在运行的执行记录,所以计划清单用todo_*前缀,
执行侧用list_tasks。task和agent也不是一回事:停掉一个task只是停那一轮运行,
agent本体还在agent列表里;把agent归档是archive_agent。
02一个agent能做的四件事
新建agent、派任务、发消息、查询。前两件产生task_id并记入任务列表,第三件不产生,第四件只读。
怎么读:四条横道从左边的"我"出发。新建和派任务都走agent这一个工具,区别只在有没有to=:
没有to=就新建一条分支,有to=就把任务交给已经存在的那条。两者都记一条任务,拿回一个task_id。
发消息走send_message,消息送达即结束,不产生task_id。查询四个工具全是只读的。
颜色:绿=创建实体 · 橙=受管任务和任务记录 · 蓝=消息 · 青=只读。虚线=异步回流。
03消息和任务的区别:消息不产生任务记录,任务可追踪可取消
同样是"把内容送到另一条分支上跑一轮",消息和任务给出的承诺完全不同。差别就写在收件方第一眼看到的那行回执头里。
| 承诺 | 消息 send_message | 任务 agent() / agent(to=) |
| 谁能发起 | 任何agent,对任何未归档的分支 | 同样任何agent,但产生的task_id只有自己能操作 |
| 有没有task_id | 没有,只回一个delivery_id告诉你送到了 | 有task_id,记入任务列表,list_tasks能查 |
| 对方必须回吗 | 不必。回执头明说"没什么要补充就别回" | 必须。这一轮的最终回复就是任务结果 |
| 能不能取消 | 不能。消息已经送达,无法撤回 | task_stop,排队中的直接撤回,在跑的停那一轮 |
| 取消会不会级联 | 不涉及 | 级联:停一个任务,它派生的任务全停 |
| 能操作别人的任务吗 | 不涉及,消息发出后没有后续操作 | 不能。task_output/task_stop有归属检查 |
| 多方并发安不安全 | 安全。不产生任务记录,最坏的结果是对方不回复 | 安全,且有记录:目标忙就排队,不会被打断 |
归档一个agent本体是另一件事,任何会话都能做:归档不中断在跑的工作、也不删数据,
所以archive_agent不做task_stop那样的归属检查。见下一节。
04两个预算:两个计数器,各管一件事
一条链是一次用户轮次产生的全部调用。消息数每跳加一,回复回流也算一跳;代数只有创建新agent时加一。读回结果花的是消息,不花代数,所以派一批活、看结果、再派一批走得通。
两个都支持设0,0就是这条线不存在,什么也不拦。
# 命令行改,即时生效
openprogram config set agent.max_spawn_depth 1 # 默认:主agent创建worker,worker自己完成工作
openprogram config set agent.max_spawn_depth 2 # worker可以再创建一层,第三层被拒
openprogram config set agent.max_spawn_depth 0 # 不限层数
openprogram config set agent.max_messages 8 # 默认:一条链最多传8跳
openprogram config set agent.max_messages 0 # 不限条数,agent之间可以一直互发
openprogram config set agent.max_spawn_fanout 8 # 默认:一轮最多创建8个agent
openprogram config set agent.max_spawn_fanout 0 # 一轮想创建多少个都行
设置页的Agent分组里是同样的数字框:Max spawn depth、Max messages per chain、Max spawn fan-out,
改完即时生效,落盘在~/.openprogram/config.json的agent段。
全设0之后,防失控就只剩并发上限(OPENPROGRAM_TASK_WORKERS,默认4)、
每轮50次工具调用的上限和用户自己按停。
扇出预算不在上面那两个链上计数器里。派生把计数交给孩子、自己那份不动,
所以链上计数器数不到兄弟。扇出按(会话,轮次)单独计,只拒绝不摘工具:
工具清单在轮次开始时就冻结了,这个数在轮次里才花掉。
回流那一跳两个计数走不同方向。消息数从孩子那边接着走,A↔B来回才会停;
代数退回派发方的计数,读结果不算创建,协调者读完第一波结果还能派第二波。
有一条规则不受预算影响:自己发给自己一律拒。to=指向发起方当前这条分支是直接的环,立刻拒掉。
05归档:停止接收投递,历史保留
分支在会话DAG里永远留着,fork、回放、读全文都要用它。所以归档不是删除,只是在分支上写一个archived标记。
06典型场景走一遍
三条最常走的路。同一批工具,组合方式不同。
场景一 · 派一批任务出去
手上有五件互不依赖的事。先写成计划,再一件件派出去,最后回来更新计划。
01 计划
todo_create(subject=…)
五件事写成五条。blocked_by标出谁等谁。
02 派任务
agent(prompt, description, run_in_background=true)
逐条派。后台形式立刻返回task_id,不占我这一轮。
03 盯
list_tasks()
五个task_id五个状态。要结果就task_output,要取消就task_stop。
04 收
结果自动回流
不用轮询也行。哪个跑完,它的结果就接到我这条会话后面,我下一轮读到。
05 更新计划
todo_update(todo_id, status="completed")
计划状态和实际执行一致。
场景二 · 找一个已经存在的agent
这件事已经有agent做过一半,不需要再创建一个从零开始的。先查agent列表,再决定是发消息还是正式派任务。
01 查agent列表
list_agents(scope="all")
按会话分组。每条分支给名字、一个能直接用的to="SID:HEAD"、轮数和大致字数、还有末尾预览。
02a 发消息
send_message(to="名字", message=…)
问一句、通知一声、把结论同步过去。不产生task_id,对方可以不回复。
02b 派任务
agent(to="名字", prompt=…)
这件事确实由它做,而且我要结果。产生task_id,可取消,结果必回。
03 忙就排队
目标在跑就进收件箱
不打断也不丢弃。它这一轮结束后就接着跑我这条。
场景三 · 回看一段旧会话
想知道上次那件事到底怎么做成的,但不想触发任何agent运行,也不想把整段历史全部读进上下文。
01 拿地址
list_agents(scope="all")
同一份agent列表。它标出的轮数和字数可以先判断体量。
02 只看结尾
read_conversation(session_id="SID:HEAD", start_turn=-10)
负数从末尾数,-10就是最后十轮。list_agents给的SID:HEAD整串粘进去也认。
03 要不要工具细节
include_function_calls=false
默认连每轮调了什么工具、参数、结果都给。只想看对话就关掉。
04 控体量
max_chars=60000
超了就丢后面的轮次。归档的分支也照读,读取不触发任何agent运行。
07和Claude Code的对应
同名的地方是有意对齐的,差别只有一处命名冲突和三个我们额外提供的工具。
| 这边 | Claude Code | 关系 |
agent | 同名 | 同义。创建新agent的唯一入口;to=派任务是我们这边的扩展 |
list_agents | 同名 | 同义。看得见对方是通信的前提 |
send_message | 同名 | 同义。跨分支发消息 |
task_output | TaskOutput | 参数形状照抄:block默认true,timeout毫秒,默认30000,上限600000 |
task_stop | 同名 | 同义。我们额外加了归属检查和级联取消 |
todo_create / todo_update / todo_list | TaskList |
同物不同名。Claude Code的TaskList是计划清单,和我们的list_tasks撞名不撞义,
所以我们的计划清单一律走todo_*前缀,把list_tasks这个名字留给真正在运行的任务 |
list_tasks | 没有 | 我们多出来的:让模型自己查后台正在跑的任务,不必一直保存派生时返回的task_id |
archive_agent | 没有 | 我们多出来的:agent不归档,agent列表就会一直堆积早就做完的worker |
read_conversation | 没有 | 我们多出来的:一条分支渲染成可读的文字稿,带轮次范围和体量上限,不用直接读原始记录文件 |
一句话收尾:四个名词分四个域,agent一个工具管新建和派任务,消息不产生任务记录所以任何agent都能发,
任务可追踪可取消所以只能操作自己派出的,两个预算限制一条链的规模,归档只停止后续投递。