use crate::cognition::goal::GoalEvaluator;
use crate::cognition::memory::{ColdStore, HotStore, MemoryEntry};
use crate::cognition::reflect::Reflector;
use crate::cognition::search::SearchOracle;
use crate::cognition::signal::CognitionSignal;
use crate::types::ThinkResult;
#[derive(Debug)]
pub struct ThinkLoop {
pub goal: String,
pub max_iterations: usize,
pub convergence_threshold: f64,
pub k_proportional: f64,
pub k_integral: f64,
pub stall_patience: usize,
pub promotion_threshold: f64,
pub hot: HotStore,
pub cold: ColdStore,
}
impl ThinkLoop {
pub fn new(
goal: impl Into<String>,
max_iterations: usize,
convergence_threshold: f64,
k_proportional: f64,
k_integral: f64,
) -> Self {
Self {
goal: goal.into(),
max_iterations: max_iterations.max(1),
convergence_threshold: convergence_threshold.clamp(0.0, 1.0),
k_proportional,
k_integral,
stall_patience: 3,
promotion_threshold: 0.5,
hot: HotStore::new(16),
cold: ColdStore::default(),
}
}
pub fn builder(goal: impl Into<String>) -> ThinkLoopBuilder {
ThinkLoopBuilder::new(goal)
}
pub fn stall_patience(mut self, n: usize) -> Self {
self.stall_patience = n.max(1);
self
}
pub fn promotion_threshold(mut self, t: f64) -> Self {
self.promotion_threshold = t.clamp(0.0, 1.0);
self
}
pub async fn run(&mut self, oracle: &mut SearchOracle) -> ThinkResult {
let evaluator = GoalEvaluator::new(self.convergence_threshold);
let mut integral = 0.0_f64;
let mut prev_reward = f64::NEG_INFINITY;
let mut stall_streak = 0_usize;
let mut converged = false;
let mut final_error = 1.0_f64;
let mut signals: Vec<CognitionSignal> = Vec::with_capacity(self.max_iterations);
for step in 0..self.max_iterations {
let query = Reflector::formulate_query(&self.goal, &self.hot);
let observation = oracle.fetch(&query).await;
let signal = CognitionSignal::new(
step,
query.clone(),
observation.clone(),
self.k_proportional,
integral,
);
integral = signal.integral;
let current_reward = signal.reward;
final_error = signal.error;
let fact_content = if observation.is_empty() {
String::new()
} else {
observation
};
if !fact_content.is_empty() {
self.hot
.push(MemoryEntry::new(fact_content, current_reward, step));
}
signals.push(signal);
if evaluator.is_converged(final_error) {
converged = true;
break;
}
if current_reward < prev_reward {
stall_streak += 1;
if stall_streak >= self.stall_patience {
break;
}
} else {
stall_streak = 0;
}
prev_reward = current_reward;
}
Reflector::drain_to_cold(&mut self.hot, &mut self.cold, self.promotion_threshold);
let steps = signals.len();
ThinkResult {
converged,
steps,
final_error,
memory_snapshot: self.hot.snapshot(),
signals,
}
}
}
pub struct ThinkLoopBuilder {
goal: String,
max_iterations: usize,
convergence_threshold: f64,
k_proportional: f64,
k_integral: f64,
stall_patience: usize,
promotion_threshold: f64,
hot_capacity: usize,
}
impl ThinkLoopBuilder {
fn new(goal: impl Into<String>) -> Self {
Self {
goal: goal.into(),
max_iterations: 10,
convergence_threshold: 0.25,
k_proportional: 1.0,
k_integral: 0.05,
stall_patience: 3,
promotion_threshold: 0.5,
hot_capacity: 16,
}
}
pub fn max_iterations(mut self, n: usize) -> Self {
self.max_iterations = n;
self
}
pub fn convergence_threshold(mut self, t: f64) -> Self {
self.convergence_threshold = t;
self
}
pub fn k_proportional(mut self, kp: f64) -> Self {
self.k_proportional = kp;
self
}
pub fn k_integral(mut self, ki: f64) -> Self {
self.k_integral = ki;
self
}
pub fn stall_patience(mut self, n: usize) -> Self {
self.stall_patience = n;
self
}
pub fn promotion_threshold(mut self, t: f64) -> Self {
self.promotion_threshold = t;
self
}
pub fn hot_capacity(mut self, cap: usize) -> Self {
self.hot_capacity = cap;
self
}
pub fn build(self) -> ThinkLoop {
ThinkLoop {
goal: self.goal,
max_iterations: self.max_iterations.max(1),
convergence_threshold: self.convergence_threshold.clamp(0.0, 1.0),
k_proportional: self.k_proportional,
k_integral: self.k_integral,
stall_patience: self.stall_patience.max(1),
promotion_threshold: self.promotion_threshold.clamp(0.0, 1.0),
hot: HotStore::new(self.hot_capacity.max(1)),
cold: ColdStore::default(),
}
}
}