1use 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
58const DEFAULT_PATTERN_PROMOTION_THRESHOLD: f32 = 0.8;
60
61#[derive(Debug, Clone)]
63pub struct IntegrationConfig {
64 pub enabled: bool,
66
67 pub seal_to_knowledge: bool,
69
70 pub knowledge_to_seal: bool,
72
73 pub min_seal_quality_for_bks_boost: f32,
76
77 pub min_seal_quality_for_pks_boost: f32,
80
81 pub pattern_promotion_threshold: f32,
84
85 pub min_pattern_uses: u32,
88
89 pub cache_bks_in_seal: bool,
91
92 pub entity_resolution_strategy: EntityResolutionStrategy,
94
95 pub seal_weight: f32,
98
99 pub bks_weight: f32,
102
103 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 pub fn full() -> Self {
133 Self::default()
134 }
135
136 pub fn seal_to_knowledge_only() -> Self {
138 Self {
139 knowledge_to_seal: false,
140 ..Self::default()
141 }
142 }
143
144 pub fn disabled() -> Self {
146 Self {
147 enabled: false,
148 ..Self::default()
149 }
150 }
151
152 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#[derive(Debug, Clone)]
176pub enum EntityResolutionStrategy {
177 SealFirst,
179
180 PksContextFirst,
182
183 Hybrid {
185 seal_weight: f32,
187 pks_weight: f32,
189 },
190}
191
192pub struct SealKnowledgeCoordinator {
194 bks_cache: Arc<Mutex<BehavioralKnowledgeCache>>,
196
197 pks_cache: Arc<Mutex<PersonalKnowledgeCache>>,
199
200 config: IntegrationConfig,
202}
203
204impl SealKnowledgeCoordinator {
205 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 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 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 if seal_result.quality_score < self.config.min_seal_quality_for_pks_boost {
247 return Ok(None);
248 }
249
250 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 let pks = self.pks_cache.lock().await;
263 let mut context_parts = Vec::new();
264
265 for entity in entities {
266 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 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 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 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 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 if total_weight > 0.0 {
362 (weighted_sum / total_weight).min(1.0)
363 } else {
364 seal_quality
365 }
366 }
367
368 pub fn adjust_retrieval_threshold(&self, base_threshold: f32, seal_quality: f32) -> f32 {
375 base_threshold * (0.7 + 0.3 * seal_quality).max(0.5)
380 }
381
382 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 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 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, None, );
436
437 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 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 let truths = bks.get_reliable_truths(0.7, 30);
476
477 let mut loaded = 0;
478 for truth in truths {
479 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 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 let key = format!("recent_entity:{}", resolution.antecedent);
512
513 pks.upsert_fact_simple(
515 &key,
516 &resolution.antecedent,
517 resolution.confidence,
518 true, )?;
520 }
521
522 Ok(())
523 }
524
525 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 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 pub fn config(&self) -> &IntegrationConfig {
566 &self.config
567 }
568
569 pub fn get_pks_cache(&self) -> Arc<Mutex<PersonalKnowledgeCache>> {
571 Arc::clone(&self.pks_cache)
572 }
573
574 pub fn get_bks_cache(&self) -> Arc<Mutex<BehavioralKnowledgeCache>> {
576 Arc::clone(&self.bks_cache)
577 }
578
579 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 fn generalize_pattern_to_rule(&self, pattern: &QueryPattern) -> String {
596 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 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 config.min_seal_quality_for_bks_boost = 1.5;
638 assert!(config.validate().is_err());
639
640 config = IntegrationConfig::default();
642 config.seal_weight = 0.5;
643 config.bks_weight = 0.5;
644 config.pks_weight = 0.5; assert!(config.validate().is_err());
646 }
647
648 #[test]
649 fn test_confidence_harmonization() {
650 let coordinator = create_test_coordinator();
651
652 let conf = coordinator.harmonize_confidence(0.8, None, None);
654 assert!((conf - 0.8).abs() < 0.01);
655
656 let conf = coordinator.harmonize_confidence(0.6, Some(0.9), Some(0.8));
658 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 let adjusted = coordinator.adjust_retrieval_threshold(0.75, 0.0);
668 assert!((adjusted - 0.525).abs() < 0.01); let adjusted = coordinator.adjust_retrieval_threshold(0.75, 1.0);
672 assert!((adjusted - 0.75).abs() < 0.01); let adjusted = coordinator.adjust_retrieval_threshold(0.75, 0.5);
676 assert!((adjusted - 0.6375).abs() < 0.01); }
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}