use mlua_swarm::{
AgentBlockInProcessSpawnerFactory, Compiler, Engine, EngineCfg, Role, SpawnerRegistry,
TaskInputSpec, TaskLaunchInput, TaskLaunchService,
};
use serde_json::{json, Value};
use std::path::{Path, PathBuf};
use std::sync::{Arc, OnceLock};
use std::time::Duration;
static AGENT_BLOCK_STATE_ISOLATED: OnceLock<()> = OnceLock::new();
fn isolate_agent_block_state() {
AGENT_BLOCK_STATE_ISOLATED.get_or_init(|| {
std::env::set_var("AGENT_BLOCK_KV_PATH", ":memory:");
std::env::set_var("AGENT_BLOCK_SQL_PATH", ":memory:");
std::env::set_var("AGENT_BLOCK_TS_PATH", ":memory:");
});
}
fn service() -> TaskLaunchService {
isolate_agent_block_state();
let mut reg = SpawnerRegistry::new();
reg.register::<AgentBlockInProcessSpawnerFactory>(Arc::new(
AgentBlockInProcessSpawnerFactory::new(),
));
TaskLaunchService::new(Engine::new(EngineCfg::default()), Compiler::new(reg))
}
fn launch_input(bp: Value, init_ctx: Value) -> TaskLaunchInput {
isolate_agent_block_state();
TaskLaunchInput::automate(
serde_json::from_value(bp).expect("blueprint deserializes"),
"gh86-e2e-op",
Role::Operator,
Duration::from_secs(30),
init_ctx,
)
}
fn write_script(tag: &str, source: &str) -> (PathBuf, PathBuf) {
let dir = std::env::temp_dir().join(format!(
"gh86-agent-block-{tag}-{}-{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("clock")
.as_nanos()
));
std::fs::create_dir_all(&dir).expect("create script dir");
let path = dir.join("gate.lua");
std::fs::write(&path, source).expect("write gate script");
(path, dir)
}
fn script_agent_bp(id: &str, script: &Path, project_root: &Path, verdict: Value) -> Value {
let mut agent = json!({
"name": "gate-danger",
"kind": "agent_block",
"spec": {
"script_path": script.display().to_string(),
"project_root": project_root.display().to_string()
},
"runner": {"backend": "agent_block_in_process", "tools": []}
});
if !verdict.is_null() {
agent["verdict"] = verdict;
}
json!({
"schema_version": "0.1.0",
"id": id,
"flow": {
"kind": "step",
"ref": "gate-danger",
"in": {"op": "lit", "value": "audit-this-payload"},
"out": {"op": "path", "at": "$.danger_result"}
},
"agents": [agent]
})
}
#[tokio::test]
async fn script_mode_gate_dispatches_in_process_and_folds_its_result() {
let (script, dir) = write_script(
"roundtrip",
r#"
bus.emit("worker_result", { ok = true, response = "seen:" .. tostring(_PROMPT) })
"#,
);
let bp = script_agent_bp("gh86-script-roundtrip", &script, &dir, Value::Null);
let out = service()
.launch(launch_input(bp, json!({})))
.await
.expect("launch must complete");
assert_eq!(
out.final_ctx["danger_result"],
json!("seen:audit-this-payload"),
"the step's `in` must reach the script as _PROMPT and its emit must fold into ctx"
);
}
#[tokio::test]
async fn script_mode_gate_satisfies_a_body_channel_verdict_contract() {
let (script, dir) = write_script(
"verdict",
r#"
bus.emit("worker_result", { ok = true, response = "PASS" })
"#,
);
let bp = script_agent_bp(
"gh86-script-verdict",
&script,
&dir,
json!({"channel": "body", "values": ["PASS", "BLOCKED"]}),
);
let out = service()
.launch(launch_input(bp, json!({})))
.await
.expect("launch must complete");
assert_eq!(out.final_ctx["danger_result"], json!("PASS"));
}
#[tokio::test]
async fn script_mode_gate_satisfies_a_part_channel_verdict_contract() {
let (script, dir) = write_script(
"verdict-part",
r#"
bus.emit("artifact", { name = "verdict", content = "PASS" })
bus.emit("worker_result", { ok = true, response = "the full prose report" })
"#,
);
let bp = script_agent_bp(
"gh86-script-verdict-part",
&script,
&dir,
json!({"channel": "part", "values": ["PASS", "BLOCKED"]}),
);
let out = service()
.launch(launch_input(bp, json!({})))
.await
.expect("launch must complete");
assert_eq!(
out.final_ctx["danger_result"],
json!({
"out": "the full prose report",
"parts": {"verdict": "PASS"},
}),
"the body stays the report under `out`, and the staged verdict is \
addressable at `parts.verdict` — the shape a downstream \
`$.<step>.parts[\"verdict\"]` cond reads"
);
}
#[tokio::test]
async fn script_mode_multiple_staged_parts_fold_by_name() {
let (script, dir) = write_script(
"verdict-parts-many",
r#"
bus.emit("artifact", { name = "verdict", content = "BLOCKED" })
bus.emit("artifact", { name = "evidence", content = { count = 2 } })
bus.emit("artifact", { name = "verdict", content = "PASS" })
bus.emit("worker_result", { ok = true, response = "report" })
"#,
);
let bp = script_agent_bp(
"gh86-script-verdict-parts-many",
&script,
&dir,
json!({"channel": "part", "values": ["PASS", "BLOCKED"]}),
);
let out = service()
.launch(launch_input(bp, json!({})))
.await
.expect("launch must complete");
assert_eq!(
out.final_ctx["danger_result"],
json!({
"out": "report",
"parts": {"verdict": "PASS", "evidence": {"count": 2}},
}),
"every staged name folds; a name staged twice keeps the last value"
);
}
#[tokio::test]
async fn script_mode_without_staged_parts_keeps_the_plain_body() {
let (script, dir) = write_script(
"no-parts",
r#"
bus.emit("worker_result", { ok = true, response = "just the body" })
"#,
);
let bp = script_agent_bp("gh86-script-no-parts", &script, &dir, Value::Null);
let out = service()
.launch(launch_input(bp, json!({})))
.await
.expect("launch must complete");
assert_eq!(out.final_ctx["danger_result"], json!("just the body"));
}
#[tokio::test]
async fn script_mode_gate_staging_an_undeclared_part_verdict_fails_the_step() {
let (script, dir) = write_script(
"verdict-part-bad",
r#"
bus.emit("artifact", { name = "verdict", content = "MAYBE" })
bus.emit("worker_result", { ok = true, response = "looks fine to me" })
"#,
);
let bp = script_agent_bp(
"gh86-script-verdict-part-bad",
&script,
&dir,
json!({"channel": "part", "values": ["PASS", "BLOCKED"]}),
);
let result = service().launch(launch_input(bp, json!({}))).await;
assert!(
result.is_err(),
"an undeclared staged verdict must fail the step: {result:?}"
);
}
#[tokio::test]
async fn script_mode_part_contract_without_a_staged_verdict_fails_the_step() {
let (script, dir) = write_script(
"verdict-part-missing",
r#"
bus.emit("worker_result", { ok = true, response = "PASS" })
"#,
);
let bp = script_agent_bp(
"gh86-script-verdict-part-missing",
&script,
&dir,
json!({"channel": "part", "values": ["PASS", "BLOCKED"]}),
);
let result = service().launch(launch_input(bp, json!({}))).await;
assert!(
result.is_err(),
"a part contract with no staged verdict must fail: {result:?}"
);
}
#[tokio::test]
async fn script_mode_gate_emitting_an_undeclared_verdict_fails_the_step() {
let (script, dir) = write_script(
"verdict-bad",
r#"
bus.emit("worker_result", { ok = true, response = "MAYBE" })
"#,
);
let bp = script_agent_bp(
"gh86-script-verdict-bad",
&script,
&dir,
json!({"channel": "body", "values": ["PASS", "BLOCKED"]}),
);
let result = service().launch(launch_input(bp, json!({}))).await;
let err = result.expect_err("an undeclared verdict token must fail the step");
assert_verdict_rejection_is_diagnosable(&err.to_string(), "MAYBE");
}
#[tokio::test]
async fn script_mode_report_body_under_a_body_channel_contract_names_the_contract() {
for payload_key in ["response", "content"] {
let (script, dir) = write_script(
&format!("verdict-report-body-{payload_key}"),
&format!(
r#"
bus.emit("worker_result", {{ ok = true, {payload_key} = std.json.encode({{
verdict = "PASS",
summary = "0 findings",
}}) }})
"#
),
);
let bp = script_agent_bp(
&format!("gh86-script-verdict-report-body-{payload_key}"),
&script,
&dir,
json!({"channel": "body", "values": ["PASS", "BLOCKED"]}),
);
let result = service().launch(launch_input(bp, json!({}))).await;
let err = result.expect_err(&format!(
"a report body must fail a body contract ({payload_key})"
));
assert_verdict_rejection_is_diagnosable(&err.to_string(), "summary");
}
}
fn assert_verdict_rejection_is_diagnosable(msg: &str, rejected_fragment: &str) {
assert!(
msg.contains("verdict contract violation"),
"the failure must name the violated contract, got: {msg}"
);
assert!(
msg.contains(rejected_fragment),
"the failure must echo the rejected value (looking for {rejected_fragment:?}): {msg}"
);
assert!(
msg.contains("PASS") && msg.contains("BLOCKED"),
"the failure must echo the declared values: {msg}"
);
assert!(
!msg.contains("no Final in output_tail"),
"the cause must replace the missing-Final symptom, not hide behind it: {msg}"
);
}
#[tokio::test]
async fn task_work_dir_overrides_spec_project_root_for_the_script() {
let (script, spec_root) = write_script(
"work-dir",
r#"
local r = sh.exec("cat marker.txt")
bus.emit("worker_result", {
ok = true,
response = { via_sh = (r.stdout or ""), root = std.env.project_root() },
})
"#,
);
let work_dir = std::env::temp_dir().join(format!(
"gh86-agent-block-workdir-{}-{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("clock")
.as_nanos()
));
std::fs::create_dir_all(&work_dir).expect("create work_dir");
std::fs::write(work_dir.join("marker.txt"), "from-work-dir").expect("write marker");
let bp = script_agent_bp("gh86-script-work-dir", &script, &spec_root, Value::Null);
let mut input = launch_input(bp, json!({}));
input.task_input = Some(TaskInputSpec {
project_root: None,
work_dir: Some(work_dir.display().to_string()),
task_metadata: None,
});
let out = service().launch(input).await.expect("launch must complete");
let got = &out.final_ctx["danger_result"];
assert_eq!(
got["via_sh"],
json!("from-work-dir"),
"sh.exec's default cwd must be the task work_dir, not spec.project_root: {got}"
);
assert_ne!(
got["root"],
json!(spec_root.display().to_string()),
"work_dir must have outranked spec.project_root: {got}"
);
assert!(
got["root"].as_str().expect("root is a string").ends_with(
work_dir
.file_name()
.and_then(|n| n.to_str())
.expect("work_dir basename")
),
"std.env.project_root() must report the task work_dir: {got}"
);
}
#[tokio::test]
async fn task_metadata_reaches_the_script_as_a_lua_global() {
let (script, dir) = write_script(
"task-metadata",
r#"
bus.emit("worker_result", {
ok = true,
response = {
issue = _TASK_METADATA and _TASK_METADATA.issue or "no-metadata",
nested = _TASK_METADATA and _TASK_METADATA.nested and _TASK_METADATA.nested[2] or "none",
},
})
"#,
);
let bp = script_agent_bp("gh86-script-task-metadata", &script, &dir, Value::Null);
let mut input = launch_input(bp, json!({}));
input.task_input = Some(TaskInputSpec {
project_root: None,
work_dir: None,
task_metadata: Some(json!({"issue": 86, "nested": ["a", "b"]})),
});
let out = service().launch(input).await.expect("launch must complete");
assert_eq!(
out.final_ctx["danger_result"],
json!({"issue": 86, "nested": "b"}),
"task_metadata must arrive as a real Lua table, nesting intact"
);
}
#[tokio::test]
async fn blueprint_declared_agent_ctx_reaches_the_script() {
let (script, dir) = write_script(
"agent-ctx",
r#"
bus.emit("worker_result", {
ok = true,
response = {
conventions = _AGENT_CTX and _AGENT_CTX.org_conventions or "none",
depth = _AGENT_CTX and _AGENT_CTX.nested and _AGENT_CTX.nested.level or "none",
},
})
"#,
);
let mut bp = script_agent_bp("gh86-script-agent-ctx", &script, &dir, Value::Null);
bp["default_agent_ctx"] = json!({
"org_conventions": "two-space indent",
"nested": { "level": 2 },
});
let out = service()
.launch(launch_input(bp, json!({})))
.await
.expect("launch must complete");
assert_eq!(
out.final_ctx["danger_result"],
json!({"conventions": "two-space indent", "depth": 2}),
"BP-declared agent ctx must arrive as a real Lua table, nesting intact"
);
}
#[tokio::test]
async fn sibling_require_resolves_with_and_without_task_metadata() {
let (script, dir) = write_script(
"sibling-require",
r#"
local helper = require("helper")
bus.emit("worker_result", {
ok = true,
response = { helped = helper.answer(), meta = _TASK_METADATA and "present" or "absent" },
})
"#,
);
std::fs::write(
dir.join("helper.lua"),
"return { answer = function() return \"from-sibling\" end }\n",
)
.expect("write sibling module");
let bp = script_agent_bp("gh86-sibling-no-meta", &script, &dir, Value::Null);
let out = service()
.launch(launch_input(bp, json!({})))
.await
.expect("no-metadata launch must complete");
assert_eq!(
out.final_ctx["danger_result"],
json!({"helped": "from-sibling", "meta": "absent"})
);
let bp = script_agent_bp("gh86-sibling-with-meta", &script, &dir, Value::Null);
let mut input = launch_input(bp, json!({}));
input.task_input = Some(TaskInputSpec {
project_root: None,
work_dir: None,
task_metadata: Some(json!({"issue": 86})),
});
let out = service()
.launch(input)
.await
.expect("with-metadata launch must complete");
assert_eq!(
out.final_ctx["danger_result"],
json!({"helped": "from-sibling", "meta": "present"}),
"supplying task_metadata must not disturb the chunk or package.path"
);
}
#[tokio::test]
async fn after_run_audit_kicks_an_agent_block_auditor_in_process() {
use mlua_swarm::{AgentBlockInProcessSpawnerFactory, RustFnInProcessSpawnerFactory};
let marker = std::env::temp_dir().join(format!(
"gh86-audit-marker-{}-{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("clock")
.as_nanos()
));
let (script, dir) = write_script(
"auditor",
&format!(
r#"
local f = assert(io.open({marker:?}, "w"))
f:write(tostring(_PROMPT))
f:close()
bus.emit("worker_result", {{ ok = true, response = "audited" }})
"#,
marker = marker.display().to_string()
),
);
let bp = json!({
"schema_version": "0.1.0",
"id": "gh86-after-run-audit-agent-block",
"flow": {
"kind": "step",
"ref": "worker",
"in": {"op": "lit", "value": "do the thing"},
"out": {"op": "path", "at": "$.result"}
},
"agents": [
{ "name": "worker", "kind": "rust_fn", "spec": {"fn_id": "identity"} },
{
"name": "auditor",
"kind": "agent_block",
"spec": {
"script_path": script.display().to_string(),
"project_root": dir.display().to_string()
},
"runner": {"backend": "agent_block_in_process", "tools": []}
}
],
"audits": [{ "agent": "auditor", "steps": ["worker"], "mode": "sync" }]
});
let mut reg = SpawnerRegistry::new();
reg.register::<AgentBlockInProcessSpawnerFactory>(Arc::new(
AgentBlockInProcessSpawnerFactory::new(),
));
reg.register::<RustFnInProcessSpawnerFactory>(Arc::new(
mlua_swarm::worker::baseline::extend_with_baseline(RustFnInProcessSpawnerFactory::new()),
));
let svc = TaskLaunchService::new(Engine::new(EngineCfg::default()), Compiler::new(reg));
let out = svc
.launch(launch_input(bp, json!({})))
.await
.expect("launch must complete");
assert_eq!(
out.final_ctx["result"]["echoed"],
json!("do the thing"),
"the audited step's own outcome is untouched by the audit"
);
assert!(
out.final_ctx["result"].get("parts").is_none(),
"an audit sidecar must not wrap the audited step's BP-chain value \
in a parts fold, got: {}",
out.final_ctx["result"]
);
let seen = {
let budget = Duration::from_secs(2);
let start = std::time::Instant::now();
loop {
match std::fs::read_to_string(&marker) {
Ok(s) => break s,
Err(e) if start.elapsed() > budget => panic!(
"the auditor script must have run in-process and written its marker \
within {budget:?}: last error {e:?}, marker path {}",
marker.display()
),
Err(_) => tokio::time::sleep(Duration::from_millis(20)).await,
}
}
};
assert!(
seen.contains("worker"),
"the auditor's prompt should name the step it audits, got: {seen:?}"
);
let _ = std::fs::remove_file(&marker);
}
#[tokio::test]
async fn script_that_never_emits_fails_the_step() {
let (script, dir) = write_script(
"no-emit",
r#"
-- Returning a value is NOT the result contract; the host reads bus.emit.
return { ok = true, response = "ignored" }
"#,
);
let bp = script_agent_bp("gh86-script-no-emit", &script, &dir, Value::Null);
let result = service().launch(launch_input(bp, json!({}))).await;
assert!(
result.is_err(),
"a script that never emits must fail the step: {result:?}"
);
}