Three modules of Python so far, and none of it reads a document or writes a wiki page. Not one line. That absence is the design, and this module is where it gets stated as a rule you can apply to code you have not written yet.
The rule comes from skills/llm-wiki/SKILL.md, in a section called Boundary, and it is two lists and a sentence:
wikictlmay hash, compare, enumerate, parse frontmatter, resolve paths, check links, calculate staleness, manage manifest metadata, stage plugin-owned artifacts, commit them, and emit JSON.
wikictlmust not summarize sources, merge knowledge, decide claims, rewrite wiki pages, resolve contradictions, choose page taxonomy, or decide semantic archival.Claude interprets sources and changes
wiki/.
Read the two lists side by side and the line between them is sharp. Everything in the first list has one right answer that does not depend on who is asking. Everything in the second list is a judgment where two careful people could disagree.
The test that puts an operation on a side
Here is the test, and it fits in one question. When you are unsure which side something belongs on, ask whether running it twice on the same input must give the same answer.
Hashing a file: yes, always, that is what a hash is. Checking whether a wikilink resolves: yes, the target file either exists or it does not. Working out whether a page cites a superseded source: yes, it is a lookup.
Deciding whether two paragraphs contradict each other: no. Choosing whether a new fact belongs on an existing page or deserves its own: no. Deciding a page has stopped being useful and should be archived: no.
| Operation | Same answer every time? | Whose job |
|---|---|---|
| Hash a raw file | Yes | wikictl |
| Find pages nothing links to | Yes | wikictl |
| List pages citing a superseded source | Yes | wikictl |
| Stage and commit the plugin’s own files | Yes | wikictl |
| Decide what a source means | No | Claude |
| Decide which page a fact belongs on | No | Claude |
| Reconcile two pages that disagree | No | Claude |
| Decide a page should be archived | No | Claude |
Now, both halves of this cost something when you get them wrong, and the costs are not symmetric. This is the part worth slowing down on.
Put a judgment in the script and you get a rule that is wrong in cases nobody thought about, applied silently, forever. A similarity threshold that decides two sources are the same document is the standard example: it will be right most of the time and its mistakes will be invisible.
Put bookkeeping in the model and you get an answer that varies between runs. Ask Claude to compare two files and report whether they changed and you will usually get the right answer, which is worse than usually getting the wrong one, because you will stop checking.
A command file is a procedure, not a request
Next up is where that rule gets written down so a model reads it every time.
The plugin ships three commands, and they are Markdown files whose bodies are numbered steps. A command file is a prompt Claude Code inserts when you type /name, so what you write in it becomes instructions in the session.
commands/wiki-ingest.md Twelve steps for one source. Every deterministic step names a wikictl command; every judgment step describes a decision without making it.
---
description: Ingest exactly one append-only raw source into compiled wiki knowledge.
argument-hint: "<raw/path> [--supersedes src_id]"
disable-model-invocation: true
---
Ingest exactly one raw source. Do not batch sources.
1. Resolve the wiki root by running `wikictl inventory`. If it exits 2, stop.
If `changed` or `deleted` is non-empty, stop and report the append-only violation first.
2. Run `wikictl lint --review-limit 0` and keep its JSON as the pre-ingest baseline.
3. Verify the requested source exists under `raw/`. Never modify an existing raw file.
4. Register only this source with `wikictl inventory --register <raw/path>`.
5. If and only if the arguments contain `--supersedes`, run `wikictl supersede OLD NEW`.
Never infer supersession merely because two sources look related.
6. Read the raw source. Then read `wiki/index.md` and use Glob/Grep to find the
smallest relevant set of existing pages. Do not read the whole wiki by default.
7. Compile the source into durable knowledge:
- update existing pages where the concept already belongs;
- create a new page only for a durable concept that has no home;
- reconcile contradictions rather than appending duplicate facts;
- put page-level provenance in frontmatter with the registered ID and exact hash.
8. Update `wiki/index.md` only if navigation changed.
9. Append one concise ingest entry to `wiki/log.md`.
10. Run `wikictl stale` and `wikictl lint --review-limit 0`. Fix what this ingest caused.
Do not expand scope to unrelated pre-existing cleanup.
11. Run `wikictl commit --message "llm-wiki: ingest <source-filename>"`.
12. Report the source ID, pages created/updated, supersession if any, and
unresolved pre-existing findings separately.
The script never writes a wiki page. All interpretation and wiki edits are yours. commands/wiki-init.md Seven steps. Step 4 is the one that protects a file the plugin did not create.
---
description: Initialize an LLM Wiki in the current project or an explicit root.
argument-hint: "[root]"
disable-model-invocation: true
---
1. Choose the root: an explicit path if given, otherwise `${CLAUDE_PROJECT_DIR}`.
2. Run `wikictl init --root <root>`.
3. Read `<root>/.llm-wiki/CLAUDE.md` and customize only the instance-specific parts:
domain, page types, naming rules. Preserve the invariant and provenance sections.
4. Do not create or overwrite a project-root `CLAUDE.md`. Instance instructions
intentionally live at `.llm-wiki/CLAUDE.md` and are injected by the SessionStart hook.
5. Run `inventory` and `lint --review-limit 0`. Exit code 1 means findings; inspect
the JSON rather than treating it as a tool failure.
6. Run `commit --message "llm-wiki: initialize"`.
7. Report the root, whether Git was initialized, and any lint findings.
Do not ingest any source during initialization. skills/llm-wiki/SKILL.md The protocol. user-invocable: false, so it loads as background knowledge rather than appearing as a command.
---
name: llm-wiki
description: Operate an LLM Wiki: append-only raw sources, page-level provenance, compiled Markdown knowledge, deterministic lint/staleness checks, and bounded maintenance. Use when working with raw/, wiki/, .llm-wiki/, wiki ingestion, provenance, stale knowledge, or dream maintenance.
user-invocable: false
---
# LLM Wiki operating protocol
Claude provides judgment and native retrieval. `wikictl.py` provides deterministic
bookkeeping and verification.
## Boundary
`wikictl` may hash, compare, enumerate, parse frontmatter, resolve paths, check links,
calculate staleness, manage manifest metadata, stage plugin-owned artifacts, commit them,
and emit JSON.
`wikictl` must not summarize sources, merge knowledge, decide claims, rewrite wiki pages,
resolve contradictions, choose page taxonomy, or decide semantic archival.
Claude interprets sources and changes `wiki/`. Look at the shape of wiki-ingest. Every step is either “run this command and read its JSON” or “make this decision, and here is the criterion.” No step asks the model to do arithmetic on hashes, and no step tells the model which page a fact belongs on. That is the two lists from SKILL.md, turned into a procedure.
Step 10 carries a rule that is worth naming separately: fix what this ingest caused, and do not expand scope to unrelated cleanup. Without it, one ingest turns into an afternoon of tidying, because a model handed a lint report will try to make it empty. The baseline captured in step 2 exists so the difference between “findings I caused” and “findings that were already here” is a comparison rather than a memory.
Where the skill goes and why it says nothing about workflow
skills/llm-wiki/SKILL.md holds the protocol: the boundary, the root resolution order, the raw-source policy, the frontmatter contract, the exit codes. It carries user-invocable: false, so it never appears as a command. It loads when the model is doing wiki work and needs to know the rules.
The split between the skill and the commands is worth being deliberate about, and it comes down to one question: does this text describe what is always true, or what to do now? Always-true goes in the skill, so it is available whenever the subject comes up. What-to-do-now goes in a command, so a person triggers it at a moment of their choosing.
Getting this backwards produces two recognisable failures. Rules in a command file are unavailable to any session where nobody typed the command, so the model works without them. A procedure in the skill is loaded into every session that mentions the wiki, which spends context on steps nobody asked to run and encourages the model to start running them uninvited.
That is the boundary, in three artifacts. Two lists that say who decides what, a skill that always knows them, and three procedures that apply them on request.
Every command file carries disable-model-invocation: true. All three write files and two of them make Git commits, so the cost of a wrong guess is a commit you did not ask for. The frontmatter field makes the trigger a person typing rather than a model deciding, which is the correct default for anything with a side effect on disk.
Write skills/llm-wiki/SKILL.md with a name, a description naming the concrete paths and words that should trigger it, and user-invocable: false. Put the boundary lists, the root order, the raw policy, and the exit codes in the body, and nothing procedural. Then write commands/wiki-ingest.md as numbered steps, with disable-model-invocation: true in its frontmatter. Apply one test to every step you write: if the step could be done wrong in two defensible ways, it belongs to the model and you write the criterion instead of the answer. Confirm the split by starting a session, mentioning your wiki without typing any command, and asking Claude what the raw-source rule is. If it knows, the skill is loading. If it also offers to run an ingest, your procedure leaked into the skill.
Adding one small judgment to the script because it is nearly deterministic. The usual candidate is auto-supersession: if two sources sit in the same directory and their text is 90 percent similar, mark the older one superseded. It works on the pair you tested it on. Then somebody adds an appendix to a spec as a separate file, the similarity check fires, and a source is marked superseded that nothing replaced. Now stale names pages that are fine, you reconcile them against a source that was never a revision, and the wiki gets quietly worse while every check stays green. There is no error and no log line, because from the tool’s point of view nothing went wrong. That is the case the boundary exists for: the operation was 90 percent deterministic, and the missing 10 percent was the whole decision.
The boundary is the most portable idea in this track and the least tied to any harness. Every agent system faces the same split between what a program should compute and what a model should judge, and the failures look the same everywhere: thresholds standing in for decisions, or a model asked to be a checksum. The file formats do not travel. A SKILL.md with that frontmatter is Claude Code’s, and disable-model-invocation is a Claude Code field. Move to another harness and you rewrite both files while keeping both lists exactly as they are.
Check yourself
- You want the tool to detect that two wiki pages cover the same subject and should be merged. Which side of the boundary is that on, and what is the specific failure if you put it on the other side?
wiki-ingestcaptures a lint baseline at step 2 and compares against it at step 10. What goes wrong in a real session if you drop step 2?- The raw-source policy lives in
SKILL.mdand the ingest procedure lives incommands/wiki-ingest.md. Give the question that decides which file a new piece of text belongs in, and name what breaks in each direction if you get it backwards.