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:
+123
-34
@@ -27,6 +27,7 @@ Phase 4 目标是提供一个**薄胶水层 + 一组 trait 抽象**,让上层
|
||||
- **`AgentSession` struct** — 智能体的"会话"实例(绑定 session_id + 状态)
|
||||
- **`TaskAgent` trait** — 任务型智能体的"规划/执行"抽象
|
||||
- **`RuntimeBundle`** — 显式依赖注入容器,集中管理 provider/registry/hook/memory 等依赖
|
||||
- **`SessionMemory`** — 会话级记忆,用于 context 间的信息桥接(基于 `MemoryStore` 后端)
|
||||
- **`AgentBuilder`** — 链式构造入口
|
||||
- **`AgentError`** — 统一错误类型,聚合 LlmError / ToolError / MemoryError
|
||||
|
||||
@@ -42,6 +43,7 @@ Phase 4 严格遵循以下原则,所有范围决策都基于这些原则推导
|
||||
| **实体/会话分离** | 同一角色可被多 session 复用 | `Agent` + `AgentSession` 两层模型 |
|
||||
| **记忆弱引用** | 记忆是"被动能力",不内嵌循环 | `memory_store: Option<Arc<dyn MemoryStore>>` 弱引用 |
|
||||
| **业务可注入** | Plan 拆解是业务能力,不在 core 库实现 | 暴露 `PlanParser` trait,上层注入 |
|
||||
| **会话级记忆** | session 内共享、context 间桥接,不是持久层也不是对话历史 | `SessionMemory` 基于 `MemoryStore`,按 session_id 命名空间隔离 |
|
||||
| **借鉴不照搬** | 4 个参考项目均非 Rust 实现 | 只取架构模式,不抄实现细节 |
|
||||
|
||||
### 1.4 与已完成的 Phase 关系
|
||||
@@ -190,6 +192,7 @@ pub struct RuntimeBundle {
|
||||
pub hook_executor: Arc<HookExecutor>,
|
||||
pub memory_store: Option<Arc<dyn MemoryStore>>, // 弱引用
|
||||
pub retriever: Option<Arc<MemoryRetriever>>, // 弱引用
|
||||
pub session_memory_backend: Option<Arc<dyn MemoryStore>>, // SessionMemory 后端(选填)
|
||||
pub config: AgentConfig,
|
||||
}
|
||||
```
|
||||
@@ -198,6 +201,7 @@ pub struct RuntimeBundle {
|
||||
- 所有运行时依赖**显式打包**(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
|
||||
@@ -205,10 +209,11 @@ pub struct RuntimeBundle {
|
||||
```rust
|
||||
pub struct AgentSession {
|
||||
pub session_id: String,
|
||||
pub agent_name: String,
|
||||
pub agent: Arc<dyn Agent>,
|
||||
bundle: Arc<RuntimeBundle>,
|
||||
turn_index: u32,
|
||||
cost_so_far: CostTracker,
|
||||
session_memory: SessionMemory,
|
||||
}
|
||||
|
||||
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" 只演示**最常见**的对话场景
|
||||
- 业务循环(多轮策略、错误重试、记忆回写时机)由上层应用或具体的 `TaskAgent` 实现决定
|
||||
- `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 memory_store(self, m: Arc<dyn MemoryStore>) -> 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 build(self) -> Result<RuntimeBundle, AgentError>;
|
||||
}
|
||||
@@ -332,7 +340,74 @@ impl AgentBuilder {
|
||||
**设计意图**:
|
||||
- `AgentBuilder` 是**唯一**的 `RuntimeBundle` 构造入口
|
||||
- 必填字段在 `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 状态机
|
||||
|
||||
@@ -440,24 +515,28 @@ pub struct HookContext {
|
||||
| `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. 实施计划
|
||||
|
||||
### 4.1 文件清单
|
||||
|
||||
#### 新增文件(7 个)
|
||||
#### 新增文件(8 个)
|
||||
|
||||
```
|
||||
src/agent.rs # 模块根 + pub use 重导出
|
||||
src/agent/agent.rs # Agent trait
|
||||
src/agent/runtime.rs # RuntimeBundle + AgentConfig
|
||||
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/builder.rs # AgentBuilder
|
||||
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` 通过;现有测试不挂 |
|
||||
| 2 | 新建 `agent/error.rs` 定义 `AgentError` | `src/agent/error.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` 通过 |
|
||||
| 5 | 新建 `agent/builder.rs` 定义 `AgentBuilder` | `src/agent/builder.rs` | `cargo build` 通过 |
|
||||
| 6 | 新建 `agent/session.rs` 定义 `AgentSession` + `submit_turn` | `src/agent/session.rs` | `cargo build` 通过 |
|
||||
| 7 | 新建 `agent/task.rs` 定义 `TaskAgent` + `Plan` / `Step` / `PlanParser` / `JsonPlanParser` | `src/agent/task.rs` | `cargo build` 通过 |
|
||||
| 8 | 新建 `src/agent.rs` 模块根 + `pub use` 重导出 | `src/agent.rs` | `cargo build` 通过 |
|
||||
| 9 | 修改 `lib.rs` 导出 `pub mod agent;` | `src/lib.rs` | `cargo build` 通过 |
|
||||
| 10 | 编写 2-3 个烟雾测试 | `src/agent/*.rs` 内联 | `cargo test` 通过 |
|
||||
| 11 | 更新 `roadmap.md` 状态翻转 | `docs/roadmap.md` | 文档 review |
|
||||
| 12 | 完整 `cargo test` 跑全量回归 | — | 所有已有测试不挂 |
|
||||
| 4 | 新建 `agent/runtime.rs` 定义 `RuntimeBundle` + `AgentConfig`(含 `session_memory_backend` 字段) | `src/agent/runtime.rs` | `cargo build` 通过 |
|
||||
| 5 | 新建 `agent/session_memory.rs` 定义 `SessionMemory` | `src/agent/session_memory.rs` | `cargo build` 通过 |
|
||||
| 6 | 新建 `agent/builder.rs` 定义 `AgentBuilder`(含 `.session_memory_backend()` 方法) | `src/agent/builder.rs` | `cargo build` 通过 |
|
||||
| 7 | 新建 `agent/session.rs` 定义 `AgentSession` + `submit_turn`(持 `Arc<dyn Agent>` + `SessionMemory`) | `src/agent/session.rs` | `cargo build` 通过 |
|
||||
| 8 | 新建 `agent/task.rs` 定义 `TaskAgent` + `Plan` / `Step` / `PlanParser` / `JsonPlanParser` | `src/agent/task.rs` | `cargo build` 通过 |
|
||||
| 9 | 新建 `src/agent.rs` 模块根 + `pub use` 重导出 | `src/agent.rs` | `cargo build` 通过 |
|
||||
| 10 | 修改 `lib.rs` 导出 `pub mod agent;` | `src/lib.rs` | `cargo build` 通过 |
|
||||
| 11 | 编写 3-4 个烟雾测试(含 SessionMemory) | `src/agent/*.rs` 内联 | `cargo test` 通过 |
|
||||
| 12 | 更新 `roadmap.md` 状态翻转 | `docs/roadmap.md` | 文档 review |
|
||||
| 13 | 完整 `cargo test` 跑全量回归 | — | 所有已有测试不挂 |
|
||||
|
||||
### 4.3 依赖关系
|
||||
|
||||
@@ -504,30 +584,33 @@ hooks.rs (1) ──┐
|
||||
│ │
|
||||
│ ▼
|
||||
│ 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 (7)
|
||||
└─────────────────► agent/task.rs (8)
|
||||
│
|
||||
▼
|
||||
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 预估工作量
|
||||
@@ -535,11 +618,11 @@ hooks.rs (1) ──┐
|
||||
| 阶段 | 行数 | 说明 |
|
||||
|------|------|------|
|
||||
| 1(hooks 扩展) | ~15 | 3 个变体 + 2 个字段 + 文档 |
|
||||
| 2-7(7 个 agent 文件) | ~600 | 含 import + trait + struct + impl + 文档 |
|
||||
| 8-9(lib.rs + agent.rs 模块根) | ~20 | 主要是 pub use 重导出 |
|
||||
| 10(烟雾测试) | ~100 | 2-3 个测试 |
|
||||
| 11(roadmap 同步) | ~5 | 状态翻转一行 |
|
||||
| **合计** | **~740** | 与 `note-agent-runtime-design.md` §6 预估一致 |
|
||||
| 2-8(8 个 agent 文件) | ~640 | 含 import + trait + struct + impl + 文档(+ session_memory.rs ~40 行) |
|
||||
| 9-10(lib.rs + agent.rs 模块根) | ~20 | 主要是 pub use 重导出 |
|
||||
| 11(烟雾测试) | ~120 | 3-4 个测试(含 SessionMemory) |
|
||||
| 12(roadmap 同步) | ~5 | 状态翻转一行 |
|
||||
| **合计** | **~800** | 从 ~740 增加 ~60 行(SessionMemory ~40 + 测试 ~20) |
|
||||
|
||||
## 5. 风险评估
|
||||
|
||||
@@ -595,30 +678,34 @@ hooks.rs (1) ──┐
|
||||
- [ ] `cargo build --release` 0 错误 0 警告(clippy)
|
||||
- [ ] `cargo test` 所有 Phase 0-3 已有测试 + Phase 4 新增测试全部通过
|
||||
- [ ] `cargo doc --no-deps` 所有公开 API 有 `///` 文档注释
|
||||
- [ ] 新增代码 700-750 行(含测试 + 文档注释),与 §4.4 预估一致
|
||||
- [ ] 新增代码 750-850 行(含测试 + 文档注释),与 §4.4 预估一致
|
||||
- [ ] `src/lib.rs` 新增一行 `pub mod agent;`
|
||||
- [ ] `src/llm/hooks.rs` 仅追加(不修改现有变体或字段)
|
||||
|
||||
### 6.2 接口验收
|
||||
|
||||
- [ ] 7 个新文件全部存在(§4.1)
|
||||
- [ ] 8 个新文件全部存在(§4.1)
|
||||
- [ ] `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 触发
|
||||
- [ ] `SessionMemory` 包含 5 个方法(set / get / snapshot / remove / clear),基于 `MemoryStore` 实现
|
||||
- [ ] `SessionMemory::snapshot` 返回 `<session-context>` 标签包裹的格式化文本
|
||||
- [ ] `TaskAgent` 提供双入口 `run` + `execute_plan`
|
||||
- [ ] `JsonPlanParser` 实现约 20 行,基于 `serde_json`
|
||||
- [ ] `AgentError` 聚合 8 个变体,含 `is_recoverable()`
|
||||
- [ ] `AgentBuilder` 提供 6 个 setter + `build()` 校验
|
||||
- [ ] `AgentBuilder` 提供 7 个 setter(含 `.session_memory_backend()`)+ `build()` 校验
|
||||
- [ ] `HookEvent` 新增 3 个变体:`OnTurnStart` / `OnTurnEnd` / `OnPlanStepComplete`
|
||||
- [ ] `HookContext` 新增 2 个 `Option` 字段:`turn_index` / `plan_step_index`
|
||||
|
||||
### 6.3 测试验收
|
||||
|
||||
至少 2-3 个烟雾测试通过:
|
||||
至少 3-4 个烟雾测试通过:
|
||||
|
||||
- [ ] **测试 1**:`Agent` trait 可实现 + `RuntimeBundle` 可构造(builder 链式调用)
|
||||
- [ ] **测试 2**:`AgentSession::submit_turn` 跑通 mock provider(Phase 0 `MockProvider` 模式)
|
||||
- [ ] **测试 3(可选)**:`JsonPlanParser::parse` 能解析合法 JSON,失败时返回 `AgentError::PlanParse`
|
||||
- [ ] **测试 3**:`SessionMemory` set / get / snapshot 基本读写(基于 `InMemoryStore`)
|
||||
- [ ] **测试 4(可选)**:`JsonPlanParser::parse` 能解析合法 JSON,失败时返回 `AgentError::PlanParse`
|
||||
|
||||
### 6.4 文档验收
|
||||
|
||||
@@ -630,9 +717,11 @@ hooks.rs (1) ──┐
|
||||
### 6.5 行为验收(人工 review)
|
||||
|
||||
- [ ] `AgentSession::submit_turn` 不持有 `ConversationMemory`(grep 验证无 `use crate::memory::ConversationMemory`)
|
||||
- [ ] `AgentSession` 持 `Arc<dyn Agent>`,可从 agent 获取 `system_prompt()` / `tool_definitions()`
|
||||
- [ ] `RuntimeBundle::new` 当 `retriever` 为 `Some` 时自动注册 `"retrieve"` tool
|
||||
- [ ] `AgentBuilder::build` 在必填字段缺失时返回 `AgentError::Config`(而非 panic)
|
||||
- [ ] `AgentError::is_recoverable()` 对各变体返回正确分类
|
||||
- [ ] `SessionMemory` 在 `session_memory_backend` 未传入时自动使用 `InMemoryStore` 兜底
|
||||
|
||||
### 6.6 风险验收
|
||||
|
||||
@@ -644,4 +733,4 @@ hooks.rs (1) ──┐
|
||||
|
||||
## 7. 一句话总结
|
||||
|
||||
> **Phase 4 = 1 个 trait(Agent)+ 1 个容器(RuntimeBundle)+ 1 个会话(AgentSession)+ 1 个任务抽象(TaskAgent)+ 4 个辅助组件(Builder / Error / PlanParser / Hook 扩展),约 740 行代码,把 Phase 0-3 已有能力"装配"成"智能体"的概念。**
|
||||
> **Phase 4 = 1 个 trait(Agent)+ 1 个容器(RuntimeBundle)+ 1 个会话(AgentSession, 持 Arc\<dyn Agent\>)+ 1 个会话级记忆(SessionMemory)+ 1 个任务抽象(TaskAgent)+ 4 个辅助组件(Builder / Error / PlanParser / Hook 扩展),约 800 行代码,把 Phase 0-3 已有能力"装配"成"智能体"的概念。**
|
||||
|
||||
Reference in New Issue
Block a user