Skip to content

Commit ef7d866

Browse files
committed
feat(bump): working on next release
1 parent eeb6e01 commit ef7d866

17 files changed

Lines changed: 2447 additions & 13 deletions

src/agent_flow/diagnoser.rs

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
//! Diagnoser for AgentFlow execution results (P5.4).
2+
//!
3+
//! Analyzes execution outputs and external feedback signals (coverage,
4+
//! sanitizer crashes, traces) to produce structured diagnostic feedback
5+
//! that the proposer uses to rewrite the harness.
6+
7+
use super::dsl::FeedbackChannel;
8+
use super::executor::{AgentOutput, ExecutionResult};
9+
use std::collections::BTreeSet;
10+
11+
/// A signal collected from the test environment after harness execution.
12+
#[derive(Debug, Clone, PartialEq)]
13+
pub enum FeedbackSignal {
14+
CoverageIncrease(f64),
15+
BranchHit(u32),
16+
SanitizerCrash { kind: String, location: String },
17+
TraceEvent { label: String, value: String },
18+
Pass,
19+
Fail(String),
20+
}
21+
22+
/// Diagnostic feedback produced by the diagnoser.
23+
#[derive(Debug, Clone)]
24+
pub struct Diagnostic {
25+
pub signals: Vec<FeedbackSignal>,
26+
pub summary: String,
27+
pub should_rewrite: bool,
28+
}
29+
30+
impl Diagnostic {
31+
pub fn is_success(&self) -> bool {
32+
!self.should_rewrite
33+
}
34+
}
35+
36+
/// Diagnose an execution result given the set of feedback channels that fired.
37+
pub fn diagnose(
38+
execution: &ExecutionResult,
39+
feedback_channels: &BTreeSet<FeedbackChannel>,
40+
signals: Vec<FeedbackSignal>,
41+
) -> Diagnostic {
42+
let all_succeeded = execution.is_success();
43+
44+
let has_crash = signals
45+
.iter()
46+
.any(|s| matches!(s, FeedbackSignal::SanitizerCrash { .. }));
47+
let has_coverage = signals
48+
.iter()
49+
.any(|s| matches!(s, FeedbackSignal::CoverageIncrease(_)));
50+
let has_pass = signals.iter().any(|s| matches!(s, FeedbackSignal::Pass));
51+
let has_fail = signals.iter().any(|s| matches!(s, FeedbackSignal::Fail(_)));
52+
53+
let channels_referenced = feedback_channels.iter().any(|c| {
54+
matches!(
55+
c,
56+
FeedbackChannel::Coverage | FeedbackChannel::Sanitizer | FeedbackChannel::Outcome
57+
)
58+
});
59+
60+
let should_rewrite =
61+
!all_succeeded || has_fail || (channels_referenced && !has_pass && !has_crash);
62+
63+
let summary = build_summary(
64+
all_succeeded,
65+
has_crash,
66+
has_coverage,
67+
has_pass,
68+
&execution.outputs,
69+
);
70+
71+
Diagnostic {
72+
signals,
73+
summary,
74+
should_rewrite,
75+
}
76+
}
77+
78+
fn build_summary(
79+
all_succeeded: bool,
80+
has_crash: bool,
81+
has_coverage: bool,
82+
has_pass: bool,
83+
outputs: &[AgentOutput],
84+
) -> String {
85+
let mut parts = Vec::new();
86+
87+
if all_succeeded {
88+
parts.push("all agents completed".to_string());
89+
} else {
90+
let failed: Vec<&str> = outputs
91+
.iter()
92+
.filter(|o| !o.success)
93+
.map(|o| o.role.as_str())
94+
.collect();
95+
parts.push(format!("failed agents: {}", failed.join(", ")));
96+
}
97+
98+
if has_crash {
99+
parts.push("sanitizer crash observed".to_string());
100+
}
101+
if has_coverage {
102+
parts.push("coverage increased".to_string());
103+
}
104+
if has_pass {
105+
parts.push("test passed".to_string());
106+
}
107+
108+
parts.join("; ")
109+
}
110+
111+
/// Format a diagnostic for the LLM proposer.
112+
pub fn format_diagnostic(diagnostic: &Diagnostic) -> String {
113+
let signal_str: Vec<String> = diagnostic
114+
.signals
115+
.iter()
116+
.map(|s| match s {
117+
FeedbackSignal::CoverageIncrease(v) => format!("coverage +{:.1}%", v * 100.0),
118+
FeedbackSignal::BranchHit(n) => format!("branches hit: {}", n),
119+
FeedbackSignal::SanitizerCrash { kind, location } => {
120+
format!("crash: {} at {}", kind, location)
121+
}
122+
FeedbackSignal::TraceEvent { label, value } => format!("trace {}: {}", label, value),
123+
FeedbackSignal::Pass => "test passed".to_string(),
124+
FeedbackSignal::Fail(msg) => format!("test failed: {}", msg),
125+
})
126+
.collect();
127+
128+
format!(
129+
"Diagnostic:\n Signals: {}\n Summary: {}\n Rewrite needed: {}",
130+
signal_str.join(", "),
131+
diagnostic.summary,
132+
if diagnostic.should_rewrite {
133+
"yes"
134+
} else {
135+
"no"
136+
}
137+
)
138+
}
139+
140+
#[cfg(test)]
141+
mod tests {
142+
use super::*;
143+
use crate::agent_flow::dsl::FeedbackChannel;
144+
use crate::agent_flow::executor::AgentOutput;
145+
use std::collections::BTreeSet;
146+
147+
fn success_outputs() -> Vec<AgentOutput> {
148+
vec![
149+
AgentOutput {
150+
role: "analyst".into(),
151+
content: "found".into(),
152+
success: true,
153+
},
154+
AgentOutput {
155+
role: "validator".into(),
156+
content: "ok".into(),
157+
success: true,
158+
},
159+
]
160+
}
161+
162+
#[test]
163+
fn test_diagnose_success_no_rewrite() {
164+
let execution = ExecutionResult {
165+
outputs: success_outputs(),
166+
rounds: 1,
167+
};
168+
let mut channels = BTreeSet::new();
169+
channels.insert(FeedbackChannel::Outcome);
170+
let signals = vec![FeedbackSignal::Pass];
171+
let diag = diagnose(&execution, &channels, signals);
172+
assert!(!diag.should_rewrite);
173+
assert!(diag.is_success());
174+
}
175+
176+
#[test]
177+
fn test_diagnose_crash_no_rewrite() {
178+
let execution = ExecutionResult {
179+
outputs: success_outputs(),
180+
rounds: 1,
181+
};
182+
let mut channels = BTreeSet::new();
183+
channels.insert(FeedbackChannel::Sanitizer);
184+
let signals = vec![FeedbackSignal::SanitizerCrash {
185+
kind: "heap-buffer-overflow".into(),
186+
location: "main.c:42".into(),
187+
}];
188+
let diag = diagnose(&execution, &channels, signals);
189+
assert!(!diag.should_rewrite);
190+
}
191+
192+
#[test]
193+
fn test_diagnose_fail_triggers_rewrite() {
194+
let mut outputs = success_outputs();
195+
outputs[1].success = false;
196+
let execution = ExecutionResult { outputs, rounds: 1 };
197+
let channels = BTreeSet::new();
198+
let signals = vec![];
199+
let diag = diagnose(&execution, &channels, signals);
200+
assert!(diag.should_rewrite);
201+
}
202+
203+
#[test]
204+
fn test_diagnose_channels_referenced_no_result() {
205+
let execution = ExecutionResult {
206+
outputs: success_outputs(),
207+
rounds: 1,
208+
};
209+
let mut channels = BTreeSet::new();
210+
channels.insert(FeedbackChannel::Coverage);
211+
let signals = vec![FeedbackSignal::CoverageIncrease(0.05)];
212+
let diag = diagnose(&execution, &channels, signals);
213+
assert!(diag.should_rewrite);
214+
}
215+
216+
#[test]
217+
fn test_format_diagnostic() {
218+
let diag = Diagnostic {
219+
signals: vec![FeedbackSignal::Pass, FeedbackSignal::BranchHit(5)],
220+
summary: "test passed".into(),
221+
should_rewrite: false,
222+
};
223+
let s = format_diagnostic(&diag);
224+
assert!(s.contains("test passed"));
225+
assert!(s.contains("branches hit: 5"));
226+
assert!(s.contains("Rewrite needed: no"));
227+
}
228+
}

src/agent_flow/dsl.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ pub struct Agent {
2121
}
2222

2323
/// Feedback channels that agents can reference in prompt templates.
24-
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
24+
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
2525
pub enum FeedbackChannel {
2626
Coverage,
2727
Branch,

0 commit comments

Comments
 (0)