1use serde::{Deserialize, Serialize};
7use std::path::PathBuf;
8use std::time::SystemTime;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
12pub enum ModelTier {
13 Architect,
15 Actuator,
17 Verifier,
19 Speculator,
21}
22
23impl ModelTier {
24 pub fn default_model(&self) -> &'static str {
27 Self::default_model_name()
30 }
31
32 pub fn default_model_name() -> &'static str {
34 "gemini-flash-lite-latest"
35 }
36}
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
40pub enum Criticality {
41 Critical,
43 High,
45 Low,
47}
48
49impl Criticality {
50 pub fn weight(&self) -> f32 {
52 match self {
53 Criticality::Critical => 10.0,
54 Criticality::High => 3.0,
55 Criticality::Low => 1.0,
56 }
57 }
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct WeightedTest {
63 pub test_name: String,
65 pub criticality: Criticality,
67}
68
69#[derive(Debug, Clone, Default, Serialize, Deserialize)]
73pub struct BehavioralContract {
74 pub interface_signature: String,
76 pub invariants: Vec<String>,
78 pub forbidden_patterns: Vec<String>,
80 pub weighted_tests: Vec<WeightedTest>,
82 pub energy_weights: (f32, f32, f32),
85}
86
87impl BehavioralContract {
88 pub fn new() -> Self {
90 Self {
91 interface_signature: String::new(),
92 invariants: Vec::new(),
93 forbidden_patterns: Vec::new(),
94 weighted_tests: Vec::new(),
95 energy_weights: (1.0, 0.5, 2.0), }
97 }
98
99 pub fn alpha(&self) -> f32 {
101 self.energy_weights.0
102 }
103
104 pub fn beta(&self) -> f32 {
106 self.energy_weights.1
107 }
108
109 pub fn gamma(&self) -> f32 {
111 self.energy_weights.2
112 }
113}
114
115#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
117pub enum ErrorType {
118 #[default]
120 Compilation,
121 ToolFailure,
123 ReviewRejection,
125 Other,
127}
128
129#[derive(Debug, Clone, Serialize, Deserialize)]
131pub struct RetryPolicy {
132 pub max_compilation_retries: usize,
134 pub max_tool_retries: usize,
136 pub max_review_rejections: usize,
138 pub compilation_failures: usize,
140 pub tool_failures: usize,
141 pub review_rejections: usize,
142 pub last_error_type: Option<ErrorType>,
144}
145
146impl Default for RetryPolicy {
147 fn default() -> Self {
148 Self {
149 max_compilation_retries: 3,
151 max_tool_retries: 5,
152 max_review_rejections: 3,
153 compilation_failures: 0,
154 tool_failures: 0,
155 review_rejections: 0,
156 last_error_type: None,
157 }
158 }
159}
160
161impl RetryPolicy {
162 pub fn record_failure(&mut self, error_type: ErrorType) {
164 self.last_error_type = Some(error_type);
165 match error_type {
166 ErrorType::Compilation => self.compilation_failures += 1,
167 ErrorType::ToolFailure => self.tool_failures += 1,
168 ErrorType::ReviewRejection => self.review_rejections += 1,
169 ErrorType::Other => self.compilation_failures += 1, }
171 }
172
173 pub fn reset_failures(&mut self, error_type: ErrorType) {
175 match error_type {
176 ErrorType::Compilation => self.compilation_failures = 0,
177 ErrorType::ToolFailure => self.tool_failures = 0,
178 ErrorType::ReviewRejection => self.review_rejections = 0,
179 ErrorType::Other => self.compilation_failures = 0,
180 }
181 }
182
183 pub fn reset_all(&mut self) {
185 self.compilation_failures = 0;
186 self.tool_failures = 0;
187 self.review_rejections = 0;
188 self.last_error_type = None;
189 }
190
191 pub fn should_escalate(&self, error_type: ErrorType) -> bool {
193 match error_type {
194 ErrorType::Compilation | ErrorType::Other => {
195 self.compilation_failures >= self.max_compilation_retries
196 }
197 ErrorType::ToolFailure => self.tool_failures >= self.max_tool_retries,
198 ErrorType::ReviewRejection => self.review_rejections >= self.max_review_rejections,
199 }
200 }
201
202 pub fn any_exceeded(&self) -> bool {
204 self.compilation_failures >= self.max_compilation_retries
205 || self.tool_failures >= self.max_tool_retries
206 || self.review_rejections >= self.max_review_rejections
207 }
208
209 pub fn failure_count(&self, error_type: ErrorType) -> usize {
211 match error_type {
212 ErrorType::Compilation | ErrorType::Other => self.compilation_failures,
213 ErrorType::ToolFailure => self.tool_failures,
214 ErrorType::ReviewRejection => self.review_rejections,
215 }
216 }
217
218 pub fn remaining_attempts(&self, error_type: ErrorType) -> usize {
220 match error_type {
221 ErrorType::Compilation | ErrorType::Other => self
222 .max_compilation_retries
223 .saturating_sub(self.compilation_failures),
224 ErrorType::ToolFailure => self.max_tool_retries.saturating_sub(self.tool_failures),
225 ErrorType::ReviewRejection => self
226 .max_review_rejections
227 .saturating_sub(self.review_rejections),
228 }
229 }
230
231 pub fn summary(&self) -> String {
233 format!(
234 "Retries: comp {}/{}, tool {}/{}, review {}/{}",
235 self.compilation_failures,
236 self.max_compilation_retries,
237 self.tool_failures,
238 self.max_tool_retries,
239 self.review_rejections,
240 self.max_review_rejections
241 )
242 }
243}
244
245#[derive(Debug, Clone, Default, Serialize, Deserialize)]
247pub struct StabilityMonitor {
248 pub energy_history: Vec<f32>,
250 pub attempt_count: usize,
252 pub stable: bool,
254 pub stability_epsilon: f32,
256 pub max_retries: usize,
258 pub retry_policy: RetryPolicy,
260}
261
262impl StabilityMonitor {
263 pub fn new() -> Self {
265 Self {
266 energy_history: Vec::new(),
267 attempt_count: 0,
268 stable: false,
269 stability_epsilon: 0.1,
270 max_retries: 3,
271 retry_policy: RetryPolicy::default(),
272 }
273 }
274
275 pub fn record_energy(&mut self, energy: f32) {
277 self.energy_history.push(energy);
278 self.attempt_count += 1;
279 self.stable = energy < self.stability_epsilon;
280 }
281
282 pub fn record_failure(&mut self, error_type: ErrorType) {
284 self.retry_policy.record_failure(error_type);
285 }
286
287 pub fn should_escalate(&self) -> bool {
289 (self.attempt_count >= self.max_retries && !self.stable) || self.retry_policy.any_exceeded()
291 }
292
293 pub fn should_escalate_for(&self, error_type: ErrorType) -> bool {
295 self.retry_policy.should_escalate(error_type)
296 }
297
298 pub fn remaining_attempts(&self) -> usize {
300 match self.retry_policy.last_error_type {
301 Some(et) => self.retry_policy.remaining_attempts(et),
302 None => self.max_retries.saturating_sub(self.attempt_count),
303 }
304 }
305
306 pub fn current_energy(&self) -> f32 {
308 self.energy_history.last().copied().unwrap_or(f32::INFINITY)
309 }
310
311 pub fn is_converging(&self) -> bool {
313 if self.energy_history.len() < 2 {
314 return true; }
316 let last = self.energy_history.last().unwrap();
317 let prev = &self.energy_history[self.energy_history.len() - 2];
318 last < prev
319 }
320}
321
322#[derive(Debug, Clone, Serialize, Deserialize)]
324pub struct SRBNNode {
325 pub node_id: String,
327 pub goal: String,
329 pub context_files: Vec<PathBuf>,
331 pub output_targets: Vec<PathBuf>,
333 pub contract: BehavioralContract,
335 pub tier: ModelTier,
337 pub monitor: StabilityMonitor,
339 pub state: NodeState,
341 pub parent_id: Option<String>,
343 pub children: Vec<String>,
345}
346
347impl SRBNNode {
348 pub fn new(node_id: String, goal: String, tier: ModelTier) -> Self {
350 Self {
351 node_id,
352 goal,
353 context_files: Vec::new(),
354 output_targets: Vec::new(),
355 contract: BehavioralContract::new(),
356 tier,
357 monitor: StabilityMonitor::new(),
358 state: NodeState::TaskQueued,
359 parent_id: None,
360 children: Vec::new(),
361 }
362 }
363}
364
365#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
367pub enum NodeState {
368 TaskQueued,
370 Planning,
372 Coding,
374 Verifying,
376 Retry,
378 SheafCheck,
380 Committing,
382 Escalated,
384 Completed,
386 Failed,
388 Aborted,
390}
391
392impl NodeState {
393 pub fn is_terminal(&self) -> bool {
395 matches!(
396 self,
397 NodeState::Completed | NodeState::Failed | NodeState::Aborted
398 )
399 }
400}
401
402#[derive(Debug, Clone, Serialize, Deserialize)]
406pub struct TokenBudget {
407 pub max_tokens: usize,
409 pub max_cost_usd: Option<f64>,
411 pub input_tokens_used: usize,
413 pub output_tokens_used: usize,
415 pub cost_usd: f64,
417 pub input_cost_per_1k: f64,
419 pub output_cost_per_1k: f64,
421}
422
423impl Default for TokenBudget {
424 fn default() -> Self {
425 Self {
426 max_tokens: 100_000, max_cost_usd: None, input_tokens_used: 0,
429 output_tokens_used: 0,
430 cost_usd: 0.0,
431 input_cost_per_1k: 0.075 / 1000.0, output_cost_per_1k: 0.30 / 1000.0, }
435 }
436}
437
438impl TokenBudget {
439 pub fn new(max_tokens: usize, max_cost_usd: Option<f64>) -> Self {
441 Self {
442 max_tokens,
443 max_cost_usd,
444 ..Default::default()
445 }
446 }
447
448 pub fn record_usage(&mut self, input_tokens: usize, output_tokens: usize) {
450 self.input_tokens_used += input_tokens;
451 self.output_tokens_used += output_tokens;
452
453 let input_cost = (input_tokens as f64 / 1000.0) * self.input_cost_per_1k;
455 let output_cost = (output_tokens as f64 / 1000.0) * self.output_cost_per_1k;
456 self.cost_usd += input_cost + output_cost;
457 }
458
459 pub fn total_tokens_used(&self) -> usize {
461 self.input_tokens_used + self.output_tokens_used
462 }
463
464 pub fn remaining_tokens(&self) -> usize {
466 self.max_tokens.saturating_sub(self.total_tokens_used())
467 }
468
469 pub fn is_exhausted(&self) -> bool {
471 self.total_tokens_used() >= self.max_tokens
472 }
473
474 pub fn cost_exceeded(&self) -> bool {
476 if let Some(max_cost) = self.max_cost_usd {
477 self.cost_usd >= max_cost
478 } else {
479 false
480 }
481 }
482
483 pub fn should_stop(&self) -> bool {
485 self.is_exhausted() || self.cost_exceeded()
486 }
487
488 pub fn usage_percent(&self) -> f32 {
490 if self.max_tokens == 0 {
491 0.0
492 } else {
493 (self.total_tokens_used() as f32 / self.max_tokens as f32) * 100.0
494 }
495 }
496
497 pub fn set_pricing(&mut self, input_per_1k: f64, output_per_1k: f64) {
499 self.input_cost_per_1k = input_per_1k;
500 self.output_cost_per_1k = output_per_1k;
501 }
502
503 pub fn summary(&self) -> String {
505 format!(
506 "Tokens: {}/{} ({:.1}%), Cost: ${:.4}{}",
507 self.total_tokens_used(),
508 self.max_tokens,
509 self.usage_percent(),
510 self.cost_usd,
511 self.max_cost_usd
512 .map(|m| format!(" / ${:.2}", m))
513 .unwrap_or_default()
514 )
515 }
516}
517
518#[derive(Debug, Clone, Serialize, Deserialize)]
520pub struct AgentContext {
521 pub working_dir: PathBuf,
523 pub history: Vec<AgentMessage>,
525 pub merkle_root: [u8; 32],
527 pub complexity_k: usize,
529 pub session_id: String,
531 pub auto_approve: bool,
533 #[serde(skip)]
535 pub last_diagnostics: Vec<lsp_types::Diagnostic>,
536 pub token_budget: TokenBudget,
538}
539
540impl Default for AgentContext {
541 fn default() -> Self {
542 Self {
543 working_dir: std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")),
544 history: Vec::new(),
545 merkle_root: [0u8; 32],
546 complexity_k: 5, session_id: uuid::Uuid::new_v4().to_string(),
548 auto_approve: false,
549 last_diagnostics: Vec::new(),
550 token_budget: TokenBudget::default(),
551 }
552 }
553}
554
555#[derive(Debug, Clone, Serialize, Deserialize)]
557pub struct AgentMessage {
558 pub role: ModelTier,
560 pub content: String,
562 pub timestamp: SystemTime,
564 pub node_id: Option<String>,
566}
567
568impl AgentMessage {
569 pub fn new(role: ModelTier, content: String) -> Self {
571 Self {
572 role,
573 content,
574 timestamp: SystemTime::now(),
575 node_id: None,
576 }
577 }
578}
579
580#[derive(Debug, Clone, Default, Serialize, Deserialize)]
582pub struct EnergyComponents {
583 pub v_syn: f32,
585 pub v_str: f32,
587 pub v_log: f32,
589}
590
591impl EnergyComponents {
592 pub fn total(&self, contract: &BehavioralContract) -> f32 {
594 contract.alpha() * self.v_syn + contract.beta() * self.v_str + contract.gamma() * self.v_log
595 }
596}
597
598#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
604#[serde(rename_all = "snake_case")]
605pub enum TaskType {
606 #[default]
608 Code,
609 UnitTest,
611 IntegrationTest,
613 Refactor,
615 Documentation,
617}
618
619#[derive(Debug, Clone, Serialize, Deserialize)]
622pub struct TaskPlan {
623 pub tasks: Vec<PlannedTask>,
625}
626
627impl TaskPlan {
628 pub fn new() -> Self {
630 Self { tasks: Vec::new() }
631 }
632
633 pub fn len(&self) -> usize {
635 self.tasks.len()
636 }
637
638 pub fn is_empty(&self) -> bool {
640 self.tasks.is_empty()
641 }
642
643 pub fn get_task(&self, id: &str) -> Option<&PlannedTask> {
645 self.tasks.iter().find(|t| t.id == id)
646 }
647
648 pub fn validate(&self) -> Result<(), String> {
650 if self.tasks.is_empty() {
651 return Err("Plan has no tasks".to_string());
652 }
653
654 let mut seen_ids = std::collections::HashSet::new();
656 for task in &self.tasks {
657 if !seen_ids.insert(&task.id) {
658 return Err(format!("Duplicate task ID: {}", task.id));
659 }
660 if task.goal.is_empty() {
661 return Err(format!("Task {} has empty goal", task.id));
662 }
663 }
664
665 for task in &self.tasks {
667 for dep in &task.dependencies {
668 if !seen_ids.contains(dep) {
669 return Err(format!("Task {} has unknown dependency: {}", task.id, dep));
670 }
671 }
672 }
673
674 Ok(())
675 }
676}
677
678impl Default for TaskPlan {
679 fn default() -> Self {
680 Self::new()
681 }
682}
683
684#[derive(Debug, Clone, Serialize, Deserialize)]
686pub struct PlannedTask {
687 pub id: String,
689 pub goal: String,
691 #[serde(default)]
693 pub context_files: Vec<String>,
694 #[serde(default)]
696 pub output_files: Vec<String>,
697 #[serde(default)]
699 pub dependencies: Vec<String>,
700 #[serde(default)]
702 pub task_type: TaskType,
703 #[serde(default)]
705 pub contract: PlannedContract,
706}
707
708impl PlannedTask {
709 pub fn new(id: impl Into<String>, goal: impl Into<String>) -> Self {
711 Self {
712 id: id.into(),
713 goal: goal.into(),
714 context_files: Vec::new(),
715 output_files: Vec::new(),
716 dependencies: Vec::new(),
717 task_type: TaskType::Code,
718 contract: PlannedContract::default(),
719 }
720 }
721
722 pub fn to_srbn_node(&self, tier: ModelTier) -> SRBNNode {
724 let mut node = SRBNNode::new(self.id.clone(), self.goal.clone(), tier);
725 node.context_files = self.context_files.iter().map(PathBuf::from).collect();
726 node.output_targets = self.output_files.iter().map(PathBuf::from).collect();
727 node.contract = self.contract.to_behavioral_contract();
728 node
729 }
730}
731
732#[derive(Debug, Clone, Default, Serialize, Deserialize)]
734pub struct PlannedContract {
735 #[serde(default)]
737 pub interface_signature: Option<String>,
738 #[serde(default)]
740 pub invariants: Vec<String>,
741 #[serde(default)]
743 pub forbidden_patterns: Vec<String>,
744 #[serde(default)]
746 pub tests: Vec<PlannedTest>,
747}
748
749impl PlannedContract {
750 pub fn to_behavioral_contract(&self) -> BehavioralContract {
752 BehavioralContract {
753 interface_signature: self.interface_signature.clone().unwrap_or_default(),
754 invariants: self.invariants.clone(),
755 forbidden_patterns: self.forbidden_patterns.clone(),
756 weighted_tests: self
757 .tests
758 .iter()
759 .map(|t| WeightedTest {
760 test_name: t.name.clone(),
761 criticality: t.criticality,
762 })
763 .collect(),
764 energy_weights: (1.0, 0.5, 2.0),
765 }
766 }
767}
768
769#[derive(Debug, Clone, Serialize, Deserialize)]
771pub struct PlannedTest {
772 pub name: String,
774 #[serde(default = "default_criticality")]
776 pub criticality: Criticality,
777}
778
779fn default_criticality() -> Criticality {
780 Criticality::High
781}