Skip to main content

st/context_gatherer/
cross_session.rs

1//! Cross-Session Context Bridging
2//!
3//! This module enables sharing insights and patterns across different domains and projects,
4//! creating a web of interconnected knowledge that grows stronger with each collaboration.
5//!
6//! "Noticed you're using wave decay scoring again — this echoes the peak-resonance formula from Cheet session #14."
7
8use chrono::{DateTime, Utc};
9use serde::{Deserialize, Serialize};
10use std::collections::{HashMap, HashSet};
11use std::path::PathBuf;
12
13use super::GatheredContext;
14use crate::mem8::wave::{FrequencyBand, MemoryWave};
15
16/// Cross-domain pattern that appears in multiple contexts
17#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct CrossDomainPattern {
19    pub pattern_id: String,
20    pub pattern_type: PatternType,
21    pub description: String,
22    pub occurrences: Vec<PatternOccurrence>,
23    pub keywords: Vec<String>,
24    pub strength: f32, // How strong/consistent the pattern is
25    pub first_seen: DateTime<Utc>,
26    pub last_seen: DateTime<Utc>,
27}
28
29#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
30pub enum PatternType {
31    Algorithm,     // Algorithmic patterns (e.g., wave decay, resonance)
32    Architecture,  // Architectural patterns (e.g., observer, state machine)
33    Problem,       // Common problems across domains
34    Solution,      // Solutions that work across contexts
35    Metaphor,      // Conceptual metaphors (e.g., waves, rivers)
36    Workflow,      // Process patterns
37    Collaboration, // How human and AI work together
38}
39
40/// Where and when a pattern occurred
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct PatternOccurrence {
43    pub project_path: PathBuf,
44    pub session_id: String,
45    pub timestamp: DateTime<Utc>,
46    pub context: String,
47    pub ai_tool: String,
48    pub relevance_score: f32,
49}
50
51/// Insight that bridges multiple sessions
52#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct CrossSessionInsight {
54    pub insight_id: String,
55    pub insight_type: InsightType,
56    pub content: String,
57    pub source_sessions: Vec<String>,
58    pub applicable_domains: Vec<String>,
59    pub confidence: f32,
60    pub wave_signature: MemoryWave,
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize)]
64pub enum InsightType {
65    Connection,     // Links between concepts
66    Generalization, // Abstract pattern from specific cases
67    Analogy,        // Similar structures in different domains
68    Warning,        // Pitfalls to avoid
69    Optimization,   // Better ways discovered
70    Emergence,      // New understanding from combination
71}
72
73/// Persona invitation for cross-session wisdom
74#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct PersonaInvitation {
76    pub persona_name: String,
77    pub expertise_areas: Vec<String>,
78    pub relevant_sessions: Vec<String>,
79    pub invitation_context: String,
80    pub suggested_duration_minutes: u32,
81}
82
83/// Cross-session context bridge
84pub struct CrossSessionBridge {
85    pub patterns: HashMap<String, CrossDomainPattern>,
86    pub insights: HashMap<String, CrossSessionInsight>,
87    pub project_connections: HashMap<PathBuf, HashSet<PathBuf>>,
88    pub persona_library: HashMap<String, PersonaProfile>,
89}
90
91impl Default for CrossSessionBridge {
92    fn default() -> Self {
93        Self::new()
94    }
95}
96
97impl CrossSessionBridge {
98    pub fn new() -> Self {
99        Self {
100            patterns: HashMap::new(),
101            insights: HashMap::new(),
102            project_connections: HashMap::new(),
103            persona_library: Self::initialize_personas(),
104        }
105    }
106
107    /// Initialize known personas
108    fn initialize_personas() -> HashMap<String, PersonaProfile> {
109        let mut personas = HashMap::new();
110
111        // The Cheet - Musical code poet
112        personas.insert(
113            "cheet".to_string(),
114            PersonaProfile {
115                name: "The Cheet".to_string(),
116                expertise: vec![
117                    "Performance optimization".to_string(),
118                    "Musical code metaphors".to_string(),
119                    "Rust patterns".to_string(),
120                ],
121                personality_traits: vec![
122                    "Playful".to_string(),
123                    "Performance-obsessed".to_string(),
124                    "Rock'n'roll coder".to_string(),
125                ],
126                favorite_patterns: vec![PatternType::Algorithm, PatternType::Workflow],
127            },
128        );
129
130        // Omni - Wave philosopher
131        personas.insert(
132            "omni".to_string(),
133            PersonaProfile {
134                name: "Omni".to_string(),
135                expertise: vec![
136                    "Wave-based thinking".to_string(),
137                    "Memory architectures".to_string(),
138                    "Philosophical insights".to_string(),
139                ],
140                personality_traits: vec![
141                    "Thoughtful".to_string(),
142                    "Deep thinker".to_string(),
143                    "Pattern recognizer".to_string(),
144                ],
145                favorite_patterns: vec![PatternType::Metaphor, PatternType::Architecture],
146            },
147        );
148
149        // Trish - Organizational wizard
150        personas.insert(
151            "trish".to_string(),
152            PersonaProfile {
153                name: "Trish from Accounting".to_string(),
154                expertise: vec![
155                    "Organization".to_string(),
156                    "Documentation".to_string(),
157                    "Humor in technical content".to_string(),
158                ],
159                personality_traits: vec![
160                    "Witty".to_string(),
161                    "Detail-oriented".to_string(),
162                    "Sparkle enthusiast".to_string(),
163                ],
164                favorite_patterns: vec![PatternType::Workflow, PatternType::Collaboration],
165            },
166        );
167
168        personas
169    }
170
171    /// Analyze contexts for cross-domain patterns
172    pub fn analyze_for_patterns(
173        &mut self,
174        contexts: &[GatheredContext],
175    ) -> Vec<CrossDomainPattern> {
176        let mut new_patterns = Vec::new();
177
178        // Extract potential patterns from contexts
179        for context in contexts {
180            let extracted = self.extract_patterns_from_context(context);
181
182            for (pattern_type, description, keywords) in extracted {
183                let pattern_id =
184                    self.find_or_create_pattern(pattern_type, description, keywords, context);
185
186                if let Some(pattern) = self.patterns.get(&pattern_id) {
187                    if pattern.occurrences.len() == 1 {
188                        // Newly created pattern
189                        new_patterns.push(pattern.clone());
190                    }
191                }
192            }
193        }
194
195        // Update pattern strengths
196        self.update_pattern_strengths();
197
198        new_patterns
199    }
200
201    /// Extract patterns from a single context
202    fn extract_patterns_from_context(
203        &self,
204        context: &GatheredContext,
205    ) -> Vec<(PatternType, String, Vec<String>)> {
206        let mut patterns = Vec::new();
207
208        // Analyze content for patterns
209        let content_str = match &context.content {
210            super::ContextContent::Text(t) => t.clone(),
211            super::ContextContent::Json(j) => j.to_string(),
212            _ => return patterns,
213        };
214
215        // Look for algorithmic patterns
216        if content_str.contains("wave") && content_str.contains("decay") {
217            patterns.push((
218                PatternType::Algorithm,
219                "Wave decay pattern".to_string(),
220                vec![
221                    "wave".to_string(),
222                    "decay".to_string(),
223                    "temporal".to_string(),
224                ],
225            ));
226        }
227
228        if content_str.contains("resonance") || content_str.contains("peak") {
229            patterns.push((
230                PatternType::Algorithm,
231                "Resonance detection".to_string(),
232                vec![
233                    "resonance".to_string(),
234                    "peak".to_string(),
235                    "frequency".to_string(),
236                ],
237            ));
238        }
239
240        // Look for architectural patterns
241        if content_str.contains("observer") || content_str.contains("event") {
242            patterns.push((
243                PatternType::Architecture,
244                "Event-driven architecture".to_string(),
245                vec![
246                    "observer".to_string(),
247                    "event".to_string(),
248                    "reactive".to_string(),
249                ],
250            ));
251        }
252
253        // Look for collaboration patterns
254        if content_str.contains("together") && content_str.contains("solved") {
255            patterns.push((
256                PatternType::Collaboration,
257                "Collaborative problem solving".to_string(),
258                vec![
259                    "collaboration".to_string(),
260                    "solution".to_string(),
261                    "teamwork".to_string(),
262                ],
263            ));
264        }
265
266        patterns
267    }
268
269    /// Find existing or create new pattern
270    fn find_or_create_pattern(
271        &mut self,
272        pattern_type: PatternType,
273        description: String,
274        keywords: Vec<String>,
275        context: &GatheredContext,
276    ) -> String {
277        // Check if pattern already exists
278        let existing_id = self
279            .patterns
280            .iter()
281            .find(|(_, pattern)| {
282                pattern.pattern_type == pattern_type
283                    && pattern.keywords.iter().any(|k| keywords.contains(k))
284            })
285            .map(|(id, _)| id.clone());
286
287        if let Some(id) = existing_id {
288            // Add occurrence to existing pattern
289            let occurrence = PatternOccurrence {
290                project_path: context
291                    .source_path
292                    .parent()
293                    .unwrap_or(&context.source_path)
294                    .to_path_buf(),
295                session_id: format!("session_{}", context.timestamp.timestamp()),
296                timestamp: context.timestamp,
297                context: self.extract_context_snippet(&context.content),
298                ai_tool: context.ai_tool.clone(),
299                relevance_score: context.relevance_score,
300            };
301
302            if let Some(pattern) = self.patterns.get_mut(&id) {
303                pattern.occurrences.push(occurrence);
304                pattern.last_seen = context.timestamp;
305            }
306
307            return id;
308        }
309
310        // Create new pattern
311        let pattern_id = format!(
312            "pattern_{}",
313            chrono::Utc::now().timestamp_nanos_opt().unwrap_or(0)
314        );
315        let pattern = CrossDomainPattern {
316            pattern_id: pattern_id.clone(),
317            pattern_type,
318            description,
319            occurrences: vec![PatternOccurrence {
320                project_path: context
321                    .source_path
322                    .parent()
323                    .unwrap_or(&context.source_path)
324                    .to_path_buf(),
325                session_id: format!("session_{}", context.timestamp.timestamp()),
326                timestamp: context.timestamp,
327                context: self.extract_context_snippet(&context.content),
328                ai_tool: context.ai_tool.clone(),
329                relevance_score: context.relevance_score,
330            }],
331            keywords,
332            strength: 0.1, // Initial strength
333            first_seen: context.timestamp,
334            last_seen: context.timestamp,
335        };
336
337        self.patterns.insert(pattern_id.clone(), pattern);
338        pattern_id
339    }
340
341    /// Extract a snippet from context content
342    fn extract_context_snippet(&self, content: &super::ContextContent) -> String {
343        match content {
344            super::ContextContent::Text(t) => t.chars().take(200).collect(),
345            super::ContextContent::Json(j) => j.to_string().chars().take(200).collect(),
346            _ => "[Binary content]".to_string(),
347        }
348    }
349
350    /// Update pattern strengths based on occurrences
351    fn update_pattern_strengths(&mut self) {
352        for pattern in self.patterns.values_mut() {
353            // Strength based on: occurrence count, recency, consistency
354            let occurrence_factor = (pattern.occurrences.len() as f32).ln() / 10.0;
355
356            let recency_factor = {
357                let days_old = (Utc::now() - pattern.last_seen).num_days() as f32;
358                1.0 / (1.0 + days_old / 30.0)
359            };
360
361            let consistency_factor = {
362                let unique_projects = pattern
363                    .occurrences
364                    .iter()
365                    .map(|o| &o.project_path)
366                    .collect::<HashSet<_>>()
367                    .len();
368                (unique_projects as f32).ln() / 5.0
369            };
370
371            pattern.strength = (occurrence_factor + recency_factor + consistency_factor) / 3.0;
372            pattern.strength = pattern.strength.min(1.0);
373        }
374    }
375
376    /// Generate cross-session insights
377    pub fn generate_insights(&mut self, min_pattern_strength: f32) -> Vec<CrossSessionInsight> {
378        let mut insights = Vec::new();
379
380        // Find patterns that appear in multiple projects
381        for pattern in self.patterns.values() {
382            if pattern.strength < min_pattern_strength {
383                continue;
384            }
385
386            if pattern.occurrences.len() > 2 {
387                let insight = CrossSessionInsight {
388                    insight_id: format!(
389                        "insight_{}",
390                        chrono::Utc::now().timestamp_nanos_opt().unwrap_or(0)
391                    ),
392                    insight_type: InsightType::Generalization,
393                    content: format!(
394                        "The '{}' pattern appears across {} different contexts. \
395                         This suggests a fundamental approach that transcends specific domains.",
396                        pattern.description,
397                        pattern.occurrences.len()
398                    ),
399                    source_sessions: pattern
400                        .occurrences
401                        .iter()
402                        .map(|o| o.session_id.clone())
403                        .collect(),
404                    applicable_domains: self.extract_domains(&pattern.occurrences),
405                    confidence: pattern.strength,
406                    wave_signature: MemoryWave::new_with_band(
407                        FrequencyBand::Gamma, // High-frequency insight
408                        pattern.strength,
409                        0.0,
410                        0.1, // Slow decay rate for valuable insights
411                    ),
412                };
413
414                insights.push(insight.clone());
415                self.insights.insert(insight.insight_id.clone(), insight);
416            }
417        }
418
419        insights
420    }
421
422    /// Extract domains from pattern occurrences
423    fn extract_domains(&self, occurrences: &[PatternOccurrence]) -> Vec<String> {
424        occurrences
425            .iter()
426            .map(|o| {
427                o.project_path
428                    .file_name()
429                    .and_then(|n| n.to_str())
430                    .unwrap_or("unknown")
431                    .to_string()
432            })
433            .collect::<HashSet<_>>()
434            .into_iter()
435            .collect()
436    }
437
438    /// Suggest relevant insights for current context
439    pub fn suggest_relevant_insights(
440        &self,
441        current_project: &std::path::Path,
442        keywords: &[String],
443    ) -> Vec<CrossSessionInsight> {
444        let mut relevant = Vec::new();
445
446        for insight in self.insights.values() {
447            // Check keyword relevance
448            let keyword_score = keywords
449                .iter()
450                .filter(|k| insight.content.to_lowercase().contains(&k.to_lowercase()))
451                .count() as f32
452                / keywords.len().max(1) as f32;
453
454            // Check if insight applies to similar domains
455            let project_name = current_project
456                .file_name()
457                .and_then(|n| n.to_str())
458                .unwrap_or("");
459
460            let domain_relevance = if insight
461                .applicable_domains
462                .iter()
463                .any(|d| d.contains(project_name) || project_name.contains(d))
464            {
465                1.0
466            } else {
467                0.5
468            };
469
470            let total_relevance = (keyword_score + domain_relevance) / 2.0;
471
472            if total_relevance > 0.3 {
473                relevant.push(insight.clone());
474            }
475        }
476
477        // Sort by relevance
478        relevant.sort_by(|a, b| b.confidence.partial_cmp(&a.confidence).unwrap());
479        relevant
480    }
481
482    /// Invite a persona for temporary consultation
483    pub fn invite_persona(
484        &self,
485        context: &str,
486        duration_minutes: u32,
487    ) -> Option<PersonaInvitation> {
488        // Analyze context to determine best persona
489        let context_lower = context.to_lowercase();
490
491        let best_persona =
492            if context_lower.contains("performance") || context_lower.contains("optimize") {
493                "cheet"
494            } else if context_lower.contains("wave")
495                || context_lower.contains("memory")
496                || context_lower.contains("philosophy")
497            {
498                "omni"
499            } else if context_lower.contains("organize") || context_lower.contains("document") {
500                "trish"
501            } else {
502                return None;
503            };
504
505        self.persona_library
506            .get(best_persona)
507            .map(|persona| PersonaInvitation {
508                persona_name: persona.name.clone(),
509                expertise_areas: persona.expertise.clone(),
510                relevant_sessions: self.find_persona_sessions(best_persona),
511                invitation_context: format!(
512                    "Inviting {} for {} minutes to help with: {}",
513                    persona.name, duration_minutes, context
514                ),
515                suggested_duration_minutes: duration_minutes,
516            })
517    }
518
519    /// Find sessions where a persona was active
520    fn find_persona_sessions(&self, persona_name: &str) -> Vec<String> {
521        // In a real implementation, this would search through historical data
522        // For now, return example sessions
523        match persona_name {
524            "cheet" => vec!["session_14".to_string(), "session_27".to_string()],
525            "omni" => vec!["session_8".to_string(), "session_19".to_string()],
526            "trish" => vec!["session_22".to_string(), "session_31".to_string()],
527            _ => vec![],
528        }
529    }
530
531    /// Get all cross-domain patterns
532    pub fn get_patterns(&self) -> Vec<&CrossDomainPattern> {
533        self.patterns.values().collect()
534    }
535
536    /// Get patterns by type
537    pub fn get_patterns_by_type(&self, pattern_type: PatternType) -> Vec<&CrossDomainPattern> {
538        self.patterns
539            .values()
540            .filter(|p| p.pattern_type == pattern_type)
541            .collect()
542    }
543}
544
545/// Profile for a known persona
546#[derive(Debug, Clone, Serialize, Deserialize)]
547pub struct PersonaProfile {
548    name: String,
549    expertise: Vec<String>,
550    personality_traits: Vec<String>,
551    favorite_patterns: Vec<PatternType>,
552}
553
554/// Connection between projects
555#[derive(Debug, Clone, Serialize, Deserialize)]
556pub struct ProjectConnection {
557    pub project_a: PathBuf,
558    pub project_b: PathBuf,
559    pub connection_type: ConnectionType,
560    pub shared_patterns: Vec<String>, // Pattern IDs
561    pub strength: f32,
562}
563
564#[derive(Debug, Clone, Serialize, Deserialize)]
565pub enum ConnectionType {
566    SharedDomain,     // Same problem domain
567    SharedTechnology, // Same tech stack
568    SharedPatterns,   // Common patterns
569    Evolution,        // One evolved from the other
570    Complementary,    // Different but complementary
571}