CockroachDB × AWS · Build with Agentic Memorylive · 305 households · 3 regions

A crew is told this address is tier 1. They are never told why.

VIGIL is a living record of who cannot afford to lose power — reconciled across every registry that holds part of it, and split so the register survives losing a region while the diagnosis never leaves one.

What this actually is, in five steps

No jargon, and no databases. If you read only this section you should be able to explain the project to somebody else.

  1. Four lists that don't talk

    Your electricity supplier, your gas supplier, the network operator and your health plan may each know you depend on powered medical equipment. None of them tells the others. The same household appears three times under three spellings, and nobody holds the union.

  2. VIGIL merges them into one record

    It matches the same household across every source — including the awkward cases, like a married name in one register and a maiden name in another. When two records are genuinely ambiguous it keeps them separate and flags them, because wrongly merging two households makes one of them disappear.

  3. It works out how urgent each household is

    From the medical facts, it computes a priority: tier 1 means hours matter, tier 4 means no elevated need. That calculation happens inside the region where the medical data was collected, and never leaves it.

  4. Only the priority travels

    The tier is copied to every region, so a crew anywhere can read it instantly — even if the region holding the medical detail has gone dark with the rest of the grid. The reason stays put. A crew is told an address is tier 1; they are never told why.

  5. And afterwards, it can prove what it knew

    Every dispatch, every access, every refusal is recorded. After an outage you can reconstruct exactly what the register knew at 2pm that day — not what we know now — which is the only fair question to ask of the people who had to act on it.

Why this is hard, in one sentence

The register has to survive losing a whole region — which needs copies elsewhere — while the medical detail must never be copied elsewhere. Those two requirements cannot both hold in one database. Splitting the record so that only a number crosses the border is what makes them compatible, and it is the reason a unified register like this does not already exist.

The same household, from both sides of the wall

Pulled from the live register. The left panel is what any of the three regions can read. The right panel exists in exactly one of them.

Any region can read this

vigil · SURVIVE REGION FAILURE

household
i okonkwo85019
priority
1Life support

hours, not days

facts behind it
5— a count, not a reason
basis digest
6c2d59a42c558edeae15a300

SHA-256 of the clinical inputs. An auditor holding them recomputes this and proves the tier is real. Everyone else sees hex.

Only phoenix can read this

vigil_local · PLACEMENT RESTRICTED

Nothing here has been sent to your region.

These rows have zero non-voting replicas outside phoenix — verified from CockroachDB’s replica map, not asserted. A dispatcher in another region is not being shown a censored value. The query they run cannot return one.

Synthetic data. This household is invented.

One household. The wall between the two panels is a database boundary, not a permission flag.

Why the record has to survive the outage

heat-related mortality during a blackout in a heat wave

Stone et al., ES&T 2023 — modelled across 2.8M residents

+122%

accidental deaths, 2003 Northeast blackout

“Lights Out”, Epidemiology

3M+

Americans on electrically powered medical equipment

PubMed 26360818

And the register that should hold these people is fragmented across four separate lists, unknown to over 80% of the people who qualify, and never re-verified. Of those who have heard of it, 21% decline — over half citing embarrassment. Waiting to be joined cannot work.

How a record crosses — and what stays behind

Registry free-text carries the reason inline, because that is what real registry data looks like. So the split happens at ingest, before anything is replicated.

How a registry record crosses the jurisdiction boundaryA registry record arrives containing a name, an address and a clinical note. At the redaction boundary it splits in two. The verbatim record, including the clinical note, is written to the pinned database and does not cross. Only a redacted identity projection continues into the replicated database, where it is embedded and matched. Triage later reads the pinned side in-region and publishes a single integer and a digest back across.REGISTRY RECORDMrs A. Iyer4 Elm Rd, 30312oxygen concentrator, 15h/day← the reason, inline in free textREDACTIONallowlist + 98-term scanvigil_local · PINNEDverbatim record + care needzero replicas outside its regionIDENTITY PROJECTIONa iyer | 4 elm road | 30312embedded as VECTOR(1024) — no clinical textan embedding of a diagnosis is still a diagnosisvigil · 3 REGIONSreconciled householdtier 2 · digest 8b2fc8b7…survives losing a regiontier + digestthe only thingthat crosses back
The clinical note never reaches the replicated database — not filtered on the way out, never written. Everything to the right of the wall is data we have decided may cross a border.

Press the same button as two different people

Sign in as a dispatcher and press reveal on any household. Then sign in as the Atlanta caseworker and press it again. The difference is the whole project.

Three things to notice

  1. 1. The refusal names why — role, or jurisdiction. It is not a generic error.
  2. 2. Your attempt is already in the disclosure log at the bottom of the page — granted with a row count, or refused with a reason.
  3. 3. Nothing is being censored. The query a dispatcher runs cannot return a reason.
Or sign in with a token

Four agents, and what each one remembers

Each runs a loop against the same memory. Two of them deliberately never call a model — a tier has to be reproducible and explainable to a coroner, and exactly-once is a constraint rather than a judgement. Using a model where arithmetic suffices is a design failure, not a feature.

Reconciler

agents/reconciler.js
  1. read unresolved claim
  2. search 3 ways, one snapshot
  3. decide or ask
  4. write link + evidence
its memory
identity_claim · person_link · VECTOR(1024)
uses a model?
only in the ambiguous band — 73 of 535 claims

Exact, deterministic and vector search run in ONE serializable transaction. With a bolt-on vector store the two queries see different states and the system creates the duplicate it exists to prevent.

Triage

agents/triage.js
  1. read care needs in-region
  2. apply the tolerance ladder
  3. hash the inputs
  4. publish an integer
its memory
care_need → tier_derivation → priority_tier
uses a model?
no — and that is the point

Deliberately deterministic. “The shortest tolerance here is four hours, therefore tier 1” must be reproducible, explainable to a coroner, and identical on Tuesday and Thursday.

Dispatcher

agents/dispatcher.js
  1. claim the most urgent unlocked task
  2. visit
  3. record the outcome
  4. release, or be reaped
its memory
welfare_task · task_claim · task_outcome
uses a model?
no — and that is the point

Exactly-once is a UNIQUE index, not a judgement. A claim can also be RELEASED — making it permanent would turn every crashed responder into a household nobody is ever sent to.

Enrolment

agents/enrolment.js
  1. infer who is likely eligible
  2. check who must NOT be contacted
  3. ask
  4. remember that we asked
its memory
eligibility_signal · outreach · consent_state
uses a model?
no — and that is the point

The one that is agentic end to end. Over 80% of eligible people have never heard of the register, so waiting to be joined cannot work — it infers candidates from consenting signals and asks. And its FIRST job every run is working out who to leave alone.

Use it from your own systems

A utility’s dispatch system does not want a web page. The register is an HTTP API with bearer tokens, and it enforces exactly the same boundary.

HTTP API endpoints and what each returns.
endpointauthreturns
GET /api/v1nonethis documentation, as JSON
GET /api/v1/healthnonecluster reachability, version, region count
GET /api/v1/registertokenhouseholds with tier, confidence and digest — never a reason
GET /api/v1/households/{id}/reasontokenthe clinical reason, if your role and jurisdiction permit
GET /api/v1/disclosuresauditorwho read what, and who was refused

The same request, two tokens

# as the dispatcher — refused in every jurisdiction, always
curl -H "Authorization: Bearer $DISPATCHER"   https://main.drsi2hrw3uwzy.amplifyapp.com/api/v1/households/$ID/reason
# 403  "role dispatcher may never read.care_need"

# as the Atlanta caseworker, for an Atlanta household — granted
curl -H "Authorization: Bearer $ATLANTA"   https://main.drsi2hrw3uwzy.amplifyapp.com/api/v1/households/$ID/reason
# 200  { "needs": [ { "kind": "oxygen_concentrator", "tolerance_hours": 8 } ] }

There is no API-specific permission code. Every route calls the same can() the web app and the CLI call — one implementation of who may read a diagnosis, so the three surfaces cannot drift apart. Refusals carry the reason, and both outcomes are written to the disclosure log before the response is sent.

Every claim here has something that can contradict it

Each command exits non-zero on failure. None of these numbers is asserted in a README and left there.

npm run db:residency8/8

no clinical vocabulary in replicated rows · voters pinned · zero non-voting replicas

npm run access6/6

the boundary above, both directions, with the log

npm run mcp3/3

an MCP client cannot reach the pinned database — enforced by GRANT

npm run db:verify10/10

isolation · exactly-once constraints · vector index · row-level TTL

npm run score0.9912

reconciliation precision against ground truth, at 240 households

npm run ui:contrast11 tokens

every colour against its WCAG 2.1 AA threshold, both themes

Including the ones that did not survive: a diagnosis we believed for weeks and then disproved, and a precision ceiling the privacy boundary imposes that no amount of tuning can move.