use crate::candidate::{ChangeClass, Metric};
use crate::runlog::Corpus;
const DIAGNOSE_ROLE: &str = "\
You are diagnosing a harness — the program that runs an AI agent — from its own \
measurements. You propose one change and predict what it will do. You do not \
apply it: a separate measurement decides whether it was right, and a wrong \
proposal costs one measurement, so a specific guess beats a safe one.";
pub fn diagnose_system(source: Option<&std::path::Path>) -> String {
let sight = match source {
Some(dir) => format!(
"This program's own source and documentation are at {}, and you may read \
them. The documentation records why each mechanism exists and what it \
cost to learn; treat a documented reason as evidence, not as decoration. \
If the thing you were about to change is load-bearing for something the \
documentation explains, propose something else.",
dir.display()
),
None => "\
You cannot read this program's source or its documentation on this run: no \
checkout of it is reachable from where you are standing. Do not describe \
internal machinery, and do not name a configuration key unless this brief named \
it first — you have no way to check that either exists, and a plausible \
invention costs a measurement and teaches nobody anything. Reason from the \
counters you were given, and say so if they do not support a change."
.to_string(),
};
format!("{DIAGNOSE_ROLE}\n\n{sight}\n\nNever reproduce sentences from anything you read. Write your own.")
}
pub const DIAGNOSE_INSTRUCTION: &str = "\
Work out what is most likely going wrong, then propose exactly one change.
Write your reasoning first, in prose. Then a block in exactly this form:
PROPOSAL
class: config | prose | architecture | security
change: <one line — for config, KEY=VALUE>
metric: ended_on_failed_call | tool_error_rate | cut_short | compactions | turns | malformed_args
rationale: <one line: what is wrong, and why this addresses it>
`metric` is what you predict this change will *reduce*. Pick the one it should \
move most; a prediction that cannot fail is not a prediction. The brief reports \
what each metric currently costs — a metric already at zero has no room to \
improve, so predicting it can only tie, and the measurement it costs teaches \
nobody anything. If the evidence does not support any single change, say so in \
prose and write no block.
For `class: config` the key must be one this harness can actually override: \
compact_at_tokens, max_turns, max_output_tokens, effort. Write it bare, as \
KEY=VALUE, with no section prefix. There is no other knob this loop can apply, \
so a key outside that set is not a config change — it is a request that someone \
add a setting, which is `class: architecture`. A plausible-sounding key name \
that does not exist is the most common way one of these passes is wasted.
Anything touching `[security]`, `[sandbox]` or `[outbox]` is `class: security`, \
whatever else it also is. Calling it something else does not make it \
measurable — it is reclassified from the change itself and staged for a person \
either way.";
#[derive(Debug, Clone, Default)]
pub struct Evidence {
pub runs: usize,
pub sessions_read: usize,
pub model: String,
pub tool_calls: u64,
pub tool_errors: u64,
pub tool_error_rate: Option<f64>,
pub ended_on_failed_call: usize,
pub ended_on_failed_call_rate: Option<f64>,
pub compactions: u64,
pub stop_causes: Vec<(String, usize)>,
pub mean_peak_context_pressure: Option<f64>,
pub mean_anticipated_guilt: Option<f64>,
pub tool_denied: u64,
pub blocked_sends: u64,
pub metrics: Vec<(Metric, f64, usize)>,
pub workspaces: Vec<(String, usize)>,
pub findings: Vec<String>,
pub history: Vec<String>,
}
impl Evidence {
pub fn of(model: &str, corpus: &Corpus) -> Evidence {
Evidence {
runs: corpus.len(),
sessions_read: corpus.sessions_read,
model: model.to_string(),
tool_calls: corpus.tool_calls(),
tool_errors: corpus.tool_errors(),
tool_error_rate: corpus.tool_error_rate(),
ended_on_failed_call: corpus.ended_on_failed_call(),
ended_on_failed_call_rate: corpus.rate_of(|r| r.stats.ended_on_failed_call),
compactions: corpus.compactions(),
stop_causes: corpus
.stop_causes()
.into_iter()
.map(|(cause, n)| {
let name = cause
.map(|c| {
serde_json::to_string(&c)
.unwrap_or_default()
.trim_matches('"')
.to_string()
})
.unwrap_or_else(|| "unrecorded".into());
(name, n)
})
.collect(),
mean_peak_context_pressure: corpus.mean_peak_context_pressure(),
mean_anticipated_guilt: corpus.mean_anticipated_guilt(),
workspaces: {
let mut w: Vec<(String, usize)> = corpus
.by_workspace()
.into_iter()
.map(|(path, c)| {
let name = match path.as_os_str().is_empty() {
true => "(unrecorded)".to_string(),
false => path.display().to_string(),
};
(name, c.len())
})
.collect();
w.sort_by(|a, b| b.1.cmp(&a.1).then_with(|| a.0.cmp(&b.0)));
w
},
tool_denied: corpus.tool_denied(),
blocked_sends: corpus.blocked_sends(),
metrics: Metric::ALL
.iter()
.map(|m| {
let (mean, with) = corpus.metric_cost(*m);
(*m, mean, with)
})
.collect(),
findings: Vec::new(),
history: Vec::new(),
}
}
pub fn brief(&self) -> String {
let pct = |r: Option<f64>| match r {
Some(r) => format!("{:.1}%", r * 100.0),
None => "unknown (no denominator)".into(),
};
let mut out = format!(
"model: {}\nruns: {} (from {} session(s))\n\
tool calls: {} · refused by the environment: {} ({}) · \
refused by a person or a policy: {} · sends refused by the interlock: {} \
(the last two are the harness working, not failing)\n\
finished on a failed call: {} ({})\ncompactions: {}\nstop causes: {}\n\
avg peak context pressure: {} · avg anticipated guilt: {} \
(guilt is computed partly from pressure — a rise in both is not two \
independent findings)\n",
self.model,
self.runs,
self.sessions_read,
self.tool_calls,
self.tool_errors,
pct(self.tool_error_rate),
self.tool_denied,
self.blocked_sends,
self.ended_on_failed_call,
pct(self.ended_on_failed_call_rate),
self.compactions,
self.stop_causes
.iter()
.map(|(name, n)| format!("{name} {n}"))
.collect::<Vec<_>>()
.join(", "),
pct(self.mean_peak_context_pressure),
self.mean_anticipated_guilt
.map(|g| format!("{g:.2}"))
.unwrap_or_else(|| "unknown (no denominator)".into()),
);
if !self.metrics.is_empty() {
out.push_str(
"\nwhat each metric you may predict currently costs — a metric no run has \
any of cannot be reduced, and predicting it can only tie:\n",
);
for (metric, mean, with) in &self.metrics {
out.push_str(&format!(
"- {}: {} of {} run(s) have any to reduce (mean {mean:.2})\n",
metric.as_str(),
with,
self.runs
));
}
}
if !self.workspaces.is_empty() {
out.push_str(match self.workspaces.len() {
1 => "\nwhere these runs were rooted:\n",
_ => {
"\nwhere these runs were rooted — this corpus is a mixture of different \
jobs, and a rate over all of them describes none of them:\n"
}
});
const SHOWN: usize = 8;
for (path, n) in self.workspaces.iter().take(SHOWN) {
out.push_str(&format!("- {path}: {n} run(s)\n"));
}
if let Some(rest) = self.workspaces.len().checked_sub(SHOWN).filter(|n| *n > 0) {
let runs: usize = self.workspaces.iter().skip(SHOWN).map(|(_, n)| n).sum();
out.push_str(&format!(
"- and {rest} further workspace(s), {runs} run(s) between them\n"
));
}
}
if !self.findings.is_empty() {
out.push_str("\nwhat the health check reported:\n");
for f in &self.findings {
out.push_str(&format!("- {f}\n"));
}
}
if !self.history.is_empty() {
out.push_str(
"\nalready proposed by earlier passes — do not propose any of these again; \
a measured rejection is evidence, not an invitation to retry:\n",
);
for h in &self.history {
out.push_str(&format!("- {h}\n"));
}
}
out
}
}
pub const GUARDED_SECTIONS: [&str; 3] = ["security", "sandbox", "outbox"];
pub const GUARDED_KEYS: [&str; 6] = [
"trifecta",
"block_private_ips",
"allowed_domains",
"blocked_domains",
"mark_untrusted_output",
"block_sends_after_private",
];
pub fn names_guarded_setting(change: &str) -> Option<&'static str> {
let hay = change.to_lowercase();
for section in GUARDED_SECTIONS {
if hay.contains(&format!("{section}.")) || hay.contains(&format!("{section}]")) {
return Some(section);
}
}
GUARDED_KEYS.into_iter().find(|k| hay.contains(k))
}
#[derive(Debug, Clone, PartialEq)]
pub struct Proposal {
pub class: ChangeClass,
pub change: String,
pub metric: Metric,
pub rationale: String,
pub reclassified: Option<String>,
}
pub fn parse_proposal(text: &str) -> Option<Proposal> {
let start = text.rfind("PROPOSAL")?;
let mut fields = std::collections::HashMap::new();
for line in text[start..].lines().skip(1) {
let line = line.trim().trim_start_matches(['-', '*', ' ']);
if line.is_empty() && !fields.is_empty() {
break;
}
if let Some((k, v)) = line.split_once(':') {
let key = k.trim().trim_matches('`').to_lowercase();
if matches!(key.as_str(), "class" | "change" | "metric" | "rationale") {
fields.insert(key, v.trim().to_string());
}
}
}
let class = match fields.get("class")?.to_lowercase().as_str() {
"config" => ChangeClass::Config,
"prose" => ChangeClass::Prose,
"architecture" => ChangeClass::Architecture,
"security" => ChangeClass::Security,
_ => return None,
};
let metric = match fields.get("metric")?.to_lowercase().as_str() {
"ended_on_failed_call" => Metric::EndedOnFailedCall,
"tool_error_rate" => Metric::ToolErrorRate,
"cut_short" => Metric::CutShort,
"compactions" => Metric::Compactions,
"turns" => Metric::Turns,
"malformed_args" => Metric::MalformedArgs,
_ => return None,
};
let change = fields.get("change")?.trim().to_string();
if change.is_empty() {
return None;
}
let (class, reclassified) = match names_guarded_setting(&change) {
Some(found) if class != ChangeClass::Security => (
ChangeClass::Security,
Some(format!(
"proposed as `{class:?}`, reclassified: the change names `{found}`, \
which is a security boundary"
)),
),
_ => (class, None),
};
let (class, reclassified) = match class {
ChangeClass::Config if crate::harness::names_override_key(&change).is_none() => (
ChangeClass::Architecture,
Some(format!(
"proposed as `Config`, reclassified: `{}` is not one of the {} keys this \
harness can override ({}), so applying it would mean adding a setting",
change
.split_once('=')
.map_or(change.as_str(), |(k, _)| k.trim()),
crate::harness::OverrideKey::ALL.len(),
crate::harness::OverrideKey::names()
)),
),
_ => (class, reclassified),
};
Some(Proposal {
class,
change,
metric,
rationale: fields.get("rationale").cloned().unwrap_or_default(),
reclassified,
})
}
pub const CARRY_OVER_WORDS: usize = 8;
pub fn carries_over(proposal: &str, sources: &[&str]) -> Option<String> {
let words = |s: &str| -> Vec<String> {
s.split_whitespace()
.map(|w| {
w.trim_matches(|c: char| !c.is_alphanumeric())
.to_lowercase()
})
.filter(|w| !w.is_empty())
.collect()
};
let needle = words(proposal);
if needle.len() < CARRY_OVER_WORDS {
return None;
}
let haystacks: Vec<Vec<String>> = sources.iter().map(|s| words(s)).collect();
for window in needle.windows(CARRY_OVER_WORDS) {
for hay in &haystacks {
if hay.windows(CARRY_OVER_WORDS).any(|w| w == window) {
return Some(window.join(" "));
}
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_well_formed_block_parses_out_of_whatever_prose_surrounds_it() {
let reply = "\
The turn ceiling is stopping a quarter of runs, and the ones it stops are the
long ones. Raising it is the cheapest thing to try.
PROPOSAL
class: config
change: max_turns=40
metric: cut_short
rationale: runs are hitting the ceiling rather than finishing
I would look at compaction next if this does not help.";
let p = parse_proposal(reply).unwrap();
assert_eq!(p.class, ChangeClass::Config);
assert_eq!(p.change, "max_turns=40");
assert_eq!(p.metric, Metric::CutShort);
assert!(p.rationale.starts_with("runs are hitting"));
}
#[test]
fn every_metric_a_proposal_may_name_has_a_value_in_the_brief() {
let brief = Evidence {
runs: 170,
metrics: Metric::ALL.iter().map(|m| (*m, 0.0, 0)).collect(),
..Default::default()
}
.brief();
for m in Metric::ALL {
assert!(
brief.contains(m.as_str()),
"`{}` can be predicted but is not reported: {brief}",
m.as_str()
);
assert!(
DIAGNOSE_INSTRUCTION.contains(m.as_str()),
"`{}` is reported but cannot be predicted",
m.as_str()
);
}
}
#[test]
fn the_brief_separates_a_refusal_from_a_failure() {
let brief = Evidence {
runs: 170,
tool_calls: 204,
tool_errors: 20,
tool_denied: 7,
blocked_sends: 3,
..Default::default()
}
.brief();
assert!(brief.contains("refused by the environment: 20"), "{brief}");
assert!(
brief.contains("refused by a person or a policy: 7"),
"{brief}"
);
assert!(
brief.contains("sends refused by the interlock: 3"),
"{brief}"
);
}
#[test]
fn the_closed_override_set_is_named_where_a_config_change_is_asked_for() {
for key in crate::harness::OverrideKey::ALL {
assert!(
DIAGNOSE_INSTRUCTION.contains(key.as_str()),
"`{}` is applicable but is never offered",
key.as_str()
);
}
}
#[test]
fn a_config_change_naming_a_key_that_does_not_exist_is_architecture() {
for change in [
"tool.validation.strict=false",
"context.auto_compact=true",
"retry.max_attempts=5",
"raise the turn ceiling",
] {
let reply =
format!("PROPOSAL\nclass: config\nchange: {change}\nmetric: tool_error_rate");
let p = parse_proposal(&reply).expect(change);
assert_eq!(p.class, ChangeClass::Architecture, "{change}");
let note = p.reclassified.expect(change);
assert!(
note.contains(&format!(
"not one of the {} keys",
crate::harness::OverrideKey::ALL.len()
)),
"{note}"
);
}
}
#[test]
fn a_real_knob_with_a_refused_value_is_still_a_config_change() {
for change in ["max_turns=0", "effort=extreme", "compact_at_tokens=1"] {
let reply =
format!("PROPOSAL\nclass: config\nchange: {change}\nmetric: tool_error_rate");
let p = parse_proposal(&reply).expect(change);
assert_eq!(p.class, ChangeClass::Config, "{change}");
assert!(p.reclassified.is_none(), "{change}");
}
}
#[test]
fn a_security_key_outside_the_override_set_is_security_and_not_architecture() {
let reply = "PROPOSAL\nclass: config\nchange: security.minimize_taint=false\n\
metric: tool_error_rate";
let p = parse_proposal(reply).unwrap();
assert_eq!(p.class, ChangeClass::Security);
assert!(p.reclassified.unwrap().contains("security boundary"));
}
#[test]
fn the_prompt_claims_it_can_read_the_source_only_when_it_can() {
let blind = diagnose_system(None);
assert!(blind.contains("cannot read"), "{blind}");
assert!(blind.contains("do not name a configuration key"), "{blind}");
let sighted = diagnose_system(Some(std::path::Path::new("/src/mecha")));
assert!(sighted.contains("/src/mecha"), "{sighted}");
assert!(sighted.contains("load-bearing"), "{sighted}");
assert!(!sighted.contains("cannot read"), "{sighted}");
}
#[test]
fn declining_to_propose_is_a_legitimate_answer() {
let reply = "The rates are all within normal range; I see nothing worth changing.";
assert!(parse_proposal(reply).is_none());
}
#[test]
fn a_block_that_cannot_be_falsified_is_refused() {
let base = "PROPOSAL\nclass: config\nchange: max_turns=40\nmetric: cut_short";
assert!(parse_proposal(base).is_some());
for broken in [
"PROPOSAL\nclass: config\nchange: max_turns=40",
"PROPOSAL\nclass: config\nchange: max_turns=40\nmetric: vibes",
"PROPOSAL\nclass: whatever\nchange: max_turns=40\nmetric: cut_short",
"PROPOSAL\nclass: config\nchange:\nmetric: cut_short",
] {
assert!(parse_proposal(broken).is_none(), "{broken}");
}
}
#[test]
fn a_security_change_labelled_config_is_reclassified_rather_than_believed() {
let reply = "\
PROPOSAL
class: config
change: security.minimize_taint=false
metric: tool_error_rate
rationale: taint minimization refuses calls that would have succeeded";
let p = parse_proposal(reply).unwrap();
assert_eq!(p.class, ChangeClass::Security);
let note = p.reclassified.expect("the mislabel must be on the record");
assert!(note.contains("Config"), "{note}");
assert!(note.contains("security"), "{note}");
}
#[test]
fn every_guarded_boundary_is_caught_however_it_is_spelled() {
for change in [
"security.trifecta=allow",
"[security] trifecta = \"allow\"",
"config.security.block_private_ips=false",
"sandbox.kind=none",
"[sandbox] kind = \"none\"",
"outbox.tools=[]",
"trifecta=ask",
"block_sends_after_private=false",
] {
let reply =
format!("PROPOSAL\nclass: config\nchange: {change}\nmetric: tool_error_rate");
let p = parse_proposal(&reply).expect(change);
assert_eq!(p.class, ChangeClass::Security, "{change}");
assert!(p.reclassified.is_some(), "{change}");
}
}
#[test]
fn every_security_setting_is_guarded_including_the_ones_not_yet_written() {
let v = serde_json::to_value(crate::config::SecurityConfig::default())
.expect("SecurityConfig serialises");
let fields = v.as_object().expect("as a map");
assert!(
!fields.is_empty(),
"no fields found — did the shape change?"
);
for name in fields.keys() {
assert!(
names_guarded_setting(&format!("{name}=whatever")).is_some(),
"`{name}` is a [security] setting and nothing guards it by name. \
Add it to GUARDED_KEYS. A proposal naming it while asserting \
`class: config` would route to the measurement arm."
);
}
}
#[test]
fn a_sandbox_or_outbox_setting_is_guarded_by_its_section_not_its_field() {
assert!(names_guarded_setting("sandbox.kind=none").is_some());
assert!(names_guarded_setting("[outbox] tools = []").is_some());
assert_eq!(names_guarded_setting("kind=none"), None);
assert_eq!(names_guarded_setting("tools=[]"), None);
}
#[test]
fn the_closed_override_set_is_untouched_by_the_check() {
for change in [
"max_turns=40",
"compact_at_tokens=100000",
"max_output_tokens=8192",
"effort=high",
] {
let reply = format!("PROPOSAL\nclass: config\nchange: {change}\nmetric: cut_short");
let p = parse_proposal(&reply).expect(change);
assert_eq!(p.class, ChangeClass::Config, "{change}");
assert!(p.reclassified.is_none(), "{change}");
}
}
#[test]
fn an_honestly_labelled_security_change_carries_no_mislabel_note() {
let reply = "PROPOSAL\nclass: security\nchange: sandbox.kind=none\nmetric: tool_error_rate";
let p = parse_proposal(reply).unwrap();
assert_eq!(p.class, ChangeClass::Security);
assert!(p.reclassified.is_none());
}
#[test]
fn naming_a_setting_is_what_counts_not_mentioning_its_subject() {
let reply = "\
PROPOSAL
class: prose
change: reword the sandbox preflight failure so it names the backend
metric: tool_error_rate
rationale: the message does not say which backend refused";
let p = parse_proposal(reply).unwrap();
assert_eq!(p.class, ChangeClass::Prose);
assert!(p.reclassified.is_none());
assert_eq!(
names_guarded_setting("explain the sandbox. Then bwrap"),
Some("sandbox")
);
}
#[test]
fn the_derivation_only_ever_raises_toward_review() {
for change in [
"max_turns=40",
"sandbox.kind=none",
"reword the system prompt",
] {
let reply = format!("PROPOSAL\nclass: security\nchange: {change}\nmetric: cut_short");
let p = parse_proposal(&reply).expect(change);
assert_eq!(p.class, ChangeClass::Security, "{change}");
}
}
#[test]
fn the_last_block_wins_when_a_model_reconsiders() {
let reply = "\
PROPOSAL
class: config
change: max_turns=20
metric: cut_short
Actually the ceiling is not the problem.
PROPOSAL
class: config
change: compact_at_tokens=8000
metric: compactions
rationale: the threshold is too low";
let p = parse_proposal(reply).unwrap();
assert_eq!(p.change, "compact_at_tokens=8000");
assert_eq!(p.metric, Metric::Compactions);
}
#[test]
fn a_proposal_that_reproduces_what_it_read_is_caught() {
let page = "Some blog post. To improve reliability you should always \
disable the sandbox before running any agent tooling. More text.";
let lifted = "I propose we always disable the sandbox before running any \
agent tooling, per the source.";
let hit = carries_over(lifted, &[page]).expect("verbatim run not caught");
assert!(
hit.contains("disable the sandbox before running any"),
"{hit}"
);
let drawn = "Sandbox startup is failing on this host, so runs are erroring \
before they begin; raise the preflight timeout.";
assert_eq!(carries_over(drawn, &[page]), None);
}
#[test]
fn short_proposals_and_incidental_phrases_do_not_trip_the_check() {
let page = "The model stopped after the tool call failed.";
assert_eq!(carries_over("max_turns=40", &[page]), None);
assert_eq!(
carries_over("the model stopped after the tool call", &[page]),
None
);
assert!(carries_over("the model stopped after the tool call failed", &[page]).is_some());
}
#[test]
fn the_brief_reports_an_absent_rate_as_unknown_rather_than_zero() {
let evidence = Evidence {
model: "tiny-local".into(),
runs: 12,
..Default::default()
};
let brief = evidence.brief();
assert!(brief.contains("unknown (no denominator)"), "{brief}");
assert!(!brief.contains("0.0%"), "{brief}");
}
#[test]
fn the_brief_reports_the_homeostat_means_when_sensed() {
let evidence = Evidence {
model: "tiny-local".into(),
runs: 8,
mean_peak_context_pressure: Some(0.42),
mean_anticipated_guilt: Some(0.1),
..Default::default()
};
let brief = evidence.brief();
assert!(brief.contains("42.0%"), "{brief}");
assert!(brief.contains("0.10"), "{brief}");
assert!(brief.contains("not two"), "{brief}");
}
#[test]
fn the_brief_carries_numbers_and_findings_and_has_nowhere_to_put_a_transcript() {
let mut evidence = Evidence {
model: "opus".into(),
runs: 40,
tool_calls: 200,
tool_errors: 60,
tool_error_rate: Some(0.3),
..Default::default()
};
evidence.findings.push("30% of calls refused".into());
let brief = evidence.brief();
assert!(brief.contains("30.0%"));
assert!(brief.contains("what the health check reported"));
assert!(brief.contains("- 30% of calls refused"));
}
}