docs(agent-runtime): 补充 SessionMemory 设计与多 context 切换备忘

- 在方案文档中新增 `SessionMemory` 作为会话级记忆桥接组件
- 将 AgentSession 的 `agent_name: String` 改为 `agent: Arc<dyn Agent>`
- RuntimeBundle 新增 `session_memory_backend` 可选字段
- 新增 `docs/note-context-switch-design.md` 记录多 context 切换方案
- 更新 Roadmap 状态并补充 Phase 4 范围界定
- Phase 4 仅实现 SessionMemory 数据结构与 API,ContextManager 延后至 v0.2+
This commit is contained in:
徐涛
2026-06-10 22:22:18 +08:00
parent 75f8736931
commit be595a6771
3 changed files with 364 additions and 47 deletions
+123 -34
View File
@@ -27,6 +27,7 @@ Phase 4 目标是提供一个**薄胶水层 + 一组 trait 抽象**,让上层
- **`AgentSession` struct** — 智能体的"会话"实例(绑定 session_id + 状态) - **`AgentSession` struct** — 智能体的"会话"实例(绑定 session_id + 状态)
- **`TaskAgent` trait** — 任务型智能体的"规划/执行"抽象 - **`TaskAgent` trait** — 任务型智能体的"规划/执行"抽象
- **`RuntimeBundle`** — 显式依赖注入容器,集中管理 provider/registry/hook/memory 等依赖 - **`RuntimeBundle`** — 显式依赖注入容器,集中管理 provider/registry/hook/memory 等依赖
- **`SessionMemory`** — 会话级记忆,用于 context 间的信息桥接(基于 `MemoryStore` 后端)
- **`AgentBuilder`** — 链式构造入口 - **`AgentBuilder`** — 链式构造入口
- **`AgentError`** — 统一错误类型,聚合 LlmError / ToolError / MemoryError - **`AgentError`** — 统一错误类型,聚合 LlmError / ToolError / MemoryError
@@ -42,6 +43,7 @@ Phase 4 严格遵循以下原则,所有范围决策都基于这些原则推导
| **实体/会话分离** | 同一角色可被多 session 复用 | `Agent` + `AgentSession` 两层模型 | | **实体/会话分离** | 同一角色可被多 session 复用 | `Agent` + `AgentSession` 两层模型 |
| **记忆弱引用** | 记忆是"被动能力",不内嵌循环 | `memory_store: Option<Arc<dyn MemoryStore>>` 弱引用 | | **记忆弱引用** | 记忆是"被动能力",不内嵌循环 | `memory_store: Option<Arc<dyn MemoryStore>>` 弱引用 |
| **业务可注入** | Plan 拆解是业务能力,不在 core 库实现 | 暴露 `PlanParser` trait,上层注入 | | **业务可注入** | Plan 拆解是业务能力,不在 core 库实现 | 暴露 `PlanParser` trait,上层注入 |
| **会话级记忆** | session 内共享、context 间桥接,不是持久层也不是对话历史 | `SessionMemory` 基于 `MemoryStore`,按 session_id 命名空间隔离 |
| **借鉴不照搬** | 4 个参考项目均非 Rust 实现 | 只取架构模式,不抄实现细节 | | **借鉴不照搬** | 4 个参考项目均非 Rust 实现 | 只取架构模式,不抄实现细节 |
### 1.4 与已完成的 Phase 关系 ### 1.4 与已完成的 Phase 关系
@@ -190,6 +192,7 @@ pub struct RuntimeBundle {
pub hook_executor: Arc<HookExecutor>, pub hook_executor: Arc<HookExecutor>,
pub memory_store: Option<Arc<dyn MemoryStore>>, // 弱引用 pub memory_store: Option<Arc<dyn MemoryStore>>, // 弱引用
pub retriever: Option<Arc<MemoryRetriever>>, // 弱引用 pub retriever: Option<Arc<MemoryRetriever>>, // 弱引用
pub session_memory_backend: Option<Arc<dyn MemoryStore>>, // SessionMemory 后端(选填)
pub config: AgentConfig, pub config: AgentConfig,
} }
``` ```
@@ -198,6 +201,7 @@ pub struct RuntimeBundle {
- 所有运行时依赖**显式打包**OpenHarness 风格) - 所有运行时依赖**显式打包**OpenHarness 风格)
- `memory_store` / `retriever` 均为 `Option`——上层应用**不传也能跑**(无记忆模式) - `memory_store` / `retriever` 均为 `Option`——上层应用**不传也能跑**(无记忆模式)
-`retriever` 存在时,`RuntimeBundle::new()` 内部自动注册一个名为 `"retrieve"` 的 tool(具体实现:在 `ToolRegistry` 里加一个 `RetrieveTool` 包装),让 LLM 在对话中**主动**调用检索能力 -`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 - `config` 集中管理所有可调参数(max_turns、max_tool_turns、session_ttl、compact_config
#### 3.2.3 `AgentSession` 与最小 reference impl #### 3.2.3 `AgentSession` 与最小 reference impl
@@ -205,10 +209,11 @@ pub struct RuntimeBundle {
```rust ```rust
pub struct AgentSession { pub struct AgentSession {
pub session_id: String, pub session_id: String,
pub agent_name: String, pub agent: Arc<dyn Agent>,
bundle: Arc<RuntimeBundle>, bundle: Arc<RuntimeBundle>,
turn_index: u32, turn_index: u32,
cost_so_far: CostTracker, cost_so_far: CostTracker,
session_memory: SessionMemory,
} }
impl AgentSession { impl AgentSession {
@@ -229,6 +234,8 @@ impl AgentSession {
``` ```
**设计意图** **设计意图**
- `agent: Arc<dyn Agent>` 而非 `agent_name: String`——`submit_turn` 从 agent 获取 `system_prompt()``tool_definitions()`,同时为 v0.2+ 的"热切换 agent"预留:替换 `self.agent` 即可切换角色
- `session_memory` 是进程内共享的会话级记忆,context 间通过它桥接信息(详见 §3.2.8)
- "最小 reference impl" 只演示**最常见**的对话场景 - "最小 reference impl" 只演示**最常见**的对话场景
- 业务循环(多轮策略、错误重试、记忆回写时机)由上层应用或具体的 `TaskAgent` 实现决定 - 业务循环(多轮策略、错误重试、记忆回写时机)由上层应用或具体的 `TaskAgent` 实现决定
- `submit_turn` 不持有 `ConversationMemory`——上层应用可独立 new 一个 `ConversationMemory`,在合适的时机(如 OnTurnEnd hook)调 `add_message` - `submit_turn` 不持有 `ConversationMemory`——上层应用可独立 new 一个 `ConversationMemory`,在合适的时机(如 OnTurnEnd hook)调 `add_message`
@@ -324,6 +331,7 @@ impl AgentBuilder {
pub fn hook_executor(self, h: Arc<HookExecutor>) -> Self; pub fn hook_executor(self, h: Arc<HookExecutor>) -> Self;
pub fn memory_store(self, m: Arc<dyn MemoryStore>) -> Self; // 选填 pub fn memory_store(self, m: Arc<dyn MemoryStore>) -> Self; // 选填
pub fn retriever(self, r: Arc<MemoryRetriever>) -> Self; // 选填 pub fn retriever(self, r: Arc<MemoryRetriever>) -> Self; // 选填
pub fn session_memory_backend(self, s: Arc<dyn MemoryStore>) -> Self; // 选填
pub fn config(self, c: AgentConfig) -> Self; pub fn config(self, c: AgentConfig) -> Self;
pub fn build(self) -> Result<RuntimeBundle, AgentError>; pub fn build(self) -> Result<RuntimeBundle, AgentError>;
} }
@@ -332,7 +340,74 @@ impl AgentBuilder {
**设计意图** **设计意图**
- `AgentBuilder` 是**唯一**的 `RuntimeBundle` 构造入口 - `AgentBuilder` 是**唯一**的 `RuntimeBundle` 构造入口
- 必填字段在 `build()` 时校验(`provider` / `tool_registry` / `hook_executor` 不可缺) - 必填字段在 `build()` 时校验(`provider` / `tool_registry` / `hook_executor` 不可缺)
- `memory_store` / `retriever` 选填,对应 §3.2.2 的"无记忆模式" - `memory_store` / `retriever` / `session_memory_backend` 选填
- `session_memory_backend` 不传时,`AgentSession` 内部用 `InMemoryStore` 兜底(进程级隔离)
#### 3.2.8 `SessionMemory` — 会话级记忆
```rust
pub struct SessionMemory {
store: Arc<dyn MemoryStore>,
namespace: String,
}
impl SessionMemory {
/// 创建新的 session 级记忆实例。
/// store:后端存储(可跨进程共享的 MemoryStore 实现)。
/// namespace:按 session_id 隔离,防止跨 session 泄漏。
pub fn new(store: Arc<dyn MemoryStore>, 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<Option<String>, AgentError>;
/// 返回所有条目的格式化快照,适合注入 system prompt。
/// 格式:
/// <session-context>
/// key1: value1
/// key2: value2
/// </session-context>
pub async fn snapshot(&self) -> Result<String, AgentError>;
/// 删除指定 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 可以自然理解 `<session-context>` 标签中的内容
- **所有方法为 `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 看到 "<session-context>\ndesign_decision: 用 PostgreSQL\n</session-context>"
→ 无需看 context_a 的 50 轮完整历史,但知道关键上下文
```
### 3.3 状态机 ### 3.3 状态机
@@ -440,24 +515,28 @@ pub struct HookContext {
| `JsonPlanParser::parse` | `serde_json::from_str` | task.rs | | `JsonPlanParser::parse` | `serde_json::from_str` | task.rs |
| `AgentError::from` | `LlmError` / `ToolError` / `MemoryError` | error.rs | | `AgentError::from` | `LlmError` / `ToolError` / `MemoryError` | error.rs |
| `HookContext` 扩展 | `HookEvent::OnTurnStart/End/OnPlanStepComplete` | llm/hooks.rs | | `HookContext` 扩展 | `HookEvent::OnTurnStart/End/OnPlanStepComplete` | llm/hooks.rs |
| `SessionMemory::set/get/snapshot` | `MemoryStore::save/load/search` | session_memory.rs |
**不调用的下层 API**(明确边界): **不调用的下层 API**(明确边界):
-`ConversationMemory`(由上层独立 task 管理) -`ConversationMemory`(由上层独立 task 管理)
-`KnowledgeStore`(由上层独立 task 管理) -`KnowledgeStore`(由上层独立 task 管理)
-`McpClient`(已由 `ToolRegistry` 包装) -`McpClient`(已由 `ToolRegistry` 包装)
-`StreamEvents::submit_stream`v1 暂不暴露流式 `submit_turn`v0.2 再说) -`StreamEvents::submit_stream`v1 暂不暴露流式 `submit_turn`v0.2 再说)
- ❌ 多 context 切换管理(v0.2+ 实现,Phase 4 只预留 `SessionMemory` 桥接通道)
-`"session_memory_set"` 等 session memory tool 自动注册(v0.2+ 可选)
## 4. 实施计划 ## 4. 实施计划
### 4.1 文件清单 ### 4.1 文件清单
#### 新增文件(7 个) #### 新增文件(8 个)
``` ```
src/agent.rs # 模块根 + pub use 重导出 src/agent.rs # 模块根 + pub use 重导出
src/agent/agent.rs # Agent trait src/agent/agent.rs # Agent trait
src/agent/runtime.rs # RuntimeBundle + AgentConfig src/agent/runtime.rs # RuntimeBundle + AgentConfig
src/agent/session.rs # AgentSession(含 submit_turn reference impl src/agent/session.rs # AgentSession(含 submit_turn reference impl
src/agent/session_memory.rs # SessionMemory(会话级记忆,基于 MemoryStore
src/agent/task.rs # TaskAgent trait + Plan/Step + PlanParser + JsonPlanParser src/agent/task.rs # TaskAgent trait + Plan/Step + PlanParser + JsonPlanParser
src/agent/builder.rs # AgentBuilder src/agent/builder.rs # AgentBuilder
src/agent/error.rs # AgentError src/agent/error.rs # AgentError
@@ -486,15 +565,16 @@ docs/7-agent-runtime.md # ✅ 本文件
| 1 | 修改 `llm/hooks.rs` 追加 3 个事件 + 2 个字段 | `src/llm/hooks.rs` | `cargo build` 通过;现有测试不挂 | | 1 | 修改 `llm/hooks.rs` 追加 3 个事件 + 2 个字段 | `src/llm/hooks.rs` | `cargo build` 通过;现有测试不挂 |
| 2 | 新建 `agent/error.rs` 定义 `AgentError` | `src/agent/error.rs` | `cargo build` 通过 | | 2 | 新建 `agent/error.rs` 定义 `AgentError` | `src/agent/error.rs` | `cargo build` 通过 |
| 3 | 新建 `agent/agent.rs` 定义 `Agent` trait | `src/agent/agent.rs` | `cargo build` 通过 | | 3 | 新建 `agent/agent.rs` 定义 `Agent` trait | `src/agent/agent.rs` | `cargo build` 通过 |
| 4 | 新建 `agent/runtime.rs` 定义 `RuntimeBundle` + `AgentConfig` | `src/agent/runtime.rs` | `cargo build` 通过 | | 4 | 新建 `agent/runtime.rs` 定义 `RuntimeBundle` + `AgentConfig`(含 `session_memory_backend` 字段) | `src/agent/runtime.rs` | `cargo build` 通过 |
| 5 | 新建 `agent/builder.rs` 定义 `AgentBuilder` | `src/agent/builder.rs` | `cargo build` 通过 | | 5 | 新建 `agent/session_memory.rs` 定义 `SessionMemory` | `src/agent/session_memory.rs` | `cargo build` 通过 |
| 6 | 新建 `agent/session.rs` 定义 `AgentSession` + `submit_turn` | `src/agent/session.rs` | `cargo build` 通过 | | 6 | 新建 `agent/builder.rs` 定义 `AgentBuilder`(含 `.session_memory_backend()` 方法) | `src/agent/builder.rs` | `cargo build` 通过 |
| 7 | 新建 `agent/task.rs` 定义 `TaskAgent` + `Plan` / `Step` / `PlanParser` / `JsonPlanParser` | `src/agent/task.rs` | `cargo build` 通过 | | 7 | 新建 `agent/session.rs` 定义 `AgentSession` + `submit_turn`(持 `Arc<dyn Agent>` + `SessionMemory` | `src/agent/session.rs` | `cargo build` 通过 |
| 8 | 新建 `src/agent.rs` 模块根 + `pub use` 重导出 | `src/agent.rs` | `cargo build` 通过 | | 8 | 新建 `agent/task.rs` 定义 `TaskAgent` + `Plan` / `Step` / `PlanParser` / `JsonPlanParser` | `src/agent/task.rs` | `cargo build` 通过 |
| 9 | 修改 `lib.rs` 导出 `pub mod agent;` | `src/lib.rs` | `cargo build` 通过 | | 9 | 新建 `src/agent.rs` 模块根 + `pub use` 重导出 | `src/agent.rs` | `cargo build` 通过 |
| 10 | 编写 2-3 个烟雾测试 | `src/agent/*.rs` 内联 | `cargo test` 通过 | | 10 | 修改 `lib.rs` 导出 `pub mod agent;` | `src/lib.rs` | `cargo build` 通过 |
| 11 | 更新 `roadmap.md` 状态翻转 | `docs/roadmap.md` | 文档 review | | 11 | 编写 3-4 个烟雾测试(含 SessionMemory | `src/agent/*.rs` 内联 | `cargo test` 通过 |
| 12 | 完整 `cargo test` 跑全量回归 | — | 所有已有测试不挂 | | 12 | 更新 `roadmap.md` 状态翻转 | `docs/roadmap.md` | 文档 review |
| 13 | 完整 `cargo test` 跑全量回归 | — | 所有已有测试不挂 |
### 4.3 依赖关系 ### 4.3 依赖关系
@@ -504,30 +584,33 @@ hooks.rs (1) ──┐
│ │ │ │
│ ▼ │ ▼
│ agent/runtime.rs (4) │ agent/runtime.rs (4)
│ │ │
│ │ ▼
│ │ agent/session_memory.rs (5)
│ │ │
│ ▼ │
│ agent/builder.rs (6)
│ │ │ │
│ ▼ │ ▼
│ agent/builder.rs (5) │ agent/session.rs (7)
│ │ │ │
│ ▼ │ ▼
│ agent/session.rs (6) └─────────────────► agent/task.rs (8)
│ │
│ ▼
└─────────────────► agent/task.rs (7)
src/agent.rs (8) src/agent.rs (9)
src/lib.rs (9) src/lib.rs (10)
cargo test (10) cargo test (11)
roadmap.md (11) roadmap.md (12)
回归 (12) 回归 (13)
``` ```
### 4.4 预估工作量 ### 4.4 预估工作量
@@ -535,11 +618,11 @@ hooks.rs (1) ──┐
| 阶段 | 行数 | 说明 | | 阶段 | 行数 | 说明 |
|------|------|------| |------|------|------|
| 1hooks 扩展) | ~15 | 3 个变体 + 2 个字段 + 文档 | | 1hooks 扩展) | ~15 | 3 个变体 + 2 个字段 + 文档 |
| 2-77 个 agent 文件) | ~600 | 含 import + trait + struct + impl + 文档 | | 2-88 个 agent 文件) | ~640 | 含 import + trait + struct + impl + 文档+ session_memory.rs ~40 行) |
| 8-9lib.rs + agent.rs 模块根) | ~20 | 主要是 pub use 重导出 | | 9-10lib.rs + agent.rs 模块根) | ~20 | 主要是 pub use 重导出 |
| 10(烟雾测试) | ~100 | 2-3 个测试 | | 11(烟雾测试) | ~120 | 3-4 个测试(含 SessionMemory |
| 11roadmap 同步) | ~5 | 状态翻转一行 | | 12roadmap 同步) | ~5 | 状态翻转一行 |
| **合计** | **~740** | `note-agent-runtime-design.md` §6 预估一致 | | **合计** | **~800** | 从 ~740 增加 ~60 行(SessionMemory ~40 + 测试 ~20 |
## 5. 风险评估 ## 5. 风险评估
@@ -595,30 +678,34 @@ hooks.rs (1) ──┐
- [ ] `cargo build --release` 0 错误 0 警告(clippy - [ ] `cargo build --release` 0 错误 0 警告(clippy
- [ ] `cargo test` 所有 Phase 0-3 已有测试 + Phase 4 新增测试全部通过 - [ ] `cargo test` 所有 Phase 0-3 已有测试 + Phase 4 新增测试全部通过
- [ ] `cargo doc --no-deps` 所有公开 API 有 `///` 文档注释 - [ ] `cargo doc --no-deps` 所有公开 API 有 `///` 文档注释
- [ ] 新增代码 700-750 行(含测试 + 文档注释),与 §4.4 预估一致 - [ ] 新增代码 750-850 行(含测试 + 文档注释),与 §4.4 预估一致
- [ ] `src/lib.rs` 新增一行 `pub mod agent;` - [ ] `src/lib.rs` 新增一行 `pub mod agent;`
- [ ] `src/llm/hooks.rs` 仅追加(不修改现有变体或字段) - [ ] `src/llm/hooks.rs` 仅追加(不修改现有变体或字段)
### 6.2 接口验收 ### 6.2 接口验收
- [ ] 7 个新文件全部存在(§4.1 - [ ] 8 个新文件全部存在(§4.1
- [ ] `Agent` trait 包含 `name` / `system_prompt` / `tool_definitions` 三个方法 - [ ] `Agent` trait 包含 `name` / `system_prompt` / `tool_definitions` 三个方法
- [ ] `RuntimeBundle` 包含 6 个字段(provider / tool_registry / hook_executor / memory_store? / retriever? / config - [ ] `RuntimeBundle` 包含 7 个字段(provider / tool_registry / hook_executor / memory_store? / retriever? / session_memory_backend? / config
- [ ] `AgentSession``Arc<dyn Agent>` 而非 `agent_name: String`,含 `session_memory: SessionMemory` 字段
- [ ] `AgentSession::submit_turn` 实现约 30 行,含 OnTurnStart/End hook 触发 - [ ] `AgentSession::submit_turn` 实现约 30 行,含 OnTurnStart/End hook 触发
- [ ] `SessionMemory` 包含 5 个方法(set / get / snapshot / remove / clear),基于 `MemoryStore` 实现
- [ ] `SessionMemory::snapshot` 返回 `<session-context>` 标签包裹的格式化文本
- [ ] `TaskAgent` 提供双入口 `run` + `execute_plan` - [ ] `TaskAgent` 提供双入口 `run` + `execute_plan`
- [ ] `JsonPlanParser` 实现约 20 行,基于 `serde_json` - [ ] `JsonPlanParser` 实现约 20 行,基于 `serde_json`
- [ ] `AgentError` 聚合 8 个变体,含 `is_recoverable()` - [ ] `AgentError` 聚合 8 个变体,含 `is_recoverable()`
- [ ] `AgentBuilder` 提供 6 个 setter + `build()` 校验 - [ ] `AgentBuilder` 提供 7 个 setter(含 `.session_memory_backend()`+ `build()` 校验
- [ ] `HookEvent` 新增 3 个变体:`OnTurnStart` / `OnTurnEnd` / `OnPlanStepComplete` - [ ] `HookEvent` 新增 3 个变体:`OnTurnStart` / `OnTurnEnd` / `OnPlanStepComplete`
- [ ] `HookContext` 新增 2 个 `Option` 字段:`turn_index` / `plan_step_index` - [ ] `HookContext` 新增 2 个 `Option` 字段:`turn_index` / `plan_step_index`
### 6.3 测试验收 ### 6.3 测试验收
至少 2-3 个烟雾测试通过: 至少 3-4 个烟雾测试通过:
- [ ] **测试 1**`Agent` trait 可实现 + `RuntimeBundle` 可构造(builder 链式调用) - [ ] **测试 1**`Agent` trait 可实现 + `RuntimeBundle` 可构造(builder 链式调用)
- [ ] **测试 2**`AgentSession::submit_turn` 跑通 mock providerPhase 0 `MockProvider` 模式) - [ ] **测试 2**`AgentSession::submit_turn` 跑通 mock providerPhase 0 `MockProvider` 模式)
- [ ] **测试 3(可选)**`JsonPlanParser::parse` 能解析合法 JSON,失败时返回 `AgentError::PlanParse` - [ ] **测试 3**`SessionMemory` set / get / snapshot 基本读写(基于 `InMemoryStore`
- [ ] **测试 4(可选)**`JsonPlanParser::parse` 能解析合法 JSON,失败时返回 `AgentError::PlanParse`
### 6.4 文档验收 ### 6.4 文档验收
@@ -630,9 +717,11 @@ hooks.rs (1) ──┐
### 6.5 行为验收(人工 review ### 6.5 行为验收(人工 review
- [ ] `AgentSession::submit_turn` 不持有 `ConversationMemory`grep 验证无 `use crate::memory::ConversationMemory` - [ ] `AgentSession::submit_turn` 不持有 `ConversationMemory`grep 验证无 `use crate::memory::ConversationMemory`
- [ ] `AgentSession``Arc<dyn Agent>`,可从 agent 获取 `system_prompt()` / `tool_definitions()`
- [ ] `RuntimeBundle::new``retriever``Some` 时自动注册 `"retrieve"` tool - [ ] `RuntimeBundle::new``retriever``Some` 时自动注册 `"retrieve"` tool
- [ ] `AgentBuilder::build` 在必填字段缺失时返回 `AgentError::Config`(而非 panic - [ ] `AgentBuilder::build` 在必填字段缺失时返回 `AgentError::Config`(而非 panic
- [ ] `AgentError::is_recoverable()` 对各变体返回正确分类 - [ ] `AgentError::is_recoverable()` 对各变体返回正确分类
- [ ] `SessionMemory``session_memory_backend` 未传入时自动使用 `InMemoryStore` 兜底
### 6.6 风险验收 ### 6.6 风险验收
@@ -644,4 +733,4 @@ hooks.rs (1) ──┐
## 7. 一句话总结 ## 7. 一句话总结
> **Phase 4 = 1 个 traitAgent+ 1 个容器(RuntimeBundle+ 1 个会话(AgentSession+ 1 个任务抽象(TaskAgent+ 4 个辅助组件(Builder / Error / PlanParser / Hook 扩展),约 740 行代码,把 Phase 0-3 已有能力"装配"成"智能体"的概念。** > **Phase 4 = 1 个 traitAgent+ 1 个容器(RuntimeBundle+ 1 个会话(AgentSession, 持 Arc\<dyn Agent\>+ 1 个会话级记忆(SessionMemory+ 1 个任务抽象(TaskAgent+ 4 个辅助组件(Builder / Error / PlanParser / Hook 扩展),约 800 行代码,把 Phase 0-3 已有能力"装配"成"智能体"的概念。**
+222
View File
@@ -0,0 +1,222 @@
# Context 切换方案设计备忘
> 创建日期:2026-06-10
> 状态:备忘(Phase 4 不实现)
> 关联文档:
> - `docs/7-agent-runtime.md` — Phase 4 方案(含 SessionMemory 设计)
> - `docs/note-opencode-agent-switching.md` — OpenCode 切换机制调研
> - `docs/roadmap.md` — 项目总 Roadmap
---
## 1. 背景
### 1.1 问题
在调研 OpenCode 的 Agent 切换机制后(详见 `docs/note-opencode-agent-switching.md`),发现其做法是:
- 切换 agent 时**不动消息历史**
- 在 user message 末尾追加 `synthetic: true``<system-reminder>` 提醒
- 同时**完全重新计算** system prompt
这个方法的问题是:**长上下文中频繁切换 agent 容易给 LLM 造成身份困惑**。同一个消息列表里有 `system: build` 的 identity,又出现 `system: plan` 的 identityLLM 容易"串味"。
### 1.2 核心思路
以一个 session 里存在**多个独立的 context** 来解决,每个 context 有自己独立的 system prompt + 消息列表:
```
OpenCode 模式(一条流):
[system: build, user: A, ass: A', user: <切plan>, system: plan, user: B]
↑ 身份困惑
建议的多 context 模式:
session {
context_a: [system: build, user: A, ass: A'] ← 只有 build 的 identity
context_b: [system: plan, user: B, ass: B'] ← 只有 plan 的 identity
}
```
### 1.3 适用范围
| 场景 | 适用性 | 说明 |
|------|--------|------|
| Agent 切换(build ↔ plan | ✅ 核心场景 | 同一 session 内更换角色 |
| 主从 Agent 协作 | ✅ 核心场景 | primary 委派子任务给 subagentsubagent 独立运作 |
| 长 session 上下文压缩 | ✅ 附带收益 | 拆分 context 后,每个 context 独立累积消息,不会互相拖长 |
| 并行 context 执行 | ⚠️ 拓展场景 | context_a 和 context_b 可各自独立推进 |
---
## 2. 三个候选方案
### 方案 AOpenCode 式(system prompt 重算 + synthetic 追加)
**做法**
- 单一消息列表
- 切换时重算 system prompt
- user message 末尾追加 `<system-reminder>` 标签
**优点**
- 实现简单
- 消息历史完整可见
**缺点**
- 长上下文身份困惑
- context 互相污染(每个 context 都要看全部历史)
**结论**:❌ 否决。不解决身份困惑问题。
### 方案 B:信息池 + 切换不重置(借鉴 OpenCode + 增强)
**做法**
- 切换时保留历史
- 使用 `<system-reminder>` 标签
- 靠 prompt 工程让 LLM 理解身份变更
**优点**
- 历史连贯
- 改动最小
**缺点**
- 仍然有身份困惑风险
- 上下文不受控增长
**结论**:❌ 否决。治标不治本。
### 方案 C:多 context 隔离 + SessionMemory 桥接(推荐)
**做法**
- 每个 agent 切换创建一个新的 context(独立消息列表 + 独立 system prompt
- context 之间通过 `SessionMemory` 桥接关键信息
- 切换时新 context 的 system prompt 末尾注入 `SessionMemory::snapshot()`
```
context_a (build)
→ 对话 50 轮
→ 写入 SessionMemory: {"design_decision": "用 PostgreSQL",
"files_changed": "src/db.rs"}
→ 销毁(或沉睡)
创建 context_b (plan)
→ system_prompt += snapshot()
→ "<session-context>
design_decision: 用 PostgreSQL
files_changed: src/db.rs
</session-context>"
→ 对话 10 轮(不需要看 context_a 的 50 轮历史)
→ 读 SessionMemory: get("design_decision") → "用 PostgreSQL"
```
**优点**
- ✅ 身份稳定:每个 context 只有一套 system prompt
- ✅ 上下文隔离:context_b 不受 context_a 的消息量影响
- ✅ 信息桥接:关键结论通过 SessionMemory 显式传递
- ✅ 并行潜力:两个 context 可各自运行
**缺点**
- ❌ 实现复杂度:从"一个消息列表"到"多个消息列表 + 桥接"
- ❌ 信息完整性:LLM 自主决定"什么值得记",可能遗漏细节
- ❌ 上层理解成本:应用层需要理解 context 概念
**结论**:✅ 推荐。架构上最干净,但 Phase 4 不做全部实现。
---
## 3. SessionMemory 桥接机制(方案 C 的核心)
### 3.1 设计决策
| 决策 | 结论 | 理由 |
|------|------|------|
| 复用 Phase 3 `MemoryStore` | ✅ 是 | 不引入新存储机制 |
| 跨进程支持 | ✅ 是 | 换后端即可(Redis / SQLite),`InMemoryStore` 兜底 |
| namespace 隔离 | ✅ 是 | `_session_{session_id}` 命名空间 |
| 谁写 SessionMemory | LLM 通过 tool 显式写(v0.2+)或上层应用 API 写 | 不支持自动写——避免 "写太多 = 噪音,写太少 = 遗漏" |
| snapshot 格式 | `<session-context>` XML 风格 | 专为注入 system prompt 设计 |
### 3.2 谁写 SessionMemory 的三种选项
| 选项 | 描述 | 评估 |
|------|------|------|
| **选项 1AgentSession 自动写** | 每轮对话后自动摘录关键信息 | ❌ 摘录什么?容易变成精简版对话历史,失去"关键信息"的定位 |
| **选项 2LLM 通过 tool 显式写** | 把 `SessionMemory::set` 暴露为 Tool 供 LLM 调用 | ✅ LLM 自主决定什么值得记;v0.2+ 实现自动注册 |
| **选项 3:上层应用 API 写** | `agent_session.session_memory.set("k", "v")` | ✅ Phase 4 即可用,最透明 |
**Phase 4 实现选项 3**v0.2+ 补充选项 2(tool 自动注册)。
### 3.3 三层记忆体系
```
持久层(Phase 3 MemoryStore / KnowledgeStore ── 跨 session 持久,长期知识
会话层(Phase 4 SessionMemory ── 单 session 内共享,context 桥接
对话层(Phase 3 ConversationMemory ── 单 context 内消息历史
```
---
## 4. Phase 4 范围 vs v0.2+ 范围
### ✅ Phase 4 做
| 组件 | 状态 | 行数 |
|------|------|------|
| `SessionMemory` struct | ✅ 做 | ~40 行 |
| `AgentSession` + `session_memory` 字段 | ✅ 做 | ~3 行 |
| `AgentSession``Arc<dyn Agent>` 替代 `agent_name: String` | ✅ 做 | ~3 行 |
| `RuntimeBundle` + `session_memory_backend` 字段 | ✅ 做 | ~1 行 |
| `AgentBuilder` + `.session_memory_backend()` | ✅ 做 | ~3 行 |
### ❌ 延后到 v0.2+
| 组件 | 状态 | 说明 |
|------|------|------|
| Context 切换管理(`switch_context` / `create_context` | ❌ 延后 | 需要 `ContextManager` 包装 |
| 多 context 生命周期管理 | ❌ 延后 | context 的创建/销毁/切换策略 |
| `"session_memory_set"` tool 自动注册 | ❌ 延后 | 在 `ToolRegistry` 里注册特殊 tool |
| Context 级别的 `ConversationMemory` 自动管理 | ❌ 延后 | 每个 context 独立消息历史 |
### 延后的理由
1. **最小范围原则**Phase 4 定位是"薄胶水层 + trait 抽象",多 context 管理属于业务编排的范畴
2. **稳定 API 优先**:先把 `AgentSession` / `RuntimeBundle` / `SessionMemory` 的 API 定稳,v0.2+ 在上面搭建 context 切换
3. **降低实施风险**:Phase 4 已有 13 个交付任务,加 context 切换会增加 2-3 倍复杂度
---
## 5. v0.2+ Context 切换的设想接口
> 以下为未来实现的草案,非承诺。记录在这里避免 v0.2+ 重新设计时丢失上下文。
```rust
pub struct ContextManager {
contexts: HashMap<String, AgentSession>,
active_context: String,
session_memory: SessionMemory,
}
impl ContextManager {
/// 创建一个新的 context,绑定指定 agent
pub fn create_context(&mut self, id: &str, agent: Arc<dyn Agent>) -> Result<(), AgentError>;
/// 切换到已有 context
pub fn switch_context(&mut self, id: &str) -> Result<&mut AgentSession, AgentError>;
/// 销毁 context
pub fn destroy_context(&mut self, id: &str) -> Result<(), AgentError>;
/// 从 context_a 桥接关键信息到 context_b 的 system prompt
pub fn bridge(&mut self, from: &str, to: &str) -> Result<(), AgentError>;
}
```
切换流程:
1. `context_manager.create_context("plan", plan_agent)` — 新 context 的 system prompt 自动附加 `session_memory.snapshot()`
2. `context_manager.switch_context("plan")` — 返回 context 的 `AgentSession`,应用层调 `submit_turn`
3. context 销毁时,关键信息经由 LLM 或上层应用写入 `SessionMemory`
---
## 6. 一句话总结
> **多 context 切换方案 = `SessionMemory`Phase 4 做信息桥接基础) + `ContextManager`v0.2+ 做切换管理)。Phase 4 只铺"水管接口",不装"水循环系统"。**
+19 -13
View File
@@ -1,13 +1,13 @@
# AG Core Roadmap # AG Core Roadmap
> 定稿日期:2026-05-11 > 定稿日期:2026-05-11
> 最后更新:2026-06-09Phase 4 设计讨论收尾;扩展计划补充 v0.2+ 候选项 > 最后更新:2026-06-10Phase 4 方案定稿 + SessionMemory 扩展 + Context 切换备忘
## 愿景 ## 愿景
AG Core 定位为构建 AI 智能体的底层工具箱,通过模块化、可插拔的架构,提供大模型调用、提示词工程、工具系统、记忆检索四大核心能力,支持快速组合出符合业务需求的智能体应用。 AG Core 定位为构建 AI 智能体的底层工具箱,通过模块化、可插拔的架构,提供大模型调用、提示词工程、工具系统、记忆检索四大核心能力,支持快速组合出符合业务需求的智能体应用。
**当前状态**:Phase 0 基础设施已全部完成,Phase 1 提示词工程已全部完成,Phase 2 工具系统已全部完成,Phase 3 记忆系统已全部完成,等待 Phase 4 启动 **当前状态**:Phase 0 基础设施已全部完成,Phase 1 提示词工程已全部完成,Phase 2 工具系统已全部完成,Phase 3 记忆系统已全部完成,Phase 4 方案文档已完成(含 SessionMemory 扩展),待编码实施
--- ---
@@ -19,7 +19,7 @@ AG Core 定位为构建 AI 智能体的底层工具箱,通过模块化、可
| 提示词工程 | ✅ 完整 | `docs/4-prompt-engineering.md` | P1 | | 提示词工程 | ✅ 完整 | `docs/4-prompt-engineering.md` | P1 |
| 工具系统 + 权限 | ✅ 完整 | `docs/5-tool-system.md` | P1 | | 工具系统 + 权限 | ✅ 完整 | `docs/5-tool-system.md` | P1 |
| 记忆检索 | ✅ 完整 | `docs/6-memory-system.md` | P2 | | 记忆检索 | ✅ 完整 | `docs/6-memory-system.md` | P2 |
| Agent 运行时 | ❌ 缺失 | — | P2 | | Agent 运行时 | ✅ 方案已完成 | `docs/7-agent-runtime.md` | P2 |
| 生命周期钩子 | ✅ 完整 | `docs/3-phase0-remaining.md` | P0LLM Cycle 扩展) | | 生命周期钩子 | ✅ 完整 | `docs/3-phase0-remaining.md` | P0LLM Cycle 扩展) |
| Provider 注册发现 | ✅ 完整 | `docs/3-phase0-remaining.md` | P0Provider 接口扩展) | | Provider 注册发现 | ✅ 完整 | `docs/3-phase0-remaining.md` | P0Provider 接口扩展) |
| 流式事件系统 | ✅ 完整 | `docs/3-phase0-remaining.md` | P0(流式接口前置) | | 流式事件系统 | ✅ 完整 | `docs/3-phase0-remaining.md` | P0(流式接口前置) |
@@ -123,20 +123,25 @@ AG Core 定位为构建 AI 智能体的底层工具箱,通过模块化、可
### Phase 4 — Agent Runtime(智能体运行时) ### Phase 4 — Agent Runtime(智能体运行时)
**目标**:实现多轮对话编排任务规划。 **目标**:实现多轮对话编排任务规划与会话级记忆桥接
**交付物** **交付物**
1. `agent.rs` + `agent/` 模块 1. `agent.rs` + `agent/` 模块8 个文件)
2. `Agent` trait — 智能体接口定义 2. `Agent` trait — 智能体角色定义(name / system_prompt / tool_definitions
3. `ConversationAgent` — 对话型智能体实现 3. `AgentSession` — 会话实例(绑定 `Arc<dyn Agent>` + `RuntimeBundle` + `SessionMemory`
4. `TaskAgent` — 任务型智能体(规划 → 执行 → 反馈 4. `RuntimeBundle` — 显式依赖注入容器(7 个字段,含 `session_memory_backend`
5. `specs/agent-runtime.md` — 方案文档 5. `SessionMemory` — 会话级记忆(基于 `MemoryStore`context 间信息桥接)
6. `TaskAgent` — 任务型智能体(规划 → 执行 → 反馈)
7. `AgentBuilder` — 链式构造入口
8. `AgentError` — 统一错误类型(聚合 LlmError / ToolError / MemoryError
9. Hook 事件扩展(OnTurnStart / OnTurnEnd / OnPlanStepComplete
10. `docs/7-agent-runtime.md` — 方案设计文档
**依赖**Phase 0, 1, 2, 3(整合所有模块) **依赖**Phase 0, 1, 2, 3(整合所有模块)
**优先级**Could Have **优先级**Could Have
**预估规模**:约 600 行代码 **预估规模**:约 800 行代码
--- ---
@@ -232,6 +237,7 @@ graph BT
|-------|---------|------|--------|------| |-------|---------|------|--------|------|
| Agent 身份切换(角色轮换) | `agent` | 借鉴 OpenCode Tab 键切换 build/plan:同一 `AgentSession` 持有可热替换的 `Agent` 引用,切换时不重置消息历史,在末尾追加 `synthetic: true` 的状态变更消息。详见 `docs/note-opencode-agent-switching.md` §4 | P2 | v0.2 待评估 | | Agent 身份切换(角色轮换) | `agent` | 借鉴 OpenCode Tab 键切换 build/plan:同一 `AgentSession` 持有可热替换的 `Agent` 引用,切换时不重置消息历史,在末尾追加 `synthetic: true` 的状态变更消息。详见 `docs/note-opencode-agent-switching.md` §4 | P2 | v0.2 待评估 |
| System Prompt 多层动态拼接 | `agent/session` | 借鉴 OpenCode `request.ts:58-66`:拆分 `base_prompt + agent_prompt + env_context` 三层,`AgentSession::submit_turn` 每轮重算(不缓存),便于按 agent 类型动态切换 | P2 | v0.2 待评估 | | System Prompt 多层动态拼接 | `agent/session` | 借鉴 OpenCode `request.ts:58-66`:拆分 `base_prompt + agent_prompt + env_context` 三层,`AgentSession::submit_turn` 每轮重算(不缓存),便于按 agent 类型动态切换 | P2 | v0.2 待评估 |
| **多 Context 切换** | `agent` | **Phase 4 的 SessionMemory 数据结构已预留信息桥接通道,v0.2+ 在其上包装 `ContextManager` 实现完整的多 context 切换:创建/销毁/切换 context、通过 SessionMemory 桥接关键信息。详见 `docs/note-context-switch-design.md`** | P2 | v0.2 待评估 |
--- ---
@@ -241,15 +247,15 @@ graph BT
2. **并行可能性**Phase 0 和 Phase 1 可并行开展(无相互依赖),可加速早期交付 2. **并行可能性**Phase 0 和 Phase 1 可并行开展(无相互依赖),可加速早期交付
3. **MCP 协议复杂性**MCP 涉及协议握手、session 管理、长期连接,建议预留充足时间调研协议细节 3. **MCP 协议复杂性**MCP 涉及协议握手、session 管理、长期连接,建议预留充足时间调研协议细节
4. **Scope 蔓延风险**:当前 specs 只有 1 份文档,建议每个模块上线前都产出对应 spec,避免边实现边设计 4. **Scope 蔓延风险**:当前 specs 只有 1 份文档,建议每个模块上线前都产出对应 spec,避免边实现边设计
5. **Phase 4 抽象化边界**AG Core 定位为"支持库"而非"Agent 产品"Phase 4 需严格控制范围——只暴露 trait + 最小 reference impl,业务循环(多轮 turn 编排、记忆自动回写、Task 拆解策略)留给上层应用,避免与 OpenHarness / Hermes / OpenHuman 等已有 Agent 产品竞争实现细节。详细设计决策见 Phase 4 设计讨论记录(待 `docs/7-agent-runtime.md` 落盘) 5. **Phase 4 抽象化边界**AG Core 定位为"支持库"而非"Agent 产品"Phase 4 需严格控制范围——只暴露 trait + 最小 reference impl,业务循环(多轮 turn 编排、对话记忆自动回写、Task 拆解策略)留给上层应用`SessionMemory`(会话级记忆)已在范围内——它提供信息桥接通道但不实现 context 切换逻辑。多 context 切换管理延后至 v0.2+。详细设计决策见 `docs/7-agent-runtime.md`
6. **参考项目语言差异**OpenClaw / Hermes / OpenHarness 均为 Python/TypeScript 实现,OpenHuman 虽是 Rust + Tauri 但定位是桌面应用。借鉴时**只取架构模式**,不照搬具体实现(如 Pydantic 工具校验、SQLite Memory Tree、Node+Python 双进程等) 6. **参考项目语言差异**OpenClaw / Hermes / OpenHarness 均为 Python/TypeScript 实现,OpenHuman 虽是 Rust + Tauri 但定位是桌面应用。借鉴时**只取架构模式**,不照搬具体实现(如 Pydantic 工具校验、SQLite Memory Tree、Node+Python 双进程等)
--- ---
## 下一步行动 ## 下一步行动
1. **Phase 4 设计讨论收尾**Phase 4 范围已收窄为「`Agent` trait + `RuntimeBundle` 依赖注入容器 + `AgentSession` 实体/会话分离 + `TaskAgent` 双入口 + 记忆弱引用 + Hook 事件扩展 3 个」。决策记录已固化,待写 `docs/7-agent-runtime.md` 方案文档后启动编码实现 1. **Phase 4 实施方案**`docs/7-agent-runtime.md` 方案文档已完成(含 SessionMemory 扩展),下一步启动编码实现,按依赖顺序完成 13 个交付任务后翻转 Roadmap 状态
2. **Phase 4 方案文档**:将 Phase 4 设计决策沉淀为方案文档,沿用 `docs/4-prompt-engineering.md` / `5-tool-system.md` / `6-memory-system.md` 的 6 段式结构,文件名 `docs/7-agent-runtime.md` 2. **Context 切换备忘**`docs/note-context-switch-design.md` 记录了多 context 切换方案讨论,作为 v0.2+ 扩展项的输入
3. **参考项目调研沉淀**:已完成 OpenClaw / Hermes / OpenHuman / OpenHarness 横向调研,结果沉淀至 `docs/note-agent-harness-references.md`,作为 v0.2+ 扩展项的输入 3. **参考项目调研沉淀**:已完成 OpenClaw / Hermes / OpenHuman / OpenHarness 横向调研,结果沉淀至 `docs/note-agent-harness-references.md`,作为 v0.2+ 扩展项的输入
4. **Phase 3 备用设计就绪**`docs/note-knowledge-graph-design.md` 记录了 KnowledgeGraph、高级评分、RecallBased 淘汰等设计,v0.2+ 记忆扩展可直接参考 4. **Phase 3 备用设计就绪**`docs/note-knowledge-graph-design.md` 记录了 KnowledgeGraph、高级评分、RecallBased 淘汰等设计,v0.2+ 记忆扩展可直接参考