Cited clinical search in Postgres

The current core fuses Postgres full-text ranking with a deterministic development vector signal and returns source citations under practice/resource/purpose policy. It does not ship BM25, production semantic embeddings, reranking, or patient/consent filters yet; those are the target contract below.

why it’s different

You search the meaning, not the schema

“Find when this kid’s anxiety started spiking around school” is a clinical question, not a FHIR query. The current core indexes dedicated search_doc rows and cites each hit back to its canonical FHIR source record. Searching app-native projections is a target design, not the current implementation.

Current retrieval core

Postgres full-text relevance + a deterministic development vector, fused with reciprocal-rank fusion and cited to the source.

Dedicated search documents

Current search reads search_doc, with every hit linked back to its canonical resource. Clean app-projection search is target work.

Policy at retrieval

Current: practice/resource/purpose scope before retrieval. Target: patient assignment, consent, and minimum-necessary filtering per hit.

Cited to the source

Every result points back to the record it came from. No floating snippets, no “trust me” answers.

One Postgres. Vectors next to the data.

bonfireDB keeps vector signals in pgvector inside the same Postgres that holds the canonical record. The current signal is a deterministic development embedder; production clinical embeddings and reranking are not shipped yet.

✓ Vectors stored beside the rows they describe — same database, same backups, same boundary.
✓ Target: production embeddings and reranking run inside your boundary — no external embedding or rerank API.
✓ Filtering, scoping, and similarity in one query — no fan-out to an external service.

Current: pgvector + deterministic development signal. Target: in-boundary semantic models.

search.ts
const hits = await clinical.search.hybrid({
  patientId,
  query: "worsening anxiety around school stress",
  citations: true
})

// target contract: hits ABAC-scoped to this caller,
// ranked by keyword + vector relevance,
// each carrying a citation to its source record
hits.map(h => ({
  view: h.source.view,        // notesByPatient | timeline | ...
  score: h.score,
  cite: h.source.recordId,    // the record this came from
  snippet: h.snippet
}))
retrieval safety

The permission check is the query, not the prompt

Putting “only show records this clinician can see” in the system prompt is a hope, not a control. bonfireDB applies ABAC at retrieval — the search itself never returns a row the caller isn’t entitled to. The model only ever sees what it’s already allowed to see.

Search-then-filter-in-prompt

Index returns everything, the prompt is told to “be careful,” and one jailbreak or a sloppy chain leaks another patient’s record into the context window.

Filter-at-retrieval (target)

The target checks patient assignment, consent, purpose, and minimum necessary before a hit leaves Postgres. Current retrieval is practice/resource/purpose scoped.

honest comparison

The closest shipping thing is Google’s — and it’s sunsetting

Credit where it’s due: Google Agent Search for healthcare already ships FHIR-native semantic search today, and it’s a serious managed offering. The catch is that Google has announced its end of life for 2027-05-15 — so building your retrieval layer on it means building on a clock. bonfireDB makes a different bet: keep hybrid search in the same Postgres you already run, so retrieval isn’t a separate managed service that can be deprecated out from under you.

Agent Search for healthcare

FHIR-native semantic search, managed by Google — genuinely the closest shipping competitor. Announced end of life 2027-05-15. Re-verify the date and migration path against Google’s current docs before you commit.

bonfireDB

Current core: pgvector colocated with the canonical record, practice-scoped retrieval, cited hits, and a deterministic development vector. Production semantic embeddings and patient/consent scope are target work.

how it stays fresh

Target: a sidecar projection with explicit freshness

Search documents are a separate write path in the current core. The target unifies governed commits, projections, and async semantic indexing behind one receipt so callers can distinguish committed, pending, and fresh state.

1

Write commits

The target governed writer commits canonical FHIR and operational projections atomically.

2

Index lane picks it up

The async index lane embeds the new content into pgvector beside the data — heavy work, off the write path.

3

Freshness is reported

The write’s freshness object marks indexes.semanticSearch as pending → fresh, so you always know what’s searchable.

Every write tells you what’s searchable

No guessing whether the index caught up. The freshness lifecycle distinguishes what’s committed and queryable now from what’s still being indexed.

✓ Target: every governed write updates required operational views atomically.
✓ Semantic index reports pending until embedded — no silent rot.
freshness
{
  status: "committed",
  views: {
    notesByPatient: "fresh",
    timeline: "fresh"
  },
  indexes: {
    semanticSearch: "pending",
    agentContext: "pending"
  }
}
feeds the agents

The same clean retrieval your agents run on

Search is one retrieval primitive underneath the AI layer. Current tools read cited search_doc hits; future agent tools can combine them with clean app projections and structured packets for exact Observations, medications, counts, or dates.

Agent context tools

Planned agent tools expose hybrid search and structured evidence packets as scoped, cited reads.

Agent context tools →

App-native primitives

Target: search over the same typed projections the app reads, while preserving canonical citations.

App-native primitives →

Fresh on commit

The freshness lifecycle that tells you what’s indexed and what’s pending.

Fresh on commit →

Clinical authorization & audit

Current retrieval is practice/resource/purpose scoped; patient/consent policy and complete read-audit coverage are target work.

Clinical authorization →

You build the app. Bonfire is the clinical data layer underneath.

Cited search documents in Postgres today; production semantics, patient/consent policy, clean-view retrieval, and unified freshness next.

FAQ

Frequently asked questions

Do I need a separate vector DB for clinical notes?

No separate vector database is required: the current core stores its development vector signal in pgvector beside the canonical record. Production semantic embeddings and reranking are still target work.

How does bonfireDB do clinical RAG without leaking other patients' records?

Current search is practice/resource/purpose scoped before retrieval. Patient assignment, consent, and minimum-necessary filtering per hit are required target work before production use over real PHI.

Is this keyword search or vector search?

The current core fuses Postgres full-text relevance with a deterministic development vector signal and cites every hit. Production semantic embeddings, reranking, and BM25 are not shipped claims.

Does hybrid search run over raw FHIR® JSON?

The current core reads dedicated search_doc rows and cites canonical FHIR source records. Search over app-native projections is part of the target contract, not the current implementation.

How does the index stay fresh after I save a note?

Search documents currently have a separate write path. The target unified governed writer updates canonical FHIR and required projections atomically, then reports semantic indexing as pending until fresh.