agent-joule/crates/joule_coded/tests/fanout.rs
David Charlot 07cd0e287c joule_coded: serve on-device inference as an OpenAI-compatible metered endpoint
Expose the worker's sealed local generation (the L3 cascade tier) on the wire the
inference ecosystem speaks. New joule_coded::responses answers /v1/chat/completions
and /v1/responses via the Mesh standard's std-only server (mesh-openai/core/engine/
meter/router/schema, reused unchanged): mesh-core routes each request cheapest-
sufficient, meters the decode, and returns the sealed mesh/1 receipt in
usage.energy (microjoules, uj-per-accepted-token, honest provenance). The on-device
model is injected as `generate` — scripted in tests, JouleClaw's GGUF runtime in
production, the same injection run_loop uses.

- main.rs: start the server on its own thread when JOULE_CODE_RESPONSES_ADDR is set
  and a model is loaded (feature l3), so it never blocks the control protocol.
- lib.rs: pub mod responses.
- Cargo.toml: mesh-* deps (open-standards rev 0a8fb779, cached locally).
- tests/acp.rs, tests/fanout.rs: updated alongside.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-02 11:01:27 -04:00

212 lines
8.4 KiB
Rust

//! The fan-out driver, end to end over real git worktrees: decompose a plan into
//! dependency-ordered parallel waves, drive the real L3 loop in an isolated
//! worktree session per task (with a scripted on-device model standing in for the
//! GGUF runtime), then commit, verify, and merge each branch into the base before
//! the next wave runs. Proves the second wave's two independent tasks both land in
//! the base on top of the first wave's merged work — parallel subagents, grounded
//! in the spec's task graph, with intact signed ledgers throughout.
use ed25519_dalek::SigningKey;
use joule_code_schema::ContentId;
use joule_coded::{run_plan_fanout, seed_from};
use joule_core::fabric::{DeviceClass, FabricInventory, IsolationTier};
use joule_core::methodology::{Plan, PlanTask};
use joule_core::sessions::{SessionGrant, SessionManager};
use std::collections::BTreeMap;
use std::process::Command;
fn task(id: &str, clauses: &[&str], deps: &[&str]) -> PlanTask {
PlanTask {
id: id.into(),
title: format!("rewrite for {id}"),
clauses: clauses.iter().map(|c| c.to_string()).collect(),
deps: deps.iter().map(|d| d.to_string()).collect(),
}
}
/// A throwaway git repo with three committed files the agents will rewrite.
/// `label` keeps parallel tests (same PID) from sharing a directory.
// Sync test setup: blocking on `git` is intended (no async executor in a test).
#[allow(clippy::disallowed_methods)]
fn scratch_repo(label: &str) -> (std::path::PathBuf, std::path::PathBuf) {
let base = std::env::temp_dir().join(format!("jc-fanout-{}-{label}", std::process::id()));
let _ = std::fs::remove_dir_all(&base);
let repo = base.join("repo");
let worktrees = base.join("worktrees");
std::fs::create_dir_all(&repo).unwrap();
std::fs::create_dir_all(&worktrees).unwrap();
let git = |args: &[&str]| {
assert!(
Command::new("git").args(args).current_dir(&repo).output().unwrap().status.success(),
"git {args:?}"
);
};
git(&["init", "-q"]);
git(&["config", "user.email", "t@t"]);
git(&["config", "user.name", "t"]);
for name in ["a.txt", "b.txt", "c.txt"] {
std::fs::write(repo.join(name), "0\n").unwrap();
}
git(&["add", "-A"]);
git(&["commit", "-qm", "init"]);
(repo, worktrees)
}
#[test]
fn fanout_runs_a_plan_across_waves_and_merges_each_into_the_base() {
let (repo, worktrees) = scratch_repo("merge");
let key = SigningKey::from_bytes(&seed_from(&repo.to_string_lossy()));
let author = key.verifying_key().to_bytes();
let grant = SessionGrant {
tiers: vec!["l0".into(), "l1".into(), "l2".into(), "l3".into()],
joule_ceiling_uj: 1_000_000_000,
};
let mut sessions = SessionManager::with_primary(
repo.clone(),
key,
worktrees,
grant.clone(),
"fanout-worker",
);
// A diamond's head and its two independent branches: T0 must finish before
// T1 and T2, which are independent of each other → wave 1 = [T0], wave 2 =
// [T1, T2] (run in parallel). Each task rewrites a distinct file.
let spec = ContentId::of_bytes(b"spec");
let clauses = ["C0", "C1", "C2"];
let plan = Plan::new(
spec,
vec![
task("T0", &["C0"], &[]),
task("T1", &["C1"], &["T0"]),
task("T2", &["C2"], &["T0"]),
],
&clauses,
)
.unwrap();
assert_eq!(plan.max_parallelism(), 2, "the plan fans out 2-wide at its widest");
// The scripted model per task: rewrite this task's file, then finish.
let targets: BTreeMap<&str, (&str, &str)> =
[("T0", ("a.txt", "alpha")), ("T1", ("b.txt", "bravo")), ("T2", ("c.txt", "charlie"))]
.into_iter()
.collect();
let model_for = |task: &PlanTask| -> Box<dyn FnMut(&str) -> anyhow::Result<String> + Send> {
let (path, content) = targets[task.id.as_str()];
let turns = [
format!("<<<TOOL edit_file>>>\n{path}\n{content}\n<<<END>>>"),
"<<<TOOL finish>>>\n".to_string(),
];
let mut step = 0usize;
Box::new(move |_prompt: &str| {
let turn = turns[step.min(turns.len() - 1)].clone();
step += 1;
Ok(turn)
})
};
// An Apple-Silicon-shaped host: CPU + GPU + NPU, isolates up to S2.
let inventory = FabricInventory {
devices: vec![DeviceClass::Cpu, DeviceClass::Gpu, DeviceClass::Npu],
max_tier: IsolationTier::S2,
};
let report = run_plan_fanout(
&mut sessions,
&plan,
&grant,
&inventory,
8,
author,
model_for,
|_trace| 10_000, // host meter stand-in: 10 mJ per task
|trace| trace.error.is_none(),
)
.unwrap();
// Two waves; the widest staffed two subagents; all three tasks merged.
assert_eq!(report.waves, 2, "T0 alone, then T1+T2");
assert_eq!(report.max_width, 2);
assert_eq!(report.merged(), 3, "every task merged its branch into the base");
assert_eq!(report.total_ops(), 3, "one edit sealed as an op per task");
assert_eq!(report.total_energy_uj(), 30_000);
// Fabric placement: every L3 task ran on the NPU (cheapest accelerator) at
// the S2 isolation its untrusted shell work requires, and the per-device
// energy rollup attributes all 30 mJ to that device. Its shell commands were
// confined by a real OS sandbox (seatbelt on macOS), network-denied.
for outcome in &report.outcomes {
assert!(outcome.ran);
assert_eq!(outcome.placement.device, DeviceClass::Npu);
assert_eq!(outcome.placement.isolation, IsolationTier::S2);
assert!(outcome.placement.feasible);
assert!(outcome.enforcement.is_enforced(), "S2 shell work must be OS-confined");
}
let by_device = report.energy_by_device();
assert_eq!(by_device.get(&DeviceClass::Npu).copied(), Some(30_000));
// The base checkout now holds all three rewrites — wave 2's independent work
// landed on top of wave 1's merged commit.
for (_, (file, content)) in targets {
let on_disk = std::fs::read_to_string(repo.join(file)).unwrap();
assert!(on_disk.contains(content), "{file} not rewritten in base: {on_disk:?}");
}
// Every session's signed ledger is intact end to end through merge + close.
for outcome in &report.outcomes {
let session = sessions.get(&outcome.session_id).unwrap();
assert!(session.verify_chain(), "ledger for {} must verify", outcome.task_id);
}
std::fs::remove_dir_all(&repo.parent().unwrap()).ok();
}
#[test]
fn fanout_refuses_every_task_when_the_host_cannot_isolate_untrusted_shell() {
let (repo, worktrees) = scratch_repo("refuse");
let key = SigningKey::from_bytes(&seed_from(&repo.to_string_lossy()));
let author = key.verifying_key().to_bytes();
let grant = SessionGrant {
tiers: vec!["l0".into(), "l1".into(), "l2".into(), "l3".into()],
joule_ceiling_uj: 1_000_000_000,
};
let mut sessions =
SessionManager::with_primary(repo.clone(), key, worktrees, grant.clone(), "fanout-worker");
let plan = Plan::new(
ContentId::of_bytes(b"spec"),
vec![task("T0", &["C0"], &[]), task("T1", &["C1"], &["T0"])],
&["C0", "C1"],
)
.unwrap();
// A host with no isolation (max_tier S0) cannot enforce the S2 floor an L3
// task's untrusted shell requires. The model factory must never be reached.
let bare = FabricInventory { devices: vec![DeviceClass::Cpu], max_tier: IsolationTier::S0 };
let report = run_plan_fanout(
&mut sessions,
&plan,
&grant,
&bare,
8,
author,
|_task| Box::new(|_p: &str| panic!("a refused task must not run the model")),
|_trace| 10_000,
|trace| trace.error.is_none(),
)
.unwrap();
assert_eq!(report.waves, 0, "nothing executed");
assert_eq!(report.merged(), 0);
assert_eq!(report.outcomes.len(), 2, "both tasks accounted for, as refused");
for outcome in &report.outcomes {
assert!(!outcome.ran, "task {} must be refused, not run unsandboxed", outcome.task_id);
assert!(!outcome.placement.feasible);
assert_eq!(outcome.placement.isolation, IsolationTier::S2);
}
// The base checkout is untouched — no worktree was even opened.
assert_eq!(std::fs::read_to_string(repo.join("a.txt")).unwrap(), "0\n");
std::fs::remove_dir_all(&repo.parent().unwrap()).ok();
}