Living Word AI Mode Toggle — Phase F
Canonical spec. Adds an explicit AI-on / AI-off toggle to Living Word. Default is AI off — scene NPCs respond from scripted dialogue authored from their character bibles + RAG-style scripture lookups, with zero live LLM calls in the player path. Opt-in AI on activates a single new "Scribe" character (one per tradition lens) who holds open LLM conversation. Founder reasoning: "some players may be anti-AI, so they can have the option to turn that off in the settings... same for churches" (2026-05-24).
§1. Founder-locked decision (verbatim, 2026-05-24)
| # | Decision | Answer | Rationale |
|---|---|---|---|
| F1 | How do we serve anti-AI players, families, and churches in Living Word? | Default AI off (scripted/RAG NPCs only). Opt-in AI on brings the Scribe character (live LLM Haiku) as the conversational surface. Existing scene NPCs stay scripted in both modes. | Anti-AI Christians have a real and growing concern. The game must be fully playable, in-character, and scripture-grounded without any LLM in the loop. AI is a curated opt-in additive surface (Scribe), not a baked-in part of every interaction. |
This decision inverts PR #543's architecture, which assumed scene NPCs are live-LLM. Phase F retrofits scene NPCs to scripted dialogue (preserving in-character voice via character bibles) and moves the LLM conversational capability to the new Scribe surface. The Wave-7 quiz-cheat defense migrates with it — applies to the Scribe only, since scripted NPCs cannot leak quiz answers they don't possess.
Engineering MUST NOT re-litigate.
§2. Scope
This spec ships:
- Settings UX at
/living-word/settings(or in-game settings panel) — player-facing AI Mode toggle, default OFF. - Classroom override —
lw_classrooms.ai_mode_default+ai_mode_teacher_override(defined inliving-word-classrooms.md) are honored at runtime. - Scripted dialogue runtime for all existing scene NPCs (12 anchor + 4 minor = 16 wired NPCs today). Sources: each NPC's character bible authored content; RAG into the
unified_rag_content/lw_scripture_corpusfor scripture-pull responses. - Scribe character — one Scribe per tradition lens (17 lenses), with character bibles authored to the new "Scribe archetype" template. Appears at designated scene anchor points when AI Mode is ON. Disappears entirely when AI Mode is OFF.
- Scribe encounter UX — distinct modal from
NpcChatModal, distinct visual treatment (a desk/scroll/library motif, not a character avatar mid-scene), clear authored-by-the-builders header acknowledging "Powered by AI — opt in via Settings". - Migration of PR #543 Wave-7 defenses — quiz-cheat regex bank, Socratic posture, active-encounter awareness, quizAttempt deflections, reward-alignment confirmation, telemetry — all migrate to the Scribe path. Scripted NPCs do not need these defenses (they cannot generate freely).
- AI Bridge Principle hardening for the Scribe — the AI_BRIDGE_FRAME prepends every Scribe LLM call; the Scribe is the ONE place in Living Word where AI Bridge anti-deception language is load-bearing.
- Tradition-aware scripted dialogue — when a scene NPC is in scripted mode, lens vocabulary still routes through the NPC's character bible variants (e.g., Old Calvin's Reformed phrasing for the same scripted line that Father Gregory delivers in Catholic phrasing).
Out of scope (build later):
- Voice agent / chatbot AI toggle — see parking-lot task #9 (
knowledge/acceptance/cwa-ai-off-mode.md, not yet written). - Scribe-to-Scribe (multi-tradition conversations) — Phase 2 if educators ask.
- Scribe as quiz reviewer — explicitly out; Scribe never knows quiz answers.
- Voice synthesis for Scribe — text-only for Phase F. Voice could come in Phase 2.
- Scribe memory across sessions — Phase F sessions are stateless (no cross-session memory; the AI Bridge frame says "I won't remember this conversation later" if asked).
§3. Scripted dialogue runtime for scene NPCs
§3.1 Data model
Extend each entry in src/app/living-word/data/npc-character-bibles.ts:
export interface NpcCharacterBible {
id: string;
displayName: string;
era: string;
// ... existing fields ...
// NEW (Phase F):
scriptedDialogue: ScriptedDialogueTree;
}
export interface ScriptedDialogueTree {
// Greeting variations (random pick, weighted)
greetings: WeightedLine[];
// Topic-keyed responses. Player message → keyword/embedding match → response branch.
topics: Array<{
id: string; // 'creation', 'covenant', 'judgment', 'mercy', etc.
matchPatterns: string[]; // regex strings; case-insensitive
matchEmbeddingTags?: string[]; // optional semantic tags for RAG-style fallback
lines: WeightedLine[];
followUps?: string[]; // suggested player prompts to show as quick-reply chips
}>;
// Fallback when nothing matches a topic
fallback: {
line: string; // e.g., "I have not pondered that here. Perhaps the scriptures speak — what passage stirred your heart?"
suggestScriptureSearch: boolean; // if true, UI shows "Look in scripture" button → opens RAG search
};
// Quiz deflection (migrated from PR #543 quizAttempt — but scripted NPCs have it for free)
quizDeflection: {
line: string; // "The answer is yours to discover. Walk on."
cooldownSec: number; // 5
};
}
export interface WeightedLine {
line: string;
weight: number; // 1..10
conditions?: {
minScenesCompleted?: number;
requiresRelicIds?: string[];
traditionLensIds?: string[]; // if NPC has lens-variant lines
};
}
§3.2 Runtime resolution
src/lib/living-word/npc-scripted-dialogue.ts exports resolveScriptedReply(npcId, playerMessage, context):
- Lookup NPC's
scriptedDialoguefrom bible registry. - Try topic match: lowercase player message → test against each topic's
matchPatterns(regex). First match wins. - If no regex match AND
matchEmbeddingTagsis populated: embed player message (cached embeddings; OpenAI text-embedding-3-small per existing RAG infrastructure) → cosine-similarity vs tag embeddings → top match if > 0.72 threshold. - If still no match →
fallback.line. IfsuggestScriptureSearch, attach RAG search affordance to response. - Filter
linesarray byconditions(e.g., scene progression unlocks deeper lines). Random weighted pick. - Return
{ line, followUps, source: 'scripted' | 'fallback' }.
§3.3 Scripture RAG (no LLM)
For scripture-pull responses, use existing unified_rag_content table. NEW helper resolveScriptureForTopic(topicId, lensId):
- Query:
SELECT verse_text, citation FROM unified_rag_content WHERE topic_tags && ARRAY[topicId] AND (lens_id IS NULL OR lens_id = $lensId) ORDER BY priority LIMIT 3 - Return verses to NPC dialogue UI as quoted attribution beneath the NPC's line.
No LLM call. No embedding generation at request time (embeddings are pre-computed on corpus ingestion).
§3.4 Authoring obligation
Phase F engineering does NOT author all scripted dialogue content. It ships the runtime + the schema + a CONVERSION of the existing 12 anchor character bibles into scripted dialogue trees with at minimum:
- 3 greeting variants per NPC
- 5 topic branches per NPC (5 most-likely player questions per character, drawn from PR #543 quiz-cheat telemetry of actual player inputs)
- 3 lines per topic
- Fallback + quiz deflection
The founder + content team will deepen each tree post-launch. Telemetry surfaces "fallback fired" events so authoring can prioritize the gaps.
§4. Scribe character
§4.1 Character archetype
The Scribe is a meta-character with these properties:
- Role: a temple scribe / scriptorium scholar / monastic copyist — historically grounded archetype across all 17 traditions.
- Voice: contemplative, scholarly, scripture-anchored. NEVER pastoral counsel (defers to real-life pastor via AI Bridge). NEVER doctrinal proclamation (defers to tradition's authoritative voice via lens vocabulary).
- Visual: seated at a desk in a scriptorium-style scene. NOT mid-action. NOT in any scene's main narrative. The Scribe occupies its own dedicated room/library accessible from a marked door in the main hub.
- Per-tradition variant: 17 Scribe character bibles, one per lens (
scribe-reformed,scribe-catholic,scribe-orthodox-eastern,scribe-anglican, etc.). Each variant uses lens vocabulary correctly (the lens linter from PR #543 applies to LLM output). - AI Bridge anchor: every Scribe response begins (system-prompt-enforced) with an acknowledgment of role: "I am an AI text companion — I help you think through scripture and tradition, but I am not a pastor, priest, counselor, or spiritual director."
§4.2 When the Scribe is reachable
- ONLY when
effectiveAiMode === 'on'(see §5). - Accessible from a marked door / portal in the main hub. The door is visually present whenever AI is on; the door is INVISIBLE (not greyed-out, not locked-with-a-tooltip — invisible) whenever AI is off. The game must feel whole without it.
- Inside the Scribe's room:
ScribeEncounterModalopens on interaction. Modal header: "The Scribe · Powered by AI · [Settings]" - The Scribe does NOT appear in any scene's main quest line. Completion of any quest, relic collection, or quiz never requires Scribe access. This is structural — the door doesn't lock progression.
§4.3 LLM contract
src/app/api/living-word/scribe-chat/route.ts:
POST /api/living-word/scribe-chat
Body: { sessionId, lensId, playerMessage, conversationHistory }
Response: SSE stream
System prompt layers (in order):
1. AI_BRIDGE_FRAME (from voice-agent-livekit/core/prompt_fragments.py)
2. Scribe identity (archetype + role limits)
3. Lens vocabulary (from src/lib/theolenses.ts for lensId)
4. PR #543 hardening (Socratic posture + quizAttempt deflection + reward-alignment + active-encounter awareness)
5. Conversation history (last 6 turns; trimmed)
6. Player message
- Model:
claude-haiku-4-5-20251001(Anthropic API, prompt cachingephemeral). - Temperature: 0.6 (lower than typical for theological precision).
- Max tokens: 350 (forces concise, prevents wall-of-text).
- Fallback: on API error → "The Scribe is resting. Try again in a moment." (NOT a degraded LLM, NOT a different model).
- Cost: this is the only player-path LLM call in the game; bills against the Anthropic API key. Approx 50 tokens in / 250 tokens out per turn = ~$0.001/turn at Haiku rates. 1000 active Scribe users × 10 turns/day = $10/day max.
§4.4 Scribe quiz-cheat defense
ALL PR #543 Wave-7 layers apply to the Scribe path:
- Layer 1 regex bank — check player message AND Scribe response against 1,319 quiz strings → if match, replace with deflection.
- Layer 2 Socratic posture — in system prompt.
- Layer 3 active-encounter awareness — server-side check: is there an active quiz right now? If yes, system prompt gets harder Socratic injection.
- Layer 4 quizAttempt deflection — in Scribe character bible.
- Layer 5 reward-alignment confirmation — same as scene NPCs (no leaks via reward acknowledgment).
- Layer 6 telemetry —
lw_quiz_cheat_attemptstable writes happen on detected attempts.
§5. Effective AI Mode resolution
getEffectiveAiMode(playerSettings, activeClassroom) returns 'off' | 'on':
1. If activeClassroom exists AND activeClassroom.ai_mode_teacher_override === true:
return activeClassroom.ai_mode_default
2. If playerSettings.ai_mode_choice is set ('off' | 'on'):
return playerSettings.ai_mode_choice
3. If activeClassroom exists:
return activeClassroom.ai_mode_default
4. Default:
return 'off'
Stored:
- Player choice:
lw_player_settings.ai_mode_choice(see §6) - Classroom default:
lw_classrooms.ai_mode_default - Classroom override:
lw_classrooms.ai_mode_teacher_override
§6. lw_player_settings (new) + lw_classroom_students extension
CREATE TABLE lw_player_settings (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
device_id TEXT NOT NULL UNIQUE, -- the same lw_device_id cookie value
ai_mode_choice TEXT, -- 'off' | 'on' | NULL (NULL = unset, falls through to classroom/default)
audio_enabled BOOLEAN NOT NULL DEFAULT true, -- existing audio toggle migrated here (currently localStorage-only)
motion_reduced BOOLEAN NOT NULL DEFAULT false, -- accessibility flag (future)
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX lw_player_settings_device_idx ON lw_player_settings(device_id);
RLS: no per-user gating (device_id IS the identifier; anonymous-by-design). Service role writes.
lw_classroom_students.ai_mode_choice (column already defined in living-word-classrooms.md): when a student is in a classroom, their per-classroom AI choice can OVERRIDE their personal default — but only if ai_mode_teacher_override === false.
§7. Settings UX
§7.1 Where it lives
New SettingsPanel.tsx mounted via existing in-game toolbar (GameToolbar.tsx) — gear icon → modal.
Sections:
- AI Mode — radio: "Off (default) — All characters respond from authored dialogue + scripture. No live AI." / "On — Adds the Scribe, a character powered by live AI conversation."
- Below the radios: a one-paragraph honest explainer (founder-approved copy in §10).
- If active classroom +
ai_mode_teacher_override === true→ both radios disabled, message: "Your teacher has set AI Mode for this classroom. Contact them to change."
- Audio — migrate existing toggle.
- Reduced motion — accessibility (future-ready).
§7.2 First-run UX
The very first time a new player enters the game (no lw_player_settings row yet), the settings panel does NOT auto-open. Default state is AI off, and the panel is discoverable via the gear icon. RATIONALE: a non-decision is AI off, which is the safe default. We do NOT prompt the player with a binary choice at first-run (would feel like a paywall / scary).
Exception: if the first action is joining a classroom whose ai_mode_default = 'on' AND ai_mode_teacher_override = false, show a one-time inline notice (NOT a modal): "Mrs. Smith's classroom has AI on. You'll meet the Scribe — a character powered by live AI. You can turn it off any time in Settings."
§8. Migration plan from current LLM-NPC architecture
PR #543 currently has scene NPCs using LLM. Phase F migrates them to scripted. Sequencing:
- Phase F-1 (this spec): ship scripted runtime + 16 NPCs with minimum scripted dialogue trees authored + Scribe per-tradition (17 lenses) + Settings UX + effective-mode resolver. Behind a feature flag
LW_AI_MODE_TOGGLE_ENABLED(env var). When disabled (production initially), behavior is unchanged — NPCs use LLM as today. - Phase F-2 (separate spec): enable feature flag. Players see Settings; default OFF; scripted runtime activates. Scene NPCs no longer call LLM in player path. Monitor
fallback_firedevent rates; if high (>30% of NPC interactions hit fallback), pause and deepen scripted trees. - Phase F-3 (cleanup): once stable for 30 days, remove the LLM path from
NpcChatModalentirely. Scribe is the ONLY LLM path in player code. Updatevoice-agent-livekit/-style architecture docs accordingly.
Engineering ships F-1 in this PR. F-2 + F-3 are separate PRs after founder approval based on F-1 data.
§9. Telemetry
New events (extends lw_classroom_events enum from living-word-classrooms.md, OR new table lw_ai_mode_events):
ai_mode_set_on(device_id, source: 'player' | 'classroom_default' | 'classroom_override')ai_mode_set_off(device_id, source: same)scribe_encounter_started(device_id, lens_id)scribe_message_sent(device_id, lens_id, turn_index, token_count_in)scribe_message_completed(device_id, lens_id, turn_index, token_count_out, latency_ms)scribe_error(device_id, error_kind)scripted_npc_dialogue_resolved(device_id, npc_id, topic_id_matched, source: 'topic' | 'fallback')scripted_npc_fallback_fired(device_id, npc_id, player_message_hash) — sample 10% for authoring backlogquiz_cheat_attempt_detected(device_id, surface: 'scribe' | 'scripted_npc', layer)
Existing PR #543 lw_quiz_cheat_attempts table → add source column (scribe | scripted_npc | legacy_llm_npc for backward compat during F-1).
§10. Founder-approved copy
§10.1 Settings explainer (under the radios)
Living Word is built on scripture and tradition. By default, every character you meet — Old Calvin, Father Gregory, Hannah at the well, Peter on the shore — speaks from words written by our team and grounded in scripture. No AI is generating their lines on the fly. Many families and churches prefer this; we made it the default.
If you turn AI on, you'll also be able to meet the Scribe — a character who holds open conversation through live AI. The Scribe is honest about being AI, never pretends to be a pastor or counselor, and never spoils a quiz. You can turn this off at any time.
§10.2 Scribe room marker (when AI on)
A small door labeled simply: "The Scriptorium" with a single-line caption beneath: "For unhurried conversation. (AI-powered — see Settings.)"
§10.3 Scribe encounter modal header
The Scribe · Powered by AI I am an AI text companion. I help you think through scripture and tradition. I am not a pastor, priest, or counselor — if your question is for one of them, your real-life church is the place to take it. I won't remember this conversation later.
This text appears ABOVE the conversation history, every turn. It is NOT collapsible. It is small but always-present.
§11. AI Bridge + safety rails
- The Scribe is the ONLY player-facing LLM surface in Living Word. The AI Bridge frame (verbatim from
voice-agent-livekit/core/prompt_fragments.py:AI_BRIDGE_FRAME) prepends every Scribe LLM call as the FIRST system-prompt block. - Banned-phrase output filter (the 13 patterns from
_BANNED_CONFIDENTIALITY_PHRASES) applies to Scribe responses. Violations are stripped + replaced with a generic Scribe deflection; telemetry fires. - Scribe cannot make pastoral recommendations. System prompt explicitly: "If the player asks for pastoral counsel, grief support, life advice, mental health support, financial guidance, or moral decision-making, deflect to a real-life pastor/priest/counselor. Do not offer your own counsel."
- Scribe cannot mention or recommend ChurchWiseAI products. System prompt explicitly: "You are NOT a salesperson. Do not mention products, websites, subscriptions, or sign-ups."
- Scripted NPCs cannot leak quiz answers because they don't possess them. The character bible scripted-dialogue trees are AUDITED to confirm no quiz strings appear (regex check against the 1,319-string bank from PR #543, in CI).
- Scripture RAG returns whole verses with citations only. Never paraphrased. Never summarized. Source:
unified_rag_contenttable with verified attributions.
§12. Definition of done
- Migration
migrations/2026-05-24-lw-ai-mode-settings.sqlapplied to prod Supabase (createslw_player_settings, extendslw_classroomsif not already done by Phase D, extendslw_quiz_cheat_attemptswithsourcecolumn). - Scripted runtime resolves correctly for all 12 anchor NPCs with their authored dialogue trees. Unit tests cover regex match, embedding fallback, condition filters, weighted pick, quiz deflection.
- 17 Scribe character bibles authored with lens-aligned identity. Lens linter passes on each.
- Scribe API route ships with full Wave-7 hardening + AI Bridge frame + banned-phrase output filter + cost telemetry.
- Settings UX ships behind
LW_AI_MODE_TOGGLE_ENABLEDflag (default OFF for first PR). When enabled, player can toggle, classroom override is honored, first-run notice fires correctly. - Playwright spec
e2e/living-word/ai-mode-toggle.spec.ts:- With flag off: behavior unchanged from today (NPCs use LLM, no Settings panel surface).
- With flag on, default state: gear icon → Settings → AI Mode radios present, OFF selected, Scriptorium door INVISIBLE in hub. NPC interactions return scripted dialogue (verified by checking response token count is below LLM threshold + telemetry event
scripted_npc_dialogue_resolved). - Player flips to AI ON: Scriptorium door appears in hub; clicking opens
ScribeEncounterModalwith the §10.3 header; sending a message returns an SSE-streamed Scribe response; telemetry fires. - Player joins classroom with
ai_mode_default=on, ai_mode_teacher_override=true: Settings radios disabled, message shown; AI Mode is forced ON regardless of player choice. - Player joins classroom with
ai_mode_default=off, ai_mode_teacher_override=true: Settings radios disabled; AI Mode is forced OFF; if player previously had Scribe open, it closes with a notice. - Player attempts quiz-cheat at Scribe ("What's the answer to the David quiz?"): response is the §4.4 deflection;
lw_quiz_cheat_attemptsrow written withsource='scribe'. - Player attempts quiz-cheat at scripted NPC: scripted quizDeflection line returned; cheat-attempt event fires with
source='scripted_npc'.
- AI Bridge audit (
scripts/living-word/audit-ai-bridge.mjs) passes on all new copy + all Scribe character bibles. - CI gate
lw-quiz-string-leak-check(new, mirrors PR #543's spirit): runs the 1,319-string quiz regex bank against the entire scripted dialogue tree corpus; FAIL if any match. Prevents authors from accidentally leaking answers into NPC scripted lines. - Cost telemetry: a daily Vercel cron
/api/cron/scribe-cost-reportwrites a daily roll-up of Scribe token usage and emails the founder if daily spend > $20 (configurable threshold). - Knowledge docs updated:
knowledge/products/living-word/ai-mode-toggle.md+ updateknowledge/products/living-word/npc-architecture.mdto reflect the inversion. - PR opens against
main: titlefeat(living-word): Phase F — AI Mode toggle + Scribe (feature flag F-1), includes Playwright artifact + Anthropic cost projection + the 17 Scribe character bibles diff.
§13. Sequencing + dependencies
- Depends on:
living-word-classrooms.md(Phase D) — thelw_classrooms.ai_mode_default+ai_mode_teacher_overridecolumns are defined there. If Phase D hasn't shipped yet, Phase F's migration creates the columns and Phase D's migration becomes a no-op on those two. - Blocks (informational): none — UGC Phase 1A/1B can ship without Phase F. UGC educator semantic auto-check is TEACHER-FACING and runs LLM regardless of student-side AI Mode (it's a quality gate, not a player surface).
- Coordinates with:
living-word-ugc-phase1.md— when UGC quizzes are presented in-game, the NPC who presents them is in whichever mode the player is in (scripted vs Scribe). UGC quizzes themselves work identically in both modes. - Future: parking-lot task #9 (church-side AI off mode) inherits the architecture pattern established here. The Settings UX shape (radio + honest explainer + classroom-style override) is the reference design for the eventual chatbot/voice toggle.
§14. What this spec does NOT change
- PR #543 Wave-7 quiz-cheat defense — migrates intact to the Scribe path. Same regex bank, same telemetry table (with new
sourcecolumn), same Socratic posture system-prompt fragment. - Scene structure, scene scripts, scene narrative, relic/quest/reward mechanics — untouched.
- 12 anchor NPC character bibles' authored content — additive only (scriptedDialogue tree added; existing fields untouched).
- Lead-gen CTAs (Phase 1B) — untouched. Bloom cards, finale panels, footer credit all stay AI-Mode-independent.
- Liturgical calendar (Phase 1A live-ops) — untouched. Calendar events have always been deterministic (no LLM); they work the same way in both modes.
- UGC educator authoring — untouched. Semantic auto-check stays LLM-powered for teachers regardless of student AI Mode.