# Ambient Expense Agent
A production-ready **ambient agent** that processes expense reports arriving via
Pub/Sub and routes them through an **ADK 2.0 graph-based workflow**. Low-value
expenses are auto-approved instantly; high-value ones go through LLM risk
analysis and **human-in-the-loop approval** before a decision is made.
| Key Features |
| 🔄 |
ADK 2.0 Graph Workflow: Conditional routing with function nodes and LLM agents in the same graph — business rules stay in code, LLM handles judgment calls. |
| 📡 |
Ambient & Event-Driven: Listens for expense events via Pub/Sub triggers and processes them automatically in the background. |
| ✋ |
Human-in-the-Loop: High-value expenses pause the workflow with RequestInput until a manager approves or rejects via a dedicated approval UI. |
| ☁️ |
Production-Ready Deployment: One-command Terraform setup — two Cloud Run services, Pub/Sub, Cloud Monitoring alerts, IAM, and IAP. |
| Attribute | Description |
| :--- | :--- |
| **Interaction Type** | Ambient (event-driven) with HITL approval |
| **Complexity** | Intermediate |
| **Agent Type** | ADK 2.0 Graph-based Workflow |
| **Trigger Sources** | Pub/Sub push |
## How It Works
The agent is built as an ADK 2.0 [`Workflow`](https://adk.dev/workflows/) with
conditional routing. The $100 threshold lives in code, not in a prompt — only
high-value expenses hit the LLM. See
[`expense_agent/agent.py`](expense_agent/agent.py) for the full graph definition.
```
Expense arrives (Pub/Sub)
│
parse & extract data
│
route by amount
│ │
< $100 >= $100
│ │
auto-approve LLM reviews risk
(done) & emails alert
│
manager approves
or rejects
(approval UI)
│
agent logs decision
& takes action
```
### Deployment Architecture
The agent deploys as two [Cloud Run](https://cloud.google.com/run) services
with [Cloud Monitoring](https://cloud.google.com/monitoring) for email alerts:
- **Backend** — runs the ADK agent. Pub/Sub pushes expense messages to it
directly (authenticated via service account).
- **Frontend** — the approval UI. Protected by
[Identity-Aware Proxy (IAP)](https://cloud.google.com/iap) so only
authorized managers can access it. Calls the backend on behalf of the user.
- **Monitoring** — when the agent flags a high-value expense, it emits a
structured log. A log-based metric triggers an email alert to the manager
with a link to the approval UI.
```
┌─────────────────────────┐
Pub/Sub ───────────► │ Backend (Cloud Run) │
│ ADK agent + triggers │
└──────┬─────────▲────────┘
│ │
structured log │
│ │
┌──────▼──────┐ │
│ Cloud │ │
│ Monitoring │ │
└──────┬──────┘ │
│ │
email alert │
│ │
┌──────▼──────┐ │
│ Manager │ │
└──────┬──────┘ │
│ │
┌──────▼─────────┴────────┐
Browser ── login ──► │ Frontend (Cloud Run) │
│ Approval UI (IAP) │
└─────────────────────────┘
```
## Getting Started
**Prerequisites:** [Python 3.11+](https://www.python.org/downloads/), [uv](https://github.com/astral-sh/uv)
### 1. Clone the repository
```bash
git clone https://github.com/google/adk-recipes.git
cd adk-recipes/core/python/ambient-expense-agent
```
### 2. Configure authentication
Create a `.env` file (see [`.env.example`](.env.example)).
**Option A: [Google AI Studio](https://aistudio.google.com/app/apikey)**
```bash
echo "GOOGLE_API_KEY=YOUR_AI_STUDIO_API_KEY" >> .env
```
**Option B: [Google Cloud Vertex AI](https://cloud.google.com/vertex-ai)**
```bash
echo "GOOGLE_GENAI_USE_VERTEXAI=TRUE" >> .env
echo "GOOGLE_CLOUD_PROJECT=YOUR_PROJECT_ID" >> .env
echo "GOOGLE_CLOUD_LOCATION=global" >> .env
gcloud auth application-default login
```
### 3. Install and run
Start the backend:
```bash
make install && make dev
```
In a separate terminal, start the approval UI:
```bash
make install-frontend && make dev-frontend
```
### 4. Try it out
Open the ADK playground to interact with the agent directly:
```bash
make playground
```
This starts the ADK web UI at `http://localhost:8501`.
To test the full Pub/Sub trigger flow, send an expense in another terminal:
```bash
curl -s http://localhost:8080/apps/expense_agent/trigger/pubsub \
-H "Content-Type: application/json" \
-d "{\"message\":{\"data\":\"$(echo '{"amount":250,"submitter":"alice@company.com","category":"travel","description":"Flight to NYC","date":"2026-04-10"}' | base64)\",\"attributes\":{\"source\":\"test\"}},\"subscription\":\"test-sub\"}"
```
This $250 expense triggers review + HITL approval. Open the approval UI
at `http://localhost:8081/approval` to approve or reject it.
> **Tip:** Expenses under $100 are auto-approved — change `amount` to
> `45` to test that path.
## Cloud Deployment
Deploy both services and all supporting infrastructure with a single command.
**Prerequisites:** [Google Cloud SDK](https://cloud.google.com/sdk/docs/install), [Terraform](https://www.terraform.io/)
```bash
gcloud config set project YOUR_PROJECT_ID
make deploy NOTIFICATION_EMAIL=finance@example.com
```
This builds container images (in parallel) and deploys everything via
Terraform: two Cloud Run services, Pub/Sub (with dead-letter), Cloud
Monitoring alerts, IAM, and IAP.
> **Note:** IAP can take **5–10 minutes** to fully propagate after the
> initial deployment. If you see a `403 Forbidden` when opening the
> approval UI, wait a few minutes and refresh.
### Test the deployed agent
```bash
make remote-test
```
This publishes a $250 travel expense. The agent will route it to the review
agent, analyze risk factors, email an alert to `NOTIFICATION_EMAIL`, and pause
for human approval. Open the approval UI (URL printed by `make deploy`) to
approve or reject.
### Cleanup
```bash
make clean NOTIFICATION_EMAIL=finance@example.com
```
## Customization
| What to change | How |
| --- | --- |
| **Approval threshold** | Change `review_threshold` in `expense_agent/config.py` |
| **LLM model** | Change `model` in `expense_agent/config.py` |
| **Expense schema** | Edit the `ExpenseData` Pydantic model in `expense_agent/agent.py` |
| **Review logic** | Edit the `review_agent` instruction in `expense_agent/agent.py` |
| **Approval UI** | Edit `frontend/static/approval.html` |
| **Downstream actions** | Add workflow nodes for Slack, databases, or notifications |
| **Multi-level routing** | Add routes (e.g., `ESCALATE` for expenses > $1000) |
| **Notification channel** | Replace email with Slack, PagerDuty, or SMS in `terraform/monitoring.tf` ([docs](https://cloud.google.com/monitoring/support/notification-options)) |
| **Email content** | The alert email uses a static template. To include dynamic expense data (amount, submitter) in the email, switch from log-based metrics to [custom metrics with template variables](https://cloud.google.com/monitoring/alerts/doc-variables) |
## Troubleshooting
- For general ADK issues, see the [ADK documentation](https://adk.dev).
- For trigger endpoint details, see [Ambient Agents](https://adk.dev/runtime/ambient-agents/).
- For Cloud Run deployment, see [Deploy to Cloud Run](https://adk.dev/deploy/cloud-run/).
## Disclaimer
This agent recipe is provided for illustrative purposes only. It serves as a basic example of an agent and a foundational starting point for individuals or teams to develop their own agents.
Users are solely responsible for any further development, testing, security hardening, and deployment of agents based on this recipe. We recommend thorough review, testing, and the implementation of appropriate safeguards before using any derived agent in a live or critical system.