use crate::candidate::{ChangeClass, Judgement, Metric};
use crate::config::Config;
use crate::message::Effort;
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::os::fd::AsRawFd;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OverrideKey {
CompactAtTokens,
MaxTurns,
MaxOutputTokens,
Effort,
}
impl OverrideKey {
pub const ALL: [OverrideKey; 4] = [
OverrideKey::CompactAtTokens,
OverrideKey::MaxTurns,
OverrideKey::MaxOutputTokens,
OverrideKey::Effort,
];
pub fn parse(key: &str) -> Option<OverrideKey> {
match key {
"compact_at_tokens" => Some(OverrideKey::CompactAtTokens),
"max_turns" => Some(OverrideKey::MaxTurns),
"max_output_tokens" => Some(OverrideKey::MaxOutputTokens),
"effort" => Some(OverrideKey::Effort),
_ => None,
}
}
pub fn as_str(self) -> &'static str {
match self {
OverrideKey::CompactAtTokens => "compact_at_tokens",
OverrideKey::MaxTurns => "max_turns",
OverrideKey::MaxOutputTokens => "max_output_tokens",
OverrideKey::Effort => "effort",
}
}
pub fn names() -> String {
Self::ALL
.iter()
.map(|k| k.as_str())
.collect::<Vec<_>>()
.join(", ")
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ConfigChange {
pub key: OverrideKey,
pub value: String,
}
pub fn names_override_key(spec: &str) -> Option<OverrideKey> {
OverrideKey::parse(spec.split_once('=')?.0.trim())
}
pub fn parse_change(spec: &str) -> Result<ConfigChange> {
let (key, value) = spec
.split_once('=')
.with_context(|| format!("expected KEY=VALUE, got `{spec}`"))?;
let key = key.trim();
let value = value.trim();
let key = OverrideKey::parse(key).with_context(|| {
format!(
"`{key}` is not in the closed override set ({})",
OverrideKey::names()
)
})?;
let canonical = match key {
OverrideKey::CompactAtTokens => {
let n: u64 = value
.parse()
.with_context(|| format!("compact_at_tokens takes a number, got `{value}`"))?;
anyhow::ensure!(
n >= 1000,
"compact_at_tokens below 1000 would compact on nearly every turn"
);
n.to_string()
}
OverrideKey::MaxTurns => {
let n: u32 = value
.parse()
.with_context(|| format!("max_turns takes a number, got `{value}`"))?;
anyhow::ensure!(n >= 1, "max_turns must be at least 1");
n.to_string()
}
OverrideKey::MaxOutputTokens => {
let n: u64 = value
.parse()
.with_context(|| format!("max_output_tokens takes a number, got `{value}`"))?;
anyhow::ensure!(n >= 1, "max_output_tokens must be at least 1");
n.to_string()
}
OverrideKey::Effort => value
.parse::<Effort>()
.map_err(|e| anyhow::anyhow!("{e}"))?
.as_str()
.to_string(),
};
Ok(ConfigChange {
key,
value: canonical,
})
}
impl ConfigChange {
pub fn apply_to_agent(&self, agent: &mut crate::config::AgentConfig) -> Result<()> {
match self.key {
OverrideKey::CompactAtTokens => agent.compact_at_tokens = Some(self.value.parse()?),
OverrideKey::MaxTurns => agent.max_turns = self.value.parse()?,
OverrideKey::MaxOutputTokens => agent.max_output_tokens = Some(self.value.parse()?),
OverrideKey::Effort => {
agent.effort = Some(self.value.parse().map_err(|e| anyhow::anyhow!("{e}"))?)
}
}
Ok(())
}
pub fn spec(&self) -> String {
format!("{}={}", self.key.as_str(), self.value)
}
}
pub const STATUS_STAGED: &str = "staged";
pub const STATUS_ACCEPTED: &str = "accepted";
pub const STATUS_REJECTED: &str = "rejected";
pub const STATUS_REVERTED: &str = "reverted";
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HarnessCandidate {
pub id: String,
pub created_at: String,
pub class: ChangeClass,
pub change: String,
pub metric: Metric,
pub rationale: String,
pub evidence: String,
#[serde(default)]
pub model: Option<String>,
pub status: String,
#[serde(default)]
pub measurement: Option<Measurement>,
#[serde(default)]
pub resolved_at: Option<String>,
#[serde(default)]
pub reason: Option<String>,
}
impl HarnessCandidate {
pub fn pending(&self) -> bool {
self.status == STATUS_STAGED
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default, PartialEq, Eq)]
pub struct TallyRecord {
pub wins: usize,
pub losses: usize,
pub ties: usize,
}
impl std::fmt::Display for TallyRecord {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}+ {}- {}=", self.wins, self.losses, self.ties)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Measurement {
pub measured_at: String,
pub model: String,
pub disposition: String,
pub reason: String,
pub selection: TallyRecord,
pub holdout: TallyRecord,
pub work_baseline: u64,
pub work_candidate: u64,
pub episodes: Vec<String>,
#[serde(default)]
pub holdout_episodes: Vec<String>,
#[serde(default)]
pub seed: u64,
pub diverged: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub replay_caveats: Vec<String>,
pub skipped: usize,
}
pub struct Drawn {
pub episodes: Vec<String>,
pub holdout_episodes: Vec<String>,
pub seed: u64,
pub diverged: Vec<String>,
pub replay_caveats: Vec<String>,
pub skipped: usize,
}
impl Measurement {
pub fn record(
judgement: &Judgement,
model: &str,
measured_at: String,
drawn: Drawn,
) -> Measurement {
let Drawn {
episodes,
holdout_episodes,
seed,
diverged,
replay_caveats,
skipped,
} = drawn;
use crate::candidate::Disposition;
let (disposition, reason) = match &judgement.disposition {
Disposition::Accept => ("accept", String::new()),
Disposition::Propose(r) => ("propose", r.clone()),
Disposition::Reject(r) => ("reject", r.clone()),
};
let tally = |t: &crate::candidate::Tally| TallyRecord {
wins: t.wins,
losses: t.losses,
ties: t.ties,
};
Measurement {
measured_at,
model: model.to_string(),
disposition: disposition.to_string(),
reason,
selection: tally(&judgement.selection),
holdout: tally(&judgement.holdout),
work_baseline: judgement.work_baseline,
work_candidate: judgement.work_candidate,
episodes,
holdout_episodes,
seed,
diverged,
replay_caveats,
skipped,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct AcceptedOverride {
pub key: String,
pub value: String,
pub candidate: String,
pub accepted_at: String,
}
#[derive(Debug, Default, Serialize, Deserialize)]
struct OverridesFile {
#[serde(default, rename = "override")]
overrides: Vec<AcceptedOverride>,
}
pub struct HarnessStore {
root: PathBuf,
}
impl HarnessStore {
pub fn default_root() -> Result<PathBuf> {
Ok(crate::learning::LearningStore::default_root()?.join("harness"))
}
pub fn open(root: impl Into<PathBuf>) -> Result<HarnessStore> {
let root = root.into();
crate::create_private_dir(&root.join("candidates"))
.with_context(|| format!("creating {}", root.display()))?;
Ok(HarnessStore { root })
}
pub fn open_default() -> Result<HarnessStore> {
Self::open(Self::default_root()?)
}
pub fn root(&self) -> &Path {
&self.root
}
pub fn write(&self, c: &HarnessCandidate) -> Result<()> {
let path = self.root.join("candidates").join(format!("{}.json", c.id));
let tmp = path.with_extension("json.tmp");
std::fs::write(&tmp, serde_json::to_string_pretty(c)?)?;
std::fs::rename(&tmp, &path)?;
Ok(())
}
pub fn all(&self) -> Result<Vec<HarnessCandidate>> {
let dir = self.root.join("candidates");
if !dir.is_dir() {
return Ok(Vec::new());
}
let mut out = Vec::new();
for entry in std::fs::read_dir(&dir)? {
let path = entry?.path();
if path.extension().and_then(|e| e.to_str()) != Some("json") {
continue;
}
match serde_json::from_str(&std::fs::read_to_string(&path)?) {
Ok(c) => out.push(c),
Err(e) => tracing::warn!("skipping unreadable candidate {}: {e}", path.display()),
}
}
out.sort_by(|a: &HarnessCandidate, b: &HarnessCandidate| a.id.cmp(&b.id));
Ok(out)
}
pub fn find(&self, id: &str) -> Result<HarnessCandidate> {
let all = self.all()?;
let matches: Vec<&HarnessCandidate> = all.iter().filter(|c| c.id.starts_with(id)).collect();
match matches.len() {
0 => anyhow::bail!("no candidate matching `{id}`"),
1 => Ok(matches[0].clone()),
n => anyhow::bail!(
"`{id}` matches {n} candidates: {}",
matches
.iter()
.map(|c| c.id.as_str())
.collect::<Vec<_>>()
.join(", ")
),
}
}
pub fn overrides_path(&self) -> PathBuf {
self.root.join("overrides.toml")
}
pub fn overrides(&self) -> Result<Vec<AcceptedOverride>> {
let path = self.overrides_path();
if !path.exists() {
return Ok(Vec::new());
}
let text = std::fs::read_to_string(&path)
.with_context(|| format!("reading {}", path.display()))?;
let file: OverridesFile =
toml::from_str(&text).with_context(|| format!("parsing {}", path.display()))?;
Ok(file.overrides)
}
pub fn set_override(&self, ov: AcceptedOverride) -> Result<Option<AcceptedOverride>> {
let _lock = self.lock()?;
let mut all = self.overrides()?;
let replaced = all
.iter()
.position(|o| o.key == ov.key)
.map(|i| all.remove(i));
all.push(ov);
self.write_overrides(&all)?;
Ok(replaced)
}
pub fn remove_override(&self, key: &str) -> Result<Option<AcceptedOverride>> {
let _lock = self.lock()?;
let mut all = self.overrides()?;
let removed = all.iter().position(|o| o.key == key).map(|i| all.remove(i));
if removed.is_some() {
self.write_overrides(&all)?;
}
Ok(removed)
}
fn write_overrides(&self, all: &[AcceptedOverride]) -> Result<()> {
let path = self.overrides_path();
let file = OverridesFile {
overrides: all.to_vec(),
};
let tmp = path.with_extension("toml.tmp");
std::fs::write(&tmp, toml::to_string_pretty(&file)?)?;
std::fs::rename(&tmp, &path)?;
Ok(())
}
fn lock(&self) -> Result<std::fs::File> {
let file = std::fs::OpenOptions::new()
.create(true)
.truncate(false)
.write(true)
.open(self.root.join(".lock"))?;
if unsafe { libc::flock(file.as_raw_fd(), libc::LOCK_EX) } != 0 {
return Err(std::io::Error::last_os_error()).context("locking the harness store");
}
Ok(file)
}
pub fn mint_id() -> String {
let now = chrono::Utc::now();
format!(
"hc-{}-{:04x}",
now.format("%Y%m%dT%H%M%S"),
(now.timestamp_subsec_nanos() ^ std::process::id()) & 0xffff
)
}
}
pub fn apply_accepted_overrides(cfg: &mut Config) {
let Ok(root) = HarnessStore::default_root() else {
return;
};
apply_overrides_file(cfg, &root.join("overrides.toml"));
}
pub fn apply_overrides_file(cfg: &mut Config, path: &Path) {
if !path.exists() {
return;
}
let text = match std::fs::read_to_string(path) {
Ok(t) => t,
Err(e) => {
tracing::warn!("harness overrides unreadable ({}): {e}", path.display());
return;
}
};
let file: OverridesFile = match toml::from_str(&text) {
Ok(f) => f,
Err(e) => {
tracing::warn!(
"harness overrides malformed ({}): {e} — applying none",
path.display()
);
return;
}
};
for ov in file.overrides {
let change = match parse_change(&format!("{}={}", ov.key, ov.value)) {
Ok(c) => c,
Err(e) => {
tracing::warn!(
"harness override `{}={}` skipped: {e:#} (from candidate {})",
ov.key,
ov.value,
ov.candidate
);
continue;
}
};
if let Err(e) = change.apply_to_agent(&mut cfg.agent) {
tracing::warn!(
"harness override `{}` failed to apply: {e:#}",
change.spec()
);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn temp_dir() -> std::path::PathBuf {
let dir = std::env::temp_dir()
.join("mecha-harness-test")
.join(uuid::Uuid::new_v4().to_string());
std::fs::create_dir_all(&dir).unwrap();
dir
}
#[test]
fn the_closed_set_refuses_everything_outside_it() {
assert!(parse_change("compact_at_tokens=24000").is_ok());
assert!(parse_change("max_turns=30").is_ok());
assert!(parse_change("max_output_tokens=8000").is_ok());
assert!(parse_change("effort=low").is_ok());
for hostile in [
"sandbox=none",
"trifecta=allow",
"outbox.tools=",
"context_window=999999",
"temperature=2.0",
] {
assert!(parse_change(hostile).is_err(), "{hostile} must be refused");
}
assert!(parse_change("just prose").is_err());
}
#[test]
fn values_are_validated_not_just_typed() {
assert!(parse_change("compact_at_tokens=1").is_err());
assert!(parse_change("max_turns=0").is_err());
assert!(parse_change("max_turns=notanumber").is_err());
assert!(parse_change("effort=extreme").is_err());
let c = parse_change(" effort = LOW ").unwrap();
assert_eq!(c.value, "low");
}
#[test]
fn overrides_apply_beneath_the_user_and_unknown_keys_are_skipped() {
let dir = temp_dir();
let path = dir.join("overrides.toml");
std::fs::write(
&path,
r#"
[[override]]
key = "compact_at_tokens"
value = "24000"
candidate = "hc-test"
accepted_at = "2026-08-22T00:00:00Z"
[[override]]
key = "sandbox"
value = "none"
candidate = "hc-evil"
accepted_at = "2026-08-22T00:00:00Z"
[[override]]
key = "max_turns"
value = "notanumber"
candidate = "hc-corrupt"
accepted_at = "2026-08-22T00:00:00Z"
"#,
)
.unwrap();
let mut cfg = Config::default();
apply_overrides_file(&mut cfg, &path);
assert_eq!(cfg.agent.compact_at_tokens, Some(24000));
assert_eq!(cfg.agent.max_turns, 40);
}
#[test]
fn a_malformed_overrides_file_applies_nothing() {
let dir = temp_dir();
let path = dir.join("overrides.toml");
std::fs::write(&path, "this is not toml [[[").unwrap();
let mut cfg = Config::default();
let before = cfg.agent.max_turns;
apply_overrides_file(&mut cfg, &path);
assert_eq!(cfg.agent.max_turns, before);
}
#[test]
fn set_and_remove_override_round_trip_and_replacement_is_returned() {
let dir = temp_dir();
let store = HarnessStore::open(&dir).unwrap();
let first = AcceptedOverride {
key: "compact_at_tokens".into(),
value: "24000".into(),
candidate: "hc-a".into(),
accepted_at: "2026-08-22T00:00:00Z".into(),
};
assert!(store.set_override(first.clone()).unwrap().is_none());
let second = AcceptedOverride {
key: "compact_at_tokens".into(),
value: "20000".into(),
candidate: "hc-b".into(),
..first.clone()
};
let replaced = store.set_override(second).unwrap();
assert_eq!(replaced.unwrap().candidate, "hc-a");
let all = store.overrides().unwrap();
assert_eq!(all.len(), 1);
assert_eq!(all[0].value, "20000");
let mut cfg = Config::default();
apply_overrides_file(&mut cfg, &store.overrides_path());
assert_eq!(cfg.agent.compact_at_tokens, Some(20000));
let removed = store.remove_override("compact_at_tokens").unwrap();
assert_eq!(removed.unwrap().value, "20000");
let mut cfg = Config::default();
apply_overrides_file(&mut cfg, &store.overrides_path());
assert_eq!(cfg.agent.compact_at_tokens, None);
assert!(store
.remove_override("compact_at_tokens")
.unwrap()
.is_none());
}
#[test]
fn candidates_round_trip_and_an_unknown_status_still_loads() {
let dir = temp_dir();
let store = HarnessStore::open(&dir).unwrap();
let c = HarnessCandidate {
id: "hc-20260822T000000-0001".into(),
created_at: "2026-08-22T00:00:00Z".into(),
class: ChangeClass::Config,
change: "compact_at_tokens=24000".into(),
metric: Metric::CutShort,
rationale: "runs are dying at the ceiling".into(),
evidence: "runs: 64".into(),
model: Some("qwen3.6-35b-a3b".into()),
status: STATUS_STAGED.into(),
measurement: None,
resolved_at: None,
reason: None,
};
store.write(&c).unwrap();
let read = store.find("hc-2026").unwrap();
assert_eq!(read.change, c.change);
assert!(read.pending());
let mut future = c.clone();
future.id = "hc-20260823T000000-0002".into();
future.status = "escalated".into();
store.write(&future).unwrap();
assert_eq!(store.all().unwrap().len(), 2);
}
}