Skip to content

fix(clinical): medication changes from free text are safe, and clear ones apply automatically - #110

Open
ac12644 wants to merge 3 commits into
mainfrom
clinical-gate-fix
Open

ac12644 wants to merge 3 commits into
mainfrom
clinical-gate-fix

Conversation

@ac12644

@ac12644 ac12644 commented Oct 22, 2026

Copy link
Copy Markdown
Owner

Medication changes read from free text could change a patient's record with no approval: in live runs 36–43% of medication-change sentences did (gpt-6-luna, gpt-5-mini, gpt-6-sol), most often leaving warfarin and apixaban both active after a switch. This fixes that, then makes the clinical preset automatic so clear changes don't need a person.

1. Safety fix (7f64efd)

The approval gate keyed on the model's action and subject labels, which vary. Every observed bypass is closed: an ADD beside an existing value, a stop filed as history / an event / an invented subject, a future-tense order outside the queue, a targeted change under a different subject, an unknown action that lost the message, brand names (Coumadin/Eliquis), and junk values ("held_for_contrast"). A sentence-level sweep catches stops the extraction dropped, and pending_changes / confirm_change warn before two anticoagulants would be current.

Live evaluation (benchmarks/evaluations/med_change_safety.py, real model calls): unsafe 93/112/105 of 260 before → 0 of 1,440 after, across 24 phrasings including 11 written after the fix; every expected change queued; latency unchanged (~2.1 s p50, all model time).

2. Automatic mode (b26d29f, 117a50a)

A gated slot now has a mode: "confirm": "auto" applies a clear change at once (marked auto_applied in history) and queues only an uncertain one: a hedge, a negation, a future-tense order, low confidence, a guessed action or rerouted subject, a sweep-only stop, or a change that would leave two anticoagulants active (so a switch applies whole or not at all). "confirm": true still means review; VAYL_CONFIRM_MODE=review|auto overrides every slot. The clinical preset puts medications on "auto" and keeps code status on "review".

Replay evaluation (benchmarks/evaluations/med_change_replay.py, no API calls): replays 2,048 recorded model outputs through the engine. The first auto run found 50 unsafe outcomes review mode had hidden (hold notes overwriting doses or added as fake drugs, a "continued" drug removed, a two-drug stop stored as one entry, a negated switch queued, a dose change breaking the drug's name); all fixed. After: 0 unsafe, 0 wrong; 97.7% of medication changes applied automatically (1,553 of 1,589). Engine time 0.07 ms per remember.

Not covered by replay: how often a model produces each shape on new text, or real-model behaviour on the 7 new risk phrasings (worst cases were used). Re-run the live harness with --risk when model credits are available.

Also

  • An exhausted OpenAI quota (HTTP 429 insufficient_quota) fails at once instead of retrying for minutes; health shows "You have no credits remaining".
  • More clinical hedges count as hypothetical ("probably", " if ", "consider", "plan to").
  • Docs: safety guide ("Automatic or reviewed"), tutorial, configuration (VAYL_CONFIRM_MODE), glossary, troubleshooting, README. Pages whose captured output shows the approval queue now say they run with VAYL_CONFIRM_MODE=review.
  • Report: benchmarks/results/med_change_safety.md.

Behaviour changes

  • Clinical preset: clear medication changes now apply by themselves. Set VAYL_CONFIRM_MODE=review to keep approving every change.
  • In review mode, adding a second medication from an untrusted source now needs approval (the preset's promise). Trusted feeds still apply directly.

Tests: 620 passed, 25 skipped; ruff and mypy clean; clinical acceptance 52/52.

Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com

ac12644 and others added 3 commits October 22, 2026 13:49
…oval

A live evaluation (benchmarks/evaluations/med_change_safety.py) fed real
medication-change sentences through LLMMemory.add() with gpt-6-luna,
gpt-5-mini and gpt-6-sol. Before: 36-43% changed the list with no
approval, most often leaving warfarin and apixaban both active after a
switch (72-92 of 100 switch trials). After: 0 of 1,440 across 24
phrasings (11 held out), every expected change queued, holds and hedges
untouched, latency unchanged. Report: benchmarks/results/med_change_safety.md.

The gate keyed on the model's action and subject labels, which vary:
- an ADD to a gated slot that already holds a value is proposed
  (pending ADD on a list; confirm() applies it), so a switch no longer
  lands half-applied; the first value on an empty gated slot still applies
- stop language naming a current item is a removal proposal, checked
  before the event rule and the valid-time gate (ARCHIVE, kind=event,
  past-tense and invented-subject shapes were all observed)
- a future-tense order on a gated slot goes to the queue
- a change aimed at a gated fact by target_id goes through that fact's
  gate whatever subject the model named
- a sentence-level sweep proposes removals the extraction missed
- unknown actions (RETIRE) map or flag instead of raising
- anticoagulant brand names fold onto generics; pending_changes and
  confirm_change warn before two anticoagulants would be current
- slugs and drugless stop text are not proposed as medications
- more clinical hedges count as hypothetical

Behaviour change: a second medication from an untrusted source now needs
approval. The clinical acceptance records and the hospital tutorial are
updated accordingly (52/52 guarantees hold); trusted feeds still apply.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…y by themselves

A gated slot now has a mode. "review" (or true, as before) queues every
change. "auto" applies a clear change at once, marked auto_applied in
history, and queues only an uncertain one: hedged or negated text ("do
not stop the warfarin"), a future-tense order, low confidence, a guessed
action or rerouted subject, a stop found only by the sentence sweep, or a
change that would leave two anticoagulants active (held whole, so a
switch never lands half-applied). VAYL_CONFIRM_MODE overrides every slot.

The clinical preset puts active_medication on "auto" and keeps
code_status on "review". examples/clinical-slots.json (confirm: true)
still drives the review-mode tests and acceptance suite, unchanged.

Also:
- an exhausted OpenAI quota (HTTP 429 insufficient_quota) fails at once
  instead of retrying for minutes; health shows the provider's reason
- a SUPERSEDE naming no current list item is proposed as a new item
  instead of being written unreviewed
- the eval harness scores both modes (AUTO/QUEUED/PARTIAL/MISSED vs
  UNSAFE) and gains an auto-mode risk set (negation, mixed orders,
  questions)

Tested offline (tests/test_clinical_auto.py, 14 cases; 611 total pass).
The live gpt-6-luna evaluation of auto mode has not run: the OpenAI
account ran out of credits mid-run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lay harness

Replaying 2,048 recorded model outputs through the engine in "auto" mode
(benchmarks/evaluations/med_change_replay.py, no API calls) found 50
unsafe outcomes the review queue had been hiding. Fixed on gated lists,
in both modes:
- hold / continue / keep / restart values are not changes ("metformin
  500 mg - hold tonight", "Metformin held for contrast", "warfarin
  (continue)"); a held drug stays active, as FHIR on-hold
- a drug the sentence says to continue is never removed
- a negated change is dropped instead of queued
- a stop naming two drugs is not stored as one entry; the sweep queues
  each removal
- list identity ignores bracketed notes and trailing connectors, so
  "metformin to 1000 mg" stays findable as metformin; a restatement
  plus a note is a DEDUP
- "cease" counts as stop language in the sentence sweep

Replay after: 0 unsafe, 0 wrong of 2,048 in auto and review; 97.7% of
medication changes apply automatically (code status stays on review).
Engine time 0.07 ms per remember; the model call is ~2.1 s (luna).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant