A self-hosted web application that uses AI agents to collaboratively write books. Seven specialized agents — Story Bible, Characters, Plot Threads, Chapter Outlines, Writer, Checker, and Editor — work together under your direction, streaming their progress in real time and pausing to ask clarifying questions when needed.
- Seven specialized agents across two phases: a 4-phase Planner (Story Bible → Characters → Plot Threads → Chapter Outlines) and a per-chapter writing pipeline (Pre-write Check → Write → Check → Edit)
- Checker → mechanical patch apply — the Checker flags continuity, grammar, repetition, and style issues as structured JSON patches; the Editor applies them mechanically (no LLM call) using indexed text matching with whitespace normalization and position hints
- Full synopsis spine — every prior chapter's title and outline is injected into Writer and Editor messages so agents stay aware of the whole narrative and avoid recycling beats or re-introducing established characters
- RAG context retrieval — Writer runs 3 targeted queries (characters, locations, plot threads) and Editor runs 4 (same + repeated-phrase detection) against pgvector embeddings
- Guided planning Q&A — agents ask clarifying questions up front before planning begins, then carry your answers through all four planning phases
- Human-assisted generation — pauses after each planning phase, and after each chapter's mechanical fixes so you can edit the book and steer the creative rewrite; pending questions are restored after page refresh; supports Ctrl+Enter to submit
- Flexible workflow controls — Plan Only, Write Book, Continue, Continue Planning, and individual per-chapter agent buttons; Stop cancels any running agent cleanly
- Interrupted runs keep their output — if a Characters, Plot Threads, or Chapter Outlines run times out, drops, or is stopped part-way, everything that finished streaming is saved instead of discarded; run the phase again to pick up where it left off
- Re-runs build on what exists — regenerating a planning phase sends the current characters, plot threads, or outlines back to the model to refine and extend, rather than starting from a blank page; archived items stay out of it and are never overwritten
- Story Bible, Characters & Plot Threads — generated by the Planner, fully editable, with per-item version history and snapshot restore
- Inline editing — edit book metadata, chapter titles/outlines, and add chapters manually without leaving the detail page
- Version history — chapters, characters, and plot threads all track history with preview and restore; soft-archive instead of delete
- Archived means archived — an archived chapter, character, or plot thread is kept purely so you can look at it or restore it. It is never sent to a model, never counted in planning or continuity checks, never included in an HTML/FB2/EPUB export or on the public reader page, and agents refuse to write to it
- Book continuation — create a sequel that copies all settings and inherits ancestor context; RAG and planning reference the full base-book chain
- Multiple export formats — HTML (6 colour themes, adjustable font size), EPUB, FB2, and a Metadata document (book info, outlines, planning artifacts, agent messages, token stats)
- Public Library — browse and read published books without logging in (when public mode is enabled); each chapter has its own URL for bookmarking and sharing
- Pluggable LLM backend — Ollama (default, local), OpenAI (or any OpenAI-compatible API), or Google AI Studio; configurable globally, per-user, or per-book
- Real-time streaming — watch chapters being written token by token via SignalR; planning phases stream with live progressive JSON previews
- Token usage statistics — per-agent prompt and completion token counts, persisted to the database and displayed in a collapsible panel. Calls that error, time out, or are cancelled are recorded too, with a Status column and the failure reason shown next to their partial counts
- MCP server — built-in Model Context Protocol server at
/mcp; connect Claude Desktop, VS Code Copilot, or any MCP client using a per-user API token - Multi-user — cookie-based authentication with admin role for user management
- Ollama model management — browse installed models, pull new ones with live progress
- Global concurrency limit — cap simultaneous agent runs across all books/users with
AgentSettings__MaxConcurrentRuns - PWA support — installable, works offline for cached content
[React SPA — served as static files from ASP.NET wwwroot]
↕ REST API + SignalR
[ASP.NET Core 10 API]
↕ Direct provider SDKs ↕ EF Core 10 + Npgsql
[LLM (Ollama / OpenAI / Google)] [PostgreSQL 16 (pgvector in-DB)]
React is built at image-build time and served from wwwroot/ — there is no separate frontend container at runtime.
- Docker + Docker Compose
- Ollama running on the host machine (or any OpenAI-compatible API)
docker-compose up -d
# App is available at
open http://localhost:5000On first launch the app shows a Create Admin Account setup screen — the first account registered automatically becomes admin. After signing in, go to Settings to configure your LLM provider and pull an Ollama model.
docker run -d \
-p 5000:8080 \
-e ConnectionStrings__DefaultConnection="Host=<postgres-host>;Port=5432;Database=abook;Username=abook;Password=abook" \
--add-host host.docker.internal:host-gateway \
jncchds/abook:latestPostgreSQL with the pgvector extension must be reachable. The compose file below starts it automatically.
Full docker-compose.yml
services:
abook-api:
image: jncchds/abook:latest
ports:
- "5000:8080"
environment:
- ConnectionStrings__DefaultConnection=Host=postgres;Port=5432;Database=abook;Username=abook;Password=abook
- ASPNETCORE_ENVIRONMENT=Production
depends_on:
postgres:
condition: service_healthy
extra_hosts:
- "host.docker.internal:host-gateway"
restart: unless-stopped
postgres:
image: pgvector/pgvector:pg16
environment:
POSTGRES_DB: abook
POSTGRES_USER: abook
POSTGRES_PASSWORD: abook
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U abook -d abook"]
interval: 5s
timeout: 5s
retries: 10
restart: unless-stopped
volumes:
postgres_data:| Variable | Default | Description |
|---|---|---|
ConnectionStrings__DefaultConnection |
— | PostgreSQL connection string |
ASPNETCORE_ENVIRONMENT |
Development |
Production disables Swagger |
LlmDefaults__Provider |
Ollama |
Default LLM provider (Ollama, OpenAI, GoogleAIStudio) |
LlmDefaults__ModelName |
llama3 |
Default model name |
LlmDefaults__Endpoint |
http://host.docker.internal:11434 |
Default LLM endpoint |
LlmDefaults__ApiKey |
— | API key (required for OpenAI / GoogleAIStudio; optional for Ollama) |
LlmDefaults__EmbeddingModelName |
— | Embedding model for RAG (optional; falls back to chat model) |
AgentSettings__MaxConcurrentRuns |
3 |
Max simultaneous agent runs across all books/users |
PublicMode |
false |
Enable public library (anonymous access to published books) |
Changing
AgentSettings__MaxConcurrentRunsrequires restarting the API process/container.
For local development, copy src/ABook.Api/appsettings.Local.example.json → appsettings.Local.json and fill in your values.
Configure the LLM backend in Settings or via the API:
| Provider | Notes |
|---|---|
| Ollama | Default. Runs locally; host.docker.internal resolves to the host from inside Docker. |
| OpenAI | Provide an API key and model name (e.g. gpt-4o). Leave endpoint blank for the real OpenAI API; set a custom endpoint for any OpenAI-compatible API (Groq, Together, LM Studio at http://host.docker.internal:1234/v1, etc.). |
| Google AI Studio | Native Gemini connector. Requires an API key from aistudio.google.com. Suggested models: gemini-2.0-flash, gemini-2.5-pro. Embedding model: text-embedding-004. |
Configurations can be set globally, per-user, or per-book. The lookup order is: book-specific → user-default → global.
ABook includes a built-in Model Context Protocol server at /mcp. Any MCP-compatible client can connect to read and write book content and trigger agent workflows.
Setup:
- Open Settings → MCP Access
- Generate an API token (or click Regenerate to rotate)
- Add the server to your MCP client config using
Authorization: Bearer <token>
Claude Desktop (claude_desktop_config.json):
{
"mcpServers": {
"abook": {
"type": "http",
"url": "http://localhost:5000/mcp",
"headers": { "Authorization": "Bearer YOUR_TOKEN" }
}
}
}VS Code / GitHub Copilot (.vscode/mcp.json):
{
"servers": {
"abook": {
"type": "http",
"url": "http://localhost:5000/mcp",
"headers": { "Authorization": "Bearer YOUR_TOKEN" }
}
}
}User creates book (title, premise, genre, target chapters)
│
├─ optional: choose a base book (settings + context copied)
│
▼
┌──────────────────────────────────────────────────────┐
│ Planner — 4-phase pipeline │
│ Phase 1: Story Bible (world-building, tone, rules) │
│ Phase 2: Character Cards (roles, arcs, goals) │
│ Phase 3: Plot Threads (subplots, themes, arcs) │
│ Phase 4: Chapter Outlines (title + synopsis each) │
│ │
│ Asks clarifying questions up front; optionally │
│ pauses after each phase in Human-assisted mode │
└──────────────────────────────────────────────────────┘
│
│ ← "Plan Only" stops here so you can review
│ and edit outlines before clicking "Continue"
▼
For each chapter:
[Checker] ── pre-write: checks outline for contradictions
│
▼
[Writer] ── writes full chapter prose
│
▼
[Checker] ── continuity + style review → structured JSON patches
│
▼
[Editor] ── applies patches mechanically (skipped if no issues)
│
│ ← optional human pause in assisted mode: read the patched
│ chapter, edit anything, and steer the rewrite
▼
[Editor] ── creative rewrite — only when the Checker asked for one
│ or you gave instructions during the pause
▼
Done ✓
Agents stream tokens via SignalR as they write.
Workflow controls:
| Button | Behaviour |
|---|---|
| Plan Only | Runs the Planner and stops so you can review outlines |
| Write Book | Full pipeline from scratch (idempotent — skips completed phases and Done chapters) |
| Continue | Resumes from the first non-Done chapter |
| Continue Planning | Re-runs only the incomplete planning phases |
| Stop | Cancels any running agent cleanly |
In Human-assisted mode the app also pauses after each planning phase, and once per chapter — after the Editor's mechanical fixes are applied but before the creative rewrite. While it is paused nothing is generating, so you are free to edit the chapter (title, outline and prose), the characters, the plot threads or the story bible; the next step re-reads all of it. Whatever you type in the answer box is passed to the rewrite as author instructions, and the rewrite is skipped altogether when the Checker found nothing to rewrite and you left the box empty.
Chapter edits you make by hand are re-embedded in the background, so retrieval for later chapters sees your text rather than the version the agent wrote.
For books created from a base book, chapter-level RAG retrieval includes embeddings from all ancestor books in the continuation chain.
- .NET 10 SDK
- Node.js 20+
- Docker (for PostgreSQL with pgvector)
# Start PostgreSQL
docker-compose up postgres -d
# Start the UI dev server (proxies API calls to localhost:5000)
cd src/abook-ui
npm install
npm run dev
# Start the API (second terminal)
cd src/ABook.Api
dotnet run --urls http://localhost:5000The React dev server runs at http://localhost:5173 and proxies /api and /hubs to the ASP.NET server.
dotnet ef migrations add <MigrationName> --project src/ABook.Infrastructure --startup-project src/ABook.Api
dotnet ef database update --project src/ABook.Infrastructure --startup-project src/ABook.ApiAlways use the dotnet ef CLI — never create migration files by hand.
docker build -t abook .The multi-stage Dockerfile builds the React app (Node 20), compiles the .NET API (.NET 10 SDK), and produces a minimal runtime image (ASP.NET 10).
Set LLM_DEBUG_LOGGING=true to print the full chat history and LLM responses to the application log at Information level.
| Layer | Technology |
|---|---|
| Frontend | React 19, TypeScript, Vite, Zustand, react-markdown |
| Backend | ASP.NET Core 10, C# |
| LLM | Ollama, OpenAI SDK, Google AI SDK (per-provider direct calls) |
| Database | PostgreSQL 16 via EF Core 10 + Npgsql |
| Vector store | pgvector (in-DB, Pgvector.EntityFrameworkCore) |
| Real-time | SignalR |
| Auth | Cookie-based, IPasswordHasher<T>, Bearer API token for MCP |
| MCP | ModelContextProtocol.AspNetCore 1.2.0 — HTTP/SSE transport, 37 tools |
| Container | Docker, Docker Compose |