Multimodal Cloud Run sandbox¶
Status: Current | Last reviewed: 2026-10-08
Deploy the standalone multimodal service to Google Cloud Run and connect a GECX agent to its MCP endpoint. Use this runbook for the sandbox companion-form demo; use persistent Postgres and Redis before deploying production workloads.
Resources¶
| Setting | Value |
|---|---|
| Project | us-con-gcp-sbx-0000500-032725 |
| Project number | 288250959474 |
| Region | us-central1 |
| Cloud Run service | trueserve-multimodal-demo |
| Private service origin | https://trueserve-multimodal-demo-288250959474.us-central1.run.app |
| Artifact Registry repository | multimodal-service |
| Runtime identity | 288250959474-compute@developer.gserviceaccount.com |
| Catalogues | standard, insurance-auto, acg-auto |
| OAuth client ID | gecx-multimodal-demo |
| Verified build | 27e2cd18-515b-4649-be17-2263fb7a281a |
| Verified revision | trueserve-multimodal-demo-00004-ggw |
The app, Postgres 16 and Redis 7 run in one Cloud Run instance. The service
keeps one warm instance, caps instances at one, and accepts requests for up to one hour.
Each container has one vCPU; memory allocations are 768 MiB, 512 MiB and
256 MiB respectively. Only the app's port 8080 is exposed. Postgres uses a
256 MiB memory volume; Redis is limited to 128 MiB with noeviction.
Warning: Instance replacement and deployments reset all experiences and submissions. Catalogues are embedded in the image and survive replacement. Do not split traffic across revisions or increase replicas while using independent sidecar stores.
Cloud Run requires Google IAM authentication on every route. The project
already grants run.routes.invoke to the CES service agent through
roles/ces.serviceAgent. GECX uses Service Agent ID Token authentication.
The app independently verifies the Google signature, issuer, audience, expiry
and allowlisted service-account email before allowing MCP calls. Operator
session creation and browser APIs retain their separate bearer/session tokens.
Unauthenticated requests to the service URL return HTTP 403.
The runtime identity already has access to the application secrets. No IAM policy changes are required. On another project, grant the runtime access to its application secrets and grant the CES service agent invocation permission on the private Cloud Run service.
The operator account cannot change the Cloud Run invoker policy. Keep this service private; use the authenticated browser preview below. A public customer deployment needs an approved ingress or customer backend that authenticates to Cloud Run.
Secrets¶
The trueserve-multimodal-demo- prefix has these Secret Manager entries:
| Suffix | Content |
|---|---|
api-token |
Operator bearer token for creating experiences |
mcp-token |
Agent bearer token |
session-secret |
Session JWT signing secret |
db-password |
Postgres sidecar password |
database-url |
Postgres connection string using that password and localhost |
oauth-secret |
Confidential OAuth client secret |
oauth-signing-secret |
OAuth signing secret |
Versions are pinned to 1 in the manifest. Rotate the corresponding version
references together. Generate independent values of
at least 32 bytes. Create secrets with gcloud secrets create --data-file=-
and pass values on private stdin; never put credentials in command arguments,
Git, build substitutions or build logs.
The initial deployment also saved generated credentials in the devcontainer at
~/.local/state/trueserve/multimodal-cloud-run/credentials.json, mode 0600.
The operator account can create secrets but cannot read secret versions; the
launcher uses this existing local file. It does not impersonate another account.
Keep this file private. On a different workstation, use an authorized operator
credential file or request access to the specific operator secret.
Build and deploy¶
Run these commands from the repository root. The upload exclusions keep virtual environments, local environment files and test/example data out of Cloud Build. The image's Dockerfile exclusions also exclude deployment material from the application image.
gcloud builds submit apps/multimodal-service \
--project=us-con-gcp-sbx-0000500-032725 \
--region=us-central1 \
--config=apps/multimodal-service/deploy/cloudbuild.yaml \
--substitutions=_IMAGE_PREFIX=us-central1-docker.pkg.dev/us-con-gcp-sbx-0000500-032725/multimodal-service,_RELEASE=gecx-20261008-v5 \
--async
Use a new release tag for each build. The build mirrors the Postgres and Redis
images into the same repository. Wait for SUCCESS, then use its build ID:
python apps/multimodal-service/deploy/render_cloud_run.py \
--project=us-con-gcp-sbx-0000500-032725 \
--region=us-central1 \
--build-id=BUILD_ID \
--output=/tmp/multimodal-cloud-run.yaml
gcloud run services replace /tmp/multimodal-cloud-run.yaml \
--project=us-con-gcp-sbx-0000500-032725 \
--region=us-central1
The renderer requires
a successful build and pins all three images by digest. It uses the deterministic
Cloud Run origin for MULTIMODAL_PUBLIC_URL, which is also the MCP Host
allowlist and OAuth issuer. Use that origin consistently instead of the
alternative hash-based Cloud Run hostname.
The renderer defaults to private access. Its optional --public flag requires
run.services.setIamPolicy and is not used for this deployment.
MULTIMODAL_GOOGLE_SERVICE_ACCOUNTS allowlists only the project's CES service
agent. Google ID token audiences are limited to the configured service origin,
its /mcp endpoint, and the canonical Cloud Run URL aliases explicitly supplied
in MULTIMODAL_GOOGLE_ID_TOKEN_AUDIENCES. No wildcard audiences are accepted.
For a new service, rerun the renderer after its first deployment and replace
the service again so it can include Google's newly assigned canonical URL.
One warm instance avoids timing out GECX discovery while the sidecar stores initialize. Idle capacity is billable. To allow the sandbox to scale to zero between demos, run:
gcloud run services update trueserve-multimodal-demo \
--project=us-con-gcp-sbx-0000500-032725 --region=us-central1 --min=0
Scaling to zero clears experiences; create new ones when resuming the demo.
The app starts only after the Postgres and Redis startup probes succeed.
MULTIMODAL_BOOTSTRAP_EDGE_SCHEMA=true creates the isolated session table.
Never use this sidecar manifest for the platform deployment.
Authenticated browser preview¶
Run the Google Cloud CLI's official proxy in the devcontainer:
gcloud run services proxy trueserve-multimodal-demo \
--project=us-con-gcp-sbx-0000500-032725 \
--region=us-central1 --port=18085
The proxy binds locally and adds X-Serverless-Authorization using your active
Google identity. It preserves the app's Authorization header and streams SSE.
Forward devcontainer port 18085 privately to your laptop in VS Code. Keep the
proxy terminal open. Do not make the forwarded port public.
Create a test experience¶
python apps/multimodal-service/examples/create_gecx_session.py \
--google-iam --app-origin=http://127.0.0.1:18085 \
--output=/tmp/gecx-experience.json
The launcher
prints the non-secret experience ID and saves the private browser link, session
token and GECX variable mapping in a mode-0600 file. Open its app_url in your
browser and put gecx_variables.experienceSessionId into the GECX test session.
Create one experience per customer conversation; do not put a shared session
ID into production agent defaults.
All three catalogues are loaded. The bundled /demo/ page renders only the
ACG driver/vehicle forms and the legacy AutoChangeForm. Other catalogue
components require a client renderer implementing their contract.
The launcher excludes connect_auto_experience: this deployment has no AWS
SMS configuration. Browser links are distributed by the operator or customer
application.
GECX configuration¶
Follow the GECX operator guide.
The toolset JSON
contains the exact API configuration, including Google service-agent ID token
authentication and the $context.variables.experienceSessionId header binding.
No MCP credential is copied into the GECX agent. The browser proxy is for the
operator; GECX calls the private Cloud Run endpoint directly.
The toolset requests synchronous execution with a 60-second timeout.
await_auto_change waits for at most 20 seconds; pending requires a later
check. A form POST returns through an outstanding call or a subsequent read,
not an unsolicited invocation into an idle GECX conversation.
Synchronous execution was verified with a native GECX agent. Asynchronous
execution returned pending placeholders and did not complete reliably in the
validation session. Validate it and voice interruption separately before changing
the pilot configuration.
Validation¶
Check IAM-authenticated health, unauthenticated ingress rejection, authenticated MCP initialization/discovery and a complete create-render-submit-read sequence. Health alone does not prove the database, catalogue or GECX connection works.
On 2026-10-08, revision trueserve-multimodal-demo-00004-ggw passed:
- 44 unit tests, including signed Google identity-token acceptance and rejection.
- Two live HTTP/MCP tests covering both insurance catalogues, submission waits, isolation, retries, replay and revocation.
- Native GECX discovery of all 12 tools and an agent catalogue-list response.
- A native GECX synchronous agent turn with a real session-variable header:
get_session_info→start_auto_change→ SSE form delivery → synthetic HTTP submission →await_auto_change→ the agent's correct demo receipt. - Anonymous Cloud Run rejection and authenticated browser-proxy access.
The temporary GECX validation application was removed after testing. No existing GECX agents were changed. Voice/PSTN/SIP and manual browser interaction remain outside this validation.
The existing live suite accepts a private MULTIMODAL_TEST_CONFIG JSON file with
base_url, api_token, oauth_secret, and
oauth_client_id: "gecx-multimodal-demo" and google_iam: true. It obtains a
Google identity token from the active CLI account and tests both insurance catalogue
variants, OAuth, cross-session denial, retries, replay, late reads and revocation:
cd apps/multimodal-service
MULTIMODAL_TEST_CONFIG=/private/path/cloud-run-test.json \
.venv/bin/python -m pytest tests/test_live_edge.py -q
Do not put the private config file into the source directory. The test uses synthetic data and sends no text messages.
Sources¶
- Google: MCP tools
- Google: tool authentication
- Google: multiple containers
- Google: private service authentication