#![recursion_limit = "256"]
use car_engine::Runtime;
use car_ir::ActionProposal;
use serde_json::Value;
use std::sync::Arc;
use tempfile::TempDir;
struct ScriptedExecutor;
#[async_trait::async_trait]
impl car_engine::ToolExecutor for ScriptedExecutor {
async fn execute(&self, tool: &str, _params: &Value) -> Result<Value, String> {
if tool.starts_with("bad") {
Err(format!("{tool} is broken"))
} else {
Ok(serde_json::json!({ "ok": true }))
}
}
}
fn proposal(actions: Vec<Value>) -> ActionProposal {
serde_json::from_value(serde_json::json!({ "source": "test", "actions": actions }))
.expect("proposal deserializes")
}
fn call(id: &str, tool: &str) -> Value {
serde_json::json!({ "id": id, "type": "tool_call", "tool": tool, "parameters": {} })
}
async fn runtime_with_store(dir: &TempDir) -> Runtime {
let store = Arc::new(car_memgine::TrajectoryStore::new(dir.path()));
let rt = Runtime::new().with_trajectory_store(store);
rt.set_executor(Arc::new(ScriptedExecutor)).await;
for t in ["good_a", "good_b", "bad_a"] {
rt.register_tool(t).await;
}
rt
}
#[tokio::test]
async fn executing_a_proposal_persists_a_trajectory() {
let dir = TempDir::new().unwrap();
let rt = runtime_with_store(&dir).await;
rt.execute(&proposal(vec![call("a0", "good_a")])).await;
let stored = rt.trajectory_store().expect("store attached").load_all();
assert_eq!(
stored.len(),
1,
"execution must persist exactly one trajectory"
);
assert!(stored[0]
.events
.iter()
.any(|e| e.tool.as_deref() == Some("good_a") && e.kind == "action_succeeded"));
}
#[tokio::test]
async fn derived_rates_reflect_what_actually_happened() {
let dir = TempDir::new().unwrap();
let rt = runtime_with_store(&dir).await;
for i in 0..4 {
rt.execute(&proposal(vec![call(&format!("g{i}"), "good_a")]))
.await;
rt.execute(&proposal(vec![call(&format!("b{i}"), "bad_a")]))
.await;
}
let fb = rt.tool_feedback(30).expect("store attached");
assert_eq!(fb.tool_dispatch_counts["good_a"], (4, 4));
assert_eq!(fb.tool_dispatch_counts["bad_a"], (0, 4));
assert!(
(fb.rate("good_a") - 5.0 / 6.0).abs() < 1e-9,
"{}",
fb.rate("good_a")
);
assert!(
(fb.rate("bad_a") - 1.0 / 6.0).abs() < 1e-9,
"{}",
fb.rate("bad_a")
);
assert!(fb.rate("good_a") > fb.rate("bad_a"));
}
#[tokio::test]
async fn a_tool_never_executed_gets_no_derived_rate() {
let dir = TempDir::new().unwrap();
let rt = runtime_with_store(&dir).await;
rt.execute(&proposal(vec![call("a0", "good_a")])).await;
let fb = rt.tool_feedback(30).unwrap();
assert!(!fb.tool_success_rates.contains_key("good_b"));
assert!(fb.tool_success_rates.contains_key("good_a"));
}
#[tokio::test]
async fn rates_feed_monte_carlo_and_move_the_verdict() {
let dir = TempDir::new().unwrap();
let rt = runtime_with_store(&dir).await;
for i in 0..10 {
rt.execute(&proposal(vec![call(&format!("g{i}"), "good_a")]))
.await;
rt.execute(&proposal(vec![call(&format!("b{i}"), "bad_a")]))
.await;
}
let fb = rt.tool_feedback(30).unwrap();
let plan: ActionProposal = serde_json::from_value(serde_json::json!({
"source": "test",
"actions": [
{"id": "fetch", "type": "tool_call", "tool": "bad_a", "parameters": {},
"expected_effects": {"data": 1}},
{"id": "use", "type": "tool_call", "tool": "good_a", "parameters": {},
"state_dependencies": ["data"], "expected_effects": {"done": true}},
],
}))
.unwrap();
assert_eq!(
car_verify::simulate(&plan, None).get("done"),
Some(&Value::from(true))
);
let mc = car_verify::simulate_monte_carlo(
&plan,
None,
&fb.tool_success_rates,
None,
&car_verify::MonteCarloConfig {
trials: 20_000,
..Default::default()
},
);
assert!(
mc.p_all_effects_landed < 0.15,
"expected a low completion probability from real history, got {}",
mc.p_all_effects_landed
);
let fetch = &mc.action_outcomes[0];
let use_ = &mc.action_outcomes[1];
assert!(
fetch.p_failed > 0.8,
"fetch should mostly fail: {}",
fetch.p_failed
);
assert!(
use_.p_rejected > 0.8,
"the dependent should be starved, not failing on its own: {}",
use_.p_rejected
);
assert!(use_.p_failed < 0.15);
}
#[tokio::test]
async fn no_store_means_no_feedback_rather_than_empty_feedback() {
let rt = Runtime::new();
rt.set_executor(Arc::new(ScriptedExecutor)).await;
rt.register_tool("good_a").await;
rt.execute(&proposal(vec![call("a0", "good_a")])).await;
assert!(rt.trajectory_store().is_none());
assert!(rt.tool_feedback(30).is_none());
}