#![recursion_limit = "256"]
use std::sync::atomic::AtomicBool;
use std::sync::{Arc, Mutex};
use harn_vm::bridge::HostBridge;
use harn_vm::value::VmError;
fn run_with_bridge(source: &str) -> Result<String, String> {
harn_vm::reset_thread_local_state();
let chunk = harn_vm::compile_source(source)?;
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.map_err(|e| e.to_string())?;
rt.block_on(async {
let local = tokio::task::LocalSet::new();
local
.run_until(async {
let bridge = Arc::new(HostBridge::from_parts(
Arc::new(tokio::sync::Mutex::new(std::collections::HashMap::new())),
Arc::new(AtomicBool::new(false)),
Arc::new(Mutex::new(())),
1,
));
harn_vm::llm::install_current_host_bridge(bridge.clone());
let mut vm = harn_vm::Vm::new();
harn_vm::register_vm_stdlib(&mut vm);
let result = vm
.execute(&chunk)
.await
.map_err(|e: VmError| format!("{e:?}"));
harn_vm::llm::clear_current_host_bridge();
result?;
Ok(vm.output().to_string())
})
.await
})
}
fn out_lines(raw: &str) -> Vec<String> {
raw.lines()
.filter_map(|l| l.strip_prefix("[harn] "))
.map(|s| s.to_string())
.collect()
}
#[test]
fn agent_loop_emits_llm_call_start_checkpoint_for_main_call() {
let raw = run_with_bridge(
r#"
import { agent_capture_events } from "std/agent/events"
fn llm_call_start_events(events) {
return events.filter({ event -> event.type == "typed_checkpoint" && event.checkpoint?.kind == "llm_call_start" })
}
pipeline main(task) {
const session = "agent-loop-llm-call-start-" + uuid()
const caller = { call -> return {
ok: true,
value: {
text: "<user_response>done</user_response>\n<done>##DONE##</done>",
provider: call?.opts?.provider ?? "",
model: call?.opts?.model ?? "",
input_tokens: 0,
output_tokens: 0,
},
} }
const captured = agent_capture_events(
session,
fn() { return agent_loop(
"finish",
nil,
{
provider: "mock",
model: "checkpoint-model",
session_id: session,
llm_caller: caller,
loop_until_done: true,
done_judge: false,
max_iterations: 1,
tool_format: "text",
},
) },
)
const starts = llm_call_start_events(captured.events)
const checkpoint = starts[0].checkpoint
log(captured.result.status)
log(len(starts))
log(checkpoint.kind)
log(checkpoint.iteration)
log(checkpoint.attempt)
log(checkpoint.provider)
log(checkpoint.model)
log(checkpoint.tool_format)
log(checkpoint.final_wrapup)
}
"#,
)
.expect("script must run");
let lines = out_lines(&raw);
assert_eq!(lines[0], "done", "lines: {lines:?}");
assert_eq!(lines[1], "1", "expected one checkpoint; lines: {lines:?}");
assert_eq!(lines[2], "llm_call_start", "lines: {lines:?}");
assert_eq!(lines[3], "1", "lines: {lines:?}");
assert_eq!(lines[4], "1", "lines: {lines:?}");
assert_eq!(lines[5], "mock", "lines: {lines:?}");
assert_eq!(lines[6], "checkpoint-model", "lines: {lines:?}");
assert_eq!(lines[7], "text", "lines: {lines:?}");
assert_eq!(lines[8], "false", "lines: {lines:?}");
}
fn exhaustion_pipeline(session_id: &str, final_wrapup_opt: &str) -> String {
format!(
r#"
pipeline main(task) {{
clear_tool_hooks()
let registry = tool_registry()
let tools = tool_define(
registry,
"keep_exploring",
"Test stand-in for a tool the model keeps calling.",
{{parameters: {{}}, handler: {{ _args -> return "explored" }}}},
)
let tool_calls_counter = shared_cell(
{{scope: "task_group", key: "wrapup-tool-calls-{session_id}", initial: 0}},
)
let wrapup_counter = shared_cell(
{{scope: "task_group", key: "wrapup-final-calls-{session_id}", initial: 0}},
)
let mock_llm = {{ _call ->
if _call?.opts?._final_wrapup == true {{
let wsnap = shared_snapshot(wrapup_counter)
shared_cas(wrapup_counter, wsnap, wsnap.value + 1)
return {{
ok: true,
value: {{
text: "<user_response>Refactored the parser and ran the tests.</user_response>\n<done>FINISHED</done>",
tool_calls: [],
provider: "mock",
model: "mock",
}},
}}
}}
let snap = shared_snapshot(tool_calls_counter)
shared_cas(tool_calls_counter, snap, snap.value + 1)
return {{
ok: true,
value: {{
text: "",
tool_calls: [{{id: "call_explore", name: "keep_exploring", arguments: {{}}}}],
provider: "mock",
model: "mock",
}},
}}
}}
let result = agent_loop(
"do the work",
nil,
{{
provider: "mock",
tools: tools,
tool_format: "native",
max_iterations: 3,
loop_until_done: true,
done_sentinel: "FINISHED",
session_id: "{session_id}",
llm_caller: mock_llm,
{final_wrapup_opt}
}},
)
log(result.status)
log(shared_get(tool_calls_counter))
log(shared_get(wrapup_counter))
log(contains(result.text, "Refactored the parser"))
}}
"#
)
}
fn clean_done_pipeline(session_id: &str) -> String {
format!(
r#"
pipeline main(task) {{
clear_tool_hooks()
const wrapup_counter = shared_cell(
{{scope: "task_group", key: "clean-done-wrapup-{session_id}", initial: 0}},
)
const mock_llm = {{ _call ->
if _call?.opts?._final_wrapup == true {{
const wsnap = shared_snapshot(wrapup_counter)
shared_cas(wrapup_counter, wsnap, wsnap.value + 1)
}}
return {{
ok: true,
value: {{
text: "<user_response>All done.</user_response>\n<done>FINISHED</done>",
tool_calls: [],
provider: "mock",
model: "mock",
}},
}}
}}
const result = agent_loop(
"do the work",
nil,
{{
provider: "mock",
max_iterations: 3,
loop_until_done: true,
done_sentinel: "FINISHED",
session_id: "{session_id}",
llm_caller: mock_llm,
}},
)
log(result.status)
log(shared_get(wrapup_counter))
log(contains(result.text, "All done."))
}}
"#
)
}
#[test]
fn exhaustion_mid_tool_use_fires_wrapup_and_surfaces_sentinel() {
let raw =
run_with_bridge(&exhaustion_pipeline("wrapup-exhaustion", "")).expect("script must run");
let lines = out_lines(&raw);
assert_eq!(
lines[0], "budget_exhausted",
"expected budget_exhausted; lines: {lines:?}"
);
assert_eq!(
lines[1], "3",
"expected three loop LLM calls; lines: {lines:?}"
);
assert_eq!(
lines[2], "1",
"expected exactly one wrap-up LLM call; lines: {lines:?}"
);
assert_eq!(
lines[3], "true",
"expected surfaced text to contain the wrap-up summary; lines: {lines:?}"
);
}
#[test]
fn clean_done_exit_does_not_fire_wrapup() {
let raw = run_with_bridge(&clean_done_pipeline("wrapup-clean-done")).expect("script must run");
let lines = out_lines(&raw);
assert_eq!(lines[0], "done", "expected done; lines: {lines:?}");
assert_eq!(
lines[1], "0",
"expected zero wrap-up calls on a clean done; lines: {lines:?}"
);
assert_eq!(
lines[2], "true",
"expected surfaced text to contain the natural final answer; lines: {lines:?}"
);
}
fn host_directive_capture_pipeline(session_id: &str) -> String {
format!(
r#"
pipeline main(task) {{
clear_tool_hooks()
let registry = tool_registry()
let tools = tool_define(
registry,
"keep_exploring",
"Test stand-in for a tool the model keeps calling.",
{{parameters: {{}}, handler: {{ _args -> return "explored" }}}},
)
let wrapup_counter = shared_cell({{scope: "task_group", key: "hd-wc-{session_id}", initial: 0}})
let sys_cell = shared_cell({{scope: "task_group", key: "hd-sys-{session_id}", initial: ""}})
let lastmsg_cell = shared_cell({{scope: "task_group", key: "hd-last-{session_id}", initial: ""}})
let mock_llm = {{ _call ->
if _call?.opts?._final_wrapup == true {{
let wsnap = shared_snapshot(wrapup_counter)
shared_cas(wrapup_counter, wsnap, wsnap.value + 1)
let msgs = _call?.opts?.messages ?? []
let last = if len(msgs) > 0 {{ msgs[len(msgs) - 1] }} else {{ {{}} }}
let ssnap = shared_snapshot(sys_cell)
shared_cas(sys_cell, ssnap, to_string(_call?.system ?? ""))
let lsnap = shared_snapshot(lastmsg_cell)
shared_cas(lastmsg_cell, lsnap, to_string(last?.content ?? ""))
return {{
ok: true,
value: {{
text: "<user_response>Stopped at the budget; parser half-migrated, tests red.</user_response>\n<done>FINISHED</done>",
tool_calls: [],
provider: "mock",
model: "mock",
}},
}}
}}
return {{
ok: true,
value: {{
text: "",
tool_calls: [{{id: "call_explore", name: "keep_exploring", arguments: {{}}}}],
provider: "mock",
model: "mock",
}},
}}
}}
let result = agent_loop(
"do the work",
nil,
{{
provider: "mock",
tools: tools,
tool_format: "native",
max_iterations: 2,
loop_until_done: true,
done_sentinel: "FINISHED",
session_id: "{session_id}",
llm_caller: mock_llm,
wrapup_directive: "PRODUCT_SADPATH_DIRECTIVE_XYZ",
wrapup_context: {{terminal_kind: "budget_exhausted", files_touched: [{{path: "parser.rs", added_lines: 12}}]}},
}},
)
log(result.status)
log(shared_get(wrapup_counter))
log(contains(shared_get(sys_cell), "PRODUCT_SADPATH_DIRECTIVE_XYZ"))
log(contains(shared_get(lastmsg_cell), "PRODUCT_SADPATH_DIRECTIVE_XYZ"))
log(contains(shared_get(lastmsg_cell), "narrate it truthfully"))
log(contains(shared_get(lastmsg_cell), "terminal_kind"))
log(contains(shared_get(lastmsg_cell), "Terminal context (authoritative facts)"))
log(contains(result.text, "parser half-migrated"))
}}
"#
)
}
fn no_tool_terminal_pipeline(session_id: &str, wrapup_opts: &str) -> String {
format!(
r#"
pipeline main(task) {{
clear_tool_hooks()
let wrapup_counter = shared_cell({{scope: "task_group", key: "nt-wc-{session_id}", initial: 0}})
let mock_llm = {{ _call ->
if _call?.opts?._final_wrapup == true {{
let wsnap = shared_snapshot(wrapup_counter)
shared_cas(wrapup_counter, wsnap, wsnap.value + 1)
return {{
ok: true,
value: {{
text: "<user_response>Ran out of budget before writing anything.</user_response>\n<done>FINISHED</done>",
tool_calls: [],
provider: "mock",
model: "mock",
}},
}}
}}
return {{
ok: true,
value: {{text: "Still thinking, no action yet.", tool_calls: [], provider: "mock", model: "mock"}},
}}
}}
let result = agent_loop(
"do the work",
nil,
{{
provider: "mock",
max_iterations: 2,
loop_until_done: true,
done_judge: false,
done_sentinel: "FINISHED",
session_id: "{session_id}",
llm_caller: mock_llm,
{wrapup_opts}
}},
)
log(result.status)
log(shared_get(wrapup_counter))
}}
"#
)
}
fn host_directive_degrade_pipeline(session_id: &str) -> String {
format!(
r#"
pipeline main(task) {{
clear_tool_hooks()
let registry = tool_registry()
let tools = tool_define(
registry,
"keep_exploring",
"Test stand-in for a tool the model keeps calling.",
{{parameters: {{}}, handler: {{ _args -> return "explored" }}}},
)
let wrapup_counter = shared_cell({{scope: "task_group", key: "deg-wc-{session_id}", initial: 0}})
let mock_llm = {{ _call ->
if _call?.opts?._final_wrapup == true {{
let wsnap = shared_snapshot(wrapup_counter)
shared_cas(wrapup_counter, wsnap, wsnap.value + 1)
return {{ok: false, status: "provider_error", error: {{message: "wrap-up provider down"}}}}
}}
return {{
ok: true,
value: {{
text: "",
tool_calls: [{{id: "call_explore", name: "keep_exploring", arguments: {{}}}}],
provider: "mock",
model: "mock",
}},
}}
}}
let result = agent_loop(
"do the work",
nil,
{{
provider: "mock",
tools: tools,
tool_format: "native",
max_iterations: 2,
loop_until_done: true,
done_sentinel: "FINISHED",
session_id: "{session_id}",
llm_caller: mock_llm,
wrapup_directive: "PRODUCT_SADPATH_DIRECTIVE_XYZ",
}},
)
log(result.status)
log(shared_get(wrapup_counter))
log(contains(result.text, "PRODUCT_SADPATH_DIRECTIVE_XYZ"))
}}
"#
)
}
#[test]
fn host_directive_rides_trailing_message_and_keeps_system_stable() {
let raw = run_with_bridge(&host_directive_capture_pipeline("wrapup-host-directive"))
.expect("script must run");
let lines = out_lines(&raw);
assert_eq!(lines[0], "budget_exhausted", "lines: {lines:?}");
assert_eq!(
lines[1], "1",
"expected exactly one wrap-up call; lines: {lines:?}"
);
assert_eq!(
lines[2], "false",
"host directive must NOT be injected into the system prompt (prefix stays stable); lines: {lines:?}"
);
assert_eq!(
lines[3], "true",
"host directive must ride the trailing user message; lines: {lines:?}"
);
assert_eq!(
lines[4], "true",
"the outcome-authority constraint must be appended to the host directive; lines: {lines:?}"
);
assert_eq!(
lines[5], "true",
"structured wrapup_context must be rendered into the trailing message; lines: {lines:?}"
);
assert_eq!(
lines[6], "true",
"the context block must carry its authoritative-facts label; lines: {lines:?}"
);
assert_eq!(
lines[7], "true",
"the host wrap-up summary must be surfaced as the run text; lines: {lines:?}"
);
}
#[test]
fn tools_zero_terminal_fires_wrapup_only_with_host_directive() {
let raw_off = run_with_bridge(&no_tool_terminal_pipeline("wrapup-notool-off", ""))
.expect("script must run");
let off = out_lines(&raw_off);
assert_eq!(off[0], "budget_exhausted", "lines: {off:?}");
assert_eq!(
off[1], "0",
"a zero-tool terminal must not fire wrap-up without a host directive; lines: {off:?}"
);
let raw_on = run_with_bridge(&no_tool_terminal_pipeline(
"wrapup-notool-on",
" wrapup_directive: \"PRODUCT_SADPATH_DIRECTIVE_XYZ\",",
))
.expect("script must run");
let on = out_lines(&raw_on);
assert_eq!(on[0], "budget_exhausted", "lines: {on:?}");
assert_eq!(
on[1], "1",
"a zero-tool terminal must fire wrap-up when a host directive is supplied; lines: {on:?}"
);
}
#[test]
fn host_directive_wrapup_failure_degrades_silently() {
let raw = run_with_bridge(&host_directive_degrade_pipeline("wrapup-host-degrade"))
.expect("script must run");
let lines = out_lines(&raw);
assert_eq!(lines[0], "budget_exhausted", "lines: {lines:?}");
assert_eq!(
lines[1], "1",
"expected one attempted wrap-up call; lines: {lines:?}"
);
assert_eq!(
lines[2], "false",
"a failed wrap-up must not surface the directive or any summary; lines: {lines:?}"
);
}
#[test]
fn final_wrapup_false_disables_the_wrapup_turn() {
let raw = run_with_bridge(&exhaustion_pipeline(
"wrapup-disabled",
" final_wrapup: false,",
))
.expect("script must run");
let lines = out_lines(&raw);
assert_eq!(
lines[0], "budget_exhausted",
"expected budget_exhausted; lines: {lines:?}"
);
assert_eq!(
lines[1], "3",
"expected three loop LLM calls; lines: {lines:?}"
);
assert_eq!(
lines[2], "0",
"expected zero wrap-up calls when final_wrapup:false; lines: {lines:?}"
);
assert_eq!(
lines[3], "false",
"expected no wrap-up summary in surfaced text when disabled; lines: {lines:?}"
);
}