Skip to content

AWS 2026-10-21

Read original ↗

Automate medical coding with Step Functions and Amazon Bedrock AgentCore

Summary

AWS shows how to automate medical coding — translating free-text clinical verbatims (e.g. the typo "migrane", or "flutters in my chest sometimes") into standardized dictionary codes (MedDRA for adverse events, WHODrug for medications) — while keeping the workflow deterministic, auditable, and human-approvable. The core design principle is a division of labor: the Step Functions state machine owns sequencing, thresholds, and the audit trail, while a bounded Bedrock AgentCore harness agent owns judgment inside a single step. Each verbatim is coded by escalating cheapest-first: a safety block-list, then case-insensitive exact/synonym matching, then an LLM agent only for the terms that need judgment. The agent reports a confidence score; a fixed confidence-gated Choice state routes ≥0.90 to autocode, 0.70–0.90 to human review, and <0.70 to open. On the sample workload of 12 verbatims, more than a third never reach the agent at all, and the execution history is a per-term audit trail of which mechanism made each decision.

Architecture

  • Orchestration: AWS Step Functions standard workflow (exactly-once executions, queryable history as a per-term audit trail). States: CaptureInput → CheckDirect → RouteDirect (Choice) → CodingAgent → ScoreThreshold (Choice) → WriteBack / MarkForReview / MarkOpen.
  • Deterministic steps: AWS Lambda functions for the direct-lookup check (block-list + exact/synonym match) and the write-back.
  • Agentic step: the AgentCore harness optimized integration, invoked from a Task state via arn:aws:states:::bedrockagentcore:invokeHarness. The harness resource declares only the model; the system prompt, tools, and allowed tool names are supplied per invocation from the task state — so the agent's full contract is readable in the workflow definition, with no container to build or operate.
  • Tools: exactly two, exposed as MCP tools through an AgentCore Gateway — search_dictionary (embeds the verbatim with Titan Text Embeddings V2 and runs a pgvector similarity search) and get_study_info (returns the study's free-text protocol description, prose only, no codes).
  • Data store: Amazon Aurora PostgreSQL-Compatible (Serverless v2) with the pgvector extension — a single store for reference data and its embeddings, so there is no separate vector store to synchronize.
  • Models: Claude Sonnet 5 (us.anthropic.claude-sonnet-5) for the agent; Amazon Titan Text Embeddings V2 (amazon.titan-embed-text-v2:0) for dictionary search — both on Amazon Bedrock.
  • Observability: AgentCore Observability records the agent's tool calls and token usage in Amazon CloudWatch.

Key takeaways

  1. The workflow owns routing; the agent owns judgment. "The agent might choose which medical code and how confident it is. It doesn't decide what happens next." A compliance reviewer can read the state machine definition and understand the execution path independent of any agent's runtime reasoning. This is the canonical shape of deterministic tool vs LLM judgment. (Source: this article)

  2. Escalate cheapest-first. Spend the cheapest, most deterministic mechanism that can code a term with confidence; escalate only when it can't. Order: (1) a terms-not-to-autocode block-list (a hard control before any model call — "a safety rule placed ahead of the model cannot be prompted away, and costs nothing to enforce"); (2) case-insensitive exact + curated-synonym match, accepted only if it resolves to exactly one distinct code; (3) the LLM agent. On the sample workload, 5 of 12 cases finish in the deterministic steps (one safety block + four autocodes at score 1.00) — more than a third never reach the agent. This is the cost face of cheap approximator with expensive fallback. (Source: this article)

  3. Route on coding confidence, not retrieval similarity. Routing on embedding distance is "measurably wrong." The typo "migrane" is textually far from "Migraine" (retrieval similarity 0.37) but the right code is obvious (agent confidence 0.97); "flutters in my chest sometimes" shares more words with "Cardiac flutter" (similarity 0.65) but is less certain (confidence 0.75). "Similarity ranks these two cases the wrong way round, and no threshold can fix that." The agent instead reports its own confidence in three bands (0.95–1.00 clear; 0.70–0.94 vague/needed study context; <0.70 nothing fits); the 0.90 autocode gate is a separate, fixed rule in the ScoreThreshold Choice state. (Source: this article)

  4. Study context as the tie-breaker a human coder would use. get_study_info exists because similarity search can return several clinically plausible candidates it can't separate on text alone. The clearest illustration: "cramps" returns an identical candidate list in two studies; only the study description differs. In an oncology study monitoring muscle spasms it coded to "Muscle spasms" (0.92, autocoded); in a reflux study it coded the same verbatim to "Abdominal pain" (0.85, review). The agent's job is to bring the right candidate to the top with a reason a coder can check. (Source: this article)

  5. Write back with a fabrication guard. Before persisting, the write-back checks the chosen code exists in the target dictionary and version — the prompt tells the agent not to invent codes, and this check enforces it, guarding against hallucinated codes copied out of free-text protocol metadata. A code that fails the check leaves the term open, with the reason recorded. Known limit: the check can't verify the code was one of the search results (the workflow only sees the agent's final response, not tool returns) — for that, inspect tool calls in AgentCore Observability. (Source: this article)

  6. Store the full family tree, not an isolated code. Write-back persists every symptom's full medical hierarchy (e.g. "muscle spasms" → "musculoskeletal and connective tissue disorders") plus a one-sentence explanation and a status_changed_by marker distinguishing machine coding from human action — so safety teams can roll up signals at any level and trace coding variance to study context rather than guessing from a confidence score. (Source: this article)

  7. Human escalation is a first-class outcome, not an error. The three terminal statuses are autocoded, approval_required, and open. Anything below the autocode gate reaches a human; the agent is instructed never to abstain (for gibberish it still returns the top candidate at low confidence, and the gate discards it). (Source: this article)

Operational numbers

  • Sample workload: 12 seeded verbatims exercising every branch; 5/12 resolve in the two deterministic steps (1 block-list, 4 exact/synonym at 1.00).
  • Retrieval vs confidence (one run, Claude Sonnet 5): migrane → Migraine, similarity 0.37 / confidence 0.97; "flutters in my chest sometimes" → Cardiac flutter, similarity 0.65 / confidence 0.75.
  • Score stability: across repeated identical runs the selected code was stable, but the confidence score moved within a band. The oncology "cramps" case scored 0.75–0.92 over six runs (autocoded twice, review four times) — "Muscle spasms" selected every time. Guidance: assert tests on the selected code, not the exact score; set the autocode threshold with margin below 0.90.
  • Harness execution cap: the InvokeHarness Task state has a hard 15-minute cap regardless of TimeoutSeconds; the two-tool single-turn design stays well under it.
  • Cost: Aurora Serverless v2 is the largest cost — kept at a 0.5 ACU minimum (billed hourly even when idle). In testing, 12 concurrent executions against a cluster scaled to zero failed 8 of 12 while still reporting SUCCEEDED, which is why the sample retains a 0.5 ACU floor and retries the resume errors.
  • Deploy: three commands (npm install && npm run build; npx cdk deploy; npm run seed). Node.js 24+, AWS CDK CLI.

Scaling guidance

To code a full study, wrap the per-verbatim workflow in a Step Functions Distributed Map state reading verbatims from an Amazon S3 manifest, and size MaxConcurrency from the model's tokens-per-minute quota divided by tokens-per-agent-run (model calls * (input + 10 * output tokens) for Sonnet 5), adjusted by the share of verbatims that reach the agent. Error paths in the sample end with the term left open; in production, route surviving errors to a Step Functions Fail state and redrive after fixing the cause.

Caveats

  • Demonstration only, not for clinical use. Validate against a known-correct dataset and measure coding precision + incorrect-autocode rate before production.
  • MedDRA and WHODrug are proprietary, licensed dictionaries (IFPMA/MSSO and Uppsala Monitoring Centre respectively); deploying against them requires a valid subscription. The sample uses generated identifiers and a de-minimis illustrative dictionary with no patient data.
  • HIPAA: Aurora and Bedrock are HIPAA-eligible only under a signed AWS BAA.
  • Retrieval is the ceiling. "If a term isn't surfaced, it can't be chosen." In the sample, "flutters in my chest sometimes" would normally be Palpitations, but vector search ranks Palpitations 19th, so the agent never sees it and selects Cardiac flutter. No prompt or study context can recover a term retrieval never returns — for production dictionaries, use hybrid retrieval (lexical + curated synonym expansion alongside vectors) and calibrate thresholds against your own verbatim distribution.
  • This isn't a pattern for open-ended conversation. If the agent must decide what happens next, a workflow engine that owns sequencing is the wrong boundary.

When the pattern fits

The article names the recognition criteria: the execution path must be reviewable independently of model reasoning; most of the workload is deterministic; only a judgment-heavy minority needs a model; and "escalate to a human" is a first-class outcome rather than an error. It generalizes beyond clinical trials to medical summaries, case narratives, registry intake, laboratory test coding, claims triage, and document classification.

Source

Last updated · 854 distilled / 2,448 read