use serde::{Deserialize, Serialize};
use uuid::Uuid;
use vex_core::{Agent, AgentConfig};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ShadowConfig {
pub challenge_intensity: f64,
pub fact_check: bool,
pub logic_check: bool,
}
impl Default for ShadowConfig {
fn default() -> Self {
Self {
challenge_intensity: 0.7,
fact_check: true,
logic_check: true,
}
}
}
#[derive(Debug, Clone)]
pub struct ShadowAgent {
pub agent: Agent,
pub config: ShadowConfig,
pub blue_agent_id: Uuid,
}
impl ShadowAgent {
pub fn new(blue_agent: &Agent, config: ShadowConfig) -> Self {
let agent_config = AgentConfig {
name: format!("{}_shadow", blue_agent.config.name),
role: format!(
"You are a critical challenger. Your job is to find flaws, \
inconsistencies, and potential errors in the following claim. \
Challenge intensity: {:.0}%",
config.challenge_intensity * 100.0
),
max_depth: 0, spawn_shadow: false, };
let mut agent = blue_agent.spawn_child(agent_config);
agent.shadow_id = None;
Self {
agent,
config,
blue_agent_id: blue_agent.id,
}
}
pub fn challenge_prompt(&self, claim: &str) -> String {
let mut challenge_types = Vec::new();
if self.config.fact_check {
challenge_types.push("factual accuracy");
}
if self.config.logic_check {
challenge_types.push("logical consistency");
}
let detected_issues = self.detect_issues(claim);
let issue_guidance = if detected_issues.is_empty() {
String::from("Look for hidden assumptions, unstated premises, and edge cases.")
} else {
format!(
"Pay special attention to these potential issues: {}",
detected_issues.join("; ")
)
};
format!(
"Critically analyze the following claim for {}:\n\n\
\"{}\"\n\n\
{}\n\n\
For each issue found:
1. State the specific problem
2. Explain why it matters
3. Suggest how it could be verified or corrected
If any issues are found, start your response with the marker: [CHALLENGE]
If no issues are found, start your response with the marker: [CLEAN]
If [CLEAN], explain what makes the claim robust.",
challenge_types.join(" and "),
claim,
issue_guidance
)
}
pub fn detect_issues(&self, claim: &str) -> Vec<String> {
let mut areas_of_interest = Vec::new();
let claim_lower = claim.to_lowercase();
if claim_lower.contains("always")
|| claim_lower.contains("never")
|| claim_lower.contains("all ")
|| claim_lower.contains("none ")
{
areas_of_interest.push("Over-generalization: Verify if universal claims 'always'/'never' hold true for all edge cases.".to_string());
}
if claim_lower.contains("many")
|| claim_lower.contains("some")
|| claim_lower.contains("significant")
{
areas_of_interest.push("Quantification: Assess if vague terms like 'many' or 'significant' hide a lack of specific data.".to_string());
}
if claim_lower.contains("because") || claim_lower.contains("therefore") {
areas_of_interest.push("Causality: Scrutinize the link between the premise and the conclusion for logical leaps.".to_string());
}
if claim_lower.contains("%") || claim_lower.contains("percent") {
areas_of_interest.push("Statistics: Determine if percentages are sourced or if they are illustrative placeholders.".to_string());
}
let certainty_markers = ["obvious", "clearly", "undeniable", "proven", "fact"];
for word in certainty_markers {
if claim_lower.contains(word) {
areas_of_interest.push(format!("Certainty Bias: The use of '{}' may indicate a claim that assumes its own conclusion.", word));
break;
}
}
let avg_words_per_sentence =
claim.split_whitespace().count() / claim.matches('.').count().max(1);
if avg_words_per_sentence > 30 {
areas_of_interest.push("Complexity: The high sentence length may obscure specific errors or contradictions.".to_string());
}
areas_of_interest
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_shadow_creation() {
let blue = Agent::new(AgentConfig::default());
let shadow = ShadowAgent::new(&blue, ShadowConfig::default());
assert_eq!(shadow.blue_agent_id, blue.id);
assert!(!shadow.agent.config.spawn_shadow);
}
#[test]
fn test_detect_issues_universal_claims() {
let blue = Agent::new(AgentConfig::default());
let shadow = ShadowAgent::new(&blue, ShadowConfig::default());
let issues = shadow.detect_issues("This method always works without fail.");
assert!(issues.iter().any(|i| i.contains("Over-generalization")));
}
#[test]
fn test_detect_issues_statistics() {
let blue = Agent::new(AgentConfig::default());
let shadow = ShadowAgent::new(&blue, ShadowConfig::default());
let issues = shadow.detect_issues("Studies show 90% of users prefer this approach.");
assert!(issues.iter().any(|i| i.contains("Statistics")));
}
#[test]
fn test_detect_issues_loaded_language() {
let blue = Agent::new(AgentConfig::default());
let shadow = ShadowAgent::new(&blue, ShadowConfig::default());
let issues = shadow.detect_issues("It is obvious that the solution is correct.");
assert!(issues.iter().any(|i| i.contains("Certainty Bias")));
}
#[test]
fn test_detect_issues_clean_claim() {
let blue = Agent::new(AgentConfig::default());
let shadow = ShadowAgent::new(&blue, ShadowConfig::default());
let issues = shadow.detect_issues("The API returns a 200 status code.");
assert!(issues.len() <= 2);
}
}