# Agent Runtime 方案设计 > 设计日期:2026-06-09 > 状态:待实施 > 关联文档: > - `docs/note-agent-runtime-design.md` — 设计决策记录(接口签名、文件清单、决策依据) > - `docs/note-agent-harness-references.md` — 参考项目调研(OpenClaw / Hermes / OpenHuman / OpenHarness) > - `docs/6-memory-system.md` — Phase 3 方案 > - `docs/5-tool-system.md` — Phase 2 方案 > - `docs/roadmap.md` — 项目总 Roadmap --- ## 1. 背景与目标 ### 1.1 背景 AG Core 已完成 Phase 0(LLM 调用周期)、Phase 1(提示词工程)、Phase 2(工具系统)、Phase 3(记忆系统)共 4 个 phase 的交付。`LlmCycle::submit_with_tools()` 已在 Phase 2 末实现"LLM 决策 → 工具执行 → 回传结果"的单次循环;`ConversationMemory` / `KnowledgeStore` / `MemoryRetriever` 在 Phase 3 提供了完整的记忆抽象。 当前缺一个**整合层**:把 Phase 0-3 的能力"装配"起来,对上层应用暴露"智能体"的概念。 ### 1.2 目标 Phase 4 整体目标是提供一个**薄胶水层 + 一组 trait 抽象**,让上层应用可以基于 AG Core 构建多轮对话、任务规划等智能体行为。为控制 scope、降低交付风险,拆分为三个子阶段实施: | 子阶段 | 定位 | 交付物 | |--------|------|--------| | **Phase 4a(核心胶水层)** | 最小可用 Agent Runtime | `Agent` + `AgentSession` + `submit_turn` + `RuntimeBundle` / `AgentBuilder` / `AgentError` + `Plan`/`Step` 纯数据 + hooks 扩展 | | **Phase 4b(任务执行)** | 自主任务规划与执行 | `TaskAgent` + `PlanParser` trait + `JsonPlanParser` + `OnPlanStepComplete` hook | | **Phase 4c(会话级记忆)** | 跨 context 信息桥接 | `SessionMemory`(基于 `MemoryStore`)+ AgentSession 接入 + builder 支持 | **每个子阶段独立交付**,Phase 4a 完成后上层即可接入;Phase 4b/4c 无相互依赖,可并行或按需延后。 Phase 4a 具体包括: - **`Agent` trait** — 智能体的"角色"抽象(不绑定 session) - **`AgentSession` struct** — 智能体的"会话"实例(绑定 session_id + 状态) - **`RuntimeBundle`** — 显式依赖注入容器,集中管理 provider/registry/hook 等依赖 - **`AgentBuilder`** — 链式构造入口 - **`AgentError`** — 统一错误类型,聚合 LlmError / ToolError / MemoryError - **`Plan` / `Step` / `StepStatus`** — 任务规划纯数据结构(不做解析逻辑) Phase 4b 追加: - **`TaskAgent` trait** — 任务型智能体的"规划/执行"抽象 - **`PlanParser` trait + `JsonPlanParser`** — Plan 解析接口与参考实现 Phase 4c 追加: - **`SessionMemory`** — 会话级记忆,用于 context 间的信息桥接(基于 `MemoryStore` 后端) ### 1.3 设计原则 Phase 4 严格遵循以下原则,所有范围决策都基于这些原则推导: | 原则 | 含义 | 推导 | |------|------|------| | **最小范围** | AG Core 是 lib crate,不是产品;不实现业务循环 | 只暴露 trait + 最小 reference impl | | **薄胶水层** | 不在 L1 重写已经做好的能力 | 复用 `LlmCycle::submit_with_tools` 等已有 API | | **依赖注入** | 所有运行时依赖显式打包传递 | 采用 OpenHarness `RuntimeBundle` 模式 | | **实体/会话分离** | 同一角色可被多 session 复用 | `Agent` + `AgentSession` 两层模型 | | **记忆弱引用** | 记忆是"被动能力",不内嵌循环 | `memory_store: Option>` 弱引用 | | **业务可注入** | Plan 拆解是业务能力,不在 core 库实现 | 暴露 `PlanParser` trait,上层注入 | | **会话级记忆** | session 内共享、context 间桥接,不是持久层也不是对话历史 | `SessionMemory` 基于 `MemoryStore`,按 session_id 命名空间隔离 | | **借鉴不照搬** | 4 个参考项目均非 Rust 实现 | 只取架构模式,不抄实现细节 | ### 1.4 与已完成的 Phase 关系 ``` Phase 0 (L0/L1) ── LlmProvider / LlmCycle / Hook / Stream / Compact Phase 1 (L2) ── PromptTemplate / PromptComposer Phase 2 (L1) ── ToolRegistry / BaseTool / PermissionChecker / McpClient Phase 3 (L2) ── MemoryStore / ConversationMemory / KnowledgeStore / MemoryRetriever ↑ │ 复用 │ Phase 4a (L1→L2) ── Agent trait + AgentSession + submit_turn + RuntimeBundle + Plan/Step 纯数据(胶水层) Phase 4b (L2) ── TaskAgent + PlanParser + JsonPlanParser(任务执行) Phase 4c (L2) ── SessionMemory(会话级记忆) ↓ 应用层 (L4) ── 上层 crate / 二进制 / Gateway(不在 Phase 4 范围) ``` 详细架构对照见 `docs/note-agent-harness-references.md` §3-5。 ## 2. 需求分析 ### 2.1 功能需求 | ID | 需求 | 优先级 | 归属 | 说明 | |----|------|--------|------|------| | F1 | `Agent` trait 抽象 | P0 | 4a | 角色定义:name / system_prompt / 工具集 | | F2 | `AgentSession` 会话实例 | P0 | 4a | 绑定 session_id、bundle、turn_index、cost_so_far | | F3 | `submit_turn()` 最小 reference impl | P0 | 4a | 组装 LlmCycle → submit → 累计 cost;~30 行 | | F6 | `Plan` / `Step` / `StepStatus` 数据结构 | P0 | 4a | 含 Pending / Running / Completed / Failed / Skipped 状态机 | | F8 | `RuntimeBundle` 依赖注入容器 | P0 | 4a | 聚合 provider/registry/hook/config(不含 session_memory_backend) | | F9 | `AgentBuilder` 链式构造 | P0 | 4a | 构建 `RuntimeBundle`,retriever 存在时自动注册为 tool | | F10 | `AgentError` 统一错误类型 | P0 | 4a | 聚合 LlmError / ToolError / MemoryError,含 `is_recoverable()` | | F11a | Hook 事件扩展:OnTurnStart / OnTurnEnd + turn_index 字段 | P0 | 4a | 在 `llm/hooks.rs` 中追加 2 个事件 + 1 个字段 | | F12a | 烟雾测试 3-4 个(Phase 4a) | P0 | 4a | trait 可装配 / RuntimeBundle 可构造 / submit_turn 跑通 mock / Plan 数据结构 | | F13 | `lib.rs` 导出 `pub mod agent;` | P0 | 4a | 一行 | | F14 | 方案文档(本文件)+ 决策记录 | P0 | — | ✅ 已完成 | | F4 | `TaskAgent::run(goal)` 自主式入口 | P0 | 4b | 内部用 LLM 拆 Plan,再调用 `execute_plan` | | F5 | `TaskAgent::execute_plan(plan)` 外部驱动式入口 | P0 | 4b | 用户预定义 Plan,逐步执行 | | F7 | `PlanParser` trait + `JsonPlanParser` 参考实现 | P0 | 4b | 注入式,上层可替换 | | F11b | Hook 事件扩展:OnPlanStepComplete + plan_step_index 字段 | P0 | 4b | 在 `llm/hooks.rs` 中追加 1 个事件 + 1 个字段 | | F12b | 烟雾测试 2-3 个(Phase 4b) | P0 | 4b | TaskAgent + PlanParser 跑通 mock | | F15a | Roadmap 状态翻转(Phase 4a) | P0 | 4a | 实施完成后做 | | F15b | Roadmap 状态翻转(Phase 4b) | P0 | 4b | 实施完成后做 | | F16 | SessionMemory 会话级记忆 | P0 | 4c | 基于 `MemoryStore`,context 间信息桥接 | | F17 | RuntimeBundle / Builder 扩展 session_memory_backend | P0 | 4c | 追加字段 + setter 方法 | | F18 | AgentSession 接入 SessionMemory | P0 | 4c | 替换内联 HashMap,接入完整 SessionMemory | | F12c | 烟雾测试 2-3 个(Phase 4c) | P0 | 4c | SessionMemory set/get/snapshot | | F15c | Roadmap 状态翻转(Phase 4c) | P0 | 4c | 实施完成后做 | ### 2.2 非功能需求 | ID | 需求 | 说明 | |----|------|------| | NF1 | 不引入新外部依赖 | 仅使用 Phase 0-3 已有的 `async-trait` / `serde` / `thiserror` / `tokio` 等 | | NF2 | 错误体系完善 | `AgentError` 聚合下层错误,含 `is_recoverable()` 分类 | | NF3 | 线程安全 | 所有公开类型满足 `Send + Sync` | | NF4 | 异步优先 | 涉及 IO 的 API 全部 `async` | | NF5 | 模块化 | 各组件独立可替换,遵循"trait 抽象 + 轻量默认实现"惯例 | | NF6 | 文档注释 | 所有公开 API 必须有 `///` 文档注释 | | NF7 | builder 模式 | 复杂配置走 builder 链式构造 | | NF8 | 显式依赖 | 不引入模块级全局状态,所有依赖通过参数或 bundle 注入 | | NF9 | 不破坏现有 API | Phase 0-3 的公开 API 一字不改;`hooks.rs` 扩展为"追加变体 + 追加字段"(兼容) | | NF10 | 最小测试覆盖 | 核心 trait 至少 1 个烟雾测试;`submit_turn` 至少 1 个 mock 测试;不强求集成测试 | ## 3. 方案设计 ### 3.1 总体架构 ``` ┌──────────────────────────────────────────────────────────────────────┐ │ 应用层(不在 Phase 4 范围) │ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │ │ CLI Agent │ │ Feishu Bot │ │ Web Service│ │ TUI App │ │ │ └─────┬──────┘ └─────┬──────┘ └─────┬──────┘ └─────┬──────┘ │ └─────────┼────────────────┼────────────────┼────────────────┼───────────┘ │ │ │ │ └────────────────┴────────────────┴────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────────────────────┐ │ Agent Runtime(Phase 4) │ │ │ │ ┌────────────────┐ ┌──────────────────┐ │ │ │ Agent trait │ 1 ──── * │ AgentSession │ │ │ │ (角色) │ │ (会话实例) │ │ │ └────────────────┘ └──────┬───────────┘ │ │ │ Arc<...> │ │ ▼ │ │ ┌──────────────────┐ │ │ │ RuntimeBundle │ │ │ │ - provider │ │ │ │ - tool_registry │ │ │ │ - hook_executor │ │ │ │ - memory_store? │ ◄─ 弱引用 │ │ │ - retriever? │ ◄─ 弱引用 │ │ │ - config │ │ │ └──────┬───────────┘ │ │ │ new() 时若 retriever 存在 │ │ ▼ │ │ ┌──────────────────┐ │ │ │ "retrieve" tool │ ◄─ 自动注册 │ │ └──────────────────┘ │ │ │ │ ┌────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │ │ │ TaskAgent trait│ │ Plan/Step/Status │ │ PlanParser trait │ │ │ │ run() │ │ 状态机 │ │ JsonPlanParser │ │ │ │ execute_plan()│ │ │ │ (参考实现 ~20行) │ │ │ └────────────────┘ └──────────────────┘ └──────────────────┘ │ │ │ │ ┌────────────────┐ ┌──────────────────┐ │ │ │ AgentError │ │ AgentBuilder │ │ │ │ (聚合) │ │ (链式构造) │ │ │ └────────────────┘ └──────────────────┘ │ └──────────────────────────────────────────────────────────────────────┘ │ ▼ 复用 ┌──────────────────────────────────────────────────────────────────────┐ │ LLM / Tool / Prompt / Memory(Phase 0-3) │ │ LlmCycle / ProviderRegistry / ToolRegistry / PermissionChecker / │ │ HookExecutor / StreamEvents / CompactConfig / │ │ PromptTemplate / PromptComposer / │ │ MemoryStore / ConversationMemory / KnowledgeStore / MemoryRetriever│ └──────────────────────────────────────────────────────────────────────┘ ``` ### 3.2 接口设计 详细接口签名见 `docs/note-agent-runtime-design.md` §4,本节说明设计意图。 #### 3.2.1 `Agent` trait ```rust pub trait Agent: Send + Sync { fn name(&self) -> &str; fn system_prompt(&self) -> Option<&str>; /// 列出该 Agent 想要暴露给 LLM 的工具定义。 /// 默认实现:从 RuntimeBundle.tool_registry 取全部(最常用)。 /// 子 trait 可覆盖做白名单/过滤。 fn tool_definitions(&self, bundle: &RuntimeBundle) -> Vec; } ``` **设计意图**: - `name` / `system_prompt` 是 LLM 调用必需的元数据 - `tool_definitions` 默认从 bundle 全量取,**Agent 可以在不修改 bundle 的情况下做工具白名单**——这与 Hermes 的"Skill 暴露"机制对齐 - 不在 trait 里强制 `submit_turn`——`submit_turn` 是 `AgentSession` 的方法,不应绑死在角色定义上 #### 3.2.2 `RuntimeBundle` ```rust pub struct RuntimeBundle { pub provider: Arc, pub tool_registry: Arc, pub hook_executor: Arc, pub memory_store: Option>, // 弱引用 pub retriever: Option>, // 弱引用 pub session_memory_backend: Option>, // SessionMemory 后端(选填) pub config: AgentConfig, } ``` **设计意图**: - 所有运行时依赖**显式打包**(OpenHarness 风格) - `memory_store` / `retriever` 均为 `Option`——上层应用**不传也能跑**(无记忆模式) - 当 `retriever` 存在时,`RuntimeBundle::new()` 内部自动注册一个名为 `"retrieve"` 的 tool(具体实现:在 `ToolRegistry` 里加一个 `RetrieveTool` 包装),让 LLM 在对话中**主动**调用检索能力 - `session_memory_backend` 是 `SessionMemory` 的持久后端。传入时 `SessionMemory` 使用该后端(支持跨进程共享);不传时 `AgentSession` 内部自动创建 `InMemoryStore` 作为进程级隔离的后端 - `config` 集中管理所有可调参数(max_turns、max_tool_turns、session_ttl、compact_config) #### 3.2.3 `AgentSession` 与最小 reference impl ```rust pub struct AgentSession { pub session_id: String, pub agent: Arc, bundle: Arc, turn_index: u32, cost_so_far: CostTracker, session_memory: SessionMemory, } impl AgentSession { /// 最小 reference impl(约 30 行): /// 1. 触发 OnTurnStart hook /// 2. 组装 LlmCycle(注入 system_prompt + messages 历史 + tool definitions) /// 3. submit_with_tools() 跑单轮对话 /// 4. 累计 cost /// 5. 触发 OnTurnEnd hook /// 6. turn_index += 1 /// 7. 返回 ChatResponse /// 不做 memory 回写(由上层独立 task 处理) pub async fn submit_turn( &mut self, user_input: impl Into, ) -> Result; } ``` **设计意图**: - `agent: Arc` 而非 `agent_name: String`——`submit_turn` 从 agent 获取 `system_prompt()` 和 `tool_definitions()`,同时为 v0.2+ 的"热切换 agent"预留:替换 `self.agent` 即可切换角色 - `session_memory` 是进程内共享的会话级记忆,context 间通过它桥接信息(详见 §3.2.8) - "最小 reference impl" 只演示**最常见**的对话场景 - 业务循环(多轮策略、错误重试、记忆回写时机)由上层应用或具体的 `TaskAgent` 实现决定 - `submit_turn` 不持有 `ConversationMemory`——上层应用可独立 new 一个 `ConversationMemory`,在合适的时机(如 OnTurnEnd hook)调 `add_message` #### 3.2.4 `TaskAgent` + `Plan` / `Step` ```rust pub struct Plan { pub id: String, pub goal: String, pub steps: Vec, } pub struct Step { pub index: usize, pub description: String, pub status: StepStatus, } pub enum StepStatus { Pending, Running, Completed(ChatResponse), Failed(AgentError), Skipped, } ``` **设计意图**: - `StepStatus` 用 enum 而非简单 bool,便于上层 UI 展示和统计 - 状态机转换:`Pending → Running → (Completed | Failed | Skipped)`,单向不可回退(重试由上层新建 Plan) - `Plan` / `Step` 故意保持简单——不引入 `dependencies` / `parallel_group` 等高级字段(v0.3+ 再考虑) #### 3.2.5 `PlanParser` trait + `JsonPlanParser` 参考实现 ```rust #[async_trait] pub trait PlanParser: Send + Sync { async fn parse(&self, raw: &str, goal: &str) -> Result; } pub struct JsonPlanParser; #[async_trait] impl PlanParser for JsonPlanParser { /// 期望 LLM 输出形如: /// {"steps": [{"description": "..."}, ...]} /// 的 JSON 文本。 /// 解析失败返回 AgentError::PlanParse。 async fn parse(&self, raw: &str, goal: &str) -> Result { /* ... */ } } ``` **设计意图**: - **注入式**:上层应用可以注入自己的 `PlanParser`(如基于 XML / YAML / 自定义 DSL) - `JsonPlanParser` 是**参考实现**,不是默认实现——上层必须显式选择 - `JsonPlanParser` 大约 20 行:`serde_json::from_str` 解析 + 字段映射 #### 3.2.6 `AgentError` ```rust pub enum AgentError { Llm(LlmError), Tool(ToolError), Memory(MemoryError), PlanParse(String), HookBlocked(String), LimitExceeded(String), Config(String), Other(String), } ``` **设计意图**: - 聚合而非包装下层错误(避免 `Box` 丢失类型) - `PlanParse` / `HookBlocked` / `LimitExceeded` / `Config` 是 Agent 层特有的错误类型 - `is_recoverable()` 根据变体类型判定(如 `Memory(_)` 可恢复、`PlanParse(_)` 不可恢复) #### 3.2.7 `AgentConfig` + `AgentBuilder` ```rust pub struct AgentConfig { pub max_turns: u32, pub max_tool_turns: u32, pub session_ttl: Option, pub compact_config: Option, } pub struct AgentBuilder { /* ... */ } impl AgentBuilder { pub fn new() -> Self; pub fn provider(self, p: Arc) -> Self; pub fn tool_registry(self, r: Arc) -> Self; pub fn hook_executor(self, h: Arc) -> Self; pub fn memory_store(self, m: Arc) -> Self; // 选填 pub fn retriever(self, r: Arc) -> Self; // 选填 pub fn session_memory_backend(self, s: Arc) -> Self; // 选填 pub fn config(self, c: AgentConfig) -> Self; pub fn build(self) -> Result; } ``` **设计意图**: - `AgentBuilder` 是**唯一**的 `RuntimeBundle` 构造入口 - 必填字段在 `build()` 时校验(`provider` / `tool_registry` / `hook_executor` 不可缺) - `memory_store` / `retriever` / `session_memory_backend` 选填 - `session_memory_backend` 不传时,`AgentSession` 内部用 `InMemoryStore` 兜底(进程级隔离) #### 3.2.8 `SessionMemory` — 会话级记忆 ```rust pub struct SessionMemory { store: Arc, namespace: String, } impl SessionMemory { /// 创建新的 session 级记忆实例。 /// store:后端存储(可跨进程共享的 MemoryStore 实现)。 /// namespace:按 session_id 隔离,防止跨 session 泄漏。 pub fn new(store: Arc, namespace: &str) -> Self; /// 写入一条 key-value 条目。 pub async fn set(&self, key: &str, value: &str) -> Result<(), AgentError>; /// 读取指定 key 的值。 pub async fn get(&self, key: &str) -> Result, AgentError>; /// 返回所有条目的格式化快照,适合注入 system prompt。 /// 格式: /// /// key1: value1 /// key2: value2 /// pub async fn snapshot(&self) -> Result; /// 删除指定 key。 pub async fn remove(&self, key: &str) -> Result<(), AgentError>; /// 清空当前 namespace 下所有条目。 pub async fn clear(&self) -> Result<(), AgentError>; } ``` **设计意图**: - `SessionMemory` 是**会话级**记忆,不是持久层(`MemoryStore`)也不是对话历史(`ConversationMemory`)——它的定位是 session 内各 context 之间的信息桥接 - **复用 Phase 3 `MemoryStore` trait**:不引入新的存储后端机制。单进程场景用 `InMemoryStore`(零序列化开销),跨进程场景换 Redis / SQLite 等实现即可 - **按 `namespace` 隔离**:每个 session 一个独立命名空间(`"_session_{session_id}"`),避免跨 session 意外泄漏 - **`snapshot()` 格式化为标记文本**:专为注入 system prompt 设计,LLM 可以自然理解 `` 标签中的内容 - **所有方法为 `async`**:因为后端可能是跨进程的(Redis / DB),虽然 `InMemoryStore` 本身是同步操作 - **不引入自己的错误类型**:错误通过 `AgentError::Memory` 传播(复用已有变体) **三层记忆体系关系**: ``` 持久层(Phase 3) MemoryStore / KnowledgeStore ── 跨 session 持久,长期知识 会话层(新增) SessionMemory ── 单 session 内共享,context 桥接 对话层(Phase 3) ConversationMemory ── 单 context 内消息历史 ``` **典型使用模式**(v0.2+ context 切换场景): ``` context_a (build agent) → 在对话中决定某个关键结论值得记下来 → 调用 session_memory.set("design_decision", "用 PostgreSQL") → 继续对话 创建 context_b (plan agent) → system_prompt 末尾追加 session_memory.snapshot() → LLM 看到 "\ndesign_decision: 用 PostgreSQL\n" → 无需看 context_a 的 50 轮完整历史,但知道关键上下文 ``` ### 3.3 状态机 #### 3.3.1 `StepStatus` 状态转换图 ``` ┌─────────────┐ │ Pending │ ◄── 初始状态 └──────┬──────┘ │ execute_plan() 进入 ▼ ┌─────────────┐ │ Running │ ◄── 触发 OnPlanStepComplete(status=Running) └──────┬──────┘ │ ┌────────────────┼────────────────┐ │ │ │ ▼ ▼ ▼ ┌─────────┐ ┌──────────┐ ┌──────────┐ │Completed│ │ Failed │ │ Skipped │ └─────────┘ └──────────┘ └──────────┘ 触发 OnPlanStepComplete(status=Completed) 触发 OnPlanStepComplete(status=Failed) 触发 OnPlanStepComplete(status=Skipped) ``` **设计约束**: - 状态转换**单向**(Pending → Running → 终态),不回退 - 终态(Completed / Failed / Skipped)触发 `OnPlanStepComplete` hook - 重试由上层应用新建 `Plan` 实现(不在 `TaskAgent` 内做自动重试) #### 3.3.2 Session 状态 `AgentSession` 的状态机比 `Step` 简单: ``` 创建 (new) ──► turn_index=0 ──► submit_turn() ──► turn_index+=1 ──► ... ──► 销毁 ``` `turn_index` 累加,`cost_so_far` 累加,无显式状态枚举(避免过度设计)。 ### 3.4 Hook 扩展设计 在 `src/llm/hooks.rs` 中追加 3 个事件 + 2 个上下文字段: ```rust pub enum HookEvent { // ... 现有 4 个:PreRequest / PostRequest / OnRetry / OnError ... // 新增 3 个(Phase 4): OnTurnStart, OnTurnEnd, OnPlanStepComplete, } pub struct HookContext { // ... 现有字段 ... // 新增 2 个(Phase 4): pub turn_index: Option, // OnTurnStart / OnTurnEnd 用 pub plan_step_index: Option, // OnPlanStepComplete 用 } ``` **设计意图**: - **不破坏现有 hook 兼容性**:3 个新事件是 enum 追加,2 个新字段是 `Option` 默认 `None` - 上层应用可通过监听 `OnTurnEnd` 实现"独立 task 回写 ConversationMemory"——呼应"记忆在独立 task 处理"原则 - `OnPlanStepComplete` 提供"步骤级别"的可观测性,与 Hermes 的"任务进度回调"对齐 ### 3.5 错误体系 `AgentError` 与下层错误的关系: ``` ┌──────────────────┐ │ AgentError │ ├──────────────────┤ │ Llm(LlmError) │──► 透传 Phase 0 错误,含 is_recoverable() │ Tool(ToolError) │──► 透传 Phase 2 错误,含 is_recoverable() │ Memory(MemoryError)│─► 透传 Phase 3 错误 │ PlanParse(String) │─► Agent 层特有 │ HookBlocked(String)│─► Agent 层特有 │ LimitExceeded(String)│► Agent 层特有 │ Config(String) │──► Agent 层特有 │ Other(String) │──► 兜底 └──────────────────┘ │ ▼ is_recoverable(): 聚合判定 - Llm/Memory 可恢复(重试) - PlanParse / Config 不可恢复(需人工介入) - Tool / HookBlocked / LimitExceeded 按内层错误判定 ``` **自动 From 转换**:通过 `#[from]` 宏实现 `From` / `From` / `From`,让 `submit_turn` 内部可以用 `?` 运算符直接传播。 ### 3.6 与 Phase 0-3 模块的集成 | Phase 4 组件 | 调用的下层 API | 调用位置 | |-------------|--------------|---------| | `AgentSession::submit_turn` | `LlmCycle::new` + `with_system_prompt` + `with_hook_executor` + `with_compact_config` + `with_messages` + `submit_with_tools` | session.rs | | `AgentSession::submit_turn` | `CostTracker::add`(累计 cost) | session.rs | | `RuntimeBundle::new` | `ToolRegistry::register`(注册 retrieve tool) | runtime.rs | | `TaskAgent::execute_plan` | `AgentSession::submit_turn`(每步调一次) | task.rs | | `JsonPlanParser::parse` | `serde_json::from_str` | task.rs | | `AgentError::from` | `LlmError` / `ToolError` / `MemoryError` | error.rs | | `HookContext` 扩展 | `HookEvent::OnTurnStart/End/OnPlanStepComplete` | llm/hooks.rs | | `SessionMemory::set/get/snapshot` | `MemoryStore::save/load/search` | session_memory.rs | **不调用的下层 API**(明确边界): - ❌ `ConversationMemory`(由上层独立 task 管理) - ❌ `KnowledgeStore`(由上层独立 task 管理) - ❌ `McpClient`(已由 `ToolRegistry` 包装) - ❌ `StreamEvents::submit_stream`(v1 暂不暴露流式 `submit_turn`,v0.2 再说) - ❌ 多 context 切换管理(v0.2+ 实现,Phase 4 只预留 `SessionMemory` 桥接通道) - ❌ `"session_memory_set"` 等 session memory tool 自动注册(v0.2+ 可选) ## 4. 实施计划 Phase 4 拆分为三个独立子阶段:**Phase 4a(核心胶水层)** → **Phase 4b(任务执行)** → **Phase 4c(会话级记忆)**。每个子阶段独立交付、独立验证,4b 与 4c 无相互依赖。 ### 4.1 文件清单 #### 新增文件(9 个) ``` src/agent.rs # [4a] 模块根 + pub use 重导出 src/agent/agent.rs # [4a] Agent trait src/agent/runtime.rs # [4a] RuntimeBundle + AgentConfig(不含 session_memory_backend) src/agent/session.rs # [4a] AgentSession(submit_turn + 内联 session_data HashMap) src/agent/task.rs # [4a] Plan / Step / StepStatus 纯数据 / [4b] TaskAgent + PlanParser + JsonPlanParser src/agent/builder.rs # [4a] AgentBuilder(不含 session_memory_backend) src/agent/error.rs # [4a] AgentError(不含 PlanParse 变体)/ [4b] 补充 PlanParse 变体 src/agent/session_memory.rs # [4c] SessionMemory(基于 MemoryStore) ``` #### 修改文件(2 个) ``` src/lib.rs # [4a] + pub mod agent; src/llm/hooks.rs # [4a] + 2 事件(OnTurnStart/OnTurnEnd)+ 1 字段(turn_index) # [4b] + 1 事件(OnPlanStepComplete)+ 1 字段(plan_step_index) ``` #### 关联文档(已完成) ``` docs/note-agent-harness-references.md # ✅ 已存在 docs/note-agent-runtime-design.md # ✅ 已存在(与本文件配套) docs/7-agent-runtime.md # ✅ 本文件 ``` --- ### 4.2 Phase 4a — 核心胶水层(最小 Agent Runtime) **范围**:Agent trait + AgentSession + submit_turn(内联 HashMap session_data)+ RuntimeBundle/AgentBuilder + AgentError + Plan/Step 纯数据 + hooks 扩展(OnTurnStart/OnTurnEnd) **任务拆解**: | 顺序 | 任务 | 涉及文件 | 验证 | |------|------|---------|------| | a1 | 修改 `llm/hooks.rs` 追加 OnTurnStart / OnTurnEnd + turn_index 字段 | `src/llm/hooks.rs` | `cargo build` 通过;Phase 0 测试不挂 | | a2 | 新建 `agent/error.rs` 定义 `AgentError`(不含 PlanParse 变体) | `src/agent/error.rs` | `cargo build` 通过 | | a3 | 新建 `agent/agent.rs` 定义 `Agent` trait | `src/agent/agent.rs` | `cargo build` 通过 | | a4 | 新建 `agent/runtime.rs` 定义 `RuntimeBundle` + `AgentConfig`(不含 session_memory_backend) | `src/agent/runtime.rs` | `cargo build` 通过 | | a5 | 新建 `agent/builder.rs` 定义 `AgentBuilder`(不含 session_memory_backend 方法) | `src/agent/builder.rs` | `cargo build` 通过 | | a6 | 新建 `agent/session.rs` 定义 `AgentSession` + `submit_turn`(内联 `HashMap` 做 session_data,不引 MemoryStore) | `src/agent/session.rs` | `cargo build` 通过 | | a7 | 新建 `agent/task.rs` 定义 `Plan` / `Step` / `StepStatus` 纯数据结构(不含 TaskAgent trait,不含 PlanParser) | `src/agent/task.rs` | `cargo build` 通过 | | a8 | 新建 `src/agent.rs` 模块根 + `pub use` 重导出 + 修改 `lib.rs` | `src/agent.rs` + `src/lib.rs` | `cargo build` 通过 | | a9 | 编写烟雾测试 3-4 个(Agent trait 可装配 / RuntimeBundle 可构造 / submit_turn 跑通 mock / Plan 数据结构) | `src/agent/*.rs` 内联 | `cargo test` 通过 | | a10 | 完整 `cargo test` 跑全量回归 + roadmap.md 状态更新 | — | 所有已有测试不挂 | **依赖关系**: ``` hooks扩展 (a1) ──┐ ├──► agent/error.rs (a2) ──► agent/agent.rs (a3) │ │ │ ▼ │ agent/runtime.rs (a4) │ │ │ ▼ │ agent/builder.rs (a5) │ │ │ ▼ │ agent/session.rs (a6) │ │ │ ▼ │ agent/task.rs (a7) [纯数据] │ │ └──────────────────► src/agent.rs + lib.rs (a8) │ ▼ cargo test (a9 → a10) ``` --- ### 4.3 Phase 4b — 任务执行 **范围**:TaskAgent trait + PlanParser trait + JsonPlanParser 参考实现 + OnPlanStepComplete hook + AgentError PlanParse 变体 **前置条件**:Phase 4a 已完成并交付。 **任务拆解**: | 顺序 | 任务 | 涉及文件 | 验证 | |------|------|---------|------| | b1 | 修改 `llm/hooks.rs` 追加 OnPlanStepComplete + plan_step_index 字段 | `src/llm/hooks.rs` | `cargo build` 通过;Phase 0 + 4a 测试不挂 | | b2 | `agent/error.rs` 追加 PlanParse 变体 | `src/agent/error.rs` | `cargo build` 通过 | | b3 | `agent/task.rs` 追加 `TaskAgent` trait + `PlanParser` trait + `JsonPlanParser` 参考实现 | `src/agent/task.rs` | `cargo build` 通过 | | b4 | 更新 `agent.rs` 模块根重导出(如有新增公开类型) | `src/agent.rs` | `cargo build` 通过 | | b5 | 编写烟雾测试 2-3 个(TaskAgent mock 执行 / JsonPlanParser 解析 / PlanParse 错误) | `src/agent/task.rs` 内联 | `cargo test` 通过 | | b6 | 完整 `cargo test` 跑全量回归 + roadmap.md 状态更新 | — | 所有已有测试不挂 | **依赖关系**: ``` hooks扩展 (b1) ──┐ ├──► error.rs 追加 (b2) ──► task.rs 追加 (b3) │ ▼ agent.rs 更新 (b4) │ ▼ cargo test (b5 → b6) ``` --- ### 4.4 Phase 4c — 会话级记忆 **范围**:SessionMemory struct(基于 MemoryStore)+ RuntimeBundle/Build 扩展 session_memory_backend + AgentSession 接入(替换内联 HashMap) **前置条件**:Phase 4a 已完成并交付(可在 4b 之前、之后或并行实施)。 **任务拆解**: | 顺序 | 任务 | 涉及文件 | 验证 | |------|------|---------|------| | c1 | 新建 `agent/session_memory.rs` 定义 `SessionMemory`(基于 `MemoryStore`,namespace 隔离) | `src/agent/session_memory.rs` | `cargo build` 通过 | | c2 | `agent/runtime.rs` 追加 `session_memory_backend` 字段到 `RuntimeBundle` | `src/agent/runtime.rs` | `cargo build` 通过 | | c3 | `agent/builder.rs` 追加 `.session_memory_backend()` 方法 | `src/agent/builder.rs` | `cargo build` 通过 | | c4 | `agent/session.rs` 替换内联 HashMap 为完整 `SessionMemory` + 更新模块根重导出 | `src/agent/session.rs` + `src/agent.rs` | `cargo build` 通过 | | c5 | 编写烟雾测试 2-3 个(SessionMemory set/get/snapshot 基于 InMemoryStore) | `src/agent/session_memory.rs` 内联 | `cargo test` 通过 | | c6 | 完整 `cargo test` 跑全量回归 + roadmap.md 状态更新 | — | 所有已有测试不挂 | **依赖关系**: ``` session_memory.rs (c1) ──► runtime.rs 追加 (c2) ──► builder.rs 追加 (c3) │ ▼ session.rs 修改 (c4) │ ▼ cargo test (c5 → c6) ``` --- ### 4.5 预估工作量(按子阶段) | 子阶段 | 文件 | 行数 | 说明 | |--------|------|------|------| | **Phase 4a** | hooks 扩展(2 事件 + 1 字段) | ~10 | 追加变体 + 字段 + 文档 | | | agent/error.rs | ~40 | AgentError 枚举 + From + is_recoverable | | | agent/agent.rs | ~30 | Agent trait + docs | | | agent/runtime.rs | ~60 | RuntimeBundle + AgentConfig | | | agent/builder.rs | ~60 | 链式构造 + build 校验 | | | agent/session.rs | ~100 | AgentSession + submit_turn + 内联 HashMap | | | agent/task.rs(纯数据) | ~40 | Plan / Step / StepStatus | | | src/agent.rs + lib.rs | ~20 | 模块根 + 导出 | | | 烟雾测试 | ~80 | 3-4 个测试 | | | **小计** | **~440** | **核心胶水层** | | **Phase 4b** | hooks 扩展(1 事件 + 1 字段) | ~5 | OnPlanStepComplete + plan_step_index | | | error.rs 追加 PlanParse | ~5 | 1 个变体 | | | task.rs 追加(TaskAgent + PlanParser + JsonPlanParser) | ~130 | trait + 参考实现 + docs | | | 烟雾测试 | ~60 | 2-3 个测试 | | | **小计** | **~200** | **任务执行** | | **Phase 4c** | session_memory.rs | ~40 | 5 个方法 + docs | | | runtime.rs / builder.rs / session.rs 修改 | ~35 | 追加字段 + setter + 替换 HashMap | | | 烟雾测试 | ~40 | 2-3 个测试 | | | **小计** | **~115** | **会话级记忆** | | **合计** | | **~755** | 与原始预估 ~800 基本持平 | ## 5. 风险评估 ### 5.1 抽象化边界(核心风险) **风险描述**:Phase 4 容易"过度抽象"——参考了 OpenHarness / Hermes 后,倾向于把它们的核心能力都搬到 Rust core 库里。 **缓解措施**: - 严格遵循 §1.3 的 7 条设计原则 - 每次添加新 trait / struct 前,先问"这属于 core 库职责吗?" - 业务能力(Plan 拆解、多 Agent 协同、技能加载)一律走 trait 注入或 v0.2+ 延后 ### 5.2 对 Phase 0-3 的侵入风险 **风险描述**:为实现 Phase 4 需修改 `src/llm/hooks.rs`,可能破坏 Phase 0 的现有测试。 **缓解措施**: - 只追加 enum 变体和 `Option` 字段(NF9) - 顺序:先跑 `cargo test` 确认 Phase 0 测试不挂,再开始 Phase 4 - 详细回归验证:实施完毕后跑全量 `cargo test` ### 5.3 参考项目语言差异 **风险描述**:OpenClaw / Hermes / OpenHarness 均为 Python/TypeScript,OpenHuman 虽是 Rust + Tauri 但定位是桌面应用。直接照搬接口形状可能导致 Rust 借用检查问题、async 复杂度增加。 **缓解措施**: - §1.3 明确"借鉴不照搬" - 反模式列表(见 `docs/note-agent-harness-references.md` §6)作为排除项 - 接口设计优先考虑 Rust 惯例(`Arc` / `async fn` / `Result`) ### 5.4 trait 设计的稳定性风险 **风险描述**:Phase 4 是 v0.1 的第一个"复杂 trait 集合",如果 trait 形状不稳定,v0.2+ 添加新能力时会 breaking。 **缓解措施**: - §3.2 的所有 trait / struct 在 `docs/note-agent-runtime-design.md` §4 已固化草案 - 实施时如需调整,应先更新决策记录再改代码 - 预留扩展点:`Agent::tool_definitions` 的默认实现可被子 trait 覆盖 ### 5.5 实施进度风险 **风险描述**:拆为 3 个子阶段后每个阶段任务量降低(4a 约 440 行、4b 约 200 行、4c 约 115 行),但阶段间衔接(4b/4c 对 4a 的依赖)可能产生等待。 **缓解措施**: - 每个子阶段独立验证,完成即交付,不阻塞后续阶段 - 4b 和 4c 无相互依赖,可并行开工 - 烟雾测试只验证"能跑通"不验证"业务正确"——避免陷入业务循环的细节 - 必要时先做 `MockProvider`(Phase 0 已有模式),不依赖真实 LLM ## 6. 验收标准 ### 6.1 通用代码验收(每个子阶段必须满足) - [ ] `cargo build --release` 0 错误 0 警告(clippy) - [ ] `cargo test` 所有已有测试 + 本阶段新增测试全部通过 - [ ] `cargo doc --no-deps` 所有公开 API 有 `///` 文档注释 - [ ] `src/llm/hooks.rs` 仅追加(不修改现有变体或字段) ### 6.2 Phase 4a 验收 #### 6.2a 代码验收 - [ ] 新增代码 ~440 行(含测试 + 文档注释),与 §4.5 预估一致 - [ ] `src/lib.rs` 新增一行 `pub mod agent;` - [ ] 新增文件:`agent.rs` / `agent/agent.rs` / `agent/runtime.rs` / `agent/builder.rs` / `agent/session.rs` / `agent/task.rs` / `agent/error.rs`(共 7 个文件,不含 `agent/builder.rs` 之外的 builder 则 7 个) #### 6.2b 接口验收 - [ ] `Agent` trait 包含 `name` / `system_prompt` / `tool_definitions` 三个方法 - [ ] `RuntimeBundle` 包含 5 个字段:provider / tool_registry / hook_executor / memory_store? / retriever? / config(不含 session_memory_backend) - [ ] `AgentBuilder` 提供 5 个 setter(不含 session_memory_backend)+ `build()` 校验 - [ ] `AgentSession` 持 `Arc` 而非 `agent_name: String` - [ ] `AgentSession::submit_turn` 实现约 30 行,含 OnTurnStart/End hook 触发 - [ ] `AgentSession` 用内联 `HashMap` 做 session_data(不引 `MemoryStore`) - [ ] `Plan` / `Step` / `StepStatus` 纯数据结构存在,状态机正确 - [ ] `AgentError` 聚合 6 个变体:Llm / Tool / Memory / HookBlocked / LimitExceeded / Config / Other(不含 PlanParse) - [ ] `AgentError::is_recoverable()` 对各变体返回正确分类 - [ ] `HookEvent` 新增 2 个变体:`OnTurnStart` / `OnTurnEnd` - [ ] `HookContext` 新增 1 个 `Option` 字段:`turn_index` #### 6.2c 测试验收 - [ ] **测试 1**:`Agent` trait 可实现 + `RuntimeBundle` 可构造(builder 链式调用) - [ ] **测试 2**:`AgentSession::submit_turn` 跑通 mock provider(Phase 0 `MockProvider` 模式) - [ ] **测试 3**:`Plan` / `Step` / `StepStatus` 状态机转换正确 - [ ] **测试 4(可选)**:session_data set/get 基本读写 #### 6.2d 行为验收 - [ ] `AgentSession::submit_turn` 不持有 `ConversationMemory`(grep 验证无 `use crate::memory::ConversationMemory`) - [ ] `AgentSession` 持 `Arc`,可从 agent 获取 `system_prompt()` / `tool_definitions()` - [ ] `RuntimeBundle::new` 当 `retriever` 为 `Some` 时自动注册 `"retrieve"` tool - [ ] `AgentBuilder::build` 在必填字段缺失时返回 `AgentError::Config`(而非 panic) --- ### 6.3 Phase 4b 验收 #### 6.3a 代码验收 - [ ] 追加代码 ~200 行(增量,在 Phase 4a 基础上),与 §4.5 预估一致 - [ ] `src/llm/hooks.rs` 追加 OnPlanStepComplete + plan_step_index(不修改 Phase 4a 新增内容) #### 6.3b 接口验收 - [ ] `TaskAgent` trait 提供双入口 `run(goal)` + `execute_plan(plan)` - [ ] `PlanParser` trait 可注入,`JsonPlanParser` 参考实现基于 `serde_json`(~20 行) - [ ] `AgentError` 追加 PlanParse 变体(共 7 个变体) - [ ] `HookEvent` 追加 1 个变体:`OnPlanStepComplete` - [ ] `HookContext` 追加 1 个 `Option` 字段:`plan_step_index` #### 6.3c 测试验收 - [ ] **测试 1**:`TaskAgent::execute_plan` 跑通 mock provider - [ ] **测试 2**:`JsonPlanParser::parse` 能解析合法 JSON,失败时返回 `AgentError::PlanParse` - [ ] **测试 3(可选)**:`OnPlanStepComplete` hook 触发正确 --- ### 6.4 Phase 4c 验收 #### 6.4a 代码验收 - [ ] 追加代码 ~115 行(增量,在 Phase 4a 基础上),与 §4.5 预估一致 - [ ] 新增文件:`agent/session_memory.rs` #### 6.4b 接口验收 - [ ] `SessionMemory` 包含 5 个方法(set / get / snapshot / remove / clear),基于 `MemoryStore` 实现 - [ ] `SessionMemory::snapshot` 返回 `` 标签包裹的格式化文本 - [ ] `RuntimeBundle` 追加 `session_memory_backend: Option>` 字段 - [ ] `AgentBuilder` 追加 `.session_memory_backend()` setter - [ ] `AgentSession` 替换内联 HashMap 为完整 `SessionMemory`,含 `session_memory: SessionMemory` 字段 - [ ] `SessionMemory` 在 `session_memory_backend` 未传入时自动使用 `InMemoryStore` 兜底 #### 6.4c 测试验收 - [ ] **测试 1**:`SessionMemory` set / get / snapshot 基本读写(基于 `InMemoryStore`) - [ ] **测试 2**:session_data 内联 HashMap ↔ SessionMemory 替换后 submit_turn 行为不变 --- ### 6.5 文档验收 - [ ] `docs/7-agent-runtime.md`(本文件)完整,6 段式结构齐备 - [ ] `docs/note-agent-runtime-design.md` 与本文件互相引用一致 - [ ] `docs/note-agent-harness-references.md` 与本文件互相引用一致 - [ ] `docs/roadmap.md` 各子阶段状态按阶段翻转 ### 6.6 风险验收 - [ ] 5.1 抽象化边界:交付物列表中**不包含** Multi-Agent / Skills / TUI / Gateway 等应用层能力 - [ ] 5.2 Phase 0-3 侵入:`git diff` 显示 `src/llm/hooks.rs` 仅追加 - [ ] 5.3 语言差异:trait 形状符合 Rust 惯例(无 Python 风格的复杂继承) - [ ] 5.4 trait 稳定性:决策记录与最终代码一致 - [ ] 5.5 实施进度:每个子阶段实际工作量与 §4.5 预估偏差 < 30% ## 7. 一句话总结 > **Phase 4 = 3 个子阶段:4a(核心胶水层:Agent + AgentSession + submit_turn + RuntimeBundle + Plan/Step 纯数据,~440 行)→ 4b(任务执行:TaskAgent + PlanParser/JsonPlanParser,~200 行)→ 4c(会话级记忆:SessionMemory + 接入,~115 行),合计 ~755 行,分步交付、逐段验证,把 Phase 0-3 已有能力"装配"成"智能体"的概念。**