use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct MetacognitionEntry {
pub task_description: String,
pub outcome: String,
pub retry_count: u8,
pub insight: String,
pub strategy_adjustment: Option<String>,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct MetacognitionEngine {
pub entries: Vec<MetacognitionEntry>,
pub consecutive_failures: u8,
pub consecutive_successes: u8,
}
impl MetacognitionEngine {
pub fn new() -> Self {
Self::default()
}
pub fn record(
&mut self,
task_description: &str,
outcome: &str,
retry_count: u8,
) -> MetacognitionEntry {
let is_failure = outcome == "skip" || outcome == "failed";
let is_success = outcome == "success" || outcome == "completed";
if is_failure {
self.consecutive_failures += 1;
self.consecutive_successes = 0;
} else if is_success {
self.consecutive_successes += 1;
self.consecutive_failures = 0;
}
let insight = self.derive_insight(retry_count);
let strategy_adjustment = if self.should_adjust() {
Some(self.derive_strategy_hint())
} else {
None
};
let entry = MetacognitionEntry {
task_description: task_description.to_string(),
outcome: outcome.to_string(),
retry_count,
insight: insight.clone(),
strategy_adjustment: strategy_adjustment.clone(),
};
self.entries.push(entry.clone());
entry
}
pub fn should_adjust(&self) -> bool {
self.consecutive_failures >= 2
|| self.entries.len().is_multiple_of(3) && !self.entries.is_empty()
}
fn derive_insight(&self, retry_count: u8) -> String {
if self.consecutive_failures >= 2 {
format!(
"{} consecutive failures detected. Strategy recalibration recommended.",
self.consecutive_failures
)
} else if retry_count > 0 {
format!(
"Task completed after {} retries. Consider adjusting action granularity.",
retry_count
)
} else if self.consecutive_successes >= 3 {
"Execution pipeline flowing well. Maintaining current strategy.".to_string()
} else {
"Task outcome recorded. Monitoring for patterns.".to_string()
}
}
fn derive_strategy_hint(&self) -> String {
let failure_tasks: Vec<&str> = self
.entries
.iter()
.filter(|e| e.outcome == "skip" || e.outcome == "failed")
.map(|e| e.task_description.as_str())
.collect();
if failure_tasks.is_empty() {
return "Review task decomposition for upcoming steps.".to_string();
}
let themes: Vec<String> = failure_tasks
.iter()
.take(3)
.map(|t| format!("- {}", &t[..t.len().min(60)]))
.collect();
format!(
"Recurring pattern in failed tasks:\n{}\nConsider breaking these into smaller actions.",
themes.join("\n")
)
}
pub fn to_prompt_context(&self) -> String {
let recent: Vec<String> = self
.entries
.iter()
.rev()
.take(5)
.rev()
.map(|e| {
format!(
" task: {} | outcome: {} | retries: {}",
&e.task_description[..e.task_description.len().min(60)],
e.outcome,
e.retry_count
)
})
.collect();
let hint = self.derive_strategy_hint();
format!(
"Recent task history:\n{}\n\nCurrent strategy pattern:\n{}",
recent.join("\n"),
hint
)
}
}