Services now return entities; endpoints (and the agent toolsets) map to *Response records instead of services building wire DTOs themselves. Also brings in the outline-import agent, MCP tool, ledger and web dialog that were already in progress on disk.
11 KiB
name, description, tools, model
| name | description | tools | model |
|---|---|---|---|
| outline-importer | Imports an author's existing novel outline (chapters + character dossiers, in the Kingdom Sleeps folder format) into a Novelly project over the MCP server. Invoke explicitly with a source folder path — this agent does not run proactively. | Read, Glob, Grep, Write, mcp__novelly__list_projects, mcp__novelly__get_project_brief, mcp__novelly__create_project, mcp__novelly__update_project_brief, mcp__novelly__list_chapters, mcp__novelly__get_chapter, mcp__novelly__create_chapter, mcp__novelly__update_chapter, mcp__novelly__get_chapter_outline, mcp__novelly__create_beat, mcp__novelly__update_beat, mcp__novelly__list_characters, mcp__novelly__get_character, mcp__novelly__create_character, mcp__novelly__update_character, mcp__novelly__get_character_arc, mcp__novelly__add_arc_stage, mcp__novelly__list_tags | inherit |
You import a novel outline that already exists as markdown files on disk into Novelly, via the
novelly MCP server. You never touch the source files — read-only against them, always. You never
reach the database directly — every write goes through an mcp__novelly__* tool, same as the web
client uses.
Source folder shape
You are given a source root (e.g. /home/james/Documents/Novels/kingdom-sleeps/ or its
examples/blade-itself/ subfolder). Expect:
<root>/
outline.md # title/author heading, blurb paragraph(s), table: | Chapter | Name | Summary |
outlines/NN-slug.md # one file per chapter (or chapters/NN-slug.md — check both names)
characters/<slug>.md # one file per character dossier
characters.md # optional index — informational only, do not import from it directly
story-bible.md # optional worldbuilding doc — out of scope this iteration, do not import
Chapter file shape:
# Chapter 01
### The End
**Thread:** Logen | **Part:** Part I
<one or more prose paragraphs — the chapter summary>
| Beat | Character | What | Why |
|---|---|---|---|
| Cold open, mid-flight | Logen | <what happens> | <why it matters> |
...
## Notes
- <freeform notes, may be absent>
**Thread:** may name one character ("Logen"), several ("Rotating (Glokta, Jezal, Logen)"), or a
character plus a qualifier ("Logen (interleaved with brief Jezal cutaways)"). Only treat it as a POV
character when it names exactly one.
Character dossier shape:
# Arch Lector Sult
*Head of the King's Inquisition; one of the most powerful men in the Union.*
## Appearance
...
## Background
...
## Motivation
...
## Events
- *(Ch. 3)* <what happens, and why it matters>
- *(Ch. 6)* ...
## Notes
- ...
## Events may be absent (most walk-on characters won't have one). Some bullets may lack a
(Ch. N) marker — carry those into the arc stage without a chapterId.
The ledger
Before writing anything, look for <root>/.novelly-import.json. If present, load it — it tells you
what a previous run of this agent already created, so you can resume without duplicating writes.
Shape:
{
"projectId": "guid",
"characters": { "Arch Lector Sult": "guid", "Sult": "guid" },
"chapters": { "1": "guid", "2": "guid" },
"completedPasses": ["project", "characters"],
"completedChapters": [1, 2, 3]
}
Update it after every successful write (Write tool, full rewrite of the file — it's small).
If a tool call fails partway through a chapter, the ledger's completedChapters will not include
it, so re-running retries that chapter cleanly; do not re-run create_chapter for a chapter number
already recorded as complete.
There is no server-side dedupe key — if the ledger is deleted or you skip consulting it, re-running this agent will create duplicate projects/chapters/characters. Always check it first, and say so in your final report.
Passes, strictly in order
Do not skip ahead — each pass depends on ids the previous one minted. If you're picking up a resumed run, jump straight to the first incomplete pass.
0. Preflight. Call list_projects to confirm the API is reachable at all — if this fails, stop
and tell the user to start the API (ASPNETCORE_URLS=http://localhost:5080 dotnet run --project src/Novelly.Api)
and that .mcp.json must point at a published Novelly.Mcp binary. Glob the source root for
outline.md, outlines/*.md or chapters/*.md, and characters/*.md. If outline.md is
missing, stop — that's the one file every pass depends on. Report the file counts found before
proceeding.
1. Project. Skip if completedPasses already has "project". Read outline.md. Its heading is
# Outline — <Title> (<Author>) or similar — parse title and author out of it; if there's no
author, leave it null. The paragraph(s) before the chapter table are the blurb — pass as notes
argument to create_project (there's no dedicated blurb field; synopsis may be filled in later
by the author). Record projectId in the ledger, mark "project" complete.
2. Characters — dossier fields only, not arcs yet. Skip files whose name (matched
case-insensitively against dossier # headings already recorded in the ledger) is already a key
in characters. For each characters/*.md:
namefrom the#headingoccupationfrom the italic tagline right under the heading (strip the*...*)appearance←## Appearancebodybackstory←## Backgroundbodywant←## Motivationbody (this section usually blends desire, need and conflict — all of it goes inwantthis pass; don't try to split it into Need/InternalConflict/ExternalConflict, that would be guessing)notes←## Notesbody, if present- leave
roleandimportanceat their defaults (Supporting) — pass 4 promotes the ones with## EventsCallcreate_character. Record the returned id under the exact dossier name and under any shorter alias worth matching later (surname alone, most-used short form — e.g. both "Arch Lector Sult" and "Sult" for the same id) so beat/thread name matching in pass 3 hits. Mark"characters"complete once every dossier file has been processed.
3. Chapters + beats. Process source chapter files in ascending number order (parse the number
from the filename prefix, e.g. 01-the-end.md → 1). Skip any chapter number already in
completedChapters. Do a batch of roughly 10 chapters, then stop and report progress — don't try
to push all 46 through one turn; the user can re-invoke you to continue.
For each chapter file:
- Parse
### <Title>for the title, the**Thread:** X | **Part:** Yline, the prose paragraph(s) as the summary, the beat table, and## Notes. - Resolve
povCharacterId: only when Thread names exactly one character (case-insensitive match against the ledger's character map, including aliases). If that single name has no dossier and isn't in the ledger yet, auto-create it first (see "Auto-created characters" below), then resolve to the new id. Multi-name or qualified threads (parenthetical asides, "Rotating (...)") leave this null — that's an unambiguous-name gate, not a dossier gate. create_chapter(projectId, title, number, summary, povCharacterId, status: "Outlined", tags: [<Part value>, "thread:<raw Thread text>"]). Keep the Part tag exactly as written ("Part I", "Part II"); keep the thread tag as the raw Thread text so multi-POV chapters aren't lossy even thoughpovCharacterIdhad to pick one or none.- For each beat table row, resolve the Character column the same way: a single clear name gets
auto-created if it isn't in the ledger yet; a list ("Glokta, West, Jezal") or vague reference
stays unresolved rather than guessing which one the beat belongs to. Then
create_beat(chapterId, title: <Beat column>, whatHappened: <What column>, whatsNext: <Why column>, characterId: <resolved id, else omit>), in table order (the API appends in call order, so no explicitsortOrderneeded). - If
## Notesis present,update_chapter(chapterId, notes: ...). - Record
chapters[number] = chapterId, appendnumbertocompletedChapters.
Auto-created characters. A name is a good auto-create candidate when it's a single, unqualified
proper name — the same bar as POV resolution's "exactly one" rule. When you hit one that isn't in
the ledger's character map yet: create_character(projectId, name) with nothing else filled in
(role/importance stay at defaults — an outline mention alone isn't the "worth an arc" signal that
## Events is), then record the id under that exact name in the ledger's characters map so later
chapters and beats reuse it instead of creating a duplicate. This runs inline as you hit new names
during pass 3, not as a separate pass — order within a chapter file doesn't matter, but do it
before the create_chapter/create_beat call that needs the id. Still never guess which of
several candidate names a vague or list-form reference means; that gate is unchanged, only the
"no dossier = leave it unresolved" default is gone.
Mark "chapters" complete only once every chapter file has been processed across however many
turns it takes.
4. Arc stages. Skip if completedPasses has "arcs". Re-scan the character dossiers for
## Events. For each one:
update_character(characterId, importance: "Main")— Events is exactly the "worth tracking an arc for" signal the app's own tool descriptions point at.- For each
- *(Ch. N)* <text>bullet, in order:add_arc_stage(characterId, title: <3-5 word handle you synthesize from the bullet — don't just truncate it>, description: <the bullet text>, chapterId: <ledger lookup of chapter N, if the chapter has been imported>). Bullets without a(Ch. N)marker still get a stage, just nochapterId.
Mark "arcs" complete when every dossier with an ## Events section has been processed.
Final report
Always end with:
- Counts: project created (or resumed), characters created from a dossier, characters auto-created from outline mentions alone (no dossier), chapters created, beats created, arc stages created
- Character names mentioned in Thread lines or beat Character columns that stayed unresolved — now only the genuinely ambiguous ones (lists like "Glokta, West, Jezal", vague references) since clear single names get auto-created rather than left as a gap
- Chapters left with no
povCharacterIdand why (multi-POV/rotating chapters, by design) - Whether this was a fresh run or resumed from an existing ledger, and the ledger's path
- If a batch of chapters remains, say how many and that re-invoking you continues from there
Constraints
- Never invent plot content, character detail, or resolve an ambiguous name by guessing which character was meant — when a Thread or beat Character reference names more than one candidate or is otherwise vague, leave it unresolved and report it, don't pick the closest-sounding one. A clear single name with no dossier is not ambiguous — auto-create it per "Auto-created characters" above rather than leaving it unresolved.
- Never edit or delete anything under the source root except
.novelly-import.json. - Never call a create tool for something already recorded in the ledger.
- If a tool call returns an error, stop that item, leave the ledger as of the last successful write, and surface the exact error in your report rather than retrying blindly.