- RunQuota now enforced: background admission acquires the run permit (global→session) and the runner holds it until terminal commit; foreground never takes run permits so nested limit=1 cannot deadlock - RuntimeAdmission activity guard held by background runners for the whole run; admission checked before any durable write - signal_contract_json/signal_delivery persisted at run admission so definition-level steer delivery actually takes effect (was silently falling back to queue) - terminal completion payload carries the run's emitted signal IDs (design §12.3) so the main Agent can recognise duplicates - ROOT caller_scope_id is the literal "ROOT" (design §9.6) - legacy general delegation returns a deprecation/migration hint Cleanup: - remove unused AgentCaller enum and run_parallel wrapper - remove unreachable http_get_only prompt machinery (http_request is RootOnly and never enters sub-agent registries) - drop never-read EmittedSignal fields; derive WakeupSource from TurnInputSource instead of duplicating match arms - replace the /stop oneshot compatibility bridge with a plain turn_busy flag; the worker's forced-cancel path now selects on the cancellation token (implementation doc §11.1) Version 1.8.0
1116 lines
62 KiB
Markdown
1116 lines
62 KiB
Markdown
# PicoBot 子 Agent 编排实施细节与可实施性审查
|
||
|
||
> 状态:实施基线(2026-08)。
|
||
>
|
||
> 本文以 [`SUB_AGENT_ORCHESTRATION_DESIGN.md`](SUB_AGENT_ORCHESTRATION_DESIGN.md) 为产品与架构规范,以 [`SUB_AGENT_ORCHESTRATION_REVIEW.md`](SUB_AGENT_ORCHESTRATION_REVIEW.md) 和 [`SUB_AGENT_ORCHESTRATION_REVIEW_RESPONSE.md`](SUB_AGENT_ORCHESTRATION_REVIEW_RESPONSE.md) 为评审记录,并逐项对照当前代码给出可以直接拆分为开发任务的实现方案。
|
||
>
|
||
> 本文不是“代码已经实现”的声明。实现完成前,运行时事实仍以当前代码、测试和 [`ARCHITECTURE.md`](ARCHITECTURE.md) 为准。
|
||
|
||
> 实施进度(2026-08):Phase 1 已落地具名 Definition/Catalog、不同 Provider profile、工具/Skill fail-closed 裁剪、显式 `AgentExecutionContext`、父子委托边与 ancestry 校验、canonical `foreground/background` schema 以及批量 foreground 并发。旧 general background 仅作为兼容路径保留。Phase 2A 已落地结构化取消:`AgentError::Cancelled/TimedOut`、CancellationToken 贯穿 root Turn、Provider 连接/stream、并行与串行工具批次以及 sleep;`/stop` 保留 oneshot 兼容桥接并同时取消 Turn token,协作式与强制路径提交同一 Cancelled 终态;树级 `max_runs_per_tree` 由共享原子计数在 foreground 委托接纳时强制。Phase 2B 已落地 durable foreground 编排:schema v6(`agent_run_groups`/`agent_runs`/`agent_session_state`/`agent_inbox_events` 与 messages/sessions 扩展列)在单一迁移事务中原子创建;Storage 领域 API(接纳、running/waiting_children 条件转换、execution-ID 条件 terminal commit、plan item 原子领取/完成、游标分页);`ExecutionGate` 分离 run quota 与 provider/tool step gate(global→session 顺序获取、弱引用键控回收、取消可中断等待),root Turn 步骤同样占用 step gate;`AgentCoordinator` 持久化全部具名 foreground run(单任务不建 group、批量建 group 并按请求顺序返回)、父 run `waiting_children` 转换、迟到结果丢弃与树位置授权;`agent_task` 工具提供 scoped get/list/get_result/cancel。foreground 不占用 run quota,嵌套 foreground 在并发上限为 1 时不死锁。Phase 3 已落地 durable inbox 与 queue continuation:具名 background 单任务经 Coordinator 接纳(completion slot 预留 → 持久化 queued → TaskSupervisor 托管 runner → terminal commit 原子转换 reservation 为 completion 事件 → notifier wake),spawn 拒绝执行补偿事务;`agent_inbox_events`/`agent_session_state` 容量条件更新与 claim/lease/admit/release/supersede/dead-letter API;`AgentInboxNotifier`(弱引用 late-bound wake)与 Session 双 lane worker(user mpsc + inbox watch 合并 wake),公平调度按 `max_user_turn_burst_before_inbox` 与 `max_inbox_wait_secs` 强制 continuation;continuation Turn 使用 hidden trigger、只读工具集,`commit_continuation_turn` 同事务提交 hidden trigger + assistant/tool/usage + event consume,失败显式 release lease;`client_visibility`/`turn_origin` 贯穿 ChatMessage/MessageMeta/协议 DTO,客户端历史查询默认过滤 hidden,`message_count` 只统计可见用户输入;activation recovery 收敛旧代 run(interrupted + failure completion)、过期 lease、group counter 与容量计数;`/stop`/archive/delete 走 `cancel_session`(suppress_continuation 写 consumed completion)并 dead-letter。background completion 不再直接通知 Channel。Phase 3 审查修复:worker 通过 `next_pending_due_at` 定时器在 release backoff 到期后重新 claim(不再依赖 wake);达到 `max_inbox_delivery_attempts` 的事件在正常运行中即 dead-letter 而非无限重试;`recover_on_activation` 对含 due 事件的在内存 session 发送合并 wake;修正 MIN 聚合无行时 NULL 被解码为 0 导致 worker 空转的缺陷(`oldest_pending_due`/`next_pending_due_at` 用 `Option<Option<i64>>` 显式解码)。Phase 4 已落地 emit_signal 与 steer:Agent Definition frontmatter 新增 `signal:` 块(`SignalContract`:delivery queue/steer、总数/字节/间隔/burst/severity allowlist/dedupe 冷却窗/JSON 深度,全部由工具与 Coordinator 强制,模型只提供 key/severity/summary/details/dedupe_key);`EmitSignalTool` 仅在带 contract 的 run 注册(fail-closed),`insert_agent_signal` 支持同冷却窗 dedupe(返回原 event + deduplicated);Coordinator `emit_signal` 校验 run 活跃与 execution ID、容量条件插入、投影+notifier wake,取消 run 时未消费 signal 自动 supersede;SteeringMailbox 泛化为来源感知 TurnMailbox(user lane 32/64KiB 与 agent lane 8/32KiB 独立容量,`TurnInput{source,delivery,durable_event_id,lease_token}`,agent steer 投影为 hidden user 消息保留 source 元数据);steer 两阶段 admission(claim → mailbox 预留 → DB admit(turn_id) → 同 Turn/generation 激活),任何失败 release lease 并 wake queue lane;`/stop`/generation 变更时已 admit 的 steer 事件按 lease token 条件释放回 pending(绝不静默丢弃),用户 Turn commit 与 steer 事件 consume 同事务(`persist_turn_batch_with_steer_consumption`)。Phase 3 收尾已落地:`ChannelContext.durable_private`(Feishu 仅 thread/root/chat_type 进入)持久化到 `sessions.delivery_context` 并被 continuation 投递复用;WS 协议 `GetAgentRuns`/`GetAgentRun` 与 `SessionAgentRuns`/`AgentRunUpdated`/`AgentEventUpdated`(有界 `AgentRunView`/`AgentEventView`,不暴露 budget/contract/delivery context/execution id);`AgentProjectionHub` broadcast(复用 plan-change 模式,lag 由客户端 GetAgentRuns 校准);HTTP `/api/agent-runs*`(列表游标分页/详情/events/cancel)+ `/api/tasks` legacy/new union;WebUI TasksPage 后台 tab 渲染 run tree(group 折叠、agent 标签、深度缩进),ChatPage 显示 continuation 标签与 Signal 卡片(投影事件,不插入 history)。Phase 5 已落地 wake-aware sleep:`TurnWakeupPublisher`/`TurnWakeupHandle` 随 active Turn 创建(root interactive Turn 专用,sub-run 与 continuation 永远没有 handle);watch revision 单调,四个 admission 点(用户进 TurnMailbox、用户进 next-turn mpsc、Agent steer durable admitted、Agent event 保持 pending 且 wake Session)在事实可见后 publish(先更新状态再发 wake,sleep 醒来必能查到输入);`SleepTool` root 上下文先 `borrow_and_update` 预检 pending>0 立即返回,否则 select timer/changed/cancellation,steer 唤醒消息携带 run_id/agent_id 与安全摘要、queue 唤醒只给类型/数量并明确不泄漏正文;child/continuation sleep 仅 timer/cancel(不受 root 输入或 sibling signal 影响)。`InputInterruptPolicy` 元数据沿用(sleep=WakeOnly,其余默认 Never,不扩展)。legacy general background 的 direct notification 兼容路径按设计保留一个版本观察,随后删除旧 adapter 与 `background_tasks` 写入。
|
||
|
||
三方对齐审查(2026-08)修复:RunQuota 接线(background admission 前按 global→session 获取 run permit,随 runner 持有至 terminal commit;foreground 不占 run permit,嵌套 limit=1 永不死锁,与 gate 注释语义一致);`signal_contract_json`/`signal_delivery` 在 run 接纳时持久化(此前 steer delivery 静默回退为 queue);terminal completion payload 携带本 run 已发出的 signal IDs(§12.3);ROOT 的 `caller_scope_id` 固定为字面量 `"ROOT"`(§9.6);legacy general 委托返回弃用迁移提示(§4.3)。已知剩余偏差:`resource_scope_id` 未建模为显式字段(browser 瞬态隔离经 session_id 惯例达成,行为等效);`idempotency_key` 仅 schema 预留、工具未开放入口;`all` policy 的 group completion 事件在 background 批量开放前不可达;legacy 适配器与 `background_tasks` 写入按设计保留一个版本后删除。
|
||
|
||
## 1. 审查结论
|
||
|
||
结论为:**设计可实施,没有需要推翻总体方案的阻断项,但必须按依赖顺序实施,不能在当前 `SubAgentManager` 上直接追加 durable inbox 或 steer signal。**
|
||
|
||
当前代码已经提供以下可复用基础:
|
||
|
||
- `AgentLoop` 已经是跨 Turn 无状态执行器,工具执行统一经过 `ToolExecutionContext` 和 `ToolOutputProcessor`。
|
||
- Session 已有单 worker、用户输入有界队列、活动 Turn steering、generation/state version 和 persistence lock。
|
||
- `TurnController`、`DeliveryCoordinator` 已建立“持久化成功后才 Completed”的边界。
|
||
- Storage 已有单事务迁移、批量消息与 usage 原子提交模式。
|
||
- `TaskSupervisor`、`RuntimeAdmission` 和 Gateway 候选运行代已经提供重载与有界关闭框架。
|
||
- WorkManager 已有 `execution_id` 条件更新和计划变更广播。
|
||
- WebSocket 已有 request/event 路由与断线后全量校准的现成模式。
|
||
|
||
必须先解决的结构性差距如下:
|
||
|
||
| 领域 | 当前实现 | 实施要求 | 风险等级 |
|
||
|------|----------|----------|----------|
|
||
| Agent 身份 | 一个共享 Provider 的临时子 Agent | 不可变 AgentCatalog、具名 definition、显式 caller/run context | 高 |
|
||
| 委托生命周期 | `inline/background/parallel` 混合建模 | `foreground/background` 与批量并发正交 | 中 |
|
||
| 取消 | Session 在 AgentLoop 外 drop future | CancellationToken 贯穿 Provider stream、工具批次和 child tree | 高 |
|
||
| 并发 | background run 持有一个 Semaphore permit | run admission 与 provider/tool step permit 分离 | 高 |
|
||
| 结果投递 | background 终态直接发 Channel | SQLite inbox 为事实源,Session wake 仅为加速器 | 高 |
|
||
| steering | mailbox 只接受用户 ChatMessage | 来源感知 TurnInput、durable admission 和可靠 queue fallback | 高 |
|
||
| continuation | 用户任务在模型调用前落库 | hidden trigger 与 assistant/tool/usage/event consume 同事务提交 | 高 |
|
||
| history | 所有 role=user 都作为用户消息 | internal replay 读 hidden,客户端默认过滤 hidden | 高 |
|
||
| Channel context | `reply_to` 与 opaque `private` 未区分稳定性 | Channel 显式提供 `durable_private`,核心不得猜 key | 高 |
|
||
| 重载恢复 | 候选代创建完整 SessionManager | Catalog 在 prepare 校验,run/inbox 恢复只能 activation 后执行 | 高 |
|
||
| 客户端 | `/api/tasks` 只读旧 `background_tasks` | 新 run/event 投影、revision、旧表 union 过渡 | 中 |
|
||
|
||
实现的关键路径为:
|
||
|
||
```text
|
||
Catalog/Tool policy
|
||
↓
|
||
AgentLoop cancellation + execution gate
|
||
↓
|
||
Run/group durable lifecycle
|
||
↓
|
||
Inbox state/capacity + Session wake + queue continuation
|
||
↓
|
||
Typed TurnMailbox + emit_signal/steer
|
||
↓
|
||
Wake-aware sleep + UI/legacy cleanup
|
||
```
|
||
|
||
Phase 3 依赖前面全部基础。若跳过 cancellation、持久化状态机或 hidden message 分层,结果会在 `/stop`、reload、SQLite 失败或进程重启时出现无法修复的丢失与重复。
|
||
|
||
## 2. 实施中固定的架构决策
|
||
|
||
以下决策在编码前固定,不留给各模块自行解释:
|
||
|
||
1. Root Agent 是运行时身份,不创建虚假的 root run。`ToolExecutionContext.agent=None` 表示 Root;child 必须携带完整 `AgentExecutionContext`。
|
||
2. `foreground/background` 只描述调用方是否等待。批量是否并发由 `tasks[]` 和调度器决定;不再存在 canonical `parallel` 模式。
|
||
3. 每个 Agent Run 都落 `agent_runs`,包括 foreground。这样截断结果、失败审计和 `agent_task.get_result` 对两种模式一致。
|
||
4. Background 接纳时预留 completion slot。signal 可以因 inbox 满而失败,已经接纳的 run completion 不得因容量耗尽而丢失。
|
||
5. SQLite 是 run、event、容量计数和消费状态的唯一事实源。watch/broadcast/内存 map 只负责降低延迟或缓存投影。
|
||
6. queue continuation 不伪造 `InboundMessage`。它通过 Session 内部 typed task 启动,并在成功时原子保存 hidden trigger、可见结果和 event consumption。
|
||
7. `steer` 不取消 Provider 或普通工具;它只在安全边界注入。`/stop` 和显式 cancel 才触发 cancellation token。
|
||
8. 候选运行代只解析和校验 Catalog,不扫描或修改数据库运行状态。恢复动作仅在新代 activation 后执行。
|
||
9. 新工具默认 `RootOnly`。只有经过逐项审计的工具可以标记 `Delegatable`;runtime control 工具由 Coordinator 注入。
|
||
10. session 归档/删除、run 取消、event supersede 和 dead-letter 都保留审计事实,不通过物理删除表达状态变化。
|
||
|
||
## 3. 目标运行时组件与依赖装配
|
||
|
||
### 3.1 GatewayState 新增成员
|
||
|
||
建议增加:
|
||
|
||
```rust
|
||
pub struct GatewayState {
|
||
// existing fields ...
|
||
agent_catalog: Arc<AgentCatalog>,
|
||
agent_coordinator: Arc<AgentCoordinator>,
|
||
agent_result_router: Arc<AgentResultRouter>,
|
||
agent_projection_hub: Arc<AgentProjectionHub>,
|
||
}
|
||
```
|
||
|
||
装配顺序必须明确:
|
||
|
||
```text
|
||
Config + registered built-in tools + SkillsLoader
|
||
→ parse/validate AgentCatalog
|
||
→ create AgentProjectionHub
|
||
→ create late-bound AgentInboxNotifier
|
||
→ create AgentCoordinator(Storage, Catalog, ProviderFactory, tools, Supervisor, Admission)
|
||
→ create SessionManager(..., notifier)
|
||
→ bind notifier to Weak<SessionManager wake interface>
|
||
→ create AgentResultRouter(Storage, notifier, projection hub)
|
||
```
|
||
|
||
`AgentInboxNotifier` 使用 late-bound weak target 解决 Coordinator/SessionManager 的环形依赖:
|
||
|
||
```rust
|
||
#[async_trait]
|
||
pub trait AgentInboxWakeTarget: Send + Sync {
|
||
async fn wake_agent_inbox(&self, session_id: &str, revision: i64);
|
||
}
|
||
|
||
pub struct AgentInboxNotifier {
|
||
target: RwLock<Weak<dyn AgentInboxWakeTarget>>,
|
||
}
|
||
```
|
||
|
||
事件提交后 notifier 失败不回滚事务;周期扫描会重新唤醒。SessionManager 也不能反向持有强 `Arc<AgentCoordinator>` 形成释放环。
|
||
|
||
### 3.2 prepare 与 activation 分离
|
||
|
||
`GatewayState::from_config()` 是候选运行代准备阶段,只允许:
|
||
|
||
- 解析配置和 Agent Markdown。
|
||
- 校验 Provider profile、工具、skill 和委托图。
|
||
- 创建无外部副作用的内存对象。
|
||
- 运行 Storage schema migration。
|
||
|
||
`start_message_processing()` 成为 activation 边界,按顺序执行:
|
||
|
||
1. 连接 MCP;第一版 Agent Definition 不允许引用 MCP 工具,因此此步不改变已经校验完成的 Catalog。
|
||
2. 启动 Session worker/router、projection relay 和 inbox recovery scanner。
|
||
3. 调用 `AgentCoordinator::recover_on_activation(runtime_generation)`,收敛旧代 queued/running/waiting runs。
|
||
4. 扫描 pending/expired leases,修复 `agent_session_state` 计数并发出合并式 wake。
|
||
5. 最后打开新代的 Agent run admission。
|
||
|
||
为避免候选代构造期间与旧代同时修改 run 状态,Coordinator 初始处于 closed 状态,activation 成功后才 `open()`。
|
||
|
||
## 4. 配置与 Agent Definition
|
||
|
||
### 4.1 配置类型
|
||
|
||
在 `src/config/mod.rs` 增加:
|
||
|
||
```rust
|
||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||
#[serde(default, deny_unknown_fields)]
|
||
pub struct AgentOrchestrationConfig {
|
||
pub enabled: bool,
|
||
pub definitions_dir: String,
|
||
pub root_delegates: Vec<String>,
|
||
pub max_tree_depth: u16,
|
||
pub max_runs_per_tree: usize,
|
||
pub max_concurrent_runs: usize,
|
||
pub max_concurrent_runs_per_session: usize,
|
||
pub max_concurrent_provider_steps: usize,
|
||
pub max_concurrent_provider_steps_per_session: usize,
|
||
pub max_concurrent_tool_steps: usize,
|
||
pub max_concurrent_tool_steps_per_session: usize,
|
||
pub max_pending_inbox_events_per_session: usize,
|
||
pub inbox_event_ttl_hours: u64,
|
||
pub max_inbox_delivery_attempts: u32,
|
||
pub max_user_turn_burst_before_inbox: usize,
|
||
pub max_inbox_wait_secs: u64,
|
||
}
|
||
```
|
||
|
||
`Config` 增加 `#[serde(default)] pub agent_orchestration: AgentOrchestrationConfig`。所有默认值必须保持现有配置可加载。旧 `gateway.max_concurrent_background_tasks` 在迁移期只控制 legacy adapter;新实现不复用该字段表达三类不同配额。
|
||
|
||
配置校验需要拒绝 0 容量、session 上限大于 global 上限、TTL/timeout 超过硬上限,以及 definitions 目录越界。配置示例、README 和运行时 config reference 在功能合并时同步更新。
|
||
|
||
热重载允许把 inbox 上限降低到当前占用以下:已有 pending 和 reservation 仍受保护,不删除也不拒绝其 completion;新 background 接纳和 signal 在计数回落到新上限前返回 capacity exceeded。
|
||
|
||
### 4.2 Markdown parser
|
||
|
||
新增 `serde_yaml` 依赖,禁止用现有 Skill frontmatter 的宽松字符串 parser 解析安全配置。建议类型:
|
||
|
||
```rust
|
||
#[derive(Deserialize)]
|
||
#[serde(deny_unknown_fields)]
|
||
struct AgentFrontmatter {
|
||
id: AgentId,
|
||
description: String,
|
||
llm_profile: String,
|
||
#[serde(default)] tools: Vec<String>,
|
||
#[serde(default)] delegates: Vec<AgentId>,
|
||
#[serde(default)] skills: Vec<String>,
|
||
#[serde(default)] limits: AgentLimits,
|
||
}
|
||
```
|
||
|
||
加载算法:
|
||
|
||
1. canonicalize definitions root,确认它是受信任目录。
|
||
2. 只读取 root 第一层的 `*.md`,按文件名排序,保证错误顺序和 hash 稳定。
|
||
3. 对每个文件先用 metadata 检查上限,再做有界读取;拒绝非 UTF-8、越界 symlink 和非普通文件。
|
||
4. frontmatter 必须以第一行 `---` 开始并有独立结束 `---`;正文为空、重复 key、未知 key 均报错。
|
||
5. `id` 使用 `[a-z][a-z0-9_-]{0,63}`,拒绝大小写折叠冲突和保留名。
|
||
6. 对规范化 frontmatter JSON 与原始 role body 计算 SHA-256 `definition_hash`。
|
||
7. 第一遍建立 ID map,第二遍解析 delegate target、Provider、工具和 skill 引用。
|
||
|
||
Catalog 最终类型不可变:
|
||
|
||
```rust
|
||
pub struct AgentCatalog {
|
||
definitions: BTreeMap<AgentId, Arc<AgentDefinition>>,
|
||
root_delegates: BTreeSet<AgentId>,
|
||
runtime_generation: u64,
|
||
}
|
||
```
|
||
|
||
第一版 Catalog 只接受候选代准备阶段已经注册的 built-in 工具。现有 MCP 连接只允许在 activation 发生,为维持候选代无外部副作用和“引用错误拒绝整代”的不变量,MCP 工具不得出现在 Agent Definition;未来只有在 MCP 提供可离线校验的 tool manifest 后才能开放。
|
||
|
||
### 4.3 legacy general
|
||
|
||
当 orchestration 未配置或旧调用没有 `target` 时,兼容层提供代码内置 `general` definition:
|
||
|
||
- Provider 使用当前 root agent profile。
|
||
- 工具只取旧 default list 与 `Delegatable` 的交集。
|
||
- 不能继续 delegate,不能 emit signal。
|
||
- 保留旧 transient browser scope 的兼容行为并返回弃用提示。
|
||
|
||
显式配置的具名 Agent 永远不继承该例外。
|
||
|
||
## 5. ToolRegistry 与执行上下文改造
|
||
|
||
### 5.1 Tool 元数据
|
||
|
||
在 `Tool` trait 增加默认安全元数据:
|
||
|
||
```rust
|
||
fn delegation_policy(&self) -> DelegationPolicy {
|
||
DelegationPolicy::RootOnly
|
||
}
|
||
|
||
fn input_interrupt_policy(&self) -> InputInterruptPolicy {
|
||
InputInterruptPolicy::Never
|
||
}
|
||
```
|
||
|
||
`DelegationPolicy` 为 `RootOnly | Delegatable | RuntimeInjected`;`InputInterruptPolicy` 为 `Never | WakeOnly | CancelSafe`。二者不能从 Markdown 覆盖。
|
||
|
||
第一轮审计建议:
|
||
|
||
| 工具类型 | 初始策略 | 说明 |
|
||
|----------|----------|------|
|
||
| file/content search、file read | Delegatable | 仍受现有进程文件权限约束,不代表硬 sandbox |
|
||
| browser 普通动作 | Delegatable | 具名 run 使用独立 transient resource scope |
|
||
| `get_skill` | RuntimeInjected wrapper | 只能读取 definition 的 skill allowlist |
|
||
| HTTP request | RootOnly | 当前实现支持写方法;以后可增加 delegated GET-only wrapper |
|
||
| bash、file write/edit | RootOnly | 需单独威胁建模后才开放 |
|
||
| send_message、todo、cron、reload、browser_profiles、管理工具 | RootOnly | 有外部或全局状态副作用 |
|
||
| delegate、agent_task、emit_signal | RuntimeInjected | 依据 caller context 动态注入 |
|
||
| sleep | Delegatable + WakeOnly | child 无 TurnWakeupHandle,只响应 timer/cancel |
|
||
|
||
`ToolRegistry` 增加只读构建方法,不在共享 registry 上删除工具:
|
||
|
||
```rust
|
||
pub fn scoped_for_agent(
|
||
&self,
|
||
definition: &AgentDefinition,
|
||
runtime_tools: Vec<Arc<dyn Tool>>,
|
||
) -> Result<Arc<ToolRegistry>, AgentCatalogError>;
|
||
```
|
||
|
||
### 5.2 ToolExecutionContext
|
||
|
||
目标类型:
|
||
|
||
```rust
|
||
#[derive(Clone)]
|
||
pub struct ToolExecutionContext {
|
||
pub session_id: Option<String>,
|
||
pub turn_id: Option<String>,
|
||
pub agent: Option<Arc<AgentExecutionContext>>,
|
||
pub cancellation: CancellationToken,
|
||
pub execution_gate: Option<Arc<ExecutionGate>>,
|
||
pub turn_wakeup: Option<TurnWakeupHandle>,
|
||
pub resource_scope_id: Option<String>,
|
||
pub turn_origin: TurnOrigin,
|
||
}
|
||
```
|
||
|
||
Root interactive context 的 `agent=None`;Coordinator 只允许在 session/turn 身份完整时把它解释成 ROOT。缺少两者的 context 不能调用 delegate。child 的 `agent=Some`,其中 run、parent、ancestry、budget 和 signal contract 是授权事实。
|
||
|
||
不要再依赖 `DELEGATE_CONTEXT` task-local 作授权。它可以暂时保留为旧 adapter 桥接,但所有新工具必须从 `ToolExecutionContext` 读取身份。
|
||
|
||
## 6. AgentLoop:取消、许可与 typed input
|
||
|
||
### 6.1 CancellationContext
|
||
|
||
给 AgentLoop 的所有 process 入口增加 `AgentLoopExecution`:
|
||
|
||
```rust
|
||
pub struct AgentLoopExecution {
|
||
pub cancellation: CancellationToken,
|
||
pub gate: Arc<ExecutionGate>,
|
||
pub tool_context: ToolExecutionContext,
|
||
}
|
||
```
|
||
|
||
Provider 请求必须在获取 provider-step permit 后执行:
|
||
|
||
```rust
|
||
let _permit = execution.gate.acquire_provider(&execution.cancellation).await?;
|
||
let response = tokio::select! {
|
||
_ = execution.cancellation.cancelled() => Err(AgentError::Cancelled),
|
||
result = self.stream_completion(...) => result,
|
||
};
|
||
```
|
||
|
||
普通工具调用同样取得 tool-step permit。runtime-control 工具不取得普通 tool permit,防止父 run 等 child 时占住唯一许可。工具 future 被取消只表示 PicoBot 不再等待;对 bash、HTTP 写入等外部副作用不能宣称已回滚,因此迟到结果必须被 execution ID 条件提交挡住。
|
||
|
||
工具批次当前使用 `join_all`。改造后每项 future 自行获取 permit,批次仍可并发;取消时等待一个很短的 cooperative grace,然后由外层 task abort。`AgentError` 增加结构化 `Cancelled` 和 `TimedOut`,不要用字符串判断终态。
|
||
|
||
### 6.2 ExecutionGate
|
||
|
||
配额分成两组:
|
||
|
||
- `RunQuota`:统计 queued/running/waiting_children 的非终态 run,直到 terminal commit 才释放。
|
||
- `StepGate`:provider/tool 步骤执行期间持有,步骤结束立即释放。
|
||
|
||
permit 固定按 global → session → agent 顺序获取;失败或取消逆序释放。为了避免动态 semaphore 缓存泄漏,session/agent gate 使用带弱引用的 keyed registry,并在没有 run/permit 时清理。
|
||
|
||
Root Turn 不是 Agent Run,不占 run quota,但它的 Provider/tool 步骤占 global/session step gate,这样 background run 不会绕过整个 Gateway 的资源上限。
|
||
|
||
### 6.3 waiting_children
|
||
|
||
`delegate` foreground 执行前创建 `WaitingChildrenGuard`:
|
||
|
||
1. child 接纳成功后,条件更新 parent `running → waiting_children`。
|
||
2. 等待期间父没有 provider/tool step permit。
|
||
3. 全部 child 终态、父取消或错误退出时 guard 尝试 `waiting_children → running`;父已经 terminal 时不覆盖。
|
||
4. 父取消递归取消未终态 foreground descendants;background run 归 root session token 所有,不因创建它的 Turn 正常结束而取消。
|
||
|
||
### 6.4 typed Turn input
|
||
|
||
Phase 4 将 `SteeringMailbox<ChatMessage>` 替换为 `TurnMailbox<TurnInput>`。在此之前 Phase 2A 只做 cancellation,不改变用户 steering 行为,降低一次改动的回归面。
|
||
|
||
`AgentLoop::append_steering_messages` 不再强制构造 user source;改为由 serializer 把 typed source 转成 Provider-compatible role,同时保留 durable source metadata。Agent event envelope 明确标注为不可信数据,不能改变 system/tool 权限。
|
||
|
||
## 7. AgentCoordinator API 与状态机
|
||
|
||
建议公开最小接口:
|
||
|
||
```rust
|
||
pub struct DelegateRequest {
|
||
pub mode: ExecutionMode,
|
||
pub tasks: Vec<DelegateTask>,
|
||
pub completion_policy: CompletionPolicy,
|
||
pub idempotency_key: Option<String>,
|
||
}
|
||
|
||
impl AgentCoordinator {
|
||
pub async fn delegate(&self, caller: CallerContext, request: DelegateRequest)
|
||
-> Result<DelegateResponse, AgentCoordinatorError>;
|
||
pub async fn get_run(&self, caller: CallerContext, run_id: &RunId) -> Result<AgentRunView, _>;
|
||
pub async fn list_runs(&self, caller: CallerContext, query: AgentRunQuery) -> Result<_, _>;
|
||
pub async fn get_result(&self, caller: CallerContext, run_id: &RunId) -> Result<_, _>;
|
||
pub async fn cancel_run(&self, caller: CallerContext, run_id: &RunId) -> Result<_, _>;
|
||
pub async fn emit_signal(&self, context: &AgentExecutionContext, signal: SignalInput)
|
||
-> Result<SignalAccepted, _>;
|
||
pub async fn cancel_session(&self, session_id: &str, reason: CancelReason) -> Result<(), _>;
|
||
pub async fn recover_on_activation(&self, generation: u64) -> Result<RecoveryReport, _>;
|
||
}
|
||
```
|
||
|
||
### 7.1 Background 接纳顺序
|
||
|
||
严格顺序:
|
||
|
||
1. 从 context 解析 caller,校验 root session、委托边、ancestry、depth 和 budget。
|
||
2. 解析全部 target definition,派生 deadline、token/run reservation 和 delivery contract。
|
||
3. 取得 RuntimeAdmission activity guard 和内存 run quota reservation;尚未写库前任何失败均直接释放。
|
||
4. Storage 事务条件增加 completion reservation、领取 plan item、插入 group/run queued 记录。
|
||
5. 把 cancellation token 和 execution ID 注册到 Coordinator active map。
|
||
6. 通过 `TaskSupervisor::spawn_graceful` 接纳 runner。
|
||
7. spawn 被拒绝时执行补偿事务:queued → cancelled、释放 completion reservation、回滚/阻塞已领取 plan item;不得向模型返回可用 run ID。
|
||
8. spawn 成功后返回 run/group ID。
|
||
|
||
第一版在步骤 1 强制 `background caller == ROOT`;child 只能 foreground 委托。该限制属于 Coordinator policy,不仅是 delegate schema 提示。以后开放 nested background 时仍必须把 run 归属到原 root session,并重新审查取消所有权和 completion reservation。
|
||
|
||
SQLite commit 与 task spawn 无法成为同一原子操作。崩溃发生在步骤 4 与 6 之间时,会留下旧 generation 的 queued run;activation recovery 必须将其收敛为 `interrupted` 并生成预留过的 completion,不能无限保持 queued。
|
||
|
||
ActivityGuard 必须移动进已接纳的 background runner,直到 terminal transaction 完成后才 drop;不能在 `delegate` 返回时提前释放。Runner 等待 run quota/provider permit 时同样受 deadline 和 cancellation 约束,排队时间计入 run timeout。
|
||
|
||
### 7.2 Foreground 执行
|
||
|
||
Foreground 同样先持久化 run,但不预留 inbox slot,也不创建 completion event。单任务直接 await;批量使用 `FuturesUnordered` 并保存原 request index,最终按请求顺序组装结果。每个 child 有独立取消 token;父取消时全部 token 同时取消。
|
||
|
||
完整结果先写 `agent_runs.result`,tool result 只返回有界 projection。这样无论是否截断,`agent_task.get_result` 都能读取相同事实。
|
||
|
||
### 7.3 terminal commit
|
||
|
||
Runner 只返回 `AgentTerminalOutcome`,不能自己发 Channel 或更新 WorkManager:
|
||
|
||
```rust
|
||
pub enum AgentTerminalOutcome {
|
||
Completed { result: String, usage: Usage, tool_calls: u32, iterations: u32 },
|
||
Failed { error: AgentRunError, usage: Option<Usage> },
|
||
TimedOut { deadline_at: i64 },
|
||
Cancelled { reason: CancelReason },
|
||
Interrupted { reason: String },
|
||
}
|
||
```
|
||
|
||
Coordinator 用 `(run_id, execution_id, runtime_generation, nonterminal status)` 条件提交。只有更新行数为 1 的赢家可以:
|
||
|
||
- 写终态和完整 result/error。
|
||
- 更新 group terminal counter。
|
||
- 转换或释放 completion reservation。
|
||
- 插入 run/group inbox event。
|
||
- 更新绑定的 task item。
|
||
- 递增 session agent revision。
|
||
|
||
迟到结果返回 `StaleExecution` 并丢弃,不改变已保存终态。
|
||
|
||
Coordinator 还需启动一个由 TaskSupervisor 管理、观察 shutdown token 的 deadline reaper。正常 runner 使用 `timeout_at(deadline)`;reaper 负责没有活跃内存 task 的 queued run 和 group deadline,分批条件终态化未完成 children,再走同一个 terminal transaction,不能维护第二套完成逻辑。
|
||
|
||
## 8. Storage schema v6
|
||
|
||
实现时把 `SCHEMA_VERSION` 从 5 升到 6。表创建、旧列增加、索引和 `PRAGMA user_version=6` 必须在当前 `migrate_schema()` 的同一事务中完成。下面的 `ALTER TABLE` 表达目标列;实际代码必须同时更新 fresh-schema 的 `CREATE TABLE`,迁移时沿用现有 `PRAGMA table_info` 检查,仅对缺失列执行 ALTER,不能在 fresh database 上重复加列。
|
||
|
||
SQLite 使用 INTEGER 表示布尔值;应用层枚举仍用 Rust enum,读到未知值要报 corruption,不能静默当成 completed。
|
||
|
||
### 8.1 session 与 message 扩展
|
||
|
||
```sql
|
||
ALTER TABLE sessions ADD COLUMN delivery_context TEXT;
|
||
ALTER TABLE sessions ADD COLUMN delivery_context_updated_at INTEGER;
|
||
|
||
ALTER TABLE messages
|
||
ADD COLUMN client_visibility TEXT NOT NULL DEFAULT 'visible';
|
||
ALTER TABLE messages
|
||
ADD COLUMN turn_origin TEXT NOT NULL DEFAULT 'user';
|
||
|
||
CREATE INDEX IF NOT EXISTS idx_messages_session_visibility_seq
|
||
ON messages(session_id, client_visibility, seq);
|
||
```
|
||
|
||
`delivery_context` 只序列化 `ChannelContext.durable_private`。现有 `reply_to` 和 `private` 永不写入该字段。
|
||
|
||
### 8.2 agent_session_state
|
||
|
||
```sql
|
||
CREATE TABLE IF NOT EXISTS agent_session_state (
|
||
root_session_id TEXT PRIMARY KEY,
|
||
revision INTEGER NOT NULL DEFAULT 0,
|
||
pending_event_count INTEGER NOT NULL DEFAULT 0,
|
||
reserved_completion_slots INTEGER NOT NULL DEFAULT 0,
|
||
updated_at INTEGER NOT NULL,
|
||
CHECK (revision >= 0),
|
||
CHECK (pending_event_count >= 0),
|
||
CHECK (reserved_completion_slots >= 0)
|
||
);
|
||
```
|
||
|
||
这是 inbox 容量和客户端 revision 的权威计数行。每个改变客户端 run/group/event 投影的事务只分配一个新 revision,并把同一 revision 写回本事务改变的所有行;仅内部读取不递增。所有容量增加操作使用条件更新:
|
||
|
||
```sql
|
||
UPDATE agent_session_state
|
||
SET reserved_completion_slots = reserved_completion_slots + ?,
|
||
revision = revision + 1,
|
||
updated_at = ?
|
||
WHERE root_session_id = ?
|
||
AND pending_event_count + reserved_completion_slots + ? <= ?
|
||
RETURNING revision;
|
||
```
|
||
|
||
若 session 尚无行,先 `INSERT ... ON CONFLICT DO NOTHING`,仍在同一写事务中执行条件更新。
|
||
|
||
### 8.3 agent_run_groups
|
||
|
||
```sql
|
||
CREATE TABLE IF NOT EXISTS agent_run_groups (
|
||
id TEXT PRIMARY KEY,
|
||
root_session_id TEXT NOT NULL,
|
||
caller_run_id TEXT,
|
||
caller_scope_id TEXT NOT NULL,
|
||
idempotency_key TEXT,
|
||
mode TEXT NOT NULL,
|
||
completion_policy TEXT NOT NULL,
|
||
expected_runs INTEGER NOT NULL,
|
||
terminal_runs INTEGER NOT NULL DEFAULT 0,
|
||
abnormal_runs INTEGER NOT NULL DEFAULT 0,
|
||
completion_slot_reserved INTEGER NOT NULL DEFAULT 0,
|
||
completion_delivery TEXT,
|
||
failure_delivery TEXT,
|
||
deadline_at INTEGER NOT NULL,
|
||
status TEXT NOT NULL,
|
||
runtime_generation INTEGER NOT NULL,
|
||
revision INTEGER NOT NULL,
|
||
created_at INTEGER NOT NULL,
|
||
updated_at INTEGER NOT NULL,
|
||
finished_at INTEGER,
|
||
CHECK (mode IN ('foreground', 'background')),
|
||
CHECK (completion_policy IN ('all', 'each')),
|
||
CHECK (status IN ('queued', 'running', 'completed', 'partial', 'failed',
|
||
'timed_out', 'cancelled', 'interrupted')),
|
||
CHECK (expected_runs > 0),
|
||
CHECK (terminal_runs >= 0 AND terminal_runs <= expected_runs),
|
||
CHECK (completion_slot_reserved IN (0, 1))
|
||
);
|
||
|
||
CREATE INDEX IF NOT EXISTS idx_agent_groups_session_created
|
||
ON agent_run_groups(root_session_id, created_at DESC);
|
||
|
||
CREATE UNIQUE INDEX IF NOT EXISTS idx_agent_groups_idempotency
|
||
ON agent_run_groups(root_session_id, caller_scope_id, idempotency_key)
|
||
WHERE idempotency_key IS NOT NULL;
|
||
```
|
||
|
||
批量请求把 request idempotency key 写在 group,所有 child run 的 `idempotency_key` 为 NULL;单任务请求不创建只含一个 child 的 group,key 直接写在 run。这样 retry 能返回原 group/run,又不会让同一批 children 触发唯一索引冲突。
|
||
|
||
### 8.4 agent_runs
|
||
|
||
```sql
|
||
CREATE TABLE IF NOT EXISTS 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,
|
||
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,
|
||
deadline_at INTEGER NOT NULL,
|
||
revision INTEGER NOT NULL,
|
||
started_at INTEGER,
|
||
finished_at INTEGER,
|
||
created_at INTEGER NOT NULL,
|
||
updated_at INTEGER NOT NULL,
|
||
CHECK (mode IN ('foreground', 'background')),
|
||
CHECK (status IN ('queued', 'running', 'waiting_children', 'completed',
|
||
'failed', 'timed_out', 'cancelled', 'interrupted')),
|
||
CHECK (depth >= 1),
|
||
CHECK (completion_slot_reserved IN (0, 1)),
|
||
FOREIGN KEY (group_id) REFERENCES agent_run_groups(id) ON DELETE RESTRICT,
|
||
FOREIGN KEY (parent_run_id) REFERENCES agent_runs(id) ON DELETE RESTRICT
|
||
);
|
||
|
||
CREATE UNIQUE INDEX IF NOT EXISTS idx_agent_runs_execution
|
||
ON agent_runs(execution_id);
|
||
|
||
CREATE UNIQUE INDEX IF NOT EXISTS idx_agent_runs_idempotency
|
||
ON agent_runs(root_session_id, caller_scope_id, idempotency_key)
|
||
WHERE idempotency_key IS NOT NULL;
|
||
|
||
CREATE INDEX IF NOT EXISTS idx_agent_runs_session_created
|
||
ON agent_runs(root_session_id, created_at DESC);
|
||
|
||
CREATE INDEX IF NOT EXISTS idx_agent_runs_parent
|
||
ON agent_runs(parent_run_id, created_at);
|
||
|
||
CREATE INDEX IF NOT EXISTS idx_agent_runs_recovery
|
||
ON agent_runs(runtime_generation, status, deadline_at);
|
||
```
|
||
|
||
Session 使用软删除,agent 表不对 `root_session_id` 建级联外键;生命周期由 `cancel_session` 和恢复扫描显式收敛。
|
||
|
||
### 8.5 agent_inbox_events
|
||
|
||
```sql
|
||
CREATE TABLE IF NOT EXISTS agent_inbox_events (
|
||
id TEXT PRIMARY KEY,
|
||
root_session_id TEXT NOT NULL,
|
||
scope_kind TEXT NOT NULL,
|
||
scope_id TEXT NOT NULL,
|
||
run_id TEXT,
|
||
group_id TEXT,
|
||
event_type TEXT NOT NULL,
|
||
event_key TEXT NOT NULL,
|
||
delivery TEXT NOT NULL,
|
||
requires_continuation INTEGER NOT NULL DEFAULT 1,
|
||
severity TEXT,
|
||
payload_json TEXT NOT NULL,
|
||
status TEXT NOT NULL,
|
||
attempt_count INTEGER NOT NULL DEFAULT 0,
|
||
lease_token TEXT,
|
||
lease_until INTEGER,
|
||
next_attempt_at INTEGER,
|
||
admitted_turn_id TEXT,
|
||
last_error TEXT,
|
||
revision INTEGER NOT NULL,
|
||
created_at INTEGER NOT NULL,
|
||
consumed_at INTEGER,
|
||
superseded_at INTEGER,
|
||
dead_lettered_at INTEGER,
|
||
fallback_notified_at INTEGER,
|
||
fallback_suppressed_reason TEXT,
|
||
CHECK (scope_kind IN ('run', 'group')),
|
||
CHECK (event_type IN ('signal', 'completion', 'group_completion')),
|
||
CHECK (delivery IN ('queue', 'steer')),
|
||
CHECK (requires_continuation IN (0, 1)),
|
||
CHECK (status IN ('pending', 'leased', 'admitted', 'consumed',
|
||
'superseded', 'dead_letter')),
|
||
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)
|
||
),
|
||
UNIQUE(scope_kind, scope_id, event_type, event_key),
|
||
FOREIGN KEY (run_id) REFERENCES agent_runs(id) ON DELETE RESTRICT,
|
||
FOREIGN KEY (group_id) REFERENCES agent_run_groups(id) ON DELETE RESTRICT
|
||
);
|
||
|
||
CREATE INDEX IF NOT EXISTS idx_agent_inbox_claim
|
||
ON agent_inbox_events(root_session_id, status, next_attempt_at, created_at);
|
||
|
||
CREATE INDEX IF NOT EXISTS idx_agent_inbox_lease
|
||
ON agent_inbox_events(status, lease_until);
|
||
|
||
CREATE INDEX IF NOT EXISTS idx_agent_inbox_revision
|
||
ON agent_inbox_events(root_session_id, revision);
|
||
```
|
||
|
||
run/group 外键使用 RESTRICT。清理默认只清理大正文或归档整组记录;若未来需要物理删除,必须先按明确保留策略删除 terminal inbox event,再删除 run/group,不能留下失去审计来源的事件。
|
||
|
||
### 8.6 事务 API
|
||
|
||
不要把 SQL 分散在 Coordinator、Router 和 WorkManager。Storage 增加领域 API:
|
||
|
||
```rust
|
||
accept_agent_group(request) -> AcceptedGroup
|
||
mark_agent_run_running(run_id, execution_id)
|
||
mark_agent_run_waiting_children(run_id, execution_id, expected_status)
|
||
commit_agent_terminal(run_id, execution_id, outcome) -> TerminalCommit
|
||
insert_agent_signal(run_id, execution_id, signal) -> EventCommit
|
||
claim_inbox_batch(session_id, now, lease) -> InboxLease
|
||
admit_inbox_event(event_id, lease_token, turn_id)
|
||
release_inbox_lease(event_ids, lease_token, retry)
|
||
commit_continuation_turn(batch) -> CommittedTurnDelta
|
||
recover_agent_state(active_generation, now) -> RecoveryReport
|
||
```
|
||
|
||
`commit_agent_terminal` 在一个事务内更新 run/group、容量计数、event、plan item 和 revision。提交后 Coordinator 调用 `WorkManager::refresh_after_external_commit()` 刷新 cache 并广播;WorkManager 不再为这条路径另开事务。
|
||
|
||
## 9. Agent Inbox、唤醒与公平调度
|
||
|
||
### 9.1 Session worker 接收面
|
||
|
||
Session 增加:
|
||
|
||
```rust
|
||
agent_tx: Option<mpsc::Sender<UserAgentTask>>,
|
||
agent_inbox_wake: watch::Sender<i64>,
|
||
consecutive_user_turns: usize,
|
||
```
|
||
|
||
把 worker 创建逻辑抽为 `ensure_agent_worker_locked()`,用户 enqueue 与 `wake_agent_inbox()` 共用。worker 同时持有 user receiver 和 wake receiver:
|
||
|
||
```rust
|
||
tokio::select! {
|
||
biased;
|
||
user = task_rx.recv(), if should_take_user => { ... }
|
||
changed = inbox_wake.changed() => { ... }
|
||
}
|
||
```
|
||
|
||
不能仅靠 `biased` 实现公平。每个 Turn 结束后查询最老 due pending event;满足以下任一条件时下一项必须是 continuation:
|
||
|
||
- `consecutive_user_turns >= max_user_turn_burst_before_inbox`;
|
||
- oldest pending age >= `max_inbox_wait_secs`。
|
||
|
||
watch value 是最新 durable revision,只合并 wake,不携带 payload。worker 收到 wake 后从 SQLite claim;发送方从不等待 Session user mpsc 容量。
|
||
|
||
### 9.2 queue continuation
|
||
|
||
claim batch 必须有大小与总字节上限,例如 8 events/32 KiB envelope。`completion_policy=each` 可在 300–500ms 内 debounce 已经 pending 的 siblings,但不能等待未终态 sibling。
|
||
|
||
执行过程:
|
||
|
||
1. Storage 把 due events `pending → leased`,返回 lease token。
|
||
2. worker 构造 `AgentTaskSource::BackgroundAgentResults` 和 hidden trigger,但暂不落 messages。
|
||
3. root Agent 使用只读 continuation ToolRegistry 执行。默认允许内容读取、检索和 scoped `agent_task.get/get_result`,不允许 send_message、todo、写文件、delegate 或其他外部副作用。
|
||
4. 成功后 `commit_continuation_turn` 原子插入 hidden trigger、tool/assistant messages、usage,并把同 token events `leased/admitted → consumed`。
|
||
5. commit 后更新 Session 内存、Turn Completed、projection 和 Channel delivery。
|
||
6. 执行失败、取消或 stale generation 时 `InboxLeaseGuard` 显式 release 到 pending 并写 `next_attempt_at`。
|
||
|
||
这一流程与当前“先持久化用户消息再调用模型”不同,必须走独立分支。不能先写 hidden trigger,否则失败重试会在 history 中累积无 assistant 配对的内部输入。
|
||
|
||
### 9.3 hidden history
|
||
|
||
`ChatMessage` 和 `MessageMeta` 增加:
|
||
|
||
```rust
|
||
pub client_visibility: ClientVisibility,
|
||
pub turn_origin: TurnOrigin,
|
||
```
|
||
|
||
默认构造器生成 `Visible/User`。需要拆分查询:
|
||
|
||
- `load_messages_for_replay(session_id)`:visible + hidden。
|
||
- `load_visible_messages(session_id, ...)`:WebSocket/HTTP/管理历史。
|
||
- `persist_message_batch_inner()`:支持两字段并保持向后兼容默认值。
|
||
- `CommittedTurnDelta`:过滤 hidden,并携带 turn origin。
|
||
|
||
Session 的 `message_count` 只统计 visible external user input;`total_message_count` 和压缩/token 估算统计全部 replay messages。自动标题只由 visible user input 触发。
|
||
|
||
### 9.4 steer admission
|
||
|
||
Router 的两阶段流程必须由测试锁定:
|
||
|
||
```text
|
||
pending --claim(token)--> leased
|
||
leased --reserve current Turn--> memory reservation (not drainable)
|
||
leased --DB admit(token, turn)--> admitted
|
||
admitted --activate same turn/generation--> drainable TurnInput
|
||
```
|
||
|
||
任何失败都取消 reservation,并以 token 把 event 恢复 pending。若 active Turn 已关闭、mailbox 满或 generation 改变,直接释放 lease并 wake queue;这就是可靠 steer→queue fallback。
|
||
|
||
TurnMailbox 分 user lane 与 agent lane 容量,最终按 session sequence 合并排空。durable agent reservation 在 DB admit 前不能被 AgentLoop 看见;`/stop` 关闭 mailbox 时返回尚未提交的 event IDs/tokens,由 SessionManager 立即 release。
|
||
|
||
## 10. emit_signal 与 completion 投影
|
||
|
||
`EmitSignalTool` 只注册到携带 background signal contract 的 runner。foreground run、Root 或没有 contract 的 child 看不到该工具。
|
||
|
||
`emit_signal` 流程:
|
||
|
||
1. 从 AgentExecutionContext 取得唯一 root session、run、execution ID 和 delivery。
|
||
2. 校验 run 仍为 running/waiting_children 且 execution ID 匹配。
|
||
3. 校验 severity、JSON 深度、summary/details 字节、信号总数、burst、最小间隔和 dedupe window。
|
||
4. 条件增加 session pending count;容量必须扣除 reserved completion slots。
|
||
5. 插入 event 和新 revision;commit 后发布 projection + wake。
|
||
6. 返回 accepted event ID。事件已存在时返回同一 ID 和 `deduplicated=true`。
|
||
|
||
Completion 永远由 Coordinator 生成。`completion_policy=all` 时每个 run terminal outcome 只存在于 `agent_runs`,不生成 per-run inbox completion;最后一个 terminal child 或 group deadline 的事务赢家生成唯一 group completion。group 有任一 failed/timed_out/interrupted/cancelled 时使用 `failure_delivery`,否则使用 `completion_delivery`。
|
||
|
||
显式 `agent_task.cancel` 可以把该 run 尚未消费的普通 signal 更新为 `superseded` 并减少 pending count;completion 事实不能 supersede。
|
||
|
||
## 11. Session 生命周期、停止与恢复
|
||
|
||
### 11.1 `/stop`
|
||
|
||
执行顺序:
|
||
|
||
1. 在 Session 锁内关闭 active Turn admission、取出 mailbox durable reservations、递增 worker generation、取出 root Turn cancellation token。
|
||
2. 锁外取消 root token,条件 release admitted/leased inbox events。
|
||
3. 调用 Coordinator cancel 当前 session 的 active background runs。
|
||
4. cancellation terminal event 使用 `requires_continuation=false` 并直接 consumed,防止 stop 后又自动启动“已取消”Turn。
|
||
5. stop 前已经 pending 的其他事件仍保持 pending;Session 下次可调度时处理。
|
||
|
||
现有 oneshot 可在过渡期由 token adapter 驱动,最终移除 `current_cancel: Option<oneshot::Sender<()>>`,统一为 `CancellationToken`。
|
||
|
||
### 11.2 archive/delete
|
||
|
||
Session 归档或软删除必须调用 `AgentCoordinator::cancel_session`。提交事务:
|
||
|
||
- 非 Scheduler 所有且未终态 run → cancelled。
|
||
- 释放所有 completion reservation。
|
||
- pending/leased/admitted events → `dead_letter(session_archived|session_deleted)`。
|
||
- 清理 mailbox reservation,不启动 continuation。
|
||
|
||
客户端仍可通过管理面看到 run/event 审计。当前没有可靠 unarchive 投递语义,因此归档事件不保留 pending。归档/删除属于用户生命周期操作,写 `fallback_suppressed_reason` 并禁止 system fallback,避免用户删除对话后又收到后台通知。
|
||
|
||
### 11.3 restart/reload recovery
|
||
|
||
activation recovery 分批执行,避免长事务:
|
||
|
||
1. 将旧 generation 的 queued/running/waiting_children 条件更新为 interrupted。
|
||
2. 为 background interrupted outcome 转换预留并插入 failure completion;foreground 只保存终态。
|
||
3. group counter 收敛并生成必要的 group completion。
|
||
4. expired leased/admitted events 恢复 pending,attempt +1,写 next retry。
|
||
5. 按每 session 重算 `pending_event_count` 和有效 reservation;差异修复并记录 structured warning。
|
||
6. 对有 pending due events 的 session 只发一次合并 wake。
|
||
|
||
旧代排空期间每个 run 持有 RuntimeAdmission activity guard。超出 reload grace 后 cancellation token 先触发;Supervisor 强制 abort 后留下的非终态记录由新代 recovery 标 interrupted。
|
||
|
||
## 12. ChannelContext 与结果投递
|
||
|
||
当前 `ChannelContext.private` 同时可能含稳定 thread/root ID 和一次性 message/reaction ID,核心无法安全猜测。类型改为:
|
||
|
||
```rust
|
||
pub struct ChannelContext {
|
||
pub reply_to: Option<String>,
|
||
pub private: HashMap<String, String>,
|
||
pub durable_private: HashMap<String, String>,
|
||
}
|
||
```
|
||
|
||
Channel adapter 负责分类:
|
||
|
||
- CLI/WebUI:通常为空。
|
||
- Feishu:thread/root/chat type 等是否可复用由 Feishu adapter 决定;message ID、reaction ID 和本次 reply target 留在 `private/reply_to`。
|
||
|
||
Session 在成功接纳外部用户输入时保存 `durable_private`。continuation 的 `TurnTarget` 使用 session 自身 channel/chat ID 和 durable context,`reply_to=None`。没有 binding 时照常提交历史和 WebSocket 投影,只跳过外部 Channel delivery。
|
||
|
||
## 13. WorkManager 原子性
|
||
|
||
当前 `assign_sub_agent` 和 `finish_sub_agent` 各自开事务,无法与 run 接纳/终态保持原子。实施方案:
|
||
|
||
- 把 plan item 条件 SQL 移入 Storage 的 agent run transaction。
|
||
- run 接纳时按 `(plan_id, item_id, status=pending)` 领取,并把 `execution_id` 写为 run execution ID。
|
||
- terminal commit 时按同 execution ID 更新 completed/blocked 和 plan version。
|
||
- WorkManager 新增 `refresh_after_external_commit(session_id, reason, item_ids)`,只负责重新查询、刷新 cache 和广播,不写数据库。
|
||
- 若 plan item 已被其他执行领取,整个 run 接纳事务回滚;不得创建一个与计划脱节的 run。
|
||
|
||
## 14. 协议、管理 API 与 WebUI
|
||
|
||
### 14.1 Rust protocol
|
||
|
||
增加 DTO 而不直接序列化 Storage row:
|
||
|
||
```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 }
|
||
```
|
||
|
||
`TurnSnapshot`、`CommittedTurnDelta`、`turn_updated` 和 `turn_committed` 增加 `turn_origin`。DTO 不暴露 task/result 全文以外的敏感 context、reasoning、Provider state、budget internals 或 delivery context。
|
||
|
||
`AgentProjectionHub` 复用 WorkManager 的 broadcast 模式。广播 lag 不补逐条事件,客户端收到 lag 或重连后调用 `GetAgentRuns` 校准。revision 来自 `agent_session_state`,客户端只接受更大的 revision;分页 cursor 使用 `(created_at,id)`,不能用 offset。
|
||
|
||
### 14.2 HTTP 管理 API
|
||
|
||
过渡期 `/api/tasks` 返回统一 projection:
|
||
|
||
- 新 `agent_runs` 映射为 `source=agent_run`。
|
||
- 旧 `background_tasks` 映射为 `source=legacy_background_task`。
|
||
- 按 created_at 合并排序,旧记录只读。
|
||
|
||
新增受设备鉴权保护的:
|
||
|
||
```text
|
||
GET /api/agent-runs?session_id=&cursor=&limit=
|
||
GET /api/agent-runs/:id
|
||
GET /api/agent-runs/:id/events
|
||
POST /api/agent-runs/:id/cancel
|
||
```
|
||
|
||
cancel endpoint 仍通过 Coordinator 做 root session/task-tree 授权,不直接执行 SQL。
|
||
|
||
### 14.3 WebUI
|
||
|
||
现有 `TasksPage.svelte` 的“后台任务”tab 演进为运行树:
|
||
|
||
- group 可折叠展示 children。
|
||
- status、Agent、Provider/model、duration、usage、工具次数。
|
||
- signal 数量、最近 severity、pending/dead-letter 标记。
|
||
- terminal result 有界预览,完整内容按需加载。
|
||
- cancel 只对允许取消的非终态 run 显示。
|
||
|
||
ChatPage 对 `turn_origin=agent_continuation` 显示轻量标签,不创建 user bubble;Signal 卡片来自 `AgentEventUpdated`,不能把 payload 插入普通聊天 history。所有新 Markdown/result 仍经过现有 sanitize 流程。
|
||
|
||
## 15. 可唤醒 sleep 的代码实现
|
||
|
||
`TurnWakeupHandle` 只由 root interactive Turn 创建并放入 ToolExecutionContext。它持有 `watch::Receiver<TurnWakeupState>`;state 使用独立于 durable inbox revision 的 session-local 单调 revision,因为用户 queue/steer 同样需要唤醒:
|
||
|
||
```rust
|
||
pub struct TurnWakeupState {
|
||
pub revision: u64,
|
||
pub pending_user_steer: usize,
|
||
pub pending_user_queue: usize,
|
||
pub pending_agent_steer: usize,
|
||
pub pending_agent_queue: usize,
|
||
pub latest_source: WakeupSource,
|
||
pub latest_safe_preview: Option<String>,
|
||
}
|
||
```
|
||
|
||
以下 admission 成功后递增 revision 并 `send_replace`:用户进入 TurnMailbox、用户进入 next-turn mpsc、Agent event durable admitted、Agent event 保持 pending 并成功 wake Session。先更新事实状态再发 wake,不能让 sleep 醒来却查询不到输入。
|
||
|
||
`SleepTool::execute_with_context`:
|
||
|
||
1. 解析并限制 duration 到 24 小时。
|
||
2. child context 的 `turn_wakeup=None`,只 select timer 与 cancellation。
|
||
3. root context 先读取 `receiver.borrow_and_update()`;若 pending 总数已大于 0,立即返回,不进入等待。
|
||
4. 否则同时等待 timer、`receiver.changed()` 和 cancellation。watch 保留最新 revision,因此 input 在检查与 select 之间到达也不会丢。
|
||
5. wake 后再次读取 state 并构造有界结果。steer 可以返回来源、run ID 和安全摘要;queue 只返回类型/数量,不返回正文。
|
||
6. cancellation 映射为统一 `AgentError::Cancelled`,不能让模型把它当普通 sleep 完成后继续执行。
|
||
|
||
Sleep 只提前结束工具 future,不消费 mailbox/inbox,也不自行改变 queue/steer。工具批次返回后,AgentLoop 在既定安全边界排空 steer;queue 留给下一个 Turn。
|
||
|
||
第一版 `InputInterruptPolicy` 只驱动 sleep 的 `WakeOnly`。不要顺带让 bash、browser、HTTP 或文件工具响应 input wake;以后开放 `CancelSafe` 必须逐工具证明取消、重试和副作用语义。
|
||
|
||
## 16. 分阶段实施清单
|
||
|
||
### 文件级落点
|
||
|
||
| 文件/模块 | 主要改动 |
|
||
|-----------|----------|
|
||
| `src/config/mod.rs` | orchestration config、默认值、边界校验 |
|
||
| `src/agent/definition.rs`(新) | Markdown/frontmatter 类型、严格 parser、definition hash |
|
||
| `src/agent/catalog.rs`(新) | immutable catalog、委托图与引用校验 |
|
||
| `src/agent/run.rs`(新) | run/group/outcome DTO、AgentRunner、ProviderFactory |
|
||
| `src/agent/coordinator.rs`(新) | 授权、预算、接纳、取消、deadline、terminal commit |
|
||
| `src/agent/inbox.rs`(新) | event/lease/projection/notifier contracts |
|
||
| `src/agent/agent_loop.rs` | cancellation、step gate、typed input safe boundaries |
|
||
| `src/agent/steering.rs` | 迁移为 typed TurnMailbox 与 reservation |
|
||
| `src/agent/sub_agent.rs` | legacy adapter,逐阶段缩减并最终删除旧 manager |
|
||
| `src/tools/traits.rs` | ToolExecutionContext、DelegationPolicy、interrupt policy |
|
||
| `src/tools/delegate.rs` | 仅 run/run_many 和兼容参数转换 |
|
||
| `src/tools/agent_task.rs`(新) | scoped get/list/cancel/get_result |
|
||
| `src/tools/emit_signal.rs`(新) | contract-bound signal |
|
||
| `src/tools/sleep.rs` | watch-aware wait 和 cancellation |
|
||
| `src/session/session.rs` | dual lane worker、internal task、atomic continuation、stop release |
|
||
| `src/session/agent_inbox.rs`(新) | claim/admit/release、fair scheduler、lease guard |
|
||
| `src/storage/mod.rs` | schema v6 migration 和领域 API re-export |
|
||
| `src/storage/agent_run.rs`(新) | run/group transaction SQL |
|
||
| `src/storage/agent_inbox.rs`(新) | capacity/event/lease/recovery SQL |
|
||
| `src/storage/message.rs`、`session.rs` | visibility/origin/durable delivery context |
|
||
| `src/gateway/mod.rs`、`reload.rs` | prepare/activation、recovery task、projection relay |
|
||
| `src/protocol.rs`、`src/session/commands.rs` | run query/event DTO 和 Turn origin |
|
||
| `src/gateway/http.rs` | 管理 API、legacy/new projection union |
|
||
| `webui/src/pages/TasksPage.svelte` | run tree、event/dead-letter/cancel UI |
|
||
| `webui/src/pages/ChatPage.svelte` | continuation origin 和 Signal 卡片 |
|
||
|
||
不要让 `session.rs` 继续吸收所有 inbox SQL 和状态机;先抽 Storage 领域 API 与 `session/agent_inbox.rs`,再接 worker,能显著降低竞态代码的审查难度。
|
||
|
||
### Phase 0:契约与 schema 冻结
|
||
|
||
- 固定本文 enums、状态转换、DDL 和默认值。
|
||
- 给现有 delegate、background notification、Session stop/reload 行为补基线测试。
|
||
- 为 config 示例准备 backward-compatible fixture。
|
||
|
||
完成条件:所有后续 PR 可以只引用固定 DTO/Storage contract,不再各自发明状态名。
|
||
|
||
### Phase 1:Catalog 与安全裁剪
|
||
|
||
- `definition.rs`、`catalog.rs`、严格 YAML parser。
|
||
- Config 扩展与候选代校验。
|
||
- Tool delegation metadata、scoped registry、skill wrapper。
|
||
- `foreground/background` schema 和 legacy parameter adapter。
|
||
- built-in general compatibility。
|
||
|
||
完成条件:具名 foreground Agent 能使用不同 Provider 和固定工具集;尚不开放 background 新路径。
|
||
|
||
### Phase 2A:AgentLoop 结构化取消
|
||
|
||
- CancellationToken 进入 root Turn 和 AgentLoop。
|
||
- Provider stream、工具批次、sleep 观察 token。
|
||
- `AgentError::Cancelled/TimedOut`。
|
||
- `/stop` oneshot 兼容桥接和迟到结果测试。
|
||
|
||
完成条件:root 行为不变;Provider、tool、preparation 各阶段 stop 均得到一致 Cancelled 终态。
|
||
|
||
### Phase 2B:Coordinator、run persistence 与 execution gate
|
||
|
||
- schema v6 的 run/group 部分。
|
||
- ProviderFactory、AgentRunner、Coordinator。
|
||
- run quota/provider gate/tool gate。
|
||
- foreground 单/批量、parent waiting_children、agent_task。
|
||
- WorkManager 原子绑定。
|
||
|
||
完成条件:foreground 全量持久化、截断结果可查询、并发上限为 1 时嵌套 foreground 不死锁。
|
||
|
||
### Phase 3:durable inbox 与 queue continuation
|
||
|
||
- `agent_session_state`、inbox Storage API、容量 reservation。
|
||
- late-bound notifier、Session watch wake、公平调度。
|
||
- hidden messages、turn origin、atomic continuation commit。
|
||
- recovery/dead-letter/fallback。
|
||
- WebSocket/HTTP run projection 和 WebUI 基础运行树。
|
||
|
||
完成条件:background completion 不直接通知 Channel;空闲/忙碌/队列满/重启/reload 下最终都能被主 Agent处理或进入可诊断 dead-letter。
|
||
|
||
### Phase 4:signal 与 steer
|
||
|
||
- EmitSignalTool、rate/dedupe/capacity。
|
||
- typed TurnMailbox 和两阶段 steer admission。
|
||
- `/stop` 立即 release、supersede 和 Signal UI。
|
||
|
||
完成条件:steer 在所有 admission 竞态中严格属于当前 Turn 或未来 queue 之一,不能重复或消失。
|
||
|
||
### Phase 5:wake-aware sleep 与清理
|
||
|
||
- TurnWakeupHandle watch revision。
|
||
- root sleep 响应 user/agent queue/steer,child sleep 仅 timer/cancel。
|
||
- 工具 interrupt policy。
|
||
- 移除 direct background notification 默认路径;观察一个版本后删除旧 adapter 和旧表写入。
|
||
|
||
完成条件:sleep 不丢 pre-listen wake,queue 内容不泄漏进当前 Turn,steer 在下一个安全边界可见。
|
||
|
||
## 17. 测试与故障注入
|
||
|
||
除主设计测试矩阵外,实施必须增加以下代码级场景。
|
||
|
||
### 17.1 Storage
|
||
|
||
- fresh database 创建 schema v6。
|
||
- v5 fixture 升级到 v6,原消息默认 visible/user。
|
||
- migration 任一步失败时 `user_version` 和全部表结构回滚。
|
||
- 两个并发 background 接纳只有一个能占最后 completion slot。
|
||
- completion 把 reservation 转 pending,不出现中间负数或超限。
|
||
- terminal transaction 在 plan update/event insert/usage 任一点失败时全回滚。
|
||
- execution ID 不匹配的迟到结果更新 0 行。
|
||
- startup reconciliation 能修复故意破坏的 state counters。
|
||
|
||
### 17.2 AgentLoop/Coordinator
|
||
|
||
- cancellation 发生在 permit wait、Provider connect、stream、工具批次、child wait。
|
||
- parent 等 child 时不持 step permit;global provider permit=1 仍能完成。
|
||
- foreground batch 并发执行但结果按 request index 返回。
|
||
- parent cancel 取消 foreground descendants,不取消已独立接纳的 background run。
|
||
- TaskSupervisor spawn reject 执行补偿事务且 delegate 不返回可用 ID。
|
||
- crash window 留下 queued old-generation run,activation 恢复为 interrupted。
|
||
|
||
### 17.3 Inbox/Session
|
||
|
||
- watch wake 在 worker 开始等待前发生也不会丢。
|
||
- user mpsc 满不影响 durable event。
|
||
- 连续用户输入达到 burst 上限后强制执行一个 event batch。
|
||
- continuation Provider 失败显式 release;进程模拟崩溃后 lease expiry 恢复。
|
||
- hidden trigger 与 assistant/event consume 原子;客户端 history 永远不返回 hidden。
|
||
- hidden user 不增加 title threshold/message_count。
|
||
- `/stop` 与 admit 的每个交错点都只产生 pending 或 consumed/admitted ownership之一。
|
||
- archived/deleted session 不启动 continuation且 reservation 归零。
|
||
|
||
### 17.4 Channel/protocol/UI
|
||
|
||
- durable context round trip 不包含 reply_to、message/reaction ID。
|
||
- 无 delivery binding 时 history commit 成功且不发送其他 chat。
|
||
- event broadcast lag/reconnect 后 full query 校准 revision。
|
||
- legacy/new tasks union 不重复并稳定分页。
|
||
- Signal payload、result Markdown 和错误信息通过 sanitize/redaction。
|
||
|
||
### 17.5 Sleep
|
||
|
||
- input 在 sleep 读取 revision 前、读取后但 select 前、select 后三个时点到达都能提前结束。
|
||
- user/agent 的 queue wake 只返回数量,不泄漏正文;对应输入仍由下一 Turn处理。
|
||
- steer wake 返回后由 AgentLoop 安全边界注入,SleepTool 自身不消费输入。
|
||
- child sleep 不因 root user input 或 sibling signal 唤醒,但 run cancel/timeout 会立即结束。
|
||
- duration 超过 24 小时拒绝;Gateway shutdown 不留下未托管等待任务。
|
||
|
||
建议把 SQLite failpoint、barrier 和暂停钩子限制在 `#[cfg(test)]`,用于精确制造 admission、stop、commit 与 reload 竞态,避免依赖随机 sleep。
|
||
|
||
## 18. 验证命令与合并门槛
|
||
|
||
每个 Rust 阶段至少执行:
|
||
|
||
```bash
|
||
cargo fmt --check
|
||
cargo test --lib
|
||
cargo test --test test_scheduler
|
||
cargo test --test test_request_format
|
||
cargo clippy --all-targets --all-features -- -D warnings
|
||
cargo build
|
||
```
|
||
|
||
涉及 WebUI 时另外执行:
|
||
|
||
```bash
|
||
cd webui
|
||
npm ci
|
||
npm run check
|
||
npm run build
|
||
cd ..
|
||
cargo build
|
||
```
|
||
|
||
文档或配置变更执行 `git diff --check`,并核对 README、配置模板、ARCHITECTURE、AGENTS 和 runtime knowledge 是否需要同步。API-backed ignored tests 只在配置了真实凭据时运行。
|
||
|
||
功能首次合并时按仓库规则提升产品中段版本;本文档本身不改变行为,不单独修改 `1.5.1`(当前功能合并版本 1.7.0)。
|
||
|
||
## 19. 主要风险与回滚
|
||
|
||
| 风险 | 预防 | 回滚方式 |
|
||
|------|------|----------|
|
||
| hidden history 过滤错误泄漏内部输入 | 查询 API 分层和协议测试 | 禁用 continuation worker,保留事件 pending |
|
||
| completion 容量计数漂移 | 单行条件更新 + 启动重算 | reconcile 修复后重启 Router |
|
||
| steer admission 竞态重复 | lease token + 不可排空 reservation | 配置强制所有 Agent delivery=queue |
|
||
| background 新路径影响现有用户 | legacy adapter 与新表分离 | 一个版本内启用 direct-notification rollback flag,二者互斥 |
|
||
| reload 双代同时恢复 | prepare/activation 硬边界 | 关闭新代 admission,旧代继续服务 |
|
||
| 工具权限误放大 | default RootOnly + Catalog fail closed | 从 definition 移除工具并 reload;已启动 run 固定旧快照 |
|
||
| continuation 重试重复外部副作用 | 默认只读 registry | dead-letter,要求用户显式处理 |
|
||
|
||
rollback flag 只能在 Phase 3 过渡期存在,并保证新 inbox continuation 与旧 direct notification 互斥。即使回滚投递方式,新 `agent_runs`/inbox 表仍保留审计事实,不能降 schema 或删除记录。
|
||
|
||
## 20. 最终实施判据
|
||
|
||
只有同时满足下列条件,才能把主设计状态从“提案”改为“已实现”:
|
||
|
||
- 具名 definition、不同 Provider、委托图和工具裁剪全部由运行时代码强制。
|
||
- foreground/background 都有 durable run,batch foreground 真正并发且父同步等待。
|
||
- run quota 与 provider/tool step gate 分离,嵌套委托无 permit 死锁。
|
||
- background completion 和 signal 先落 SQLite,丢 wake、queue 满、重启和 reload 均可恢复。
|
||
- queue continuation 使用 hidden trigger 原子提交;客户端历史没有伪用户消息。
|
||
- steer admission 与 `/stop` 竞态经过故障注入证明无丢失、无双重归属。
|
||
- sleep 的 wake 只改变等待,不破坏 queue/steer 内容边界。
|
||
- session archive/delete、dead-letter、fallback 和 management UI 均可诊断。
|
||
- 全部离线测试、Clippy、build、WebUI check/build 通过,并同步公开文档和产品版本。
|
||
|
||
在达到这些判据前,可以按 Phase 逐步合并内部基础,但不能提前对外宣称已经具备可靠的 background Agent orchestration。
|