Module 34 built the wiki and its rules by hand. Now you hand the agents the pen, with the tool the Learn track taught in module 05: a skill, a reusable workflow the model loads when its moment arrives.
Two skills ship. One rule governs both, and it is the module’s whole idea in a sentence: a skill names the file to read; it never carries the fact. A fact copied into a skill body looks helpful and goes stale silently, because a skill body is text the model reads and nothing checks it. The schema has a lint. The skill has nothing. So the skill points, and the schema stays the one place the rules live.
flowchart LR fact["a fact arrives<br/>in chat"] --> cap["capture-fact:<br/>read the schema,<br/>find the owning page"] --> diff["diff shown,<br/>wait for yes"] --> wiki["the page,<br/>plus a log line"] lint["npm run kb<br/>findings"] --> audit["kb-freshness-audit"] --> q["_questions.md<br/>one owner per question"]
Four files ship, and all four are below in full. Read capture-fact/SKILL.md first, read it the way a model does, top to bottom, obeying as it goes. Then check it against the claim this page is about to make: there is no fact in it, and no digit either.
.claude/skills/capture-fact/SKILL.md The capture workflow. Ordered imperative steps, no rule of the schema restated.
---
name: capture-fact
description: Use when somebody states a fact about this application that is not written down yet, how a part of it behaves, what a setting does, what a run of it produced, and it should become or extend a page in the knowledge base. Intake, one-place check, diff to approve, log line.
---
# Capture a fact into the knowledge base
Do these in order. Do not skip ahead to writing; every step before the write
exists to stop a page being minted that should have been an edit.
## Read the contract first
Open `knowledge-base/_schema.md` and read it before anything else. It states
the layers, the frontmatter, the provenance values and what each of them
obliges the body to carry.
Open it by that repo-root path, typed out. Not by a wikilink: a wikilink is a
convention between pages and resolves to nothing when a tool goes looking for a
file. Not from memory of what the schema says, either, including any memory
formed while reading this file.
Nothing in this skill restates a rule from the schema, and that is the point.
A rule copied here would be a second copy with no one checking it against the
first, and it would keep reading as true for as long as it took somebody to
change the schema and not know this file existed. This file holds the order of
the steps. The schema holds the rules.
## Decide the layer
Ask whether the thing said is a wiki fact at all.
- A fact about how this application behaves, that a reader would want and
cannot get quickly from the source: a page.
- A choice that was made, and the reasons it was made over the alternatives: a
decision record, not a page. The wiki cites it.
- A number or a line that the source already states plainly: leave it in the
source. A page that restates a source is a copy that will drift away from it.
- Something nobody has checked and cannot check right now: a question, filed as
one, not a page wearing a confident frontmatter.
## Find the page that already owns it
Search the wiki before writing. If a page already owns the concept, extend that
page. Do not mint a second page whose title is a synonym of the first.
If two pages could plausibly own it, the one whose concept it actually is takes
the fact, and the other one links to it. If the fact contradicts what a page
already says, stop and follow the schema's procedure for that; the disagreement
is the finding, and resolving it quietly destroys the only thing worth knowing.
## Write it in the owning page's register
Match the page you are editing: its sentence length, its heading shapes, how
much it explains before it asserts. A paragraph in a different voice reads as
an insertion, and a reader who notices the seam stops trusting the page either
side of it.
State the fact first and the evidence under the heading the schema names for
it.
## Set the frontmatter honestly
Copy the block from `knowledge-base/_template.md` rather than typing it from
memory. Fill every required field.
Pick the provenance tier for what actually happened, and not for what would
look better. If a command was run and its output read, say so and quote it. If
a file was read, cite it. If nothing was checked against a source, the schema
has a value that says exactly that, and it is the correct one. Claiming a tier
whose evidence is not in the body is the failure the field was added to catch.
## Run the lint
Run `npm run kb`. Read every finding it prints against the page you touched,
and fix what is yours. The lint checks a subset of the schema; a clean run is
not a claim that the page obeys the rest of it.
## Show the diff and wait
Show the person the exact diff you intend to write and the lint's output, then
stop and wait for them to say yes. Do not write first and offer to revert.
The person who stated the fact is the only one who can tell you that you wrote
down something subtly different from what they meant, and the moment they can
still tell you cheaply is before it is a file. This step is a habit in the
procedure rather than a permission rule because a permission rule can only ask
whether a file may be written; it cannot show anybody what is about to go in
it, and an approval prompt on an unseen edit trains a person to say yes.
## Append the log line
After the write lands, append one line to `knowledge-base/_log.md` in the form
that file states at its top, including its Why. The Why is the half that is
worth something later: what changed is visible in the history, and why it
changed is not.
If the fact was one somebody had already filed as an open question, move that
question to answered in `knowledge-base/_questions.md` in the same round. .claude/skills/kb-freshness-audit/SKILL.md The audit workflow. It runs the lint, files questions, and edits no page.
---
name: kb-freshness-audit
description: Use when asked to audit the knowledge base, check whether the wiki has gone stale, or work out what needs re-verifying. Runs the lint, turns each finding into a question with an owner in the questions file, and changes no page.
---
# Audit the knowledge base and file what it turns up
This skill reads the wiki and writes to one file. It does not edit pages.
## Read the contract first
Open `knowledge-base/_schema.md` by that repo-root path and read it, then open
`tools/kb-lint.mjs`. The schema states the rules; the lint is the only thing
that checks them, and its header states what each of its findings means and
what its exit codes are.
Read both rather than trusting a summary of either, this file included. No
finding name, threshold or exit code is written down here, because a copy of
one here would go on reading as true after the lint changed.
## Run the lint
Run `npm run kb`. Note the exit code as well as the output, the two say
different things, and the lint's own header says which is which. A run that
prints nothing has told you something; read the header to know what.
## Turn each finding into a question
Every finding is somebody's question, and the useful move is naming who. For
each one, write a line in `knowledge-base/_questions.md` in the form that file
states at its top, under the section that matches how urgent it is.
The owner is the role that can actually answer it, which is usually the page's
own owner and not whoever ran the audit. Where a finding is answerable by
running something rather than by asking anybody, say so in the question, and
name the thing to run.
Do not open a second question for something already listed there. Extend or
re-date the line that exists.
## Change no page
Do not edit a page in the wiki from this skill. Not the dates, not the
frontmatter, not a sentence.
A finding is a claim that a page might now be wrong, and the only way to
retire it honestly is for somebody to check the fact against its source again.
An audit that edits the field it is auditing turns the whole scheme into
bookkeeping: every date current, every page passing, nothing re-checked. The
verification is the work, and it belongs to the owner, in a separate round,
through the capture skill.
## Report
Tell the person what the lint said, what you filed, and against whom. Then
stop. Filing the questions is the whole of this job. knowledge-base/_questions.md Machinery, not a page. Three sections, one owner and one date per line.
# Open questions
One line per question, in the form
`YYYY-MM-DD | owner | the question, ending in a question mark | where it came from`.
The date is the day the question was raised, not the day it was last thought
about. The owner is one role that can answer it, never a list and never a
person's name. An answered question keeps its original line, gains
` -> the answer, and where it landed`, and moves to the last section rather
than being deleted.
This file starts with `_`, so it is machinery and not a page: no frontmatter,
no provenance, and the lint applies no page rule to it. That is on purpose and
argued in `docs/decisions/0111`. A question is the state a fact is in before it
has a provenance, and the file that holds questions cannot be asked for one.
Three sections, and a question sits in exactly one:
- **blocking**, work is stopped until somebody answers.
- **open**, worth answering, nothing is waiting on it.
- **answered**, kept for the record, with its answer on the line.
## blocking
## open
2026-08-30 | maintainer | Can two or more SDK subprocesses run at the same time under one shared `CLAUDE_CONFIG_DIR` without corrupting each other's session state? | `docs/decisions/0030` left the contents of a handed config directory unverified, `docs/decisions/0031` pins one directory for every session, and the roster lets more than one agent hold a session at once. Answerable only with an API key: run two agents concurrently and read what lands in the directory.
## answered .claude-plugin/plugin.json The plugin manifest, now registering the skills surface.
{
"name": "chat-workbench",
"version": "0.35.0",
"description": "The chat app the Build the Chat Plugin track builds: a multi-agent chat workbench with a schema-governed knowledge base.",
"author": {
"name": "Learn Claude Code"
},
"skills": [
"./.claude/skills/"
]
} The capture skill, step by step
.claude/skills/capture-fact/SKILL.md has a one-line description written around its trigger, somebody states a fact about the app that is not written down yet, and a body of ordered imperative steps: read knowledge-base/_schema.md first, by repo-root path; decide whether this is a wiki fact at all; find the page that already owns it, because extending an owning page beats minting a duplicate every time; write in that page’s register; take the frontmatter from _template.md with honest provenance; run the lint; show the person the diff and the lint output and wait for a yes; append the _log.md line with its Why.
Two of those steps carry the module’s design arguments. The path-not-fact rule is enforced, not requested: the skill body contains no digit at all, and the repo’s own check greps for one. A digit is a deliberately blunt proxy, defended in record 0107, every fact tempting to copy into a skill, a port, a TTL, a ceiling, an exit code, is or contains a number, and a blunt check nobody can argue with beats a precise one with an exception list. The check caught a real draft during the build, which is what a check is for.
The diff-and-wait step lives in the skill, not in permissions, which sounds backwards until you name the failure it guards. A permission prompt asks “may this path be written”. The danger here is a faithful, well-formed write of a subtly wrong fact, which every permission system in this course would happily allow. Only a person reading the diff catches it, so the skill makes showing the diff a step of the workflow itself. Record 0108 states the trade plainly: a body step is discretionary where a permission rule is not, and the skill takes that trade with open eyes.
The audit, and the file that stops confident wrong answers
kb-freshness-audit runs the lint and turns each finding into a one-line question in knowledge-base/_questions.md, grouped by who can answer it. It edits no wiki page, and its own body says so with the reason: the one-field edit that would silence a STALE finding, bumping last-verified without checking anything, is exactly the edit that turns provenance into bookkeeping (record 0110).
_questions.md is machinery, not a page, riding the schema’s underscore exemption on purpose: a question is a fact without a provenance yet, so page frontmatter is unfillable for it (record 0111). Three sections, blocking, open, answered, one owner and one date per line, and answered lines keep their answers instead of vanishing. It ships seeded with one real open question from the app’s own build, whether two SDK subprocesses can share the one pinned config directory, owned by the maintainer, because a questions file that starts empty teaches that it is decoration. The blocking section is where a confident wrong answer dies: an agent asked about something a blocking question covers has, in its context, a file saying the answer is not yet known and who owes it.
Certified without a key, by the harness itself
This round surfaced a fact the whole course benefits from: the SDK ships the real Claude Code binary, and its validator runs offline. claude plugin validate --strict passes against the repo and against the skills tree, no API key involved, which is a structural check by the same program that will load these skills for real. The manifest from module 30 now registers the skills surface, so the plugin carries them; that also namespaces their invocation names, which is why the exact /skill-name spelling stays out of this page and in your own session. What no keyless machine can certify is listed in record 0113 rather than glossed: whether a live session actually opens the schema when told to, stops at the diff, and keeps the audit out of the pages. Those are the first things to watch when you run this track with a key.
git checkout module-35
npm run check # includes the wiki lint and the no-digits skill check
node_modules/@anthropic-ai/claude-agent-sdk-win32-x64/claude.exe plugin validate --strict .
The ladder:
- Run it. The three commands above, then read the two skill bodies. Neither contains a number; now you know why.
- Read one file.
capture-fact/SKILL.mdbeside record 0108, and find the step a permission system could not replace. - Change one line and see it. Add a port number to the capture skill’s body “as a helpful example” and run
npm run check: the skills check fails and names the rule. Take it back out. - Build. With a real key: state a fact about the app in chat, watch the skill fire, refuse the first diff, and confirm nothing was written and no log line appeared. The refusal path is the half of diff-and-wait that fake certification cannot reach.
You put the port number in the skill body, “so it does not have to look it up every time”. It works, and it keeps working: the skill answers fast, the number is right, everyone is pleased. Then the port changes in config.js, and the skill keeps saying the old number, every session, confidently, forever, because a skill body is text the model reads and nothing checks it. There is no error to see. The wiki’s page updated, the lint is silent, and the one copy of the fact that nothing lints is the one the model reads first.
The fix is the module’s rule: the skill carries the path to _schema.md and the path to the owning page, and pays one extra file read per invocation for the guarantee that what the model states was read from the linted place. That cost is real and small; the silent-stale cost is real and unbounded.
Skills travel further than anything else on this site, and the one-place rule travels with them: any harness with reusable prompt workflows has this exact staleness trap, and points-not-facts is the cure in all of them. The questions-file pattern travels to any team. What is this SDK’s own: the SKILL.md format, the plugin skills surface, and the offline validator in the bundled binary.
Check yourself
- Why does the capture skill’s body contain no digit, what does the check that enforces it deliberately over-fire on, and why was that cost accepted?
- A permission rule could require approval before any write to
knowledge-base/. Name the failure that rule cannot catch and the diff-and-wait step can. - The audit skill finds twelve STALE pages. What does it write, where, and what is the one edit it must never make?