Inspiration
Business email compromise against accounts payable is a 30-billion-dollar-a-year problem, and the thing that makes it work is that it never breaks authentication — it succeeds through it. Someone writes to your AP office as a vendor you have paid for years and asks to update remittance details. The grammar is clean, the invoice reference is real, the payment history is real. Everything checks out except the account number, and no control in the stack is looking at that one field.
It lands hardest where nobody's job is security and/or systems get overwhelmed: school districts, county utilities, regional hospitals. There is no SOC, no threat team, and one person in finance deciding under time pressure whether an email is what it claims to be.
We built Interdict around a single observation: there is one last controllable moment, and it is before the money leaves. Everything upstream is advisory.
What it does
Interdict sits at the release gate and refuses to let a payment go until the payee has been independently verified.
When a remittance-change request arrives, Interdict freezes the scheduled payment first and opens a case. Twelve Google ADK agents then fan out across four concurrent lanes — provenance and header forensics, registry and entity checks, an out-of-band callback on the number of record rather than the number in the email, and ledger and payment-history analysis. Each returns findings that carry evidence; a finding with a verdict and no evidence fails validation and cannot be filed.
A Challenger agent then argues the other side. It builds the strongest good-faith case that the request is legitimate and tries to defeat the findings on the merits. An Adjudicator weighs what survives against deterministic policy rails — exposure ceilings, mandatory-callback thresholds, named safety rails — and returns BLOCK, ESCALATE or RELEASE with the reasoning attached.
Around that core: prompt-injection screening that strikes hostile instructions out of attached documents and logs the literal removed text; a named threat dossier assembled from correlated cases; a proactive sweep for the same tradecraft elsewhere in the book; cross-case attribution; a cross-tenant exchange that lets a second district recognise an operator on first contact while withholding nine identifying fields; and a precedent surface so a human decision becomes reusable evidence.
The whole system is durable. Every step checkpoints before it runs, side-effecting tools are idempotent, and the audit trail is hash-chained. Kill the runner mid-case and resume: completed steps do not re-execute, and the payment is released exactly once or not at all.
How we built it
Agents. Twelve real ADK agents on google-adk 2.7.1 — every reasoning step is an
LlmAgent on a real Runner, with each agent's scope-permitted tools declared as
FunctionTools and the scope gate wired into before_tool_callback, so an agent
that reaches outside its manifest is stopped by the runtime rather than by a prompt.
It is a fleet, not API calls in a loop.
Models. Gemini 3.6 Flash for routine work, Gemini 3.7 Flash for the two adversarial roles, all on Vertex AI.
Google Cloud. Cloud Run hosts the backend. Firestore holds cases and checkpoints. Model Armor screens untrusted document content. Cloud Scheduler drives the dormant-case wake path. Vertex AI Agent Registry and Agent Runtime back the discovery and versioning surfaces.
Platform discipline. Every Google service sits behind a Protocol in
backend/app/platform/ with paired cloud and local implementations, so the full test
suite runs credential-free and no agent ever calls a cloud API directly.
Determinism. All time reads from an injected Clock — a test fails the build on a
bare datetime.now(). Case ids, finding ids and request ids are derived rather than
random, and the two adversarial agents sort their inputs, because anything that
reaches a prompt has to be stable across runs.
Front end. React, TypeScript, Vite and Tailwind across four surfaces — Console, Docket, Registry and Posture — against a 491-line design spec with 29 testable conformance assertions.
Verification. 255 tests, plus a Red Team agent that attacks the fleet and scores it.
Challenges we ran into
A single word almost inverted the flagship result. Both Flash models were
returning supports for blatant fraud — reading the label as "supports my analysis"
rather than "supports the payment being legitimate." The Adjudicator counts supports
toward RELEASE, so this would have turned a $340,000 BLOCK into a release. We wrote one
shared verdict rubric, hashed it into the prompt, and forbade per-agent paraphrase.
A green offline suite proved nothing about prompt semantics. That bug lived behind 250-plus passing tests, because tests asserted structure and never asked whether the model actually disagreed with fraud. We started testing discrimination live: assert the agent says the opposite on inverted evidence.
Documented expected outcomes are not tests. Two defects were invisible to a green suite because no test had ever executed the path — including one where two scenarios sharing a vendor starved each other, so every beat after the first opened at $0.
Declaring tools made latency worse. Handing agents tool definitions without telling them their observations had already been gathered made them re-fetch what they had just been given. One phase went from about 50 seconds to over 80 and blew the budget. The fix was a tool protocol appended to the prompt and hashed with it.
Thinking models eat small output budgets. Gemini 3.x Flash consumed a tight
maxOutputTokens entirely on thoughts and returned empty text — which reads as a model
failure and is actually a configuration one.
Cold start is not model latency. Our first live Cloud Run measurement was 113 seconds and looked like the cloud being slow. Warm, the same scenario runs in 51 to 69 seconds — faster than a laptop. Setting a minimum instance turned a would-be crisis into a config flag.
Accomplishments that we're proud of
- An adversarial step that actually changes outcomes. The Challenger builds a real steelman for the payment and is defeated on evidence — visible, on camera, in one take.
- Exactly-once release under failure. Kill the runner mid-case and resume; completed steps are skipped by checkpoint match, not silently passed over, and the effects ledger guarantees the money moves once or never.
- A fleet that can be run without credentials. Replay mode serves the entire runbook offline in about 1.8 seconds per scenario with identical outcomes, so a reviewer can reproduce every result without a Google Cloud account.
- Cross-tenant intelligence that respects the boundary. A second district recognises the same operator on tradecraft alone, at 0.85, while nine identifying fields are deliberately withheld.
- A red team inside the repo that attacks our own agents and scores them.
- Verified end to end: 57 substantive checks across three consecutive clean runs, asserting on content — a BLOCK carrying four evidenced findings and a named rail, the literal injected sentence struck through, an abstention, a clock-driven escalation, a hash-chained audit — not merely on HTTP status.
What we learned
Determinism is a system property, not a prompt trick. One random identifier anywhere upstream poisons every cache downstream — and the failure is reported against the agent after the culprit, which is why it took us so long to find.
Latency is a product decision. We budgeted every step and wrote the narration to outlast the model, so the wait becomes the argument: this is long-running asynchronous execution that keeps working whether anyone is watching.
Verdict vocabulary is load-bearing. If a label can be read two ways, a model will eventually read it the wrong way, and in a payments system that is the whole ballgame.
Inherited code that looks finished usually isn't. We started on a prior full-stack attempt with zero LLM calls, no ADK, a no-op kill switch and a sequential "parallel" fan-out. Reading it file by file, rather than trusting the docstrings, was the single highest-leverage day of the project.
What's next for Interdict.ai
- Real AP integration — ERP and banking connectors so the hold is enforced in the payment rail itself rather than in our own ledger.
- The full fleet on Vertex AI Agent Runtime, not just the backend on Cloud Run.
- A broader threat library, with dossiers contributed by and shared across tenants.
- Analyst ergonomics — full responsive and accessibility passes on all four surfaces.
- Enterprise readiness — SSO, RBAC, configurable retention, and the compliance evidence a treasury team needs before this touches a live payment file.
Built With
- adk
- cloud-run
- cloud-scheduler
- fastapi
- firestore
- gemini
- gemini-3.6
- gemini-3.6-flash
- gemini-3.7-flash
- genaisdk
- google-adk
- google-cloud
- model-armor
- opentelemetry
- pydantic
- pytest
- python
- react
- vertex-ai
- vertex-ai-agent-engine
Log in or sign up for Devpost to join the conversation.