Skip to main content

brainwires_seal/
knowledge_integration.rs

1//! SEAL + Knowledge System Integration
2//!
3//! This module bridges SEAL's entity-centric learning with the Knowledge System's
4//! behavioral truths (BKS) and personal facts (PKS), enabling bidirectional learning
5//! and context-aware entity resolution.
6//!
7//! ## Key Integration Points
8//!
9//! 1. **SEAL → PKS**: Entity resolutions trigger lookups of personal facts
10//! 2. **SEAL → BKS**: Query context triggers lookups of behavioral truths
11//! 3. **SEAL → BKS Promotion**: High-reliability patterns promoted to shared knowledge
12//! 4. **BKS → SEAL**: Behavioral truths loaded into SEAL's global memory
13//! 5. **Quality-Aware Context**: SEAL quality scores adjust retrieval thresholds
14//!
15//! ## Architecture
16//!
17//! ```text
18//! User Input
19//!     │
20//!     ▼
21//! ┌────────────────────────────────────────────┐
22//! │   SealKnowledgeCoordinator                 │
23//! │                                            │
24//! │   ┌─────────────────────────────────────┐ │
25//! │   │ SEAL Preprocessing                   │ │
26//! │   │ • Coreference: "it" → "main.rs"     │ │
27//! │   │ • Query extraction: S-expressions   │ │
28//! │   │ • Quality score: 0.0-1.0            │ │
29//! │   └──────────┬──────────────────────────┘ │
30//! │              │                             │
31//! │              ▼                             │
32//! │   ┌─────────────────────────────────────┐ │
33//! │   │ Knowledge Lookup (PARALLEL)         │ │
34//! │   │ • PKS: entity facts (main.rs)       │ │
35//! │   │ • BKS: behavioral truths (rust)     │ │
36//! │   └──────────┬──────────────────────────┘ │
37//! │              │                             │
38//! │              ▼                             │
39//! │   ┌─────────────────────────────────────┐ │
40//! │   │ Confidence Harmonization            │ │
41//! │   │ • Combine: SEAL + BKS + PKS         │ │
42//! │   │ • Adjust thresholds by quality      │ │
43//! │   └──────────┬──────────────────────────┘ │
44//! └──────────────┼──────────────────────────────┘
45//!                │
46//!                ▼
47//!     Enhanced Context → OrchestratorAgent
48//! ```
49
50use super::{QueryPattern, ResolvedReference, SealProcessingResult};
51use anyhow::Result;
52use brainwires_knowledge::knowledge::bks_pks::{
53    BehavioralKnowledgeCache, BehavioralTruth, PersonalKnowledgeCache, TruthCategory, TruthSource,
54};
55use std::sync::Arc;
56use tokio::sync::Mutex;
57
58/// Default minimum pattern reliability for BKS promotion (80% success rate).
59const DEFAULT_PATTERN_PROMOTION_THRESHOLD: f32 = 0.8;
60
61/// Configuration for SEAL + Knowledge integration
62#[derive(Debug, Clone)]
63pub struct IntegrationConfig {
64    /// Master toggle for the integration
65    pub enabled: bool,
66
67    /// Enable SEAL patterns → BKS promotion
68    pub seal_to_knowledge: bool,
69
70    /// Enable BKS truths → SEAL pattern loading
71    pub knowledge_to_seal: bool,
72
73    /// Minimum SEAL quality score to boost with BKS context (0.0-1.0)
74    /// Default: 0.7 (only high-quality SEAL results get BKS boost)
75    pub min_seal_quality_for_bks_boost: f32,
76
77    /// Minimum SEAL quality score to boost with PKS context (0.0-1.0)
78    /// Default: 0.5 (medium-quality SEAL results get PKS boost)
79    pub min_seal_quality_for_pks_boost: f32,
80
81    /// Minimum pattern reliability for BKS promotion (0.0-1.0)
82    /// Default: 0.8 (80% success rate required)
83    pub pattern_promotion_threshold: f32,
84
85    /// Minimum pattern uses before considering promotion
86    /// Default: 5 (need statistical significance)
87    pub min_pattern_uses: u32,
88
89    /// Cache BKS truths in SEAL's global memory
90    pub cache_bks_in_seal: bool,
91
92    /// Entity resolution strategy
93    pub entity_resolution_strategy: EntityResolutionStrategy,
94
95    /// Weight for SEAL quality in confidence harmonization (0.0-1.0)
96    /// Default: 0.5 (50% weight)
97    pub seal_weight: f32,
98
99    /// Weight for BKS confidence in harmonization (0.0-1.0)
100    /// Default: 0.3 (30% weight)
101    pub bks_weight: f32,
102
103    /// Weight for PKS confidence in harmonization (0.0-1.0)
104    /// Default: 0.2 (20% weight)
105    pub pks_weight: f32,
106}
107
108impl Default for IntegrationConfig {
109    fn default() -> Self {
110        Self {
111            enabled: true,
112            seal_to_knowledge: true,
113            knowledge_to_seal: true,
114            min_seal_quality_for_bks_boost: 0.7,
115            min_seal_quality_for_pks_boost: 0.5,
116            pattern_promotion_threshold: DEFAULT_PATTERN_PROMOTION_THRESHOLD,
117            min_pattern_uses: 5,
118            cache_bks_in_seal: true,
119            entity_resolution_strategy: EntityResolutionStrategy::Hybrid {
120                seal_weight: 0.6,
121                pks_weight: 0.4,
122            },
123            seal_weight: 0.5,
124            bks_weight: 0.3,
125            pks_weight: 0.2,
126        }
127    }
128}
129
130impl IntegrationConfig {
131    /// Create config with all features enabled (recommended)
132    pub fn full() -> Self {
133        Self::default()
134    }
135
136    /// Create config with only SEAL → Knowledge enabled (no BKS → SEAL loading)
137    pub fn seal_to_knowledge_only() -> Self {
138        Self {
139            knowledge_to_seal: false,
140            ..Self::default()
141        }
142    }
143
144    /// Create config with integration disabled
145    pub fn disabled() -> Self {
146        Self {
147            enabled: false,
148            ..Self::default()
149        }
150    }
151
152    /// Validate configuration
153    pub fn validate(&self) -> Result<()> {
154        if self.min_seal_quality_for_bks_boost < 0.0 || self.min_seal_quality_for_bks_boost > 1.0 {
155            anyhow::bail!("min_seal_quality_for_bks_boost must be between 0.0 and 1.0");
156        }
157
158        if self.pattern_promotion_threshold < 0.0 || self.pattern_promotion_threshold > 1.0 {
159            anyhow::bail!("pattern_promotion_threshold must be between 0.0 and 1.0");
160        }
161
162        let weight_sum = self.seal_weight + self.bks_weight + self.pks_weight;
163        if (weight_sum - 1.0).abs() > 0.01 {
164            anyhow::bail!(
165                "Confidence weights must sum to 1.0 (got: {:.2})",
166                weight_sum
167            );
168        }
169
170        Ok(())
171    }
172}
173
174/// Strategy for resolving entity references when both SEAL and PKS have candidates
175#[derive(Debug, Clone)]
176pub enum EntityResolutionStrategy {
177    /// Always prefer SEAL's resolution
178    SealFirst,
179
180    /// Always prefer PKS context-based resolution
181    PksContextFirst,
182
183    /// Weighted combination of SEAL and PKS confidence
184    Hybrid {
185        /// Weight given to SEAL confidence scores.
186        seal_weight: f32,
187        /// Weight given to PKS confidence scores.
188        pks_weight: f32,
189    },
190}
191
192/// Bridges SEAL's entity-centric learning with Knowledge System's behavioral truths
193pub struct SealKnowledgeCoordinator {
194    /// BKS cache for behavioral truths
195    bks_cache: Arc<Mutex<BehavioralKnowledgeCache>>,
196
197    /// PKS cache for personal facts
198    pks_cache: Arc<Mutex<PersonalKnowledgeCache>>,
199
200    /// Integration settings
201    config: IntegrationConfig,
202}
203
204impl SealKnowledgeCoordinator {
205    /// Create a new coordinator
206    pub fn new(
207        bks_cache: Arc<Mutex<BehavioralKnowledgeCache>>,
208        pks_cache: Arc<Mutex<PersonalKnowledgeCache>>,
209        config: IntegrationConfig,
210    ) -> Result<Self> {
211        config.validate()?;
212
213        Ok(Self {
214            bks_cache,
215            pks_cache,
216            config,
217        })
218    }
219
220    /// Create with default configuration
221    pub fn with_defaults(
222        bks_cache: Arc<Mutex<BehavioralKnowledgeCache>>,
223        pks_cache: Arc<Mutex<PersonalKnowledgeCache>>,
224    ) -> Result<Self> {
225        Self::new(bks_cache, pks_cache, IntegrationConfig::default())
226    }
227
228    /// Get PKS context for SEAL entity resolutions
229    ///
230    /// Given SEAL's entity resolutions, look up relevant personal facts
231    /// that provide context about those entities.
232    ///
233    /// Example:
234    /// - SEAL resolves "it" → "main.rs"
235    /// - PKS has: "main.rs is entry point for brainwires-cli"
236    /// - Returns: Formatted context string with relevant facts
237    pub async fn get_pks_context(
238        &self,
239        seal_result: &SealProcessingResult,
240    ) -> Result<Option<String>> {
241        if !self.config.enabled {
242            return Ok(None);
243        }
244
245        // Check if SEAL quality is high enough for PKS boost
246        if seal_result.quality_score < self.config.min_seal_quality_for_pks_boost {
247            return Ok(None);
248        }
249
250        // Extract entities from SEAL resolutions
251        let entities: Vec<&str> = seal_result
252            .resolutions
253            .iter()
254            .map(|r| r.antecedent.as_str())
255            .collect();
256
257        if entities.is_empty() {
258            return Ok(None);
259        }
260
261        // Look up facts for each entity
262        let pks = self.pks_cache.lock().await;
263        let mut context_parts = Vec::new();
264
265        for entity in entities {
266            // Get facts related to this entity by looking for keys containing the entity name
267            // This is a simple heuristic - could be improved with fuzzy matching
268            let all_facts: Vec<_> = pks
269                .get_all_facts()
270                .into_iter()
271                .filter(|f| !f.deleted && (f.key.contains(entity) || f.value.contains(entity)))
272                .collect();
273
274            if !all_facts.is_empty() {
275                context_parts.push(format!("\n**{}:**", entity));
276
277                for fact in all_facts {
278                    // Filter by confidence (only include reliable facts)
279                    if fact.confidence >= 0.5 {
280                        context_parts.push(format!(
281                            "  - {} (confidence: {:.2})",
282                            fact.value, fact.confidence
283                        ));
284                    }
285                }
286            }
287        }
288
289        if context_parts.is_empty() {
290            Ok(None)
291        } else {
292            Ok(Some(format!(
293                "# PERSONAL CONTEXT\n\nRelevant facts about entities mentioned:\n{}",
294                context_parts.join("\n")
295            )))
296        }
297    }
298
299    /// Get BKS context for query
300    ///
301    /// Given the user's query (after SEAL processing), look up relevant
302    /// behavioral truths that might help with execution.
303    ///
304    /// Example:
305    /// - Query: "How do I run the Rust project?"
306    /// - BKS has: "For Rust projects, use 'cargo run' not 'rustc main.rs'"
307    /// - Returns: Formatted context string with relevant truths
308    pub async fn get_bks_context(&self, query: &str) -> Result<Option<String>> {
309        if !self.config.enabled {
310            return Ok(None);
311        }
312
313        let bks = self.bks_cache.lock().await;
314
315        // Get truths matching the query context with scores
316        let truths = bks.get_matching_truths_with_scores(query, 0.5, 5)?;
317
318        if truths.is_empty() {
319            return Ok(None);
320        }
321
322        let mut context_parts = vec!["# BEHAVIORAL KNOWLEDGE\n".to_string()];
323        context_parts.push("Learned patterns that may be relevant:\n".to_string());
324
325        for (truth, score) in truths {
326            context_parts.push(format!(
327                "\n**{}** (confidence: {:.2}, relevance: {:.2}):",
328                truth.context_pattern, truth.confidence, score
329            ));
330            context_parts.push(format!("  Rule: {}", truth.rule));
331            context_parts.push(format!("  Why: {}", truth.rationale));
332        }
333
334        Ok(Some(context_parts.join("\n")))
335    }
336
337    /// Harmonize confidence from multiple sources
338    ///
339    /// Combines SEAL quality score with BKS and PKS confidence values
340    /// using weighted averaging to produce a unified confidence score.
341    pub fn harmonize_confidence(
342        &self,
343        seal_quality: f32,
344        bks_confidence: Option<f32>,
345        pks_confidence: Option<f32>,
346    ) -> f32 {
347        let mut weighted_sum = seal_quality * self.config.seal_weight;
348        let mut total_weight = self.config.seal_weight;
349
350        if let Some(bks) = bks_confidence {
351            weighted_sum += bks * self.config.bks_weight;
352            total_weight += self.config.bks_weight;
353        }
354
355        if let Some(pks) = pks_confidence {
356            weighted_sum += pks * self.config.pks_weight;
357            total_weight += self.config.pks_weight;
358        }
359
360        // Normalize by total weight
361        if total_weight > 0.0 {
362            (weighted_sum / total_weight).min(1.0)
363        } else {
364            seal_quality
365        }
366    }
367
368    /// Adjust retrieval threshold based on SEAL quality
369    ///
370    /// When SEAL quality is low, we need more context to compensate,
371    /// so we lower the retrieval threshold to include more historical messages.
372    ///
373    /// When SEAL quality is high, we can be more selective with context.
374    pub fn adjust_retrieval_threshold(&self, base_threshold: f32, seal_quality: f32) -> f32 {
375        // Quality adjustment factor: lower quality → lower threshold (more context)
376        // Formula: adjusted = base * (0.7 + 0.3 * quality)
377        // - quality = 0.0 → 70% of base threshold
378        // - quality = 1.0 → 100% of base threshold
379        base_threshold * (0.7 + 0.3 * seal_quality).max(0.5)
380    }
381
382    /// Check if a SEAL pattern should be promoted to BKS
383    ///
384    /// Patterns are promoted when they:
385    /// 1. Have reliability above threshold (default: 0.8)
386    /// 2. Have been used enough times (default: 5)
387    /// 3. Integration is enabled
388    pub async fn check_and_promote_pattern(
389        &mut self,
390        pattern: &QueryPattern,
391        execution_context: &str,
392    ) -> Result<Option<BehavioralTruth>> {
393        if !self.config.enabled || !self.config.seal_to_knowledge {
394            return Ok(None);
395        }
396
397        // Check promotion criteria
398        if pattern.reliability() < self.config.pattern_promotion_threshold {
399            tracing::debug!(
400                "Pattern '{}' reliability ({:.2}) below threshold ({:.2})",
401                pattern.template,
402                pattern.reliability(),
403                self.config.pattern_promotion_threshold
404            );
405            return Ok(None);
406        }
407
408        let total_uses = pattern.success_count + pattern.failure_count;
409        if total_uses < self.config.min_pattern_uses {
410            tracing::debug!(
411                "Pattern '{}' uses ({}) below minimum ({})",
412                pattern.template,
413                total_uses,
414                self.config.min_pattern_uses
415            );
416            return Ok(None);
417        }
418
419        // Create BKS truth from SEAL pattern
420        let category = self.infer_category(&pattern.question_type);
421        let rule = self.generalize_pattern_to_rule(pattern);
422        let rationale = format!(
423            "Learned from {} successful executions with {:.1}% reliability (SEAL pattern)",
424            pattern.success_count,
425            pattern.reliability() * 100.0
426        );
427
428        let truth = BehavioralTruth::new(
429            category,
430            execution_context.to_string(),
431            rule,
432            rationale,
433            TruthSource::SuccessPattern, // Pattern emerged from successful usage
434            None,                        // Anonymous
435        );
436
437        // Submit to BKS
438        let mut bks = self.bks_cache.lock().await;
439        match bks.queue_submission(truth.clone()) {
440            Ok(_) => {
441                tracing::info!(
442                    "✓ Promoted SEAL pattern to BKS: '{}' (reliability: {:.2}, uses: {})",
443                    pattern.template,
444                    pattern.reliability(),
445                    total_uses
446                );
447                Ok(Some(truth))
448            }
449            Err(e) => {
450                tracing::warn!("Failed to promote SEAL pattern to BKS: {}", e);
451                Err(e)
452            }
453        }
454    }
455
456    /// Load relevant BKS truths into SEAL's global memory on startup
457    ///
458    /// This enables SEAL to benefit from collective learning by having
459    /// high-confidence behavioral truths available for pattern matching.
460    pub async fn sync_bks_to_seal(
461        &mut self,
462        seal_learning: &mut super::learning::LearningCoordinator,
463    ) -> Result<u32> {
464        if !self.config.enabled || !self.config.knowledge_to_seal {
465            return Ok(0);
466        }
467
468        if !self.config.cache_bks_in_seal {
469            return Ok(0);
470        }
471
472        let bks = self.bks_cache.lock().await;
473
474        // Get high-confidence truths (> 0.7) from last 30 days
475        let truths = bks.get_reliable_truths(0.7, 30);
476
477        let mut loaded = 0;
478        for truth in truths {
479            // Convert BKS truth to structured SEAL pattern hint and store it
480            if let Some(hint) = self.truth_to_pattern_hint(truth) {
481                seal_learning.global.add_pattern_hint(hint);
482                tracing::debug!(
483                    "Loaded BKS truth into SEAL: {} -> {}",
484                    truth.context_pattern,
485                    truth.rule
486                );
487                loaded += 1;
488            }
489        }
490
491        tracing::info!("Loaded {} BKS truths into SEAL global memory", loaded);
492        Ok(loaded)
493    }
494
495    /// Observe SEAL resolutions for PKS learning
496    ///
497    /// Tracks which entities the user focuses on, allowing PKS to build
498    /// a profile of user interests and recently-used entities.
499    pub async fn observe_seal_resolutions(
500        &mut self,
501        resolutions: &[ResolvedReference],
502    ) -> Result<()> {
503        if !self.config.enabled {
504            return Ok(());
505        }
506
507        let mut pks = self.pks_cache.lock().await;
508
509        for resolution in resolutions {
510            // Track entity as context fact
511            let key = format!("recent_entity:{}", resolution.antecedent);
512
513            // Upsert fact (will update timestamp if exists)
514            pks.upsert_fact_simple(
515                &key,
516                &resolution.antecedent,
517                resolution.confidence,
518                true, // local_only (don't sync entity tracking)
519            )?;
520        }
521
522        Ok(())
523    }
524
525    /// Record a tool failure pattern for shared learning
526    ///
527    /// When validation fails or tools error, record the pattern in BKS
528    /// so other users can benefit from this knowledge.
529    pub async fn record_tool_failure(
530        &mut self,
531        tool_name: &str,
532        error_message: &str,
533        context: &str,
534    ) -> Result<()> {
535        if !self.config.enabled || !self.config.seal_to_knowledge {
536            return Ok(());
537        }
538
539        // Create behavioral truth about tool failure pattern
540        let truth = BehavioralTruth::new(
541            TruthCategory::ErrorRecovery,
542            context.to_string(),
543            format!(
544                "Tool '{}' commonly fails with: {}",
545                tool_name, error_message
546            ),
547            "Observed from validation failures".to_string(),
548            TruthSource::FailurePattern,
549            None,
550        );
551
552        let mut bks = self.bks_cache.lock().await;
553        bks.queue_submission(truth)?;
554
555        tracing::debug!(
556            "Recorded tool failure pattern: {} in context: {}",
557            tool_name,
558            context
559        );
560
561        Ok(())
562    }
563
564    /// Get the current configuration
565    pub fn config(&self) -> &IntegrationConfig {
566        &self.config
567    }
568
569    /// Get reference to PKS cache
570    pub fn get_pks_cache(&self) -> Arc<Mutex<PersonalKnowledgeCache>> {
571        Arc::clone(&self.pks_cache)
572    }
573
574    /// Get reference to BKS cache
575    pub fn get_bks_cache(&self) -> Arc<Mutex<BehavioralKnowledgeCache>> {
576        Arc::clone(&self.bks_cache)
577    }
578
579    // --- Private helper methods ---
580
581    /// Infer BKS category from SEAL question type
582    fn infer_category(&self, question_type: &super::query_core::QuestionType) -> TruthCategory {
583        use super::query_core::QuestionType;
584
585        match question_type {
586            QuestionType::Definition => TruthCategory::CommandUsage,
587            QuestionType::Dependency => TruthCategory::TaskStrategy,
588            QuestionType::Location => TruthCategory::TaskStrategy,
589            QuestionType::Count | QuestionType::Superlative => TruthCategory::TaskStrategy,
590            _ => TruthCategory::TaskStrategy,
591        }
592    }
593
594    /// Generalize a SEAL pattern template into a BKS rule
595    fn generalize_pattern_to_rule(&self, pattern: &QueryPattern) -> String {
596        // Note: QueryPattern has required_types, not entity_types
597        let types_str = pattern
598            .required_types
599            .iter()
600            .map(|t| format!("{:?}", t))
601            .collect::<Vec<_>>()
602            .join(", ");
603
604        format!(
605            "For '{:?}' queries about {}, use pattern: {}",
606            pattern.question_type, types_str, pattern.template
607        )
608    }
609
610    /// Convert BKS truth to a structured SEAL pattern hint.
611    ///
612    /// This is a best-effort conversion since BKS and SEAL use different schemas.
613    /// Returns None if the truth doesn't map to a SEAL pattern.
614    fn truth_to_pattern_hint(
615        &self,
616        truth: &BehavioralTruth,
617    ) -> Option<super::learning::PatternHint> {
618        Some(super::learning::PatternHint {
619            context_pattern: truth.context_pattern.clone(),
620            rule: truth.rule.clone(),
621            confidence: truth.confidence as f64,
622            source: "bks".to_string(),
623        })
624    }
625}
626
627#[cfg(test)]
628mod tests {
629    use super::*;
630
631    #[test]
632    fn test_integration_config_validation() {
633        let mut config = IntegrationConfig::default();
634        assert!(config.validate().is_ok());
635
636        // Invalid quality threshold
637        config.min_seal_quality_for_bks_boost = 1.5;
638        assert!(config.validate().is_err());
639
640        // Invalid weight sum
641        config = IntegrationConfig::default();
642        config.seal_weight = 0.5;
643        config.bks_weight = 0.5;
644        config.pks_weight = 0.5; // Sum > 1.0
645        assert!(config.validate().is_err());
646    }
647
648    #[test]
649    fn test_confidence_harmonization() {
650        let coordinator = create_test_coordinator();
651
652        // Only SEAL
653        let conf = coordinator.harmonize_confidence(0.8, None, None);
654        assert!((conf - 0.8).abs() < 0.01);
655
656        // SEAL + BKS + PKS
657        let conf = coordinator.harmonize_confidence(0.6, Some(0.9), Some(0.8));
658        // Expected: 0.6*0.5 + 0.9*0.3 + 0.8*0.2 = 0.3 + 0.27 + 0.16 = 0.73
659        assert!((conf - 0.73).abs() < 0.01);
660    }
661
662    #[test]
663    fn test_retrieval_threshold_adjustment() {
664        let coordinator = create_test_coordinator();
665
666        // Low quality → lower threshold (need more context)
667        let adjusted = coordinator.adjust_retrieval_threshold(0.75, 0.0);
668        assert!((adjusted - 0.525).abs() < 0.01); // 0.75 * 0.7
669
670        // High quality → higher threshold (can be selective)
671        let adjusted = coordinator.adjust_retrieval_threshold(0.75, 1.0);
672        assert!((adjusted - 0.75).abs() < 0.01); // 0.75 * 1.0
673
674        // Medium quality
675        let adjusted = coordinator.adjust_retrieval_threshold(0.75, 0.5);
676        assert!((adjusted - 0.6375).abs() < 0.01); // 0.75 * 0.85
677    }
678
679    fn create_test_coordinator() -> SealKnowledgeCoordinator {
680        let bks_cache = Arc::new(Mutex::new(
681            BehavioralKnowledgeCache::in_memory(100).unwrap(),
682        ));
683        let pks_cache = Arc::new(Mutex::new(PersonalKnowledgeCache::in_memory(100).unwrap()));
684
685        SealKnowledgeCoordinator::new(bks_cache, pks_cache, IntegrationConfig::default()).unwrap()
686    }
687}