#![expect(
clippy::wildcard_enum_match_arm,
reason = "test code: this driver names the outcomes it asserts on and treats the rest as one arm, rather than modelling an enum the assertion does not reach"
)]
#![allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::indexing_slicing,
clippy::redundant_closure,
clippy::disallowed_methods,
reason = "test code"
)]
use kernel::ledger::chain_hash;
use kernel::model::message_payload;
use kernel::{
Address, AxError, B3Hash, BuildingPolicy, Completion, ContentBlock, EventDraft, EventRef,
GENESIS_PREV, Ledger, Locator, Model, ModelRequest, ModelReturn, Payload, RunId, TimeMs,
ToolCall, ToolName, ToolOutcome,
};
use runtime::handoff::Handoff;
use runtime::prefix::{FrozenPrefix, FrozenSegment, SegmentSlot};
use runtime::run::{Advance, Run, RunHooks, RunPlan, SafePoint, drive};
use runtime::turn::{CallShape, Interrupt};
struct RecordingLedger {
lines: Vec<Vec<u8>>,
next: kernel::Seq,
prev: B3Hash,
}
impl RecordingLedger {
fn new() -> Self {
RecordingLedger {
lines: Vec::new(),
next: kernel::Seq::FIRST,
prev: GENESIS_PREV,
}
}
fn field(&self, key: &str) -> Vec<String> {
self.lines
.iter()
.map(|line| {
let value: serde_json::Value = serde_json::from_slice(line).unwrap();
value[key].to_string().trim_matches('"').to_owned()
})
.collect()
}
fn kinds(&self) -> Vec<String> {
self.field("kind")
}
fn stamps(&self) -> Vec<u64> {
self.lines
.iter()
.map(|line| {
let value: serde_json::Value = serde_json::from_slice(line).unwrap();
value["t"].as_u64().unwrap()
})
.collect()
}
}
impl Ledger for RecordingLedger {
fn append(&mut self, draft: EventDraft) -> Result<EventRef, AxError> {
let record = kernel::EventRecord::from_draft(draft, self.next, self.prev);
let line = record.canonical_line()?;
self.prev = chain_hash(&line);
self.next = self.next.next()?;
let echo = record.to_ref();
self.lines.push(line);
Ok(echo)
}
}
struct ScriptedModel {
waves: Vec<Vec<ToolCall>>,
seen: std::rc::Rc<std::cell::RefCell<Vec<String>>>,
}
impl Model for ScriptedModel {
fn call(&mut self, req: &ModelRequest) -> Result<ModelReturn, AxError> {
self.seen
.borrow_mut()
.push(format!("{:?}", req.chat.messages));
let calls = if self.waves.is_empty() {
Vec::new()
} else {
self.waves.remove(0)
};
Ok(ModelReturn::bare(
message_payload(&[ContentBlock::Text {
text: "working".to_owned(),
}])
.unwrap(),
calls,
))
}
}
struct FailingModel {
code: kernel::AxCode,
}
impl Model for FailingModel {
fn call(&mut self, _req: &ModelRequest) -> Result<ModelReturn, AxError> {
Err(
AxError::failure(self.code, "read the model's reply", "not a tool name")
.with_recovery("name a tool the run registered, or register this one"),
)
}
}
struct FlakyModel {
failures_left: u32,
calls: std::rc::Rc<std::cell::RefCell<u32>>,
}
impl Model for FlakyModel {
fn call(&mut self, _req: &ModelRequest) -> Result<ModelReturn, AxError> {
let spent = *self.calls.borrow();
*self.calls.borrow_mut() = spent.saturating_add(1);
if self.failures_left > 0 {
self.failures_left = self.failures_left.saturating_sub(1);
return Err(AxError::failure(
kernel::AxCode::Provider,
"call the model",
"the provider answered 503",
)
.retriable()
.with_recovery("wait for the provider and send the turn again"));
}
Ok(ModelReturn::bare(
message_payload(&[ContentBlock::Text {
text: "working".to_owned(),
}])
.unwrap(),
Vec::new(),
))
}
}
fn call(id: &str) -> ToolCall {
ToolCall {
id: id.to_owned(),
name: ToolName::parse("status").unwrap(),
args: Payload::empty(),
}
}
fn plan() -> RunPlan {
let addr = Address::parse("lab/room1").unwrap();
RunPlan {
run: RunId::from_bytes([7; 16]),
who: "resident".to_owned(),
addr: addr.clone(),
task: "close the loop".to_owned(),
goal: "one turn, then stop".to_owned(),
opening: runtime::Opening::FromJob,
job: Locator::parse(&format!("file:{}/JOB.md@{}", addr.as_str(), "a".repeat(40))).unwrap(),
parent: None,
predecessor: None,
dispatched_by: kernel::event::Who::Person,
run_policy: kernel::RunPolicy::of(kernel::Mode::Work),
naming: None,
inherited: Vec::new(),
shape: CallShape {
model: "script".to_owned(),
max_tokens: kernel::Ceiling::new(4096),
effort: None,
context_tokens: 0,
},
second_threshold: None,
context: runtime::ContextReading::default(),
prefix: FrozenPrefix::assemble(
FrozenSegment::new(SegmentSlot::City, b"city".to_vec()),
FrozenSegment::new(SegmentSlot::Building, b"building".to_vec()),
FrozenSegment::new(SegmentSlot::Resident, b"resident".to_vec()),
FrozenSegment::new(SegmentSlot::Run, b"run".to_vec()),
)
.unwrap(),
policy: BuildingPolicy::default(),
tools: Vec::new(),
skills: Vec::new(),
retries: kernel::Retries::UntilHalted,
}
}
fn handoff() -> Handoff {
Handoff::new(
vec![Locator::parse(&format!("file:lab/room1/JOB.md@{}", "a".repeat(40))).unwrap()],
"driver test".to_owned(),
"see roadmap".to_owned(),
"scripted world".to_owned(),
"resume from the job file".to_owned(),
)
.unwrap()
}
fn counter() -> impl FnMut() -> Result<TimeMs, AxError> {
let mut tick: u64 = 0;
move || {
let now = TimeMs::new(tick);
tick = tick.saturating_add(1);
Ok(now)
}
}
#[test]
fn a_run_that_finishes_writes_dispatch_turns_and_freeze_in_that_order() {
let mut ledger = RecordingLedger::new();
let mut model = ScriptedModel {
seen: std::rc::Rc::new(std::cell::RefCell::new(Vec::new())),
waves: vec![vec![call("t-1")]],
};
let mut now = counter();
let mut interrupt = |_: SafePoint| Interrupt::None;
let mut invoke = |_: &ToolCall, _: TimeMs| {
Ok(ToolOutcome {
result: Payload::empty(),
attachments: Vec::new(),
})
};
let mut hooks = RunHooks {
now: &mut now,
interrupt: &mut interrupt,
checkpoint: None,
writes: &|_: &kernel::ToolCall| kernel::Writes::Domain,
invoke: &mut invoke,
wait: &mut |_: TimeMs| runtime::NextCall::Allowed,
deltas: None,
};
let frozen = drive(plan(), &mut ledger, &mut model, &mut hooks, &handoff()).unwrap();
assert_eq!(
ledger.kinds(),
vec![
"checkpoint_committed",
"run_started",
"prompt_assembled",
"prompt_shape_compared",
"model_called",
"model_returned",
"tool_called",
"tool_result",
"prompt_shape_compared",
"model_called",
"model_returned",
"handoff_written",
"run_frozen",
]
);
assert!(matches!(frozen.completion(), Completion::Done(_)));
assert_eq!(frozen.turns(), 2);
assert_eq!(
ledger.stamps(),
vec![0, 1, 2, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
);
for line in &ledger.lines {
let record = kernel::EventRecord::parse_line(line).unwrap();
let waited_for = matches!(
record.kind(),
kernel::EventKind::ModelCalled
| kernel::EventKind::ModelReturned
| kernel::EventKind::ToolCalled
| kernel::EventKind::ToolResult
);
let expected = waited_for.then(|| record.t());
assert_eq!(record.moment(), expected, "{:?}", record.kind());
}
}
#[test]
fn a_retriable_failure_is_made_again_up_to_the_number_the_person_set() {
let attempts = std::rc::Rc::new(std::cell::RefCell::new(0));
let mut ledger = RecordingLedger::new();
let mut model = FlakyModel {
failures_left: 2,
calls: std::rc::Rc::clone(&attempts),
};
let mut now = counter();
let mut interrupt = |_: SafePoint| Interrupt::None;
let mut invoke = |_: &ToolCall, _: TimeMs| {
Ok(ToolOutcome {
result: Payload::empty(),
attachments: Vec::new(),
})
};
let mut hooks = RunHooks {
now: &mut now,
interrupt: &mut interrupt,
checkpoint: None,
writes: &|_: &kernel::ToolCall| kernel::Writes::Domain,
invoke: &mut invoke,
wait: &mut |_: TimeMs| runtime::NextCall::Allowed,
deltas: None,
};
let plan = RunPlan {
retries: kernel::Retries::AtMost(2),
..plan()
};
let frozen = drive(plan, &mut ledger, &mut model, &mut hooks, &handoff()).unwrap();
assert!(matches!(frozen.completion(), Completion::Done(_)));
assert_eq!(*attempts.borrow(), 3, "two failures, then the answer");
assert_eq!(
ledger
.kinds()
.iter()
.filter(|k| *k == "watchdog_fired")
.count(),
2,
"each retry is a record, not a silent repeat"
);
}
#[test]
fn a_ceiling_that_is_reached_ends_the_run() {
let attempts = std::rc::Rc::new(std::cell::RefCell::new(0));
let mut ledger = RecordingLedger::new();
let mut model = FlakyModel {
failures_left: 9,
calls: std::rc::Rc::clone(&attempts),
};
let mut now = counter();
let mut interrupt = |_: SafePoint| Interrupt::None;
let mut invoke = |_: &ToolCall, _: TimeMs| {
Ok(ToolOutcome {
result: Payload::empty(),
attachments: Vec::new(),
})
};
let mut hooks = RunHooks {
now: &mut now,
interrupt: &mut interrupt,
checkpoint: None,
writes: &|_: &kernel::ToolCall| kernel::Writes::Domain,
invoke: &mut invoke,
wait: &mut |_: TimeMs| runtime::NextCall::Allowed,
deltas: None,
};
let plan = RunPlan {
retries: kernel::Retries::AtMost(1),
..plan()
};
let frozen = drive(plan, &mut ledger, &mut model, &mut hooks, &handoff()).unwrap();
assert!(matches!(frozen.completion(), Completion::Cancelled));
assert_eq!(*attempts.borrow(), 2, "the first call, then the one retry");
}
#[test]
fn a_run_ends_when_its_work_runs_out_rather_than_at_a_ceiling() {
let mut ledger = RecordingLedger::new();
let mut model = ScriptedModel {
seen: std::rc::Rc::new(std::cell::RefCell::new(Vec::new())),
waves: (0..26)
.map(|turn| vec![call(&format!("t-{turn}"))])
.collect(),
};
let mut now = counter();
let mut interrupt = |_: SafePoint| Interrupt::None;
let mut invoke = |_: &ToolCall, _: TimeMs| {
Ok(ToolOutcome {
result: Payload::empty(),
attachments: Vec::new(),
})
};
let mut hooks = RunHooks {
now: &mut now,
interrupt: &mut interrupt,
checkpoint: None,
writes: &|_: &kernel::ToolCall| kernel::Writes::Domain,
invoke: &mut invoke,
wait: &mut |_: TimeMs| runtime::NextCall::Allowed,
deltas: None,
};
let frozen = drive(plan(), &mut ledger, &mut model, &mut hooks, &handoff()).unwrap();
assert!(matches!(frozen.completion(), Completion::Done(_)));
assert_eq!(frozen.turns(), 27, "twenty-six waves, then the empty one");
let kinds = ledger.kinds();
assert_eq!(kinds[kinds.len() - 2], "handoff_written");
assert_eq!(kinds[kinds.len() - 1], "run_frozen");
}
#[test]
fn a_cancel_at_a_safe_point_freezes_inside_the_interrupted_turn() {
let mut ledger = RecordingLedger::new();
let mut model = ScriptedModel {
seen: std::rc::Rc::new(std::cell::RefCell::new(Vec::new())),
waves: vec![vec![call("t-1")]],
};
let mut now = counter();
let mut interrupt = |point: SafePoint| match point {
SafePoint::BeforeCall { turn: 0 } => Interrupt::Cancel,
_ => Interrupt::None,
};
let mut invoke = |_: &ToolCall, _: TimeMs| {
Ok(ToolOutcome {
result: Payload::empty(),
attachments: Vec::new(),
})
};
let mut hooks = RunHooks {
now: &mut now,
interrupt: &mut interrupt,
checkpoint: None,
writes: &|_: &kernel::ToolCall| kernel::Writes::Domain,
invoke: &mut invoke,
wait: &mut |_: TimeMs| runtime::NextCall::Allowed,
deltas: None,
};
let frozen = drive(plan(), &mut ledger, &mut model, &mut hooks, &handoff()).unwrap();
assert!(matches!(frozen.completion(), Completion::Cancelled));
let kinds = ledger.kinds();
assert_eq!(kinds[kinds.len() - 3], "cancel_received");
assert_eq!(kinds[kinds.len() - 2], "handoff_written");
assert_eq!(kinds[kinds.len() - 1], "run_frozen");
let stamps = ledger.stamps();
assert_eq!(stamps[2], 2);
assert_eq!(stamps[stamps.len() - 2], 2);
assert_eq!(stamps[stamps.len() - 1], 3);
}
#[test]
fn a_checkpoint_runs_before_the_wave_and_carries_the_turns_stamp() {
let mut ledger = RecordingLedger::new();
let mut model = ScriptedModel {
seen: std::rc::Rc::new(std::cell::RefCell::new(Vec::new())),
waves: vec![vec![call("t-1")]],
};
let mut now = counter();
let mut interrupt = |_: SafePoint| Interrupt::None;
let mut invoke = |_: &ToolCall, _: TimeMs| {
Ok(ToolOutcome {
result: Payload::empty(),
attachments: Vec::new(),
})
};
let mut checkpointed: Vec<u64> = Vec::new();
let mut checkpoint = |t: TimeMs| {
checkpointed.push(t.value());
Ok(Payload::empty())
};
{
let mut hooks = RunHooks {
now: &mut now,
interrupt: &mut interrupt,
checkpoint: Some(&mut checkpoint),
writes: &|_: &kernel::ToolCall| kernel::Writes::Domain,
invoke: &mut invoke,
wait: &mut |_: TimeMs| runtime::NextCall::Allowed,
deltas: None,
};
let frozen = drive(plan(), &mut ledger, &mut model, &mut hooks, &handoff()).unwrap();
assert!(matches!(frozen.completion(), Completion::Done(_)));
}
assert_eq!(checkpointed, vec![2, 7]);
let kinds = ledger.kinds();
assert_eq!(kinds[6], "checkpoint_committed");
assert_eq!(kinds[7], "tool_called");
}
#[test]
fn a_run_that_calls_nothing_puts_up_no_checkpoint() {
let mut ledger = RecordingLedger::new();
let mut model = ScriptedModel {
seen: std::rc::Rc::new(std::cell::RefCell::new(Vec::new())),
waves: Vec::new(),
};
let mut now = counter();
let mut interrupt = |_: SafePoint| Interrupt::None;
let mut invoke = |_: &ToolCall, _: TimeMs| {
Ok(ToolOutcome {
result: Payload::empty(),
attachments: Vec::new(),
})
};
let mut checkpointed: Vec<u64> = Vec::new();
let mut checkpoint = |t: TimeMs| {
checkpointed.push(t.value());
Ok(Payload::empty())
};
{
let mut hooks = RunHooks {
now: &mut now,
interrupt: &mut interrupt,
checkpoint: Some(&mut checkpoint),
writes: &|_: &kernel::ToolCall| kernel::Writes::Domain,
invoke: &mut invoke,
wait: &mut |_: TimeMs| runtime::NextCall::Allowed,
deltas: None,
};
let frozen = drive(plan(), &mut ledger, &mut model, &mut hooks, &handoff()).unwrap();
assert!(matches!(frozen.completion(), Completion::Done(_)));
}
assert_eq!(checkpointed, Vec::<u64>::new());
}
#[test]
fn a_read_only_wave_puts_up_no_checkpoint() {
let mut ledger = RecordingLedger::new();
let mut model = ScriptedModel {
seen: std::rc::Rc::new(std::cell::RefCell::new(Vec::new())),
waves: vec![vec![call("t-1")]],
};
let mut now = counter();
let mut interrupt = |_: SafePoint| Interrupt::None;
let mut invoke = |_: &ToolCall, _: TimeMs| {
Ok(ToolOutcome {
result: Payload::empty(),
attachments: Vec::new(),
})
};
let mut checkpointed: Vec<u64> = Vec::new();
let mut checkpoint = |t: TimeMs| {
checkpointed.push(t.value());
Ok(Payload::empty())
};
{
let mut hooks = RunHooks {
now: &mut now,
interrupt: &mut interrupt,
checkpoint: Some(&mut checkpoint),
writes: &|_: &kernel::ToolCall| kernel::Writes::Nothing,
invoke: &mut invoke,
wait: &mut |_: TimeMs| runtime::NextCall::Allowed,
deltas: None,
};
let frozen = drive(plan(), &mut ledger, &mut model, &mut hooks, &handoff()).unwrap();
assert!(matches!(frozen.completion(), Completion::Done(_)));
}
assert_eq!(checkpointed, Vec::<u64>::new());
}
#[test]
fn advance_reports_each_turn_so_a_caller_can_stop_between_them() {
let mut ledger = RecordingLedger::new();
let mut model = ScriptedModel {
seen: std::rc::Rc::new(std::cell::RefCell::new(Vec::new())),
waves: vec![vec![call("t-1")]],
};
let mut now = counter();
let mut interrupt = |_: SafePoint| Interrupt::None;
let mut invoke = |_: &ToolCall, _: TimeMs| {
Ok(ToolOutcome {
result: Payload::empty(),
attachments: Vec::new(),
})
};
let mut hooks = RunHooks {
now: &mut now,
interrupt: &mut interrupt,
checkpoint: None,
writes: &|_: &kernel::ToolCall| kernel::Writes::Domain,
invoke: &mut invoke,
wait: &mut |_: TimeMs| runtime::NextCall::Allowed,
deltas: None,
};
let mut run = Run::dispatch(plan(), &mut ledger, &mut hooks).unwrap();
assert!(matches!(
run.advance(&mut ledger, &mut model, &mut hooks).unwrap(),
Advance::Turned
));
let ending = run.advance(&mut ledger, &mut model, &mut hooks).unwrap();
let Advance::Concluded(completion) = ending else {
panic!("the second turn makes no calls, so the run concludes");
};
assert!(matches!(completion, Completion::Done(_)));
let frozen = run
.freeze(&mut ledger, &handoff(), completion, &mut hooks)
.unwrap();
assert_eq!(frozen.turns(), 2);
}
#[test]
fn a_steer_at_a_safe_point_reaches_the_next_window_and_not_only_the_ledger() {
let mut ledger = RecordingLedger::new();
let seen = std::rc::Rc::new(std::cell::RefCell::new(Vec::new()));
let mut model = ScriptedModel {
seen: std::rc::Rc::clone(&seen),
waves: vec![vec![call("t-1")]],
};
let mut now = counter();
let mut interrupt = |point: SafePoint| match point {
SafePoint::BeforeAssemble { turn: 1 } => Interrupt::Steer {
source: "user".to_owned(),
text: "measure it in metres".to_owned(),
},
_ => Interrupt::None,
};
let mut invoke = |_: &ToolCall, _: TimeMs| {
Ok(ToolOutcome {
result: Payload::empty(),
attachments: Vec::new(),
})
};
let mut hooks = RunHooks {
now: &mut now,
interrupt: &mut interrupt,
checkpoint: None,
writes: &|_: &kernel::ToolCall| kernel::Writes::Domain,
invoke: &mut invoke,
wait: &mut |_: TimeMs| runtime::NextCall::Allowed,
deltas: None,
};
drive(plan(), &mut ledger, &mut model, &mut hooks, &handoff()).unwrap();
assert!(
ledger.kinds().contains(&"steer_received".to_owned()),
"the arrival is recorded"
);
let windows = seen.borrow();
assert_eq!(windows.len(), 2, "two turns, two windows");
assert!(
!windows[0].contains("measure it in metres"),
"a steer never rewrites a request already assembled"
);
assert!(
windows[1].contains("measure it in metres"),
"and the next assembly carries it: {}",
windows[1]
);
assert!(windows[1].contains("user"), "attributed to whoever sent it");
}
#[test]
fn a_cancel_after_the_wave_stops_the_run_before_anything_it_handed_down_starts() {
let mut ledger = RecordingLedger::new();
let mut model = ScriptedModel {
seen: std::rc::Rc::new(std::cell::RefCell::new(Vec::new())),
waves: vec![vec![call("t-1")]],
};
let mut now = counter();
let mut interrupt = |point: SafePoint| match point {
SafePoint::BeforeSpawn { turn: 0 } => Interrupt::Cancel,
_ => Interrupt::None,
};
let mut invoke = |_: &ToolCall, _: TimeMs| {
Ok(ToolOutcome {
result: Payload::empty(),
attachments: Vec::new(),
})
};
let mut hooks = RunHooks {
now: &mut now,
interrupt: &mut interrupt,
checkpoint: None,
writes: &|_: &kernel::ToolCall| kernel::Writes::Domain,
invoke: &mut invoke,
wait: &mut |_: TimeMs| runtime::NextCall::Allowed,
deltas: None,
};
let frozen = drive(plan(), &mut ledger, &mut model, &mut hooks, &handoff()).unwrap();
assert!(matches!(frozen.completion(), Completion::Cancelled));
assert_eq!(
frozen.turns(),
0,
"a turn cancelled at its close did not count"
);
let kinds = ledger.kinds();
assert_eq!(kinds[kinds.len() - 3], "cancel_received");
assert!(kinds.iter().any(|kind| kind == "tool_result"));
}
#[test]
fn a_run_that_dies_of_a_loadtime_failure_still_writes_its_verdict() {
let mut ledger = RecordingLedger::new();
let mut model = FailingModel {
code: kernel::AxCode::WireMismatch,
};
let mut now = counter();
let mut interrupt = |_: SafePoint| Interrupt::None;
let mut invoke = |_: &ToolCall, _: TimeMs| {
Ok(ToolOutcome {
result: Payload::empty(),
attachments: Vec::new(),
})
};
let mut hooks = RunHooks {
now: &mut now,
interrupt: &mut interrupt,
checkpoint: None,
writes: &|_: &kernel::ToolCall| kernel::Writes::Domain,
invoke: &mut invoke,
wait: &mut |_: TimeMs| runtime::NextCall::Allowed,
deltas: None,
};
let outcome = drive(plan(), &mut ledger, &mut model, &mut hooks, &handoff());
let Err(err) = outcome else {
panic!("the diagnosis still reaches the caller");
};
assert_eq!(*err.code(), kernel::AxCode::WireMismatch);
let kinds = ledger.kinds();
assert_eq!(
kinds.last().map(String::as_str),
Some("run_frozen"),
"a run that started must not be left without a verdict: {kinds:?}"
);
assert_eq!(
kinds[kinds.len() - 2],
"handoff_written",
"the one exit writes both of its lines: {kinds:?}"
);
assert!(
!kinds.iter().any(|kind| kind == "provider_degraded"),
"a loadtime code invents no carrier: {kinds:?}"
);
}
#[test]
fn a_provider_failure_writes_its_carrier_and_then_the_verdict() {
let mut ledger = RecordingLedger::new();
let mut model = FailingModel {
code: kernel::AxCode::Provider,
};
let mut now = counter();
let mut interrupt = |_: SafePoint| Interrupt::None;
let mut invoke = |_: &ToolCall, _: TimeMs| {
Ok(ToolOutcome {
result: Payload::empty(),
attachments: Vec::new(),
})
};
let mut hooks = RunHooks {
now: &mut now,
interrupt: &mut interrupt,
checkpoint: None,
writes: &|_: &kernel::ToolCall| kernel::Writes::Domain,
invoke: &mut invoke,
wait: &mut |_: TimeMs| runtime::NextCall::Allowed,
deltas: None,
};
let frozen = drive(plan(), &mut ledger, &mut model, &mut hooks, &handoff())
.expect("a carrier code ends the run rather than escaping it");
assert!(matches!(frozen.completion(), Completion::Cancelled));
let kinds = ledger.kinds();
assert_eq!(kinds[kinds.len() - 3], "provider_degraded");
assert_eq!(kinds[kinds.len() - 2], "handoff_written");
assert_eq!(kinds.last().map(String::as_str), Some("run_frozen"));
}
#[test]
fn failures_in_a_row_back_off_and_a_halt_during_the_wait_stops_the_run() {
let attempts = std::rc::Rc::new(std::cell::RefCell::new(0));
let mut ledger = RecordingLedger::new();
let mut model = FlakyModel {
failures_left: 5,
calls: std::rc::Rc::clone(&attempts),
};
let mut now = counter();
let mut interrupt = |_: SafePoint| Interrupt::None;
let mut invoke = |_: &ToolCall, _: TimeMs| {
Ok(ToolOutcome {
result: Payload::empty(),
attachments: Vec::new(),
})
};
let mut waited: Vec<u64> = Vec::new();
let mut wait = |until: TimeMs| {
waited.push(until.value());
if waited.len() < 3 {
runtime::NextCall::Allowed
} else {
runtime::NextCall::Halted
}
};
let mut hooks = RunHooks {
now: &mut now,
interrupt: &mut interrupt,
checkpoint: None,
writes: &|_: &kernel::ToolCall| kernel::Writes::Domain,
invoke: &mut invoke,
wait: &mut wait,
deltas: None,
};
let frozen = drive(plan(), &mut ledger, &mut model, &mut hooks, &handoff()).unwrap();
let fired: Vec<(u64, u64)> = ledger
.lines
.iter()
.map(|line| serde_json::from_slice::<serde_json::Value>(line).unwrap())
.filter(|line| line["kind"] == "watchdog_fired")
.map(|line| {
(
line["t"].as_u64().unwrap(),
line["data"]["until_ms"].as_u64().unwrap(),
)
})
.collect();
let backoffs: Vec<u64> = fired.iter().map(|(t, until)| until - t).collect();
let untils: Vec<u64> = fired.iter().map(|(_, until)| *until).collect();
assert!(
backoffs
.iter()
.zip([500, 1_000, 2_000])
.all(|(wait, base)| (base..=base + base / 2).contains(wait))
&& backoffs.len() == 3,
"the wait doubles, plus at most half again of jitter: {backoffs:?}"
);
assert_eq!(waited, untils, "the run waits for the moment it records");
assert_eq!(*attempts.borrow(), 3, "no call goes out after the halt");
assert!(matches!(frozen.completion(), Completion::Cancelled));
}
#[test]
fn a_steer_inside_a_tool_wave_is_recorded_before_the_model_reads_it() {
let mut ledger = RecordingLedger::new();
let seen = std::rc::Rc::new(std::cell::RefCell::new(Vec::new()));
let mut model = ScriptedModel {
seen: std::rc::Rc::clone(&seen),
waves: vec![vec![call("t-1"), call("t-2")]],
};
let mut now = counter();
let mut interrupt = |point: SafePoint| match point {
SafePoint::BeforeToolCall { turn: 0, call: 1 } => Interrupt::Steer {
source: "user".to_owned(),
text: "measure it in metres".to_owned(),
},
_ => Interrupt::None,
};
let mut invoke = |_: &ToolCall, _: TimeMs| {
Ok(ToolOutcome {
result: Payload::empty(),
attachments: Vec::new(),
})
};
let mut hooks = RunHooks {
now: &mut now,
interrupt: &mut interrupt,
checkpoint: None,
writes: &|_: &kernel::ToolCall| kernel::Writes::Domain,
invoke: &mut invoke,
wait: &mut |_: TimeMs| runtime::NextCall::Allowed,
deltas: None,
};
drive(plan(), &mut ledger, &mut model, &mut hooks, &handoff()).unwrap();
let kinds = ledger.kinds();
let steered = kinds
.iter()
.position(|kind| kind == "steer_received")
.unwrap_or_else(|| panic!("the steer inside the wave is recorded: {kinds:?}"));
let called_again = kinds
.iter()
.rposition(|kind| kind == "model_called")
.unwrap();
assert!(
steered < called_again,
"recorded before the call that carries it: {kinds:?}"
);
assert!(
seen.borrow()[1].contains("measure it in metres"),
"and the next assembly carries it"
);
}
const READS: u32 = 3;
const ARRIVAL_WAIT: std::time::Duration = std::time::Duration::from_secs(5);
struct Meeting {
started: std::sync::Mutex<u32>,
arrived: std::sync::Condvar,
fewest_seen: std::sync::Mutex<u32>,
}
struct MeetingRead {
meta: kernel::ToolMeta,
meeting: Option<std::sync::Arc<Meeting>>,
}
impl kernel::Tool for MeetingRead {
fn meta(&self) -> &kernel::ToolMeta {
&self.meta
}
fn invoke(&self, _call: &ToolCall) -> Result<ToolOutcome, AxError> {
if let Some(meeting) = &self.meeting {
let mut started = meeting.started.lock().unwrap();
*started = started.saturating_add(1);
meeting.arrived.notify_all();
let (started, _) = meeting
.arrived
.wait_timeout_while(started, ARRIVAL_WAIT, |seen| *seen < READS)
.unwrap();
let mut fewest = meeting.fewest_seen.lock().unwrap();
*fewest = (*fewest).min(*started);
}
Ok(ToolOutcome {
result: Payload::empty(),
attachments: Vec::new(),
})
}
}
struct Placed {
bench: runtime::bench::ToolBench,
next: u64,
}
impl Placed {
fn with(meeting: Option<std::sync::Arc<Meeting>>) -> Placed {
Placed::of(Box::new(MeetingRead {
meta: read_meta(),
meeting,
}))
}
fn of(read: Box<dyn kernel::Tool>) -> Placed {
let domain = kernel::WriteDomain::new(vec![Address::parse("lab").unwrap()]).unwrap();
let mut bench = runtime::bench::ToolBench::new(domain);
bench.register(read).unwrap();
Placed { bench, next: 0 }
}
fn key(&mut self, call: &ToolCall) -> kernel::IdemKey {
let at = self.next;
self.next = at.saturating_add(1);
kernel::IdemKey::derive(
&RunId::from_bytes([7; 16]),
kernel::Seq::new(at),
call.id.as_bytes(),
)
}
}
struct OneByOne(Placed);
impl runtime::ConcurrentInvoke for OneByOne {
fn meta_of(&self, call: &ToolCall) -> Option<&kernel::ToolMeta> {
self.0.bench.meta_of(&call.name)
}
fn admit(&mut self, call: &ToolCall, t: TimeMs) -> runtime::Admitted {
let key = self.0.key(call);
runtime::Admitted::Answered(self.0.bench.invoke(call, &key, t).and_then(answer))
}
fn tool(&self, ticket: &runtime::bench::Ticket) -> Result<&dyn kernel::Tool, AxError> {
self.0.bench.tool_for(ticket)
}
fn account(
&mut self,
_call: &ToolCall,
ticket: runtime::bench::Ticket,
answered: Result<ToolOutcome, AxError>,
) -> Result<ToolOutcome, AxError> {
self.0.bench.account(ticket, answered).and_then(answer)
}
}
fn read_meta() -> kernel::ToolMeta {
kernel::ToolMeta {
name: ToolName::parse("read").unwrap(),
disclosure: "reads a file".to_owned(),
params: Payload::empty(),
effect: kernel::Effect::Read,
cost_tier: kernel::CostTier::Free,
timeout: None,
render: kernel::RenderIntent::Generic,
temporal: kernel::Temporal::Timeless,
}
}
fn answer(outcome: runtime::bench::BenchOutcome) -> Result<ToolOutcome, AxError> {
match outcome {
runtime::bench::BenchOutcome::Ran { outcome, .. }
| runtime::bench::BenchOutcome::Duplicate { outcome } => Ok(outcome),
runtime::bench::BenchOutcome::Refused { refusal } => Err(*refusal),
}
}
impl runtime::ConcurrentInvoke for Placed {
fn meta_of(&self, call: &ToolCall) -> Option<&kernel::ToolMeta> {
self.bench.meta_of(&call.name)
}
fn admit(&mut self, call: &ToolCall, t: TimeMs) -> runtime::Admitted {
let key = self.key(call);
match self.bench.clear(call, &key, t) {
Ok(runtime::bench::Clearance::Cleared(ticket)) => runtime::Admitted::Cleared(ticket),
Ok(runtime::bench::Clearance::Answered(outcome)) => {
runtime::Admitted::Answered(answer(outcome))
}
Err(refused) => runtime::Admitted::Answered(Err(refused)),
}
}
fn ahead(&self, call: &ToolCall) -> Option<&dyn kernel::Tool> {
self.bench.tool_named(&call.name)
}
fn tool(&self, ticket: &runtime::bench::Ticket) -> Result<&dyn kernel::Tool, AxError> {
self.bench.tool_for(ticket)
}
fn account(
&mut self,
_call: &ToolCall,
ticket: runtime::bench::Ticket,
answered: Result<ToolOutcome, AxError>,
) -> Result<ToolOutcome, AxError> {
self.bench.account(ticket, answered).and_then(answer)
}
}
fn three_reads_driven(invoke: &mut dyn runtime::ConcurrentInvoke) -> RecordingLedger {
let read = |id: &str| ToolCall {
id: id.to_owned(),
name: ToolName::parse("read").unwrap(),
args: Payload::empty(),
};
let mut ledger = RecordingLedger::new();
let mut model = ScriptedModel {
seen: std::rc::Rc::new(std::cell::RefCell::new(Vec::new())),
waves: vec![vec![read("r1"), read("r2"), read("r3")]],
};
let mut now = || -> Result<TimeMs, AxError> { Ok(TimeMs::new(0)) };
let mut interrupt = |_: SafePoint| Interrupt::None;
let mut hooks = RunHooks {
now: &mut now,
interrupt: &mut interrupt,
checkpoint: None,
writes: &|_: &kernel::ToolCall| kernel::Writes::Domain,
invoke,
wait: &mut |_: TimeMs| runtime::NextCall::Allowed,
deltas: None,
};
drive(plan(), &mut ledger, &mut model, &mut hooks, &handoff()).unwrap();
ledger
}
#[test]
fn three_reads_a_run_makes_in_one_wave_are_in_flight_together_and_leave_the_serial_ledger() {
let serial = three_reads_driven(&mut OneByOne(Placed::with(None)));
let meeting = std::sync::Arc::new(Meeting {
started: std::sync::Mutex::new(0),
arrived: std::sync::Condvar::new(),
fewest_seen: std::sync::Mutex::new(u32::MAX),
});
let concurrent = three_reads_driven(&mut Placed::with(Some(meeting.clone())));
assert_eq!(concurrent.lines, serial.lines);
assert_eq!(*meeting.fewest_seen.lock().unwrap(), READS);
}
#[test]
fn a_steer_after_assembly_leaves_the_sent_request_untouched() {
let mut ledger = RecordingLedger::new();
let seen = std::rc::Rc::new(std::cell::RefCell::new(Vec::new()));
let mut model = ScriptedModel {
seen: std::rc::Rc::clone(&seen),
waves: vec![vec![call("t-1")]],
};
let mut now = counter();
let mut interrupt = |point: SafePoint| match point {
SafePoint::BeforeCall { turn: 0 } => Interrupt::Steer {
source: "user".to_owned(),
text: "measure it in metres".to_owned(),
},
_ => Interrupt::None,
};
let mut invoke = |_: &ToolCall, _: TimeMs| {
Ok(ToolOutcome {
result: Payload::empty(),
attachments: Vec::new(),
})
};
let mut hooks = RunHooks {
now: &mut now,
interrupt: &mut interrupt,
checkpoint: None,
writes: &|_: &kernel::ToolCall| kernel::Writes::Domain,
invoke: &mut invoke,
wait: &mut |_: TimeMs| runtime::NextCall::Allowed,
deltas: None,
};
drive(plan(), &mut ledger, &mut model, &mut hooks, &handoff()).unwrap();
let seen = seen.borrow();
let first = seen[0].strip_suffix(']').unwrap();
assert!(
seen[1].starts_with(first),
"the second request extends the first:\n{}\n{}",
seen[0],
seen[1]
);
assert!(
seen[1].ends_with("Text { text: \"user: measure it in metres\" }] }]"),
"the steer lands after the result it arrived during: {}",
seen[1]
);
}
const WRITING: std::time::Duration = std::time::Duration::from_millis(500);
const READING: std::time::Duration = std::time::Duration::from_millis(500);
struct SlowRead;
impl kernel::Tool for SlowRead {
fn meta(&self) -> &kernel::ToolMeta {
static META: std::sync::OnceLock<kernel::ToolMeta> = std::sync::OnceLock::new();
META.get_or_init(read_meta)
}
fn invoke(&self, _call: &ToolCall) -> Result<ToolOutcome, AxError> {
std::thread::sleep(READING);
Ok(ToolOutcome {
result: Payload::empty(),
attachments: Vec::new(),
})
}
}
struct GeneratingModel {
waves: Vec<Vec<ToolCall>>,
}
impl Model for GeneratingModel {
fn call(&mut self, req: &ModelRequest) -> Result<ModelReturn, AxError> {
self.call_speculating(req, &mut |_: &kernel::Increment| {}, &mut |_: &ToolCall| {})
}
fn call_speculating(
&mut self,
_req: &ModelRequest,
_onto: kernel::Increments<'_>,
early: kernel::EarlyCalls<'_>,
) -> Result<ModelReturn, AxError> {
let calls = if self.waves.is_empty() {
Vec::new()
} else {
self.waves.remove(0)
};
calls.iter().for_each(|call| early(call));
if !calls.is_empty() {
std::thread::sleep(WRITING);
}
Ok(ModelReturn::bare(
message_payload(&[ContentBlock::Text {
text: "reading".to_owned(),
}])
.unwrap(),
calls,
))
}
}
fn one_read_while_generating(
invoke: &mut dyn runtime::ConcurrentInvoke,
) -> (RecordingLedger, std::time::Duration) {
let mut ledger = RecordingLedger::new();
let mut model = GeneratingModel {
waves: vec![vec![ToolCall {
id: "r1".to_owned(),
name: ToolName::parse("read").unwrap(),
args: Payload::empty(),
}]],
};
let mut now = counter();
let mut interrupt = |_: SafePoint| Interrupt::None;
let mut hooks = RunHooks {
now: &mut now,
interrupt: &mut interrupt,
checkpoint: None,
writes: &|_: &kernel::ToolCall| kernel::Writes::Domain,
invoke,
wait: &mut |_: TimeMs| runtime::NextCall::Allowed,
deltas: None,
};
let began = std::time::Instant::now();
drive(plan(), &mut ledger, &mut model, &mut hooks, &handoff()).unwrap();
(ledger, began.elapsed())
}
#[test]
fn a_read_handed_over_while_the_model_writes_runs_during_the_writing() {
let (serial, _) = one_read_while_generating(&mut OneByOne(Placed::of(Box::new(SlowRead))));
let (speculated, took) = one_read_while_generating(&mut Placed::of(Box::new(SlowRead)));
assert!(
took < WRITING + READING,
"the read waited for the model to finish writing: the run took {took:?}"
);
assert_eq!(speculated.lines, serial.lines);
}