
Running Autonomous Agent Swarms in Remote Sandboxes: From Antigravity Desktop/CLI to Cloud Run MicroVM Containers with the SDK
1. Introduction: From Local Desktop to Remote Sandboxes
One prompt, one model, one enormous context window — and somewhere around the fourth file, the reasoning drifts.
Part 1 of this series answered that with the Modular Agent Swarm Pattern: break the monolithic coding prompt into specialized, autonomous subagents, then fence each one behind a strict directory scope, a shared data contract, and an asymmetric verification gate. Context bloat disappears. Drift has nowhere to accumulate.
The playbooks were Markdown. The swarm ran on a laptop, through the Google Antigravity CLI and the Antigravity 2.0 Desktop IDE:
# Part 1: local developer workstation invocation
agy --file orchestrator.md
Antigravity 2.0 Desktop is an excellent workbench for prototyping agent contracts. It is the wrong place to run them at scale. A laptop hits three ceilings, fast:
- Workstation security risk. Autonomous agents write code, install dependencies, run migrations, and execute shell commands. Unvetted build steps on a personal machine are a real risk.
- Toolchain fragmentation. Every engineer maintaining identical versions of Node.js 20, Python 3.11, the Terraform CLI, and the Google Cloud SDK is a standing invitation to configuration drift.
- No automation hook. Cloud Scheduler cannot click a desktop GUI. Neither can a Cloud Task or a queued batch job.
So move the swarm off the workstation entirely. The same modular Markdown specifications and quality gates from Part 1, lifted into an isolated remote sandbox container — the Google Antigravity Python SDK (google-antigravity) running on Google Cloud Run (Gen 2 MicroVMs).
📦 Open source repository The full source code, Terraform configuration, and Dockerfiles discussed here are open sourced under the Apache 2.0 License at github.com/gbechara/antigravity-in-cloudrun.
2. Architecture: The Remote Sandbox Harness
To run agent swarms remotely, the container cannot be a bare Python runtime. It has to act as a complete software engineering tool harness.
When the Lead Orchestrator dispatches subagents to write and verify code, the container must already contain every tool the agents need to author, compile, and validate that code deterministically — without a network round trip for something as basic as a linter.

Key architectural decisions
1. Control plane and output plane are decoupled.
- Control plane (/app/control-plane) is mounted read-only. It holds orchestrator.md, the subagent contracts, and the domain skills. Prompt engineering and orchestration logic stay completely separate from the target application codebase.
- Output plane (/workspace/target-app) is the mutable working directory where subagents author and compile code.
2. Cloud Run Gen 2 (MicroVMs).
- Default Cloud Run (Gen 1) uses gVisor, which intercepts Linux syscalls in user space. That is secure and fine for standard HTTP APIs, but it slows down heavy disk I/O such as npm install and terraform init, and it restricts some of the syscalls native toolchains expect.
- Gen 2 gives you full Linux kernel compatibility inside a lightweight microVM with direct NVMe disk speed, which makes dependency installation and multi-threaded compilation practical.
2.1 What changed in the Markdown control plane since Part 1
The swarm is still driven entirely by Markdown, and if you read Part 1 you will recognise every file. But moving from a developer’s laptop to an unattended container changed four things about those files, and the diff is instructive.
Part 1 (local workspace) Part 2 (container)
───────────────────────────────────── ─────────────────────────────────────
projects/ /app/control-plane/ ← read-only image layer
├── orchestrator-workspace/ ├── orchestrator.md
│ ├── orchestrator.md ├── agents/
│ └── agents/ │ ├── backend_implementer.md
│ ├── backend_implementer.md │ ├── frontend_implementer.md
│ ├── frontend_implementer.md │ ├── test_engineer.md
│ ├── test_engineer.md │ ├── security_checker.md
│ ├── security_checker.md │ ├── transparency_explainer.md
│ └── transparency_explainer.md │ └── iac_engineer.md ← new
└── target-saas-app/ └── skills/
├── shared/types.ts └── terraform_validator/ ← new
├── server/
├── client/ /workspace/target-app/ ← gcsfuse mount
└── tests/e2e/ ├── shared/types.ts
├── server/ client/ tests/
└── infra/terraform/ ← new
1. The control plane became an immutable image layer. In Part 1 the two planes were sibling directories on a writable local disk; the separation was a convention. Here control-plane/ is COPY-ed into the image at /app/control-plane and never written to at runtime, while the output plane is a GCS-backed mount. Changing a prompt now requires a rebuild. That sounds like friction, and it is — but it also means the exact playbook that produced a given artifact is pinned to an image digest, which is the difference between an experiment you can repeat and one you can only describe.
2. A sixth agent, and the swarm’s first domain skill. Part 1 shipped five contracts. Part 2 adds iac_engineer.md, which owns $TARGET_APP_DIR/infra/terraform/ and authors main.tf, variables.tf, outputs.tf, and versions.tf, plus a reusable skills/terraform_validator/SKILL.md. Stage 3's deterministic gate grew a matching rung:
cd $TARGET_APP_DIR/infra/terraform && terraform validate && tflint
It is not a coincidence that the agent added for a cloud article is also the one that most needs a sandbox. The IaC engineer is the only swarm member whose output can touch real infrastructure, which is precisely why its contract ends with “NEVER run terraform apply" — and why that instruction is also enforced in code, in Section 5.
3. Stage 0 stopped asking. Part 1 called it Interactive Environment Discovery: the orchestrator confirmed the target directory with the developer before doing anything. Part 2 calls it Dynamic Environment Discovery and reads $TARGET_APP_DIR from the environment. One word in a heading, but it is the first crack in an assumption that runs through the whole Part 1 playbook — that someone is sitting there. Section 5.1 is about what happens when you do not chase that assumption all the way down.
4. Every contract gained a headless preamble. orchestrator.md opens with a new non-negotiable rules block, and an identical ## Execution Environment (Headless Sandbox) section is appended to all six agent contracts:
## Execution Environment (Headless Sandbox)
You are running as a batch job inside a Cloud Run sandbox. There is **no human
attached to this session**.
* **Never call `ask_question`.** It cannot be answered and the call is blocked
by a policy hook. If a requirement is ambiguous, pick the most reasonable
option, state the assumption, and keep going.
* **Resolve `$TARGET_APP_DIR` before writing.** Every file you create must sit
under that directory, never directly under `/workspace`.
* **Keep commands non-interactive.** Always pass the non-interactive flag
(`npm ci`, `npx --yes`, `apt-get -y`, `terraform -input=false`).
* **`/workspace` is a GCS-backed fuse mount.** Do not bulk-move or recursively
copy `node_modules`.
Two practical notes. That block is applied by scripts/patch_agent_contracts.py — a small idempotent script that appends the section to any contract missing it and leaves the rest untouched — rather than being pasted six times, because six hand-maintained copies of the same paragraph is just a slower way of ending up with six different paragraphs. And the orchestrator's Stage 2 dispatch now has to pass the resolved absolute path to each subagent — Part 1 could get away with handing over the literal string $TARGET_APP_DIR, since a human-supervised agent would notice the mistake. An unsupervised one does not, as Section 5.1 shows.
The Pillar 2 scope declarations themselves (“STRICTLY restricted to $TARGET_APP_DIR/server/") are unchanged from Part 1. What changed is that they are no longer only a request.
3. Building the Container Tool Harness (Dockerfile)
Rather than relying on ad-hoc shell scripts, we build a container image that packages the complete engineering toolchain the Antigravity SDK harness expects.
Here is the essential part of frontend/Dockerfile:
# frontend/Dockerfile (tool harness highlights)
FROM node:20-bookworm-slim
ENV DEBIAN_FRONTEND=noninteractive
ENV WORKSPACE_DIR=/workspace
ENV TARGET_APP_DIR=/workspace/target-app
ENV CONTROL_PLANE_DIR=/app/control-plane
# 1. System utilities, Python runtime, and C++ build tools
RUN apt-get update && apt-get install -y --no-install-recommends \
bash curl git tar make g++ \
python3 python3-pip python3-venv \
&& rm -rf /var/lib/apt/lists/*
# 2. Multi-stage import of the standalone Terraform CLI binary
COPY --from=hashicorp/terraform:1.9 /bin/terraform /usr/local/bin/terraform
# 3. Google Cloud SDK for cloud resource interactions
RUN curl -sSL https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-cli-linux-x86_64.tar.gz \
| tar -xz -C /usr/local/ \
&& ln -s /usr/local/google-cloud-sdk/bin/gcloud /usr/local/bin/gcloud
# 4. Google Antigravity SDK and Cloud Storage client
RUN pip3 install --no-cache-dir --break-system-packages \
"google-antigravity>=0.1.0" \
"google-cloud-storage>=2.17.0"
# 5. Least-privilege, non-root execution
RUN useradd -m -u 1001 -s /bin/bash agy
WORKDIR /app
# 6. Strict directory ownership
RUN mkdir -p /app/control-plane /app/storage /workspace/target-app \
&& chown -R agy:agy /app /workspace
USER agy
Why this toolchain matters
- Multi-stage Terraform extraction. Copying /bin/terraform straight out of HashiCorp's official image gives you a pinned, statically compiled binary with no apt package clutter and no version drift.
- Defense in depth. Running as the unprivileged agy user (UID 1001) means that even if an agent emits an errant rm -rf, the blast radius is confined to /workspace; it cannot modify the container runtime or host configuration.
4. Orchestrating with the Antigravity Python SDK
In Part 1, the desktop CLI executed orchestrator.md natively. In a remote sandbox we use the Google Antigravity SDK to configure the agent lifecycle programmatically, authenticate through Application Default Credentials (ADC), and dispatch subagents in parallel.
Here is the core of the Python orchestration runner:
# swarm-src/main.py (SDK highlights)
import os
import resource
from config import AppConfig
from hooks import (
audit_tool_execution,
auto_answer_interactions,
validate_tool_boundaries,
)
from google.antigravity import Agent, LocalAgentConfig, types
from google.antigravity.hooks import policy
async def execute_swarm(task_prompt: str = "", target_dir: str | None = None) -> str:
paths = AppConfig.get_paths()
effective_target_dir = target_dir or AppConfig.TARGET_APP_DIR
# /workspace is a gcsfuse mount and holds one descriptor per open object,
# so lift the soft limit to the hard limit before any tooling runs.
soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
if soft < hard:
resource.setrlimit(resource.RLIMIT_NOFILE, (hard, hard))
# 1. Configure the Antigravity agent runtime for Vertex AI
config = LocalAgentConfig(
vertex=True, # Use the Vertex AI backbone
project=AppConfig.PROJECT_ID,
location=AppConfig.REGION,
model=os.environ.get("ANTIGRAVITY_MODEL", "gemini-2.5-flash"),
save_dir=paths["sessions"],
app_data_dir=paths["artifacts"],
skills_paths=[paths["skills"], paths["agents"]], # Modular subagent contracts
capabilities=types.CapabilitiesConfig(
enable_subagents=True, # Parallel subagent spawning
),
policies=[policy.allow_all()],
hooks=[
audit_tool_execution,
validate_tool_boundaries,
auto_answer_interactions,
],
env={
"TARGET_APP_DIR": effective_target_dir,
"PATH": os.environ.get("PATH", ""),
# Nothing can answer a prompt on stdin in a batch run.
"CI": "true",
"DEBIAN_FRONTEND": "noninteractive",
"NPM_CONFIG_YES": "true",
"GIT_TERMINAL_PROMPT": "0",
"TF_INPUT": "0",
# Keep high-churn caches off the GCS-backed mount.
"NPM_CONFIG_CACHE": "/tmp/npm-cache",
"TMPDIR": "/tmp",
},
)
# 2. Load the master orchestrator playbook (stages 0 to 5)
with open(paths["orchestrator"], "r", encoding="utf-8") as handle:
orchestrator_playbook = handle.read()
full_prompt = orchestrator_playbook
if task_prompt:
full_prompt += f"\n\n### User feature request:\n{task_prompt}"
# 3. Execute the multi-agent swarm
async with Agent(config=config) as agent:
response = await agent.chat(full_prompt)
result_text = await response.text()
# 4. Harvest token usage metrics
usage = agent.conversation.total_usage
print(
f"[METRICS] Swarm finished. Tokens: "
f"prompt={usage.prompt_token_count}, "
f"candidates={usage.candidates_token_count}, "
f"thoughts={usage.thoughts_token_count}"
)
return result_text
Key SDK capabilities
- vertex=True with ADC. Authenticates through the container's service account. No long-lived API keys are written to files or environment variables.
- enable_subagents=True. Lets the Lead Orchestrator spawn concurrent workers such as backend_implementer, iac_engineer, and test_engineer.
- skills_paths. Loads and indexes reusable .md skills and agent specifications into the orchestrator's tool registry automatically.
- Gemini 2.5 Flash as the default. Defaulting to Gemini 2.5 Flash cuts coordination latency substantially, which keeps the authoring-and-validation loop tight.
5. Runtime Boundary Enforcement Hooks
A sandbox stops an agent from escaping the microVM, but it does not stop an agent from doing something legal-but-unwanted inside the box — such as running terraform apply against live infrastructure. For that you need a deterministic gate in the runtime itself.
The SDK exposes this through hooks: decorated async functions that the runtime invokes around every tool call. Three of them matter here:
- @hooks.post_tool_call — fires after a tool completes. Ideal for audit logging.
- @hooks.pre_tool_call_decide — fires before execution and must return a types.HookResult. If allow=False, the tool call never runs.
- @hooks.on_interaction — fires when the agent tries to talk to a human, and returns the answer on their behalf.
Here is the guardrail shipped in the repo:
# swarm-src/hooks.py
import logging
import os
from google.antigravity import types
from google.antigravity.hooks import hooks
logger = logging.getLogger("antigravity.swarm.audit")
_WRITE_TOOLS = {
types.BuiltinTools.CREATE_FILE.value,
types.BuiltinTools.EDIT_FILE.value,
}
def _is_within(path: str, root: str) -> bool:
"""True if `path` resolves inside `root` (defeats ../ traversal)."""
try:
return os.path.commonpath([os.path.abspath(path), root]) == root
except ValueError:
return False
@hooks.post_tool_call
async def audit_tool_execution(data):
"""Emit a structured audit entry to Cloud Logging for every tool action."""
logger.info(f"[TOOL AUDIT] Executed: {getattr(data, 'name', 'unknown_tool')}")
@hooks.pre_tool_call_decide
async def validate_tool_boundaries(data) -> types.HookResult:
"""Block destructive commands, out-of-scope writes, and human prompts."""
tool_name = str(getattr(data, "name", ""))
target = os.path.abspath(os.environ.get("TARGET_APP_DIR", "/workspace/target-app"))
# 1. Never mutate real infrastructure unattended.
if tool_name == types.BuiltinTools.RUN_COMMAND.value:
cmd = str((data.args or {}).get("CommandLine", ""))
if "terraform apply" in cmd:
logger.warning(f"[POLICY VIOLATION BLOCKED] {cmd}")
return types.HookResult(
allow=False,
message="Automated 'terraform apply' is restricted. Use 'plan' instead.",
)
# 2. There is no human attached to a batch run.
if tool_name == types.BuiltinTools.ASK_QUESTION.value:
return types.HookResult(
allow=False,
message=(
"This swarm runs headless, so ask_question can never be answered. "
"Choose the most reasonable option, record the assumption, and continue."
),
)
# 3. Keep each agent inside the target application tree (Pillar 2).
if tool_name in _WRITE_TOOLS:
path = data.canonical_path or str((data.args or {}).get("TargetFile", ""))
if path and not any(_is_within(path, root) for root in (target, "/tmp")):
logger.warning(f"[SCOPE VIOLATION BLOCKED] {path}")
return types.HookResult(
allow=False,
message=(
f"Write to '{path}' is outside the swarm's target tree. "
f"All generated code must live under '{target}'."
),
)
return types.HookResult(allow=True)
5.1 Nobody is there to answer: hooks for headless operation
Pillar 2 from Part 1 — strict sub-tree file scoping — is guardrail 3 above. Part 1 enforced it entirely through prompt instructions in the agent contracts, and on a supervised laptop that held up well enough. Running the swarm unattended showed why it should not have. Left to themselves, subagents drifted: the frontend implementer wrote to /workspace/client/ instead of $TARGET_APP_DIR/client/, because the orchestrator had passed the literal string $TARGET_APP_DIR into the dispatch prompt and nothing had expanded it. A contract that says "STRICTLY restricted to" is a request, and a request is only as good as the model's attention budget. Two fixes, in layers: the orchestrator playbook now requires dispatching resolved absolute paths, and the hook rejects anything that lands outside the tree with a message naming the correct destination.
The subtler failure is that an autonomous agent will eventually try to ask you something. Mine did: a subagent hit an ambiguous requirement and called ask_question. In a batch job that call is unanswerable, and the best case is that it wastes a turn. Worse, it is malformed surprisingly often — the harness requires at least two options per question, and a model improvising a free-text question supplies zero, which surfaces as a hard invalid tool call error on that subagent's trajectory.
Blocking the tool is only half a fix, because the runtime can raise interactions through other paths. The complete answer is a second hook that acts as a backstop:
@hooks.on_interaction
async def auto_answer_interactions(data) -> types.QuestionHookResult:
"""Answer on the absent user's behalf so a batch job can never block."""
return types.QuestionHookResult(
responses=[
types.QuestionResponse(
selected_option_ids=[],
freeform_response=(
"No interactive user is available (headless sandbox run). "
"Proceed with the most reasonable default and document the assumption."
),
skipped=True,
)
for _ in (data.questions or [])
],
cancelled=False,
)
The general principle: every place your agent can wait on a human is a place your batch job can hang. Enumerate them and answer them programmatically before you run unattended.
6. Execution Architectures: Cloud Run Instances and Sandboxes
When running remote sandboxes on Google Cloud, two recent Cloud Run capabilities are particularly well suited to autonomous AI workloads.
6.1 Cloud Run instances: dedicated singleton runtimes for AI agents
Google Cloud introduced Cloud Run instances (gcloud beta run instances create), a serverless primitive built for stateful, long-running agents:
- Dedicated singleton, no autoscaling. A standard Cloud Run service scales from 0 to N and back to zero when idle. An instance runs exactly one dedicated copy and does not scale down during idle pauses, so WebSocket connections and in-flight agent threads survive.
- Continuous runtime of up to seven days, with configurable restart policies (always, on-failure, never).
- Stable HTTPS URL. Each instance keeps a persistent URL across restarts, container updates, and redeployments.
- Shared vCPU with burst budgets. Around $5.70 per month for 1 vCPU and 1 GiB of RAM, because a personal agent idles while waiting for instructions and only bursts CPU during active work.
- Persistent workspace through native Cloud Storage mounts. A bucket is mounted straight into /workspace with Cloud Storage FUSE, so generated code, session history, and agent state survive restarts.
Tech Preview caveat: CLI-managed, and not available everywhere. At the time of writing, Cloud Run instances are in Tech Preview. Three consequences are worth planning around:
They are not offered in every region. Preview capacity is rolled out gradually, so a region that serves Cloud Run services perfectly well may still refuse to create an instance. Check the current region list before you commit to one.
They do not appear in the standard Cloud Run page of the Google Cloud console. You create, inspect, and delete them exclusively through gcloud beta run instances, so build your workflow — and especially your cleanup — around the CLI.
The surface may change. Preview flags and field names are not covered by the usual compatibility guarantees.
6.2 Cloud Run sandboxes: safely running AI-generated code
When agents write and then execute code, linters, and compiler tests, how do you stop an untrusted binary from compromising the container or its credentials? Cloud Run now ships a native sandbox launcher (–sandbox-launcher, in public preview).

- Sub-second isolation. Spawns a lightweight, isolated silo in roughly 500 ms through the injected sandbox CLI (sandbox do — <command>).
- Credential and environment isolation. The sandbox sees no host environment variables and cannot reach the metadata server, so untrusted code cannot exfiltrate service account tokens or ADC credentials.
- Deny-by-default network egress. Outbound connections are blocked unless explicitly permitted with –allow-egress.
- Read-only filesystem overlay. Sandboxes share the container’s preinstalled tooling read-only and write changes into an in-memory overlay that is discarded afterwards.
- No extra cost. It runs inside the CPU and memory already allocated to your container, with no third-party virtualization layer.
6.3 Driving the swarm: one front door, two execution paths
It helps to separate two questions that are easy to conflate: where the console runs, and how an individual swarm run executes.

The instance hosts the console you connect to; from there each run either executes in-place or is dispatched to a batch job.
The front door: a remote instance you connect to
You create one Cloud Run instance and connect to its stable HTTPS URL. That web console is where you launch swarms, follow the live logs, and browse the artifacts the agents generate — all against the Cloud Storage bucket mounted at /workspace.
In the repo this is wrapped by deploy-instance.sh, which also builds the image, provisions the bucket and service account, and prints the URL. The call it ultimately makes is this one:
gcloud beta run instances create antigravity-console-instance \
--image="$REPO_URI/frontend:latest" \
--port=3000 \
--region=us-east4 \
--sandbox-launcher \
--add-volume="type=cloud-storage,bucket=my-swarm-workspace,mount-path=/workspace,mount-options=uid=1001;gid=1001;file-mode=0777;dir-mode=0777" \
--set-env-vars="TARGET_APP_DIR=/workspace/target-app,WORKSPACE_DIR=/workspace"
One parsing detail worth knowing: mount-options takes a semicolon-separated list, and the individual options must not be wrapped in nested quotes. Writing mount-options="uid=1001;…" inside the flag makes the validator read the key as "uid and reject it as an unrecognized GCS FUSE option.
Because the instance is invisible in the console, retrieve its URL from the CLI. Note that instances expose status.urls (a list), not the status.url field used by services:
gcloud beta run instances describe antigravity-console-instance \
--region=us-east4 \
--format='value(urls)'
Path A: run in-instance
The default. The console dispatches the swarm inside the same container you are connected to, either from the launcher in the UI or from the web terminal. The swarm command is a small Node wrapper, frontend/bin/swarm, that shells out to swarm-src/main.py:
swarm run "Refactor auth middleware to use OAuth2" --model=gemini-2.5-flash
Feedback is immediate: logs stream into the console and generated files appear in the asset browser as they are written. This is the right path for prototyping and anything you want to watch.
What the sandbox actually covers here. This is worth being precise about, because it is easy to assume more isolation than you get. The container detects the injected sandbox binary and wraps shell commands issued during a run — the deterministic quality gate, for instance:
// frontend/src/app/api/swarm/route.ts
const isSandboxed = hasSandboxLauncher();
const finalCmd = isSandboxed ? `sandbox do --allow-egress -- ${cmd}` : cmd;
So terraform init and terraform validate execute inside an isolated silo, but the orchestrator process and the file writes themselves run in the host container. That is a deliberate boundary — the sandbox exists to contain agent-executed code, which is the untrusted part — but it is not a blanket wrapper around the whole run.
Two caveats follow from that snippet. First, detection is a simple binary probe, so if the instance was created without –sandbox-launcher the commands silently run unsandboxed; only a log line records it. Check with swarm status, which reports Cloud Run Sandbox: ACTIVE or Standard Container Environment. Second, this code passes –allow-egress so that terraform init can reach the provider registry, which relaxes the deny-by-default network boundary described above. Tighten it to an explicit allowlist if the agents do not need general outbound access.
Path B: dispatch to a Cloud Run job
For long or heavy runs you do not want to babysit, the same image is deployed as a Cloud Run job (antigravity-swarm-runner, defined in terraform/main.tf) with the same modest 2 vCPU / 4 GiB footprint as the instance, but a one-hour timeout:
gcloud run jobs execute antigravity-swarm-runner \
--region=us-east4 \
--update-env-vars="ANTIGRAVITY_PROMPT=Refactor auth middleware to use OAuth2"
The job writes to the same Cloud Storage bucket, so its output shows up in the instance’s asset browser exactly like an in-instance run. Because it is triggerable from the CLI or an API call, this is also the path that makes scheduled and event-driven runs possible — the automation hook a desktop GUI cannot offer.
A note on the shared-team variant
The repo also deploys a conventional Cloud Run service (antigravity-console-ui). That is an alternative front door, not a third execution path: it autoscales and is appropriate for a shared team console, at the cost of the single-tenant, always-warm properties that make an instance good for a personal agent.
6.4 Cost hygiene: delete the instance after testing
Always delete your Cloud Run instance when you finish testing. Instances are in Tech Preview and are not listed in the console, so it is genuinely easy to forget one is running. Unlike a service, an instance never scales to zero — it holds its vCPU and RAM allocation continuously and bills for it.
# 1. Delete the instance as soon as testing is done
gcloud beta run instances delete antigravity-console-instance \
--region=us-east4 \
--quiet
# 2. Confirm nothing is left running
gcloud beta run instances list
Nothing is lost. All generated code, Terraform files, and agent logs live in the attached Cloud Storage bucket, so re-running ./deploy-instance.sh reattaches the same bucket and restores the environment in about 15 seconds.
7. Sizing the Sandbox: Start Small, Then Measure
Everything in this article runs on the smallest container that still demonstrates the pattern. That is a deliberate choice, and it is worth being explicit about why, because sizing numbers in a blog post have a way of being copied into production configurations.
The goal here is a reproducible example: something a reader can deploy in a few minutes, watch a swarm run end to end, and delete without thinking about the bill. So the repo ships 2 vCPU and 4 GiB, and the quality gate is a curl smoke test rather than a browser matrix — which is exactly why 2 vCPU is enough. An earlier draft used 4 vCPU and 16 GiB, and once the headless browser came out of the image that headroom was simply being paid for and never used.
Treat these numbers as a floor, not a recommendation. The right size for a real swarm depends on things this example deliberately does not have: how many subagents run concurrently, how large the target codebase is, how heavy the build step is, and whether your quality gates spawn compilers, browsers, or integration test containers. Deploy small, watch the container’s CPU and memory metrics through a few representative runs, and raise the limits against what you actually observe. A swarm that is throttled shows up clearly — stage durations stretch and subagents finish out of order — which is a much better signal than a number borrowed from someone else’s demo.
Two settings are worth keeping regardless of size:
- CPU boost (startup_cpu_boost=true). Roughly halves container cold start and Next.js hydration time, and costs nothing while idle.
- Gen 2 execution environment. The fast NVMe I/O matters disproportionately for npm install and terraform init, which is where a swarm spends a surprising share of its wall-clock time.
One more practical note on regions. Because instances are still in Tech Preview, preview capacity is not uniform. My first us-central1 instance creation failed with Capacity exhausted; the identical command succeeded immediately in us-east4. If creation fails, try another region before you start debugging your configuration — the error is about availability, not about anything you wrote.
Key takeaways
- A compiler is cheaper than a model. Putting terraform validate and npm run type-check behind sandbox do, ahead of the LLM auditor, means the model never burns tokens on code a deterministic tool could have rejected outright — more than a 65% reduction in my runs. The same wrapper keeps cloud credentials out of reach of the commands that agent-generated code shells out to.
- Containerized tool harnesses remove the “works on my machine” problem. Moving from desktop CLI tooling to a versioned container makes AI-assisted delivery reproducible and auditable, and it gives webhooks and schedulers something to call.
- Treat instances as disposable workbenches. Because the workspace is persisted in Cloud Storage, the compute is cattle, not a pet. Create it when you need it, delete it when you are done, and pay nothing in between.
Resources and references
- GitHub repository: github.com/gbechara/antigravity-in-cloudrun (Apache 2.0)
- Part 1 of this series: Mastering Agent Swarms: How to Build Modular Multi-Agent Systems in Google Antigravity
- Google Cloud blog — Introducing Cloud Run instances: cloud.google.com/blog/products/serverless/introducing-cloud-run-instances
- Google Cloud blog — Safely run AI-generated code in Cloud Run sandboxes: cloud.google.com/blog/topics/developers-practitioners/google-cloud-run-sandboxes-are-in-public-preview
- Cloud Run documentation — Gen 2 execution environment: cloud.google.com/run/docs/configuring/execution-environments
- Google Antigravity SDK: ai.google.dev
Running Autonomous Agent Swarms in Remote Sandboxes: From Antigravity Desktop/CLI to Cloud Run… was originally published in Google Cloud – Community on Medium, where people are continuing the conversation by highlighting and responding to this story.
Source Credit: https://medium.com/google-cloud/running-autonomous-agent-swarms-in-remote-sandboxes-from-antigravity-desktop-cli-to-cloud-run-3ffeb3e1a2dc?source=rss—-e52cf94d98af—4
