diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index b88db32..0588f83 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -2,7 +2,7 @@ 本文档描述 PicoBot 当前实现的运行时边界、数据流、并发模型和演进约束。它面向维护者和后续参与改进的 Agent,是代码架构的主入口;行为细节仍以代码和测试为最终依据。 -流式模型输出、reasoning 展示、活动 Turn 快照和 Channel 实时投递的详细设计与取舍见 [STREAMING_TURN_DESIGN.md](STREAMING_TURN_DESIGN.md)。用户输入路由、Session 执行拆分、终态投递确认和历史增量校准的重构方案见 [MESSAGE_FLOW_REFACTOR_DESIGN.md](MESSAGE_FLOW_REFACTOR_DESIGN.md)。配置运行代、重载边界和失败语义见 [CONFIG_HOT_RELOAD_DESIGN.md](CONFIG_HOT_RELOAD_DESIGN.md)。 +流式模型输出、reasoning 展示、活动 Turn 快照和 Channel 实时投递的详细设计与取舍见 [STREAMING_TURN_DESIGN.md](STREAMING_TURN_DESIGN.md)。用户输入路由、Session 执行拆分、终态投递确认和历史增量校准的重构方案见 [MESSAGE_FLOW_REFACTOR_DESIGN.md](MESSAGE_FLOW_REFACTOR_DESIGN.md)。配置运行代、重载边界和失败语义见 [CONFIG_HOT_RELOAD_DESIGN.md](CONFIG_HOT_RELOAD_DESIGN.md)。具名子 Agent、委托图、后台收件箱、`queue`/`steer` 信号和可唤醒 `sleep` 的升级提案见 [SUB_AGENT_ORCHESTRATION_DESIGN.md](SUB_AGENT_ORCHESTRATION_DESIGN.md)。 ## 1. 设计目标 diff --git a/docs/SUB_AGENT_ORCHESTRATION_DESIGN.md b/docs/SUB_AGENT_ORCHESTRATION_DESIGN.md new file mode 100644 index 0000000..dabaf3f --- /dev/null +++ b/docs/SUB_AGENT_ORCHESTRATION_DESIGN.md @@ -0,0 +1,1193 @@ +# PicoBot 子 Agent 编排与信号投递架构升级设计 + +> 状态:提案(2026-08)。 +> +> 本文定义具名子 Agent、委托图、多 Provider、`delegate`、`emit_signal`、后台结果收件箱、`queue`/`steer` 投递以及可唤醒 `sleep` 的目标架构。本文描述待实现设计;在实现完成并通过测试前,当前行为仍以代码和 `docs/ARCHITECTURE.md` 为准。 + +## 1. 背景与现状 + +PicoBot 已经具备一版子 Agent 能力:根交互 Agent 通过 `delegate` 创建临时 Agent,支持 `inline`、`background` 和 `parallel`,可以过滤工具、绑定计划子项、持久化后台任务,并在后台任务完成后向原 Channel 发送通知。 + +当前实现适合作为“一次性子任务执行器”,但还不能表达完整的多 Agent 编排: + +1. 所有子 Agent 复用同一个 `LLMProviderConfig`,不能按角色选择 Provider/Model。 +2. 子 Agent 没有具名、可校验的角色文件;工具权限由 `delegate.allowed_tools` 临时决定。 +3. 子 Agent 被统一移除 `delegate`,不能按受控委托图继续委派。 +4. `DelegateContext` 只有 session/channel/chat,没有 caller、current agent、parent run、depth 和 ancestry,无法执行多级授权。 +5. `parallel` 把“委托方是否等待”和“子任务是否并发”混成一个模式。 +6. 后台完成通知直接走 `MessageBus.outbound` 发给用户,不会自动成为主 Agent 的输入。 +7. 当前 steering mailbox 只建模用户输入;Agent 信号若直接复用会被误标为用户消息,并继承 `/stop` 丢弃语义。 +8. `SleepTool` 只等待定时器;除整个 Turn 被取消外,不能被新用户输入或 Agent 信号唤醒。 +9. `send_message` 同时覆盖跨 Channel 消息、目标会话历史写入和同 Turn 附件暂存,不能作为子 Agent 内部信号的安全替代。 + +本设计在保留 `AgentLoop` 无状态、每 Session 单 Turn、TurnSnapshot latest-wins、持久化后才 Completed 等既有不变量的基础上,引入显式编排层。 + +## 2. 设计目标 + +### 2.1 功能目标 + +- 每个子 Agent 由独立 Markdown 文件定义角色、工具、Provider profile、委托目标和资源上限。 +- 主 Agent 和子 Agent、子 Agent 与子 Agent 之间可以按有向权限图委托任务。 +- 主 Agent 是特殊根身份,只能委托其他 Agent,永远不能成为委托目标。 +- 委托执行模式收敛为成对概念 `foreground | background`。 +- 单任务/批量任务与串行/并发属于调度维度,不再作为第三种执行模式。 +- Background Agent 可以主动发出非终态重要信号,最终完成/失败/超时/取消由运行时自动生成终态事件。 +- Background 事件使用 `queue | steer` 决定进入下一 Turn 还是当前 Turn。 +- 主 Agent 忙碌、空闲、等待 sleep、正在模型调用或工具调用时都有明确、无丢失的投递语义。 +- Agent 信号和后台完成结果先持久化,再通过 SessionManager 投递;Gateway 崩溃或内存唤醒丢失后可以恢复。 +- `sleep` 可以由当前 session 的新输入唤醒,但不能因此破坏 queue/steer 的内容可见性边界。 +- 工具、任务树、事件和结果具备有界并发、取消、超时、去重、审计和可观测性。 + +### 2.2 架构目标 + +- Agent 编排属于 `agent`/`session` 领域,不把内部 Agent 信号塞入外部 Channel 数据面。 +- ToolRegistry 只提供经过角色定义和系统策略共同裁剪后的能力。 +- 所有慢 I/O 在 Session 锁外执行;投递 admission、Turn 关闭和可靠 fallback 保持原子。 +- Background 任务与旧运行代绑定;配置热重载不在任务中途切换角色、Provider 或工具权限。 +- 完成事件、主 Agent 消费确认和 Turn 持久化使用事务与条件更新,避免内存/数据库静默分叉。 + +## 3. 非目标 + +- 不把 PicoBot 改成分布式 Agent 集群;所有运行仍在单 Gateway 进程内。 +- 不恢复 Gateway 崩溃前正在进行的 Provider 流或工具调用现场。 +- 不承诺外部 LLM 调用严格 exactly-once;崩溃恢复可能重新调用 Provider。 +- 不允许模型在委托参数中直接指定 API key、base URL、任意工具或任意目标 session。 +- 不让子 Agent 直接访问 SessionManager 内部状态。 +- 不把 Agent reasoning、Provider 私有 replay state 或完整工具轨迹转发给其他 Agent、客户端或日志。 +- 不默认硬中断正在进行的 Provider 请求或有副作用工具;`steer` 只保证最近安全边界注入。 +- 不用本功能替代跨进程可靠监控。小时/天级持久监控仍应优先使用 Scheduler 的 monitor job。 + +## 4. 术语与核心语义 + +| 术语 | 定义 | +|------|------| +| Root Agent | 当前用户会话的主 Agent,运行时身份为 `ROOT`,不属于可寻址子 Agent 目录 | +| Agent Definition | 从一个 Markdown 文件解析出的具名角色、模型、工具、委托边和限制 | +| Agent Catalog | 当前 Gateway 运行代中全部有效 Agent Definition 的不可变快照 | +| Agent Run | 一个 Agent 对一个具体任务的执行实例 | +| Run Group | 一次批量委托创建的多个同级 Agent Run | +| Foreground | 委托方等待任务终态并直接取得结果;不代表子任务串行 | +| Background | 委托调用立即返回 run ID,任务独立执行,结果通过事件投递 | +| Queue | 输入属于后续 Turn;不改变当前 Turn 的模型上下文 | +| Steer | 输入尝试进入当前 Turn,并在最近安全边界注入;失败时可靠退化为 queue | +| Agent Signal | Background Agent 在运行中主动发出的非终态重要事件 | +| Agent Completion | Agent Run 进入 completed/failed/timed_out/cancelled/interrupted 时由运行时自动产生的终态事件 | +| Agent Inbox | 持久化的主 Agent 内部收件箱,是 Background 结果与信号的权威来源 | +| Turn Mailbox | 当前 Turn 接受 steer 输入的有界内存邮箱,保留来源、顺序和 durable event ID | + +### 4.1 两个正交维度 + +执行方式和结果投递必须分开: + +```text +执行生命周期:foreground | background +后台事件投递:queue | steer +``` + +多个 foreground 子任务可以并发运行,父 Agent 仍同步等待全部结果。多个 background 子任务也可以并发运行,但 `delegate` 立即返回 run IDs。并发与否由批量请求、Coordinator 调度和并发配额决定,不由 mode 名称决定。 + +### 4.2 Foreground 与 Background + +| 行为 | Foreground | Background | +|------|------------|------------| +| `delegate` 返回时机 | 子任务进入终态后 | 任务持久化并成功接纳后 | +| 直接返回 | 结构化结果 | run ID / group ID | +| 父 Agent 当前 Turn | 阻塞等待 | 继续运行 | +| 最终结果路径 | 当前 delegate tool result | Agent Inbox event | +| 适用场景 | 当前工作依赖结果 | 长任务、监控、可稍后处理的任务 | + +若当前工作必须及时依赖结果,应使用 foreground。Background 的 queue completion 不保证参与发起它的原 Turn;需要抢占式关注的重要信号应显式使用 steer。 + +## 5. Agent Markdown 定义 + +### 5.1 文件位置 + +第一版只从受信任配置目录加载: + +```text +~/.picobot/ +├── config.json +└── agents/ + ├── researcher.md + ├── coder.md + └── reviewer.md +``` + +角色文件决定工具权限和委托能力,属于安全配置,不应默认从可被普通 Agent 写入的 workspace 自动加载。未来若支持 workspace 角色目录,必须由配置显式开启,并说明它不是硬安全边界。 + +### 5.2 文件格式 + +```md +--- +id: researcher +description: 搜索、阅读并整理技术资料 +llm_profile: research-sonnet + +tools: + - file_read + - file_search + - content_search + - web_fetch + +delegates: + - reviewer + +limits: + timeout_secs: 900 + max_iterations: 24 + max_children: 4 + max_depth: 3 + max_result_chars: 16000 +--- + +# Role + +你是一名严谨的研究 Agent。 + +- 优先使用原始资料。 +- 明确区分事实、推断与建议。 +- 只返回与任务有关的结论、证据和不确定性。 +- 不修改项目文件。 +``` + +Frontmatter 只保存非秘密引用和限制;API key、base URL、headers 继续保存在 `config.json`/`.env`。`llm_profile` 引用现有 `config.agents` key,由 `Config::get_provider_config()` 解析 Provider 与 Model。 + +### 5.3 配置扩展 + +```json +{ + "agent_orchestration": { + "definitions_dir": "~/.picobot/agents", + "root_delegates": ["researcher", "coder", "reviewer"], + "max_tree_depth": 4, + "max_runs_per_tree": 16, + "max_concurrent_runs": 6, + "max_concurrent_runs_per_session": 4, + "max_pending_inbox_events_per_session": 128, + "inbox_event_ttl_hours": 168 + } +} +``` + +`root_delegates` 是 Root Agent 的出边白名单。Root 不需要也不允许出现在 definitions 目录中。 + +### 5.4 加载与校验 + +AgentCatalog 在 Gateway 候选运行代准备阶段完成全部校验: + +- 文件大小、UTF-8、frontmatter 格式和必填字段。 +- ID 格式、重复 ID、保留 ID(`ROOT`、`main` 等)。 +- `llm_profile` 能解析为完整 `LLMProviderConfig`。 +- 每个工具已注册且允许委托。 +- 每个 delegates 目标存在且不是 Root。 +- 限制值在系统硬上限内。 +- 角色正文和描述长度有界。 +- canonical path 位于允许目录,拒绝越界 symlink。 + +任一引用错误应拒绝候选运行代激活,而不是静默删除工具或委托边。AgentCatalog 以 `Arc` 固定在运行代中,已启动任务不读取修改后的文件。 + +## 6. 总体组件设计 + +```mermaid +flowchart LR + Root[Root Agent] --> DT[DelegateTool] + Sub[Sub Agent] --> DT + DT --> C[AgentCoordinator] + C --> AC[AgentCatalog] + C --> DP[DelegationPolicy] + C --> PF[ProviderFactory] + C --> TR[Filtered ToolRegistry] + C --> AR[AgentRunner] + AR --> AL[AgentLoop] + AR --> ES[AgentEventSink] + ES --> DB[(agent_runs / agent_inbox_events)] + DB --> RR[AgentResultRouter] + RR --> SM[SessionManager] + SM --> TM[TurnMailbox / Session Queue] + TM --> Root + SM --> DC[DeliveryCoordinator] +``` + +### 6.1 AgentCatalog + +拥有运行代内不可变的 Agent Definition,提供按 ID 查找、委托目标描述和 definition hash。主 Agent系统提示只获得 ID 与短 description,不加载所有角色正文。 + +### 6.2 AgentCoordinator + +替代当前承担过多职责的 `SubAgentManager`,负责: + +- 解析 caller/target 和授权委托边。 +- 创建 run/group ID、父子关系和预算。 +- 持久化接纳状态后启动 AgentRunner。 +- 管理 foreground await、background spawn、取消和超时。 +- 控制全局、session、Agent 与任务树并发。 +- 生成自动 completion event。 +- 向 WorkManager 条件提交计划子项结果。 + +### 6.3 AgentRunner + +负责一次 Agent Run: + +1. 从 Definition 和运行代创建 Provider。 +2. 构造有效 ToolRegistry。 +3. 组装系统规则、角色正文、任务和显式上下文。 +4. 调用无状态 AgentLoop。 +5. 收集最终正文、媒体、usage、工具次数和子运行引用。 +6. 返回类型化终态,不直接向 Channel 发消息。 + +### 6.4 AgentEventSink / AgentResultRouter + +`AgentEventSink` 负责持久化 signal/completion;`AgentResultRouter` 负责把 pending inbox event 送到原 root session。Router 的内存 wakeup 是加速器,SQLite inbox 才是权威来源。 + +### 6.5 ProviderFactory + +根据 `llm_profile` 创建 Provider,注入 Storage/Observer,并复用当前运行代的 workspace、input types、token limit 和价格信息。Provider 仍是纯 HTTP client,不感知 Session、Channel 或 Agent 图。 + +## 7. 委托图与权限模型 + +### 7.1 基本规则 + +每次委托必须同时满足: + +```text +target != ROOT +target in allowed_targets(caller) +depth < effective_max_depth +tree_run_count < max_runs_per_tree +target not in current_agent_ancestry +remaining budget > 0 +runtime admission is open +``` + +Root 的 allowed targets 来自 `root_delegates`;子 Agent 来自自身 Markdown 的 `delegates`。 + +配置可以出现 A→B 和 B→A,允许两者在不同任务树中互相委托,但一个执行链默认禁止再次出现同一 Agent ID,从而拒绝 A→B→A 的递归乒乓。未来若需要受控返工循环,应设计显式 iteration workflow,而不是放开隐式递归。 + +### 7.2 工具权限 + +调用参数不再提供 `allowed_tools` 扩权。有效工具集为: + +```text +AgentDefinition.tools +∩ 当前运行代已注册工具 +∩ 系统可委托工具策略 +``` + +Tool 增加安全元数据: + +```rust +enum DelegationPolicy { + RootOnly, + Delegatable, + RuntimeInjected, +} +``` + +- `reload_config`、`todo`、管理配置和任意外部发送默认 `RootOnly`。 +- 普通只读工具在明确审查后标记 `Delegatable`。 +- `delegate`、`emit_signal` 等由 Coordinator 根据运行上下文注入,标记 `RuntimeInjected`,不能仅靠 Markdown 获得。 +- 新工具默认 `RootOnly`,避免未来工具无意暴露。 + +目标 Agent 可以拥有调用方没有的专业工具,因为委托边本身就是管理员授权调用该能力;模型不能在单次调用中越过 Definition 扩权。 + +## 8. AgentExecutionContext + +当前只含 session/channel/chat 的隐式上下文不足以支持嵌套委托。目标结构为: + +```rust +pub struct AgentExecutionContext { + pub root_session_id: String, + pub root_turn_id: Option, + pub run_id: String, + pub group_id: Option, + pub parent_run_id: Option, + pub caller_agent_id: String, + pub current_agent_id: String, + pub ancestry: Vec, + pub depth: u16, + pub plan_item_id: Option, + pub cancellation: CancellationToken, + pub budget: AgentBudget, + pub signal_contract: Option, +} +``` + +`ToolExecutionContext` 扩展为包含可选 `AgentExecutionContext`、Turn wakeup handle 和资源 scope。`DelegateTool`、`EmitSignalTool` 必须实现 `execute_with_context`;权限判断不能依赖模型参数或仅依赖 Tokio task-local。 + +task-local 可以继续作为同一调用栈的便利桥接,但不是授权事实来源。Background spawn 必须显式复制所需上下文,不能假设 task-local 跨 `tokio::spawn` 传播。 + +## 9. Delegate 工具设计 + +### 9.1 职责 + +`delegate` 只负责创建 Agent Run,不再同时承担查询、取消、列表。任务管理拆给 `agent_task`: + +```text +delegate → run / run_many +agent_task → get / list / cancel / get_result +``` + +较小、单一的 schema 能减少模型错误调用,也便于分别授权。 + +### 9.2 单任务请求 + +```json +{ + "target": "researcher", + "task": "分析当前 Provider 扩展点", + "context": "重点关注热重载和 usage 持久化", + "mode": "foreground", + "plan_item_id": "T2" +} +``` + +### 9.3 批量请求 + +```json +{ + "mode": "foreground", + "tasks": [ + {"target": "researcher", "task": "研究方案 A"}, + {"target": "coder", "task": "分析实现 B"}, + {"target": "reviewer", "task": "评审风险 C"} + ] +} +``` + +批量 foreground 的三个子任务并发执行,delegate 等待全部终态后返回聚合结果。这等价于旧 `parallel`,但不再把 parallel 当作生命周期模式。 + +批量 background 同样并发接纳,立即返回: + +```json +{ + "group_id": "group-123", + "runs": [ + {"run_id": "run-a", "agent": "researcher", "status": "queued"}, + {"run_id": "run-b", "agent": "coder", "status": "queued"}, + {"run_id": "run-c", "agent": "reviewer", "status": "queued"} + ] +} +``` + +### 9.4 Background 投递契约 + +```json +{ + "target": "service-monitor", + "task": "监控服务错误率", + "mode": "background", + "delivery": { + "signal": "steer", + "completion": "queue", + "failure": "steer" + } +} +``` + +- `signal`:运行中主动事件的投递方式。 +- `completion`:正常终态结果的投递方式,默认 queue。 +- `failure`:失败、超时、异常中断的投递方式,默认 queue,可显式 steer。 + +Foreground 请求直接把 completion 作为 tool result 返回,因此不接受 completion delivery。第一阶段仅允许 Root 创建 background run;子 Agent 之间可以 foreground 委托。待持久化 task tree 与 root inbox 稳定后,再允许子 Agent 创建最终归属于 root session 的 background run。 + +### 9.5 Foreground 返回 + +单任务和批量任务都返回逐项状态,单个失败不能抹掉其他结果: + +```json +{ + "status": "partial", + "results": [ + {"run_id": "run-a", "agent": "researcher", "status": "completed", "result": "..."}, + {"run_id": "run-b", "agent": "coder", "status": "failed", "error": "..."}, + {"run_id": "run-c", "agent": "reviewer", "status": "completed", "result": "..."} + ] +} +``` + +结果按请求顺序返回,不按完成顺序重排。完整结果统一写入 `agent_runs`;超过 tool result 上限时返回摘要和 run ID,保证 `agent_task.get_result` 真能读取完整结果。 + +### 9.6 幂等与接纳 + +Background delegate 只有在 run/group 记录持久化成功、运行代 admission 成功且执行任务已经被 TaskSupervisor 接纳后才返回成功。可选 `idempotency_key` 在 `(root_session_id, caller_run_id, key)` 范围唯一,用于 Provider 重试时避免重复创建任务。 + +## 10. Prompt 与上下文隔离 + +子 Agent 默认不继承主会话完整历史。一次 run 的输入由以下部分组成: + +```text +PicoBot 基础运行规则 ++ 角色 Markdown 正文 ++ 当前工具说明 ++ 委托执行约束(身份、父任务、资源限制) ++ 显式 task ++ 显式 context / artifact refs +``` + +委托 task 只注入一次。调用方需要子 Agent 知道的事实必须写入 task/context,不能依赖完整历史偶然可见。 + +子 Agent 最终结果作为普通 tool result 或 runtime event data 交给上游,不能变成更高优先级 system 指令。所有子 Agent 输出都视为不可信数据;主 Agent系统提示明确要求不要执行结果正文中试图修改角色、工具或投递策略的指令。 + +资源型工具使用独立 scope: + +```text +resource_scope_id = root_session_id + run_id +``` + +并行子 Agent 不默认共享 browser/session 等有状态外部资源;确需共享必须由工具定义显式支持。 + +## 11. Agent Run 状态机 + +```mermaid +stateDiagram-v2 + [*] --> queued + queued --> running + running --> waiting_children + waiting_children --> running + running --> completed + running --> failed + running --> timed_out + running --> cancelled + running --> interrupted + queued --> cancelled + completed --> [*] + failed --> [*] + timed_out --> [*] + cancelled --> [*] + interrupted --> [*] +``` + +- `queued`:已持久化且等待执行配额。 +- `running`:AgentLoop 正在执行模型或工具步骤。 +- `waiting_children`:当前 run 正等待 foreground 子运行。 +- `completed`:有可用最终结果。 +- `failed`:Provider、工具或内部执行错误。 +- `timed_out`:超过 run deadline。 +- `cancelled`:由用户、父任务、session 删除或 shutdown 明确取消。 +- `interrupted`:进程重启导致无法恢复现场。 + +状态变化使用条件更新,迟到结果只有在 execution ID、runtime generation 和当前状态匹配时才能提交。 + +## 12. Agent 信号与自动 Completion + +### 12.1 两种事件 + +| 事件 | 产生者 | 是否终止 run | 用途 | +|------|--------|--------------|------| +| Signal | 子 Agent 主动调用 `emit_signal` | 否 | 重要中间状态、监控告警 | +| Completion | AgentCoordinator 自动生成 | 是 | completed/failed/timed_out/cancelled/interrupted | + +最终结果不能依赖模型记得调用工具。即使 Provider 异常、超时或任务被取消,Coordinator 也必须产生终态事件。 + +### 12.2 EmitSignalTool + +```json +{ + "key": "service-error-threshold", + "severity": "critical", + "summary": "服务错误率超过 5%", + "details": { + "current": 0.071, + "threshold": 0.05 + }, + "dedupe_key": "service-a:error-rate" +} +``` + +`emit_signal` 不接受 target session、channel、chat ID 或 delivery 参数。目标、queue/steer、最大次数、速率和 root session 全部来自 `AgentExecutionContext.signal_contract`。 + +工具调用只有在 signal event 持久化后才成功返回: + +```json +{ + "signal_id": "signal-123", + "status": "accepted", + "delivery": "steer" +} +``` + +Coordinator 强制执行: + +- 每 run 信号总数与累计字节上限。 +- 最小发送间隔和 burst 上限。 +- dedupe key 冷却窗口。 +- severity allowlist。 +- summary/details 大小与 JSON 深度限制。 +- 只能投递到创建该 run 的 root session。 + +普通进度不应滥用 signal。工具调用进度继续通过内部 Observer/TurnEvent 投影到 UI;只有需要主 Agent采取行动的事件才使用 `emit_signal`。 + +### 12.3 Completion 去重 + +Completion 包含本 run 已发出的 signal IDs。若最终总结重复某个信号,主 Agent可以识别并避免再次报告。正常 completion 可以配置 queue;关键 failure 可以配置 steer。禁止完全静默丢弃失败,`silent` 若未来开放也只能用于正常 completion。 + +## 13. SendMessage、EmitSignal 与附件职责 + +三者方向不同,不合并为一个万能工具: + +```text +emit_signal 子 Agent → Agent Inbox → 主 Agent(内部输入) +send_message Agent → OutboundDispatcher → Channel/用户(外部输出) +attach_artifact 工具产物 → 当前 Turn → DeliveryCoordinator(当前回复附件) +``` + +### 13.1 send_message + +只负责用户明确授权的跨 Channel/跨会话外部消息,具有真实外部副作用。默认 `RootOnly`,目标和文件参数继续受 Channel/file transfer 限制。`origin` 不再由模型自由填写,改由 ToolExecutionContext 生成,避免来源伪造。 + +### 13.2 emit_signal + +只负责后台子 Agent 的结构化内部事件。它没有任意目标、媒体或直接用户投递能力,不写目标会话 assistant history。 + +### 13.3 attach_artifact + +当前 `send_message(files=...)` 对同 session 的特殊暂存行为长期应拆成 `attach_artifact` 或统一 ToolResult media side channel。短期保留兼容路径,但新子 Agent 信号设计不得依赖它。 + +### 13.4 自动 completion + +Completion 不是工具。AgentRunner 的终结路径统一保存结果并创建 event,避免模型遗漏。 + +## 14. 持久化模型 + +### 14.1 agent_runs + +```text +agent_runs +---------- +id TEXT PRIMARY KEY +group_id TEXT +root_session_id TEXT NOT NULL +root_turn_id TEXT +parent_run_id TEXT +caller_agent_id TEXT NOT NULL +agent_id TEXT NOT NULL +definition_hash TEXT NOT NULL +provider_profile TEXT NOT NULL +provider_name TEXT NOT NULL +model_id TEXT NOT NULL +mode TEXT NOT NULL +depth INTEGER NOT NULL +plan_item_id TEXT +task TEXT NOT NULL +context_json TEXT +status TEXT NOT NULL +result TEXT +error TEXT +prompt_tokens INTEGER +completion_tokens INTEGER +cost REAL +tool_calls_count INTEGER NOT NULL DEFAULT 0 +iterations INTEGER NOT NULL DEFAULT 0 +runtime_generation TEXT NOT NULL +attempt INTEGER NOT NULL DEFAULT 1 +started_at INTEGER +finished_at INTEGER +created_at INTEGER NOT NULL +``` + +不保存 API key、Authorization header、Provider 私有 reasoning state 或完整 connection URL。 + +### 14.2 agent_run_groups + +```text +agent_run_groups +---------------- +id +root_session_id +caller_run_id +mode +completion_policy all | each +expected_runs +terminal_runs +deadline_at +status +created_at +finished_at +``` + +批量 background 默认 `completion_policy=all`,等全部 run 进入终态或 group deadline 后只唤醒主 Agent一次。独立 run 可通过 300–500ms debounce 合并,避免连续启动多个内部 Turn。 + +### 14.3 agent_inbox_events + +```text +agent_inbox_events +------------------ +id TEXT PRIMARY KEY +root_session_id TEXT NOT NULL +run_id TEXT NOT NULL +group_id TEXT +event_type signal | completion +event_key TEXT NOT NULL +delivery queue | steer +severity TEXT +payload_json TEXT NOT NULL +status pending | leased | admitted | consumed | dead_letter +attempt_count INTEGER NOT NULL DEFAULT 0 +lease_token TEXT +lease_until INTEGER +admitted_turn_id TEXT +created_at INTEGER NOT NULL +consumed_at INTEGER + +UNIQUE(run_id, event_type, event_key) +``` + +完整结果保存在 `agent_runs.result`,inbox payload 默认只放有界摘要、元数据和 result reference,避免复制大文本。 + +### 14.4 原子事务 + +Agent completion 必须在一个 Storage 事务中: + +```text +UPDATE agent_runs terminal state/result/usage +UPDATE agent_run_groups terminal count/status +INSERT agent_inbox_events ... ON CONFLICT DO NOTHING +UPDATE bound task item by execution_id +COMMIT +``` + +事务失败时不能对外宣称任务完成。内存 wakeup 只有在 commit 成功后发送。 + +## 15. Background 事件投递 + +### 15.1 统一输入类型 + +当前 user-only SteeringMailbox 演进为保留来源的 TurnMailbox: + +```rust +pub struct TurnInput { + pub id: String, + pub sequence: u64, + pub source: TurnInputSource, + pub delivery: InputDelivery, + pub content: String, + pub media_refs: Vec, + pub durable_event_id: Option, + pub received_at: i64, +} + +pub enum TurnInputSource { + User, + AgentSignal { run_id: String, agent_id: String }, + AgentCompletion { run_id: String, agent_id: String }, +} + +pub enum InputDelivery { + Queue, + Steer, +} +``` + +`SourceKind` 增加 `agent_signal`、`agent_result`。Provider 不支持 runtime role 时可以序列化为有明确 envelope 的 user-compatible message,但持久化来源、客户端渲染和取消恢复必须保持类型,不得显示成用户气泡。 + +### 15.2 路由规则 + +| 主 Agent 状态 | queue | steer | +|----------------|-------|-------| +| 无活动 Turn | 入 session queue,启动内部 Turn | 退化为 queue,启动内部 Turn | +| 活动 Turn 接受输入 | 入下一 Turn | 入当前 TurnMailbox | +| TurnMailbox 满/已关闭 | 保持 durable pending 后排队 | 可靠退化为 queue | +| Provider 请求进行中 | 等下一 Turn | 等请求结束后的安全边界 | +| 普通工具批次进行中 | 等下一 Turn | 等完整工具批次结束 | +| sleep 进行中 | 唤醒 sleep,内容仍留在 queue | 唤醒 sleep,并在工具批次后注入当前 Turn | + +Steer 不承诺硬实时抢占。最迟可见时间由当前不可分割 Provider 请求或工具步骤决定。需要当前逻辑必然依赖子结果时应使用 foreground;不要用 background+steer 模拟同步调用。 + +### 15.3 原子 admission 与 fallback + +AgentResultRouter 对 steer 事件执行与用户 steering 相同级别的原子判定: + +1. 获取目标 Session。 +2. 在 Session 状态锁内分配单调 sequence。 +3. 若 active Turn 正在 accepting,尝试 push TurnMailbox。 +4. 若 closed/full/不存在,创建内部 AgentTask 放入有界 session queue。 +5. 内存 admission 结果与 durable event lease 关联。 + +任何竞态下事件只能属于当前 Turn 或后续 Turn之一,不能同时进入两者,也不能两者都不进入。Session queue 饱和时事件继续保持 durable pending,由 Router 重试;不能像普通瞬时通知一样丢弃。 + +### 15.4 Mailbox 容量与公平性 + +用户输入和 Agent 事件共享接收顺序,但使用独立容量配额,避免相互挤占: + +```text +user steer lane: 32 messages / 64 KiB +agent event lane: 8 messages / 32 KiB +``` + +排空时按 session sequence 合并。重要 AgentSignal 可以保留专用容量,但不默认越过更早已接受的用户输入。信号洪泛由 emit_signal rate limit 和 inbox 上限共同控制。 + +### 15.5 安全边界注入 + +AgentLoop 只在以下边界排空 steer: + +- 一个完整工具批次结束后。 +- Provider 返回无工具候选最终回复、但 mailbox 有新输入时。 +- 明确可安全取消的等待工具被唤醒后。 + +输入被排空后保留为 in-flight,只有整个 Turn 消息和 event consumption 原子提交成功才确认。Provider/工具/持久化失败时恢复原事件。 + +## 16. 主 Agent 忙碌与空闲 + +### 16.1 主 Agent 忙碌 + +- queue event 只进入后续内部任务,不改变当前上下文。 +- steer event 进入 TurnMailbox,在安全边界参与当前 Turn。 +- 当前 Turn 已 finalizing/closed 时,steer 自动退化为 queue。 +- UI 可以立即展示“信号已接纳/后台任务已完成”,但用户可见最终结论仍由主 Agent产生。 + +### 16.2 主 Agent 空闲 + +Session worker 不做固定频率忙轮询。正常路径由 AgentResultRouter 发出 best-effort session wakeup;worker 被唤醒后领取 inbox event 并创建内部 Turn。Gateway 启动、reload 激活和周期恢复任务扫描 pending/expired lease,弥补丢失唤醒。 + +Session worker 调度优先级: + +```text +当前活动 Turn +> 已排队用户输入 +> queue background result continuation +> 等待新事件 +``` + +Steer event 在没有活动 Turn 时按 queue 处理。用户输入优先避免后台总结打断新请求;UI 未读状态避免持续用户流量下结果不可见。 + +### 16.3 内部 continuation Turn + +内部任务不是伪造的 InboundMessage: + +```rust +enum AgentTaskSource { + UserInput, + BackgroundAgentResults { event_ids: Vec, group_id: Option }, + ScheduledTask, +} +``` + +SessionManager 直接把领取的结果构造成 bounded runtime context,并加入一个内部触发语义:“检查这些后台结果,结合原始目标验证和汇总,再向用户报告。”内部输入不显示用户气泡;主 Agent输出按普通 assistant Turn 持久化和投递。 + +### 16.4 消费确认 + +领取流程: + +```text +pending → leased → admitted → consumed +``` + +`lease_token` 防止重复 worker 处理。同一事务必须保存主 Agent Turn 和把对应 inbox events 标记 consumed。若 AgentLoop 失败、Turn 取消或 Gateway 崩溃,lease 到期后事件恢复 pending。 + +外部 LLM 调用无法严格 exactly-once。为降低恢复重跑的副作用,background result continuation 默认只开放只读/汇总工具;需要外部写操作时由主 Agent向用户确认,或工具自身使用幂等键。 + +## 17. 可唤醒 Sleep 设计 + +### 17.1 目标语义 + +`sleep` 仍是最长 24 小时、不可持久恢复的前台等待工具,但当前 session 接收到任何新输入时立即结束等待: + +- user steer +- user queue +- AgentSignal queue/steer +- AgentCompletion queue/steer +- `/stop`、shutdown 和父 cancellation + +Sleep 只负责唤醒,不负责消费输入。queue 内容仍属于下一 Turn;steer 内容仍由 TurnMailbox 在工具批次后注入。 + +### 17.2 Wakeup handle + +`ToolExecutionContext` 增加: + +```rust +pub struct TurnWakeupHandle { + pub receiver: watch::Receiver, +} + +pub struct TurnWakeupState { + pub revision: u64, + pub pending_steer: usize, + pub pending_queue: usize, + pub latest_source: WakeupSource, + pub latest_preview: Option, +} +``` + +使用 watch revision 而不是裸 Notify,避免输入恰好在 sleep 开始监听前到达而丢失唤醒。执行前先比较当前 revision/pending,再进入 select。 + +### 17.3 Sleep 执行 + +```rust +tokio::select! { + _ = tokio::time::sleep(duration) => SleepOutcome::Elapsed, + changed = wakeup.changed() => SleepOutcome::InputArrived(changed), + _ = cancellation.cancelled() => SleepOutcome::Cancelled, +} +``` + +返回示例: + +```text +Sleep 提前结束:已等待 37 秒。 +收到一条 steer AgentSignal(run_id=run-123):服务错误率超过 5%。 +该信号将在当前 Turn 的下一个安全边界注入。 +``` + +queue 输入不能把正文泄漏给当前 Turn,否则等价于偷偷 steer。其返回只能说明类型和数量: + +```text +Sleep 提前结束:收到一条排队输入。 +内容不会进入当前 Turn,将在当前工作结束后的下一 Turn处理。 +``` + +### 17.4 工具中断策略 + +新增工具元数据: + +```rust +enum InputInterruptPolicy { + Never, + WakeOnly, + CancelSafe, +} +``` + +- `sleep`:`WakeOnly`,输入使工具正常提前返回。 +- 明确只读且可重试的等待工具可标记 `CancelSafe`。 +- bash、写文件、发送消息和未知外部副作用工具默认 `Never`。 + +Steer 不自动取消 `Never` 工具。未来若需要硬抢占,应新增独立 `interrupt` 策略并定义 partial tool 状态、幂等和恢复;本设计不把它隐含进 steer。 + +## 18. 取消、停止与恢复 + +### 18.1 Foreground 结构化取消 + +Foreground 子 run 是父 run 的结构化子任务: + +- 父 Turn 取消会取消所有未终态 foreground 后代。 +- timeout token 与父 cancellation token 组合。 +- run 进入 waiting_children 时仍保留所有权,但不能长期占用模型执行 permit。 +- 父取消后迟到结果不能提交为 completed。 + +### 18.2 Background 所有权 + +Background run 归 root session 所有,不归发起它的模型 future 所有。Root Turn结束不会自动取消它。 + +保留当前 `/stop` 的明确停止语义:取消目标 session 的 active Turn、排队用户输入以及所有非 Scheduler background Agent run。需要跨 `/stop` 和重启长期存在的监控应创建 Scheduler monitor,而不是普通 background delegate。 + +### 18.3 `/stop` 与 durable Agent events + +用户 steering 按现有语义可被 `/stop` 丢弃;已经持久化的 AgentSignal/Completion 不能静默消失: + +- 已进入 current Turn 但尚未提交的 durable event 恢复 pending。 +- 被取消 background run 产生 cancelled completion,供 UI/主 Agent获知。 +- 用户显式执行 `agent_task.cancel` 后,可以将该 run 未消费的普通 signal 标记 superseded,但保留审计记录。 + +### 18.4 Gateway reload + +AgentCatalog、ProviderFactory、Coordinator 和 inbox router 属于 Gateway runtime generation: + +- reload 关闭 admission 后不接受新 background run/signal。 +- 已进入旧代的 run 固定使用旧 definition hash、Provider 和工具策略。 +- 排空期等待前台 Turn、Scheduler 和 background run 到持久化边界。 +- 超过总排空期限的 run 被取消/中断并写终态事件。 +- pending inbox event 留在 SQLite,由新运行代恢复投递。 + +### 18.5 进程重启 + +进程退出后无法恢复正在进行的 LLM stream。启动恢复将旧 `running/waiting_children` 标记 `interrupted` 并生成 completion。普通有副作用 Agent run 不自动重试;只读、显式配置 idempotency/restart policy 的监控任务可以创建新 attempt,并保留原 run 的中断记录。 + +## 19. 并发、预算与死锁避免 + +### 19.1 限制层次 + +```text +Gateway 全局 active provider/tool permits +└── per-session permits + └── per-agent permits + └── per-tree max runs/depth/children/token/cost +``` + +批量请求必须有 `maxItems`,Coordinator 还会按剩余 tree budget 裁剪/拒绝,不能让模型生成任意数量任务。 + +### 19.2 父子等待死锁 + +不能让一个等待 foreground child 的父 run 一直持有唯一执行 permit,否则并发上限为 1 时形成: + +```text +父 run 持有 permit → 等子 run → 子 run 永远拿不到 permit +``` + +permit 应限制活跃 Provider/工具步骤,而不是整个 Agent Run 生命周期。父 run 进入 `waiting_children` 前释放执行 permit,子 run 完成后父 run 再竞争 permit 继续模型迭代。Task tree ownership、timeout 和 cancellation 不随 permit 释放而消失。 + +### 19.3 预算传播 + +每次子委托从父 budget 派生硬上限: + +```text +child deadline <= parent deadline +child max depth <= remaining depth +sum child token/cost reservation <= remaining tree budget +``` + +调用方可以收紧 timeout/结果大小,但不能超过 Agent Definition 和系统上限。 + +## 20. 可观测性与客户端表现 + +### 20.1 运行树 + +WebUI 管理面展示: + +- group/run/parent ID。 +- Agent ID、Provider profile、Model。 +- queued/running/waiting_children/terminal 状态。 +- signal 数量和最近 severity。 +- duration、usage、cost、工具次数。 +- 取消/超时/中断原因。 + +### 20.2 Chat 表现 + +- Foreground delegate 继续作为当前 Turn 的可折叠工具块。 +- Background delegate 启动后显示 run/group ID,不假装任务已完成。 +- AgentSignal 显示为独立运行时信号卡片,不显示成用户气泡。 +- queue completion 在主 Agent内部 continuation 后只显示主 Agent汇总回复。 +- steer 信号可以在当前 Turn 工具状态中显示“已接纳”,最终历史由 Turn commit 校准。 + +### 20.3 隐私与日志 + +- 不显示/记录 Agent reasoning 和 Provider 私有 state。 +- 默认日志只记录 run ID、Agent ID、状态、duration、usage 和截断错误。 +- task/result 正文不进入 info 日志。 +- API key、headers、临时凭据、含 credential URL 永不落库或日志。 + +## 21. 失败语义 + +| 失败点 | 对外语义 | +|--------|----------| +| Agent Definition 无效 | 拒绝候选运行代;旧代继续服务 | +| 委托边不允许 | delegate 立即返回 permission denied,不创建 run | +| Background 持久化失败 | delegate 返回失败,不报告 run ID | +| TaskSupervisor 拒绝 spawn | run 条件更新 cancelled/failed,再返回失败 | +| Provider 创建失败 | run failed,foreground 返回错误;background 生成 failure event | +| Inbox wakeup 丢失 | pending event 由恢复扫描重新唤醒 | +| TurnMailbox closed/full | steer 可靠退化 queue | +| Session queue 满 | durable event 保持 pending,Router 有界重试 | +| Main continuation Provider 失败 | event lease 到期并重试;不标 consumed | +| 主 Agent回复持久化失败 | event 不确认,避免结果消失 | +| Channel 最终投递失败 | assistant history已持久化;沿用 DeliveryCoordinator terminal fallback | + +所有重试必须有次数、退避、deadline 和分类;永久错误立即终态化,不能无界重试。 + +## 22. 兼容迁移 + +### 22.1 Delegate 参数 + +旧模式映射: + +```text +inline → foreground +parallel → foreground + tasks[] +background → background +async → background(若曾接受该别名) +``` + +过渡期解析旧参数并在 tool result/日志中给出弃用提示;新 system prompt 只描述 canonical 值 `foreground/background`。 + +### 22.2 allowed_tools + +旧 `allowed_tools` 首先变成只能收紧 Definition.tools 的兼容字段,不能扩权;随后从 schema 删除。没有 target 的旧委托映射到内置 `general` Agent Definition。 + +### 22.3 background_tasks + +新增 `agent_runs` 后: + +- 新任务只写新表。 +- 管理 API 在过渡期 union 读取旧 `background_tasks` 与新 `agent_runs`。 +- 旧终态记录按原 TTL 清理,不强制迁移正文。 +- 旧 pending/running 记录在升级启动时按 interrupted/cancelled 规则收敛。 + +### 22.4 版本与文档 + +本设计文档本身不改变产品行为。实现功能合并时按项目规则增加中段版本,并同步更新 README、`docs/ARCHITECTURE.md`、AGENTS.md 和 `resources/skills/about-picobot/references/`。 + +## 23. 实现分期 + +### Phase 1:具名 Agent 与 Foreground + +- 新增 AgentDefinition/AgentCatalog loader。 +- Definition 解析不同 Provider profile 和固定工具集。 +- Delegate schema 使用 target + foreground/background canonical modes。 +- 批量 foreground 并发执行并聚合。 +- 显式 AgentExecutionContext 和委托图授权。 +- 保持旧 background 通知路径作为兼容,但不开放嵌套 background。 + +### Phase 2:统一 Agent Run 持久化 + +- 新增 `agent_runs`、`agent_run_groups`、Storage transaction API。 +- 拆分 `delegate` 与 `agent_task`。 +- Foreground 结果也持久化,修复截断结果不可查询。 +- 实现结构化取消、预算与 permit 释放。 + +### Phase 3:Agent Inbox 与 Queue Completion + +- 新增 `agent_inbox_events`、lease、恢复扫描。 +- Background completion 从 Channel direct notification 改为主 Agent内部 continuation。 +- 增加 SourceKind::AgentResult、内部 AgentTask 和 WebUI 投影。 +- 批次 completion 合并与 debounce。 + +### Phase 4:Emit Signal 与 Steer + +- 新增 EmitSignalTool、SignalContract 和 rate/dedupe。 +- SteeringMailbox 泛化为来源感知 TurnMailbox。 +- 实现 steer admission、queue fallback、durable ack/recovery。 +- 添加 AgentSignal UI 和任务树。 + +### Phase 5:可唤醒 Sleep 与工具中断元数据 + +- ToolExecutionContext 增加 TurnWakeupHandle。 +- SleepTool 使用 watch revision + timer + cancellation select。 +- queue/steer 唤醒内容边界和测试。 +- 为工具增加 InputInterruptPolicy,默认 Never。 + +## 24. 预计代码边界 + +建议模块拆分: + +```text +src/agent/ +├── definition.rs AgentDefinition / loader +├── catalog.rs immutable AgentCatalog +├── coordinator.rs authorization / lifecycle / budgets +├── run.rs AgentRun types / AgentRunner +├── inbox.rs event types / router contracts +└── sub_agent.rs 迁移兼容层,最终缩减或删除 + +src/tools/ +├── delegate.rs create run/group only +├── agent_task.rs get/list/cancel/get_result +├── emit_signal.rs constrained internal signal +├── sleep.rs wake-aware wait +└── send_message.rs external delivery only + +src/session/ +├── turn_mailbox.rs typed steer inputs +├── agent_inbox.rs claim/admit/ack orchestration +└── session.rs typed AgentTask scheduling + +src/storage/ +├── agent_run.rs +└── agent_inbox.rs +``` + +`AgentLoop` 只需要理解来源感知输入的安全边界追加,不拥有 AgentCatalog、任务树或 inbox persistence。 + +## 25. 测试矩阵 + +### 25.1 Definition 与授权 + +- 解析合法 Markdown、frontmatter 与 Unicode 正文。 +- 重复/保留 ID、未知 Provider、未知工具、未知 delegate target 拒绝加载。 +- path traversal/symlink 越界拒绝。 +- Root 永远不能成为 target。 +- 未声明 A→B 时拒绝;声明后允许。 +- A→B→A 在单链中拒绝。 +- 模型参数不能扩大工具、timeout、depth 或预算。 + +### 25.2 Foreground/Background + +- 单 foreground 结果立即成为父 tool result。 +- 三个 foreground task 并发执行、父等待全部、结果按请求顺序。 +- 单项失败不丢其他项结果。 +- Background 只有持久化并成功 spawn 后才返回 run ID。 +- 同 idempotency key 不重复创建 run。 +- Background completion 不直接伪装为用户消息。 + +### 25.3 Provider 与工具 + +- 不同 Agent 使用不同 provider/model profile。 +- Provider storage/observer 正确注入。 +- RootOnly 工具不能通过 Markdown 或兼容 allowed_tools 获得。 +- runtime-injected delegate/emit_signal 只在上下文允许时存在。 +- 并行 run 的 browser/resource scope 隔离。 + +### 25.4 Inbox 与投递竞态 + +- completion update 与 inbox insert 原子。 +- wakeup 丢失后启动扫描恢复。 +- active accepting Turn 的 steer 进入 current Turn。 +- finalizing/closed/full 时 steer 恰好一次退化 queue。 +- 无活动 Turn 的 steer 启动内部 continuation。 +- 用户队列优先于 queue completion。 +- Turn persist 失败时 event 不 consumed。 +- lease 超时后可重领,旧 lease token 不能提交。 +- 多 run group 只触发一次汇总 Turn。 + +### 25.5 Signal + +- emit_signal 无上下文或 foreground 禁止策略时失败。 +- 不能指定任意 target/channel/delivery。 +- dedupe key、速率、数量和大小上限生效。 +- signal 不结束 Agent Run。 +- Agent 异常退出仍自动生成 failure completion。 +- 已发 signal IDs 出现在 completion,避免重复汇报。 + +### 25.6 Sleep + +- 无输入时精确等待至 timer。 +- user steer、AgentSignal steer 立即唤醒并随后注入当前 Turn。 +- user queue、AgentCompletion queue 唤醒但正文不泄漏当前 Turn。 +- 输入先于 sleep 订阅时 revision 检查仍立即返回。 +- 多条输入只消费一次且顺序稳定。 +- `/stop`、parent cancellation、shutdown 取消 sleep 并终态化工具块。 +- wakeup 与 timer 同时发生时不丢输入;输入若未入当前 Turn则可靠排队。 + +### 25.7 取消、并发与恢复 + +- 父 foreground 取消级联后代。 +- 父 waiting_children 不持有唯一 permit,无死锁。 +- `/stop` 取消 session background runs,并恢复未提交 durable events。 +- 迟到结果不能覆盖 cancelled/interrupted。 +- reload 关闭 admission 后拒绝新 run/signal,pending inbox 由新代恢复。 +- Gateway 重启把 running 标记 interrupted 并生成 completion。 +- session 删除/归档后的事件按明确 dead-letter/cancel 策略收敛。 + +### 25.8 消息与客户端 + +- AgentSignal 不渲染为用户气泡。 +- Internal continuation 输入不出现在普通历史,assistant 汇总正常持久化。 +- reasoning/provider state 不进入信号、API、客户端和日志。 +- send_message 仍走外部投递确认;emit_signal 不走 OutboundDispatcher。 +- 同 Turn 附件兼容路径与未来 attach_artifact 不产生重复历史。 + +## 26. 必须保持的架构不变量 + +1. 同一 Session 最多一个活动主 Agent Turn;Agent 子 run 可以并行,但不能并发提交主会话历史。 +2. Root Agent 不能成为委托目标,结果回传不等同于反向委托。 +3. Foreground/Background 只描述委托方等待行为;并发是独立调度维度。 +4. Queue 输入永不泄漏正文到当前 Turn;Steer 只在安全边界注入。 +5. Signal 先持久化后唤醒;内存通知不是事实来源。 +6. Signal 是非终态事件,Completion 由运行时自动生成且恰好对应一个 run 终态。 +7. SendMessage 是外部输出,EmitSignal 是内部输入,不能用一个公开万能工具混合权限。 +8. Durable Agent event 在 `/stop`、Turn 失败或 Gateway 崩溃时不能静默丢失。 +9. Agent Definition 和 Provider 绑定 runtime generation;运行中不热切换。 +10. 不持有 Session mutex 等待 Provider、工具、SQLite 或子 run。 +11. 父 run 等待子 run 时不持有会造成递归死锁的执行 permit。 +12. 完成状态、结果、usage、计划子项和 inbox event 使用事务/条件更新提交。 +13. 客户端、Channel 和日志永不暴露 Provider 私有 reasoning state、secret 或本地内部路径。 + +## 27. 设计结论 + +目标架构把现有“一个 delegate 工具创建临时 Agent”提升为明确的编排系统: + +```text +Markdown Agent Definition + ↓ +AgentCatalog + DelegationPolicy + ↓ +AgentCoordinator + ├─ foreground:并发执行、父等待、tool result 返回 + └─ background:run ID 返回、signal/completion 进入 durable inbox + ↓ + queue | steer + ↓ + Session queue | current TurnMailbox + ↓ + Root Agent +``` + +Foreground 解决依赖型子任务;Background+Queue 解决稍后统一处理;Background+Steer 解决长任务期间的重要监控信号;可唤醒 Sleep 为安全等待提供及时响应点。三条消息路径各自保持单一职责:`emit_signal` 内部告警、自动 completion 终态回传、`send_message` 外部投递。该划分能够在不破坏 PicoBot Session/Turn/Delivery 既有不变量的前提下分阶段实现,并为权限、持久化、取消、热重载和客户端表现提供可验证边界。