多 Agent 编排
octos 可以同时运行多个 Agent,形态分为三种。按由谁拥有这份工作以及你何时需要结果来选择:
| 模型 | 入口 | 归属 | 结果 | 生命周期 |
|---|---|---|---|---|
| 子 Agent | spawn 工具 | 当前轮次的子代 | 在本轮内返回(同步),或作为新的入站消息返回(后台) | 随轮次结束而销毁 |
| 对等 Agent(peer) | peer_handoff 工具 | 独立自治的会话 | 异步返回,通过文件 | 存活;断连时关闭 |
| 流水线(pipeline) | run_pipeline 工具 | DOT 图工作流 | 结构化的多节点结果 | 每次运行 |
子 Agent 是函数调用;对等 Agent 是独立的协作者。本页其余部分聚焦于对等 Agent —— 持久、分离的 Agent。关于 spawn 子 Agent 的内部机制,参见架构 → 子 Agent 与对等 Agent。
什么是对等 Agent
**对等 Agent(peer)**是一个自治的会话,拥有自己持久化的简报(brief)、工作区和生命周期,独立于创建它的那一轮运行。与子 Agent(当前轮次的子代、结果内联返回)不同,对等 Agent:
- 收到一份自包含的简报,且看不到发起它的会话;
- 运行自己的
session/open+turn/start生命周期; - 在轮次结束后继续存活 —— 没有逐轮自动关闭;只有在其客户端断开连接时才关闭;
- 每一轮都在
peers/<slug>/下写出持久化的result.md(外加带版本号的result-N.md和turns.txt索引),因此其产出可审阅、可 diff、可重新拉起。
当工作应当独立存在时使用对等 Agent —— 一次并行调研、一个长时任务、一个你会去查看的第二 Agent。当本轮需要结果来继续推理时,使用子 Agent(spawn)。
对等 Agent 工具
Agent 通过五个工具与对等 Agent 交互,覆盖完整生命周期 —— 创建(peer_handoff)、引导(peer_send_input)、读取(peer_gather)、列举(peer_list)、关闭(peer_close)。它们仅在网关/serve 运行时可用(一次性的 octos chat 中不可用)。
peer_handoff —— 创建对等 Agent
把工作从当前会话中提升为一个新的自治对等 Agent。
- 参数:
brief(必填,≤ 64 KB —— 对等 Agent 的全部上下文,必须自包含)、title(可选,用于生成 slug)、worktree(可选布尔 —— 在分支peer/<slug>上用 git worktree 隔离该对等 Agent)。 - 行为: 完成对等 Agent 的暂存后立即返回,指向
peers/<slug>/result.md。发射即忘 —— 你不会在本轮拿到结果。每轮限 4 次 handoff。
peer_send_input —— 引导一个运行中的对等 Agent
向一个已打开的对等 Agent 注入一个后续轮次。
- 参数:
slug(必填)、message(必填,≤ 64 KB)。 - 行为: 该消息作为对等 Agent 的下一个用户轮次投递,原样呈现(如同操作者亲自键入),并持久化为一条真实的用户消息。只有对等 Agent 的**发起者(originator)**可以发送输入(见护栏)。多次发送各自携带唯一的 occurrence id,因此不同消息不会被折叠,而真正的重试仍会去重。
peer_gather —— 收集结果
读取对等 Agent 的“黑板”(fan-in 汇聚)。
- 参数:
slugs(可选数组;省略则读取所有对等 Agent)。 - 行为: 以文本返回每个指定对等 Agent 的简报与最新结果;尚未完成的对等 Agent 被标记为仍在运行。只读。这是唯一的跨对等 Agent 通道 —— 对等 Agent 之间不在带内共享上下文。
peer_list —— 查看有哪些对等 Agent
你的对等 Agent 的紧凑状态索引(peer_gather 的搭档)。
- 参数: 无 —— 它总是列出你暂存的每一个对等 Agent。
- 行为: 每个对等 Agent 返回一行 —— slug、状态(running 运行中 / done 已完成 / closed 已关闭)、最近更新时间、轮次数,以及是否拥有自己的 worktree。用
peer_list查看存在哪些、哪些已完成;用peer_gather读取某个对等 Agent 的实际产出。只读。
peer_close —— 退役一个对等 Agent
优雅地关闭一个你创建的、运行中的对等 Agent。
- 参数:
slug(必填)。 - 行为: 把该对等 Agent 标记为已关闭(closed)(一个持久化标记)并驱逐其活动连接,使其不再接收任何输入;随后
peer_list与peer_gather都会报告它已关闭,peer_send_input也会拒绝它。只有该对等 Agent 的发起者(originator)可以关闭它。这是一次优雅的退役 —— 该对等 Agent 会完成任何进行中的轮次;它不会中止正在运行的轮次 —— 其result.md仍可通过peer_gather读取。
从客户端创建与汇聚
人类通过两个服务端方法驱动对等 Agent,在 octoscode 中表现为 /peer 和 /gather:
peer/prepare—— 以编队方式暂存 1–8 个对等 Agent(全有或全无)。纯资源预留;随后由客户端打开每个会话并启动其首轮。peer/gather—— 黑板的面向人类的一侧;把各对等 Agent 的结果汇入调用方的会话。
生命周期
- 暂存(Stage) —— 预留
peers/<slug>/(原子式目录占位),可选地添加 git worktree,写入brief.md,并写下一个originator文件记录该对等 Agent 归属哪个会话。任何失败都会干净回滚。 - 通知(Notify) —— 一个持久化的
peer/staged事件请求客户端在后台打开该对等会话。由于它是持久化的,重连会重放它;若会话已打开,客户端会去重。 - 打开与追踪(Open & track) —— 对等会话打开时,其活动连接被记录进一个进程级全局的对等连线注册表(peer wire registry)(
{profile}:peer:{slug}→ 活动会话,最新打开者胜出,上限 8192 条)。 - 运行(Run) —— 对等 Agent 运行自己的轮次,每轮写出
result.md。它在轮次结束后继续存活。 - 关闭(Close) —— 有两种方式。显式地,Agent 调用
peer_close(仅发起者):它写入一个持久化的closed标记并驱逐 wire,使该对等 Agent 不再接收任何输入 —— 这是一次优雅退役,其中进行中的轮次仍会完成。隐式地,WebSocket 断连时驱逐该 wire 映射(仅当它仍指向本会话时,从而并发的重新打开者胜出),删除会话会清除其 actor 与收件箱。
发送输入:投递路径
peer_send_input 必须触达一个运行中的对等 Agent,而它可能位于不同的连接、甚至不同的进程,因此投递不只是一次函数调用:
- 鉴权(Authorize) —— 调用方必须是该对等 Agent 记录的 originator;不匹配即拒绝(fail-closed 失败即拒)。
- 两条路径,取决于进程:
- 在网关中,消息被直接推入对等 actor 的收件箱(快路径)。
- 在 serve 中,该收件箱位于另一个进程,因此投递回落到一个持久化的延续队列(continuation queue):消息作为一个对等延续项入队(以唯一 occurrence id 为键)并被持久化。若持久化失败,入队会回滚,工具返回错误,而不是虚假的“已发送”。
- 抽取(Drain) —— 一个逐连接的抽取器大约每 2 秒运行一次,并由一个与连接无关的**全局抽取(每 5 秒)**兜底,用于无客户端连接时。延续项仅在对等 Agent 空闲时运行。
- 新鲜度门控(Freshness gate) —— 仅当目标仍是该 slug当前注册的 wire 时才派发注入;否则重新入队。
- 派发(Dispatch) —— 作为对等 Agent 的下一个用户轮次投递(原样呈现,持久化为真实用户消息)。
- 重新打开时的重新归位(Re-home on reopen) —— 若对等 Agent 关闭又重新打开,滞留在旧 wire 上的注入会被迁移到新 wire(崩溃安全:新的持久化记录在旧记录被退役之前写入)。
- 带上限的重试(Retry with a cap) —— 派发失败的消息会重新入队,但会被推进到新工作之后,因此永远不会饿死其他消息,并在 5 次尝试(约 10 秒)处封顶 —— 超过上限即丢弃并记录日志,而非无限重试。
投递保证与已知限制
对等输入投递是**尽力而为、单用户(best-effort, single-user)**的。正常运行时它表现为至少一次(at-least-once),由去重折叠重试;在对抗性竞态下,它偶尔可能丢失,或(在有持久化存储时)重复一条消息。三个限制已被记录并接受:
- 关闭-重开竞态 —— 新鲜度检查与派发并非原子,因此在一个狭窄窗口内关闭又重开的对等 Agent,可能出现一条消息被投递到正在关闭的会话(丢失),或在窗口中途崩溃时于重启后被重放(重复)。
- 断电持久性 —— 持久化队列会 flush 到操作系统但不做
fsync,因此硬断电或内核崩溃(而非普通的进程崩溃)可能丢失最近入队的消息。这是全存储范围的性质,并非对等 Agent 特有。 - 墓碑写入失败导致的泄漏 —— 若在重新打开时的重新归位过程中磁盘写入失败,旧记录可能持久残留(每次重启时被再次丢弃)。正常运行时它不会造成重复 —— 只是一处小泄漏。
在下述单用户模型下,这些是可接受的;要彻底关闭它们需要跨子系统的原子加锁或逐次写入的 fsync,对一个尽力而为的本地通道而言代价不成比例。
护栏、鉴权与限制
- 深度-1(Depth-1) —— 对等 Agent 不能调用
peer_handoff、peer_send_input或peer_close(这些工具不在对等会话上提供),因此对等 Agent 不能递归地 handoff、注入或退役其他对等 Agent。对等 Agent 可以调用peer_gather与peer_list(只读,无递归风险)。 - 每轮 4 次 handoff;编队上限 1–8 个对等 Agent。
- 仅发起者可注入(Originator-only) —— 只有创建该对等 Agent 的会话可以向它发送输入;对照对等 Agent 的
originator文件以 fail-closed 方式强制执行。 - 大小上限 —— 简报与注入消息均为 64 KB。
- 单用户单配置档案的威胁模型 —— 在 serve 中,通过鉴权的身份就是该配置档案(profile),因此一个 profile 即一个用户的信任域。LLM 不能跨会话注入(其调用方身份由服务端捕获,且它无法打开会话);跨用户注入被 profile 作用域阻断。剩余情形 —— 用户有意跨越自己的多个会话 —— 被视为用户行使其自身权限,属于设计上接受的行为。不可伪造的能力(capability)模型将推迟到 serve 具备多用户身份之后再引入。