use std::sync::Mutex;
use io_harness::{
run_with_observed, Anthropic, ApproveAll, EventKind, Flow, Observer, OpenRouter, Policy,
Provider, RunEvent, Store, TaskContract, WebAccess,
};
#[derive(Default)]
struct Watcher {
used: Mutex<Vec<(String, String, bool)>>,
}
impl Observer for Watcher {
fn event(&self, event: &RunEvent) -> Flow {
if let EventKind::ServerToolUsed { provider, tool, ok } = &event.kind {
println!(
" [event] {provider} ran {tool}: {}",
if *ok { "ok" } else { "FAILED" }
);
self.used
.lock()
.unwrap()
.push((provider.clone(), tool.clone(), *ok));
}
Flow::Continue
}
}
#[tokio::main]
async fn main() -> io_harness::Result<()> {
let has_anthropic = ["ANTHROPIC_API_KEY", "ANTHROPIC_MODEL"]
.iter()
.all(|k| std::env::var(k).is_ok_and(|v| !v.trim().is_empty()));
match Anthropic::from_env().and_then(|p| {
if has_anthropic {
Ok(p)
} else {
Err(io_harness::Error::Config("no anthropic key".into()))
}
}) {
Ok(provider) => {
println!("=== provider: anthropic (search + fetch, with a domain filter) ===");
let web = WebAccess::search()
.with_fetch()
.max_uses(4)
.allow("docs.rs")
.allow("crates.io")
.allow("github.com");
run(&provider, web).await
}
Err(_) => {
println!("=== provider: openrouter (search only, no domain filter) ===");
println!(" (set ANTHROPIC_API_KEY to exercise fetch and the domain filter too)");
run(&OpenRouter::from_env()?, WebAccess::search().max_uses(4)).await
}
}
}
async fn run<P: Provider>(provider: &P, web: WebAccess) -> io_harness::Result<()> {
let dir = tempfile::tempdir().expect("a temp workspace");
let root = dir.path();
let db = root.join("trace.db");
let store = Store::open(&db)?;
let policy = Policy::default()
.layer("live")
.allow_read("*")
.allow_write("*");
println!(" declared: {web:?}");
let contract = TaskContract::workspace(
"Look up what the latest published version of the `io-harness` crate is on \
crates.io, and what its most recent release added. Search the web — do not \
answer from memory, because your training data is older than the answer. \
Say the version number and one sentence about the release, and cite where \
you found it.",
root,
)
.with_max_steps(6)
.with_web(web);
let watcher = Watcher::default();
let result =
run_with_observed(&contract, provider, &store, &policy, &ApproveAll, &watcher).await?;
println!(" outcome: {:?}", result.outcome);
let used = watcher.used.lock().unwrap().clone();
assert!(
!used.is_empty(),
"the provider ran no server tool at all. Either the model chose not to \
search — rerun; the prompt asks it to — or the vendor ignored the \
declaration, which is the finding worth recording"
);
assert!(
used.iter().any(|(_, _, ok)| *ok),
"every provider-executed call failed: {used:?}. A failure inside a 200 is \
recorded honestly by design, but a run where all of them broke proves \
nothing about the happy path"
);
let cited = store.citations(result.run_id)?;
println!(" {} source(s) cited:", cited.len());
for citation in &cited {
println!(
" {} — {}",
citation.title.as_deref().unwrap_or("(no title)"),
citation.url
);
}
assert!(
!cited.is_empty(),
"the provider searched but cited nothing. The citation parser is what turns \
a search into a checkable answer, so an empty list here is a defect in this \
crate, not in the model"
);
let calls = store.server_tool_calls(result.run_id)?;
println!(" {} provider-executed call(s):", calls.len());
for call in &calls {
println!(
" {} {} — {}",
call.provider,
call.tool,
call.error.as_deref().unwrap_or("ok")
);
}
let requests: u64 = store
.provider_calls(result.run_id)?
.iter()
.filter_map(|c| c.usage)
.map(|u| u.server_tool_requests)
.sum();
println!(" server_tool_requests reported by the vendor: {requests}");
assert!(
!calls.is_empty(),
"the run must have left a `server_tool_calls` row: that is the \
vendor-independent record of a provider-executed call, and unlike the \
usage counter it does not depend on the vendor choosing to report one"
);
if requests == 0 {
println!(
" NOTE: this vendor reports no per-request counter, so the meter stays \
at zero. The search itself is recorded above."
);
}
let summary = result.summary(&store)?;
println!(" spend: {summary:?}");
println!("\nOK — a live answer, cited, and recorded");
Ok(())
}