1use crate::types::{AgentContext, AgentMessage, ModelTier, SRBNNode};
7use anyhow::Result;
8use async_trait::async_trait;
9use perspt_core::llm_provider::GenAIProvider;
10use std::sync::Arc;
11
12#[async_trait]
17pub trait Agent: Send + Sync {
18 async fn process(&self, node: &SRBNNode, ctx: &AgentContext) -> Result<AgentMessage>;
20
21 fn name(&self) -> &str;
23
24 fn can_handle(&self, node: &SRBNNode) -> bool;
26}
27
28pub struct ArchitectAgent {
30 model: String,
31 provider: Arc<GenAIProvider>,
32}
33
34impl ArchitectAgent {
35 pub fn new(provider: Arc<GenAIProvider>, model: Option<String>) -> Self {
36 Self {
37 model: model.unwrap_or_else(|| ModelTier::Architect.default_model().to_string()),
38 provider,
39 }
40 }
41
42 fn build_planning_prompt(&self, node: &SRBNNode, ctx: &AgentContext) -> String {
43 format!(
44 r#"You are an Architect agent in a multi-agent coding system.
45
46## Task
47Goal: {}
48
49## Context
50Working Directory: {:?}
51Context Files: {:?}
52Output Targets: {:?}
53
54## Requirements
551. Break down this task into subtasks if needed
562. Define behavioral contracts for each subtask
573. Identify dependencies between subtasks
584. Specify required interfaces and invariants
59
60## Output Format
61Provide a structured plan with:
62- Subtask list with goals
63- File dependencies
64- Interface signatures
65- Test criteria"#,
66 node.goal, ctx.working_dir, node.context_files, node.output_targets,
67 )
68 }
69}
70
71#[async_trait]
72impl Agent for ArchitectAgent {
73 async fn process(&self, node: &SRBNNode, ctx: &AgentContext) -> Result<AgentMessage> {
74 log::info!(
75 "[Architect] Processing node: {} with model {}",
76 node.node_id,
77 self.model
78 );
79
80 let prompt = self.build_planning_prompt(node, ctx);
81
82 let response = self
83 .provider
84 .generate_response_simple(&self.model, &prompt)
85 .await?;
86
87 Ok(AgentMessage::new(ModelTier::Architect, response))
88 }
89
90 fn name(&self) -> &str {
91 "Architect"
92 }
93
94 fn can_handle(&self, node: &SRBNNode) -> bool {
95 matches!(node.tier, ModelTier::Architect)
96 }
97}
98
99pub struct ActuatorAgent {
101 model: String,
102 provider: Arc<GenAIProvider>,
103}
104
105impl ActuatorAgent {
106 pub fn new(provider: Arc<GenAIProvider>, model: Option<String>) -> Self {
107 Self {
108 model: model.unwrap_or_else(|| ModelTier::Actuator.default_model().to_string()),
109 provider,
110 }
111 }
112
113 fn build_coding_prompt(&self, node: &SRBNNode, ctx: &AgentContext) -> String {
114 let contract = &node.contract;
115
116 format!(
117 r#"You are an Actuator agent responsible for implementing code.
118
119## Task
120Goal: {}
121
122## Behavioral Contract
123Interface Signature: {}
124Invariants: {:?}
125Forbidden Patterns: {:?}
126
127## Context
128Working Directory: {:?}
129Files to Read: {:?}
130Files to Modify: {:?}
131
132## Instructions
1331. Implement the required functionality
1342. Follow the interface signature exactly
1353. Maintain all specified invariants
1364. Avoid all forbidden patterns
1375. Write clean, documented code
138
139## Output Format
140Provide the complete implementation with:
141- File path
142- Code content
143- Brief explanation of changes"#,
144 node.goal,
145 contract.interface_signature,
146 contract.invariants,
147 contract.forbidden_patterns,
148 ctx.working_dir,
149 node.context_files,
150 node.output_targets,
151 )
152 }
153}
154
155#[async_trait]
156impl Agent for ActuatorAgent {
157 async fn process(&self, node: &SRBNNode, ctx: &AgentContext) -> Result<AgentMessage> {
158 log::info!(
159 "[Actuator] Processing node: {} with model {}",
160 node.node_id,
161 self.model
162 );
163
164 let prompt = self.build_coding_prompt(node, ctx);
165
166 let response = self
167 .provider
168 .generate_response_simple(&self.model, &prompt)
169 .await?;
170
171 Ok(AgentMessage::new(ModelTier::Actuator, response))
172 }
173
174 fn name(&self) -> &str {
175 "Actuator"
176 }
177
178 fn can_handle(&self, node: &SRBNNode) -> bool {
179 matches!(node.tier, ModelTier::Actuator)
180 }
181}
182
183pub struct VerifierAgent {
185 model: String,
186 provider: Arc<GenAIProvider>,
187}
188
189impl VerifierAgent {
190 pub fn new(provider: Arc<GenAIProvider>, model: Option<String>) -> Self {
191 Self {
192 model: model.unwrap_or_else(|| ModelTier::Verifier.default_model().to_string()),
193 provider,
194 }
195 }
196
197 fn build_verification_prompt(&self, node: &SRBNNode, implementation: &str) -> String {
198 let contract = &node.contract;
199
200 format!(
201 r#"You are a Verifier agent responsible for checking code correctness.
202
203## Task
204Verify the implementation satisfies the behavioral contract.
205
206## Behavioral Contract
207Interface Signature: {}
208Invariants: {:?}
209Forbidden Patterns: {:?}
210Weighted Tests: {:?}
211
212## Implementation
213{}
214
215## Verification Criteria
2161. Does the interface match the signature?
2172. Are all invariants satisfied?
2183. Are any forbidden patterns present?
2194. Would the weighted tests pass?
220
221## Output Format
222Provide:
223- PASS or FAIL status
224- Energy score (0.0 = perfect, 1.0 = total failure)
225- List of violations if any
226- Suggested fixes for each violation"#,
227 contract.interface_signature,
228 contract.invariants,
229 contract.forbidden_patterns,
230 contract.weighted_tests,
231 implementation,
232 )
233 }
234}
235
236#[async_trait]
237impl Agent for VerifierAgent {
238 async fn process(&self, node: &SRBNNode, ctx: &AgentContext) -> Result<AgentMessage> {
239 log::info!(
240 "[Verifier] Processing node: {} with model {}",
241 node.node_id,
242 self.model
243 );
244
245 let implementation = ctx
247 .history
248 .last()
249 .map(|m| m.content.as_str())
250 .unwrap_or("No implementation provided");
251
252 let prompt = self.build_verification_prompt(node, implementation);
253
254 let response = self
255 .provider
256 .generate_response_simple(&self.model, &prompt)
257 .await?;
258
259 Ok(AgentMessage::new(ModelTier::Verifier, response))
260 }
261
262 fn name(&self) -> &str {
263 "Verifier"
264 }
265
266 fn can_handle(&self, node: &SRBNNode) -> bool {
267 matches!(node.tier, ModelTier::Verifier)
268 }
269}
270
271pub struct SpeculatorAgent {
273 model: String,
274 provider: Arc<GenAIProvider>,
275}
276
277impl SpeculatorAgent {
278 pub fn new(provider: Arc<GenAIProvider>, model: Option<String>) -> Self {
279 Self {
280 model: model.unwrap_or_else(|| ModelTier::Speculator.default_model().to_string()),
281 provider,
282 }
283 }
284}
285
286#[async_trait]
287impl Agent for SpeculatorAgent {
288 async fn process(&self, node: &SRBNNode, _ctx: &AgentContext) -> Result<AgentMessage> {
289 log::info!(
290 "[Speculator] Processing node: {} with model {}",
291 node.node_id,
292 self.model
293 );
294
295 let prompt = format!(
296 "Quickly evaluate if this approach is viable: {}\nProvide a brief YES/NO with one sentence justification.",
297 node.goal
298 );
299
300 let response = self
301 .provider
302 .generate_response_simple(&self.model, &prompt)
303 .await?;
304
305 Ok(AgentMessage::new(ModelTier::Speculator, response))
306 }
307
308 fn name(&self) -> &str {
309 "Speculator"
310 }
311
312 fn can_handle(&self, node: &SRBNNode) -> bool {
313 matches!(node.tier, ModelTier::Speculator)
314 }
315}
316
317#[cfg(test)]
318mod tests {
319 #[test]
323 fn test_architect_prompt_building() {
324 }
326}