Control Tower — a deterministic front door for an agent fleet
10 model calls instead of 200. A 95% reduction, measured on Google Cloud Run.
The friction
I ran out of credit on a paid model. Mid-project. The agents stopped thinking and the work stopped.
That accident exposed something I had never questioned: every single agent action was going through a large model — including the actions we already knew by heart. Read the inventory. Give the status. Run a written procedure. Each one cost money, took seconds, and could answer differently twice in a row. For work that is not speculative at all.
What we built
A deterministic front door that decides before the model.
request → guardrails (pure code)
├── known capability → circuit runs, ZERO model calls
└── no match → Gemini 3.5 Flash decides, explicitly
The model is not removed. It is moved to where it earns its cost.
Measured, not claimed
200 mixed agent requests, handed over as one asynchronous batch. The caller gets a job id in ~100 ms and walks away.
| path | count | median | model calls |
|---|---|---|---|
| answered by a circuit | 180 | 9 ms | 0 |
| refused by a guardrail | 10 | 0 ms | 0 |
| escalated to Gemini 3.5 Flash | 10 | 5 899 ms | 10 |
10 model calls instead of 200. That figure is a count, not a timing: it does not move with hardware, network or load. GET /metrics publishes both counters live, so the claim is a measurement anyone can reproduce.
A fleet, not one agent with many prompts
21 agents, each with a written specification, a declared scope and an engine. 9 of them are bound to no model at all — deterministic code only.
POST /delegate splits a broad request across the agents whose specifications cover each piece:
| agent | piece | outcome |
|---|---|---|
| Jimmy — the test bench | run the regression tests | done, no model |
| Victor — security | audit the secrets | done, no model |
| Lois — review | review the wording | done, no model |
| Chloe — assistant | publish the article | refused |
The refusal names its own cause: "Chloe may not publish: its specification grants may_publish = false." Separation of concerns is enforced in code, not suggested in a prompt. A failing sub-agent marks only its own piece; the rest continue.
Guardrails that actually refuse
A safety check must return the same verdict twice in a row. A check whose answer varies is an opinion, not a control. So the guardrails are plain Python: a deny list that does not negotiate, and writes that refuse unless explicitly confirmed.
An independent oracle recomputes every expected value in a separate program. It caught a real fabrication in our own repository: a dataset claimed 5468, the oracle computed 712, and the reasoning code had 5468 hard-coded so it "agreed" with the fake. Two lies confirming each other. A checker that shares code with the thing it checks is not a checker.
Architecture
- Model: Gemini 3.5 Flash through Vertex AI, via the Google GenAI SDK
- Serving: FastAPI on Google Cloud Run
- Second deployment: the governed platform itself on Compute Engine — 93 Odoo modules, 21 circuit templates, 67 control gates, 58 skills
Mandatory disclosures
Pre-existing. The Living Map discovery mechanism and the Circuit orchestration model come from an earlier Odoo-based project that has been running in production for a month: 1 580 circuit runs, 2 912 gate crossings, 2 130 human decisions of which 513 were refused.
Built during the submission period. The deterministic router, the Gemini fallback through the GenAI SDK, the asynchronous batch endpoint, the delegation to sub-agents, the independent-oracle wiring, /metrics observability, both Cloud deployments, and an audit script that fails the build when a claim in the README is not corroborated by the files on disk.
Honest limits. The circuit used in the demo reads a 478-entry living map — real work, but modest. A circuit querying a business database would be slower and the ratio smaller. The "small reasoning model" is a prototype: it selects among hand-written strategies. It is not a trained model, and calling it one would repeat exactly the overclaim the oracle exists to catch.
What I learned
Constraints are a filter, not only a loss. Running out of credit removed the solutions I could previously afford, and left the ones that stand on their own. I would never have built this with money in the account.
Log in or sign up for Devpost to join the conversation.