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.

Built With

  • agentic-ai
  • agents
  • artificial-intelligence
  • autonomous-agents
  • azure
  • cloud
  • devtools
  • enterprise-ai
  • finops
  • gemini
  • geminiapi
  • gemma
  • google-cloud-run
  • google-genai
  • governance
  • guardrails
  • llm
  • mcp
  • odoo
  • open-source
  • python
  • redis
  • security
  • webmcp
  • workflow-automation
Share this project:

Updates