brainwires-cognition 0.8.0

Unified intelligence layer — knowledge graphs, adaptive prompting, RAG, spectral math, and code analysis for the Brainwires Agent Framework
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
//! Temperature Optimization
//!
//! This module provides adaptive temperature selection per task cluster,
//! based on the paper's findings:
//! - Low temp (0.0): Best for logical tasks (Zebra Puzzles, Web of Lies, Boolean Expressions)
//! - High temp (1.3): Best for linguistic tasks (Hyperbaton - adjective order judgment)
//!
//! Temperature performance is tracked per cluster and can be shared via BKS/PKS.

use super::clustering::TaskCluster;
#[cfg(feature = "knowledge")]
use crate::knowledge::bks_pks::{
    BehavioralKnowledgeCache, BehavioralTruth, TruthCategory, TruthSource,
};
use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::Mutex;

/// Tracks performance metrics for a specific temperature setting
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TemperaturePerformance {
    /// Success rate (0.0-1.0) using EMA
    pub success_rate: f32,
    /// Average quality score (0.0-1.0) using EMA
    pub avg_quality: f32,
    /// Number of samples collected
    pub sample_count: u32,
    /// Last updated timestamp
    pub last_updated: i64,
}

impl TemperaturePerformance {
    /// Create a new performance record with neutral defaults.
    pub fn new() -> Self {
        Self {
            success_rate: 0.5, // Neutral starting point
            avg_quality: 0.5,
            sample_count: 0,
            last_updated: chrono::Utc::now().timestamp(),
        }
    }

    /// Update metrics with new outcome using EMA (alpha = 0.3)
    pub fn update(&mut self, success: bool, quality: f32) {
        let alpha = 0.3;
        self.success_rate =
            alpha * (if success { 1.0 } else { 0.0 }) + (1.0 - alpha) * self.success_rate;
        self.avg_quality = alpha * quality + (1.0 - alpha) * self.avg_quality;
        self.sample_count += 1;
        self.last_updated = chrono::Utc::now().timestamp();
    }

    /// Combined score for ranking (60% success rate, 40% quality)
    pub fn score(&self) -> f32 {
        0.6 * self.success_rate + 0.4 * self.avg_quality
    }
}

impl Default for TemperaturePerformance {
    fn default() -> Self {
        Self::new()
    }
}

/// Manages adaptive temperature selection per task cluster
pub struct TemperatureOptimizer {
    /// Maps (cluster_id, temperature_int) → performance stats
    /// Temperature stored as i32 (multiply by 10: 0.0 → 0, 0.2 → 2, 1.3 → 13)
    performance_map: HashMap<(String, i32), TemperaturePerformance>,
    /// BKS cache for querying shared temperature preferences
    bks_cache: Option<Arc<Mutex<BehavioralKnowledgeCache>>>,
    /// Candidate temperatures to test (from paper)
    candidates: Vec<f32>,
    /// Minimum samples before trusting a temperature setting
    min_samples: u32,
}

impl TemperatureOptimizer {
    /// Create a new temperature optimizer
    pub fn new() -> Self {
        Self {
            performance_map: HashMap::new(),
            bks_cache: None,
            candidates: vec![0.0, 0.2, 0.4, 0.6, 0.8, 1.0, 1.3],
            min_samples: 5,
        }
    }

    /// Convert temperature f32 to i32 for HashMap key
    fn temp_to_key(temp: f32) -> i32 {
        (temp * 10.0).round() as i32
    }

    /// Set BKS cache for querying shared temperature knowledge
    pub fn with_bks(mut self, bks_cache: Arc<Mutex<BehavioralKnowledgeCache>>) -> Self {
        self.bks_cache = Some(bks_cache);
        self
    }

    /// Set minimum samples required before trusting a temperature
    pub fn with_min_samples(mut self, min_samples: u32) -> Self {
        self.min_samples = min_samples;
        self
    }

    /// Get optimal temperature for a cluster
    ///
    /// Selection order:
    /// 1. BKS shared knowledge (if available)
    /// 2. Local learned temperature (if enough samples)
    /// 3. Default heuristic based on cluster characteristics
    pub async fn get_optimal_temperature(&self, cluster: &TaskCluster) -> f32 {
        // Source 1: BKS shared knowledge
        if let Some(bks_temp) = self.query_bks_temperature(&cluster.id).await {
            return bks_temp;
        }

        // Source 2: Local learned temperature
        if let Some(local_temp) = self.get_local_optimal(&cluster.id) {
            return local_temp;
        }

        // Source 3: Default heuristic based on cluster characteristics
        self.get_default_temperature(cluster)
    }

    /// Get locally learned optimal temperature
    fn get_local_optimal(&self, cluster_id: &str) -> Option<f32> {
        let mut best_temp = None;
        let mut best_score = f32::NEG_INFINITY;

        for &temp in &self.candidates {
            let temp_key = Self::temp_to_key(temp);
            if let Some(perf) = self
                .performance_map
                .get(&(cluster_id.to_string(), temp_key))
                && perf.sample_count >= self.min_samples
            {
                let score = perf.score();
                if score > best_score {
                    best_score = score;
                    best_temp = Some(temp);
                }
            }
        }

        best_temp
    }

    /// Query BKS for shared temperature knowledge
    async fn query_bks_temperature(&self, cluster_id: &str) -> Option<f32> {
        if let Some(ref bks_cache) = self.bks_cache {
            let bks = bks_cache.lock().await;

            // Query for temperature truths for this cluster
            // get_matching_truths takes just a context string
            let truths = bks.get_matching_truths(cluster_id);

            // Parse temperature from truth content
            // Example: "For numerical_reasoning, use temperature 0.0 for optimal results"
            for truth in truths {
                // Filter for TaskStrategy category
                if truth.category == TruthCategory::TaskStrategy
                    && let Some(temp) = self.parse_temperature_from_truth(truth)
                {
                    return Some(temp);
                }
            }
        }

        None
    }

    /// Parse temperature value from BKS truth
    fn parse_temperature_from_truth(&self, truth: &BehavioralTruth) -> Option<f32> {
        // Look for "temperature X.X" pattern in rule or rationale
        let text = format!("{} {}", truth.rule, truth.rationale);

        // Simple regex-like parsing
        if let Some(idx) = text.find("temperature") {
            let substr = &text[idx..];
            // Find first number after "temperature"
            let parts: Vec<&str> = substr.split_whitespace().collect();
            for part in parts.iter().skip(1) {
                if let Ok(temp) = part.parse::<f32>()
                    && self.candidates.contains(&temp)
                {
                    return Some(temp);
                }
            }
        }

        None
    }

    /// Get default temperature based on cluster characteristics (heuristic)
    fn get_default_temperature(&self, cluster: &TaskCluster) -> f32 {
        let desc = cluster.description.to_lowercase();

        // Logic/reasoning tasks: Low temperature (0.0)
        if desc.contains("logic")
            || desc.contains("boolean")
            || desc.contains("reasoning")
            || desc.contains("puzzle")
            || desc.contains("deduction")
        {
            return 0.0;
        }

        // Creative/linguistic tasks: High temperature (1.3)
        if desc.contains("creative")
            || desc.contains("linguistic")
            || desc.contains("story")
            || desc.contains("writing")
            || desc.contains("generation")
        {
            return 1.3;
        }

        // Numerical/calculation tasks: Low temperature (0.2)
        if desc.contains("numerical")
            || desc.contains("calculation")
            || desc.contains("math")
            || desc.contains("arithmetic")
        {
            return 0.2;
        }

        // Code generation: Moderate temperature (0.6)
        if desc.contains("code")
            || desc.contains("programming")
            || desc.contains("implementation")
            || desc.contains("algorithm")
        {
            return 0.6;
        }

        // Default: Moderate temperature
        0.7
    }

    /// Record outcome for a temperature setting
    pub fn record_temperature_outcome(
        &mut self,
        cluster_id: String,
        temperature: f32,
        success: bool,
        quality: f32,
    ) {
        let temp_key = Self::temp_to_key(temperature);
        let key = (cluster_id, temp_key);
        let perf = self.performance_map.entry(key).or_default();

        perf.update(success, quality);
    }

    /// Check if temperature should be promoted to BKS
    pub async fn check_and_promote_temperature(
        &self,
        cluster_id: &str,
        temperature: f32,
        min_score: f32,
        min_samples: u32,
    ) -> Result<()> {
        let temp_key = Self::temp_to_key(temperature);
        let key = (cluster_id.to_string(), temp_key);

        if let Some(perf) = self.performance_map.get(&key)
            && perf.sample_count >= min_samples
            && perf.score() >= min_score
        {
            // Promote to BKS
            if let Some(ref bks_cache) = self.bks_cache {
                let truth = BehavioralTruth::new(
                    TruthCategory::TaskStrategy,
                    cluster_id.to_string(),
                    format!(
                        "For {} tasks, use temperature {} for optimal results",
                        cluster_id, temperature
                    ),
                    format!(
                        "Learned from {} executions with {:.1}% success rate and {:.2} avg quality",
                        perf.sample_count,
                        perf.success_rate * 100.0,
                        perf.avg_quality
                    ),
                    TruthSource::SuccessPattern,
                    None,
                );

                let mut bks = bks_cache.lock().await;
                bks.queue_submission(truth)?;
            }
        }

        Ok(())
    }

    /// Get all performance data (for debugging/inspection)
    pub fn get_all_performance(&self) -> &HashMap<(String, i32), TemperaturePerformance> {
        &self.performance_map
    }

    /// Get performance for a specific cluster and temperature
    pub fn get_performance(
        &self,
        cluster_id: &str,
        temperature: f32,
    ) -> Option<&TemperaturePerformance> {
        let temp_key = Self::temp_to_key(temperature);
        self.performance_map
            .get(&(cluster_id.to_string(), temp_key))
    }
}

impl Default for TemperatureOptimizer {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::prompting::techniques::PromptingTechnique;

    #[test]
    fn test_temperature_performance_update() {
        let mut perf = TemperaturePerformance::new();
        assert_eq!(perf.success_rate, 0.5);
        assert_eq!(perf.sample_count, 0);

        // Record success
        perf.update(true, 0.9);
        assert!(perf.success_rate > 0.5); // Should increase
        assert_eq!(perf.sample_count, 1);

        // Record failure
        perf.update(false, 0.3);
        assert_eq!(perf.sample_count, 2);
        assert!(perf.avg_quality < 0.9); // Should decrease
    }

    #[test]
    fn test_temperature_performance_score() {
        let mut perf = TemperaturePerformance::new();
        perf.success_rate = 0.8;
        perf.avg_quality = 0.7;

        let score = perf.score();
        assert!((score - 0.76).abs() < 0.01); // 0.6 * 0.8 + 0.4 * 0.7 = 0.76
    }

    #[test]
    fn test_default_temperature_heuristics() {
        let optimizer = TemperatureOptimizer::new();

        // Logic task: Low temperature
        let logic_cluster = TaskCluster::new(
            "logic_task".to_string(),
            "Boolean logic and reasoning puzzles".to_string(),
            vec![0.5; 768],
            vec![PromptingTechnique::LogicOfThought],
            vec![],
        );
        assert_eq!(optimizer.get_default_temperature(&logic_cluster), 0.0);

        // Creative task: High temperature
        let creative_cluster = TaskCluster::new(
            "creative_task".to_string(),
            "Creative writing and story generation".to_string(),
            vec![0.5; 768],
            vec![PromptingTechnique::RolePlaying],
            vec![],
        );
        assert_eq!(optimizer.get_default_temperature(&creative_cluster), 1.3);

        // Code task: Moderate temperature
        let code_cluster = TaskCluster::new(
            "code_task".to_string(),
            "Code implementation and algorithm design".to_string(),
            vec![0.5; 768],
            vec![PromptingTechnique::PlanAndSolve],
            vec![],
        );
        assert_eq!(optimizer.get_default_temperature(&code_cluster), 0.6);
    }

    #[test]
    fn test_record_and_retrieve_local_optimal() {
        let mut optimizer = TemperatureOptimizer::new();

        // Record outcomes for different temperatures
        for _ in 0..10 {
            optimizer.record_temperature_outcome("test_cluster".to_string(), 0.0, true, 0.9);
            optimizer.record_temperature_outcome("test_cluster".to_string(), 0.6, false, 0.5);
        }

        // Get optimal (should be 0.0 due to high success rate)
        let optimal = optimizer.get_local_optimal("test_cluster");
        assert_eq!(optimal, Some(0.0));
    }

    #[test]
    fn test_min_samples_requirement() {
        let mut optimizer = TemperatureOptimizer::new().with_min_samples(5);

        // Record only 3 samples
        for _ in 0..3 {
            optimizer.record_temperature_outcome("test_cluster".to_string(), 0.0, true, 0.95);
        }

        // Should not return optimal (not enough samples)
        assert_eq!(optimizer.get_local_optimal("test_cluster"), None);

        // Add 2 more samples
        for _ in 0..2 {
            optimizer.record_temperature_outcome("test_cluster".to_string(), 0.0, true, 0.95);
        }

        // Now should return optimal
        assert_eq!(optimizer.get_local_optimal("test_cluster"), Some(0.0));
    }

    #[tokio::test]
    async fn test_get_optimal_temperature_fallback() {
        let optimizer = TemperatureOptimizer::new();

        // No BKS, no local data → should use heuristic
        let cluster = TaskCluster::new(
            "logic_test".to_string(),
            "Boolean logic problems".to_string(),
            vec![0.5; 768],
            vec![PromptingTechnique::LogicOfThought],
            vec![],
        );

        let temp = optimizer.get_optimal_temperature(&cluster).await;
        assert_eq!(temp, 0.0); // Heuristic for logic tasks
    }
}