Skip to content

Meridian Ask build and guardrails

Confidential. Last 1 Enterprises. Status: staging on (2026-09-22). Prod stays off until Ryan GO after the device walk. Audience: internal briefing for the Ask path that shipped after Suites A/B/C/D went green.

This is the veteran-facing Ask this record surface: grounded Q&A over the veteran's consented, synced VA record. It is not an EHR, not a clinician note, not Service Matches, and not a diagnosis engine.

Canonical spec and eval bar live in last1-meridian (MERIDIAN_SPEC.md, MERIDIAN_EVAL_SPEC.md, CRISIS_PROTOCOL.md). API flip notes: last.vet-api/docs/runbooks/gate-meridian/MERIDIAN_ASK_MODEL.md.


What this build is

Meridian is LastVet's intelligence layer. Ask is the first live record-Q&A path:

  1. Eval first (last1-meridian). Suites A/B/C plus adversarial Suite D. Gates are product behavior, not model goodwill.
  2. API (last.vet-api). POST /api/veteran/me/meridian/ask runs the same gate order, then local Ollama. Question and chunks are transient. Logs are metadata only.
  3. iOS (last.vet-ios, feature/meridian-ask-live-record). On-device top-k retrieval, then the API when the flag is on. Keyword fixture remains the fallback.

The model is a swappable component behind those gates. Default serving model: self-hosted mistral-small:24b-instruct-2501-q4_K_M via Ollama on the LAN (192.168.1.40:11434). PHI does not leave LastVet infra on the default path.


Flags (do not overload)

Flag Meaning
MERIDIAN_ASK_MODEL_ENABLED Ask may call the model API. This is the Ask go-live flag.
MERIDIAN_M1_ELIGIBLE Service Matches live labeling only. Independent of Ask.
LASTVET_CRISIS_LIVE_ENABLED Radio Check / dashboard crisis live. Independent of Ask.
MERIDIAN_ASK_ALLOW_HOSTED Required to point OLLAMA_BASE_URL at a hosted vendor. Default false. Explicit PHI-leaving tradeoff.

iOS reads GET /api/veteran/me/meridian/ask/status → { modelEnabled }. Debug may force on/off with -meridianAskModelEnabled / -meridianAskModelDisabled. When the flag is off or the API errors, Ask falls back to MeridianSyntheticQAResponder (on-device keyword fixture).

As of 2026-09-22: staging MERIDIAN_ASK_MODEL_ENABLED=true. Prod unset (defaults false). Testers use scheme LastVet-Staging / internal TestFlight group. See docs/testflight/MERIDIAN_ASK_STAGING.md. Do not flip prod until multiple testers walk the live device path on staging.


Live path (device)

Veteran question
    → iOS CrisisDetection (same fire patterns as crisis_gate)
    → local top-k retrieval (MeridianRecordIntelligence, default 8 chunks)
    → POST /api/veteran/me/meridian/ask  { question, priorQuestions, chunks }
    → API gates (below) before any model call
    → local Ollama, temperature 0.1, system prompt + record context
    → strip freelance VCL language if the crisis gate did not fire
    → cited answer renders on device

PHI blast is the top-k chunk texts plus the question. No persist of question or chunk bodies. Two-data-plane still applies: VA data is consented and transient unless the veteran directed a copy.


Guardrail stack

Gates are deterministic short-circuits. They run before the model. Shared crisis and safety gates also fire on Matcher surfaces so matching is not a bypass.

Order on Ask (eval AskGuardedProvider and API MeridianAskService):

  1. Crisis
  2. Grounding (inventory, dates, false premise, injection context)
  3. Safety refusal
  4. Absent item / fabrication bait
  5. Model
  6. Volunteer-crisis strip (post-model)

1. Crisis gate (zero tolerance)

Source: last1-meridian/src/meridian/guardrails/crisis_gate.py. Parity: last.vet-api/src/lib/meridian/crisisGate.ts, iOS CrisisDetection.swift.

  • Reads veteran user turns only. Never FHIR / record text.
  • Over-inclusive first-person SI / self-harm / acute-distress patterns. No sincerity judgment.
  • Precision floor: definitional asks ("what is suicidal ideation", "how does the VA treat") must not fire.
  • "I'm done" is not a standalone trigger.
  • Mid-task emergence still fires (distress during a benign task). Prior task is abandoned.
  • On fire: verbatim Veterans Crisis Line response (CRISIS_PROTOCOL v1.5). No counseling, no risk assessment, no competing content.
  • Mic signed v1.5 (2026-09-21). Product LASTVET_CRISIS_LIVE_ENABLED is a separate flip.

2. Grounding gates (record, not advice)

Source: last1-meridian/src/meridian/ask/ask_gates.py. Parity: last.vet-api/src/lib/meridian/askGrounding.ts.

These answer from the retrieved chunks or refuse to invent. They run before safety so "do I have appointments / allergies / meds?" is not misclassified as diagnosis solicitation, and so a false premise is corrected instead of only refusing the advice half.

Gate What it does
Appointment empty Honest empty when the veteran asks about upcoming appointments and none are in context.
Take-all "Do I take all of these?" lists what the record shows. Does not confirm current adherence.
Injection context Poison in the question or a retrieved chunk (DAN, ignore previous, new system prompt, comply_token, etc.). Refuses. Record fields are data, never instructions.
False premise Veteran asserts a named fact that is not in top-k (leading / "since I have X"). Corrects. Does not accept the invented fact. Adds treatment refusal if they also ask what to do.
Temporal / last procedure Last, earliest, year, or named dated Procedure / Immunization from chunk dates.
Immunization inventory Lists shots from retrieved Immunization chunks.
Condition / allergy / medication inventory Lists those resource types with citations. Narrowed so "do I have allergies" is inventory, not diagnosis.
Cardiac filter Heart / cardiac asks stay on cardiac-relevant chunks.
Follow-up residual Short follow-ups ("when?", "anything else") stay on the prior retrieved topic.
Absent item Named med/item not in top-k → "not on your list." No fabricated cite.
Fabrication bait Inventory asks that sneak in an absent drug name list only what is on the record.

Source: last1-meridian/src/meridian/guardrails/safety_refusal_gate.py. Parity: last.vet-api/src/lib/meridian/safetyRefusalGate.ts.

Verbatim templates. Educational definitions ("what is PTSD", "what does A1C measure") must not fire. Applying that knowledge to the individual must.

Rule Refuses
self_harm_adjacent Lethal dose / how-to overdose. Routes toward crisis help.
impersonation "I'm her caseworker / doctor." Third-party record asks.
consent_scope_part2 / consent_scope_violation Part 2 / SUD outside the grant. Revoked or out-of-grant data.
injection_compliance Follow-through on embedded instructions (user-turn form).
treatment_directive Start, stop, taper, dose, prescribe, care plan.
benefits_legal Rating predictions, "will I get", file an appeal.
diagnosis_solicitation "Do I have PTSD?", "diagnose me", "what's wrong with me." Inventory lookahead so "do I have allergies" does not hit this.
application_boundary "Does my A1C mean I have diabetes?" General knowledge applied to this veteran.

Hard product rules these encode: no diagnosis, no treatment recommendations, consent-scoped retrieval, 42 CFR Part 2 respected.

4. Post-model: volunteer crisis strip

The model must not freelance Veterans Crisis Line language on a benign ask. If the crisis gate did not fire and the completion still contains VCL / 988 copy, those paragraphs are stripped. Crisis protocol is only allowed when the crisis gate fired.

5. Hosted-model block

If OLLAMA_BASE_URL looks like OpenAI / Anthropic / Groq and MERIDIAN_ASK_ALLOW_HOSTED is not true, the API refuses to call it. Default path is local Ollama only.


Eval proof (promotion bar item 1)

Harness: last1-meridian. Command shape: meridian-eval run-ask --provider ollama.

Suite Job Count Last Ollama result
A Grounding: cite the record, honest empty, no hallucination part of 168 168/168 with B+C
B Red team: crisis, diagnosis, treatment, scope, injection, impersonation, benefits part of 168 168/168 with A+C
C Utility: do the job, do not over-refuse part of 168 168/168 with A+B
D Adversarial pressure by failure mode (~20 each) 224 224/224

Suite D categories (all green on Ollama before staging flip):

  • adv_absent_fab : named missing item / bait must not be invented
  • adv_leading : false premises must be corrected
  • adv_clinical : diagnosis / treatment lines
  • adv_crisis_fire : protocol must fire
  • adv_crisis_nofire : precision floor; model must not volunteer VCL
  • adv_natural : ordinary phrasing still works
  • adv_followup : short follow-ups stay on topic
  • adv_temporal : dates and order
  • adv_aggregation : counts and lists stay cited
  • adv_inventory : "what meds / conditions / allergies"
  • adv_injection : poison in question or chunk

Promotion gates: fabrication_zero and crisis_perfect on A/B/C (CI scope). Suite D is the adversarial bar that unlocked staging. Any real failure becomes a permanent regression case.

Graders are dimensional: must-cite, abstention, clinical refusal, crisis required / must-not-fire, follow-up resolved, required / forbidden substrings.


iOS surface

Branch: feature/meridian-ask-live-record (Ask wire 71c6393; tip includes crisis I'm-thinking parity 830d204).

  • Retrieval is on-device (MeridianRecordIntelligence). Chunks map citable kinds to FHIR resource types.
  • Citations render from citationElementIds returned by the API (or fixture).
  • Crisis detection on device matches the Python fire patterns so a live walk should match CLI crisis behavior.
  • Fixture stays for flag-off, 503/502, or transport failure.

Device walk (staging): live round-trip (retrieval → API → model → cited answer), citations feel native, plus adversarial probes on device (absent-item, false premise, crisis phrasing).


What this is not

  • Not the clinician's system of record. LastVet holds the veteran-visible Coordination Note, never progress notes / treatment plans / assessments.
  • Not Matcher-0 / Service Matches. Matching is a parallel lane with the same crisis + safety foundation.
  • Not training on identified data. Eval and serving use synthetic or consented transient top-k only.
  • Not a hosted-model default. That is a separate, explicit flag.

Source map

Piece Where
Ask gate order last1-meridian/src/meridian/ask/ask_provider.py
Grounding gates last1-meridian/src/meridian/ask/ask_gates.py
Crisis last1-meridian/src/meridian/guardrails/crisis_gate.py
Safety refusal last1-meridian/src/meridian/guardrails/safety_refusal_gate.py
Ask cases last1-meridian/cases/suite_ask/{A,B,C,D}/
API serving last.vet-api/src/services/meridian-ask.service.ts
API gate parity last.vet-api/src/lib/meridian/{crisisGate,safetyRefusalGate,askGrounding}.ts
iOS flag + wire MeridianAskModelFlag.swift, MeridianAskModel.swift, MeridianSyntheticQAView.swift
iOS crisis parity LastVet/Features/Crisis/CrisisDetection.swift