- AgentCatalog/definitions with strict Markdown frontmatter, delegation graph, fail-closed tool scoping, and signal contracts - structured cancellation (AgentError::Cancelled/TimedOut) across provider streams, tool batches, and sleep; /stop drives the same terminal state - schema v6 run/group/inbox persistence with execution-ID conditional transitions and completion-slot reservations - ExecutionGate separating run quota from provider/tool step permits - background completion inbox with hidden-trigger continuation turns, fairness scheduling, lease release, dead-lettering, and activation recovery - typed TurnMailbox with two-phase steer admission and atomic consumption at turn commit; /stop releases admitted steer events back to pending - emit_signal tool with contract-enforced rate/dedupe/severity/size limits - WS run/event projection (GetAgentRuns, AgentRunUpdated, AgentEventUpdated), /api/agent-runs* management endpoints, /api/tasks union, WebUI run tree and signal cards - ChannelContext.durable_private persisted for continuation delivery reuse Version 1.7.0
16 KiB
子 Agent 编排与信号投递设计审核报告
状态:审核完成(2026-08)。审核对象为设计提案
docs/SUB_AGENT_ORCHESTRATION_DESIGN.md,该设计尚未实现;本文所有"现状"描述以当前代码和测试为准。本文结合现有实现逐条核实设计的现状诊断,评估架构合理性,并按严重程度列出缺陷与落地前必须补齐的定义。行号基于审核时的代码快照,后续实现合并后可能过时。
设计方逐项答复见
SUB_AGENT_ORCHESTRATION_REVIEW_RESPONSE.md;已接受结论同步写入设计文档。
1. 审核范围与依据
1.1 审核对象
- 设计文档:
docs/SUB_AGENT_ORCHESTRATION_DESIGN.md(提案,未实现;rg确认src/与webui/中无任何AgentCatalog/AgentCoordinator/agent_inbox/emit_signal相关实现) - 对照实现:
src/agent/sub_agent.rs、src/agent/agent_loop.rs、src/agent/steering.rs、src/session/session.rs、src/session/turn_input.rs、src/session/persistence.rs、src/tools/delegate.rs、src/tools/sleep.rs、src/tools/send_message.rs、src/tools/traits.rs、src/storage/、src/work/mod.rs、src/scheduler/mod.rs、src/task_supervisor.rs、src/config/mod.rs、src/gateway/reload.rs
1.2 审核依据
docs/ARCHITECTURE.md与 AGENTS.md 中的架构边界和并发不变量- 现有相似机制:Scheduler durable lease(
src/storage/scheduler.rs:310-348)、WorkManager 乐观并发(src/work/mod.rs:320-342)、TurnController"持久化后才 Completed"(src/session/persistence.rs:147-167)、RuntimeAdmission(src/gateway/reload.rs)
2. 总体结论
设计方向合理,可以按分期推进;但存在 5 处与现有代码强耦合的接缝缺口(A1–A5),落地前必须先补齐定义,否则 Phase 2/3 会被迫返工。
设计的核心决策——foreground/background 与 queue/steer 两个正交维度、先持久化后唤醒、SQLite inbox 为权威来源、lease/consumed 事务提交、ancestry 环检查、AgentCatalog 绑定运行代——与 PicoBot 既有不变量一致,且现状诊断(设计 §1 的 9 条)逐条属实(见第 3 节)。主要问题不在方向,而在设计与现有 session worker、/stop、AgentLoop 取消机制的衔接处留白过多。
3. 现状诊断核实
设计 §1 的 9 条诊断全部与代码一致:
| # | 设计诊断 | 代码证据 | 结论 |
|---|---|---|---|
| 1 | 所有子 Agent 复用同一 LLMProviderConfig |
SubAgentManager.provider_config 单实例(src/agent/sub_agent.rs:119),inline/background 均用它创建 Provider(:204、:477) |
属实 |
| 2 | 无具名角色文件,工具权限由 allowed_tools 临时决定 |
delegate schema 的 allowed_tools 数组(src/tools/delegate.rs:50-54);未填时用默认只读集(sub_agent.rs:34-41) |
属实 |
| 3 | 子 Agent 被统一移除 delegate |
filter_tools 硬编码排除 delegate/todo/reload_config(sub_agent.rs:177-181) |
属实 |
| 4 | DelegateContext 只有 session/channel/chat |
sub_agent.rs:90-95;无 caller/parent/depth/ancestry |
属实 |
| 5 | parallel 混淆"委托方是否等待"与"是否并发" |
run_parallel 就是 join_all(run_inline)(sub_agent.rs:332-346) |
属实 |
| 6 | 后台完成通知直发 MessageBus.outbound,不成为主 Agent 输入 |
background-task-notifications 任务格式化后 publish_outbound fire-and-forget(src/session/session.rs:1757-1776),不写会话历史、不触发 Turn |
属实 |
| 7 | Steering mailbox 只建模用户输入,且继承 /stop 丢弃语义 |
admission 只推 SourceKind::UserInput(session.rs:2910-2938);AgentLoop 防御性归一 role=user(src/agent/agent_loop.rs:624-628);/stop 调 close_and_take_pending 主动丢弃(session.rs:2120-2128、src/agent/steering.rs:215-229) |
属实 |
| 8 | SleepTool 只等定时器 |
tokio::time::sleep 单一路径(src/tools/sleep.rs:72),无 wakeup/cancel 分支 |
属实 |
| 9 | send_message 同时覆盖跨 Channel、目标会话写入和同 Turn 附件暂存 |
src/tools/send_message.rs 的 target/content/origin/files 参数;OutboundDelivery::AttachedToCurrentTurn 同 Turn 分支(src/tools/traits.rs:127-131) |
属实 |
补充:设计隐含覆盖了一个现存 bug。 inline 结果截断时提示"完整结果请使用 check_task 查看"(sub_agent.rs:809),但 run_inline 从不写 background_tasks 表(只有 run_background 写,sub_agent.rs:373-398),check_task(sub_agent.rs:707-713)查不到 inline 结果。设计 Phase 2"Foreground 结果也持久化"(§23)修复此问题,分期安排正确。
4. 设计合理性评估
以下决策予以肯定:
| 设计点 | 评估 |
|---|---|
| 执行生命周期与投递方式正交化(§4.1) | ✅ 干净消除 parallel 的语义混淆;批量 foreground 并发等价旧 parallel 但不作为第三种模式 |
| 先持久化后唤醒、内存 wakeup 仅为加速器(§6.4、§14.4) | ✅ 与"持久化后才 Completed"既有不变量(persistence.rs:147-167)同构 |
| lease/consumed 与条件更新(§14、§16.4) | ✅ 复用 Scheduler durable lease 与 WorkManager 乐观并发的成熟模式 |
target not in ancestry 拒绝 A→B→A(§7.1) |
✅ 以显式 iteration workflow 替代隐式递归,边界正确 |
| 授权不依赖 task-local(§8) | ✅ 正确诊断现状 DELEGATE_CONTEXT task-local(sub_agent.rs:19-28)不是授权事实来源;tokio::spawn 不传播 task-local |
| AgentCatalog 以 Arc 固定运行代(§5.4、§18.4) | ✅ 与 RuntimeAdmission 现有集成一致(SubAgentManager 已接 admission,sub_agent.rs:157-163、353-358) |
llm_profile 引用现有 config.agents(§5.2) |
✅ Config.agents 与 get_provider_config(agent_name) 已存在(src/config/mod.rs:45、712-745),无需配置重构 |
| Completion 由运行时自动生成、不依赖模型记得调工具(§12.1) | ✅ 正确;emit_signal 无任意目标参数,收敛了权限面 |
| 可唤醒 sleep 用 watch revision 而非裸 Notify(§17.2) | ✅ 正确规避"输入先于订阅到达"的丢失唤醒竞态 |
| §26 不变量清单 | ✅ 与 ARCHITECTURE.md 一致,可作为实现验收标准 |
| 分期顺序(§23) | ✅ Phase 1 纯增量;Phase 2 顺带修复 inline 截断 bug;依赖方向正确 |
5. 缺陷清单
严重程度:A=主要(落地前必须补齐定义);B=中等(实现对应 Phase 前补齐);C=次要(修订文档即可)。
5.1 A 级:主要缺陷
A1 — Session 队列饱和语义与现状冲突(设计 §15.3)
现状:session 队列容量 32(session.rs:27),满时 try_send 失败直接丢弃输入并回复"队列已满"(session.rs:3001-3007)。设计要求 durable event 在队列饱和时"保持 durable pending,由 Router 有界重试,不能丢弃",但未定义:
- agent 事件与用户输入是否共用同一 mpsc(共用则用户流量可长期占满队列,事件重试无收敛界);
- Router 重试的退避、deadline 与最终处置;
- §15.4 只拆分了 TurnMailbox 的 lane(user 32/64KiB、agent 8/32KiB),session 级队列的 agent lane 容量与优先级未定义。
A2 — /stop 与 worker 退出时 durable event 的恢复机制缺失(设计 §18.3)
现状 /stop:current_cancel.take()(session.rs:2117)→ close_and_take_pending 丢弃 steering(:2120-2128)→ agent_tx.take() 丢弃全部排队任务(:2136)→ bump generation/state_version(:2139-2140)→ 取消后台子任务(:2144-2148)。mpsc 被 drop 时没有逐项回调。设计未定义:
- 被丢弃的内部
AgentTask(含BackgroundAgentResults)如何触发 inbox lease 释放——只能靠 lease 超时被动收敛(应明说延迟界),或为 AgentTask 增加 Drop guard(未提); /stop后 worker 退出(task_rx.recv()返回 None 即 break,session.rs:3150-3152),被取消 run 异步生成的 cancelled completion 由谁、何时消费,完整链路未写。
A3 — waiting_children permit 释放在现有结构中无落点(设计 §19.2)
AgentLoop 目前没有任何 permit/cancellation 原语(agent_loop.rs 无 CancellationToken/select;取消靠 worker 整体 drop future,session.rs:3778-3804);TaskSupervisor 也没有并发上限,只在 stopping 时拒绝 spawn(task_supervisor.rs:60-89)。设计只给出原则"permit 限制活跃 Provider/工具步骤",未定义:
- permit 由谁持有与获取/释放(AgentLoop?AgentRunner?Coordinator?);
- delegate 在父 run 的 tool batch 内执行(
agent_loop.rs:1187-1242),父 run 进入等待时释放 permit 的钩子如何嵌入现有批处理流程。
这是 Phase 2 复杂度最高的部分,只给原则不够。
A4 — 结构化取消是前置条件,但 AgentLoop 当前零支持(设计 §18.1)
新架构中子 run 是 Coordinator spawn 的独立任务,父 future 被 drop 不再传播取消,必须用显式 CancellationToken 树贯穿 AgentLoop——这是横切重构,设计只在 Phase 2 列了一行"实现结构化取消"。另有一处表述需要澄清:§3 非目标"不默认硬中断正在进行的 Provider 请求"只约束 steer;现有 /stop 恰是硬 drop(drop process_future 连带中断 provider 流,session.rs:3778-3804)。文档应显式声明 /stop 保持硬语义,避免实现时误读为 /stop 也要走安全边界。
A5 — 内部 continuation Turn 的消息/持久化/渲染模型未定义(设计 §16.3)
现有 Turn 以用户消息为起点:先持久化用户消息(session.rs:3191);prepare_turn_input 把 runtime context 附加到最后一条 user message(src/session/turn_input.rs:19-25);WebUI/TUI 按 user/assistant 交替渲染。设计说"内部输入不显示用户气泡",但未定义:
- continuation Turn 写什么消息行(无 user 行?系统行?)、历史 replay 给 provider 时的形态;
- 客户端如何渲染无用户消息的 Turn(§15.1 的 SourceKind 扩展只解决标记问题);
- continuation 输出的投递目标:
AgentTask的 channel/chat_id/channel_context 来自 InboundMessage(session.rs:631-641),内部任务没有 channel 上下文,应显式规定投递到 session 最近的 channel/chat。
5.2 B 级:中等缺陷
B1 — browser/resource scope 隔离是行为破坏,且无共享出口(设计 §10)。 现状子 Agent 复用父对话的 browser session(browser_session_id 回退到 delegate context 的 session_id,sub_agent.rs:264-279;background 路径 :505-508 同)。改为 root_session_id + run_id 隔离会破坏依赖父会话登录态/cookie 的场景。"确需共享必须由工具定义显式支持"没有给出机制,应指明 browser_profiles persistent ID 为官方共享路径。
B2 — dead_letter 与重试上限策略空缺(设计 §14.3、§21)。 Phase 3 移除直发通知后,continuation 反复失败转 dead_letter 时结果对用户彻底不可见,文档未定义 dead-letter 后的用户可见行为(如回退一条系统通知)。max_pending_inbox_events_per_session=128 打满后新事件的行为同样未定义。
B3 — completion_policy=each 只有字段没有语义(设计 §14.2)。 正文只描述了 all:一个 run 超时会把全组结果交付拖到 group deadline,缺少 per-run 提前交付或分组拆分策略。
B4 — "UI 未读状态"是防饥饿的关键依赖,但不存在且未立项(设计 §16.2)。 调度优先级把 queue completion 排在用户输入之后,持续用户流量下后台结果会被无限推迟,设计靠"UI 未读状态"兜底;该 WebUI 功能当前不存在,Phase 3 只写了"WebUI 投影",未列为明确工作项。
B5 — cost 字段假设了不存在的定价配置(设计 §6.5、§14.1)。 agent_runs.cost 与 ProviderFactory"复用价格信息"的前提不成立:config 从不填 price_input/output_per_million(src/config/mod.rs:742-743 硬编码 None,无配置键解析)。要么补定价配置,要么注明 cost 暂为 NULL。
B6 — 子 Agent run 内 sleep 的唤醒语义未定义(设计 §17)。 全章隐含 root session 的 Turn;sub-run 没有"当前 session 用户输入"概念。应显式规定 sub-run 内 sleep 只响应自身 cancellation/timeout,否则 TurnWakeupHandle 的来源不明。
5.3 C 级:次要问题
C1 — SQLite UNIQUE 与 NULL 语义(设计 §14.3、§9.6)。 UNIQUE(run_id, event_type, event_key):emit_signal 未提供 dedupe_key 时 event_key 的生成规则未定义。idempotency_key 的唯一范围 (root_session_id, caller_run_id, key) 在 ROOT 调用时 caller_run_id 为 NULL,SQLite 中 NULL≠NULL 会导致去重失效,需要哨兵值(如 root)。
C2 — 授权与上下文的细节留白(设计 §7.1、§9.1、§10)。 definition max_depth 与全局 max_tree_depth 是否取 min 未明说;agent_task 工具能否操作本 root session 任务树之外的 run(跨 session 越权)未明说;skills/memory 是否进入子 Agent 上下文未提(现状子 Agent 可带 skills prompt,sub_agent.rs:188-197;memory recall 只在 session Turn,turn_input.rs:47)。
C3 — async 别名是多余假设(设计 §22.1)。 现代码从未接受 async(delegate.rs:151-165 只解析 inline/background/parallel),该迁移条目可删。
C4 — 客户端协议变更未枚举(设计 §20.2、Phase 4)。 AgentSignal 卡片、"已接纳"状态、continuation Turn 都需要新的 WsOutbound 消息类型(src/protocol.rs),Phase 4 只写"添加 AgentSignal UI 和任务树",未列协议变更清单。
6. 修订建议
实现启动前,建议在设计文档中补充五个专项定义(对应 A 级缺陷):
- Durable event 与 session 队列的 lane 划分:agent 事件是否独立队列、饱和时的重试退避/deadline/dead-letter 策略、与用户输入的优先级关系(A1)。
/stop、worker 退出与 lease 释放的衔接:被丢弃内部任务的 lease 释放路径(Drop guard 或明确依赖 lease 超时及延迟界)、/stop后生成的 cancelled completion 的消费链路(A2)。- Permit 归属:执行 permit 在 AgentLoop/AgentRunner/Coordinator 之间的获取与释放点,特别是
waiting_children前后的钩子位置(A3)。 - Continuation Turn 模型:消息行写入形态、provider replay 形态、客户端渲染契约、输出投递目标(A5)。
- 取消横切方案:CancellationToken 贯穿 AgentLoop 的接口设计,并显式声明
/stop保持硬 drop 语义、安全边界注入只约束steer(A4)。
B 级问题建议在对应 Phase 实现前补齐:B1/B6 在 Phase 1,B5 在 Phase 2,B2/B3/B4 在 Phase 3。
7. 分期实施意见
| Phase | 风险 | 意见 |
|---|---|---|
| 1 具名 Agent 与 Foreground | 低 | 纯增量。llm_profile 直接复用 Config::get_provider_config(config/mod.rs:712-745),无配置重构。注意 B1:browser scope 隔离会改变现有子 Agent 共享父会话 browser 的行为,需要迁移说明 |
| 2 统一 Run 持久化 | 高 | 改动面最大:AgentLoop 取消与 permit 均为横切变更。建议先独立原型"CancellationToken 贯穿 AgentLoop",用现有 sleep 取消测试(sleep.rs:194-237)与 steering 恢复测试锁定回归基线,再叠加 permit |
| 3 Inbox 与 Queue Completion | 中 | UX 拐点:完成通知从直发 Channel 改为主 Agent continuation。建议保留配置开关回退直发通知,覆盖 B2 的 dead-letter 空窗;§25.4 测试矩阵是本 Phase 验收关键 |
| 4 Emit Signal 与 Steer | 中 | TurnMailbox 泛化触及 handle_message 核心 admission 路径(session.rs:2815-2953),与 /stop 的原子性必须沿用现有同锁判定模式;先补 C4 协议清单 |
| 5 可唤醒 Sleep | 低 | 相对独立。watch revision 方案正确;先补 B6 的 sub-run 语义 |
8. 结论
设计的现状诊断准确、核心决策与既有架构不变量兼容、分期依赖方向正确,审核结论为"方向通过,需修订后实现"。A1–A5 五个接缝缺口不是方向错误,而是设计与 session worker//stop/AgentLoop 取消机制的衔接定义不足;按第 6 节补齐专项定义后,可按第 7 节顺序分期实施。