An agentic civic-issue platform: a citizen snaps one photo and an autonomous AI Caseworker categorizes, dedups, routes, tracks, and auto-escalates the issue — with every decision shown on a public Accountability Ledger.
Most civic-complaint apps are a black box: you report a pothole or a water leak, and it disappears. You don't know which department got it, whether it was even the right one, when it's due, or who's accountable when it slips.
CivicLoop is different — it doesn't just collect issues, it works them, and it makes every step of the reasoning public and auditable:
- Autonomous AI Caseworker — a real Sense → Plan → Act → Reflect loop that branches on confidence: high-confidence reports are acted on automatically; low-confidence ones are flagged for human review.
- The Accountability Ledger — a realtime, append-only log of every agent decision (goal, tool chosen, input, result, reasoning, confidence). Anyone can watch a case being worked, live.
- Self-correction — a "water-on-road" report first routed to Roads is re-examined by the Reflection agent (standing water, no surface crack) and re-routed to the Water Board, logging the correction on the Ledger.
- Proactive escalation — an SLA-Watcher scans overdue cases and escalates to the next authority tier automatically, posting a public update — triggerable live via a "Run Now" button.
- 📸 ~3-tap photo report — snap → confirm → submit, with a draggable map location picker
- 🤖 Agentic pipeline — intake → geo → dedup → route → reflect, with real confidence branching
- 🔗 Public Accountability Ledger — realtime, append-only stream of agent decisions
- 🔁 Live self-correction re-route (water-on-road → Water Board)
- ⏰ Proactive SLA-Watcher with a "Run Now" button and auto-escalation
- 🗺️ Map of open cases with status-colored pins (Google Maps, with a keyless Leaflet fallback)
- 🧭 360° Street View on case detail and in the report location picker
- 👍 Community confirmations (dedup merges into one case) and author-only delete
- 📊 Dashboard with live stat tiles and routing metrics
CivicLoop is a multi-agent system orchestrated by a Caseworker that runs a confidence-gated agentic loop. Each agent writes an append-only decision record — those decisions are the public Ledger.
flowchart TD
A[📸 Citizen snaps 1 photo] --> B[Caseworker writes a 'plan' decision]
B --> C[Intake Agent<br/>Gemini multimodal → structured JSON]
C --> D{intake confidence?}
D -->|low| H[needs_human_review<br/>review queue]
D -->|high| E[Geo Agent<br/>reverse-geocode → ward]
E --> G[Dedup Agent<br/>embeddings + 150m radius<br/>merge +vote OR create new]
G --> I[Routing Agent<br/>routing KB → dept + contact + SLA]
I --> J[Reflection / Verifier<br/>re-read facts vs route → self-correct]
J --> K[(Supabase<br/>cases + decisions = Ledger)]
K --> L[🔗 Public Accountability Ledger]
M[⏰ Proactive SLA-Watcher<br/>'Run Now' button] --> K
| Agent | Role |
|---|---|
| Caseworker (Orchestrator) | Writes the plan, then branches on intake confidence (high → act; low → human review). |
| Intake Agent | One Gemini multimodal call → structured JSON {category, severity, hazardFlags, description, confidence}. |
| Geo Agent | Reverse-geocodes coordinates → address + neighborhood + ward. |
| Dedup Agent | Finds nearby open cases → merge as a community confirmation (+vote) vs. create new. |
| Routing Agent | Reasons over the routing knowledge base → correct department + contact + SLA. |
| Reflection / Verifier | Re-reads the case facts vs. the chosen route → self-corrects mis-routes. |
| SLA-Watcher | Scans overdue cases → escalates to the next tier → posts a public update. |
Deeper design notes live in
docs/architecture.mdanddocs/design-spec.md.
- Frontend: React 19 + TypeScript + Vite, with a small custom reactive store (no state library) and
motionfor animation. - Backend: Supabase (Postgres + Row Level Security + Realtime) is the shared source of truth for
casesandmetrics. Optional — with no Supabase env vars the app runs fully local (localStorage). Seedocs/supabase-setup.md. - AI: Google Gemini (multimodal intake, routing reasoning, reflection) via
@google/genai. The app ships a deterministic simulated pipeline so it works with no keys, and switches to real Gemini when a key is provided. - Maps: Google Maps JS SDK + Street View for geocoding and the map UI, with a keyless Leaflet + CARTO fallback.
npm install # install dependencies
cp .env.example .env # then fill in any keys you have (all are optional)
npm run dev # http://localhost:5173The app runs out of the box with no keys (simulated AI + local storage + keyless map). Add keys in .env to light up the real services:
| Variable | Enables | Without it |
|---|---|---|
VITE_SUPABASE_URL + VITE_SUPABASE_ANON_KEY |
Shared realtime backend (cases + Ledger sync across devices) | Local-only (localStorage) |
GEMINI_API_KEY |
Real Gemini AI pipeline (server-side; never sent to the browser) | Built-in simulated caseworker |
VITE_GOOGLE_MAPS_API_KEY |
Google Maps + Street View + accurate geocoding | Leaflet map + OpenStreetMap geocoding |
Other scripts: npm run build (type-check + production build), npm run typecheck, npm run preview.
To turn on the shared realtime backend: create a free Supabase project, run supabase/schema.sql once in the SQL Editor, and paste your Project URL + anon key into .env. Full walkthrough in docs/supabase-setup.md.
cases— one row per case:caseId, title, category, severity, hazardFlags, description, status, confidence, photo, location{lat,lng,address,neighborhood,ward}, routedDepartment{name,contact,tier}, sla{dueAt,tier,breached}, voteCount, confirmations[], createdBy, timestamps, and the append-onlydecisions[]Ledger.metrics— a singleglobalrow of dashboard counters (reported, deduped, routed, resolved, escalated, …).routingKB(data/routingKB.json) — department / SLA / escalation knowledge base used by the routing agent.
Categories: pothole · water_leak · streetlight · waste · other
Statuses: new · routed · in_progress · escalated · resolved · needs_human_review
civicloop/
├── data/ # routing knowledge base + seed data
├── supabase/
│ └── schema.sql # Postgres tables, RLS, realtime — run once
├── src/
│ ├── app/ # screens: Dashboard, ReportFlow, CaseDetail, Ledger, maps…
│ ├── server/ # reference agent pipeline (schemas, prompts, gemini client)
│ ├── backend.ts # Supabase data access (cases + metrics + realtime)
│ ├── supabase.ts # Supabase client (auto-disabled when unconfigured)
│ ├── store.ts # reactive store, wired to Supabase
│ ├── engine.ts # deterministic simulated pipeline (keyless mode)
│ └── geo.ts · googleMaps.ts · types.ts · ui.ts · styles.css
├── server/
│ └── caseworker.mjs # local dev endpoint that runs the real Gemini pipeline
├── functions/
│ └── slaWatcher.ts # SLA-Watcher (Firebase Cloud Function)
└── docs/ # architecture, design spec, Supabase setup
CivicLoop — because a complaint should never disappear into a black box. Snap it. We'll work the case. The whole city can watch.