An AI agent that handles the prior-authorization lifecycle for small specialty practices — from deciding whether a service needs a prior authorization (PA), to assembling a criteria-matched request, to auto-drafting the appeal when a payer denies.
Project status — the full loop runs, end to end, on a public URL. Phases P0 through P6 are complete and gated, plus multi-agent orchestration and the AgentCore entrypoint (P7-S1–S3): you upload a clinical note, and it becomes a criteria-matched submission packet behind a clinician approval gate; upload the payer's denial and it becomes an evidence-backed appeal behind a second one. The whole suite — 366 tests — replays offline from recorded model responses in about thirty seconds, with no API key and no network, on Linux, macOS and Windows alike. What remains is the demo video and the Devpost entry (P8-S4, P8-S5), plus an open final-improvements phase (P9). See Project status & roadmap, and STATUS.md for the live board.
- The problem
- What Attest does
- Who it's for
- How it works
- Design principles
- Tech stack
- Project status & roadmap
- Repository layout
- Getting started
- Compliance, safety & scope
- Hackathon context
- License
Prior authorization is insurer pre-approval required before a clinician can deliver a covered service. It is the largest administrative time-sink in outpatient care, and small/solo specialty practices (behavioral health, physical therapy, occupational/speech therapy, pain, imaging) absorb it directly because they have no dedicated PA staff.
- Practices complete ~39–43 PA requests per physician per week (AMA 2024/2025 surveys).
- ~13 hours/week of physician + staff time is consumed by PA.
- ~31% of physicians report requests are often or always denied — and denials have risen over five years.
- Appeals work but are under-used — many practices don't appeal because they expect to lose, and those who do rebuild each appeal from scratch.
Existing PA vendors target large health systems and deep EHR integrations. Solo and small practices are left with manual portals, faxes, and copy-paste appeal letters. The judgment-heavy part — does this need a PA? does the note satisfy the payer's criteria? how do we rebut this specific denial? — is exactly what current tooling leaves to a human. That is the gap Attest fills.
Given a patient's clinical note and insurance details, Attest:
- Reads and structures the case — service requested, diagnosis, requested duration/units, payer, and plan.
- Determines whether a prior authorization is required for that service under that payer/plan, and explains why, pointing to the policy it relied on.
- Finds the payer's own clinical criteria and checks the note against each one, marking each as met, unmet, or insufficiently supported — quoting the exact note evidence for each.
- Flags gaps — where a criterion isn't supported, it says so and asks the practice for the missing detail rather than guessing.
- Assembles a submission-ready request — completed request fields, a medical-necessity justification written only from approved evidence, and a plain-language checklist of how each criterion is covered.
- Pauses for the clinician to approve or edit every clinical assertion before anything is submitted. (Gate 1)
- Produces the outbound submission artifact once approved.
- Turns a denial into an appeal — reads the denial reason, identifies which criteria the payer contests, and drafts an appeal citing the payer's own policy language and the note evidence — again held for human approval. (Gate 2)
- Tracks each case and its appeal deadline, and reuses language from prior successful appeals on similar future denials.
The goal: cut the human time per PA from ~20–30 minutes to a short review-and-approve step, raise first-pass approval rates by mapping every submission to the payer's stated criteria before it goes out, and make appeals the default rather than the exception.
- Primary: the office manager, front-desk staff, or owner-clinician at a 1–10 provider specialty practice who personally handles PAs.
- Secondary: the treating clinician who must approve the clinical justification.
- Specialty focus: one specialty at a time, so the product speaks that specialty's language and payer rules precisely. The flagship demo case is outpatient behavioral health — TMS (transcranial magnetic stimulation) for treatment-resistant depression, whose criteria are among the most enumerable of any common PA (age, confirmed severe MDD, failed antidepressant trials at adequate dose and duration, psychotherapy trial, seizure/implant contraindications, baseline PHQ‑9/HAM‑D, FDA-cleared device). Physical therapy ships later as an extensibility pack to prove the engine is specialty-agnostic.
The engine runs autonomously between two hard human-approval gates:
The same flow as text
flowchart TD
A["Clinical note + insurance details"] --> B["Intake: structure the case"]
B --> C{"PA required?"}
C -- "unmapped" --> C1["Return UNKNOWN<br/>(never assume 'not required')"]
C -- "yes" --> D["Find the payer's clinical criteria"]
D --> E["Match each criterion to note evidence<br/>met / unmet / insufficient"]
E --> F["Deterministic evidence verifier<br/>every quote must appear verbatim in the note"]
F --> G["Gap list: ask the practice for missing detail"]
F --> H["Assemble submission packet<br/>medical-necessity justification from verified evidence only"]
H --> GATE1{{"Gate 1 · clinician approves & edits"}}
GATE1 --> I["Emit submission artifact (Markdown + PDF)"]
I --> J["Payer denies"]
J --> K["Parse denial → contested criteria"]
K --> L["Draft appeal citing the payer's own policy + verified evidence"]
L --> GATE2{{"Gate 2 · clinician approves & edits"}}
GATE2 --> M["Emit appeal artifact + track appeal deadline"]
classDef gate fill:#f6c343,stroke:#7a5901,color:#1a1a1a;
class GATE1,GATE2 gate;
Attest is built as a multi-step, tool-using agent system on the Strands Agents SDK, with specialist agents (intake, criteria matching, packet assembly, appeal drafting) composed under an orchestrator. Both approval gates are implemented on Strands' first-class human-in-the-loop primitive (BeforeToolCallEvent.interrupt(...)), so the gate holds even when the agent is driven headlessly — it is a product requirement, not UI logic.
These are non-negotiable and encoded as tests, not aspirations:
- The agent assembles and argues; humans decide and submit. It never makes a coverage or medical-necessity decision on its own. Two hard gates — before submission and before an appeal is sent — require a clinician to review and approve every clinical assertion.
- Evidence-traceable by design. No clinical claim enters any document unless a deterministic verifier confirms the quote appears verbatim in the source note (whitespace runs and Markdown emphasis markers are treated as equivalent; nothing else is). A semantically correct paraphrase is rejected on purpose — paraphrase is the hallucination failure mode being defended against. Unverifiable spans downgrade their criterion to insufficient and are logged, never silently dropped.
- Absence of a policy is not evidence that no PA is needed. An unmapped service returns
UNKNOWN, never "not required" — telling a practice "no PA needed" because a policy wasn't found is the worst possible failure. - The engine is specialty-agnostic; specialties are data. Policy packs and extraction schemas are data files; no specialty knowledge is hardcoded. Adding a specialty costs a pack, not a rewrite.
- Synthetic data only. All notes and denial letters are synthetic and banner-marked; ground-truth expected outcomes are committed before any matching code exists, which is what makes every later step objectively verifiable. Live payer-portal / EHR integration and real-PHI handling are explicitly post-hackathon.
| Area | Choice |
|---|---|
| Language | Python |
| Agent framework | strands-agents, strands-agents-tools |
| Model | Gemini via Google AI Studio — gemini-3.5-flash-lite (fast) and gemini-3.6-flash (reasoning). Amazon Bedrock is the intended provider and the swap is one constructor in src/attest/llm.py — nothing else names a provider. See DECISIONS.md. |
| Structured output | Strands structured output → Pydantic models (typed verdicts, never parsed from prose) |
| Human-in-the-loop | BeforeToolCallEvent.interrupt(...) |
| Deployment | Amazon Bedrock AgentCore Runtime (BedrockAgentCoreApp + @app.entrypoint) — planned, P7 |
| UI | Streamlit, hosted on Streamlit Community Cloud for a free, public, judge-testable link |
| Documents | fpdf2 for the submission and appeal PDFs |
| Validation / data | pydantic, pyyaml |
| Testing | pytest, with every build step gated behind a cumulative test marker |
Work is organized into small, individually verifiable steps. A step is done only when its gate (./scripts/verify.sh <STEP_ID>) exits zero — running that step's tests plus every prior step's, so a later step cannot silently break an earlier one. The authoritative Definitions of Done live in PLAN.md; live progress lives in STATUS.md.
| Phase | Focus | State |
|---|---|---|
| P0 | Foundation, model provider access, engineering protocol, repo skeleton | ✅ done |
| P1 | Domain models, policy-pack format, real public TMS policies, synthetic corpus + ground truth | ✅ done |
| P2 | Intake (note → structured case) and PA-required determination | ✅ done |
| P3 ▲ | Criteria engine — per-criterion evidence matching + the deterministic verifier (the core) | ✅ done |
| P4 ▲ | Packet assembly & Gate 1 (approval before submission) | ✅ done |
| P5 ▲ | Denial → appeal loop & Gate 2 (approval before appeal) | ✅ done |
| P6 ▲ | Case tracking, precedent reuse, Streamlit UI, public deploy | ✅ done |
| — | Submittable product complete through here | |
| P7 | Multi-agent orchestration depth + AgentCore deployment | 🟡 S1–S3 done; S4 blocked on AWS Bedrock authorization |
| P8 | Submission deliverables — README, architecture diagram, metrics, demo video, Devpost | 🟡 S1–S2 done; S4 (video) and S5 (Devpost) outstanding |
| P9 | Final improvements — upload-driven intake, a keyless green gate, an error boundary, criteria that say what they mean (S1–S3, S8 done); phase deliberately open | 🟡 in progress |
▲ = required for a viable submission.
The submission and appeal loops both run end to end, and every number below is reproducible from a clean clone with no credentials:
| Measure | Result |
|---|---|
| Criterion verdicts vs. committed ground truth | 30/30 across three cases and two payers |
| Evidence spans verifying verbatim against their note | 39/39 (100%), with zero criteria downgraded |
| Gap list vs. ground truth | exact on all three cases |
| Contested criteria parsed from the denial letter | exact — plus the one objection that maps to no criterion, surfaced rather than dropped |
| An uploaded note reaching its own payer's pack | derived from the extracted payer/plan/CPT — an unlisted payer stops the review rather than guessing |
./scripts/verify.sh ALL --offline |
exits zero, 366 tests, no API key, on Linux, macOS and Windows |
The same command runs in CI on every push, on Linux, with no credentials configured — the judge's scenario rather than ours.
Alongside the engine, the repository holds the product definition and the engineering protocol that lets two contributors hand work off cleanly across sessions and machines:
| File | Purpose |
|---|---|
Attest-PRODUCT.md |
The product specification — what it is, what it must achieve, and the rules it must satisfy. No architecture or build planning. |
PLAN.md |
The contract: every phase, step, and machine-checkable Definition of Done. Changes rarely. |
STATUS.md |
The live status board — one row per step; the single source of truth for progress. |
DECISIONS.md |
Append-only decision log — every non-obvious choice and the reasoning behind it. |
LICENSE |
Apache License 2.0. |
src/attest/ |
The engine: domain models, policy-pack loader, intake, criteria matching, the evidence verifier, and the gap list. |
data/synthetic/ |
Synthetic notes and denial letters, plus ground truth committed before the matching code existed. |
cassettes/ |
Recorded model responses, so the whole suite replays offline with no API key and no quota. |
scripts/verify.sh |
The step gate. Runs a step's tests plus every prior step's. |
src/attest/demo.py |
python -m attest.demo — one case end to end in a terminal, stopping at Gate 1. No browser, no key. |
docs/architecture.svg |
The agent graph, the verifier, both gates, and where it runs. |
docs/setup.md |
Developer setup, model provider, and the daily-quota notes. |
docs/uiux-review.md |
A walkthrough of the console judged against the scoring criteria, and the P9 steps drawn from it. |
All of the above has landed, along with src/attest/packet/, src/attest/appeal/, the case store, the orchestrator, and app.py — the reviewer console, which takes its case as an uploaded document and reaches nothing preloaded.
This is a two-person, sequential build (one contributor works until their usage limit, then the other resumes on another machine). Because context is lost at every handoff, the three committed files above — plan, status, decisions — are the shared memory. A session starting cold reads STATUS.md → DECISIONS.md → PLAN.md, runs the gate for the last step marked done to verify (not trust) the previous session's claim, then continues.
https://attest.streamlit.app — the live console, public and free, no account and no API key.
Nothing is preloaded. Upload a clinical note in the sidebar and walk it: Attest reads the note, works out from the note itself which payer's policy applies, checks the record against that policy criterion by criterion, shows each verdict with the exact quote behind it, and stops at Gate 1. Upload the payer's denial letter — a second document, as it is in a practice — and it drafts the appeal, stopping again at Gate 2.
No note of your own? The landing screen offers the synthetic corpus as downloads. Upload
clean.md for a fully documented case, gap.md to see the agent ask the practice for what is
missing, or denial.md plus denial_001.md for the full denial-to-appeal loop. Uploading
denial.md after clean.md is the thing worth watching: the same screen, unchanged, reaches a
different payer's criteria, because the pack is derived from what was read rather than chosen
from a menu.
Every model call replays from committed cassettes, so the hosted app needs no credentials and shows the same results this repository reproduces offline — provided you upload the committed files unmodified, since the cassette key is a hash of the note text.
The hosted container is ephemeral — Streamlit Community Cloud restarts it when the app sleeps, so stored cases do not persist across days and precedent reuse demonstrates within a session. That is a property of the free hosting tier, not of the case store, which is durable and proven across real processes by
test_case_survives_process_restart.
The engine, the reviewer console, and the full test suite all run today, offline, with no API key and no network — every model call replays from a recorded cassette. Once installed, streamlit run app.py opens the console locally; upload a note from data/synthetic/notes/ to walk a case.
git clone https://github.com/ojassug/Attest.git
cd AttestPython 3.12 specifically — 3.13+ is too new for the Strands dependency tree. CPython's venv writes executables to bin/ on POSIX and Scripts/ on Windows:
# macOS / Linux
python3.12 -m venv .venv && .venv/bin/pip install -e ".[dev]"# Windows
py -3.12 -m venv .venv && .venv/Scripts/python.exe -m pip install -e ".[dev]"See it work in about ten seconds, with no browser and no key:
python -m attest.demo --case cleanIt reads a synthetic note, works out which payer's policy applies, checks the record criterion by
criterion, prints the evidence counts and the gap list, and stops at Gate 1 with the content
hash a clinician would approve. It writes nothing — there is no --approve flag, because approval
is a person reading the assertions. Try --case gap to watch it ask the practice for what is
missing, --case denial for a different payer's criteria entirely, or --case pt for a different
specialty. --note path/to/note.md reads your own.
Then run the full gate. It needs nothing else — no key, no network:
./scripts/verify.sh ALL --offlineExpect 366 passed, 5 deselected. The five are the provider-connectivity tests, which cannot be
replayed from a cassette; --offline deselects them and that is a valid pass. The same command
runs in CI on every push, on a clean Linux machine with no credentials configured — the judge's
scenario rather than ours.
And to use the console locally:
streamlit run app.pyTo re-record cassettes or run the three provider-connectivity tests you need a free Google AI Studio key in .env. Note the free tier allows 20 requests per day per model — see docs/setup.md for the model roster, rotation, and why cassettes exist. Amazon Bedrock is a one-constructor swap in src/attest/llm.py; the AgentCore entrypoint is written and verified, and deployment is blocked on Bedrock model access rather than on code — see STATUS.md.
- Privacy-first. The product is designed around encryption in transit and at rest, least-privilege access, and a complete audit trail of every agent action and every human approval.
- Synthetic data only for the hackathon. No real patient data is used in the build or the demo video. Live payer-portal / EHR integration and real-PHI handling are explicitly post-hackathon, gated on the appropriate agreements.
- Honest scope. Attest is an assistant that produces submission-ready and appeal-ready documents for human approval — not an autonomous authority over care decisions.
Explicitly out of scope: it is not an EHR, practice-management, or billing/clearinghouse system; not a coverage or medical decision-maker; not a general healthcare chatbot; and it does no live payer/EHR integration or real-PHI handling in the hackathon build.
Attest is being built for the AWS "Agents for Humans" hackathon (sponsored by Amazon Web Services, administered by Devpost), in the Professional Agents track. The submission window runs Aug 10 – Sep 14, 2026. The hackathon requires a newly built agent on the Strands Agents SDK that does real work end to end; deploying with Amazon Bedrock AgentCore is optional but strengthens the technical-implementation score. Judging weighs Technical Implementation, Design, Potential Impact, Creativity & Originality, and Presentation equally.
Licensed under the Apache License 2.0.
"Attest" is a working name and may change.