BK

Index/Case study

Radar Autonomy

An agentic operating system with a product on top. The product decides where autonomous fleets should put their depots. The operating system underneath is what makes the product’s answers checkable — a governed control plane that routes machine work, bounds what any agent can see, and keeps a person on the irreversible verbs. I designed and built both.

Next.jsReactTypeScriptMapLibre GLD3PostgresGeospatial ETL
189,323

Lines of TypeScript across 574 tracked files

230,553

GeoJSON features published across 28 datasets

44

Automated data-integrity checks standing in for a test suite

4

Cron pipelines that fetch, validate and commit data unattended

01The problem

A research problem wearing real-estate clothes.

Autonomous fleets need ground. Not abstractly — specific parcels, close enough to where demand actually is, with enough grid capacity to charge a fleet overnight, zoned to permit the use, with a yard geometry that a vehicle can actually circulate through, and with a path to site control that does not take three years.

Each of those is a different public-records problem with a different source, a different update cadence, and a different failure mode. Assembled by hand, the answer arrives as a spreadsheet and a recommendation — and six months later, when someone asks why this parcel, the reasoning has evaporated. The number survives; the evidence behind it does not.

Radar exists to make that chain unbreakable. Not a map with scores on it. A traceable path from a public source, through a documented derivation, to a gate that a site either cleared or failed — with the record that decided it still attached at the end.

01bShape

Two halves, and the second one is the interesting one.

The visible half is a geospatial workbench: markets, layers, candidate parcels, gates, underwriting, exports. Everything below in this case study describes it.

The half nobody sees is an operating system for the machine work that keeps it honest — a separate repository holding no application code at all, only registries, policies, schemas and adversarial evaluations. A deterministic router decides which specialist handles a task and exactly what it may read, before any model reasons about anything. Specialists launch with no tools and no inherited history. Four verbs stay with a human.

I built it in that order for a reason. The product got good fast and then started drifting — numbers that were right on Monday, unsourced by Friday, with no way to tell which agent or which run had changed them. The operating system is what stopped that, and it is the part of this project I would rebuild first somewhere else.

02The spine

Source to decision, without a gap.

01

Brief

What the operator actually needs

02

Demand

Where the trips are

03

Candidates

Parcels and facilities

04

Gates

Pass, fail, or unresolved

05

Underwriting

Weighted, versioned

06

Dossier

Frozen and hashed

07

Monitoring

Sources age; so does the answer

03Evidence

Unknown is a value. It never scores.

The single most consequential design decision in the system is that a missing input is not a zero, not an average, and not a hidden default. It is null with a reason code, and it drops the record into a review band instead of quietly producing a number that looks like knowledge.

Every value in the system carries an evidence mode, and each mode carries the confidence weight it deserves. Stale utility data is nulled rather than shown. A pipeline build date is never promoted into a claim about source freshness. A future-dated source is flagged invalid, not fresh.

Evidence modes and their confidence weight

Measured
Read directly from the source record. Full weight — 1.00
Modeled
Derived through a documented model. 0.65
Directional
Indicative only; will not carry a decision. 0.35
Demo
Illustrative. Contributes nothing — 0.00
Unavailable
Source could not be resolved. Contributes nothing — 0.00

Unresolved gates contribute zero confidence and zero suitability, and the weight they took with them is reported back as an explicit audit figure rather than being silently redistributed across the gates that did resolve.

Freshness, enforced

Grid hosting-capacity data is only allowed to populate a parcel when the record was measured at the nearest line section and is inside its 45-day service-level window. Outside it, the field goes null and the method becomes an unavailable-source clock with a human-readable reason.

No double-counting

The land-use component deliberately excludes every permit-derived signal, because permit activity is already scored in a separate readiness component. Weights re-normalise over whichever components actually have data, so the composite can never reward the same permit rows twice.

04Underwriting

Screening is not approval.

Nine weighted gates, summing to a hundred points, resolve a site into one of four states. A fatal failure forces a screen-out no matter how well the rest scores. An unresolved fatal gate forces a hold. Confidence below seventy caps you at conditional advance.

Candidate ranking then sorts by decision state first — then fatal failures, then material failures, then unresolved weight, and only then by score. That ordering is the point: it is structurally impossible for a high-scoring screened-out site to outrank a viable one.

Site identity itself is a gate. It is not satisfied by an address string matching; it requires the parcel match to be a containing point, exactly one matched parcel feature whose identifier equals the assessor parcel number, and the official address point to agree with the site coordinate to within 1e-8 degrees.

The nine gates

Legal use · 16
Does the zoning actually permit the operation
Utility · 16
Is there grid capacity to charge the fleet
Fleet capacity · 14
Does the site hold the vehicle count
Site control · 14
Is there a realistic path to control it
Budget · 10
Does the basis survive the operator’s envelope
Identity · 8
Is this provably the parcel we think it is
Yard geometry · 8
Can vehicles actually circulate
Access · 7
Routing, restrictions, deadhead
Environmental · 7
Screens that can kill a deal late
Screen outHold for diligenceConditional advanceAdvance
05The map

Network timing must not decide what you see.

The workbench renders more than a hundred map layers — 109 typed layer specifications over a set of shared sources — and several of them are multi-megabyte GeoJSON assets that land whenever the network delivers them. In the naive implementation, whichever asset arrives last paints on top, so the same site can look different on two loads.

So the stack is reconciled rather than appended. Every layer is declared once in one ordered canonical array; new layers insert relative to the next already-mounted canonical identifier; and the entire stack is re-sorted whenever an async asset arrives. Broad context layers are additionally pinned beneath the basemap’s own road and place labels, so geography never buries its own names.

Two details I am fond of. First, geographic scope is a compile-time contract: layer and mode availability live in tables closed with a TypeScript satisfies clause, so adding a layer without declaring which markets it is valid in is a build failure — and switching markets projects the user’s visibility rather than mutating it, so one city can never render or cite another city’s evidence. Second, the basemap has a three-tier fallback ending in an inline offline style, because a blocked CDN should degrade to a working dark map with every bundled layer still attached, not to a dead black rectangle.

06Depth

What one candidate site actually carries.

1,354

San Francisco parcels individually screened for grid power and deadhead distance

39

Distinct public-record enrichment feeds joined onto every candidate site

12

Candidates that survive screening and carry full underwriting

Those thirty-nine feeds are not a data dump. Each one runs the same house pattern end to end — a fetch or derive script, JSON keyed by site identifier, a typed facts interface, a dedicated panel component, and a row in both the coverage ledger and the methodology ledger — which is why there are forty facts panels in the product and no orphaned datasets behind them. Grid hosting capacity, zoning, census demographics, occupational wage, fire and EMS response, pavement condition, curb parking, truck restrictions, sensitive receptors, environmental screens.

07The copilot

Grounded, or it refuses.

There is a research agent inside the workbench. It runs a read-only tool loop over the product’s own data — workbench context, candidate search, site briefs, operator facilities, market summaries, data limitations — plus live intel and hosted web search, bounded to eight tool rounds and force-finalised after five data rounds.

The rule it lives under is the same rule the rest of the system lives under: a reply is checked for real tool evidence, and without it the agent falls back to a safe ungrounded response rather than improvising. Citations are derived mechanically from the tool calls that actually happened, not written by the model.

It is also budgeted like something that runs in production — capped body size, per-principal and per-IP rate limits, and a token-cost ceiling whose units are deliberately decoupled from billing prices, so renaming a model can never silently disable the guardrail. Limiter keys are hashes; no raw user identifier, access token, or IP is retained.

08Autonomy

It keeps itself current.

Four scheduled pipelines run without anyone present. They pull from public sources, validate what comes back, snapshot the raw response into an append-only archive with a diff log, and commit the result. Two machine identities appear in the commit history alongside mine, which is the honest way to describe a repository that partly maintains itself.

Above them sits a scheduled analyst agent that runs daily in an isolated worktree, sweeps ten defined work lanes, ships one change end to end, and promotes its own release through a written gate: clean detached main, exactly one commit, checks run against committed bytes only, re-fetch before push, never force-push, poll for the exact commit hash, and confirm the deployment health endpoint uncached.

Its memory is deliberately not conversational. It writes a staged backlog, a skill registry with dated evolution decisions, and a run note per day. Six of the ten recorded skill decisions are deliberate no change or defer entries, with written reasoning about context cost — which I consider the strongest evidence the thing is actually thinking rather than accumulating.

What is checked, and by whom

Test suite
There is no npm test and no CI gate. 44 domain check scripts are the suite, and local validation is the only thing standing between a change and production.
Source rights
Every source carries a rights decision; an expired or revoked one fails the build rather than degrading quietly.
Temporal integrity
Evidence cannot claim a validity window it does not have. Inverted or future-dated records are rejected.
Entity resolution
A mapped facility clue is never allowed to be restated as a physical facility.
Staleness
A monitored registry of source artefacts with per-source age limits and a due-ratio threshold.
Publication
Datasets are hash-addressed behind a manifest pointer that is replaced last, so the app can never advertise a half-built pair.
09The operating system

What the agents are not allowed to do.

Alongside the product sits a small second repository that does nothing but govern how machine work is delegated. It holds no application code — it is registries, policies, schemas and evaluations, and it is machine-checked on every change.

Routing happens before any model reasoning. A deterministic router classifies the task, selects one specialist, and resolves exactly which context items that specialist may read — returning a three-way split of readable items, metadata-only items, and blocked items, each blocked one carrying a named machine-readable reason. Specialists then launch as no-tool subagents in a fresh, non-resumed context that can see nothing but a signed packet.

The packet is where the design earns its keep. It embeds a revision hash computed over every registry, so editing any of them invalidates every outstanding packet. And validation refuses to trust the packet’s own claims: it re-runs the router live and byte-compares the result. A packet whose two revision strings match each other but are stale is rejected. A packet that quietly appends one extra source grant on a current revision is also rejected — because the freshly derived route does not contain it.

Above all of it is a four-verb human boundary — send, spend, sign, release — declared once, derived mechanically, and enforced in code rather than requested in prose. Approvals bind to a hash of the exact payload, so a changed recipient or amount invalidates the approval. A producer can never review its own work.

78

Pinned routing cases, plus adversarial registry mutations that must fail closed

23

Adversarial safety evaluations — 20 of them required rejections

24

Machine-enforced schemas across the control plane

13

Specialist agents, each with no tools and no inherited context

10What it taught me

Rigour is a feature you can ship.

The instinct on a project this size is to defer the discipline — get the map working, make the numbers look right, add provenance later. That order does not work. Provenance is not a layer you can retrofit; it is a property of how each value was constructed, and once a value has been through three derivations without carrying its source, the information is gone.

The second lesson is that constraints written in prose do not hold. Every rule in this system that actually survived contact with a deadline is one that fails a build, fails a check, or fails a schema. The rules that lived in documentation got broken — including by me.

Both of those transfer directly to diligence work. The question I ask of a target’s AI claims now is not whether the model is good. It is: what happens here when an input is missing, and who finds out?