#![recursion_limit = "256"]
use harn_vm::value::VmError;
use tempfile::tempdir;
fn run(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 mut vm = harn_vm::Vm::new();
harn_vm::register_vm_stdlib(&mut vm);
vm.execute(&chunk)
.await
.map_err(|e: VmError| format!("{e:?}"))?;
Ok(vm.output().to_string())
})
.await
})
}
fn out(source: &str) -> Vec<String> {
let raw = run(source).unwrap();
raw.lines()
.filter_map(|l| l.strip_prefix("[harn] "))
.map(|s| s.to_string())
.collect()
}
fn harn_string(value: &str) -> String {
serde_json::to_string(value).expect("string literal")
}
#[test]
fn open_mints_and_is_idempotent() {
let lines = out(r"
pipeline main(task) {
let a = agent_session_open()
let b = agent_session_open(a)
log(a == b)
log(agent_session_exists(a))
}
");
assert_eq!(lines, vec!["true", "true"]);
}
#[test]
fn inject_then_length_and_snapshot() {
let lines = out(r#"
pipeline main(task) {
let s = agent_session_open()
agent_session_inject(s, {role: "user", content: "hello"})
agent_session_inject(s, {role: "assistant", content: "hi"})
log(agent_session_length(s))
let snap = agent_session_snapshot(s)
log(len(snap["messages"]))
log(snap["parent_id"] == nil)
log(len(snap["child_ids"]))
log(snap["branched_at_event_index"] == nil)
}
"#);
assert_eq!(lines, vec!["2", "2", "true", "0", "true"]);
}
#[test]
fn reset_clears_history_preserves_id() {
let lines = out(r#"
pipeline main(task) {
let s = agent_session_open()
agent_session_inject(s, {role: "user", content: "a"})
agent_session_inject(s, {role: "user", content: "b"})
agent_session_reset(s)
log(agent_session_length(s))
log(agent_session_exists(s))
}
"#);
assert_eq!(lines, vec!["0", "true"]);
}
#[test]
fn tool_format_contract_is_first_class_and_resettable() {
let lines = out(r#"
pipeline main(task) {
let s = agent_session_open("tool-contract")
log(agent_session_tool_format(s) == nil)
agent_session_claim_tool_format(s, "native")
log(agent_session_tool_format(s))
let conflict = try {
agent_session_claim_tool_format(s, "text")
}
log(is_err(conflict))
agent_session_reset(s)
log(agent_session_tool_format(s) == nil)
agent_session_claim_tool_format(s, "text")
log(agent_session_tool_format(s))
}
"#);
assert_eq!(lines, vec!["true", "native", "true", "true", "text"]);
}
#[test]
fn agent_loop_rejects_tool_format_switch_on_same_session() {
let lines = out(r#"
pipeline main(task) {
llm_mock_clear()
llm_mock({text: "first ##DONE##"})
let s = agent_session_open("agent-loop-tool-contract")
let first = agent_loop(
"first turn",
nil,
{provider: "mock", model: "mock", session_id: s, max_iterations: 1, tool_format: "text"},
)
log(first?.tools?.mode)
log(agent_session_tool_format(s))
let switched = try {
agent_loop(
"second turn",
nil,
{provider: "mock", model: "mock", session_id: s, max_iterations: 1, tool_format: "native"},
)
}
log(is_err(switched))
log(contains(json_stringify(unwrap_err(switched)), "tool_format"))
}
"#);
assert_eq!(lines, vec!["text", "text", "true", "true"]);
}
#[test]
fn fork_is_independent_in_both_directions() {
let lines = out(r#"
pipeline main(task) {
let src = agent_session_open()
agent_session_inject(src, {role: "user", content: "shared"})
let dst = agent_session_fork(src)
let src_snap = agent_session_snapshot(src)
let dst_snap = agent_session_snapshot(dst)
let dst_ancestry = agent_session_ancestry(dst)
log(agent_session_length(dst))
log(dst_snap["parent_id"] == src)
log(len(src_snap["child_ids"]))
log(dst_ancestry["root_id"] == src)
agent_session_inject(src, {role: "user", content: "src-only"})
agent_session_inject(dst, {role: "user", content: "dst-only-1"})
agent_session_inject(dst, {role: "user", content: "dst-only-2"})
log(agent_session_length(src))
log(agent_session_length(dst))
log(src == dst)
}
"#);
assert_eq!(lines, vec!["1", "true", "1", "true", "2", "3", "false"]);
}
#[test]
fn fork_carries_tool_format_contract() {
let lines = out(r#"
pipeline main(task) {
let src = agent_session_open("tool-fork-src")
agent_session_claim_tool_format(src, "native")
let dst = agent_session_fork(src, "tool-fork-dst")
log(agent_session_tool_format(dst))
let conflict = try {
agent_session_claim_tool_format(dst, "text")
}
log(is_err(conflict))
}
"#);
assert_eq!(lines, vec!["native", "true"]);
}
#[test]
fn fork_at_records_branch_index_and_root_lineage() {
let lines = out(r#"
pipeline main(task) {
let root = agent_session_open("root")
agent_session_inject(root, {role: "user", content: "a"})
agent_session_inject(root, {role: "assistant", content: "b"})
agent_session_inject(root, {role: "user", content: "c"})
let branch = agent_session_fork_at(root, 2, "branch")
let snap = agent_session_snapshot(branch)
let ancestry = agent_session_ancestry(branch)
log(agent_session_length(branch))
log(snap["branched_at_event_index"])
log(ancestry["parent_id"] == root)
log(ancestry["root_id"] == root)
}
"#);
assert_eq!(lines, vec!["2", "2", "true", "true"]);
}
#[test]
fn trim_retains_last_n() {
let lines = out(r#"
pipeline main(task) {
let s = agent_session_open()
agent_session_inject(s, {role: "user", content: "a"})
agent_session_inject(s, {role: "user", content: "b"})
agent_session_inject(s, {role: "user", content: "c"})
agent_session_inject(s, {role: "user", content: "d"})
let kept = agent_session_trim(s, 2)
log(kept)
log(agent_session_length(s))
let snap = agent_session_snapshot(s)
log(snap["messages"][0]["content"])
log(snap["messages"][1]["content"])
}
"#);
assert_eq!(lines, vec!["2", "2", "c", "d"]);
}
#[test]
fn trim_clamps_to_available() {
let lines = out(r#"
pipeline main(task) {
let s = agent_session_open()
agent_session_inject(s, {role: "user", content: "only"})
log(agent_session_trim(s, 100))
}
"#);
assert_eq!(lines, vec!["1"]);
}
#[test]
fn close_removes_session() {
let lines = out(r"
pipeline main(task) {
let s = agent_session_open()
agent_session_close(s)
log(agent_session_exists(s))
}
");
assert_eq!(lines, vec!["false"]);
}
#[test]
fn inject_without_role_errors() {
let err = run(r#"
pipeline main(task) {
let s = agent_session_open()
agent_session_inject(s, {content: "oops"})
}
"#)
.unwrap_err();
assert!(err.to_lowercase().contains("role"), "got: {err}");
}
#[test]
fn operations_on_unknown_session_error() {
for op in [
r#"agent_session_reset("does-not-exist")"#,
r#"agent_session_fork("does-not-exist")"#,
r#"agent_session_close("does-not-exist")"#,
r#"agent_session_trim("does-not-exist", 1)"#,
r#"agent_session_inject("does-not-exist", {role: "user"})"#,
r#"agent_session_length("does-not-exist")"#,
] {
let src = format!("pipeline main(task) {{ {op} }}");
let err = run(&src).unwrap_err();
assert!(
err.contains("does-not-exist") || err.to_lowercase().contains("unknown"),
"{op} => {err}"
);
}
}
#[test]
fn exists_and_snapshot_on_unknown_are_safe() {
let lines = out(r#"
pipeline main(task) {
log(agent_session_exists("nope"))
let snap = agent_session_snapshot("nope")
log(snap == nil)
let ancestry = agent_session_ancestry("nope")
log(ancestry == nil)
}
"#);
assert_eq!(lines, vec!["false", "true", "true"]);
}
#[test]
fn fork_at_on_unknown_or_negative_keep_first_errors() {
for op in [
r#"agent_session_fork_at("does-not-exist", 1)"#,
r"
let s = agent_session_open()
agent_session_fork_at(s, -1)
",
] {
let src = format!("pipeline main(task) {{ {op} }}");
let err = run(&src).unwrap_err();
assert!(
err.contains("does-not-exist")
|| err.contains("keep_first")
|| err.to_lowercase().contains("unknown"),
"{op} => {err}"
);
}
}
#[test]
fn lru_eviction_kicks_in_at_cap() {
harn_vm::reset_thread_local_state();
harn_vm::agent_sessions::set_session_cap(3);
let a = harn_vm::agent_sessions::open_or_create(Some("a".to_string()));
let _b = harn_vm::agent_sessions::open_or_create(Some("b".to_string()));
let _c = harn_vm::agent_sessions::open_or_create(Some("c".to_string()));
harn_vm::agent_sessions::open_or_create(Some(a));
let _d = harn_vm::agent_sessions::open_or_create(Some("d".to_string()));
assert!(harn_vm::agent_sessions::exists("a"));
assert!(!harn_vm::agent_sessions::exists("b"), "b should be evicted");
assert!(harn_vm::agent_sessions::exists("c"));
assert!(harn_vm::agent_sessions::exists("d"));
harn_vm::agent_sessions::set_session_cap(harn_vm::agent_sessions::DEFAULT_SESSION_CAP);
}
#[test]
fn compact_unknown_key_errors() {
let err = run(r"
pipeline main(task) {
let s = agent_session_open()
agent_session_compact(s, {bogus: 1})
}
")
.unwrap_err();
assert!(err.contains("bogus"), "got: {err}");
}
#[test]
fn open_pins_workspace_anchor_and_surfaces_in_snapshot() {
let lines = out(r#"
pipeline main(task) {
let s = agent_session_open(
"anchor-open",
{
workspace_anchor: {
primary: "/workspace/main",
anchored_at: "2026-05-23T00:00:00Z",
},
},
)
log(agent_session_workspace_anchor(s)["primary"])
let snap = agent_session_snapshot(s)
log(snap["workspace_anchor"]["primary"])
log(snap["workspace_anchor"]["anchored_at"])
log(len(snap["workspace_anchor"]["additional_roots"]))
}
"#);
assert_eq!(
lines,
vec![
"/workspace/main",
"/workspace/main",
"2026-05-23T00:00:00Z",
"0"
]
);
}
#[test]
fn set_workspace_anchor_replaces_and_clears() {
let lines = out(r#"
pipeline main(task) {
let s = agent_session_open("anchor-set")
log(agent_session_workspace_anchor(s) == nil)
let changed = agent_session_set_workspace_anchor(s, {
primary: "/workspace/initial",
anchored_at: "2026-05-23T00:00:00Z",
})
log(changed)
log(agent_session_workspace_anchor(s)["primary"])
let same = agent_session_set_workspace_anchor(s, {
primary: "/workspace/initial",
anchored_at: "2026-05-23T00:00:00Z",
})
log(same)
let cleared = agent_session_set_workspace_anchor(s, nil)
log(cleared)
log(agent_session_workspace_anchor(s) == nil)
}
"#);
assert_eq!(
lines,
vec![
"true",
"true",
"/workspace/initial",
"false",
"true",
"true"
]
);
}
#[test]
fn workspace_anchor_round_trips_through_fork() {
let lines = out(r#"
pipeline main(task) {
let src = agent_session_open("anchor-fork-src", {
workspace_anchor: {
primary: "/workspace/main",
additional_roots: [
{path: "/workspace/lib", mount_mode: "read_only", mounted_at: "2026-05-23T00:00:00Z"},
],
anchored_at: "2026-05-23T00:00:00Z",
},
})
let dst = agent_session_fork(src, "anchor-fork-dst")
let anchor = agent_session_workspace_anchor(dst)
log(anchor["primary"])
log(len(anchor["additional_roots"]))
log(anchor["additional_roots"][0]["mount_mode"])
}
"#);
assert_eq!(lines, vec!["/workspace/main", "1", "read_only"]);
}
#[test]
fn open_rejects_unknown_option_keys() {
let err = run(r"
pipeline main(task) {
agent_session_open(nil, {bogus: 1})
}
")
.unwrap_err();
assert!(err.contains("bogus"), "got: {err}");
}
#[test]
fn workspace_anchor_requires_primary() {
let err = run(r#"
pipeline main(task) {
agent_session_open(nil, {workspace_anchor: {anchored_at: "2026-05-23T00:00:00Z"}})
}
"#)
.unwrap_err();
assert!(err.contains("primary"), "got: {err}");
}
#[test]
fn workspace_anchor_rejects_unknown_mount_mode() {
let err = run(r#"
pipeline main(task) {
agent_session_open(nil, {
workspace_anchor: {
primary: "/workspace/main",
additional_roots: [{path: "/workspace/lib", mount_mode: "bogus"}],
},
})
}
"#)
.unwrap_err();
assert!(err.contains("bogus"), "got: {err}");
}
#[test]
fn add_root_uses_default_mount_mode_emits_event_and_removes_cleanly() {
let primary = tempdir().expect("primary tempdir");
let mounted = tempdir().expect("mounted tempdir");
let primary_path = primary.path().display().to_string();
let mounted_path = std::fs::canonicalize(mounted.path())
.expect("canonical mounted tempdir")
.display()
.to_string();
let primary_literal = harn_string(&primary_path);
let mounted_literal = harn_string(&mounted_path);
let lines = out(&format!(
r#"
pipeline main(task) {{
let s = agent_session_open("anchor-roots", {{
workspace_policy: {{default_mount_mode: "extend"}},
workspace_anchor: {{
primary: {primary_literal},
anchored_at: "2026-05-24T00:00:00Z",
}},
}})
let added = agent_session_add_root(s, {mounted_literal}, {{reason: "shared"}})
log(added.ok)
log(added.mounted_at != nil)
let roots = agent_session_list_roots(s)
log(roots["primary"])
log(len(roots["additional"]))
log(roots["additional"][0]["mount_mode"])
let mounted_events = transcript_events_by_kind(agent_session_snapshot(s), "RootMounted")
log(len(mounted_events))
log(mounted_events[0]["metadata"]["path"])
log(mounted_events[0]["metadata"]["mount_mode"])
log(mounted_events[0]["metadata"]["reason"])
let updated = agent_session_add_root(s, {mounted_literal}, {{mount_mode: "sandboxed"}})
log(updated.ok)
log(agent_session_list_roots(s)["additional"][0]["mount_mode"])
log(len(agent_session_list_roots(s)["additional"]))
let removed = agent_session_remove_root(s, {mounted_literal})
log(removed.ok)
log(len(agent_session_list_roots(s)["additional"]))
let missing = agent_session_remove_root(s, {mounted_literal})
log(missing.ok)
}}
"#
));
assert_eq!(
lines,
vec![
"true".to_string(),
"true".to_string(),
primary_path,
"1".to_string(),
"extend".to_string(),
"1".to_string(),
mounted_path,
"extend".to_string(),
"shared".to_string(),
"true".to_string(),
"sandboxed".to_string(),
"1".to_string(),
"true".to_string(),
"0".to_string(),
"true".to_string(),
]
);
}
#[test]
fn add_root_reports_missing_directory_in_result_envelope() {
let primary = tempdir().expect("primary tempdir");
let missing = primary.path().join("missing-root");
let primary_path = primary.path().display().to_string();
let missing_path = missing.display().to_string();
let primary_literal = harn_string(&primary_path);
let missing_literal = harn_string(&missing_path);
let lines = out(&format!(
r#"
pipeline main(task) {{
let s = agent_session_open("anchor-roots-missing", {{
workspace_anchor: {{
primary: {primary_literal},
anchored_at: "2026-05-24T00:00:00Z",
}},
}})
let added = agent_session_add_root(s, {missing_literal})
log(added.ok)
log(contains(added.error ?? "", "must exist"))
}}
"#
));
assert_eq!(lines, vec!["false", "true"]);
}