Inspiration

Most agent demos end the same way. The agent does something clever, it updates the database, everyone claps. Nobody shows the other version, where a poisoned prompt updates the wrong database, pays the wrong vendor $50,000, or sends European customer data to a server in the US.

Companies are not slow to adopt agents because the agents are not smart enough. They are slow because two questions have no good answer. What happens when the agent is wrong? And can you prove later what it actually did?

What it does

Aegis sits between your agents and your real data. Every request gets routed, checked by six guardrails, and only then allowed to act. Every decision is written to an audit trail that cannot be edited, with a reason, a severity, and an id you can look up.

Four stages, always in this order. Janus routes the request and holds no permissions at all, so there is nothing to steal from him. Vigil screens it with the armor scan, the identity check, and the policy check. Then a worker acts. Tally handles money, Echo can only read, Relay does general tasks. Last, the verdict is sealed.

The six guardrails are injection scanning, PII detection, tool poisoning, identity and registry, spend ceiling, and data sovereignty. They run on both sides of the model: inputs are judged before anything acts, and tool responses are judged again on the way back.

If any check says no, everything after it is skipped.

The part that makes it different: a segment is a named set of rules. Injection patterns, PII rules, worker permissions, a spending limit, and which regions data may touch. The same fleet obeys whichever segment you pick.

Send the same request under two segments and you get opposite answers.

Request: Record an invoice from Acme for $20000

Segment Limit Result
Healthcare (US) $2,500 BLOCKED — amount 20000.0 exceeds limit 2500
EU Fintech $50,000 CLEARED

Request: Process this record, region: us

Segment Regions Result
EU Fintech eu only BLOCKED — region 'us' is outside allowed regions ['eu']
Global E-commerce open CLEARED

Same words, same fleet, same worker, opposite answer. The only thing that changed is policy, and policy lives in Firestore, not in the code. Adding a new industry means writing a document, not shipping a release.

How we built it

Google ADK runs the fleet, a coordinator with departmental workers under it. The guardrails are wired in as callbacks, so they fire on every request instead of only when the model decides to call them. Gemini 3.5 Flash on Vertex AI powers the agents.

Two services on Cloud Run: the ADK agent, and a FastAPI service that feeds the dashboard. Both run the same guardrail code. Firestore stores the audit trail, threat memory, registry, and segments. The dashboard is Next.js.

Every pillar of the track is a real module, not a label:

Pillar Where it lives
Agent Registry registry.py — suspend an agent and it cannot act
Memory Bank memory.py — threat fingerprints that survive restarts
Agent Runtime runtime.py — async background execution
Agent Identity armor.py — per agent, per tool permissions
Agent Gateway coordinator.py — unified routing, registry gated
Model Armor armor.py, scanners.py — injection, PII, tool poisoning
Observability audit.py — severity, reasoning chain, trace id

One decision needs explaining. The guardrails never ask a model anything. A guardrail that asks a model for permission can be talked out of it, and that is exactly what prompt injection is. So Gemini does the real work and the guardrails watch from outside, deciding in plain code. The same request always gets the same answer, checking costs nothing, and it cannot break because a model was slow. If a guardrail itself crashes, the request is blocked.

Challenges we ran into

The model lives nowhere. Gemini 3.5 Flash on Vertex is served from a global endpoint, but ADK writes the deploy region into the container, pointing the client at a region where the model does not exist. Fixed by setting the location to global inside llm.py rather than reading it from the environment.

Cloud Run forgets everything. My first deploy used SQLite, so every container restart emptied the audit trail. An audit trail that forgets is worse than useless. I wrote db.py as a switch: SQLite locally, Firestore in production, same code either way. A Firestore problem can never take the system down.

One line of CSS lied about my architecture. When Armor blocked a request, my styling reached inside the sentinel card and painted every check red, including Identity and Policy. It looked like three guardrails failed. They never ran, because Armor stopped the request first, which is the system working correctly. Anyone reading that screenshot would have seen three failures instead of one block.

Making a security layer visible. Deterministic screening is invisible by nature. Nothing looks like it is happening when a check quietly passes. I gave the five agents distinct behaviour so the architecture reads at a glance: Vigil moves on the shortest loop because he never stops watching, Echo waits to be asked because he can only read.

Accomplishments that we're proud of

The segment engine. One request, two segments, opposite verdicts, no code changed between them.

The threat memory. Send the same attack twice and the second comes back known, without being judged again. Security that improves the more you attack it.

Read only really means read only. Echo carries no write permission at all. Not a rule the model is asked to follow, but an identity with nothing to grant. A fully compromised prompt still cannot make him write.

And it is genuinely live. Two Cloud Run services, Firestore behind them, a public dashboard anyone can open and try.

What we learned

The hard part of agent security is not spotting bad requests. It is deciding which rules apply to this request, and proving afterwards that they were applied. That is where the segment engine came from.

I also learned to distrust my own screenshots. The CSS bug above hid for days because the data underneath was always right. Only the picture was wrong.

What's next for Aegis Fleet

Threat memory per segment instead of one shared pool. A policy editor so a compliance officer never opens a JSON file. Traces exported to Cloud Trace. Alerts when something critical is blocked.

Built With

Share this project:

Updates