use crate::harness::{agent, human, plan_of, World, REFUSED, REPORTING_MEMBER};
use serde_json::{json, Value};
fn configs_of(world: &World, run: &str, member: &str) -> Vec<(String, String)> {
let events = world.journal(run);
let started: Vec<&Value> = events
.iter()
.filter(|event| event["kind"] == "member-started")
.filter(|event| event["labels"]["member"] == member)
.collect();
assert!(
!started.is_empty(),
"no member '{member}' started in {run}: {events:#?}"
);
started
.into_iter()
.map(|event| {
let path = event["payload"]["config"]
.as_str()
.expect("the sibling publishes the config it launched the member with");
let text =
std::fs::read_to_string(path).unwrap_or_else(|e| panic!("{path} unreadable: {e}"));
let node = event["labels"]["onepipeline.node"]
.as_str()
.unwrap_or_default()
.to_string();
(node, text)
})
.collect()
}
fn config_of(world: &World, run: &str, member: &str) -> String {
configs_of(world, run, member).swap_remove(0).1
}
fn ready_and_undriven(world: &World, run: &str, node: Value) {
let path = world.plan(run, &plan_of(run, vec![human("approve", &[]), node]));
world
.run(&["start", &path.to_string_lossy(), "--attach"])
.exited(0);
world.run(&["attest", run, "approve"]).exited(0);
}
#[test]
fn relative_default_graphs_dispatch_from_the_launch_directory() {
let world = World::new("real-relative-defaults");
world.write_graphs();
let path = world.plan(
"relative-defaults",
&plan_of("relative-defaults", vec![agent("build", &[])]),
);
let mut command = world.agentgraph_cmd(&[
"start",
&path.to_string_lossy(),
"--attach",
"--dag-graph",
"graphs/dag-scope.yaml",
]);
command
.current_dir(&world.root)
.env_remove("ONEPIPELINE_NODE_GRAPH");
let started = world.run_on(command, "start relative defaults");
started.exited(0).settled();
assert!(
world
.turns()
.iter()
.any(|turn| turn.prompt.contains("Do build.")),
"the node-scope graph did not dispatch its member: {}",
world.dump()
);
let launch = world.run_json("relative-defaults", "launch.json");
for field in ["graph", "node_graph"] {
assert!(
std::path::Path::new(launch[field].as_str().expect("a graph path")).is_absolute(),
"{field} was not resolved at launch: {launch}"
);
}
assert_eq!(launch["dir"], json!(world.root));
let announced = world
.journal("relative-defaults")
.into_iter()
.find(|event| event["kind"] == "graph-started" && event["labels"]["node"].is_null())
.expect("the driver announced itself into the merged store");
assert_eq!(
launch["graph_run"], announced["labels"]["run_id"],
"the record names a different graph run from the one that drove the run"
);
}
#[test]
fn relative_node_and_step_graph_overrides_dispatch_from_the_launch_directory() {
let world = World::new("real-relative-plan-overrides");
world.write_graphs();
world.repository("local-direct", &["true"]);
for (source, target) in [
("node-scope.yaml", "node-override.yaml"),
("node-scope.yaml", "step-override.yaml"),
] {
std::fs::copy(world.graphs().join(source), world.root.join(target))
.expect("the relative graph override is written");
}
let worker_config = "oneharness-worker.toml";
std::fs::copy(
world.graphs().join(worker_config),
world.root.join(worker_config),
)
.expect("the relative graphs' harness config is written");
let node = json!({
"id": "service",
"repo": "service",
"title": "feat: land the workstream",
"agent_graph": "node-override.yaml",
"steps": [
{"id": "implement", "persona": "engineer", "task": "## What\nimplement"},
{
"id": "review",
"persona": "reviewer",
"task": "## What\nreview",
"deps": ["implement"],
"agent_graph": "step-override.yaml",
},
],
});
let path = world.plan(
"relative-plan-overrides",
&plan_of("relative-plan-overrides", vec![node]),
);
let mut command = world.agentgraph_cmd(&["start", &path.to_string_lossy(), "--attach"]);
command.current_dir(&world.root);
world
.run_on(command, "start relative plan graph overrides")
.exited(0)
.settled();
for (step, graph) in [
("implement", world.root.join("node-override.yaml")),
("review", world.root.join("step-override.yaml")),
] {
let graph = graph
.canonicalize()
.expect("the expected relative graph path resolves");
assert!(
world
.journal("relative-plan-overrides")
.iter()
.any(|event| {
event["kind"] == "graph-started"
&& event["labels"]["node"] == "service"
&& event["labels"]["step"] == step
&& event["payload"]["graph"].as_str().is_some_and(|actual| {
std::fs::canonicalize(actual)
.map(|actual| actual == graph)
.unwrap_or(false)
})
}),
"{step} did not dispatch with its resolved graph: {}",
world.dump()
);
}
}
#[test]
fn a_lifecycle_nodes_two_graphs_are_the_ones_its_launch_resolved() {
let world = World::new("lifecycle-recorded-default-graph");
world.repository("local-direct", &["true"]);
world.script("driver.wait", "hold");
world.script("service.work", "the worker wrote this\n");
let launch_graph = crate::harness::repo_file("graphs/node-scope.yaml");
let later_graph = world.root.join("later-node-scope.yaml");
std::fs::copy(&launch_graph, &later_graph).expect("the later graph is written");
let drafting = world.pr_author_graph();
let mut service = crate::harness::lifecycle("service", &["approve"]);
service["deps"] = json!(["approve"]);
let path = world.plan(
"recorded-lifecycle-graph",
&plan_of(
"recorded-lifecycle-graph",
vec![human("approve", &[]), service],
),
);
let mut start = world.cmd(&[
"start",
&path.to_string_lossy(),
"--attach",
"--pr-author-graph",
&drafting,
]);
start.env("ONEPIPELINE_NODE_GRAPH", &launch_graph);
world
.run_on(start, "start recorded lifecycle graph")
.exited(0);
assert_eq!(
world.run_json("recorded-lifecycle-graph", "launch.json")["pr_author_graph"],
json!(drafting),
"the launch record does not name the graph the launch was given"
);
world
.run(&["attest", "recorded-lifecycle-graph", "approve"])
.exited(0);
let mut adopted = world.cmd(&["adopt", "recorded-lifecycle-graph"]);
adopted.env("ONEPIPELINE_NODE_GRAPH", &later_graph);
world
.run_on(adopted, "adopt with a changed live node graph")
.exited(0);
let invocations = world.invocations();
let relevant: Vec<&Value> = invocations
.iter()
.filter(|call| {
call["tool"] == "oneagentgraph"
&& call["args"]
.as_array()
.is_some_and(|args| args.iter().any(|arg| arg == "onepipeline.node=service"))
})
.collect();
let under = |persona: &str| -> Vec<&Value> {
relevant
.iter()
.filter(|call| {
call["args"]
.as_array()
.is_some_and(|args| args.iter().any(|arg| arg == persona))
})
.copied()
.collect()
};
let drafts = under("onepipeline.persona=pr-author");
assert_eq!(
drafts.len(),
1,
"the body drafting dispatch did not run after adoption: {relevant:?}"
);
assert_eq!(
drafts[0]["args"][1], drafting,
"the drafting dispatch ran a graph the launch did not record: {drafts:?}"
);
let worked = under("onepipeline.persona=engineer");
assert!(
!worked.is_empty(),
"the node never dispatched: {relevant:?}"
);
assert!(
worked
.iter()
.all(|call| call["args"][1] == launch_graph.to_string_lossy().as_ref()),
"a lifecycle dispatch re-read the live graph instead of launch state: {worked:?}"
);
}
#[test]
fn an_unreadable_relative_graph_names_its_launch_base() {
let world = World::new("relative-graph-error");
let path = world.plan(
"relative-error",
&plan_of("relative-error", vec![agent("build", &[])]),
);
let mut command = world.agentgraph_cmd(&[
"start",
&path.to_string_lossy(),
"--attach",
"--dag-graph",
"graphs/missing-dag.yaml",
]);
command.current_dir(&world.root);
let failed = world.run_on(command, "start missing relative graph");
failed.exited(crate::harness::REFUSED);
failed.err_has("graphs/missing-dag.yaml");
failed.err_has(&world.root.to_string_lossy());
}
#[test]
fn an_unreadable_relative_node_graph_names_its_launch_base() {
let world = World::new("relative-node-graph-error");
world.write_graphs();
let path = world.plan(
"relative-node-error",
&plan_of("relative-node-error", vec![agent("build", &[])]),
);
let mut command = world.agentgraph_cmd(&["start", &path.to_string_lossy(), "--attach"]);
command
.current_dir(&world.root)
.env("ONEPIPELINE_NODE_GRAPH", "graphs/missing-node.yaml");
let failed = world.run_on(command, "start missing relative node graph");
failed.exited(crate::harness::REFUSED);
failed.err_has("graphs/missing-node.yaml");
failed.err_has(&world.root.to_string_lossy());
}
#[test]
fn unreadable_relative_plan_graphs_name_their_path_and_launch_base() {
let world = World::new("relative-plan-graph-errors");
world.write_graphs();
world.repository("local-direct", &["true"]);
let cases = [
(
"missing-node-override",
json!({
"id": "build",
"persona": "engineer",
"task": "## What\nbuild",
"agent_graph": "graphs/missing-node-override.yaml",
}),
"graphs/missing-node-override.yaml",
),
(
"missing-step-override",
json!({
"id": "service",
"repo": "service",
"title": "feat: land the workstream",
"steps": [{
"id": "implement",
"persona": "engineer",
"task": "## What\nimplement",
"agent_graph": "graphs/missing-step-override.yaml",
}],
}),
"graphs/missing-step-override.yaml",
),
];
for (name, node, missing) in cases {
let path = world.plan(name, &plan_of(name, vec![node]));
let mut command = world.agentgraph_cmd(&["start", &path.to_string_lossy(), "--attach"]);
command.current_dir(&world.root);
let failed = world.run_on(command, &format!("start {name}"));
failed.exited(crate::harness::REFUSED);
failed.err_has(missing);
failed.err_has(&world.root.to_string_lossy());
}
}
#[test]
fn broken_launch_records_refuse_the_adoption_before_direct_or_lifecycle_dispatch() {
let direct = World::new("corrupt-launch-direct");
let mut build = agent("build", &["approve"]);
build["deps"] = json!(["approve"]);
ready_and_undriven(&direct, "corrupt-direct", build);
std::fs::write(direct.run_file("corrupt-direct", "launch.json"), "not json")
.expect("the launch record is corrupted");
direct
.run(&["adopt", "corrupt-direct"])
.exited(crate::harness::REFUSED)
.err_has("launch.json");
let lifecycle_world = World::new("missing-launch-lifecycle");
lifecycle_world.repository("local-direct", &["true"]);
let mut service = crate::harness::lifecycle("service", &["approve"]);
service["deps"] = json!(["approve"]);
ready_and_undriven(&lifecycle_world, "missing-lifecycle", service);
std::fs::remove_file(lifecycle_world.run_file("missing-lifecycle", "launch.json"))
.expect("the launch record is removed");
lifecycle_world
.run(&["adopt", "missing-lifecycle"])
.exited(crate::harness::REFUSED)
.err_has("launch.json");
}
#[test]
fn a_legacy_launch_without_a_node_graph_fails_instead_of_reading_live_environment() {
let world = World::new("legacy-empty-node-graph");
let mut build = agent("build", &["approve"]);
build["deps"] = json!(["approve"]);
ready_and_undriven(&world, "legacy-empty", build);
let path = world.run_file("legacy-empty", "launch.json");
let mut launch: Value =
serde_json::from_str(&std::fs::read_to_string(&path).expect("the launch record reads"))
.expect("the launch record parses");
launch["node_graph"] = json!("");
std::fs::write(&path, serde_json::to_vec_pretty(&launch).unwrap())
.expect("the legacy launch record is written");
let mut driving = world.cmd(&["adopt", "legacy-empty"]);
driving.env(
"ONEPIPELINE_NODE_GRAPH",
world.graphs().join("node-scope.yaml"),
);
world
.run_on(driving, "adopt legacy-empty")
.exited(crate::harness::REFUSED)
.err_has("has no resolved node graph");
}
#[test]
fn launch_overrides_reach_the_graphs_that_actually_run() {
let world = World::new("real-overrides");
world.write_graphs();
std::fs::write(
world.graphs().join("dag-override.toml"),
"run_mode = \"fallback\"\nharnesses = [\"claude-code\"]\n# DAG_OVERRIDE\n",
)
.expect("the dag override config is written");
std::fs::write(
world.graphs().join("node-override.toml"),
"run_mode = \"fallback\"\nharnesses = [\"claude-code\"]\n# NODE_OVERRIDE\n",
)
.expect("the node override config is written");
let path = world.plan(
"overrides",
&plan_of("overrides", vec![agent("build", &[])]),
);
let started = world.run_on_agentgraph(&[
"start",
&path.to_string_lossy(),
"--attach",
"--dag-graph",
&world.dag_graph(),
"--set",
"members.monitor.oneharness_config=./dag-override.toml",
"--node-set",
"members.worker.oneharness_config=./node-override.toml",
]);
started.exited(0).settled();
let turns = world.turns();
for (member, marker, job) in [
("monitor", "DAG_OVERRIDE", "Observe this run"),
("worker", "NODE_OVERRIDE", "Do build."),
] {
let config = config_of(&world, "overrides", member);
assert!(
config.contains(marker),
"the {member} member did not receive its override: {config}"
);
assert!(
turns.iter().any(|turn| turn.prompt.contains(job)),
"the {member} member never ran its turn: {turns:?}"
);
}
}
#[test]
fn a_plan_persona_reaches_the_member_that_actually_runs() {
let world = World::new("real-plan-persona");
world.write_graphs();
std::fs::write(
world.graphs().join("requested-reviewer.yaml"),
"agent:\n name: requested-reviewer\n instructions: Review the change.\nuser:\n persona: Demand evidence.\n",
)
.expect("the requested persona is written");
let mut node = agent("review", &[]);
node["persona"] = Value::from("./requested-reviewer.yaml");
let path = world.plan("plan-persona", &plan_of("plan-persona", vec![node]));
let started = world.run_on_agentgraph(&["start", &path.to_string_lossy(), "--attach"]);
started.exited(0).settled();
let turns = world.turns();
assert!(
turns.iter().any(|turn| turn.prompt.contains("Do review.")),
"the node's member never ran: {turns:?}"
);
let records: Vec<Value> = std::fs::read_dir(world.root.join("graph-state"))
.expect("oneagentgraph wrote its state root")
.filter_map(Result::ok)
.filter_map(|entry| std::fs::read_to_string(entry.path().join("record.json")).ok())
.filter_map(|text| serde_json::from_str(&text).ok())
.collect();
assert!(
records
.iter()
.any(|record| record["refs"].as_array().is_some_and(|refs| refs
.iter()
.any(|reference| { reference["origin"] == "./requested-reviewer.yaml" }))),
"the graph that dispatched the member did not resolve the plan's persona: {records:?}"
);
}
#[test]
fn adoption_retains_node_overrides_for_later_dispatches() {
let world = World::new("real-adopted-node-override");
world.write_graphs();
world.script("harness.fail", "");
std::fs::write(
world.graphs().join("adopted-node.toml"),
"run_mode = \"fallback\"\nharnesses = [\"claude-code\"]\n# ADOPTED_NODE_OVERRIDE\n",
)
.expect("the adopted node config is written");
let path = world.plan(
"adopted-override",
&plan_of("adopted-override", vec![agent("build", &[])]),
);
let mut start = world.agentgraph_cmd(&[
"start",
&path.to_string_lossy(),
"--attach",
"--node-set",
"members.worker.oneharness_config=./adopted-node.toml",
]);
start
.current_dir(&world.root)
.env("ONEPIPELINE_NODE_GRAPH", "graphs/node-scope.yaml");
world.run_on(start, "start adopted-override");
world.until("the run to settle on the failure", |world| {
world.run_file("adopted-override", "result.json").is_file()
});
std::fs::remove_file(world.fakes.join("harness.fail")).expect("the failure is cleared");
world
.run_with_stdin(
&["reply", "adopted-override"],
&json!({
"version": 1,
"commands": [{
"op": "retry",
"id": "build",
"node": {"id": "build-2", "persona": "engineer",
"task": "## What\nDo build.\n\n## Why\nIt failed.\n\n\
## Acceptance criteria\n- build is done."},
}],
})
.to_string(),
)
.exited(0);
let mut adopt = world.agentgraph_cmd(&["adopt", "adopted-override"]);
adopt
.current_dir(&world.project)
.env("ONEPIPELINE_NODE_GRAPH", "missing-node.yaml");
let adopted = world.run_on(adopt, "adopt adopted-override");
adopted.exited(0).settled();
let configs = configs_of(&world, "adopted-override", "worker");
let retried = configs
.iter()
.find(|(node, _)| node == "build-2")
.unwrap_or_else(|| panic!("the replacement node was never dispatched: {configs:?}"));
assert!(
retried.1.contains("ADOPTED_NODE_OVERRIDE"),
"the node dispatched after adoption did not run under its retained override: {}",
retried.1
);
let turns = world.turns();
assert!(
turns.iter().any(|turn| turn.prompt.contains("It failed.")),
"the replacement node's turn never ran: {turns:?}"
);
}
#[test]
fn a_plan_dispatches_through_the_real_oneagentgraph_and_its_members_run() {
let world = World::new("real-dispatch");
world.write_graphs();
let path = world.plan("real", &plan_of("real", vec![agent("build", &[])]));
let started = world.run_on_agentgraph(&[
"start",
&path.to_string_lossy(),
"--attach",
"--dag-graph",
&world.dag_graph(),
]);
started.exited(0).settled();
let run = started.json()["run_id"]
.as_str()
.expect("the launch named its run")
.to_string();
for (member, job) in [("monitor", "Observe this run"), ("worker", "Do build.")] {
let prompt = world.turn_of(member);
assert!(
prompt.contains(job),
"the {member} member was not given its own job: {prompt}"
);
assert!(
!config_of(&world, &run, member).is_empty(),
"the {member} member was started with an empty configuration"
);
}
assert!(
world
.journal(&run)
.iter()
.any(|event| event["source"] == "agentgraph"
&& event["kind"] == "graph-started"
&& event["labels"]["node"].is_null()),
"the driver's own start never reached the merged store: {}",
world.dump()
);
assert!(
world
.journal(&run)
.iter()
.any(|event| event["kind"] == "turn-activity"),
"no member reported a turn: {}",
world.dump()
);
assert_eq!(
world.run_json(&run, "result.json")["state"],
"complete",
"the run did not settle: {}",
world.dump()
);
let relayed: Vec<serde_json::Value> = world
.journal(&run)
.into_iter()
.filter(|event| event["source"] == "agentgraph" && event["labels"]["node"] == "build")
.collect();
assert!(
!relayed.is_empty(),
"no relayed envelope belongs to the node: {}",
world.dump()
);
for event in relayed {
assert_eq!(
event["labels"]["onepipeline.run_id"],
run.as_str(),
"{event}"
);
assert_ne!(
event["labels"]["run_id"],
run.as_str(),
"the graph run's own id was overwritten by this run's: {event}"
);
}
}
fn events_reported(status: &str, node: &str) -> u64 {
let line = status
.lines()
.find(|line| line.trim_start().starts_with(&format!("{node}: running")))
.unwrap_or_else(|| panic!("`status` has no in-flight line for {node}:\n{status}"));
let at = line
.find(" event(s)")
.unwrap_or_else(|| panic!("`{line}` carries no event count"));
let digits: String = line[..at]
.chars()
.rev()
.take_while(char::is_ascii_digit)
.collect();
digits
.chars()
.rev()
.collect::<String>()
.parse()
.unwrap_or_else(|e| panic!("`{line}` carries no readable count: {e}"))
}
#[cfg(unix)]
#[test]
fn two_dispatches_running_in_one_driver_are_stopped_as_one_run() {
let world = World::new("real-shared-process");
world.write_graphs();
world.script("turn.hold", "hold");
let path = world.plan(
"shared",
&plan_of(
"shared",
vec![
agent("first", &[]),
human("approve", &[]),
agent("second", &["approve"]),
],
),
);
world
.run_on_agentgraph(&["start", &path.to_string_lossy(), "--detach"])
.exited(0);
let dispatched = |world: &World| -> Vec<String> {
world
.events_of("shared", "node-dispatched")
.iter()
.filter_map(|event| event["labels"]["node"].as_str().map(str::to_string))
.collect()
};
world.until(
"the first node to be in flight beside the person",
|world| {
dispatched(world).contains(&"first".to_string())
&& !world.events_of("shared", "node-settled").is_empty()
},
);
world.run(&["attest", "shared", "approve"]).exited(0);
world.until("both nodes to be in flight", |world| {
dispatched(world).contains(&"second".to_string())
});
world
.run(&["status", "shared"])
.exited(0)
.out_has("first: running")
.out_has("second: running");
let stopped = world.run(&["stop", "shared"]);
stopped.exited(0).out_has("\"stopped\":true");
assert_eq!(
stopped.json()["teardown"],
json!("signalled"),
"a stop over two dispatches in one driver did not report reaching them:\n{}",
stopped.stdout
);
world.release("turn.go");
world.release("turn.settle");
}
#[test]
fn status_says_what_a_live_dispatch_is_doing_and_the_readout_advances() {
let world = World::new("real-activity");
world.write_graphs();
world.script("turn.hold", "hold");
let path = world.plan("watched", &plan_of("watched", vec![agent("build", &[])]));
world
.run_on_agentgraph(&["start", &path.to_string_lossy(), "--detach"])
.exited(0);
world.until("the dispatch to report a turn", |world| {
!world.events_of("watched", "turn-activity").is_empty()
});
let first = world.run(&["status", "watched"]);
first
.exited(0)
.out_has("build: running")
.out_has("now bash echo the turn ran")
.out_has("event(s)")
.out_has("ago");
let before = events_reported(&first.stdout, "build");
world.release("turn.go");
world.until("the dispatch to report a second turn", |world| {
world.events_of("watched", "turn-activity").len() > 1
});
let second = world.run(&["status", "watched"]);
second
.exited(0)
.out_has("build: running")
.out_has("now bash cargo llvm-cov --workspace");
assert!(
events_reported(&second.stdout, "build") > before,
"the readout did not advance while the node was still in flight:\n{}",
second.stdout
);
world.release("turn.settle");
world.until("the run to settle", |world| {
world.run_file("watched", "result.json").is_file()
});
}
#[test]
fn a_drafted_body_reaches_the_change_request_through_the_real_siblings() {
let world = World::new("real-pr-author");
world.write_graphs();
world.repository("change-open", &["true"]);
world.script("harness.work", "the worker wrote this");
let drafted = "## What\nRead off the branch's own diff.\n\n## Why\nSo a reviewer knows.";
world.script("harness.body", drafted);
let drafting = world.pr_author_graph();
let node = json!({
"id": "service",
"repo": "service",
"persona": "engineer",
"title": "feat: land what the member made",
"task": "## What\nship the thing",
});
let path = world.plan("authored", &plan_of("authored", vec![node]));
let launched = world.run_on_agentgraph(&[
"start",
&path.to_string_lossy(),
"--attach",
"--pr-author-graph",
&drafting,
]);
launched.settled();
let opened = world.changes_opened();
assert_eq!(opened.len(), 1, "{opened:?}\n{}", world.dump());
assert_eq!(
opened[0]["body"],
drafted,
"the drafted body did not reach the change request: {opened:?}\n{}",
world.dump()
);
let kept: Vec<serde_json::Value> = std::fs::read_dir(world.run_file("authored", "reports"))
.expect("the run kept the reports its dispatches settled with")
.filter_map(Result::ok)
.filter_map(|entry| std::fs::read_to_string(entry.path()).ok())
.filter_map(|text| serde_json::from_str(&text).ok())
.collect();
assert!(
kept.iter().any(|report| {
report["results"]
.as_array()
.is_some_and(|results| results.iter().any(|result| {
result["schema_valid"] == json!(true) && result["structured"]["body"] == drafted
}))
}),
"no report this run retained carries the validated answer the body was read from: {kept:#?}"
);
}
#[test]
fn a_validated_answer_carrying_no_body_publishes_the_change_request_without_one() {
let world = World::new("blank-pr-author");
world.write_graphs();
world.repository("change-open", &["true"]);
world.script("harness.work", "the worker wrote this");
world.script("harness.body", " \n");
let drafting = world.pr_author_graph();
let node = json!({
"id": "service",
"repo": "service",
"persona": "engineer",
"title": "feat: land it with a blank draft",
"task": "## What\nship the thing",
});
let path = world.plan("blankdraft", &plan_of("blankdraft", vec![node]));
let launched = world.run_on_agentgraph(&[
"start",
&path.to_string_lossy(),
"--attach",
"--pr-author-graph",
&drafting,
]);
launched.settled();
let opened = world.changes_opened();
assert_eq!(opened.len(), 1, "{opened:?}\n{}", world.dump());
assert_eq!(opened[0]["title"], "feat: land it with a blank draft");
assert_eq!(
opened[0]["body"], "",
"a validated answer with no body in it still put one on the change request: {opened:?}"
);
assert_eq!(
world.run_json("blankdraft", "result.json")["state"],
"complete",
"a drafting dispatch that answered blank took the publication with it:\n{}",
world.dump()
);
let kept: Vec<serde_json::Value> = std::fs::read_dir(world.run_file("blankdraft", "reports"))
.expect("the run kept the reports its dispatches settled with")
.filter_map(Result::ok)
.filter_map(|entry| std::fs::read_to_string(entry.path()).ok())
.filter_map(|text| serde_json::from_str(&text).ok())
.collect();
assert!(
kept.iter().any(|report| {
report["results"].as_array().is_some_and(|results| {
results.iter().any(|result| {
result["schema_valid"] == json!(true) && result["structured"]["body"] == ""
})
})
}),
"no report this run retained carries a validated answer with a blank body: {kept:#?}"
);
}
#[test]
fn a_drafting_graph_the_runner_refuses_still_publishes_the_change_request() {
let world = World::new("real-pr-author-refused");
world.write_graphs();
world.repository("change-open", &["true"]);
world.script("harness.work", "the worker wrote this");
let refused = world.graphs().join("unrunnable.yaml");
std::fs::write(
&refused,
format!(
"version: {}\nname: pr-author\nmembers:\n author:\n kind: nonesuch\n",
oneagentgraph::config::SCHEMA_VERSION
),
)
.expect("the unrunnable graph is written");
let node = json!({
"id": "service",
"repo": "service",
"persona": "engineer",
"title": "feat: land it with no body",
"task": "## What\nship the thing",
});
let path = world.plan("refuseddraft", &plan_of("refuseddraft", vec![node]));
let launched = world.run_on_agentgraph(&[
"start",
&path.to_string_lossy(),
"--attach",
"--pr-author-graph",
&refused.to_string_lossy(),
]);
launched.settled();
let opened = world.changes_opened();
assert_eq!(opened.len(), 1, "{opened:?}\n{}", world.dump());
assert_eq!(opened[0]["title"], "feat: land it with no body");
assert_eq!(opened[0]["body"], "", "{opened:?}");
assert_eq!(
world.run_json("refuseddraft", "result.json")["state"],
"complete",
"a drafting graph the runner refused took the publication with it:\n{}",
world.dump()
);
assert!(
launched
.stderr
.contains("the drafting dispatch could not start"),
"the refusal never reached the operator:\n{}",
launched.stderr
);
}
#[test]
fn transcript_renders_a_real_dispatched_turns_tools_and_words() {
let world = World::new("real-transcript");
world.write_graphs();
let path = world.plan("read", &plan_of("read", vec![agent("build", &[])]));
world
.run_on_agentgraph(&["start", &path.to_string_lossy(), "--attach"])
.exited(0)
.settled();
let transcript = world.run(&["transcript", "read", "build"]);
transcript.exited(0).out_has("read build");
transcript.out_has("tool_call bash echo the turn ran");
transcript.out_has("report ");
transcript.out_has("Ran what the task asked for.");
assert!(
!transcript.stdout.contains("unreadable from this host"),
"the retained report was named and not read:\n{}",
transcript.stdout
);
world
.run(&["transcript", "read", "nowhere"])
.exited(crate::harness::REFUSED)
.err_has("has recorded nothing for node 'nowhere'")
.err_has("build");
}
#[test]
fn a_transcript_names_the_harness_that_answered_and_skips_the_ones_it_stepped_past() {
let world = World::new("real-fallback-transcript");
world.write_graphs();
std::fs::write(
world.graphs().join("chain.toml"),
"run_mode = \"fallback\"\nharnesses = [\"codex\", \"claude-code\"]\n",
)
.expect("the two-candidate chain is written");
let path = world.plan("chained", &plan_of("chained", vec![agent("build", &[])]));
world
.run_on_agentgraph(&[
"start",
&path.to_string_lossy(),
"--attach",
"--node-set",
"members.worker.oneharness_config=./chain.toml",
])
.exited(0)
.settled();
let advanced = world.events_of("chained", "fallback-advanced");
assert!(
advanced
.iter()
.any(|event| event["payload"]["identity"] == "codex"),
"the chain never stepped past its first candidate: {advanced:#?}"
);
let transcript = world.run(&["transcript", "chained", "build"]);
transcript
.exited(0)
.out_has("claude-code")
.out_has("Ran what the task asked for.");
assert!(
!transcript.stdout.contains("codex"),
"a candidate the chain stepped past was rendered as a turn:\n{}",
transcript.stdout
);
}
#[test]
fn a_launch_the_graph_refuses_fails_with_the_graphs_own_words() {
let world = World::new("real-refusal");
let path = world.plan("refused", &plan_of("refused", vec![agent("build", &[])]));
for form in ["--detach", "--attach"] {
let started = world.run_on_agentgraph(&[
"start",
&path.to_string_lossy(),
form,
"--dag-graph",
&world.dag_graph(),
]);
started.exited(crate::harness::REFUSED);
started.err_has("oneagentgraph");
started.err_has("dag-scope.yaml");
assert!(
!started.stdout.contains("\"pid\""),
"`start {form}` still printed a pid to drive:\n{}",
started.stdout
);
}
}
#[test]
fn an_adoption_the_graph_refuses_fails_rather_than_leaving_the_run_undriven() {
let world = World::new("real-adopt-refusal");
world.write_graphs();
let path = world.plan(
"orphaned",
&plan_of("orphaned", vec![human("approve", &[])]),
);
world
.run_on_agentgraph(&[
"start",
&path.to_string_lossy(),
"--detach",
"--dag-graph",
&world.dag_graph(),
])
.exited(0);
world.until("the driver to be gone", |world| {
world
.run_on_agentgraph(&["status", "orphaned"])
.stdout
.contains("DRIVER DEAD")
});
std::fs::remove_file(world.graphs().join("dag-scope.yaml")).expect("the graph is removed");
let adopted = world.run_on_agentgraph(&["adopt", "orphaned"]);
adopted.exited(crate::harness::REFUSED);
adopted.err_has("oneagentgraph");
assert!(
world.events_of("orphaned", "driver-adopted").len() == 1,
"the adoption was recorded more than once: {:?}",
world.events_of("orphaned", "driver-adopted")
);
}
#[test]
fn the_run_state_this_crate_places_is_where_the_sibling_looks_for_it() {
let world = World::new("state-dir-drift");
world.write_graphs();
let state = world.root.join("graph-state");
let path = world.plan(
"state-drift",
&plan_of("state-drift", vec![agent("build", &[])]),
);
world
.run_on_agentgraph(&["start", &path.to_string_lossy(), "--attach"])
.exited(0)
.settled();
let listed = std::process::Command::new(crate::harness::oneagentgraph_binary())
.arg("history")
.env("ONEAGENTGRAPH_STATE_DIR", &state)
.output()
.expect("the real oneagentgraph runs");
let listed = String::from_utf8_lossy(&listed.stdout);
assert!(
listed.lines().any(|line| line.contains("node-scope")),
"the sibling found no run where this crate placed one — the state-directory variable, \
or the fallback around it, has drifted on one side:\n{listed}\n{}",
world.dump()
);
}
#[test]
fn the_siblings_own_refusals_still_exit_with_the_codes_this_crate_maps_onto() {
let world = World::new("exit-code-drift");
let missing = world.root.join("no-such-graph.yaml");
let refused = std::process::Command::new(crate::harness::oneagentgraph_binary())
.args(["run", &missing.to_string_lossy(), "--task", "anything"])
.env("ONEAGENTGRAPH_STATE_DIR", world.root.join("graph-state"))
.output()
.expect("the real oneagentgraph runs");
assert_eq!(
refused.status.code(),
Some(oneagentgraph::error::EXIT_INVALID_CONFIG),
"an unreadable graph is no longer the invalid-config exit this crate maps \
`Error::InvalidConfig` onto: {}",
String::from_utf8_lossy(&refused.stderr)
);
}
#[test]
fn the_model_turn_double_refuses_an_argument_the_real_claude_does_not_take() {
let world = World::new("claude-argv");
let sent = |extra: &[&str]| {
let mut args = vec![
"-p",
"Do build.",
"--permission-mode",
"acceptEdits",
"--output-format",
"json",
];
args.extend_from_slice(extra);
std::process::Command::new(crate::harness::double("fake-claude"))
.args(&args)
.env(onepipeline_testfakes::SCRIPT_DIR_ENV, &world.fakes)
.output()
.expect("the double runs")
};
let refused = sent(&["--dangerously-skip-permissions"]);
let said = String::from_utf8_lossy(&refused.stderr).to_string();
assert_eq!(
refused.status.code(),
Some(i32::from(onepipeline_testfakes::USAGE)),
"an argv the real claude exits on ran a turn instead: {said}"
);
assert!(
said.contains("--dangerously-skip-permissions"),
"the refusal does not name what it refused: {said}"
);
let truncated = sent(&["--input-format"]);
let said = String::from_utf8_lossy(&truncated.stderr).to_string();
assert_eq!(
truncated.status.code(),
Some(i32::from(onepipeline_testfakes::USAGE)),
"an option sent with no value after it ran a turn instead: {said}"
);
assert!(
said.contains("--input-format"),
"the refusal does not name the option that was left without a value: {said}"
);
let ran = sent(&[]);
assert_eq!(
ran.status.code(),
Some(0),
"the argv `oneharness` really sends was refused: {}",
String::from_utf8_lossy(&ran.stderr)
);
}
#[test]
fn the_sibling_still_takes_its_harness_from_the_variable_this_crate_restates() {
let world = World::new("harness-bin-drift");
world.write_graphs();
write_supervised_node_graph(&world);
write_persona(&world, "engineer");
let mut node = agent("build", &[]);
node["persona"] = Value::from("./engineer.yaml");
let path = world.plan("harness-bin", &plan_of("harness-bin", vec![node]));
let named = "oneharness-that-is-not-installed";
let mut command = world.agentgraph_cmd(&["start", &path.to_string_lossy(), "--attach"]);
command.env("ONEAGENTGRAPH_ONEHARNESS_BIN", named);
world.run_on(command, "start --attach").settled();
let config = config_of(&world, "harness-bin", "worker");
assert!(
config.contains(named),
"the config the sibling composed does not name the harness it was told to \
drive, so the variable was not read — it has drifted:\n{config}"
);
}
#[test]
fn a_note_delivered_through_the_real_sibling_records_what_its_lever_answered() {
let world = World::new("real-context");
world.write_graphs();
world.script("turn.hold", "hold");
let path = world.plan("noted", &plan_of("noted", vec![agent("build", &[])]));
world
.run_on_agentgraph(&["start", &path.to_string_lossy(), "--detach"])
.exited(0);
world.until("the dispatch to report a turn", |world| {
!world.events_of("noted", "turn-activity").is_empty()
});
let note = "the fixture moved to tests/data; stop editing src/old.rs";
let submitted = world.run_with_stdin(
&["reply", "noted"],
&json!({
"version": 1,
"commands": [{"op": "context", "id": "build", "note": note}],
})
.to_string(),
);
submitted.exited(0);
world.until("the note to be reconciled", |world| {
!world.events_of("noted", "edit-committed").is_empty()
});
let committed = world.events_of("noted", "edit-committed");
assert_eq!(
committed[0]["payload"]["operations"][0]["delivery"],
json!("deferred"),
"a note the sibling could not land live was not deferred onto the next dispatch: {:?}",
committed
);
let interrupted = world.events_of("noted", "turn-interrupted");
assert_eq!(
interrupted.len(),
1,
"the lever was pulled and the run does not say so: {}",
world.dump()
);
assert_eq!(interrupted[0]["payload"]["delivered"], json!(false));
assert_eq!(interrupted[0]["payload"]["member"], json!("worker"));
assert_eq!(
interrupted[0]["payload"]["input_bytes"],
json!(note.len()),
"the envelope does not say how much redirection was offered"
);
assert!(
interrupted[0]["payload"]["reason"].is_string(),
"an interrupt that did not land carries no reason: {}",
interrupted[0]
);
assert_eq!(
interrupted[0]["labels"]["node"],
json!("build"),
"the envelope is not stamped with the node it is about — its producer cannot know it, \
so this crate has to"
);
world.release("turn.go");
world.release("turn.settle");
}
#[test]
fn a_cancel_against_a_real_dispatch_asks_its_lever_and_reaps_it_at_the_deadline() {
let world = World::new("real-cancel");
world.write_graphs();
world.script("turn.hold", "hold");
let path = world.plan("stopped", &plan_of("stopped", vec![agent("build", &[])]));
let mut launch = world.agentgraph_cmd(&["start", &path.to_string_lossy(), "--detach"]);
launch.env(crate::harness::CANCEL_GRACE_ENV, "1");
world.run_on(launch, "start --detach").exited(0);
world.until("the dispatch to report a turn", |world| {
!world.events_of("stopped", "turn-activity").is_empty()
});
world
.run_with_stdin(
&["reply", "stopped"],
&json!({"version": 1, "commands": [{"op": "cancel", "id": "build"}]}).to_string(),
)
.exited(0);
world.until("the interrupt to be recorded", |world| {
!world.events_of("stopped", "turn-interrupted").is_empty()
});
let interrupted = world.events_of("stopped", "turn-interrupted");
assert_eq!(interrupted[0]["payload"]["delivered"], json!(false));
assert_eq!(
interrupted[0]["labels"]["node"], "build",
"the envelope is not stamped with the node it is about: {}",
interrupted[0]
);
assert!(
interrupted[0]["payload"]["input_bytes"]
.as_u64()
.is_some_and(|bytes| bytes > 0),
"the cancellation offered the turn no redirection at all: {}",
interrupted[0]
);
world.until("the deadline to expire", |world| {
world
.events_of("stopped", "planner-surface-queued")
.iter()
.any(|event| event["payload"]["kind"] == "dispatch-killed")
});
world.until("the cancelled node to settle", |world| {
world
.events_of("stopped", "node-settled")
.iter()
.any(|event| event["labels"]["node"] == "build")
});
let settled = world
.events_of("stopped", "node-settled")
.into_iter()
.find(|event| event["labels"]["node"] == "build")
.expect("the settlement was just seen");
assert_eq!(settled["payload"]["status"], "cancelled", "{settled}");
world.release("turn.go");
world.release("turn.settle");
}
#[test]
fn consuming_a_surface_restarts_the_real_pacemakers_clock() {
let world = World::new("real-pacemaker");
world.write_graphs_with_pacemaker();
let path = world.plan("paced", &plan_of("paced", vec![human("approve", &[])]));
world
.run_on_agentgraph(&[
"start",
&path.to_string_lossy(),
"--detach",
"--dag-graph",
&world.dag_graph(),
])
.exited(0);
let graph_run = world.run_json("paced", "launch.json")["graph_run"]
.as_str()
.expect("the launch record names the graph run driving this run")
.to_string();
assert_ne!(graph_run, "paced");
world
.run_on_agentgraph(&[
"surface",
"paced",
"--kind",
"check-in",
"--message",
"steady",
])
.exited(0);
let read = world.run_on(world.agentgraph_cmd(&["next", "paced"]), "next paced");
read.exited(0).out_has("\"surface\"");
assert!(
!read
.stderr
.contains("could not reset the check-in pacemaker"),
"the real sibling refused the reset: {}",
read.stderr
);
let signalled = world
.graph_state()
.join(&graph_run)
.join("signals")
.join("check-in.reset");
assert!(
signalled.is_file(),
"the reset did not reach the run's own signal directory: {}",
signalled.display()
);
}
#[test]
fn a_view_renders_with_the_health_block_read_through_the_library() {
let world = World::new("real-health");
world.write_graphs();
let path = world.plan("probed", &plan_of("probed", vec![agent("build", &[])]));
world
.run_on_agentgraph(&["start", &path.to_string_lossy(), "--attach"])
.exited(0)
.settled();
let status = world.run_on(world.agentgraph_cmd(&["status", "probed"]), "status probed");
status.exited(0).out_has("probed").out_has("SETTLED");
assert!(
!status.stdout.contains("fake-provider"),
"the view carried the override's health block on the default path:\n{}",
status.stdout
);
}
#[test]
fn a_launchs_own_environment_reaches_the_member_the_library_backend_runs() {
let world = World::new("real-launch-env");
world.write_graphs();
let path = world.plan("carried", &plan_of("carried", vec![agent("build", &[])]));
world
.run_on_agentgraph(&[
"start",
&path.to_string_lossy(),
"--attach",
"--dag-graph",
&world.dag_graph(),
])
.exited(0)
.settled();
let saw = world.observer_saw();
assert_eq!(
saw.first().map(|saw| saw["run"].clone()),
Some(json!("carried")),
"the observer was not told which run it was started for: {saw:?}\n{}",
world.dump()
);
assert_eq!(
saw[0]["launch_record"],
json!(true),
"the observer was not told where the run's ledger lives: {saw:?}"
);
}
#[test]
fn a_document_the_runner_accepts_launches_whichever_way_it_is_asked_for() {
for form in ["--attach", "--detach"] {
let world = World::new(&format!("runner-schema-{}", form.trim_start_matches("--")));
world.write_graphs_at_the_runners_schema();
let path = world.plan("schema", &plan_of("schema", vec![agent("build", &[])]));
let mut command = world.agentgraph_cmd(&[
"start",
&path.to_string_lossy(),
form,
"--dag-graph",
&world.dag_graph(),
]);
command.env("PATH", world.empty_path());
let started = world.run_on(command, &format!("start {form}"));
assert!(
!started.stderr.contains("schema_version"),
"the launch refused the document the runner accepts:\n{}",
started.stderr
);
world.until("the graph the launch named to run", |world| {
!world.observer_saw().is_empty()
});
world.until("the run to settle", |world| {
world.run_file("schema", "result.json").is_file()
});
assert!(
!world.events_of("schema", "node-dispatched").is_empty(),
"the loop never dispatched the node:\n{}",
world.dump()
);
let settled = world.events_of("schema", "node-settled");
#[cfg(unix)]
assert_eq!(
settled[0]["payload"]["outcome"],
json!("infrastructure-failure"),
"a dispatch nothing could stamp settled as something else: {}",
settled[0]
);
#[cfg(windows)]
assert_eq!(
settled[0]["payload"]["status"],
json!("done"),
"a dispatch this host could stamp did not run: {}",
settled[0]
);
let results = world.run(&["results", "schema"]);
results.exited(0).out_has("build");
}
}
#[test]
fn every_dag_scope_member_is_given_the_runs_description_and_its_own_job() {
let world = World::new("neutral-run-task");
world.write_graphs_at_the_runners_schema();
let path = world.plan("neutral", &plan_of("neutral", vec![agent("build", &[])]));
world
.run_on(
world.agentgraph_cmd(&[
"start",
&path.to_string_lossy(),
"--attach",
"--dag-graph",
&world.dag_graph(),
]),
"start neutral",
)
.exited(0)
.settled();
let monitor = world.turn_of("monitor");
let reporter = world.turn_of(REPORTING_MEMBER);
for (member, prompt) in [("monitor", &monitor), (REPORTING_MEMBER, &reporter)] {
for expected in ["neutral", "Deliver neutral"] {
assert!(
prompt.contains(expected),
"member '{member}' was not told {expected:?}: {prompt}"
);
}
}
assert!(
monitor.contains("Observe this run"),
"the monitor was not given its own job: {monitor}"
);
assert!(
!reporter.contains("Observe this run"),
"a member whose job is not the monitor's was given it: {reporter}"
);
assert!(
reporter.contains("Report on this run"),
"the reporter was not given its own job: {reporter}"
);
}
#[test]
fn the_retained_driver_relays_its_graphs_stream_and_exits_with_its_code() {
let world = World::new("drive-relay");
world.write_graphs();
let graph = world.graphs().join("node-scope.yaml");
let dir = world.root.join("driven");
std::fs::create_dir_all(&dir).expect("a directory for the driven graph");
let driven = world.run_on(
world.agentgraph_cmd(&[
"drive",
&graph.to_string_lossy(),
"--task",
"Do the work and settle.",
"--dir",
&dir.to_string_lossy(),
]),
"drive node-scope",
);
driven.exited(0);
let relayed: Vec<Value> = driven
.stdout
.lines()
.filter(|line| !line.trim().is_empty())
.map(|line| {
serde_json::from_str(line).unwrap_or_else(|error| {
panic!("`drive` wrote a line that is not an envelope: {error}\n{line}")
})
})
.collect();
assert!(
relayed
.iter()
.any(|event| event["kind"] == "member-started"),
"the relay carried no member-started:\n{}",
driven.stdout
);
assert!(
relayed
.iter()
.all(|event| event["source"] == "agentgraph" && event["v"] == 1),
"the relay rewrote the envelopes it was given:\n{}",
driven.stdout
);
}
#[cfg(target_os = "linux")]
#[test]
fn a_retained_driver_that_cannot_write_its_relay_refuses() {
let world = World::new("drive-nospace");
world.write_graphs();
let graph = world.graphs().join("node-scope.yaml");
let dir = world.root.join("driven");
std::fs::create_dir_all(&dir).expect("a directory for the driven graph");
let mut command = world.agentgraph_cmd(&[
"drive",
&graph.to_string_lossy(),
"--task",
"Do the work and settle.",
"--dir",
&dir.to_string_lossy(),
]);
command.stdout(
std::fs::OpenOptions::new()
.write(true)
.open("/dev/full")
.expect("/dev/full"),
);
let refused = world.run_on(command, "drive onto a full disk");
assert_ne!(
refused.code, 0,
"a driver that could not relay its own stream reported success:\n{}",
refused.stderr
);
refused.err_has("relaying graph event");
}
#[test]
fn a_retained_driver_carries_a_failing_graphs_own_exit_code() {
let world = World::new("drive-failed");
world.write_graphs();
let graph = world.graphs().join("node-scope.yaml");
let dir = world.root.join("driven-failed");
std::fs::create_dir_all(&dir).expect("a directory for the driven graph");
world.script("harness.fail", "the turn did not get there");
let failed = world.run_on(
world.agentgraph_cmd(&[
"drive",
&graph.to_string_lossy(),
"--task",
"Do the work and settle.",
"--dir",
&dir.to_string_lossy(),
]),
"drive a graph whose member fails",
);
assert_eq!(
failed.code,
oneagentgraph::error::EXIT_MEMBER_FAILED,
"a driver did not carry its graph's own exit code:\nstdout: {}\nstderr: {}",
failed.stdout,
failed.stderr
);
assert!(
failed
.stdout
.lines()
.filter_map(|line| serde_json::from_str::<Value>(line).ok())
.any(|event| event["kind"] == "member-started"),
"the graph never started a member, so its code is not a settlement:\n{}",
failed.stdout
);
}
fn turns_dispatched(world: &World, run: &str, node: &str, step: Option<&str>) -> u64 {
let events = world.journal(run);
let started = events
.iter()
.filter(|event| event["kind"] == "member-started")
.find(|event| {
event["labels"]["onepipeline.node"] == node
&& step.is_none_or(|step| event["labels"]["onepipeline.step"] == step)
})
.unwrap_or_else(|| panic!("no member started for {node}/{step:?}: {events:?}"));
let config = started["payload"]["config"]
.as_str()
.expect("the sibling publishes the config it launched the member with");
let text = std::fs::read_to_string(config).expect("that configuration is on disk");
let effective: Value = serde_norway::from_str(&text).expect("it parses");
effective["user"]["max_turns"]
.as_u64()
.unwrap_or_else(|| panic!("{config} states no turn ceiling: {text}"))
}
fn write_supervised_node_graph(world: &World) {
std::fs::write(
world.graphs().join("onejudge.base.yaml"),
"agent:\n instructions: Do the work.\nuser:\n persona: Review it.\n \
done_when: the original task is complete\n max_turns: 12\n",
)
.expect("the onejudge base config is written");
std::fs::write(
world.graphs().join("node-scope.yaml"),
"version: 1\nname: node-scope\nmembers:\n worker:\n kind: onejudge\n \
base_config: ./onejudge.base.yaml\n agent:\n \
oneharness_config: ./oneharness.toml\n judge:\n \
oneharness_config: ./oneharness.toml\n mode: bypass\n",
)
.expect("the node-scope graph is written");
}
fn write_persona(world: &World, name: &str) {
std::fs::write(
world.graphs().join(format!("{name}.yaml")),
format!("agent:\n name: {name}\n instructions: Ship it.\nuser:\n persona: Review it.\n"),
)
.expect("the persona is written");
}
#[test]
fn a_nodes_turn_budget_reaches_its_dispatch_and_outranks_the_run_wide_one() {
let world = World::new("real-turn-budget");
world.write_graphs();
write_supervised_node_graph(&world);
for persona in ["budgeted", "plain"] {
write_persona(&world, persona);
}
let dispatched = |run: &str, node: Value| {
let path = world.plan(run, &plan_of(run, vec![node]));
world
.run_on_agentgraph(&[
"start",
&path.to_string_lossy(),
"--attach",
"--node-set",
"members.worker.max_turns=9",
])
.settled();
turns_dispatched(&world, run, run, None)
};
let mut budgeted = agent("budgeted", &[]);
budgeted["persona"] = Value::from("./budgeted.yaml");
budgeted["max_turns"] = json!(45);
let mut plain = agent("plain", &[]);
plain["persona"] = Value::from("./plain.yaml");
assert_eq!(
dispatched("budgeted", budgeted),
45,
"the node's own turn budget did not reach the member that runs its work"
);
assert_eq!(
dispatched("plain", plain),
9,
"the operator's run-wide override did not reach a node that declared none"
);
}
fn two_party_worktree(world: &World, run: &str) -> String {
let started: Vec<Value> = world
.journal(run)
.into_iter()
.filter(|event| event["kind"] == "member-started")
.collect();
started
.iter()
.find(|event| event["payload"]["engine"] == "onejudge")
.and_then(|event| event["payload"]["worktree"].as_str().map(str::to_string))
.unwrap_or_else(|| panic!("no two-party member was started in {run}: {started:#?}"))
}
#[test]
fn a_two_party_member_is_started_in_the_directory_the_graph_was_given() {
let world = World::new("real-two-party-cwd");
world.write_graphs();
write_supervised_node_graph(&world);
write_persona(&world, "engineer");
world.repository("local-direct", &["true"]);
let node = json!({
"id": "service",
"repo": "service",
"persona": "./engineer.yaml",
"task": "## What\nship the thing",
"title": "feat: land what the member made",
});
let path = world.plan("twoparty", &plan_of("twoparty", vec![node]));
world
.run_on_agentgraph(&["start", &path.to_string_lossy(), "--attach"])
.settled();
let session_worktree = world
.journal("twoparty")
.into_iter()
.filter(|event| event["source"] == "vcs" && event["kind"] == "session-opened")
.find_map(|event| event["payload"]["worktree"].as_str().map(str::to_string))
.expect("the lifecycle node's session opened a worktree");
assert_eq!(
two_party_worktree(&world, "twoparty"),
session_worktree,
"the two-party member was started somewhere other than the directory the graph was \
given. A member started in its own scratch has no repository to work in, and the work \
it leaves there is discarded at publication as `no-changes`."
);
}
#[test]
fn a_steps_turn_budget_reaches_that_steps_own_dispatch() {
let world = World::new("real-step-budget");
world.write_graphs();
write_supervised_node_graph(&world);
write_persona(&world, "implementer");
world.repository("local-direct", &["true"]);
let node = json!({
"id": "service",
"repo": "service",
"title": "feat: land what the step made",
"steps": [
{"id": "implement", "persona": "./implementer.yaml", "task": "## What\nimplement",
"max_turns": 45},
],
});
let path = world.plan("stepbudget", &plan_of("stepbudget", vec![node]));
world
.run_on_agentgraph(&["start", &path.to_string_lossy(), "--attach"])
.settled();
assert_eq!(
turns_dispatched(&world, "stepbudget", "service", Some("implement")),
45,
"the step's own turn budget did not reach the dispatch that ran it; the graph's \
own default is 12"
);
}
#[test]
fn a_launchs_agentgraph_filter_reaches_the_real_sibling_and_narrows_what_it_relays() {
let world = World::new("real-agentgraph-filter");
world.write_graphs();
let relayed = |run: &str| -> String { world.run(&["monitor", run, "--all"]).stdout };
let path = world.plan(
"unfiltered",
&plan_of("unfiltered", vec![agent("build", &[])]),
);
world
.run_on_agentgraph(&["start", &path.to_string_lossy(), "--attach"])
.settled();
let ingested = relayed("unfiltered");
for kind in ["turn-activity", "member-settled"] {
assert!(
ingested.contains(kind),
"a launch naming no filters did not ingest {kind}:\n{ingested}"
);
}
let path = world.plan("filtered", &plan_of("filtered", vec![agent("build", &[])]));
world
.run_on_agentgraph(&[
"start",
&path.to_string_lossy(),
"--attach",
"--filter-agentgraph",
r#"{"exclude": [{"kind": "turn-*"}]}"#,
])
.settled();
let kinds = relayed("filtered");
assert!(
!kinds.contains("turn-"),
"the source filter did not reach `oneagentgraph`:\n{kinds}"
);
assert!(
kinds.contains("member-settled"),
"the source filter dropped the settlement, which it admits:\n{kinds}"
);
world
.run(&["results", "filtered"])
.exited(0)
.out_has("build")
.out_has("done");
}
#[test]
fn the_observer_graphs_own_stream_is_filtered_too_and_the_spec_may_be_a_file() {
let world = World::new("real-observer-filter");
world.write_graphs();
let spec = world.root.join("relay.json");
std::fs::write(&spec, r#"{"exclude": [{"kind": "turn-*"}]}"#).expect("the spec is written");
let observed = |run: &str| -> Vec<String> {
world
.journal(run)
.iter()
.filter(|event| event["source"] == "agentgraph" && event["labels"]["node"].is_null())
.filter_map(|event| event["kind"].as_str().map(str::to_string))
.collect()
};
let path = world.plan("watched", &plan_of("watched", vec![agent("build", &[])]));
world
.run_on_agentgraph(&[
"start",
&path.to_string_lossy(),
"--attach",
"--dag-graph",
&world.dag_graph(),
])
.settled();
let ingested = observed("watched");
assert!(
ingested.iter().any(|kind| kind.starts_with("turn-")),
"the observer graph relayed no turn of its own, so this journey could not \
tell a filtered observer from a quiet one: {ingested:?}\n{}",
world.dump()
);
let path = world.plan("quiet", &plan_of("quiet", vec![agent("build", &[])]));
world
.run_on_agentgraph(&[
"start",
&path.to_string_lossy(),
"--attach",
"--dag-graph",
&world.dag_graph(),
"--filter-agentgraph",
&spec.to_string_lossy(),
])
.settled();
let kinds = observed("quiet");
assert!(
!kinds.iter().any(|kind| kind.starts_with("turn-")),
"the source filter did not reach the observer graph's own launch: {kinds:?}"
);
assert!(
!kinds.is_empty(),
"the observer graph relayed nothing at all, so nothing here is about the filter"
);
}
#[test]
fn an_adoption_relaunches_the_observer_under_the_launchs_own_filter() {
let world = World::new("real-adopt-filter");
world.write_graphs();
let observed = |run: &str| -> Vec<String> {
world
.journal(run)
.iter()
.filter(|event| event["source"] == "agentgraph" && event["labels"]["node"].is_null())
.filter_map(|event| event["kind"].as_str().map(str::to_string))
.collect()
};
let path = world.plan(
"readopted",
&plan_of(
"readopted",
vec![human("approve", &[]), agent("build", &["approve"])],
),
);
world
.run_on_agentgraph(&[
"start",
&path.to_string_lossy(),
"--attach",
"--dag-graph",
&world.dag_graph(),
"--filter-agentgraph",
r#"{"exclude": [{"kind": "turn-*"}]}"#,
])
.exited(0);
world.run(&["attest", "readopted", "approve"]).exited(0);
let before = observed("readopted").len();
world
.run_on_agentgraph(&["adopt", "readopted"])
.exited(0)
.settled();
let kinds = observed("readopted");
assert!(
kinds.len() > before,
"the adoption relaunched no observer, so nothing here is about its filter: {kinds:?}"
);
assert!(
!kinds.iter().any(|kind| kind.starts_with("turn-")),
"the adoption relaunched the observer without the launch's own filter: {kinds:?}"
);
}
#[test]
fn the_retained_driver_reads_its_own_event_filter_and_refuses_an_unusable_one() {
let world = World::new("drive-filter");
world.write_graphs();
let graph = world.graphs().join("node-scope.yaml");
let dir = world.root.join("driven");
std::fs::create_dir_all(&dir).expect("a directory for the driven graph");
let drive = |spec: &str| {
world.run_on(
world.agentgraph_cmd(&[
"drive",
&graph.to_string_lossy(),
"--task",
"Do the work and settle.",
"--dir",
&dir.to_string_lossy(),
"--event-filter",
spec,
]),
"drive with an event filter",
)
};
let refused = drive(r#"{"include": [{"role": "agent"}]}"#);
assert_eq!(refused.code, REFUSED, "{}", refused.stderr);
assert!(
refused.stderr.contains("role"),
"the refusal does not name the offending field:\n{}",
refused.stderr
);
let driven = drive(r#"{"exclude": [{"kind": "turn-*"}]}"#);
assert_eq!(driven.code, 0, "{}", driven.stderr);
assert!(
!driven.stdout.contains("turn-activity"),
"the retained driver relayed what its filter excluded:\n{}",
driven.stdout
);
assert!(
driven.stdout.contains("member-settled"),
"the retained driver relayed nothing at all, so nothing here is about the \
filter:\n{}",
driven.stdout
);
}