Skip to main content
Glama
README.md
# story-agent

`story-agent` is a local, MCP-first writing assistant backend for maintaining
long-form story continuity.

It keeps established story state in SQLite, protects unfinished writing with
persistent drafts, retrieves relevant older passages with local embeddings,
and gives an MCP client controlled tools for reading and updating the story.
The MCP client—Codex or another compatible agent—does the creative writing and
reasoning. The server does not call an LLM.

> This project is still under development, but the workflows documented here
> are implemented and tested.

## Features

- Multiple isolated story projects, created and selected through chat
- Transactional story initialization with metadata, a main character, and
  optional facts, lore, and an opening event
- Canonical characters, facts, relationships, chapters, events, and lore
- Persistent chapter drafts and automatic chunk-by-chunk saves
- Revision-checked, user-initiated draft rewriting
- Proposed canon changes kept separate until explicit review
- Deterministic continuity validation against established canon
- Atomic chapter commit with accepted story changes
- Recoverable canonical chapter commits and explicit undo
- Monotonic per-story canon revisions and compact commit receipts
- Confirmed project deletion without arbitrary filesystem access
- Bounded scene-context gathering for long writing sessions
- Local semantic search over chapters, lore, events, and character facts
- MCP tools, resources, and reusable workflow prompts
- Client-independent operation over the standard MCP `stdio` transport

## Architecture

```text
Codex or another MCP client
        |
        | stdio MCP
        v
story-agent MCP server
        |
        +-- safe project registry
        |       `-- selected isolated story database
        +-- deterministic domain services
        +-- SQLite canonical state
        +-- SQLite persistent drafts and proposals
        `-- local derived semantic index
```

- **MCP client:** conversation, prose, story reasoning, identifying claims,
  producing continuity reports, and proposing durable changes.
- **MCP server:** validation, bounded retrieval, controlled writes,
  transactions, and project selection.
- **SQLite:** canonical story state and persistent draft state.
- **Semantic index:** derived retrieval data; it is never canonical truth.

The core writing rule is:

```text
autosave protects work
commit changes canon
```

## Requirements

- Python 3.11 or newer
- An MCP client supporting `stdio`
- Node.js only for the optional MCP Inspector

Embeddings use the free local `BAAI/bge-small-en-v1.5` model through
`fastembed`. No paid API is required. A first-time model download must be
explicitly allowed from the admin CLI; normal searches never download a model
or call an external service.

## Installation

```bash
cd story-agent
python3 -m venv .venv
.venv/bin/pip install -e '.[dev]'
.venv/bin/story-agent --help
```

You can replace `.venv/bin/story-agent` with `.venv/bin/python main.py` in the
CLI examples if the console command is not installed.

## Connect an MCP client

The entry point is `mcp_server.py`. Two environment variables control storage:

- `STORY_AGENT_PROJECTS_DIR` holds the safe project registry and each managed
  story's isolated database.
- `STORY_AGENT_DATABASE` is the legacy single-database fallback when no
  managed project is selected.

### Codex configuration

Add this to `~/.codex/config.toml`:

```toml
[mcp_servers.story-agent]
command = "/absolute/path/to/story-agent/.venv/bin/python"
args = ["/absolute/path/to/story-agent/mcp_server.py"]
default_tools_approval_mode = "approve"

[mcp_servers.story-agent.env]
STORY_AGENT_PROJECTS_DIR = "/absolute/path/to/story-agent/data/projects"
STORY_AGENT_DATABASE = "/absolute/path/to/story-agent/data/story_agent.db"
```

Replace `/absolute/path/to/story-agent` with the repository's location on your
machine, then restart the MCP session. For MCP Inspector and second-client
setup, see
[docs/mcp-clients.md](docs/mcp-clients.md).

## Normal use: chat first

Normal writers do not need the terminal for story setup.

### Create a story

Say:

```text
Create a new fantasy story.
```

The client should ask once for genuinely missing required information:

- project display name
- story title and premise
- genre and tone
- setting summary
- main character name and description

Initial character facts, lore, and an opening event are optional. The client
then calls `create_story_project`, which creates and selects a safe isolated
database, and `initialize_story`, which writes the initial canon in one
transaction.

Project creation and initialization are intentionally separate. An empty
project can exist before its story details are supplied. Initialization refuses
to overwrite an already initialized story.

### Work with multiple stories

```text
List my stories.
Create another story called Moon Gate.
Switch to The Glass Harbor.
Which story is selected?
```

Every project has its own SQLite database. The model receives generated project
IDs, never arbitrary filesystem paths. Creating a project selects it; listing
does not. Switching requires an explicit `select_story_project` call.

Selection is stored in `project_registry.db`, so it survives restarts. Clients
using the same `STORY_AGENT_PROJECTS_DIR` share the registry, current selection,
and canonical data.

Deleting a project requires its generated ID and an exact display-name
confirmation. A selected project also requires an explicit selected-deletion
flag; successful deletion clears selection instead of silently choosing another
story.

### Write a long chapter

```text
Start chapter 1. Mara arrives at the abandoned lighthouse.
Continue with her meeting in the harbor office.
Continue, but keep the argument restrained.
```

After every generated chunk, the client should call `append_to_draft`. The
complete draft remains locally stored across prompts and server restarts but is
not yet a canonical chapter.

The client can keep context bounded by using the draft summary, recent excerpt,
pending changes, focused canonical scene context, semantic retrieval, and
`get_draft_segment` only when an older portion is needed. It does not need to
send the entire chapter on every turn.

### Finish and commit

Only explicit requests start the commit workflow:

```text
I'm done with this chapter.
Finish chapter 1.
Commit this chapter.
Make this chapter canon.
```

`Continue`, `next`, `looks good`, and `okay` are not commit approval.

The intended flow is:

1. Load the full stored draft.
2. Extract and validate continuity-relevant structured claims.
3. Present the continuity report.
4. Review every proposed persistent change.
5. Ask the user to accept or reject each proposal.
6. Mark the draft ready.
7. Commit the chapter and accepted changes in one transaction.
8. Read canonical state back to verify it.

A commit rolls back on failure. It cannot proceed until continuity is reviewed
and every pending change has an explicit decision.

Every successful commit returns and persists a compact receipt containing the
new canon revision, chapter summary, commit/version IDs, and the accepted
events, facts, relationship changes, lore, and new characters that were
actually applied. The receipt never duplicates the complete chapter.

### Rewrite an active draft

A rewrite is performed only when the user explicitly requests one. The client
reads the current draft and its revision, produces revised text, then calls
`replace_current_draft`. If another autosave changed the draft meanwhile, the
stale revision is rejected instead of overwriting newer work.

Replacement changes only draft content and its optional summary. It does not
modify or commit canon. Existing pending proposals are retained and reported
for re-review because rewritten prose may no longer support them.

### Undo a chapter commit

Every successful `commit_draft` stores a canonical snapshot from immediately
before that commit. To undo, the client first calls `get_story_versions`, shows
or resolves the latest version, and calls `undo_last_commit` with that expected
version ID after explicit user intent.

Undo restores the complete structured canon transactionally. Draft state is
separate and is not restored. Derived semantic chunks are removed, ensuring
rolled-back content cannot appear in search; rebuild the semantic index before
using semantic retrieval again.

An undone receipt remains in SQLite as historical audit metadata but is marked
inactive with its undo revision and timestamp. `get_recent_canon_changes`
returns only active receipts, so an undone commit never appears to be current.

## Canon revisions and commit receipts

Each story database has a monotonic `canon_revision`. Canonical domain
transactions advance it exactly once; draft autosaves, rewrites, proposal
storage, and read operations do not. Undo also advances the revision because it
creates a new, intentionally restored canonical state rather than moving the
counter backward.

Commit receipts are deterministic audit and retrieval aids generated from
already-approved structured proposals and their actual write outcomes. They
are not model memory and are not an alternative source of truth. Exact
characters, relationships, timeline events, lore, and chapters must still be
read from their canonical tables.

At the beginning of a new session, an MCP client can call
`get_recent_canon_changes` to recover a bounded view of recent durable changes
without rereading full chapters.

## Canon versus drafts

Canonical state contains story metadata, characters, explicit facts,
relationships, committed chapters, timeline events, and lore.

Draft state contains an in-progress or ready chapter, its full autosaved text,
a bounded summary, and proposed new characters, facts, relationships, lore,
and events. Saving a draft never changes canonical story state. Proposals remain
pending until explicit review during commit.

## Continuity checking

```text
draft prose
  -> client identifies structured claims
  -> server compares claims with canonical SQLite state
  -> client writes the final continuity report
```

The validator supports character existence, character facts, explicit
knowledge, relationship state, event order, chapter existence, and lore
content. Results are `confirmed`, `contradicted`, or `unknown`. Missing canon is
unknown, not an error. Knowledge checks use explicit facts such as `Knows: ...`
and `Does not know: ...`.

The server does not interpret raw prose, rewrite drafts, or persist corrections.
Those reasoning and writing decisions remain with the MCP client.

## Exact and semantic retrieval

Use exact tools for authoritative identities, facts, relationships, chronology,
chapter numbers, and lore. Use semantic search to locate unstructured passages
whose wording differs from the query. For example, a search for `Alex distrusts
authority` can retrieve a passage where Alex avoids a commander and refuses an
officer's orders.

Semantic sources include chapter content and summaries, lore passages, event
descriptions, and character facts. Results contain traceable source metadata.
Similarity is only a lead; verify important structured claims with exact tools.

The index is derived and can always be rebuilt from canonical SQLite state:

```bash
.venv/bin/story-agent --database data/my-story.db search-index-rebuild

# Permit the first local model download when needed:
.venv/bin/story-agent --database data/my-story.db \
  search-index-rebuild --allow-model-download

.venv/bin/story-agent --database data/my-story.db search-index-status

.venv/bin/story-agent --database data/my-story.db \
  semantic-search "Alex distrusts authority" --limit 5

.venv/bin/story-agent --database data/my-story.db \
  semantic-search "the red stone" \
  --source-type chapter --source-type lore
```

## MCP interface

### Project tools

| Tool | Purpose |
| --- | --- |
| `list_story_projects` | List registered projects. |
| `create_story_project` | Create an isolated empty project and select it. |
| `select_story_project` | Switch explicitly to a registered project ID. |
| `get_current_story_project` | Report selection and initialization state. |
| `delete_story_project` | Permanently delete a confirmed registered project. |

There is no path-based project selection or generic file-delete tool.

### Canonical read and validation tools

| Tool | Purpose |
| --- | --- |
| `get_character` | Read a character and facts by name. |
| `get_chapter` | Read one canonical chapter by number. |
| `get_recent_chapters` | Read bounded recent chapters. |
| `get_timeline` | Read events with optional chapter bounds. |
| `search_lore` | Perform case-insensitive exact lore search. |
| `get_relationship` | Read a relationship between two characters. |
| `get_scene_context` | Gather bounded characters, relationships, chapters, events, and lore. |
| `semantic_search_story` | Locate semantically related canonical passages. |
| `validate_story_claims` | Compare structured claims with canon. |
| `validate_story_change_proposal` | Validate a client-authored proposal without saving it. |

### Setup and draft tools

| Tool | Purpose |
| --- | --- |
| `initialize_story` | Atomically create metadata and initial canon. |
| `get_story_status` | Read bounded story, chapter, draft, and proposal status. |
| `get_current_draft` | Read bounded active-draft context. |
| `get_draft_segment` | Read one bounded part of the full draft. |
| `save_draft` | Create or replace an in-progress draft. |
| `append_to_draft` | Append a chunk and autosave it. |
| `replace_current_draft` | Replace draft text with optimistic revision checking. |
| `save_pending_draft_changes` | Store proposed durable changes outside canon. |
| `mark_draft_ready` | Mark the active draft ready for commit. |
| `commit_draft` | Atomically promote the chapter and accepted changes. |
| `get_story_versions` | List bounded canonical commit-version metadata. |
| `get_recent_canon_changes` | Read recent active compact commit receipts. |
| `undo_last_commit` | Restore canon to before the latest commit. |

Pending change types are `new_character`, `character_fact`, `relationship`,
`lore`, and `event`. Each requires a short evidence excerpt.

### Controlled canonical writes

| Tool | Purpose |
| --- | --- |
| `create_character` | Create a canonical character. |
| `save_chapter` | Save canonical chapter text directly. |
| `add_story_event` | Add a canonical timeline event. |
| `add_character_fact` | Add an explicit canonical character fact. |
| `update_relationship` | Create or update a canonical relationship. |
| `add_lore_entry` | Add a canonical lore entry. |

These remain useful for administration and explicitly approved changes. Normal
chapter writing should use drafts and reviewed commit.

### Resources

| URI | Content |
| --- | --- |
| `story://summary` | Story overview, latest chapter, and bounded state. |
| `story://characters` | Bounded canonical character directory. |
| `story://timeline` | Bounded recent canonical timeline. |
| `story://chapter/{number}` | Metadata and bounded chapter content. |
| `story://character/{name}` | Description and bounded character facts. |

Resources are concise and read-only.

### Reusable prompts

| Prompt | Purpose |
| --- | --- |
| `write_next_chapter` | Context retrieval, chunked writing, autosave, review, and commit workflow. |
| `check_draft_continuity` | Evidence-based continuity review without mutation. |
| `review_story_changes` | Review proposed canon changes before writes. |

Prompts are declarative instruction templates returned to the client. They do
not call a model or execute tools themselves. Presentation varies by client: a
prompt may appear as a menu item, slash command, API feature, or an instruction
an agent chooses during conversation.

## Admin and development CLI

The CLI is optional and works with an explicit database path. Put `--database`
before the command:

```bash
.venv/bin/story-agent --database data/my-story.db COMMAND
```

Different unmanaged stories should use different files. Multi-project chat use
is managed through MCP, not these direct CLI paths.

### Setup, status, and drafts

```bash
# Terminal setup fallback; refuses to overwrite an initialized story
.venv/bin/story-agent --database data/my-story.db init

.venv/bin/story-agent --database data/my-story.db status
.venv/bin/story-agent --database data/my-story.db schema-status
.venv/bin/story-agent --database data/my-story.db draft
.venv/bin/story-agent --database data/my-story.db \
  draft --start 0 --max-characters 4000

.venv/bin/story-agent --database data/my-story.db \
  draft-append 1 "Chapter title" "New chapter text"

.venv/bin/story-agent --database data/my-story.db \
  draft-append 1 "Chapter title" "New chapter text" \
  --summary "Summary so far"

.venv/bin/story-agent --database data/my-story.db draft-ready
.venv/bin/story-agent --database data/my-story.db \
  commit --continuity-reviewed

# Every pending ID must be decided
.venv/bin/story-agent --database data/my-story.db \
  commit --continuity-reviewed \
  --accept-change 1 --accept-change 2 --reject-change 3

# Does not remove canonical chapters
.venv/bin/story-agent --database data/my-story.db discard-draft
```

### Direct canonical commands

```bash
# Characters and facts
.venv/bin/story-agent --database data/my-story.db \
  character-create "Alex" --description "A reluctant courier"
.venv/bin/story-agent --database data/my-story.db character-get 1
.venv/bin/story-agent --database data/my-story.db fact-add 1 "Fears the tribunal"
.venv/bin/story-agent --database data/my-story.db fact-list 1

# Chapters
.venv/bin/story-agent --database data/my-story.db \
  chapter-save 1 "Arrival" "Chapter content"
.venv/bin/story-agent --database data/my-story.db chapter-get 1
.venv/bin/story-agent --database data/my-story.db chapter-list

# Timeline timestamps require a timezone
.venv/bin/story-agent --database data/my-story.db \
  event-add "Alex reaches the city" "2040-01-01T12:00:00+00:00" \
  --chapter-id 1 --character-id 1
.venv/bin/story-agent --database data/my-story.db timeline

# Relationships
.venv/bin/story-agent --database data/my-story.db \
  relationship-save 1 2 "distrustful" \
  --description "They suspect each other"
.venv/bin/story-agent --database data/my-story.db relationship-get 1 2

# Lore
.venv/bin/story-agent --database data/my-story.db \
  lore-create "Red Stone" "The relic glows near broken oaths"
.venv/bin/story-agent --database data/my-story.db lore-search "relic"
```

```bash
.venv/bin/story-agent --help
.venv/bin/story-agent commit --help
```

## Cross-client test

To prove state belongs to the backend rather than one agent:

1. Configure two clients to launch the same `mcp_server.py`.
2. Give both the same `STORY_AGENT_PROJECTS_DIR`.
3. Create or select a disposable test project.
4. Add a harmless character fact from client A.
5. Read it from client B.
6. Add another fact from client B and read it from client A.

The clients need not run simultaneously. Sharing only the source code is not
enough; they must share the project registry directory. Tested Inspector
commands are in [docs/mcp-clients.md](docs/mcp-clients.md).

## Tests

### Synthetic scale fixtures

The developer-only generator creates a **new**, clearly marked test project in
the configured project registry. It never overwrites an existing project and
needs no LLM, API key, or network access:

```bash
.venv/bin/python -m tools.generate_test_story --project-name scale-100 \
  --chapters 100 --characters 30 --facts-per-character 10 \
  --events-per-chapter 3 --relationships 75 --lore 50 \
  --words-per-chapter 2500 --seed 42
```

Use `--with-active-draft` to leave an uncommitted next chapter. `--build-index`
uses the normal semantic-index rebuild path and requires the embedding model to
be cached locally; it never downloads a model. The output reports the project
ID, counts, seed, and index state. Configuration has per-field and total-size
limits (`--help` lists the arguments). The generator uses production domain
writes to make a canonical end-state fixture; it deliberately does not simulate
chapter-by-chapter commit receipts or undo history. Use the existing commit
workflow tests for those behaviors.

### Backend benchmarks

Run the offline benchmark against disposable generated projects, or a temporary
SQLite copy of an existing registered synthetic project:

```bash
.venv/bin/python -m tools.benchmark_story_agent --preset all \
  --iterations 7 --warmup 2 --json benchmark-results.json
.venv/bin/python -m tools.benchmark_story_agent --project-id story_... \
  --compare benchmark-results.json
```

Presets contain 5, 50, and 120 chapters. The harness uses the locally cached
FastEmbed model and never downloads one. The report gives median, mean, min,
max, and p95 milliseconds; JSON also retains every sample. `--json` creates a
new file and refuses to overwrite one. Optional `--warn-ms OPERATION=MS` and
`--warn-percent PERCENT` annotate results without failing the benchmark.
Preparation and project cloning are excluded from measured operation times.
"Cold" means the first call in the current process; OS file caches are not
flushed. The adapter measurements invoke Python MCP adapter helpers, not the
stdio transport, so they should not be read as full client round-trip latency.
Draft and commit tests run only on temporary copies; every measured commit is
undone on its own copy. Ordinary story projects are rejected by `--project-id`.

Run service/application and MCP transport tests separately:

```bash
.venv/bin/python -m pytest -q --ignore=tests/test_mcp_transport.py
.venv/bin/python -m pytest -q tests/test_mcp_transport.py
```

Run the long-story correctness suite explicitly:

```bash
.venv/bin/python -m pytest -q -m long_story
# Explicitly exclude the optional real-model check
.venv/bin/python -m pytest -q -m 'long_story and not local_embedding'
```

Normal runs exclude `long_story`. Its fixed 5-, 50-, and 120-chapter fixtures are
generated once per test session, with a separate SQLite copy for each test.
The suite covers old character/event retrieval, relationship history, bounded
scene context, draft recreation, commit/undo/recommit, restarts, two large
isolated projects, migrations, and broken-reference detection. The terminal
summary records fixture counts. Deterministic concept vectors check search
contracts. The cached FastEmbed check is opt-in with
`STORY_AGENT_TEST_LOCAL_EMBEDDINGS=1` and skips if its model is unavailable;
it requires additional memory and never downloads a model. Document embeddings
use batches of 32 rather than FastEmbed's default 256 to bound inference memory.
Canon-revision
compare-and-set writes are not implemented, so that capability is explicitly
skipped; draft revision conflicts and stale semantic indexes are tested.

Starting a discarded draft again, or starting an undone chapter again, clears
the prior draft proposals and continues the draft revision counter. Accepted
canon must be reviewed again before recommit. Opening a database detects invalid
canonical references or required story metadata; semantic search reports an
invalid index if its chunks contain missing sources or invalid vector shapes.

Coverage includes initialization and rollback, canonical services, autosave,
atomic commit, proposals, continuity, semantic retrieval, resources, prompts,
project isolation, persistent selection, and MCP transport discovery.

## Data safety

### Native ZIP backup and restore

Backup/import are local CLI operations, not MCP file-path tools:

```bash
story-agent backup-create moon-gate.zip
story-agent backup-create moon-gate.zip --project-id STORY_PROJECT_ID
story-agent import-backup moon-gate.zip
story-agent import-backup moon-gate.zip --json
# Optional synchronous rebuild using the already-cached local embedding model:
story-agent import-backup moon-gate.zip --rebuild-index
```

The registry root follows `STORY_AGENT_PROJECTS_DIR`, as with MCP. Export uses
the selected project unless an ID is given, and refuses an existing output file.
The ZIP v1 format contains exactly `manifest.json` and `story.sqlite`. The manifest
records container/schema versions separately, original project ID, display name,
title, creation time, component flags, optional application version, and the
database's SHA-256 checksum. Drafts (including pending changes), canon revision,
and commit/undo history are included. Vectors are deliberately omitted. Backups
contain private story/draft content; do not commit them to a public repository.

Import validates archive names, duplicates, links, compression/size limits,
manifest, checksum, native schema objects, canonical references, drafts, and
historical snapshots. Only the fixed database member is written to a controlled
temporary directory; arbitrary ZIP paths are never extracted. Limits are 512 MiB
for the database/archive (plus a 64 KiB manifest) and a 1000:1 compression ratio.
Custom database views, virtual tables, and non-native triggers are unsupported.
Checksums detect damage, not authenticity: restore only backups you trust.

Older supported databases migrate on the temporary copy. Newer schemas or
container versions are rejected. Registration happens only after validation,
using a fresh generated project ID and a duplicate-safe name such as
`Moon Gate (Imported)`. Existing databases and selection are untouched, even if
index rebuilding or final validation fails. After import, explicitly select
the returned ID in chat. Canon revision is preserved, not reset. Imported draft
text remains non-canonical, and compatible version history retains undo support.
If component flags indicate omitted drafts/history, their corresponding tables
must be empty; no missing history or structured canon is fabricated.

Rebuilding is off by default: missing retrieval is a nonfatal health warning.
Use the normal index rebuild command afterward, or request `--rebuild-index`.
No model download or hidden API call occurs. A requested rebuild failure rejects
the import; retry without it to restore canon first. Normal failures clean up the
unpublished project copy; a hard crash may leave an unregistered managed file,
but never selects it or overwrites another project. This phase does not support
Markdown, TXT, EPUB, or arbitrary novel-to-canon extraction.

### Project health / doctor

In chat, ask for project health. The read-only MCP tool
`get_project_health(project_id=None)` inspects the selected story or one
registered project without switching it. It reports schema, canon revision and
references, active draft metadata, semantic index health, and undo availability.
An active draft is normal and does not lower health.

```bash
story-agent doctor
story-agent doctor --project-id STORY_PROJECT_ID
story-agent doctor --json
story-agent doctor --deep
# Explicit single-database developer fallback:
story-agent --database data/my-story.db doctor
```

Statuses: `healthy` means the checked metadata is consistent; `warning` covers
missing optional retrieval, empty setup, or migration needed; `degraded` covers
stale/invalid retrieval or recoverable draft/version problems; `invalid` covers
incompatible/unreadable canon or broken canonical references. Results contain
machine-readable codes and recommended actions. `safe_to_continue` is false for
core corruption, incompatible schema, missing selection, and pending migration;
it is not canon approval or a guarantee about uninspected prose.

Standard checks use read-only metadata/count/reference queries. They do not load
chapter bodies, vectors, full snapshots, or an embedding model. Snapshot checks
are explicitly `header_only`; index checks are `metadata`. `--deep` additionally
checks SQLite integrity, the latest available undo snapshot's shape/references,
and full index checksums and source contents. Neither mode repairs, migrates,
rebuilds, creates a registry/database, or changes selection. Diagnostics target
one project; they do not sweep every registered project or validate every
historical snapshot. An older schema is reported without migrating it.

### Semantic index recovery

The semantic index is disposable derived data in each story's SQLite database.
Canonical tables, drafts, and undo snapshots do not depend on it. Schema v5 adds
an index manifest: registered project ID (or no binding for standalone admin
databases), schema/index/chunking versions, source count, source and vector
digests, build completion, timestamps, and indexed canon revision. Model,
dimension, chunk count, and text-change revision remain in index state.

`search-index-status` reports `health`: `fresh`, `missing`, `stale`, `building`,
or `invalid`, plus `rebuild_required`. Legacy `state` values `ready` and
`unbuilt` remain for compatibility. Missing/stale/invalid search returns
`ok: false`, a `semantic_index_*` error, and no results. Reads never rebuild
automatically or download a model.

Rebuild a registered project without changing the selected project:

```bash
.venv/bin/story-agent search-index-status --project-id STORY_PROJECT_ID
.venv/bin/story-agent search-index-rebuild --project-id STORY_PROJECT_ID
```

Replace `STORY_PROJECT_ID` with an ID returned by `list_story_projects`.
The existing `--database` admin option also remains available. Use `--model`
on rebuild to change models; `--allow-model-download` is an explicit opt-in.

Rebuilds stage batches of 32 embeddings in temporary SQLite storage. They
validate dimensions, finite vectors, counts, and digests before replacing all
cache tables and metadata in one transaction. Failure preserves the old cache;
concurrent canon changes reject promotion. Search checks source mappings,
checksums, configuration, and canonical chunk content before returning results.
An old valid cache stays available during staging; partial staging data is
never published. Temporary build storage is removed on normal success/failure;
a process crash may leave unused temporary files, never promoted results.

Canon writes make the index stale; undo removes vectors; draft rewrites do not
invalidate it. Schema v5 migration requires rebuilding older indexes without
changing canon. Project deletion removes its database and index together.
Copying/importing a database into a different registered project requires a
rebuild. There is currently no product import command. Removing all three
`semantic_*` cache tables is recoverable through rebuild; corruption of the
entire SQLite file still requires normal database backup recovery.

Recovery tests use deterministic test vectors, not a real model quality claim:

```bash
.venv/bin/python -m pytest -q tests/test_index_recovery.py
.venv/bin/python -m pytest -q -m long_story
```

Story databases record their schema version with SQLite `PRAGMA user_version`.
New databases start at the current version. Existing, recognized story-agent
databases migrate in order when selected or opened; listing projects only
inspects their versions and does not migrate them. Before migration, the server
creates a `.schema-vN.*.bak` SQLite backup beside the story database. A failed
migration rolls back without advancing the version. Databases from a newer
story-agent version are refused, never downgraded.

The optional `schema-status` CLI command reports the database version, the
application version, and whether migration is needed without changing the
database. Semantic vectors are derived data: migrations that change their
layout invalidate the index, which can be rebuilt with `search-index-rebuild`.
Story undo restores canonical content, not the database schema, so it cannot
reverse a schema migration.

- Project filenames use random internal IDs, not user input.
- Selection accepts only registered, validated project IDs.
- MCP responses do not expose full internal database paths.
- Duplicate display names are rejected case-insensitively.
- Project creation never overwrites an existing database.
- Project deletion requires ID, display-name confirmation, and extra intent for
  the selected project.
- Story initialization and chapter commit are transactional.
- Canonical undo validates the expected latest version and restores atomically.
- Failed commits create neither a receipt nor a revision increment.
- Draft replacement rejects stale revisions.
- Reads, resources, prompts, validation, and search do not mutate canon.
- No delete-all, raw SQL, automatic canon persistence, or automatic draft
  rewrite interface exists.

Back up `STORY_AGENT_PROJECTS_DIR` to preserve managed stories. For an unmanaged
CLI story, back up its `.db` file. Do not manually edit `project_registry.db` or
move its story databases independently.

## Troubleshooting

### The MCP server is not visible

- Check that the configured Python and `mcp_server.py` paths are absolute.
- Confirm dependencies are installed in `.venv`.
- Restart the MCP session after configuration changes.
- `mcp_server.py` speaks protocol messages over standard I/O; it is not an
  interactive terminal program.

### Two clients see different stories

Ensure both use exactly the same `STORY_AGENT_PROJECTS_DIR`, then inspect the
selection with `get_current_story_project`.

### Initialization is rejected

Initialization needs a selected empty project. Create one first. An initialized
project cannot be overwritten; create or select another project.

### Semantic search is unavailable or stale

Run `search-index-status`, then rebuild the affected story's index. Add
`--allow-model-download` only when you intentionally permit the local model
download.

### A draft will not commit

Confirm that the draft is ready, continuity was reviewed, every pending change
was accepted or rejected, and referenced canonical entities are valid. A failed
commit should leave the draft and canon unchanged.

## Project layout

```text
story-agent/
├── database.py                  # SQLite schema and connection boundary
├── models.py                    # Domain models
├── main.py                      # Admin/development CLI
├── mcp_server.py                # Thin MCP interface
├── services/
│   ├── story_service.py         # Canonical operations
│   ├── workflow_service.py      # Setup, drafts, proposals, atomic commit
│   ├── project_service.py       # Multi-story registry and selection
│   ├── continuity_service.py    # Structured claim checks
│   ├── proposal_service.py      # Proposal validation
│   └── retrieval_service.py     # Chunking, embeddings, semantic search
├── tests/
├── docs/mcp-clients.md
└── data/
```

## Current boundaries

The project intentionally has no hidden/server-side LLM, paid embedding API,
GUI, automatic planning, automatic draft rewriting, automatic canon promotion,
raw filesystem/SQL access, destructive reset, or autonomous background
behavior. Structured canon remains authoritative; embeddings only support
retrieval.

## License

See [LICENSE](LICENSE).

Maintenance

ActivityMaintained
ResponsivenessNo issues