I built this project and wrote this article for the purposes of entering the Google Cloud "All Things Agentic" Hackathon 2026.
My human approval gate checked its one time signature, ran the action, then marked the signature used. Two operators double clicking Execute at the same moment is enough to run the action twice.
It is fixed now, and it is still the most useful thing I learned building Syntrueno, my solo entry for Track 3 of the hackathon. An incident arrives, sometimes from a Cloud Monitoring alert with nobody in the loop. Agents diagnose it, a judge scores the proposed fix against a safety rubric, and anything consequential stops until a human signs for it.
That part I can draw on a whiteboard. What actually taught me something was two bugs, neither of them in the interesting layer.
The gate mints a SHA-256 signature bound to one exact action and one exact parameter set, good for a single use, expiring after 30 minutes. The order of operations was: check it, run the mutation, then spend it.
FastAPI serves sync endpoints from a threadpool, so two requests arriving together both got a yes, both wrote to Cloud Run, and the ledger recorded one event. One signature, two mutations, one audit entry.
The fix was ordering, not cleverness. Find and spend the signature under one lock, and spend it before the mutation rather than after. If the call raises before anything changed, hand the signature back so a transient error does not cost the operator their approval. There is now a test that fires two requests at one signature and expects a single execution.
Every outcome, including every refusal, is appended to a hash chained ledger, and that append was a read, modify, write over the chain head with no lock. Same threadpool, same shape: two incidents interleave, both claim the same position, the chain forks, and nothing raises. A verifier would report a broken chain long after the run that broke it.
Underneath it, the health check had been answering the wrong question. Connected meant a client object existed, not that writes were landing, so the stores had quietly fallen back to memory while still reporting themselves durable. A system that claims durable storage while writing to a dictionary in RAM is the exact failure this project exists to be incapable of.
Neither bug announced itself, which is why I trust the test suite more than the diagram. Pytest collects 284 tests. They pass offline in about three seconds with no API key and no cloud credentials, and CI runs them on a machine deliberately given no secret. If a test can only pass when the cloud is up, it was never testing my code.
The agent's action space is a closed enum, handed to Gemini as its structured response schema. There is no destructive verb in it. Not disabled, not on a denylist. Absent. A prompt injection that fully succeeds still cannot emit one, because no delete verb is blocked; none exists.
A blocked path can be reached by a bug. An absent one cannot.
The honest limit on that: the enum still contains verbs that change a live service, and a wrong parameter on one of those can still hurt. It bounds the blast radius, it does not remove it. The property rests on every member of that enum being safe under every parameter set, which is a claim about what I put in my enum rather than a result from type theory. The two bugs above are part of what keeping that claim true actually costs.
The same outage alert, sent twice.
Once with a prompt injection buried in the error text, which three screens, one of them Model Armor, quarantine.
Once quoting a real DROP TABLE
from a slow query log, which is a database engineer's evidence about what broke, and which passes straight through to the agent. Instructions are stopped. Evidence gets through. That distinction is the whole design.
It is a hackathon project, built solo, with no users. What stays with me is the habit of asking what a green check is actually measuring. Both of these bugs sat behind something that looked fine.
Built on Cloud Run, Firestore and Vertex AI, with Gemini and Gemma 4.
Live service: [https://syntrueno-18489510475.us-central1.run.app](https://syntrueno-18489510475.us-central1.run.app)
Code, Apache 2.0: [https://github.com/Shaan-alpha/syntrueno](https://github.com/Shaan-alpha/syntrueno)