use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use super::belief::SharedBeliefState;
use super::gate::GateResult;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum GainMode {
Phasic,
Tonic,
#[default]
Neutral,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum ResponseStrategy {
ClarifyFirst,
StepByStep,
StructuredAnalysis,
ExecuteTask,
DirectAnswer,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum StrategyConfidence {
Habit,
Strong,
Moderate,
Weak,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum StrategyCompliance {
Compliant,
DeviatedBetter,
DeviatedWorse,
NoRecommendation,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct SuccessEntry {
pub success_rate: f64,
pub count: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StrategyCompetition {
pub winner: Option<StrategyCompetitionEntry>,
pub runner_up: Option<StrategyCompetitionEntry>,
pub margin: f64,
pub urgency: f64,
pub should_commit: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StrategyCompetitionEntry {
pub name: String,
pub score: f64,
pub confidence: Option<StrategyConfidence>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum ConversationRegime {
#[default]
Opening,
Exploration,
DeepDive,
ProblemSolving,
Divergent,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DynamicsState {
pub regime: ConversationRegime,
pub depth: f64,
pub turns_in_regime: u32,
pub accumulated_turn_pe: f64,
}
impl Default for DynamicsState {
fn default() -> Self {
Self {
regime: ConversationRegime::Opening,
depth: 0.0,
turns_in_regime: 0,
accumulated_turn_pe: 0.0,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LearnedState {
pub gain_mode: GainMode,
pub tick: u32,
pub response_success: HashMap<String, SuccessEntry>,
pub response_strategies: HashMap<String, HashMap<String, SuccessEntry>>,
}
impl Default for LearnedState {
fn default() -> Self {
Self {
gain_mode: GainMode::Neutral,
tick: 0,
response_success: HashMap::new(),
response_strategies: HashMap::new(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorldModel {
pub belief: SharedBeliefState,
pub gate: GateResult,
pub sensory_pe: f64,
pub body_budget: f64,
pub idle_cycles: u32,
pub turns_since_switch: u32,
pub resource_pressure: f64,
pub last_response_prediction: f64,
pub response_rpe: f64,
pub pe_history: Vec<f64>,
pub pe_volatility: f64,
pub last_response_strategy: Option<ResponseStrategy>,
pub last_response_length: Option<usize>,
pub last_response_question_ratio: Option<f64>,
pub pre_computed_competition: Option<StrategyCompetition>,
pub pre_computed_for_cluster: Option<String>,
pub last_composition: Option<String>,
pub discussed_topics: HashMap<String, u32>,
pub recommended_strategy: Option<ResponseStrategy>,
pub last_compliance: Option<StrategyCompliance>,
pub dynamics: Option<DynamicsState>,
pub learned: LearnedState,
}
impl WorldModel {
pub fn new(conversation_id: String) -> Self {
Self {
belief: SharedBeliefState::new(conversation_id),
gate: GateResult::default(),
sensory_pe: 0.0,
body_budget: 1.0, idle_cycles: 0,
turns_since_switch: 0,
resource_pressure: 0.0,
last_response_prediction: 0.5,
response_rpe: 0.0,
pe_history: Vec::new(),
pe_volatility: 0.0,
last_response_strategy: None,
last_response_length: None,
last_response_question_ratio: None,
pre_computed_competition: None,
pre_computed_for_cluster: None,
last_composition: None,
discussed_topics: HashMap::new(),
recommended_strategy: None,
last_compliance: None,
dynamics: None,
learned: LearnedState::default(),
}
}
}
pub const PE_VOLATILITY_WINDOW: usize = 5;
pub const RESPONSE_SUCCESS_EMA: f64 = 0.25;
pub const HABIT_MIN_COUNT: u32 = 12;
pub const HABIT_MIN_SUCCESS: f64 = 0.75;
pub const STRONG_MIN_COUNT: u32 = 8;
pub const STRONG_MIN_SUCCESS: f64 = 0.65;
pub const MODERATE_MIN_COUNT: u32 = 5;
pub const MODERATE_MIN_SUCCESS: f64 = 0.5;
pub const AVOID_MAX_SUCCESS: f64 = 0.4;
pub const AVOID_MIN_COUNT: u32 = 5;
pub fn classify_strategy_confidence(success_rate: f64, count: u32) -> StrategyConfidence {
if count >= HABIT_MIN_COUNT && success_rate >= HABIT_MIN_SUCCESS {
StrategyConfidence::Habit
} else if count >= STRONG_MIN_COUNT && success_rate >= STRONG_MIN_SUCCESS {
StrategyConfidence::Strong
} else if count >= MODERATE_MIN_COUNT && success_rate >= MODERATE_MIN_SUCCESS {
StrategyConfidence::Moderate
} else {
StrategyConfidence::Weak
}
}
pub fn should_avoid_strategy(success_rate: f64, count: u32) -> bool {
count >= AVOID_MIN_COUNT && success_rate <= AVOID_MAX_SUCCESS
}
pub fn get_recommended_strategy(
topic_cluster: &str,
learned: &LearnedState,
) -> Option<(ResponseStrategy, StrategyConfidence)> {
let strategies = learned.response_strategies.get(topic_cluster)?;
let mut best: Option<(ResponseStrategy, StrategyConfidence, f64)> = None;
for (strategy_key, entry) in strategies {
if should_avoid_strategy(entry.success_rate, entry.count) {
continue;
}
let confidence = classify_strategy_confidence(entry.success_rate, entry.count);
if matches!(confidence, StrategyConfidence::Weak) {
continue;
}
let strategy = match strategy_key.as_str() {
"ClarifyFirst" => ResponseStrategy::ClarifyFirst,
"StepByStep" => ResponseStrategy::StepByStep,
"StructuredAnalysis" => ResponseStrategy::StructuredAnalysis,
"ExecuteTask" => ResponseStrategy::ExecuteTask,
"DirectAnswer" => ResponseStrategy::DirectAnswer,
_ => continue, };
let dominated = best
.as_ref()
.is_some_and(|(_, _, best_rate)| entry.success_rate <= *best_rate);
if !dominated {
best = Some((strategy, confidence, entry.success_rate));
}
}
best.map(|(strategy, confidence, _)| (strategy, confidence))
}