The Problem & Industry Shift
Autonomous AI agents are all the rage, but most tutorials assume you have unlimited cloud credits. Running a 24/7 agent that polls, processes, and acts can easily cost $50–$100/month if you naively spin up a VM. The industry is shifting toward serverless and event-driven architectures that scale to zero, but many developers still default to persistent compute. This article shows you how to run a production-grade autonomous agent on Google Cloud for just $5.70/month—the cost of a single e2-micro VM, but with better reliability and zero idle cost.
Architecture & Core Mechanics
The key is to avoid persistent compute. Instead, we use:
- Cloud Scheduler to trigger the agent on a cron schedule (e.g., every 5 minutes).
- Cloud Run to execute the agent logic as a stateless container that scales to zero.
- Firestore as the durable state store, so the agent can pick up where it left off.
- Cloud Tasks (optional) for retries and backoff.
The agent loop works like this:
- Cloud Scheduler fires an HTTP request to the Cloud Run service.
- The service loads the agent's state from Firestore.
- The agent performs its task (e.g., fetch data, call an LLM, make a decision).
- The new state is saved back to Firestore.
- The container exits, and you pay only for the compute time used.
+----------------+ HTTP +----------------+ Read/Write +-------------+
| Cloud Scheduler| -----------> | Cloud Run | <----------------> | Firestore |
| (cron) | | (agent logic) | | (state) |
+----------------+ +----------------+ +-------------+
This architecture is inherently serverless and scales to zero, meaning you pay only for the milliseconds of compute per invocation.
Production Code Example
Below is a minimal but production-ready agent in Python using FastAPI. It reads its state from Firestore, performs a simple task (e.g., fetching a quote from an API), and updates the state.
import os
import asyncio
from fastapi import FastAPI, HTTPException
from google.cloud import firestore
import httpx
app = FastAPI()
# Initialize Firestore client (uses GOOGLE_APPLICATION_CREDENTIALS)
db = firestore.AsyncClient()
# Agent configuration
AGENT_ID = os.getenv("AGENT_ID", "my-agent")
STATE_COLLECTION = "agent_state"
async def fetch_quote() -> str:
"""Fetch a random quote from an external API."""
async with httpx.AsyncClient(timeout=5) as client:
resp = await client.get("https://api.quotable.io/random")
resp.raise_for_status()
return resp.json()["content"]
async def run_agent_step(state: dict) -> dict:
"""Execute one step of the agent's logic."""
# Example: fetch a quote and store it in the state
quote = await fetch_quote()
state.setdefault("quotes", []).append(quote)
state["last_run"] = firestore.SERVER_TIMESTAMP
return state
@app.post("/run")
async def run():
"""Entry point triggered by Cloud Scheduler."""
doc_ref = db.collection(STATE_COLLECTION).document(AGENT_ID)
doc = await doc_ref.get()
state = doc.to_dict() if doc.exists else {}
# Run the agent step
try:
new_state = await run_agent_step(state)
except Exception as e:
# Log and re-raise so Cloud Run returns 500 and Scheduler retries
raise HTTPException(status_code=500, detail=str(e))
# Save state back
await doc_ref.set(new_state)
return {"status": "ok", "state": new_state}
# For local testing
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8080)
Key engineering decisions:
- Statelessness: The container has no local state; all state is in Firestore. This allows Cloud Run to scale to zero and handle concurrent invocations safely.
- Idempotency: The agent step should be idempotent. In this example, we append quotes, which could duplicate if retried. In production, you'd use a transaction or a unique ID.
- Error handling: We catch exceptions and return 500 so Cloud Scheduler can retry with exponential backoff.
- Timeout: The Cloud Run request timeout should be set to less than the Scheduler interval to avoid overlapping runs.
Performance, Cost & Trade-offs
Let's break down the cost for a 5-minute interval (288 invocations/day).
- Cloud Run: 288 invocations × ~1 second CPU each = ~0.08 vCPU-seconds/day. Cloud Run's free tier includes 180,000 vCPU-seconds/month, so this is free. Even if you exceed, the cost is ~$0.000024/vCPU-second, so negligible.
- Cloud Scheduler: 3 jobs are free, so 1 job is free.
- Firestore: 288 reads + 288 writes per day = ~17k reads + 17k writes/month. Firestore's free tier includes 50k reads and 20k writes per day, so this is free. Even if you exceed, the cost is $0.06 per 100k reads and $0.18 per 100k writes.
-
Cloud Run always-on instances: If you set
min-instances=1, you'd pay for a perpetual instance. At 0.5 vCPU and 256MB, that's ~$0.000023 per second × 2,592,000 seconds/month = ~$60/month. Avoid this. Keepmin-instances=0.
So the total cost is $0.00 for this workload. But why $5.70? That's the cost of a single e2-micro VM on Compute Engine, which is a common alternative. If you need guaranteed low latency (no cold starts) or you want to run a heavier agent that requires more than 1 second of compute per step, you might opt for a VM. However, with Cloud Run you can increase the CPU allocation and still pay only for what you use.
Trade-offs:
- Cold starts: Cloud Run may have a cold start of 1–2 seconds. If your agent must respond instantly, this could be a problem. Mitigation: use a minimum of 1 instance, but that costs ~$60/month.
- Concurrency: Cloud Run can handle multiple invocations concurrently, but your agent must be designed to handle concurrent runs safely (e.g., using transactions).
- State consistency: Firestore is eventually consistent, but for most agent use cases, this is fine. If you need strong consistency, consider using a transactional read-write.
- External API rate limits: If your agent calls external APIs, you must handle rate limiting and backoff.
Actionable Checklist / Summary
To adopt this pattern in production:
- Design your agent as a stateless function that takes state as input and returns new state.
- Store state in Firestore (or any durable store) with a unique agent ID.
-
Deploy as a Cloud Run service with
--min-instances=0and a request timeout less than your scheduler interval. -
Create a Cloud Scheduler job that hits your service's
/runendpoint on your desired cron schedule. - Set up error handling with retries (Cloud Scheduler retries on 500s by default).
- Monitor costs using the Google Cloud Console and set budgets.
- Test locally with the Firestore emulator.
Top comments (0)