A guest asks at 2 AM. The Concierge checks live availability, quotes the exact price, holds the room, and points at the resort's own pay page — in English or Bengali — while the front desk sleeps. Anything it can't verify goes to staff with the full transcript. One product, many resorts: every property gets its own rooms, booking backend, knowledge, voice, staff scope, and logins.
Video lands here before launch — script is ready (Hook → live quote → draft → pay pointer → handoff → CTA).
flowchart LR
G[Guest on resort site] -->|"<script> widget"| W[Chat panel]
W --> A[Muse Spark agent]
A --> T1[get_quote / draft]
A --> T2[lookup / verify]
A --> T3[handoff to staff]
T1 --> S[Resort's Suite backend]
A --> R[(Property knowledge)]
T3 --> B[Staff board / bridge API]
A --> P[Resort's own pay page]
style P stroke-dasharray: 5 5
Built for the Meta Global AI Developer Hackathon (Startup track, "Build for Your World"). Pre-existing: Vivereply Suite and the demo1 resort site. Built during the hackathon (Oct 26 – Nov 4, 2026): this Concierge service — multi-tenant chat, agent tools, quote/rate layer, staff page + bridge + widget, settings, onboarding/RAG, and the auth core. The demo properties are fictional, not real resorts.
- Chat (
/) — bilingual English/Bengali. Language follows the guest's latest real language; the taka sign (৳) alone never flips it. Signed-in owners chat with their own resort; visitors get the demo. - Agent tools, via Muse Spark on the Meta Model API — the model
acts, it doesn't just answer:
check_availability— live units from the property's backendget_quote— exact quote from catalogue prices + rate rulescreate_booking_draft— holds a room (PENDING, no payment), only after the guest confirms the quote and gives name, email, phonerequest_handoff— passes the conversation to staff with a summarylookup_booking_by_code,request_booking_details,verify_booking— the privacy-safe verification loopget_resort_payment_link— deep-links the resort's OWN pay page with?booking=CODEafter a draft (points, never takes)
- Cards — quote, draft (with booking code, plus a "Suite total governs" warning if it ever differs from the quote), resort-pay pointer, and handoff cards, built only from that turn's tool logs.
- Tenants — each resort is isolated by property: rooms, backend, knowledge, voice, staff, logins. Owners touch only their property (cross-property reads answer 404). Resorts join via public signup (email verification + Turnstile spam defense); configuration stays locked until the email is verified.
- Guest payments (the bot points, the resort collects) — after a
draft the chatbot sends guests to the resort's own payment page
with the booking code prefilled. Guest money never touches our
systems; the bot can never say whether a guest paid (known only on
the resort's site). The owner pastes one URL in Settings. See
docs/payments.md. - Suite backends — any backend implementing
docs/suite-api-spec.mdplugs in per property (URL + encrypted key + catalogue, Test connection, failure health). Test properties run on the labeled fixture backend (FIX-codes, deterministic, in-memory). - Staff page (
/staff) — triage board for one property's handoffs: status filters with counts, compact list, one transcript at a time, notes, assignment, resolve/reopen. Login-protected, owner-scoped, verified email required. - Staff bridge (server API) — client dashboards list, read, assign,
close, and annotate handoffs as JSON, guarded by per-property keys
minted in Settings. See
docs/bridge-integration.md. - Staff widget (iframe) — the staff loop embeddable in a client dashboard: the dashboard server mints a 15-minute origin-bound token per page load and renders the iframe. The bridge key never reaches the browser.
- Settings (
/settings) — the single configuration surface, one property at a time: chatbot name + topics, widget codes, domains, bridge codes + widget snippets, keys, booking system connection (incl. your payment page URL), catalogues, security board, profile- password change. Secrets and Appearance are honest Later rooms. There is no separate admin page: super-admins additionally see Tenants (cross-property owners, suspend/resume) and Onboarding re-runs (approvals) at the top.
- Onboarding (
/onboarding) — one budgeted run per property builds its vector knowledge from a business form, website, and uploads. A Modal Python worker owns fetch/parse/chunk/embed/store behind SSRF guards; chunks stay invisible until clean completion. One run per client unless super-admin approves more. Seedocs/onboarding.md. - RAG grounding (supporting only) — property questions are answered from that property's indexed knowledge via pgvector + Gemini embeddings, similarity ≥ 0.45, top 4. Below threshold the agent says it does not know and offers a handoff. Prices and availability never come from RAG — the backend tools govern those.
Never invent a room, price, availability, booking, or property fact. A quote is shown before any draft; one draft per conversation; the Suite total governs on any mismatch; anything unknown goes to staff. Verification shows dates + first name + status by code, and nothing by contact — details go only to the email already on the booking.
Independent resorts with no night front desk lose guest enquiries that arrive after hours, and a wrong answer costs more than silence. The Concierge answers at night and holds booking value: one held Terracotta Villa draft for 2 weeknights = ৳14,500 × 2 = ৳29,000 of booking value that an unanswered enquiry would have lost. Presented as booking value held, never as profit or ROI — no pilot has run yet, so no conversion rates, revenue, or testimonials exist or are claimed. The demo (a real property, real data, real prices) is proof point #1; a pilot is next.
Muse Spark (muse-spark-1.3 via the Meta Model API) IS the product's
decision-maker: every guest turn runs a hand-rolled tool-use loop
(lib/agent.ts, max 6 rounds) where the model picks room-scoped tools
built per request from the property's own catalogue, reads live Suite
results, and either answers or calls again. Model budget: max_tokens
2048 with tools / 1024 plain — generous because Muse Spark reasons
first and reasoning tokens count against the cap (observed: 337
reasoning tokens for one word). Token usage (prompt/completion/
reasoning, per backend) is logged on every call; pnpm eval:dashboard
aggregates it. On primary failure the same payload retries exactly
once against Baseten-hosted Muse Spark Open
(meta-models/muse-spark-open-1026) — same tools, same guards,
different weights — and every fallback serve is logged. Data-flow
consequence, stated plainly: on fallback turns, guest text flows to
Baseten instead of Meta.
Next.js (App Router) + TypeScript + Tailwind. Supabase Postgres + Prisma 7 + pgvector, the Concierge's own database — it never shares a Suite database, it talks to backends over HTTP only. Answering model: Muse Spark via the Meta Model API (see above). Embeddings only: Gemini gemini-embedding-001 at 768 dimensions (the Meta API lists no embedding model). Ingestion worker: Modal (Python, CPU).
cp .env.example .env.local # then fill it in; never commit .env*
pnpm installEnvironment (see .env.example for the full list):
META_API_KEY— Meta Model API key (startsLLM_)BASETEN_API_KEY— optional fallback backend (demo insurance only)META_API_BASE_URL,META_MODEL— provider endpoint + agent modelSUITE_API_BASE_URL— demo Suite deployment (demo1, fallback)DEMO_PROPERTY_ID— demo property slugDATABASE_URL— Supabase transaction pooler (port 6543, runtime)DIRECT_URL— Supabase session pooler (port 5432, migrations)GEMINI_API_KEY— Google AI Studio, embeddings onlyADMIN_EMAIL+ADMIN_PASSWORD— founder bootstrap login (super-admin)SESSION_SECRET— signs sessions + tokens (≥16 chars; unset in production fails login and chat closed)BINDING_SECRET_KEY— encrypts backend keys at rest (≥32 chars)SUPABASE_URL,SUPABASE_SERVICE_KEY— Storage signing (server only)WORKER_SHARED_SECRET,MODAL_WORKER_URL— RAG worker auth + endpoint- Seed-tenant logins live inline in
prisma/seed-tenants.mjs(test tenants only — seedocs/seed-users.md); no password env vars
Then, in order:
# 1. Enable pgvector in the Concierge Supabase project:
# Dashboard → SQL Editor → CREATE EXTENSION IF NOT EXISTS vector;
pnpm exec prisma generate
pnpm exec prisma db push # uses DIRECT_URL via prisma.config.ts
# 2. Register tenants: fill owner emails + passwords in
# prisma/seed-tenants.mjs (see docs/seed-users.md), then
pnpm seed:tenants # needs database access
pnpm dev # http://localhost:3000Standalone scripts must import lib/env first, before lib/prisma —
tsx does not load .env files by itself, and the Prisma Pool is
created at import time.
pnpm test # unit tests: rates, language, cards, tool guards, prompts, resort-pay, setup
pnpm typecheck # strict tsc
pnpm eval # 19-case live referee against the fixture backend (needs API keys)
pnpm probes # 10 tenant-isolation probes against a live app (needs tenant creds)pnpm test needs nothing but node: it runs tests/*.test.mjs on the
real TypeScript modules via node's type-stripping — no framework, no
database, no network. Anything needing those lives in eval/ or
probes/, not in tests/.
<script src="https://<your-concierge-host>/widget.js" data-key="<property-key>"></script>That one script tag adds a floating Chat button and an iframe panel
loading /widget?key=… from the Concierge host — the same chat as /,
in compact mode. The script derives its origin from its own src, so it
works on localhost and on a deployment with no code change. The key is a
public label, not a secret: each seeded property has its own key (see
Settings → Chat widget codes); an unknown key shows a "not configured"
panel, never a guessed property.
- Logins: owners join via public signup (email verification +
Turnstile spam defense; configuration locked until verified) and
sign in with email + scrypt password. Seed tenants carry inline
test passwords (committed deliberately, rotation forced at first
human login — see
docs/seed-users.md). The founder bootstrap isADMIN_EMAIL+ADMIN_PASSWORD(env, checked first). Sessions are signed cookies bound to server-side rows — revocable per session (logout), per user (password change, owner kill-switch), with 7-day absolute + 24-hour idle expiry. - Login throttle: 5 failures/15 min per email or 20 per IP → 429.
- Approved domains (Settings): only hosts listed there (and still active) can embed the widget for a property. Deactivating a domain stops its widget on the next chat message.
- Widget token handshake:
widget.jsasks/api/widget-tokenfrom the host page, and the browser attaches the host page's Origin. Only an approved origin gets a short-lived signed token, and/api/chatverifies that token — and re-checks domain approval — on every message. The token's propertyId governs; a propertyId sent by the browser is ignored./issues a direct token server-side and needs no approved domain. - Bridge: per-property keys, hashed at rest, revocable instantly, never in the browser. Staff widget tokens live 15 minutes and are origin-bound (strict Referer check at iframe load).
Honest limits: no 2FA, no password reset-by-email, no per-tenant
spend quotas yet. Chat is throttled per property (60 turns/min) and
per IP (30/min) → 429 + Retry-After. Clickjacking: middleware sets
frame-ancestors 'self' everywhere except /widget and
/staff/embed, which rely on token + origin binding instead (they
must embed on client sites). Admin actions land in the audit log
(Settings → Recent activity, super-admin only). Localhost is approved
automatically in development only.
Post-hackathon: Supabase Auth migration, 2FA, per-domain
frame-ancestors, spend quotas.
journey
title 2 AM guest, zero staff online
section Chat
Ask for sea-facing room: 5: Guest
Exact quote in seconds: 5: Concierge
Confirm + give details: 4: Guest
Room held, code issued: 5: Concierge
section Pay & verify
Pay on resort's own page: 4: Guest
Booking lookup stays private: 5: Concierge
section Edge cases
25-guest wedding → staff: 5: Concierge
- Sea-facing room, 2 nights, ~৳15,000/night → quote card, then confirm + details → draft card with booking code.
- Bengali cottage conversation + halal-food question → grounded Bengali answer.
- 25-person group rate → no invented rate, handoff card, and the
case appears at
/staffwith the transcript. - Second property (Seabreeze, fixture backend): Harbor Suite 2 nights
→ ৳25,000 quote →
FIX-draft → lookup round-trip — then confirm neither property sees the other's data.
Copyright (c) 2026 ViveScript Solutions. All rights reserved — see
LICENSE. Source is visible for hackathon evaluation; reuse requires
written permission.
