feat: docs/ carries the operator's how-to guides, a configuration reference and the architecture and telemetry explanations carved out of README.md, INSTALL.md, INSTALL-MCP.md and EVALS.md; the last two retired (#190)
Files changed: - .gitea/workflows/ci.yml - .gitignore - .vibe/hooks.toml - AGENTS.md - CHANGES.md - EVALS.md - INSTALL-MCP.md - INSTALL.md - README.md - VERSION - docs/dev/development.md - docs/explanation/README.md - docs/explanation/architecture.md - docs/explanation/language-boundaries.md - docs/explanation/telemetry-and-evaluation.md - docs/how-to/README.md - docs/how-to/browse-with-obsidian.md - docs/how-to/capture-repository-docs.md - docs/how-to/export-guidelines.md - docs/how-to/ingest-a-source.md - docs/how-to/maintain-the-wiki.md - docs/how-to/query-the-wiki.md - docs/how-to/run-the-weekly-review.md - docs/how-to/set-up-the-mcp-server.md - docs/how-to/trace-and-score-sessions.md - docs/how-to/troubleshoot.md - docs/how-to/update-an-instance.md - docs/reference/README.md - docs/reference/commands/distribution-and-versioning.md - docs/reference/commands/instance-health.md - docs/reference/commands/raw-material-and-uploads.md - docs/reference/commands/telemetry.md - docs/reference/commands/types-instructions-and-docs.md - docs/reference/configuration.md - instructions/dev/dev-setup.md - instructions/dev/doc-pull-through.md - instructions/dev/stack-build/SKILL.md - instructions/dev/testing-conventions.md - instructions/dev/version-parts.md - instructions/setup-instance.md - instructions/upgrade-instance.md - reports/CONTRACT.md - tools/.coveragerc - tools/CONTRACT.md - tools/README.md - tools/chemenu/commands/dist_cmd.py - tools/chemenu/commands/docs_verify.py - tools/chemenu/commands/doctor.py - tools/chemenu/commands/eval_cmd.py - tools/chemenu/commands/git_publish.py - tools/chemenu/commands/upload_cmd.py - tools/chemenu/commands/version_cmd.py - tools/chemenu/evals/__init__.py - tools/chemenu/evals/scorecard.py - tools/chemenu/evals/trajectory.py - tools/chemenu/telemetry/policy.py - tools/chemenu/tests/test_dist_cmd.py - tools/chemenu/tests/test_git_publish.py - tools/chemenu/tests/test_instructions_shell.py - tools/chemenu/tests/test_pipeline_l0.py - tools/chemenu/toc.py Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnAJ7Z3CpVD3PRbN73QtU2
This commit is contained in:
1 parent
cff7df4ffa
commit
f9056c04f7
61 files changed
+2169
-1772
No files matched your search
+21
-3
@@ -6,6 +6,24 @@ handed to the agent. A how-to guide never retells an agent's procedure: the step
|
||||
live in `instructions/`, and a guide here says what to prepare, what to ask for and where the
|
||||
agent stops.
|
||||
|
||||
Nothing is here yet. Until the guides are written, the task-shaped parts of
|
||||
[README.md](../../README.md), [INSTALL.md](../../INSTALL.md),
|
||||
[INSTALL-MCP.md](../../INSTALL-MCP.md) and [EVALS.md](../../EVALS.md) still carry this material.
|
||||
Working with the wiki:
|
||||
|
||||
- [Ingest a source](ingest-a-source.md) - a file, a folder, a web page, or a document submitted
|
||||
through the MCP server
|
||||
- [Capture documentation from a git repository](capture-repository-docs.md) - and keep it current
|
||||
- [Export guidelines to captured repositories](export-guidelines.md)
|
||||
- [Query the wiki](query-the-wiki.md) - asking, and searching yourself
|
||||
- [Maintain the wiki](maintain-the-wiki.md) - lint, provenance, and the gates on the way to a publish
|
||||
- [Run the weekly review](run-the-weekly-review.md) - commitments in a task tracker against the
|
||||
project pages
|
||||
- [Browse the wiki in Obsidian](browse-with-obsidian.md) - and how pages are named
|
||||
|
||||
Running the instance:
|
||||
|
||||
- [Update an instance](update-an-instance.md) - to a newer release of the stack
|
||||
- [Troubleshoot an instance](troubleshoot.md)
|
||||
- [Set up the MCP read server](set-up-the-mcp-server.md) - for a consumer that is not a terminal
|
||||
on this machine
|
||||
- [Trace and score agent sessions](trace-and-score-sessions.md)
|
||||
|
||||
Installing a new instance is [INSTALL.md](../../INSTALL.md) at the repository root.
|
||||
@@ -0,0 +1,54 @@
|
||||
# Browse the wiki in Obsidian
|
||||
|
||||
The wiki is a folder of Markdown files joined by `[[wikilinks]]`, so any Markdown editor can read
|
||||
it. Obsidian adds a graph of the links between pages, which makes it a good way to look around.
|
||||
|
||||
## What you prepare
|
||||
|
||||
Open the instance's root directory as an Obsidian vault. Obsidian keeps its own settings in
|
||||
`.obsidian/`. That folder is personal state: it is never shipped with a release, and it has no
|
||||
effect on the wiki.
|
||||
|
||||
What you get:
|
||||
|
||||
- the graph view of how pages connect;
|
||||
- navigation by wikilink, the way the agent writes them;
|
||||
- plugins such as Dataview, which can query page frontmatter, or Marp for slides.
|
||||
|
||||
## Reading, not writing
|
||||
|
||||
Browse and read freely. Change pages through the agent, though, not by editing them in Obsidian. A
|
||||
page carries generated frontmatter, cross-references and footnote citations that `tools/wikitool`
|
||||
keeps consistent across the whole wiki. A hand edit can quietly break them. Renaming or moving a
|
||||
file in Obsidian is the clearest case: a title is the page's only identifier, so it also appears in
|
||||
every wikilink and citation that points at the page. A rename goes through
|
||||
`tools/wikitool rename`, which updates every reference. The full procedure is
|
||||
[instructions/page-lifecycle.md](../../instructions/page-lifecycle.md).
|
||||
|
||||
Generated files - `kb/index.md`, `kb/log.md`, `kb/provenance.md` and every `INDEX.md` - are
|
||||
rebuilt by the tool and never edited by hand.
|
||||
|
||||
## How pages are named
|
||||
|
||||
Knowing the naming rules makes the vault easier to find your way in:
|
||||
|
||||
- **Titles are human-readable names with spaces:** `Hybrid Search.md`, not kebab-case. A wikilink
|
||||
matches the file name exactly: `[[Hybrid Search]]`.
|
||||
- **Entities are singular:** `HA Integration.md`, not `HA Integrations.md`.
|
||||
- **A title follows the subject's own established name, not the wiki's language.** `Act Runner`
|
||||
and `GitOps Ownership Model` keep their names in a German wiki too. Translating a title is a
|
||||
rename, not an edit.
|
||||
- **A title works on every operating system.** It contains none of `< > : " / \ | ? *`, is not a
|
||||
reserved name such as `CON` or `Index`, does not end in a dot, and does not differ from another
|
||||
page's title by case alone. A file's whole path below the instance root stays at 160 characters
|
||||
or fewer, so a Windows checkout works without long paths. `wikitool new` and `wikitool rename`
|
||||
refuse titles that break these rules, and `wikitool lint` reports existing ones.
|
||||
[kb/CONTRACT.md](../../kb/CONTRACT.md) has the full rule.
|
||||
|
||||
## Letting the structure grow
|
||||
|
||||
- Start with a few broad categories and refine as the material arrives. Don't over-organise early.
|
||||
- Use tags for concerns that cut across collections.
|
||||
- Review what the agent changed from time to time - `kb/log.md` lists every operation - and tell it
|
||||
when something is off. It keeps to the standards in `AGENTS.md` and the contracts, but you are the
|
||||
one who notices when a page misses the point.
|
||||
@@ -0,0 +1,59 @@
|
||||
# Capture documentation from a git repository
|
||||
|
||||
Documentation that lives in a git repository is captured, not copied by hand. A capture records the
|
||||
repository, the ref rule and the commit it came from. Later, the wiki can tell which captured
|
||||
material has fallen behind its repository, and take in the new edition as a whole.
|
||||
|
||||
## What you prepare
|
||||
|
||||
Access to the repository from this machine. Git uses your own keys and credential helpers, and
|
||||
nothing is stored in the instance. Then decide three things:
|
||||
|
||||
- the repository URL;
|
||||
- the ref to follow, usually a branch such as `main`;
|
||||
- which files count, as a glob, for example `docs/**/*.md`.
|
||||
|
||||
## Taking a repository in for the first time
|
||||
|
||||
You can run the capture yourself, or name the three values to the agent and let it run the
|
||||
command:
|
||||
|
||||
```bash
|
||||
tools/wikitool raw capture <repo-url> --ref main --path 'docs/**/*.md' --name <bundle>
|
||||
```
|
||||
|
||||
This writes the matching files, byte for byte, into `incoming/<bundle>/`, together with a
|
||||
`_capture.json` manifest naming the repository, the ref rule and the commit. From there it is an
|
||||
ordinary source: `Ingest incoming/<bundle>` ([Ingest a source](ingest-a-source.md)), with the same
|
||||
questions and the same gate.
|
||||
|
||||
## Keeping it current
|
||||
|
||||
```bash
|
||||
tools/wikitool raw status
|
||||
```
|
||||
|
||||
This lists every captured bundle that has fallen behind its repository, file by file, marking each
|
||||
file as added, modified or deleted. It only reads.
|
||||
|
||||
To bring them in, say:
|
||||
|
||||
```text
|
||||
Update the captured repositories
|
||||
```
|
||||
|
||||
The agent checks every captured bundle. It takes one changed bundle per run into the wiki, replacing
|
||||
the old edition as a whole, and tells you how many are still behind. Run it again for the next one.
|
||||
`raw capture --update` and `raw accept --replaces-bundle` are the commands underneath. What each one
|
||||
does is in the [command reference](../reference/commands/raw-material-and-uploads.md).
|
||||
|
||||
## Where the agent stops
|
||||
|
||||
The same places as any ingest: the `fidelity`/`authority` questions, the discussion of what
|
||||
matters, and the Mass-Update Gate when it publishes. When one repository cannot be reached, the
|
||||
agent reports the error and carries on with the others. A failure in one never stops the run.
|
||||
|
||||
## Related
|
||||
|
||||
- [Export guidelines](export-guidelines.md) - sending the wiki's guidelines back into the same
|
||||
repositories.
|
||||
@@ -0,0 +1,54 @@
|
||||
# Export guidelines to captured repositories
|
||||
|
||||
The repositories the wiki captures documentation from can get something back: the wiki's
|
||||
guidelines, as one generated `GUIDELINES.md` in each repository's root, which that repository's own
|
||||
agents then read.
|
||||
|
||||
## What you prepare
|
||||
|
||||
- **Decide which pages count as guidelines.** That decision is yours, and it is written down in
|
||||
`kb/CONVENTIONS.md` § Guidelines for other repositories. Usually it is a tag. To see the file
|
||||
exactly as it would be written, without touching any repository:
|
||||
|
||||
```bash
|
||||
tools/wikitool export guidelines --tag guideline
|
||||
```
|
||||
|
||||
- **Opt each repository in, by hand, once.** A repository takes part only if it carries a
|
||||
`GUIDELINES.md` whose first line is exactly:
|
||||
|
||||
```markdown
|
||||
<!-- wikitool:export kind=guidelines -->
|
||||
```
|
||||
|
||||
Commit that one line there. Then point the repository's own `AGENTS.md` at the file (for Claude
|
||||
Code: `@GUIDELINES.md`). A `GUIDELINES.md` without that first line is hand-written, and the export
|
||||
never touches it.
|
||||
|
||||
The repository has to be one the wiki has captured
|
||||
([Capture documentation from a git repository](capture-repository-docs.md)). That is how the export
|
||||
knows where the repository lives. Its capture also has to follow a branch: a repository captured by
|
||||
a tag pattern is skipped, because a tag has no branch to commit onto.
|
||||
|
||||
## What you say
|
||||
|
||||
```text
|
||||
Roll out the guidelines to the captured repositories
|
||||
```
|
||||
|
||||
## Where the agent stops
|
||||
|
||||
The agent runs `export guidelines --push`, which stops before pushing anything. This is the
|
||||
**Guideline Push Gate** (exit 42). For every repository, it prints the status, the diff it would
|
||||
write and a `--confirm <token>` line. The agent puts every diff in front of you. Only your approval
|
||||
pushes, and then only one commit per repository, which changes only `GUIDELINES.md` and goes
|
||||
straight onto its branch.
|
||||
|
||||
The token is a digest of each target's branch tip and of the generated file. If a branch has moved
|
||||
or a guideline was edited since you looked, the gate asks again rather than pushing something you
|
||||
did not see.
|
||||
|
||||
## Related
|
||||
|
||||
- [Guideline export commands](../reference/commands/guideline-export.md)
|
||||
- [Why the gates are code](../explanation/why-gates-are-code.md)
|
||||
@@ -0,0 +1,82 @@
|
||||
# Ingest a source
|
||||
|
||||
Turn a file, a folder or a web page into wiki pages: a source page that summarises it, and the
|
||||
entity and concept pages it touches, cross-referenced and published. The agent does the work, using
|
||||
the `wiki-ingest` skill ([skill catalogue](../reference/skills.md)). This page covers what you hand
|
||||
it and where it waits for you.
|
||||
|
||||
## What you prepare
|
||||
|
||||
Put the material into `incoming/` at the root of the instance. You don't have to classify it or
|
||||
pick a directory:
|
||||
|
||||
- **One file** goes in directly: `incoming/my-article.md`.
|
||||
- **Several files that belong together** go into one folder, `incoming/<name>/`. The folder is one
|
||||
source. It is accepted whole, and its structure is kept.
|
||||
- **A web page** needs no download of your own. Name the URL to the agent, and
|
||||
`tools/wikitool raw fetch` puts two files into `incoming/`: the HTML exactly as received, and a
|
||||
text derived from it with a header recording where and when it was fetched.
|
||||
- **A page behind a paywall or a login** is saved from your browser into `incoming/`, HTML only. The
|
||||
agent derives the same text from that file with `raw fetch --html`.
|
||||
|
||||
Where a file ends up in `raw/` is computed by `tools/wikitool raw accept`: a `YYYY/MM` shard for the
|
||||
day it was accepted. So is whether several files of one source get bundled. You never choose either.
|
||||
`incoming/` itself is gitignored, so whatever waits there is never published by accident.
|
||||
|
||||
## What you say
|
||||
|
||||
```text
|
||||
Ingest incoming/my-article.md
|
||||
Ingest https://example.org/post
|
||||
Ingest
|
||||
```
|
||||
|
||||
A bare `Ingest` takes the oldest entry waiting in `incoming/`. `tools/wikitool raw pending` lists
|
||||
what is waiting and names the entry the next bare `Ingest` will take.
|
||||
|
||||
## Where the agent asks you
|
||||
|
||||
- **Before promoting the file into `raw/`**, the agent asks how faithful the capture is
|
||||
(`fidelity`) and what the material may claim about its subject (`authority`). Both are recorded
|
||||
once and never guessed: you know the answers now, and nobody can recover them later.
|
||||
[raw/CONTRACT.md](../../raw/CONTRACT.md) defines the values.
|
||||
- **Before writing pages**, the agent presents the key takeaways and asks which points matter most
|
||||
and what to emphasise. The pages follow your answer.
|
||||
- **When it publishes**, a source that touches ten or more files stops at the Mass-Update Gate
|
||||
(exit 42). The agent shows you the file list and the `--confirm` line. Nothing is committed until
|
||||
you approve. [Exit codes and gates](../reference/exit-codes.md) lists every gate.
|
||||
|
||||
## What you get back
|
||||
|
||||
A source page under `kb/sources/`, new or updated entity and concept pages, cross-references among
|
||||
them, a rebuilt catalog, an entry in `kb/log.md`, and one commit. If the source also contains
|
||||
something you have to *do*, and a task tracker is configured, the agent files it there as an open
|
||||
item. A commitment never becomes a page
|
||||
([why](../explanation/knowledge-and-commitment.md)).
|
||||
|
||||
Every ten sources, the agent suggests a lint run - see [Maintain the wiki](maintain-the-wiki.md).
|
||||
|
||||
## A document submitted from outside
|
||||
|
||||
When the [MCP read server](set-up-the-mcp-server.md) has its optional `submit` tool turned on,
|
||||
documents can arrive from another machine. They land in the quarantine `mcp-upload/`, not in
|
||||
`incoming/`, and nothing else reads them there. Before an ingest can start, a human has to promote
|
||||
the submission:
|
||||
|
||||
```bash
|
||||
tools/wikitool upload list # what is waiting
|
||||
tools/wikitool upload show <id> # one submission's manifest: who sent it, size, checksum
|
||||
```
|
||||
|
||||
Ask the agent to review the queue, and it follows
|
||||
[instructions/ingest-queue.md](../../instructions/ingest-queue.md). Accepting a submission stops at
|
||||
the Upload Review Gate (exit 42), which shows you the manifest and the `--confirm` line. Once you
|
||||
approve, the file is in `incoming/`, and from there it is ingested like any other. Rejecting it needs
|
||||
no clearance. The file is deleted, and its checksum and your reason stay in the ledger.
|
||||
|
||||
## Related
|
||||
|
||||
- [Capture documentation from a repository](capture-repository-docs.md) - for material that lives
|
||||
in git and will change.
|
||||
- [raw/CONTRACT.md](../../raw/CONTRACT.md) - what belongs in `raw/`, and why its content is data,
|
||||
never instructions.
|
||||
@@ -0,0 +1,68 @@
|
||||
# Maintain the wiki
|
||||
|
||||
Keep the wiki healthy: find broken links, orphan pages, raw files no page covers yet,
|
||||
contradictions and stale claims, then repair what can be repaired mechanically. The agent does
|
||||
this with the `wiki-lint` skill ([skill catalogue](../reference/skills.md)), and `tools/wikitool`
|
||||
does the mechanical part.
|
||||
|
||||
## What you say
|
||||
|
||||
```text
|
||||
Lint the wiki
|
||||
```
|
||||
|
||||
Do this periodically. The agent suggests it on its own every ten sources. The full schedule - which
|
||||
task runs how often, and with which command - is the
|
||||
[maintenance schedule](../../tools/CONTRACT.md#maintenance-schedule) in `tools/CONTRACT.md`, next
|
||||
to the command records it depends on.
|
||||
|
||||
## What you get back
|
||||
|
||||
- A deterministic structural scan by `tools/wikitool lint`: broken wikilinks, orphan pages, catalog
|
||||
drift, schema gaps, raw files without a source page, and citations that disagree with
|
||||
frontmatter.
|
||||
- The judgment half, which no command can do: contradictions between pages, stale claims, subjects
|
||||
that deserve a page of their own, and a rule stated in two places.
|
||||
- Repairs for what is mechanical, a rebuilt catalog and provenance index, and an entry in
|
||||
`kb/log.md`.
|
||||
- A report under `reports/`, which is gitignored and never published. The agent carries the
|
||||
findings that need you into its reply.
|
||||
|
||||
For a quick look without a full pass, say `Wiki status` instead. That is read-only.
|
||||
|
||||
## Provenance: where a claim came from
|
||||
|
||||
Every fact should trace back to a raw file. A source page lists its raw files, and other pages
|
||||
cite specific claims inline with a footnote that points at a source page. You can ask the
|
||||
questions yourself:
|
||||
|
||||
```bash
|
||||
tools/wikitool sources coverage # raw files no source page covers yet
|
||||
tools/wikitool sources trace --raw raw/2026/09/x.md # which pages rest on this file
|
||||
tools/wikitool sources trace --page "HA Integration" # which raw files this page rests on
|
||||
```
|
||||
|
||||
`kb/provenance.md` is the generated reverse index. The commands are in the
|
||||
[provenance reference](../reference/commands/provenance.md).
|
||||
|
||||
## Where the agent stops
|
||||
|
||||
- **Publishing ten or more files** stops at the Mass-Update Gate (exit 42). A lint pass that repairs
|
||||
many pages does this routinely. The agent shows you the full file list and the `--confirm` line,
|
||||
and publishes only after you approve. The token is a digest of that file list, so your approval
|
||||
never covers a changeset you did not see.
|
||||
- **The call budget.** Every `wikitool` call counts against a per-session budget: 60 calls, or 3
|
||||
identical calls in a row. Past it, calls are refused. The agent stops and summarises where it is.
|
||||
If you want it to go on, tell it so. Only then may it pass `--override-budget` on the command it
|
||||
needs. A refusal usually means the task should be split, not pushed through.
|
||||
- **No reachable remote.** Without `--no-push`, `publish` refuses with exit 1 before committing when
|
||||
the remote is not configured or cannot be reached. An instance that is meant to stay local passes
|
||||
`--no-push` on every publish.
|
||||
|
||||
Every gate, and which command it belongs to: [exit codes and gates](../reference/exit-codes.md).
|
||||
Why they are enforced in code: [why-gates-are-code.md](../explanation/why-gates-are-code.md).
|
||||
|
||||
## Related
|
||||
|
||||
- [Troubleshoot](troubleshoot.md) - when `doctor` or a command reports a problem with the instance
|
||||
itself rather than with its pages.
|
||||
@@ -0,0 +1,50 @@
|
||||
# Query the wiki
|
||||
|
||||
Ask the wiki a question and get an answer built from its compiled pages, with the pages and sources
|
||||
it came from. The agent answers using the `wiki-query` skill
|
||||
([skill catalogue](../reference/skills.md)), which only reads unless you ask it to keep an answer.
|
||||
|
||||
## What you say
|
||||
|
||||
Ask the way you would ask a colleague:
|
||||
|
||||
```text
|
||||
What projects use MQTT?
|
||||
Show me the architecture of HA Integration
|
||||
Compare gdeploy and plugnburn-edl
|
||||
What decisions were made about E3DC integration?
|
||||
```
|
||||
|
||||
## What you get back
|
||||
|
||||
An answer that names the wiki pages it came from and the sources behind them. Where the wiki has no
|
||||
source for a claim, the agent says so instead of filling the gap. An answer it cannot back is never
|
||||
written into the wiki either.
|
||||
|
||||
If an answer is worth keeping - a comparison you will want again, a synthesis across several pages -
|
||||
the agent proposes filing it back as a page. It names the pages it would create before it creates
|
||||
any, and nothing is written unless you agree.
|
||||
|
||||
## Searching yourself
|
||||
|
||||
The agent searches with the same command you can run:
|
||||
|
||||
```bash
|
||||
tools/wikitool search "<text>"
|
||||
tools/wikitool search --field entity_type=system
|
||||
tools/wikitool search --field '!sources' # pages without a sources: list
|
||||
```
|
||||
|
||||
`search` covers all of `kb/`, by text, by frontmatter or both. Each hit carries the page's path and
|
||||
full title, so you can open it directly. It only reads, and it does not count against the
|
||||
session's call budget, because looking before acting is the habit worth encouraging. The predicate forms are in the
|
||||
[command reference](../reference/commands/finding-and-checking.md).
|
||||
|
||||
For browsing rather than searching, `kb/index.md` is the catalog map, and each collection carries
|
||||
its own `INDEX.md`. All of these are generated. Read them, but never edit them. To browse the pages as
|
||||
a graph, see [Browse the wiki in Obsidian](browse-with-obsidian.md).
|
||||
|
||||
## Asking from another machine
|
||||
|
||||
A consumer that is not a terminal on this machine can ask the same questions through the MCP read
|
||||
server and get the same answers: [Set up the MCP read server](set-up-the-mcp-server.md).
|
||||
@@ -0,0 +1,64 @@
|
||||
# Run the weekly review
|
||||
|
||||
Go through your committed initiatives once a week and decide what each one needs: a next action, a
|
||||
pause, a close. A machine prepares the list, so you don't have to reconstruct it from memory. The
|
||||
agent runs the `gtd-weekly-review` skill ([skill catalogue](../reference/skills.md)).
|
||||
|
||||
## Two places, on purpose
|
||||
|
||||
Knowledge and commitments change at different speeds, so they live in different places:
|
||||
|
||||
- **A page under `kb/gtd/`** is one initiative's durable memory: its goal, who is involved, where it
|
||||
stands, and why it is worth doing. It never summarises the task list.
|
||||
- **The open items** live in a task tracker, which owns them.
|
||||
|
||||
Nothing syncs between the two. `tools/wikitool review` joins them when it reads, matching on the
|
||||
project name, and stores nothing - not even a report file. Why the split runs this way rather than
|
||||
syncing: [knowledge-and-commitment.md](../explanation/knowledge-and-commitment.md).
|
||||
|
||||
## What you prepare
|
||||
|
||||
A task tracker, configured for this checkout in `.wikitool-tasks.json`. Super Productivity and
|
||||
CalDAV (for example Nextcloud Tasks) are supported. The file's shape, and the difference between a
|
||||
tracker the wiki can only read and one it can write to, is in the
|
||||
[configuration reference](../reference/configuration.md#task-tracker). Without a tracker, the
|
||||
review has nothing to join against. That is a valid state, though, and the pages work without one.
|
||||
|
||||
## What you say
|
||||
|
||||
```text
|
||||
Run the weekly review
|
||||
```
|
||||
|
||||
## What you get back
|
||||
|
||||
The agent runs `tools/wikitool review` and walks you through its findings, one at a time:
|
||||
|
||||
- initiatives with no next action;
|
||||
- waiting-fors past their follow-up date;
|
||||
- tracker projects that have no page;
|
||||
- active pages with no open item;
|
||||
- someday items that have gone stale.
|
||||
|
||||
For each finding, the agent asks what is actually true, because "this looks stalled" can mean a
|
||||
next action is missing, the initiative was paused on purpose, or it is finished. Then it proposes the
|
||||
exact change: a new item in the tracker, an item marked done, or a page's state moved to `dormant`,
|
||||
`completed` or `abandoned`. Nothing is written to the tracker until you confirm that exact call.
|
||||
What was decided goes onto the page, and the page is published.
|
||||
|
||||
A quiet week is a valid result. If one of the checks could not run because the tracker did not
|
||||
answer, the agent says which check was skipped. An empty result under a skipped check means nothing.
|
||||
|
||||
## Starting something new
|
||||
|
||||
Ask the agent for a new project and name it. It runs `tools/wikitool new project`, which gives the
|
||||
initiative its page under `kb/gtd/` and, where a tracker is configured, a tracker project under the
|
||||
same name. A CalDAV tracker gets that project created automatically. On Super Productivity the
|
||||
command stops with exit 42 and asks you to create the project in the app yourself. Tell the agent
|
||||
when you are done, and it continues with `--resume`. A tracker the wiki can only read refuses the
|
||||
command outright.
|
||||
|
||||
A single open item with no page of its own - a commitment that came with a source, say - is filed
|
||||
with `tools/wikitool task new`. `task list` reads a project's open items back, and `task close --id`
|
||||
marks one done. Nothing deletes an item. The commands are in the
|
||||
[command reference](../reference/commands/pages.md).
|
||||
@@ -0,0 +1,263 @@
|
||||
# Set up the MCP read server
|
||||
|
||||
Serve an instance's wiki read-only over MCP, so that a consumer that is not a terminal on this
|
||||
machine - another agent, a chat client, a service - can ask it the same questions and get the same
|
||||
answers. This page covers what you set up around the server: the client, the authentication in
|
||||
front of it, the optional `submit` path, and how to check that it works. Installing, starting and
|
||||
syncing the server itself is
|
||||
[instructions/mcp-read-server.md](../../instructions/mcp-read-server.md), which an agent follows
|
||||
and which is readable for you as well.
|
||||
|
||||
<!-- wikitool:toc -->
|
||||
## Contents
|
||||
|
||||
- [What the server is, and what it is not](#what-the-server-is-and-what-it-is-not)
|
||||
- [What you prepare](#what-you-prepare)
|
||||
- [Wiring a client to it](#wiring-a-client-to-it)
|
||||
- [Running it as a service](#running-it-as-a-service)
|
||||
- [Authentication in front of it](#authentication-in-front-of-it)
|
||||
- [Keeping the served tree current](#keeping-the-served-tree-current)
|
||||
- [Optional: the submit path](#optional-the-submit-path)
|
||||
- [Checking that it works](#checking-that-it-works)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
- [What is deliberately not here](#what-is-deliberately-not-here)
|
||||
<!-- /wikitool:toc -->
|
||||
|
||||
## What the server is, and what it is not
|
||||
|
||||
The server is a second consumer of the same core as the CLI, not a second program. Both call the
|
||||
same functions, and a golden test in the origin repository holds their output together. What
|
||||
`tools/wikitool search --json` returns, the MCP tool `search` returns too, together with the commit
|
||||
the answer was computed from.
|
||||
|
||||
It is not a write path into `kb/`. No tool creates, changes or publishes a page - not because a list
|
||||
is filtered, but because the server imports nothing under `tools/chemenu/commands/`. Those functions
|
||||
cannot be reached from its process. The one exception is opt-in: a sixth tool, `submit`, which
|
||||
writes only into a quarantine no other command reads. A human decides about everything that leaves
|
||||
it ([below](#optional-the-submit-path)).
|
||||
|
||||
| Tool | What it answers |
|
||||
|---|---|
|
||||
| `search` | Pages in `kb/` by text, by frontmatter (`!sources`, `tags~k8s`), or both |
|
||||
| `types` | Which page types this wiki knows |
|
||||
| `describe_type` | A type's full contract: fields, required values, enums |
|
||||
| `lint` | Structural findings: broken wikilinks, orphans, catalog drift, schema gaps |
|
||||
| `status` | A snapshot: page count, distribution over collections, finding counts |
|
||||
| `submit` *(optional)* | Hands a document to the review queue - into a quarantine, never into `kb/` |
|
||||
|
||||
## What you prepare
|
||||
|
||||
- A working instance ([INSTALL.md](../../INSTALL.md)), including `tools/.venv` and ripgrep.
|
||||
- For a deployment, a reverse proxy that can authenticate requests before they reach the server
|
||||
([below](#authentication-in-front-of-it)).
|
||||
|
||||
Then ask the agent:
|
||||
|
||||
```text
|
||||
Set up the MCP read server for this instance
|
||||
```
|
||||
|
||||
It installs the server's optional dependency, which is deliberately not in
|
||||
`tools/requirements.txt`: an instance that only uses the CLI should not have to install pydantic,
|
||||
starlette, uvicorn and cryptography. It also checks that tracing cannot write into the served tree.
|
||||
The server refuses to start if it could:
|
||||
|
||||
```text
|
||||
ERROR Telemetry is on and would write into the served checkout (...). Set WIKI_TRACE=0,
|
||||
or point WIKI_TRACE_DIR outside the corpus.
|
||||
```
|
||||
|
||||
That refusal is expected, not an installation fault. Telemetry is off by default in an instance
|
||||
installed from a release and on in a clone of the origin repository
|
||||
([configuration](../reference/configuration.md#telemetry)). The server consults the same policy
|
||||
`wikitool doctor` does. Setting `WIKI_TRACE=0` explicitly, as every example here does, is correct
|
||||
either way and makes the check moot.
|
||||
|
||||
## Wiring a client to it
|
||||
|
||||
Locally, over `stdio`, the client starts the server itself: one process per consumer, no network.
|
||||
The configuration follows the usual MCP client shape, with absolute paths, because the client
|
||||
inherits no working directory:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"chemenu": {
|
||||
"command": "/path/to/instance/tools/.venv/bin/python",
|
||||
"args": ["-m", "chemenu.mcp"],
|
||||
"cwd": "/path/to/instance/tools",
|
||||
"env": {
|
||||
"WIKI_TRACE": "0",
|
||||
"CHEMENU_ROOT": "/path/to/instance"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`CHEMENU_ROOT` says **which** wiki is served. Without it, the server takes the checkout its own
|
||||
package sits in. That is enough for a single instance, but with several corpora it is safer to always
|
||||
set it. The process speaks MCP over stdin and stdout and prints nothing of its own, which is correct.
|
||||
|
||||
## Running it as a service
|
||||
|
||||
Deployed, the server speaks `streamable-http`, the only transport a reverse proxy can sit in front
|
||||
of. The command a service unit or container runs looks like this:
|
||||
|
||||
```bash
|
||||
WIKI_TRACE=0 CHEMENU_ROOT=/srv/chemenu \
|
||||
tools/.venv/bin/python -m chemenu.mcp \
|
||||
--transport streamable-http --host 0.0.0.0 --port 8000
|
||||
```
|
||||
|
||||
The endpoint is then `http://<host>:8000/mcp`. `--host 0.0.0.0` is deliberately not the default:
|
||||
without it the server binds to loopback, which is right locally and wrong in a container, where the
|
||||
proxy has to reach it. If you set it, make sure something stands in front. `sse` is not offered. It
|
||||
is the superseded remote transport, and building on it now would only postpone the switch.
|
||||
|
||||
## Authentication in front of it
|
||||
|
||||
**The server does not authenticate on its own, by design.** A request that is not properly
|
||||
authenticated should never reach the Python process at all. The authentication is a Traefik
|
||||
ForwardAuth middleware:
|
||||
|
||||
> **<https://gitea.nehmer.net/torben/gitea-mcp-forward-auth>**
|
||||
|
||||
In short, it checks `Authorization: Bearer <token>` against SHA-256 hashes of allowed tokens. It
|
||||
answers `200` for a valid token and `401` for a missing or wrong one, and always leaves
|
||||
`GET /healthz` open. Plaintext tokens appear neither in the configuration nor in the log, only
|
||||
hashes and a short fingerprint. It is configured with `AUTH_PROXY_TOKEN_HASHES_DIR` (a directory with
|
||||
one file per token hash, which suits a Kubernetes secret volume) or `AUTH_PROXY_TOKEN_HASHES` (a
|
||||
comma-separated list). The README there has the details, reference manifests and a test script.
|
||||
|
||||
To create a token hash:
|
||||
|
||||
```bash
|
||||
echo -n "my-token" | sha256sum | awk '{print $1}'
|
||||
```
|
||||
|
||||
**Rate limiting belongs in the same place,** in front of the process and beside the
|
||||
authentication. It does not belong in the Iteration Budget Gate. That gate stops an *agent session*
|
||||
from iterating over the wiki's state unnoticed, which is why retrieval is exempt from it. Using it
|
||||
as a rate limiter would water it down.
|
||||
|
||||
## Keeping the served tree current
|
||||
|
||||
The server reads the working tree, and a stale checkout answers confidently and wrongly. So **every
|
||||
answer carries the commit** it was computed from:
|
||||
|
||||
```json
|
||||
{ "commit": "<40-character SHA>", "as_of": "<ISO-8601, UTC>", "count": 3, "...": "..." }
|
||||
```
|
||||
|
||||
A timer beside the server keeps the tree current by polling: a fetch, then a hard reset to the
|
||||
remote branch. The two commands, and why the hard reset is load-bearing, are in the instruction's
|
||||
sync step. Polling rather than a webhook, because it needs no inbound endpoint and no signature
|
||||
check, which is a smaller attack surface than the thing a webhook would optimise. **Never add
|
||||
`git clean` to that timer.** The reset leaves every gitignored path alone, which is what keeps the
|
||||
`submit` quarantine and any traces intact. A clean would silently delete a submission nobody has
|
||||
reviewed yet.
|
||||
|
||||
## Optional: the submit path
|
||||
|
||||
Without this, `submit` does not exist as a tool - not unused, but not registered. A file
|
||||
`.wikitool-upload.json` in the served corpus turns it on:
|
||||
|
||||
```json
|
||||
{
|
||||
"schema": 1,
|
||||
"identity_header": "X-Forwarded-User",
|
||||
"max_bytes": 10485760,
|
||||
"allowed_extensions": [".md", ".txt", ".pdf", ".html", ".csv", ".json", ".png", ".jpg"],
|
||||
"quota": { "submissions_per_day": 20, "bytes_per_day": 52428800 }
|
||||
}
|
||||
```
|
||||
|
||||
Every field is required, and none has a built-in default except `identity_header`. A malformed file
|
||||
stops the server from starting. It never means "no limits": the failure deliberately goes the safe
|
||||
way. Size and quota are your deployment's decision, not defaults worth copying blindly.
|
||||
|
||||
**With `submit` on, the middleware has a second duty.** It has to set the identity header itself, on
|
||||
every authenticated request, and discard any copy the client sent. The server trusts that header as
|
||||
the value for `submitter`. A header the client could set itself would be a claim, not an identity.
|
||||
If `identity_header` names a header the middleware does not set, every submission is refused for
|
||||
lack of an identity.
|
||||
|
||||
Submitted files land in `mcp-upload/<id>/`, which is gitignored and read by no other command. From
|
||||
there a human promotes or rejects them, in the same checkout as the server rather than in its
|
||||
process. That review is part of
|
||||
[ingesting a source](ingest-a-source.md#a-document-submitted-from-outside).
|
||||
|
||||
## Checking that it works
|
||||
|
||||
The quickest test without a client starts the server over stdio, lists its tools and asks one
|
||||
question:
|
||||
|
||||
```bash
|
||||
cd tools && WIKI_TRACE=0 .venv/bin/python - <<'EOF'
|
||||
import asyncio, os
|
||||
from mcp import ClientSession, StdioServerParameters
|
||||
from mcp.client.stdio import stdio_client
|
||||
|
||||
async def main():
|
||||
params = StdioServerParameters(
|
||||
command=".venv/bin/python", args=["-m", "chemenu.mcp"],
|
||||
env={"WIKI_TRACE": "0", "PATH": os.environ["PATH"]},
|
||||
)
|
||||
async with stdio_client(params) as (r, w):
|
||||
async with ClientSession(r, w) as s:
|
||||
await s.initialize()
|
||||
print("Tools:", [t.name for t in (await s.list_tools()).tools])
|
||||
out = await s.call_tool("status", {})
|
||||
d = getattr(out, "structuredContent", None) or out.structured_content
|
||||
print("Pages:", d["pages"], "| Commit:", d["commit"])
|
||||
|
||||
asyncio.run(main())
|
||||
EOF
|
||||
```
|
||||
|
||||
Expected output, roughly:
|
||||
|
||||
```text
|
||||
Tools: ['search', 'types', 'describe_type', 'lint', 'status']
|
||||
Pages: 176 | Commit: 576df2cdddc96614a7e6641e562022d52112d411
|
||||
```
|
||||
|
||||
Then cross-check against the CLI. Both have to give the same answer:
|
||||
|
||||
```bash
|
||||
tools/wikitool search "<term>" --json
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**`ERROR Telemetry is on and would write into the served checkout`**: expected behaviour, not an
|
||||
installation fault. Set `WIKI_TRACE=0`, or point `WIKI_TRACE_DIR` outside the corpus.
|
||||
|
||||
**`ModuleNotFoundError: No module named 'mcp'`**: the optional dependency is not installed, or
|
||||
the client starts a different Python than the instance's. Give the client the absolute path to
|
||||
`tools/.venv/bin/python`.
|
||||
|
||||
**`"commit": null` in every answer**: the served tree has uncommitted changes. Either the sync is not
|
||||
running, or something writes into the corpus that has no business there. The five read tools never
|
||||
write. `submit`, when it is on, writes only to `mcp-upload/`, which is gitignored and so is never a
|
||||
cause of `"commit": null` either.
|
||||
|
||||
**`commit` names an old revision**: the sync timer is not running.
|
||||
|
||||
**The server answers differently from `wikitool`**: that is a defect, not a configuration
|
||||
difference, since both go through the same functions. First check that both point at the same root.
|
||||
`CHEMENU_ROOT` is easy to set for one and not the other.
|
||||
|
||||
**Not reachable from outside**: without `--host 0.0.0.0` the server binds to loopback. If it is
|
||||
reachable but every call gets `401`, the middleware is working correctly and the token is wrong.
|
||||
|
||||
## What is deliberately not here
|
||||
|
||||
Deployment specifics - the cluster, ingress hosts, the secret store, the GitOps source - are
|
||||
private infrastructure, and this repository is public.
|
||||
|
||||
There is no container image for the server yet. Until there is, the path above - the venv,
|
||||
`python -m chemenu.mcp`, a proxy in front - is the complete one. With `submit` on, `mcp-upload/`
|
||||
has to survive restarts the same way the rest of the checkout does. Otherwise a submitted file that
|
||||
nobody has reviewed yet loses its quarantine.
|
||||
@@ -0,0 +1,176 @@
|
||||
# Trace and score agent sessions
|
||||
|
||||
Record what an agent did in a session, read the record back, and score it. A score covers the state
|
||||
the session left the wiki in and how it got there: whether it repeated a refused call, or passed a
|
||||
gate's flag without that gate ever refusing. Why this exists, what the trace contains, and what a
|
||||
score means are covered in [telemetry-and-evaluation.md](../explanation/telemetry-and-evaluation.md).
|
||||
|
||||
<!-- wikitool:toc -->
|
||||
## Contents
|
||||
|
||||
- [What you prepare](#what-you-prepare)
|
||||
- [Reading a session back](#reading-a-session-back)
|
||||
- [What each harness can report](#what-each-harness-can-report)
|
||||
- [Claude Code](#claude-code)
|
||||
- [Copilot CLI](#copilot-cli)
|
||||
- [VS Code Chat](#vs-code-chat)
|
||||
- [Mistral Vibe](#mistral-vibe)
|
||||
<!-- /wikitool:toc -->
|
||||
|
||||
## What you prepare
|
||||
|
||||
**Turn tracing on, if it is off.** It is on in a clone of the origin repository and off in an
|
||||
instance installed from a release. `tools/wikitool doctor` reports which state this checkout is in,
|
||||
and why. To turn it on for one checkout, create `.wikitool-telemetry.json` at the root:
|
||||
|
||||
```json
|
||||
{ "enabled": true }
|
||||
```
|
||||
|
||||
The file and its other keys are in the [configuration reference](../reference/configuration.md#telemetry).
|
||||
Everything stays on this machine: traces land under `reports/telemetry/`, which is gitignored, and
|
||||
no exporter is configured.
|
||||
|
||||
**Scope the session to the task.** Every source of events joins on one session id. Setting it
|
||||
yourself keeps one task in one trace:
|
||||
|
||||
```bash
|
||||
export WIKITOOL_SESSION_ID="my-task"
|
||||
```
|
||||
|
||||
[instructions/session-setup.md](../../instructions/session-setup.md) has the line for each shell,
|
||||
and what to do on a harness that starts a fresh shell for every command.
|
||||
|
||||
## Reading a session back
|
||||
|
||||
```bash
|
||||
tools/wikitool eval sessions # which sessions have a trace
|
||||
|
||||
# what did that session do?
|
||||
jq -r '[.ts, .source, .event, (.attrs.command // .attrs.tool_name // "")] | @tsv' \
|
||||
reports/telemetry/my-task/trace.jsonl
|
||||
|
||||
# how well did it do it?
|
||||
tools/wikitool eval score --session my-task
|
||||
tools/wikitool eval score --session my-task --save # also write the scorecard under reports/evals/
|
||||
```
|
||||
|
||||
Both `eval` commands only read, and neither counts against the session's call budget: reading back
|
||||
what a session did is not iterating on the wiki. Their options are in the
|
||||
[telemetry command reference](../reference/commands/telemetry.md).
|
||||
|
||||
To see how a harness hook payload would be recorded, feed one in by hand without writing anything:
|
||||
|
||||
```bash
|
||||
echo '{"session_id":"x","hook_event_name":"post_tool","tool_name":"bash"}' \
|
||||
| tools/trace_ingest.py --source mistral-vibe --dry-run
|
||||
```
|
||||
|
||||
## What each harness can report
|
||||
|
||||
`wikitool` records its own calls on every harness. What the agent did *between* those calls -
|
||||
file reads, searches, shell commands, prompts - comes from the harness's hooks, and harnesses differ
|
||||
a lot in what they offer. Verified against vendor documentation on 2026-08-23:
|
||||
|
||||
| | Claude Code | Copilot CLI | VS Code Chat | Mistral Vibe |
|
||||
|---|---|---|---|---|
|
||||
| Hook events | ~30 | 14 | none | 3 (`pre_tool`, `post_tool`, `post_agent`) |
|
||||
| Session start/end hook | yes | yes | - | **no** |
|
||||
| Prompt submit hook | yes | yes | - | **no** |
|
||||
| Which instructions loaded | yes (`InstructionsLoaded`) | no | no | no |
|
||||
| Block / rewrite a tool call | yes | yes | - | yes |
|
||||
| OTel to your own collector | yes | via MDM `telemetry` | no | **no** - `enable_otel` targets Mistral Studio only |
|
||||
| `TRACEPARENT` to subprocesses | yes | undocumented | - | undocumented |
|
||||
| Programmatic mode | `-p`, `stream-json` | `-p` | no | `-p`, `--output streaming` |
|
||||
| Isolated config home | to be confirmed | `COPILOT_HOME` | no | `VIBE_HOME` |
|
||||
| Local session log | `~/.claude/projects/*.jsonl` (unstable format) | `~/.copilot/session-state/<id>/events.jsonl` | chronicle SQLite | `$VIBE_HOME/logs/` (no format guarantee) |
|
||||
| Wired up here | **partial** - `.claude/settings.json` (`UserPromptSubmit` + a `permissions.ask` rule on the clearing publish) | **yes** - `.github/hooks/wiki-trace.json` | **yes** - `tools/import_chronicle.py` | **yes** - `.vibe/hooks.toml` |
|
||||
|
||||
A rule that needs an event a harness cannot report is shown as *skipped*, not as passed or failed.
|
||||
|
||||
### Claude Code
|
||||
|
||||
`.claude/settings.json` wires `UserPromptSubmit` to `tools/trace_ingest.py`, through
|
||||
`tools/trace-hook` like every hook here. That gives the trace its turn boundaries, which the rule
|
||||
`clearance-ended-the-turn` needs. Without a `prompt.submitted` event it reports "cannot say"
|
||||
instead of a verdict.
|
||||
|
||||
**No `PreToolUse` decision hook is wired, and that is a finding, not an oversight.** Verified against
|
||||
the live CLI on 2026-08-27: a `PreToolUse` hook that returns
|
||||
`hookSpecificOutput.permissionDecision: "ask"` does **not** override a matching `permissions.allow`
|
||||
rule. Permissions are evaluated before the hook's decision, so a hook cannot force a confirmation
|
||||
prompt on a command that is already allowed. A `permissions.ask` rule *does* win, which is why
|
||||
`.claude/settings.json` carries one on the `--confirm` form of `publish`
|
||||
(`Bash(tools/wikitool publish --confirm:*)`). The clearing call prompts, and ordinary publishes below
|
||||
the threshold do not. It is a prefix match, so it depends on `--confirm` coming first, which is why
|
||||
`publish` always prints its re-run line in that order. Treat it as a useful second line, not a
|
||||
guarantee: an agent that reorders the flags gets around it.
|
||||
|
||||
### Copilot CLI
|
||||
|
||||
[.github/hooks/wiki-trace.json](../../.github/hooks/wiki-trace.json) is committed, so a clone
|
||||
brings its own tracing. Eleven events go to `tools/trace_ingest.py`. `disableAllHooks` in
|
||||
`.github/copilot/settings.json` turns them off without deleting anything. `userPromptSubmitted` is
|
||||
among them, so `clearance-ended-the-turn` can be scored on Copilot CLI with no change.
|
||||
|
||||
A `preToolUse` entry that forces a decision on the clearing call would first need Copilot's own
|
||||
decision-document schema verified against a live CLI. That has not happened, and an adapter that
|
||||
cannot be verified is not written.
|
||||
|
||||
Four details of the hook configuration matter. The first two hold for all three harnesses that have
|
||||
hooks:
|
||||
|
||||
- **The interpreter is the venv's, never the script's shebang.** Each `bash` command is
|
||||
`./tools/trace-hook ...`, which runs `trace_ingest.py` with `tools/.venv`'s Python in either venv
|
||||
layout. Each `powershell` command names `tools\.venv\Scripts\python.exe` directly. On Windows in
|
||||
Git Bash, the shebang's `python3` was the Microsoft Store alias, which made every hook a silent
|
||||
no-op. Before the preflight has created the venv, `trace-hook` records nothing and exits 0.
|
||||
- **`./tools/trace-hook` has a PowerShell twin, `tools/trace-hook.ps1`.** PowerShell on Windows
|
||||
resolves the string to the `.ps1` first. Without the twin, it hands the sh script to a file
|
||||
association, and Windows asks which app should open it - on every hook event. Copilot CLI also
|
||||
reads `.claude/settings.json` and runs its single `command` under PowerShell on Windows, so a
|
||||
`bash` field alone does not keep the string out of PowerShell. The twin follows the sh script's
|
||||
rules - venv Python, silent without a venv, exit 0 whatever happens - and stays parseable by
|
||||
Windows PowerShell 5.1, because VS Code starts hooks under 5.1.
|
||||
- **Every command ends in `|| true`.** `preToolUse` hooks fail closed: a non-zero exit denies the
|
||||
tool call. Without the guard, a missing interpreter would turn the observer into a blocker that
|
||||
refuses every tool call in the session. Timeouts fail open, so the 5-second `timeoutSec` is no
|
||||
hazard.
|
||||
- **The event name is passed explicitly.** Copilot serves two payload dialects: camelCase event
|
||||
names give camelCase fields, and PascalCase names give the snake_case shape that VS Code and Claude
|
||||
use. `--event` means the mapping does not depend on which one a configuration chose.
|
||||
|
||||
### VS Code Chat
|
||||
|
||||
VS Code Chat has no hooks, so nothing observes a session while it runs. Its chronicle store
|
||||
(`session-store.db`, the same schema as Copilot CLI's) keeps sessions, turns and touched files, and
|
||||
[tools/import_chronicle.py](../../tools/import_chronicle.py) rebuilds a trace from it afterwards:
|
||||
|
||||
```bash
|
||||
tools/import_chronicle.py --dry-run # this repository's sessions
|
||||
tools/import_chronicle.py --session 0f1e2d3c
|
||||
```
|
||||
|
||||
The store starts empty, and `/chronicle reindex` fills it. That command also syncs session data to
|
||||
your GitHub account, so run it deliberately rather than from a script. A rebuilt trace marks itself
|
||||
`reconstructed: true`, and its `completeness` names `tool.post` but not `tool.pre`: the store records
|
||||
that a file was touched, not that a tool was about to run.
|
||||
|
||||
### Mistral Vibe
|
||||
|
||||
[.vibe/hooks.toml](../../.vibe/hooks.toml) declares the three hooks Vibe has, and
|
||||
[.vibe/config.toml](../../.vibe/config.toml) keeps the telemetry settings in the repository rather
|
||||
than in someone's shell profile. Both were checked against the installed CLI's own loader
|
||||
(`mistral-vibe 2.24.2`) rather than against its documentation. What that turned up:
|
||||
|
||||
- **A failing hook cannot block anything.** With `strict = false`, the default, a crash or a timeout
|
||||
is only a warning. Only a hook that opts into `strict` can deny a tool call - the mirror image of
|
||||
Copilot.
|
||||
- **`post_agent` carries no response text,** only the session context. That is why it maps to
|
||||
`turn.end` rather than `assistant.message`.
|
||||
- **`enable_telemetry` defaults to `true`.** Turning it off is a real change, and it also gates OTel
|
||||
export, which needs both flags on. This repository leaves both off: Vibe's `enable_otel` sends
|
||||
prompts to Mistral Studio.
|
||||
- **Vibe already reads `.agents/skills/` and `AGENTS.md`.** The directory that
|
||||
`wikitool instructions sync` publishes is a project-scope skill source for Vibe, so working with
|
||||
Vibe needs no adaptation, only a trusted folder.
|
||||
@@ -0,0 +1,88 @@
|
||||
# Troubleshoot an instance
|
||||
|
||||
What a common failure means, and what to do about it. During installation itself, the preflight
|
||||
names its own stops - what is missing, why, and the command that fixes it - and
|
||||
[INSTALL.md](../../INSTALL.md) explains them. This page covers an instance that is already set up.
|
||||
|
||||
For the MCP read server, see the troubleshooting section of
|
||||
[Set up the MCP read server](set-up-the-mcp-server.md#troubleshooting).
|
||||
|
||||
## `tools/wikitool` ends with `STOP - this checkout is not set up yet` (exit 42)
|
||||
|
||||
The preflight has not run in this checkout yet, or not since the last update. Run it:
|
||||
|
||||
```bash
|
||||
tools/preflight.sh
|
||||
```
|
||||
|
||||
In PowerShell 7 on Windows, the call is
|
||||
`pwsh -NoProfile -ExecutionPolicy Bypass -File tools/preflight.ps1`. The `-ExecutionPolicy Bypass`
|
||||
applies to that one process only and changes no setting.
|
||||
[instructions/preflight.md](../../instructions/preflight.md) is the procedure, and its output says
|
||||
what to install if anything is missing.
|
||||
|
||||
## The agent offers no skills (`wiki-ingest`, `wiki-query`, ...)
|
||||
|
||||
`.agents/skills/` and `.claude/skills/` are generated and not committed. Run:
|
||||
|
||||
```bash
|
||||
tools/wikitool instructions sync
|
||||
```
|
||||
|
||||
Then restart the agent session. A harness reads its skills only when the session starts.
|
||||
|
||||
## `doctor` reports `personalization: FAIL`
|
||||
|
||||
`USER.md` or `SOUL.md` is missing, or still carries the sentinel line from its template. A renamed
|
||||
template does not count as a filled one. Ask the agent to run the personalization step of
|
||||
[instructions/setup-instance.md](../../instructions/setup-instance.md). For a further clone of an
|
||||
older instance, that is the only step to catch up on.
|
||||
|
||||
## `doctor` reports `environment: WARN`
|
||||
|
||||
`ENVIRONMENT.md` exists but still carries the template's sentinel line. Fill it in (the template is
|
||||
`ENVIRONMENT.md.template`) and remove the line, or delete the file. It is optional, and `absent` is
|
||||
a valid state.
|
||||
|
||||
## `new` stops with "No author configured"
|
||||
|
||||
Neither `$WIKI_AUTHOR` nor `git config user.name` is set. Run `git config user.name "<Name>"`, or
|
||||
export `WIKI_AUTHOR` ([configuration](../reference/configuration.md)).
|
||||
|
||||
## `publish` ends with exit 42 (Mass-Update Gate)
|
||||
|
||||
Expected when ten or more files are counted, for example on the very first commit of a new instance
|
||||
or after a lint pass. It is not an error. It asks that a human see the output: the complete file
|
||||
list and the exact `--confirm <token>` line that publishes once you approve.
|
||||
[instructions/gates.md](../../instructions/gates.md) has the details.
|
||||
|
||||
## `publish` ends with exit 42 (Publish-Remote Gate)
|
||||
|
||||
This checkout has a `.wikitool-remotes.json`, and the remote being pushed to is not listed in it.
|
||||
Also not an error: the output names the push URL it would write to and the URLs that are allowed.
|
||||
Unlike the Mass-Update Gate, there is **no token and no flag** here. If the target really is right,
|
||||
you add its URL to the file yourself. An agent that edits the file to get past the refusal is
|
||||
opening a gate on its own initiative, which it must never do.
|
||||
|
||||
## Setup or an update fails, and you want to report it
|
||||
|
||||
Ask the agent to follow [instructions/bug-report.md](../../instructions/bug-report.md), or call
|
||||
`tools/bugreport` yourself. It works the same from Bash, Git Bash and PowerShell. It finds a
|
||||
Python 3.8 or newer by itself, skipping the Store aliases `python3` points at on Windows. It needs
|
||||
neither the preflight nor a working `wikitool`, and if it finds no Python, it says why.
|
||||
|
||||
The script writes a bundle to `reports/bugreport-<timestamp>/`, with a zip beside it.
|
||||
|
||||
- **What is removed:** secrets, and page titles from everything the script itself produces.
|
||||
`--titles` keeps the titles.
|
||||
- **What may still be in it:** the session trace is included by default (`--no-trace` leaves it
|
||||
out). Like the chronology and the transcripts, it can contain page content and titles. The bundle
|
||||
also contains machine, user and path names.
|
||||
- **Pseudonymising:** `--pseudonymise` replaces user, host, path, git and remote names with
|
||||
placeholders that keep their length, spaces, hyphens and path depth. The agent can then add more
|
||||
names - people, companies, customers, projects - with `--bundle … --candidates …`. That is a
|
||||
model's judgment, and it leaves a remainder: read the bundle before you share it. The mapping,
|
||||
the checklist and the candidates file contain the originals, and they sit beside the bundle,
|
||||
never in it.
|
||||
|
||||
Nothing is uploaded anywhere. You choose the channel.
|
||||
@@ -0,0 +1,145 @@
|
||||
# Update an instance
|
||||
|
||||
Bring an instance installed from a release up to a newer release of the stack: the tools, types,
|
||||
instructions and contracts. Your pages stay yours. The agent carries out the upgrade by following
|
||||
[instructions/upgrade-instance.md](../../instructions/upgrade-instance.md), which sets the order of
|
||||
the steps, what each one decides, and where the session has to restart. This page covers the
|
||||
decision before it, and what you'll see along the way.
|
||||
|
||||
<!-- wikitool:toc -->
|
||||
## Contents
|
||||
|
||||
- [What is running, and is there something newer?](#what-is-running-and-is-there-something-newer)
|
||||
- [Read the notes first](#read-the-notes-first)
|
||||
- [What you say](#what-you-say)
|
||||
- [Where the agent stops](#where-the-agent-stops)
|
||||
- [Where the release comes from, and whom you trust](#where-the-release-comes-from-and-whom-you-trust)
|
||||
- [Two first-time cases](#two-first-time-cases)
|
||||
- [Pitfalls](#pitfalls)
|
||||
<!-- /wikitool:toc -->
|
||||
|
||||
## What is running, and is there something newer?
|
||||
|
||||
Every instance carries the version of the **stack**, not of its content. It is in `VERSION`, and
|
||||
`.wikitool-release.json` beside it records where the release came from and when it was exported.
|
||||
|
||||
```bash
|
||||
tools/wikitool version # what runs here, and where it came from
|
||||
tools/wikitool version check # is there a newer release?
|
||||
tools/wikitool version notes # what the newest release changes
|
||||
```
|
||||
|
||||
`version check`, `version notes` and `dist upgrade --latest` are the only commands that go to the
|
||||
network. All three ask the same release feed: the one named in the stamp, unless
|
||||
`WIKITOOL_UPDATE_URL` overrides it. A feed that cannot be reached is reported as an error, **never**
|
||||
as "up to date" and never as "no notes".
|
||||
|
||||
## Read the notes first
|
||||
|
||||
What a version number promises: two versions are compatible when they agree in the *leftmost
|
||||
non-zero part*. `0.1.3 → 0.1.4` is a safe update and `0.1.3 → 0.2.0` is not. From `1.0.0` on, the
|
||||
same rule reads as the familiar "a MAJOR breaks". `version check` says which case you are in
|
||||
(`state: update` or `state: migration`).
|
||||
|
||||
That number only answers whether the new machinery can simply be copied over the old. Whether your
|
||||
*content* has to be migrated is a second, independent question. So the release notes of a MAJOR
|
||||
carry two separate lines, and both are worth reading before you start:
|
||||
|
||||
- **Breaking Change:** what stops working, and what this instance has to do about it.
|
||||
- **Migration:** whether and how the pages are rewritten (`none required` when they are not).
|
||||
|
||||
`version notes` prints both. On an instance it reads them from the release feed, because an
|
||||
instance's own `CHANGES.md` is a stub that an update never overwrites. It always asks for the
|
||||
**newest** release, so while `VERSION` still names the old one, it answers with a version other than
|
||||
the one installed, and says so on stderr. If the feed cannot be reached, the error names the release
|
||||
page recorded as `release_url` in `.wikitool-release.json`. `--offline` sends you there directly.
|
||||
|
||||
Why the two questions are kept apart: [version-model.md](../explanation/version-model.md).
|
||||
|
||||
## What you say
|
||||
|
||||
```text
|
||||
Update this instance to the latest release
|
||||
```
|
||||
|
||||
## Where the agent stops
|
||||
|
||||
In the order you'll meet them:
|
||||
|
||||
- **The release notes.** The agent shows you both lines before it touches anything. A migration the
|
||||
instance already owes from an earlier release is finished first.
|
||||
- **A stack file you changed by hand.** `dist upgrade` lists every file that differs from what the
|
||||
installed release shipped, instead of overwriting it silently. For each one, you decide: take the
|
||||
release's version, or keep yours. Keeping yours means the file is reported again at every future
|
||||
upgrade.
|
||||
- **The preflight.** A new release may need other tools than the old one. If something is missing,
|
||||
the preflight stops with exit 42 and says what to install, exactly as during installation.
|
||||
- **The first publish.** The swapped machinery is published on its own, before any content changes.
|
||||
That is far more than ten files, so the Mass-Update Gate (exit 42) shows you the list and the
|
||||
`--confirm` line.
|
||||
- **The session restart.** The running session still holds the *old* `AGENTS.md`, contracts and
|
||||
skills, so the agent asks you to restart it before migrating content. The new session picks up
|
||||
where the old one stopped.
|
||||
- **An offered migration.** A required migration is simply carried out. An *offered* one changes a
|
||||
file the instance owns, so taking it or declining it is your decision.
|
||||
- **The second publish,** of the migrated content, behind the same gate.
|
||||
|
||||
If a step fails for a reason that isn't obvious, the agent stops and offers you a bug report rather
|
||||
than improvising ([Troubleshoot](troubleshoot.md)).
|
||||
|
||||
`doctor` warns as long as the content's version (`kb_version` in `.wikitool-kb.json`) is behind
|
||||
`VERSION` and migrations are still open. An instance older than `.wikitool-kb.json` has no such file
|
||||
at all. In that case `tools/wikitool migrate baseline <version>` is called once, and nothing is
|
||||
guessed.
|
||||
|
||||
## Where the release comes from, and whom you trust
|
||||
|
||||
`tools/wikitool dist upgrade --latest --expect <version>` asks the release feed for its newest
|
||||
release, downloads the tarball and its `.sha256` into a scratch directory, checks one against the
|
||||
other and applies the release. The scratch directory is removed on every exit, including
|
||||
`--dry-run`. `--expect` is the version `version notes` showed you: the feed only knows its *newest*
|
||||
release, and `--expect` refuses **before** downloading if a newer one has appeared since, rather
|
||||
than applying something nobody read. Pre-releases (`-beta.N`) need `--pre`.
|
||||
|
||||
**What the checksum does, and what it does not do.** The `.sha256` comes from the same feed as the
|
||||
tarball. It protects against a damaged transfer, not against a compromised feed. Whether a release
|
||||
is genuine rests on your trust in the host whose feed the instance asks (`update_url` in the stamp).
|
||||
Nothing forces https. A configured `http://` feed is a deliberate choice. `WIKITOOL_UPDATE_TOKEN`
|
||||
is sent only to downloads on the same host as the feed.
|
||||
|
||||
**Offline, or with a tarball from someone else:** put the tarball and its `.sha256` side by side,
|
||||
and the agent passes the file instead of `--latest` (`dist upgrade <tarball>`). A tarball has to
|
||||
unpack to exactly one top-level directory, which is the shape the release workflow builds.
|
||||
|
||||
## Two first-time cases
|
||||
|
||||
**The first jump to a release that knows `--latest`:** the option does not exist in the instance
|
||||
yet, because the instruction there belongs to the release being left. Fetch the tarball and the
|
||||
`.sha256` once from the release page, put them side by side, and run `dist upgrade <tarball>`. From
|
||||
the next release on, the instance has `--latest` itself.
|
||||
|
||||
**The first jump to `4.5.0` or later:** `dist upgrade` itself does not exist in the instance yet,
|
||||
because it arrived with `4.5.0`. Use the tool from the unpacked *new* tarball and point it at the old
|
||||
instance:
|
||||
|
||||
```bash
|
||||
tar -xzf chemenu-stack-<version>.tar.gz
|
||||
CHEMENU_ROOT="$PWD" chemenu-stack-<version>/tools/wikitool \
|
||||
dist upgrade chemenu-stack-<version>.tar.gz --dry-run
|
||||
```
|
||||
|
||||
`CHEMENU_ROOT` tells the package which corpus to work on ([configuration](../reference/configuration.md)).
|
||||
Without it, the package would take the unpacked tarball itself for the instance. From the second
|
||||
upgrade on, the instance carries the command and the instruction itself.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- An instance without a local `.wikitool-release.json`, or with one that has no `files` block (from
|
||||
before `4.5.0`), gives `dist upgrade` nothing to detect a local change against, so it refuses
|
||||
the swap. There is no repair for that today.
|
||||
- Given a tarball or directory, `dist upgrade` downloads nothing. Only `--latest` downloads.
|
||||
- To follow the swap by hand anyway, perhaps with a tool `wikitool` cannot run, use the file list in
|
||||
the `files` block of `.wikitool-release.json`. The exceptions - `kb/CONVENTIONS.md`, every
|
||||
`kb/*/COLLECTION.md`, `.wikitool-kb.json` - are in the
|
||||
[`dist upgrade` record](../reference/commands/distribution-and-versioning.md#dist-upgrade), along
|
||||
with what the command classifies and refuses.
|
||||
Reference in new issue
Block a user