Skip to main content
Glama

RxRelay

A voice agent that has to prove it helped.

Consent-first voice coordination for prescription access.
A case can close only when consent ∧ action ∧ counterpart outcome ∧ patient update are on the record — not when an LLM says “done.”

Website Pitch Deck License: MIT Node ≥20 Runtime deps

CI Tests PAVO Live telephony

Website · Judge demo · Pitch deck · PPTX · Quickstart · Proof gate · Paper


✅ Product completeness (v0.2)

Everything judges need for demo + evaluation is shipped and tested.

Surface

Status

Consent + deterministic 4/4 proof gate

Works (tested)

Pharmacy→clinic→ready→SMS proof path

Works (sandbox E2E; voice + dashboard)

Inbound TeXML voice (voice-server.mjs)

Works (isolated blast radius)

PAVO demand routing + safe-stop

Works; verified turns upgrade speech+DTMF + strong model

Signed hash-chained proof receipts

Works (/api/cases/:id/receipt)

Counterpart attestation (pharmacy/clinic)

Works (/attest/:token — attestation seam without EHR)

Human ops queue + resume + timeout scan

Works

Live SSE proof stream

Works (/api/events)

MCP tools (8)

Works

Marketing site + 13-slide HTML/PPTX deck

Shipped

Outbound SMS + coordination call adapters

Works — sandbox always; live carrier path is intentionally fail-closed until OTP allowlist + ALLOW_LIVE_TELEPHONY / call-action URL (safety, not a missing feature)

Related MCP server: BRAINS MCP Server

📖 Table of contents

Demo number (live inbound): +1 (802) 676-8127 · full judge script: docs/JUDGE_DEMO.md


⚡ Why this exists

A prescription can be clinically approved and still be unreachable. Something stalls — prior auth, stock, a missing form — and the patient becomes the switchboard:

call pharmacy → call clinic → call insurer → repeat context → still no trustworthy answer.

Voice agents are an obvious fit for that loop. The failure mode is subtler:

An agent that says “I’ve taken care of it” and an agent that actually coordinated something look identical at the transcript layer.

In medication access, that gap is the whole risk.

RxRelay closes the gap by refusing to close a case it cannot substantiate.

Generic voice agent

RxRelay

“I’ll take care of it.”

“Here is the evidence I can prove.”

One inference path for every turn

PAVO-style routing across ASR and reasoning

Conversation ends ⇒ task done

Case stays open until the proof gate is satisfied

Treats every request as automatable

Hard-stops clinical advice, Rx changes, emergency cues, controlled-inventory questions

Failed API call still narrates success

Failed provider call records no action evidence


🔐 The proof gate

This is the core idea, and it is deliberately boring: an LLM never decides that a case is resolved. A pure function over recorded state does.

// src/store.mjs — the close gate is not generative
function resolutionProof(caseRecord) {
  const checks = [
    { id: "consent",      label: "Explicit consent recorded",               passed: caseRecord.evidence.consentRecorded },
    { id: "action",       label: "Permitted coordination action completed", passed: caseRecord.evidence.permittedActionCompleted },
    { id: "outcome",      label: "Counterpart outcome recorded",            passed: caseRecord.evidence.counterpartOutcomeRecorded },
    { id: "notification", label: "Patient notification sent",               passed: caseRecord.evidence.patientNotificationSent },
  ];
  return { checks, ready: checks.every((check) => check.passed) };
}
 consent  ∧  permitted action  ∧  counterpart outcome  ∧  patient update
 ───────     ────────────────     ──────────────────     ──────────────
 recorded    provider-accepted    pharmacy/clinic fact   consented SMS
                 (or sandbox)
                              │
                              ▼
                     Resolution verified
                   (every other state stays open)

Property

How it is enforced

No action before consent

requireConsent() throws on every coordination and messaging path

No fabricated completions

Evidence flags are set by state transitions, never by model output

Failure is visible

A rejected provider call leaves permittedActionCompleted === false

Partial progress stays open

Clinic submission ≠ resolved Rx; pharmacy confirmation is still required

There is a dedicated honesty test — a provider that throws must not manufacture action evidence:

test("failed outbound coordination cannot create a false action proof", async () => {
  const failingTelephony = { placeCoordinationCall: async () => { throw new Error("Provider unavailable"); } };
  const store = new CaseStore({ telephony: failingTelephony });
  await assert.rejects(() => store.beginCoordination("RX-1048"), /Provider unavailable/);
  assert.equal(store.get("RX-1048").evidence.permittedActionCompleted, false);
});

🖥️ Screenshots

Proof board

Architecture

PAVO routing

Deterministic close gate

End-to-end system topology

Demand-conditioned pipelines

Problem framing

Live demo flow

Safety contract

Full narrative deck: HTML · PPTX (npm run deck)


🏗 Architecture

Two processes. One shared case file. The public tunnel only ever touches the TeXML voice gateway — never the dashboard or MCP surface.

  Caller (consented)
        │
        ▼
 ┌──────────────────────────────────────────┐
 │  Cloudflare quick tunnel                 │
 │  (scripts/live-inbound.mjs)              │
 └────────────────────┬─────────────────────┘
                      │ TeXML only
                      ▼
 ┌──────────────────────────────────────────┐     ┌──────────────────────────────────────────┐
 │  voice-server.mjs :3001                  │     │  server.mjs :3000                         │
 │  /voice  /voice/turn  /health            │     │  proof board · /api/cases · /mcp          │
 │  token-gated · no dashboard · no MCP     │     │  webhook seam · demo lab                  │
 └────────────────────┬─────────────────────┘     └────────────────────┬─────────────────────┘
                      │                                                │
                      └──────────────────┬─────────────────────────────┘
                                         ▼
                           ┌─────────────────────────┐
                           │  shared CaseStore       │
                           │  persist → data/cases.json
                           └───────────┬─────────────┘
                                       │
              ┌────────────────────────┼────────────────────────┐
              ▼                        ▼                        ▼
        pavo.mjs                 inference.mjs            telephony.mjs
   demand-conditioned         OpenAI Responses           sandbox | fail-closed
        routing                 + local fallback              live adapter

Deep dive: docs/ARCHITECTURE.md · full diagram: docs/ARCHITECTURE_DIAGRAM.md · pitch architecture slide in assets/architecture.png.


🚀 Quickstart

git clone https://github.com/vnmoorthy/rxrelay.git
cd rxrelay
cp .env.example .env
npm test        # 27 tests · node:test · no install step
npm run deck    # rebuild PPTX → deck/output/… ; HTML at deck/pitch.html
npm run dev     # http://localhost:3000

There is nothing to npm install. The sandbox demo has zero runtime dependencies — Node 20+ provides the HTTP server, test runner, --env-file-if-exists, and fetch.

Default mode is TELEPHONY_PROVIDER=demo with ALLOW_LIVE_TELEPHONY=false, so the entire flow runs without dialing or texting a real person.

Pitch deck

Demo in 100 seconds

  1. Open RX-1048 (consent already recorded).

  2. Call pharmacy → sandbox coordination action.

  3. Record blocker → prior authorization needed.

  4. Record clinic step → follow-up submitted.

  5. Confirm readiness → pharmacy outcome + consented sandbox SMS.

  6. Watch the close gate turn green only at 4/4.

  7. Try an uncertain / unsafe turn in the PAVO lab — upgrade the pipeline or safe-stop; never invent completion.

Full script (dashboard sandbox): docs/DEMO.md · judge live call: docs/JUDGE_DEMO.md (+18026768127)


📞 Live inbound voice

voice-server.mjs is a deliberately isolated TeXML gateway. It shares cases with the dashboard through data/cases.json.

npm run dev            # proof board on :3000
npm run live:inbound   # voice :3001 → public tunnel → point claimed number

live:inbound tries Cloudflare quick tunnel first, then falls back to Serveo (ssh -R … serveo.net) when Cloudflare returns 429/1015. Override with VOICE_TUNNEL=serveo or reuse an existing URL via TUNNEL_PUBLIC_URL=https://….

Then call the claimed number and say:

I consent to a pharmacy status follow-up and text updates.

IMPORTANT

Inbound voice ≠ outbound messaging. Live SMS/calls stay disabled until ALLOW_LIVE_TELEPHONY=true and LIVE_ALLOWED_RECIPIENTS contains OTP-verified numbers. The live adapter refuses completion without a provider-issued action id.

OTP helpers:

npm run verify -- +1XXXXXXXXXX
npm run confirm -- +1XXXXXXXXXX 123456

Details: docs/A1MOBILE_LIVE_SETUP.md

Optional LiveKit + OpenAI Realtime (post-hackathon)

Tonight's demo stays on TeXML. ChatGPT Realtime needs media streaming (WebRTC/WebSocket), not TeXML <Gather> turn-taking. The a1 PAVO gateway (hack.a1mobile.com/gw/v1) exposes chat models only (sol / terra / luna) — /realtime returns 404 — so Realtime needs a direct OPENAI_API_KEY, plus LiveKit Cloud, plus switching the claimed number from webhook → SIP using creds from GET /api/numbers/me.

Twilio is a different carrier and is not on the a1mobile claimed DID without leaving hackathon rails. Photon Spectrum (@photon-ai/voice-ts) is a real product (messaging + voice over Photon's gRPC plane) but needs a Photon VOICE_TOKEN and does not answer +18026768127 faster than TeXML tonight.

Scaffold (fails closed until keys exist; does not remove TeXML):

npm run voice:realtime   # checks LIVEKIT_* + OPENAI_API_KEY + A1_SIP_*

🔬 PAVO: route the pipeline, not just the model

Grounded in PAVO: Pipeline-Aware Voice Orchestration with Demand-Conditioned Inference Routing.

A better LLM cannot repair a misheard authorization number.

When a turn is uncertain or carries a critical entity, RxRelay upgrades transcription and reasoning together.

Route

Triggered by

Pipeline

Fast

greetings, simple confirmations

fast ASR → compact reasoning

Balanced

routine status coordination

reliable ASR → tool-aware reasoning

Verified

noise, low ASR confidence, names/numbers/dates, prior auth, contradiction

high-accuracy ASR → structured verifier

Safe stop

clinical advice, emergency cues, Rx changes, controlled inventory, identity data

no autonomous action → human handoff

Safe stop is checked first. The router is a readable pure function in src/pavo.mjs.

Research: paper · pavo-bench


🛡 Safety contract

RxRelay does not:

  • give medical advice or interpret symptoms

  • prescribe, change, refill, or transfer a prescription

  • determine insurance coverage or eligibility

  • disclose controlled-medication inventory

  • contact anyone without explicit, scope-limited consent

Urgent medical cues are a handoff, not an automation opportunity. Voice consent requires both a consent phrase and a scope term (pharmacy / status / coordinate / text / update).

Live mode is a configuration decision, never a code-path accident.


🔌 MCP tools

POST /mcp exposes JSON-RPC tools that share the same consent + proof gate as the UI:

Tool

Purpose

create_rx_case

Create a consent-gated coordination case

record_consent

Record explicit patient consent

begin_coordination_call

Start a non-clinical pharmacy status call

record_external_outcome

Record pharmacy_blocker · clinic_submission · pharmacy_ready

issue_counterpart_link

Issue a single-use pharmacy/clinic/insurer attestation link

export_proof_receipt

Export a signed hash-chained proof receipt

get_case_brief

Return status + deterministic resolution proof

list_human_queue

List cases held for human review

No tool can bypass the proof gate.


🥊 How it compares

RxRelay

Typical voice agent demo

Human switchboard

Completion claim

Deterministic proof gate

Conversational “done”

Memory / sticky notes

Uncertain audio

Upgrade ASR and reasoning (PAVO)

Hope the LLM repairs it

Ask the patient to repeat

Clinical / emergency language

Safe stop → human

Often continues

Escalates unevenly

Failed provider call

No action evidence recorded

Often narrates success

Unknown

Outbound contact

OTP allowlist + consent

Frequently unconstrained

Manual

Public blast radius

Voice-only process

Full app exposed

N/A


🏆 Built for the a1mobile Voice AI Hackathon 2026

Criterion

Evidence

Idea & creativity

Moves voice agents from talking → evidence-backed access coordination

Real-world value

Removes patient-as-switchboard work in prescription access

Technical execution

Case state machine, PAVO routing, TeXML gateway, counterpart portal, signed receipts, human ops, SSE, MCP (8), proof gate, CI + 27 tests

Voice UX

Voice-first consent, confirmation on uncertain critical details, explicit safe stops

Works live

Sandbox E2E today; real inbound TeXML path; live outbound fails closed until provider accepts + returns an id


Research

Systems by the same author


📁 Repository map

src/pavo.mjs              PAVO-inspired demand-conditioned routing (pure function)
src/inference.mjs         Guarded OpenAI-compatible client + local fallback
src/dialogue.mjs          Phone turn shaping, ASR repairs, TeXML Say helpers
src/voice-lexicon.mjs     Consent / intent paraphrase expansion
src/voice-training/       Mined lexicon + few-shot exemplars for Maya
src/store.mjs             Consent-gated case state machine + proof gate
src/persist.mjs           Shared local JSON store for dashboard + voice
src/telephony.mjs         Sandbox adapter + fail-closed live provider adapter
src/receipt.mjs           Signed hash-chained proof receipts
src/counterpart.mjs       Magic-link attestation tokens
src/bus.mjs               Case event bus for live SSE
server.mjs                HTTP API, webhook seam, MCP endpoint, proof board
voice-server.mjs          Token-protected TeXML inbound gateway
scripts/                  live:inbound · point · verify · confirm
public/                   Proof-board dashboard
site/                     Marketing site → GitHub Pages
assets/                   Social preview + pitch visuals for the README
docs/                     Architecture, live setup, DEMO, JUDGE_DEMO
deck/                     13-slide HTML + PPTX (`npm run deck`)
test/                     node:test suite (27) — routing, consent, proof honesty

🤝 Contributing

See CONTRIBUTING.md and the CODE_OF_CONDUCT.md.

npm run check
npm test

Security reports: SECURITY.md — especially anything that could fabricate proof or skip consent.

License

MIT

A
license - permissive license
-
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • A
    license
    -
    quality
    B
    maintenance
    Provides MCP tools for lead qualification, enabling evidence gathering from CRM, scoring, and knowledge base with role-based access and deterministic decision gating.
    Last updated
    AGPL 3.0
  • A
    license
    A
    quality
    D
    maintenance
    Enables HIPAA-aware healthcare workflow automation including patient intake, clinical summaries, compliance checking, and appointment scheduling via MCP tools.
    Last updated
    4
    97
    1
    Business Source 1.1

View all related MCP servers

Related MCP Connectors

  • Remote MCP for MCP consent scope receipt, structured receipts, audit logs, and reviewer-ready eviden

  • Phone, SMS & email for AI agents — one remote MCP endpoint, OAuth login, zero install.

  • A paid remote MCP for Skybridge, built to return verdicts, receipts, usage logs, and audit-ready JSO

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/vnmoorthy/rxrelay'

If you have feedback or need assistance with the MCP directory API, please join our Discord server