1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
//! Measures AetherShell's **repair rate**: of the calls that fail, what fraction
//! does a purely mechanical agent fix using nothing but the structured error?
//!
//! This is the number §11 of `docs/AGENTIC_FIRST_DESIGN.md` left as a placeholder
//! (`≥X%` of failed agent calls repaired without human input). It was never filled
//! in because it was never measured — the design *inferred* self-correction from
//! the presence of structured errors. This test measures it instead.
//!
//! The repair strategy here is deliberately dumb: no model, no heuristics beyond
//! what the error record literally contains. That makes the result a **floor** —
//! what the error surface alone is worth, before any intelligence is applied. A
//! model-driven strategy should do better; if it does not, the errors are the
//! problem.
use aethershell::value::Value;
use agentic_eval::repair::{assess_repair, Attempt, ErrorFacts};
use std::sync::Mutex;
static LOCK: Mutex<()> = Mutex::new(());
#[derive(Debug, Clone)]
struct Case {
name: String,
args: Vec<Value>,
}
fn case(name: &str, args: Vec<Value>) -> Case {
Case {
name: name.to_string(),
args,
}
}
fn s(x: &str) -> Value {
Value::Str(x.to_string())
}
/// Run one call and reduce its failure to the facts an agent would actually see.
fn attempt(c: &Case) -> Attempt {
let mut env = aethershell::env::Env::new();
match aethershell::builtins::call(&c.name, c.args.clone(), &mut env) {
Ok(_) => Attempt::Ok,
Err(e) => match e.downcast_ref::<aethershell::safety::SafetyError>() {
Some(se) => Attempt::Failed(ErrorFacts {
code: se.code.as_str().to_string(),
retryable: se.code.retryable(),
suggestions: se.did_you_mean.clone(),
}),
// No structured error at all — the dead end the boundary net exists
// to eliminate. Scored honestly rather than excluded.
None => Attempt::Failed(ErrorFacts::uncoded()),
},
}
}
/// The mechanical strategy: if the error named a replacement, take the first one.
/// Otherwise decline — inventing an argument value is not repairing *from the
/// error*, and pretending otherwise would inflate the score.
fn repair_from_error(c: &Case, facts: &ErrorFacts) -> Option<Case> {
let name = facts.suggestions.first()?;
Some(case(name, c.args.clone()))
}
/// A corpus of realistic agent mistakes: names an LLM plausibly emits for a shell
/// it half-remembers. Every entry must fail on the first attempt — a corpus that
/// quietly succeeds would report a vacuous rate.
///
/// The **name is the only defect**: each row's arguments are valid for the builtin
/// it is a misspelling of. That isolation is the point — if the arguments were also
/// wrong, a corrected name would still fail and the harness would score a perfectly
/// good suggestion as misleading, measuring the corpus rather than the product.
fn misspelling_corpus() -> Vec<Case> {
let row = || {
let mut r = std::collections::BTreeMap::new();
r.insert("a".to_string(), Value::Int(1));
r.insert("b".to_string(), Value::Int(2));
Value::Record(r)
};
vec![
case("piick", vec![Value::Array(vec![row()]), s("a")]),
case("digets", vec![Value::Array(vec![row()])]),
case("aeconn", vec![Value::Array(vec![row()])]),
case("tokns", vec![s("hello")]),
case("ontology_manifst", vec![]),
case("canonicial", vec![Value::Int(1)]),
case("safety_stats", vec![]),
case("governer_status", vec![]),
]
}
#[test]
fn the_repair_rate_on_misspelled_calls_is_measured_not_assumed() {
let _l = LOCK.lock().unwrap_or_else(|e| e.into_inner());
std::env::remove_var("AETHER_MODE");
let corpus = misspelling_corpus();
let report = assess_repair(&corpus, attempt, repair_from_error);
println!(
"repair rate: {}/{} = {:.0}% (actionable {:.0}%, misleading {}, dead ends {}, \
declined {})",
report.repaired,
report.failed_first,
report.repair_rate * 100.0,
report.actionable_rate * 100.0,
report.misleading,
report.dead_ends,
report.no_repair_proposed,
);
assert_eq!(
report.failed_first,
corpus.len(),
"every case in this corpus must actually fail, or the rate is vacuous"
);
assert_eq!(
report.dead_ends, 0,
"a misspelled name must never be an uncoded dead end"
);
assert_eq!(
report.misleading, 0,
"a suggestion that does not fix the call is worse than no suggestion: \
it costs the agent a whole extra round trip to learn nothing"
);
// The floor this locks in. Mechanical, model-free repair of a plausible
// misspelling should be the common case, not the lucky one.
assert!(
report.repair_rate >= 0.75,
"mechanical repair rate {:.0}% is below the 75% floor",
report.repair_rate * 100.0
);
}
/// The honest other half: failures the error surface *cannot* mechanically fix.
/// A wrong-typed argument is diagnosable but not repairable without deciding what
/// the value should have been — that is a model's job, not the error's. This test
/// exists so the headline rate is never mistaken for "all failures are repairable".
#[test]
fn wrong_argument_failures_are_actionable_but_not_mechanically_repairable() {
let _l = LOCK.lock().unwrap_or_else(|e| e.into_inner());
std::env::remove_var("AETHER_MODE");
let corpus = vec![
case("pick", vec![Value::Int(1)]),
case("tokens", vec![]),
case("aecon_decode", vec![Value::Int(7)]),
case("ontology_describe", vec![Value::Bool(true)]),
];
let report = assess_repair(&corpus, attempt, repair_from_error);
assert_eq!(report.failed_first, corpus.len());
assert_eq!(
report.dead_ends, 0,
"a bad argument must still arrive with a branchable code"
);
assert_eq!(
report.actionable_rate, 1.0,
"every wrong-argument failure should be actionable"
);
assert_eq!(
report.repaired, 0,
"the mechanical strategy must decline rather than guess a value"
);
assert_eq!(report.no_repair_proposed, corpus.len());
}
/// The boundary net's contract: across a mixed corpus of name errors, argument
/// errors and genuine runtime failures, **every** failure carries a stable code.
/// Before the net, the runtime-failure rows here were bare prose an agent could
/// only pattern-match on.
///
/// Note this asserts *coded*, not *retryable*. A genuine runtime failure gets
/// `E_UNKNOWN` with `retryable: false` — branchable, and correctly telling the
/// agent to stop rather than re-run the same call. Conflating the two would
/// score a correct refusal as a defect.
#[test]
fn every_failure_in_a_mixed_corpus_carries_a_code() {
let _l = LOCK.lock().unwrap_or_else(|e| e.into_inner());
std::env::remove_var("AETHER_MODE");
let mut corpus = misspelling_corpus();
corpus.extend([
case("pick", vec![Value::Int(1)]),
case("tokens", vec![]),
// Genuine runtime failures — these are the ones that used to be prose.
case("cat", vec![s("definitely_no_such_file_ae.txt")]),
case("file_read_lines", vec![s("definitely_no_such_file_ae.txt")]),
case("json_parse", vec![s("{not valid json")]),
]);
let mut failures = 0usize;
let mut uncoded: Vec<String> = Vec::new();
let mut codes: std::collections::BTreeMap<String, usize> = Default::default();
for c in &corpus {
if let Attempt::Failed(facts) = attempt(c) {
failures += 1;
if facts.code.is_empty() {
uncoded.push(c.name.clone());
} else {
*codes.entry(facts.code.clone()).or_default() += 1;
}
}
}
assert_eq!(
failures,
corpus.len(),
"every case in this corpus must actually fail"
);
assert!(
uncoded.is_empty(),
"{} of {failures} failures carried no code ({uncoded:?}) — every failure \
must be branchable",
uncoded.len()
);
let report = assess_repair(&corpus, attempt, repair_from_error);
println!(
"mixed corpus: {failures} failures, 0 uncoded, codes {codes:?}, \
{} repaired mechanically",
report.repaired
);
}