fix(memory): 跨会话记忆断裂修复 — profile_store连接+双数据库统一+诊断日志
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
根因: 3个断裂点 1. profile_store未连接: create_middleware_chain()中GrowthIntegration未设置 UserProfileStore, 导致extract_combined()的profile_signals被静默丢弃 2. 双数据库不一致: UserProfileStore写入data.db, agent_get读取memories.db, 两库隔离导致UserProfile永远读不到 3. 缺少关键日志: 提取/存储/检索链路无info级别日志, 问题难以诊断 修复: - create_middleware_chain()中添加 with_profile_store(memory.pool()) - agent_get改为使用kernel.memory()而非viking_commands::get_storage() - Kernel暴露memory()方法返回Arc<MemoryStore> - growth.rs增强日志: 存储成功/失败/提取详情/profile更新数 验证: Tauri端E2E测试通过 - 会话A发送消息 → 提取6记忆+4 profile signals → 存储成功 - 新会话B发送消息 → Injected memories → LLM回复提及之前话题 - 管家Tab显示: 用户画像(医疗/健康)+近期话题+53条记忆分组
This commit is contained in:
@@ -365,17 +365,6 @@ impl Kernel {
|
||||
chain.register(Arc::new(mw));
|
||||
}
|
||||
|
||||
// Data masking middleware — DISABLED for desktop single-user scenario.
|
||||
// The regex-based approach over-matches common Chinese text (e.g. "有一家公司"
|
||||
// gets masked as a company entity). Response unmask was also missing.
|
||||
// Re-enable when NLP-based entity detection is available.
|
||||
// {
|
||||
// use std::sync::Arc;
|
||||
// let masker = Arc::new(zclaw_runtime::middleware::data_masking::DataMasker::new());
|
||||
// let mw = zclaw_runtime::middleware::data_masking::DataMaskingMiddleware::new(masker);
|
||||
// chain.register(Arc::new(mw));
|
||||
// }
|
||||
|
||||
// Growth integration — cached to avoid recreating empty scorer per request
|
||||
let growth = {
|
||||
let mut cached = self.growth.lock().expect("growth lock");
|
||||
@@ -388,6 +377,12 @@ impl Kernel {
|
||||
if let Some(ref embed_client) = self.embedding_client {
|
||||
g.configure_embedding(embed_client.clone());
|
||||
}
|
||||
// Bridge UserProfileStore so extract_combined() can persist profile signals
|
||||
{
|
||||
let profile_store = zclaw_memory::UserProfileStore::new(self.memory.pool());
|
||||
g = g.with_profile_store(std::sync::Arc::new(profile_store));
|
||||
tracing::info!("[Kernel] UserProfileStore bridged to GrowthIntegration");
|
||||
}
|
||||
*cached = Some(std::sync::Arc::new(g));
|
||||
}
|
||||
cached.as_ref().expect("growth present").clone()
|
||||
@@ -567,6 +562,11 @@ impl Kernel {
|
||||
self.viking.clone()
|
||||
}
|
||||
|
||||
/// Get a reference to the shared MemoryStore
|
||||
pub fn memory(&self) -> Arc<MemoryStore> {
|
||||
self.memory.clone()
|
||||
}
|
||||
|
||||
/// Set the LLM extraction driver for the Growth system.
|
||||
///
|
||||
/// Required for `MemoryMiddleware` to extract memories from conversations
|
||||
|
||||
@@ -330,15 +330,43 @@ impl GrowthIntegration {
|
||||
&& combined.experiences.is_empty()
|
||||
&& !combined.profile_signals.has_any_signal()
|
||||
{
|
||||
tracing::debug!(
|
||||
"[GrowthIntegration] Combined extraction produced nothing for agent {}",
|
||||
agent_id
|
||||
);
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let mem_count = combined.memories.len();
|
||||
tracing::info!(
|
||||
"[GrowthIntegration] Combined extraction for agent {}: {} memories, {} experiences, {} profile signals",
|
||||
agent_id,
|
||||
mem_count,
|
||||
combined.experiences.len(),
|
||||
combined.profile_signals.signal_count()
|
||||
);
|
||||
|
||||
// Store raw memories
|
||||
self.extractor
|
||||
match self.extractor
|
||||
.store_memories(&agent_id.to_string(), &combined.memories)
|
||||
.await?;
|
||||
.await
|
||||
{
|
||||
Ok(stored) => {
|
||||
tracing::info!(
|
||||
"[GrowthIntegration] Stored {} memories for agent {}",
|
||||
stored,
|
||||
agent_id
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!(
|
||||
"[GrowthIntegration] Failed to store memories for agent {}: {}",
|
||||
agent_id,
|
||||
e
|
||||
);
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Track learning event
|
||||
self.tracker
|
||||
@@ -362,6 +390,11 @@ impl GrowthIntegration {
|
||||
// Update user profile from extraction signals (L1 enhancement)
|
||||
if let Some(profile_store) = &self.profile_store {
|
||||
let updates = self.profile_updater.collect_updates(&combined);
|
||||
tracing::info!(
|
||||
"[GrowthIntegration] Applying {} profile updates for agent {}",
|
||||
updates.len(),
|
||||
agent_id
|
||||
);
|
||||
let user_id = agent_id.to_string();
|
||||
for update in updates {
|
||||
let result = match update.kind {
|
||||
|
||||
@@ -185,16 +185,23 @@ pub async fn agent_get(
|
||||
|
||||
let mut info = kernel.get_agent(&id);
|
||||
|
||||
// Extend with UserProfile if available
|
||||
// Extend with UserProfile if available (reads from same MemoryStore pool as middleware writes to)
|
||||
if let Some(ref mut agent_info) = info {
|
||||
if let Ok(storage) = crate::viking_commands::get_storage().await {
|
||||
let profile_store = zclaw_memory::UserProfileStore::new(storage.pool().clone());
|
||||
if let Ok(Some(profile)) = profile_store.get(&agent_id).await {
|
||||
let memory_store = kernel.memory();
|
||||
let profile_store = zclaw_memory::UserProfileStore::new(memory_store.pool());
|
||||
match profile_store.get(&agent_id).await {
|
||||
Ok(Some(profile)) => {
|
||||
match serde_json::to_value(&profile) {
|
||||
Ok(val) => agent_info.user_profile = Some(val),
|
||||
Err(e) => tracing::warn!("[agent_get] Failed to serialize UserProfile: {}", e),
|
||||
}
|
||||
}
|
||||
Ok(None) => {
|
||||
tracing::debug!("[agent_get] No UserProfile found for agent {}", agent_id);
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::warn!("[agent_get] Failed to read UserProfile: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user