MicroFish/docs/superpowers/specs/2026-03-26-narrative-layer-...

27 KiB
Raw Blame History

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

{
  "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

{
  "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

{
  "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

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