reasonkit/thinktool/
profiles.rs

1//! Reasoning Profiles - Compositions of ThinkTool protocols
2//!
3//! Profiles chain multiple protocols together for different use cases:
4//! - `quick`: Fast 2-step analysis (GigaThink → LaserLogic)
5//! - `balanced`: Standard 4-module chain
6//! - `deep`: Thorough analysis with meta-cognition
7//! - `paranoid`: Maximum verification (95% confidence target)
8//! - `decide`: Decision support focused
9//! - `scientific`: Research and experiments
10
11use serde::{Deserialize, Serialize};
12use std::collections::HashMap;
13
14/// A reasoning profile (composition of protocols)
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct ReasoningProfile {
17    /// Profile identifier (e.g., "quick", "balanced", "paranoid")
18    pub id: String,
19
20    /// Human-readable name
21    pub name: String,
22
23    /// Description of what this profile does
24    pub description: String,
25
26    /// Protocols in execution order
27    pub chain: Vec<ChainStep>,
28
29    /// Expected confidence threshold
30    pub min_confidence: f64,
31
32    /// Typical token budget (hint)
33    pub token_budget: Option<u32>,
34
35    /// Tags for categorization
36    #[serde(default)]
37    pub tags: Vec<String>,
38}
39
40/// A step in a protocol chain
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct ChainStep {
43    /// Protocol to execute
44    pub protocol_id: String,
45
46    /// Input mapping from previous step outputs
47    /// Key: input field name, Value: source expression (e.g., "input.query", "steps.gigathink.perspectives")
48    #[serde(default)]
49    pub input_mapping: HashMap<String, String>,
50
51    /// Condition to execute (optional)
52    #[serde(default)]
53    pub condition: Option<ChainCondition>,
54
55    /// Override configuration for this step
56    #[serde(default)]
57    pub config_override: Option<StepConfigOverride>,
58}
59
60/// Conditions for conditional execution
61#[derive(Debug, Clone, Serialize, Deserialize)]
62#[serde(tag = "type", rename_all = "snake_case")]
63#[derive(Default)]
64pub enum ChainCondition {
65    /// Always execute
66    #[default]
67    Always,
68
69    /// Execute if previous step confidence below threshold
70    ConfidenceBelow {
71        /// Confidence threshold
72        threshold: f64,
73    },
74
75    /// Execute if previous step confidence above threshold
76    ConfidenceAbove {
77        /// Confidence threshold
78        threshold: f64,
79    },
80
81    /// Execute if a specific output field exists
82    OutputExists {
83        /// Step ID to check
84        step_id: String,
85        /// Field name to check
86        field: String,
87    },
88}
89
90/// Configuration overrides for a chain step
91#[derive(Debug, Clone, Default, Serialize, Deserialize)]
92pub struct StepConfigOverride {
93    /// Override temperature
94    pub temperature: Option<f64>,
95
96    /// Override max tokens
97    pub max_tokens: Option<u32>,
98
99    /// Override min confidence
100    pub min_confidence: Option<f64>,
101}
102
103use super::yaml_loader;
104
105/// Registry of reasoning profiles
106#[derive(Debug, Default)]
107pub struct ProfileRegistry {
108    profiles: HashMap<String, ReasoningProfile>,
109}
110
111impl ProfileRegistry {
112    /// Create a new empty registry
113    pub fn new() -> Self {
114        Self::default()
115    }
116
117    /// Create registry with built-in profiles
118    pub fn with_builtins() -> Self {
119        let mut registry = Self::new();
120        registry.register_builtins();
121        registry
122    }
123
124    /// Register built-in profiles
125    pub fn register_builtins(&mut self) {
126        // Try to load from YAML first
127        let mut loaded_from_yaml = false;
128
129        if let Ok(cwd) = std::env::current_dir() {
130            let yaml_path = cwd.join("protocols").join("profiles.yaml");
131            if yaml_path.exists() {
132                match yaml_loader::load_profiles_from_yaml_file(&yaml_path) {
133                    Ok(profiles) => {
134                        for profile in profiles {
135                            self.register(profile);
136                        }
137                        tracing::info!("Loaded profiles from profiles.yaml");
138                        loaded_from_yaml = true;
139                    }
140                    Err(e) => {
141                        tracing::warn!(
142                            "Failed to load profiles.yaml: {}, falling back to built-ins",
143                            e
144                        );
145                    }
146                }
147            }
148        }
149
150        if !loaded_from_yaml {
151            tracing::info!("Using hardcoded fallback profiles");
152            self.register(builtin_quick());
153            self.register(builtin_balanced());
154            self.register(builtin_deep());
155            self.register(builtin_paranoid());
156            self.register(builtin_decide());
157            self.register(builtin_scientific());
158            self.register(builtin_powercombo());
159        }
160    }
161
162    /// Register a profile
163    pub fn register(&mut self, profile: ReasoningProfile) {
164        self.profiles.insert(profile.id.clone(), profile);
165    }
166
167    /// Get a profile by ID
168    pub fn get(&self, id: &str) -> Option<&ReasoningProfile> {
169        self.profiles.get(id)
170    }
171
172    /// Check if profile exists
173    pub fn contains(&self, id: &str) -> bool {
174        self.profiles.contains_key(id)
175    }
176
177    /// List all profile IDs
178    pub fn list_ids(&self) -> Vec<&str> {
179        self.profiles.keys().map(|s| s.as_str()).collect()
180    }
181
182    /// List all profiles
183    pub fn list(&self) -> Vec<&ReasoningProfile> {
184        self.profiles.values().collect()
185    }
186
187    /// Get profile count
188    pub fn len(&self) -> usize {
189        self.profiles.len()
190    }
191
192    /// Check if empty
193    pub fn is_empty(&self) -> bool {
194        self.profiles.is_empty()
195    }
196}
197
198// ═══════════════════════════════════════════════════════════════════════════
199// BUILT-IN PROFILES
200// ═══════════════════════════════════════════════════════════════════════════
201
202/// Quick: Fast 2-step analysis
203/// GigaThink → LaserLogic
204fn builtin_quick() -> ReasoningProfile {
205    ReasoningProfile {
206        id: "quick".to_string(),
207        name: "Quick Analysis".to_string(),
208        description: "Fast 2-step analysis for rapid insights".to_string(),
209        chain: vec![
210            ChainStep {
211                protocol_id: "gigathink".to_string(),
212                input_mapping: HashMap::from([("query".to_string(), "input.query".to_string())]),
213                condition: None,
214                config_override: Some(StepConfigOverride {
215                    max_tokens: Some(1000),
216                    ..Default::default()
217                }),
218            },
219            ChainStep {
220                protocol_id: "laserlogic".to_string(),
221                input_mapping: HashMap::from([
222                    // Use synthesize step output from gigathink
223                    (
224                        "argument".to_string(),
225                        "steps.gigathink.synthesize".to_string(),
226                    ),
227                ]),
228                condition: None,
229                config_override: None,
230            },
231        ],
232        min_confidence: 0.70,
233        token_budget: Some(3000),
234        tags: vec!["fast".to_string(), "creative".to_string()],
235    }
236}
237
238/// Balanced: Standard 4-module chain
239/// GigaThink → LaserLogic → BedRock → ProofGuard
240fn builtin_balanced() -> ReasoningProfile {
241    ReasoningProfile {
242        id: "balanced".to_string(),
243        name: "Balanced Analysis".to_string(),
244        description: "Standard 4-module chain for thorough but efficient analysis".to_string(),
245        chain: vec![
246            ChainStep {
247                protocol_id: "gigathink".to_string(),
248                input_mapping: HashMap::from([
249                    ("query".to_string(), "input.query".to_string()),
250                    ("context".to_string(), "input.context".to_string()),
251                ]),
252                condition: None,
253                config_override: None,
254            },
255            ChainStep {
256                protocol_id: "laserlogic".to_string(),
257                input_mapping: HashMap::from([(
258                    "argument".to_string(),
259                    "steps.gigathink.synthesize".to_string(),
260                )]),
261                condition: None,
262                config_override: None,
263            },
264            ChainStep {
265                protocol_id: "bedrock".to_string(),
266                input_mapping: HashMap::from([(
267                    "statement".to_string(),
268                    "steps.laserlogic.check_validity".to_string(), // Fixed: use actual step ID
269                )]),
270                condition: Some(ChainCondition::ConfidenceBelow { threshold: 0.9 }),
271                config_override: None,
272            },
273            ChainStep {
274                protocol_id: "proofguard".to_string(),
275                input_mapping: HashMap::from([(
276                    "claim".to_string(),
277                    "steps.bedrock.identify_axioms".to_string(), // Fixed: use actual step ID
278                )]),
279                condition: None,
280                config_override: None,
281            },
282        ],
283        min_confidence: 0.80,
284        token_budget: Some(8000),
285        tags: vec!["standard".to_string(), "thorough".to_string()],
286    }
287}
288
289/// Deep: Thorough analysis with meta-cognition
290/// GigaThink → LaserLogic → BedRock → ProofGuard → BrutalHonesty (conditional)
291fn builtin_deep() -> ReasoningProfile {
292    ReasoningProfile {
293        id: "deep".to_string(),
294        name: "Deep Analysis".to_string(),
295        description: "Thorough analysis with first principles and verification".to_string(),
296        chain: vec![
297            ChainStep {
298                protocol_id: "gigathink".to_string(),
299                input_mapping: HashMap::from([
300                    ("query".to_string(), "input.query".to_string()),
301                    ("context".to_string(), "input.context".to_string()),
302                ]),
303                condition: None,
304                config_override: None,
305            },
306            ChainStep {
307                protocol_id: "laserlogic".to_string(),
308                input_mapping: HashMap::from([(
309                    "argument".to_string(),
310                    "steps.gigathink.synthesize".to_string(),
311                )]),
312                condition: None,
313                config_override: None,
314            },
315            ChainStep {
316                protocol_id: "bedrock".to_string(),
317                input_mapping: HashMap::from([(
318                    "statement".to_string(),
319                    "steps.laserlogic.check_validity".to_string(), // Fixed: use actual step ID
320                )]),
321                condition: None,
322                config_override: None,
323            },
324            ChainStep {
325                protocol_id: "proofguard".to_string(),
326                input_mapping: HashMap::from([(
327                    "claim".to_string(),
328                    "steps.bedrock.identify_axioms".to_string(), // Fixed: use actual step ID
329                )]),
330                condition: None,
331                config_override: None,
332            },
333            ChainStep {
334                protocol_id: "brutalhonesty".to_string(),
335                input_mapping: HashMap::from([(
336                    "work".to_string(),
337                    "steps.proofguard.triangulate".to_string(), // Fixed: use actual step ID
338                )]),
339                condition: Some(ChainCondition::ConfidenceBelow { threshold: 0.85 }),
340                config_override: None,
341            },
342        ],
343        min_confidence: 0.85,
344        token_budget: Some(12000),
345        tags: vec!["thorough".to_string(), "analytical".to_string()],
346    }
347}
348
349/// Paranoid: Maximum verification (95% confidence target)
350/// GigaThink → LaserLogic → BedRock → ProofGuard → BrutalHonesty → ProofGuard (2nd pass)
351fn builtin_paranoid() -> ReasoningProfile {
352    ReasoningProfile {
353        id: "paranoid".to_string(),
354        name: "Paranoid Verification".to_string(),
355        description: "Maximum rigor with adversarial critique and multi-pass verification"
356            .to_string(),
357        chain: vec![
358            ChainStep {
359                protocol_id: "gigathink".to_string(),
360                input_mapping: HashMap::from([
361                    ("query".to_string(), "input.query".to_string()),
362                    ("context".to_string(), "input.context".to_string()),
363                ]),
364                condition: None,
365                config_override: None,
366            },
367            ChainStep {
368                protocol_id: "laserlogic".to_string(),
369                input_mapping: HashMap::from([(
370                    "argument".to_string(),
371                    "steps.gigathink.synthesize".to_string(),
372                )]),
373                condition: None,
374                config_override: None,
375            },
376            ChainStep {
377                protocol_id: "bedrock".to_string(),
378                input_mapping: HashMap::from([(
379                    "statement".to_string(),
380                    "steps.laserlogic.check_validity".to_string(), // Fixed: use actual step ID
381                )]),
382                condition: None,
383                config_override: None,
384            },
385            ChainStep {
386                protocol_id: "proofguard".to_string(),
387                input_mapping: HashMap::from([(
388                    "claim".to_string(),
389                    "steps.bedrock.identify_axioms".to_string(), // Fixed: use actual step ID
390                )]),
391                condition: None,
392                config_override: None,
393            },
394            ChainStep {
395                protocol_id: "brutalhonesty".to_string(),
396                input_mapping: HashMap::from([(
397                    "work".to_string(),
398                    "steps.proofguard.triangulate".to_string(), // Fixed: use actual step ID
399                )]),
400                condition: None,
401                config_override: Some(StepConfigOverride {
402                    temperature: Some(0.3), // Lower temp for more focused critique
403                    ..Default::default()
404                }),
405            },
406            // Second verification pass after critique
407            ChainStep {
408                protocol_id: "proofguard".to_string(),
409                input_mapping: HashMap::from([(
410                    "claim".to_string(),
411                    "steps.brutalhonesty.verdict".to_string(), // verdict exists in brutalhonesty
412                )]),
413                condition: Some(ChainCondition::ConfidenceBelow { threshold: 0.95 }),
414                config_override: None,
415            },
416        ],
417        min_confidence: 0.95,
418        token_budget: Some(18000),
419        tags: vec![
420            "rigorous".to_string(),
421            "verification".to_string(),
422            "adversarial".to_string(),
423        ],
424    }
425}
426
427/// Decide: Decision support focused
428/// LaserLogic → BedRock → BrutalHonesty
429fn builtin_decide() -> ReasoningProfile {
430    ReasoningProfile {
431        id: "decide".to_string(),
432        name: "Decision Support".to_string(),
433        description: "Focused on evaluating options and making decisions".to_string(),
434        chain: vec![
435            ChainStep {
436                protocol_id: "laserlogic".to_string(),
437                input_mapping: HashMap::from([("argument".to_string(), "input.query".to_string())]),
438                condition: None,
439                config_override: None,
440            },
441            ChainStep {
442                protocol_id: "bedrock".to_string(),
443                input_mapping: HashMap::from([(
444                    "statement".to_string(),
445                    "steps.laserlogic.check_validity".to_string(), // Fixed: use actual step ID
446                )]),
447                condition: None,
448                config_override: None,
449            },
450            ChainStep {
451                protocol_id: "brutalhonesty".to_string(),
452                input_mapping: HashMap::from([(
453                    "work".to_string(),
454                    "steps.bedrock.reconstruct".to_string(), // Fixed: use actual step ID
455                )]),
456                condition: None,
457                config_override: None,
458            },
459        ],
460        min_confidence: 0.85,
461        token_budget: Some(6000),
462        tags: vec!["decision".to_string(), "analytical".to_string()],
463    }
464}
465
466/// Scientific: Research and experiments
467/// GigaThink → BedRock → ProofGuard
468fn builtin_scientific() -> ReasoningProfile {
469    ReasoningProfile {
470        id: "scientific".to_string(),
471        name: "Scientific Method".to_string(),
472        description: "For research, hypothesis testing, and empirical analysis".to_string(),
473        chain: vec![
474            ChainStep {
475                protocol_id: "gigathink".to_string(),
476                input_mapping: HashMap::from([
477                    ("query".to_string(), "input.query".to_string()),
478                    ("constraints".to_string(), "input.constraints".to_string()),
479                ]),
480                condition: None,
481                config_override: Some(StepConfigOverride {
482                    temperature: Some(0.8), // Higher for hypothesis generation
483                    ..Default::default()
484                }),
485            },
486            ChainStep {
487                protocol_id: "bedrock".to_string(),
488                input_mapping: HashMap::from([
489                    (
490                        "statement".to_string(),
491                        "steps.gigathink.synthesize".to_string(), // synthesize exists
492                    ),
493                    ("domain".to_string(), "input.domain".to_string()),
494                ]),
495                condition: None,
496                config_override: None,
497            },
498            ChainStep {
499                protocol_id: "proofguard".to_string(),
500                input_mapping: HashMap::from([
501                    (
502                        "claim".to_string(),
503                        "steps.bedrock.identify_axioms".to_string(),
504                    ), // Fixed: use actual step ID
505                    ("sources".to_string(), "input.sources".to_string()),
506                ]),
507                condition: None,
508                config_override: Some(StepConfigOverride {
509                    min_confidence: Some(0.85),
510                    ..Default::default()
511                }),
512            },
513        ],
514        min_confidence: 0.85,
515        token_budget: Some(8000),
516        tags: vec![
517            "research".to_string(),
518            "empirical".to_string(),
519            "verification".to_string(),
520        ],
521    }
522}
523
524/// PowerCombo: Ultimate reasoning mode - ALL 5 ThinkTools
525/// 🌈 GigaThink → LaserLogic → BedRock → ProofGuard → BrutalHonesty → ProofGuard (validation)
526fn builtin_powercombo() -> ReasoningProfile {
527    ReasoningProfile {
528        id: "powercombo".to_string(),
529        name: "PowerCombo Ultimate".to_string(),
530        description: "Maximum reasoning power - all 5 ThinkTools in sequence with cross-validation"
531            .to_string(),
532        chain: vec![
533            ChainStep {
534                protocol_id: "gigathink".to_string(),
535                input_mapping: HashMap::from([
536                    ("query".to_string(), "input.query".to_string()),
537                    ("context".to_string(), "input.context".to_string()),
538                ]),
539                condition: None,
540                config_override: Some(StepConfigOverride {
541                    temperature: Some(0.8), // Creative exploration
542                    ..Default::default()
543                }),
544            },
545            ChainStep {
546                protocol_id: "laserlogic".to_string(),
547                input_mapping: HashMap::from([(
548                    "argument".to_string(),
549                    "steps.gigathink.synthesize".to_string(),
550                )]),
551                condition: None,
552                config_override: None,
553            },
554            ChainStep {
555                protocol_id: "bedrock".to_string(),
556                input_mapping: HashMap::from([(
557                    "statement".to_string(),
558                    "steps.laserlogic.check_validity".to_string(),
559                )]),
560                condition: None,
561                config_override: None,
562            },
563            ChainStep {
564                protocol_id: "proofguard".to_string(),
565                input_mapping: HashMap::from([(
566                    "claim".to_string(),
567                    "steps.bedrock.identify_axioms".to_string(),
568                )]),
569                condition: None,
570                config_override: Some(StepConfigOverride {
571                    min_confidence: Some(0.9), // High bar for verification
572                    ..Default::default()
573                }),
574            },
575            ChainStep {
576                protocol_id: "brutalhonesty".to_string(),
577                input_mapping: HashMap::from([(
578                    "work".to_string(),
579                    "steps.proofguard.triangulate".to_string(),
580                )]),
581                condition: None,
582                config_override: Some(StepConfigOverride {
583                    temperature: Some(0.3), // Focused critique
584                    ..Default::default()
585                }),
586            },
587            // Second verification pass - cross-check after critique
588            ChainStep {
589                protocol_id: "proofguard".to_string(),
590                input_mapping: HashMap::from([(
591                    "claim".to_string(),
592                    "steps.brutalhonesty.verdict".to_string(),
593                )]),
594                condition: Some(ChainCondition::ConfidenceBelow { threshold: 0.95 }),
595                config_override: None,
596            },
597        ],
598        min_confidence: 0.95,
599        token_budget: Some(25000),
600        tags: vec![
601            "ultimate".to_string(),
602            "all-tools".to_string(),
603            "maximum-rigor".to_string(),
604        ],
605    }
606}
607
608// ═══════════════════════════════════════════════════════════════════════════
609// TESTS
610// ═══════════════════════════════════════════════════════════════════════════
611
612#[cfg(test)]
613mod tests {
614    use super::*;
615
616    #[test]
617    fn test_profile_registry_creation() {
618        let registry = ProfileRegistry::new();
619        assert!(registry.is_empty());
620    }
621
622    #[test]
623    fn test_builtin_profiles() {
624        let registry = ProfileRegistry::with_builtins();
625
626        assert_eq!(registry.len(), 7);
627        assert!(registry.contains("quick"));
628        assert!(registry.contains("balanced"));
629        assert!(registry.contains("deep"));
630        assert!(registry.contains("paranoid"));
631        assert!(registry.contains("decide"));
632        assert!(registry.contains("scientific"));
633        assert!(registry.contains("powercombo"));
634    }
635
636    #[test]
637    fn test_get_profile() {
638        let registry = ProfileRegistry::with_builtins();
639
640        let quick = registry.get("quick").unwrap();
641        assert_eq!(quick.chain.len(), 2);
642        assert_eq!(quick.min_confidence, 0.70);
643
644        let paranoid = registry.get("paranoid").unwrap();
645        assert_eq!(paranoid.chain.len(), 6);
646        assert_eq!(paranoid.min_confidence, 0.95);
647    }
648
649    #[test]
650    fn test_profile_chain_structure() {
651        let registry = ProfileRegistry::with_builtins();
652        let balanced = registry.get("balanced").unwrap();
653
654        // Verify chain order
655        assert_eq!(balanced.chain[0].protocol_id, "gigathink");
656        assert_eq!(balanced.chain[1].protocol_id, "laserlogic");
657        assert_eq!(balanced.chain[2].protocol_id, "bedrock");
658        assert_eq!(balanced.chain[3].protocol_id, "proofguard");
659
660        // Verify conditional execution on bedrock
661        assert!(matches!(
662            balanced.chain[2].condition,
663            Some(ChainCondition::ConfidenceBelow { threshold: 0.9 })
664        ));
665    }
666
667    #[test]
668    fn test_list_profiles() {
669        let registry = ProfileRegistry::with_builtins();
670        let ids = registry.list_ids();
671
672        assert_eq!(ids.len(), 7);
673        assert!(ids.contains(&"quick"));
674        assert!(ids.contains(&"powercombo"));
675    }
676
677    #[test]
678    fn test_powercombo_profile() {
679        let registry = ProfileRegistry::with_builtins();
680        let powercombo = registry.get("powercombo").unwrap();
681
682        // Should have 6 steps (all 5 tools + validation pass)
683        assert_eq!(powercombo.chain.len(), 6);
684        assert_eq!(powercombo.min_confidence, 0.95);
685        assert_eq!(powercombo.token_budget, Some(25000));
686
687        // Verify chain order
688        assert_eq!(powercombo.chain[0].protocol_id, "gigathink");
689        assert_eq!(powercombo.chain[1].protocol_id, "laserlogic");
690        assert_eq!(powercombo.chain[2].protocol_id, "bedrock");
691        assert_eq!(powercombo.chain[3].protocol_id, "proofguard");
692        assert_eq!(powercombo.chain[4].protocol_id, "brutalhonesty");
693        assert_eq!(powercombo.chain[5].protocol_id, "proofguard"); // validation pass
694    }
695}