Keep an agent's docs green
An agent edits a page and stops. The pull request then goes red, and you paste the log into a new session and wait for a second round. The plugin closes that loop. The agent hears about a broken page at the edit that broke it, and it cannot finish while an error is outstanding.
The plugin runs manni check for the agent. It checks
only what your repository has set up. A domain you never set up never blocks the
agent, so a repository that only uses docevals is not failed by meta.
It also gives the agent two things to read with. A read of a docs page brings the page’s metadata, sidecar fields included. An MCP server serves your docs graph, published for the session and kept current as the agent works.
Install the plugin
Section titled “Install the plugin”Two commands add the marketplace and install the plugin.
claude plugin marketplace add hawkeyexl/manniclaude plugin install manni@manniTo offer it to everyone who opens the repository in Claude Code, commit it to
.claude/settings.json. Claude Code then offers the plugin to each
contributor who opens the repository.
{ "extraKnownMarketplaces": { "manni": { "source": { "source": "github", "repo": "hawkeyexl/manni" } } }, "enabledPlugins": { "manni@manni": true }}The hooks and skills run the first manni they find:
- In the manni repository itself, that checkout’s own build,
dist/cli.js. Beforenpm run build, nothing runs there. - The copy the project installed, found by walking up through
node_modules. - A global install, found under
npm root -g. - In a repository with a
manni.config.yamland no manni at all, the version the plugin ships at, throughnpx --yes. npx downloads it into its own cache on first use, so the first hook waits for that download.
A repository with no manni config and no manni gets nothing, and no step asks the registry. Pinning a version yourself means installing it in the project.
npm install --save-dev @hawkeyexl/manniWith no manni config and no manni, a hook exits at once and prints nothing to the agent.
What happens at session start
Section titled “What happens at session start”Three hooks run when a session starts. The two tracevals hooks run only for a user who turned tracevals on. For everyone else they start no manni at all.
manni tracevals capture records a manifest of the session under
.manni/tracevals/sessions/. It stores the hash of every instruction artifact
the project holds, so tracevals can tell
whether one changed after the session began. It runs at startup only.
manni tracevals prepare runs in the background and never holds the session up. When
tracevals.runtime exists, it fetches a local judge model that is not on disk yet. It also
extracts the rules of every file that applies to every session. The first Stop then finds both ready. With no
runtime section it prints one line and does nothing else.
manni status prints what is in play, and Claude Code adds that to the agent’s
context. With no manni.config.yaml it prints nothing.
manni 4.3.0 config manni.config.yaml collection site (2 files)
meta in play meta: sectioncite not set up no page carries citationslint not set up no lint: sectiondocevals not set up no page resolves any evalsterm not set up no terms in any collectiongraph not set up no graph: sectiona11y not checked needs a running site and a browsertest not checked runs the shell steps the pages carrytracevals not checked judges recorded sessions with a model
After you edit a file in site, manni check runs on it, and errors come back to you at once.Before you finish, manni check runs on every file you changed. You get one repair pass.The manni:fix skill says how to repair each finding.Where the strategy domain is in play, the briefing gains one line before the last:
Pages name their readers and jobs in audiences:, personas: and journeys:. Write each page for the ones it names; manni strategy list prints every record.You see the same table when you run manni status yourself, so you and the
agent read one answer. Yours also has a line under each domain that is not in
play, naming what would set it up. That choice is yours, so the agent never
sees those lines.
The status hook also exports MANNI_GENERATED_BY for the session. It appends
export MANNI_GENERATED_BY=<model> to the file Claude Code names in
CLAUDE_ENV_FILE, using the model id from the session. It does nothing when the
variable is already set, so a value you chose wins. Every command the agent runs
afterwards records which model wrote the uncommitted lines, with no line about it
in your agent instructions. See
Record an agent’s edit.
The docs graph, served to the agent
Section titled “The docs graph, served to the agent”The plugin registers one MCP server, graph. Claude Code starts it with every
session and lists it in /mcp as plugin:manni:graph. The first session asks
you to approve it, as Claude Code does for a project’s .mcp.json.
In a repository with collections: in manni.config.yaml, the server runs
manni graph serve --publish. The
agent gets four tools: lookup, retrieve, traverse and impact. Each
passage they return carries its pins and dates, so the agent knows what is
verified before it quotes it. Serve the docs to an agent
covers what each tool answers.
The server publishes the graph at start when the published copy is missing or older than the checkout. It answers Claude Code first and publishes behind that, so a large site never misses the start-up deadline. A call made during the publish waits for it.
Before each later call, the server checks whether any file git status lists
has changed. If one has, it publishes again and then answers. The agent’s own
edits reach lookup without a restart.
manni: publishing the graph before serving it: <plugin data>/graph/3f9c0a1e7b2d4c58/chunks.json does not exist.manni: published the graph in 12.1s.manni: serving lookup, retrieve, traverse and impact over stdio from <plugin data>/graph/3f9c0a1e7b2d4c58/chunks.json.Those lines go to Claude Code’s debug log, not to the agent.
The graph goes to the plugin’s data directory, one folder per project,
outside your working tree. It never goes beside graph.out, so a session
never writes into the folder a site deploys. The server runs
manni graph serve --publish -i <plugin data>/graph/<hash>, and the build’s
parse cache under .manni/graph/analyze-cache/ ignores itself. So
git status shows nothing new.
The served graph carries author-facing content, as a default build does. That
covers the strategy records and
each passage’s pins and eval verdicts. A local manni site preview serves the
graph beside graph.out, so run manni graph publish --collection site before
it.
A repository with no collections: has nothing to build. The server then
offers no tools and adds nothing to the agent’s context.
What happens when the agent reads a page
Section titled “What happens when the agent reads a page”When the agent reads a page in one of your collections, the plugin hands it the
page’s metadata beside the file. That is every field the page stores, in its
frontmatter and in every sidecar file its collection declares. A field kept in
{page}.meta.yaml is one the read alone never shows.
Metadata for docs/limits.md, from the page and its sidecars:{"title":"Limits","description":"What the service accepts.","owner":["@docs-team"],"eval-suite":"reference"}The values come from
manni meta get --all-fields --no-derived,
so you can ask for the same answer yourself.
A second read of the page adds nothing. When a field changes, in the page or a sidecar, the next read brings the new values and says they replace the old.
Metadata for docs/limits.md changed since it was last given here. This replaces it:{"title":"Limits","description":"What the service accepts.","owner":["@api-team"],"eval-suite":"reference"}Each subagent gets its own, since it has its own context. After Claude Code compacts the conversation, the next read of each page brings its metadata again. Encrypted values arrive as their stored ciphertext.
A file outside every collection gets nothing. Neither does a file manni cannot read, and reading source code starts no manni process.
A page that names its readers
Section titled “A page that names its readers”A page can name strategy records in audiences:, personas: and journeys:,
and a persona names its audience in audience:. When one of those values names
a record that exists, the reply names the record too. Each record gets one line
with its type, its id and its file. The record’s content stays in its file, one
read away.
Metadata for docs/limits.md, from the page and its sidecars:{"title":"Limits","description":"What the service accepts.","personas":["devin"],"owner":["@docs-team"],"eval-suite":"reference"}The page names these records. Read a record's file for its details, or run manni strategy get <id>, which also lists the pages that name it:persona devin docs/content-strategy/personas/devin.mdOn such a page the key fields come first. Those are the fields of manni:core
and manni:lifecycle, plus the four record fields. The reply also stays under
Claude Code’s limit of 10,000 characters. When the page stores more, the reply
leaves out whole fields, largest first. It never leaves out a key field or a
reference, and it never cuts a value. Its last line then names each field it
left out, and the command that prints them.
This leaves out citations and provenance to stay under Claude Code's limit of 10,000 characters. manni meta get citations,provenance --no-derived docs/limits.md prints them.A page that names no record gets every field in page order, as in the first example. Past 10,000 characters, Claude Code saves that reply to a file and gives the model a preview.
What happens after an edit
Section titled “What happens after an edit”When the agent uses Edit, Write, MultiEdit or NotebookEdit, the plugin
runs manni check on that one file. The checks are the per-file checks of each
domain in play: meta, cite, lint and docevals.
A file outside every collection passes without a word, so an edit to
CLAUDE.md or to source code runs nothing.
When the check finds an error, the hook exits 2. Claude Code treats that as a
block and shows the agent what was written to stderr.
manni found errors in docs/limits.md. Fix them before you continue. The manni:fix skill says how to repair each finding.
meta validate✗ docs/limits.md (root) must have required property 'description' (line 1) [./schemas/page.schema.json]
1 file checked, 0 passed, 1 failed, 1 errorThe reply carries only the reports of the checks that failed. The agent fixes
the file before it moves on, and the manni:fix skill says how. Warnings and
notices never block.
If a check cannot run, the hook does not block, because exit 2 would blame the
agent for manni’s own fault. The agent gets context instead. It names the check
and the file, says the cause is in manni’s setup, and carries the whole error.
It also tells the agent to report the error to you and leave manni.config.yaml
as it is.
manni lint check could not check docs/limits.md. The cause is in manni's setup, not in the file. Tell the user what it says, and leave manni.config.yaml as it is.Unknown key "bogus" under lint: in manni.config.yaml. Supported keys: allowEmpty, overrides, structure, template, templates, types.What happens before the agent stops
Section titled “What happens before the agent stops”When the agent tries to finish, the plugin runs manni check over what it
changed. That is the per-file checks on every file the git working tree changed
against HEAD, whether modified, added, renamed or untracked. It also checks
every citation, since a source edit can drift one on a page the agent never
opened. The glossary reads only collection documents, so it is checked when
the agent changed or removed one, and skipped when it changed only code. The
graph is never checked here. It is the slowest whole-set check, and its
findings span pages, so CI checks it with a bare manni check.
When tracevals.runtime exists and you turned tracevals on, it also judges the
turn that just ended. The fast in-loop model holds it to the rules your
repository declares for agents. Otherwise the Stop runs the file checks alone and never
reads the transcript.
With a clean working tree, no file check runs.
If errors remain, the stop is blocked once. The agent gets the report and one repair pass.
{"decision":"block","reason":"manni found errors in the files you changed. Fix them, then finish. The manni:fix skill says how to repair each finding.\n\nmeta validate\n✗ docs/limits.md\n …"}If errors remain after that pass, the agent stops, and you see a message.
{"systemMessage":"manni still reports errors after one repair pass. Run manni check to see them."}One pass bounds the loop. A failure that predates the session cannot hold an agent forever.
A broken rule from the rules files arrives in the same reply. The report names the rule, the file it came from, and what the turn did. The agent fixes the work, or says why the rule does not apply, and then finishes. Catch a broken rule before the agent hands back covers what counts as a rule and how the model is chosen.
What happens when a subagent stops
Section titled “What happens when a subagent stops”For a user who turned tracevals on, a subagent that finishes runs the same check over its own transcript alone. Parallel subagents each get a judgement of their own run, and an agent definition’s rules apply to it. The repair pass works as it does for the main session. With tracevals off, no hook runs when a subagent stops.
What happens when the session ends
Section titled “What happens when the session ends”For a user who turned tracevals on, a last hook runs manni tracevals release. It hands back the
session’s hold on the local model, so the model unloads once no session needs it. With tracevals
off, nothing runs at session end.
Turn on tracevals for your sessions
Section titled “Turn on tracevals for your sessions”The plugin’s tracevals hooks are off until you turn them on. Off, the plugin
records no manifest, fetches no model and judges no turn. A committed
tracevals.runtime section says turns can be judged in this repository. Whether
yours are is your own choice.
To turn the option on, open /plugin, then Installed, manni and
Configure options. Check Judge each turn with tracevals. In a session,
/plugin configure manni@manni opens the same option. From a shell, set it as
you install the plugin.
claude plugin install manni@manni --config tracevals=trueClaude Code then says to restart, and your next session runs the hooks. It keeps the answer in your user settings.
{ "pluginConfigs": { "manni@manni": { "options": { "tracevals": true } } } }Claude Code ignores this option in project and local settings. Setting it in a committed settings file therefore never turns it on for anyone else.
With the option on, the plugin sets MANNI_TRACEVALS_HOOKS=1 for every manni it
starts. If you run manni from hooks of your own, without the plugin, set that
variable yourself. The env block of your own ~/.claude/settings.json is one
place for it. Only 1 turns the hooks on. Any other value leaves them off, and
manni warns once.
manni: MANNI_TRACEVALS_HOOKS is "true"; only 1 turns the tracevals hooks on.The session briefing reads the plugin’s option. A manni status in your own
terminal reads that shell’s environment instead. Unless you export the variable
there, it shows tracevals as turned off here, with the next step under it.
tracevals not checked runtime evals are set up, and turned off here Turn on the manni plugin's tracevals option, or set MANNI_TRACEVALS_HOOKS=1.MANNI_TRACEVALS_HOOKS=1 manni status shows the row your sessions see.
The four skills
Section titled “The four skills”The plugin ships four skills. Claude uses them when the task matches, or you can call one by name.
| Skill | What it does |
|---|---|
manni:setup | Installs @hawkeyexl/manni as a dev dependency and writes manni.config.yaml with a collections: entry for the docs it finds. It asks which domains to set up, then ends with manni status and manni check. |
manni:check | Runs manni check and walks the findings, one check at a time. |
manni:fix | Maps each finding to its repair. A meta finding is a page edit, or meta derive for a managed field. A cite finding is cite update for moved lines. |
manni:evals | Authors evals with docevals generate, fill and promote for pages, and tracevals fill for agent artifacts. It shows the diff for review. |
manni:fix carries two rules. The agent never loosens a schema, a config key or
an eval to make a check pass. It also never runs cite update --accept on a
changed citation before it reads the changed source lines.
Turn it off
Section titled “Turn it off”Disable the plugin and every hook stops.
claude plugin disable manni@manniTo keep the hooks but stop the graph server, turn plugin:manni:graph off in
/mcp. Claude Code remembers that choice for the project.
To stop the tracevals hooks for yourself alone, turn the plugin’s tracevals option off and restart Claude Code. The rest of the plugin keeps running, and so does tracevals for anyone else who turned it on.
To keep the plugin but stop one domain from blocking, remove that domain’s
section from manni.config.yaml. A domain counts as set up only when its config
or its content says so, so the next manni status shows it as not set up.