# An AI agent backend on Cloud Run: the loop, the stream, the state, and the bill

> An AI agent's backend on Cloud Run: the model-and-tools loop as a streaming service, sessions in Firestore, keyless access, tools as MCP servers, and why tokens are most of the bill.

Published 2026-10-12 · Updated 2026-10-12 · [By Exodus team](https://exodus.elim.io/about) · New product, Patterns
<https://exodus.elim.io/blog/an-ai-agent-backend-on-cloud-run>

An AI agent backend on Cloud Run is a service that runs the model-and-tools loop: it streams each step as server-sent events, keeps every turn in Firestore, calls Gemini or Claude with its own service account, and hands runs over 15 minutes to a worker. The loop mostly waits, so one instance at concurrency 80 carries dozens of runs, and the model's tokens are most of the bill.

## In short

- Run the agent as a Cloud Run service with request-based billing, 1 vCPU, 512 MiB and concurrency 80: in our test 50 runs at once used 0.61 seconds of CPU in 3.5 seconds.
- Keep every turn in Firestore, never in memory; its free 50,000 reads and 20,000 writes a day cover 30,000 runs a month.
- Cap the loop at 8 model calls and retry only the model call on 429, with backoff.
- Send runs longer than 15 minutes through Cloud Tasks to a worker, and stop the run when the client leaves.
- At 30,000 runs a month Gemini 3.5 Flash tokens cost $594 and Claude Sonnet 5 tokens $738, against at most $11.37 for Cloud Run.

## What the backend does, and where the time goes

One run of an agent looks like this. The user sends "Where is order A-1001?". The backend sends the conversation, a system instruction and a list of tools to the model. The model answers with a tool call: `get_order` with `id: "A-1001"`. The backend runs that function, appends the result to the conversation, and calls the model again. This time the model answers in text, and the run ends. A real agent may go around this loop five or ten times.

Each model call takes from about a second to tens of seconds. The backend's own work between calls (parse JSON, query a database, write a row) takes milliseconds. In our test, 50 runs at once on one instance held to 1 CPU used 0.61 seconds of CPU in total while the runs took 3.5 seconds of wall time. The instance was waiting, not working.

So concurrency should stay high: request-based billing charges for the time an instance has a request open, so fifty waiting runs on one instance are billed once, not fifty times. And a long request needs visible progress and has to survive a dropped connection.

## Which Cloud Run resource

Google's page on hosting agents names four Cloud Run resources ([what Cloud Run is](https://exodus.elim.io/blog/what-cloud-run-is) covers the basics):

- **Service.** Request-driven agents behind an API or a chat window, with autoscaling. This guide uses a service.
- **Instance.** One always-on agent with its own address that you start and stop by hand. In Preview, with no persistent disk.
- **Worker pool.** A fleet of agents that pull work from Pub/Sub or Kafka and expose no HTTP endpoint.
- **Job.** Run-to-completion work: a batch evaluation, a nightly sync. See [services, jobs and worker pools](https://exodus.elim.io/blog/cloud-run-services-jobs-and-worker-pools).

Google also sells a managed host for agents, Agent Runtime, in its Agent Platform. It bills $0.085 per vCPU-hour and $0.009 per GiB-hour after 50 vCPU-hours and 100 GiB-hours free a month, and does not bill the idle time between turns. Cloud Run's request-based rate works out to $0.0864 per vCPU-hour and $0.009 per GiB-hour. The price is close; on Cloud Run you own the container, the framework and the data.

## The shape

![A browser posts to the agent service on Cloud Run and reads events back; the service calls tools on a private MCP server, stores turns in Firestore, calls Gemini or Claude, and sends long runs through Cloud Tasks to a worker](https://exodus.elim.io/media/v2026-09-16/t/exodus/assets/image-c4cd9276970b05a46bf6a57a55d578cfb1b328934f69133c65f791322ede57eb-680x384-svg/bytes "The agent service runs the loop and holds no state; dashed parts are added when runs outgrow a request")

The agent service is the only public piece. Turns go to Firestore, model calls to Agent Platform's global endpoint. Tools with their own permissions run as private services, often MCP servers. Runs too long for one request go through a Cloud Tasks queue to a worker.

## Step 1. A project, APIs, and an identity without keys

Turn on the APIs, create a service account for the agent, and give it two roles: Vertex AI User to call models and Cloud Datastore User to read and write Firestore. Then create the Firestore database in the same region as the service.

```shell
gcloud services enable run.googleapis.com aiplatform.googleapis.com firestore.googleapis.com cloudbuild.googleapis.com
gcloud iam service-accounts create agent-api
gcloud projects add-iam-policy-binding PROJECT_ID \
  --member=serviceAccount:agent-api@PROJECT_ID.iam.gserviceaccount.com \
  --role=roles/aiplatform.user
gcloud projects add-iam-policy-binding PROJECT_ID \
  --member=serviceAccount:agent-api@PROJECT_ID.iam.gserviceaccount.com \
  --role=roles/datastore.user
gcloud firestore databases create \
  --location=europe-west1
```

On Cloud Run the service gets a short-lived access token for this account from the metadata server, so the code holds no secret. If you call a vendor's API directly instead (an OpenAI or Anthropic key), keep the key in Secret Manager, as in [secrets on Cloud Run](https://exodus.elim.io/blog/secrets-on-cloud-run), never in an environment variable typed into a deploy command. More on roles in [IAM for a Cloud Run app](https://exodus.elim.io/blog/iam-for-a-cloud-run-app).

## Step 2. The loop

The sample is plain Node with one dependency, the Firestore client. It calls Gemini's REST method `generateContent`; frameworks such as Google's ADK, LangGraph or the vendors' SDKs wrap the same loop. The model call gets a token from the metadata server and retries 429 (too many requests) and 5xx with exponential backoff and jitter (a longer wait after each failure, shifted by a random amount), which is what Google's retry page asks for:

```ts
let token = { value: "", until: 0 };
async function accessToken() { // the service account's token from the metadata server, no key file
  if (Date.now() < token.until) return token.value;
  const r = await fetch(`${META}/computeMetadata/v1/instance/service-accounts/default/token`,
    { headers: { "Metadata-Flavor": "Google" } });
  const j = await r.json();
  token = { value: j.access_token, until: Date.now() + (j.expires_in - 60) * 1000 };
  return token.value;
}

async function callModel(contents, signal) {
  const url = `${API}/v1/projects/${PROJECT}/locations/global/publishers/google/models/${MODEL}:generateContent`;
  for (let attempt = 0; ; attempt++) {
    const r = await fetch(url, {
      method: "POST", signal,
      headers: { authorization: `Bearer ${await accessToken()}`, "content-type": "application/json" },
      body: JSON.stringify({ systemInstruction: SYSTEM, tools: TOOLS, contents }),
    });
    if (r.ok) return r.json();
    if (![429, 500, 503].includes(r.status) || attempt === 4) throw new Error(`model: ${r.status}`);
    await new Promise((ok) => setTimeout(ok, 2 ** attempt * 1000 * (0.5 + Math.random() / 2)));
  }
}
```

`MODEL` is `gemini-3.5-flash`, `API` is `https://aiplatform.googleapis.com` and `META` is `http://metadata.google.internal`; the last two are variables only so the local test can point them at a stand-in. The loop itself, inside the `POST /chat` handler:

```ts
for (let turn = 0; turn < MAX_TURNS; turn++) {
  const reply = await callModel(contents, stop.signal);
  usage.calls++;
  usage.in += reply.usageMetadata?.promptTokenCount ?? 0;
  usage.out += reply.usageMetadata?.candidatesTokenCount ?? 0;
  const content = reply.candidates[0].content;
  await save(content);
  const calls = content.parts.filter((p) => p.functionCall);
  if (calls.length === 0) {
    send("answer", { text: content.parts.map((p) => p.text ?? "").join("") });
    return;
  }
  const results = [];
  for (const { functionCall: { name, args } } of calls) {
    send("tool", { name, args });
    const response = tools[name] ? await tools[name](args) : { error: `no tool ${name}` };
    results.push({ functionResponse: { name, response } });
  }
  await save({ role: "user", parts: results });
}
send("error", { message: `stopped after ${MAX_TURNS} model calls` });
```

`MAX_TURNS` (8) stops a model that keeps calling tools. `save` writes each turn to Firestore before the next step. `usage` collects the token counts the model reports, logged as one JSON line per run, so Cloud Logging can sum the cost per user or feature.

## Step 3. Sessions in Firestore, not in memory

A chat has many messages, and Cloud Run sends each request to whichever instance has room. Session affinity can route a client back to the same instance, but Google calls it best effort: it breaks when the instance ends or is full. So the conversation lives in Firestore, one document per turn under `sessions/{id}/turns`, and the handler loads it at the start of every message:

```ts
const turns = (session) => db.collection("sessions").doc(session).collection("turns");

const past = await turns(session).orderBy("n").get();
const contents = past.docs.map((d) => d.data().content);
const save = (content) => {
  contents.push(content);
  return turns(session).doc(String(contents.length).padStart(6, "0")).set({ n: contents.length, content });
};
```

The session id comes from the client, so the handler checks it against `^[\w-]{1,64}$` before it becomes part of a document path, and refuses anything else with 400. In a real product the id also belongs to a signed-in user, checked on the server, as in [a SaaS architecture on Cloud Run](https://exodus.elim.io/blog/a-saas-architecture-on-cloud-run).

The same trap sits inside frameworks. ADK's `adk deploy cloud_run` keeps sessions and artifacts in memory unless you give it a session service and an artifact service (the `session_service_uri` and `artifact_service_uri` options), and its documentation says they are lost whenever an instance is recycled. Firestore is one choice; Postgres works as well, see [Cloud SQL, Firestore or serverless Postgres](https://exodus.elim.io/blog/cloud-sql-firestore-or-serverless-postgres).

## Step 4. Deploy

```shell
gcloud run deploy agent-api \
  --source . \
  --region europe-west1 \
  --service-account agent-api@PROJECT_ID.iam.gserviceaccount.com \
  --cpu 1 \
  --memory 512Mi \
  --concurrency 80 \
  --timeout 15m \
  --max-instances 10 \
  --set-env-vars GOOGLE_CLOUD_PROJECT=PROJECT_ID,MODEL=gemini-3.5-flash \
  --no-allow-unauthenticated
```

The timeout is 5 minutes by default and 60 at most; this sets 15. The instance cap limits what a spike can spend. The service starts private; a web app in front or a sign-in check decides who may call it.

## Streaming, and what happens when the client leaves

The handler answers with `content-type: text/event-stream` and writes one event per step: `tool` when the model asks for a tool, `answer` at the end, `error` when the run stops. Node sends a response without a length as `Transfer-Encoding: chunked`, which is all Cloud Run needs to stream it. By default Cloud Run downgrades HTTP/2 to HTTP/1 on the way to the container, which is fine for server-sent events. [WebSockets and SSE on Cloud Run](https://exodus.elim.io/blog/websockets-and-sse-on-cloud-run) covers the client side.

When the user closes the tab, the request ends. With request-based billing Cloud Run gives an instance CPU only while it is processing requests, so a loop that carries on after the client is gone runs without guaranteed CPU, and every model call it makes is still billed. The sample does the opposite: on `close` it aborts the model call in flight and stops.

```ts
const stop = new AbortController();
res.on("close", () => { if (!res.writableEnded) stop.abort(); });
```

In the test the client left after 2 seconds, during the second model call. The stand-in saw that call aborted, made no further calls, and Firestore held three saved turns, so the user can send the next message and the conversation continues from there. See [work after the response](https://exodus.elim.io/blog/work-after-the-response) for the general rule.

## Runs longer than a request

Google's timeout page recommends, for any timeout over 15 minutes, retries and handlers that resume where they left off, because a long connection is more likely to drop and a client that reconnects is not guaranteed the same instance. An agent that researches for 20 minutes should not hold an HTTP request open.

Split it. `POST /runs` writes the run to Firestore, queues a Cloud Task, and returns the run id at once. A private worker service does the loop, writing each event to Firestore. The client reads the events with `GET /runs/{id}/events?after=N`, and after a dropped connection it asks again from the last event it saw. A Cloud Tasks HTTP handler gets 10 minutes by default and 30 at most per attempt; longer work is cut into steps, one task per step, or goes to a [job](https://exodus.elim.io/blog/cloud-run-services-jobs-and-worker-pools). The queue and its permissions are in [background workers with Cloud Tasks](https://exodus.elim.io/blog/background-workers-with-cloud-tasks); the receiver-queue-worker shape is the same as in [Stripe webhooks on Cloud Run](https://exodus.elim.io/blog/stripe-webhooks-on-cloud-run).

## Tools: MCP servers and running code

A tool that is a few lines of code lives in the agent's container, like `get_order` here. A tool with its own data, permissions or release cycle becomes its own service, and the Model Context Protocol (MCP) is the common way to expose it. Cloud Run hosts MCP servers that use the streamable HTTP transport; servers that only speak stdio do not run there.

Keep an MCP server private. An agent on Cloud Run calls it with an ID token, the same service-to-service authentication as any private service, or runs it as a sidecar container and calls `http://localhost` on another port. From a laptop, `gcloud run services proxy` exposes a private server on localhost with your own credentials.

An agent that writes and runs code needs a box it cannot climb out of. Cloud Run sandboxes, in Preview, run a command in an isolated environment inside the instance, with no access to the parent's environment variables, secrets or the metadata server, and with outbound traffic blocked unless you allow it. You turn them on with the `sandbox-launcher` flag of `gcloud beta run deploy` and call the binary at `/usr/local/gcp/bin/sandbox`:

```shell
sandbox do \
  -- /usr/bin/python3 -c "print(1+2)"
```

Sandboxes share the instance's CPU and memory, so size the instance for the agent plus its running sandboxes. For code from your customers rather than from your own agent, Google's guide on untrusted code recommends one project per tenant.

## Concurrency, measured

The sample ran in two containers, each held to 1 CPU and 512 MiB like a 1 vCPU Cloud Run instance, against the Firestore emulator and a stand-in that answers in Gemini's format after 1.5 seconds:

![Terminal output of the test script: a run on a new and a warm instance, 50 and 200 runs at once with CPU used and peak memory, a client that left, two 429s, a runaway loop, a message on a second instance, and a bad session id](https://exodus.elim.io/media/v2026-09-16/t/exodus/assets/image-eda0d6ae23513e42ec71be52d15df29ded6eb249e4c06fcfdb7536221f86e3be-680x356-svg/bytes "The sample's cases, real output from our build machine")

Fifty runs at once all answered in 3.5 seconds of wall time and used 0.61 seconds of CPU. Two hundred took 4.1 seconds and 1.9 seconds of CPU, with memory peaking at 101 MiB (154 MiB in an earlier run). The first run on a new instance took 3.8 seconds longer than a warm one; a separate check put it on the Firestore client's first query, 3.75 seconds against the emulator and 7 ms after that. Start the client and send one cheap query at startup, or keep a [minimum instance](https://exodus.elim.io/blog/min-instances-and-startup-cpu-boost). The same first work, a database connection and a token from the metadata server, shows in the measurements of [Rust on Cloud Run](https://exodus.elim.io/blog/rust-on-cloud-run).

![Bar chart: 50 agent runs billed 173.3 seconds at concurrency 1, 3.5 seconds on one instance at concurrency 80, and the instance used 0.612 seconds of CPU](https://exodus.elim.io/media/v2026-09-16/t/exodus/assets/image-71f56394debc530dd6df153fc8cb87eeb11b3bf1341b0028ee5569398db84812-680x380-svg/bytes "Request-based billing counts open requests, so concurrency decides the bill")

With concurrency 1, each of those 50 runs would sit on its own instance and Cloud Run would bill the sum of their times, 173.3 seconds. With concurrency 80 the same work is 3.5 billed seconds on one instance. Concurrency 1 is common advice for CPU-heavy apps; for an agent it multiplies the compute bill. The default of 80 per vCPU fits; at 200 runs at once Cloud Run would start a third instance. More in [concurrency on Cloud Run](https://exodus.elim.io/blog/concurrency-on-cloud-run).

## Rate limits, retries and runaway loops

On pay-as-you-go, Agent Platform answers 429 "Resource exhausted, please try again later." when shared capacity runs short. Google's advice is the global endpoint, which spreads requests across regions, and truncated exponential backoff; its SDKs default to a 1 second first delay, 5 attempts, base 2 and a 60 second ceiling. In the test, two 429s in a row made a run 4.9 seconds slower, and it still answered. Claude models launched after May 26, 2026 share one quota per model family on the global endpoint, and only requests that return 200 are charged.

Retry the model call, never the whole run. A run that already charged a card or sent an email must not do it again because a later model call failed; give such tools an idempotency key. In the runaway case the stand-in asked for a tool every time, and the loop stopped after 8 calls with an `error` event. Pair the cap with the per-run token log and a [budget alert](https://exodus.elim.io/blog/budget-alerts-and-the-billing-export).

## What it costs

Take an assistant with 30,000 runs a month (1,000 a day), each with three model calls and two tools: 7,800 input tokens in total (the context grows with each call), 900 output tokens, 15 seconds of wall time. List prices of October 12, 2026, global endpoint, Cloud Run in europe-west1:

![Bar chart: for 30,000 runs a month, Gemini 3.5 Flash tokens cost $594, Claude Sonnet 5 tokens $738, Cloud Run at most $11.37, Firestore $0](https://exodus.elim.io/media/v2026-09-16/t/exodus/assets/image-b7af778044e235875982bff079546397de133a7413c3ec3a6f0b034d3452cdef-680x380-svg/bytes "The model's tokens are most of the bill; Cloud Run is a few percent at most")

- **Gemini 3.5 Flash** at $1.50 per million input tokens and $9.00 per million output: $0.0198 a run, $594.00 a month. Output includes the model's reasoning, and from Gemini 3.5 the tool declarations count as input on every call.
- **Claude Sonnet 5** at $2.00 and $10.00: $0.0246 a run, $738.00 a month. Regional and multi-region endpoints cost 10% more for either vendor.
- **Cloud Run**, 1 vCPU and 512 MiB, counted as if no two runs overlapped: 450,000 instance-seconds, $11.37 a month with the free tier spent elsewhere, $6.48 with it. Overlap makes it lower.
- **Firestore**: 6 writes and 10 reads a run is 6,000 writes and 10,000 reads a day, inside the free 20,000 and 50,000.

At 3,000 runs the Gemini tokens are $59.40 and Cloud Run $0 to $1.14. At 300,000 runs they are $5,940 against at most $113.75, plus $1.68 for Firestore. The levers are on the model side: a smaller model for easy steps, a shorter history, and caching (cached Gemini 3.5 Flash input is $0.15 per million instead of $1.50; a Claude cache read is 10% of the input price). For a model you host yourself, see [GPUs on Cloud Run](https://exodus.elim.io/blog/gpus-on-cloud-run).

## What breaks

1. **Sessions in memory.** Lost when an instance is recycled or the next message lands elsewhere.
1. **Work after the client leaves.** No guaranteed CPU, and the model calls are still billed.
1. **The 5 minute default timeout.** A long run is cut mid-stream.
1. **No cap on the loop.** A model that calls tools forever spends until something stops it.
1. **Retrying side effects.** Retrying a whole run repeats the tools that already ran.
1. **Concurrency 1.** Fifty runs bill fifty times the time.
1. **An API key in an environment variable or the image.**
1. **Model-written code in the agent's own container,** with its service account and network.

## Checklist

1. The agent runs as a service with request-based billing, 1 vCPU, 512 MiB, concurrency 80 and an instance cap.
1. The service has its own service account with `roles/aiplatform.user` and `roles/datastore.user`, and no key file.
1. Every turn is in Firestore or Postgres; the client's session id is validated and tied to a signed-in user.
1. Events stream as SSE; a closed connection aborts the model call.
1. The loop has a cap, and only the model call is retried, with backoff and jitter.
1. Each run logs its model calls and tokens as one JSON line.
1. Runs over 15 minutes go through Cloud Tasks to a worker, and the client can resume from the last event.
1. Tools with their own data are private services; generated code runs in a sandbox.
1. A budget alert watches the model's spend as well as Cloud Run's.

## Where Exodus comes in

`exodus audit` on this sample found one thing: the `text/event-stream` line in `server.js`, reported as a long-lived connection that keeps its instance billed while it is open, with a link to [the patterns that break on Cloud Run](https://exodus.elim.io/blog/patterns-that-break-on-cloud-run). It also confirmed what was not there: no sessions in memory, no work after the response, no files on disk. It does not recognize model SDKs or MCP, and its estimate does not include model tokens, so for an agent add the token line from the section above to the report's number yourself.

## FAQ

### Can Cloud Run stream a language model's output to the browser?

Yes. Cloud Run streams any response sent with chunked transfer encoding, with no setting, so a handler that answers with the event stream type and writes one server-sent event per step works. Cloud Run downgrades HTTP/2 to HTTP/1 on the way to the container by default, which is fine for server-sent events.

### Where should an agent keep conversation history on Cloud Run?

In a database such as Firestore or Postgres, never in the instance's memory. Cloud Run sends each request to whichever instance has room, and session affinity is best effort. ADK keeps sessions in memory unless it gets a session service, and loses them when an instance is recycled.

### How long can an agent run on Cloud Run?

A request runs 5 minutes by default and 60 at most. For timeouts over 15 minutes Google recommends retries and handlers that resume, so a long run goes through Cloud Tasks to a private worker, which gets 10 minutes per attempt by default and 30 at most, with longer work cut into steps.

### What concurrency should an AI agent service use?

Keep the default of 80 per vCPU. The loop waits on the model, so in our test 50 runs on one instance used 0.61 seconds of CPU and were billed 3.5 seconds. At concurrency 1 the same runs would be billed 173.3 seconds on separate instances.

### Does a Cloud Run service need an API key to call Gemini?

No. The service gets a short-lived access token for its own service account from the metadata server, and the account needs the Vertex AI User role. A vendor's key for a direct API call belongs in Secret Manager, never in an environment variable typed into a deploy command.

## Sources

1. [Host AI agents on Cloud Run resources, Google (services for request-driven agents, instances for always-on singleton agents, worker pools for queue consumers, jobs for run-to-completion work; streaming HTTP responses and WebSockets; Gemini API, Agent Platform endpoints or a GPU service for models; Memorystore, Firestore or Memory Bank for memory; pgvector on Cloud SQL or AlloyDB; MCP, code execution, browser automation as tools; updated Oct 9, 2026, read Oct 12, 2026)](https://docs.cloud.google.com/run/docs/ai-agents)
2. [Create and manage Cloud Run instances, Google (Preview; individually addressable singleton containers managed with create, stop, start, update, delete; gcloud beta run instances; no persistent disk, stopping deletes in-memory files; read Oct 12, 2026)](https://docs.cloud.google.com/run/docs/instances/create-and-manage-instances)
3. [Configure request timeout for services, Cloud Run, Google (5 minutes by default, up to 60 minutes; for a timeout over 15 minutes Google recommends retries and handlers that resume, a client that reconnects is not guaranteed the same instance; updated Oct 9, 2026, read Oct 12, 2026)](https://docs.cloud.google.com/run/docs/configuring/request-timeout)
4. [Invoke with an HTTPS request, Cloud Run, Google (response streaming needs no configuration, the server answers with Transfer-Encoding: chunked; HTTP/2 requests are downgraded to HTTP/1 unless HTTP/2 end-to-end is set; read Oct 12, 2026)](https://docs.cloud.google.com/run/docs/triggering/https-request)
5. [Billing settings for services, Cloud Run, Google (request-based billing charges only while instances process requests, start and shut down, and allocates CPU only during request processing; instance-based billing for work after the response; read Oct 12, 2026)](https://docs.cloud.google.com/run/docs/configuring/billing-settings)
6. [Maximum concurrent requests for services, Cloud Run, Google (up to 1,000; gcloud and Terraform default to 80 times the number of vCPUs; higher concurrency suits I/O-bound work and fewer instances cost less; read Oct 12, 2026)](https://docs.cloud.google.com/run/docs/about-concurrency)
7. [Set session affinity for services, Cloud Run, Google (a cookie with a 30-day TTL, best effort only: broken when the instance ends or is at maximum concurrency or CPU; read Oct 12, 2026)](https://docs.cloud.google.com/run/docs/configuring/session-affinity)
8. [Cloud Run pricing, Google (request-based billing in Tier 1 regions: $0.000024 per vCPU-second and $0.0000025 per GiB-second active, $0.40 per million requests; free each month 180,000 vCPU-seconds, 360,000 GiB-seconds and 2 million requests; europe-west1 rates confirmed in the Cloud Billing Catalog API; read Oct 12, 2026)](https://cloud.google.com/run/pricing)
9. [Host MCP servers on Cloud Run, Google (streamable HTTP transport is supported, stdio is not; local clients authenticate with the invoker role through gcloud run services proxy, an OIDC ID token or IAP; clients on Cloud Run use a sidecar on localhost or service-to-service authentication; updated Oct 9, 2026, read Oct 12, 2026)](https://docs.cloud.google.com/run/docs/host-mcp-servers)
10. [Code execution in Cloud Run and Configure sandboxes for services, Google (Preview; sandboxes run untrusted code inside the instance in the second-generation environment and share its CPU and memory; no access to the parent's environment variables, secrets or metadata server by default; outbound traffic blocked unless --allow-egress; enabled with gcloud beta run deploy --sandbox-launcher; the binary is /usr/local/gcp/bin/sandbox; updated Oct 9, 2026, read Oct 12, 2026)](https://docs.cloud.google.com/run/docs/code-execution)
11. [Cloud Run for multi-tenant platforms running untrusted code, Google (Google recommends one project per tenant for code it cannot scan, dedicated service identities with restricted permissions; read Oct 12, 2026)](https://docs.cloud.google.com/run/docs/securing/multi-tenant)
12. [Deploy to Cloud Run, Agent Development Kit documentation (adk deploy cloud_run with --project, --region, --service_name, --session_service_uri, --memory_service_uri, --with_ui; without --session_service_uri and --artifact_service_uri sessions and artifacts are kept in memory and lost when an instance is recycled; gcloud flags after --; read Oct 12, 2026)](https://google.github.io/adk-docs/deploy/cloud-run/)
13. [Build and deploy an AI agent to Cloud Run using ADK, Google (Cloud Run Admin, Vertex AI and Cloud Build APIs; the Vertex AI User role roles/aiplatform.user; GOOGLE_GENAI_USE_VERTEXAI; updated Oct 9, 2026, read Oct 12, 2026)](https://docs.cloud.google.com/run/docs/ai/build-and-deploy-ai-agents/deploy-adk-agent)
14. [Gemini 3.5 Flash, Gemini Enterprise Agent Platform, Google (model ID gemini-3.5-flash; read Oct 12, 2026)](https://docs.cloud.google.com/gemini-enterprise-agent-platform/models/gemini/3-5-flash)
15. [Agent Platform generative AI pricing, Google (Gemini 3.5 Flash on the global endpoint: $1.50 per million input tokens, $9.00 output including reasoning, $0.15 cached input; non-global endpoints $1.65 and $9.90; from Gemini 3.5, function declarations count as input tokens; only requests that return 200 are charged; Claude Sonnet 5 $2.00 input and $10.00 output; confirmed in the Cloud Billing Catalog API, where EU and US multi-region Claude SKUs are 10% higher and a Sonnet 5 cache read is $0.20; read Oct 12, 2026)](https://cloud.google.com/gemini-enterprise-agent-platform/generative-ai/pricing)
16. [Claude on Google Cloud, Anthropic (model IDs such as claude-sonnet-5; global, multi-region and regional endpoints, the last two with a 10% premium; read Oct 12, 2026)](https://platform.claude.com/docs/en/build-with-claude/claude-on-vertex-ai)
17. [Pricing, Anthropic (Claude Sonnet 5 at $2 input and $10 output per million tokens is now the standard price; a cache hit costs 0.1 times the input price on most models; read Oct 12, 2026)](https://platform.claude.com/docs/en/about-claude/pricing)
18. [Quotas for Anthropic Claude models, Google (models launched after May 26, 2026 share one quota per model lineage on the global endpoint; global and multi-region quotas are separate buckets; read Oct 12, 2026)](https://docs.cloud.google.com/gemini-enterprise-agent-platform/models/partner-models/claude/quotas)
19. [Error code 429 and Retry strategy, Gemini Enterprise Agent Platform, Google (pay-as-you-go answers 429 'Resource exhausted, please try again later.'; use the global endpoint and truncated exponential backoff; SDK defaults: initial delay 1 s, 5 attempts, base 2, maximum delay 60 s; retry only 429, 408 and 5xx; do not retry non-idempotent operations unconditionally; read Oct 12, 2026)](https://docs.cloud.google.com/gemini-enterprise-agent-platform/models/deploy/error-code-429)
20. [Gemini Enterprise Agent Platform pricing: Agent Runtime, Google ($0.085 per vCPU-hour and $0.009 per GiB-hour after 50 vCPU-hours and 100 GiB-hours free each month; idle time between turns is not billed; Sessions billed from September 1, 2026; read Oct 12, 2026)](https://cloud.google.com/products/gemini-enterprise-agent-platform/pricing)
21. [Firestore Standard edition pricing, Google (one free database per project: 1 GiB, 50,000 reads, 20,000 writes and 20,000 deletes a day; in europe-west1 the Cloud Billing Catalog API lists $0.033 per 100,000 reads and $0.099 per 100,000 writes above that; read Oct 12, 2026)](https://cloud.google.com/firestore/pricing)
22. [Firestore IAM roles, Google (Cloud Datastore User, roles/datastore.user, read and write access to data; read Oct 12, 2026)](https://docs.cloud.google.com/iam/docs/roles-permissions/firestore)
23. [Create HTTP target tasks, Cloud Tasks, Google (an HTTP handler has 10 minutes by default and 30 at most; OIDC token for a Cloud Run handler; updated Oct 9, 2026, read Oct 12, 2026)](https://docs.cloud.google.com/tasks/docs/creating-http-target-tasks)
24. [gcloud CLI reference, Google (run deploy --timeout as a duration, --concurrency, --service-account, --cpu, --memory, --max-instances, --no-allow-unauthenticated; beta run deploy --[no-]sandbox-launcher; beta run instances create, start, stop, delete; firestore databases create --location, edition standard by default; run services proxy; gcloud 587.0.0, read Oct 12, 2026)](https://docs.cloud.google.com/sdk/gcloud/reference/run/deploy)
25. Exodus team: the sample agent service in this guide ran on our build machine on Node 24 with @google-cloud/firestore 9.3.1, against the Firestore emulator and a local stand-in that answers in Gemini's REST format after 1.5 s, in containers held to 1 CPU and 512 MiB; exodus 0.1.1 audit on the sample; read-only gcloud commands against our own project; Oct 12, 2026
