# ZCLAW Agent 成长功能设计规格 > **版本**: 1.0 > **日期**: 2026-03-26 > **状态**: 已批准 > **作者**: Claude + 用户协作设计 --- ## 一、概述 ### 1.1 背景 ZCLAW 当前的学习系统存在**前后端分离问题**: - 前端有完整的学习逻辑 (`active-learning.ts`, `memory-extractor.ts`) - 但这些学习结果存储在 localStorage/IndexedDB - 后端执行系统 (Rust) 无法获取这些学习结果 - 导致 Agent 无法真正"成长" ### 1.2 目标 设计并实现完整的 Agent 成长功能,让 Agent 像个人管家一样: - **记住偏好**:用户的沟通风格、回复格式、语言偏好等 - **积累知识**:从对话中学习用户相关事实、领域知识、经验教训 - **掌握技能**:记录技能/Hand 的使用模式,优化执行效率 ### 1.3 需求决策 | 维度 | 决策 | 理由 | |------|------|------| | 成长维度 | 偏好 + 知识 + 技能(全部) | 完整的管家式成长体验 | | 整合策略 | 完全后端化,Rust 重写 | 避免前后端数据隔离问题 | | 存储架构 | OpenViking 作为完整记忆层 | 利用现有的 L0/L1/L2 分层 + 语义搜索 | | 学习触发 | 对话后自动 + 用户显式触发 | 平衡自动化和可控性 | | 行为影响 | 智能检索 + Token 预算控制 | 解决长期使用后数据量过大的问题 | --- ## 二、系统架构 ### 2.1 整体架构图 ``` ┌─────────────────────────────────────────────────────────────────┐ │ ZCLAW Agent 成长系统 │ ├─────────────────────────────────────────────────────────────────┤ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ zclaw-growth (新 Crate) │ │ │ │ ────────────────────────────────────────────────────── │ │ │ │ • MemoryExtractor - 从对话中提取偏好/知识/经验 │ │ │ │ • MemoryRetriever - 语义检索相关记忆 │ │ │ │ • PromptInjector - 动态构建 system_prompt │ │ │ │ • GrowthTracker - 追踪成长指标和演化 │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ OpenViking (记忆层) │ │ │ │ ────────────────────────────────────────────────────── │ │ │ │ URI 结构: │ │ │ │ • agent://{id}/preferences/{category} - 用户偏好 │ │ │ │ • agent://{id}/knowledge/{domain} - 知识积累 │ │ │ │ • agent://{id}/experience/{skill} - 技能经验 │ │ │ │ • agent://{id}/sessions/{sid} - 对话历史 │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ zclaw-runtime (修改) │ │ │ │ ────────────────────────────────────────────────────── │ │ │ │ AgentLoop 集成: │ │ │ │ 1. 对话前 → MemoryRetriever 检索相关记忆 │ │ │ │ 2. 构建请求 → PromptInjector 注入记忆 │ │ │ │ 3. 对话后 → MemoryExtractor 提取新记忆 │ │ │ └─────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────┘ ``` ### 2.2 数据流 ``` 用户输入 │ ▼ ┌─────────────────────────────────────────┐ │ 1. 记忆检索 │ │ • 用当前输入查询 OpenViking │ │ • 召回 Top-5 相关记忆 │ │ • Token 预算控制 (500 tokens) │ └─────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────┐ │ 2. Prompt 构建 │ │ system_prompt = base + │ │ "## 用户偏好\n" + preferences + │ │ "## 相关知识\n" + knowledge │ └─────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────┐ │ 3. LLM 对话 │ │ • 正常的 AgentLoop 执行 │ └─────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────┐ │ 4. 记忆提取 (对话后) │ │ • 分析对话内容 │ │ • 提取偏好/知识/经验 │ │ • 写入 OpenViking (L0/L1/L2) │ └─────────────────────────────────────────┘ ``` ### 2.3 OpenViking URI 结构 ``` agent://{agent_id}/ ├── preferences/ │ ├── communication-style # 沟通风格偏好 │ ├── response-format # 回复格式偏好 │ ├── language-preference # 语言偏好 │ └── topic-interests # 主题兴趣 ├── knowledge/ │ ├── user-facts # 用户相关事实 │ ├── domain-knowledge # 领域知识 │ └── lessons-learned # 经验教训 ├── experience/ │ ├── skill-{id} # 技能使用经验 │ └── hand-{id} # Hand 使用经验 └── sessions/ └── {session_id}/ # 对话历史 ├── raw # 原始对话 (L0) ├── summary # 摘要 (L1) └── keywords # 关键词 (L2) ``` --- ## 三、详细设计 ### 3.1 新 Crate 结构 ``` crates/zclaw-growth/ ├── Cargo.toml ├── src/ │ ├── lib.rs # 入口和公共 API │ ├── extractor.rs # 记忆提取器 │ ├── retriever.rs # 记忆检索器 │ ├── injector.rs # Prompt 注入器 │ ├── tracker.rs # 成长追踪器 │ ├── types.rs # 类型定义 │ └── viking_adapter.rs # OpenViking 适配器 ``` ### 3.2 核心类型定义 ```rust // types.rs /// 记忆类型 #[derive(Debug, Clone, Serialize, Deserialize)] pub enum MemoryType { Preference, // 偏好 Knowledge, // 知识 Experience, // 经验 Session, // 对话 } /// 记忆条目 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct MemoryEntry { pub uri: String, pub memory_type: MemoryType, pub content: String, pub keywords: Vec, pub importance: u8, // 1-10 pub access_count: u32, pub created_at: DateTime, pub last_accessed: DateTime, } /// 提取的记忆 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ExtractedMemory { pub memory_type: MemoryType, pub category: String, pub content: String, pub confidence: f32, // 提取置信度 0.0-1.0 pub source_session: SessionId, } /// 检索配置 #[derive(Debug, Clone)] pub struct RetrievalConfig { pub max_tokens: usize, // 总 Token 预算,默认 500 pub preference_budget: usize, // 偏好 Token 预算,默认 200 pub knowledge_budget: usize, // 知识 Token 预算,默认 200 pub experience_budget: usize, // 经验 Token 预算,默认 100 pub min_similarity: f32, // 最小相似度阈值,默认 0.7 pub max_results: usize, // 最大返回数量,默认 10 } impl Default for RetrievalConfig { fn default() -> Self { Self { max_tokens: 500, preference_budget: 200, knowledge_budget: 200, experience_budget: 100, min_similarity: 0.7, max_results: 10, } } } /// 检索结果 #[derive(Debug, Clone, Default)] pub struct RetrievalResult { pub preferences: Vec, pub knowledge: Vec, pub experience: Vec, pub total_tokens: usize, } /// 提取配置 #[derive(Debug, Clone)] pub struct ExtractionConfig { pub extract_preferences: bool, // 是否提取偏好,默认 true pub extract_knowledge: bool, // 是否提取知识,默认 true pub extract_experience: bool, // 是否提取经验,默认 true pub min_confidence: f32, // 最小置信度阈值,默认 0.6 } impl Default for ExtractionConfig { fn default() -> Self { Self { extract_preferences: true, extract_knowledge: true, extract_experience: true, min_confidence: 0.6, } } } ``` ### 3.3 MemoryExtractor 接口 ```rust // extractor.rs /// 记忆提取器 - 从对话中提取有价值的记忆 pub struct MemoryExtractor { llm_driver: Arc, } impl MemoryExtractor { pub fn new(llm_driver: Arc) -> Self { Self { llm_driver } } /// 从对话中提取记忆 pub async fn extract( &self, messages: &[Message], config: &ExtractionConfig, ) -> Result> { let mut results = Vec::new(); if config.extract_preferences { let prefs = self.extract_preferences(messages).await?; results.extend(prefs); } if config.extract_knowledge { let knowledge = self.extract_knowledge(messages).await?; results.extend(knowledge); } if config.extract_experience { let experience = self.extract_experience(messages).await?; results.extend(experience); } // 过滤低置信度结果 results.retain(|m| m.confidence >= config.min_confidence); Ok(results) } /// 提取偏好 async fn extract_preferences( &self, messages: &[Message], ) -> Result> { // 使用 LLM 分析对话,提取用户偏好 // 例如:用户喜欢简洁的回复、用户偏好中文等 // ... } /// 提取知识 async fn extract_knowledge( &self, messages: &[Message], ) -> Result> { // 使用 LLM 分析对话,提取有价值的事实和知识 // 例如:用户是程序员、用户在做一个 Rust 项目等 // ... } /// 提取经验 async fn extract_experience( &self, messages: &[Message], ) -> Result> { // 分析对话中的技能/工具使用,提取经验教训 // 例如:某个技能执行失败、某个工具效果很好等 // ... } } ``` ### 3.4 MemoryRetriever 接口 ```rust // retriever.rs /// 记忆检索器 - 从 OpenViking 检索相关记忆 pub struct MemoryRetriever { viking: Arc, } impl MemoryRetriever { pub fn new(viking: Arc) -> Self { Self { viking } } /// 检索与当前输入相关的记忆 pub async fn retrieve( &self, agent_id: &AgentId, query: &str, config: &RetrievalConfig, ) -> Result { // 1. 检索偏好 let preferences = self.retrieve_by_type( agent_id, MemoryType::Preference, query, config.max_results, ).await?; // 2. 检索知识 let knowledge = self.retrieve_by_type( agent_id, MemoryType::Knowledge, query, config.max_results, ).await?; // 3. 检索经验 let experience = self.retrieve_by_type( agent_id, MemoryType::Experience, query, config.max_results / 2, ).await?; // 4. 计算 Token 使用 let total_tokens = self.estimate_tokens(&preferences, &knowledge, &experience); Ok(RetrievalResult { preferences, knowledge, experience, total_tokens, }) } /// 按类型检索 async fn retrieve_by_type( &self, agent_id: &AgentId, memory_type: MemoryType, query: &str, limit: usize, ) -> Result> { let scope = format!("agent://{}/{}", agent_id, memory_type_to_scope(&memory_type)); let results = self.viking.find(query, FindOptions { scope: Some(scope), limit: Some(limit), level: Some("L1"), // 使用摘要级别 }).await?; // 转换为 MemoryEntry // ... } fn estimate_tokens( &self, preferences: &[MemoryEntry], knowledge: &[MemoryEntry], experience: &[MemoryEntry], ) -> usize { // 简单估算:约 4 字符 = 1 token let total_chars: usize = preferences.iter() .chain(knowledge.iter()) .chain(experience.iter()) .map(|m| m.content.len()) .sum(); total_chars / 4 } } fn memory_type_to_scope(ty: &MemoryType) -> &'static str { match ty { MemoryType::Preference => "preferences", MemoryType::Knowledge => "knowledge", MemoryType::Experience => "experience", MemoryType::Session => "sessions", } } ``` ### 3.5 PromptInjector 接口 ```rust // injector.rs /// Prompt 注入器 - 将记忆动态注入 system_prompt pub struct PromptInjector { config: RetrievalConfig, } impl PromptInjector { pub fn new(config: RetrievalConfig) -> Self { Self { config } } /// 构建增强的 system_prompt pub fn inject( &self, base_prompt: &str, memories: &RetrievalResult, ) -> String { let mut result = base_prompt.to_string(); // 注入偏好 if !memories.preferences.is_empty() { let prefs_section = self.format_preferences( &memories.preferences, self.config.preference_budget, ); result.push_str("\n\n## 用户偏好\n"); result.push_str(&prefs_section); } // 注入知识 if !memories.knowledge.is_empty() { let knowledge_section = self.format_knowledge( &memories.knowledge, self.config.knowledge_budget, ); result.push_str("\n\n## 相关知识\n"); result.push_str(&knowledge_section); } // 注入经验 if !memories.experience.is_empty() { let exp_section = self.format_experience( &memories.experience, self.config.experience_budget, ); result.push_str("\n\n## 经验参考\n"); result.push_str(&exp_section); } result } fn format_preferences(&self, entries: &[MemoryEntry], budget: usize) -> String { let mut result = String::new(); let mut used = 0; for entry in entries.iter().take(5) { // 最多 5 条偏好 let line = format!("- {}\n", entry.content); let line_tokens = line.len() / 4; if used + line_tokens > budget { break; } result.push_str(&line); used += line_tokens; } result } fn format_knowledge(&self, entries: &[MemoryEntry], budget: usize) -> String { // 类似 format_preferences // ... } fn format_experience(&self, entries: &[MemoryEntry], budget: usize) -> String { // 类似 format_preferences // ... } } ``` ### 3.6 AgentLoop 集成 修改 `crates/zclaw-runtime/src/loop_runner.rs`: ```rust pub struct AgentLoop { agent_id: AgentId, driver: Arc, tools: ToolRegistry, memory: Arc, model: String, system_prompt: Option, max_tokens: u32, temperature: f32, skill_executor: Option>, // 新增:成长系统 memory_retriever: Option>, memory_extractor: Option>, prompt_injector: Option, growth_enabled: bool, } impl AgentLoop { pub async fn run(&self, session_id: SessionId, input: String) -> Result { // 1. 检索相关记忆 (新增) let memories = if self.growth_enabled { if let Some(retriever) = &self.memory_retriever { retriever.retrieve( &self.agent_id, &input, &RetrievalConfig::default(), ).await.unwrap_or_default() } else { RetrievalResult::default() } } else { RetrievalResult::default() }; // 2. 构建增强的 system_prompt (修改) let enhanced_prompt = if self.growth_enabled { if let Some(injector) = &self.prompt_injector { injector.inject( self.system_prompt.as_deref().unwrap_or(""), &memories, ) } else { self.system_prompt.clone().unwrap_or_default() } } else { self.system_prompt.clone().unwrap_or_default() }; // 3. 添加用户消息 let user_message = Message::user(input); self.memory.append_message(&session_id, &user_message).await?; // 4. 获取完整上下文 let mut messages = self.memory.get_messages(&session_id).await?; // 5. 执行 LLM 循环 (使用增强的 prompt) let mut iterations = 0; let max_iterations = 10; loop { // ... 现有的 LLM 循环逻辑 // 使用 enhanced_prompt 作为 system message } // 6. 对话结束后提取记忆 (新增) if self.growth_enabled { if let Some(extractor) = &self.memory_extractor { let final_messages = self.memory.get_messages(&session_id).await?; let extracted = extractor.extract( &final_messages, &ExtractionConfig::default(), ).await?; // 写入 OpenViking for memory in extracted { // 通过 VikingAdapter 写入 } } } Ok(result) } } ``` --- ## 四、前端变化 ### 4.1 新增组件 ```typescript // desktop/src/components/GrowthPanel.tsx interface GrowthPanelProps { agentId: string; } export function GrowthPanel({ agentId }: GrowthPanelProps) { // 功能: // - 显示 Agent 成长指标 // - 手动触发学习 // - 查看/编辑记忆 // - 配置学习参数 } ``` ### 4.2 Store 扩展 ```typescript // desktop/src/store/agentStore.ts interface AgentState { // ... 现有字段 // 新增:成长相关 growthEnabled: boolean; memoryStats: { totalMemories: number; preferences: number; knowledge: number; experience: number; lastLearningTime: string | null; }; } ``` ### 4.3 Tauri Commands ```rust // desktop/src-tauri/src/growth_commands.rs #[tauri::command] async fn get_memory_stats(agent_id: String) -> Result; #[tauri::command] async fn trigger_learning(agent_id: String, session_id: String) -> Result, String>; #[tauri::command] async fn get_memories(agent_id: String, memory_type: Option) -> Result, String>; #[tauri::command] async fn delete_memory(agent_id: String, uri: String) -> Result<(), String>; #[tauri::command] async fn update_memory(agent_id: String, uri: String, content: String) -> Result<(), String>; ``` --- ## 五、执行计划 ### 5.1 Phase 1: Crate 骨架 (1-2 天) - [ ] 创建 `crates/zclaw-growth/` 目录结构 - [ ] 定义 `types.rs` 核心类型 - [ ] 设置 `Cargo.toml` 依赖 ### 5.2 Phase 2: 检索系统 (2-3 天) - [ ] 实现 `VikingAdapter` 封装 - [ ] 实现 `MemoryRetriever` - [ ] 单元测试 ### 5.3 Phase 3: 注入 + 集成 (2-3 天) - [ ] 实现 `PromptInjector` - [ ] 修改 `AgentLoop` 集成点 - [ ] 集成测试 ### 5.4 Phase 4: 提取系统 (3-4 天) - [ ] 实现 `MemoryExtractor` - [ ] 设计 LLM prompt 模板 - [ ] 测试提取质量 ### 5.5 Phase 5: 前端 UI (2-3 天) - [ ] 实现 `GrowthPanel` 组件 - [ ] 扩展 Agent Store - [ ] 添加 Tauri Commands ### 5.6 Phase 6: 测试 + 优化 (2-3 天) - [ ] 端到端测试 - [ ] 性能优化 - [ ] 文档完善 **总计**: 约 12-18 天 --- ## 六、关键文件路径 ### 核心类型 - `crates/zclaw-types/src/agent.rs` - AgentConfig - `crates/zclaw-types/src/message.rs` - Message - `crates/zclaw-types/src/id.rs` - AgentId, SessionId ### 存储层 - `crates/zclaw-memory/src/store.rs` - MemoryStore - `crates/zclaw-memory/src/schema.rs` - SQLite Schema ### 运行时 - `crates/zclaw-runtime/src/loop_runner.rs` - AgentLoop ### OpenViking 集成 - `desktop/src/lib/viking-client.ts` - 前端客户端 - `desktop/src-tauri/src/viking_commands.rs` - Tauri 命令 - `docs/features/03-context-database/00-openviking-integration.md` - 文档 ### 前端学习系统(将被后端化) - `desktop/src/lib/active-learning.ts` - `desktop/src/lib/memory-extractor.ts` - `desktop/src/store/activeLearningStore.ts` --- ## 七、风险与缓解 | 风险 | 影响 | 缓解措施 | |------|------|---------| | OpenViking 不可用 | 高 | 实现 LocalStorageAdapter 降级方案 | | 记忆提取质量低 | 中 | 可配置的置信度阈值 + 人工审核 | | Token 预算超限 | 中 | 严格的 Token 控制和截断 | | 前端学习数据丢失 | 高 | 提供迁移脚本导入旧数据 | --- ## 八、新会话执行指南 在新会话中执行此方案时,请: 1. **阅读本文档**:`docs/superpowers/specs/2026-03-26-agent-growth-design.md` 2. **参考计划文件**:`plans/crispy-spinning-reef.md`(包含更多分析细节) 3. **从 Phase 1 开始**:创建 zclaw-growth crate 骨架 4. **遵循设计**:严格按照本文档的接口定义实现 5. **保持沟通**:如有疑问,与用户确认后再修改设计