docs: add narrative layer design spec and implementation plan

- docs/superpowers/specs/2026-03-26-narrative-layer-design.md
- docs/superpowers/plans/2026-03-26-narrative-layer-foundation.md

Spec extends MiroFish into a creative storytelling platform via a new
narrative layer over the existing OASIS simulation engine.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
anadoris007 2026-04-20 20:41:14 +05:30
parent fa0f6519b1
commit c5cc599053
2 changed files with 2000 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,532 @@
# MiroFish Narrative Layer — Design Specification
**Date:** 2026-03-26
**Approach:** A — Narrative Layer on top of existing OASIS simulation
**Target:** Creative simulation engine (novel continuation, world-building, interactive fiction)
**Scale:** Small community (1050 users), basic auth, moderate concurrency
---
## 1. Problem Statement
MiroFish is a multi-agent swarm intelligence engine built for prediction — it simulates social media interactions (Twitter/Reddit) to forecast outcomes. The simulation infrastructure (OASIS engine, Zep graph memory, LLM-driven agents) is powerful but locked into a social-media metaphor.
**Goal:** Extend MiroFish into a creative storytelling platform where users can upload fiction, define worlds, and watch AI agents generate emergent narratives — without replacing the proven OASIS simulation backbone.
**Key insight:** Social media actions map naturally to narrative actions. A "post" is speech. A "like" is agreement. A "repost" is rumor-spreading. By adding a translation layer, we reinterpret simulation output as story prose.
---
## 2. Architecture
### 2.1 System Diagram
```
┌──────────────────────────────────────────────────────┐
│ Frontend (Vue 3) │
│ ┌────────────┐ ┌──────────────┐ ┌────────────────┐ │
│ │ Story │ │ Character │ │ God Mode │ │
│ │ Timeline │ │ Workshop │ │ Control Panel │ │
│ └────────────┘ └──────────────┘ └────────────────┘ │
│ ┌────────────┐ ┌──────────────┐ ┌────────────────┐ │
│ │ World │ │ Branching │ │ Export │ │
│ │ Map View │ │ Timeline │ │ Studio │ │
│ └────────────┘ └──────────────┘ └────────────────┘ │
├──────────────────────────────────────────────────────┤
│ Narrative Engine (NEW) │
│ ┌──────────────────────────────────────────────┐ │
│ │ Narrative Translator │ │
│ │ - Action → Story Event mapping │ │
│ │ - Emotional state tracking │ │
│ │ - Plot arc detection │ │
│ └──────────────────────────────────────────────┘ │
│ ┌─────────────┐ ┌──────────────┐ ┌─────────────┐ │
│ │ Character │ │ World State │ │ Timeline │ │
│ │ Engine │ │ Manager │ │ Brancher │ │
│ └─────────────┘ └──────────────┘ └─────────────┘ │
├──────────────────────────────────────────────────────┤
│ Existing MiroFish Backend │
│ Graph Builder → OASIS Simulation → Report Agent │
│ Zep Memory → Profile Generator → LLM Client │
└──────────────────────────────────────────────────────┘
```
### 2.2 New Backend Services
All new services live in `backend/app/services/narrative/` to keep the existing codebase untouched.
| Service | File | Purpose |
|---|---|---|
| Narrative Translator | `narrative_translator.py` | Converts OASIS actions into story prose via LLM |
| Character Engine | `character_engine.py` | Manages extended character profiles (backstory, emotions, arcs) |
| World State Manager | `world_state.py` | Tracks locations, factions, resources, world rules |
| Timeline Brancher | `timeline_brancher.py` | Snapshots and forks simulation state |
| Seed Enhancer | `seed_enhancer.py` | Processes EPUB/DOCX/templates into MiroFish seeds |
| Export Studio | `export_studio.py` | Generates EPUB, screenplay, chapter formats |
### 2.3 New API Routes
New route blueprint: `narrative_bp = Blueprint('narrative', __name__)` in `backend/app/api/narrative.py`. Register in `backend/app/__init__.py` via `app.register_blueprint(narrative_bp, url_prefix='/api/narrative')`. Import added to `backend/app/api/__init__.py`.
| Method | Endpoint | Purpose |
|---|---|---|
| POST | `/api/narrative/translate` | Translate a simulation round into story prose |
| GET | `/api/narrative/story/{sim_id}` | Get full story for a simulation |
| GET | `/api/narrative/story/{sim_id}/round/{round}` | Get story for a specific round |
| POST | `/api/narrative/characters` | Create/update extended character profiles |
| GET | `/api/narrative/characters/{sim_id}` | Get all characters with emotional states |
| GET | `/api/narrative/characters/{sim_id}/{char_id}` | Get single character detail |
| POST | `/api/narrative/world` | Create/update world state |
| GET | `/api/narrative/world/{sim_id}` | Get current world state |
| POST | `/api/narrative/godmode/inject` | Inject an event into a running simulation |
| POST | `/api/narrative/godmode/modify-character` | Modify character state mid-simulation |
| POST | `/api/narrative/godmode/change-rules` | Change world rules mid-simulation |
| POST | `/api/narrative/branch/{sim_id}/{round}` | Fork timeline at a specific round |
| GET | `/api/narrative/branches/{sim_id}` | List all branches for a simulation |
| POST | `/api/narrative/export/{sim_id}` | Export story in specified format |
| GET | `/api/narrative/export/{sim_id}/status` | Check export generation status |
### 2.4 New Frontend Views
| View | File | Purpose |
|---|---|---|
| Story Timeline | `views/StoryTimelineView.vue` | Read the generated narrative chapter by chapter |
| Character Workshop | `views/CharacterWorkshopView.vue` | Create, edit, and browse characters |
| World Builder | `views/WorldBuilderView.vue` | Define locations, factions, rules |
| God Mode | `views/GodModeView.vue` | Control panel for mid-simulation intervention |
| Branch Explorer | `views/BranchExplorerView.vue` | Visual timeline tree, compare branches |
| Export Studio | `views/ExportStudioView.vue` | Choose format, preview, download |
### 2.6 Frontend Routes
| Route | View | Description |
|---|---|---|
| `/story/:simId` | StoryTimelineView | Read generated narrative |
| `/characters/:simId` | CharacterWorkshopView | Create and browse characters |
| `/world/:simId` | WorldBuilderView | Define world settings |
| `/godmode/:simId` | GodModeView | Mid-simulation controls |
| `/branches/:simId` | BranchExplorerView | Timeline tree and comparison |
| `/export/:simId` | ExportStudioView | Format and download stories |
Routes are added to `frontend/src/router/index.js` following the existing pattern of `/:simulationId` parameterized routes.
### 2.5 New Frontend Components
| Component | Purpose |
|---|---|
| `StoryBeat.vue` | Single story event/paragraph with character attribution |
| `CharacterCard.vue` | Character portrait with stats, emotions, relationships |
| `RelationshipGraph.vue` | Visual graph of character relationships (D3.js) |
| `EmotionTracker.vue` | Per-character emotional state over time (sparkline) |
| `WorldMap.vue` | Visual map of locations and character positions |
| `TimelineTree.vue` | Branching timeline visualization |
| `GodModeToolbar.vue` | Floating toolbar for injecting events |
| `ExportPreview.vue` | Preview formatted output before download |
---
## 3. Narrative Translator — Core Logic
### 3.0 Integration Point — How the Narrative Layer Connects to OASIS
The existing OASIS simulation runs as a subprocess that writes actions to `actions.jsonl` (one JSON line per agent action per round). The existing `_monitor_simulation` method in `SimulationRunner` already polls this file every 2 seconds, tracking file position to read only new lines.
**Narrative translation is asynchronous and on-demand.** The Narrative Translator does NOT hook into the simulation loop. Instead:
1. **During simulation:** The existing `_monitor_simulation` continues reading `actions.jsonl` as-is. No changes to the simulation runner.
2. **After each round completes:** The frontend polls a new endpoint `GET /api/narrative/story/{sim_id}/latest` which triggers translation of any untranslated rounds.
3. **Translation reads `actions.jsonl`** directly, maintaining its own file offset in `narrative/translator_state.json`. It reads actions for the next untranslated round, generates prose, and stores the result.
4. **Batch translation** is also available via `POST /api/narrative/translate` for retroactively translating a completed simulation.
**Why on-demand (not in-loop):** LLM prose generation takes 2-5 seconds per scene. Injecting this into the simulation monitor would slow round processing by 10-20x. By decoupling translation, the simulation runs at full speed and narrative is generated as users view it.
**IPC for God Mode:** The existing `SimulationIPCClient` supports file-based IPC with `INTERVIEW`, `BATCH_INTERVIEW`, and `CLOSE_ENV` commands. God Mode leverages `INTERVIEW` to inject custom prompts into running agents (see Section 6). New IPC command types (`INJECT_EVENT`, `MODIFY_AGENT_PROMPT`) will be added to `SimulationIPCServer` and the OASIS runner scripts.
### 3.1 Action Mapping
| OASIS Action | Narrative Interpretation |
|---|---|
| `CREATE_POST` | Character speaks, declares, announces |
| `LIKE_POST` | Character agrees, supports, nods |
| `REPOST` | Character spreads rumor, amplifies, gossips |
| `QUOTE_POST` | Character responds, debates, challenges |
| `FOLLOW` | Character allies with, shows loyalty to |
| `DO_NOTHING` | Character reflects, observes, waits *(inferred — see note below)* |
| `CREATE_COMMENT` | Character engages in dialogue |
| `DISLIKE_POST` | Character opposes, confronts, disapproves |
| `LIKE_COMMENT` | Character validates someone's response |
| `DISLIKE_COMMENT` | Character dismisses, mocks |
| `SEARCH_POSTS` | Character investigates, seeks information |
| `SEARCH_USER` | Character seeks out a specific person |
| `MUTE` | Character avoids, ignores, shuns |
**Note on `DO_NOTHING`:** This action may not appear in `actions.jsonl` since the logger only records actions agents take. The translator infers inaction: any agent present in the simulation but absent from a round's action log is treated as "observing/waiting." This is narratively valuable.
### 3.2 Translation Pipeline
For each simulation round:
1. **Collect actions** — read `actions.jsonl` from the translator's saved file offset, gather all actions until the next `round_end` event
2. **Infer inaction** — compare active agents against the full character roster; absent agents are marked as "observing"
3. **Enrich with context** — pull character emotional states, relationships, world state from `narrative/` files
4. **Group into scenes** — cluster related actions (same thread / interacting agents)
5. **Generate prose** — LLM call per scene with action data + context → narrative paragraph
6. **Detect plot events** — classify the round as rising action / climax / falling action / resolution
7. **Update states** — update character emotions and relationships based on what happened
8. **Store** — persist story beat to `narrative/story_beats.json`, update translator file offset in `narrative/translator_state.json`
### 3.3 Resilience
- **LLM retry:** 3 retries with exponential backoff (1s, 2s, 4s) using the existing `retry` utility in `backend/app/utils/retry.py`
- **Partial failure:** If a scene fails after retries, store a placeholder beat with raw action data. User can retry individual beats via API.
- **Graceful degradation:** If the LLM is completely unavailable, fall back to template-based summary: `"{agent_name} {action_verb} {target}"`
- **Cost estimate:** ~3-5 LLM calls per round (one per scene). A 20-round simulation ≈ 60-100 calls ≈ 30k-50k tokens ≈ $0.15-$0.25 with GPT-4o-mini.
### 3.4 LLM Prompt Structure
The translator uses a structured prompt:
```
You are a narrative writer. Convert these simulation events into a story passage.
World: {world_description}
Setting: {current_location_context}
Characters involved: {character_summaries_with_emotions}
Events this round:
{structured_action_list}
Previous story context (last 2 beats):
{previous_prose}
Write a story passage (2-4 paragraphs) that:
- Uses third-person past tense
- Shows character emotions through action and dialogue
- Maintains consistency with the world rules
- Advances the narrative naturally from the previous context
Tone: {user_defined_tone — e.g., "dark fantasy", "lighthearted comedy", "political thriller"}
```
---
## 4. Character Engine
### 4.1 Extended Character Schema
```json
{
"id": "char_abc123",
"name": "Elena Voss",
"oasis_agent_id": "agent_xyz",
"backstory": "Former diplomat turned rogue negotiator...",
"motivations": ["power", "redemption"],
"personality_traits": ["cunning", "loyal_to_few", "distrustful"],
"speech_style": "Formal, precise, with occasional dark humor",
"emotional_state": {
"current": {"anger": 0.3, "fear": 0.1, "joy": 0.0, "sadness": 0.4, "trust": 0.2},
"history": [{"round": 1, "state": {...}}, ...]
},
"relationships": {
"char_def456": {"type": "rival", "intensity": 0.8, "history": ["betrayal in round 3"]},
"char_ghi789": {"type": "ally", "intensity": 0.6, "history": ["formed alliance in round 1"]}
},
"arc": {
"archetype": "fall_from_grace",
"stage": "descent",
"key_moments": [{"round": 3, "event": "Betrayed longtime ally"}]
},
"location": "The Iron Tower",
"faction": "The Council"
}
```
### 4.2 Emotional State Model
Six basic emotions tracked as 0.01.0 floats: `anger`, `fear`, `joy`, `sadness`, `trust`, `surprise`.
Updated each round based on:
- Actions taken by the character
- Actions taken *toward* the character
- World events
- Relationship changes
**v1: Display-only.** Emotional states are derived from actions and displayed in the UI but do NOT feed back into OASIS agent prompts. OASIS profiles are generated once at simulation setup time by `OasisProfileGenerator` and are not modified mid-simulation.
**v2 (future):** Use the new `INJECT_CONTEXT` IPC command (see Section 6.2) to prepend emotional context to agent prompts each round. E.g., `"You are currently feeling angry and distrustful after the betrayal."` This creates a feedback loop where emotions influence behavior, but adds an IPC call per agent per round.
### 4.3 Character Arc Detection
After each round, the Character Engine evaluates arc progression using pattern matching:
| Arc Type | Pattern |
|---|---|
| Hero's Journey | Comfort → Crisis → Growth → Triumph |
| Fall from Grace | Status → Temptation → Descent → Consequences |
| Redemption | Flaw → Suffering → Realization → Atonement |
| Coming of Age | Innocence → Challenge → Learning → Maturity |
| Tragedy | Hope → Hubris → Downfall → Loss |
**Detection mechanism:** Rule-based classification using emotional state deltas:
- Trust drops > 0.3 in one round → "crisis" stage
- Joy sustained > 0.6 for 3+ rounds → "comfort" or "triumph" stage
- Anger + fear both > 0.5 → "descent" stage
- Transition from high-negative to high-positive emotions → "redemption"
If rule-based detection is ambiguous, a single LLM classification call is made with the character's action history summary (~200 tokens). Detected arcs are stored in `characters.json` and surfaced in the UI and export.
---
## 5. World State Manager
### 5.1 World State Schema
```json
{
"sim_id": "sim_abc123",
"name": "The Shattered Kingdoms",
"genre": "dark_fantasy",
"tone": "grim, political",
"era": "medieval",
"rules": [
"Magic exists but is feared and regulated",
"The monarchy has fallen; power is contested by three factions",
"Winter is approaching and resources are scarce"
],
"locations": {
"iron_tower": {"name": "The Iron Tower", "type": "fortress", "faction": "council", "characters": ["char_abc"]},
"market_district": {"name": "Market District", "type": "city", "faction": "neutral", "characters": []}
},
"factions": {
"council": {"name": "The Council", "power": 0.6, "members": ["char_abc"], "goals": ["restore order"]},
"rebels": {"name": "The Free Folk", "power": 0.3, "members": ["char_def"], "goals": ["overthrow council"]}
},
"resources": {
"food": {"abundance": 0.3, "controlled_by": "council"},
"weapons": {"abundance": 0.5, "controlled_by": "rebels"}
},
"timeline_position": {"round": 5, "branch": "main"}
}
```
### 5.2 World State Updates
Each simulation round, the World State Manager:
1. Processes narrative events for world-level changes (faction power shifts, resource changes)
2. Updates character locations based on their actions
3. Checks world rules for constraint violations
4. Triggers world events (e.g., "winter arrives" at round 10)
---
## 6. God Mode
### 6.1 Intervention Types
| Intervention | Effect |
|---|---|
| **Inject Event** | Add a world event ("earthquake strikes", "a stranger arrives") that agents must react to |
| **Modify Character** | Change a character's emotional state, motivation, or relationships |
| **Change Rules** | Add or remove world rules ("magic is now forbidden") |
| **Force Action** | Make a specific character take a specific action next round |
| **Kill Character** | Remove an agent from the simulation permanently |
| **Resurrect Character** | Re-introduce a removed character |
| **Time Skip** | Jump forward N rounds with summarized events |
| **Rewind** | Go back to a previous round (creates a branch) |
### 6.2 Implementation — IPC Dispatch Mechanism
God Mode leverages the existing **file-based IPC** system (`SimulationIPCClient` / `SimulationIPCServer`). The IPC currently supports `INTERVIEW`, `BATCH_INTERVIEW`, and `CLOSE_ENV` commands.
**Mapping God Mode interventions to IPC:**
| Intervention | IPC Mechanism |
|---|---|
| **Inject Event** | `BATCH_INTERVIEW` — send all agents a prompt describing the event, forcing them to react in their next action. E.g., `"An earthquake just struck. How do you respond?"` |
| **Modify Character** | `INTERVIEW` — send the target agent a prompt that reframes their emotional/motivational state. E.g., `"You have just learned that your ally betrayed you. Your trust is shattered."` The Character Engine also updates its local emotional state JSON. |
| **Change Rules** | `BATCH_INTERVIEW` — inform all agents of the rule change. E.g., `"Magic has been outlawed. Anyone caught using it faces death."` Also update `world_state.json`. |
| **Force Action** | `INTERVIEW` — send the target agent a directive prompt. E.g., `"You decide to confront Marcus in front of the council. Describe your accusation."` The response is logged as the agent's action. |
| **Kill Character** | Stop sending actions for this agent in subsequent rounds. Mark as "dead" in `characters.json`. OASIS agent continues to exist but receives no prompts. |
| **Resurrect Character** | Reverse of kill — resume prompting the agent with a resurrection context prompt. |
| **Time Skip** | Set simulation to run N rounds without narrative translation, then batch-translate with a "summary" prompt instead of detailed prose. |
| **Rewind** | Creates a new simulation from scratch using the same config but with a modified starting prompt that includes "the story so far up to round N." See Section 7 for details. |
**New IPC command type needed:** `INJECT_CONTEXT` — a variant of `BATCH_INTERVIEW` that prepends context to all agents' next-round system prompts rather than triggering an immediate interview response. This requires a small addition to the OASIS runner scripts (`run_twitter_simulation.py`, `run_reddit_simulation.py`).
Each intervention is logged to `narrative/god_mode_log.json` for story coherence and auditability.
---
## 7. Timeline Branching
### 7.1 Branch Model
```json
{
"sim_id": "sim_abc123",
"branches": {
"main": {"parent": null, "fork_round": 0, "status": "running", "rounds_completed": 10},
"what_if_elena_dies": {"parent": "main", "fork_round": 5, "status": "running", "rounds_completed": 3},
"peaceful_ending": {"parent": "main", "fork_round": 7, "status": "paused", "rounds_completed": 1}
}
}
```
### 7.2 Branching Mechanics — Replay-Based Approach
**OASIS does not support checkpointing.** The simulation database is deleted on startup and there is no state serialization for mid-simulation resumption. Therefore, branching uses a **replay + diverge** strategy:
1. User selects a round N to branch from
2. System creates a new simulation directory under `uploads/simulations/{sim_id}/branches/{branch_name}/`
3. System copies the original `simulation_config.json` and `profiles.json`
4. System generates a **"story so far" summary** — an LLM-generated condensation of all narrative beats from rounds 1 to N
5. This summary is injected into every agent's starting prompt as context: `"The following events have already occurred: {summary}. You are now at this point in the story."`
6. A new OASIS subprocess launches with modified agent prompts that include this prior context
7. User can optionally inject a God Mode event to differentiate the branch (e.g., "In this timeline, Elena survives the betrayal")
8. Both branches run as independent OASIS processes — user can switch between them in the UI
**Trade-offs of replay approach:**
- **Pro:** No OASIS modifications needed. Uses existing subprocess launch mechanism.
- **Con:** Agents don't have exact memory of previous rounds — they have an LLM-summarized version. This means branches may drift from the original timeline's "feel." For creative fiction, this is acceptable (stories branch naturally).
- **Con:** Starting a branch requires an LLM call for summary generation (~5-10 seconds). Not a major bottleneck.
- **Performance:** Branch launch takes the same time as a fresh simulation start (~10-30 seconds), regardless of which round you branch from.
Storage: Each branch gets its own subdirectory under `uploads/simulations/{sim_id}/branches/{branch_name}/` with its own `simulation_config.json`, `actions.jsonl`, and `narrative/` folder.
---
## 8. Enhanced Input Pipeline
### 8.1 New Seed Formats
| Format | Processing |
|---|---|
| **EPUB** | Extract text → identify characters (NER) → extract settings → generate world state |
| **DOCX** | Same as EPUB but with python-docx parsing |
| **YAML template** | Structured world definition — characters, locations, rules — no LLM needed |
| **Image** | Send to multimodal LLM → extract character description / setting description |
| **URL** | Scrape page content → process as text seed |
### 8.2 YAML World Template Format
```yaml
world:
name: "The Shattered Kingdoms"
genre: "dark_fantasy"
tone: "grim, political"
rules:
- "Magic exists but is feared"
- "Winter is approaching"
characters:
- name: "Elena Voss"
role: "protagonist"
backstory: "Former diplomat..."
motivations: ["power", "redemption"]
personality: ["cunning", "loyal"]
- name: "Marcus Iron"
role: "antagonist"
backstory: "Military commander..."
motivations: ["control", "legacy"]
locations:
- name: "The Iron Tower"
type: "fortress"
description: "A dark spire..."
factions:
- name: "The Council"
goals: ["restore order"]
members: ["Elena Voss"]
simulation:
max_rounds: 20
tone: "dark_fantasy"
platform: "twitter" # underlying OASIS platform
```
---
## 9. Export Studio
### 9.1 Output Formats
| Format | Structure |
|---|---|
| **Chapters** | Story beats grouped into chapters (every 3-5 rounds), with chapter titles |
| **Screenplay** | Character names in caps, dialogue, (parenthetical action), scene headings |
| **EPUB** | Full e-book with cover, table of contents, chapters, character appendix |
| **Character Dossiers** | Per-character PDF with backstory, arc summary, key moments, relationships |
| **Raw Timeline** | JSON export of all events for programmatic use |
### 9.2 Export Pipeline
1. User selects simulation + branch + format
2. Backend collects all story beats for the selected scope
3. LLM pass for cohesion — smooth transitions between beats, add chapter breaks
4. Format-specific rendering (EPUB via `ebooklib`, screenplay via template)
5. Return download URL
**Async handling:** Export runs in a background thread (consistent with existing `SimulationRunner._monitor_simulation` pattern). The `POST /api/narrative/export/{sim_id}` endpoint returns immediately with an export job ID. Frontend polls `GET /api/narrative/export/{sim_id}/status` until complete. Note: server restarts will lose in-progress exports — acceptable for the target scale.
---
## 10. Data Model Changes
### 10.1 New Files per Simulation
```
uploads/simulations/{sim_id}/
├── state.json # existing
├── profiles.json # existing
├── entities.json # existing
├── simulation_config.json # existing
├── narrative/ # NEW
│ ├── world_state.json # world definition
│ ├── characters.json # extended character profiles
│ ├── story_beats.json # generated narrative per round
├── branches/ # Timeline branches (see Section 7)
│ ├── what_if_branch/
│ │ ├── simulation_config.json
│ │ ├── actions.jsonl
│ │ └── narrative/
│ │ ├── story_beats.json
│ │ └── characters.json
│ └── exports/
│ ├── story_chapters.epub
│ └── screenplay.txt
```
### 10.2 Database Additions
No new database — continues using file-based storage consistent with existing MiroFish patterns. For the 10-50 user scale, this is sufficient. If we need to scale later, we migrate to SQLite or PostgreSQL.
---
## 11. Authentication & Multi-User
**Deferred to a separate spec.** The existing MiroFish codebase has zero authentication — no middleware, no user model, no token checks. Adding JWT auth is a cross-cutting concern that touches every existing endpoint and deserves its own design. For v1 of the Narrative Layer, we operate without auth (same as existing MiroFish). Multi-user isolation is handled by simulation IDs — each simulation is self-contained in its own directory.
**When auth is added (separate spec):** Simple JWT tokens, user model in SQLite, middleware on all `/api/` routes, project isolation by user directory, optional "public" flag for sharing.
---
## 12. Non-Goals (Explicitly Out of Scope)
- Real-time collaborative editing (too complex for v1)
- Voice/audio output of stories
- Image generation for scenes (future enhancement)
- Mobile app
- Horizontal scaling / microservices
- Payment / billing