Inspiration

A crew arriving at a structure fire has about ninety seconds of usable decision time before entry.

In those ninety seconds, firefighters need construction type, past alterations, and hazardous contents. That data exists — across six disconnected municipal, county, and federal systems that cannot be queried under time pressure:

What the crew needs Where it lives today
🏗️ Construction type, storey count, alterations Municipal permit portal
🏠 Occupancy, assessed structure, parcel geometry County assessor's roll
🚿 Suppression systems, last inspection, open violations Fire inspections & violations databases
☣️ Chemicals on site, quantities, classification EPA FRS and Tier II
⛽ Transmission pipelines under the collapse zone PHMSA
⚡ Rooftop solar: a live DC circuit crews can't kill at the panel NREL & unparsed roof imagery

The instinctive fix is to point an agent at those feeds and ask it a question on dispatch. No system can cold-query ten municipal and federal APIs, reconcile their disagreements, and return a trustworthy answer inside ninety seconds.

The inversion

TERSAGE does the work in the months when nothing is burning, and has the answer ready when the call comes in.

Nine agents survey a district continuously, on their own schedules, for months. When a 911 call arrives, the incident loop queries nothing — it reads one immutable snapshot of work already done.

Every agent is a LangGraph state machine, not a script — it decides its own next step, loops back when a fact is ambiguous, and checkpoints when it runs out of budget rather than returning a guess.

And a second thing has to be true: it has to be honest about what it does not know. A confidently wrong storey count sends a crew onto a roof that isn't there. That constraint shaped more of this system than the latency one did.


What it does

Nine scheduled agents · three publishing departments · two loops · one governance layer. Cataloged in Google Cloud Agent Registry, running on Cloud Run, reasoning on Gemini 3.5 Flash using the Google GenAI SDK.

The Slow Loop — months before the fire

Agent Budget Writes to
records-watcher 40 s Structure profiles — internal facts only
hazard-watcher 180 s Classified hazard facts — internal facts only
geometry-watcher 300 s Pre-incident plan store
structure-watch 60 s Inspection work orders
referral-clerk 60 s Building department — captain approval

records-watcher polls DataSF permits, assessor, inspections and violations — two injection screens, Gemma 4 26B triage, then Gemini 3.5 Flash extracting typed values bound to document spans. A LangGraph graph chases permits that cite prior permit numbers, opening a durable question when the cited filing falls outside the published window.

hazard-watcher reads EPA FRS, PHMSA, NREL and Tier II. Its hard problem is identity — is ACME PLATING INC in a federal registry the facility at this parcel? The graph pulls a candidate, notices the identity is ambiguous, queries a second registry to disambiguate, and loops until confident or out of budget — recording what it ruled out so the next pass doesn't repeat the work.

geometry-watcher derives roof geometry, height and collapse zone from the Google Solar API and USGS 3DEP. Height is a subtraction — roof plane minus ground datum — and a physically implausible difference produces no height at all rather than a wrong one.

structure-watch reads the district at one instant, runs the deterministic conflict rules, and ranks every structure for survey: 0.40 conflict severity · 0.25 confidence decay · 0.20 source churn · 0.15 survey age. A queue entry cannot be constructed without at least one cited reason.

referral-clerk drafts an inter-agency referral from the worst open conflict, printing the supporting fact ids and stating it reports a disagreement, not a violation. A draft that drops a fact id, invents one, or loses the no-determination sentence is rejected and the deterministic template ships.

The Incident Loop — the ninety seconds

Agent Budget Role
incident-interceptor 12 s Head of the loop — brief, focus, routing, compose
sensor-fusion 12 s Multimodal thermal / optical read
agency-notifier 5 s Mutual aid, utilities, emergency management
incident-recorder 15 s RMS write-through, NERIS draft, question closure

incident-interceptor opens the incident against the snapshot and emits brief v1 — no model call, milliseconds. Gemini then binds the caller's reported values to spans in the transcript, and a focus composer routes the incident to other agents by their declared capabilities, writing pointers that each carry an id and a reason, never a value.

Caller says third floor. The permit says two storeys. Lidar measured three. A conflict has been open since March. The head points every downstream agent at exactly those ids.

sensor-fusion reads drone frames with Gemini multimodal, returning observations bound to image regions. Which wall is resolved from the footprint geometry — not from the model. A face with no frame is UNSCANNED, never cool.

agency-notifier matches hazards to who needs to hear it — rooftop solar is one call, a transmission pipeline inside the collapse zone is a more urgent one.

The interceptor then assesses readiness, solves an A* entry path over a graph priced by what the sweep just measured, and streams the brief over SSE onto a Three.js model of the measured geometry.

incident-recorder runs after the incident closes, now that a crew has physically stood in the building.

That is the self-improving loop. A question opened in March is answered in August by the incident that resolved it, and the next crew entering a similar structure inherits the answer.

Epistemics

State Meaning
UNKNOWN Nothing settled it — with the sources checked, named
DISPUTED Two sources disagree, both facts intact. Never averaged
UNAVAILABLE A source could not be reached, named. Not an absence of hazard

Every fact carries its source, snapshot, observation time, a confidence that decays on a source-tier half-life, and the document span behind it. Facts are immutable; a correction is a new fact that supersedes an old one and both remain. A 2019 permit is not treated as current knowledge.

The model may not author a fact. Models extract typed values bound to spans, route documents, compose prose. Deterministic code decides what is true, what conflicts, and what gets ranked.

Human gates, placed where accountability is

Action Approver Why
File an inter-agency referral Captain Filing accuses a property owner of unpermitted construction
Close a utility / gas main Chief A shutoff affects a neighbourhood
Notify mutual aid (autonomous) Notifying is not acting

Both gates are enforced at the decision and again at the write.


How We Built It

Gemini Enterprise Agent Platform

Component How it is implemented
Agent Registry Govern — Nine agents as A2A cards across three publishing departments; a department subscribes to a pinned version of an agent it does not own.
Agent Identity Govern — Each agent runs as its own service account with only the roles its declared scopes imply. IAM is generated from the descriptors, and a conformance test fails the build if they drift.
Agent Gateway GovernDefault-deny policy engine, ten rules in order; every decision recorded with the rule that produced it.
Model Armor Govern — Two screens with different failure modes on every ingested document. A screen that cannot run withholds the document rather than passing it.
Agent Runtime Scale — 12 Cloud Run services. Workers scale to zero; the incident service keeps one instance warm, because a cold start on dispatch is the one latency this system exists to avoid. A graph that exhausts its budget checkpoints and resumes.
Memory Bank Scale — Semantic recall over open-question prose; the record stays in Firestore. Recall is scope-gated against the stored record, never the index.
Observability Optimize — A span per agent run and per graph node; an append-only audit log that never holds document contents.

Key Technologies

  • AI & Reasoning: Vertex AI (gemini-3.5-flash for span-bound extraction, prose and multimodal; gemma-4-26b-a4b-it-maas), Model Armor, Agent Engine Memory Bank, Google Search grounding
  • Compute & Data: Cloud Run (12 services) · Firestore (32 collections) · Pub/Sub · Cloud Storage · Cloud Scheduler · Secret Manager
  • Delivery: Artifact Registry + Cloud Build (deploy by digest, never by tag) · Cloud Trace + OpenTelemetry · Billing Budgets
  • Data Sources: Google Solar API, USGS 3DEP, DataSF, EPA FRS, Tier II, PHMSA, NREL, NASA FIRMS
  • Stack: Python 3.12 · FastAPI · Pydantic v2 · LangGraph · Next.js 14 · React · Tailwind · Three.js · deck.gl · OpenTofu/Terraform (14 modules, 377 resources)

Challenges We Ran Into

  • Memory Bank didn't fit the whole problem. A Memory.fact caps at 2048 characters; a long-running identity thread's eliminations do not. We adopted the service for the half it fits — semantic recall — and kept the record in Firestore.
  • Entity resolution is not a string match. The fix was making the agent's uncertainty first-class, so an ambiguous identity costs budget and opens a durable question instead of resolving to a guess.
  • The entry path had to be solved without interior floor plans. No open source gives you the inside, and departments we contacted didn't reply before the deadline. So the navigation graph is built from measured exterior geometry.

Accomplishments We're Proud Of

  • Genuine cross-department publication — which is what "enterprise fleet" has to mean if it means anything.
  • Verification that holds the claims. 1,807 backend tests, 577 console tests, strict mypy across 206 files, a contract suite holding in-memory and Firestore to one behaviour set, an infrastructure suite holding Terraform to the agent descriptors, and an observability suite asserting telemetry carries no document content. make verify runs all of it plus a secret scan over full git history.
  • It runs on a laptop with no credentials. Although our video shows a live demo requiring credentials and API keys, make setup && make demo allows you to try out a simulation of our project without any of those.

What We Learned

  • Latency is an architecture problem, not an optimisation problem. The entire fleet shape falls out of that one decision.
  • Models at the edges, determinism in the core is what makes the audit trail meaningful.
  • Context rot is a data-modelling problem before it is a memory problem. Long-horizon agents fail because they compress; the fact model above is what makes this system's picture degrade legibly instead.
  • Human gates belong where accountability does — not as a blanket setting. Getting the three placements right took longer than building the agents.

What's Next for TERSAGE

  • Beyond San Francisco. The source framework already abstracts caching, rate limits, snapshots and backfill — next is a second city's portal onto the same ports, to prove the seams are in the right places.
  • Cross-department subscription in production, with grants and audit crossing a real org boundary.
  • Richer sensing — bodycam and drone LiDAR into the fusion port, with the same UNSCANNED discipline.

Apache-2.0 · No real person's records appear anywhere in this project.

Built With

Share this project:

Updates