AI Frontier Post
An open manuscript glowing on a dark desk, its pages showing a branching interactive story map, with a quill and inkwell beside it
An open manuscript glowing on a dark desk, its pages showing a branching interactive story map. Illustration generated for AI Frontier Post.

Large language models are wonderful narrators and terrible bookkeepers. Start an interactive story with one, play for twenty turns, and your grizzled sea captain has quietly become a different person with a different name — and the model will insist it was always so. AI Novel Studio (MIT licensed, around 225 stars in its first week) attacks exactly this: it’s an AI interactive-fiction app in Next.js and Go where the model proposes each turn’s prose and state changes, but a server-side engine validates and commits them. The narrator can’t rewrite its own facts.

What you’ll need

Step 1 — run the in-memory demo

The docs’ smallest runnable example needs nothing but Go. Clone the repo and run the demo straight from the repository root:

git clone https://github.com/weike-zhang/ai-novel-studio.git
cd ai-novel-studio/services/server
go run ./cmd/demo

You should see three lines of behavior, exactly as the architecture guide documents:

COMMITTED turn=1 status=committed
REJECTED code=identity_mutation field=characterPatches.0.current.name
PRESERVED turn=1 version=2 name=Keeper

Here’s what happened: the engine committed a harbor scene as turn 1, the fake model then proposed renaming an established character, and the validator refused the patch — identity_mutation — leaving the stored version (Keeper, version 2) intact. The demo also prints a fixed Chinese narrative alongside it, to show prose and state flowing through the same turn. This is the whole thesis in one command: a model may propose, but it may not mutate identity.

Step 2 — run the engine’s own tests

From the same services/server directory:

go test ./internal/storyengine -v

These tests draw the boundary lines: continuity context rejects uncommitted turns, mismatched snapshots and duplicate sequence numbers. They’re worth skimming before you touch the full stack, because they’re the executable spec for what the engine promises — and, just as importantly, what it doesn’t.

Step 3 — read the boundary: proposals vs committed state

The design the docs describe is a clean split. Each turn, the model produces a TurnProposal — proposed prose, choices, events, scenes and character patches. The server then decides whether those patches can become part of the journey, running them through a validator that checks identity protection, version or previous-value preconditions, event requirements and scene constraints. An authorization layer further limits what can change between authorized and later proposals.

Starting a journey captures a frozen snapshot of the world, so later edits to the public world can’t retroactively rewrite your premise. The continuity context fed to the model is the snapshot plus committed simulation, a committed summary, and a bounded tail of committed turns — eight recent turns by default. And the durable machinery is unglamorous in the best way: credit reservations and turn jobs inside a MySQL transaction, idempotency keys, row locks on the simulation and wallet, an outbox into RabbitMQ.

An AI storyteller handing a draft page to a glowing validation gate guarding locked character records
Proposals in, facts out: the engine’s validation gate. Illustration generated for AI Frontier Post.

Step 4 — bring up the full web app

When you want the actual product — world discovery, authoring, private journeys, account flows — follow the development guide from the repository root. Copy the example config, install, and start the dev services:

cp .env.example .env
npm ci
docker compose -f deploy/compose/compose.yaml up -d

Fill in your own MinIO details in .env — MINIO_ENDPOINT, MINIO_PUBLIC_URL, MINIO_ACCESS_KEY, MINIO_SECRET_KEY. Note the docs’ warning: the API rejects loopback MinIO endpoints, so this needs a real external MinIO server, not localhost. Then migrate, seed the synthetic development content, and start the three processes in three terminals:

node --env-file=.env scripts/run-server.mjs migrate up
docker compose -f deploy/compose/compose.yaml exec -T mysql \
  mysql -unovelstudio -pnovelstudio novelstudio < services/server/db/seeds/development.sql
node --env-file=.env scripts/run-server.mjs api
node --env-file=.env scripts/run-server.mjs worker
node --env-file=.env --run dev:web

Open http://localhost:3000. The local sample uses a synthetic development identity for requests — fine for exploration, explicitly not for anything you expose.

Step 5 — point it at a real model endpoint

The fake provider only carries fake worker turns; real authoring assistance and cover generation need configured endpoints. Set an OpenAI-compatible provider:

MODEL_PROVIDER=openai_compatible
TEST_MODEL_BASE_URL=https://your-provider.example/v1
TEST_MODEL_API_KEY=your-key
TEST_MODEL_NAME=your-model

The docs define the accepted provider names in services/server/cmd/worker/main.go and warn that the variable names are legacy but real — don’t put a production key in shared examples. Cover generation takes its own trio with fallback handling:

COVER_BASE_URL=https://your-provider.example/v1
COVER_API_KEY=your-key
COVER_MODEL=your-image-model

What you built

A working interactive-fiction loop with an unusual property: the AI is the narrator, not the librarian. Prose, choices and events are generated; facts are validated and committed in transactions. If you want to extend it, the honest entry point the docs suggest is the validator — decide what counts as an illegal state mutation, and the rest of the pipeline (snapshots, proposals, outbox, settlement) already holds the line.

A branching story tree where one rejected plot branch is struck through
A rejected branch never enters canon. Illustration generated for AI Frontier Post.

Honest limitations

The uncomfortable truth AI Novel Studio bets on: in agentic storytelling, the hard problem was never generating the next paragraph — it’s deciding which paragraphs are allowed to become true. Roughly 225 developers starred that distinction in the project’s first week.