1use async_trait::async_trait;
4use std::sync::Arc;
5use uuid::Uuid;
6
7use vex_adversarial::{
8 Consensus, ConsensusProtocol, Debate, DebateRound, ShadowAgent, ShadowConfig, Vote,
9};
10use vex_core::{Agent, ContextPacket};
11
12#[derive(Debug, Clone)]
14pub struct ExecutorConfig {
15 pub max_debate_rounds: u32,
17 pub consensus_protocol: ConsensusProtocol,
19 pub enable_adversarial: bool,
21}
22
23impl Default for ExecutorConfig {
24 fn default() -> Self {
25 Self {
26 max_debate_rounds: 3,
27 consensus_protocol: ConsensusProtocol::Majority,
28 enable_adversarial: true,
29 }
30 }
31}
32
33#[derive(Debug, Clone)]
35pub struct ExecutionResult {
36 pub agent_id: Uuid,
38 pub response: String,
40 pub verified: bool,
42 pub confidence: f64,
44 pub context: ContextPacket,
46 pub debate: Option<Debate>,
48}
49
50#[async_trait]
52pub trait LlmBackend: Send + Sync {
53 async fn complete(&self, system: &str, prompt: &str) -> Result<String, String>;
54}
55
56pub struct AgentExecutor<L: LlmBackend> {
58 pub config: ExecutorConfig,
60 llm: Arc<L>,
62}
63
64impl<L: LlmBackend> AgentExecutor<L> {
65 pub fn new(llm: Arc<L>, config: ExecutorConfig) -> Self {
67 Self { config, llm }
68 }
69
70 pub async fn execute(
72 &self,
73 agent: &mut Agent,
74 prompt: &str,
75 ) -> Result<ExecutionResult, String> {
76 let blue_response = self.llm.complete(&agent.config.role, prompt).await?;
78
79 let (final_response, verified, confidence, debate) = if self.config.enable_adversarial {
81 self.run_adversarial_verification(agent, prompt, &blue_response)
82 .await?
83 } else {
84 (blue_response, false, 0.5, None)
85 };
86
87 let mut context = ContextPacket::new(&final_response);
89 context.source_agent = Some(agent.id);
90 context.importance = confidence;
91
92 agent.context = context.clone();
94 agent.fitness = confidence;
95
96 Ok(ExecutionResult {
97 agent_id: agent.id,
98 response: final_response,
99 verified,
100 confidence,
101 context,
102 debate,
103 })
104 }
105
106 async fn run_adversarial_verification(
108 &self,
109 blue_agent: &Agent,
110 _original_prompt: &str,
111 blue_response: &str,
112 ) -> Result<(String, bool, f64, Option<Debate>), String> {
113 let shadow = ShadowAgent::new(blue_agent, ShadowConfig::default());
115
116 let mut debate = Debate::new(blue_agent.id, shadow.agent.id, blue_response);
118
119 for round_num in 1..=self.config.max_debate_rounds {
121 let challenge_prompt = shadow.challenge_prompt(blue_response);
123 let red_challenge = self
124 .llm
125 .complete(&shadow.agent.config.role, &challenge_prompt)
126 .await?;
127
128 let rebuttal = if red_challenge.to_lowercase().contains("disagree")
130 || red_challenge.to_lowercase().contains("issue")
131 || red_challenge.to_lowercase().contains("concern")
132 {
133 let rebuttal_prompt = format!(
134 "Your previous response was challenged:\n\n\
135 Original: \"{}\"\n\n\
136 Challenge: \"{}\"\n\n\
137 Please address these concerns or revise your response.",
138 blue_response, red_challenge
139 );
140 Some(
141 self.llm
142 .complete(&blue_agent.config.role, &rebuttal_prompt)
143 .await?,
144 )
145 } else {
146 None
147 };
148
149 debate.add_round(DebateRound {
150 round: round_num,
151 blue_claim: blue_response.to_string(),
152 red_challenge,
153 blue_rebuttal: rebuttal,
154 });
155
156 if debate
158 .rounds
159 .last()
160 .map(|r| r.red_challenge.to_lowercase().contains("agree"))
161 .unwrap_or(false)
162 {
163 break;
164 }
165 }
166
167 let mut consensus = Consensus::new(self.config.consensus_protocol);
169
170 let blue_confidence = if let Some(last_round) = debate.rounds.last() {
174 let red_found_issues = last_round.red_challenge.to_lowercase().contains("issue")
175 || last_round.red_challenge.to_lowercase().contains("concern")
176 || last_round.red_challenge.to_lowercase().contains("disagree")
177 || last_round.red_challenge.to_lowercase().contains("flaw");
178
179 if red_found_issues {
180 if last_round.blue_rebuttal.is_some() {
182 0.6 } else {
184 0.3 }
186 } else {
187 0.85 }
189 } else {
190 0.5 };
192
193 consensus.add_vote(Vote {
194 agent_id: blue_agent.id,
195 agrees: true,
196 confidence: blue_confidence,
197 reasoning: Some(format!("Blue confidence: {:.0}%", blue_confidence * 100.0)),
198 });
199
200 let red_agrees = debate
202 .rounds
203 .last()
204 .map(|r| !r.red_challenge.to_lowercase().contains("disagree"))
205 .unwrap_or(true);
206
207 consensus.add_vote(Vote {
208 agent_id: shadow.agent.id,
209 agrees: red_agrees,
210 confidence: 0.7,
211 reasoning: debate.rounds.last().map(|r| r.red_challenge.clone()),
212 });
213
214 consensus.evaluate();
215
216 let final_response = if consensus.reached && consensus.decision == Some(true) {
218 blue_response.to_string()
219 } else if let Some(last_round) = debate.rounds.last() {
220 last_round
222 .blue_rebuttal
223 .clone()
224 .unwrap_or_else(|| blue_response.to_string())
225 } else {
226 blue_response.to_string()
227 };
228
229 let verified = consensus.reached;
230 let confidence = consensus.confidence;
231
232 debate.conclude(consensus.decision.unwrap_or(true), confidence);
233
234 Ok((final_response, verified, confidence, Some(debate)))
235 }
236}
237
238#[cfg(test)]
239mod tests {
240 use super::*;
241 use vex_core::AgentConfig;
242
243 struct MockLlm;
244
245 #[async_trait]
246 impl LlmBackend for MockLlm {
247 async fn complete(&self, _system: &str, prompt: &str) -> Result<String, String> {
248 if prompt.contains("challenge") {
249 Ok("I agree with this assessment. The logic is sound.".to_string())
250 } else {
251 Ok("This is a test response.".to_string())
252 }
253 }
254 }
255
256 #[tokio::test]
257 async fn test_executor() {
258 let llm = Arc::new(MockLlm);
259 let executor = AgentExecutor::new(llm, ExecutorConfig::default());
260 let mut agent = Agent::new(AgentConfig::default());
261
262 let result = executor.execute(&mut agent, "Test prompt").await.unwrap();
263 assert!(!result.response.is_empty());
264 assert!(result.verified);
265 }
266}