Inspiration
It is two in the morning and one person is standing over an aeration basin deciding whether to move a blower.
He is part-time. He may cover three plants. He holds the certification, which means if what leaves the outfall tonight is wrong, the violation carries his name — not the town's, not the mayor's. His.
Nobody thanks him for a quiet night. Nothing happening is the entire job, and nothing happening is invisible.
Eight miles downstream, a town drinks the river he discharges into. About half the drinking-water intakes serving larger communities in the continental US sit below somebody's wastewater outfall. The EPA has a phrase for it — de facto reuse. Almost nobody has heard it. Almost everybody is drinking it.
I build software in Casablanca. I have never met him and I never will. I built Steward so that he doesn't have to be right alone.
What it does
Steward staffs his night shift.
Five long-lived agents hold one station each — flow at the headworks, biology in the aeration basin, the permit at the outfall, weather over the creek, and a clerk who may draft but never send. Above them sits a supervisor that audits every claim, and an arbiter for the nights when two agents are both right.
It sees the violation coming. Each parameter's history is forecast against its own enforceable permit limit; when the 90th-percentile band crosses the line, that is a month worth an operator's attention. Against the EPA's public record it would have flagged half of the exceedances that really happened, a median of 51 days before the monthly report reached the regulator.
It argues in the open. Raising the blowers rescues the biology, shortens retention time, and breaches solids before ammonia recovers. Three agents each own one of those truths and none can see the whole picture. When no path is free the fleet stops, prices every option, and gives the choice to the person whose licence is on it.
It hires help it doesn't have. When a condition surfaces a role nobody catalogued — is this bypass lawful under 40 CFR 122.41(m)? — the fleet searches the Agent Registry, resolves a live agent card published by a different department, checks the advertised version against its own pin, and mounts the specialist for the event. Or refuses, and says why.
It remembers. What the operator chose under pressure last week, and what he deprioritised, is written to Memory Bank and reloaded before the next shift reasons about anything. A covering operator inherits the reasoning, not a spreadsheet.
It cannot act. Authority is checked on every single tool call, not suggested in a prompt. Anything irreversible needs a token that only the operator's confirmed decision can mint — one action, five minutes, burned on use. There is no code path by which an agent mints its own.
And it admits what it couldn't do. Every week it writes the one document that leaves the system: which obligations it let slip to protect which others, and what it would take to stop having to choose. A fleet of agents whose deliverable is an honest account of the limits of automation.
How we built it
Scaffolded the Google way. agents-cli create produced the ADK
project, the Agent Runtime deployment target, Terraform with Workload
Identity Federation for keyless CI/CD, and BigQuery agent analytics. The
manifest is committed, so the path is reproducible rather than described.
The fleet. Python 3.12 on ADK 2.6 with A2A. Gemini 3.7 Flash handles
every reasoning moment under a strict JSON contract that requires a
resolvable citation for any number. Authority lives in an ADK
BasePlugin consulted on every tool call — observe, recommend, act —
which is what makes restraint structural instead of instructional. Step
budgets and wall-clock ceilings sit around every invocation so a worker
that loops is quarantined rather than left to spend tokens unobserved.
The data spine. 66 million reported values streamed straight out of
the EPA's public zips into a partitioned, clustered BigQuery table —
nothing unpacked to disk. Three SQL files build the whole backtest
serverlessly: the series, the AI.FORECAST quantiles, and the finding.
Deployed twice, on purpose. The shift loop runs on Vertex AI Agent Runtime as a long-lived engine with its own workload identity and a platform-served A2A card. Three Cloud Run services carry the rest: fleet plus console, the state primacy agency's publisher, and the self-hosted Gemma edge that never calls an API.
The console. Hand-rolled React/Vite, and deliberately not a dashboard — a cross-section of the plant, headworks to outfall to the creek, with the intake the town drinks from marked from the first frame. The audit ledger stays on screen the entire time, denials interleaved with allowances, every row carrying its Cloud Trace id.
Challenges we ran into
Keeping a fleet honest costs more than making it capable. A model that cites a source is not the same as a source that says what the model claims. The supervisor therefore does not grade text — it re-reads the cited sensor itself and compares. That forced a stricter tool contract than we wanted, and the fleet is slower for it. The benefit is that "quarantined" is a computation, not a judgement.
Authority has to live at the tool call, not in the prompt. Telling an agent it may only observe is a suggestion. Moving observe / recommend / act into a policy object consulted on every invocation — with irreversible tools additionally gated on a single-use token — is the difference between an agent that is asked not to act and one that cannot.
"Long-lived" is a different engineering problem from "works." Every
architectural claim survived a four-minute demonstration. What did not
survive being left alone for a few hours was the infrastructure beneath
it: a Cloud Run service with min-instances: 0 is reclaimed whenever
nobody is looking, so a fleet advertised as continuous had never once
completed a shift; and a simulation clock that counts forward without
wrapping leaves a plant frozen at its last values while the console goes
on reporting days of uptime. Neither is an agent bug. Both make
"long-lived" false, and neither is visible in a demo. Our advice to
anyone else on this track: deploy early, then ignore it for a day.
Memory Bank has create-with-id and no update verb. Facts here are
keyed by their own text and the interesting field is the observation
count — which changes on every re-observation, so every write after the
first is a collision. Handling it means delete-then-create, and the API
reports the collision as 400 INVALID_ARGUMENT rather than 409, while
a partial resource name in the delete resolves against the project id
when creates land under the project number. Getting persistence to
survive its own success took longer than getting it to work at all.
Managed services are regional; Gemini 3.x is global. Building the
Model Armor endpoint from GOOGLE_CLOUD_LOCATION — correctly set to
global — produced a URL that fails quietly and falls back to a regex
screener. Model Armor also carries its own IAM role set, so roles/owner
does not grant it.
The public record punishes a careless WHERE clause. ICIS-NPDES mixes enforceable limits with monitoring-only rows, and using the flag wrong inflates every metric silently. An early per-parameter query averaged concentration and mass-loading limits together and reported 1,989 mg/L of ammonia — wrong in a way only a domain reviewer would catch, which is exactly the kind of error a confident agent will repeat.
Live models misbehave in ways mocks never do. A worker guessed a facility id it was not scoped to and was denied; several cited sources the supervisor could not independently read; one asserted a flow figure from a cached briefing the live sensor contradicted. Every incident became policy rather than a patch.
Accomplishments that we're proud of
A finding, not a demonstration. Against 10,396 real municipal facilities and 6,030,868 real reported values from the EPA's public record, the forecast would have flagged 53.9% of the 18,338 permit exceedances that actually occurred — a median of 51 days before the monthly report reached the regulator. Anyone who clones the repo reproduces it with two commands.
Fortification proved by refusal. Cross-facility reads denied. Unsourced claims quarantined before they reach the operator. Poisoned lab reports stripped by Model Armor with the numbers kept. All of it as attributed ledger rows with trace ids, sitting in the same list as the allowances.
A fleet that is deliberately not fully autonomous, with the reasoning written down. The operator carries the legal consequence, so he keeps the decision. Designing toward restraint produced a better product than designing toward independence would have.
Every fallback announces itself. The screener names itself on each guard row; the memory store names the backend actually in use; an unverifiable citation is allowed through but permanently marked unverifiable. Nearly every defect we found was found because a component said out loud that it was degraded instead of quietly carrying on.
A chaos harness that injects faults, never outcomes. A historian replica serves one agent readings from forty plant-minutes ago, labelled as current — wrong by 40 to 80% mid-surge. Whether the supervisor catches it is decided at runtime, every time. That is why the beat is not perfectly deterministic, and why it is worth watching.
What we learned
A fleet is more convincing when its agents disagree than when they cooperate. One proposed action and two quantified counter-consequences demonstrates more about multi-agent reasoning than any number of agents completing a pipeline.
Denials belong in the same ledger as allowances. The cheapest security interface we built was refusing to have a separate one. A reviewer scrolling one list can tell in seconds whether the fences are load-bearing or decorative. Systems that log only their successes cannot be audited by looking at them.
Autonomy is not the goal when a human carries the liability. A fleet that acted for this operator would transfer a risk his licence does not let him delegate. Designing toward restraint is why the deliverable is an assessment rather than an action.
Forecasts should be delivered as quantiles, not points. "One month in ten this goes wrong" is a sentence an operator can act on. A single predicted number invites false precision and gives no handle on how much attention it deserves.
The public record contains its own measuring stick. ICIS-NPDES
records not just what was exceeded but when the regulator was told —
VALUE_RECEIVED_DATE. That one column turns "early warning" from a
marketing claim into something with a unit attached, and it is why the
finding reports a median in days rather than an impression.
What's next for Steward — Water Guardian
Point it at a real plant. The architecture already assumes an OT boundary with Gemma inside it, so the next step is a cooperating utility's own historian behind that boundary — real telemetry, same guarantees, nothing raw crossing.
Widen the backtest. Weekly and daily statistical bases, not just monthly averages, and per-region recall so an operator can see how the forecast performs on plants like theirs rather than on a national average.
Let the registry become a staffing model. One visiting specialist proves the mechanism. Pretreatment, biosolids, stormwater and industrial pretreatment specialists published by the same primacy agency would make it a way of working — an operator hiring the expertise he needs for the night he needs it, and releasing it when the event is over.


Log in or sign up for Devpost to join the conversation.