# Prompts
A **prompt** in Inkhaven is a reusable template you can dispatch to the
AI pane without retyping the whole instruction. Type `/` in the AI prompt
bar to open the picker, choose one, hit Enter, and the prompt's text
(with `{{selection}}` / `{{context}}` substituted) lands in the bar ready
to send.
Two sources of prompts are merged in the picker:
1. **System prompts** — `prompts.hjson` in the project root. Ship-able,
versionable, written in HJSON. Marked `[ system ]` in cyan.
2. **Book prompts** — paragraphs in the **Prompts** system book of the
current project. Author them in the TUI like any other paragraph,
carry them with the manuscript. Marked `[ book ]` in green.
Both kinds support `{{selection}}` and `{{context}}` substitution.
## Table of contents
- [The picker workflow](#the-picker-workflow)
- [Direct invocation: `/name`](#direct-invocation-name)
- [System prompts in `prompts.hjson`](#system-prompts-in-promptshjson)
- [Book prompts in the Prompts system book](#book-prompts-in-the-prompts-system-book)
- [Substitutions: `{{selection}}` and `{{context}}`](#substitutions-selection-and-context)
- [Built-in special prompts](#built-in-special-prompts)
- [Patterns for good prompts](#patterns-for-good-prompts)
## The picker workflow
1. Focus the AI prompt bar (`Ctrl+I`).
2. Type `/`. The picker overlay opens just above the bar.
3. Keep typing — the list filters live by substring across both the
prompt name and description.
4. `↑` / `↓` to move the selection, `Enter` (or `Tab`) to expand the
chosen template into the bar.
5. Edit the expanded text if you want; then `Enter` to send.
```
┌── Prompts ─────────────────────────────────────────────────┐
│ system /tighten │
│ Tighten the prose without changing meaning │
│ book /worldbuilding-pass │
│ Run a worldbuilding consistency pass on selection │
│ system /darker │
│ Make the tone darker, keep facts │
└────────────────────────────────────────────────────────────┘
> /tigh│
```
Press `Esc` to close the picker without picking.
## Direct invocation: `/name`
You can skip the picker by typing the full name yourself. From the AI
prompt:
```
/tighten
```
`Enter` looks up `tighten` (system first, then the Prompts book by
slug or title), expands the template with the current selection, and
sends. If the name doesn't match anything you get a status like
`no prompt 'tighten' — type / to see the list`.
Trailing text after the name is **ignored** by the resolver — the
template is what gets sent. To append a custom note, expand the prompt
into the bar (Enter inside the picker) and edit it before submitting.
## System prompts in `prompts.hjson`
The file lives at `<project-root>/prompts.hjson` and is generated by
`inkhaven init` from a shipped default. Its shape:
```hjson
{
prompts: [
{
name: "tighten"
description: "Tighten the prose without changing meaning"
template: '''
Tighten the prose below without changing meaning or voice.
Aim for 10–20 % fewer words. Keep Typst markup verbatim.
--- begin
{{selection}}
--- end
'''
}
{
name: "darker"
description: "Make the tone darker, keep facts"
template: '''
Rewrite the passage in a darker tone — somber palette, unease,
understated dread. Keep every fact, name, and timeline intact.
Preserve any Typst markup.
Context: {{context}}
--- begin
{{selection}}
--- end
'''
}
]
}
```
Each entry has three fields:
| `name` | The `/name` identifier. Lowercase, no spaces, hyphens allowed. |
| `description` | One-line blurb shown in the picker. Make it scannable. |
| `template` | The actual prompt body. Use HJSON triple-quoted strings (`'''`) for multi-line content; placeholders `{{selection}}` and `{{context}}` are substituted at send time. |
You edit this file with any text editor. Inkhaven reloads it when the
TUI launches; live reloads are not (yet) supported, so close and
reopen after editing.
### Add a new system prompt
Append an entry to the `prompts:` list:
```hjson
{
name: "expand"
description: "Expand the selected passage by ~30 %, same voice"
template: '''
Expand the passage below by roughly 30 %. Preserve voice and
Typst markup. Add concrete sensory detail; do not introduce new
plot facts.
--- begin
{{selection}}
--- end
'''
}
```
Save. Restart the TUI. `/expand` shows in the picker.
## Book prompts in the Prompts system book
Project-local prompts live as **paragraphs** under the `Prompts` system
book. To create one:
1. Navigate to the **Prompts** book in the Tree pane.
2. Press `+` to add a paragraph, or `P` to insert one after the
cursor.
3. In the Add modal, enter a title (this becomes the prompt's
description in the picker).
4. Press Enter to open the new paragraph in the editor.
5. Write the prompt body. It can include `{{selection}}` / `{{context}}`
substitutions exactly like system prompts.
6. `Ctrl+S` to save.
The paragraph's **slug** is the `/name`; the **title** is the
description in the picker.
For example, a title of `"Worldbuilding consistency pass"` slugs to
`worldbuilding-consistency-pass`. Typing `/world` in the picker
filters straight to it.
### Why use a book prompt instead of `prompts.hjson`?
- **Travels with the manuscript.** Anyone who clones the project gets
your prompts for free.
- **Editable in the TUI.** No round-trip through a text editor.
- **Indexed for search.** Semantic search and Tantivy hits both find
it; `inkhaven search "tighten"` lists your book prompts alongside
prose.
- **Snapshot-able.** F5 captures a snapshot before you tweak the
prompt; F6 picks an older version back out.
Use `prompts.hjson` for prompts you want to ship between projects (your
"signature" templates). Use the Prompts book for project-specific
prompts that depend on this manuscript's voice / world / glossary.
### Heading stripping
When a book prompt is expanded into the AI prompt bar, the leading
`= Title` Typst heading line is stripped so the model doesn't receive
editor chrome — only the prose. Empty leading blank lines after the
heading are also dropped.
## Substitutions: `{{selection}}` and `{{context}}`
Two placeholders are filled in at send time:
| `{{selection}}` | The current editor selection. If no selection is active, the **entire open paragraph** is substituted. Used as the working text in most prompts. |
| `{{context}}` | A breadcrumb of titles for the open paragraph: `Book › Chapter › Subchapter › Paragraph`. Useful when you want the model to know where in the manuscript you're working without flooding it with the full text. |
You can use both in the same template:
```text
Context: {{context}}
Selection:
{{selection}}
```
Both substitutions are pre-rendered before the prompt is sent — what
the model receives is plain text with the placeholders already
replaced. Inkhaven never modifies your `prompts.hjson` or book
paragraphs at runtime.
## Built-in special prompts
A few prompt-driven flows are powered by **named lookups** with fallback
to a built-in default:
### Grammar check (`F7`)
Resolver, in order:
1. Paragraph slugged `grammar-check` (or title containing "Grammar
check") in the Prompts book.
2. Entry named `grammar-check` (or `grammar check`) in `prompts.hjson`.
3. Built-in fallback: a copy-editor prompt scoped to the configured
`language` that preserves Typst markup and emits the corrected
paragraph between `<<<CORRECTED>>>` / `<<<END>>>` markers.
You can override #1 or #2 to customise the grammar style guide. Make
sure your custom prompt **also emits the markers** if you want the AI
pane's `g`-apply key to extract just the corrected text. The markers
look like:
```
<<<CORRECTED>>>
the corrected paragraph here, Typst markup preserved
<<<END>>>
```
See `Tutorials/06-grammar-check.md` for the full apply flow.
### Help question (`F1` / `Help!` prefix)
This one is **not** user-customisable — F1 uses a fixed strict-grounding
system prompt, RAGs against the Help book paragraphs, and pins the
inference to Local mode so the model never invents features.
## Patterns for good prompts
A few patterns that hold up across providers:
1. **Lead with the role.** `You are a meticulous copy-editor reviewing
…`. Models drift less from clear personas.
2. **Bound the change set.** `Keep voice and pacing. Do not introduce
new facts. Preserve every Typst markup token verbatim.` Stronger
constraints, fewer regressions.
3. **Frame the input.** Wrap `{{selection}}` in `--- begin / --- end`
markers so the model can see exactly where the work text starts and
stops.
4. **Ask for structured output.** A summary line + a list of changes +
the corrected text is more useful than a wall of prose, and easier
to apply with `r` / `i` / `b` / `g`.
5. **Defer style.** When you want the model to match an existing
chapter's voice, pass that chapter as scope (`F9` set to Chapter
then Enter). The chapter content arrives with the prompt; the
model has a sample to imitate.
### Provider-specific notes
- **Gemini** — handles long contexts well; the F9 *Book* scope is
practical even on full manuscripts up to ~200K tokens.
- **DeepSeek** — fast and inexpensive for tighten / rewrite passes;
occasionally emits empty streaming chunks (a warning lands in
`.inkhaven.log` and is harmless).
- **Ollama** — local, free, no API key. Smaller open models (`llama3.2`,
`qwen2.5`) cope with editing tasks but struggle with long manuscripts;
start with scope=Paragraph or scope=Selection.
- **Anthropic / OpenAI** — work via genai; add a provider entry per
[`CONFIGURATION.md`](CONFIGURATION.md#llm).
## Where prompts intersect with scope and mode
Be aware of these orthogonal axes when designing prompts:
- **Scope (F9)** prepends a context block (selection / paragraph /
subchapter / chapter / book) **above** the prompt text. The model
sees both.
- **Inference mode (F10)** swaps the system prompt: Local forbids
general knowledge, Full allows it. Grammar / Help inferences pin to
Local regardless.
- **Chat history** is replayed on every non-Help / non-Grammar
inference. F9-scoped content lands in history too — that's how
follow-up questions work.
For one-shot tasks (tighten this paragraph, run grammar check), the
prompt itself can contain `{{selection}}` and you don't need scope or
chat history. For ongoing conversations (brainstorm a subplot, plan a
chapter), keep scope at None and let chat history accumulate; clear it
with `Ctrl+B C` when you want a fresh thread.