Inspiration
Autonomous agents should not be trusted merely because they sound confident. We wanted to make the safety boundary visible: an order must not lock because a model says it is complete; it must pass a check that code can enforce.
What it does
A customer places an order. Three roles handle it — triage, handler, auditor — and a fourth component, the ledger, is the only thing that can actually lock an order.
In a single ~70-second run on Cloud Run you see two things happen:
The door holds. The handler drafts a confirmation and misses required fields. The auditor returns it with the reason. The handler rewrites. The second review passes. Only then does the ledger lock. Across three consecutive runs the handler missed a different set of fields each time — unit+total price, quantity+total, merged fields. Nothing is scripted; the model fails differently every run and the door catches it every run.
Something evicted comes back. Two steps earlier the context window was full, so the engine evicted an after-sales commitment into a recoverable pool. When the customer raises an old issue, the engine recalls it, and the handler answers with the phone call date, the shipping damage, and the free-shipping promise — details that never appeared in the conversation. Without recall, that sentence cannot be produced.
Why this needs a fleet, not a bigger prompt: the handler is structurally unable to approve its own work. That is not a system-prompt instruction the model could talk its way around — the release path is a code branch that only opens on a boolean true.
How we built it
triage → gardener → gate → ledger, built on Google ADK with Gemini 3.5 Flash through Vertex AI, deployed on Cloud Run.
- gardener runs a zero-dependency selection engine (pure Python standard library, bundled in the repo). It decides which items enter the handler's page, evicts when the window is full, and recalls when a later step needs an evicted item. Eviction is pressure-driven; recall is need-driven.
- gate is a
BaseAgent, not a prompt. It orchestrates handler → auditor → parse → ledger row, and returns the draft with the auditor's reasons when it is not released. Maximum two drafts. - ledger is code. It locks on
state['放行态'] is Trueand nothing else. A model saying "order locked" locks nothing.
The parse is the whole design. Parse failure, missing field, non-boolean value, the string "false", retry limit reached — every one of them resolves to stop. The gate fails closed by construction.
The model, runtime and agent framework are Google's — Gemini 3.5 Flash on Vertex AI, under ADK, on Cloud Run. What we built is the policy layer above them.
⚠️ This is not a managed memory service, and it is not backed by one in this build. Context and the ledger live in process memory here — Firestore and Vertex AI Memory Bank were scoped and cut for time.
Data sources
The shift is a hand-written scenario: a product catalogue, a service-terms table, and six background items (past complaints, commitments, preferences) that live only inside the engine and never enter the conversation history. That separation makes recall falsifiable — if an item is not on the page, the handler cannot know its contents.
No customer data of any kind is used. No third-party corpus is bundled.
Findings and learnings
1. An auditor that cannot parse its own input will approve everything.
An earlier build had if parsed is None: released = True in its error branch. The default direction of a gate is a business decision that looks exactly like exception handling. An external reviewer found it; in that run the branch never executed, so the ledger showed only approvals. Use isinstance(x, bool), not bool("false").
2. adk deploy cloud_run bakes the Cloud Run region into the image as GOOGLE_CLOUD_LOCATION.
Gemini 3.x is available at global, not us-central1. Set it explicitly and verify with gcloud run services describe.
3. AI Studio prepaid credits and GCP billing are different wallets.
An exhausted AI Studio key returns 429 · prepayment credits are depleted even when GCP billing is open. Moving to Vertex AI cost nothing and also satisfied the infrastructure requirement.
4. session.state assignment does not survive a run in ADK.
get_session returns a copy; only event state_delta persists.
5. Three runs failing three different ways is stronger evidence than three identical successes.
The handler's varied failures became the clearest proof that the code gate was not staged.
What we did not do
Sessions and the ledger are in memory — restart and they are gone; Firestore was scoped and cut for time. Model Armor is not implemented. One shift, three steps, three runs: this demonstrates the mechanism, it is not a benchmark.
Log in or sign up for Devpost to join the conversation.