I built an AI agent that approves or rejects proof-backed payment requests. That sentence should make you nervous, and it made me nervous too, which is exactly why the agent's decisions are not something you have to take on faith. Every single decision it makes comes with a zero-knowledge proof, verified on-chain, that the decision was the correct one. This is the story of building it for the zkVerify Agent Games, and the engineering lessons that came out of it. #AgentGames
The problem: "trust me" is not an audit trail
Agents are starting to do things that matter: moving money, approving transfers, signing off on conditions. When an agent says "I checked everything and approved this payment", what do you actually have? A log line, written by the same software that made the decision. If the agent had a bug, or its inputs were manipulated, or someone swapped the proof it was supposed to check, the log would look exactly the same.
So the design goal for my agent, BAOK-GK, was strict: an approval should be independently checkable by anyone, forever, without trusting the agent, its host, or me.
What the agent does
BAOK-GK is a payment gatekeeper. It receives signed tasks describing a payment condition and answers APPROVE or REJECT. It approves only when every field agrees:
| Check | Trap it catches |
|---|---|
| The proof's verification key is the registered one | VK substitution |
| The referenced proof has never been used before | Proof replay |
| The balance meets the required minimum | Threshold mismatch |
| The condition has not expired | Expired condition |
| The recipient matches the condition's recipient | Recipient mismatch |
| The amount is within the transfer cap | Amount overflow |
Each task arrives as an HMAC-signed HTTPS request with a 60 second response window. The agent verifies the signature over the raw body before parsing anything, evaluates the fields with deterministic logic (no language model anywhere near the decision; the same input must always give the same output), generates a Groth16 proof locally with snarkjs, submits the proof to zkVerify mainnet through Kurier, and returns a signed response carrying the on-chain transaction hash.
The circuit is the referee
The part that makes this more than "an agent with good intentions" is the proving circuit. The gatekeeper circuit does not just attest that a decision was made. It asserts, inside the proof, that the decision bit equals the AND of the five condition checks over the task's public fields.
That has a consequence I find genuinely beautiful: a wrong decision is unprovable. If the fields describe a trap, witness generation for "APPROVE" simply fails; the mathematics refuses to produce the proof. My code can be buggy, my server can be compromised, and an attacker can prompt-inject the operator all day: without a valid proof, an approval does not exist. The platform re-verifies every proof against its own registered verification key, and the proof's record sits on zkVerify mainnet as a permanent audit trail.
Three lessons from building it
1. The spec will be ambiguous somewhere; design so reality can correct you.
The build documentation mandated HMAC signatures but never stated the exact key encoding. I implemented the conventional reading, registered the agent, and let the platform's own preflight be the judge: it came back "Healthy, 579 ms", and practice tasks confirmed the choice end to end. Where I could not resolve an ambiguity from the docs, I made the uncertain surface as small as possible (one module, one function) so a correction would be a one-line change.
2. On-chain finality and real-time deadlines pull in opposite directions.
Submitting a proof through Kurier is fast, but full finalization on zkVerify took about 27 seconds in my measurements, and once exceeded 35 seconds. Inside a 60 second task window that also includes proving time, waiting for finality is how you turn a correct agent into a timing-out agent. Polling the job status showed the transaction hash already exists at the Submitted stage, around 4 seconds in. So the agent returns that hash as soon as the transaction exists and lets finalization complete server-side. End-to-end task time dropped from 36.5 s to 5.6 s, and every proof still finalizes on-chain.
3. Fail closed, and remember what you have seen.
Any internal error produces a REJECT, never an approval. And because proofs can be replayed, the agent keeps a persistent seen-set of proof references plus a per-task response cache, so a redelivered task is answered idempotently and a reused proof is caught even days later. In a long-lived server process, that memory is part of the security model, not an optimization.
The scoreboard so far
- 83 automated tests pass, covering signature handling, the preflight handshake, all six trap classes, idempotent redelivery, and local verification of generated proofs.
- Platform preflight: Healthy, 579 ms.
- Live practice against the platform's own verifier: 4 out of 4 decisions correct, every proof marked verified, including one approval and three different trap rejections.
- A finalized example proof from the production agent is on zkVerify mainnet: transaction
0x3fb367969837d125a4ed106f44b891e70bdd07489155f372197416218103ed95. - Source code, architecture notes, and the full test harness are public: https://github.com/andruu21/proof-gatekeeper-agent — the live endpoint answers its health check at https://zkverify-gatekeeper.vercel.app/agent
Why this matters beyond one competition
Most agent demos ask you to trust the demo. Verifiable agents invert that: the claim an agent makes about its own work becomes a mathematical object that anyone can check, cheaply, at any time in the future. Payment approval is one instance. The same pattern fits an agent that trades, moderates, signs, or operates infrastructure: do the work, then prove the work followed the rules.
We are going to delegate real authority to these systems. "The agent said so" will not be good enough, and it does not have to be.
Built for the zkVerify Agent Games (Proof Gatekeeper track). #AgentGames
![Robotics Engineering Standards — HRS-013: Quadruped [EN]](https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F42u7msle9fmc8ba0f0p8.png)
