Gauntlet: Gate + Recall

Inspiration

Every check is green. The PR gets merged. And every customer on a phone can no longer buy anything.

That's not a hypothetical. It's the first thing Gauntlet caught.

A pull request added a friendly floating Help button to a checkout page. Lint passed. Unit tests passed. Code review looked fine. On a 375-pixel phone screen, that Help button sat exactly on top of "Place order." Every tap meant to buy something opened a help widget instead.

No test caught it, because nobody had written a test for a small screen. That's the real problem with CI in 2026:

CI only checks what a human remembered to check. AI is now writing a large share of our code, and nobody is writing the tests for it.

Then we asked the scarier question. AI agents now approve refunds, answer customers, and read internal knowledge bases. Suppose a bad article slipped in โ€” one that told a support agent "ownership was already verified upstream." Weeks later someone notices. Which of the agent's past decisions did that article actually change?

Nobody can answer that today. Teams either reverse everything in a panic or reverse nothing and hope. We built Gauntlet to answer both questions with evidence instead of guesses.


What it does

Gauntlet is an adversarial tester that reads what changed and proves what broke. It has two modes.

๐Ÿ›ก๏ธ Gate: catch it before merge

1. Read โ€” Maps every changed line to what it affects: a page, a component, a CSS breakpoint, an AI agent's tool.

2. Attack โ€” An AI planner invents the test the PR deserves: "open checkout at 375px and try to tap Place order."

3. Freeze โ€” The plan is fingerprinted with SHA-256 before anything runs, so nobody can move the goalposts.

4. Prove โ€” Each attack runs 2ร— on the new code and 2ร— on the old code in isolated cloud sandboxes, in a real browser.

5. Judge โ€” A fixed oracle checks hard evidence (which element actually received the tap) and ignores the model's opinion.

6. Report โ€” A verdict lands on the PR as a GitHub check: confirmed_regression, introduced by this PR.

7. Fix โ€” A proposed patch waits for a human to approve its exact fingerprint, then gets re-attacked to prove it works.

Here's what the Help-button PR looked like:

Gate run ยท "Add a floating Help button"
  Planner  : attack below and at the 900px breakpoint
  Plan hash: frozen before any sandbox existed
  Head (PR): โœ— โœ—   tap landed on Help, not Place order
  Base     : โœ“ โœ“   tap reached Place order
  Verdict  : confirmed_regression ยท introduced by this PR

On an unrelated change, it said passed. No false alarm.

It doesn't stop at UI. When a PR let the support assistant issue refunds and accidentally deleted the ownership check, Gauntlet switched strategies by itself: it played an attacker in a chat with the bot, asked for a refund on someone else's order, and checked the ledger, not the chat. On the PR, money went to the wrong person. On main, the tool refused.

๐Ÿ” Recall: measure the damage after release

Point Recall at the bad knowledge-base article, and it:

  1. Traces the blast radius through recorded history, including second-order exposure: article โ†’ saved memory note โ†’ a decision made days later.
  2. Replays each decision twice: once with everything pinned (it must reproduce the original, or we don't trust it), and once with only that article removed.
  3. Sorts the truth:
  • ๐Ÿ”ด tainted โ€” The article changed the decision. (3 in the demo, one through memory)
  • ๐ŸŸข cleared โ€” Exposed, but the decision would have been the same. (2 in the demo)
  • โšช not_replayable โ€” A critical input was never logged, so we refuse to guess. (1 in the demo)

A separate audit checked 36 refunds against the ledger and found 3 real violations. Everything goes into a human review queue. Nothing is reversed automatically.


How we built it

The pipeline: PR diff โ†’ Impact map โ†’ Planner (AWS Bedrock) โ†’ Validator โ†’ Frozen plan โ†’ Daytona sandboxes (base + head) โ†’ Anchor cloud browser โ†’ Write-once evidence โ†’ Trusted oracle โ†’ Verdict โ†’ GitHub check run + Dashboard + Voice briefing.

The core rule: the AI proposes, the oracle decides. The planner can only output typed steps โ€” navigate, resize, inspect, click โ€” built from registered pages, buttons, and test users. No AI-written code ever runs. Repository text and web pages are treated as untrusted data.

A verdict you can verify by hand. Gauntlet only calls something a regression introduced by the PR when it sees at least 2 failures on the PR build and 0 failures on the main build. One failure is never enough. If main fails too, it's a pre-existing bug โ€” not blamed on the PR.

Counterfactuals with a sanity check. For a past decision, Gauntlet first replays it with all original inputs pinned โ€” it must reproduce the original outcome, or the result is inconclusive. Only if the control passes does it replay again with the suspect source removed. If removing it changes the outcome, the decision is tainted. Missing data is never reported as "clear."

Evidence that can't be faked. Every screenshot and measurement is stored write-once with a hash the store computes itself, bound to the exact run, attempt, commit, and step. The oracle re-reads the bytes. Swap or alter one and the check fails closed.

Every sponsor does real work:

  • AWS Bedrock (GPT-6 Luna) โ€” The planner brain: reads the diff and invents the attack
  • Daytona โ€” Spins up identical, isolated copies of the app for "before" and "after"
  • Anchor Browser โ€” The cloud browser that actually taps, scrolls, and screenshots
  • Arize AX โ€” Traces every planner call, worker step, and oracle check
  • SurrealDB โ€” The event log and lineage graph behind Recall
  • SurrealDB Agent Memory โ€” Remembers verified findings so the next plan is smarter, without leaking hindsight
  • ElevenLabs โ€” Speaks a briefing of the findings; it can read results but can never approve anything
  • GitHub โ€” Signed webhooks start a run; the verdict comes back as a check on the PR

Stack: Python 3.12 ยท FastAPI ยท Pydantic v2 ยท hexagonal architecture with swappable ports ยท Next.js 15 ยท Tailwind ยท shadcn/ui ยท React Flow ยท Recharts ยท Playwright ยท ~900 automated tests plus a browser rehearsal of the full demo in CI.

What actually ran live:

  • A real GitHub pull request fired a signed webhook โ†’ live Daytona sandboxes โ†’ live Anchor browser โ†’ head failed 2/2, main passed 2/2 โ†’ confirmed_regression โ†’ check run posted back to GitHub.
  • The live GPT-6 Luna planner on AWS Bedrock found the same bug blind, from the diff alone, by testing both sides of the changed breakpoint.
  • Every screen carries a provenance badge showing exactly what ran live and what didn't. We'd rather show less than overclaim.

Challenges we ran into

  • "Just let the AI test it" doesn't work. Our first AI plans used made-up selectors that nothing could verify. We had to teach the planner each oracle's evidence rules and reject any attack that didn't fit them.
  • The AI sometimes missed it. One early live run only tested desktop and passed. A single general rule โ€” "test both sides of every changed condition" โ€” made run after run catch the bug.
  • A screenshot isn't proof. We had to prove which element received the tap, on both versions, and store it in a way nobody could tamper with.
  • A new model, a new format. GPT-6 Luna on Bedrock rejects the temperature setting and thinks out loud before answering, so every first call failed until we taught our adapter to handle reasoning models.
  • Fair comparisons are hard. "Before" and "after" must start from byte-identical state, or the comparison is meaningless. Any mismatch is reported as an environment error, never a bug.
  • Cloud resources cost money. Sandboxes and browser sessions must always be cleaned up, even when a run is cancelled halfway through.

Accomplishments that we're proud of

  • ๐ŸŽฏ Caught a real mobile-checkout regression that every normal CI check missed, and proved the PR caused it.
  • ๐Ÿค– The AI found the bug on its own, with no hint about what was wrong.
  • ๐Ÿง˜ No false alarms: an unrelated change correctly passed.
  • ๐Ÿ’ธ Caught an AI agent sending money to the wrong person by checking the ledger instead of believing the chat.
  • ๐Ÿงญ Separated real harm from noise after a bad article shipped: 3 tainted, 2 cleared, 1 honestly marked "can't tell."
  • ๐Ÿ”’ Safety enforced in code, not in a slide: frozen plans, write-once evidence, single-use human approvals, and no auto-merge, ever.

What we learned

  • Let AI be creative and let rules be strict. An AI that proposes plus a deterministic oracle that decides gave us imagination without hallucinated verdicts.
  • "It's broken" is half an answer. "This PR broke it" requires testing the old code too, and that comparison is what makes a result worth acting on.
  • "I don't know" builds trust. Saying inconclusive out loud made every confident answer more believable.
  • Build offline first. Every integration has a fake twin, so three of us could build in parallel and the demo never depended on a single API being up.

What's next for Gauntlet

  • ๐Ÿ”Œ One-click install on any repository through the GitHub App
  • ๐Ÿงช More attackers: multi-step workflows and race conditions
  • ๐Ÿ“Š A held-out benchmark on unseen codebases to measure misses and false positives publicly
  • ๐Ÿ” Locked-down network egress for the cloud browser, and team roles before connecting real customer systems

Every PR has to survive the Gauntlet.

Built With

  • anchorbrowser
  • arizeai
  • aws-bedrock
  • daytona
  • elevenlabs
  • fastapi
  • githubactions
  • githubapps
  • gpt-6-luna
  • next.js
  • playwright
  • pydantic
  • python
  • reactflow
  • recharts
  • shadcn/ui
  • surrealdb
  • tailwindcss
Share this project:

Updates

Submission history