Rehearsal Room
Model a person (personality type + situation) and 2–3 intervention options — an activity, a phrasing, a choice — and see how each might land, side by side, before it's real. Public + throttled. North star: a decision-testing marketplace.
Hook (general audience): "Got a big conversation you're dreading? Rehearse it first. Real personalities, real choices — see how it might land." The flagship seed scenario is the ur-dreaded conversation, "We need to talk."
Honesty posture (load-bearing)
A rehearsal, not a prediction. An LLM persona is a plausible-fiction generator, not a validated model of a real human. Every reaction is framed as one plausible response; no diagnosis, no "what they will do," no clinical claims (same wellness-not-clinical line as Throughline). This keeps it defensible and is truer to the tech.
How it works
It's the AI-persona engine pointed sideways: instead of "let this persona
journal," it's "react in character to each of these approaches." Reuses
MBTI_WRITING_STYLES + an OCEAN-style tendency description
(apps/backend/src/services/ai-personas.ts) to prompt the reaction via the
cost-logged complete() shim (feature_key: 'rehearsal_room.reaction' →
/dashboard/admin/llm-spend).
v1 surfaces (shipped)
/rehearsal-room— the public tool: define a person (archetype + situation), add ≤3 options, Run → side-by-side reactions (reaction · reads-as · watch-for). Saved runs + history (byanon_tokenin localStorage, or account). Share link.- Public, anonymized gallery — runs go in public by default so the
homepage becomes a real, searchable feed ("conversations people are
rehearsing") of real runs + curated examples. Privacy: before modeling, a
prep step (
prepareForPublic) swaps real person NAMES for consistent pseudonyms across every field and generates topic tags; modeling runs on the sanitized inputs so reactions are public-safe too. The UI tells the user (heads-up before Run + a "names swapped: Sam → Riley" notice after). Gallery supports?q=search +?tag=filter; cards show tags. Opt-out: a "keep this private" toggle (keep_private) skips pseudonymization entirely and stores the run owner-only with real names —is_public=false, noshare_slug, no tags, never in the gallery. The share endpoint refuses to publish such a run (it has real names); the user must re-run it publicly to anonymize. - Saved people ("remember this person") — a private library so users don't
re-describe recurring people. A "Remember this person/character for next time"
toggle saves the ORIGINAL (un-pseudonymized) description; a "Your saved people"
picker loads them back. Migration
366_rehearsal_personas.sql; owner-only (user_id or anon_token). Personas CRUD:GET/POST /personas,DELETE /personas/:id; the run endpoint also saves whenremember:true. Anon→account:POST /claimattaches anon_token-keyed runs + saved people to the account on signup/login; the tool sends the auth token on every call so signed-in users' data is keyed byuser_id(proper ownership + cross-device). /rehearsal-room/shared/:slug— read-only shared/gallery run.- Public, throttled: per-IP daily cap (
rehearsal_rate_limits), input caps (≤3 options, situation ≤1000, option ≤500), gpt-4o-mini only, + asite_settingsadmin kill-switch (rehearsal_room_throttle, normal|paused).
Data: 364_rehearsal_room.sql — rehearsal_runs (persona/options/results
jsonb, user_id or anon_token, share_slug) + rehearsal_rate_limits.
365_rehearsal_room_tags_public.sql — tags text[] + GIN/public indexes +
is_public default true. RLS-enabled, service-role access only.
Backend: routes/rehearsal-room.ts (run / runs / share / gallery / archetypes),
services/rehearsal-room.ts, data/rehearsal-gallery.ts.
Frontend: app/rehearsal-room/{page,layout}.tsx + shared/[slug],
components/rehearsal-room/{RehearsalResults,RehearsalHistory,RehearsalGallery}.tsx,
OG app/api/og/rehearsal-room/route.tsx.
Verticals (the same engine, different doorways)
One config-driven engine (components/rehearsal-room/RehearsalTool.tsx +
mode on the backend) powers all verticals; each page passes its own copy + seed.
- General audience — dreaded conversations. The homepage hook. (shipped)
- Work / office —
/rehearsal-room/work(mode='life'): rehearse a hard conversation with a manager, colleague, or direct report before the meeting. Own copy/seed + OG. (shipped) - Therapists — rehearse an intervention for a client type. Cross-linked from
/for-therapists(Throughline). (shipped: cross-link) - Writers — "how would your character act in this scene?" (shipped)
/rehearsal-room/writers—mode='writer': a character is the persona, a scene is the situation. Own copy/seed + OG card (Bluesky-ready) + a story-universe cross-sell (WritersCrossSell) into HiveJournal / write.cafe / Graphene after a run. Likely the highest-leverage vertical given the founder's Bluesky reach. Thevillain-shown-mercygallery example renders in writer framing.
Writers vertical — roadmap (what's left)
Shipped: the writers landing/mode, character-framed modeling, the OG hook, the
cross-sell hand-off, and the write.cafe plugin — a collapsible
RehearsalCharacterPanel
embedded in the entry composer's options drawer (test a character mid-draft,
"use my draft as the scene", private-by-default, saved-characters picker) +
a "🎭 Rehearse a character" CTA on the /write-cafe home.
Story-universe capture (shipped): the writers cross-sell
(WritersCrossSell)
captures genre + premise and seeds the hand-off — the HiveJournal / write.cafe
cards deep-link the composer prefilled (?prompt=) with a "journal as this
character" starter (+ source=write-cafe); the captured world personalizes the
copy and is sent on the cross-sell analytics event. Writer-mode runs also skip
name pseudonymization (character names are fictional — writers keep them; tags
still generated).
Roadmap now clear; future depth (a persistent story bible / multi-character universe) is a larger feature, not a v1 gap.
- This already makes Rehearsal Room a top-of-funnel acquisition tool for the whole story ecosystem, not just a standalone toy.
Deferred (later)
Reusable saved persona/character library; richer builder (OCEAN sliders);
non-therapy/non-writer verticals (managers, parents, negotiation); the public
gallery growing from curated → real shares; anon→account claim polish (set
user_id where anon_token matches on signup).