Parallel is an AI narrative world engine for murder mysteries, investigative roleplay, and multi-character scenes that keep evolving after every turn.
Parallel is not a single-character chatbot and not a fixed-branch visual novel.
Each player message enters a living scene:
- a director decides who should respond and how the moment should feel
- characters answer from their own personality, memory, and relationships
- the world stores facts, memories, clues, events, and time progression
- the next turn starts from the updated world state, not from scratch
The result is closer to an AI-driven murder mystery engine than a chat UI.
- Multi-character orchestration instead of one assistant persona
- Persistent world state across turns and sessions
- Relationship changes, clue extraction, and event generation
- Provider-agnostic LLM layer with OpenAI-compatible and Anthropic-style support
- World import/export through YAML so new scenarios can be authored without code
- The browser sends a message to
POST /api/chat. runTurn()advances world time and loads the active scene state.- The director picks speakers and generates narration.
- Characters reply in sequence with their own memory and emotional context.
- Post-turn analysis updates facts, memories, relationships, clues, and optional world events.
Architecture details live in docs/ARCHITECTURE.md.
POST /api/chat supports optional streaming via stream: true in the request body. When enabled, the response is delivered as Newline-Delimited JSON (application/x-ndjson) with incremental events:
- narration_done — narration text arrives first, before character dialogue
- character_delta — real token-level streaming for Anthropic-compatible providers (including MiniMax); full message for others
- character_reset — emitted when stream fails mid-output; client should clear accumulated text
- done — full
TurnResultafter store commit
What streaming improves: Time to first text drops from ~18s to ~7s (user sees content sooner).
What streaming does not improve: Total wall time is unchanged or slightly higher due to streaming overhead. Streaming is an experience improvement, not a speed optimization.
Token-level streaming currently works with Anthropic-compatible providers only. Other providers (OpenAI, Ollama, Mock) fall back to phase-level streaming (narration → full character messages).
API details in docs/API.md.
Requirements:
- Node.js 20+
- npm
Run locally:
npm install
npm run devThen open http://localhost:3000.
Windows users can also double-click start.bat.
Parallel supports OpenAI-compatible, Anthropic-style, OpenRouter, and Ollama endpoints. When no provider is configured, it falls back to mock mode.
There are two places to configure LLM access, and they serve different purposes:
This file sits on your machine and is gitignored. It provides defaults for npm run dev / npm run start so you don't have to configure anything in the browser during development.
LLM_PROVIDER=openai
OPENAI_API_KEY=your_api_key_here
OPENAI_BASE_URL=https://api.openai.com/v1
OPENAI_MODEL=gpt-4o-miniMore configuration examples are in docs/CONFIG.md.
The in-app Settings modal stores provider, API URL, model, and API key in browser localStorage. This configuration:
- is per-browser, per-device — it doesn't travel with the code
- is never sent to the server except in the body of
/api/chatand/api/llm/testrequests - overrides
.env.localwhen present - is not bundled into the build — if you give a copy of this project to a friend, they get your
.env.localdefaults (if you didn't gitignore it), but NOT your browser localStorage
If you give someone a local copy of this project:
.env.localis gitignored by default — it won't be in the repo unless you explicitly committed it- Each person needs to create their own
.env.localor configure Settings in their own browser - The server never broadcasts your API key to connected browsers
- If you want to share a pre-configured setup, include
.env.local.examplewith placeholder values and instructions
The project is at a polished prototype stage:
- playable multi-world experience
- test, lint, and build coverage in CI
- recent work on narration cleanup, destructive action confirmation, browser storage fallback, and provider compatibility
- active performance tuning on
/api/chat, especially director and post-turn analysis latency
app/: Next.js pages and API routescomponents/: UI for the play surface, settings, sidebar, and timelinelib/engine/: turn orchestration, director, memory, relationships, events, clueslib/llm/: provider resolution, API clients, URL validationdata/worlds/: YAML world definitionsdocs/: architecture, config, API, roadmap, world format
These files are intentionally not committed:
.env.local: local provider keys and endpointsdata/store.json: runtime session stateCODEX_HANDOFF.md: private handoff notes
The project also includes:
- SSRF protection for provider URLs
- sanitized error output
- browser-local settings that do not expose server-side keys back to the UI
MIT