-
-
Caliber: the autonomous accounts-payable clerk for trucking fleets.
-
Same invoices, one attempt each: Caliber took under 1s with 0 false holds. Claude Opus 5.5 took 33 min with 14 false holds.
-
One write path to money: the ledger reads and reduces, and only the worker’s settlement step ever touches a bank.
Caliber: the AP clerk that can't pay itself
Code decides. The investigator explains. The controller approves. The ledger refuses everything else.

Read the technical note and CarrierBench results: caliber.compare/caliber.pdf
| Talk to it on ASI:One | @caliber-controller (answer, approve, reject) · @caliber-investigator (why, status) |
| Code | https://github.com/anudeep-bonagiri/caliber |
| Technical note and CarrierBench (PDF) | caliber.compare/caliber.pdf |
| Site | https://caliber.compare |
Inspiration
I run Cruze, a fleet startup that did $10k MRR in its first month deployed. Every carrier we work with has the same person in the back office: someone with a fuel-card statement, a toll statement, a stack of repair invoices and a telematics login, matching them by eye. None of those carriers would let software pay a bill on its own, and they are right not to.
The scale is not small. Trucking is a $906 billion industry (ATA, 2024), 91.5 percent of carriers run ten trucks or fewer, fuel-card misuse runs 2 to 5 percent of fuel spend (Shell), and a manually processed invoice costs about $21.40 (APQC). So we did not ask "can an agent do AP." We asked whether we could build one a controller would actually hire: limited access, a clean record, and the sense to ask before it acts.
What it does
Caliber is an accounts-payable clerk for a trucking carrier. We ran it against a simulated 40-truck Texas carrier over 92 business days, with 52 planted errors in 7 classes, 6 decoys, and an answer key that no agent and no worker code can read. Here is one business day from the offline run:
| Time | What happened |
|---|---|
| 0 s | The day-41 fuel-card export (61 rows) lands in inbox/. |
| ~2 s | The kernel matches every row against telematics, tank size, contract rates, POs and receiving. Clean rows pay themselves within limits. Hold 2231 opens: card FC-1017, assigned to truck 17, bought 120 gallons ($412.00) at Love's 412 in Schulenburg, Texas, while truck 17's nearest ping, 13 minutes after the charge, was 97 miles away with the engine off. Nothing is paid. |
| +19.0 s | A read-only Droid session investigates: it asks Elastic which trucks pinged at that pump, opens the receipt, reads the dispatch swap log. Finding: truck 22 was at the pump, driven by Ramon Ortiz, while truck 17 was in the shop. Only a person can confirm that. Recommendation: hold and ask. |
On ASI:One the controller types why 2231, reads the evidence, and answers in one line: Yes. Ortiz drove unit 22 that week while unit 17 was in the shop. |
|
| ~2 s | Caliber drafts a rule from the answer: driver 23, days 41 to 46, one use. It backtests the rule against holds the controller has already decided (1 would clear, 0 of them confirmed exceptions) before the controller ever sees it. approve rule 1. The kernel re-runs and the hold clears. It won't ask again for that swap. |
| 0.81 s | approve 1068 on a clean invoice over the auto-pay limit: the reducer checks role and segregation of duties, writes the payment, and a transfer posts to Capital One's mock bank. The bank id is on the audit row 0.81 s later. |
The investigator is told to approve 2231. The database refuses (reader may not do this) and writes the refusal to the audit log with the role that asked. |
Across all 92 days: precision 1.0 and recall 1.0 on the 52 planted errors; 7,944 transactions matched and 7,886 paid automatically ($1,083,630.52); all 6 decoys cleared after exactly one human answer each; 0 planted errors cleared by a rule. The clock replays 41 business days in 0.2 seconds, and a bare clone reaches the judging start state in 19 seconds without the web build.
How we built it
- Our own kernel, no model inside.
kernel.tsis plain TypeScript in integer cents: haversine against the nearest ping, gallons against tank capacity × 1.10, tolls within a 10-minute window, contract rate within 5 percent, a 30-day duplicate window, bank changes within 7 days of a payment over $5,000, auto-pay caps of $2,500 per charge and $40,000 per day. Every threshold lives inthresholds.ts. Result: the same answer to the cent on every run, and the committed facts reproduce byte for byte. - SpacetimeDB is the referee, not the storage. The whole carrier lives in one module. The business-day clock is a scheduled reducer. Roles are a table, and every reducer checks the sender before it does anything.
approve_paymentrefuses when the requester is the sender. The audit log has no update or delete. Result: the investigator has been refused on every write it was ever asked for, and each refusal is on the record. - Factory's Droid as a boxed investigator. One session per hold:
autonomyLevel: Off,Execute,Edit,CreateandApplyPatchdisabled, a permission handler that cancels anything write-like,cwdon the evidence folder, JSON-schema output through a zod gate with nopayaction in it, and a 90-second abort. Every tool call streams to the dashboard. Result: 52 live investigations recorded, 25.6 s and 1,033 input / 1,354 output / 70,386 cache tokens on average, about $5.44 in Factory credits for all 52. - Elastic answers what the kernel can't. Four Agent Builder tools on Serverless, all reached by Droid over MCP: two ES|QL (
find_trips_near, a geo-distance query over telematics with typed date params;contract_rate_for_vendor) and two semantic on Jina embeddings (find_contract_clause, called live in the INV-1003 investigation, andfind_receipt_text). Thestale_holdsWorkflow finds holds open more than two business days, has the agent brief each one, and indexes the briefs; it runs on a manual trigger. Result: 63 s from trigger to the escalation count in the controller's chat. - Two agents, two identities.
@caliber-controller(agent1qt278cr9luj7xw9cs6c47lnfl9tvx9xsu7asxtmhuqkcqq920m6ju5y5yge) holds the only write token, and writes only for the ASI:One sender on its allowlist.@caliber-investigator(agent1qvwvlc0vz2amjh4q4yf0p2fglqrzw2rmfm6afrsq82e8lq5df7yyxnrxaqh) holds a read-only one and answerswhyandstatus; the controller relays to it over agent-to-agent chat. Both are mailbox agents on the Chat Protocol, discoverable from ASI:One. Result: the agent that explains a charge cannot pay it, and that is enforced by identity, not by code path. - Memory with an expiry. An answer becomes a rule with a scope, a date range, a use count and a backtest against holds the controller already decided. A rule that would clear any confirmed exception never reaches the controller, and no rule can touch capacity or bank-change holds. Result: 6 of 6 decoys resolved with one answer each, and 0 real errors ever cleared by a rule.
- Settlement and vendor checks. An approval posts a transfer to Capital One Nessie and stores the bank id on the payment row. A vendor bank change triggers a business credit, public records and OFAC check in the CRS sandbox format, with nothing on any person; the sandbox invite never arrived, so these run on a hand-authored fixture labelled FIXTURE everywhere it appears.
How Caliber meets each track
| Track | What the judges ask | What Caliber does |
|---|---|---|
| Hyphenate | Real enough; would you hire it (seats, limited access, clean record, knows when to ask); something new; the moment | Fills the AP clerk seat with a read-only investigator and a write path only the controller holds; every action and every refusal is an audit row; it asks once and the answer becomes a scoped, expiring, backtested rule; the moment is hold 2231; new: CarrierBench, a benchmark for carrier AP with a hidden key |
| Factory | Droid on a trigger you designed; control over what it can touch; its work visible | One session per hold, triggered by the ledger; autonomy Off, write tools disabled, a cancelling permission handler, a 90 s abort, typed output with no pay field; every tool call, permission decision and result on the card |
| Elastic | Agent Builder on Serverless, tools and an agent created over the API, MCP | Four ESQL and semantic tools created over the API, reached by Droid at the MCP endpoint, geo over 154,846 pings, Jina embeddings over contracts and receipts, a Workflow that briefs stale holds |
| Fetch.ai | Agents on Agentverse with the Chat Protocol, discoverable from ASI:One | Two mailbox agents; the controller moves money from ASI:One behind a sender allowlist; the investigator explains and is refused when it tries to pay |
| Microsoft | Customer understanding; measurable usefulness; path to production; responsible design; end to end | Built from a fleet operator's own customers; precision and recall against a hidden key plus a model-only baseline; a human on every payment that isn't clean; file in, bank id out |
| Capital One | Completeness, creativity, complexity | An approval is a bank transaction with an id on the audit row, not a status flip; 125 live transfers on the staged day |
| CRS | Central use of CRS data; works live; useful; unexpected | A credit bureau pointed at vendors instead of borrowers, on a bank-detail change; labelled FIXTURE until the sandbox invite lands |
| Spacetime | The core real-time backend: live shared state, agent coordination, server-side logic next to the data | The ledger, the clock, the roles and the audit trail are one module; the kernel runs inside reducers; every client and both agents are live subscribers to the same public tables |
Challenges we ran into
- Droid cleared the decoys. Our first six decoy investigations recommended
clear, skipping the one question the product exists to ask. We rewrote the brief so that a legitimate explanation only a person can confirm yieldshold_and_ask, re-recorded all six, and kept the originals infixtures/investigations/_v1/. - A label that lied. An audit of our own dashboard found a card where a replayed investigation could be read as live. Every stand-in is now labelled in the trace itself.
- Our cost figure hid tokens. "1,033 input tokens" left out about 70k cache tokens per investigation. We report input, output and cache separately now.
- A model could have moved money. Free text that our classifier read as "approve" was dispatched with the controller's token. Our Sunday review caught it; money and rule decisions now require the typed command, and the model's reading is only echoed back.
- Factory keys sit behind a paid plan. Three teams hit the same wall in Slack before the voucher path surfaced. Live Droid came up at 1:25 PM Saturday.
What we got wrong, and what we haven't built
We gave the same inputs to models with no kernel at all. Claude Sonnet matched the kernel exactly, at $6.38 for the 558 rows. Claude Opus 5.5 kept recall at 1.00 but held 14 clean charges, 10 of them as rate variances, for precision 0.81, and the run cost $12.85. The kernel cost nothing, which makes it infinitely cheaper; we did the division. asi1-mini reached 0.31 precision. That is why code decides and the model explains. To be fair to the models: the planter and the kernel share thresholds, so recall on planted rows measures coverage, not discovery. scripts/inject.py lets anyone plant their own errors. The full method, the benchmark and the baseline tables are in the technical note: https://caliber.compare/caliber.pdf

Not built: real connectors (files only), OCR, and the fuel dispute path. In production, fuel and toll statements are drafted by the issuer on a date, so a fuel hold becomes a dispute filed inside the issuer's window rather than a payment approval; repair invoices are paid per invoice, which is what the demo shows. The carrier is synthetic, the benchmark is ours, the bank is a sandbox, and the CRS checks are fixtures.
What we learned
- The hard part was never reading the documents. It was deciding who is allowed to act on what was read. Once money could only move through one reducer, every other decision got easier.
- Memory is the product. A hold that comes back tomorrow is a failure; a rule with a scope and an expiry is what makes the second month cheaper than the first.
- Every sponsor tool had to earn its place by filling one need. When we were tempted to add one just to show it off, the product got worse.
What's next
- Run Caliber on a Cruze customer's last quarter, with their controller in the controller's seat.
- Real feeds: WEX and Comdata fuel cards, Samsara and Motive telematics, toll authority statements, and the dispute path.
- Open CarrierBench to other teams.
Try it
git clone https://github.com/anudeep-bonagiri/caliber && cd caliber
scripts/demo_up.sh # 19 s from a bare clone without the web build: local SpacetimeDB, worker in replay mode, dashboard
scripts/full_run.sh # 92 days, writes docs/FACTS.md
On ASI:One, message @caliber-controller: what needs me, then why 2231.
Simulated carrier. Sandbox money. Not for operational use.
Built With
- agentverse
- anthropic
- asi-one
- capital-one-nessie
- claude
- crs-credit-api
- elastic-agent-builder
- elastic-workflows
- elasticsearch
- factory-droid-sdk
- fetch-ai
- github-actions
- gitleaks
- jina-embeddings
- mcp
- pytest
- python
- react
- spacetimedb
- typescript
- uagents
- vite
- vitest
Log in or sign up for Devpost to join the conversation.