RxRelay

RxRelay

Enables consent-first voice coordination for prescription access with a deterministic proof gate, exposing MCP tools to manage cases, attestations, and evidence.

Category
访问服务器

README

<div align="center">

<img src="assets/social-preview.png" alt="RxRelay — Make the calls. Bring the proof." width="100%" />

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

</div>


✅ 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)

📖 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
<img src="assets/proof-board.png" alt="Proof board" width="100%" /> <img src="assets/architecture.png" alt="Architecture" width="100%" /> <img src="assets/pavo-routing.png" alt="PAVO routing" width="100%" />
Deterministic close gate End-to-end system topology Demand-conditioned pipelines
Problem framing Live demo flow Safety contract
<img src="assets/problem.png" alt="Problem" width="100%" /> <img src="assets/demo-flow.png" alt="Demo flow" width="100%" /> <img src="assets/safety.png" alt="Safety" width="100%" />

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.

<img src="assets/architecture.png" alt="RxRelay detailed architecture diagram" width="100%" />


🚀 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

🧠 Related work

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

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选