use crate::cognition::convergence::{converge, ConvergenceContext};
use crate::cognition::delta_modulation::compute_delta_modulation;
use crate::cognition::intervention::{build_cognitive_state, compute_sampling_override};
use crate::cognition::locus_coeruleus::LocusCoeruleus;
use crate::cognition::resource_allocator::ModelTier;
use crate::cognition::signals::compute_signals;
use crate::cognition::world_model::{consolidate, maintain, perceive};
use crate::math::clamp;
use crate::types::belief::AffectValence;
use crate::types::gate::{GateType, RecentMessage};
use crate::types::intervention::{CognitiveSignals, CognitiveState, DeltaModulation, SamplingOverride};
use crate::types::world::{GainMode, LearnedState, ResponseStrategy, WorldModel};
const MAX_RECENT_MESSAGES: usize = 10;
const COST_DEPLETION_RATE: f64 = 0.02;
const GATE_ACTIVE_MIN_ALPHA: f64 = 0.1;
const GATE_AROUSAL_PRIOR_WEIGHT: f64 = 0.8;
const GATE_PASSIVE_DECAY: f64 = 0.95;
const GATE_SURPRISE_MIN_DELTA: f64 = 0.05;
const GATE_PE_PRIOR_WEIGHT: f64 = 0.7;
pub struct CognitiveSession {
model: WorldModel,
lc: LocusCoeruleus,
history: Vec<RecentMessage>,
turn_count: usize,
num_model_layers: usize,
}
#[derive(Debug, Clone)]
pub struct TurnResult {
pub cognitive_state: CognitiveState,
pub sampling: SamplingOverride,
pub delta_modulation: DeltaModulation,
pub convergence_iterations: usize,
pub converged: bool,
pub recommended_strategy: Option<ResponseStrategy>,
pub body_budget: f64,
pub sensory_pe: f64,
pub gain_mode: GainMode,
pub gate_type: GateType,
pub gate_confidence: f64,
pub arousal: f64,
pub valence: AffectValence,
pub signals: CognitiveSignals,
}
impl CognitiveSession {
pub fn new() -> Self {
Self {
model: WorldModel::new("session".into()),
lc: LocusCoeruleus::new(),
history: Vec::new(),
turn_count: 0,
num_model_layers: 64,
}
}
pub fn with_model_layers(num_layers: usize) -> Self {
Self {
num_model_layers: num_layers,
..Self::new()
}
}
pub fn process_message(&mut self, message: &str) -> TurnResult {
self.model = perceive(&self.model, message);
let ctx = ConvergenceContext {
model_tier: ModelTier::Medium,
fok_average: None, has_graph_data: false,
message_count: self.turn_count,
};
let convergence = converge(
&self.model,
message,
&self.history,
&mut self.lc,
&ctx,
);
self.model = convergence.model;
let cognitive_state = build_cognitive_state(&self.model, self.lc.gain_mode());
let sampling = compute_sampling_override(&cognitive_state);
let delta_modulation =
compute_delta_modulation(&cognitive_state, self.num_model_layers);
let signals = compute_signals(&self.model, self.lc.gain_mode());
self.history.push(RecentMessage {
role: "user".into(),
content: message.to_string(),
});
if self.history.len() > MAX_RECENT_MESSAGES {
self.history.remove(0);
}
self.turn_count += 1;
TurnResult {
cognitive_state,
sampling,
delta_modulation,
convergence_iterations: convergence.iterations,
converged: convergence.converged,
recommended_strategy: self.model.recommended_strategy,
body_budget: self.model.body_budget,
sensory_pe: self.model.sensory_pe,
gain_mode: self.lc.gain_mode(),
gate_type: self.model.gate.gate,
gate_confidence: self.model.gate.confidence,
arousal: self.model.belief.affect.arousal,
valence: self.model.belief.affect.valence,
signals,
}
}
pub fn process_response(&mut self, response: &str, quality: f64) {
if !quality.is_finite() {
return;
}
self.model = consolidate(&self.model, response, quality);
self.history.push(RecentMessage {
role: "assistant".into(),
content: response.to_string(),
});
if self.history.len() > MAX_RECENT_MESSAGES {
self.history.remove(0);
}
}
pub fn idle_cycle(&mut self) {
self.model = maintain(&self.model);
}
pub fn track_cost(&mut self, cost: f64) {
let cost = clamp(cost, 0.0, 1.0);
let depletion = cost * COST_DEPLETION_RATE;
self.model.body_budget = clamp(self.model.body_budget - depletion, 0.0, 1.0);
}
pub fn inject_gate_feedback(&mut self, gate_alpha: f64, gate_delta_gain: f64) {
if gate_alpha > GATE_ACTIVE_MIN_ALPHA {
self.model.belief.affect.arousal = self.model.belief.affect.arousal
* GATE_AROUSAL_PRIOR_WEIGHT
+ gate_alpha * (1.0 - GATE_AROUSAL_PRIOR_WEIGHT);
} else {
self.model.belief.affect.arousal *= GATE_PASSIVE_DECAY;
}
let gate_surprise = (gate_delta_gain - 1.0).abs();
if gate_surprise > GATE_SURPRISE_MIN_DELTA {
self.model.sensory_pe = self.model.sensory_pe * GATE_PE_PRIOR_WEIGHT
+ gate_surprise * (1.0 - GATE_PE_PRIOR_WEIGHT);
}
self.lc.set_arousal(self.model.belief.affect.arousal);
}
pub fn world_model(&self) -> &WorldModel {
&self.model
}
pub fn turn_count(&self) -> usize {
self.turn_count
}
pub fn export_learned(&self) -> LearnedState {
let mut snapshot = self.model.learned.clone();
self.lc.sync_to_learned(&mut snapshot);
snapshot
}
pub fn import_learned(&mut self, learned: LearnedState) {
self.model.learned = learned;
self.lc.sync_from_learned(&self.model.learned);
}
pub fn with_learned(learned: LearnedState, num_layers: usize) -> Self {
let mut session = Self::with_model_layers(num_layers);
session.import_learned(learned);
session
}
}
impl Default for CognitiveSession {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_session_starts_calm() {
let session = CognitiveSession::new();
assert_eq!(session.model.body_budget, 1.0);
assert_eq!(session.model.belief.affect.arousal, 0.0);
assert_eq!(session.turn_count, 0);
}
#[test]
fn process_message_returns_turn_result() {
let mut session = CognitiveSession::new();
let result = session.process_message("Hello, how are you?");
assert!(result.converged);
assert!(result.body_budget > 0.0);
assert!(result.sampling.temperature > 0.0);
assert_eq!(session.turn_count, 1);
}
#[test]
fn arousal_rises_on_emotional_input() {
let mut session = CognitiveSession::new();
let calm = session.process_message("Hello");
let angry = session.process_message("I'm so frustrated!!! Nothing works!!!");
assert!(
angry.arousal > calm.arousal,
"Emotional input should raise arousal"
);
}
#[test]
fn body_budget_depletes_under_stress() {
let mut session = CognitiveSession::new();
let initial_budget = session.world_model().body_budget;
for _ in 0..5 {
session.process_message("This is terrible!!! Everything is broken!!!");
}
assert!(
session.world_model().body_budget < initial_budget,
"Repeated stress should deplete body budget"
);
}
#[test]
fn process_response_updates_learning() {
let mut session = CognitiveSession::new();
session.process_message("How do I use async in Rust?");
session.process_response(
"Here's a step-by-step guide:\n1. First, add tokio\n2. Then, use async fn",
0.8,
);
assert!(session.world_model().last_response_strategy.is_some());
}
#[test]
fn process_response_nan_quality_dropped_silently() {
let mut session = CognitiveSession::new();
session.process_message("hello");
let initial_rpe = session.world_model().response_rpe;
session.process_response("ok", f64::NAN);
let after_rpe = session.world_model().response_rpe;
assert_eq!(
initial_rpe, after_rpe,
"NaN quality should be silently dropped, leaving response_rpe untouched"
);
assert!(
session.world_model().body_budget.is_finite()
&& (0.0..=1.0).contains(&session.world_model().body_budget),
"body_budget stays in [0, 1] after NaN quality"
);
}
#[test]
fn process_response_infinity_quality_dropped_silently() {
let mut session = CognitiveSession::new();
session.process_message("hello");
session.process_response("ok", f64::INFINITY);
assert!(session.world_model().response_rpe.is_finite());
session.process_response("ok", f64::NEG_INFINITY);
assert!(session.world_model().response_rpe.is_finite());
}
#[test]
fn strategy_learning_across_turns() {
let mut session = CognitiveSession::new();
for i in 0..10 {
session.process_message(&format!("Tell me about Rust topic {i}"));
session.process_response(
"Here's a step-by-step explanation:\n1. First step\n2. Second step\n3. Third step",
0.85,
);
}
let _result = session.process_message("Another Rust question");
assert!(!session.world_model().learned.response_strategies.is_empty());
}
#[test]
fn turn_result_includes_delta_modulation() {
let mut session = CognitiveSession::new();
let result = session.process_message("Hello");
assert_eq!(
result.delta_modulation.gain_factor, 1.0,
"Calm message should produce neutral delta modulation"
);
assert_eq!(result.delta_modulation.target.total_layers, 64);
}
#[test]
fn custom_model_layers() {
let mut session = CognitiveSession::with_model_layers(32);
let result = session.process_message("Hello");
assert_eq!(
result.delta_modulation.target.total_layers, 32,
"Should target 32-layer model"
);
}
#[test]
fn history_limited_to_max() {
let mut session = CognitiveSession::new();
for i in 0..20 {
session.process_message(&format!("Message {i}"));
session.process_response(&format!("Response {i}"), 0.7);
}
assert!(session.history.len() <= MAX_RECENT_MESSAGES);
}
#[test]
fn convergence_settles_quickly() {
let mut session = CognitiveSession::new();
let result = session.process_message("Simple hello");
assert!(result.convergence_iterations <= 5);
assert!(result.converged);
}
#[test]
fn idle_cycle_replenishes_budget() {
let mut session = CognitiveSession::new();
for _ in 0..5 {
session.process_message("Everything is terrible!!!");
}
let depleted = session.world_model().body_budget;
for _ in 0..10 {
session.idle_cycle();
}
assert!(
session.world_model().body_budget > depleted,
"Idle cycles should replenish body budget"
);
}
#[test]
fn gate_feedback_high_alpha_increases_arousal() {
let mut session = CognitiveSession::new();
let initial = session.world_model().belief.affect.arousal;
session.inject_gate_feedback(0.8, 1.0);
assert!(
session.world_model().belief.affect.arousal > initial,
"High gate_alpha should increase arousal"
);
}
#[test]
fn gate_feedback_low_alpha_decays_arousal() {
let mut session = CognitiveSession::new();
session.inject_gate_feedback(0.9, 1.0);
let elevated = session.world_model().belief.affect.arousal;
assert!(elevated > 0.0);
for _ in 0..5 {
session.inject_gate_feedback(0.0, 1.0);
}
assert!(
session.world_model().belief.affect.arousal < elevated,
"Low gate_alpha should decay arousal"
);
}
#[test]
fn gate_feedback_delta_deviation_updates_pe() {
let mut session = CognitiveSession::new();
let initial_pe = session.world_model().sensory_pe;
session.inject_gate_feedback(0.0, 1.5);
assert!(
session.world_model().sensory_pe > initial_pe,
"Delta gain deviation from 1.0 should increase sensory PE"
);
}
#[test]
fn export_import_preserves_learned_state() {
let mut session = CognitiveSession::new();
for i in 0..8 {
session.process_message(&format!("How to fix Rust error {i}?"));
session.process_response(
"Step 1: Check the error\nStep 2: Fix it\nStep 3: Test",
0.8,
);
}
let learned = session.export_learned();
let has_data = !learned.response_strategies.is_empty()
|| !learned.response_success.is_empty()
|| learned.tick > 0;
assert!(has_data, "Should have learned some cross-session data");
assert!(
learned.tick > 0,
"LC tick must be flushed into exported LearnedState, got {}",
learned.tick
);
let session2 = CognitiveSession::with_learned(learned.clone(), 64);
assert_eq!(
session2.world_model().learned.tick,
learned.tick,
"Imported session should preserve tick count"
);
}
#[test]
fn export_import_preserves_strategies() {
let mut session = CognitiveSession::new();
for i in 0..10 {
session.process_message(&format!("Rust question {i}"));
session.process_response(
"Step 1: First\nStep 2: Second\nStep 3: Third",
0.85,
);
}
let learned = session.export_learned();
assert!(
!learned.response_strategies.is_empty(),
"Should have learned strategy data"
);
let session2 = CognitiveSession::with_learned(learned.clone(), 64);
assert_eq!(
session2.world_model().learned.response_strategies.len(),
learned.response_strategies.len(),
);
}
#[test]
fn signals_reflect_cognitive_state() {
let mut session = CognitiveSession::new();
let calm = session.process_message("Hello, how are you?");
assert!(calm.signals.conservation < 0.3, "Calm message → low conservation");
assert!(calm.signals.salience < 0.5, "Calm message → low salience");
for _ in 0..5 {
session.process_message("Everything is terrible!!! PANIC!!!");
}
let stressed = session.process_message("Still terrible!!!");
assert!(
stressed.signals.conservation > calm.signals.conservation,
"Stressed session should have higher conservation than calm"
);
}
#[test]
fn track_cost_depletes_budget() {
let mut session = CognitiveSession::new();
let initial = session.world_model().body_budget;
session.track_cost(1.0); let after = session.world_model().body_budget;
assert!(after < initial, "Full cost should deplete body budget");
}
#[test]
fn track_cost_zero_no_change() {
let mut session = CognitiveSession::new();
let initial = session.world_model().body_budget;
session.track_cost(0.0);
assert_eq!(session.world_model().body_budget, initial,
"Zero cost should not deplete budget");
}
#[test]
fn track_cost_clamps_invalid_input() {
let mut session = CognitiveSession::new();
session.track_cost(10.0); assert!(
session.world_model().body_budget >= 1.0 - 0.021,
"Cost must clamp — over-range should not deplete more than 1.0 worth"
);
}
#[test]
fn track_cost_accumulates_across_calls() {
let mut session = CognitiveSession::new();
let initial = session.world_model().body_budget;
for _ in 0..30 {
session.track_cost(0.8);
}
let depleted = session.world_model().body_budget;
assert!(
depleted < initial - 0.1,
"Sustained cost should accumulate depletion, initial={} final={}",
initial, depleted
);
}
#[test]
fn track_cost_nan_does_not_corrupt_body_budget() {
let mut session = CognitiveSession::new();
let initial = session.world_model().body_budget;
session.track_cost(f64::NAN);
let after = session.world_model().body_budget;
assert!(
after.is_finite() && (0.0..=1.0).contains(&after),
"NaN cost must not corrupt body_budget: was {initial} → {after}"
);
}
#[test]
fn track_cost_infinity_does_not_corrupt_body_budget() {
let mut session = CognitiveSession::new();
session.track_cost(f64::INFINITY);
let pos_after = session.world_model().body_budget;
assert!(pos_after.is_finite() && (0.0..=1.0).contains(&pos_after));
let mut session2 = CognitiveSession::new();
session2.track_cost(f64::NEG_INFINITY);
let neg_after = session2.world_model().body_budget;
assert!(neg_after.is_finite() && (0.0..=1.0).contains(&neg_after));
}
#[test]
fn track_cost_reflected_in_conservation_signal() {
let mut session = CognitiveSession::new();
let baseline = session.process_message("Hello");
for _ in 0..20 {
session.track_cost(1.0);
}
let after_cost = session.process_message("Hello");
assert!(
after_cost.signals.conservation > baseline.signals.conservation,
"Cost-depleted budget should raise conservation signal"
);
}
}