Inspiration
I kept reading about the oldest scam in accounts payable: a vendor emails, “We changed banks, please send payment to this new account,” and someone in finance updates the details and pays a stranger. At the same time, companies are starting to hand AP work to AI agents that read invoices and move money on their own. When I read the Hyphenate brief, it described that situation almost exactly: bank details changing the day before a big payment. I realized the real problem isn’t that the model isn’t smart enough. A fake bank letter looks exactly like a real one. The problem is that the part of the agent that decides where the money goes is reading text anyone can write. I wanted to build an agent that couldn’t be talked into paying the wrong account, no matter what the document said.
What it does
Countersign is an AI accounts payable employee for Fogline Festival, a fictional music festival with real-looking vendors, invoices, and a bank account on Capital One’s Nessie sandbox. Every invoice is processed by two agents side by side: a normal agent that reads the documents and decides, and Countersign. In live runs, the normal agent read a fake bank-change letter and paid $640 to the scammer’s account in 3 of 3 complete runs, while Countersign paid the four real invoices and held that one with BANK_DETAILS_MISMATCH. It works on autopilot, picking invoices up from an inbox without anyone pressing run. It keeps double-entry books that reconcile against the bank, and it can’t approve its own payments: a new bank account needs two different people. There’s an Attack Lab where anyone can write their own scam into an invoice, an eval of 40 invoices with planted errors and an answer key, and an auditor built on Elastic’s Agent Builder that answers questions about every decision by querying the audit log.
How we built it
I built it solo. The backend is FastAPI with SQLite and server-sent events. The design comes from CaMeL-style security research. Gemini Flash reads each PDF in quarantine and pulls out fields with their exact source spans, with Claude Haiku as a fallback. Every value becomes an opaque label like $D3.remit_account_number. The Claude Sonnet planner writes a plan using only those labels, so it never sees a word of the document. A leak guard checks every planner prompt for document text and hidden canaries before it’s sent. Then plain code, not a model, checks the vendor, amount, duplicates, and whether the account matches the one on file. The unguarded agent and the attack mutator run on Bitdeer (DeepSeek-V4-Flash, Qwen, and GLM-5.3). Every event goes into Elastic, where an Agent Builder agent with ES|QL tools does the auditing. The frontend is React, TypeScript, and Vite, and I planned and logged the whole build in Notability.
Challenges we ran into
The biggest challenge was turning a security demo into something Hyphenate would actually hire. That meant adding books, reconciliation, roles, approval limits, an autopilot inbox, and an eval late in the build. The auditor was hard too. Elastic’s agent runs several LLM and query steps, so answers took a minute or more and sometimes hit rate limits, and I had to redesign the page so it didn’t look frozen. I went through dozens of UI layouts before landing on the paper-and-cobalt style with Sig, the mascot. Nessie’s transfer endpoint kept rejecting requests, so payments became paired withdrawals and deposits. Gemini’s daily quota ran out in the middle of evals. And modern models mostly ignored obvious “ignore previous instructions” attacks, which pushed me toward the realistic bank-change letter that actually worked.
Accomplishments that we're proud of
This was my first hackathon without a team, so I’m proud that the first project I built entirely on my own works end to end. The planner’s input was the same across 40 different generated attacks, so the attack text never reached it. Across 61 checked strings and 120 hidden canaries, zero leaked. The eval scored 38 of 40 on the first run. The two misses showed a real gap in the duplicate check. After I fixed it without touching the answer key, it scored 40 of 40 with zero false holds on clean invoices. I’m also proud that I kept every result honest, including the runs where the normal agent wasn’t fooled.
What we learned
I learned that telling an AI agent to be careful doesn’t make it secure. You have to control what it’s allowed to see and make the money-moving decisions in plain code. I also learned how to run two AI coding agents at once without them stepping on each other by using a shared contract, separate worktrees, and making each one show real test output before I merged anything. And I learned some practical lessons the hard way, like checking API quotas before running big evals and pushing right after committing.
What's next for Countersign
Next I’d add the checks real AP teams use when a bank change comes in. That means calling the vendor back on the number already on file, flagging look-alike sender domains, and capping the first payment to any new account until it clears. I’d connect it to a real email inbox and accounting system instead of a sandbox, and run the eval on real anonymized invoices. Then I’d test it with an actual AP team to see whether it saves them time without adding false alarms.
Built With
- anthropic
- bitdeer
- capital-one-nessie
- claude
- claude-haiku
- claude-sonnet
- deepseek
- elastic
- elastic-agent-builder
- elasticsearch
- es|ql
- fastapi
- gemini
- github
- glm
- notability
- playwright
- pydantic
- python
- qwen
- react
- server-sent-events
- sqlite
- typescript
- vite
Log in or sign up for Devpost to join the conversation.