Every character file of a book is checked after saving by the PostToolUse hook validate_character.py. This page describes:
characters_present in the briefThe template is in the plugin at templates/character.md and contains hints for every section.
| Book category | Directory | Example |
|---|---|---|
fiction |
{book}/characters/{slug}.md |
characters/arne-kruse.md |
memoir |
{book}/people/{slug}.md |
people/grandfather.md |
| Series | series/{series}/characters/{tracker-slug}.md |
characters/king-caelan.md |
One file per character. INDEX.md is skipped by the hook. For memoir the person files follow a different schema, see Memoir Support.
The hook requires at least:
---
name: "Display name"
role: protagonist
status: Concept
---
For fiction characters, name, role and status are required. If one is missing, the hook reports it back to the model with exit code 2 after the write, and the model has to add it. For memoir person files role does not apply, see "Memoir person files" below. Further fields such as age, job, description, archetype or pov are free metadata the hook ignores. The template additionally carries gender (pre-filled empty) and aliases (commented out).
These optional blocks are evaluated by checking tools:
tactical: for combat and travel scenes, see Technical Detailsknowledge: for the POV boundary, see Technical DetailsThe chapter writing brief reads inventory, clothing, injuries, altered states and perception limiters of the POV character in this order:
current_inventory, current_clothing, current_injuries, altered_states and environmental_limiters of the character file, if presentchapter-writer writes with update_character_snapshot when closing a chapter (the character file stays unchanged)A frontmatter field as_of_chapter may sit in the file, but the brief does not read it.
If no source delivers anything, the brief carries a warning and the skill asks instead of inventing. Frontmatter values arise, for example, from bootstrapping from a previous volume or by your own hand.
series_evolution_imported_fromWhen /storyforge:bootstrap-book-from-series fills a character file, it carries the field series_evolution_imported_from: B1. It is informational and says which volume the start values came from. After that you can change any field freely.
scan_for_named_characters fills characters_present in the brief by searching the outline for the name field and all aliases. Two sources are combined:
name counts as an alias. Theodore "Theo" Wilkons yields Theo.aliases: ["S.", "Sera"] in the frontmatter.Matching uses word boundaries, so Lin does not hit in Linguistics, and dotted initials work.
| Role | When |
|---|---|
protagonist |
main POV character |
deuteragonist |
second most important character, equal rank |
antagonist |
main opponent |
love-interest |
romantic anchor |
mentor |
guides the protagonist |
foil |
contrast character |
herald |
brings the call to action |
confidant |
trusted counterpart for the inner voice |
supporting |
named, recurring characters |
minor |
one-off or subplot-only characters, the hook skips the section recommendations |
An unknown role produces a warning with the list of valid values.
Concept → Profile → Backstory → Arc Defined → Final
| Status | Meaning | Content |
|---|---|---|
Concept |
idea sketched, role known | name, role, short description |
Profile |
profile worked out | plus appearance, personality, rough voice |
Backstory |
wound and lie identified | plus ghost, want vs. need, fatal flaw |
Arc Defined |
arc mapped end to end | plus character arc with beats |
Final |
production-ready | plus voice with sample dialogue, all relationships |
The status is a signal to you about how mature the file is. It does not control any tool.
The hook warns if these are missing for a character who is not minor. It accepts level-2 and level-3 headings.
| Section | Alternatives that suffice |
|---|---|
## Want vs. Need |
|
## Fatal Flaw |
|
## The Ghost |
## Backstory / The Wound or ## Backstory |
## Motivation Chain |
## GMC or ## GMC (Goal / Motivation / Conflict) |
The external goal against the inner truth. This includes the lie the character believes, which keeps them from the need.
Example: Want: prove he is more than others see. Need: accept that he is enough. Lie: "I am not enough."
The single formative event or lasting condition that shaped the character. It is the root of the lie. The template creates it as ### The Ghost under ## Backstory.
Not a mere weakness, but a flaw that actively causes problems, ties into the theme and is rooted in the ghost.
Ask "why?" three times: first the stated goal, then the emotional engine, then the wound-driven core need.
Arc type (positive, negative, flat) plus beats: setup, challenge, midpoint, crisis, resolution. Required for the status Arc Defined.
The template lists the section as ## Key Relationships, and that is exactly the heading the harvest reader of /storyforge:harvest-character-evolution accepts too — along with ## Relationships, ## Relationship and ## Beziehungen (with or without the "über die Bände" suffix). No renaming is needed. Bullets with a bold name are the usual format:
## Relationships
- **Theo:** partner, the relationship that pulls him back into the family.
- **Caelan:** father, his longest-running unresolved conflict.
Not every named character needs Arc Defined. The hook is only a signal here, the yardstick depends on the role:
| Character | Typical status | Expected |
|---|---|---|
| Protagonist (POV) | Arc Defined or Final |
all recommended sections and voice with a sample |
| Deuteragonist | Arc Defined |
all recommended sections |
| Antagonist | at least Backstory |
want vs. need, fatal flaw, ghost; villains have inner logic too |
| Love interest | Backstory, with their own arc Arc Defined |
want vs. need and ghost |
| Mentor, herald, foil, confidant | Profile to Backstory |
voice and the slice of psychology that carries their function |
| Supporting with own arc | Backstory |
ghost and arc |
| Supporting (recurring) | Profile |
personality, voice, appearance |
| Minor | Profile or Concept |
just enough to render them |
The hook runs as PostToolUse after Write, Edit and MultiEdit on .md files in characters/ and people/. The file is already written at that point. Exit code 2 returns the message to the model, which has to fix the finding.
| Condition | Message |
|---|---|
| no YAML frontmatter | Missing YAML frontmatter |
| invalid YAML | Invalid YAML frontmatter: ... |
| frontmatter is not a mapping | Frontmatter is not a YAML mapping |
| missing required field | Missing frontmatter field: 'status' |
| Condition | Message |
|---|---|
| unknown role | Unknown role 'wizard'. Valid: ... |
| missing recommended section | Missing recommended section '## Want vs. Need' |
characters/ and people/INDEX.mdseries/{series}/characters/{file} or series/{series}/people/{file} — the trailing four path segments have to be exactly series / series slug / characters or people / file name. Character files of books that live under series/{series}/{book}/characters/ do not match this pattern and are checked like any other character file.Files recognizable as memoir person files by person_category, consent_status or real_name in the frontmatter get a reduced check: the value of role is not compared against the role list, and the recommended sections of fiction characters do not apply. role is also not a required field for them — only name and status are. The tool create_person writes status but no role, and passes the check right away. Details: Memoir Support.
Series projects have, per character, an additional tracker file under series/{series}/characters/{tracker-slug}.md. It is a thin series layer: it records what is constant across all volumes and keeps the development per volume.
| File type | Path | Purpose |
|---|---|---|
| Character file in the book | projects/{book}/characters/{slug}.md |
full profile: psychology, arc, voice. Source of truth for one book. |
| Series tracker | series/{series}/characters/{tracker-slug}.md |
development across volumes, relationships across volumes |
The two are linked through the optional field book_slug in the tracker. If it is missing, the tracker's slug counts as the slug of the book file:
---
name: "King Caelan"
slug: king-caelan # slug of the tracker
book_slug: caelan # optional: book file is caelan.md
role: protagonist
species: vampire
status: "Profile"
recurs_in: ["B1", "B2"]
tracker_type: "thin"
---
You only need the field when the tracker carries a role or title slug but the book file the bare name. Without the link, the brief returns series_evolution: null for that character.
tracker_type is thin (default, the book file stays the source of truth) or full (the tracker itself, rare).
## Snapshot
One paragraph of essence at series scale: what is constant across all volumes?
## Evolution per Band
### B1 Start
### B1 Ende
### B2 (geplant)
## Beziehungen über die Bände
- **Other character:** B1 dynamic → B2 shift → B3 resolution
## Updates Log
The parser knows two forms: bullets per volume (### B1 Firelight with **Start:** and **Ende:**) and separate H3 headings (### B1 Start, ### B1 Ende). Writing skills keep the form your tracker already uses. The template is templates/series-character-tracker.md.
This is how a character moves from one volume to the next:
chapter-writer proposes a snapshot of the POV character and, after your confirmation, saves it in the database. If the chapter stays at Draft, the proposal is skipped.Final. Editing, Export Ready and Published you set yourself. Once the book is at Export Ready or Published, /storyforge:harvest-character-evolution firelight proceeds without a question. At Editing or Proofread the skill asks whether to run anyway, earlier it blocks. It reads the end state of each recurring character and their relationships, synthesizes a "B1 Ende" summary and goes through the characters one by one with you (accept, edit, skip, keep existing). What is confirmed goes into the tracker, plus an entry in the updates log./storyforge:new-book itself asks for the series and the number, and registers the book via add_book_to_series right while creating it if the number is known. With the series and number known, the skill then automatically copies the recurring character files (filtered by recurs_in) 1:1 into the new book with copy_recurring_chars_to_new_book, after your confirmation. Characters that only appear in book 1 stay out, and you create new book 2 characters manually. The copy step derives the band tag from series_number and skips itself when the number is missing, 0 or 1. If the number was left open at creation and you register it later via add_book_to_series, this automatic copy step no longer runs — you then copy by hand. The Series Workflow Example shows the order./storyforge:bootstrap-book-from-series firelight moonrise reads "B1 Ende" and "B2 (geplant)" per tracker, synthesizes a start state, goes through the characters one by one with you and writes it into the frontmatter of the character file in the new volume, together with series_evolution_imported_from. If the previous book is not yet at Export Ready or Published, the skill asks whether to run anyway.chapter-writer reads characters_present[*].series_evolution: the end state in the previous volume (previous_book_end), the plan for the current volume (current_book_plan) and the relationship development (relationships_evolution). The brief fills this for books in either layout, flat under projects/{book} and nested under series/{series}/{book}.| Skill or tool | When | Reads | Writes |
|---|---|---|---|
chapter-writer (POV snapshot) |
chapter end at Review or Final | chapter timeline | snapshot in the database |
harvest-character-evolution |
end of book, at book status Export Ready or Published (Editing and Proofread with a question) | character file, relationships | tracker B{N} Ende, updates log |
copy_recurring_chars_to_new_book (after add_book_to_series) |
start of the next volume | tracker recurs_in, character files |
character files in the new book (copies) |
bootstrap-book-from-series |
after creating, before writing | tracker B{prev} Ende, B{new} (geplant) |
frontmatter of the character file, updates log |
Chapter writing brief (series_evolution) |
every brief in a series book | tracker | writes nothing |
A complete example with all outputs is in the Series Workflow Example.
series_evolution stays emptyThe field is always present in the brief so consumers can check for null. It is null when one of these conditions applies:
series: in the README. harvest-character-evolution then reports that the book belongs to no series and ends cleanly. bootstrap-book-from-series requires both books to share the same series.series_number is less than 1. A book that has not been numbered with add_book_to_series has series_number 0 or none.recurs_in for this.