Track: Agents for Good Capstone for: 5-Day AI Agents: Intensive Vibe Coding Course with Google (Kaggle x Google)
A multi-agent system that turns raw river-monitoring sensor data (camera-based garbage detection + ultrasonic water-level sensing) into decision-ready alerts and plain-language reports for local environmental officers — without anyone having to watch a live camera feed all day.
Rivers running through dense urban areas (e.g. Kalimas in Surabaya) accumulate floating garbage that clogs flow and worsens flooding. Low-cost embedded sensor rigs (ESP32-S3 camera + ultrasonic sensors) can now detect this automatically and cheaply — but raw sensor telemetry is not something a busy kelurahan officer or volunteer cleanup coordinator can act on directly. Someone (or something) still has to watch the stream, decide what's significant, and turn it into a message a human will actually read and act on.
This is a perception -> decision -> communication pipeline with different skills at each stage, which is exactly where a small team of specialist agents beats one giant prompt:
- A Perception Agent doesn't need any language ability — it just needs to validate and classify.
- An Alert Agent needs to draft clear, calm, non-alarmist human communication, and must never independently trigger a physical-world action.
- A Report Agent needs to synthesize a trend, not just a single reading.
- A Coordinator ties them together with explicit, auditable routing logic instead of one opaque mega-prompt deciding everything.
Splitting these concerns means each agent can be tested, replaced, or scaled independently — e.g. swapping in a stronger LLM just for report-writing without touching the perception/validation code path at all.
flowchart LR
subgraph Hardware["Field hardware (ESP32-S3 + OV2640 + dual JSN-SR04T)"]
S[Sensor packet: coverage_camera + height_ratio_sensor]
end
S --> P[Perception Agent\nvalidate + fuse + classify]
P -->|severity=normal/watch| C[Coordinator Agent\nADK-style orchestrator]
P -->|severity=alert| C
C -->|route: alert| A[Alert Agent\nloads river-report-skill\nrate-limited + human-in-the-loop]
C -->|route: periodic| R[Report Agent\nloads river-report-skill\nsynthesizes trend]
A --> H{{Human operator\nconfirms physical action}}
R --> O[Plain-language summary]
C -.exposed via.-> M[MCP Server\nget_latest_reading\nget_detection_log\nget_river_health_summary\nrequest_action]
M -.queried by.-> Ext[Any MCP client:\nAntigravity / Claude / Gemini CLI / dashboard]
swara-agent-capstone/
├── main.py # CLI demo: streams sample data through the agents
├── agents/
│ ├── store.py # shared in-memory state (readings/alerts/reports)
│ ├── llm_client.py # Gemini wrapper w/ offline-safe fallback
│ ├── perception_agent.py # validates + classifies sensor packets
│ ├── alert_agent.py # drafts alerts, rate-limited, human-in-the-loop
│ ├── report_agent.py # synthesizes periodic plain-language reports
│ └── coordinator.py # ADK-style orchestrator / routing logic
├── mcp_server/
│ └── server.py # exposes the system as MCP tools
├── skills/
│ └── river-report-skill/
│ └── SKILL.md # progressive-disclosure drafting style guide
├── security/
│ └── guardrails.py # input validation, rate limiting, human approval gate
├── data/
│ └── sample_sensor_stream.json # synthetic demo data (30 readings, 2 locations)
├── requirements.txt
└── .env.example
| Concept (rubric) | Where |
|---|---|
| Multi-agent system (ADK-style) | agents/coordinator.py orchestrates perception_agent.py, alert_agent.py, report_agent.py as independent specialists |
| MCP Server | mcp_server/server.py — exposes get_latest_reading, get_detection_log, get_river_health_summary, request_action as MCP tools |
| Agent skills | skills/river-report-skill/SKILL.md, loaded on-demand (progressive disclosure) only when drafting an alert or report |
| Security features | security/guardrails.py — input validation against malformed/spoofed sensor packets, alert rate-limiting, mandatory human-in-the-loop approval before any physical-world action |
| Deployability | Stateless agent modules + MCP server designed to run as separate Cloud Run services in production (see "Deploying" below) |
pip install -r requirements.txt
python main.pyThis streams 30 synthetic sensor readings through the full agent pipeline and prints every agent's reasoning and decisions to the console, ending with a health summary. It runs in offline mode by default (template-based drafting) so it works immediately with zero setup.
cp .env.example .env
# edit .env and paste a free key from https://aistudio.google.com/app/apikey
export $(cat .env | xargs)
python main.pypython -m mcp_server.serverThen point any MCP-compatible client (Antigravity, Claude Desktop, Gemini
CLI) at this script over stdio to query get_river_health_summary,
get_detection_log, etc. directly.
Each agent module is stateless and takes its dependencies (store, LLM client) as constructor arguments, so in production:
mcp_server/server.pydeploys as its own Cloud Run service (swap the in-memoryStorefor Firestore).coordinator.pyruns as a Cloud Run job/service triggered by each new sensor packet arriving from the ESP32-S3 devices (e.g. via Pub/Sub).- Alerts route to a real channel (WhatsApp Business API / Telegram) instead
of stdout, but the
pending_human_approvalgate stays in place before any dispatch of a physical crew or (future) skimmer robot.
The sensor-fusion formula and hardware (OV2640 camera + dual JSN-SR04T
ultrasonic sensors on an ESP32-S3, Score = α·Coverage_camera + (1−α)·Height_ratio_sensor)
mirrors an actual river-garbage-monitoring hardware project (Swara, PKM-KI
2026) currently in development. This capstone builds the agentic decision
and communication layer on top of that kind of sensor stream — the part
that turns raw detections into something a human can actually act on.
Submission and source code licensed under CC-BY 4.0, per competition rules.