Fully autonomous book authoring — from idea to finished PDF/EPUB.
⚠️ Security Notice: Review SECURITY.md before running, especially for first-time use. Use step-by-step mode initially. Images are disabled by default.
PhaseModuleDurationDescription1Concept1-2hGenre, characters, plot, worldbuilding2Writer Extras30-60minProlog (optional) — before chapters3ImagesStart immediately, ~40-55minDon't wait, proceed to phase 4. Categories: cover, characters, settings, chapter images4Chapters4-8hSee detailed workflow below5Writer Extras30-60minEpilog (optional) — after chapters6Publication15-30minFirst without images, then with images (if ready)
CRITICAL: Chapters run strictly sequential with proper tracking.
text
┌─────────────────────────────────────────────────────────────────┐
│ STEP 1: Check Manifest │
│ Read project-manifest.md → chapter_XX.status │
└─────────────────┬───────────────────────────────────────────────┘
│
┌─────────────┼─────────────┐
▼ ▼ ▼
pending writing approved
│ │ │
│ │ └─→ SKIP to next chapter
│ │
│ └─→ Check subagent status
│ IF running: wait
│ IF not: resume/restart decision
│
└─→ STEP 2: Acquire Lock
│
▼
STEP 3: Spawn Subagent
│
├─ Label: NovelCraft-Chapter-XX
├─ Mode: run
├─ Timeout: 3600s (1 hour)
├─ Pre-check: Kill existing Chapter-XX subagents
│
▼
STEP 4: Update Manifest
│
├─ status: "writing"
├─ subagent.session_key: [key]
├─ subagent.run_id: [id]
├─ subagent.started_at: [timestamp]
└─ subagent.expected_duration: "1800s"
│
▼
STEP 5: Wait (Push-Based)
│
├─ DO NOT poll sessions_list
├─ DO NOT use sessions_yield
├─ Wait for subagent completion event
│
▼
STEP 6: Handle Result
│
├─ SUCCESS: Validate output → Go to Review
├─ TIMEOUT: Check partial draft → Resume or Retry
└─ ERROR: Log → Retry (max 3)
Before spawning ANY chapter subagent:
javascript
// 1. List active subagents
const active = await subagents({ action: "list" });
// 2. Find existing for this chapter
const existing = active.find(s =>
s.label === `NovelCraft-Chapter-${chapterNum}`
);
// 3. Kill if exists (prevent duplicates)
if (existing) {
await subagents({
action: "kill",
target: existing.sessionKey
});
}
// 4. Verify killed
const verify = await subagents({ action: "list" });
const stillExists = verify.find(s =>
s.label === `NovelCraft-Chapter-${chapterNum}`
);
if (stillExists) {
throw new Error(`Failed to kill existing Chapter-${chapterNum}`);
}
// 5. NOW spawn new
await sessions_spawn({
label: `NovelCraft-Chapter-${chapterNum}`,
mode: "run",
runtime: "subagent",
task: "...",
runTimeoutSeconds: 3600 // 1 hour minimum
});
Subagent MUST write progress header:
markdown
<!-- chapter_XX_draft.md -->
<!--
STATUS: writing
WORDS: 650
TARGET: 1000
STARTED: 2026-04-06T19:14:00Z
LAST_UPDATE: 2026-04-06T19:20:00Z
ETA: 10 minutes
-->
# Kapitel XX: Titel
[Content...]
Main Session can check progress:
javascript
// Read draft and parse progress
const draft = await read({ file: 'chapter_XX_draft.md' });
const progress = parseProgressHeader(draft);
console.log(`${progress.words}/${progress.target} words`);
console.log(`ETA: ${progress.eta}`);
On subagent timeout:
javascript
// 1. Check if partial draft exists
const draft = await read({ file: 'chapter_XX_draft.md' });
const progress = parseProgressHeader(draft);
// 2. Decide action
if (progress.words > 0) {
// Partial progress → Resume
await sessions_spawn({
label: `NovelCraft-Chapter-${chapterNum}-Resume`,
mode: "run",
task: `Resume Chapter ${chapterNum} from ${progress.words} words...`,
runTimeoutSeconds: 3600
});
} else {
// No progress → Retry
const retries = manifest.chapters[chapterNum].retries || 0;
if (retries < 3) {
await sessions_spawn({
label: `NovelCraft-Chapter-${chapterNum}-Retry-${retries + 1}`,
mode: "run",
task: `Rewrite Chapter ${chapterNum} (attempt ${retries + 1})...`,
runTimeoutSeconds: 3600
});
} else {
// Max retries reached → Manual intervention
await message({
action: "send",
message: `Chapter ${chapterNum} failed after 3 retries. Manual review needed.`
});
}
}
Extended chapter entry:
yaml
chapters:
'05':
status: 'writing' # pending | writing | reviewing | approved | failed
subagent:
session_key: 'agent:opencode:subagent:...'
run_id: '...'
started_at: '2026-04-06T19:14:00Z'
expected_duration: '1800s'
draft_file: '01-drafts/chapter_05_draft.md'
review_file: null # Set after review
approved_file: '02-chapters/chapter_05.md'
word_count: 1188
score: 8.8
retries: 0
error: null # Set if failed
Before running NovelCraft, ensure you have:
pandoc — Document conversionxelatex (optional, for enhanced PDF)Check with: which pandoc && which xelatex
step-by-step mode for testingNovelCraft uses a 3-level configuration hierarchy with clear override rules:
LevelNameFilePurposeOverride1HardcodedSkill codeFallback defaults—2Module Configsworkspace/config/module-*.mdTechnical settingsLevel 13Project Manifestworkspace/Books/projects/{PROJECT}/project-manifest.mdBook-specific dataLevel 1+2
Rule: Higher level wins. Only defined fields override lower levels.
Create in ~/.openclaw/workspace/novelcraft/config/:
ConfigKey Settingsmodule-concept.mdgenre, theme, characters, plot, world, chapter_countmodule-writer-extras.mdhas_prolog, has_epilog, tonemodule-images.mdprovider, generate_cover, generate_characters, generate_settings, generate_chapter_images, settings_have_peoplemodule-chapters.mdmin_words, max_words, target_words, max_revisions, scoring_enabledmodule-publication.mdformats: [pdf, epub], pdf_engine, layout, typography
See: workspace/config/CONFIG-SCHEMA.md for complete schema documentation.
Path: workspace/Books/projects/{PROJECT}/project-manifest.md
Central status file with:
See: workspace/config/PROJECT-MANIFEST-TEMPLATE.md for template.
ModuleNameOptionalOrderDescription0Target Audience✅0.NEW v3.2 — Auto-configures all modules based on age profile1Concept❌1.Genre, plot, characters, world2Writer Extras✅2. & 5.Prolog (before) & Epilog (after)3Images✅ParallelCover, characters, settings, chapter images4Chapters❌3.Sequential writing with review5Publication❌4.PDF/EPUB creation
Each module has a template that standardizes subagent calls:
ModuleTemplateConcepttemplates/module-concept-template.mdWriter Extrastemplates/module-writer-extras-template.mdImagestemplates/module-images-template.mdChapterstemplates/module-chapters-template.mdReviewtemplates/module-review-template.mdRevisiontemplates/module-revision-template.mdPublicationtemplates/module-publication-template.md
Weighted ScoreDecisionAction8.0 - 10.0✅ APPROVEDCopy to 02-chapters/, next chapter6.0 - 7.9⚠️ MINOR_REVISIONSpecific fixes, max 3 revisions4.0 - 5.9🔧 MAJOR_REVISIONMajor rewrite, max 3 revisions0.0 - 3.9❌ REJECTEDComplete rewrite, max 3 revisions
CriterionWeightDescriptionUTF-8 Encoding×3 (CRITICAL)No foreign charactersWord Count 7000-8000×2 (HIGH)Target: 7500 wordsContinuity×2 (HIGH)Consistent with previous chapterPlot Progression×2 (HIGH)Story developsCharacter Voice×1.5 (MEDIUM)Believable charactersStyle & Atmosphere×1.5 (MEDIUM)Fits project styleGrammar×1 (LOW)Correct language
chapter_XX_review.mdScenarioCauseRecoverySubagent timeoutChapter too longCheck partial draft, resume or retryDuplicate subagentsSpawned twiceKill all, restart with clean stateWrong chapter outputTask unclearValidate output, restart if mismatchFile not foundSubagent failed silentlyRetry with stronger error handlingManifest out of syncCrash during updateRebuild from filesystem state
Detection:
javascript
// Subagent status: timed_out
{
status: "timed_out",
runtime: "6m26s",
sessionKey: "..."
}
Recovery Steps:
javascript
async function handleTimeout(chapterNum) {
// 1. Check for partial draft
const draftPath = `01-drafts/chapter_${chapterNum}_draft.md`;
const draftExists = await fileExists(draftPath);
if (!draftExists) {
// No draft at all → Full retry
return await retryChapter(chapterNum, 'no_draft');
}
// 2. Parse progress from draft header
const draft = await read({ file: draftPath });
const progress = parseProgressHeader(draft);
// 3. Decide: Resume vs Retry
if (progress.words > 0) {
// Has progress → Resume
console.log(`Resuming Chapter ${chapterNum} at ${progress.words} words`);
return await resumeChapter(chapterNum, progress.words);
} else {
// No progress → Retry
console.log(`Retrying Chapter ${chapterNum} from start`);
return await retryChapter(chapterNum, 'no_progress');
}
}
async function retryChapter(chapterNum, reason) {
const manifest = await readManifest();
const retries = manifest.chapters[chapterNum].retries || 0;
if (retries >= 3) {
// Max retries → Manual intervention
await notifyUser(`Chapter ${chapterNum} failed after 3 retries. Reason: ${reason}`);
return { action: 'manual_intervention', reason };
}
// Update manifest
manifest.chapters[chapterNum].retries = retries + 1;
manifest.chapters[chapterNum].error = reason;
await saveManifest(manifest);
// Spawn retry subagent
return await sessions_spawn({
label: `NovelCraft-Chapter-${chapterNum}-Retry-${retries + 1}`,
mode: "run",
runtime: "subagent",
task: `Rewrite Chapter ${chapterNum} (attempt ${retries + 2}). Previous: ${reason}`,
runTimeoutSeconds: 3600
});
}
Problem: Multiple subagents for same chapter.
Solution:
javascript
async function spawnChapterSafely(chapterNum, task) {
// 1. List ALL active subagents
const active = await subagents({ action: "list" });
// 2. Find any for this chapter (fuzzy match)
const existing = active.filter(s =>
s.label.includes(`Chapter-${chapterNum}`)
);
// 3. Kill all existing
for (const sub of existing) {
console.log(`Killing existing: ${sub.label} (${sub.sessionKey})`);
await subagents({
action: "kill",
target: sub.sessionKey
});
}
// 4. Wait and verify
await sleep(1000);
const verify = await subagents({ action: "list" });
const stillRunning = verify.filter(s =>
s.label.includes(`Chapter-${chapterNum}`)
);
if (stillRunning.length > 0) {
throw new Error(`Failed to kill ${stillRunning.length} subagents`);
}
// 5. Safe to spawn
return await sessions_spawn({
label: `NovelCraft-Chapter-${chapterNum}`,
mode: "run",
runtime: "subagent",
task,
runTimeoutSeconds: 3600
});
}
Problem: Subagent returns wrong chapter or incomplete data.
Validation:
javascript
async function validateChapterOutput(chapterNum, content) {
const errors = [];
// 1. Check chapter number in content
const chapterMatch = content.match(/Kapitel\s+(\d+)/i);
if (chapterMatch && parseInt(chapterMatch[1]) !== chapterNum) {
errors.push(`Wrong chapter number: expected ${chapterNum}, got ${chapterMatch[1]}`);
}
// 2. Check word count
const words = countWords(content);
const config = await readModuleConfig('chapters');
if (words < config.min_words || words > config.max_words) {
errors.push(`Word count ${words} outside range ${config.min_words}-${config.max_words}`);
}
// 3. Check for required content
if (!content.includes('#')) {
errors.push('Missing chapter title (H1)');
}
// 4. Return result
return {
valid: errors.length === 0,
errors,
word_count: words
};
}
// Usage
const result = await validateChapterOutput(5, content);
if (!result.valid) {
console.error('Validation failed:', result.errors);
await retryChapter(5, 'validation_failed');
}
Problem: Manifest out of sync with filesystem.
Reconstruction:
javascript
async function rebuildManifest(projectPath) {
const manifest = {
chapters: {}
};
// Scan 01-drafts/
const drafts = await listFiles(`${projectPath}/01-drafts/chapter_*.md`);
for (const draft of drafts) {
const num = extractChapterNumber(draft);
manifest.chapters[num] = manifest.chapters[num] || {};
manifest.chapters[num].draft_file = draft;
manifest.chapters[num].status = 'draft';
}
// Scan 02-chapters/
const approved = await listFiles(`${projectPath}/02-chapters/chapter_*.md`);
for (const chapter of approved) {
const num = extractChapterNumber(chapter);
manifest.chapters[num] = manifest.chapters[num] || {};
manifest.chapters[num].approved_file = chapter;
manifest.chapters[num].status = 'approved';
}
// Scan reviews
const reviews = await listFiles(`${projectPath}/01-drafts/chapter_*_review.md`);
for (const review of reviews) {
const num = extractChapterNumber(review);
if (manifest.chapters[num]) {
manifest.chapters[num].review_file = review;
if (manifest.chapters[num].status === 'draft') {
manifest.chapters[num].status = 'reviewing';
}
}
}
await saveManifest(manifest);
return manifest;
}
IMPORTANT: All project data (Books) lives in the workspace, not the skill folder!
text
~/.openclaw/workspace/novelcraft/ ← Workspace (localized)
├── config/
│ ├── CONFIG-SCHEMA.md # Schema v3.0 documentation
│ ├── PROJECT-MANIFEST-TEMPLATE.md # Project template
│ ├── module-concept.md # Module: Concept
│ ├── module-writer-extras.md # Module: Prolog/Epilog
│ ├── module-images.md # Module: Images
│ ├── module-chapters.md # Module: Chapters
│ └── module-publication.md # Module: Publication
│
└── Books/projects/novel-[TITLE]/
├── project-manifest.md # Central project manifest
├── 00-concept/ # Concept, characters, worldbuilding
├── 01-drafts/ # WIP chapters, reviews, revisions
│ ├── chapter_01_draft.md
│ ├── chapter_01_review.md
│ └── chapter_01_review_v2.md
├── 02-chapters/ # ✅ APPROVED final chapters
│ ├── chapter_01.md
│ └── chapter_02.md
└── 03-final/ # PDF, EPUB
~/.openclaw/skills/novelcraft/ ← Skill folder (read-only)
├── SKILL.md # This file
├── README.md # Quick start
├── CHANGELOG.md # Version history
├── CONTRIBUTING.md # Contribution guidelines
├── templates/ # Module templates
│ ├── module-concept-template.md
│ ├── module-writer-extras-template.md
│ ├── module-images-template.md
│ ├── module-chapters-template.md
│ ├── module-review-template.md
│ ├── module-revision-template.md
│ └── module-publication-template.md
└── references/
└── CONFIG.md # Config documentation
Workflow:
01-drafts/02-chapters/02-chapters/Books Path: ~/.openclaw/workspace/novelcraft/Books/ (always load from workspace)
ModeBehaviorautonomousNo intermediate questions, runs throughstep-by-stepConfirm after each module
Default: DISABLED — Images must be explicitly enabled in module-images.md.
ProviderDescriptionNetwork Callsnone (default)No image generationNomcpMCP serverDepends on MCP configlocalLocal tools (e.g., MFLUX)NomanualUser provides imagesNoapiExternal APIYes — data leaves machine
⚠️ Warning: Using api provider sends book content descriptions to external services. Review your provider's privacy policy.
See references/CONFIG.md for detailed provider configuration.
Current: No dashboard available. NovelCraft works via command line.
Planned: Web-based dashboard for project monitoring and control.
See ROADMAP.md for details.
Audiobook generation from completed chapters:
See ROADMAP.md for details.
FilePurposeSKILL.mdThis file — main documentationREADME.mdQuick start guideSECURITY.mdSecurity considerationsROADMAP.mdFuture features (Dashboard, Audio)CHANGELOG.mdVersion historyCONTRIBUTING.mdContribution guidelinessetup.mdChat-based setup (Quick-Start)project-setup.mdChat commands for project managementreferences/CONFIG.mdDetailed config documentationworkspace/config/CONFIG-SCHEMA.mdSchema v3.0 specificationworkspace/config/PROJECT-MANIFEST-TEMPLATE.mdProject manifest template
NEW: Select a profile to auto-configure all modules for your intended readers!
ProfileAgeChapter LengthImagesFontUse Caseearly-readers6-8800-1,200 words8+ chars, chapter images14ptPicture books, first readersmiddle-grade8-121,500-2,500 words6 chars, chapter images12ptAdventure, fantasyyoung-adult12-163,000-5,000 words4 chars11ptTeen themes, romancenew-adult16-254,000-6,000 words3 chars11ptComing-of-ageadult25+5,000-8,000 words3 chars10ptFull narrative freedomsenior60+3,000-5,000 words4 chars13ptLarge text, relaxed
ModuleSettingsChaptersMin/max words, sentences/paragraph, max revisionsImagesCharacter count, chapter images, settingsConceptWording style, vocabulary complexityPublicationFont size, line height, margins, font family
text
Setup: "Start NovelCraft Setup"
→ "Select target audience profile:"
[1] early-readers (6-8)
[2] middle-grade (8-12)
[3] young-adult (12-16)
[4] new-adult (16-25)
[5] adult (25+)
[6] senior (60+)
[7] custom (manual)
→ User selects: 1
→ All modules auto-configure for 6-8 year olds!
Keep profile, change one value:
text
"Change chapter target to 1500 words"
"Enable chapter images"
See setup.md for detailed profile specifications.
Images are disabled by default (provider: none). When enabled, configure categories:
CategoryDefaultContains People?DescriptionCover✅ yesYes (monsters/characters)Book cover artworkCharacters✅ yesYes (children)Main character portraitsMonsters✅ yesYes (monsters only)Monster portraitsSettings✅ yesNO (empty places)Location/environment imagesChapter Images❌ noScene-dependentOne illustration per chapter
RuleDescriptionEnglish OnlyAll prompts must be in English (FLUX requirement)No TextEvery prompt must include: "no text, no letters, no words, no typography"Settings = EmptySettings images NEVER contain people, monsters, or charactersNegative PromptAlways add: "watermark, signature" to negative prompt
Cover:
text
Children's book cover illustration, four friendly colorful monsters standing in front of a school building, whimsical and playful style, soft pastel colors, storybook art style, magical atmosphere, no text, no letters, no words, no typography, no writing, no watermark, no signature
Character:
text
Children's book character portrait, young girl 7 years old, brown hair in pigtails, bright curious eyes, friendly smile, soft pastel colors, storybook illustration style, white background, no text, no letters, no words, no typography, no writing, no watermark, no signature
Setting (NO people):
text
Children's book illustration, cozy elementary school classroom interior, colorful desks and chairs neatly arranged, sunlight streaming through windows, empty room no children no teacher, warm atmosphere, storybook art style, no text, no letters, no words, no typography, no writing, no people, no characters, no humans, no monsters, no watermark, no signature
Chapter Image:
text
Children's book illustration, scene from chapter: monsters hiding in school closet, surprised expressions, playful mood, colorful and whimsical, storybook art style, no text, no letters, no words, no typography, no writing, no watermark, no signature
See setup.md for complete prompt guidelines and configuration details.
CommandAction/novelcraft projectCreate new project/novelcraft project listList all projects with status/novelcraft project <number>Switch to project by number/novelcraft project <name>Switch to project by name
CommandAction/novelcraft setupSetup/Reconfigure current project/novelcraft setup imagesConfigure images module/novelcraft setup chaptersConfigure chapters module/novelcraft reconfigureReconfigure all modules/novelcraft reconfigure imagesReconfigure images only
CommandAction/novelcraft helpShow help/novelcraft help imagesHelp for specific module/novelcraft statusShow project statusNovelCraft --helpAlternative help syntax
text
NovelCraft --project # Create project
NovelCraft --project-list # List projects
NovelCraft --setup
NovelCraft --setup --module=images
NovelCraft --reconfigure
NovelCraft --help
NovelCraft --status
FilePurposesetup.mdChat-based setup guideproject-setup.mdComplete command referencereferences/CONFIG.mdConfig schema details
Current: 3.2.0 — Target Audience Profiles & Enhanced Image Configuration
Maintained by: Felix (AI) with Ronny (User) 🧠💡