- 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
1368 lines
66 KiB
Markdown
1368 lines
66 KiB
Markdown
# PicoBot 子 Agent 编排与信号投递架构升级设计
|
||
|
||
> 状态:分阶段实施中(2026-08)。Phase 1 的具名 Definition/Catalog、Provider profile、工具与 Skill 裁剪、显式执行上下文、委托图校验和批量 foreground 已落地;durable run/inbox、signal/steer 与可唤醒 sleep 仍按本文后续阶段实施。
|
||
>
|
||
> 本文定义具名子 Agent、委托图、多 Provider、`delegate`、`emit_signal`、后台结果收件箱、`queue`/`steer` 投递以及可唤醒 `sleep` 的目标架构。各阶段是否已经实现以代码、测试和 `docs/ARCHITECTURE.md` 为准;未落地章节仍是目标设计。
|
||
>
|
||
> 2026-08 评审提出的 A1–A5、B1–B6、C1–C4 已纳入本文;逐项决策与理由见 [`SUB_AGENT_ORCHESTRATION_REVIEW_RESPONSE.md`](SUB_AGENT_ORCHESTRATION_REVIEW_RESPONSE.md),代码级落地方案与验收门槛见 [`SUB_AGENT_ORCHESTRATION_IMPLEMENTATION.md`](SUB_AGENT_ORCHESTRATION_IMPLEMENTATION.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 时由运行时自动产生的终态事实;background 按 group policy 投影为 run/group inbox event |
|
||
| 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
|
||
|
||
skills:
|
||
- technical-research
|
||
|
||
limits:
|
||
timeout_secs: 900
|
||
max_iterations: 24
|
||
max_children: 4
|
||
max_depth: 3
|
||
max_concurrent_runs: 2
|
||
max_concurrent_provider_steps: 1
|
||
max_concurrent_tool_steps: 4
|
||
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。
|
||
|
||
`skills` 是可选的受信任 allowlist;只有 Definition 工具集包含 `get_skill` 时才向子 Agent注入这些 Skill。子 Agent不继承主会话临时启用的 Skill、完整历史或 memory recall。调用方需要传递的事实必须进入显式 task/context;未来若支持 memory,只能通过管理员配置的只读 scope 开放。
|
||
|
||
### 5.3 配置扩展
|
||
|
||
```json
|
||
{
|
||
"agent_orchestration": {
|
||
"definitions_dir": "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_concurrent_provider_steps": 8,
|
||
"max_concurrent_provider_steps_per_session": 4,
|
||
"max_concurrent_tool_steps": 16,
|
||
"max_concurrent_tool_steps_per_session": 8,
|
||
"max_pending_inbox_events_per_session": 128,
|
||
"inbox_event_ttl_hours": 168,
|
||
"max_inbox_delivery_attempts": 8,
|
||
"max_user_turn_burst_before_inbox": 4,
|
||
"max_inbox_wait_secs": 30
|
||
}
|
||
}
|
||
```
|
||
|
||
`root_delegates` 是 Root Agent 的出边白名单。Root 不需要也不允许出现在 definitions 目录中。
|
||
|
||
`definitions_dir` 的相对路径按实际加载的 `config.json` 所在目录解析;第一版要求 canonical path 保持在该受信任配置目录内。默认值 `agents` 对应 `~/.picobot/config.json` 旁的 `~/.picobot/agents/`,也能让仓库内 fallback `./config.json` 使用同仓库配置目录而不跨越信任边界。
|
||
|
||
### 5.4 加载与校验
|
||
|
||
AgentCatalog 在 Gateway 候选运行代准备阶段完成全部校验,但候选代不得在此时扫描或修改 inbox/run 恢复状态;恢复扫描、旧 run 收敛和 Router 启动只能在候选代成为活动运行代后的 activation 阶段执行:
|
||
|
||
- 文件大小、UTF-8、frontmatter 格式和必填字段。
|
||
- ID 格式、重复 ID、保留 ID(`ROOT`、`main` 等)。
|
||
- `llm_profile` 能解析为完整 `LLMProviderConfig`。
|
||
- 每个工具已注册且允许委托。
|
||
- 每个 delegates 目标存在且不是 Root。
|
||
- 限制值在系统硬上限内。
|
||
- 角色正文和描述长度有界。
|
||
- Skill allowlist 中的每个 ID 均存在,且只有允许 `get_skill` 的角色可以声明。
|
||
- canonical path 位于允许目录,拒绝越界 symlink。
|
||
|
||
任一引用错误应拒绝候选运行代激活,而不是静默删除工具或委托边。AgentCatalog 以 `Arc` 固定在运行代中,已启动任务不读取修改后的文件。
|
||
|
||
第一版 Definition 只允许引用候选代准备阶段已经注册的 built-in 工具。MCP 连接按现有架构只能在 activation 阶段发生,无法在不产生外部副作用的候选代准备阶段完成严格校验,因此 MCP 工具委托暂不开放;未来需要先增加可离线校验的 MCP tool manifest,再扩展 Catalog。
|
||
|
||
## 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 / Inbox Wake + Worker Claim]
|
||
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 与任务树的 run admission quota,以及 Provider/普通工具步骤的 execution permit;两类配额不共用生命周期。
|
||
- 生成自动 completion terminal outcome,并按 group policy 物化 inbox 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 和按 policy 生成的 run/group completion event;`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 图。`cost` 只有在 profile 明确配置 input/output/cache 价格后才计算;当前没有价格来源时持久化为 `NULL`,不能根据模型名猜测价格。
|
||
|
||
## 7. 委托图与权限模型
|
||
|
||
### 7.1 基本规则
|
||
|
||
每次委托必须同时满足:
|
||
|
||
```text
|
||
target != ROOT
|
||
target in allowed_targets(caller)
|
||
next_depth <= global max_tree_depth
|
||
remaining_delegation_depth > 0
|
||
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,而不是放开隐式递归。
|
||
|
||
全局 `max_tree_depth` 是 root-relative 硬上限;Definition 的 `limits.max_depth` 表示该 Agent可继续创建的最大相对后代深度。child context 的 remaining depth 取 `min(parent_remaining - 1, target_definition.max_depth)`,任一限制耗尽即拒绝继续委托。
|
||
|
||
### 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<String>,
|
||
pub run_id: String,
|
||
pub group_id: Option<String>,
|
||
pub parent_run_id: Option<String>,
|
||
pub caller_agent_id: String,
|
||
pub current_agent_id: String,
|
||
pub ancestry: Vec<String>,
|
||
pub depth: u16,
|
||
pub plan_item_id: Option<String>,
|
||
pub cancellation: CancellationToken,
|
||
pub budget: AgentBudget,
|
||
pub signal_contract: Option<SignalContract>,
|
||
}
|
||
```
|
||
|
||
`ToolExecutionContext` 扩展为包含可选 `AgentExecutionContext`、CancellationToken、execution gate、Turn wakeup handle 和资源 scope。Root interactive Agent 没有伪造的 run ID,其 `agent` 字段为 `None`,由 session/turn context 明确识别为 `ROOT`;sub-run 的 `agent` 字段必须为 `Some` 且 run ID 非空。Turn wakeup handle 只为 root interactive Turn 提供,sub-run 中为 `None`。`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 能减少模型错误调用,也便于分别授权。
|
||
|
||
`agent_task` 的每次操作都必须同时校验 `root_session_id` 和调用者在任务树中的位置。Root 只能操作当前 session 的任务树;子 Agent只能读取自身及后代、取消未终态后代,不能访问祖先、sibling 或其他 session。run ID 不是授权凭证。
|
||
|
||
### 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 成功、completion inbox 容量已经预留且执行任务已经被 TaskSupervisor 接纳后才返回成功。可选 `idempotency_key` 在 `(root_session_id, caller_scope_id, key)` 范围唯一,用于 Provider 重试时避免重复创建任务;Root 的 `caller_scope_id` 固定为非空字面量 `ROOT`,数据库使用仅覆盖非空 key 的 partial unique index,避免 SQLite `NULL` 破坏去重。
|
||
|
||
## 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 等有状态外部资源。共享 browser 登录态的唯一正式路径是:Root 通过 `browser_profiles` 创建/选择经过校验的 persistent ID,把它作为显式 task/artifact reference 交给获准使用 browser 的子 Agent,子 Agent在每次相关调用中显式传入该 ID;瞬时父 session browser scope 不能隐式继承。
|
||
|
||
无 target 的旧委托映射出的内置 `general` 兼容 Agent可在弃用期保留父 session transient browser scope,并给出迁移提示;具名 Agent不继承这一兼容例外。
|
||
|
||
## 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 outcome | AgentCoordinator 自动生成 | 是 | completed/failed/timed_out/cancelled/interrupted |
|
||
|
||
最终结果不能依赖模型记得调用工具。即使 Provider 异常、超时或任务被取消,Coordinator 也必须持久化 run 的终态 outcome。Foreground 将其返回为 tool result;background `each` 将每个 outcome 物化为 run completion inbox event,`all` 只在 group 终态时物化一个 group completion inbox event。
|
||
|
||
### 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。
|
||
|
||
未提供 `dedupe_key` 时,每次调用使用 `signal:<event_uuid>` 作为非空 event key;提供 key 时使用 `signal:<normalized-key>:<cooldown-window-id>`,只在冷却窗口内去重,不能因数据库唯一约束永久压制同类告警。
|
||
|
||
普通进度不应滥用 signal。工具调用进度继续通过内部 Observer/TurnEvent 投影到 UI;只有需要主 Agent采取行动的事件才使用 `emit_signal`。
|
||
|
||
### 12.3 Completion 去重
|
||
|
||
run completion payload 包含本 run 已发出的 signal IDs;group completion 则按 run 分组携带这些 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 的终结路径统一返回 terminal outcome,Coordinator 保存结果并按 foreground/background 与 group policy 创建相应投递 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
|
||
caller_scope_id TEXT NOT NULL
|
||
idempotency_key TEXT
|
||
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
|
||
execution_id TEXT NOT NULL
|
||
task TEXT NOT NULL
|
||
context_json TEXT
|
||
budget_json TEXT NOT NULL
|
||
signal_contract_json TEXT
|
||
signal_delivery TEXT
|
||
completion_delivery TEXT
|
||
failure_delivery TEXT
|
||
deadline_at INTEGER NOT NULL
|
||
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 INTEGER NOT NULL
|
||
attempt INTEGER NOT NULL DEFAULT 1
|
||
completion_slot_reserved INTEGER NOT NULL DEFAULT 0
|
||
started_at INTEGER
|
||
finished_at INTEGER
|
||
created_at INTEGER NOT NULL
|
||
```
|
||
|
||
```sql
|
||
CREATE UNIQUE INDEX agent_runs_idempotency
|
||
ON agent_runs(root_session_id, caller_scope_id, idempotency_key)
|
||
WHERE idempotency_key IS NOT NULL;
|
||
```
|
||
|
||
不保存 API key、Authorization header、Provider 私有 reasoning state 或完整 connection URL。`cost` 是 nullable projection:Provider profile 未配置价格时必须为 `NULL`,usage token 不受影响。
|
||
|
||
### 14.2 agent_run_groups
|
||
|
||
```text
|
||
agent_run_groups
|
||
----------------
|
||
id
|
||
root_session_id
|
||
caller_run_id
|
||
caller_scope_id
|
||
idempotency_key
|
||
mode
|
||
completion_policy all | each
|
||
expected_runs
|
||
terminal_runs
|
||
completion_slot_reserved
|
||
deadline_at
|
||
status
|
||
created_at
|
||
finished_at
|
||
```
|
||
|
||
批量请求的 `idempotency_key` 绑定 group;其 child run 的 key 为 `NULL`。单任务请求没有 group 时,key 绑定 run。两者分别使用 `(root_session_id, caller_scope_id, idempotency_key)` partial unique index,避免批量 children 互相冲突。
|
||
|
||
批量 background 默认 `completion_policy=all`:单个 run 终态只更新 group 计数,全部 run 终态或 group deadline 到达后创建唯一 `group_completion` event;deadline 到达时先把未终态 child 条件更新为 `timed_out`。`completion_policy=each` 则在每个 run 终态时立即创建独立 completion event,不等待 sibling;Router 可通过 300–500ms debounce 把已经到达的多个 event 合并为一次 continuation,但不能用 debounce 改变 deadline 或确认语义。
|
||
|
||
接纳 background run/group 时按 policy 在 session inbox 配额中预留 completion slot:`each` 在每个 run 的 `completion_slot_reserved` 记一个,`all` 在 group 字段记一个。Storage 用同一写事务统计该 session 的 `pending/leased/admitted` 事件和有效 reservation,避免并发接纳越过上限;`consumed/dead_letter` 受 TTL 清理但不占 pending 配额。容量不足在创建 run 前拒绝;signal 只能使用未预留容量。预留在 completion 事务落库或接纳回滚时释放。
|
||
|
||
容量判断不能在每次接纳时通过无锁 `COUNT(*)` 推断。新增每 root session 一行的 `agent_session_state`,在同一 SQLite 写事务中以条件 `UPDATE` 维护 `pending_event_count`、`reserved_completion_slots` 和单调 `revision`。background 接纳先增加 reservation;signal 只有在 `pending + reserved < limit` 时增加 pending;completion 将 reservation 原子转换为 pending;consume/dead-letter 减少 pending。启动恢复会以事件与 run/group 事实重算计数,发现差异时修复并记录告警。
|
||
|
||
### 14.3 agent_inbox_events
|
||
|
||
```text
|
||
agent_inbox_events
|
||
------------------
|
||
id TEXT PRIMARY KEY
|
||
root_session_id TEXT NOT NULL
|
||
scope_kind run | group
|
||
scope_id TEXT NOT NULL
|
||
run_id TEXT
|
||
group_id TEXT
|
||
event_type signal | completion | group_completion
|
||
event_key TEXT NOT NULL
|
||
delivery queue | steer
|
||
requires_continuation BOOLEAN NOT NULL DEFAULT TRUE
|
||
severity TEXT
|
||
payload_json TEXT NOT NULL
|
||
status pending | leased | admitted | consumed | superseded | dead_letter
|
||
attempt_count INTEGER NOT NULL DEFAULT 0
|
||
lease_token TEXT
|
||
lease_until INTEGER
|
||
next_attempt_at INTEGER
|
||
admitted_turn_id TEXT
|
||
last_error TEXT
|
||
created_at INTEGER NOT NULL
|
||
consumed_at INTEGER
|
||
dead_lettered_at INTEGER
|
||
fallback_notified_at INTEGER
|
||
revision INTEGER NOT NULL
|
||
|
||
UNIQUE(scope_kind, scope_id, event_type, event_key)
|
||
CHECK(
|
||
(scope_kind = 'run' AND run_id IS NOT NULL AND group_id IS NULL AND scope_id = run_id) OR
|
||
(scope_kind = 'group' AND group_id IS NOT NULL AND run_id IS NULL AND scope_id = group_id)
|
||
)
|
||
```
|
||
|
||
完整结果保存在 `agent_runs.result`,inbox payload 默认只放有界摘要、元数据和 result reference,避免复制大文本。
|
||
|
||
event key 始终非空:无 dedupe key 的 signal 用 `signal:<event_uuid>`,有 dedupe key 的 signal 加冷却窗口 ID;run completion 固定为 `completion:terminal-v1`,group completion 固定为 `group-completion:terminal-v1`。由同一次 `/stop` 产生、无需主 Agent再次解释的 cancelled completion 使用 `requires_continuation=false`,在终态事务中直接记为 consumed,但仍保留事件审计和客户端投影。
|
||
|
||
### 14.4 原子事务
|
||
|
||
Agent completion 必须在一个 Storage 事务中:
|
||
|
||
```text
|
||
UPDATE agent_runs terminal state/result/usage
|
||
UPDATE agent_run_groups terminal count/status
|
||
CONSUME reserved completion capacity
|
||
INSERT run completion OR group completion ... ON CONFLICT DO NOTHING
|
||
UPDATE bound task item by execution_id
|
||
COMMIT
|
||
```
|
||
|
||
事务失败时不能对外宣称任务完成。内存 wakeup 只有在 commit 成功后发送。
|
||
|
||
`completion_policy=all` 只有把 group 从 non-terminal 条件更新为 terminal 的事务赢家可以插入 group completion;其他 sibling 的迟到终态只完成自己的 run 条件更新,不能重复生成 event。
|
||
|
||
### 14.5 continuation 消息与投递绑定
|
||
|
||
现有 message 持久化需要增加两个可向后兼容字段:
|
||
|
||
```text
|
||
client_visibility visible | hidden(默认 visible)
|
||
turn_origin user | agent_continuation | scheduled(默认 user)
|
||
```
|
||
|
||
hidden message 参与 Provider replay 和事务回滚,但 `SessionHistory`、`TurnCommitted.messages` 与 Channel 投递只投影 visible message。Storage 的 continuation commit API 必须在一个事务中写 hidden trigger、可见 assistant/tool 消息、usage 和 event consumption。
|
||
|
||
内部历史读取与客户端历史读取必须拆开:Session 恢复和 Provider replay 读取 visible+hidden;WebSocket/HTTP 历史、管理面消息查询和 committed delta 默认只读 visible。hidden `role=user` 不增加面向用户的 `message_count`,也不参与自动标题生成阈值;上下文压缩和 Provider token 占用仍必须统计它。
|
||
|
||
root session 还需持久化最近一次有效 delivery binding:`channel`、`chat_id` 和 Channel 明确标为 durable 的 opaque context。`ChannelContext` 必须把 `durable_private` 与当前仅用于一次回复的 `reply_to`/`private` 分开;核心只持久化 `durable_private`,不通过猜测 key 名过滤现有 `private`。binding 在成功接纳外部用户输入时更新;平台 thread/root 等稳定字段保持 Channel 私有,核心只存取和回传,不解释。binding 不得包含 message/reaction ID、token、临时上传 ID 或其他短期 credential。
|
||
|
||
## 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<MediaRef>,
|
||
pub durable_event_id: Option<String>,
|
||
pub received_at: i64,
|
||
}
|
||
|
||
pub enum TurnInputSource {
|
||
User,
|
||
AgentSignal { run_id: String, agent_id: String },
|
||
AgentCompletion { run_id: String, agent_id: String },
|
||
AgentGroupCompletion { group_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 | 保持 pending、唤醒 worker claim,启动内部 Turn | 退化为 queue,同左 |
|
||
| 活动 Turn 接受输入 | 入下一 Turn | 入当前 TurnMailbox |
|
||
| TurnMailbox 满/已关闭 | 保持 durable pending,唤醒 worker | 可靠退化为 queue |
|
||
| Provider 请求进行中 | 等下一 Turn | 等请求结束后的安全边界 |
|
||
| 普通工具批次进行中 | 等下一 Turn | 等完整工具批次结束 |
|
||
| sleep 进行中 | 唤醒 sleep,内容仍留在 queue | 唤醒 sleep,并在工具批次后注入当前 Turn |
|
||
|
||
Steer 不承诺硬实时抢占。最迟可见时间由当前不可分割 Provider 请求或工具步骤决定。需要当前逻辑必然依赖子结果时应使用 foreground;不要用 background+steer 模拟同步调用。
|
||
|
||
### 15.3 原子 admission 与 fallback
|
||
|
||
AgentResultRouter 对 steer 事件使用不跨 Session 锁做 SQLite I/O 的两阶段 admission:
|
||
|
||
1. 在锁外以条件更新把 event 从 `pending` claim 为 `leased`。
|
||
2. 在 Session 状态锁内为当前 accepting Turn 分配 sequence 和不可排空的 mailbox reservation;closed/full/不存在则不创建 reservation。
|
||
3. 在锁外以 lease token 把 event 条件更新为 `admitted` 并写 `admitted_turn_id`。
|
||
4. 重新取得 Session 锁;只有同一 Turn/generation 仍 accepting 时才把 reservation 激活为 AgentLoop 可见输入。
|
||
5. 任一步失败都删除 reservation,并以 lease token 把 event 恢复 `pending`;若恰逢 `/stop`,由 `/stop` 的 admitted-turn 条件释放和 lease expiry 兜底。
|
||
|
||
AgentLoop 只能排空已经激活的 reservation,因此不会在 durable admission 成功前看到事件。若事件不适合当前 Turn,Router 释放 lease、保持 `pending`,只递增该 session 的 inbox wake revision。
|
||
|
||
任何竞态下事件只能属于当前 Turn 或后续 Turn之一,不能同时进入两者,也不能两者都不进入。durable event payload 不进入保存用户 `AgentTask` 的 mpsc,因此普通 session queue 饱和不影响它。worker 在准备运行 continuation 时才从 SQLite claim lease;内存 wake 丢失由 pending/expired lease 扫描恢复。
|
||
|
||
Session worker 的接收面分为:
|
||
|
||
```text
|
||
user task lane bounded mpsc(32),保存 payload,满时明确拒绝
|
||
agent inbox wake lane watch revision,只合并“SQLite 有待处理事件”的提示
|
||
```
|
||
|
||
watch revision 是延迟优化而不是事实来源;不为每个 event 建立另一个可饱和 payload 队列。Router/worker 遇到瞬时错误按 `next_attempt_at` 退避重试,默认最多 8 次并受 event TTL 限制;永久错误立即进入 dead-letter。
|
||
|
||
### 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 上限共同控制。
|
||
|
||
queue continuation 在 Turn 调度边界采用有界公平,而不是依赖 UI 保证可见:通常先处理用户任务;连续处理 `max_user_turn_burst_before_inbox`(默认 4)个用户 Turn,或最老 pending event 等待达到 `max_inbox_wait_secs`(默认 30 秒)后,下一个调度项必须是一个有界 event batch。当前活动 Turn 从不被 queue event 抢占,因此等待上限从下一个调度边界计算。UI 未读状态只是投影,不参与正确性。
|
||
|
||
### 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
|
||
> 已排队用户输入(受 burst/age 公平上限约束)
|
||
> queue background result continuation
|
||
> 等待新事件
|
||
```
|
||
|
||
Steer event 在没有活动 Turn 时按 queue 处理。用户输入通常优先以避免后台总结打断新请求,但 §15.4 的 burst/age 规则保证结果不会在持续用户流量下无限饥饿。UI 未读状态只展示 pending/dead-letter 数量,不承担调度正确性。
|
||
|
||
### 16.3 内部 continuation Turn
|
||
|
||
内部任务不是伪造的 InboundMessage:
|
||
|
||
```rust
|
||
enum AgentTaskSource {
|
||
UserInput,
|
||
BackgroundAgentResults { event_ids: Vec<String>, group_id: Option<String> },
|
||
ScheduledTask,
|
||
}
|
||
```
|
||
|
||
SessionManager 直接把领取的结果构造成 bounded runtime context,并加入一个内部触发语义:“检查这些后台结果,结合原始目标验证和汇总,再向用户报告。”内部输入不显示用户气泡;主 Agent输出按普通 assistant Turn 持久化和投递。
|
||
|
||
continuation 必须创建一条 durable hidden trigger message,而不是只在内存临时拼 prompt:
|
||
|
||
- 数据库 role 使用 Provider-compatible `user`,source 为 `agent_signal`/`agent_result`,并标记 `client_visibility=hidden`、`turn_origin=agent_continuation`。
|
||
- hidden content 是有界 runtime envelope,包含 event/run references 和不可信数据边界;Provider history replay 会保留它,普通 history/WebSocket 投影会过滤它。
|
||
- assistant/tool 消息照常可见。`turn_updated`/`turn_committed` 携带 turn origin,客户端可以显示“后台结果处理”标签,但不创建伪用户气泡。
|
||
|
||
内部 Turn 的 delivery target 来自 root session 的 durable binding:`channel`、`chat_id` 和可复用的 thread/root context。一次性 `reply_to` 不得复用。没有可用外部 binding 时仍提交历史并等待 WebUI/TUI 读取,不能猜测或改投其他 chat。
|
||
|
||
### 16.4 消费确认
|
||
|
||
领取流程:
|
||
|
||
```text
|
||
pending → leased → admitted → consumed
|
||
```
|
||
|
||
`lease_token` 防止重复 worker 处理。同一事务必须保存 hidden trigger、主 Agent Turn、usage,并把对应 inbox events 标记 consumed。continuation 持有 `InboxLeaseGuard`:AgentLoop 失败、Turn 取消、generation stale 等正常退出会以 token 显式 release;只有进程崩溃或强制 abort 才依赖 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 在工具批次后注入。
|
||
|
||
上述“当前 session 输入”只适用于 root interactive Turn。sub-run 没有独立 session input lane,`ToolExecutionContext.turn_wakeup=None`,其 sleep 只响应 timer、run cancellation、timeout 或 shutdown;不会因 root session 用户输入或 sibling signal 被唤醒。Root 如需停止 sleeping sub-run,应调用 `agent_task.cancel`。
|
||
|
||
### 17.2 Wakeup handle
|
||
|
||
`ToolExecutionContext` 增加:
|
||
|
||
```rust
|
||
pub struct TurnWakeupHandle {
|
||
pub receiver: watch::Receiver<TurnWakeupState>,
|
||
}
|
||
|
||
pub struct TurnWakeupState {
|
||
pub revision: u64,
|
||
pub pending_steer: usize,
|
||
pub pending_queue: usize,
|
||
pub latest_source: WakeupSource,
|
||
pub latest_preview: Option<String>,
|
||
}
|
||
```
|
||
|
||
使用 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。
|
||
|
||
这是 AgentLoop 的显式接口约束:root Turn、foreground child、run timeout 和 runtime shutdown 的 `CancellationToken` 必须贯穿 Provider stream、可取消等待和工具批次外层,不能只依赖调用 future 被 drop。结构化取消先作为 Phase 2A 独立落点实现并回归现有 root Turn 行为,再接入嵌套 run。
|
||
|
||
“不默认硬中断 Provider/副作用工具”只约束普通 `steer`;`steer` 等待安全边界。`/stop` 保持现有强停止语义:取消 token 并使 root Turn future 失效,独立 child 在有界宽限期后仍未退出则由 Coordinator/Supervisor abort。Coordinator 的 terminal condition update 始终阻止取消后的迟到完成提交。
|
||
|
||
### 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 不能静默消失:
|
||
|
||
- `/stop` 关闭 TurnMailbox 时收集尚未提交的 event ID,并在 generation 失效后按 `lease_token`/`admitted_turn_id` 条件更新立即恢复 pending;lease expiry 只是崩溃兜底。
|
||
- continuation 的 `InboxLeaseGuard` 在 worker 正常退出、失败或取消时显式 release;durable payload 不进入会被 `agent_tx.take()` 丢弃的普通 mpsc。
|
||
- 被取消 background run 仍由 Coordinator 条件事务写 cancelled completion。由本次 `/stop` 自身造成的 completion 使用 `requires_continuation=false`,保留审计和 UI 状态但不反向启动新 Turn;`/stop` 前已经存在的其他 durable event 恢复 pending 后继续投递。
|
||
- 用户显式执行 `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 的中断记录。
|
||
|
||
### 18.6 Session 归档与删除
|
||
|
||
- session 被归档后不再启动内部 continuation;未消费事件进入 `dead_letter(session_archived)` 并继续在管理面可见,非 Scheduler 所有的未终态 run 被取消。该生命周期原因不发送 system fallback。
|
||
- session 被软删除后同样取消未终态 run,释放 reservation,并将未消费事件收敛为 `dead_letter(session_deleted)`;不得猜测其他 session 或 Channel 作为替代目标,也不发送 system fallback。
|
||
- `/stop` 不是归档或删除:它取消当前 Turn 和该 session 的 active background run,但 `/stop` 前已经存在的 durable event 仍恢复为 pending;由本次停止产生的取消 completion 仅做 status-only 审计。
|
||
|
||
## 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 释放而消失。
|
||
|
||
具体归属如下:
|
||
|
||
- Coordinator 的 run admission quota 统计已接纳且未终态的 run,可以跨 `waiting_children` 持有。
|
||
- AgentLoop 在每次 Provider 请求前按 global → session → agent 的固定顺序获取 provider step permits,stream 结束/取消即释放。
|
||
- tool executor 只为普通工具调用获取 tool step permit;`delegate`、`agent_task`、`emit_signal` 等 runtime-control 工具不占这种 permit。
|
||
- foreground delegate 通过状态 guard 在等待前条件更新 `running → waiting_children`,返回/取消时再条件更新;等待动作本身不持有 provider/tool permit。
|
||
|
||
### 19.3 预算传播
|
||
|
||
每次子委托从父 budget 派生硬上限:
|
||
|
||
```text
|
||
child deadline <= parent deadline
|
||
child max depth <= remaining depth
|
||
sum child token reservation <= remaining tree budget
|
||
sum child cost reservation <= remaining tree budget(仅有价格配置时)
|
||
```
|
||
|
||
调用方可以收紧 timeout/结果大小,但不能超过 Agent Definition 和系统上限。Provider profile 没有价格信息时不启用 cost reservation,只执行 token、迭代、deadline 和 run-count 硬预算。
|
||
|
||
## 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 校准。
|
||
|
||
WebSocket 协议使用通用 run/event 投影,不为 Signal 复制一套状态机:
|
||
|
||
```text
|
||
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 }
|
||
```
|
||
|
||
`AgentEventUpdated` 覆盖 accepted/admitted/consumed/dead-letter 状态,客户端按 `(session_id, revision, event_id)` 幂等合并;重连后用 `GetAgentRuns` 全量校准。`TurnSnapshot` 和 `TurnCommitted` 增加 `turn_origin = user | agent_continuation | scheduled`。第一版取消入口继续使用 `/stop`、`agent_task.cancel` 或受保护管理 API,不增加缺少任务树授权上下文的裸 WebSocket cancel 帧。
|
||
|
||
### 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 |
|
||
| Completion capacity 无法预留 | delegate 在创建 run 前返回 inbox capacity exceeded |
|
||
| TaskSupervisor 拒绝 spawn | run 条件更新 cancelled/failed,再返回失败 |
|
||
| Provider 创建失败 | run failed,foreground 返回错误;background 生成 failure event |
|
||
| Signal inbox 无可用容量 | emit_signal 返回 inbox_full;run 继续执行 |
|
||
| Inbox wakeup 丢失 | pending event 由恢复扫描重新唤醒 |
|
||
| TurnMailbox closed/full | steer 可靠退化 queue |
|
||
| Session user queue 满 | 用户输入明确拒绝;durable event 不经过该队列 |
|
||
| Main continuation Provider 失败 | lease guard 显式 release 并退避重试;崩溃时才等 lease 到期 |
|
||
| 主 Agent回复持久化失败 | event 不确认,避免结果消失 |
|
||
| Event 超过 attempts/TTL | 标 dead_letter,并幂等尝试一次 system fallback |
|
||
| Channel 最终投递失败 | assistant history已持久化;沿用 DeliveryCoordinator terminal fallback |
|
||
|
||
所有重试必须有次数、退避、deadline 和分类;永久错误立即终态化,不能无界重试。默认最多 8 次,退避为 `1s/5s/30s/2m/10m` 后封顶 10 分钟,并同时受 inbox event TTL 限制。
|
||
|
||
dead-letter 记录最终原因和时间,并通过 OutboundDispatcher 最多发送一次有界 system fallback,只包含 run/group ID、终态和查询提示;`fallback_notified_at` 保证幂等。fallback 渠道失败时,SQLite run/event 记录和管理 UI 是最终诊断出口,不能把 dead-letter 伪装成已交付。
|
||
|
||
## 22. 兼容迁移
|
||
|
||
### 22.1 Delegate 参数
|
||
|
||
旧模式映射:
|
||
|
||
```text
|
||
inline → foreground
|
||
parallel → foreground + tasks[]
|
||
background → background
|
||
```
|
||
|
||
过渡期只解析代码中确实存在的旧值并在 tool result/日志中给出弃用提示;`async` 从未是有效值,不新增该别名。新 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 和委托图授权。
|
||
- 明确 skills/memory 不继承、具名 Agent browser scope 隔离和 legacy general scope 兼容。
|
||
- 保持旧 background 通知路径作为兼容,但不开放嵌套 background。
|
||
|
||
### Phase 2A:AgentLoop 结构化取消
|
||
|
||
- CancellationToken 贯穿 root Turn、Provider stream、工具批次和 AgentRunner。
|
||
- 保持 `/stop` 强停止、普通 `steer` 安全边界语义。
|
||
- 用现有 sleep cancellation、Provider stream 和 steering recovery tests 锁定回归基线。
|
||
|
||
### Phase 2B:统一 Agent Run 持久化
|
||
|
||
- 新增 `agent_runs`、`agent_run_groups`、Storage transaction API。
|
||
- 拆分 `delegate` 与 `agent_task`。
|
||
- Foreground 结果也持久化,修复截断结果不可查询。
|
||
- 实现预算、run admission quota 与 step execution permit 释放。
|
||
|
||
### Phase 3:Agent Inbox 与 Queue Completion
|
||
|
||
- 新增 `agent_inbox_events`、lease、恢复扫描。
|
||
- 新增 completion capacity reservation、合并式 wake lane和 worker 有界公平。
|
||
- Background completion 从 Channel direct notification 改为主 Agent内部 continuation。
|
||
- 增加 hidden continuation trigger、SourceKind::AgentResult、Turn origin 和 WebSocket run/event 投影。
|
||
- 批次 completion 合并与 debounce。
|
||
- 增加 dead-letter system fallback、UI 未读计数和 reconnect 全量校准。
|
||
|
||
### Phase 4:Emit Signal 与 Steer
|
||
|
||
- 新增 EmitSignalTool、SignalContract 和 rate/dedupe。
|
||
- SteeringMailbox 泛化为来源感知 TurnMailbox。
|
||
- 实现 steer admission、queue fallback、durable ack/recovery。
|
||
- 添加 AgentSignal UI;任务树复用 Phase 3 的 run/event 协议。
|
||
|
||
### Phase 5:可唤醒 Sleep 与工具中断元数据
|
||
|
||
- ToolExecutionContext 增加 TurnWakeupHandle。
|
||
- SleepTool 使用 watch revision + timer + cancellation select。
|
||
- queue/steer 唤醒内容边界和测试。
|
||
- 为工具增加 InputInterruptPolicy,默认 Never。
|
||
- sub-run 保持 timer/cancellation-only,不获得 root TurnWakeupHandle。
|
||
|
||
## 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、lease guard、coalesced wake
|
||
└── 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。
|
||
- Root caller scope 的 idempotency key 在 SQLite 中同样去重。
|
||
- 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 隔离。
|
||
- persistent browser profile 可显式共享;具名 Agent不继承 transient parent scope。
|
||
- 子 Agent不隐式继承主会话 history/memory/临时 Skill。
|
||
|
||
### 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 不能提交。
|
||
- 正常取消/worker 退出由 lease guard 立即 release,不等待 lease timeout。
|
||
- user mpsc 满不影响 durable wake;wake revision 丢失后扫描可恢复。
|
||
- 用户持续输入时 burst/age 公平上限仍调度 continuation。
|
||
- completion capacity 在 background 接纳时预留,signal 不能抢占。
|
||
- `each` 逐 run 提前投递;`all` 只触发一次 group 汇总 Turn。
|
||
- retries/TTL 耗尽进入 dead-letter,system fallback 最多发送一次。
|
||
|
||
### 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则可靠排队。
|
||
- sub-run sleep 不被 root session 输入唤醒,只响应 timer/cancellation。
|
||
|
||
### 25.7 取消、并发与恢复
|
||
|
||
- 父 foreground 取消级联后代。
|
||
- 父 waiting_children 不持有唯一 permit,无死锁。
|
||
- `/stop` 取消 session background runs,并恢复未提交 durable events。
|
||
- `/stop` 产生的 cancelled completion 只投影状态,不启动新的 continuation。
|
||
- 迟到结果不能覆盖 cancelled/interrupted。
|
||
- reload 关闭 admission 后拒绝新 run/signal,pending inbox 由新代恢复。
|
||
- Gateway 重启把 running 标记 interrupted 并生成 completion。
|
||
- session 删除/归档后的事件按明确 dead-letter/cancel 策略收敛。
|
||
|
||
### 25.8 消息与客户端
|
||
|
||
- AgentSignal 不渲染为用户气泡。
|
||
- Internal continuation 输入不出现在普通历史,assistant 汇总正常持久化。
|
||
- hidden trigger 会参与 Provider replay,并与 assistant/usage/event consumed 原子提交。
|
||
- continuation 使用稳定 delivery binding,绝不复用一次性 reply_to。
|
||
- reasoning/provider state 不进入信号、API、客户端和日志。
|
||
- send_message 仍走外部投递确认;emit_signal 不走 OutboundDispatcher。
|
||
- 同 Turn 附件兼容路径与未来 attach_artifact 不产生重复历史。
|
||
- run/event 增量按 revision 幂等,断线重连后可全量校准。
|
||
|
||
## 26. 必须保持的架构不变量
|
||
|
||
1. 同一 Session 最多一个活动主 Agent Turn;Agent 子 run 可以并行,但不能并发提交主会话历史。
|
||
2. Root Agent 不能成为委托目标,结果回传不等同于反向委托。
|
||
3. Foreground/Background 只描述委托方等待行为;并发是独立调度维度。
|
||
4. Queue 输入永不泄漏正文到当前 Turn;Steer 只在安全边界注入。
|
||
5. Signal 先持久化后唤醒;内存通知不是事实来源。
|
||
6. Signal 是非终态事件;run Completion 由运行时自动生成且恰好对应一个 run 终态,`all` policy 的 Group Completion 恰好对应一个 group 终态。
|
||
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 或本地内部路径。
|
||
14. Durable event payload 只以 SQLite inbox 为权威来源;普通 user task mpsc 和合并式 wake lane 都不能成为确认点。
|
||
15. Continuation 的 hidden trigger 必须可供 Provider replay,但不得投影为用户消息;trigger、回复和 event consumption 原子提交。
|
||
16. 已接纳 background run 的 terminal completion 容量已经预留;运行结束不能因 signal 洪泛丢失 completion。
|
||
|
||
## 27. 设计结论
|
||
|
||
目标架构把现有“一个 delegate 工具创建临时 Agent”提升为明确的编排系统:
|
||
|
||
```text
|
||
Markdown Agent Definition
|
||
↓
|
||
AgentCatalog + DelegationPolicy
|
||
↓
|
||
AgentCoordinator
|
||
├─ foreground:并发执行、父等待、tool result 返回
|
||
└─ background:run ID 返回、signal/completion 进入 durable inbox
|
||
↓
|
||
queue | steer
|
||
↓
|
||
inbox wake/worker claim | current TurnMailbox
|
||
↓
|
||
Root Agent
|
||
```
|
||
|
||
Foreground 解决依赖型子任务;Background+Queue 解决稍后统一处理;Background+Steer 解决长任务期间的重要监控信号;可唤醒 Sleep 为安全等待提供及时响应点。三条消息路径各自保持单一职责:`emit_signal` 内部告警、自动 completion 终态回传、`send_message` 外部投递。该划分能够在不破坏 PicoBot Session/Turn/Delivery 既有不变量的前提下分阶段实现,并为权限、持久化、取消、热重载和客户端表现提供可验证边界。
|