PicoBot/docs/SUB_AGENT_ORCHESTRATION_REVIEW_RESPONSE.md
xiaoxixi 5501c539fc feat: remove agent run groups, add WebUI agent definition management
- drop agent_run_groups table and group_id/scope_kind/scope_id columns (schema v8)
- remove group_id from AgentExecutionContext and recovery group counters
- flatten TasksPage background tab into a per-run list
- add WebUI Agents page with definition CRUD and inline provider/model
- bump version to 1.11.0
2026-08-13 14:03:01 +08:00

16 KiB
Raw Blame History

子 Agent 编排与信号投递设计评审答复

状态设计方答复2026-08

本文逐项回应 docs/SUB_AGENT_ORCHESTRATION_REVIEW.md。评审原文作为审核记录保留;已接受的结论同时回写到 docs/SUB_AGENT_ORCHESTRATION_DESIGN.md,后者仍是后续实现的规范来源。代码级实施方案见 SUB_AGENT_ORCHESTRATION_IMPLEMENTATION.md

1. 总体答复

接受评审的总体结论:方案方向成立,但 A1A5 必须在实现前成为明确契约。全部 A、B、C 项均采纳;其中 A2、B2 和 B4 不只补充文字,还调整了原方案的数据流:

  • durable Agent event 不进入普通 session 消息队列,而由 SQLite inbox 保存 payload、独立的合并式 wake lane 只传递“有待处理事件”的提示。
  • /stop 清理瞬时用户工作,但不通过丢弃内存队列来确认 durable event事件由条件更新立即释放lease expiry 只作为崩溃兜底。
  • 后台结果调度采用有界公平策略UI 未读状态降为可观察性能力,不再承担防饥饿正确性。
  • inbox 容量在接纳 background run 时预留终态事件空间signal 可以因容量不足被拒绝completion 不能在 run 结束时才发现无处落库。
  • continuation 使用“持久化但对客户端隐藏”的内部触发消息,保证 Provider replay、事务提交和客户端渲染三者一致。
评审项 答复 设计处理
A1 接受 独立 durable wake lane、claim-on-run、公平调度和有界重试
A2 接受 /stop 条件释放、lease guard、取消 completion 的 status-only 语义
A3 接受 run admission 与 step execution permit 分离
A4 接受 CancellationToken 贯穿 AgentLoop明确 /stopsteer 不同
A5 接受 hidden trigger message、Turn origin、稳定 delivery binding
B1 接受 默认隔离persistent browser profile 是唯一显式共享路径
B2 接受 容量预留、dead-letter fallback、重试边界
B3 接受 alleach 的事件生成和 deadline 语义
B4 接受 worker 有界公平UI 未读只负责呈现
B5 接受 未配置价格时 cost=NULL
B6 接受 sub-run sleep 只响应 timer/cancellation
C1 接受 非空 event key、非空 caller scope、partial unique index
C2 接受 深度、task-tree 授权、skills/memory 继承规则
C3 接受 删除不存在的 async 迁移别名
C4 接受 枚举 WebSocket 请求、投影和 Turn origin 变更

2. A 级意见答复

A1 — Session 队列饱和语义

答复接受。durable event 不与用户 AgentTask 共用 payload mpsc。

实现采用两条不同语义的 lane

user task lane       bounded mpsc(32),保存任务;满时明确拒绝新用户输入
agent inbox wake lane watch revision合并通知payload 始终留在 SQLite

Router 对活动 Turn 的 steer 使用 lease → mailbox reservation → durable admitted → activate 的两阶段 admissionreservation 在 durable 更新成功前不可被 AgentLoop 排空,且 SQLite I/O 不跨 Session 锁。失败或 queue 事件都恢复/保持 pending,只递增 wake revision。worker 在真正准备执行 continuation 时才领取 lease不先把已 leased 的事件塞进可能被丢弃的 mpsc。

watch 只负责降低延迟发送失败、revision 被合并或进程退出都不影响事实状态。Gateway 启动、reload 激活和周期恢复扫描会重新发现 pending/expired lease。普通 session 队列满不再构成 durable event 丢失或无限重试问题。

worker 在每个 Turn 调度边界执行有界公平:通常先处理用户任务;连续处理 4 个用户 Turn或最老 pending event 已等待 30 秒后,必须先领取一批 continuation。它仍不能中断当前不可分割的 Turn因此时限从下一个调度边界计算。

A2 — /stop、worker 退出与事件恢复

答复接受。lease expiry 只能是崩溃兜底,不能是正常 /stop 的唯一恢复路径。

调整后的链路为:

  1. /stop 在关闭 TurnMailbox 时取回尚未提交的 durable event IDs。
  2. 在 session generation 失效后,以 lease_token/admitted_turn_id 条件更新把这些事件立即恢复为 pending
  3. continuation 执行持有 InboxLeaseGuard;正常失败、取消或 stale generation 会显式 release只有进程崩溃或任务被强制 abort 才等待 lease_until 到期。
  4. 普通内部 continuation 不作为 payload 存在 session mpsc 中,因此 agent_tx.take() 不会吞掉 leased eventworker 领取后才在本地构造 typed task source。
  5. Coordinator 独立拥有 background run。/stop 取消 run token 后Coordinator 仍负责用条件事务写入 cancelled 终态,迟到的 completed 结果不能覆盖它。

由本次 /stop 自身造成的 cancelled completion 设为 requires_continuation=false:事件和 run 状态会持久化并投影到任务树,但事件在同一事务中记为 status-only consumed不会在 /stop 后反向启动一个“任务已取消”的主 Agent Turn。/stop 前已经存在、尚未处理的 signal/completion 不被确认或删除,恢复为 pending 后仍可继续投递。

A3 — waiting_children permit 归属

答复接受。permit 不由整个 Agent Run 持有,也不由 delegate 等编排工具持有。

Coordinator 管理两类限制:

  • run admission quota限制树、session、Agent 的已接纳/未终态 run 数量;可以跨 waiting_children 持有。
  • step execution permit:限制当前正在占用 Provider 或普通工具执行资源的步骤;只在一个步骤期间持有。

AgentLoop 在每次 Provider 请求前按固定顺序获取 global → session → agent provider permits流结束或取消后立即释放。普通工具调用由 tool executor 获取 tool permitdelegateagent_taskemit_signal 等 runtime-control 工具不获取这种稀缺执行 permit。

foreground delegate 在创建 child 前通过状态 guard 把父 run 从 running 条件更新为 waiting_children,等待期间没有 provider/tool permit。children 终态后 guard 把父状态恢复为 running;父 Agent 的下一次模型迭代重新竞争 permit。这样即使 provider 并发上限为 1父等待 child 也不会死锁。

A4 — AgentLoop 结构化取消

答复:接受,并把它提升为 Phase 2 的独立前置里程碑。

AgentLoop 的执行入口将显式接收 cancellation context而不是只依赖父 future 被 drop

root Turn token
└── foreground run token
    └── descendant foreground run token

root session token ── independently owns background run tokens

Provider stream、可取消等待和工具批次外层都观察 tokenAgentRunner 的终结路径把取消归一为类型化 cancelledCoordinator 再用 execution ID 条件提交。父取消、run timeout、reload/shutdown 可以组合为任一触发即取消。

“不默认硬中断”只约束普通 steer:它等待安全边界,不取消 Provider 或副作用工具。/stop 保持现有强停止语义,会取消 token 并使 root Turn future 失效;未在宽限期内自行退出的独立 child task 由 Coordinator/Supervisor abort。无论 future 如何结束terminal condition update 都阻止迟到结果提交。

A5 — continuation Turn 模型

答复接受。continuation 需要同时满足 durable replay、无伪用户气泡和正常 Turn 投递。

每个 continuation 生成一条内部触发消息:

  • 数据库 role 使用 Provider 可兼容的 usersource 为 agent_signal/agent_result
  • 增加 client_visibility=hiddenturn_origin=agent_continuation;普通历史 API 和 turn_committed.messages 不投影这条消息。
  • 内容是有界、带 event/run reference 的 runtime envelope不伪造外部 sender也不直接采用子 Agent 输出中的指令优先级。
  • Provider 历史 replay 会包含该隐藏消息,因此后续 assistant 回复不会成为无来源的悬空历史。
  • 隐藏触发消息、assistant/tool 结果、usage 和 inbox consumed 在同一事务中提交;失败时全部不确认。

客户端继续使用 turn_updated/turn_committed 展示 assistant Turn但帧增加 turn_origin,从而可以显示“后台结果处理”标记且不创建用户气泡。

内部 Turn 的出站目标来自 root session 的 durable delivery bindingchannelchat_id 以及可复用的 thread/root 上下文。一次性 reply_to 不得复用。若没有可用的外部 binding结果仍持久化并供 WebUI/TUI 历史读取,不猜测其他目标。

3. B 级意见答复

B1 — browser/resource scope 隔离

答复:接受。 新具名 Agent 默认使用 root_session_id + run_id 的瞬时资源 scope不继承父会话 browser cookie。需要共享登录态时官方路径是由 Root 创建/选择经过校验的 browser_profiles persistent ID并把该 ID 作为显式 task/artifact reference 交给获准使用 browser 的子 Agent子 Agent必须在每次相关调用中显式传入该 ID。

为平滑迁移,由无 target 的旧调用映射出的内置 general 兼容 Agent 可在弃用期保留父 session transient scope具名 Agent不继承这个例外。文档和 tool result 会明确提示两种 scope 的差异。

B2 — dead letter、重试和 inbox 上限

答复:接受。 接纳 background run 时按 completion policy 预留不可抢占的终态 event sloteach 每个 run 一个,all 每个 group 一个。容量不足时 delegate 在创建 run 前拒绝。signal 只使用未预留容量,满时 emit_signal 返回 inbox_full,但不终止 run。因此已接纳 run 的 completion 永远不会在终结时因 inbox 满而丢失。

暂定恢复策略为 8 次可配置尝试,退避 1s/5s/30s/2m/10m 后封顶 10 分钟,并同时受 event TTL 限制。瞬时 Storage/worker/Provider continuation 失败可重试session 已删除、授权事实失效或 payload 永久损坏立即 dead-letter。lease timeout 不单独计作永久错误,但会记录 attempt 和原因。

事件进入 dead-letter 后:

  1. 保存最终原因和 dead_lettered_at,在任务树/API 中持续可见。
  2. 通过 OutboundDispatcher 最多发送一次有界 system fallback内容只包含 run ID、终态和查询提示不复制大结果。
  3. fallback_notified_at 保证 fallback 幂等;渠道也失败时仍以 SQLite 记录和管理 UI 为最终可诊断出口。

B3 — completion_policy=each

答复:接受。 语义修订为:

  • each:每个 run 进入终态即创建独立 completion eventRouter 可在 300500ms debounce 窗口合并一次 continuation但不能等待其他 sibling。
  • all:单 run 终态只更新 group 计数,不创建可投递 completion全部终态或 group deadline 到达后创建一个 group_completion event包含全部逐项状态和 result references。
  • group deadline 到达时,未终态 children 被取消并条件更新为 timed_out,随后生成唯一 group completion。

因此 inbox schema 允许 run-scoped 或 group-scoped event 二选一,而不是强制 run_id NOT NULL

B4 — UI 未读状态与防饥饿

答复:接受。 正确性由 A1 的 worker 有界公平策略保证UI 未读只呈现尚未汇总/已 dead-letter 的事件数量不参与调度。Phase 3 明确包含未读计数、event revision 和 reconnect 后全量校准。

B5 — cost 与定价配置

答复:接受。 Phase 2 保留 nullable cost 字段,但只有 Provider profile 明确提供 input/output/cache 价格时才计算;当前配置没有价格来源,因此写 NULL。usage token 仍照常持久化。价格配置和历史重算不属于本次编排功能的前置条件。

B6 — sub-run 内 sleep

答复:接受。 TurnWakeupHandle 只存在于 root interactive Turn。sub-run 的 ToolExecutionContext.turn_wakeup=None,其 sleep 只等待 timer、run cancellation、timeout 或 shutdown不会监听 root session 用户输入或其他 Agent signal。需要被主 Agent立即控制时使用 agent_task.cancel,由 cancellation token 唤醒。

4. C 级意见答复

C1 — SQLite NULL 与事件去重

答复:接受。 所有参与唯一约束的 scope/key 都规范化为非空值:

  • background idempotency 使用 caller_scope_id TEXT NOT NULLRoot 固定为字面量 ROOT
  • idempotency_key 仍可为空,但使用 CREATE UNIQUE INDEX ... WHERE idempotency_key IS NOT NULL 的 partial unique index。
  • dedupe_key 的 signal 使用 signal:<event_uuid>
  • dedupe_key 的 signal 使用 signal:<normalized-key>:<cooldown-window-id>,只在冷却窗口内去重,不会永久压制同类告警。
  • run completion 使用固定 completion:terminal-v1group completion 使用 group-completion:terminal-v1

C2 — 深度、task-tree 授权和上下文继承

答复:接受。

  • 全局 max_tree_depth 是 root-relative 硬上限Definition limits.max_depth 是该 Agent可继续创建的最大相对后代深度。child 的 remaining depth 为 min(parent_remaining - 1, target_definition.max_depth),任何一项为 0 都不能继续委托。
  • agent_task 查询必须匹配当前 root_session_id。Root 可操作本 session 的整棵树;子 Agent只能读取自身与后代只能取消其未终态后代不能通过猜测 run ID 跨 session 或操作祖先/sibling。
  • 子 Agent不继承主会话 history、memory recall 或临时 activated skills。第一版仅在 Definition 工具集中包含 get_skill 时注入受信任 Skill catalog调用方只能通过显式 task/context 传递事实。若未来开放 memory必须新增管理员配置的只读 scope不能默认继承。

C3 — async 别名

答复:接受。 删除 async → background。迁移只接受代码中确实存在的 inlineparallelbackground;新 prompt/schema 只公布 foregroundbackground

C4 — 客户端协议清单

答复:接受。 协议按通用运行投影设计,不为 Signal 单独复制一套模型:

  • WsInbound::GetAgentRuns { session_id, cursor, limit }
  • WsInbound::GetAgentRun { session_id, run_id }
  • WsOutbound::SessionAgentRuns { session_id, revision, runs, next_cursor }
  • WsOutbound::AgentRunUpdated { session_id, revision, run }
  • WsOutbound::AgentEventUpdated { session_id, revision, event }
  • TurnSnapshotWsOutbound::TurnCommitted 增加 turn_origin = user | agent_continuation | scheduled

AgentEventUpdated 同时承载 accepted、admitted、consumed、dead-letter 等状态,按 (session_id, revision, event_id) 幂等合并。断线重连后客户端用 GetAgentRuns 全量校准,实时帧只是增量。取消操作第一版继续通过 /stopagent_task.cancel 或受保护管理 API不额外开放一个缺少权限上下文的裸 WebSocket cancel 帧。

5. 对分期的调整

Phase 调整后的完成条件
1 除原内容外,明确 browser 兼容 scope、skills/memory 规则和 sub-run sleep 行为
2A CancellationToken 贯穿 AgentLoop先以现有 root Turn/sleep/Provider tests 锁定取消语义
2B run 持久化、step execution gate、foreground child cancellation 和结果查询
3 durable wake lane、capacity reservation、hidden continuation trigger、bounded fairness、dead-letter fallback 与 WebSocket run/event projection
4 typed TurnMailbox、emit_signal、steer admission以及同一 AgentEventUpdated 的 Signal 卡片呈现
5 root Turn wake-aware sleepsub-run 保持 timer/cancellation-only

Phase 3 上线时保留旧 direct notification 的受控兼容开关,但仅作为 rollback 手段,默认路径必须是 inbox/continuation不能同时投递两条用户通知。开关移除前必须验证 dead-letter fallback、重启恢复和 reconnect 校准。

6. 最终结论

评审结论“方向通过,需修订后实现”成立。修订后的关键边界是:

SQLite inbox 保存事实
    ├── direct steer admission → 当前 TurnMailbox
    └── coalesced wake → worker claim → hidden continuation Turn

普通 user mpsc 满 ≠ durable event 丢失
/stop 丢弃瞬时用户工作 ≠ 确认 durable event
run 存活配额 ≠ Provider/tool step permit
UI 未读提示 ≠ 调度正确性

在 A1A5 的专项契约和上述 schema/protocol 调整落地前,不应开始 Phase 3/4 的生产实现。