use std::path::Path;
use io_harness::tools::documents::xlsx;
use io_harness::tools::Workspace;
use io_harness::{run_with, ApproveAll, Policy, RunResult, Store, TaskContract, Verification};
const BOOK: &str = "ledger.xlsx";
const SHEET: &str = "Q3";
const TOTAL_CELL: &str = "C12";
const ROWS: &[(&str, &str, u32)] = &[
("Jul", "Kattegat", 41_293),
("Jul", "Skagerrak", 17_884),
("Jul", "Oresund", 9_442),
("Aug", "Kattegat", 38_017),
("Aug", "Skagerrak", 22_651),
("Aug", "Oresund", 11_309),
("Sep", "Kattegat", 44_760),
("Sep", "Skagerrak", 19_223),
("Sep", "Oresund", 13_875),
];
fn total() -> u32 {
ROWS.iter().map(|(_, _, amount)| amount).sum()
}
fn sheet_rows() -> Vec<Vec<String>> {
let mut rows = vec![vec![
"Month".to_string(),
"Berth".to_string(),
"Tonnage".to_string(),
]];
rows.extend(ROWS.iter().map(|(month, berth, amount)| {
vec![month.to_string(), berth.to_string(), amount.to_string()]
}));
rows.push(vec![String::new(), String::new(), String::new()]);
rows.push(vec!["TOTAL".to_string(), String::new(), String::new()]);
rows
}
fn workspace() -> io_harness::Result<tempfile::TempDir> {
let dir = tempfile::tempdir()?;
xlsx::write_new(&Workspace::new(dir.path()), BOOK, SHEET, &sheet_rows())?;
Ok(dir)
}
fn goal() -> String {
format!(
"The workspace holds a spreadsheet {BOOK} with one sheet named {SHEET:?}. \
Read it, add up every value in the Tonnage column, and write that total \
into cell {TOTAL_CELL} using the xlsx_set_cell tool, which keeps the rest \
of the workbook as it is. Write the number on its own, with no thousands \
separator, no currency symbol and no formula. Do not create a new \
workbook and do not change any other cell."
)
}
fn fingerprint(root: &Path) -> u64 {
let bytes = std::fs::read(root.join(BOOK)).unwrap_or_default();
bytes.iter().fold(0xcbf2_9ce4_8422_2325, |h, b| {
(h ^ u64::from(*b)).wrapping_mul(0x1_0000_01b3)
})
}
fn report(store: &Store, result: &RunResult) -> io_harness::Result<()> {
println!(" outcome: {:?}", result.outcome);
for step in store.steps(result.run_id)? {
let call = step.tool_call.replace('\n', " ");
println!(
" step {}: {} | {}",
step.step,
step.decision,
&call[..call.len().min(160)]
);
}
for e in store
.events(result.run_id)?
.iter()
.filter(|e| e.kind == "refusal")
{
println!(
" refused: {} {} (rule {})",
e.act,
e.target,
e.rule.clone().unwrap_or_else(|| "-".into())
);
}
Ok(())
}
fn cell_of(text: &str, cell: &str) -> Option<String> {
let column = cell.chars().next()?;
let row = cell[1..].parse::<usize>().ok()?;
let index = (column as u8).checked_sub(b'A')? as usize;
text.lines()
.find(|l| l.split('\t').next() == Some(row.to_string().as_str()))?
.split('\t')
.nth(index + 1)
.map(str::to_string)
}
fn inspect(root: &Path) -> io_harness::Result<()> {
let ws = Workspace::new(root);
let text = xlsx::read_sheet(&ws, BOOK, Some(SHEET))?;
let total = total().to_string();
let survived = ROWS
.iter()
.filter(|(_, berth, amount)| text.contains(berth) && text.contains(&amount.to_string()))
.count();
println!(
" original rows still readable: {survived}/{} {}",
ROWS.len(),
if survived == ROWS.len() {
"(the edit preserved the workbook)"
} else {
"(the workbook was rewritten, not edited — the gate would still pass)"
}
);
println!(
" the total {total} is somewhere in the sheet: {}",
text.contains(&total)
);
println!(
" and it is in {TOTAL_CELL}, the cell it was asked for: {}",
cell_of(&text, TOTAL_CELL).as_deref() == Some(total.as_str())
);
println!(" sheet as the model would read it back:");
for line in text.lines() {
println!(" {line}");
}
Ok(())
}
#[tokio::main]
async fn main() -> io_harness::Result<()> {
let only = std::env::args().nth(1).unwrap_or_default();
let provider = io_harness::OpenRouter::from_env()?;
let total = total().to_string();
println!(
"workbook: {BOOK}, sheet {SHEET:?}, {} data rows",
ROWS.len()
);
println!("total the agent has to arrive at: {total}");
println!("cell it has to land in: {TOTAL_CELL}");
if only != "control" {
println!("\n=== run 1: edit the workbook, under a policy that allows it ===");
let dir = workspace()?;
let contract = TaskContract::workspace(
goal(),
dir.path(),
Verification::DocumentContains {
file: BOOK.into(),
needle: total.clone(),
},
)
.with_max_steps(6)
.with_token_budget(200_000);
let policy = Policy::default()
.layer("app")
.allow_read("*")
.allow_write("*");
let store = Store::open(dir.path().join("runs.db"))?;
let result = run_with(&contract, &provider, &store, &policy, &ApproveAll).await?;
report(&store, &result)?;
inspect(dir.path())?;
}
if only != "positive" {
println!("\n=== run 2: the same contract, with the workbook denied to writes ===");
let dir = workspace()?;
let before = fingerprint(dir.path());
let contract = TaskContract::workspace(
goal(),
dir.path(),
Verification::DocumentContains {
file: BOOK.into(),
needle: total,
},
)
.with_max_steps(4)
.with_token_budget(200_000);
let policy = Policy::default()
.layer("app")
.allow_read("*")
.allow_write("*")
.deny_write(BOOK);
let store = Store::open(dir.path().join("runs.db"))?;
let result = run_with(&contract, &provider, &store, &policy, &ApproveAll).await?;
report(&store, &result)?;
let after = fingerprint(dir.path());
println!(
" workbook bytes unchanged: {} ({before:016x} -> {after:016x})",
before == after
);
}
Ok(())
}