A forkable starter kit for AI-powered medical intake, built on Tavus. Clone it, configure the persona, and ship a real-time video intake experience where patients complete a structured pre-appointment intake with an AI coordinator that sees, hears, and responds to them in real time.
One-click deploy to Vercel. You'll be prompted for
TAVUS_API_KEY(required). Thepersona_id/replica_idship inconfig/presets.config.json, so they aren't deployment env vars.
If you're new to Tavus, here's a quick glossary of terms used throughout the repo:
| Term | What it means |
|---|---|
| Persona | The AI's complete identity — system prompt, behavior rules, and conversation structure. Created via the Tavus API. |
| Replica | The AI's face and voice — a video avatar trained from real footage. You pick one during setup. |
| Objective | A single step in the intake (e.g., "Collect chief complaint and symptom duration"). Objectives chain together to form the intake flow. |
| Guardrail | A behavioral constraint (e.g., "Never diagnose or prescribe"). Keeps the AI within its role as a data collector. |
| Raven | Tavus's perception model. Observes the patient's environment, privacy, distress signals, and audio quality via camera and microphone. |
| Sparrow | Tavus's turn-taking model. Controls when the AI speaks vs. listens — configured here for patient-paced, unhurried interaction. |
- Real-time video medical intake — a Tavus-powered AI coordinator that sees, hears, and responds to the patient in real time
- Structured intake flow — four objectives guide the session step by step (patient info, chief complaint, medical history, consent & insurance)
- Hair-check lobby — live camera preview via a single
getUserMedia({video, audio})call (one combined browser permission prompt); audio + video tracks are stopped and removed before handing off so Daily takes over capture cleanly - Live transcript + text input — closed-captions side panel with the full running transcript (selectable, with a "Copy all" button next to the close ×) and an inline text-message input (sends
conversation.respond). Both user and replica text stream in progressively viaconversation.utterance.streaming(the canonical transcript event for both roles); the single-shotconversation.utteranceis used only to carry Raven awareness fields, not transcript text. The video shrinks to make room when CC is open (no overlay). - Closed captions on by default — the conversation is created with
enable_closed_captions: true - 10-minute hard cap — every conversation is created with
properties.max_call_duration: 600and the title bar shows a liveMM:SS / 10:00timer; the call auto-leaves at 10:00 - Perception analysis — Tavus Raven observes room privacy, patient distress, engagement, and audio clarity throughout the session
- Magic Canvas cards — persona-driven cards render mid-call (the name text-input, the date-of-birth calendar, and the urgency question card). Submitted values flow back into the conversation context.
- Developer Inspector — always rendered. A right-docked, collapsible developer panel that boots collapsed (a small terminal-style toggle) and expands to a full-height panel against the right edge. A pinned Live State vitals strip sits below the header, above two tabs (Inspector + Events). The Inspector tab shows objectives progress, live persona guardrails, persona tools captured (LLM + visual + audio), and Raven perception state; the Events tab is a severity-colored CVI event console. The running utterance transcript lives in a separate
TranscriptPanel(toggled by the CC button), not in the inspector. Open/closed state + active tab persist in localStorage undermedical-intake.dev-panel.v3. Every column reads exclusively from the Tavus API and live CVI events — no hardcoded fallbacks. In post-call summary mode the columns reflect truth — tri-state objectives (done / active / not-reached), "Guardrails configured", and "no observations" if Raven didn't capture any. - Patient-facing results + clinician report — a "thanks, you're done" ResultsScreen for the patient, with a View summary button that opens a clinician-facing ReportScreen: an intake summary, chief complaint, urgency level + rationale, the collected intake fields, and Raven perception notes. The report is produced by the persona's
submit_intake_reportpost-call tool (no Anthropic key) and read back from the conversation once Tavus finishes it.
You'll need basic familiarity with the terminal (running commands, navigating directories). Install these before you start:
| Requirement | How to get it | Verify |
|---|---|---|
| Node.js v18+ | nodejs.org (LTS recommended — this also installs npm) | node -v and npm -v |
| Git | git-scm.com | git --version |
| Tavus account + API key | Sign up at platform.tavus.io, then go to API Keys | You'll paste the key during setup |
| Camera + microphone | Built-in or external — the intake is a live video call with the AI | Test with your browser's permission settings before starting |
| Modern browser | Chrome, Firefox, or Edge recommended. Safari has partial WebRTC support. Mobile browsers are not supported. | The app uses WebRTC (via Daily.js) for real-time video — requires a desktop browser. |
| AI coding assistant (recommended) | Claude Code (npm install -g @anthropic-ai/claude-code) or Cursor |
Not required, but highly recommended for customization |
git clone https://github.com/Tavus-Engineering/tavus-intake.git
cd tavus-intakenpm installCopy .env.example to .env and fill in the values. Each variable is documented inline — at minimum you need TAVUS_API_KEY. The persona and replica IDs live in config/presets.config.json, not in .env.
cp .env.example .envThen open .env and paste in:
TAVUS_API_KEY— from platform.tavus.io/api-keys
The persona_id and replica_id are set in config/presets.config.json (under presets[0]), which is the single source of truth:
persona_id— pick an existing persona from platform.tavus.io/personas, or runnpm run init(see below) to copy a reference persona into your account and have the new ID written to the presetreplica_id(optional) — overrides the persona's default replica
One-time persona setup: After setting
TAVUS_API_KEYin.env, runnpm run initto copy a reference persona to your account. The copy command writes the newpersona_idintoconfig/presets.config.jsonautomatically. (Skip this if the preset already has a custom persona ID.)
The Developer Inspector is always rendered — a right-docked, collapsible developer panel that boots collapsed (a small terminal-style toggle) and expands to a full-height panel against the right edge. A pinned Live State vitals strip sits below the header, above two tabs (Inspector + Events). The Inspector tab shows objectives, guardrails (with verbal/visual modality tags), LLM tools, and Raven visual/audio awareness in real time. The Events tab shows a severity-colored CVI event console. The running utterance log lives in a separate TranscriptPanel (opened by the CC button), not in the inspector. Open/closed state + active tab persist in localStorage under medical-intake.dev-panel.v3. Every column reads exclusively from the Tavus API or live CVI events — no hardcoded fallbacks.
npm run devYour browser will open automatically to http://localhost:5173.
Once the app is running, here's what to expect:
- Lobby — the app boots directly into the lobby (there is no separate welcome screen). It shows the intake intro ("Welcome", "You're starting your medical intake", a short description of the 10-minute intake naming the AI coordinator Jolene) alongside a hair-check: live camera preview plus three device selectors (Camera, Microphone, Audio output). The conversation is created in the background while you pick devices. The browser will ask for camera + microphone permission once (single combined prompt) — click Allow, then click I'm ready when you want to join.
- Join — clicking "I'm ready" connects you to the Tavus conversation that was created on lobby entry.
- Intake — the AI replica appears and begins the session. The title bar shows a live
MM:SS / 10:00timer; the call auto-leaves when it hits 10:00 (Tavus also force-ends the call server-side viamax_call_duration: 600). The 4-button control bar (mic, camera, CC, end intake) is at the bottom of the video frame. Noise cancellation is applied automatically by default — there is no user toggle. The CC button opens theTranscriptPanelbeside the video (the video shrinks to make room — no overlay); the transcript is selectable and has a "Copy all" button. The Developer Inspector overlay shows objective progress, live guardrails, captured persona tools, and Raven perception state. - Results — when the intake ends, you'll see a "Thanks for completing the intake" thank-you card. Click View summary to open the clinician-facing summary: chief complaint, urgency, the collected intake fields, and Raven perception notes. The report is generated by the persona's
submit_intake_reportpost-call tool a few seconds after the call ends, so the screen briefly shows a loading state while Tavus finishes it.
Tip: The intake is capped at 10 minutes end-to-end. Try it once before customizing anything — it helps you understand what each config file controls.
Once the app is running, open the project in an AI coding assistant to help you understand and customize the codebase. The repo includes a CLAUDE.md file that gives AI assistants full context about the architecture, conventions, and how everything connects.
cd tavus-intake
claudecd tavus-intake
cursor .Or open Cursor, then File > Open Folder and select the project directory.
Paste this into your AI assistant to get started:
I just cloned the tavus-intake repo — a Tavus-powered AI medical intake
experience built with React + Vite. I need your help customizing it.
Key context:
- the persona (the AI coordinator's identity, intake objectives, guardrails) is managed on Tavus directly and fetched live from the Tavus API — there are no local persona JSON definition files
- config/ controls frontend branding and the single preset (perception queries live on the persona, fetched live from the Tavus API)
- api/ is the serverless proxy layer that keeps API keys server-side
- Every folder has a .md file explaining its contents
- Setup is manual: copy .env.example to .env, fill in the values, and run npm run dev
- The intake session works with only a Tavus API key; Magic Canvas renders persona-driven cards mid-call (name input, date-of-birth calendar, urgency question)
Read CLAUDE.md first for full architectural context, then help me
[customize this for a different clinic type / add new intake fields / change the
branding / deploy to Vercel].
Replace the bracketed text with what you actually want to do.
Edit config/branding.config.json to set clinicName (the only branding field) — it shows as the ResultsScreen eyebrow. The LobbyScreen is the patient-facing entry screen (there is no welcome screen); its copy is hardcoded in the component.
To add or change what data is collected, edit the persona's objectives in the Tavus dashboard. Each objective's output_variables define the field names that will be extracted and accumulated in SessionResult.objectiveOutputs. The app fetches objectives live at runtime — no code changes are required.
Guardrails are managed directly on the persona — create them via the Guardrails API (POST /v2/guardrails) and attach via guardrail_ids / guardrail_tags, or edit them in the Tavus dashboard. The deployed persona is the source of truth. How the app reacts to a triggered guardrail (replica speaks / toast / inspector-only) is configured per guardrail in src/lib/guardrailActions.ts.
Update replica_id in config/presets.config.json (browse replicas at platform.tavus.io/replicas).
Designed for Vercel. The api/ directory maps directly to Vercel serverless functions.
-
Push your repo to GitHub
-
Import the repo in Vercel
-
Set environment variables in the Vercel dashboard:
TAVUS_API_KEY(required)
The
persona_idandreplica_idship inconfig/presets.config.json, so they don't need to be set in the dashboard. -
Deploy — Vercel automatically maps
api/*.tsto serverless functions
# Test the production build locally before deploying
npm run build
npm run previewFor other platforms, adapt api/*.ts to your serverless runtime or add a lightweight Express server. See API.md for details on the proxy layer.
| Problem | Cause | Fix |
|---|---|---|
No .env file found |
Haven't created .env yet |
Copy .env.example to .env and fill in the values |
Missing required: TAVUS_API_KEY |
.env exists but key is empty |
Add the key to .env from platform.tavus.io/api-keys |
| Conversation fails — no persona | persona_id not set in config/presets.config.json |
Pick an existing persona at platform.tavus.io/personas and paste its ID into presets[0].persona_id (or run npm run init to copy a reference persona) |
Tavus API error 401 |
Invalid API key | Verify your key at platform.tavus.io/api-keys |
| Camera/mic not working | Browser permissions blocked | Click the lock/camera icon in the address bar and allow access. HTTPS is required in production. |
| Magic Canvas cards not rendering | Magic Canvas is a preview feature that must be enabled for your account on production | The app talks to production (https://tavusapi.com) only; confirm with Tavus that Magic Canvas is enabled on your account |
| Lobby shows spinner indefinitely | Conversation creation failed | Check browser console for API errors. Verify TAVUS_API_KEY is set, and that persona_id/replica_id in config/presets.config.json are correct |
| Script | Description |
|---|---|
npm run dev |
Start dev server (runs env check + config validation first) |
npm run build |
Production build (type-checks then bundles) |
npm run preview |
Preview the production build locally |
npm run validate |
Validate config files (Zod schemas) |
npm run check-env |
Verify required environment variables are set |
npm run init |
Copy a reference persona into your Tavus account and write its persona_id into the preset |
├── api/ Serverless API proxies → API.md
├── config/ Frontend config (branding, → CONFIG.md
│ presets)
├── persona/ Dev sample answers → README.md
├── scripts/ Setup and validation scripts → SCRIPTS.md
├── src/ React + Vite frontend → SRC.md
│ ├── screens/ LobbyScreen, IntakeScreen,
│ │ ResultsScreen, ReportScreen
│ ├── components/
│ │ ├── video/ Tavus video integration (Daily.js)
│ │ ├── layout/ FloatingInspector (right-docked collapsible dev panel: vitals strip + Inspector/Events tabs)
│ │ ├── inspector/ DeveloperInspector four-column panel + VitalsStrip + EventsConsole rendered inside FloatingInspector (always rendered)
│ │ ├── intake/ TranscriptPanel (in-flow flex panel — video shrinks when open; selectable + Copy all)
│ │ └── ui/ Spinner
│ ├── hooks/ State management + event hooks
│ ├── lib/ Utilities, API client, config loader
│ └── types/ TypeScript type definitions
└── docs/ Architecture + customization → DOCS.md
Every folder contains a .md file that explains the concept behind it. An AI agent (or developer) can drop into any folder, read the doc, and understand what that piece does, why it exists, and how to modify it.
The persona is the AI coordinator's complete identity — who it is, what it collects, and what it won't do. It's managed directly on Tavus (the dashboard or the Tavus API); see persona/README.md. The app references it by persona_id and fetches its objectives, guardrails, and tools live at runtime. Concepts: Objectives, Guardrails. The app's per-guardrail reactions live in src/lib/guardrailActions.ts.
Frontend configuration that tells the web app how to display and label the experience.
| Doc | Teaches you |
|---|---|
| CONFIG.md | Branding, presets, and how config connects to the persona |
Serverless functions that keep the Tavus API key on the server.
| Doc | Teaches you |
|---|---|
| API.md | Why the proxy exists, how each endpoint works, how to add new ones, and security rules |
CLI tools for setup, deployment, and validation.
| Doc | Teaches you |
|---|---|
| SCRIPTS.md | Config validation (validate), env check (check-env), persona setup (init), and how they connect |
The React + Vite frontend. Screens, components, hooks, types.
| Doc | Covers |
|---|---|
| SRC.md | Full directory map, FSM routing, event flow, and hooks architecture |
| Doc | Covers |
|---|---|
| DOCS.md | Index of documentation files and when to read them |
| architecture.md | Screen flow, FSM states, data flow, key principles |
| customization.md | Step-by-step instructions for common changes |
| tavus-features.md | Which Tavus features are used and how (objectives, guardrails, Raven, Sparrow) |
| walkthrough.md | End-to-end walkthrough from setup to completed intake with results |
If you want to understand the system deeply enough to reconfigure it for a different clinical use case:
1. config/CONFIG.md ← Branding + presets
2. api/API.md ← Server proxy layer
3. scripts/SCRIPTS.md ← Setup and validation tools
4. src/SRC.md ← Frontend architecture (only if modifying the app)
The persona itself (identity, intake objectives, guardrails, layers) is managed on Tavus — see persona/README.md and the Tavus persona docs. Items 2-4 are needed only if you're changing the infrastructure or frontend code.
# Server-side only (no VITE_ prefix — never in browser bundle)
TAVUS_API_KEY= # Required — from https://platform.tavus.io/api-keysNote: all Tavus API calls go to production (
https://tavusapi.com) — the base URL is hardcoded inapi/_lib/handlers/tavus.ts, not configurable.
Persona & replica: These are not environment variables. Set
persona_idandreplica_idinconfig/presets.config.json(underpresets[0]) — that file is the single source of truth.