An agentic operating system for luxury hospitality. A Sense of Place, threaded through every guest.
Built for Hospitality 2030 · Rosewood Sand Hill Hackathon (2026-05-16). Live demo: app.redthread.boutique · Agent endpoint: elias.redthread.boutique · Schema: /.well-known/agent.json
A live, single-URL dashboard at app.redthread.boutique where you can:
- Submit a reservation — toggle between two pre-set guests (Benjamin Shyong / Ms. Lin Chen), edit any of five fields (name, email, reservation #, flight #, departure date), and Hold the Thread (the guest's 0–10 Privacy Openness Score) is mirrored from their profile.
- Watch the agent work — Begin briefing runs a 9-step live chain: CRM cross-property → 3 web-research steps (LinkedIn / X / press, gated by Hold the Thread) → AviationStack flight lookup → Claude Haiku reasoning for luggage, customs, transit → ETA composition. Each card streams reasoning tokens, expands on click to show the exact input, result, tech provenance, and timing.
- See the brief render — the full Claude Opus + Haiku Discretion pass produces a Dossier that lands in the three editorial zones: research streams, the brief, actuators (room state, welcome amenity, itinerary).
- Listen to the brief — Brief Me button plays an ElevenLabs TTS read of the composed dossier through the AV.
- Run the A2A handoff — Agent handoff button kicks a live two-machine conversation: Threadkeeper (Rosewood-side, on Vercel) talks to elias (Ben's personal AI, on a Mac Mini in his house, exposed via a Cloudflare tunnel). Real Claude on each side; structured manifest extracted; dashboard rerenders with the negotiated preferences.
- Switch properties — flip Sand Hill ↔ Hong Kong in the header; same guest, completely different dossier (the no-cookie-cutter moment).
- Watch the live thread — the bottom band runs the full lifecycle in six phases (Pre-arrival → Arrive → Check-in → Stay → Checkout → Post-stay) and pulls beats from the right source in the store: arrival chain → arrival summary → dossier actuators → in-stay events → reservation checkout → continuity beats.
There's also a separate voice intake flow at /intake — an ElevenLabs Convai widget runs a 5-question call with Alice (the live route) plus a bulletproof "Play demo" path that streams a scripted two-voice (Alice + Eric) conversation through ElevenLabs TTS.
┌─────────────────────────────────────────────────────────┐
│ app.redthread.boutique · Vercel (Next.js 16 nodejs) │
│ │
│ / Dashboard (6-input form + │
│ zones + LiveThread) │
│ /intake ElevenLabs Convai voice + │
│ scripted demo path │
│ /profile Guest-side Hold-the-Thread │
│ │
│ POST /api/arrival-chain SSE — 9-step live chain │
│ POST /api/agent SSE — Opus 4.7 tool-use │
│ + Haiku 4.5 Discretion │
│ POST /api/agent-handoff-live SSE — Threadkeeper │
│ ↔ elias bridge │
│ POST /api/agent-handoff SSE — scripted fallback │
│ POST /api/voice ElevenLabs TTS proxy + │
│ /tmp disk cache │
│ POST /api/voice/intake/complete pulls conv from EL │
│ POST /api/airport-eta standalone airport ETA │
│ GET /.well-known/agent.json A2A protocol manifest │
└────────────────────────┬────────────────────────────────┘
│ HTTPS (CF tunnel)
▼
┌─────────────────────────────────────────────────────────┐
│ elias.redthread.boutique · Mac Mini (Bun HTTP) │
│ │
│ GET / identity card (who am I, who's │
│ my principal, which endpoint) │
│ POST /respond Claude Haiku 4.5 call with elias │
│ persona + Ben's prefs in system │
│ prompt; returns the next utterance │
│ │
│ Process: /Users/elias/code/redthread-bridge │
│ Tunnel: named Cloudflare tunnel (permanent) │
└─────────────────────────────────────────────────────────┘
A. Reservation → live agent dossier (the centerpiece)
- User submits reservation form →
POST /api/arrival-chainSSE - Server runs 9 sequential steps, emitting
step_start/step_thinking(token deltas) /step_completeper step:01 crm—getGuest(guestId)fromdata/guests/<id>.json02 linkedin— pre-baked replay of Claude Haiku +web_search(live tool burns 10–30s per query; we cache for demo reliability)03 twitter— same04 press— same05 flight— liveAviationStackcall, time-of-day projected onto reservation check-in date06 luggage— liveClaude Haiku 4.5reasoning,<value>tag parsed07 customs— live Haiku reasoning over nationality + airport + arrival hour08 transit— live Haiku reasoning over route + day-of-week + congestion09 eta— math composition: landing + customs + bags + transit
- After the chain, client kicks
POST /api/agent { live: true }—Claude Opus 4.7with tool-use (web_search, crm_cross_property, flight_lookup) produces a structuredDossier. A secondClaude Haiku 4.5pass (Discretion Layer) filters it per the guest's saved Privacy Openness Score. - Final Dossier lands in zustand store → Zones II + III + LiveThread render.
B. Live A2A — Threadkeeper ↔ elias
- Click Agent handoff →
POST /api/agent-handoff-liveSSE - Vercel runs a turn loop. Each turn:
- Threadkeeper turn:
Claude Haiku 4.5on Vercel, system prompt is Rosewood-side intake agent, returns the next utterance. - elias turn:
POST elias.redthread.boutique/respond { conversation }→ the Mac Mini bridge callsClaude Haiku 4.5with elias's persona + Ben's authoritative preferences in the system prompt, returns the next utterance.
- Threadkeeper turn:
- After 4–6 turns, Threadkeeper emits
[HANDOFF_SEALED](or wall-clock cap). A finalClaude Haiku 4.5extracts the structured manifest (assistant-prefill anchored to<manifest>{to force JSON shape). - Client receives the manifest, kicks
streamAgent({ overrides })→ dashboard re-populates with negotiated prefs applied.
C. Voice intake (ElevenLabs Convai)
/intakepage hosts the ElevenLabs Convai React widget pointed atagent_5001krs8b43gfjstkz92k6fx9e3n(the Pre-Arrival Intake agent, Alice voice, structured data collection for 5 fields).- Conversation ends → client
POST /api/voice/intake/complete { conversationId }→ server pulls the conversation from ElevenLabs (GET /v1/convai/conversations/{id}), extractsanalysis.data_collection_results, returns the normalized overrides. - Client stashes overrides in
sessionStorage["redthread:intake"]→ redirects to/?fromIntake=1→ DemoLoader picks them up and kicks the agent run with them applied.
A second "Play demo" path on /intake sidesteps Convai entirely and plays a 12-line scripted conversation through /api/voice (Alice + Eric voices) for demo-day reliability.
The 0–10 dial is not a 3-band coarse switch. It's an 11-step descending ladder: each adjacent tick drops one specific signal from the dossier so the judge can drag from 6 → 5 → 4 and watch the brief and actuators shrink one line at a time. Every removal lands in dossier.suppressed[] with the reason, so the audit log shows exactly what the band cost.
| UI | POS | What gets removed at this tick |
|---|---|---|
| 10 | 100 | — full dossier, no reduction |
| 9 | 90 | A2A / cross-property continuity reasoning |
| 8 | 80 | Recent-press hook (LinkedIn / TechCrunch / fundraise / keynote) |
| 7 | 70 | Calendar-adjacency itinerary entry |
| 6 | 60 | Personal-history hook (family / anniversary / partner) |
| 5 | 50 | Remaining conversation hooks |
| 4 | 40 | Amenity sourcing reasoning (amenity itself kept) |
| 3 | 30 | Full itinerary (room + amenity kept) |
| 2 | 20 | Amenity downgraded to generic "Welcome tea service" |
| 1 | 10 | Room scent + lighting personalization |
| 0 | 0 | Vault — room defaults only (no temperature, no bedding logged) |
The ladder is implemented twice, in lockstep: bandReduceFixture in src/app/api/agent/route.ts for the fast fixture path that powers slider repaints, and the DISCRETION_LAYER_SYSTEM prompt in src/lib/prompts.ts for the live Claude Haiku 4.5 path. When the guest moves the dial on /profile and saves, /api/agent re-streams with the new previewPos → fixture path applies the ladder per-tick → zones 1/2/3 repaint without burning a Claude call. body.live=true forces the slow live path when you want to demo the real Haiku reasoning.
- API key:
ELEVENLABS_API_KEY(workspace-level) - Two Convai agents (both public, no auth required):
agent_5001krs8b43gfjstkz92k6fx9e3n· Red Thread — Pre-Arrival Intake · Alice voice ·gemini-2.5-flashbrain · 5 data-collection fields (room_temp_c,bedding,morning_ritual,dietary,privacy_posture)agent_6701krs7smnqeg8ae4hyskjcq7g8· Brief Me · Alice voice ·claude-opus-4-7brain · reads dossiers TO staff
- Voices (premade, no cloning):
- Threadkeeper / Concierge: Alice
Xb7hH8MSUJpSbSDYk0k2(British female, calm) - elias / Guest demo voice: Eric
cjVigY5qzO86Huf0OWal(American male, smooth) - George
JBFqnCBsd6RMkjVDRZzb(British male) used for A2A panel
- Threadkeeper / Concierge: Alice
- TTS:
eleven_turbo_v2_5model, settingsstability=0.55 similarity_boost=0.75 style=0.2 use_speaker_boost=true - Caching:
/tmp/voice-cache/<sha256>.mp3keyed bytext + voiceId— warm calls return in ~5ms
- API key:
ANTHROPIC_API_KEY(personal) - Models:
claude-opus-4-7— main agent loop (/api/agent): research with web_search server tool + custom tool-use loop, up to 8 turns, max 4096 output tokens per turnclaude-haiku-4-5— Discretion Layer (/api/agent), arrival-chain reasoning (luggage / customs / transit), A2A turns (both Threadkeeper and elias), manifest extraction
- Tool surface (
src/lib/tools.ts):web_search— Anthropic server-side tool (typeweb_search_20260209)crm_cross_property— custom; readsdata/guests/*.jsonanddata/properties/*.jsonflight_lookup— custom; AviationStack with deterministic mock fallback
- Prompt structure (
src/lib/prompts.ts):RESEARCH_AGENT_SYSTEM— instructs Opus to verify identity, research in parallel (LinkedIn / X / company / email-domain), synthesize a Dossier wrapped in<dossier>tagsDISCRETION_LAYER_SYSTEM— instructs Haiku to filter the dossier along the 11-step Hold-the-Thread ladder (see Hold the Thread — per-tick discretion above) and log every removal intosuppressed[]with the tick that caused it
- Streaming:
messages.streamfor token-by-token reasoning (arrival chain web research, A2A turns);messages.createfor the tool-use loop in the main agent
- AviationStack — real flight status (
AVIATIONSTACK_API_KEY), with deterministic mock fallback forUA857/CX870if the key is missing or invalid - Cloudflare Tunnel — permanent named tunnel exposing the Mac Mini bridge at
elias.redthread.boutique - Vercel — Next.js hosting,
runtime = nodejs,maxDuration60–120s per route depending on workload
src/
app/
layout.tsx fonts (Cormorant Garamond, Inter, Noto Serif SC), root layout
page.tsx dashboard: ReservationIntake + DashboardDial + DemoTrigger + 4 zones
globals.css theme tokens (rose-red palette)
intake/page.tsx ElevenLabs Convai voice intake + scripted demo path
profile/page.tsx guest-side Hold-the-Thread slider
api/
agent/route.ts Opus research + Haiku discretion loop (SSE)
agent-handoff/route.ts scripted A2A (SSE) — fallback
agent-handoff-live/route.ts real A2A — Threadkeeper ↔ elias bridge (SSE)
arrival-chain/route.ts 9-step live arrival research (SSE)
airport-eta/route.ts standalone ETA estimator
voice/route.ts ElevenLabs TTS proxy + disk cache
voice/intake/complete/route.ts pulls Convai conversation, normalizes overrides
components/
Header.tsx wordmark + property switcher (loads fixture per guest+property)
DashboardDial.tsx concierge mirror of guest's POS (read-only)
DiscretionDial.tsx guest-side POS slider used on /profile
ReservationIntake.tsx 5-input form + 9-step arrival-chain panel
DemoTrigger.tsx Voice intake link + Agent handoff button
DemoLoader.tsx SSE consumer for /api/agent + intake bootstrap
AgentHandoffPanel.tsx A2A transcript + audio queue (Alice + George)
BriefMeButton.tsx /api/voice playback with state machine
zones/
ZoneShell.tsx editorial card frame (light/dark)
ResearchStreams.tsx Zone I — click-to-expand tool-call cards
TheBrief.tsx Zone II — bio, hooks, handle-with-care
Actuators.tsx Zone III — room, amenity, itinerary
LiveThread.tsx Zone IV — 6-phase lifecycle on the red thread
lib/
types.ts Guest, Property, Dossier, ToolCallTrace
anthropic.ts SDK singleton + MODELS const
prompts.ts RESEARCH + DISCRETION system prompts
tools.ts TOOL_DEFINITIONS + dispatchTool
crm.ts getGuest / getProperty / crmCrossProperty
flight.ts flightLookup (live AviationStack + mock fallback)
pos.ts POS calculator (unused live; baked into Discretion)
dossierStore.ts zustand store: dossier, arrivalSteps, inStayEvents,
activeProperty, activeGuestId, activeGuestPos
arrivalChainFixtures.ts pre-baked web_search fixtures per guest
data/
guests/
ben.json Benjamin Shyong — founder, vegetarian, POS 55
lin-chen.json Ms. Lin Chen — fintech founder, pescatarian, POS 62
bellandi.json backup guest (Aldo Bellandi)
properties/
sand-hill.json, hong-kong.json, crillon.json — full content per property
fixtures/
ben__hong-kong.json, ben__sand-hill.json
lin-chen__hong-kong.json, lin-chen__sand-hill.json, lin-chen__crillon.json
public/
.well-known/agent.json A2A protocol manifest
The elias bridge lives in a separate repo at ~/code/redthread-bridge/:
redthread-bridge/
server.ts Bun HTTP server on :4040 with GET / + POST /respond
prefs.json Ben's authoritative preferences for elias's system prompt
Required:
ANTHROPIC_API_KEY— Opus + HaikuELEVENLABS_API_KEY— TTS + Convai
Optional:
AVIATIONSTACK_API_KEY— real flight data; mocks if missingELEVENLABS_VOICE_ID— overrides Alice as the default TTS voiceNEXT_PUBLIC_ELEVENLABS_AGENT_INTAKE— public agent id for /intake widgetNEXT_PUBLIC_ELEVENLABS_AGENT_BRIEF— public agent id for Brief Me (currently unused; TTS path is server-side)NEXT_PUBLIC_ELEVENLABS_VOICE_GUEST— the second voice in the scripted /intake demo (Eric by default)NEXT_PUBLIC_ELEVENLABS_VOICE_CONCIERGE— the concierge voice (Alice by default)ELIAS_BRIDGE_URL— public URL of the elias bridge (set tohttps://elias.redthread.boutique)DEMO_MODE—1short-circuits/api/agentto fixture replay (used for property switcher)
For local dev with secrets pulled from 1Password:
op run -- bun devbun install
cp .env.example .env.local # fill in ANTHROPIC_API_KEY + ELEVENLABS_API_KEY
bun devOpen http://localhost:3000.
To run the elias bridge locally too:
cd ~/code/redthread-bridge
ANTHROPIC_API_KEY=$(grep ANTHROPIC ~/code/redthread/.env.local | cut -d= -f2-) bun server.ts
# in another terminal:
cloudflared tunnel run redthread-elias-bridge- A Sense of Place — Rosewood's compass, trademarked 1979
- Relationship Hospitality — Sonia Cheng's framing; the dossier operationalises it
- Affluential Explorers — Rosewood's target guest
- Discovery — the 2025 rebrand keyword. Microcopy: "Threading the dossier…" not "Loading…"
- Hold the Thread — the guest-set Privacy Openness Score (0–10); concierge mirrors read-only
- No cookie-cutter — the property switcher is the demo of this principle
- Not a CRM (CRMs store what the hotel already knows; Red Thread is the substrate that adds context)
- Not a chatbot (chatbots wait for the guest to ask; Red Thread is always-on, agentic)
- Not a marketing tool (Discretion Layer's job is to make sure it never feels like one)
- Not Rosewood-exclusive in code (the brand-specific bits are in
data/and the prompts; the framework is property-agnostic)