Production-ready agents, Part 12.5: Front doors and runtimes: Gemini Enterprise, the alternatives, and agents that run anywhere
Gemini Enterprise is Acme's front door, but it is not the only one, and Agent Runtime is not the only place an agent can run. This part separates the three choices (front door, connection and runtime), compares Gemini Enterprise with your own app, Google Chat, Slack and other agents over A2A, and shows how to write an agent whose code does not care which front door called it or where it runs.
When I show partners the Acme architecture, one question comes up almost every time: “Does it have to be Gemini Enterprise?”. Some of their customers live in Slack. Some want the agent inside their own product. Some already have an orchestrator from another vendor and want to call our agent from there. And some want to run the agent on their own Kubernetes cluster, not on a managed agent service.
The short answer is no. Part 12 put the router in front of people through Gemini Enterprise, and that is a very good default. But “where people ask” and “where the agent runs” are separate decisions, and a well-written agent keeps both open. This part explains the choices, compares the options, and shows how to write agent code that can move between them without a rewrite.
If you prefer to watch first, here is the heart of this part as a short animated explainer.
Video: how to separate an agent's front door, connection and runtime, in three minutes. The narration is a synthetic voice; the script and animation are mine.
Where are we in the architecture?
This part adds the other front doors (your own app, Google Chat, Slack, other agents over A2A, and Gemini Enterprise for CX for customers) and shows how the same agents can run on Agent Runtime, Cloud Run, GKE or any container. Part 1.25 walks through the whole picture, layer by layer.
where Part 12.5 sits in the Acme architecture: highlighted boxes are the subject of this part, solid boxes come from earlier parts, faded boxes arrive later
What do I need to know first?
Three words do most of the work in this part: where the question is asked, how it travels, and where it is answered are three different things.
Start with a takeaway restaurant. You can order at the counter, by phone, or through a delivery app. The order then reaches the kitchen in different ways: the cashier types it into the till, the phone operator writes a ticket, the delivery app prints its own ticket on a tablet. And the food can be cooked in the restaurant’s own kitchen or in a rented kitchen across town. The chef does not care which door the order came through, as long as someone copies it onto the kitchen’s ticket format.
The same three ideas, with their real names:
- The front door is where a person (or another program) asks the question: the Gemini Enterprise app, your own web page, a chat tool, or another agent.
- The connection is how the front door reaches the agent. In this series there are three: ADK registration (Gemini Enterprise calls an ADK agent on Agent Runtime directly), A2A (the open Agent2Agent protocol from Part 6), and a plain REST API.
- The runtime is where the agent’s code runs: Agent Runtime, Cloud Run, GKE, or any machine that runs containers.
An adapter is the small piece of code that translates one front door’s request into the shape your agent expects. Keeping adapters small, and the agent ignorant of them, is the whole trick.
Mapped one to one: the counter, the phone and the delivery app are front doors; the till, the handwritten ticket and the tablet are connections; the two kitchens are runtimes; the person who copies every order onto the kitchen’s ticket is the adapter; and the chef is the agent’s core.
How does this apply to Acme? Maria asks in the Gemini Enterprise app (front door). The app calls the router through ADK registration (connection). The router runs on Agent Runtime (runtime). The few lines that turn her email and token into a user ID the router understands are the adapter, and the router, the specialists and the tools are the chef, who never looks at which door the question came through.
What are the three choices?
Front door, connection and runtime are three separate decisions, but they are not independent: the connection you pick limits where the agent can run. The common mistake is to treat “we use Gemini Enterprise” as one decision, when it quietly made three.
They are easy to mix up because Acme made all three at once: Gemini Enterprise as the front door, ADK registration as the connection, and Agent Runtime as the runtime. Drawn separately, they look like this:
three separate choices: the front door where people ask (Gemini Enterprise, your own app, Google Chat, Slack, or another agent), the connection between the front door and the agent (ADK registration, A2A or a REST API), and the runtime where the agent runs (Agent Runtime, Cloud Run, GKE or any container)
Not every combination works. ADK registration only reaches agents on Agent Runtime. A2A and REST reach any runtime. So the connection you choose limits the runtimes you can use, and the front door limits the connections. The rest of this part goes through each layer.
In the restaurant, it is as if the delivery app’s tablet were only installed in the restaurant’s own kitchen: orders through that app can never be cooked in the rented kitchen, while phone orders, written on paper, can go to either. ADK registration is the tablet; A2A and REST are the paper ticket.
Here is one question, “Which Harvest Row SKUs had stockouts last week?”, taken down two paths. Path A is what Acme runs; path B is how a Slack front door would work at Acme:
Path A front door: Gemini Enterprise connection: ADK registration runtime: Agent Runtime
1. Maria asks in the Gemini Enterprise app.
2. The app calls the router on Agent Runtime with her email and her token in temp:<AUTH_ID>.
3. The adapter turns that into Caller(user_id="u-maria", ...).
4. Router -> supply agent -> supply_service as Maria -> answer.
Path B front door: Slack connection: REST (get_fast_api_app) runtime: Cloud Run
1. Maria asks in a Slack channel.
2. The Slack app verifies the request and calls the REST API of the same router code, deployed
a second time on Cloud Run.
3. The adapter maps her Slack user to Caller(user_id="u-maria", ...).
4. The same router logic, the same supply agent, the same answer.
Not allowed: Slack -> ADK registration. ADK registration is a connection from Gemini Enterprise to
Agent Runtime only, so no other front door can use it.
Steps 3 and 4 are identical on both paths. Only the first two steps, the front door and the connection, differ, which is why they belong in adapters and not in the agent.
What does Gemini Enterprise give you as a front door?
Gemini Enterprise gives you sign-in, sharing, consent and governance for free, and offers two ways in: ADK registration if your ADK agent lives on Agent Runtime, A2A registration if it lives anywhere else. The way in you choose decides what identity reaches your code and how you secure the endpoint.
Part 12 covered this in detail, so here is the summary that matters for the comparison. Gemini Enterprise gives your users a place they may already use every day, with Google sign-in, sharing controls (who can open which agent), consent screens for agents that act for the user, and other agents and enterprise search beside yours. You write none of that.
There are two ways to connect an agent to it, and they make different promises.
two ways to put an agent into Gemini Enterprise: ADK registration needs the agent on Agent Runtime and passes the user's email; A2A registration accepts an A2A agent hosted anywhere, speaks A2A v0.3 streaming, and can send a user OAuth token in the Authorization header; in both cases Model Armor must be called from the agent's own code
ADK registration. The registration guide is for “ADK agents hosted on Agent Runtime”: you “must host and maintain your ADK agents on Agent Runtime for them to function”. In return, the connection is “compliant with VPC Service Controls”, agents in another Google Cloud project are supported, and “ADK agents receive the user’s email address from Gemini Enterprise”. This is what Acme uses.
A2A registration. The A2A registration guide connects “agents that are built using A2A and hosted on any platform”. The release notes list A2A registration as generally available since 17 August 2026, although the guide itself still carries a Preview label as of 28 October 2026. Four details from that page matter in practice:
- Gemini Enterprise “supports the A2A v0.3 streaming mechanism”. An agent built on A2A v1.0 or later
should use the SDK’s compatibility package (for Python,
a2a.compat.v0_3). Part 6 already serves both versions on Acme’s agent cards for this kind of reason. - If the agent runs on Cloud Run at its default
.run.appaddress and requires authentication, you grant the Cloud Run Invoker role to the Discovery Engine service agent. Gemini Enterprise then sends a Google-signed ID token in theX-Serverless-Authorizationheader. Behind Apigee, a load balancer or a custom domain, it does not send that header. - If you configure OAuth, Gemini Enterprise gets a token for the signed-in user after consent and sends
it in the standard
Authorizationheader, so your code can call other services as that user. - It “doesn’t use the
securitySchemesandsecurityfields of your agent card” to authenticate, and it “doesn’t support theapiKeysecurity scheme”. Plan your endpoint security around the headers it does send.
Where people get tripped up is that there are two identities in play, not one. Picture a courier collecting a parcel from a bank on someone’s behalf. The courier shows a company pass at the door, which proves the courier works for a known delivery firm, and the guard lets them in. At the counter the courier hands over a signed letter of authority from the customer, and the clerk releases the parcel because of the letter, not because of the pass. Lose the pass and the courier never reaches the counter, however good the letter is.
The pass is the service identity (who is calling), the letter is the user’s token (on whose behalf), the guard is the platform in front of your code, and the clerk is your code.
How does this apply to Acme? Put together, a request from Gemini Enterprise to an A2A agent at its default Cloud Run address carries the pass and the letter as two different tokens in two headers:
POST https://<service>.run.app/...
X-Serverless-Authorization: Bearer <Google-signed ID token> proves the caller is Gemini Enterprise,
checked by Cloud Run (Invoker role)
Authorization: Bearer <user token> Maria's OAuth token after consent,
used by your code to act as her
Move the same agent behind a custom domain and the first header is no longer sent. If Cloud Run still requires authentication, the request is refused before your code runs, even though the user token is fine. That is why the endpoint security has to be planned around these headers.
One warning applies to both ways in: Gemini Enterprise’s console setting for Model Armor does not protect these agents. Both guides say developers must call Model Armor from the agent’s own code, which is exactly what Part 9’s plugin does.
Which way in? A short guide, based only on what the two guides above say:
Your agent is built with ADK and runs on Agent Runtime -> ADK registration (what Acme uses)
Your agent runs on Cloud Run, GKE or elsewhere, or is not ADK -> A2A registration (A2A v0.3 streaming)
Your agent must act as the user against another service -> A2A registration with OAuth, or ADK
registration with Part 12's auth flow
Your endpoint only accepts API keys -> change the endpoint first; neither
way in sends one
What are the other front doors?
Every other front door can reach the same agent; what changes is who builds sign-in, consent and the interface. The misconception is that adding a front door is a protocol problem. The protocol is usually the easy part; deciding who the person is, and proving it to the agent, is the work.
Gemini Enterprise is one option. These are the others I see most often, each with public documentation you can follow.
- Your own app. A web or mobile app with its own sign-in calls the agent’s API (AG-UI is an open
protocol for that connection; Part 12.75
explains it). ADK’s
API server exposes an agent as a REST API, and the
Cloud Run deployment guide shows
get_fast_api_app(), which builds a FastAPI app for an agent, with the session store set by a URI. You get full control over the experience, and you build everything Gemini Enterprise would have given you: the interface, sign-in, history, sharing and consent. - Google Chat. Google publishes two quickstarts: a Chat app with an ADK agent and a Chat app with an A2A agent. In both, Chat app logic in Apps Script or a web server receives the message, passes it to the agent, and “sends responses asynchronously” through the Chat API. Good for teams that already work in Chat spaces.
- Slack. Slack’s guide to developing an agent
describes a response loop (“receive input, reason, call tools, render output”), agent features in the
Slack interface, and an
assistant:writescope that labels your app as an agent. The guide notes that some AI features “require a paid plan”. Your Slack app becomes the adapter in front of the same agent. - Another organisation’s orchestrator, over A2A. Microsoft’s Copilot Studio documentation describes connecting “your custom agent to another agent that supports the A2A protocol”, for agents “built on external frameworks” and “hosted outside Copilot Studio”. An A2A server you already run for your own router can, in principle, be called from there too. The hard part is not the protocol; it is deciding whether to trust that caller, and whose identity it carries.
- A front door for customers: Gemini Enterprise for Customer Experience. Everything above serves employees. When the people asking are your customers (a shopper on acme.com asking “Where is my Verde refill order?”), Google’s answer is a separate solution, Gemini Enterprise for CX, with its own agent studio, Agent Assist for human reps, and commerce agents that “connect frontend interfaces like chat and voice directly to backend tools”. Who is asking changes completely: a customer, often not signed in, rather than an employee. Acme’s design uses it for consumer care during stockouts, with conversation insights exported to BigQuery as the only link to the employee side. Part 11.75 sets it beside the Gemini Enterprise app and the industry editions.
This is how they compare on the questions I ask first:
front doors compared on who signs users in, how the agent learns who is asking, what governance comes with it, and how much you build: Gemini Enterprise gives the most out of the box, your own app the most control, Chat and Slack meet people where they already work, and A2A lets another organisation's orchestrator call your agent
My view: start with Gemini Enterprise when your users are in a Google Workspace or Gemini Enterprise organisation, because sign-in, sharing, consent and governance are the expensive parts to build, and it gives you all of them. Add a second front door only when a real group of users cannot use the first. Build your own app when the agent is part of a product you sell, because then the experience is the product.
As a litmus test, cheapest first:
1. Are the users employees who can use the Gemini Enterprise app? -> Gemini Enterprise. Stop here.
2. Do they spend their day in Chat or Slack and refuse to leave? -> add a Chat or Slack adapter.
3. Is the caller another organisation's orchestrator? -> A2A, once IAM and Agent Gateway
say that caller is trusted.
4. Are the users your customers? -> Gemini Enterprise for CX.
5. Is the agent part of a product you sell? -> only then, your own app.
Where can the agent run?
The runtime choice is about how much you want managed for you, and the connection has already ruled some runtimes out. Choosing a runtime is a bit like choosing somewhere to live. A serviced apartment comes furnished and cleaned, but you live by its rules. A rented flat gives you the walls and the plumbing, and you bring your own furniture. Your own house lets you change anything, and every repair is yours. A caravan goes wherever there is a pitch.
ADK’s deployment overview lists four places, and they map onto those four homes in order.
- Agent Runtime is “a fully managed auto-scaling service on Google Cloud specifically designed for deploying, managing, and scaling AI agents”. It is the only runtime ADK registration in Gemini Enterprise accepts, and it brings managed sessions and Memory Bank (Part 8), Agent Identity (Part 7) and the deployment flow from Part 11.
- Cloud Run is “a managed auto-scaling compute platform” that runs the agent “as a container-based application”. It works with A2A registration, the REST API and chat adapters, and you choose the session store yourself.
- GKE is for when you “need more control over the deployment as well as for running Open Models” (Part 13 covered serving open models).
- Any container platform. The same page says you can package the agent as a container and run it “in any environment that supports container images”, including offline or disconnected.
So Agent Runtime is the serviced apartment (sessions, Memory Bank and identity come furnished), Cloud Run is the rented flat (the platform runs and scales the container; you bring the session store), GKE is your own house, and any container platform is the caravan.
A litmus test, most managed first:
1. Does Gemini Enterprise reach the agent through ADK registration? -> Agent Runtime. No choice.
2. Do you want managed sessions, Memory Bank and Agent Identity? -> Agent Runtime.
3. Is it a container you are happy to give a session store? -> Cloud Run.
4. Do you need cluster control or to serve open models? -> GKE.
5. Must it run outside Google Cloud, or disconnected? -> only then, any container platform.
How does this apply to Acme? The router lives in the serviced apartment because question 1 says it must. The specialists are already A2A servers (Part 6), so each one could move to Cloud Run or GKE without the router noticing; only the agent card URL in the router’s settings would change. The router stays on Agent Runtime because ADK registration requires it.
In the router’s settings (Part 11 passes this as an environment variable), the move would look like this, with placeholder addresses:
before: SUPPLY_AGENT_CARD_URL=<supply agent's card URL on Agent Runtime>
after: SUPPLY_AGENT_CARD_URL=https://acb-supply-<id>.run.app/<card path>
That is the whole code change. The work that is not code still has to be done: who may call the new service and with which identity (Part 7), and which hosts Agent Gateway allows (Part 12).
How do I write an agent that can run anywhere?
Keep three things out of the agent’s core: the front door, the host and the services. Think of a laptop charger on a trip abroad. The laptop is the same in every country. A cheap plug adaptor deals with the shape of each country’s socket, and the charger itself accepts the local voltage, so nobody rewires the laptop at the border. If you had soldered a British plug onto the laptop “just for now”, every new country would need a soldering iron.
Mapped: the laptop is the core app, the plug adaptors are the front-door adapters, the charger’s
voltage handling is the services chosen by settings, and soldering the plug on is front-door code
leaking into a tool. That last one is the usual mistake: a single temp: read or Slack field inside a
tool looks harmless until the second front door arrives.
How does this apply to Acme? Here is the shape I would use for the router and the specialists, with the laptop, the adaptors and the charger each in their place.
an agent that can run anywhere: the core ADK app (agents, tools, plugins and guardrails) knows nothing about front doors or hosts; thin adapters translate each front door's request into one caller shape; sessions, memory, models and secrets are chosen by settings at start-up
1. One core app that imports nothing from a front door. The router workflow, the specialists, the
tools, the skills and the plugins live in one ADK App. Nothing in it reads a Gemini Enterprise
session key, a Slack event or an HTTP header directly.
2. Thin adapters, one per front door. Each adapter does three things only: read the request, work out who is asking, and start a run on the core app. ADK already provides most of them:
# Illustrative: the same root agent behind two adapters (Part 6 used to_a2a; Part 11 the REST app).
from google.adk.a2a.utils.agent_to_a2a import to_a2a # A2A server: Gemini Enterprise (A2A reg.),
a2a_app = to_a2a(root_agent, port=8080) # Chat (A2A quickstart), other orchestrators
from google.adk.cli.fast_api import get_fast_api_app # REST API for your own app
rest_app = get_fast_api_app(agents_dir=AGENT_DIR, web=False,
session_service_uri=os.environ["SESSION_SERVICE_URI"])
For Gemini Enterprise’s ADK registration, Agent Runtime itself is the adapter: agents-cli deploy and
agents-cli publish gemini (Parts 11 and 12) do the work.
Here are the three steps on one Slack message, using the Caller shape from the
exercise at the end of this part (it would need a Slack branch added):
Input: a Slack message event, user "U0MARIA", text "Which Harvest Row SKUs had stockouts last week?"
1. Read: verify the request really came from Slack; reject it otherwise.
2. Who: from_request("slack", event) -> Caller(user_id="U0MARIA", ...);
the lookup maps U0MARIA to Acme's user ID: Caller(user_id="u-maria", ...).
3. Run: start a run on the core app with user_id="u-maria" and the question text.
Output: the core app's answer, posted back to the channel through the Slack API.
Everything Slack-specific sits in steps 1 and 2. The core app receives a user ID and a question, as it does from Gemini Enterprise, and imports nothing from Slack.
3. One shape for “who is asking”. Every front door delivers identity differently: a user email and
a temp: session value from ADK registration (Part 12), an Authorization header from A2A
registration, a user resource in a Chat event, your own session in your own app. Write one function
that turns each into the same Caller object, and let the rest of the code use only that. This is the
same rule as Part 6 (identity in metadata, written and read by code, never in the prompt), extended to
every front door. The exercise at the end of this part builds one.
4. Services chosen by settings. Acme’s router already does this for sessions (Part 8): it uses a
database when SESSION_SERVICE_URI is set, Agent Platform Sessions when it runs on Agent Runtime, and
memory on a laptop. Do the same for memory, models (MODEL, WRITER_MODEL, Part 13) and secrets
(Secret Manager, Part 11). get_fast_api_app takes session, memory and artifact service URIs for this
reason.
The same code, three settings:
laptop, SESSION_SERVICE_URI unset -> in-memory sessions, gone when the process stops
Cloud Run, SESSION_SERVICE_URI=<db URI> -> database sessions, shared by every instance
Agent Runtime -> Agent Platform Sessions, managed for you
Nothing in the router changes between the three lines; only the environment does.
5. Guardrails inside the agent, not in the front door. Front doors differ in what they check, and Gemini Enterprise’s own Model Armor setting does not cover custom agents. Part 9’s plugins run inside the core app, so they apply whichever door the request came through. For example, suppose the same prompt injection arrives twice, once from Slack and once from Gemini Enterprise. Both requests reach the core app, so Part 9’s Model Armor plugin screens both. A check written only in the Slack adapter would have caught the first and missed the second.
6. The same evals through every adapter. Run the golden set (Part 10) through each adapter, not just the core app. A front door that drops the user token or truncates a message shows up there, not in a unit test.
Here is that failure on one golden case, q3_delegation (“What did the stockouts behind the Acme
Pure Refill Launch cost us?”, expected number 51331). At Acme, this is how it would show up:
core app, token set by the test -> supply answers, 51331 present PASS
A2A adapter -> token forwarded, 51331 present PASS
REST adapter (forgets the user token) -> supply: "not_authorized: the user has not connected
Databricks yet", 51331 missing FAIL
The core-app test passes because it sets the token itself. Only running the case through each adapter shows that one door loses the token, and the gate, which is code, blocks that adapter.
One short note: Acme has only one front door in production, Gemini Enterprise through ADK registration. The Chat, Slack, own-app and Copilot Studio paths in this part show how each would work at Acme, following each vendor’s documentation rather than the series’ build.
What would Acme do with a second front door?
A second front door is mostly identity and trust work, not agent work. It is like a shop adding a phone-order line: the stockroom, the prices and the till stay the same, but the shop now has to check who is on the phone before it takes an order on their account. Each item below follows that pattern: the agent is unchanged, and the new work sits at the door.
At Acme, this is how it would work if the supply team wanted to ask questions from Slack, and a partner wanted to call the supply agent from their own orchestrator.
- Slack for the supply team. A small Slack app is the adapter. It verifies the Slack request, maps the Slack user to the same Acme user ID that Gemini Enterprise uses, and calls the router over its REST or A2A interface. Everything after that (routing, specialists, number check, guardrails) is unchanged. The supply agent still needs the user’s Databricks token, so the Slack app would also have to run the OAuth consent flow that Gemini Enterprise handled for us in Part 12.
- A partner orchestrator over A2A. The supply agent’s A2A server already exists. The new work is
trust: which callers IAM allows (Part 7), what identity their requests carry, and whether Agent
Gateway (Part 12) should allow the traffic at all. Until those answers exist, the safe answer is no.
For example, a partner’s call arrives with
"user_id": "u-maria"in its metadata. The metadata is only a claim. IAM has no binding for the partner’s identity, so the call is refused before the supply agent reads a word, and Maria’s name in the request changes nothing. - Hosting. The specialists could move to Cloud Run for cost or region reasons with no code change, because the router finds them by agent card URL. The router stays on Agent Runtime as long as Gemini Enterprise reaches it through ADK registration.
What can go wrong
- Guardrails in the front door only. A check that lives in one front door is missing from the others. Put every rule that must hold in the core app’s plugins.
- One user, many IDs. Gemini Enterprise, Chat and your own app each identify Maria differently. If the agent keys memory on the raw ID, she gets three separate memories. Map every front door to one user ID.
- A2A version mismatch. Gemini Enterprise’s A2A registration speaks v0.3 streaming; a v1.0-only agent card will not work without the compatibility package.
- Endpoint security that Gemini Enterprise cannot satisfy. API keys, custom headers and the agent card’s security schemes are not used. Behind a custom domain, the service token header is not sent.
- Front-door logic leaking into the core. A
temp:<AUTH_ID>read in the middle of a tool ties that tool to one front door. Read it once, in the adapter. - In-memory sessions on a container platform. On a laptop, memory is fine. On a service with several instances, set a real session store (Part 8).
- Trusting a caller because it speaks A2A. The protocol says nothing about whether the caller is allowed. Decide that with IAM and Agent Gateway.
Try it: one shape for “who is asking”
This needs nothing but Python. It turns requests from three front doors into one Caller, and refuses
any front door it does not know.
# who_is_asking.py: one shape for "who is asking", whatever the front door. No cloud calls.
from dataclasses import dataclass
@dataclass(frozen=True)
class Caller:
user_id: str # stable ID the agent keys state and memory on
email: str | None # for display and audit, never for access decisions
user_token: str | None # passed on to Databricks as the user; never shown to the model
front_door: str
def from_request(front_door: str, req: dict) -> Caller:
"""Each front door delivers identity differently; the agent only ever sees a Caller."""
if front_door == "gemini-enterprise-a2a": # A2A registration: user token in the Authorization header,
# verified by your code before this point (verified_* below come from that check)
token = req["headers"].get("Authorization", "").removeprefix("Bearer ") or None
return Caller(req["verified_user_id"], req.get("verified_email"), token, front_door)
if front_door == "google-chat": # Chat app: the event names the user
user = req["event"]["user"]
return Caller(user["name"], user.get("email"), None, front_door)
if front_door == "own-app": # your web app: your own sign-in, already verified
return Caller(req["session"]["sub"], req["session"].get("email"), req["session"].get("dbx_token"), front_door)
raise ValueError(f"unknown front door: {front_door}") # fail closed
samples = [
("gemini-enterprise-a2a", {"headers": {"Authorization": "Bearer abc"}, "verified_user_id": "u-maria",
"verified_email": "maria@acme.example"}),
("google-chat", {"event": {"user": {"name": "users/123"}}}),
("own-app", {"session": {"sub": "u-maria", "email": "maria@acme.example"}}),
]
for door, req in samples:
c = from_request(door, req)
print(f"{door:22} user_id={c.user_id:10} token={'yes' if c.user_token else 'no'}")
Running it prints:
gemini-enterprise-a2a user_id=u-maria token=yes
google-chat user_id=users/123 token=no
own-app user_id=u-maria token=no
Look at the second line: the Chat user ID is a different string from the others for the same person. That is the “one user, many IDs” problem from the list above, and the next step is a lookup from each front door’s ID to Acme’s own user ID. Notice also that only one front door brought a user token, so the supply agent can only act as Maria for that door until the others run their own consent flow.
What code did we write in this part?
Every part ends with the same map of Acme’s build: which files exist so far, and the code this part added, explained line by line. The map grows with each part.
This part adds no code to Acme’s build. It compares front doors, connections and runtimes, and the snippets are illustrations: two ADK adapters (to_a2a and get_fast_api_app) around the same root agent, a settings change that would move a specialist to Cloud Run, and a standalone who_is_asking.py exercise that maps three front doors to one Caller shape.
Where does it live?
router/ the router agent: extract, route, write
app/agent.py P5,8 workflow; extract reads memory; App with compaction and resumability
app/app_utils/services.py P8 picks the session, artifact and memory services
app/fast_api_app.py P8 Runner with session, artifact and memory services
app/remote.py P6,7 A2A client over mTLS; metadata carries Sam's token
app/writer.py P5,8 writer reads memories and saves the conversation to Memory Bank
scripts/create_memory_bank.py P8 creates the Memory Bank and its topics, once
tests/eval/acb_metrics.py P10 code metrics: route_correct, grounded, golden_numbers
tests/eval/build_datasets.py P10 splits source cases into golden and held-out, seed 42
tests/eval/calibrate.py P10 compares judge scores with human labels (kappa)
tests/eval/datasets/golden_marketing.json P10 golden cases with expected route, target and numbers
tests/eval/eval_config.yaml P10 eval metrics, including the faithfulness judge
tests/eval/gate.py P10 thresholds per metric; exits 1 when one fails
tests/unit/test_router.py P10 unit tests; routing checked with one row per question type
marketing/ the marketing specialist, on BigQuery
app/agent.py P1,2,3,4 marketing agent with its skill in a SkillToolset
app/app_utils/a2a.py P6 A2A endpoint and agent card (1.0 and 0.3) for marketing
app/skills/marketing-kpis/SKILL.md P4 marketing know-how: which tool, definitions, story checks
app/skills/marketing-kpis/references/tables.md P4 tables, grain, measures and the join rule, read on demand
app/tools.py P1,2 three narrow tools: measures, unserved demand, SQL fallback
tests/unit/test_marketing.py P2 unit tests for the tools, with a fake context and database
supply/ the supply specialist, on Databricks
app/agent.py P3,7,8,9 supply agent and App with the confirmed replan tool
app/app_utils/a2a.py P6 A2A endpoint and agent card (1.0 and 0.3) for supply
app/skills/supply-catalog/SKILL.md P4 supply know-how, given only to the supply agent
app/tools.py P3,7,9 supply tools; the replan tool asks a human first
shared/ code all three services share
adk_common.py P3,6,9,11 plugins and redacted JSON logs, now with the usage ledger
auth_manager.py P7 auth manager callback: token or consent (off by default)
engines.py P3,7 run_databricks connects as the signed-in user
factcheck.py P5 ungrounded_numbers: finds figures not in the findings
registry.py P2 reads semantic.yaml: measure names, computable measures
routing.py P1 classify: picks the specialist in code, no model
safety.py P9 per-user token bucket and injection pattern scan
semantic.yaml P1,2 registry of measures, owners and published formulas
skill_source.py P4 loads a skill from disk or a pinned registry revision
sqlguard.py P3 guard_sql read-only allow-list; redact for log lines
tests/test_cost_report.py P11 unit tests for the cost maths
ops/ build, deploy and evaluation tooling
.cloudbuild/eval_gate.yaml P10 after staging: eval run, then gate.py
.cloudbuild/pr_checks.yaml P10,11 offline PR checks, cheapest first, ending with the mutants
env.sh.example P11 settings for every engine: ids, models, plugin switches, OTEL; no secrets
infra/gateway/setup_gateway.sh P12 egress gateway, endpoint registry and IAP rules, in dry-run
tools/cost_report.py P11 prices the ledger and reports cost per resolved question
tools/mutants/acb_mutate.py P10 pytest plugin that injects each mutant's fault
tools/pricing.json P11 list prices per model, kept out of the code
tools/run_mutants.py P10 runs the offline suites once per mutant
+ added in this part ~ changed in this part Pn = the parts that touched the file
Acme still has one front door in production, Gemini Enterprise through ADK registration; the Slack, Chat, own-app and A2A partner paths are design options the series has not built.
What did we learn?
- The front door, the connection and the runtime are three separate choices. Acme chose Gemini Enterprise, ADK registration and Agent Runtime, but the agent code does not have to.
- Gemini Enterprise accepts ADK agents on Agent Runtime (with VPC-SC and the user’s email) or A2A agents
hosted anywhere (GA per the release notes, A2A v0.3 streaming, user OAuth token in the
Authorizationheader). Either way, Model Armor must be called from the agent’s code. - Other front doors (your own app, Google Chat, Slack, another organisation’s orchestrator over A2A) trade built-in governance for reach or control.
- ADK agents can run on Agent Runtime, Cloud Run, GKE or any container platform.
- A portable agent keeps front doors, hosts and services out of its core: thin adapters, one caller shape, services chosen by settings, guardrails inside, and the same evals through every adapter.
What comes next?
Before the models, one more question about front doors: what can they show besides text? Production-ready agents, Part 12.75: How agents show more than text covers A2UI cards, AG-UI and MCP Apps. Then Part 13: Choosing and running models for agents looks at the models behind every step: which families exist, what matters for an agent beyond the leaderboards, and how to change a model without breaking anything.
Sources
- Google Cloud: Register and manage ADK agents hosted on Agent Runtime (Gemini Enterprise)
- Google Cloud: Register and manage A2A agents (Gemini Enterprise)
- ADK: Deploying your agent
- ADK: Cloud Run
- ADK: GKE
- ADK: API server
- ADK: Exposing an agent over A2A
- A2A protocol
- Google Workspace: Build a Google Chat app with an ADK AI agent
- Google Workspace: Build a Google Chat app with an Agent2Agent agent
- Slack: Developing an agent
- Microsoft: Connect to an agent over the Agent2Agent (A2A) protocol in Copilot Studio
Opinions are my own. Every fact in this post comes from the public documentation linked above.