Autonomous freshwater monitoring — a deterministic remote-sensing core fused with a five-agent Gemini workflow that turns satellite pixels into field-ready risk briefs.
🌍 Problem · 💧 Solution · 📸 Screenshots · 🧬 Agents · 🏗️ Architecture · 🚀 Quick Start · 🏆 Judge Sheet
Warning
Advisory only. Hydra does not certify water safety, detect toxins, or replace laboratory testing. It is a triage instrument that tells field teams where to sample first — so limited lab budgets go where they matter most.
Freshwater is the smallest slice of Earth's water budget — <3% of all water, and most of it is locked in ice. What remains is fought over by agriculture, industry, and billions of people — while its quality degrades invisibly, from space, week after week.
| 🚨 The hard numbers | Source |
|---|---|
| ~2 billion people lack safely managed drinking water at home | WHO/UNICEF JMP |
| ~80% of global wastewater is discharged untreated | UN Water |
| Most lakes & rivers are never sampled — lab capacity is the bottleneck, satellite coverage is not | — |
The result: field teams and NGOs fly blind. Algal blooms, turbidity spikes, and shoreline degradation go unnoticed until they become crises — because field sampling doesn't scale, but sentinel monitoring can.
Hydra's thesis: every point sampled is precious — so let a satellite watch every angle, and let agents turn pixels into a prioritized sampling plan.
Hydra is an autonomous freshwater triage system with two linked pipelines:
- 🛰️ A deterministic remote-sensing core — picks your area of interest, pulls a fresh Sentinel-2 scene, computes six water-quality spectral indices, and produces a reproducible 0–100 risk score. Same pixels, same number, every time.
- 🤖 A five-agent Gemini workflow — plans the analysis, scouts the best scene, recalls historical context, drafts and self-critiques the expert brief, and writes a citizen-friendly summary. All traceable, all with deterministic fallbacks.
The LLM writes the narrative. It can never move the risk band. Numbers stay deterministic; prose stays grounded; users stay safe.
Every session is captured end-to-end:
- 📊 Spectral indices + risk rows → Postgres
- 🧾 Every agent's tool calls, args, results, latency, tokens →
agent_traces(JSONB) - 🧠 Cross-session memory →
agent_memorywith text-embedding-004 vectors + pgvector HNSW index, so recurring water bodies get smarter over time
flowchart LR
subgraph P1[Pipeline 1 · Deterministic core 🛰️]
direction LR
A[Pick area<br/>search · coords · map] --> B[Fetch Sentinel-2 L2A<br/>Planetary Computer STAC]
B --> C[Compute 6 indices<br/>NDWI · MNDWI · NDTI<br/>NDCI · NDVI · WRI]
C --> D[Risk score 0–100<br/>level · urgency]
end
subgraph P2[Pipeline 2 · Gemini agent workflow 🤖]
direction LR
E[Coordinator<br/>thinking mode] --> F[Scout<br/>function calling + Vision]
E --> G[Historian<br/>search · memory · code exec]
F --> H[Analyst<br/>draft → critique → rewrite]
G --> H
H --> I[Reporter<br/>citizen summary]
end
D --> E
I --> J[Session detail + agent trace + branded PDF]
classDef det fill:#0e3c2f,stroke:#34d399,color:#d1fae5,stroke-width:1.4px;
classDef agent fill:#33205a,stroke:#a78bfa,color:#ede9fe,stroke-width:1.4px;
classDef out fill:#1e293b,stroke:#94a3b8,color:#e2e8f0,stroke-width:1.4px;
class A,B,C,D det;
class E,F,G,H,I agent;
class J out;
Pipeline 1 is the trusted numeric core. Pure-Python deterministic numpy band math; the LLM has zero influence over the risk number.
Pipeline 2 is the agent layer. Five specialist Gemini agents choose inputs, gather grounded context, write prose. Each degrades gracefully — every agent has a deterministic fallback.
| # | Agent | Action | Capability |
|---|---|---|---|
| 1 | 🧭 Coordinator | plans the workflow | Gemini thinking mode |
| 2 | 🔭 Scout | picks the satellite scene | Function calling + Gemini Vision on the real RGB tile |
| 3 | 📚 Historian | pulls trends & grounded context | History + Google Search + URL Context + code execution + pgvector memory |
| 4 | ✍️ Analyst | writes & self-critiques the brief | Structured output + critique-then-rewrite loop |
| 5 | 📣 Reporter | writes the citizen summary | Structured response schema (deterministic fallback) |
Each run lands in the Agentic Workflow card with tool calls, JSON outputs, latency, and token usage — the same colour vocabulary across README, UI, and PDF:
- 🧭 Coordinator — aqua · 🔭 Scout — sky · 📚 Historian — amber · ✍️ Analyst — violet · 📣 Reporter — emerald
Deep dive: docs/agent_layer.md.
flowchart TB
subgraph Client[Browser]
direction TB
UI[Next.js 15 app router<br/>Tailwind 4 · TS strict]
TRACE[Agentic Workflow card<br/>live SWR polling]
PDFBTN[Download PDF]
end
subgraph Backend[FastAPI 0.115 · SQLModel · Alembic]
direction TB
API[/api/v1 router/]
PIPE[Pipeline 1<br/>deterministic core]
ORCH[Pipeline 2 orchestrator<br/>Coordinator → Scout → Historian → Analyst → Reporter]
REPORT[WeasyPrint + Jinja2<br/>branded PDF]
end
subgraph Data[Persistence]
PG[(PostgreSQL 16 + PostGIS<br/>monitoring_sessions · water_bodies<br/>spectral_indices · risk_assessments)]
TR[(agent_traces<br/>JSONB tool-call log)]
MEM[(agent_memory<br/>pgvector 768 · HNSW cosine)]
DISK[/uploads + reports on disk/]
end
subgraph External[External providers]
STAC[Microsoft Planetary Computer<br/>Sentinel-2 L2A STAC]
GEMINI[Gemini API<br/>2.5 Flash · embeddings]
end
UI --> API
TRACE --> API
PDFBTN --> API
API --> PIPE
PIPE --> ORCH
PIPE --> PG
ORCH --> TR
ORCH --> MEM
PIPE --> STAC
ORCH --> GEMINI
API --> REPORT
REPORT --> DISK
classDef client fill:#0f172a,stroke:#38bdf8,color:#e0f2fe,stroke-width:1.3px;
classDef backend fill:#0e3c2f,stroke:#34d399,color:#d1fae5,stroke-width:1.3px;
classDef data fill:#1e1b4b,stroke:#a78bfa,color:#ede9fe,stroke-width:1.3px;
classDef ext fill:#3a2a0c,stroke:#fbbf24,color:#fef3c7,stroke-width:1.3px;
class UI,TRACE,PDFBTN client;
class API,PIPE,ORCH,REPORT backend;
class PG,TR,MEM,DISK data;
class STAC,GEMINI ext;
sequenceDiagram
autonumber
actor U as User
participant FE as Next.js frontend
participant API as FastAPI /sessions
participant PIPE as Deterministic core
participant ORCH as Agent orchestrator
participant GEM as Gemini
participant DB as Postgres
U->>FE: Pick AOI + window + cloud ceiling
FE->>API: POST /sessions
API->>DB: Insert session (status=processing)
API-->>FE: 201 session
API->>PIPE: BackgroundTask: run_full
PIPE->>PIPE: STAC search · COG read · index math
PIPE->>DB: Persist indices + risk row
PIPE->>ORCH: run_orchestrator(...) when AOI is water
ORCH->>GEM: Coordinator plan
ORCH->>GEM: Scout (function calling + vision)
opt history exists
ORCH->>GEM: Historian (search · memory · code exec)
end
ORCH->>GEM: Analyst draft → critique → rewrite
ORCH->>GEM: Reporter structured citizen summary
ORCH->>DB: Persist agent_traces (incremental)
PIPE->>DB: status=complete + risk.field_brief = reporter
loop while status != complete
FE->>API: GET /sessions/{id}
API-->>FE: status_message + partial trace
end
FE->>API: GET /sessions/{id}/report
API-->>FE: WeasyPrint PDF
erDiagram
WATER_BODIES ||--o{ MONITORING_SESSIONS : has
MONITORING_SESSIONS ||--o{ SPECTRAL_INDICES : produces
MONITORING_SESSIONS ||--o| RISK_ASSESSMENTS : has
MONITORING_SESSIONS ||--o{ FIELD_EVIDENCE : has
MONITORING_SESSIONS ||--o| REPORTS : has
MONITORING_SESSIONS ||--o| AGENT_TRACES : has
WATER_BODIES ||--o{ AGENT_MEMORY : remembers
RISK_ASSESSMENTS ||--o| AGENT_TRACES : linked_to
WATER_BODIES {
UUID id PK
string name
geometry polygon
point centroid
float area_km2
string source
}
MONITORING_SESSIONS {
UUID id PK
UUID water_body_id FK
date start_date
date end_date
float max_cloud_cover
string status
string status_message
string scene_id
datetime scene_capture_date
float scene_cloud_cover
float water_fraction
string aoi_type
}
SPECTRAL_INDICES {
UUID id PK
UUID session_id FK
string name
float value
float min_value
float max_value
float stddev
int sample_count
json bands
string interpretation
}
RISK_ASSESSMENTS {
UUID id PK
UUID session_id FK
UUID agent_trace_id FK
float score
string level
string urgency
string recommendation
string reasoning
string limitations
json contributors
json field_brief
string model_id
}
AGENT_TRACES {
UUID id PK
UUID session_id FK
json coordinator_plan
json agent_runs
int total_tokens_in
int total_tokens_out
int total_latency_ms
string gemini_model
}
AGENT_MEMORY {
UUID id PK
UUID water_body_id FK
string note
vector embedding
datetime created_at
bool archived
}
FIELD_EVIDENCE {
UUID id PK
UUID session_id FK
string water_color
string odor
bool algae_present
int dead_fish_count
float rainfall_mm
int complaints_count
string photo_path
string notes
}
REPORTS {
UUID id PK
UUID session_id FK
string file_path
int byte_size
}
All endpoints live under /api/v1; full schemas in docs/api_contract.md. OpenAPI at /docs when the backend runs.
| Domain | Endpoint highlights |
|---|---|
| 💧 Water bodies | CRUD + GeoJSON polygons / buffered points · transactional bulk delete |
| 📡 Sessions | Kick off a run · paginated list · full detail · indices · risk · evidence · agent trace · PDF report |
| ❤️ System | GET /health liveness probe |
hydra/
├── assets/ # Brand assets · screenshots · animated SVG wordmark
├── backend/
│ ├── app/
│ │ ├── api/v1/ # FastAPI routers (sessions · evidence · water_bodies · report · health)
│ │ ├── core/ # config · logging · database · BackgroundTask runner
│ │ ├── models/ # SQLModel tables (sessions · water_bodies · risk · indices · agent_traces · agent_memory · evidence · report)
│ │ ├── schemas/ # Pydantic request/response models
│ │ ├── services/
│ │ │ ├── pipeline.py # Deterministic core (Pipeline 1)
│ │ │ ├── indices.py · risk_model.py · reasoning.py
│ │ │ ├── citizen_summary.py # Deterministic citizen summary fallback
│ │ │ ├── report_generator.py # Jinja2 + WeasyPrint
│ │ │ └── agent/ # Pipeline 2 — orchestrator + 5 agents + tool layer
│ │ ├── utils/ # Geo · charts · location formatting · PDF helpers
│ │ └── main.py
│ ├── alembic/ # Database migrations
│ └── tests/ # 102 pytest tests
├── frontend/
│ ├── app/
│ │ ├── (marketing)/ # Landing · methodology · about · changelog · limitations
│ │ └── (app)/ # Dashboard · monitor · sessions · water-bodies · settings
│ ├── components/ # Marketing · session · map · evidence · ui primitives
│ ├── lib/ # api-client · query-client · seo · location
│ └── tests/ # Vitest + Playwright
├── docs/ # Architecture · agent_layer · risk_model · indices · api · manual · prd
├── infrastructure/ # render.yaml · vercel.json · deployment.md
├── docker-compose.yml
└── README.md
cp .env.example .envRequired
| Variable | Notes |
|---|---|
DATABASE_URL |
Postgres string, e.g. postgres://user:pass@host:5432/hydra |
GOOGLE_API_KEY |
Gemini 2.5 Flash + embeddings (free tier works for demos) |
NEXT_PUBLIC_API_URL |
Backend origin, http://localhost:8000 in dev |
Recommended
| Variable | Notes |
|---|---|
GOOGLE_API_KEY_FALLBACK[_2] |
Rollover keys on quota / 429 |
HYDRA_AGENTIC_MODE |
true (default); false → Pipeline 1 only |
HYDRA_FAKE_GEMINI |
1 for deterministic offline mode (CI) |
docker compose up --buildFrontend → http://localhost:3000 · Backend → http://localhost:8000 · Postgres+PostGIS sidecar.
Backend (Python 3.11+):
cd backend
python3 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
alembic upgrade head
uvicorn app.main:app --reload --reload-dir app --host 0.0.0.0 --port 8000Frontend (pnpm + Node 20+):
cd frontend
pnpm install
pnpm dev| Variable | Default | Purpose |
|---|---|---|
HYDRA_AGENTIC_MODE |
true |
Enables the 5-agent orchestration; off → Pipeline 1 + deterministic narrator |
HYDRA_FAKE_GEMINI |
0 |
Skip real Gemini; canned outputs for CI |
HYDRA_AGENT_STEP_DELAY_MS |
0 |
Delay between agent stages for live demo sequencing |
GEMINI_MODEL |
gemini-2.5-flash |
Runtime model ID |
GEMINI_EMBED_MODEL |
text-embedding-004 |
Historian's pgvector memory |
REPORT_DIR |
backend/data/reports |
PDF cache |
MAX_UPLOAD_BYTES |
8388608 |
Field-evidence photo ceiling |
WATER_FRACTION_LAND_THRESHOLD |
0.2 |
Below → land; agents skipped |
WATER_FRACTION_MIXED_THRESHOLD |
0.7 |
Below (≥ land) → mixed |
Full schema: backend/app/core/config.py.
Every layer has a deterministic safety net — a session always produces a usable brief:
| 💥 Failure | ✅ Fallback |
|---|---|
| Gemini key 429 / quota | Roll over to fallback keys |
| Coordinator parse error | Baseline plan: Scout + Analyst + Reporter (+ Historian if history) |
| Scout vision timeout | Freshest STAC candidate under cloud ceiling |
| Historian failure | Analyst runs without briefing; memory skipped |
| Analyst failure | Deterministic narrator (reasoning._fake_bundle) |
| Reporter failure | Deterministic citizen summary |
| AOI = land/mixed | Pipeline 2 skipped (zero Gemini cost); UI shows Not water |
| WeasyPrint error | Regeneration per-download — no stale cached PDFs ever |
Degraded behaviour is never silent — all failures are recorded in the per-session trace.
# Backend
cd backend
.venv/bin/python -m pytest -q # 102 tests in <10s
.venv/bin/ruff check . && .venv/bin/black --check app/ tests/ alembic/
# Frontend
cd frontend
pnpm typecheck && pnpm lint && pnpm test && pnpm e2eCI runs every gate plus a WeasyPrint smoke test on each PR.
| Surface | Provider | Notes |
|---|---|---|
| Backend | Render (Docker) | infrastructure/render.yaml |
| Frontend | Vercel | infrastructure/vercel.json |
| Database | Postgres 16 + PostGIS + pgvector | Local docker-compose.yml; managed PG in prod |
Walkthrough: infrastructure/deployment.md.
| Criterion | Where Hydra answers it |
|---|---|
| 🎨 Originality | Most water tools are static dashboards. Hydra fuses a provably-deterministic spectral core with a self-critiquing agent team that remembers every lake it has ever seen (pgvector HNSW memory) — a pattern we haven't seen at hackathons. |
| 🌍 Earth Forward adherence | Freshwater quality triage: conservation + ecosystem monitoring + community resilience. Satellite scale where lab budgets can't reach. |
| ✅ Completion | Full-stack: map → satellite → six indices → five agents → PDF. 102 passing backend tests, CI gates, and a fallback matrix that guarantees a brief on every run. |
| 📚 Learning | STAC/COG satellite pipelines, pgvector HNSW vector memory, agent orchestration with Gemini function calling + Vision, WeasyPrint PDF pipeline. |
| 🖌️ Design | Live agent trace UI, brand-tuned dark system, distribution sparklines, one-click CSV, branded PDF reports. |
| ⚙️ Technology | The wow-factor: an LLM that can never move the risk number. Numpy band math decides; agents only narrate. |
The Hydra of myth guarded its waters with many heads — no matter the angle of approach, one head was always watching. This platform works the same way. The deterministic core (the central head) never blinks: same pixels → same number, every time. Around it, five Gemini agents (the outer heads) patrol from different angles — planning, scouting, remembering, drafting, and translating for citizens.
Many heads. One mission: watch the water. 🐉💧
Source and docs under MIT; third-party notices in NOTICE.md.
Sentinel-2 imagery © European Union — modified Copernicus Sentinel data via Microsoft Planetary Computer.
digiflowhelp-stack — GitHub @digiflowhelp-stack · digiflow.help@gmail.com







