diff --git a/crates/zclaw-growth/src/retriever.rs b/crates/zclaw-growth/src/retriever.rs index 7d1a110..814f48e 100644 --- a/crates/zclaw-growth/src/retriever.rs +++ b/crates/zclaw-growth/src/retriever.rs @@ -331,6 +331,36 @@ impl MemoryRetriever { self.config.experience_budget, ).await?; + // Fallback: if no results for this agent, search across ALL agents + // for identity-critical info (user name, workplace, preferences) + if preferences.is_empty() && knowledge.is_empty() && experience.is_empty() { + tracing::info!( + "[MemoryRetriever] No memories for agent {}, falling back to global scope", + agent_str + ); + let global_prefs = self.retrieve_by_scope_any_agent( + MemoryType::Preference, + self.config.max_results_per_type, + self.config.preference_budget, + ).await?; + let global_knowledge = self.retrieve_by_scope_any_agent( + MemoryType::Knowledge, + self.config.max_results_per_type, + self.config.knowledge_budget, + ).await?; + let total: usize = global_prefs.iter() + .chain(global_knowledge.iter()) + .map(|m| m.estimated_tokens()) + .sum(); + + return Ok(RetrievalResult { + preferences: global_prefs, + knowledge: global_knowledge, + experience, + total_tokens: total, + }); + } + let total_tokens = preferences.iter() .chain(knowledge.iter()) .chain(experience.iter()) @@ -352,6 +382,43 @@ impl MemoryRetriever { }) } + /// Retrieve memories across ALL agents for a given type. + /// Used as fallback when agent-scoped retrieval returns nothing for identity recall. + async fn retrieve_by_scope_any_agent( + &self, + memory_type: MemoryType, + max_results: usize, + token_budget: usize, + ) -> Result> { + // Match any agent by using only the type suffix as scope pattern + let scope_pattern = format!("/{}", memory_type); + let options = FindOptions { + scope: None, // No scope filter — search all agents + limit: Some(max_results * 3), + min_similarity: None, + }; + let entries = self.viking.find("", options).await?; + // Filter to only matching memory type + let mut filtered: Vec = entries + .into_iter() + .filter(|e| e.uri.contains(&scope_pattern) || e.memory_type == memory_type) + .collect(); + filtered.sort_by(|a, b| { + b.importance.cmp(&a.importance) + .then_with(|| b.access_count.cmp(&a.access_count)) + }); + let mut result = Vec::new(); + let mut used_tokens = 0; + for entry in filtered { + let tokens = entry.estimated_tokens(); + if used_tokens + tokens > token_budget { break; } + used_tokens += tokens; + result.push(entry); + if result.len() >= max_results { break; } + } + Ok(result) + } + /// Retrieve memories by scope only (no text search). /// Returns entries sorted by importance and recency, limited by budget. async fn retrieve_by_scope(