use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Mutex};
use async_trait::async_trait;
use supercode_harness::configfile::{resolve, ResolveOptions};
use supercode_harness::store::SessionStore;
use supercode_harness::turn_record::{FinishReason, TurnMarker};
use supercode_harness::{
Agent, AgentEvent, ChatMessage, ChatRequest, Config, FunctionCall, Provider, ToolCall, Usage,
};
const PARITY_PRESETS: &[&str] = &["cc-parity", "cx-parity"];
fn preset_config(name: &str) -> Config {
let top = format!("extends = \"{name}\"\n");
let resolved = resolve(&top, None, &ResolveOptions { strict: true })
.unwrap_or_else(|e| panic!("preset `{name}` failed to resolve: {e}"));
let mut config = resolved.config;
config.api_key = Some("test-key".to_string());
config.base_url = "http://127.0.0.1:1".to_string();
if config.model.is_empty() {
config.model = "openai/gpt-5-codex".to_string();
}
config
}
fn temp_dir(tag: &str) -> std::path::PathBuf {
use std::sync::atomic::AtomicU64;
static N: AtomicU64 = AtomicU64::new(0);
let dir = std::env::temp_dir().join(format!(
"sc-bp7-{tag}-{}-{}",
std::process::id(),
N.fetch_add(1, Ordering::SeqCst)
));
std::fs::create_dir_all(&dir).unwrap();
dir
}
struct Recorder {
requests: Arc<Mutex<Vec<ChatRequest>>>,
reply: String,
usage: Usage,
}
impl Recorder {
fn new(reply: &str) -> (Self, Arc<Mutex<Vec<ChatRequest>>>) {
let requests = Arc::new(Mutex::new(Vec::new()));
(
Recorder {
requests: requests.clone(),
reply: reply.to_string(),
usage: Usage {
prompt_tokens: 1_000_000,
completion_tokens: 1_000_000,
total_tokens: 2_000_000,
prompt_tokens_details: None,
},
},
requests,
)
}
}
#[async_trait]
impl Provider for Recorder {
async fn complete(
&self,
req: &ChatRequest,
_on_delta: &(dyn for<'a> Fn(&'a str) + Send + Sync),
) -> supercode_harness::Result<(ChatMessage, Usage)> {
self.requests.lock().unwrap().push(req.clone());
Ok((
ChatMessage::assistant(self.reply.clone()),
self.usage.clone(),
))
}
}
struct ToolCaller {
turns: AtomicUsize,
calls_per_turn: usize,
usage: Usage,
}
impl ToolCaller {
fn new(calls_per_turn: usize, completion_tokens: u64) -> Self {
ToolCaller {
turns: AtomicUsize::new(0),
calls_per_turn,
usage: Usage {
prompt_tokens: 0,
completion_tokens,
total_tokens: completion_tokens,
prompt_tokens_details: None,
},
}
}
}
#[async_trait]
impl Provider for ToolCaller {
async fn complete(
&self,
_req: &ChatRequest,
_on_delta: &(dyn for<'a> Fn(&'a str) + Send + Sync),
) -> supercode_harness::Result<(ChatMessage, Usage)> {
let n = self.turns.fetch_add(1, Ordering::SeqCst);
let calls: Vec<ToolCall> = (0..self.calls_per_turn)
.map(|i| ToolCall {
id: format!("call_{n}_{i}"),
kind: "function".into(),
function: FunctionCall {
name: "no_such_tool".into(),
arguments: "{}".into(),
},
})
.collect();
let mut msg = ChatMessage::assistant("working");
msg.tool_calls = Some(calls);
Ok((msg, self.usage.clone()))
}
}
#[tokio::test]
async fn every_round_trip_writes_context_usage_and_finish_markers_under_both_presets() {
for preset in PARITY_PRESETS {
let (provider, _seen) = Recorder::new("done");
let mut agent = Agent::with_provider(preset_config(preset), Box::new(provider));
agent.send("hello").await.unwrap();
let kinds: Vec<&TurnMarker> = agent.turn_records().iter().map(|r| &r.marker).collect();
assert!(
matches!(kinds.first(), Some(TurnMarker::Context { messages, .. }) if *messages >= 2),
"{preset}: the round-trip must OPEN with a context marker: {kinds:?}"
);
assert!(
kinds
.iter()
.any(|m| matches!(m, TurnMarker::Usage { total_tokens, .. } if *total_tokens > 0)),
"{preset}: no usage marker: {kinds:?}"
);
assert!(
matches!(
kinds.last(),
Some(TurnMarker::Finish {
reason: FinishReason::EndTurn
})
),
"{preset}: the round-trip must CLOSE with a finish marker: {kinds:?}"
);
for record in agent.turn_records() {
assert_eq!(record.turn, 0, "{preset}: one round-trip, turn 0");
assert_eq!(record.model, agent.model(), "{preset}: model provenance");
}
}
}
#[tokio::test]
async fn the_marker_log_persists_beside_the_session_and_reloads_losslessly() {
for preset in PARITY_PRESETS {
let (provider, _seen) = Recorder::new("done");
let mut agent = Agent::with_provider(preset_config(preset), Box::new(provider));
agent.send("hello").await.unwrap();
let store = SessionStore::open(temp_dir("events")).unwrap();
agent.save_turn_records(&store, "sess").unwrap();
assert!(
store.root().join("sess.events.jsonl").exists(),
"{preset}: the marker log lands on the sidecar family's own events member"
);
assert_eq!(
store.load_turn_records("sess").unwrap(),
agent.turn_records(),
"{preset}: reload is lossless"
);
}
}
#[tokio::test]
async fn cc_parity_persists_a_priced_usage_record_per_round_trip() {
let (provider, _seen) = Recorder::new("done");
let mut agent = Agent::with_provider(preset_config("cc-parity"), Box::new(provider));
assert!(
agent.model_priced(),
"cc-parity's pinned model is priceable"
);
agent.send("hello").await.unwrap();
let records = agent.usage_records();
assert_eq!(records.len(), 1);
let cost = records[0].cost_usd.expect("a priced model yields a cost");
let expected = supercode_harness::pricing_ref::REF_INPUT_PER_MTOK
+ supercode_harness::pricing_ref::REF_OUTPUT_PER_MTOK;
assert!((cost - expected).abs() < 1e-9, "{cost} vs {expected}");
assert!((agent.total_cost_usd() - expected).abs() < 1e-9);
let store = SessionStore::open(temp_dir("usage")).unwrap();
agent.save_usage_log(&store, "sess").unwrap();
let reloaded = store.load_usage_log("sess").unwrap();
assert_eq!(reloaded, records, "the persisted log round-trips with cost");
assert!(store.root().join("sess.usage.jsonl").exists());
}
#[tokio::test]
async fn an_unpriceable_model_records_no_cost_rather_than_a_guess() {
let mut config = preset_config("cx-parity");
config.model = "someone-elses/model-1".to_string();
let (provider, _seen) = Recorder::new("done");
let mut agent = Agent::with_provider(config, Box::new(provider));
assert!(!agent.model_priced());
agent.send("hello").await.unwrap();
assert_eq!(agent.usage_records()[0].cost_usd, None);
}
#[tokio::test]
async fn all_four_caps_are_enforced_by_the_loop() {
let mut config = preset_config("cc-parity");
config.max_iterations = 2;
let mut agent = Agent::with_provider(config, Box::new(ToolCaller::new(1, 0)));
let err = agent.send("go").await.unwrap_err();
assert!(
err.to_string().contains("2 reasoning/tool iterations"),
"{err}"
);
assert!(matches!(
agent.turn_records().last().map(|r| &r.marker),
Some(TurnMarker::Finish {
reason: FinishReason::MaxIterations
})
));
let mut config = preset_config("cc-parity");
config.max_total_output_tokens = Some(10);
let mut agent = Agent::with_provider(config, Box::new(ToolCaller::new(1, 100)));
agent.send("go").await.unwrap();
assert!(agent.turn_records().iter().any(|r| matches!(
r.marker,
TurnMarker::Finish {
reason: FinishReason::OutputTokenBudget
}
)));
let mut config = preset_config("cc-parity");
config.max_budget_usd = Some(0.01);
let mut agent = Agent::with_provider(config, Box::new(ToolCaller::new(1, 1_000_000)));
agent.send("go").await.unwrap();
assert!(
agent.total_cost_usd() > 0.01,
"the turn that tripped the cap is billed: {}",
agent.total_cost_usd()
);
assert!(agent.turn_records().iter().any(|r| matches!(
r.marker,
TurnMarker::Finish {
reason: FinishReason::SpendBudget
}
)));
let last = agent.history().last().unwrap();
assert_eq!(last.role, supercode_harness::Role::Tool);
assert!(last.content.as_deref().unwrap().contains("spend budget"));
let err = agent.send("more").await.unwrap_err();
assert!(err.to_string().contains("spend budget exhausted"), "{err}");
let mut config = preset_config("cx-parity");
config.max_steps = Some(2);
let mut agent = Agent::with_provider(config, Box::new(ToolCaller::new(3, 0)));
agent.send("go").await.unwrap();
assert_eq!(agent.total_steps(), 0, "the batch never ran");
assert!(agent.turn_records().iter().any(|r| matches!(
r.marker,
TurnMarker::Finish {
reason: FinishReason::StepBudget
}
)));
}
#[test]
fn a_spend_cap_on_an_unpriceable_model_is_refused_at_construction() {
let mut config = preset_config("cx-parity");
config.model = "someone-elses/model-1".to_string();
config.max_budget_usd = Some(5.0);
let mut priced = preset_config("cx-parity");
priced.model = config.model.clone();
priced.max_budget_usd = config.max_budget_usd;
let err = match Agent::new(config) {
Ok(_) => panic!("an unpriceable spend cap must be refused"),
Err(e) => e,
};
assert!(err.to_string().contains("no known price"), "{err}");
priced.price_input_per_mtok = Some(1.0);
priced.price_output_per_mtok = Some(2.0);
assert!(Agent::new(priced).is_ok());
}
#[tokio::test]
async fn an_interrupted_turn_persists_an_abort_marker_and_keeps_its_partial_work() {
for preset in PARITY_PRESETS {
let (provider, _seen) = Recorder::new("first answer");
let mut agent = Agent::with_provider(preset_config(preset), Box::new(provider));
agent.send("hello").await.unwrap();
let before = agent.history().len();
let seen = Arc::new(Mutex::new(Vec::new()));
let sink_seen = seen.clone();
agent.set_event_sink(Box::new(move |e: AgentEvent| {
if let AgentEvent::TurnAborted { source } = e {
sink_seen.lock().unwrap().push(source);
}
}));
agent.note_abort("ctrl_c");
assert_eq!(
seen.lock().unwrap().as_slice(),
["ctrl_c"],
"{preset}: the live event fires"
);
let marker = agent.turn_records().last().unwrap();
assert!(
matches!(&marker.marker, TurnMarker::Aborted { source, messages }
if source == "ctrl_c" && *messages == before),
"{preset}: {marker:?}"
);
assert_eq!(
agent.history().len(),
before,
"{preset}: partial work is preserved, not rewound"
);
let store = SessionStore::open(temp_dir("abort")).unwrap();
agent.save_turn_records(&store, "sess").unwrap();
assert!(
store
.load_turn_records("sess")
.unwrap()
.iter()
.any(|r| matches!(r.marker, TurnMarker::Aborted { .. })),
"{preset}: the abort survives a reload — the interruption is a FACT, not an inference"
);
}
}
#[tokio::test]
async fn a_retried_request_surfaces_as_an_event_and_a_persisted_record() {
use tokio::io::{AsyncReadExt, AsyncWriteExt};
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let server = tokio::spawn(async move {
for n in 0..2u32 {
let (mut sock, _) = listener.accept().await.unwrap();
let mut buf = [0u8; 8192];
let _ = sock.read(&mut buf).await;
let resp = if n == 0 {
"HTTP/1.1 503 Service Unavailable\r\nContent-Length: 0\r\nConnection: close\r\n\r\n"
.to_string()
} else {
let sse = "data: {\"choices\":[{\"delta\":{\"content\":\"ok\"}}]}\n\n\
data: [DONE]\n\n";
format!(
"HTTP/1.1 200 OK\r\nContent-Type: text/event-stream\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{sse}",
sse.len()
)
};
sock.write_all(resp.as_bytes()).await.unwrap();
sock.flush().await.unwrap();
}
});
let mut config = preset_config("cc-parity");
config.base_url = format!("http://{addr}");
config.retry_enabled = true;
config.retry_max_retries = Some(2);
config.retry_base_delay_ms = Some(1);
let mut agent = Agent::new(config).unwrap();
let events = Arc::new(Mutex::new(Vec::new()));
let sink_events = events.clone();
agent.set_event_sink(Box::new(move |e: AgentEvent| {
if let AgentEvent::ProviderRetry {
attempt, reason, ..
} = e
{
sink_events.lock().unwrap().push((attempt, reason));
}
}));
let reply = agent.send("hi").await.unwrap();
assert_eq!(reply, "ok", "the retry succeeded");
server.await.unwrap();
let seen = events.lock().unwrap().clone();
assert_eq!(seen.len(), 1, "one retry surfaced as an event: {seen:?}");
assert_eq!(seen[0].0, 0, "attempt 0 is the one that failed");
assert!(seen[0].1.contains("503"), "{:?}", seen[0].1);
let retries: Vec<&TurnMarker> = agent
.turn_records()
.iter()
.map(|r| &r.marker)
.filter(|m| matches!(m, TurnMarker::Retry { .. }))
.collect();
assert_eq!(retries.len(), 1, "and as a persisted record: {retries:?}");
let store = SessionStore::open(temp_dir("retry")).unwrap();
agent.save_turn_records(&store, "sess").unwrap();
assert!(store
.load_turn_records("sess")
.unwrap()
.iter()
.any(|r| matches!(r.marker, TurnMarker::Retry { .. })));
}
#[tokio::test]
async fn a_goal_is_restated_on_every_request_persisted_and_kept_out_of_history() {
for preset in PARITY_PRESETS {
let config = preset_config(preset);
assert!(
config.goals_enabled,
"{preset} must arm capabilities.todos.goals"
);
let (provider, seen) = Recorder::new("done");
let mut agent = Agent::with_provider(config, Box::new(provider));
assert!(agent.set_goal("ship BP-7 with proof"));
agent.send("first").await.unwrap();
agent.send("second").await.unwrap();
let requests = seen.lock().unwrap();
assert_eq!(requests.len(), 2);
for (i, req) in requests.iter().enumerate() {
let tail = req.messages.last().unwrap();
assert!(
tail.content
.as_deref()
.unwrap_or_default()
.contains("ship BP-7 with proof"),
"{preset}: request {i} must restate the standing objective"
);
}
drop(requests);
assert!(
!agent.history().iter().any(|m| m
.content
.as_deref()
.unwrap_or_default()
.contains("<goal>")),
"{preset}: the goal is harness-tracked, never written into the transcript"
);
let store = SessionStore::open(temp_dir("goal")).unwrap();
agent.save_goal(&store, "sess").unwrap();
let loaded = store.load_goal("sess").unwrap().expect("persisted goal");
assert_eq!(loaded.objective, "ship BP-7 with proof");
assert!(store.root().join("sess.goal.json").exists());
let mut resumed =
Agent::with_provider(preset_config(preset), Box::new(Recorder::new("x").0));
resumed.restore_goal(Some(loaded));
assert_eq!(
resumed.goal().map(|g| g.objective.as_str()),
Some("ship BP-7 with proof"),
"{preset}: the objective crosses a resume"
);
assert!(agent.clear_goal());
agent.save_goal(&store, "sess").unwrap();
assert!(store.load_goal("sess").unwrap().is_none());
}
}
#[test]
fn a_goal_is_refused_when_the_module_is_off() {
let mut config = preset_config("cc-parity");
config.goals_enabled = false;
let mut agent = Agent::with_provider(config, Box::new(Recorder::new("x").0));
assert!(
!agent.set_goal("nope"),
"the module gate, not a silent success"
);
assert!(agent.goal().is_none());
}
#[tokio::test]
async fn the_review_turn_sends_the_presets_own_report_format() {
for preset in PARITY_PRESETS {
let config = preset_config(preset);
let (provider, seen) = Recorder::new("reviewed");
let mut agent = Agent::with_provider(config, Box::new(provider));
let prompt = agent
.review_prompt("focus on the parser")
.unwrap_or_else(|| panic!("{preset} must pin a review template"));
assert!(
prompt.contains("focus on the parser"),
"{preset}: args land"
);
for section in ["Correctness", "Security", "Verdict"] {
assert!(
prompt.contains(section),
"{preset}: the template IS the report format, missing `{section}`"
);
}
assert!(!prompt.contains("{args}"), "{preset}: the slot is filled");
agent.review("focus on the parser").await.unwrap();
let requests = seen.lock().unwrap();
let sent = requests[0]
.messages
.iter()
.any(|m| m.content.as_deref().unwrap_or_default().contains("Verdict"));
assert!(
sent,
"{preset}: the review turn actually carries the format"
);
assert!(
!requests[0].tools.is_empty(),
"{preset}: a review is a TURN of this session — it keeps the session's tools"
);
}
}
#[tokio::test]
async fn a_side_question_sees_the_whole_context_has_no_tools_and_records_nothing() {
for preset in PARITY_PRESETS {
let (provider, seen) = Recorder::new("the answer");
let mut agent = Agent::with_provider(preset_config(preset), Box::new(provider));
agent.send("what is in this repo?").await.unwrap();
let history_before = agent.history().to_vec();
let usage_before = agent.usage_records().len();
let records_before = agent.turn_records().len();
let answer = agent
.side_question("by the way, what model are you?")
.await
.unwrap();
assert_eq!(answer, "the answer");
let requests = seen.lock().unwrap();
let side = requests.last().unwrap();
assert!(
side.tools.is_empty(),
"{preset}: a side question is TOOL-LESS: {:?}",
side.tools.iter().map(|t| &t.name).collect::<Vec<_>>()
);
assert!(
side.messages.len() > history_before.len(),
"{preset}: it is asked OVER the full context, not in a blank one"
);
assert!(side
.messages
.iter()
.any(|m| m.content.as_deref().unwrap_or_default() == "what is in this repo?"));
assert!(side
.messages
.last()
.unwrap()
.content
.as_deref()
.unwrap()
.contains("by the way, what model are you?"));
drop(requests);
assert_eq!(
agent.history(),
history_before.as_slice(),
"{preset}: the exchange never enters history"
);
assert_eq!(agent.usage_records().len(), usage_before);
assert_eq!(agent.turn_records().len(), records_before);
}
}
#[tokio::test]
async fn effort_changes_mid_session_including_an_off_toggle() {
for preset in PARITY_PRESETS {
let config = preset_config(preset);
assert_eq!(
config.effort.as_deref(),
Some("medium"),
"{preset} pins the starting level"
);
let (provider, seen) = Recorder::new("done");
let mut agent = Agent::with_provider(config, Box::new(provider));
agent.send("one").await.unwrap();
assert_eq!(
agent.set_effort(Some("high".into())).as_deref(),
Some("medium")
);
agent.send("two").await.unwrap();
assert_eq!(agent.set_effort(None).as_deref(), Some("high"));
assert_eq!(agent.effort(), None);
agent.send("three").await.unwrap();
let requests = seen.lock().unwrap();
let efforts: Vec<Option<String>> = requests.iter().map(|r| r.effort.clone()).collect();
assert_eq!(
efforts,
vec![Some("medium".to_string()), Some("high".to_string()), None],
"{preset}: each change reaches the very next request"
);
drop(requests);
let changes: Vec<(Option<String>, Option<String>)> = agent
.turn_records()
.iter()
.filter_map(|r| match &r.marker {
TurnMarker::Effort { from, to } => Some((from.clone(), to.clone())),
_ => None,
})
.collect();
assert_eq!(
changes,
vec![
(Some("medium".to_string()), Some("high".to_string())),
(Some("high".to_string()), None)
],
"{preset}: every change is recorded, the model_change analog"
);
}
}
#[test]
fn both_presets_arm_auto_title() {
for preset in PARITY_PRESETS {
assert!(
preset_config(preset).auto_title,
"{preset}: `[core.session] auto_title` is the gate the built titler waits on"
);
}
}
async fn turn_patch_over_preset(preset: &str) -> (String, bool) {
use supercode_harness::tools::WriteObserver;
let project = temp_dir(&format!("diff-{preset}"));
std::fs::write(project.join("kept.txt"), "one\ntwo\nthree\n").unwrap();
let mut config = preset_config(preset);
config.cwd = project.clone();
config.checkpoint_dir = Some(temp_dir(&format!("shadow-{preset}")));
assert!(
config.checkpoint_enabled,
"{preset} must arm capabilities.checkpoint for turn-diff tracking"
);
let restore_enabled = config.checkpoint_restore;
let (provider, _seen) = Recorder::new("done");
let mut agent = Agent::with_provider(config, Box::new(provider));
agent.send("edit the file").await.unwrap();
let observer = agent
.checkpoint_observer()
.expect("the preset arms the observer");
observer.before_write(&project.join("kept.txt")).await;
std::fs::write(project.join("kept.txt"), "one\nTWO\nthree\n").unwrap();
observer.before_write(&project.join("kept.txt")).await;
std::fs::write(project.join("kept.txt"), "one\nTWO\nthree\nfour\n").unwrap();
observer.before_write(&project.join("made.txt")).await;
std::fs::write(project.join("made.txt"), "brand new\n").unwrap();
let id = observer.current().expect("the turn's checkpoint is open");
(observer.turn_patch(&id).unwrap(), restore_enabled)
}
#[tokio::test]
async fn both_presets_report_a_turns_cumulative_unified_diff() {
for preset in PARITY_PRESETS {
let (patch, _) = turn_patch_over_preset(preset).await;
assert!(
patch.contains("--- a/kept.txt") && patch.contains("+++ b/kept.txt"),
"{preset}: a real unified diff, not a file list:\n{patch}"
);
assert!(
patch.contains("-two") && patch.contains("+TWO") && patch.contains("+four"),
"{preset}: the turn's NET effect across two writes to one file:\n{patch}"
);
assert!(
!patch.contains("+TWO\n+TWO"),
"{preset}: cumulative, not per-write:\n{patch}"
);
assert!(
patch.contains("--- a/made.txt") && patch.contains("+brand new"),
"{preset}: a file created this turn diffs against nothing:\n{patch}"
);
}
}
#[tokio::test]
async fn cx_parity_tracks_the_diff_but_refuses_a_restore_it_has_no_surface_for() {
let (patch, cc_restore) = turn_patch_over_preset("cc-parity").await;
assert!(cc_restore, "cc-parity keeps /rewind's restore half");
assert!(!patch.is_empty());
let mut config = preset_config("cx-parity");
config.cwd = temp_dir("cx-restore");
config.checkpoint_dir = Some(temp_dir("cx-restore-shadow"));
assert!(!config.checkpoint_restore);
let observer = supercode_harness::checkpoint::observer_for_config(&config)
.expect("cx-parity arms the observer");
assert!(!observer.restore_enabled());
let id = observer.store().create_checkpoint("t").unwrap();
let err = observer.restore(&id).unwrap_err();
assert!(err.to_string().contains("restore = false"), "{err}");
}
#[tokio::test]
async fn a_project_agent_file_is_discovered_under_both_presets_and_carries_permissions() {
for preset in PARITY_PRESETS {
let project = temp_dir(&format!("agents-{preset}"));
std::fs::create_dir_all(project.join(".claude/agents")).unwrap();
std::fs::write(
project.join(".claude/agents/auditor.md"),
"---\n\
name: auditor\n\
model: anthropic/claude-haiku-4-5\n\
tools: [Bash]\n\
sandbox: read-only\n\
approval: untrusted\n\
auto_approved_tools: [Bash]\n\
deny: [\"bash(rm -rf*)\"]\n\
---\n\
You audit, you never write.\n",
)
.unwrap();
let mut config = preset_config(preset);
config.cwd = project.clone();
let agent = Agent::with_provider(config, Box::new(Recorder::new("x").0));
let def = agent
.config()
.subagents_definitions
.get("auditor")
.unwrap_or_else(|| panic!("{preset}: .claude/agents/*.md was not discovered"));
assert_eq!(def.system_prompt, "You audit, you never write.");
assert_eq!(def.model.as_deref(), Some("anthropic/claude-haiku-4-5"));
assert_eq!(def.tools.as_deref(), Some(&["bash".to_string()][..]));
let perms = def
.permissions
.as_ref()
.unwrap_or_else(|| panic!("{preset}: the permissions component is missing"));
assert_eq!(
perms.sandbox,
Some(supercode_harness::SandboxPolicy::ReadOnly)
);
assert_eq!(
perms.approval,
Some(supercode_harness::ApprovalPolicy::Untrusted)
);
assert_eq!(perms.deny, vec!["bash(rm -rf*)".to_string()]);
}
}
#[tokio::test]
async fn an_agent_definition_can_only_tighten_its_parents_posture() {
use supercode_harness::{ApprovalPolicy, SandboxPolicy};
let project = temp_dir("agents-tighten");
std::fs::create_dir_all(project.join(".claude/agents")).unwrap();
std::fs::write(
project.join(".claude/agents/tight.md"),
"---\nname: tight\nsandbox: read-only\napproval: untrusted\ndeny: [\"bash(curl*)\"]\n---\nStrict.\n",
)
.unwrap();
std::fs::write(
project.join(".claude/agents/loose.md"),
"---\nname: loose\nsandbox: danger-full-access\napproval: never\n---\nLoose.\n",
)
.unwrap();
let mut config = preset_config("cc-parity");
config.cwd = project;
config.sandbox = SandboxPolicy::WorkspaceWrite;
config.approval = ApprovalPolicy::OnRequest;
config.auto_approved_tools = ["read_file".to_string(), "bash".to_string()]
.into_iter()
.collect();
let agent = Agent::with_provider(config, Box::new(Recorder::new("x").0));
let tight = agent.child_config_for_agent_type("tight").expect("tight");
assert_eq!(tight.sandbox, SandboxPolicy::ReadOnly, "tighter wins");
assert_eq!(tight.approval, ApprovalPolicy::Untrusted);
assert!(
tight.tool_deny_patterns.iter().any(|p| p == "bash(curl*)"),
"deny is a union"
);
let loose = agent.child_config_for_agent_type("loose").expect("loose");
assert_eq!(
loose.sandbox,
SandboxPolicy::WorkspaceWrite,
"a looser sandbox in a repo file is IGNORED, never honored"
);
assert_eq!(
loose.approval,
ApprovalPolicy::OnRequest,
"a looser approval in a repo file is IGNORED"
);
}
#[test]
fn both_presets_detach_background_children_with_a_c6_policy() {
for preset in PARITY_PRESETS {
let config = preset_config(preset);
assert!(
config.subagents_enabled && config.subagents_background,
"{preset}: background children must actually detach"
);
assert!(
config.subagents_background_prompts.is_some(),
"{preset}: C6 — a detached child needs an approval policy, and \
`run_spawn_subagent` refuses the spawn without one"
);
}
}
#[tokio::test]
async fn both_presets_advertise_the_mailbox_and_resume_intrinsics() {
for preset in PARITY_PRESETS {
let agent = Agent::with_provider(preset_config(preset), Box::new(Recorder::new("x").0));
let names: Vec<String> = agent.tool_schemas().into_iter().map(|t| t.name).collect();
for intrinsic in [
"spawn_subagent",
"subagent_status",
"subagent_message",
"subagent_resume",
] {
assert!(
names.iter().any(|n| n == intrinsic),
"{preset}: `{intrinsic}` is not advertised: {names:?}"
);
}
}
}
struct TeamProvider {
started: Arc<std::sync::atomic::AtomicBool>,
delivered: Arc<std::sync::atomic::AtomicBool>,
child_requests: Arc<Mutex<Vec<Vec<String>>>>,
parent_step: AtomicUsize,
subagent_id: Arc<Mutex<Option<String>>>,
}
fn user_texts(req: &ChatRequest) -> Vec<String> {
req.messages
.iter()
.filter(|m| m.role == supercode_harness::Role::User)
.map(|m| m.content.clone().unwrap_or_default())
.collect()
}
#[async_trait]
impl Provider for TeamProvider {
async fn complete(
&self,
req: &ChatRequest,
_on_delta: &(dyn for<'a> Fn(&'a str) + Send + Sync),
) -> supercode_harness::Result<(ChatMessage, Usage)> {
let users = user_texts(req);
let is_child = users.first().map(|t| t.starts_with("CHILD:")) == Some(true);
if is_child {
self.started.store(true, Ordering::SeqCst);
self.child_requests.lock().unwrap().push(users.clone());
if users.len() == 1 {
for _ in 0..600 {
if self.delivered.load(Ordering::SeqCst) {
break;
}
tokio::time::sleep(std::time::Duration::from_millis(5)).await;
}
let mut msg = ChatMessage::assistant("thinking");
msg.tool_calls = Some(vec![ToolCall {
id: "child_call".into(),
kind: "function".into(),
function: FunctionCall {
name: "no_such_tool".into(),
arguments: "{}".into(),
},
}]);
return Ok((msg, Usage::default()));
}
return Ok((
ChatMessage::assistant(format!("child heard: {}", users.join(" | "))),
Usage::default(),
));
}
let step = self.parent_step.fetch_add(1, Ordering::SeqCst);
let id = self.subagent_id.lock().unwrap().clone();
let call = |name: &str, args: serde_json::Value| {
let mut msg = ChatMessage::assistant("working");
msg.tool_calls = Some(vec![ToolCall {
id: format!("parent_{name}"),
kind: "function".into(),
function: FunctionCall {
name: name.into(),
arguments: args.to_string(),
},
}]);
msg
};
if id.is_none() {
if let Some(text) = req
.messages
.iter()
.rev()
.find(|m| m.role == supercode_harness::Role::Tool)
.and_then(|m| m.content.clone())
{
if let Ok(v) = serde_json::from_str::<serde_json::Value>(&text) {
if let Some(new_id) = v.get("subagent_id").and_then(|x| x.as_str()) {
*self.subagent_id.lock().unwrap() = Some(new_id.to_string());
}
}
}
}
let id = self.subagent_id.lock().unwrap().clone();
match (step, id) {
(0, _) => Ok((
call(
"spawn_subagent",
serde_json::json!({"task": "CHILD: count to three", "background": true}),
),
Usage::default(),
)),
(_, Some(id)) => {
let last_tool = req
.messages
.iter()
.rev()
.find(|m| m.role == supercode_harness::Role::Tool)
.and_then(|m| m.content.clone())
.unwrap_or_default();
if !self.delivered.load(Ordering::SeqCst) {
for _ in 0..600 {
if self.started.load(Ordering::SeqCst) {
break;
}
tokio::time::sleep(std::time::Duration::from_millis(5)).await;
}
self.delivered.store(true, Ordering::SeqCst);
return Ok((
call(
"subagent_message",
serde_json::json!({"subagent_id": id, "message": "MAIL: also count backwards"}),
),
Usage::default(),
));
}
if last_tool.contains("\"status\":\"done\"") {
return Ok((
ChatMessage::assistant(format!("parent done: {last_tool}")),
Usage::default(),
));
}
Ok((
call("subagent_status", serde_json::json!({"subagent_id": id})),
Usage::default(),
))
}
(_, None) => Ok((ChatMessage::assistant("no child"), Usage::default())),
}
}
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn a_running_background_child_receives_a_message_from_its_parent() {
for preset in PARITY_PRESETS {
let mut config = preset_config(preset);
config.max_iterations = 40;
let child_requests = Arc::new(Mutex::new(Vec::new()));
let provider = TeamProvider {
started: Arc::new(std::sync::atomic::AtomicBool::new(false)),
delivered: Arc::new(std::sync::atomic::AtomicBool::new(false)),
child_requests: child_requests.clone(),
parent_step: AtomicUsize::new(0),
subagent_id: Arc::new(Mutex::new(None)),
};
let mut agent = Agent::with_provider_arc(config, Arc::new(provider));
let answer = agent.send("run the team").await.unwrap();
assert!(answer.contains("parent done"), "{preset}: {answer}");
assert!(
answer.contains("child heard"),
"{preset}: the parent collected the child's result: {answer}"
);
let seen = child_requests.lock().unwrap();
assert_eq!(
seen.first().map(|t| t.as_slice()),
Some(&["CHILD: count to three".to_string()][..]),
"{preset}: the child began with only its task — the message it later receives was NOT part of the spawn: {seen:?}"
);
assert!(
seen.iter().any(|turn| turn
.iter()
.any(|t| t.contains("MAIL: also count backwards"))),
"{preset}: the message never reached the running child: {seen:?}"
);
}
}
struct ResumeProvider {
parent_step: AtomicUsize,
child_requests: Arc<Mutex<Vec<Vec<String>>>>,
subagent_id: Arc<Mutex<Option<String>>>,
}
#[async_trait]
impl Provider for ResumeProvider {
async fn complete(
&self,
req: &ChatRequest,
_on_delta: &(dyn for<'a> Fn(&'a str) + Send + Sync),
) -> supercode_harness::Result<(ChatMessage, Usage)> {
let users = user_texts(req);
if users.first().map(|t| t.starts_with("CHILD:")) == Some(true) {
self.child_requests.lock().unwrap().push(users.clone());
return Ok((
ChatMessage::assistant("the secret is 41".to_string()),
Usage::default(),
));
}
let step = self.parent_step.fetch_add(1, Ordering::SeqCst);
let mut msg = ChatMessage::assistant("working");
let (name, args) = match step {
0 => (
"spawn_subagent",
serde_json::json!({"task": "CHILD: find the secret", "background": false}),
),
1 => return Ok((ChatMessage::assistant("phase one done"), Usage::default())),
2 => {
let id = self.subagent_id.lock().unwrap().clone().unwrap_or_default();
(
"subagent_resume",
serde_json::json!({"subagent_id": id, "task": "CHILD: repeat the secret"}),
)
}
_ => {
let last = req
.messages
.iter()
.rev()
.find(|m| m.role == supercode_harness::Role::Tool)
.and_then(|m| m.content.clone())
.unwrap_or_default();
return Ok((
ChatMessage::assistant(format!("parent done: {last}")),
Usage::default(),
));
}
};
msg.tool_calls = Some(vec![ToolCall {
id: format!("parent_{step}"),
kind: "function".into(),
function: FunctionCall {
name: name.into(),
arguments: args.to_string(),
},
}]);
Ok((msg, Usage::default()))
}
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn a_finished_child_resumes_with_its_own_context_intact() {
for preset in PARITY_PRESETS {
let mut config = preset_config(preset);
config.max_iterations = 12;
let child_requests = Arc::new(Mutex::new(Vec::new()));
let ids = Arc::new(Mutex::new(None));
let provider = ResumeProvider {
parent_step: AtomicUsize::new(0),
child_requests: child_requests.clone(),
subagent_id: ids.clone(),
};
let mut agent = Agent::with_provider_arc(config, Arc::new(provider));
agent.send("phase one").await.unwrap();
let id = agent
.reaped_subagent_ids()
.first()
.cloned()
.unwrap_or_else(|| panic!("{preset}: no child was reaped"));
*ids.lock().unwrap() = Some(id);
let answer = agent.send("phase two").await.unwrap();
assert!(answer.contains("\"resumed\":true"), "{preset}: {answer}");
let seen = child_requests.lock().unwrap();
assert_eq!(seen.len(), 2, "{preset}: one spawn, one resume: {seen:?}");
assert_eq!(
seen[0],
vec!["CHILD: find the secret".to_string()],
"{preset}: the first run starts blank"
);
assert_eq!(
seen[1],
vec![
"CHILD: find the secret".to_string(),
"CHILD: repeat the secret".to_string()
],
"{preset}: the RESUMED child carries its own earlier conversation"
);
}
}