feat(engine): 实现 Agent 执行引擎(SessionManager + Checkpointer + SessionSnapshot)
Phase 17 主体交付:解决 v0.2 中 session 在变量里、无父子关系、无 checkpoint、
不可序列化的空白。
新增模块 src/engine/(5 文件,约 1300 行纯实现 + 21 个内联测试):
- error.rs(46 行)—— EngineError 枚举(6 变体)
- SessionNotFound / SessionAlreadyExists / CheckpointNotFound
- Memory(#[from] MemoryError) 透传(与 AgentError 风格一致)
- Serialization / Agent(#[from] AgentError)
- snapshot.rs(36 行)—— SessionSnapshot + SessionMemoryEntry
- 独立 struct 避开 Arc<dyn Agent> 不可序列化限制
- SessionMemoryEntry 保留 value/metadata/created_at 完整信息
- 所有字段 #[serde(default)] 宽松反序列化保证前向兼容
- checkpointer.rs(377 行)—— Time-travel 检查点管理器
- checkpoint() / rollback_load() / list_checkpoints() / delete_all() / latest_snapshot()
- 存储 key:ckpt:{session_id}:{ckpt_id}
- ckpt_id 纳秒+计数器(无外部依赖,ponytail)
- CkptMeta.created_at_nanos 字段确保同秒内精确降序
- 6 个内联测试覆盖 roundtrip / 不存在 ckpt / 降序排序 / delete_all 幂等 /
latest_snapshot / 跨 session 隔离
- session_manager.rs(906 行)—— SessionManager 会话树管理
- 内部 RwLock<HashMap> + Arc<tokio::sync::Mutex<AgentSession>> 双重锁
- create / create_child / get / recover / replace / children / parent /
destroy / submit_turn / submit_turn_stream / finalize_turn_stream 共 11 个公开方法
- SessionManagerConfig.auto_checkpoint 默认 true(同步写入 +
tracing::error! 失败不阻断主流程,不提供强持久化保证)
- 孤儿策略:destroy 不递归删除子 session;父被销毁后 parent() 返回 None
- create_child 限制:父 session 必须先 get/recover 到内存(bundle 不可序列化)
- 15 个内联测试覆盖 CRUD / recover / replace / 树形 / 孤儿 / auto_checkpoint 开关 /
序列化兼容性 / 幂等 / 10 并发创建
- mod.rs(20 行)—— 统一 pub use 重导出 EngineError / SessionManager /
SessionManagerConfig / Checkpointer / CkptMeta / SessionSnapshot /
SessionMemoryEntry
AgentSession 扩展(src/agent/session.rs,+152 行):
- to_snapshot() pub async —— 从 MemoryStore 拍平 session_memory 全量数据
- from_snapshot() pub fn Result —— 纯同步构造器(pending_memory_restore 暂存)
- restore_memory() pub async &mut self —— 写回持久层并清空 pending
- has_pending_memory_restore() —— 查询 pending 状态
- pub(crate) fn bundle() —— accessor 供 SessionManager::create_child 继承
SessionMemory 扩展(src/agent/session_memory.rs,+57 行):
- list_entries() —— 返回 Vec<(key, value, metadata, created_at_unix_secs)>
- set_with_meta() —— 保留 metadata/created_at 写入(供 restore_memory 完整恢复)
src/lib.rs —— pub mod engine 声明
examples/engine_demo.rs(+251 行):
端到端演示 create → submit_turn → checkpoint → list_checkpoints →
rollback_load → from_snapshot → restore_memory → replace → destroy 完整链路,
含 rollback 一致性 assert(turn_index 和 cost 恢复到 checkpoint 时刻)。
零新外部依赖(serde_json 已有)。全量 353 → 374(+21 新测试)。
clippy 0 警告,doc 0 warning,example exit 0。
Phase 17: Agent 执行引擎 — Step 2-7(合并提交)
This commit is contained in:
@@ -44,12 +44,37 @@ impl SessionMemory {
|
||||
}
|
||||
|
||||
/// 写入一条 key-value 条目(覆盖同名 key)。
|
||||
///
|
||||
/// **不保留 metadata 和 created_at** —— 写入时 metadata 为空 JSON `{}`,created_at 为 `now_utc()`。
|
||||
/// 若需保留这两个字段(如 checkpoint rollback),使用 [`Self::set_with_meta`]。
|
||||
pub async fn set(&self, key: &str, value: &str) -> Result<(), AgentError> {
|
||||
self.set_with_meta(key, value, serde_json::json!({}), None).await
|
||||
}
|
||||
|
||||
/// 写入一条 key-value 条目(含完整 metadata + created_at)。
|
||||
///
|
||||
/// Phase 17 新增:供 `AgentSession::restore_memory()` 使用,保证 checkpoint rollback 时
|
||||
/// 恢复完整的 session_memory 条目(包括原 metadata 和创建时间戳)。
|
||||
///
|
||||
/// - `metadata`: 通常为 `serde_json::Value`(快照中保留的 metadata JSON)
|
||||
/// - `created_at`: 快照中的原始时间戳(Unix 秒);若为 `None` 则用 `now_utc()`(默认行为)
|
||||
pub async fn set_with_meta(
|
||||
&self,
|
||||
key: &str,
|
||||
value: &str,
|
||||
metadata: serde_json::Value,
|
||||
created_at: Option<i64>,
|
||||
) -> Result<(), AgentError> {
|
||||
let created_at_dt = match created_at {
|
||||
Some(secs) => OffsetDateTime::from_unix_timestamp(secs)
|
||||
.unwrap_or_else(|_| OffsetDateTime::now_utc()),
|
||||
None => OffsetDateTime::now_utc(),
|
||||
};
|
||||
let item = MemoryItem {
|
||||
id: self.internal_key(key),
|
||||
content: value.to_string(),
|
||||
metadata: serde_json::json!({}),
|
||||
created_at: OffsetDateTime::now_utc(),
|
||||
metadata,
|
||||
created_at: created_at_dt,
|
||||
};
|
||||
self.store.save(item).await.map_err(AgentError::Memory)
|
||||
}
|
||||
@@ -103,6 +128,34 @@ impl SessionMemory {
|
||||
.map_err(AgentError::Memory)
|
||||
}
|
||||
|
||||
/// 列出当前 namespace 下所有条目(含完整 `MemoryItem`:value / metadata / created_at)。
|
||||
///
|
||||
/// Phase 17 新增:供 `AgentSession::to_snapshot()` 拍平 session_memory 时使用,
|
||||
/// 保留 metadata 和 created_at 时间戳(用 `set/get/remove` 三个 API 会丢字段)。
|
||||
///
|
||||
/// 返回 `Vec<(原始 key, value, metadata, created_at_unix_secs)>`,原始 key 已剥离 namespace 前缀。
|
||||
pub async fn list_entries(
|
||||
&self,
|
||||
) -> Result<Vec<(String, String, serde_json::Value, i64)>, AgentError> {
|
||||
let filter = MemoryFilter {
|
||||
prefix: Some(format!("{}:", self.namespace)),
|
||||
..Default::default()
|
||||
};
|
||||
let items = self.store.list(&filter).await.map_err(AgentError::Memory)?;
|
||||
let prefix_with_colon = format!("{}:", self.namespace);
|
||||
let mut out = Vec::with_capacity(items.len());
|
||||
for item in items {
|
||||
let key = item
|
||||
.id
|
||||
.strip_prefix(&prefix_with_colon)
|
||||
.unwrap_or(&item.id)
|
||||
.to_string();
|
||||
let created_at_unix = item.created_at.unix_timestamp();
|
||||
out.push((key, item.content, item.metadata, created_at_unix));
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// 清空当前 namespace 下所有条目。
|
||||
pub async fn clear(&self) -> Result<(), AgentError> {
|
||||
let filter = MemoryFilter {
|
||||
|
||||
Reference in New Issue
Block a user