use newt_core::agentic::InstantiatedFeatures;
use newt_core::{BehaviorSignal, ErrorClass, ParseSignal, TurnEndReason};
pub const CONTRACT_VERSION: &str = "2";
pub const AGENT: &str = "newt-agent";
pub struct ContractInputs<'a> {
pub requested_model: &'a str,
pub effective_model: &'a str,
pub model_digest: Option<&'a str>,
pub backend_name: &'a str,
pub backend_kind: &'a str,
pub outcome: &'static str,
pub context_window: Option<u32>,
pub tenacity: &'a str,
pub initiative: &'a str,
pub cognition: &'a str,
pub semantic_cognition: Option<Option<newt_core::role_profile::Cognition>>,
pub responses_capability: Option<&'a newt_core::model_card::ResponsesCapability>,
pub reasoning_effort: Option<newt_core::model_card::ReasoningEffort>,
pub crew: &'static str,
pub ocap: &'static str,
pub max_rounds: u32,
pub wall_ms: u64,
pub gen_tokens: Option<u64>,
pub smart_harness: Option<&'a serde_json::Value>,
pub output_allowance: Option<newt_core::agentic::OutputAllowance>,
pub run_allowance: Option<u32>,
pub features: Option<InstantiatedFeatures>,
pub durable_grants: &'a [String],
pub verification: Option<serde_json::Value>,
pub scratchpad_seed: Option<&'a str>,
pub required: &'a [Feature],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Terminal {
Completed,
StoppedShort(TurnEndReason),
Failed(Option<ErrorClass>),
}
pub fn terminal(
clean: bool,
class: Option<ErrorClass>,
end_reason: Option<TurnEndReason>,
smart_harness: bool,
) -> Terminal {
if !clean {
return Terminal::Failed(class);
}
match end_reason {
Some(TurnEndReason::NarrationFinalRound) if !smart_harness => Terminal::Completed,
Some(
reason @ (TurnEndReason::RoundCap
| TurnEndReason::RunAllowance
| TurnEndReason::NoProgress
| TurnEndReason::Empty
| TurnEndReason::Cancelled
| TurnEndReason::AwaitingOperator
| TurnEndReason::NarrationCapExhausted
| TurnEndReason::NarrationFinalRound
| TurnEndReason::RepairExhausted
| TurnEndReason::VerificationIncomplete),
) => Terminal::StoppedShort(reason),
Some(TurnEndReason::Completed) | None => Terminal::Completed,
Some(TurnEndReason::Failed) => Terminal::Failed(Some(ErrorClass::Harness)),
}
}
pub fn outcome_label(t: Terminal) -> &'static str {
match t {
Terminal::Completed => "completed",
Terminal::StoppedShort(
TurnEndReason::RoundCap
| TurnEndReason::RunAllowance
| TurnEndReason::NoProgress
| TurnEndReason::Cancelled,
) => "timeout",
Terminal::StoppedShort(
TurnEndReason::RepairExhausted | TurnEndReason::VerificationIncomplete,
) => "completed",
Terminal::StoppedShort(
TurnEndReason::Empty
| TurnEndReason::AwaitingOperator
| TurnEndReason::NarrationCapExhausted
| TurnEndReason::NarrationFinalRound,
) => "model_error",
Terminal::StoppedShort(TurnEndReason::Completed | TurnEndReason::Failed) => "harness_error",
Terminal::Failed(Some(ErrorClass::Model)) => "model_error",
Terminal::Failed(Some(ErrorClass::ContextExceeded)) => "context_exceeded",
Terminal::Failed(Some(ErrorClass::Transport)) => "transport_error",
Terminal::Failed(Some(ErrorClass::Timeout)) => "timeout",
Terminal::Failed(Some(ErrorClass::Harness) | None) => "harness_error",
}
}
pub fn status_label(t: Terminal) -> &'static str {
match t {
Terminal::Completed => "completed",
Terminal::StoppedShort(TurnEndReason::AwaitingOperator) => "awaiting_operator",
Terminal::StoppedShort(_) => "incomplete",
Terminal::Failed(_) => "failed",
}
}
pub fn calls_after_last_write(events: &[newt_core::ToolEvent]) -> Option<usize> {
let last = events
.iter()
.rposition(|e| e.ok && newt_core::agentic::is_workspace_write_call(&e.tool))?;
Some(events.len() - 1 - last)
}
pub(crate) const HANDBACK_MAX_FILES: usize = 200;
pub enum RepoFileList {
Root(Vec<String>),
Nested {
files: Vec<String>,
by_repo: Vec<(String, Vec<String>)>,
unprobed: Vec<String>,
},
Subtree {
files: Vec<String>,
by_repo: Vec<(String, Vec<String>)>,
unprobed: Vec<String>,
},
}
struct RepoFileListStanza {
value: serde_json::Value,
source: &'static str,
truncated: bool,
by_repo: Option<Vec<(String, Vec<String>)>>,
unprobed: Vec<String>,
}
fn repo_file_list_stanza(
list: Option<RepoFileList>,
root_source: &'static str,
) -> RepoFileListStanza {
match list {
Some(RepoFileList::Root(mut files)) => {
let truncated = files.len() > HANDBACK_MAX_FILES;
files.truncate(HANDBACK_MAX_FILES);
RepoFileListStanza {
value: serde_json::json!(files),
source: root_source,
truncated,
by_repo: None,
unprobed: Vec::new(),
}
}
Some(RepoFileList::Nested {
mut files,
by_repo,
unprobed,
}) => {
let truncated = files.len() > HANDBACK_MAX_FILES;
files.truncate(HANDBACK_MAX_FILES);
RepoFileListStanza {
value: serde_json::json!(files),
source: "nested-repos",
truncated,
by_repo: Some(by_repo),
unprobed,
}
}
Some(RepoFileList::Subtree {
mut files,
by_repo,
unprobed,
}) => {
let truncated = files.len() > HANDBACK_MAX_FILES;
files.truncate(HANDBACK_MAX_FILES);
RepoFileListStanza {
value: serde_json::json!(files),
source: "git_status_subtree",
truncated,
by_repo: Some(by_repo),
unprobed,
}
}
None => RepoFileListStanza {
value: serde_json::Value::Null,
source: "unavailable",
truncated: false,
by_repo: None,
unprobed: Vec::new(),
},
}
}
fn by_repo_json(by_repo: Vec<(String, Vec<String>)>) -> serde_json::Value {
serde_json::json!(by_repo
.into_iter()
.map(|(repo, mut files)| {
let truncated = files.len() > HANDBACK_MAX_FILES;
files.truncate(HANDBACK_MAX_FILES);
serde_json::json!({"repo": repo, "files": files, "truncated": truncated})
})
.collect::<Vec<_>>())
}
#[must_use]
pub fn handback(
delta: Option<RepoFileList>,
events: &[newt_core::ToolEvent],
end_reason: &str,
reply_present: bool,
uncommitted: Option<RepoFileList>,
commits: Option<Vec<String>>,
) -> serde_json::Value {
let stanza = repo_file_list_stanza(delta, "git_status_delta");
let (files, source, truncated, by_repo, unprobed) = (
stanza.value,
stanza.source,
stanza.truncated,
stanza.by_repo,
stanza.unprobed,
);
let uncommitted_stanza = repo_file_list_stanza(uncommitted, "git_status");
let (
uncommitted_files,
uncommitted_source,
uncommitted_truncated,
uncommitted_by_repo,
uncommitted_unprobed,
) = (
uncommitted_stanza.value,
uncommitted_stanza.source,
uncommitted_stanza.truncated,
uncommitted_stanza.by_repo,
uncommitted_stanza.unprobed,
);
let last_exec = events
.iter()
.rev()
.find_map(|e| e.execution.as_ref())
.and_then(|x| serde_json::to_value(x).ok());
let mut record = serde_json::json!({
"files_changed": files,
"files_changed_source": source,
"files_changed_truncated": truncated,
"uncommitted_files": uncommitted_files,
"uncommitted_files_source": uncommitted_source,
"uncommitted_files_truncated": uncommitted_truncated,
"last_exec_outcome": last_exec,
"end_reason": end_reason,
"model_reply_present": reply_present,
});
conditional_stanza(
&mut record,
"files_changed_by_repo",
by_repo.map(by_repo_json),
);
conditional_stanza(
&mut record,
"files_changed_unprobed",
(!unprobed.is_empty()).then_some(unprobed),
);
conditional_stanza(
&mut record,
"uncommitted_files_by_repo",
uncommitted_by_repo.map(by_repo_json),
);
conditional_stanza(
&mut record,
"uncommitted_files_unprobed",
(!uncommitted_unprobed.is_empty()).then_some(uncommitted_unprobed),
);
conditional_stanza(
&mut record,
"commits",
commits.filter(|c| !c.is_empty()).map(|mut c| {
c.truncate(HANDBACK_MAX_FILES);
serde_json::json!(c)
}),
);
record
}
pub fn parse_signal_line(signal: &ParseSignal) -> serde_json::Value {
serde_json::to_value(signal).expect("ParseSignal serializes infallibly")
}
pub fn behavior_signal_line(signal: &BehaviorSignal) -> serde_json::Value {
serde_json::to_value(signal).expect("BehaviorSignal serializes infallibly")
}
pub fn conditional_stanza(
target: &mut serde_json::Value,
key: &str,
value: Option<impl Into<serde_json::Value>>,
) {
if let Some(value) = value {
debug_assert!(
target.get(key).is_none(),
"conditional stanza `{key}` would overwrite an existing field"
);
target[key] = value.into();
}
}
pub fn usage_stanza(
totals: &newt_core::attempts::UsageTotals,
cost_usd: Option<f64>,
ledger_head: Option<&str>,
) -> serde_json::Value {
let mut stanza = serde_json::json!({
"attempts": totals.attempts,
"in_tokens": totals.in_tokens,
"out_tokens": totals.out_tokens,
"usage_missing": totals.usage_missing,
"usage_complete": totals.usage_complete,
});
conditional_stanza(&mut stanza, "cost_usd", cost_usd);
conditional_stanza(&mut stanza, "ledger_head", ledger_head);
stanza
}
pub fn attempt_line(
line: &newt_core::event_journal::JournalLine<newt_core::attempts::AttemptRecord>,
) -> serde_json::Value {
let mut value = serde_json::to_value(line).unwrap_or(serde_json::Value::Null);
conditional_stanza(&mut value, "kind", Some("attempt"));
value
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, clap::ValueEnum)]
#[value(rename_all = "snake_case")]
pub enum Feature {
Scratchpad,
CodeSearch,
Crew,
}
impl Feature {
fn key(self) -> &'static str {
match self {
Self::Scratchpad => "scratchpad",
Self::CodeSearch => "code_search",
Self::Crew => "crew",
}
}
fn instantiated(self, f: InstantiatedFeatures) -> bool {
match self {
Self::Scratchpad => f.scratchpad,
Self::CodeSearch => f.code_search,
Self::Crew => f.crew,
}
}
fn absence(self) -> (&'static str, &'static str) {
match self {
Self::Scratchpad => ("unavailable", "no --scratchpad-state was supplied"),
Self::CodeSearch => ("unsupported", "headless builds no retrieval index"),
Self::Crew => (
"unavailable",
"crew is not enabled for this run (NEWT_TEAM)",
),
}
}
}
pub fn admit_required(
required: &[Feature],
supplies: impl Fn(Feature) -> bool,
) -> anyhow::Result<()> {
for &feature in required {
if !supplies(feature) {
let (state, reason) = feature.absence();
anyhow::bail!("required feature `{}` is {state}: {reason}", feature.key());
}
}
Ok(())
}
pub fn feature_receipt(
f: Option<InstantiatedFeatures>,
required: &[Feature],
scratchpad_seed: Option<&str>,
) -> Option<serde_json::Value> {
use clap::ValueEnum;
let mut features = serde_json::Map::new();
for &feature in Feature::value_variants() {
let is_required = required.contains(&feature);
let mut entry = match f {
Some(f) if feature.instantiated(f) => serde_json::json!({ "state": "instantiated" }),
_ if is_required => serde_json::json!({
"state": "requested",
"reason": "required, but the turn did not confirm it was instantiated",
}),
Some(_) => {
let (state, reason) = feature.absence();
serde_json::json!({ "state": state, "reason": reason })
}
None => continue,
};
conditional_stanza(&mut entry, "required", is_required.then_some(true));
if feature == Feature::Scratchpad && entry["state"] == "instantiated" {
conditional_stanza(&mut entry, "scope", scratchpad_seed.map(|_| "fresh"));
conditional_stanza(&mut entry, "seed", scratchpad_seed);
}
features.insert(feature.key().into(), entry);
}
(!features.is_empty()).then(|| serde_json::json!({ "features": features }))
}
pub fn contract_record(i: &ContractInputs<'_>) -> serde_json::Value {
let mut timing = serde_json::json!({ "wall_ms": i.wall_ms });
conditional_stanza(&mut timing, "gen_tokens", i.gen_tokens);
let tok_s = i
.gen_tokens
.filter(|_| i.wall_ms > 0)
.map(|gen| gen as f64 * 1000.0 / i.wall_ms as f64);
conditional_stanza(&mut timing, "tok_s", tok_s);
let mut effective_config = serde_json::json!({
"tenacity": i.tenacity,
"initiative": i.initiative,
"cognition": i.cognition,
"crew": i.crew,
"ocap": i.ocap,
"max_rounds": i.max_rounds,
});
conditional_stanza(
&mut effective_config,
"semantic_cognition",
i.semantic_cognition.map(|value| serde_json::json!(value)),
);
if let Some(capability) = i.responses_capability {
conditional_stanza(
&mut effective_config,
"responses_capability",
Some(serde_json::json!(capability)),
);
conditional_stanza(
&mut effective_config,
"reasoning_effort",
Some(serde_json::json!(i.reasoning_effort)),
);
}
conditional_stanza(&mut effective_config, "context_window", i.context_window);
let output_allowance = i
.output_allowance
.map(|a| serde_json::to_value(a).expect("OutputAllowance serializes infallibly"));
conditional_stanza(&mut effective_config, "output_allowance", output_allowance);
conditional_stanza(&mut effective_config, "run_allowance", i.run_allowance);
conditional_stanza(
&mut effective_config,
"smart_harness",
i.smart_harness.cloned(),
);
conditional_stanza(
&mut effective_config,
"durable_grants",
(!i.durable_grants.is_empty()).then(|| serde_json::json!(i.durable_grants)),
);
let mut record = serde_json::json!({
"contract_version": CONTRACT_VERSION,
"requested_model": i.requested_model,
"effective_model": i.effective_model,
"outcome": i.outcome,
"backend": { "name": i.backend_name, "kind": i.backend_kind },
"agent": AGENT,
"agent_version": newt_core::build_info::VERSION_WITH_COMMIT,
"effective_config": effective_config,
"timing": timing,
});
conditional_stanza(&mut record, "model_digest", i.model_digest);
let mut receipt = feature_receipt(i.features, i.required, i.scratchpad_seed);
if let (Some(verification), Some(_)) = (&i.verification, &i.features) {
receipt.get_or_insert_with(|| serde_json::json!({}))["verification"] = verification.clone();
}
conditional_stanza(&mut record, "receipt", receipt);
record
}
#[cfg(test)]
const BENCH_OUTCOME_VALUES: &str = include_str!("../contract/bench_outcome_values_v1.txt");
#[cfg(test)]
fn permitted_outcomes() -> Vec<&'static str> {
BENCH_OUTCOME_VALUES
.lines()
.map(str::trim)
.filter(|l| !l.is_empty() && !l.starts_with('#'))
.collect()
}
#[cfg(test)]
mod tests {
include!("headless_contract_cognition_tests.rs");
use super::*;
#[test]
fn handback_reports_delta_caps_it_and_never_fakes_an_empty_list() {
let h = handback(
Some(RepoFileList::Root(vec!["a.rs".into()])),
&[],
"None",
false,
None,
None,
);
assert_eq!(h["files_changed"], serde_json::json!(["a.rs"]));
assert_eq!(h["files_changed_source"], "git_status_delta");
assert_eq!(h["model_reply_present"], false);
assert!(h["last_exec_outcome"].is_null());
let mut shell =
newt_core::ToolEvent::from_call("run_command", &serde_json::json!({}), false, None);
shell.execution = Some(newt_core::ExecOutcome::TimedOut);
let later =
newt_core::ToolEvent::from_call("read_file", &serde_json::json!({}), true, None);
let h = handback(None, &[shell, later], "None", true, None, None);
assert_eq!(
h["last_exec_outcome"], "timed_out",
"class only, from the last SHELL call"
);
let many: Vec<String> = (0..201).map(|i| format!("f{i}")).collect();
let h = handback(
Some(RepoFileList::Root(many)),
&[],
"RoundCap",
true,
None,
None,
);
assert_eq!(h["files_changed"].as_array().unwrap().len(), 200);
assert_eq!(h["files_changed_truncated"], true);
let h = handback(None, &[], "None", false, None, None);
assert!(h["files_changed"].is_null());
assert_eq!(h["files_changed_source"], "unavailable");
let h = handback(
Some(RepoFileList::Root(vec![])),
&[],
"None",
false,
None,
None,
);
assert_eq!(h["files_changed"], serde_json::json!([]));
}
#[test]
fn handback_names_uncommitted_files_bounded_like_files_changed() {
let h = handback(
None,
&[],
"None",
false,
Some(RepoFileList::Root(vec!["dirty.rs".into()])),
None,
);
assert_eq!(h["uncommitted_files"], serde_json::json!(["dirty.rs"]));
assert_eq!(h["uncommitted_files_source"], "git_status");
assert_eq!(h["uncommitted_files_truncated"], false);
let h = handback(None, &[], "None", false, None, None);
assert!(h["uncommitted_files"].is_null());
assert_eq!(h["uncommitted_files_source"], "unavailable");
let h = handback(
None,
&[],
"None",
false,
Some(RepoFileList::Root(vec![])),
None,
);
assert_eq!(h["uncommitted_files"], serde_json::json!([]));
let many: Vec<String> = (0..201).map(|i| format!("d{i}")).collect();
let h = handback(
None,
&[],
"None",
false,
Some(RepoFileList::Root(many)),
None,
);
assert_eq!(h["uncommitted_files"].as_array().unwrap().len(), 200);
assert_eq!(h["uncommitted_files_truncated"], true);
}
#[test]
fn handback_carries_commit_ids_or_omits_the_field() {
let h = handback(None, &[], "None", false, None, Some(vec!["abc123".into()]));
assert_eq!(h["commits"], serde_json::json!(["abc123"]));
let h = handback(None, &[], "None", false, None, Some(vec![]));
assert!(h.get("commits").is_none());
let h = handback(None, &[], "None", false, None, None);
assert!(h.get("commits").is_none());
}
#[test]
fn handback_renders_the_nested_repos_source_and_per_repo_breakdown() {
let h = handback(
Some(RepoFileList::Nested {
files: vec!["repoA/a.txt".into()],
by_repo: vec![
("repoA".into(), vec!["a.txt".into()]),
("repoB".into(), vec![]),
],
unprobed: vec!["repoC".into()],
}),
&[],
"None",
false,
Some(RepoFileList::Nested {
files: vec!["repoA/a.txt".into()],
by_repo: vec![("repoA".into(), vec!["a.txt".into()])],
unprobed: vec![],
}),
None,
);
assert_eq!(h["files_changed"], serde_json::json!(["repoA/a.txt"]));
assert_eq!(h["files_changed_source"], "nested-repos");
assert_eq!(
h["files_changed_by_repo"],
serde_json::json!([
{"repo": "repoA", "files": ["a.txt"], "truncated": false},
{"repo": "repoB", "files": [], "truncated": false},
])
);
assert_eq!(h["files_changed_unprobed"], serde_json::json!(["repoC"]));
assert_eq!(h["uncommitted_files"], serde_json::json!(["repoA/a.txt"]));
assert_eq!(h["uncommitted_files_source"], "nested-repos");
assert_eq!(
h["uncommitted_files_by_repo"],
serde_json::json!([{"repo": "repoA", "files": ["a.txt"], "truncated": false}])
);
assert!(h.get("uncommitted_files_unprobed").is_none(), "{h}");
}
#[test]
fn handback_caps_each_repos_by_repo_file_list_independently() {
let huge: Vec<String> = (0..HANDBACK_MAX_FILES + 5)
.map(|n| format!("f{n}.txt"))
.collect();
let h = handback(
Some(RepoFileList::Nested {
files: vec!["big/f0.txt".into()],
by_repo: vec![("big".into(), huge.clone())],
unprobed: vec![],
}),
&[],
"None",
false,
None,
None,
);
let by_repo = h["files_changed_by_repo"].as_array().expect("array");
assert_eq!(by_repo.len(), 1);
let files = by_repo[0]["files"].as_array().expect("files array");
assert_eq!(files.len(), HANDBACK_MAX_FILES, "{files:?}");
assert_eq!(by_repo[0]["truncated"], true, "{h}");
}
use newt_core::{BehaviorSignal, ToolCallDialect};
const ALL_END_REASONS: &[TurnEndReason] = &[
TurnEndReason::Completed,
TurnEndReason::NarrationCapExhausted,
TurnEndReason::NarrationFinalRound,
TurnEndReason::RoundCap,
TurnEndReason::Empty,
TurnEndReason::Cancelled,
TurnEndReason::Failed,
TurnEndReason::AwaitingOperator,
TurnEndReason::RepairExhausted,
TurnEndReason::VerificationIncomplete,
TurnEndReason::RunAllowance,
TurnEndReason::NoProgress,
];
fn tool_event(tool: &str, ok: bool) -> newt_core::ToolEvent {
newt_core::ToolEvent::from_call(tool, &serde_json::json!({"path": tool}), ok, None)
}
#[test]
fn a_run_that_never_landed_a_write_has_no_grind_measurement() {
let events = [
tool_event("read_file", true),
tool_event("write_file", false),
tool_event("run_command", true),
];
assert_eq!(calls_after_last_write(&events), None);
assert_eq!(calls_after_last_write(&[]), None);
}
#[test]
fn a_write_as_the_final_call_is_a_grind_of_zero() {
assert_eq!(
calls_after_last_write(&[tool_event("read_file", true), tool_event("edit_file", true)]),
Some(0)
);
}
#[test]
fn the_grind_is_measured_from_the_last_successful_write() {
let events = [
tool_event("write_file", true),
tool_event("edit_file", true),
tool_event("read_file", true),
tool_event("text_search", true),
];
assert_eq!(calls_after_last_write(&events), Some(2));
}
fn end_reason_index(r: TurnEndReason) -> usize {
match r {
TurnEndReason::Completed => 0,
TurnEndReason::NarrationCapExhausted => 1,
TurnEndReason::NarrationFinalRound => 2,
TurnEndReason::RoundCap => 3,
TurnEndReason::Empty => 4,
TurnEndReason::Cancelled => 5,
TurnEndReason::Failed => 6,
TurnEndReason::AwaitingOperator => 7,
TurnEndReason::RepairExhausted => 8,
TurnEndReason::VerificationIncomplete => 9,
TurnEndReason::RunAllowance => 10,
TurnEndReason::NoProgress => 11,
}
}
const ALL_ERROR_CLASSES: &[ErrorClass] = &[
ErrorClass::Model,
ErrorClass::Transport,
ErrorClass::Timeout,
ErrorClass::Harness,
ErrorClass::ContextExceeded,
];
fn error_class_index(c: ErrorClass) -> usize {
match c {
ErrorClass::Model => 0,
ErrorClass::Transport => 1,
ErrorClass::Timeout => 2,
ErrorClass::Harness => 3,
ErrorClass::ContextExceeded => 4,
}
}
fn all_terminals() -> Vec<Terminal> {
let mut out = vec![Terminal::Completed, Terminal::Failed(None)];
out.extend(ALL_END_REASONS.iter().copied().map(Terminal::StoppedShort));
out.extend(
ALL_ERROR_CLASSES
.iter()
.copied()
.map(|c| Terminal::Failed(Some(c))),
);
out
}
#[test]
fn every_reachable_outcome_is_a_value_the_bench_can_parse() {
let mut seen: Vec<usize> = ALL_END_REASONS
.iter()
.copied()
.map(end_reason_index)
.collect();
seen.sort_unstable();
assert_eq!(
seen,
(0..ALL_END_REASONS.len()).collect::<Vec<_>>(),
"ALL_END_REASONS stopped covering TurnEndReason — a variant was \
added to the index match but not to the array, so the coverage \
below is measuring less than it appears to"
);
let mut seen: Vec<usize> = ALL_ERROR_CLASSES
.iter()
.copied()
.map(error_class_index)
.collect();
seen.sort_unstable();
assert_eq!(
seen,
(0..ALL_ERROR_CLASSES.len()).collect::<Vec<_>>(),
"ALL_ERROR_CLASSES stopped covering ErrorClass"
);
let permitted = permitted_outcomes();
assert!(
!permitted.is_empty(),
"the permitted set parsed empty — a checked-in file that reads as \
'nothing is allowed' would fail every case below for the wrong \
reason, and one that reads as 'everything' would pass them all"
);
for t in all_terminals() {
let label = outcome_label(t);
assert!(
permitted.contains(&label),
"outcome_label({t:?}) emitted {label:?}, which \
gilamonster-bench's closed `Outcome` enum cannot deserialize. \
The run will not score badly — its row will VANISH from the \
matrix. Permitted: {permitted:?}. If the contract genuinely \
moved, update contract/bench_outcome_values_v1.txt against the \
upstream enum, in its own commit."
);
}
}
#[test]
fn an_exhausted_narration_rescue_is_not_recorded_as_a_completion() {
let t = terminal(
true,
None,
Some(TurnEndReason::NarrationCapExhausted),
false,
);
assert_eq!(
t,
Terminal::StoppedShort(TurnEndReason::NarrationCapExhausted),
"the loop stopped before finishing — that is StoppedShort, not a \
genuine final answer"
);
assert_eq!(
status_label(t),
"incomplete",
"newt's own trace line must say the run did not finish what it set \
out to do (#2212's question, asked of a second exit)"
);
assert_eq!(
outcome_label(t),
"model_error",
"the model WAS reached and emitted unusable content — a real \
attempt that failed, not a budget wall and not a clean completion"
);
assert_eq!(
terminal(true, None, Some(TurnEndReason::Completed), false),
Terminal::Completed
);
assert_eq!(outcome_label(Terminal::Completed), "completed");
}
#[test]
fn final_round_narration_preserves_legacy_outcomes_and_accounts_for_smart_runs() {
for (smart_harness, expected, status, outcome) in [
(false, Terminal::Completed, "completed", "completed"),
(
true,
Terminal::StoppedShort(TurnEndReason::NarrationFinalRound),
"incomplete",
"model_error",
),
] {
let actual = terminal(
true,
None,
Some(TurnEndReason::NarrationFinalRound),
smart_harness,
);
assert_eq!(actual, expected);
assert_eq!(status_label(actual), status);
assert_eq!(outcome_label(actual), outcome);
}
for reason in ALL_END_REASONS
.iter()
.copied()
.filter(|reason| *reason != TurnEndReason::NarrationFinalRound)
{
assert_eq!(
terminal(true, None, Some(reason), false),
terminal(true, None, Some(reason), true)
);
}
}
#[test]
fn the_verification_receipt_is_carried_only_when_supplied() {
let entry = serde_json::json!({"mode": "result_aware", "repair_allowance": 3});
let mut with = inputs();
with.verification = Some(entry.clone());
assert_eq!(contract_record(&with)["receipt"]["verification"], entry);
let without = inputs();
assert!(contract_record(&without)["receipt"]
.get("verification")
.is_none());
let mut no_outcome = inputs();
no_outcome.features = None;
no_outcome.verification = Some(entry);
assert!(
contract_record(&no_outcome)["receipt"]
.get("verification")
.is_none(),
"{}",
contract_record(&no_outcome)
);
}
#[test]
fn verification_endings_are_completed_attempts_with_incomplete_status() {
for reason in [
TurnEndReason::RepairExhausted,
TurnEndReason::VerificationIncomplete,
] {
let t = terminal(true, None, Some(reason), false);
assert_eq!(t, Terminal::StoppedShort(reason));
assert_eq!(outcome_label(t), "completed");
assert_eq!(status_label(t), "incomplete");
}
}
#[test]
fn a_question_awaits_the_operator_instead_of_completing_the_task() {
let t = terminal(true, None, Some(TurnEndReason::AwaitingOperator), false);
assert_eq!(t, Terminal::StoppedShort(TurnEndReason::AwaitingOperator));
assert_eq!(status_label(t), "awaiting_operator");
assert_eq!(outcome_label(t), "model_error");
assert!(permitted_outcomes().contains(&outcome_label(t)));
}
#[test]
fn the_checked_in_permitted_set_matches_the_contract() {
assert_eq!(
permitted_outcomes(),
vec![
"completed",
"model_error",
"transport_error",
"timeout",
"harness_error",
"context_exceeded"
],
"the declared gilamonster-bench `Outcome` set changed. That requires \
an explicit contract decision and a coordinated consumer update, \
not a way to make a test pass — re-read gilamonster-bench/src/\
contract.rs and confirm contract_version before editing this."
);
}
fn inputs() -> ContractInputs<'static> {
ContractInputs {
requested_model: "qwen3.6_35b",
effective_model: "qwen3.6_35b",
model_digest: None,
backend_name: "dgx",
backend_kind: "openai",
outcome: "completed",
context_window: Some(32768),
tenacity: "normal",
initiative: "measured",
cognition: "default",
semantic_cognition: None,
responses_capability: None,
reasoning_effort: None,
crew: "off",
ocap: "off",
max_rounds: 40,
wall_ms: 10_000,
gen_tokens: Some(500),
smart_harness: None,
output_allowance: None,
run_allowance: None,
features: Some(InstantiatedFeatures {
crew: true,
..InstantiatedFeatures::default()
}),
verification: None,
scratchpad_seed: None,
required: &[],
durable_grants: &[],
}
}
#[test]
fn a_round_cap_exit_files_as_timeout_not_completed() {
assert_eq!(
outcome_label(terminal(true, None, Some(TurnEndReason::RoundCap), false)),
"timeout",
"a run that exhausted its round budget without reaching the goal \
must be excluded from capability scoring — is_real_attempt() is \
Completed|ModelError. #2215 emitted `round_cap`, which the bench's \
closed enum cannot parse, so the row vanished instead."
);
}
#[test]
fn a_run_allowance_exit_files_like_a_round_cap() {
for reason in [TurnEndReason::RunAllowance, TurnEndReason::NoProgress] {
let t = terminal(true, None, Some(reason), false);
assert_eq!(outcome_label(t), "timeout", "{reason:?}");
assert_eq!(status_label(t), "incomplete", "{reason:?}");
assert!(permitted_outcomes().contains(&outcome_label(t)));
}
}
#[test]
fn a_round_cap_exit_reports_an_incomplete_run() {
assert_eq!(
status_label(terminal(true, None, Some(TurnEndReason::RoundCap), false)),
"incomplete",
"a run that ended at the tool-round cap reported itself finished"
);
}
#[test]
fn one_classification_renders_two_honest_answers() {
let t = terminal(true, None, Some(TurnEndReason::RoundCap), false);
assert_eq!(outcome_label(t), "timeout", "excluded from scoring");
assert_eq!(
status_label(t),
"incomplete",
"and honestly named on our own line"
);
}
#[test]
fn clean_turn_is_completed() {
assert_eq!(
outcome_label(terminal(true, None, None, false)),
"completed"
);
}
#[test]
fn model_class_is_model_error() {
assert_eq!(
outcome_label(terminal(false, Some(ErrorClass::Model), None, false)),
"model_error"
);
}
#[test]
fn transport_class_is_transport_error() {
assert_eq!(
outcome_label(terminal(false, Some(ErrorClass::Transport), None, false)),
"transport_error"
);
}
#[test]
fn timeout_class_is_timeout() {
assert_eq!(
outcome_label(terminal(false, Some(ErrorClass::Timeout), None, false)),
"timeout"
);
}
#[test]
fn harness_class_and_unclassified_failures_are_harness_error() {
assert_eq!(
outcome_label(terminal(false, Some(ErrorClass::Harness), None, false)),
"harness_error"
);
assert_eq!(
outcome_label(terminal(false, None, None, false)),
"harness_error"
);
}
#[test]
fn no_parseable_tool_call_line_shape() {
assert_eq!(
parse_signal_line(&ParseSignal::NoParseableToolCall { round: 3 }),
serde_json::json!({"kind": "no_parseable_tool_call", "round": 3})
);
}
#[test]
fn recovered_tool_call_line_names_the_dialect() {
assert_eq!(
parse_signal_line(&ParseSignal::RecoveredToolCall {
round: 1,
dialect: ToolCallDialect::FunctionTag,
calls: vec![],
}),
serde_json::json!({
"kind": "recovered_tool_call", "round": 1, "dialect": "function_tag"
})
);
}
#[test]
fn reasoning_overflow_line_carries_the_bounded_recovery_result() {
let signal = BehaviorSignal::ReasoningOverflow {
round: 0,
reasoning_overflow_detected: true,
continuation_attempted: true,
continuation_succeeded: true,
finish_reason: "length".into(),
reasoning_tokens_estimate: 2500,
};
assert_eq!(
behavior_signal_line(&signal),
serde_json::json!({
"kind": "reasoning_overflow",
"round": 0,
"reasoning_overflow_detected": true,
"continuation_attempted": true,
"continuation_succeeded": true,
"finish_reason": "length",
"reasoning_tokens_estimate": 2500,
})
);
}
#[test]
fn chat_completion_finish_line_records_backend_reason() {
let signal = BehaviorSignal::ChatCompletionFinish {
round: 3,
finish_reason: Some("length".into()),
};
assert_eq!(
behavior_signal_line(&signal),
serde_json::json!({
"kind": "chat_completion_finish",
"round": 3,
"finish_reason": "length",
})
);
}
#[test]
fn record_round_trips_with_exactly_the_contract_fields() {
let record = contract_record(&inputs());
let wire = record.to_string();
let parsed: serde_json::Value = serde_json::from_str(&wire).expect("valid JSON");
let keys: Vec<&str> = {
let mut k: Vec<&str> = parsed.as_object().unwrap().keys().map(|s| &**s).collect();
k.sort_unstable();
k
};
assert_eq!(
keys,
vec![
"agent",
"agent_version",
"backend",
"contract_version",
"effective_config",
"effective_model",
"outcome",
"receipt",
"requested_model",
"timing",
],
"exactly the contract fields (model_digest absent: not supplied)"
);
assert_eq!(parsed["contract_version"], "2");
assert_eq!(parsed["agent"], "newt-agent");
assert_eq!(
parsed["agent_version"],
newt_core::build_info::VERSION_WITH_COMMIT
);
assert_eq!(
parsed["backend"],
serde_json::json!({"name": "dgx", "kind": "openai"})
);
assert_eq!(
parsed["effective_config"],
serde_json::json!({
"context_window": 32768, "tenacity": "normal",
"initiative": "measured",
"cognition": "default", "crew": "off", "ocap": "off",
"max_rounds": 40
})
);
assert_eq!(
parsed["timing"],
serde_json::json!({"wall_ms": 10_000, "gen_tokens": 500, "tok_s": 50.0})
);
assert_eq!(
parsed["receipt"],
serde_json::json!({"features": {
"scratchpad": {"state": "unavailable", "reason": "no --scratchpad-state was supplied"},
"code_search": {"state": "unsupported", "reason": "headless builds no retrieval index"},
"crew": {"state": "instantiated"},
}})
);
}
#[test]
fn an_instantiated_scratchpad_names_its_fresh_scope_and_seed() {
let f = InstantiatedFeatures {
scratchpad: true,
..InstantiatedFeatures::default()
};
assert_eq!(
feature_receipt(Some(f), &[], Some("bseed")).unwrap()["features"]["scratchpad"],
serde_json::json!({"state": "instantiated", "scope": "fresh", "seed": "bseed"})
);
}
#[test]
fn a_required_feature_the_turn_did_not_confirm_is_requested() {
let requested = serde_json::json!({
"state": "requested",
"reason": "required, but the turn did not confirm it was instantiated",
"required": true,
});
let crew_only = InstantiatedFeatures {
crew: true,
..InstantiatedFeatures::default()
};
let receipt =
feature_receipt(Some(crew_only), &[Feature::Crew, Feature::Scratchpad], None).unwrap();
assert_eq!(
receipt["features"]["crew"],
serde_json::json!({"state": "instantiated", "required": true})
);
assert_eq!(receipt["features"]["scratchpad"], requested);
assert_eq!(receipt["features"]["code_search"]["state"], "unsupported");
assert_eq!(
feature_receipt(None, &[Feature::Scratchpad], None),
Some(serde_json::json!({"features": {"scratchpad": requested}}))
);
assert_eq!(feature_receipt(None, &[], None), None);
}
#[test]
fn admission_names_the_receipts_state_and_reason() {
let refused = admit_required(&[Feature::Crew, Feature::CodeSearch], |f| {
f == Feature::Crew
})
.unwrap_err()
.to_string();
assert_eq!(
refused,
"required feature `code_search` is unsupported: headless builds no retrieval index"
);
assert!(admit_required(&[Feature::Crew], |f| f == Feature::Crew).is_ok());
}
#[test]
#[cfg(debug_assertions)]
#[should_panic(expected = "would overwrite an existing field")]
fn a_conditional_stanza_never_overwrites_an_existing_field() {
let mut record = contract_record(&inputs());
conditional_stanza(&mut record, "outcome", Some("completed"));
}
#[test]
fn smart_harness_configuration_is_declared_with_a_permitted_v1_outcome() {
let manifest = serde_json::json!({"head":"retained-head", "invocation_mode":"fresh", "configuration":{"auxiliary":{"placement":"cpu","max_calls":4}}});
let mut inputs = inputs();
inputs.smart_harness = Some(&manifest);
let record = contract_record(&inputs);
assert_eq!(record["effective_config"]["smart_harness"], manifest);
assert_eq!(record["contract_version"], "2");
assert!(permitted_outcomes().contains(&record["outcome"].as_str().unwrap()));
}
#[test]
fn durable_grants_are_listed_when_admitted_and_omitted_when_none() {
let i = inputs();
assert!(
contract_record(&i)["effective_config"]
.get("durable_grants")
.is_none(),
"no admitted grants must mean no stanza, not an empty array"
);
let mut with_grants = inputs();
let admitted = vec!["fs_read:/opt/canvas-token".to_string()];
with_grants.durable_grants = &admitted;
assert_eq!(
contract_record(&with_grants)["effective_config"]["durable_grants"],
serde_json::json!(["fs_read:/opt/canvas-token"])
);
}
#[test]
fn model_digest_only_when_supplied() {
let mut i = inputs();
assert!(contract_record(&i).get("model_digest").is_none());
i.model_digest = Some("a3f6…deadbeef");
assert_eq!(contract_record(&i)["model_digest"], "a3f6…deadbeef");
}
#[test]
fn unknown_optionals_are_omitted_not_invented() {
let mut i = inputs();
i.gen_tokens = None;
i.context_window = None;
i.features = None;
let record = contract_record(&i);
assert_eq!(record["timing"], serde_json::json!({"wall_ms": 10_000}));
assert!(record["effective_config"].get("context_window").is_none());
assert!(record.get("receipt").is_none());
i.gen_tokens = Some(500);
i.wall_ms = 0;
assert!(contract_record(&i)["timing"].get("tok_s").is_none());
}
}
#[cfg(test)]
#[path = "headless_contract_context_tests.rs"]
mod context_tests;