Documentation · OpenMRS integration · updated November 2026
What we are contributing to OpenMRS.
We are contributing our ChartSearchAI and QueryStore work upstream to the OpenMRS repositories across three
ordered gh-stack pull-request stacks: seven QueryStore layers, nine ChartSearchAI backend layers,
and five ChartSearchAI frontend (ESM) layers. Each layer targets the layer below it so its diff and test checks
are scoped to a single reviewable step.
In plain words
Two review stacks and one shared dependency.
QueryStore is the OpenMRS module that indexes a patient's records so they can be searched and read back quickly. ChartSearchAI is the module that answers a clinician's question about a chart using a language model, either one bundled with the module or an optional external service (Med Agent Hub). The ChartSearchAI ESM is the part of the OpenMRS web interface that shows the question, the answer, the records it drew on and the checks that ran.
Both stacks keep the bundled model as the default and add Med Agent Hub behind a shared interface. Maintainers
review and merge each stack from the bottom up via gh-stack. The frontend ESM stack (#72–#76) has
merged into upstream main; QueryStore (#92–#93 merged, #94–#98 open) and the backend module
(#615, #616 & #625 merged, #617–#623 open) are advancing through maintainer review.
Shared dependency: QueryStore #92–#98
Query-cache separator spelling (#92, merged 3491096)
and versioned Elasticsearch deletes (#93, merged 0c12429)
are in upstream main. Open layers cover incremental bootstrap skip-unchanged (#94, f08dc41),
patient-record reads over REST with completeness and date semantics (#95, aecb9f6),
the tiered context slice (#96, 7a1f47f),
question interpretation with retrieval preprocessing (#97, bc3d7d5),
and similarity diversity with deeper hybrid pools (#98, 3c0783d).
Backend review and merge order
- #615 Shared provider contract 5272a5c · Merged
- #616 Exact token counting 69add36 · Merged
- #617 Safety-check execution status c83fa3c · In Review
- #618 Conversation persistence b5d4115 · In Review
- #619 QueryStore context and token budgets 09ad023 · In Review
- #620 Bundled inference and cancellation 455fcdf · In Review
- #621 Hub discovery and transport bbb1f57 · In Review
- #622 Conversation endpoints and history 03325a3 · In Review
- #623 Account context forwarding a087da3 · In Review
Additionally, #625 (88f369f) has merged into upstream main.
Frontend review and merge order (Merged 27 October 2026)
- #72 Staged stream transport Merged
- #73 History client and session state Merged
- #74 Markdown, tables and citations Merged
- #75 Provider and model-profile selection Merged
- #76 Conversation lifecycle, staged answers, evidence and streaming toggle aee3449 · Merged
All five frontend ESM layers (#72–#76) are merged into openmrs/openmrs-esm-chartsearchai main. Open any pull request above for its review history and checks.
Where review stands
Two maintainer review rounds answered; merging is the maintainers' decision.
An OpenMRS maintainer reviewed every pull request in both stacks, first on 5 October and again between 9 and 13 October. We answered the first round with fixes on 7 October and the second with fixes on 18 October, each in the pull request that owns the behavior and each with a test that failed before the fix. The second-round threads stay open on GitHub until the replies are posted.
Still open
- #587 needs measured evidence for its context selection, and its temporary dependency job must come out before the stack reaches main.
- QueryStore #68 stays open until the maintainer picks up the three pull requests that replace it.
What has been checked
- Every backend layer passed the module's full test suites on 18 October (between 3,244 and 3,380 API tests and between 294 and 343 web tests per layer), and the stack tip passed again after the day's final rebase onto upstream: 3,402 API tests and 343 web tests, 63 skipped, none failing.
- The frontend stack passed 701 tests, lint, type checks, the build and translation extraction at #59.
- QueryStore revision 844dcd0 builds and passes its tests, and the backend stack was tested against that exact build.
What the hosted checks show
- OpenMRS runs its hosted checks only for pull requests that target main, so #583 and #55 report hosted results and the other eleven do not. That is why the build-trigger changes above exist.
- The backend stack's own checks on our fork pass the builds against the reviewed QueryStore source on Java 11, 17 and 21. The ordinary build fails until the QueryStore API is published, which is the one open dependency.
QueryStore #85 to #87
One authorized way to read a patient's records, which says when the set is incomplete.
QueryStore takes on record projection, dates, completeness and question-aware selection. It does not compose prompts and knows nothing about models; that stays with the module asking.
Read API
GET /ws/rest/v1/querystore/patientrecordin three modes: the full chart bypatient; a ranked top-K bypatientandq, orqalone; a context slice withmode=context.- Every mode requires the OpenMRS
Get Patientsprivilege. Unknown modes and malformed typed parameters return 400. - Paging honours the live
webservices.rest.maxResultsAbsoluteceiling: an oversizedlimitis clamped, a ranked window that would extend past the ceiling returns 400, andlinkscarry the effective page size. - All three exits send
Cache-Control: private, no-cache, must-revalidate; the full chart also carries an ETag for revalidation.
Completeness
chartTruncatedcomes only from the backend's explicit signal: an Elasticsearch size limit, failed shard, timeout or early termination; a MySQL per-table read failure; a Lucene per-index failure or an index the enumerator could not open.projectionCompletereports the deployment-wide bootstrap state; a failed lazy projection for one patient is disclosed on the read instead of returning an empty chart marked complete.- Snapshot identity and the ETag include both completeness bits, so a cache cannot serve a partial chart as the complete one.
- The warning logged for an incomplete read names the signal that fired, so an operator chasing it looks at cluster health or paging, whichever applies.
Dates
- Each record carries
clinicalDateanddateKind(clinical_event,administrative,unknown) beside the sort date, so a temporal consumer never reads an administrative date as an event. - Providers of custom record types must declare
date_kind; the architecture record carries the re-bootstrap advisory for deployments indexed before the fields existed.
Context slice
getContextSlice(patient, question, request)returns tier-tagged records: mandatory clinical core, recency anchor, typed completeness, similarity.- Each page carries
chartSnapshotId(the same complete-chart fingerprint the full read publishes),sliceId,effectiveTypesandtemporalApplied; a consumer combining a ledger with a later slice must match the snapshot. - Selection is deterministic when interpretation is off.
interpret=trueadds typed and temporal cues; lab-panel expansion and stopword stripping run in the store, and words that carry clinical meaning (negation, status, laterality, body site) are protected.
What the tests pin down
- A handled backend failure is never reported as a complete empty chart.
- Paging never links to a request the same controller rejects.
- Existing in-JVM
QueryStoreServiceimplementations keep working unchanged.
ChartSearchAI backend #583 to #590
Two ways to produce an answer, one set of rules for both.
The bundled model stays the default for a fresh installation, and the full chart stays the default context. Med Agent Hub is a separately configured option, never a silent fallback. ChartSearchAI keeps authorization, prompt assembly, sessions, audit and persistence in either case.
Provider boundary
ClinicalAnswerProviderwith bundled and hub implementations; a registry driven bychartsearchai.providers.enabled(defaultbundled).GET /providerslists enabled providers with readiness and a reason when unavailable; a picker is requested only when more than one is enabled.- An unknown, disabled or unready selection fails with an explicit turn error. There is no cross-provider fallback.
- Declared capabilities gate optional features: answer check, review, In-Depth, structured blocks, multi-turn context, token streaming.
Turn lifecycle
- One request, event and envelope model for Answer, validation, In-Depth, final evidence, errors and cancellation; a validator enforces stage order and rejects post-answer events that arrive before the answer is done.
- Three text channels stay distinct on the stream: a provisional preview with its own citation numbering, committed reasoning, and the answer.
- Cancellation reaches a running inference, including a Hub response that arrives after the cancel. A terminal event is always delivered, and it waits for audit persistence, so a save failure emits one error rather than a success followed by an error.
Conversations
- Persistent clinical conversations and turns with Hibernate mappings and Liquibase changesets; a conversation is bound to one provider, and a closed conversation cannot be reopened by a late request.
POST /chat/new,GET /chat,POST /chat/stream, besideGET /providersandGET /models; a restored session hydrates the same lifecycle the live stream produced.
Context and budgets
- Query-scoped chart selection, when configured, is delegated to QueryStore's context slice; the turn abstains on a truncated chart or when mandatory evidence cannot fit, and chart search requires a completed QueryStore bootstrap, which the setup documentation now states.
- Exact token budgets from the inference server, with a 4,096-token reservation for the answer; an oversized prompt is rejected explicitly instead of being silently cut, and the module's own drug-safety records are counted before optional records are admitted.
Safety and evidence
- The safety badge reports the validation that actually ran, so it means the same thing on both providers.
- Medication-rule and cross-reactivity findings are reported separately; a check with missing data, failed reads or partial scope reports limited or unavailable rather than a clean result.
- The five answer-limit statements and the safety chip ride on every envelope-bearing event and on the persisted turn; In-Depth validation summaries are persisted for reload and review.
Hub pathway
- One OpenAI-shaped chat completion request with extension fields (
patient,session,request_id,require_product_profile,context), answered as completion chunks mapped onto the turn events. - The hub's validation, In-Depth and safety results are relayed, not reinterpreted in Java;
GET /modelsrelays hub product profiles. A rejected request logs the Hub's HTTP status, and the Hub's text never reaches the OpenMRS log above debug level.
What the tests pin down
- Provider selection never changes silently; changing provider starts a new conversation.
- Final citations, temporal checks, medication safety, audit records and restored sessions use one provider-neutral contract.
- From backend #587 onward, pull-request builds on our fork use the exact QueryStore source revision under review, a temporary arrangement until that API is published.
ChartSearchAI frontend #55 to #59
The same answer, checking, evidence and safety display, whichever source answered.
The interface half of the backend change. Low-confidence, edited, rejected and withheld output stays visible for a person to review; nothing important lives only in a tooltip.
Turn lifecycle
- One reducer for Answer, validation, In-Depth, final evidence, errors and cancellation, driven by
/chat/stream. - Token streaming can be turned off. When enabled and supported, answer and reasoning deltas accumulate while answering; the final event restates the whole answer. Unsupported optional features do not leave the interface waiting for a stage that will never arrive.
- Stale or post-terminal events cannot leave the interface pending; the chat store outlives the panel.
Provider choice
- A provider picker appears only when discovery returns more than one enabled provider.
- An explicit or restored choice that becomes unavailable stays selected and visibly unavailable; only a deliberate switch changes provider, and it starts a new conversation.
- A product-profile picker is backed entirely by Med Agent Hub metadata.
Evidence and safety rendering
- Citation chips with per-finding severity badges and per-kind reference titles; a misattributed marker is struck through and does not navigate.
- The five answer-limit statements render on both providers, reconciled with the upstream change that introduced them.
- Medication-safety status reads Checked, Limited safety check or Safety check unavailable, with package id, version, review state and provenance visible without hover.
- A rejected In-Depth section shows the concrete validation reason; low-confidence and edited content stay available for manual review.
Contract fixtures
- The shared dual-provider conformance fixture is mirrored into the ESM and pinned by tests, byte-identical to the copies in QueryStore, ChartSearchAI and Med Agent Hub.
What the tests pin down
- Both providers produce the same lifecycle semantics on screen.
- Provider selection survives refresh and session hydration.
- Cancellation and errors always reach a terminal state.
How this fits our other work
The same revisions run our local research environment.
The pull-request heads listed above are the exact module revisions pinned in our local OpenMRS research environment with the HIV dataset. Timing comparisons between the bundled model and Med Agent Hub, and the evaluations run by the validation harness, use those pinned revisions, so what is measured locally is what is proposed upstream.
Deployment, release signoff and clinical acceptance are separate from everything on this page. An open pull request or a passing build does not mean that the change is released, deployed or clinically validated.
Sources: the linked pull requests' heads, bases, review threads and build results checked in November 2026. Product behavior is defined in the assembled backend documentation and the QueryStore API contract.
These are proposed contributions. Maintainers decide whether and when to merge them.