Fill the vocabulary
Most of the graph comes out of pages for free: titles, links, headings, code blocks, git history. The categorization does not. Which concept a page owns, which product variant it is about, what it is narrower than. Nobody wrote any of that on a page older than the vocabulary.
manni graph fill proposes those fields from each page’s content and writes the
ones that clear a confidence bar. Every written value is attributed, so what
lands is a review queue rather than a claim that the corpus is now categorized.
This is Maya’s page.
Dry run first, always
Section titled “Dry run first, always”$ manni graph fill --dry-run docs/guidesproposed docs/guides/install.md (label, concepts, type) — dry run, not writtenno-op docs/guides/changelog.md (model proposed nothing new)The dry run is where the inference calls are spent. Proposals are cached
before the confidence gate, so the write pass after it is a cache hit.
Re-running at a different --confidence therefore costs nothing at all. Read
the proposals, decide the bar, then run it again without --dry-run.
What each line means
Section titled “What each line means”| Status | Meaning |
|---|---|
filled | Fields were written to the page, and listed. |
proposed | The same, under --dry-run: nothing was written. |
complete | The page already carries everything in scope. Not asked. |
no-op | The model was asked and proposed nothing new. |
skipped | Not processed, with the reason. It reads turn budget when the budget ran out, and offline when no local model was there to ask. |
error | The page could not be filled, with why. |
A line can carry annotations after the fields:
[cached], meaning no inference call was made for this page.[low confidence, not written: type 0.45], meaning proposed, below the bar, reported rather than written.[graph check rejected: broader], meaning the guardrail refused it. See below.[no such section: installing], meaning the model addressed a heading that is not there. That usually means the page was edited after it was described.
The bar
Section titled “The bar”$ manni graph fill --confidence 0.85 docs/guidesA proposed value carries the model’s own confidence, 0 to 1, and only values
above the bar are written. The default is 0.7, set in config as
fill.confidenceThreshold.
Raise it when you are filling a corpus nobody will review closely. Lower it when you are reviewing everything anyway and would rather see the marginal proposals than lose them. Re-gating is free, so this is cheap to experiment with.
Narrowing what is proposed
Section titled “Narrowing what is proposed”--fields names what may be proposed at all, comma-separated and given once:
$ manni graph fill --dry-run --fields label,concepts,type docs/guidesThe full list is label, alt-labels, broader, narrower,
related-concepts, concepts, type, applies-to,
about-product-lifecycle, about-product-aspect, not-applicable-to,
not-about-product-aspect and sample-questions. An unknown name is a usage
error, with the allowed set in the message.
Narrowing is usually the right first move. A pass that proposes only label
and concepts produces a review you can actually do. The SKOS hierarchy keys
are also the ones a model is worst at.
--sections also proposes per-section metadata. It is opt-in, because it is a
great deal more output. One wrinkle is worth knowing before you use it.
Section values are not recorded in meta-provenance, since /graph/sections
is one of the three hand-curated pointers. The run says so:
manni: Section metadata was written but is NOT recorded in meta-provenance: /graph/sections is one of the three hand-curated pointers graph refuses, with /graph/revision-of and /graph/derived-from, so recording it would make `manni graph check` report an error. Review section values by hand — the review queue will not list them.Sample questions
Section titled “Sample questions”sample-questions drafts the questions readers ask of a page, and pins each
one to the lines that answer it. It runs beside the graph fields:
$ manni graph fill --dry-run --fields sample-questions docs/guidesproposed docs/guides/retry.md (sample-questions) — dry run, not written re-pinned "How do I resume a paused upload?" lines 9 to 12 paired "How do I retry a failed upload?" lines 4 (0.77) drafted "How often does the client try again?" lines 8 (0.90)An answer whose lines moved is re-pinned with no model and no turn. A question
with no answer is paired with the passage the model quotes, and a page with
fewer than fill.questions.perPage questions gets drafts. A draft that is not
on the page, repeats a question asked elsewhere, or copies its answer’s words
is dropped.
Everything fill adds here is written reviewed: false, so graph check
reports it at notice until someone confirms it. Read the diff. Set
reviewed: true on a question or a pin you agree with, or delete the question.
The CLI reference lists every
gate and output line.
The budget is turns
Section titled “The budget is turns”$ manni graph fill --dry-run --max-turns 50 docs/A turn is one inference call. One page is one turn, and a cached page spends none, so the page count of an uncached batch is its size. The cap is checked before the first call, which is what makes it a budget rather than a regret.
A run that exhausts it stops asking and says so:
skipped docs/guides/upgrading.md (turn budget)
--max-turns reached (50); some pages were not processed.The tool reports no dollar figure, for one reason. Not every model is in a price table, and a cap that silently reads as zero for an unpriced model is worse than no cap. Converting turns to money is your provider’s rate card.
Where the inference happens
Section titled “Where the inference happens”fill is the only verb that reaches a model. It resolves a provider in this
order, later winning: the family’s providers.provider, then graph.provider,
then --provider, then --local.
$ manni graph fill --dry-run --local docs/guides--local runs llama-cpp on this machine, over everything else. It is the
answer when the pages must not leave the runner, and it is the whole answer.
There is no partial mode where some pages go out.
--offline, or fill.offline: true, goes one step further. Nothing leaves
the machine, not even a model download. A local model already on disk runs as
under --local. With none, fill re-pins moved answers, skips each page that
needed a model as offline, and exits 0 with one warning.
Unset everywhere, the provider is auto, detected exactly as manni meta fill
detects: an Anthropic key, then an OpenAI key, then the Claude CLI, then a
local model. That default is a fact about the machine rather than a decision.
Pin a provider in config for anything that runs more than once.
What a call carries is the page’s content and the schema for the fields in
scope. If your organization has a data-egress policy, that sentence and
--local are the two halves of the conversation.
The guardrail
Section titled “The guardrail”Before a proposal is written it is simulated in the derived graph and checked against the SHACL shapes. Anything that would violate them is dropped, and the line says which field:
filled docs/guides/install.md (label, concepts) [graph check rejected: broader]Accepted proposals fold into the guard’s state as the run goes, so two pages in
one run cannot jointly corrupt the graph. The second page’s broader is
checked against a graph that already holds the first page’s.
Rejection sits downstream of the cache, so a later corpus change can
re-admit a proposal without re-asking the model. --no-validate-graph turns
the guard off, and there is very little reason to.
Reviewing what a machine wrote
Section titled “Reviewing what a machine wrote”Every written value is recorded in the page’s meta-provenance, in the same
edit, naming the model and the fields as JSON Pointers:
---title: Installinggraph: label: Installation concepts: [onboarding]meta-provenance: - generated-by: claude-fable-5 fields: ["/graph/label", "/graph/concepts"]---The record is the point of the command. A surviving entry means unreviewed machine metadata, and deleting it is how you sign off. That makes review a thing you can measure and finish, rather than a thing you meant to do.
meta-provenance is the metadata tool’s record, shared rather than copied. A
page filled by manni graph fill and by manni docevals fill carries one entry
per model, and neither run disturbs the other’s. The graph block has no
provenance key of its own, so this record is the only one.
Set fill.writeProvenance: false and the record is not written. Do not.
Where the fields land
Section titled “Where the fields land”fill writes each page in its own format, where manni meta fill would, so
the page’s own tooling reads what was written:
| Page | Where graph and meta-provenance go |
|---|---|
| Markdown, MDX | YAML frontmatter, created when the page has none. |
| HTML | One <meta name="graph"> in <head>, the block as one line of YAML flow. |
| DITA | An <othermeta name="graph"> in the topic’s <prolog>. |
| AsciiDoc, reStructuredText | The page’s YAML fence, edited in place. |
A key an external-metadata manifest owns goes to the page’s entry there, whatever the page’s format.
Two kinds of page cannot take a write. An AsciiDoc or reStructuredText page
with no fence has nowhere safe to put one, because a bare --- changes how
both render. An HTML page with no <head> has no place for a <meta> tag.
Each is reported as an error line in manni meta fill’s words, its bytes are
left alone, and the run exits 1. Add a fence or a <head>, or keep the page’s
graph block in a manifest. The
formats reference has the detail.
A page’s own fields win
Section titled “A page’s own fields win”A graph field a human set is left alone. --force overwrites it, and it is
named that way because it is a deliberate act. The values a person wrote are
the ones the corpus is actually built on.
A realistic pass
Section titled “A realistic pass”$ manni graph fill --dry-run --fields label,concepts,type --max-turns 100 docs/guides$ manni graph fill --fields label,concepts,type --confidence 0.8 docs/guides$ git diff docs/guides$ manni graph build && manni graph checkOne directory at a time. Nobody can review a three-thousand-page pull request, and a review nobody does is the same as no record at all.