diff --git a/docs/roadmap.md b/docs/roadmap.md
index fdbdbc8..6a38530 100644
--- a/docs/roadmap.md
+++ b/docs/roadmap.md
@@ -1,13 +1,13 @@
# AG Core Roadmap
> 定稿日期:2026-05-11
-> 最后更新:2026-06-11(Phase 4b 编码实施完成;Phase 4c 仍待启动)
+> 最后更新:2026-06-11(Phase 4c 编码实施完成)
## 愿景
AG Core 定位为构建 AI 智能体的底层工具箱,通过模块化、可插拔的架构,提供大模型调用、提示词工程、工具系统、记忆检索四大核心能力,支持快速组合出符合业务需求的智能体应用。
-**当前状态**:Phase 0 基础设施已全部完成,Phase 1 提示词工程已全部完成,Phase 2 工具系统已全部完成,Phase 3 记忆系统已全部完成,Phase 4a 核心胶水层已全部完成,Phase 4b 任务执行已全部完成(113 个测试通过,0 警告),Phase 4c 待启动。
+**当前状态**:Phase 0 基础设施已全部完成,Phase 1 提示词工程已全部完成,Phase 2 工具系统已全部完成,Phase 3 记忆系统已全部完成,Phase 4a 核心胶水层已全部完成,Phase 4b 任务执行已全部完成,Phase 4c 会话级记忆已全部完成(116 个测试通过,0 警告)。
---
@@ -190,9 +190,9 @@ AG Core 定位为构建 AI 智能体的底层工具箱,通过模块化、可
**前置条件**:Phase 4a 已完成(可与 Phase 4b 并行)。
**交付物**:
-1. `SessionMemory` struct — 基于 `MemoryStore`,按 session_id namespace 隔离
-2. `RuntimeBundle` + `AgentBuilder` 扩展 `session_memory_backend` 字段
-3. `AgentSession` 替换内联 HashMap 为完整 `SessionMemory`
+1. ✅ `SessionMemory` struct — 基于 `MemoryStore`,按 session_id namespace 隔离
+2. ✅ `RuntimeBundle` + `AgentBuilder` 扩展 `session_memory_backend` 字段
+3. ✅ `AgentSession` 替换内联 HashMap 为完整 `SessionMemory`
**依赖**:Phase 4a(Phase 3 MemoryStore)
@@ -200,7 +200,15 @@ AG Core 定位为构建 AI 智能体的底层工具箱,通过模块化、可
**预估规模**:约 115 行代码(增量)
-**状态**:⏳ 待 Phase 4a 完成后启动
+**实际新增**:
+- 新增文件 1 个(agent/session_memory.rs)
+- 修改文件 4 个(agent/runtime.rs +5 行;agent/builder.rs +10 行;agent/session.rs +30 行;agent.rs +2 行)
+- 新增代码约 180 行(含测试;纯实现约 100 行)
+- 新增内联测试 3 个;全量测试 113 → 116(0 失败)
+- clippy 0 警告
+- 无新增外部依赖
+
+**状态**:✅ Phase 4c 全部交付物已完成
---
@@ -214,7 +222,7 @@ graph BT
P3["Phase 3: Memory System
MemoryStore
ConversationMemory
KnowledgeStore"]:::done
P4a["Phase 4a: Core Glue
AgentSession
RuntimeBundle
Plan/Step 纯数据"]:::done
P4b["Phase 4b: Task Execution
TaskAgent
PlanParser
JsonPlanParser"]:::done
- P4c["Phase 4c: Session Memory
SessionMemory"]:::pending
+ P4c["Phase 4c: Session Memory
SessionMemory"]:::done
P1 --> P0
P2 --> P0
@@ -317,7 +325,7 @@ graph BT
## 下一步行动
-1. **Phase 4c 启动评估**:Phase 4a + 4b 已交付(113 测试通过,0 clippy 警告)。可启动 Phase 4c(会话级记忆:SessionMemory + RuntimeBundle/Builder 扩展 + AgentSession 接入)
+1. **Phase 4c 已完成**:Phase 4a + 4b + 4c 已交付(116 测试通过,0 clippy 警告)。可启动 v0.2+ 扩展评估(如多 Context 切换、Multi-Agent 协同等)
2. **Context 切换备忘**:`docs/note-context-switch-design.md` 记录了多 context 切换方案讨论,作为 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+ 记忆扩展可直接参考
@@ -329,4 +337,4 @@ graph BT
- ✅ Phase 3 Memory System — 全部交付物已完成
- ✅ Phase 4a Core Glue — 全部交付物已完成
- ✅ Phase 4b Task Execution — 全部交付物已完成
-- ⏳ Phase 4c Session Memory — 依赖 4a
+- ✅ Phase 4c Session Memory — 全部交付物已完成
diff --git a/src/agent.rs b/src/agent.rs
index df0d5e8..fede02b 100644
--- a/src/agent.rs
+++ b/src/agent.rs
@@ -14,6 +14,7 @@ pub mod builder;
pub mod error;
pub mod runtime;
pub mod session;
+pub mod session_memory;
pub mod task;
// 重导出公共 API(按使用频度排序)
@@ -22,5 +23,6 @@ pub use builder::AgentBuilder;
pub use error::AgentError;
pub use runtime::{AgentConfig, RuntimeBundle};
pub use session::AgentSession;
+pub use session_memory::SessionMemory;
pub use task::{Plan, PlanParser, Step, StepStatus, TaskAgent};
pub use task::JsonPlanParser;
diff --git a/src/agent/builder.rs b/src/agent/builder.rs
index d8d2b02..9f2ae70 100644
--- a/src/agent/builder.rs
+++ b/src/agent/builder.rs
@@ -34,6 +34,7 @@ pub struct AgentBuilder {
hook_executor: Option>,
memory_store: Option>,
retriever: Option>,
+ session_memory_backend: Option>,
config: Option,
}
@@ -73,6 +74,12 @@ impl AgentBuilder {
self
}
+ /// 设置 SessionMemory 后端(选填,不传则 `AgentSession` 内部用 `InMemoryStore` 兜底)。
+ pub fn session_memory_backend(mut self, s: Arc) -> Self {
+ self.session_memory_backend = Some(s);
+ self
+ }
+
/// 整体覆盖 `AgentConfig`(选填,不传则用默认值)。
pub fn config(mut self, c: AgentConfig) -> Self {
self.config = Some(c);
@@ -102,6 +109,7 @@ impl AgentBuilder {
hook_executor,
self.memory_store,
self.retriever,
+ self.session_memory_backend,
config,
))
}
diff --git a/src/agent/runtime.rs b/src/agent/runtime.rs
index cdc0fa1..bd20f33 100644
--- a/src/agent/runtime.rs
+++ b/src/agent/runtime.rs
@@ -68,6 +68,11 @@ pub struct RuntimeBundle {
/// 传入时可在 `submit_turn` 内部将检索能力作为工具暴露给 LLM。
pub retriever: Option>,
+ /// SessionMemory 后端(选填)。
+ /// 传入时 `SessionMemory` 使用该后端(支持跨进程共享);
+ /// 不传时 `AgentSession` 内部自动创建 `InMemoryStore` 作为进程级隔离的后端。
+ pub session_memory_backend: Option>,
+
/// 运行时配置。
pub config: AgentConfig,
}
@@ -79,6 +84,10 @@ impl std::fmt::Debug for RuntimeBundle {
.field("tool_names", &self.tool_registry.list_tools())
.field("has_memory_store", &self.memory_store.is_some())
.field("has_retriever", &self.retriever.is_some())
+ .field(
+ "has_session_memory_backend",
+ &self.session_memory_backend.is_some(),
+ )
.field("config", &self.config)
.finish()
}
@@ -96,6 +105,7 @@ impl RuntimeBundle {
hook_executor: Arc,
memory_store: Option>,
retriever: Option>,
+ session_memory_backend: Option>,
config: AgentConfig,
) -> Self {
Self {
@@ -104,6 +114,7 @@ impl RuntimeBundle {
hook_executor,
memory_store,
retriever,
+ session_memory_backend,
config,
}
}
diff --git a/src/agent/session.rs b/src/agent/session.rs
index 843c5cd..cd62657 100644
--- a/src/agent/session.rs
+++ b/src/agent/session.rs
@@ -7,22 +7,23 @@
//! - **不做业务循环**:多轮策略、错误重试、记忆回写由上层应用或具体 `TaskAgent` 决定
//! - **不持有 ConversationMemory**:上层可独立 new 一个 `ConversationMemory`,在合适的时机调 `add_message`
-use std::collections::HashMap;
use std::sync::Arc;
use crate::agent::agent::Agent;
use crate::agent::error::AgentError;
use crate::agent::runtime::RuntimeBundle;
+use crate::agent::session_memory::SessionMemory;
use crate::llm::cycle::{CostTracker, CycleConfig, LlmCycle};
use crate::llm::hooks::{HookContext, HookEvent};
use crate::llm::types::ChatResponse;
+use crate::memory::store::InMemoryStore;
/// Agent 会话实例。
///
/// 同一 `Agent` 可被多个 `AgentSession` 复用(不同 session_id 互不干扰)。
/// `submit_turn` 一次只跑一轮 LLM 调用(含自动 tool 循环)。
///
-/// **不实现 `Clone`**:session 持有累计 `turn_index` / `cost_so_far` / `session_data`,
+/// **不实现 `Clone`**:session 持有累计 `turn_index` / `cost_so_far` / `session_memory`,
/// 共享这些状态需要显式 sync 语义;如果上层需要并发访问,自己用 `Arc>` 包装。
pub struct AgentSession {
/// 会话 ID(由调用方指定,用于日志/追踪/记忆关联)。
@@ -32,8 +33,8 @@ pub struct AgentSession {
bundle: Arc,
turn_index: u32,
cost_so_far: CostTracker,
- /// 会话级键值数据(Phase 4a 用内联 HashMap;Phase 4c 替换为 `SessionMemory`)。
- session_data: HashMap,
+ /// 会话级记忆(Phase 4c 替换内联 HashMap)。
+ pub session_memory: SessionMemory,
}
impl std::fmt::Debug for AgentSession {
@@ -43,7 +44,7 @@ impl std::fmt::Debug for AgentSession {
.field("agent", &self.agent.name())
.field("turn_index", &self.turn_index)
.field("cost_so_far", &self.cost_so_far.total())
- .field("session_data_keys", &self.session_data.keys().collect::>())
+ .field("session_memory", &"")
.finish()
}
}
@@ -57,13 +58,19 @@ impl AgentSession {
session_id: impl Into,
bundle: Arc,
) -> Self {
+ let session_id_str = session_id.into();
+ let backend = bundle
+ .session_memory_backend
+ .clone()
+ .unwrap_or_else(|| Arc::new(InMemoryStore::new()));
+ let session_memory = SessionMemory::new(backend, &session_id_str);
Self {
- session_id: session_id.into(),
+ session_id: session_id_str,
agent,
bundle,
turn_index: 0,
cost_so_far: CostTracker::default(),
- session_data: HashMap::new(),
+ session_memory,
}
}
@@ -77,19 +84,23 @@ impl AgentSession {
&self.cost_so_far
}
- /// 会话级数据快照引用。
- pub fn session_data(&self) -> &HashMap {
- &self.session_data
+ /// 会话级记忆引用。
+ pub fn session_memory(&self) -> &SessionMemory {
+ &self.session_memory
}
/// 写入一条会话级数据(覆盖同名 key)。
- pub fn set_session_data(&mut self, key: impl Into, value: impl Into) {
- self.session_data.insert(key.into(), value.into());
+ pub async fn set_session_data(
+ &mut self,
+ key: impl Into,
+ value: impl Into,
+ ) -> Result<(), AgentError> {
+ self.session_memory.set(&key.into(), &value.into()).await
}
/// 读取一条会话级数据。
- pub fn get_session_data(&self, key: &str) -> Option<&str> {
- self.session_data.get(key).map(String::as_str)
+ pub async fn get_session_data(&self, key: &str) -> Result