# Inkhaven 1.4.1 — Chat with your book

_The AI pane's **Book** scope used to ship the entire manuscript with every question —
slow on a real book, and grounded in nothing. 1.4.1 replaces it with **retrieval-augmented
generation** (BOOK_RAG-1): a Book-scope question now retrieves the handful of paragraphs
that actually bear on it, grounds the answer in them, and makes the model **cite** them.
Built entirely on infrastructure already in the tree — the on-save vector index every
semantic feature uses — with **no new dependencies**._

_Plan: [BOOK_RAG-1_PLAN.md](../PROPOSALS/BOOK_RAG-1_PLAN.md) · New tutorial:
[87](../Tutorials/87-chat-with-your-book.md)_

## The change — Book scope is retrieval-augmented

Put the cursor in a manuscript book, cycle the AI pane to **Book** scope (**F9**), and ask
a question. Instead of sending the whole book, Inkhaven now:

1. **Retrieves** the most semantically relevant paragraphs (the same vecstore HNSW index
   that powers every other semantic search),
2. **Expands** each hit with a neighbouring paragraph or two so it reads in context,
3. **Token-budgets** the result into a focused grounding block, and
4. **Grounds** the answer in those passages — every claim about the book is a markdown
   citation, `[label](#id)`, pointing at the paragraph it came from.

If the model ever cites an id that wasn't in what it was given, Inkhaven flags it inline —
`[citation could not be validated: …]` — so a hallucinated reference can't pass as
grounded. When nothing in the book addresses a question, the model is told to say so
plainly rather than confabulate.

This is **always on** for Book scope — there is no toggle and no "send the whole book"
fallback. **The other scopes are unchanged:** Selection, Paragraph, Subchapter, and
Chapter still send exactly the text they name.

### See what it retrieved

Above the conversation, a collapsed line shows how many passages grounded the answer —
**`p`** expands it (and `p` again collapses), listing each passage's similarity score, a
**★** for a direct hit, its breadcrumb, and its opening. Retrieval happens **once per chat
session**: follow-ups reason over the same passages; clearing the chat history re-grounds
the next question. Save an edited paragraph while a Book conversation is open and Inkhaven
nudges you once — *"book changed since retrieval — clear chat to re-ground on the new
text"* — a reminder, never an interruption.

### What's in scope

Retrieval is always anchored to the user book the cursor is in. Because a question about
your story often wants your *notes* about the story, a curated set of author-content
system books joins the pool — **Notes, Research, Places, Characters, Artefacts, World,**
and **Language** by default — while the internal/meta books (Scripts, Prompts, Typst,
Help, Intent) never enter retrieval. All of it is tunable in the new **`book_rag:`**
config block (`top_k`, `context_expansion`, `max_context_tokens`,
`include_system_books` / `exclude_system_books`).

## From the terminal — `inkhaven book-rag`

**`inkhaven book-rag retrieve <query>`** inspects exactly what Book-scope chat would
ground on — no LLM call, no cost:

```sh
inkhaven book-rag retrieve "what does Mara fear about the voyage?"
inkhaven book-rag retrieve "the harbour market" --top-k 8 --context
```

`--book-name` selects the book in a multi-book project, `--top-k` overrides the config for
that run only, and `--context` prints the literal grounding block the model would receive
instead of the human-readable listing. The CLI and the pane share one retrieval core, so
what you see is what grounds the chat.

## From a script — `ink.book_rag.*`

Eight new **read-only** Bund words expose the same retrieval to scripts (also aliased
`book_rag.X`):

| Word | Stack | What it does |
|------|-------|--------------|
| `ink.book_rag.retrieve` | `( anchor query -- passages )` | passage dicts `{id, breadcrumb, body, score, is_hit}` |
| `ink.book_rag.context` | `( anchor query -- text )` | the composed grounding block |
| `ink.book_rag.scope` | `( anchor -- ids )` | the node ids in the retrieval pool |
| `ink.book_rag.config` | `( -- dict )` | the active `book_rag` config |
| `ink.book_rag.system_prompt` | `( lang -- text )` | the localized grounding prompt |
| `ink.book_rag.estimate_tokens` | `( text -- n )` | rough token estimate (≈ chars/4) |
| `ink.book_rag.cited_ids` | `( passages -- ids )` | the valid-citation set |
| `ink.book_rag.validate_citations` | `( response ids -- text )` | flag hallucinated `](#id)` citations |

An *anchor* is any node's slug-path; retrieval runs against the user book it belongs to.
All eight gate under `store_read` (default-allowed): the retrieval is local, nothing
mutates, and no LLM is called. See [BUND_TUTORIAL.md](../Bund/BUND_TUTORIAL.md).

## Multilingual

The grounding contract — cite the passages, admit when they're silent — ships localized
for the five baseline languages (English, Russian, Spanish, French, German), selected by
the active prompt language and falling back to English for any other. Answers come back in
the language you ask in.

## Cost

Each Book answer is one local retrieval plus one grounded model call — far less than
shipping the whole manuscript. Those calls are tagged under the **`book_rag`** category in
`inkhaven cost` / **`Ctrl+B $`**, so you can see what chatting with your book costs. True
to Inkhaven's permissive principle, that figure **informs; it never blocks** a question.

## Dependencies & compatibility

**No external application or binary dependencies; no new runtime crates** — BOOK_RAG-1 is
built entirely on the existing `.typ` prose, DuckDB store, Fastembed embeddings, and the
vecstore HNSW index. The one behavioural change: **Book scope no longer sends the whole
book** — it retrieves and grounds instead. No on-disk shape moved; no config migration is
required (the `book_rag:` block is optional, every default preserving good behaviour).
Tests 1850 → 1855.
