Skip to main content

fabricatio-novel

MIT Python Versions PyPI Version PyPI Downloads PyPI Downloads Bindings: PyO3 Build Tool: uv + maturin

AI-powered novel generation — outline to publication-ready EPUB.

Full dataflow and generation-logic walkthrough: docs/superpowers/specs/2026-08-21-novel-generation-dataflow.md

Installation

pip install fabricatio[novel]
# or
uv pip install fabricatio[novel]

For the CLI tool:

pip install fabricatio-novel[cli]

Pipeline

Generation runs a staged, template-driven pipeline over a context tree (NovelContext → ChapterContext → StoryContext → SceneContext), with every plan and character state proposed by the LLM in batched calls:

  1. Metadata — outline → NovelPlan (title, description, word count, global writing constraint, bible)
  2. Characters — bible roster → one CharacterSpan (start + end card) per character for the whole novel
  3. Chapters — chapter plans, then N-1 boundary cards per character so chapter 1 starts at the roster start and the last chapter ends at the roster end
  4. Stories — per chapter: story plans, then S-1 boundary cards per character anchored on the chapter's start/end
  5. Scenes — per story: scene plans; the story's spans are broadcast to every scene, which is where the prose is actually written
  6. Assembly — the composed context tree becomes a Novel; NovelBuilder (Rust/PyO3) produces the EPUB
  7. Illustration (wri only) — one post-process pass proposes an image prompt per scene, renders it via ComfyUI, and embeds the PNGs in the EPUB

Character state is two pure cards, never an interpolated chain: the LLM is only asked for the coarsest boundary states, and code stitches them so continuity is guaranteed. Single-child levels inherit their parent's spans with no LLM call.

The CLI runs the pipeline as a staged workflow (DebugNovelWorkflow), persisting a whole-tree JSON snapshot after every stage so any wrong result is traceable:

01_init → 02_metadata → 03_bible → 04_characters → 05_chapter_plans → 06_story_plans
       → 07_scene_plans → 08_scenes → 09_novel → export

Those snapshots are also the run's resume points. --resume <run> reloads the newest snapshot the run holds as the starting state and restarts at the stage that follows it — the newest stage itself when the run reached the end of the pipeline, so a finished novel can be exported again or drawn. --stage <name>, e.g. 08_scenes, restarts at that stage instead, continuing from the newest snapshot taken before it; the named stage runs again, so it is also the fix for a run that died inside it. In both cases the run drives the same workflow with every stage up to the restart point leaving itself out, so a run stopped mid-novel finishes instead of starting over and nothing already written is paid for twice. A stage the run holds no state before fails the resume before any LLM call rather than quietly running the novel again, and a run is only ever resumed by a command of its own family: w for a plain run, wr/wri for a sealed one. Everything the snapshot carries comes back with it — the outline, the resolved skills, the retrieval settings and the seals — while routing (--send-to) and the export options are per invocation and are stated again as usual. The two sealed commands interchange, so wri --resume <wr run> --stage 09_novel draws the illustrations of a novel that was written without them.

An optional RAG variant (RagDebugNovelWorkflow) retrieves WritingStyleDocument entries from LanceDB at two levels. The novel level searches the outline in the init stage's before-hook and renders the documents into the metadata and chapter-planning prompts. The chapter level retrieves once per story, renders the documents raw into that story's scene prompts, and stops rendering them into later prefix walks once every scene of the story carries content (the raw texts stay on the context for scoring and audit).

The wri variant (RagIllustrationDebugNovelWorkflow) appends one final post-process stage that illustrates every scene of the finished context before export; its stage snapshots stay identical to wr's.

Data Flow & Prompt Assembly

The pipeline follows one principle: every LLM touchpoint is a template plus named context variables, and nothing else. Each stage renders a Handlebars template from the current context channel; planner calls parse the reply into validated pydantic models, while the single prose call captures raw paragraphs. Everything between calls — word-count allocation, character-arc stitching, prefix propagation, assembly — is deterministic code.

Staged pipeline with its LLM call inventory

LLM call inventory

Stage Template · prompted with Yields
Metadata novel_metadata_requirement ← outline, language, constraint, skills, style_references NovelPlan (adopted onto the root)
Bible setting_bible_characters + setting_bible_background ← outline, language, skills SeriesBible — character roster and background settings, immutable for the run
Roster spans novel_character_span ← outline, skills, bible prompt block, title, description CharacterSpan[] — skipped without a roster
Chapter plans plan_requirement ← outline, novel fields, word count, styles, constraint, characters ChapterPlan[]
Chapter boundaries boundary_requirement ← skills, roster spans, chapter titles/descriptions N−1 boundary cards per character
Story plans plan_requirement ← chapter fields, styles, constraint, characters, cast StoryPlan[]
Story boundaries boundary_requirement ← skills, chapter spans, story titles/descriptions S−1 boundary cards per character
Scene plans plan_requirement ← story fields, styles, constraint, characters, cast ScenePlan[]
Scene prose scene_requirement ← 11 variables, see below plain prose → Scene.content
Scene illustration scene_illustration_prompt ← novel/chapter/story/scene titles, description, content, cast, illustration_constraint SketchSpec (prompt, negative prompt, LLM-chosen mp/prop) → PNGs rendered concurrently per scene (post-process)

Templates live in templates/built-in/ and are selectable through the Configuration keys below.

What flows down the tree

Each level materializes its plan via create(outline).update_from(plan).set_plan(plan), with word counts assigned out-of-band via expect_ (the root takes the novel plan's count, children take their allocated share). update_from adopts only the plan's scalar fields (title, description, cast); the style and constraint channels are stacked explicitly by the composing capability — set_writing_styles and set_writing_constraints each receive the parent's list followed by the child plan's own — so both accumulate down the chain, the novel's own channels being seeded from the novel plan. Each level then passes state down:

  • Running manuscript — an append-only ContextLog; every walk seeds each child with exactly the bytes that precede it in the final book (iter_prefixed_contexts)
  • Setting bible — rendered once at the root into a setting_bible prefix entry; every descendant inherits it through its own log
  • Selected skills — the names the user gave (plus their lookup roots) are resolved once at the root, carried by every level, and fetched by name from the skill library whenever a prompt is assembled: one byte-stable section heads the metadata proposal, the setting bible proposals, the roster-span proposal, the boundary-card proposals, every plan prompt, every scene write prompt and every RAG query refinement
  • Word budget — each level splits its expected_word_count among children by plan weight
  • Writing style — accumulated verbatim down the chain (style stacking); entries select the diction the prose must use — from the run's skill documents when the run carries them — rather than inventing amounts
  • Writing constraint — accumulated verbatim down the chain (constraint stacking): the ancestors' entries first, the level's own last, and the whole list reaches that level's planner and every scene's prose prompt; where two contradict, the later (deeper) entry wins
  • Unit partition — a level's children are proposed as one batch per parent, so the batch itself is the partition: the ## Requirements block in the plan prompt keeps every unit inside the parent's description and makes the units cover it exactly once
  • Character arcs — the roster fixes both endpoints; intermediate boundary cards are proposed per level and stitched in code; scenes receive the finished span list read-only

Composed prose flows back up: scene content enters the logs, and Novel.from_context aggregates the whole tree for export.

Context-tree data flow: what flows down, what flows up

How a scene prompt is assembled

The scene write is the only content-producing call, so its prompt is engineered for provider prefix caching: every row above ## Scene is byte-identical across the scenes of a story, and divergence starts exactly at the per-scene tail.

Scene prompt assembly: sources, template sections, response

Benchmark

Every finished run is measured against its own artifacts — no LLM calls, no re-execution. w/wr/wri print the scorecard right after the run, next to the delta against the newest comparable earlier run:

  • Hard gates — terms the corpus marks as gated, a chapter export that disagrees with the scenes, an empty scene, prose in the wrong script.
  • Compared metrics — length against target, both sides in the framework's own words (the core's word_count, the same counter the scene planner's satisfaction ratio uses), seam echo (the same moment written at the end of one scene and again at the top of the next), verbatim sentence repeats, cross-scene 12-gram overlap, sentence lengths (mean, median, max, spread and the share of run-on sentences, on the core's Unicode sentence segmentation), vocabulary repeats (how many n-grams repeat inside a fixed-size window, plus the thirty most frequent n-grams of every size from one to six), watch-term rate, run duration. Sentences and vocabulary are counted in characters, so every metric reads the same for a Chinese and an English run without a per-language rule.
  • Provenance — a plan_fingerprint over the planning snapshots: two runs sharing it replayed planning from the cache, so they compare per scene, with an exact sign test over the scenes that changed.

Every number above is measured in Rust — one Measure trait per signal, in fabricatio_novel.rust — and the scorer only sequences those measures: it owns the report and never re-measures, so a metric a measure adds reaches the scorecard JSON without a change here. The sizes a run is measured at live under [ext.novel.benchmark] (see Configuration).

A metric that moves off zero is never called noise — one gated term is a regression regardless of sample size. A metric with no better side (how long the sentences are, how much their lengths vary) reports an out-of-band move as drift instead of inventing a verdict. Semantic over-run (a scene staging the next scene's beats in its own words) is deliberately not gated: calibration showed term statistics cannot decide it, which is why benchmark/scorecard.py records what is measurable and what is not.

The scorecard and the comparison are handlebars templates (bench_scorecard, bench_comparison), so their wording and layout change without touching the scorer; the board is one table and nothing else, so Board renders it directly. The scorer only measures: every number a report prints is carried by the models themselves as a *_display field (handlebars has no arithmetic helpers, so rounding, percentages and units are precomputed next to the measurement), and a table's column widths span several rows (Comparison.table, Board), so the models measure and pad it for the same reason and the template prints it as-is; --json leaves those report-only fields out of the machine-readable form.

Each model also owns how it is read and measured, as classmethod factories — StageArtifact.collect/ load, SceneRef.collect, SceneScore.of, RepetitionScore.of, ProbeScore.of and their siblings — so benchmark/scorecard.py only sequences those factories and no measurement lives outside the model that carries it.

Content probes

Term lists belong to a corpus, not to this package, so they live in one table beside it — probes.toml in the working directory, which every fanvl bench command reads without being handed a path. gated terms fail the run when they appear in the prose, watch terms are reported per 1000 characters (raw and unlicensed — vocabulary the outline or the bible uses itself is licensed), and each aliases group warns when one novel mixes two names for the same object.

gated   = ["hologram"]              # every occurrence fails the run
watch   = ["sapphire"]              # counted per 1000 characters
aliases = [["lamp", "lantern"]]     # names one object must not alternate between

JSON works as well. --probes measures a single invocation against another table, and fanvl bench scan applies the same table to prose that has no run directory behind it:

fanvl bench score novels/20260101-101010     # one run's scorecard (--json for the raw form)
fanvl bench compare novels/20260101-101010   # against the newest comparable run (--against to pin one)
fanvl bench board novels -n 12               # the newest runs side by side
fanvl bench scan novels/ draft.txt           # manuscripts, chapters, drafts (dirs read every *.txt)
fanvl bench score novels/20260101-101010 --probes corpus/probes.toml

--probes defaults to probes.toml in the working directory, so a project with one table never repeats a path, and a path that does not exist only skips the probe rows — the benchmark never fails a run over its probe file. Without a table the corpus-independent metrics still run, and the probe row reads not configured.

Key Classes

Context channels

Class Role
NovelContext Root channel: outline, language, the run's skill_names, roster charactor_span, chapter_context
RagNovelContext NovelContext subclass carrying the novel-level RagRetrieval settings and the documents fetched from the outline; renders them into the metadata and chapter-planning prompts
ChapterContext Chapter channel: charactor_span, story_context, heading block
RagChapterContext ChapterContext subclass whose stories are RagStoryContexts; the RAG chapter stage promotes what it plans, so a reloaded snapshot keeps the sealed types
StoryContext Story channel: charactor_span, scene_context, accumulated writing_styles
RagStoryContext StoryContext subclass sealed with RagRetrieval settings; the RAG pipeline swaps it in before scene planning and stops rendering its references once every scene of the story carries content
SceneContext Leaf channel: broadcast charactor_span, content (the only composed prose)
CharacterSpan Start + end CharacterCard; derive_child_spans stitches boundary cards
ContextLog / ContextEntry Append-only manuscript log per channel: append, branch (fork history), clear (fresh fork); renders the prefixed-content prompt streams
SeriesBible characters name list + background_settings fact list; rendered once into a setting_bible prefix entry at the root

Every channel carries its running manuscript as an append-only ContextLog: composed blocks enter as frozen ContextEntry records, parents seed children with pure log snapshots, and prompts render them exactly like the former prefixed-content strings. Logs support with_entry/with_entries appends, branch() to fork alternative continuations from any point (copy-on-write), and clear() which returns a fresh empty log while the original history stays intact.

Plans & models

Class Description
NovelPlan Novel metadata: title, description, word count, global constraint, bible
ChapterPlan / StoryPlan / ScenePlan Weighted per-element plans (title, description, weight, style, constraint, cast)
Scene / Story / Chapter / Novel Materialized output tree with word-count satisfaction
SketchSpec / IllustratedScene LLM-proposed generation instruction — positive/negative prompt plus per-scene mp/prop (comfyui model); the scene output carrying its rendered illustration
WritingStyleDocument / EnrichedDocument LanceDB-backed writing-style references

Capabilities (mixins)

Class Description
SceneCompose Scene requirement rendering, prose generation, and the refusal guard: a reply that comes back far short of the scene's word budget, or that a judge calls a refusal, is asked again and then fails the run
StoryCompose Scene planning, scene write preparation, serial scene composition
ChapterCompose Story planning, draft_story_spans (S-1 boundary cards), story composition
NovelCompose Metadata, prepare_character_span (roster), chapter planning, draft_chapter_spans (N−1 boundary cards), and the run's skills — fetch_skills resolves names through the fabricatio-skill library (which logs and skips an unknown one), apply_skills binds the names that resolved to the root, whose prompts render their bodies as one leading section
RAGStyleFetch Writing-style retrieval shared by the RAG-bound levels: the rag_query/rag_limit/rag_decompose_attempts settings plus the decomposed multi-head search, whose refinement prompt leads with the run's skills section
RAGNovelCompose Seals the root with the retrieval settings in before_compose_novel_context, searches the outline, and renders the documents into the metadata and chapter-planning prompts
RAGChapterCompose Retrieves style docs once per story, extends scene prompts, and stops rendering them into later prefix walks once the story's scenes are written
BibleCompose Composes the setting bible from the outline once; immutable for the run
IllustrateScenes Post-process illustration: batch-proposes one complete generation instruction (SketchSpec: prompt, negative prompt, LLM-chosen mp/prop) per pending scene (honoring illustration_constraint), renders them concurrently via ComfyUI into the run's images/ directory, and attaches IllustratedScene outputs

Actions (staged workflow)

Action Stage
InitNovelContext / RagInitNovelContext 01_init — build context from outline/language/bible, then fire before_compose_novel_context (the RAG variant's hook seals the root and searches the outline)
ProposeNovelMetadataStage 02_metadata — propose_novel_metadata
ProposeSettingBibleStage 03_bible — compose_setting_bible, then seed the bible prefix; skipped when a bible is already present
PrepareCharacterSpanStage 04_characters — prepare_character_span (roster)
PlanChaptersStage / RagPlanChaptersStage 05_chapter_plans — plan_chapters_phase + boundary drafting (the RAG variant renders the novel's references and promotes the chapters to RagChapterContext)
PlanStoriesStage / RagPlanStoriesStage 06_story_plans — fires before_compose_chapter_context per chapter, then plan_stories_phase + boundary drafting (RAG seals each chapter's stories)
PlanScenesStage / RagPlanScenesStage 07_scene_plans — fires before_compose_story_context per story, then plan_scenes_phase (with RAG)
ComposeScenesStage / RagComposeScenesStage 08_scenes — writes scene prose, then closes each story (after_compose_story_context + post_process_story) and each chapter (after_compose_chapter_context + post_process_chapter)
AssembleNovelStage 09_novel — fires after_compose_novel_context, then assemble_novel
IllustrateNovelStage DumpNovelStage of the illustrated pipeline: post_process_novel resolves to IllustrateScenes', whose interface declares persist_dir, send_to and the illustration knobs, so this action declares and passes them; adds no snapshot dir
DumpNovelStage fires post_process_novel(ctx, novel) — exactly the arguments the novel capability declares — then exports: JSON always, EPUB and/or chapters/NN.txt per the unpacked export_format (ExportFormat) plus output_path/font/cover

The structured stages (02–07: metadata, setting bible, roster spans, and the three plan levels) fall back to the PLAN agent variant: a run whose context names no routing group proposes and plans through the [agent] plan slot, while the scene write (08) and assembly (09) keep their TASK default and the illustrated pipeline's illustration proposals ride the SMOL variant — mechanical prompt drafting that need not ride the run's writing group. An explicit --send-to still governs every stage alike. Leave --send-to unset to plan on the plan model, keep the prose on the TASK model, and draft illustrations on the SMOL model.

The RAG query refinement is no stage of its own: decomposing a level's text into search heads is mechanical, so it rides the SMOL agent variant — arefined_query's own default — and neither --send-to nor a level's fallback moves it off the [agent] smol slot. A decomposition that never arrives — the reply parses into no list — is asked again, up to rag_decompose_attempts times, each retry bypassing the cache read so a stored failure is refreshed rather than replayed, and a level still unanswered after that searches its raw text; an empty answer is the model's own verdict, so that level retrieves nothing at all, while a lone head is discarded for the raw text.

The scene write is guarded against refusals. A model that declines answers with a few hundred characters of policy prose in place of the scene, which the scene's own word budget already gives away: measured on a refused run, every refusal came back at 0.15–0.78 of its budget, while every composed scene ran 1.03–2.11. A reply under scene_refusal_ratio_floor is a refusal and nothing is asked, one at or above scene_refusal_ratio_accept is prose and nothing is asked, and only the band between them goes to ajudge on the SMOL tier, asking whether the requested scene is present rather than whether the answer reads as a refusal — a refusal-shaped question reads explicit prose as one — while a verdict that comes back unparseable counts as a refusal, because a reply nobody could vouch for is not one to write down. Every refusal is asked again, up to scene_refusal_max_retries times, with the cache read bypassed but the answer still stored, so the refusal sitting in the cache for that prompt is replaced rather than replayed. A scene refused on its last attempt raises SceneRefusedError and the run stops: a refusal is not prose, and written down it would reach the chapter, the prefix every later scene reads, and the EPUB with nothing downstream to flag it. refusal_ratio_floor, refusal_ratio_accept and refusal_max_retries on a role override the [ext.novel] defaults for that role's scenes.

Every stage wraps one compose_novel chain segment and fires the chain's lifecycle hooks at their chain positions, so a hook override on a stage customizes the staged run exactly like it customizes the programmatic chain; the scene-level hooks fire inside compose_scenes_phase, exactly as they do in the chain.

Workflows

Workflow Description
DebugNovelWorkflow Outline → exported novel (--format epub|txt|both), one stage per action with per-stage snapshots
RagDebugNovelWorkflow Same, with writing-style RAG at the novel (outline) and story levels
RagIllustrationDebugNovelWorkflow Same, plus a single post-process pass that renders a ComfyUI illustration for every scene into the EPUB

Rust / PyO3

Symbol Description
NovelBuilder Builder for EPUB 3.0 novels: title/description/authors, chapters (auto-XHTML), cover, fonts, CSS, TOC
split_paragraphs / join_paragraphs / text_to_xhtml_paragraphs Paragraph splitting and XHTML wrapping
significant_terms The distinctive terms of a text: every CJK 3..4-char n-gram and every Latin word of 4+, lowercased
Metric One number a measure reported: name (<measure>.<metric>) and value
Knobs The sizes every measure reads a run at; an argument left out keeps its calibrated value
measure_repetition(scenes, knobs=None) The overlap of every scene pair, the echo at every seam, and the stats over both
measure_vocabulary(text, knobs=None) N-gram slots, distinct/repeated grams, windows measured, recycled slots per 1000, and one gram table per size from 1 to 6
measure_probes(prose, watch, gated, aliases, licensed) Watch and gated hits, alias groups mixing two names, hits the licence never uses
measure_script(text) Characters by script — CJK, Latin, digits, other — whitespace included
RepetitionReport / VocabularyReport / ProbeReport / ScriptReport What each measure read, number by number, plus metrics()
GramTable One size's n-gram counts and the most frequent grams of that size, min(knobs.vocab_tops, distinct) of them

Every measure is one implementation of one trait, so a new measurement is a new type and nothing else: it reads any iterator of documents (Measure<T> where T: AsRef<str> — scenes, drafts, whole manuscripts), returns its own report, and names its numbers <measure>.<metric> through Reported. A report carries the evidence it was read from (the pairs, the seams, the ordered gram counts) and the flat metrics() list the scorecard JSON publishes, so a measure that gains a number reaches the artifact without a Python change. The sizes a run is measured at are calibrated once, in Knobs::default(); [ext.novel.benchmark] moves any of them for a run.

from fabricatio_novel.rust import Knobs, measure_repetition, measure_script, measure_vocabulary

knobs = Knobs(seam_size=4)          # only the named knob moves off its calibrated value
measured = measure_vocabulary(prose, knobs)
print(measured.size, measured.distinct, measured.windows, measured.recycled_per_1k)
for table in measured.tables:       # every size from 1 to 6, 30 grams each by default
    print(table.size, table.distinct, table.tops[:5])

repeats = measure_repetition([scene.content for scene in scenes])
print(repeats.max_pair, repeats.median_pair, repeats.p90_pair, repeats.worst_seam_index, repeats.loud_pairs)

print(measure_script(prose).cjk_share)

Vocabulary windows are packed into integers (21 bits per scalar value) and counted by sorting, so measuring a 200k-character text costs one pass and a single key vector per size rather than a string per window. Only the grams a report names are ranked and unpacked — the tail is selected away rather than sorted — so naming thirty grams of each of the six sizes costs about half a millisecond on that text.

Configuration

All options below are read through the fabricatio configuration chain (see the Configuration Guide). Set them under the [ext.novel] table in fabricatio.toml, equivalently under [tool.fabricatio.ext.novel] in pyproject.toml, or via FABRICATIO_EXT__NOVEL__<FIELD_UPPER> environment variables.

[ext.novel]
novel_metadata_requirement_template = "built-in/novel_metadata_requirement"
Option Type Default Description
novel_metadata_requirement_template str "built-in/novel_metadata_requirement" template used to extract the novel metadata (title, synopsis, word count) from the outline.
plan_requirement_template str "built-in/plan_requirement" template used to plan the chapters of the novel, the stories of a chapter and the scenes of a story.
scene_requirement_template str "built-in/scene_requirement" template used to write a single scene in full prose.
render_chapter_xhtml_template str "built-in/render_chapter_xhtml" template used to render a chapter as a full XHTML document.
scene_overlap_min_chars int 40 minimum whitespace-normalized overlap between a new scene's prefix and the previous prose that gets stripped; shorter echoes are kept.
scene_overlap_max_ratio float 0.6 maximum fraction of a generated scene the overlap may cover before the content is kept untouched with a warning instead of stripped.
scene_refusal_ratio_floor float 0.8 word count satisfaction below which a scene's reply is a refusal without asking a judge; refusals measured 0.15–0.78 of the budget where composed scenes ran 1.03–2.11.
scene_refusal_ratio_accept float 1.5 word count satisfaction at or above which a scene's reply is prose without asking a judge.
scene_refusal_max_retries int 3 how many times a scene is asked again when its reply reads as a refusal before the run fails.
bench_scorecard_template str "built-in/bench_scorecard" template used to render one run's benchmark scorecard.
bench_comparison_template str "built-in/bench_comparison" template used to render two runs' benchmark comparison.
setting_bible_characters_template str "built-in/setting_bible_characters" template used to propose the bible's character roster as a list of plain strings, one character per item.
setting_bible_background_template str "built-in/setting_bible_background" template used to propose the bible's background settings as a list of strings.
setting_bible_context_template str "built-in/setting_bible_context" template that renders the bible into the block seeded into the running manuscript prefix.
setting_bible_export_template str "built-in/setting_bible_export" template used to render the bible as a human-readable markdown document.
writing_style_as_prompt_template str "built-in/writing_style_as_prompt" template used to render writing style documents as prompts.
enriched_as_prompt_template str "built-in/enriched_as_prompt" template used to render enriched reference documents as prompts.
novel_character_span_template str "built-in/novel_character_span" template used to propose the novel roster as one CharacterSpan per character.
boundary_requirement_template str "built-in/boundary_requirement" template used to draft the N-1 chapter- and the S-1 story-boundary cards from the parent's spans.
scene_illustration_prompt_template str "built-in/scene_illustration_prompt" template used to propose one image-generation prompt for a composed scene.
illustration_constraint str "" global style/content constraint merged into every scene illustration prompt proposal; empty when unset.
illustration_negative_prompt str quality/anatomy exclusion list negative prompt forwarded to ComfyUI for every scene illustration unless the proposal supplies its own.
illustration_prompt_suffix str "best quality,masterpiece,4k,highres" quality tags appended to every scene illustration render prompt after the LoRA trigger augmentation; set empty to append nothing.
illustration_always_loras list[LoraEntry] [] LoRA entries chained into every scene illustration render before any selection — the slot for always-on style/character loras; each entry's trigger_words activate it in the prompt. Selectable loras live in the [ext.comfyui] loras catalog, where the LLM picks per scene on top of this chain.
illustration_choose_loras bool False opt-in per-scene LLM LoRA selection from the [ext.comfyui] loras catalog during illustration; off by default. illustration_always_loras chains regardless of this flag.
illustration_judge bool False opt-in per-scene visual judgement of each rendered illustration via a vision LLM; off by default. A failed verdict re-proposes the illustration prompt with the accumulated defect feedback and re-renders; an unavailable judge (None verdict) accepts the image.
illustration_judge_max_tries int 3 total generation attempts per scene when illustration_judge is on; the last attempt's image is always kept, judged or not. Rejected attempts are archived beside the canonical PNG as scene_XX_YY.attempt<N>.png.
scene_illustration_feedback_template str "built-in/scene_illustration_feedback" template rendering the rejected-attempt feedback tail appended to the requirement when a judged render is retried.
illustration_mp float | None None megapixel budget of each scene illustration's finished image (1.0 = 1,000,000 px); a per-scene proposal's mp wins, otherwise None falls back to [ext.comfyui] mp, then the active ComfyUI template's canvas.
illustration_mp_max float 1.2 hard ceiling on every scene illustration's megapixel budget, enforced at render time even when the LLM proposes a larger mp; the renderable quality brink (512x512x2.2x2.2) is ~1.27 MP, so 1.2 keeps a margin under it.
illustration_prop Prop | None None aspect-ratio preset of each scene illustration — enum member names like prop_2_3 (portrait), coerced to the Prop enum at load; a per-scene proposal's prop wins, otherwise None falls back to [ext.comfyui] prop, then the active ComfyUI template's ratio.
illustration_seed int | None None scene illustration sampler seed; None keeps the bundled ComfyUI template's seed.
illustration_skip_existing bool True skip scenes whose illustration PNG already exists so re-runs fill only the gaps.
illustration_timeout_per_image float 210.0 per-image render timeout in seconds; the total render timeout scales linearly with the batch size (value x pending renders) since all renders share one ComfyUI queue; 0 falls back to [ext.comfyui] timeout.

[ext.novel.benchmark] re-takes every measurement at other sizes than the calibrated ones; every key is optional, and a key left unset keeps the value the measures were calibrated with:

Option Type Default Description
pair_size int | None 12 characters per shingle when two scenes are compared.
seam_size int | None 8 characters per shingle when a scene seam is compared.
seam_window int | None 300 characters read from each side of a seam.
echo_warn float | None 0.05 overlap from which a pair or a seam is reported as a repetition.
vocab_size int | None 3 characters per vocabulary n-gram (1..=6).
vocab_window int | None 1000 n-grams per vocabulary window; windows are averaged, so the rate does not follow the run's length.
vocab_tops int | None 30 how many of the most frequent n-grams each size's table names.
duplicate_min_chars int | None 10 shortest sentence counted as a verbatim duplicate.
long_sentence_chars int | None 80 length from which a sentence counts as long.

Access at runtime: from fabricatio_novel.config import novel_config.

Usage

CLI

# Generate a novel from an outline
fanvl w -o "In a world where dreams are currency..."

# Generate with writing style RAG (LanceDB)
fanvl wr -o "In a world where dreams are currency..." -rq "Hemingway terse prose style"

# Generate with RAG + ComfyUI scene illustrations embedded in the EPUB
fanvl wri -o "In a world where dreams are currency..." -rq "Hemingway terse prose style"
fanvl wri -o "..." --choose-loras  # per-scene LLM-chosen LoRAs from the [ext.comfyui] catalog
fanvl wri -o "..." --judge --judge-tries 5  # vision-judge each illustration; retry with a revised prompt up to N total attempts

# Constrain generation with a setting bible
fanvl w -o "..." -b settings/bible.json

# Write with the user's own skills from the fabricatio-skill library
fanvl w  -o "..." -s terse-action -s no-adverbs   # repeatable, or comma-separated: -s terse-action,no-adverbs
# Extra skill roots are configured once, not per run: [ext.skill] extra_skill_dirs in fabricatio.toml

# Export as plain text instead of (or besides) EPUB: chapters/01.txt, 02.txt, …
fanvl w -o "..." --format both
fanvl w -o "..." --format txt

# Resume a run that stopped mid-novel without re-running what is on disk
fanvl wr --resume novels/20260101-101010                    # restarts at the stage after the newest snapshot
fanvl wr --resume novels/20260101-101010 --stage 08_scenes  # restarts at the prose stage, running it again

# Draw the illustrations of a novel that was written without them
fanvl wri --resume novels/20260101-101010 --stage 09_novel --judge

# Create / update / show the setting bible
fanvl bible create -o "In a world where dreams are currency..." --out settings/bible.json
fanvl bible update settings/bible.json -o "..." --sections characters
fanvl bible show settings/bible.json

# Store reference texts as writing style documents in LanceDB
fanvl store-refs ./corpus/*.txt
fanvl enrich-refs ./corpus/*.txt -eg "Extract world-building facts"

Skills

Skills are built into novel composition, not an add-on: a run names them and every planning prompt and scene write honors them. --skill/-s (repeatable, comma-separated allowed) resolves each name through the fabricatio-skill library — <root>/<name>/SKILL.md then <root>/<name>.md, over the cross-client skill dirs plus [ext.skill] extra_skill_dirs — and a name that resolves nowhere is logged by the library, with the roots it searched, while the run goes on with the names that resolved. Extra skill roots are configured there once (extra_skill_dirs), never per run.

The run binds only the names it resolved to its root context (skill_names), and every level carries that selection the way it carries outline. The bodies stay in the process-wide fabricatio-skill library, which parses a skill once and hands the same text to every walk: prompt assembly fetches them by name through ctx.skill_references(), so a run renders byte-identical text on every walk and a tree rebuilt in a fresh process re-reads exactly the files it resolved (a name that no longer resolves is logged and dropped rather than crashing the walk that renders it). Each reference comes back rendered as <name>body</name> — the skill's body wrapped in a tag named after it — so a prompt separates one skill's instructions from the next.

The resolved text forms one section (ctx.skill_section()) whose bytes are identical everywhere they appear: the metadata proposal, the setting bible proposals, the roster-span and boundary-card proposals, every plan prompt and every scene write prompt render it as their first block — ahead of the outline, or, for a scene, ahead of the manuscript block — so a run's calls to one model open with the same head and the provider's prefix cache carries over between them. The refinement prompt builds its head the same way: the section is prepended to the wrapped outline its template renders at byte 0. Nothing stores the text as prompt context: the log carries the manuscript itself, and the section is rendered from the root's skill_names wherever a prompt shows it.

import asyncio

from fabricatio_novel.actions.novel import InitNovelContext

ctx = asyncio.run(
    InitNovelContext().init_novel_context("In a world where dreams are currency...", skills=["terse-action"])
)

Programmatic

from fabricatio_novel.workflows.novel import DebugNovelWorkflow
from fabricatio_core import Event

event = Event.instantiate("write")
event.payload["novel_outline"] = "In a world where dreams are currency..."
event.payload["skills"] = ["terse-action"]  # resolved once at the root, honored by every prompt
role = Role.with_bio(name="writer").subscribe(event, DebugNovelWorkflow).dispatch()

EPUB Builder (Rust)

from fabricatio_novel.rust import NovelBuilder, text_to_xhtml_paragraphs

xhtml = text_to_xhtml_paragraphs(raw_chapter_text)

builder = (
    NovelBuilder()
    .new_novel()
    .set_title("My Novel")
    .add_author("Author Name")
    .add_chapter("Chapter 1", xhtml)
    .add_inline_toc()
)

builder.export("output.epub")

Dependencies

  • fabricatio-core — Core interfaces, template management, LLM capabilities
  • fabricatio-skill — Markdown skill library the run resolves its --skill names against
  • fabricatio-character — Character card models
  • pydantic — Data validation via models
  • Optional: fabricatio-lancedb — writing style RAG, typer — CLI

License

MIT — see LICENSE

Metadata

Release files for fabricatio-novel 0.32.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distributions (wheels)

Table of built distributions (wheels) for fabricatio-novel 0.32.0
File
fabricatio_novel-0.32.0-cp314-cp314-win_amd64.whl CPython 3.14 CPython 3.14 Windows x86-64 Details
fabricatio_novel-0.32.0-cp314-cp314-manylinux_2_34_x86_64.whl CPython 3.14 CPython 3.14 Linux glibc 2.34+ x86-64 Details
fabricatio_novel-0.32.0-cp314-cp314-manylinux_2_34_aarch64.whl CPython 3.14 CPython 3.14 Linux glibc 2.34+ ARM64 Details
fabricatio_novel-0.32.0-cp314-cp314-macosx_11_0_arm64.whl CPython 3.14 CPython 3.14 macOS 11.0+ ARM64 Details
fabricatio_novel-0.32.0-cp313-cp313-win_amd64.whl CPython 3.13 CPython 3.13 Windows x86-64 Details
fabricatio_novel-0.32.0-cp313-cp313-manylinux_2_34_x86_64.whl CPython 3.13 CPython 3.13 Linux glibc 2.34+ x86-64 Details
fabricatio_novel-0.32.0-cp313-cp313-manylinux_2_34_aarch64.whl CPython 3.13 CPython 3.13 Linux glibc 2.34+ ARM64 Details
fabricatio_novel-0.32.0-cp313-cp313-macosx_11_0_arm64.whl CPython 3.13 CPython 3.13 macOS 11.0+ ARM64 Details
fabricatio_novel-0.32.0-cp312-cp312-win_amd64.whl CPython 3.12 CPython 3.12 Windows x86-64 Details
fabricatio_novel-0.32.0-cp312-cp312-manylinux_2_34_x86_64.whl CPython 3.12 CPython 3.12 Linux glibc 2.34+ x86-64 Details
fabricatio_novel-0.32.0-cp312-cp312-manylinux_2_34_aarch64.whl CPython 3.12 CPython 3.12 Linux glibc 2.34+ ARM64 Details
fabricatio_novel-0.32.0-cp312-cp312-macosx_11_0_arm64.whl CPython 3.12 CPython 3.12 macOS 11.0+ ARM64 Details

Total release size: 28.3 MB

Release files / fabricatio_novel-0.32.0-cp314-cp314-win_amd64.whl

Download URL fabricatio_novel-0.32.0-cp314-cp314-win_amd64.whl
Size 2.3 MB
Tags CPython 3.14 Windows x86-64
SHA-256 checksum
How to use checksums
56ad4bc7e0e609b34054882278fea71252eec338422bdc87e177f9cf4892d0dd
BLAKE2b-256 checksum
How to use checksums
0fffce73afb6830f2f19eb67b5df1f489c0880876cd8749bcb5984632d68be67
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / fabricatio_novel-0.32.0-cp314-cp314-manylinux_2_34_x86_64.whl

Download URL fabricatio_novel-0.32.0-cp314-cp314-manylinux_2_34_x86_64.whl
Size 2.6 MB
Tags CPython 3.14 Linux glibc 2.34+ x86-64
SHA-256 checksum
How to use checksums
987a4c62d3958ee44f515dc9dda1e6e91b66235aaf95e4055353077ad3ffb473
BLAKE2b-256 checksum
How to use checksums
7975c0dee6a829fb11d4ac60013be4fa67508fc23e437ebed81e3b596a3c1014
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / fabricatio_novel-0.32.0-cp314-cp314-manylinux_2_34_aarch64.whl

Download URL fabricatio_novel-0.32.0-cp314-cp314-manylinux_2_34_aarch64.whl
Size 2.3 MB
Tags CPython 3.14 Linux glibc 2.34+ ARM64
SHA-256 checksum
How to use checksums
04a83268a6a6e1a89076a9a44b7a5330ed6aff301158623208a3f211f25a9977
BLAKE2b-256 checksum
How to use checksums
5fb849adcebb65411dc5f4dc8aa59f96bade9afc00fbfaa9613434d9512f71eb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / fabricatio_novel-0.32.0-cp314-cp314-macosx_11_0_arm64.whl

Download URL fabricatio_novel-0.32.0-cp314-cp314-macosx_11_0_arm64.whl
Size 2.2 MB
Tags CPython 3.14 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
db6564d1c0b6b6ab91579cb14f70c725f940b9d90c528f79b674c61b8057569f
BLAKE2b-256 checksum
How to use checksums
f10dd11983e90015f8d4019fc83ffc69b625b2a1f6ecd3d770e9a36a24f918c3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / fabricatio_novel-0.32.0-cp313-cp313-win_amd64.whl

Download URL fabricatio_novel-0.32.0-cp313-cp313-win_amd64.whl
Size 2.4 MB
Tags CPython 3.13 Windows x86-64
SHA-256 checksum
How to use checksums
a10379439e9a8d2c79cfabe6b457219ee8db908f5821882279a5b98b167c3580
BLAKE2b-256 checksum
How to use checksums
0edc8bf958caebd8d1e07c72cac8c09ab4dd26e9f93071dbbde47ae4b42ef1da
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / fabricatio_novel-0.32.0-cp313-cp313-manylinux_2_34_x86_64.whl

Download URL fabricatio_novel-0.32.0-cp313-cp313-manylinux_2_34_x86_64.whl
Size 2.6 MB
Tags CPython 3.13 Linux glibc 2.34+ x86-64
SHA-256 checksum
How to use checksums
c74a14e889430ac6f74418e26aff2a8283ab08d5f239e1df743fe96345152d26
BLAKE2b-256 checksum
How to use checksums
b25f5cd8b6fb0bb37026fc00ae933be0b87e9bed702d115edc5e100844331dd9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / fabricatio_novel-0.32.0-cp313-cp313-manylinux_2_34_aarch64.whl

Download URL fabricatio_novel-0.32.0-cp313-cp313-manylinux_2_34_aarch64.whl
Size 2.3 MB
Tags CPython 3.13 Linux glibc 2.34+ ARM64
SHA-256 checksum
How to use checksums
c7f8c0b8a837e1690f930f69c4547f11897848a3d003813ca206c9f0ba2bf625
BLAKE2b-256 checksum
How to use checksums
00a033644154b9cf710bd106fd9f17c88572b7f5716726fc297bf7bcd8f21c54
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / fabricatio_novel-0.32.0-cp313-cp313-macosx_11_0_arm64.whl

Download URL fabricatio_novel-0.32.0-cp313-cp313-macosx_11_0_arm64.whl
Size 2.2 MB
Tags CPython 3.13 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
ce819810ff2985a0518d7803038123c8caefb597b85bd73f67f1a539a9acd5f0
BLAKE2b-256 checksum
How to use checksums
d2ff5ce36a567f1b88b6d650b7a419621d64dec769be3a900b317f638a992e1f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / fabricatio_novel-0.32.0-cp312-cp312-win_amd64.whl

Download URL fabricatio_novel-0.32.0-cp312-cp312-win_amd64.whl
Size 2.4 MB
Tags CPython 3.12 Windows x86-64
SHA-256 checksum
How to use checksums
8b17d1eae00527297ce2084bc8619677d9246f6245856e692bd60e8602959b65
BLAKE2b-256 checksum
How to use checksums
78f11c1920fd376f6e64088aee921021dee726befa065d61dd6bdc8c29a44da7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / fabricatio_novel-0.32.0-cp312-cp312-manylinux_2_34_x86_64.whl

Download URL fabricatio_novel-0.32.0-cp312-cp312-manylinux_2_34_x86_64.whl
Size 2.6 MB
Tags CPython 3.12 Linux glibc 2.34+ x86-64
SHA-256 checksum
How to use checksums
01904bdd64f4eb7baec695891d9d4f7a624b05e3675ae5b6966fa5c792b10c1f
BLAKE2b-256 checksum
How to use checksums
182ec8bcac2474746cb1ee6e47c26acd1a841cde13af6408fa35a30cf3986f07
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / fabricatio_novel-0.32.0-cp312-cp312-manylinux_2_34_aarch64.whl

Download URL fabricatio_novel-0.32.0-cp312-cp312-manylinux_2_34_aarch64.whl
Size 2.3 MB
Tags CPython 3.12 Linux glibc 2.34+ ARM64
SHA-256 checksum
How to use checksums
84cf5387e027638fc731f7d050c8c8ab9fbd93f5ea2d2ebd8153ef3981cf0cc0
BLAKE2b-256 checksum
How to use checksums
56d1b3d4f54a40b5d9414e4221db692c8467a3ea942d0a68490e623d53e92ff7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / fabricatio_novel-0.32.0-cp312-cp312-macosx_11_0_arm64.whl

Download URL fabricatio_novel-0.32.0-cp312-cp312-macosx_11_0_arm64.whl
Size 2.2 MB
Tags CPython 3.12 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
484ea00fa3dfd31641b7038946b7dccd8cf652fcc0e9d00c41611f9ba5dff5f2
BLAKE2b-256 checksum
How to use checksums
3846c85e7d804de3b4356ac9acbb89ad85574033c919ebd6dcb59b3b97e5f0e4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

This release

0.32.0 This release

12 release files

0.8.0

12 release files

0.7.0

12 release files

0.6.0

12 release files

0.4.1

12 release files

0.4.0

12 release files

0.3.3

11 release files

0.3.2

12 release files

0.3.1

12 release files

0.3.0

12 release files

0.1.8

12 release files

0.1.7

12 release files

0.1.6

12 release files

0.1.5

12 release files

0.1.4

12 release files

0.1.3

8 release files

0.1.2

8 release files

0.1.1

8 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page