# Project export: CareLoop

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: UC Berkeley AI Hackathon 2026
- Tagline: CareLoop is a voice-first healthcare concierge that triages urgency, finds nearby providers, estimates cost, and helps book care from one calm conversation.
- Devpost: https://devpost.com/software/careloop-gsiy8w
- GitHub: https://github.com/VishalDani1602/AIHACK26.git
- Result: winner (Best Use of The Agentverse by Fetch AI)
- Team: 3 GitHub contributor(s) — Claude Opus 4.8 (28 commits), VishalDani1602 (28 commits), paria03 (14 commits)

## Devpost submission (written by the team)

### Inspiration

CareLoop was inspired by how stressful healthcare navigation can be. Even when someone knows they need care, they still have to figure out urgency, insurance, cost, providers, and booking, often while anxious or helping a loved one.

### What it does

CareLoop lets users describe a health concern by voice or text. It triages urgency, routes emergencies to 911, finds nearby providers, estimates out-of-pocket cost, and helps guide appointment booking in one conversation.

### How we built it

We built CareLoop with Fetch.ai uAgents, ASI:One, Deepgram voice, FastAPI, Redis, Stripe, and CMS NPPES provider data. A main orchestrator agent coordinates specialist agents for triage, provider search, cost estimation, scheduling, and payment.

### Challenges we ran into

The hardest part was making many moving pieces feel simple: voice input, AI reasoning, agent messaging, provider data, payments, and booking. We also had to build strong safety guardrails so emergency symptoms are handled immediately.

### Accomplishments we're proud of

We are proud that CareLoop feels like a real healthcare access workflow, not just a chatbot. It combines voice, agents, real provider data, cost estimates, booking flow, fallback logic, and emergency routing into one usable demo.

### What we learned

We learned that healthcare AI needs structure as much as intelligence. Clear agent roles, state management, safety rules, and honest limitations are what make the experience trustworthy.

### What's next

Next, we would integrate live insurance eligibility, real appointment availability, provider booking APIs, multilingual caregiver workflows, and stronger clinical review so CareLoop can move from demo to dependable care navigation.

## README (from the GitHub repository)

# CareLoop — a voice-first, multi-agent healthcare-access concierge

> Speak a health problem in plain language and CareLoop's team of AI agents
> **triages urgency → finds real in-network providers → estimates your
> out-of-pocket cost → books an appointment** — then reads the confirmation
> back to you by voice.

Built for the **UC Berkeley AI Hackathon 2026**: **Fetch.ai** (uAgents + ASI:One)
multi-agent orchestration, **Anthropic Claude** clinical triage, a **Deepgram** voice
layer, **Redis** caching/state, and **Stripe** payments. Developed with **Claude Code**.

⚠️ **CareLoop is a navigation aid, not a medical provider.** It does not diagnose.
Red-flag symptoms (chest pain, stroke signs, trouble breathing, etc.) are routed
straight to **911** and never to a booking.

---

## Why it matters
The hardest part of healthcare for many people — elderly patients, caregivers,
non-native English speakers, anyone in distress — isn't the medicine, it's the
**navigation**: *Who do I see? Is it urgent? Who's nearby and takes my insurance?
What will it cost? How do I book it?* CareLoop collapses that maze into one calm
conversation you can have **by voice, in your own language**.

## How it works (architecture)

```
              ┌──────────────── ASI:One chat ────────────────┐    🎙 Browser voice app + 📊 dashboard
              │   (Agent Chat Protocol — no frontend needed)  │       (Deepgram STT/TTS, interactive cards)
              └───────────────────────┬──────────────────────┘                 │  POST /voice (REST)
                            ChatMessage │                                       │
                                        ▼                                       ▼
   ┌─────────────────────────────  Orchestrator Agent  ──────────────────────────────┐
   │  ASI:One LLM: parse intent, manage the conversation, compose replies + cards      │
   └──┬──────────┬──────────┬──────────┬───────────┬──────────┬───────────────────────┘
      │ send_and_receive (agent-to-agent messaging via Agentverse)
      ▼          ▼          ▼          ▼           ▼          ▼
  ┌────────┐┌──────────┐┌────────┐┌──────────┐┌────────┐┌────────────┐
  │ Triage ││ Provider ││  Cost  ││ Scheduler││Payment ││  Evidence  │
  │  911   ││CMS NPPES ││  est.  ││  .ics +  ││ Stripe ││  trials +  │
  │ rules  ││          ││        ││ confirm  ││ deposit││ drug safety│
  └────────┘└──────────┘└────────┘└──────────┘└────────┘└────────────┘
```

- **7 agents**, each registered on **Agentverse** with its own mailbox + profile.
- The **Orchestrator** speaks the **Agent Chat Protocol**, so the *entire workflow
  runs from ASI:One with no custom frontend* — then the Deepgram web app layers a
  natural voice experience (and interactive cards) on top of the same agent.
- **Real data:** providers from **CMS NPPES**, recruiting trials from
  **ClinicalTrials.gov**, drug-safety from **openFDA**.
- **Takes action:** for serious/chronic conditions it surfaces relevant **clinical
  trials + drug-safety notes**, and it completes a real (test-mode) **Stripe deposit**
  before booking.
- **Resilient:** every agent-to-agent call falls back to in-process logic, so a
  single agent hiccup never breaks the demo.
- **Redis** is shared infrastructure across all agent processes: a provider/triage/
  trials cache (huge repeat-latency savings), a session store, a **Streams audit
  trail**, and live stat counters — all degrading gracefully if Redis is offline.

## The agents

| Agent | Address (derived from seed) | Role |
|-------|------------------------------|------|
| Orchestrator | `agent1qfxka5afzlk2wsp3agw77l7gz426d9vymwr9l4cfhy2gdvvhq2h8kn798uc` | ASI:One-facing brain; chat protocol + voice REST |
| Triage | `agent1q29ewatrgfnskvkk020y3p2n7qkl36xw293v4x74kpy49czdjqxrqxl9xpe` | **Claude-powered** urgency + specialty reasoning; hard 911 red-flag rules |
| Provider-Finder | `agent1qdjaj2ctxsjs3vpt5j7uxk46vq9znrquhn9q8fkljqhjueqrr93nzwj3whq` | Real providers from CMS NPPES (filtered to the requested city/state) |
| Cost-Estimator | `agent1qtrge69qnynvpfwd7duw9pgjeffsmmlemflyz06v6v4r2yndtvcwq5k4sd6` | Plan-, provider- & region-aware out-of-pocket estimate |
| Scheduler | `agent1q0d726utvqsgjmctt4etsclcapk9tvx75t7tlrcp0luyvsecyr7yz9ujgyf` | Confirmation + iCalendar invite |
| Payment | `agent1qdr7s04hzndeefr2tt085nt29q8jxklf8hne9yhchn63huey9eurczs5ux5` | Stripe Checkout deposit + server-side verify |
| Evidence | `agent1qt2nusghdan489nh92w37ax33gz8yxfuklezkph029eyju5dyt7vv7m2eag` | Recruiting clinical trials (ClinicalTrials.gov) + drug safety (openFDA) |

Profile URL pattern: `https://agentverse.ai/agents/details/<address>/profile`
(run `./venv/bin/python -m scripts.print_addresses` to reprint).

## Quick start

```bash
# 1. Install
python3 -m venv venv && ./venv/bin/pip install -r requirements.txt

# 2. Configure keys
cp .env.example .env   # fill in ASI1_API_KEY, AGENTVERSE_API_KEY, DEEPGRAM_API_KEY
                       # optional: STRIPE_SECRET_KEY (deposit), REDIS_URL

# 3. One command: starts Redis + all 7 agents + voice app, registers, runs a full demo
./scripts/demo.sh
#    -> then open the voice app at http://127.0.0.1:8080
#       and the live dashboard at http://127.0.0.1:8080/dashboard
```

<details><summary>…or run each step manually</summary>

```bash
./venv/bin/python -m scripts.selftest          # sanity-check the pipeline (no keys/network needed)
redis-server --daemonize yes                   # or: docker run -d --name careloop-redis -p 6379:6379 redis:7-alpine
./scripts/run_all.sh                           # start all 7 agents (each in its own process + mailbox)
./venv/bin/python -m scripts.register_agents   # auto-register every mailbox on Agentverse (no browser clicks)
./venv/bin/uvicorn voice.backend:app --port 8080
```
</details>

ASI:One promo code: `BERKELEYAI` · Agentverse promo code: `BERKELEYAIAV`

## Try it

**Golden path** (books a real provider):
```
my dad has had a bad cough and a low fever for five days,
he's on Medicare and we're in Berkeley
```
→ "…I'd start with Primary Care … the best match near Berkeley is **Allison Aiken, M.D.**
at 2222 Bancroft Way with an opening in 2 days … with a **provider- and plan-specific
out-of-pocket estimate** … want me to book it?" → *"yes"* → **pay a refundable $25
deposit** via a real Stripe
Checkout link (test card `4242 4242 4242 4242`) → *"done"* → agent verifies payment →
✅ booked + calendar invite.

> If `STRIPE_SECRET_KEY` is not set, CareLoop skips the deposit and books directly.

**Clinical-evidence path** (serious/chronic condition → real recruiting trials + drug safety):
```
my mom was diagnosed with early-stage breast cancer,
she's in San Francisco on Medicare and takes tamoxifen
```
→ routes to **Oncology**, names a real SF oncologist, and surfaces **recruiting
clinical trials** near her (ClinicalTrials.gov) plus a **tamoxifen drug-interaction
note** (openFDA) — options to discuss with her doctor.

**Emergency path** (routes to 911, no booking):
```
I'm having crushing chest pain and I can't breathe
```

## Tech
- **Fetch.ai uAgents** — multi-agent framework, Agentverse mailboxes, Almanac discovery
- **Agent Chat Protocol** — ASI:One discoverability & chat
- **ASI:One** (`asi1-mini`) — orchestrator intent parsing + reply composition
- **Anthropic Claude** (`claude-sonnet-4-6`) — triage clinical reasoning (falls back to ASI:One)
- **Deepgram** — Nova-3 STT (multilingual) + Aura-2 TTS
- **Stripe** — real (test-mode) Checkout for a refundable booking deposit, verified server-side
- **Redis** (beyond caching) — shared provider/triage/trials cache (91×–853× faster on repeats),
  session store, **Streams audit trail** of every clinical/payment/booking decision, live stat counters
- **CMS NPPES** — real provider data · **ClinicalTrials.gov** — recruiting trials · **openFDA** — drug safety
- **FastAPI** — voice web app + interactive cards + live **/dashboard**

## Deliverables
- 🔗 ASI:One shared chat: _add link here_
- 🤖 Agentve

[README truncated for size]

## Detected evidence (automated analysis)

Indexed codebase: 46 recognized source files, 225 KB.
- Anthropic (technology) — detected in the code
- CSS (language) — detected in the code
- FastAPI (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- OpenAI (technology) — detected in the code
- Python (language) — detected in the code
- Redis (technology) — detected in the code
- AI coding agent: Claude Code — evidence: config files committed to the repository; commit authorship or trailers

## Codebase structure (from repository index)

### Files (52 of 52)

```
.claude/scheduled_tasks.lock
.env.example
.gitignore
agents/__init__.py
agents/common/__init__.py
agents/common/asi.py
agents/common/claude_llm.py
agents/common/clinical.py
agents/common/config.py
agents/common/logic.py
agents/common/models.py
agents/common/nppes.py
agents/common/orchestration.py
agents/common/prompts.py
agents/common/store.py
agents/common/stripe_pay.py
agents/cost.py
agents/evidence.py
agents/orchestrator.py
agents/payment.py
agents/provider_finder.py
agents/readmes/cost.md
agents/readmes/evidence.md
agents/readmes/orchestrator.md
agents/readmes/payment.md
agents/readmes/provider_finder.md
agents/readmes/scheduler.md
agents/readmes/triage.md
agents/scheduler.py
agents/triage.py
data/cost_table.json
data/taxonomy_map.json
DEVPOST.md
docs/UIUX_PLAN.md
PRESENTATION.md
README.md
requirements.txt
scripts/__init__.py
scripts/chat_client.py
scripts/demo.sh
scripts/print_addresses.py
scripts/register_agents.py
scripts/run_all.sh
scripts/selftest.py
scripts/showcase.py
voice/__init__.py
voice/backend.py
voice/static/app.js
voice/static/dashboard.html
voice/static/dashboard.js
voice/static/index.html
voice/static/style.css
```

### Dependencies

- requirements.txt: anthropic@>=0.40.0, deepgram-sdk@>=3.5.0, fastapi@>=0.110.0, openai@>=1.30.0, python-dotenv@>=1.0.0, redis@>=5.0.0, requests@>=2.31.0, stripe@>=9.0.0, uagents@>=0.22.0, uagents-core@>=0.1.0, uvicorn[standard]@>=0.29.0, websockets@>=12.0

### Recent commits (newest first)

- Pitch: full word-for-word 2-person script + 60s version
- Add pitch script (3-4 min, 2 presenters) + judge Q&A
- Stripe: auto-continue original tab after payment (no dead-end /?paid=1)
- Dashboard: live architecture/explanation board + triage-via-Claude indicator
- Take insurance plan as explicit input (+ ask for it conversationally)
- docs: README reflects Claude triage, location-filtered providers, provider/region-aware cost
- Add Claude-powered triage (Anthropic) with graceful fallback to ASI:One
- Cost: vary estimate by provider + region (no more flat $108-$216)
- Add dashboard and demo polish
- Add multilingual reply flourish
- docs: update README to 7 agents (+ Evidence) and latest features
- Add persisted dark mode
- Refresh CareLoop identity polish
- Backend: structured card + actions in voice response (interactive cards)
- Polish frontend micro interactions
- Add frontend accessibility controls
- Fix location and provider action handling
- Add interactive care cards
- Add voice feedback and thinking state
- Add first run example prompts

## Key source files (fetched from GitHub, selected and truncated for size)

### DEVPOST.md

```markdown
# CareLoop — Devpost submission

_Voice-first, multi-agent healthcare-access concierge. Fetch.ai uAgents + ASI:One + Deepgram._

## Inspiration
For millions of people the hardest part of healthcare isn't the medicine — it's the
**navigation**. An elderly parent, a caregiver juggling work, someone who speaks
English as a second language, or anyone scared in the moment all hit the same wall:
*Who do I see? Is this urgent? Who's nearby and takes my insurance? What will it
cost? How do I actually book it?* That maze is where people give up, delay care, or
end up in the ER. CareLoop turns it into one calm conversation you can have **out
loud, in your own language**.

## What it does
You describe a health concern in plain language (by **voice** or text). CareLoop:
1. **Triages** urgency and the right kind of clinician — with hard-coded red-flag
   rules that route emergencies straight to **911**, never to a booking.
2. **Finds real providers** near you via the public **CMS NPPES** registry.
3. **Estimates your out-of-pocket cost** based on your insurance type.
4. **Takes a refundable deposit** via a real **Stripe** Checkout (test mode) and
   verifies the payment server-side before confirming.
5. **Books the appointment** and produces a calendar invite — then reads the
   confirmation back to you by voice.

## How we built it
- **Fetch.ai uAgents**: six agents — an **Orchestrator** plus **Triage**,
  **Provider-Finder**, **Cost-Estimator**, **Scheduler**, and **Payment**
  specialists — each registered on **Agentverse** with its own mailbox and profile.
- **Stripe agent transaction**: the Payment agent creates a real (test-mode)
  Checkout session and verifies `payment_status == "paid"` server-side before the
  Scheduler confirms — a genuine intent→action transaction, not a mock.
- **Redis (beyond caching)**: shared infrastructure across every agent process — a
  provider/triage cache (91×–853× faster on repeats), a session store, a **Streams
  audit trail** of every clinical/payment/booking decision (a real healthcare audit
  log), and live stat counters surfaced in the UI. Degrades gracefully if offline.
- **Agent Chat Protocol + ASI:One**: the Orchestrator is discoverable and fully
  usable from ASI:One, so the **entire workflow runs in ASI:One chat with no
  custom frontend**. Agents coordinate through real agent-to-agent messaging
  (`send_and_receive`).
- **ASI:One LLM** (`asi1-mini`) parses intent and drives the multi-turn conversation.
- **Anthropic Claude** (`claude-sonnet-4-6`) powers the triage agent's clinical
  reasoning (urgency + specialty), with graceful fallback to ASI:One — so the build
  is on Claude Code *and* uses the Claude API in the product.
- **Deepgram**: Nova-3 multilingual STT + Aura-2 TTS power a click-to-talk web app
  layered on top of the same Orchestrator — voice is the primary, essential interface.
- **CMS NPPES NPI Registry** for real, verifiable provider data.
- Built with **Claude Code**.

## Target users
Caregivers
[truncated — 3104 more characters]
```

### PRESENTATION.md

```markdown
# CareLoop — Pitch script (3–4 min) + Judge Q&A

**Roles:** **A = product/host** · **B = technical** (drives the live demo).
**Setup:** voice app open at `http://127.0.0.1:8080`; dashboard open in a 2nd tab at
`/dashboard`; mic on. Pre-run one golden query so the cache is warm. Lead with the
**local voice app** (network can't break it); show ASI:One only if Wi-Fi is solid.

Stage directions are in [brackets]; everything else is spoken word-for-word.

---

## FULL SCRIPT (read aloud)

**A:** Quick show of hands — has anyone here ever had to find a doctor for a sick parent
or grandparent? Right. Now picture doing that at seventy years old, in your second
language, while you're scared. Here's the thing we kept coming back to: the hard part of
healthcare usually isn't the medicine — it's the **navigation**. Who do I even see? Is
this urgent? Who's nearby, who takes my insurance, what will it cost, and how do I
actually book it? That maze is where people give up, delay care, or just show up at the ER.

**A:** So we built **CareLoop** — a voice-first *team* of AI agents that takes you from a
spoken symptom all the way to a booked appointment. It doesn't just chat. It acts. [B] is
going to show you, live.

**B:** Thanks. Everything here is real and running. I'll just talk to it. [tap mic]
*"My dad's had a bad cough and a fever for five days. He's on Medicare, and we're in San
Francisco."* [release]

**B:** [as the card appears] So in a couple of seconds: Deepgram transcribed that; our
triage agent — running on Claude — decided this is primary care and not an emergency; and
we pulled a **real** San Francisco doctor from the federal provider registry, with a cost
estimate for his Medicare plan. It comes back as a card with a **Book** button.

**B:** I'll book it. [click Book] That opens a real **Stripe** checkout for a small
refundable deposit. I'll pay… [enter test card 4242…] …and watch the original screen — it
just **continued on its own**. We're booked, with a calendar invite. No dead end, no
copy-pasting a confirmation.

**A:** And it goes deeper when it needs to.

**B:** [tap mic] *"My mom was just diagnosed with early-stage breast cancer."* [release]

**A:** For a serious condition, CareLoop surfaces **real, currently-recruiting clinical
trials** near her from ClinicalTrials.gov, plus **drug-safety** information from the FDA —
options she can take to her doctor. A normal booking app doesn't do that.

**B:** And safety is built in. [tap mic] *"I'm having crushing chest pain and I can't
breathe."* [release; red banner shows] Red-flag symptoms never go to booking — it routes
straight to **911**, before any model even runs.

**B:** One last thing. [switch to dashboard tab] This is our live system board: **seven
agents**, what each one does, who talks to whom, and real-time stats. You can see triage
runs on **Claude**, and the orchestrator on **ASI:One**.

**B:** Architecturally: seven **uAgents on Fetch.ai's Agentverse**, fully usable from
**ASI:One wi
[truncated — 5975 more characters]
```

### requirements.txt

```
# CareLoop — voice-first multi-agent healthcare-access concierge
# Fetch.ai uAgents + ASI:One + Deepgram voice

uagents>=0.22.0
uagents-core>=0.1.0
openai>=1.30.0          # OpenAI-compatible client, pointed at ASI:One (api.asi1.ai/v1)
deepgram-sdk>=3.5.0     # STT (Nova-3) + TTS (Aura-2)
fastapi>=0.110.0        # voice web app backend
uvicorn[standard]>=0.29.0
requests>=2.31.0        # NPPES provider lookup
python-dotenv>=1.0.0
websockets>=12.0
stripe>=9.0.0           # refundable booking-deposit flow (test mode)
redis>=5.0.0           # shared cache + session store + audit stream + live stats
anthropic>=0.40.0      # optional: Claude powers triage clinical reasoning

```

### voice/static/app.js

```javascript
// CareLoop voice web app — click-to-talk, record-then-send.
const chat = document.getElementById("chat");
const micBtn = document.getElementById("mic");
const micLabel = micBtn.querySelector(".mic-label");
const statusEl = document.getElementById("status");
const srUpdates = document.getElementById("srUpdates");
const viaEl = document.getElementById("via");
const langSel = document.getElementById("lang");
const player = document.getElementById("player");
const textInput = document.getElementById("textInput");
const sendBtn = document.getElementById("sendBtn");
const composer = document.querySelector(".composer");
const meterBars = Array.from(document.querySelectorAll(".voice-meter span"));
const largeTextToggle = document.getElementById("largeTextToggle");
const contrastToggle = document.getElementById("contrastToggle");
const darkModeToggle = document.getElementById("darkModeToggle");
const reduceMotionQuery = window.matchMedia("(prefers-reduced-motion: reduce)");

let sessionId = "web-" + Math.random().toString(36).slice(2, 10);
let mediaRecorder = null;
let chunks = [];
let recording = false;
let busy = false;
let emergencyFocusTimer = null;
let audioContext = null;
let analyser = null;
let meterFrame = null;
let meterData = null;
let lastDetectedLanguage = null;
const DISPLAY_PREF_KEY = "careloopDisplayPrefs";
const displayPrefs = readDisplayPrefs();

const LANGUAGE_LABELS = {
  ar: "Arabic",
  en: "English",
  es: "Spanish",
  fr: "French",
  he: "Hebrew",
  hi: "Hindi",
  zh: "Chinese",
};
const RTL_TEXT = /[\u0590-\u08ff]/;

const EXAMPLE_PROMPTS = [
  "My dad has a bad cough and fever",
  "Find a cardiologist near me",
  "My mom was diagnosed with diabetes",
  "I have chest pain",
];

function addMessage(role, text, opts = {}) {
  const wrap = document.createElement("div");
  const language = opts.language || null;
  const isRtl = RTL_TEXT.test(text) || (role === "user" && language?.dir === "rtl");
  wrap.className = "msg " + role + (opts.emergency ? " emergency" : "") + (isRtl ? " rtl" : "");
  const bubble = document.createElement("div");
  bubble.className = "bubble" + (opts.rich ? " rich-bubble" : "");
  if (language?.code) bubble.setAttribute("lang", language.code);
  if (isRtl) bubble.setAttribute("dir", "rtl");
  bubble.innerHTML = opts.emergency ? renderEmergency(text, language) : renderMessageBody(text, opts);
  wrap.appendChild(bubble);
  chat.appendChild(wrap);
  scrollChatToEnd();
  return bubble;
}

function addThinkingBubble() {
  const wrap = document.createElement("div");
  wrap.className = "msg bot thinking";
  wrap.innerHTML = `
    <div class="bubble" role="status" aria-live="polite">
      <span class="thinking-label">CareLoop is thinking</span>
      <span class="typing-dots" aria-hidden="true"><span></span><span></span><span></span></span>
      <span class="thinking-skeleton" aria-hidden="true"><span></span><span></span></span>
    </div>
  `;
  chat.appendChild(wrap);
  scrollChatToEnd();
  return wrap;
}

// Minimal markdown: **bold**, clickable links, and newlines.
function render(t) {
  return escapeHtml(t)
    .replace(/\*\*(.+?)\*\*/g, "<b>$1</b>")
    .replace(/(https?:\/\/[^\s<]+)/g, '<a href="$1" target="_blank" rel="noopener">$1</a>')
    .replace(/\n/g, "<br>");
}

function renderMessageBody(text, opts = {}) {
  const languageHtml = opts.language ? renderLanguageChip(opts.language) : "";
  const cardsHtml = opts.cards?.length ? `<div class="card-stack">${opts.cards.map(renderCard).join("")}</div>` : "";
  const actionsHtml = opts.actions?.length ? renderActions(opts.actions) : "";
  if (!cardsHtml && !actionsHtml) return `${languageHtml}${render(text)}`;
  const summary = cardsHtml ? `<details class="reply-details"><summary>Conversation note</summary>${render(text)}</details>` : render(text);
  return `${languageHtml}${summary}${cardsHtml}${actionsHtml}`;
}

function setStatus(s) { statusEl.textContent = s; }

function scrollChatToEnd() {
  requestAnimationFrame(() => {
    chat.scrollTo({
      top: chat.scrollHeight,
      behavior: reduceMotionQuery.matches ? "auto" : "smooth",
    });
  });
}

function playAudio(b64) {
  if (!b64) return;
  player.src = "data:audio/mpeg;base64," + b64;
  player.play().catch(() => {});
}

async function sendTurn(promise, opts = {}) {
  if (busy) return;
  busy = true;
  micBtn.classList.add("disabled");
  if (opts.userText) {
    lastDetectedLanguage = detectLanguage(opts.userText, langSel.value);
    addMessage("user", opts.userText, { language: lastDetectedLanguage });
  }
  const thinking = addThinkingBubble();
  try {
    const res = await promise;
    const data = await res.json();
    thinking.remove();
    if (data.error) { setStatus("Warning: " + data.error); return; }
    if (data.transcript && data.transcript !== opts.userText) {
      lastDetectedLanguage = detectLanguage(data.transcript, langSel.value);
      addMessage("user", data.transcript, { language: lastDetectedLanguage });
    }
    addBotReply(data);
    setEmergencyFocus(Boolean(data.emergency));
    viaEl.textContent = data.via ? "routed via: " + data.via : "";
    playAudio(data.audio);
    setStatus("");
  } catch (e) {
    thinking.remove();
    setStatus("Network error: " + e.message);
  } finally {
    busy = false;
    micBtn.classList.remove("disabled");
  }
}

function addBotReply(data) {
  announceBotReply(data.reply);
  const language = data.language ? normalizeLanguage(data.language) : lastDetectedLanguage;
  if (data.emergency) {
    addMessage("bot", data.reply, { emergency: true, language });
    return;
  }
  const cards = collectCards(data);
  const actions = normalizeActions(data.actions) || inferActions(data, cards);
  addMessage("bot", data.reply, { cards, actions, rich: Boolean(cards.length || actions.length), language });
}

function renderEmergency(text, language) {
  return `
    <section class="emergency-card" role="alert" aria-label="Emergency guidance">
      <div class="emergency-mark" aria-hidden="true">!</di
[truncated — 19445 more characters]
```

### scripts/print_addresses.py

```python
"""Print the deterministic agent addresses (derived from seeds in config).

Use these for your Agentverse profiles / README. Run:
  ./venv/bin/python -m scripts.print_addresses
"""
from agents.common import config

print("CareLoop agent addresses (stable across restarts):\n")
for name, addr in config.ADDRESSES.items():
    print(f"  {name:13s} {addr}")
print(
    "\nAgentverse profile URL pattern:\n"
    "  https://agentverse.ai/agents/details/<address>/profile"
)

```

### agents/triage.py

```python
"""CareLoop Triage Agent.

Receives a TriageRequest, runs symptom triage (hard red-flag rules + ASI:One),
and replies with a TriageResult. Navigation only — never a diagnosis.
"""
from uagents import Agent, Context

from agents.common import config, logic
from agents.common.models import TriageRequest, TriageResult

agent = Agent(
    name="careloop-triage",
    seed=config.SEEDS["triage"],
    port=config.PORTS["triage"],
    mailbox=True,
    publish_agent_details=True,
    readme_path=config.readme("triage"),
    description="CareLoop Triage — urgency + specialty routing (Claude-powered clinical reasoning) with hard 911 red-flag rules.",
)


@agent.on_message(model=TriageRequest, replies=TriageResult)
async def handle_triage(ctx: Context, sender: str, msg: TriageRequest):
    result = logic.triage(msg.session_id, msg.symptoms, msg.patient_age)
    ctx.logger.info(
        f"triage[{msg.session_id}] urgency={result.urgency} "
        f"specialty={result.recommended_specialty} emergency={result.emergency}"
    )
    await ctx.send(sender, result)


@agent.on_event("startup")
async def startup(ctx: Context):
    ctx.logger.info(f"Triage agent address: {agent.address}")


if __name__ == "__main__":
    agent.run()

```

### agents/cost.py

```python
"""CareLoop Cost-Estimator Agent.

Receives a CostRequest, applies a simplified plan cost-sharing model, and replies
with an out-of-pocket range. Always an estimate, never a quote.
"""
from uagents import Agent, Context

from agents.common import config, logic
from agents.common.models import CostRequest, CostResult

agent = Agent(
    name="careloop-cost-estimator",
    seed=config.SEEDS["cost"],
    port=config.PORTS["cost"],
    mailbox=True,
    publish_agent_details=True,
    readme_path=config.readme("cost"),
    description="CareLoop Cost-Estimator — plan-aware out-of-pocket estimate for a visit.",
)


@agent.on_message(model=CostRequest, replies=CostResult)
async def handle_cost(ctx: Context, sender: str, msg: CostRequest):
    low, high, why = logic.estimate_cost(msg.visit_type, msg.insurance, msg.provider_seed, msg.region)
    ctx.logger.info(f"cost[{msg.session_id}] {msg.visit_type}/{msg.insurance} -> ${low}-${high}")
    await ctx.send(sender, CostResult(
        session_id=msg.session_id, estimate_low=low, estimate_high=high, explanation=why))


@agent.on_event("startup")
async def startup(ctx: Context):
    ctx.logger.info(f"Cost-estimator agent address: {agent.address}")


if __name__ == "__main__":
    agent.run()

```

### agents/scheduler.py

```python
"""CareLoop Scheduler Agent.

Receives a BookingRequest, creates a confirmation + an iCalendar invite, and
replies with a BookingResult. (Mock booking; the .ics is real and importable.)
"""
from uagents import Agent, Context

from agents.common import config, logic
from agents.common.models import BookingRequest, BookingResult

agent = Agent(
    name="careloop-scheduler",
    seed=config.SEEDS["scheduler"],
    port=config.PORTS["scheduler"],
    mailbox=True,
    publish_agent_details=True,
    readme_path=config.readme("scheduler"),
    description="CareLoop Scheduler — booking confirmation + real iCalendar (.ics) invite.",
)


@agent.on_message(model=BookingRequest, replies=BookingResult)
async def handle_booking(ctx: Context, sender: str, msg: BookingRequest):
    code, summary, ics = logic.book(
        msg.session_id, msg.provider_name, msg.provider_address, msg.slot,
        msg.patient_name, msg.reason)
    ctx.logger.info(f"scheduler[{msg.session_id}] booked {msg.provider_name} -> {code}")
    await ctx.send(sender, BookingResult(
        session_id=msg.session_id, confirmation_code=code, summary=summary, ics=ics))


@agent.on_event("startup")
async def startup(ctx: Context):
    ctx.logger.info(f"Scheduler agent address: {agent.address}")


if __name__ == "__main__":
    agent.run()

```

### scripts/run_all.sh

```shell
#!/usr/bin/env bash
# Start all CareLoop agents (each in its own process, each with its own mailbox).
# On first run, open each agent's Inspector URL (printed in its log) and click
# "Connect -> Mailbox" to register it on Agentverse.
#
# Usage:
#   ./scripts/run_all.sh          # start everything (logs in ./logs)
#   ./scripts/run_all.sh stop     # stop everything
set -euo pipefail
cd "$(dirname "$0")/.."

PY="./venv/bin/python"
LOGDIR="logs"
PIDFILE=".careloop.pids"

stop() {
  if [[ -f "$PIDFILE" ]]; then
    while read -r pid; do
      kill "$pid" 2>/dev/null || true
    done < "$PIDFILE"
    rm -f "$PIDFILE"
    echo "Stopped CareLoop agents."
  else
    echo "No PID file; nothing to stop."
  fi
}

if [[ "${1:-}" == "stop" ]]; then stop; exit 0; fi

mkdir -p "$LOGDIR"
: > "$PIDFILE"

start() { # name module
  local name="$1" module="$2"
  echo "Starting $name ..."
  $PY -m "$module" > "$LOGDIR/$name.log" 2>&1 &
  echo $! >> "$PIDFILE"
}

# Specialists first, then the orchestrator.
start triage          agents.triage
start provider-finder agents.provider_finder
start cost            agents.cost
start scheduler       agents.scheduler
start payment         agents.payment
start evidence        agents.evidence
sleep 2
start orchestrator    agents.orchestrator

echo
echo "All agents starting (logs in ./$LOGDIR)."
echo "Next: auto-register their mailboxes on Agentverse (no browser needed):"
echo "  ./venv/bin/python -m scripts.register_agents"
echo "Stop with: ./scripts/run_all.sh stop"

```

### scripts/chat_client.py

```python
"""Throwaway client that exercises the orchestrator's Agent Chat Protocol path
(the exact entry point ASI:One uses). Sends a ChatMessage, prints the reply, exits.

Run (with the mesh up):  ./venv/bin/python -m scripts.chat_client
"""
import os
import signal
from datetime import datetime, timezone
from uuid import uuid4

from uagents import Agent, Context
from uagents_core.contrib.protocols.chat import ChatMessage, TextContent

from agents.common import config

client = Agent(
    name="careloop-chat-tester",
    seed="careloop-chat-tester-seed-2026",
    port=8009,
    endpoint=["http://127.0.0.1:8009/submit"],
)

ORCH = config.ADDRESSES["orchestrator"]
QUESTION = os.getenv(
    "CHAT_TEST_MSG",
    "my dad has had a bad cough and low fever for five days, he is on Medicare and we are in Berkeley",
)


@client.on_event("startup")
async def go(ctx: Context):
    msg = ChatMessage(
        timestamp=datetime.now(timezone.utc),
        msg_id=uuid4(),
        content=[TextContent(type="text", text=QUESTION)],
    )
    ctx.logger.info(f"Sending ChatMessage to orchestrator {ORCH[:24]}…")
    reply, status = await ctx.send_and_receive(ORCH, msg, response_type=ChatMessage, timeout=70)
    if reply is not None:
        text = " ".join(c.text for c in reply.content if hasattr(c, "text"))
        ctx.logger.info("✅ CHAT PROTOCOL REPLY RECEIVED:")
        print("\n----- ChatMessage reply from orchestrator -----")
        print(text[:600])
        print("-----------------------------------------------\n")
    else:
        ctx.logger.error(f"❌ No ChatMessage reply: {status}")
    os.kill(os.getpid(), signal.SIGINT)


if __name__ == "__main__":
    client.run()

```

[32 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]