Imported from 15990187550/novel-dev (
AGENTS.md). Install upstream withnpx skills add 15990187550/novel-dev. Copyright stays with the author.
AGENTS.md
This file provides guidance to Codex (Codex.ai/code) when working with code in this repository.
Common Commands
All Python commands require PYTHONPATH=src because the package is novel_dev under src/.
# Run the FastAPI server
PYTHONPATH=src python3.11 -m uvicorn novel_dev.api:app --reload
# Run the MCP server
PYTHONPATH=src python3.11 -m novel_dev.mcp_server
# Run tests
PYTHONPATH=src python3.11 -m pytest tests/ -q
# Run a single test
PYTHONPATH=src python3.11 -m pytest tests/path/to/test.py::test_name -v
# Install in editable mode
pip install -e ".[dev]"
# Database migrations (Alembic)
alembic upgrade head
alembic revision --autogenerate -m "description"
Architecture Overview
This is an AI-driven novel writing pipeline. It uses a multi-agent system where each agent is an LLM-driven Python class. A NovelDirector orchestrates phase transitions.
Pipeline Phases
The 9 phases, in order, controlled by NovelDirector (src/novel_dev/agents/director.py):
- brainstorming —
BrainstormAgentgenerates a synopsis from uploaded setting documents - volume_planning —
VolumePlannerAgentgenerates a volume plan with self-scoring/revision loop - context_preparation —
ContextAgentassembles chapter context (entities, timeline, foreshadowings) - drafting —
WriterAgentwrites chapter draft beat-by-beat - reviewing —
CriticAgentscores the chapter on 5 dimensions - editing —
EditorAgentpolishes low-scoring beats - fast_reviewing —
FastReviewAgentchecks consistency and cohesion - librarian —
LibrarianAgentextracts world-state updates;ArchiveServicewrites to Markdown - completed — Pipeline loops back to
context_preparationfor the next chapter, orvolume_planningfor the next volume
Phase transitions are explicit: API POST /api/novels/{id}/advance calls director.advance(), which validates prerequisites and runs the appropriate agent.
LLM Abstraction
LLMFactory (src/novel_dev/llm/factory.py) is a global singleton (imported as llm_factory from novel_dev.llm). It reads llm_config.yaml and provides per-agent, per-task LLM clients with automatic fallback chains.
Key pattern:
from novel_dev.llm import llm_factory
from novel_dev.llm.models import ChatMessage
client = llm_factory.get("AgentName", task="task_name")
config = llm_factory._resolve_config("AgentName", "task_name")
response = await client.acomplete([ChatMessage(role="user", content=prompt)], config)
Critical: acomplete() receives two positional arguments: (messages, config). Any mock of acomplete in tests must accept both.
The call_and_parse() helper (src/novel_dev/agents/_llm_helpers.py) wraps this pattern: it calls the LLM, strips markdown code blocks, extracts the first JSON object, and runs a parser function. If parsing fails, it retries up to max_retries with exponential backoff.
Database
SQLAlchemy 2.0 async with asyncpg for PostgreSQL. Models use Mapped/mapped_column declarative style. Key tables:
novel_state— single row per novel;checkpoint_data(JSON) holds the entire working state (synopsis, volume plan, chapter plan, scores, etc.)chapters— per-chapter draft/polished text and scoresentities/entity_versions— versioned world-state entities (characters, items, etc.)entity_relationships— directed graph between entitiestimeline/spaceline— temporal and spatial narrative structureforeshadowings—伏笔 tracking with 埋下/回收 lifecyclenovel_documents— uploaded setting/style documents with vector embeddingspending_extractions— intermediate extraction results awaiting user approval
VectorCompat (src/novel_dev/db/models.py) is a compatibility type that uses pgvector.Vector on PostgreSQL and falls back to JSON on SQLite.
Important: Writes require explicit await session.commit(). The get_session() dependency in API routes yields a session but does NOT commit automatically.
Testing
Tests use a shared SQLite file DB (test_novel_dev.db) so the global engine (used by MCP server and non-overridden API routes) connects to the same database. See tests/conftest.py.
The mock_llm_factory fixture (autouse) globally mocks llm_factory.get() and llm_factory.get_embedder(). When writing new tests that mock LLM calls, either rely on this fixture or patch novel_dev.llm.llm_factory.
Services and Repositories
- Repositories (
src/novel_dev/repositories/) — thin async data access layers over SQLAlchemy - Services (
src/novel_dev/services/) — business logic:EmbeddingService,EntityService,ExtractionService,ArchiveService,ExportService - Agents (
src/novel_dev/agents/) — LLM-driven workflow steps; each agent typically instantiates its own repositories
EmbeddingService is instantiated per-request (not a singleton) because it needs a session and an embedder. The same pattern applies to most services.
API Structure
FastAPI with a single router in src/novel_dev/api/routes.py. The __init__.py mounts static files from src/novel_dev/web/ (a Vue 3 SPA single-file index.html).
Key endpoints:
POST /api/novels/{id}/documents/upload— upload setting/style documentsPOST /api/novels/{id}/brainstorm— generate synopsisPOST /api/novels/{id}/volume_plan— generate volume planPOST /api/novels/{id}/chapters/{cid}/context— prepare chapter contextPOST /api/novels/{id}/chapters/{cid}/draft— write chapter draftPOST /api/novels/{id}/advance— advance pipeline phasePOST /api/novels/{id}/librarian— run librarian manually
Configuration
llm_config.yaml at repo root defines per-agent LLM settings. Environment variables are loaded from .env via pydantic-settings. Required env vars for production: DATABASE_URL, MOONSHOT_API_KEY, MINIMAX_API_KEY.
Key Conventions
- All agent names are PascalCase in code (e.g.
VolumePlannerAgent) but snake_case inllm_config.yaml(e.g.volume_planner_agent).LLMFactory._normalize_agent_name()handles the conversion. - Word count for CJK text is computed by stripping whitespace and counting characters (
len(text.replace(" ", "").replace("\n", ""))). - The EditorAgent uses CJK bigram overlap (35% threshold) as a hallucination guard when rewriting beats.
- VolumePlannerAgent has a self-scoring loop: it generates a plan, scores it, and revises if
overall < 85, up to 3 attempts. - Fallback volume plans (when LLM parsing fails) must have at least 3 beats so the writer can reach target word count.
Formal Novel Workflow Generalization
- The formal novel workflow must stay genre-agnostic and source-driven. Do not hardcode a specific novel, character, setting domain, external IP, cultivation system, item, place, plot event, or example sentence into production prompts, cleanup logic, validators, or fallback content.
- Agent prompts are a hard red-line surface: every prompt embedded in agent code must be generalized, reusable, and source-driven. Do not write prompt text that targets one specific novel, chapter, character, scene, plot beat, setting term, item, clue, or generated failure example; put concrete story facts only in runtime context assembled from approved source materials, plans, settings, or chapter state.
- Novel genre templates are type-level rules only. Production templates must not contain concrete novel characters, places, organizations, plot events, one-off fallback paragraphs, or external-IP facts. Genre prompts may describe generic type expectations such as power-system boundaries, clue fairness, modern vocabulary policy, or cross-domain consistency, but must stay source-driven and reusable across novels in the same category.
- Fallbacks may preserve structure, mark missing information, or request/trigger a same-stage retry; they must not invent story facts such as pursuers, evidence, sects, artifacts, realm names, locations, or character choices unless those facts already exist in the imported materials, approved settings, outline, chapter plan, or current beat contract.
- Text cleanup and repair code must be non-generative by default: remove broken fragments, normalize wrappers/separators, or escalate to contextual rewrite. It must not patch known failed chapters with concrete replacement prose.
- Quality-left-shift checks must be configurable or derived from source context/style profile. Genre-specific forbidden terms, external-world validation, and domain mappings must not be global constants that bias unrelated novels.
- Test fixtures may use concrete names, but production code and formal workflow scripts must not leak fixture titles, minimal-acceptance wording, or sample plot assumptions into real novel data.