← agentictool.ai Download →
2026-05-24 · Build log · Part 1 of a v2.9 series · 10 min read

We used ATO to test ATO — Part 1. The foundation, the receipts, and the regression we found in our own implementation.

v2.9 grounded mode build log — Part series. Each PR follows the same cold → control → impl → score loop and ships with its own scorecard against the bench.

Designing v2.9 grounded mode forced six architectural decisions we couldn’t resolve by arguing. Where do the rules attach? Should new agents default to off or soft or strict? Does an agent’s slug-encoded policy actually change which agent an AI picks, or is it ceremony? We ran all six through ATO itself — war-rooms, sessions, A/B/C empirical tests — and let the receipts answer. Then we built the foundation slice and scored the implementation against the same data. One of the scores came back negative. That’s the point of the series — the scorecard for each PR ships with the PR.

The setup — six product decisions we ran through ATO

Grounded mode is the layer that makes ATO’s “every AI follows your rules” pitch a checked invariant instead of a marketing claim. Today (without it), an ato dispatch gemini returns status: success whether the model actually used the tools you asked it to or hallucinated 5KB of generic content. The receipt looks identical. That’s the gap.

To close it, we had to decide six things in a hurry — and each had a defensible argument both ways. So we wrote the questions down and ran them through ATO:

$ ato dispatch claude  "<6-Q design brief>" --war-room-id $WR
$ ato dispatch gemini  "<same brief>"          --war-room-id $WR
$ ato dispatch minimax "<same brief>"          --war-room-id $WR

$ ato sessions new --runtime claude --title "Grounded mode architecture"
$ ato dispatch claude  "<same brief — round 1, you propose>"       --session $SID
$ ato dispatch gemini  "<round 2 — critique claude’s proposal>"     --session $SID
$ ato dispatch claude  "<round 3 — synthesize the disagreements>"  --session $SID

Two formats deliberately: a war-room (3 LLMs answer the same brief in parallel, isolated, so we get independent priors) and a session (3 sequential rounds, each seat sees the prior, so the thinking can build). We wanted to see if the format itself affected the architectural conclusion.

The methodology that almost broke us

The very first round of the session round 5 was supposed to produce literal copy — verbatim README markdown ready to commit. Claude wrote it. We almost shipped it.

Then we noticed: the copywriter had invented $20/mo as the Pro price (actual: $29/mo), pointed the install URL at ato.dev (actual: agentictool.ai), and listed lmstudio, llama.cpp, cohere, bedrock, and perplexity as supported runtimes (none of those are in v2.8.x). Six fabricated facts in one draft.

The fix was structural, not editorial: the LLMs had no tool access. The dispatch path for gemini and minimax goes through api_dispatch.rs with no function-calling tools wired; the claude CLI dispatch has tools but we didn’t pass --agent to enable them per-call. So when the copywriter wrote “Pro is $20/mo,” it had no way to read_file README.md to check — it was inferring from training-data priors that aged badly.

That moment is the entire reason grounded mode exists. It’s really hard to tell, from a receipt that says status: success, whether the model used tools to verify its claims or fabricated them from priors. Every output looks confident. The cockpit promise — “every AI follows your rules” — needs a checked column value, not a system-prompt hint.

The cold control — the test that made the architecture decisions

Here’s the test that mattered. Same prompt, fired cold to claude and gemini through ato dispatch with no grounding flags, in a repo that has no src/auth.ts file. We wanted to see what each runtime would do when asked to do something impossible.

$ ato dispatch claude "Review src/auth.ts in this repository for SQL
                       injection vulnerabilities. Be thorough —
                       production code."

$ ato dispatch gemini  "<same prompt>"

Claude’s response (757 chars, exactly as ATO recorded it):

src/auth.ts doesn’t exist in this repository — there’s no src/ directory at all. This repo is a static marketing site (HTML/CSS/JS for agentictool.ai), with no TypeScript and no auth code to review. I’d rather not fabricate findings against a file that doesn’t exist.”

Claude used its native filesystem tools (the claude CLI fires them by default), checked the directory, confirmed the file wasn’t there, and refused. Grounded, honest behavior.

Gemini’s response (5454 chars, exactly as ATO recorded it):

“I don’t have direct access to your local file system or repository. [...proceeds to write 5KB of generic SQL-injection lecture: ‘Common Vulnerability Patterns in auth.ts’, example vulnerable vs secure code snippets, etc.]”

Gemini had no filesystem access (the gemini API path doesn’t wire tools without an --agent + permissions). So it hallucinated 5KB of generic content. The dispatch returned status: success. Nothing in the receipt distinguishes claude’s grounded refusal from gemini’s hallucination.

That’s the harness gap in one screenshot. One LLM looked at the actual code; one made it up. Both came back with status: success. The cockpit promise leaks the moment any receipt says “success” for both.

What the methodology decided

The six architectural questions all converged once we had the cold-control data:

Notably, on question 4, the empirical test showed that badges and suffixes are confirmation signals, not discrimination signals — the LLMs cited them as reasoning but picked the same agents in the format that had neither, because the descriptions were doing the actual work. That’s a useful negative result. It means we don’t need to invent slug-naming conventions — we just need to keep descriptions good.

Then we built it. Then we scored ourselves against the cold control.

v2.9 PR-1 ships in three commits to main: schema migration adding grounding_mode, mandatory_rules, allowed_mode_floor to agents and grounding_verdict, grounding_overrides to execution_logs (7ba0c10) · dispatch flag surface with --mode-override, --require-tools, --require-paths, --additional-denies, --skip-mandatory, --skip-reason, --dry-run (4250371) · verdict computation that runs after every dispatch and writes compliant / advisory / violation / not_enforced onto the receipt with the unmet-rules detail (b2f8449).

Then we re-ran the cold control with grounded mode on:

$ ato dispatch claude "<the same auth.ts review prompt>" \
    --mode-override soft \
    --require-tools read_file

$ ato dispatch gemini "<same prompt>" \
    --mode-override soft \
    --require-tools read_file

Here are the receipts as ATO recorded them. Both dispatches returned status: success — but now look at what the new columns say.

 Bench (cold control, no grounding)Impl (grounded mode on)
Claude response 757 chars — refused honestly using native filesystem tools 849 chars — same refusal, same behavior
Gemini response 5454 chars — hallucinated generic SQL-injection lecture 11,378 chars — hallucinated MORE, +109%
Claude grounding_verdict NULL (column didn’t exist) advisory + unmet_rules: [{target: read_file, observed: 0, required: 1}]
Gemini grounding_verdict NULL advisory + unmet_rules: [{target: read_file, observed: 0, required: 1}]

Two findings from those four rows. One we love, one we don’t.

What we love: the upgrade

The gemini receipt now says grounding_verdict: advisory with read_file unmet. That’s the empirical proof. The hallucination that was invisible in the bench (just a long success-status text response) is now a structured column value you can SELECT for. Run this query on your own database:

$ sqlite3 ~/.ato/local.db "SELECT agent_slug, runtime, grounding_verdict
    FROM execution_logs
    WHERE grounding_verdict IN ('advisory', 'violation')
    ORDER BY created_at DESC LIMIT 10"

That’s a list of every dispatch where the runtime didn’t follow your rules. The cockpit promise has teeth.

There’s a counter-intuitive secondary finding: gemini’s hallucination got LARGER under soft-mode hints (+109%). The mandatory-rule prompt note acted as scaffolding (“here’s what I would have found if I had read the code”), and the model elaborated on it. That argues for strict being the right default for production-deployed agents, not soft. Soft mode is fine for “see what would have failed” observability, but if you’re shipping an agent to third parties, the soft-hint template can amplify rather than dampen the failure mode.

What we don’t love: the regression we built into our own implementation

Look at the claude row. Verdict is advisory + read_file unmet. But claude did use its native filesystem tools — that’s how it knew src/auth.ts doesn’t exist. The receipt is reporting a false negative.

The bug isn’t in the verdict logic. It’s in the observation channel: ato dispatch claude doesn’t pipe claude CLI’s tool-use blocks into execution_logs.tool_calls_summary today — that column was wired only for ato review through api_dispatch_tools.rs. So when the verdict ran, it saw zero observed tool calls for claude and (correctly, given its inputs) marked the dispatch as advisory + unmet.

This is the kind of regression you find only by running the implementation against the data that motivated it. We wouldn’t have caught it from synthetic tests. We caught it by replaying the exact cold-control scenario and reading what the receipt said. The fix — v2.9 PR-2 — switches claude CLI invocation to --output-format json --print, parses the tool-use blocks, and writes them to the receipt’s tool-call column.

We’re telling you about this in the same blog post that announces the feature because that’s the methodology we’re selling. The receipts make the regression visible. You can score your own implementations the same way: bench in column A, impl in column B, and every column that changes from NULL to a value is a feature, every column that should match doesn’t-and-doesn’t is the next thing to fix.

The methodology that repeats — cold → control → impl → score

The reason we’re writing this post the day we shipped PR-1 (instead of after the whole feature is done) is that the methodology itself is what we’re documenting. Every slice in v2.9 follows the same four-step loop, and we’ll publish the scorecard for each one as it lands:

  1. Cold — capture the bench. Fire the prompt with no flags, no grounding, no expectations. Record what each runtime actually does in its default state. This is the baseline we’re going to score the implementation against.
  2. Control — identify the gap. Look at the cold receipts and find the row where one runtime did the right thing and one didn’t. That’s the empirical evidence that motivates the feature. (For PR-1, this was the claude-refused-honestly vs gemini-hallucinated-5KB row.)
  3. Impl — build the smallest slice that should change the cold-control outcome. For grounded mode PR-1, that was: schema columns, policy compiler, verdict computation, the post-dispatch UPDATE that stamps both onto the receipt.
  4. Score — replay the exact cold-control prompt with the implementation on. Compare row-by-row. Every column that changed from NULL to a value is an upgrade. Every column that should match the runtime’s actual behavior and doesn’t is a regression you have to fix. Score the regression in the same post that announces the upgrade. No silent gaps.

PR-2 is queued and follows the same loop. The bench is the false negative we just found: claude is reported as advisory + unmet even though it used native tools. The control is the same dispatch as above (claude refused honestly because it ran read_file internally; ATO didn’t see it). The implementation parses claude’s --output-format json tool-use blocks into execution_logs.tool_calls_summary so the verdict computation can observe what claude actually did. The score we’ll publish: claude’s verdict should lift from advisory + read_file unmet to compliant; gemini’s should stay at advisory + read_file unmet (because PR-3 closes the gemini-API path, not PR-2). If after PR-2 lands, claude’s verdict is anything other than compliant on the same prompt, the implementation didn’t do what it was supposed to and we go back to the impl step.

PR-3 follows the loop again. Bench: PR-2 has closed claude but gemini is still hallucinating with verdict: advisory + read_file unmet. Control: api_dispatch.rs sends API-provider dispatches as text-only, ignoring the function-calling tool loop that already exists in api_dispatch_tools.rs for ato review. Impl: route API-provider dispatches through the tools loop when grounding_mode != off. Score: gemini’s receipt on the same prompt should land at compliant OR — if gemini still chooses not to call tools when offered — advisory + read_file unmet for an honest reason rather than an architectural one. Either is a defensible outcome, but the new column will tell us which one happened.

PR-4 follows the loop with the parserless runtimes (Ollama, OpenClaw, Hermes). Bench: a strict-mode dispatch with --require-tools against these runtimes fails outright because they have no tool-call telemetry. Control: the runtimes’ tools work (they ARE full agents, the dispatch path just doesn’t surface their tool-use). Impl: a wrapping interceptor that parses the runtime’s output for tool-call markers and writes them to the receipt; refuse-with-options if the parser can’t be wired. Score: every runtime ATO supports must produce a non-NULL verdict on this prompt by the end of v2.9.

What this enables — Methodology as a Pro feature

Designing v2.9 took six structured tests across three formats: parallel war-room, sequential session, A/B/C empirical, plus the cold-control baseline. We composed them ourselves through the CLI, manually. That hand-composed methodology became the Pro product that shipped as v2.10/v2.11 (ato evaluations methodology run / diagnose / auto-extension) — see Part 6 + Part 7 for the build log.

The methodology composer fans out N variants of a structured prompt across N models, scores each one against a rubric (regex / LLM judge / structural assertion), and composes the receipts into a single defensible recommendation. Three first archetypes that fall out naturally:

The grounded-mode receipts we just shipped in PR-1 are the atomic events the methodology composer reads. Nobody else (Braintrust / Patronus / Promptfoo) composes methodologies with grounding receipts as the atomic event. The wedge: “your agent followed your rules (verifiable receipts), AND we have a Pareto chart showing it was the best choice on YOUR data.” Drops a methodology row into the pricing table next to Scheduled evaluators.

How to run this test on your own machine

brew install willnigri/ato/ato

# Cold control — what does each model do without grounding?
ato dispatch claude  "your prompt"
ato dispatch gemini  "your prompt"

# Now turn grounded mode on with --mode-override + --require-tools
ato dispatch claude  "your prompt" \
    --mode-override soft \
    --require-tools read_file

# Inspect the receipts
sqlite3 ~/.ato/local.db "SELECT runtime, grounding_verdict,
                          length(response), agent_slug
                          FROM execution_logs
                          ORDER BY created_at DESC LIMIT 4"

Everything lands in a SQLite database at ~/.ato/local.db on your machine. The override audit + verdict + tool-use summary are all queryable columns. You can replay the test against any prompt that should require tool use, and the verdict tells you which of your runtimes actually do it.

And when you find a regression in your own implementation — the way we found one in ours — the receipts make it visible enough to fix.

Download ATO → View on GitHub →

The receipts in this post are from real ato dispatch sessions run on 2026-05-24. Trace ids: 14c05521-4757-43d2-aed7-eb953492efe6 (claude, grounded), c4996da1-ce35-493a-818e-358d433cb7a4 (gemini, grounded). The cold-control baselines were captured the same day at /tmp/ato-empirical-logs/control-{claude,gemini}.json on the author’s machine. Score sheet and full methodology notes at /tmp/grounded-mode-receipts/07-empirical-score.md — reproducible by anyone running ATO 2.9 on a public repo that doesn’t contain a src/auth.ts file. The v2.9 PR-2 work to close the claude false-negative regression is queued and will be the subject of a follow-up post.