Skip to content
abhinavbhatia
Sheet 02 / 05 — Blogs← Archive

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.

ACME ARCHITECTURE · PART 12.5 AGENT RUNTIME (PART 11) each agent is its own engine with its own identity A2A A2A Shopper Acme's customers P11.75 GE for CX customer chat, voice, reps P11.75 Legal edition industry skills, connectors P11.75 User Maria in marketing P1 Gemini Enterprise where people use agents P12 Other front doors own app (AG-UI), Chat, Slack P12.5 Plugins + Model Armor · checks around every step P9 Router agent Workflow: extract, route, write P1 Supply agent Databricks specialist P3 Marketing agent BigQuery specialist P2 Code sandbox runs generated code P3.5 Skills versioned in Agent Registry P4 Sessions + Memory Bank what it remembers about each user P8 AGENT GATEWAY · EGRESS (FORWARD PROXY) · P12 Gemini models the reasoning engine P1 Other models Model Garden P13 Knowledge RAG and search P14 BigQuery marketing data P2 Databricks supply data P3 Market watch managed agent, public web P1.75 Agent Identity one per agent P7 Auth manager acts for the user P7 Secret Manager no secrets in code P11 Human approval for the one write P9 Cloud Build CI on every change P11 Eval gate blocks a worse agent P10 Cloud Trace one per question P11 Cloud Logging redacted events P11 Agent Analytics BigQuery plugin P9 IDENTITY AND SAFETY SHIPPING MODELS, KNOWLEDGE, DATA OBSERVABILITY deploy this part earlier parts later parts Pn = the part that adds it

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:

FRONT DOOR, CONNECTION, RUNTIME: THREE CHOICES, NOT ONE FRONT DOORS CONNECTIONS RUNTIMES Gemini Enterprise your own app Google Chat Slack another agent ADK registration Agent Runtime only A2A any host REST API any host Agent Runtime managed for agents Cloud Run managed containers GKE your cluster any container even offline Acme today: Gemini Enterprise, ADK registration, Agent Runtime (highlighted). Every other path is open to the same agent code, as long as the code does not assume which front door called it.

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 INTO GEMINI ENTERPRISE (AS OF OCTOBER 2026) ADK registration agent must run on Agent Runtime one agents-cli publish command VPC Service Controls compliant link cross-project agents supported agent receives the user's email Acme uses this (Part 12) A2A registration agent hosted on any platform speaks A2A v0.3 streaming (v1.0 agents: use compat packages) Cloud Run .run.app: Google-signed token in X-Serverless-Authorization optional user OAuth token in the Authorization header GA since 17 Aug 2026 (release notes) Same for both Gemini Enterprise's console Model Armor setting does not protect these agents: screen inside the agent (Part 9)

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.app address 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 the X-Serverless-Authorization header. 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 Authorization header, so your code can call other services as that user.
  • It “doesn’t use the securitySchemes and security fields of your agent card” to authenticate, and it “doesn’t support the apiKey security 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:write scope 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:

WHICH FRONT DOOR? WHAT EACH ONE GIVES YOU, AND WHAT YOU BUILD FRONT DOOR SIGN-IN WHO IS ASKING COMES WITH YOU BUILD Gemini Enterprise Google identity email (ADK reg.), sharing, consent, little: register OAuth token (A2A) other agents, search Your own app your choice your session nothing UI, sign-in, history Google Chat app Workspace the Chat event spaces, DMs Chat app + adapter Slack agent Slack the Slack event agent UI surfaces Slack app + adapter Another agent (A2A) the caller's A2A metadata their orchestrator A2A server, trust (e.g. Copilot Studio) The agent behind every door can be the same code. Only the adapter that reads the request changes. Product features change often: check each vendor's current documentation before you choose.

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.

THE SHAPE OF A PORTABLE AGENT ADK registration thin adapter A2A server (to_a2a) thin adapter REST (get_fast_api_app) thin adapter Chat or Slack webhook thin adapter who_is_asking(): one Caller shape (user_id, email, user_token) Core ADK App: the same in every deployment router workflow, specialists, narrow tools, skills plugins: kill switch, rate limit, Model Armor, audit (Part 9) no imports from any front door; identity arrives only as a Caller OpenTelemetry traces and structured logs (Part 11) Sessions SESSION_SERVICE_URI Memory memory service URI Models MODEL, WRITER_MODEL Secrets Secret Manager Chosen by settings at start-up, so the same code runs on Agent Runtime, Cloud Run, GKE or a laptop.

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 Authorization header). 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

Opinions are my own. Every fact in this post comes from the public documentation linked above.