Add mesh-knowledge, the knowledge base's first slice (hq issue 425, ADR 0290)
mesh/merge-gate pass: builds new: modules/mesh-knowledge, sent nowhere; no bus step; every machine composes with the change as it did without (4 of 4 compo…
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery superseded: a newer head of the same pull request

An agent learns what a decision says only by reading whole records. This module
holds every decision record and research effort whole in a database in the store,
with clauses that carry their own status and source, typed links read from both
ends, and every version. It answers ask, get and holds within a stated bound, and
takes a record through reserve, propose (refused on any of the 29 per-record
rules), review by someone other than the author, and acceptance, on the
operator's proven warrant when the record needs it. The 4 whole-set rules make
the module unhealthy when they fail. The import reads hq at a commit through the
records module, never overwrites, and changes nothing when run again. Prose
decisions get drafted clauses that a second judgement must confirm before they
are served.
This commit is contained in:
2026-10-11 04:58:35 +02:00
parent 27cb95e98f
commit ba7b455719
50 changed files with 11883 additions and 0 deletions
@@ -0,0 +1,86 @@
---
status: graduated
initiated: 2026-10-10
touches:
- 02-DECISIONS/0019-how-this-repository-works.md
- 02-DECISIONS/0080-the-development-cycle-is-checked.md
- 00-META/process/03-issues.md
- 00-META/glossary.md
- the runtime's state verbs and the SDK's state interface
became:
- 03-DESIGN/01-to-be/53-the-issue-tracker.md
- 02-DECISIONS/0275-the-mesh-numbers-and-tracks-its-issues-in-a-module-of-its-own-and-hq-keeps-only-the-narrative-of-a-design-or-governance-issue.md
---
# 042 — The mesh keeps its own issues
## What
Whether the mesh should keep its issues in a module of its own, on the bus, instead of only as
numbered folders in this repository's `04-ISSUES/`, and what the smallest useful first version of
that module is. The operator's ask of 2026-10-10: work on a ticketing system on the bus, consolidate
every current issue into it, start small and extend the module along the way.
The effort answers six questions, in [01-findings.md](01-findings.md):
1. The smallest useful first slice: what the module holds, which tools it serves, which events it
emits, and what it explicitly leaves out.
2. Where the truth lives during and after the move: the folders here, the module, or a split
between them, and what each choice does to the public-repository rule, playbook 03 and the hq
checks.
3. How the 357 issue folders are imported and consolidated (duplicates, still-open ones, grouping).
4. How the controller's conditions, the router's asks and deliveries relate to issues.
5. How the operator sees issues: through the mesh's tools first, through a view onto the bus later.
6. The open questions for the operator, and the slices after the first.
The operator also named an existing project as inspiration: *hits*, a headless issue tracker built
as NATS micro services on JetStream, with an MCP server for agents. Its model is studied in the
findings, along with whether to reuse its code or build on its model.
## Why
Numbers collide. Playbook 03 tells an author to take the next free number across `main` and every
open pull request, and [issue 155](../../04-ISSUES/155-two-records-may-share-a-number-and-nothing-says-so/00-report.md)
records the second collision reaching `main` with every check passing. `cycle.py` now refuses two
records sharing a number, which catches a collision after it happens and does not prevent one. On
`main` at the time of writing, 31 commits mention a renumbering. On 2026-10-07 three decision numbers
collided in one evening. On 2026-10-10 an open pull request still carries a branch named for issue
349 and a title naming issue 350, because 349 was taken on `main` first. The operator's answer on
2026-10-07 was that numbering belongs to the mesh's own issue tracker.
Status is also hard to see. Of 357 issue folders on `main`, 73 are not closed (27 `open`, 45
`located`, 1 `diagnosing`). Finding them means reading frontmatter across the repository.
Nothing connects a condition the controller raises to the issue that would fix it: on 2026-10-10 a
condition was silenced with the words "hq issue to follow", and nothing records whether one followed.
## What it touches
- [ADR 0019](../../02-DECISIONS/0019-how-this-repository-works.md) (statuses live in frontmatter) and
[ADR 0080](../../02-DECISIONS/0080-the-development-cycle-is-checked.md) (the cycle is checked), with
playbook [03](../../00-META/process/03-issues.md) and `00-META/checks/cycle.py`.
- [ADR 0201](../../02-DECISIONS/0201-a-module-keeps-its-current-state-in-key-value-buckets-it-declares-and-reaches-through-the-runtime.md)
(a module keeps its state in key-value buckets it declares),
[ADR 0197](../../02-DECISIONS/0197-every-tool-announces-itself-on-the-bus-in-the-nats-services-protocol.md)
(tools announce themselves on the bus) and
[ADR 0041](../../02-DECISIONS/0041-events-are-a-relationship.md) (events are declared).
- The conditions of [ADR 0231](../../02-DECISIONS/0231-a-healer-acts-on-what-observation-raised-and-only-observation-says-it-worked.md)
and the asks of [ADR 0259](../../02-DECISIONS/0259-an-ask-is-a-message-on-the-bus-any-channel-answers-it-and-the-asker-acts-on-a-warrant.md).
- The records module, which indexes this repository for agents.
**Graduated 2026-10-10** into [ADR 0275](../../02-DECISIONS/0275-the-mesh-numbers-and-tracks-its-issues-in-a-module-of-its-own-and-hq-keeps-only-the-narrative-of-a-design-or-governance-issue.md)
and [to-be 53](../../03-DESIGN/01-to-be/53-the-issue-tracker.md). The operator delegated the five open questions; each
was decided as recommended. `touches:` names what the decision changes.
## Words
The glossary's word for one of these is **issue** (*The record*: "a report in `04-ISSUES/`"). This
effort keeps that word for the thing the module holds, and proposes **issue tracker** for the module
that holds them, with the module named `issues`. It does not use *ticket*, the operator's word in
the ask, or *item*, the word hits uses. If the glossary entry is widened (an issue is then held by
the issue tracker, and its narrative may live in `04-ISSUES/`), *ticket* would go on its *Not:* line.
That change belongs to the decision this effort graduates into, not to the effort itself.
## Open questions for the operator
Listed in [01-findings.md § 7](01-findings.md#7-open-questions-for-the-operator), each with a
recommendation.
@@ -0,0 +1,259 @@
# 01 — Findings
Measured on 2026-10-10 against `main` of this repository, the open pull requests on the forge, the
controller's open conditions, the module SDK's source, and a clone of the hits repository at its
release v0.7.0. Each section ends with what this effort recommends; section 7 lists what needs the
operator.
## 0. What exists today, measured
| Fact | Measured |
|---|---|
| Issue folders on `main` | 357, numbered up to 362; numbers 283 and 284 were never used, and 338, 341 and 350 sit on open pull requests (363 too) |
| Status in frontmatter | 283 `resolved`, 1 `wontfix`, 45 `located`, 27 `open`, 1 `diagnosing` |
| Frontmatter keys | `status`, `opened`, `located-in` on all 357; `fixed-by` 356; `amended-design` 344; `replay` 22; `replay-none` 44 |
| Size | 1.9 MiB of markdown in all; median folder 4.6 KiB, largest 25 KiB; 244 folders hold only a report, 111 also a diagnosis |
| Links between issues | 168 folders link another issue's folder (265 links); 193 name another issue in prose |
| `located-in` | free text: the controller is written `mesh-controller`, `cmd/mesh-controller`, `mesh-control` and as file paths; 17 issues name nothing |
| Renumberings | 31 commits on `main` mention one; one open pull request still carries the old number in its branch name |
| The forge's own issues | none in this repository or the controller's; [issue 178](../../04-ISSUES/178-a-routed-name-resolves-to-a-provider-merely-told-it/00-report.md) was filed there once and moved here, because "its numbers collide with hq's pull request numbers in conversation" |
| Conditions | 10 open; 5 of kind `healer-wanted` (a hand repair repeated, "an automatic repair is wanted"); one silenced with "hq issue to follow" |
Two things follow. The collisions come from allocating a number by reading a repository that many
branches write at once; nothing short of one allocator removes them. And the frontmatter is a small,
regular data set (six keys, 1.9 MiB) that fits easily in the bus's key-value store under the limits
[ADR 0201](../../02-DECISIONS/0201-a-module-keeps-its-current-state-in-key-value-buckets-it-declares-and-reaches-through-the-runtime.md)
§6 sets (256 KiB a value, 64 MiB a bucket).
## 1. The first slice
**A Go module `issues`, one instance on the control-node, serving one seat held once for the mesh.**
Its state is in buckets it declares (ADR 0201), its tools announce themselves on the bus
([ADR 0197](../../02-DECISIONS/0197-every-tool-announces-itself-on-the-bus-in-the-nats-services-protocol.md)),
and every change is an event it declares under `emits`
([ADR 0041](../../02-DECISIONS/0041-events-are-a-relationship.md)). The module that delivers pull
requests already has this shape: Go, one mesh seat, two state buckets, declared events.
**What one issue holds in v1**, the frontmatter of today plus what the folders carry in prose:
- `number` (the key), `title`, `status` (`open`, `diagnosing`, `located`, `resolved`, `wontfix`),
`opened`, `closed`;
- `located-in`, from a registered list of repositories and modules rather than free text;
- `fixed-by` (pull requests or commits), `amended-design`, `replay` or `replay-none`;
- `notes`, appended and never edited: who wrote, when, and the text;
- `links`, typed: to another issue (`duplicates`, `relates-to`), to a pull request, a decision
record, a design, a condition key or a delivery;
- `record`: the path of the issue's folder in this repository, when it has one (section 2).
**Storage.** One bucket `issues` with one key per issue, keeping a short history per key, and a
`counter` key for the next number. The events (`opened`, `changed`, `noted`, `linked`) carry what
changed and who changed it, so they are the audit trail, and a view can watch the bucket. Section 6
compares this with a stream as the source of record.
**Tools** (verbs of the seat, found with `mesh_search`):
| Tool | Does |
|---|---|
| `open` | takes the next number atomically and writes the issue; refuses a title or report that is empty |
| `get` | one issue, whole |
| `list` | by status, by `located-in`, by a phrase in title or notes; compact rows, newest first |
| `move` | changes status, enforcing today's cycle rules at the write: `located` needs `located-in`; `resolved` needs `fixed-by`; a core issue needs `replay` or `replay-none`; `wontfix` needs a reason |
| `note` | appends to the trail |
| `link` / `unlink` | adds or removes a typed link |
| `import` | reads this repository's issue folders and writes them as issues with their own numbers (section 3); idempotent, reporting what it would change before it does |
**The caller is the bus credential, not an argument.** The module records who acted from the
identity the runtime gives it with each call, so an agent cannot write as someone else.
**Atomic numbering needs compare-and-set.** The SDK's state interface (v0.1.9) offers `get`, `put`,
`delete`, `keys` and `watch`, and no write that is refused when the key changed since it was read.
Whether the runtime's `mesh/state.put` takes an expected revision was not checked; the tool runner's
code was not read for this effort. Two ways out: add `create` and an `update` that takes the revision
read to the state interface (small, and every module with a counter gains it), or run exactly one
instance and number inside it. **Recommended: the first.** One instance is also right for v1, but the
counter should not depend on it.
> **Correction — 2026-10-10.** The finding above is wrong about the SDK. Version v0.1.9, the one read, had no
> compare-and-set, but the SDK's Go state interface already had it from go/v0.1.10: `Create` (writes a key only if it
> does not exist) and `Update` (writes only at the revision the caller read), with the runtime's matching state
> verbs, both from [ADR 0259](../../02-DECISIONS/0259-an-ask-is-a-message-on-the-bus-any-channel-answers-it-and-the-asker-acts-on-a-warrant.md).
> What the tracker's counter needed and slice 1 added is `Increment`, in go/v0.1.12, which reads a counter and writes
> the next number with `Create` or `Update` at the revision it read, retrying a bounded number of times. The
> recommendation stands; the work it named was smaller than stated. ADR 0275 carries the same correction.
**Explicitly left out of v1:** decision and research numbers (slice 2), any change to playbook 03 or
the hq checks (slice 2), duplicate finding beyond links (slice 3), opening an issue from a condition
(slice 4), a web view (slice 5), full-text or semantic search, priorities, claims, notifications, and
the forge's own issues.
## 2. Where the truth lives
| | (a) Split: the tracker holds the issue, hq holds the narrative | (b) Everything moves | (c) The tracker mirrors hq |
|---|---|---|---|
| Number | tracker | tracker | hq, so collisions stay |
| Status, `located-in`, `fixed-by`, links | tracker | tracker | hq frontmatter, copied |
| Report and diagnosis | hq folder for a design or governance question; notes in the tracker for the rest | tracker notes | hq |
| Public-repository rule | holds: hq keeps only scrubbed narrative, the tracker is private and may name nodes | the public record of design questions is lost | holds |
| Playbook 03 | changes: open in the tracker first, write a folder only for a design or governance question | replaced | unchanged |
| `cycle.py` | its issue rules move into the tracker's `move`, refused at the write instead of at merge | same | unchanged, and a second copy to keep in step |
| Records module | still finds the narrative; the tracker's `list` and `get` serve status | finds no issues | unchanged |
Option (c) keeps the collisions and adds a second copy that can drift, so it answers neither of the
operator's problems. Option (b) loses the public record that hq exists to be
([ADR 0019](../../02-DECISIONS/0019-how-this-repository-works.md)), and with it the
symptom-to-component memory that playbook 03 says is never deleted.
**Recommended: (a).** Every issue gets a number from the tracker, including ones that never need a
document here (a node's quirk, an operational follow-up, which playbook 03's table already sends
elsewhere). A design or governance issue also gets a folder `04-ISSUES/NNN-slug/` with the tracker's
number, whose frontmatter shrinks to a pointer (`issue: NNN`) and whose prose stays reviewed in pull
requests. Status lives in one place, the tracker. This reverses ADR 0019's "statuses live only in
frontmatter" for issues and moves part of ADR 0080's check, so it needs a decision record that
supersedes those parts, written when this effort graduates.
**Numbering continues.** The tracker's counter starts above the highest number on `main` and on open
branches at the moment of the switch (today 363, so the first new issue is 364). Numbers 283 and 284
stay unused, and a number never moves again.
**During the move**, hq frontmatter stays authoritative until the import has run and been checked
against it, and one pull request then removes the status keys from every report and changes playbook
03 and `cycle.py` together. There is no period in which both are written.
## 3. Importing and consolidating the 357 issues
**Import.** `import` reads the folders through the records module (`records_list`, `records_read`),
over the bus, rather than from a checkout. Each folder becomes an issue with its own number, its
frontmatter as fields, its title from the heading, the commit it was read at, and its path as
`record`. Folder-to-folder links (265) become `relates-to` links. The four open pull requests'
issues (338, 341, 350, 363) are reserved as numbers and imported when they merge.
**Consolidating** means four concrete passes, each producing a list for the operator or an agent to
confirm, never applying itself:
1. **Close what is fixed.** A `located` or `open` issue whose `located-in` repository has a merged
pull request naming its number is a candidate for `resolved`. hits's auditor (section 6) checks
exactly this against git and is the model.
2. **Merge duplicates.** Candidates are pairs that link each other and share a `located-in`, and
pairs whose titles share most words. A confirmed duplicate is closed `wontfix` with a
`duplicates` link and the reason, keeping its number and folder.
3. **Normalise `located-in`** onto the registered list (`mesh-controller`, `mesh-catalog`, the
node-engine's repository, a module's name), keeping the file paths as a note.
4. **Group the 73 not closed** by domain (the glossary's domains, ADR 0244) and by `located-in`, so
the operator sees, for example, how many open issues the delivery domain carries.
## 4. Conditions, asks and deliveries
A condition is one open fact, raised and cleared only by observation (ADR 0231); an issue is a
question someone must answer. They are different things, and a condition must not be closed by
closing an issue. But a condition can say that a fix is owed: five of today's ten are `healer-wanted`,
and one silence says "hq issue to follow".
Recommended for a later slice (slice 4): an issue links a condition by its key; the controller shows
the link on the condition; and for `healer-wanted` the controller opens one issue per condition key
when it first raises it, linked both ways, instead of an agent remembering to. A silence whose reason
names an owed issue should carry its number. Asks
([ADR 0259](../../02-DECISIONS/0259-an-ask-is-a-message-on-the-bus-any-channel-answers-it-and-the-asker-acts-on-a-warrant.md))
stay with the router: the tracker asks nothing in v1. Deliveries relate through pull requests: when
the forge says a pull request merged, the tracker notes it on every issue the pull request names, and
offers `resolved` (slice 4). Nothing in v1 depends on these.
## 5. How the operator sees issues
First through the mesh's tools: `list` returns rows short enough to read in a chat, `get` one issue
whole, and every agent session reaches them through the mesh MCP server. The responder (`@mesh`,
ADR 0234) answers from read verbs, so "what is open in delivery" works on a channel once the seat
exists. Later a view onto the bus: the `issues` bucket watched over the bus (a browser reaches it through a WebSocket listener, which this effort did not check for),
read-only, which the operator named as a future direction for every module's state. Nothing in v1
blocks it, because the state is already a bucket and every change an event.
## 6. hits: what it is, and what to take from it
**What it is.** A headless issue tracker in Go (module `github.com/impire-io/hits`), with no web
interface and no HTTP interface: NATS micro services on JetStream, a command line for people and a
stdio MCP server for agents. One author, 81 commits between 2026-09-02 and 2026-09-14, release
v0.7.0, about 9,700 lines of Go outside tests and 21 test files, tested against an embedded NATS
server. Its model descends from a method like this repository's: a sibling `hits-hq` with numbered
playbooks and decisions.
**Data model.** An *item* has a type (`bug`, `task`, `improvement`), a status (`open`, `diagnosing`,
`located`, `blocked`, `resolved`, `wontfix`), a priority, `located-in` (from a registered list of
*projects*), `fixed-by` (pull request, commit or action), `amended-design`, append-only notes, typed
links (`duplicates`, `relates-to`), a claim, a block, and a tombstone for a filing mistake. Its
statuses and fields are this repository's frontmatter almost key for key. Projects, *initiatives*
(groups of projects that also scope the numbering) and *releases* are registered lists that are
never reused once retired.
**How it stores.** The source of record is a stream `hits-ops` with subjects
`hits.ops.item.<id>` (and `project`, `initiative`, `release`). Every write appends an operation,
published with the per-subject expected last sequence, so two writers to one item cannot both win.
A bucket `hits-state` holds the folded snapshot of each item, the registered lists and one counter
per initiative. The counter is advanced by compare-and-set and an item is numbered only after it is
validated, so a refused create spends no number. On start it rebuilds the bucket from the stream.
**Services and subjects.** One micro service `hits` with 23 endpoints under `hits.api.*` (`create`,
`get`, `edit`, `transition`, `claim`, `release`, `block`, `unblock`, `link`, `unlink`, `note`,
`tombstone`, and register, list and retire for projects, initiatives and releases); three index
services, each a projection of the stream: full text (`hits.search.query`, a local index on disk),
a graph (`hits.graph.neighbors`, `hits.graph.walk`) and an optional semantic search that needs an
outside embedding provider.
**MCP tools.** 27, one per endpoint: `create_item`, `get_item`, `edit_item`, `transition_item`,
`claim_item`, `release_item`, `block_item`, `unblock_item`, `link_items`, `unlink_items`,
`note_item`, `tombstone_item`, the project, initiative and release tools, and `search_items`,
`semantic_search`, `graph_neighbors`, `graph_walk`. One server serves one agent under one actor.
Its command line also has an auditor that checks every item's `fixed-by` against git and finds
merged pull requests whose items are still open.
| | Adopt as it is | Adapt to the mesh |
|---|---|---|
| Statuses, fields, append-only notes, typed links, tombstone | yes: they are already ours | — |
| Stream of operations as the record, bucket as its projection | the pattern | the stream and bucket declared in the manifest and created by the controller (ADR 0201 §3), not created by the service on start |
| Expected-sequence writes; compare-and-set counter; number only after validation | yes | needs compare-and-set in the state interface (section 1) |
| The auditor | the idea, as a tool | reads the forge through the gitea module, not a local clone |
| Subjects `hits.api.*`, its own MCP server | no | the seat's verbs, served by the tool runner and found with `mesh_search` |
| Actor named by the caller | no | the caller is the bus credential the mesh issued |
| Contexts, OAuth device flow, semantic search | no | the mesh issues credentials; no outside provider |
| *item*, *project*, *initiative*, *release* | no | *issue*; a repository or module; no initiatives in v1; *release* is already a homonym here (glossary) |
**Reuse its code, or build on its model?** Its licence is the Sustainable Use License 1.0: use and
modification are allowed for internal business, non-commercial or personal use, and distribution only
free of charge and for non-commercial purposes. It is not an open-source licence. Running it on a
personal mesh is allowed; vendoring it into the catalogue binds the catalogue to that licence for as
long as the code is there. Its services create their own stream and bucket on start, which ADR 0201
§3 forbids a module to do; its subjects carry no prefix and assume one tracker per bus account; its
words clash with the glossary. Changing those means a fork of a two-week-old, single-author project.
v1 here is the item store without the indexes: roughly the size of the module that delivers pull
requests.
**Recommended: build our own module on hits's model,** and say so in the module's README. Do not
depend on or vendor its code. Revisit only if its licence changes, or if its author agrees to a
licence for this use.
## 7. Open questions for the operator
1. **Should the tracker hold every issue's number and status, while hq keeps a folder only for design
and governance issues, pointing at the tracker by number (option (a) in section 2)?**
Recommended: yes. It ends the collisions, keeps the public record, and moves the cycle rules into
the write, where they refuse instead of report.
2. **Should we build our own `issues` module on hits's model rather than run or vendor hits?**
Recommended: build our own. hits's licence limits distribution, its services create their own bus
resources (against ADR 0201), and its words clash with ours. Its model, its compare-and-set
numbering and its auditor are worth copying.
3. **Should the tracker also hand out decision and research numbers?** Recommended: yes, in slice 2.
Decision numbers collided three times on 2026-10-07; the same counter mechanism serves any
sequence, and the tracker then reserves the number when a branch starts.
4. **Should the word stay *issue*, with the module named `issues` and *ticket* retired?**
Recommended: yes. *Issue* is the glossary's word for this, and one word per thing (ADR 0244).
5. **Should the controller open an issue by itself for a `healer-wanted` condition, or only link
one an agent or the operator opened?** Recommended: link only, until slice 4 has run for a week;
then open by itself for `healer-wanted`, one issue per condition key.
## 8. Slices after the first
| Slice | Delivers | Done when the operator sees |
|---|---|---|
| 1 | The `issues` module: open, get, list, move, note, link, import; compare-and-set in the state interface | the next issue number comes from the tracker and the 73 not closed are listed by one call |
| 2 | Decision and research numbers from the tracker; playbook 03 and `cycle.py` changed; status keys removed from every report | two agents opening at once get two numbers, and no hq pull request is renumbered |
| 3 | Consolidation: the four passes of section 3, and an audit tool for `fixed-by` against the forge | the not-closed list shrinks by what was already fixed or duplicated, each with its reason |
| 4 | Conditions and pull requests linked; `healer-wanted` opens an issue | a condition shows its issue, and a merge notes the issues it names |
| 5 | A read-only view onto the `issues` bucket | the operator reads issues in a browser without an agent |
@@ -0,0 +1,112 @@
---
status: graduated
initiated: 2026-10-10
touches:
- 02-DECISIONS/0019-how-this-repository-works.md
- 02-DECISIONS/0080-the-development-cycle-is-checked.md
- 02-DECISIONS/0153-the-record-is-read-by-a-module-and-the-console-lists-it.md
- 02-DECISIONS/0248-the-decision-index-is-generated-when-it-is-read-and-never-stored.md
- 02-DECISIONS/0274-an-agent-proposes-a-shared-instruction-section-and-every-node-takes-it-only-on-the-operators-warrant-which-it-proves-itself.md
- 02-DECISIONS/0275-the-mesh-numbers-and-tracks-its-issues-in-a-module-of-its-own-and-hq-keeps-only-the-narrative-of-a-design-or-governance-issue.md
- 03-DESIGN/01-to-be/53-the-issue-tracker.md
- 00-META/process/
- 00-META/checks/
- 00-META/glossary.md
- the records module's tools
became:
- 02-DECISIONS/0290-a-decision-record-lives-whole-in-the-knowledge-base-made-of-clauses.md
- 02-DECISIONS/0291-an-issue-lives-whole-in-the-issue-tracker.md
- 03-DESIGN/01-to-be/55-the-knowledge-base.md
---
# 045 — A knowledge base the mesh writes: decisions, guidelines, policies
## What
Whether the mesh should **write** its own knowledge (decision records, guidelines, policies, designs and
playbooks) through a module's verbs, instead of only **reading** hand-written markdown, and whether it
should hold light content beside them: meeting notes, architectural drawings and blog posts.
The operator's purpose, given on 2026-10-11: the record becomes **shared knowledge that agents reach
through tools**, so that an agent gets what was decided as an answer instead of reading markdown files,
and a session no longer has to start in this repository's folder to know it. A session still starts here
for other work, such as writing a record. The operator later put the light content last, and decided that
an issue's content belongs in the issue tracker rather than in this repository.
Today the record is read-only. The `records` module reads this repository and serves it to sessions
([ADR 0153](../../02-DECISIONS/0153-the-record-is-read-by-a-module-and-the-console-lists-it.md)).
Everything in it is written by hand as markdown and reaches `main` through a pull request; the hq checks
in `00-META/checks/` judge it only after it is written. The issue tracker has already taken issues out
of this path: it numbers and tracks them in a module of its own, and hq keeps only the narrative
([ADR 0275](../../02-DECISIONS/0275-the-mesh-numbers-and-tracks-its-issues-in-a-module-of-its-own-and-hq-keeps-only-the-narrative-of-a-design-or-governance-issue.md)).
## Why
Numbers collide, and the checks find a collision only after it happens. On 2026-10-10, research number
043 was taken by one open pull request while a second session held an unpushed branch claiming the same
number for a different effort. On 2026-10-07 three decision numbers collided in one evening. A
record written by a verb could take its number from one counter, the way the issue tracker does.
Reading is costly too. Answering one question about what holds today means reading several records
whole, and the generated decision index is larger than one tool answer may be.
Some knowledge also has no home. This repository is public, so it may not name nodes or describe
operations; the mesh's knowledge about its own installation lives in agents' private notes, in no
shared and searchable place.
## The two options to weigh
- **A. Git stays the store.** The `records` module gains write verbs (propose, amend as a progressive
insight, supersede, retire), each of which makes a branch and a pull request through the forge. Numbers
come from the issue tracker's counters, as a further sequence beside `issue`
([to-be 53](../../03-DESIGN/01-to-be/53-the-issue-tracker.md) §4).
- **B. A store is the truth, and hq is its rendered public export**, as the issue tracker already is for
issues (ADR 0275).
## The questions
1. **Types and lifecycles.** Which kinds of knowledge exist: decision, guideline, policy, design,
playbook, and any others? What lifecycle does each have (proposed, accepted, superseded, retired;
to-be and as-is for a design), and which transitions does each kind allow?
2. **Numbering from one counter.** One sequence per kind, or one for all? How is a number reserved for
a draft that is later abandoned, given that a number is never reused? How are today's collisions
(043 is the latest example) resolved on import?
3. **Typed links.** Which links between records are needed: supersedes, amends, implements, cites,
decided-by, and others? Which of them does frontmatter express today (`became:`, `decisions:`,
`fixed-by:`), and which exist only as prose links that no check reads?
4. **Approval.** A decision or a policy changes how the mesh is run. Should accepting one require the
operator's warrant on a channel that proves who answered, as a shared instruction section already
does ([ADR 0274](../../02-DECISIONS/0274-an-agent-proposes-a-shared-instruction-section-and-every-node-takes-it-only-on-the-operators-warrant-which-it-proves-itself.md))?
Which kinds need it, and which may an agent write on its own?
5. **Public and private knowledge.** Node names and operational knowledge have no home today. Does the
knowledge base hold private records beside public ones, with a rule for which may be exported? How is
a record checked so that nothing private reaches the public repository?
6. **Checks as refusals at write.** `records.py`, `cycle.py`, `words.py` and `index.py` judge a pull
request after it is written. Which of their rules can become refusals of the write verb itself, and
which only make sense across the whole record?
7. **Import.** How are the decision records (274 on `main` on 2026-10-11, numbered up to 0289), the designs and the playbooks imported without
losing their history, their numbers or their links? Is the import repeatable, as the issue tracker's
is?
8. **What git gives today.** Review on a pull request, line comments, a history of every change with
its author, a diff, a revert, and the forge's required checks. What of that would a store have to
rebuild under option B, and what would option A lose by making every write a pull request?
## Words
The glossary has words for **decision record**, **design**, **playbook** and **the record** (the
`records` module), but none for **guideline** or **policy** in this sense, and *policy* is already used
for a module's **upgrade policy**. This effort uses "guideline" and "policy" as plain descriptions, and
"knowledge base" for the whole, until a decision settles the words through the glossary's own rules.
## What it touches
How this repository works (ADR 0019) and how the development cycle is checked (ADR 0080), the
`records` module (ADR 0153) and the generated decision index (ADR 0248), the issue tracker and its
counters (ADR 0275, to-be 53), approval on a proving channel (ADR 0274), the playbooks in
`00-META/process/`, the hq checks in `00-META/checks/`, and the glossary.
## Documents
- [01 — Findings](01-findings.md): what exists, measured on 2026-10-11; where the truth lives (the store
for each record's structure and rules, this repository for its reasoning); issues whole in the issue
tracker; the order of work; and what is still open.
@@ -0,0 +1,295 @@
# 01 — Findings
Measured on 2026-10-11 against `main` of this repository, the records module and the issue tracker as they
answer through the mesh MCP server, and the hq checks in `00-META/checks/`. Section 0 is what exists; section 1 is where the truth lives, which
decides the rest; sections 2 to 9 answer the overview's questions and the operator's additions; section 10 is
the order of work, and section 11 what is still open.
## The purpose, in the operator's words
The operator gave the purpose on 2026-10-11: the decisions and the rest of the record should become **shared
knowledge that agents reach through tools**, instead of reading markdown files, and a session should no longer
have to start in this repository's folder to know what was decided. A session still starts here for other work,
such as writing a record. Later that day the operator widened the scope to **light records**
(meeting notes, architectural drawings, blog posts) and then put them last; asked for a structured store of
the decisions and research efforts; and decided that an issue's content belongs in the issue tracker, not in
this repository.
That purpose moves the weight of the overview's questions. Where the truth is stored (option A or B) matters
less than **what an answer looks like**: an agent needs the rule that holds now, with the records it rests on,
in one call and a few kilobytes.
An acceptance sentence for the whole effort: *a session started in any folder on any node asks "what holds
about the merge window, and why" and gets, in one call of under 4 KiB, the rules in force, the records they
come from, what changed them since, and the commit it was read at.*
## 0. What exists today, measured
| Fact | Measured |
|---|---|
| Records on `main` | 274 decision records, 44 research efforts (119 files), 55 to-be and 16 as-is designs, 8 playbooks, 361 issue narratives (479 files): 951 files, and the glossary |
| Size | 6.9 MB of markdown, about 1.7 million tokens at four bytes a token; decision records alone 2.6 MB (about 650,000 tokens) |
| One question, today | "what holds about the merge window": `records_search` gives 26 places in 11 documents; reading the five records and the design they point to is 85 KB, about 21,000 tokens |
| The decision index | `records_decisions` answers 77 KB in one piece; the agent's tool-answer limit refused it inline, and it had to be read from a file in parts |
| What `records_search` does | a literal, case-insensitive phrase match giving document, line and heading; no ranking, no notion of which record is still in force |
| Frontmatter of a decision | `topic`, `status`, `date`, `deciders` on all 274; `extends` on 229 (182 with the folder prefix, 47 without); `supersedes-in-part` on 15 (21 links), `supersedes` on 8, `superseded-by` on 9, `rests-on` on 2, `amended` on 1 |
| Status of a decision | 265 `accepted`, 9 `superseded`; no record is marked as changed in part, though 21 links say so |
| Supersession said only in prose | 6 records say they supersede, amend or replace another and carry no field for it; 7 records carry a "superseded" marker in the body, of which only 1 has the field or the status |
| Progressive insights | 39 records carry the dated marker, 55 markers in all; 2 more use an older form |
| Change after writing | 189 of 271 traceable decision records were changed after the commit that created them; 34 paths were changed five times or more |
| Pace | commits touching decision records: 87 in the last ten days of August, 160 in September, 274 in the first eleven days of October |
| Number collisions | 16 decision numbers were taken twice by sessions working at once (2026-09-25 to 10-10), each repaired by a renumbering commit; 15 numbers on `main` are gaps; no research number collided on `main` (the 043 collision of the overview never reached it) |
| A decision's rules | 263 records have a `Decision` section; only 37 of them write it as numbered rules with a bold lead (198 rules in all); the rest write prose |
| Drawings | no file but markdown in the repository; no diagram source; 22 designs carry diagrams drawn in text characters |
| Knowledge with no home | the repository is public, and its README says what only makes sense to someone who knows this installation "is either a note in the wrong place or a disclosure"; one agent's private notes hold 89 files for one repository on one node, and issue 367 (a session's memory stays on the node where it was learned) is open |
| The hq checks | 33 rules: 25 judge one record, 4 the whole record, 4 the prose (section 7) |
Three things follow.
1. **The cost of an answer is the problem, not where the files are.** 85 KB for one question, and an index
too large for one tool answer, mean a session reads in parts or reads less than it should. Moving the
markdown into a store without changing the shape of the answer would keep that cost.
2. **The data that makes an answer cheap is missing or loose.** What a record decided is prose in 226 of 263
records. Which records changed which is half in fields, half in prose: 6 records say it only in prose, and
6 more carry a body marker that no field or status agrees with.
Status says `accepted` for records that 21 later links change in part.
3. **The records change after they are written, and that is reviewed.** 189 of 271 decision records were
edited after creation, every edit through a pull request with a human approval. Whatever is built must keep
that review for what governs the mesh.
## 1. Where the truth lives: what a clean start would choose
The operator asked on 2026-10-11 which option this effort would choose with no prior setup. The answer
decides the rest, so it comes first.
**The unit the mesh reasons about is a rule, not a file.** A decision is a handful of rules. A later decision
changes one of them: ADR 0282 replaces one rule of ADR 0276 and leaves the others standing. Git and markdown
know files only, so that change can be written only as prose. The earlier record keeps `accepted`, and every
reader must read both records to learn what holds. Section 0's loose links all have this one cause: 21 partial
supersessions with no reverse, 6 said only in prose, 6 markers no field agrees with.
**A store can hold what a repository cannot.** In a store, each rule is an entry with its own status and links
("in force", "changed by rule 2 of 0282"). A write that leaves the record inconsistent is refused when it is
made, not found by a check afterwards. Numbers come from one counter. "What holds now" is one query.
**What git does best is review of prose.** The reasoning behind a decision (its context, the options weighed,
its consequences) is read and commented on by people. Agents rarely ask for it. They ask what holds.
So a clean start divides **by layer, not by kind**:
- **the store is the truth for the structure** of every governing record: its number, title, kind, topic,
status, its rules one by one, and its typed links, at the level of the rule where a link is about a rule;
- **this repository keeps the reasoning** as markdown under the store's number, reviewed in pull requests.
That is the division the issue tracker already made for issues
([ADR 0275](../../02-DECISIONS/0275-the-mesh-numbers-and-tracks-its-issues-in-a-module-of-its-own-and-hq-keeps-only-the-narrative-of-a-design-or-governance-issue.md)):
the tracker holds number, status and links, and hq held the narrative. Section 9 takes issues one step further,
on the operator's word: their narrative leaves this repository too.
Option A (git stays the truth, and a module's write verbs open pull requests, with numbers from a counter) was weighed and is not chosen. It keeps review, but it
keeps the rule-level links in prose and the refusals after the fact, which is the cause of section 0's mess.
Option B in full (the reasoning moves into the store too) is not chosen either: it would rebuild review, line
comments, history and diff for prose, which git already gives.
## 2. Kinds and their lifecycles
| Kind | Lifecycle | Notes |
|---|---|---|
| decision | proposed → accepted → superseded; a rule can be changed or superseded on its own; a dated progressive insight corrects a fact | 274 records |
| research | active → graduated or abandoned | 44 efforts |
| design | proposed → designed → in-progress → implemented, in one of two layers (to-be, as-is) | 71 documents; a later slice (section 10) |
| playbook | in force, or retired | 8, with no frontmatter today; a later slice |
| glossary entry | in force, or retired with its replacement | one file today; a later slice |
The first slice holds decisions and research, the two the operator named.
A **guideline** or a **policy**, which the overview named, has no record of its own today. What the operator
calls a policy is written as a decision; what an agent should do is a shared instruction section (ADR 0274) or
a personal rule ([ADR 0273](../../02-DECISIONS/0273-the-agents-home-is-the-modules-and-the-operators-personal-rules-and-lessons-are-registered-through-it.md)).
No new kind is proposed for either.
**Light records** (meeting notes, drawings, blog posts) were added to the scope by the operator on 2026-10-11
and put last by the operator the same day. They govern nothing, need no review to exist, and are private by
default; a drawing's file belongs in an object-store bucket
([ADR 0288](../../02-DECISIONS/0288-the-object-store-stays-on-the-maintained-fork-and-garage-is-chosen-only-after-the-consumers-s3-features-are-counted.md))
with a text description an agent reads. They are a later slice (section 10) and shape nothing in the first.
## 3. Numbering from one counter
The 16 concurrent collisions all come from reading the next number off a repository that many branches write
at once. The issue tracker hands out numbers by compare-and-set on a counter key
([to-be 53](../../03-DESIGN/01-to-be/53-the-issue-tracker.md) §4); the knowledge base asks it for every number.
- **One sequence per kind**: decision, research, and later design. Numbers keep their meaning in conversation
("ADR 0276").
- **A reserved number is spent.** A proposal that is abandoned keeps its number with status `withdrawn`, who
reserved it and when. Gaps already exist (15 on `main`) and harm nothing; a reused number would.
- **The counter starts above the highest number on `main` and on every open branch**, as the tracker's did.
- **The counters stay where ADR 0275 put them.** Its rule 8 decided that the issue tracker hands out decision
and research numbers too, in its second slice; to-be 53 §4 so far names only the issue counter. The knowledge
base's `reserve` asks the tracker for the number, so one counter holds every sequence and no accepted record
is superseded for it.
## 4. Rules and typed links
**A rule** is one short statement a decision makes, numbered within its record ("0276 rule 1"), with its own
status: `in force`, `changed by` a rule of a later record, or `superseded`. 37 of 263 decision sections already
write numbered rules with a bold lead (198 rules); the other 226 write prose, from which the import drafts the
rules (section 7).
The links the first slice holds, each stored in both directions:
| Link | From → to | Today |
|---|---|---|
| supersedes | record → record | `supersedes` / `superseded-by`, 9 of 9 mirrored |
| changes | rule or record → rule | `supersedes-in-part` on 15 records, one direction; 6 more only in prose |
| extends | record → record | `extends` on 229, two spellings |
| became | research → decision or design | `became` |
| decided by | design → decision | `decisions` |
| corrects | progressive insight → record | the dated marker, 55 in 39 records |
| cites | any → any | prose links: 1,785 decision to decision, 469 decision to issue |
"What holds about X" is then: the rules matching X whose status is `in force`, each with its record and the
changes that brought it to its present wording.
## 5. Approval
A decision governs how the mesh is built and run, so it needs a human approval before it holds.
- **Proposing** reserves a number and writes the structure with status `proposed`. Any agent may propose.
- **Accepting** happens when the pull request carrying the record's reasoning merges with its human approval
and both required checks. The knowledge base sees the merge on the bus and moves the record and its rules to
`accepted` and `in force`, and every rule it changes to `changed by`. The merge is the warrant; a second
approval beside it would split the evidence.
- **A progressive insight** is a correction of fact and goes the same way: a pull request on the reasoning,
and the structure recorded on merge.
- A **structural write without a pull request** (a link added, a rule's wording fixed) is refused for an
accepted record. Changing what an accepted record says is a new decision, as it is today.
## 6. Public and private
This repository is public and stays so. The reasoning of a decision or research effort stays here, public.
The structure in the store is derived from public records and may be exported here as generated pages, like
the decision index of [ADR 0248](../../02-DECISIONS/0248-the-decision-index-is-generated-when-it-is-read-and-never-stored.md),
generated when read and never stored.
What changes is the issues' narrative (section 9): it moves into the tracker, which is the mesh's own and not
public. The public repository loses those narratives. ADR 0275 kept them public on purpose, citing *the mesh
remembers*; the tracker remembers them as well, but only for the mesh. Whether a resolved issue's narrative is
ever exported publicly is a question for the decision that supersedes that part of ADR 0275.
Knowledge about this installation (one agent's 89 private notes, issue 367) stays out of scope. Agents' lessons
and rules belong to the agent module's register (ADR 0273).
## 7. Checks as refusals at write, and the import
Of the 33 rules the hq checks enforce, **25 judge one record** and become refusals of the knowledge base's
write verbs: a number taken twice, a supersession not mirrored, a cited record not accepted, a record without a
topic, a research effort graduated without `became`. Two need a look beyond the record: a write that
supersedes must check who cites what it supersedes. **4 judge the whole record** and **4 judge the prose**;
those stay checks on this repository, run on every pull request. As frontmatter leaves this repository
(section 8), the 25 leave `records.py` and `cycle.py` with it, and a check is added that a record's reasoning
names a number the knowledge base holds.
**The import** reads this repository at a commit through the records module and writes each decision and
research effort at its own number: status, topic, date, deciders, links, and its rules. Like the tracker's, it
creates what is absent, never overwrites, never advances a counter, and a second run changes nothing and says
so. Two things a reading cannot recover alone:
- **Loose links.** The 6 supersessions said only in prose, the 6 markers no field agrees with, the 47 bare
`extends` targets and the 21 one-way partial supersessions are repaired in this repository first, by one
pull request, so the import reads clean input.
- **The rules of 226 decisions written as prose.** The import drafts them through the agent's judgement
([ADR 0278](../../02-DECISIONS/0278-the-issue-tracker-checks-every-new-issue-for-a-duplicate-through-a-judgement-the-agent-makes-without-tools.md))
and stores them as `drafted`, not `in force`. A topic's drafts are confirmed by the operator or a reviewer in
one pass. An answer marks a drafted rule as drafted, and never presents it as the record's word.
## 8. What leaves this repository, and what stays
| | Today, in this repository | After |
|---|---|---|
| a decision's number, status, topic, links | frontmatter | the knowledge base; the file keeps the number in its name and heading |
| a decision's rules | prose in the `Decision` section | the knowledge base; the section stays as prose for the reasoning |
| a decision's context, options, consequences | markdown | unchanged |
| a research effort's status and `became` | frontmatter | the knowledge base |
| a research effort's documents | markdown | unchanged |
| the decision index | generated from frontmatter when read | generated from the knowledge base when read |
| an issue's report, diagnosis, resolution | `04-ISSUES/NNN-slug/` for 361 issues | the issue tracker (section 9) |
Frontmatter leaves a file only after the import has proven it holds the same values, one kind at a time, as
ADR 0275 did for issues' status.
## 9. Issues live in the issue tracker, whole
The operator decided on 2026-10-11 that an issue's content belongs in the issue tracker, not in this
repository. Measured on `main`: 361 issue folders, 1.9 MB; a folder's median is 4.4 KB and the largest
25.9 KB, well under a tracker value's 256 KiB. Every folder has `00-report.md`; 107 have a diagnosis, 9 a
resolution, 1 a progress file. Since ADR 0275, new issues live in the tracker already; the highest issue folder
is 364.
What the tracker needs to hold it:
- **an issue's documents**: the report it already holds, plus a diagnosis and a resolution as named documents
of the issue, each with its author and date, revised in place with history (notes stay append-only);
- **an import** of the 361 folders, repeatable, keeping each issue's number and dates;
- **links** from records: 250 files in this repository mention a path into `04-ISSUES/` 765 times, counted
as every `04-ISSUES/NNN` path in the text (markdown links alone are fewer, about 660). Those mentions become
plain "issue N" references that the knowledge base and the tracker resolve, rewritten in one pull request
once the import is proven; the link check in `records.py` then refuses a new link into `04-ISSUES/`.
This supersedes part of ADR 0275 (hq keeps the narrative of a design or governance issue), so it needs its own
decision record.
## 10. Slices
1. **Clean the input** (this repository, one pull request): repair the loose links of section 7, so the import
reads clean records. In review as hq pull request 305, which declares every partial supersession in both
records and adds the `partial-supersession` check. Beside it, a shared instruction section for every
session: take a decision or research number above every one on `main` and on open branches until the
counter exists, and name a partial supersession in a field, never only in prose.
2. **The knowledge base, decisions and research**: a Go module serving one seat held once for the mesh, on
the control-node beside the issue tracker. The import, the rules and links, and the answers; numbers come from the issue tracker (section 3):
| Verb | Answers |
|---|---|
| `ask` | the records and rules that bear on a question, ranked: number, kind, status, title, and whether each rule still holds or what changed it |
| `get` | one record: its rules, its links both ways, what changed it since; its reasoning's text only when asked |
| `holds` | for a topic or a record, the rules in force now, each with the record it comes from |
| `reserve` | the next number of a kind, from the issue tracker's counter (section 3), spent whether or not the record lands |
| `propose` | a new record's structure, status `proposed` |
| `import` | this repository's decisions and research at a commit, repeatable |
Storage is a database in the store, provided to the module: the structure is small, but `ask` needs ranked
full-text search, which the store's server does and a bucket does not. Every answer is bounded in size.
3. **Frontmatter leaves this repository**, kind by kind, once the import is proven equal.
4. **Issues whole in the tracker** (section 9): documents, import, links rewritten, folders removed.
5. **Designs, playbooks and glossary entries** into the knowledge base the same way.
6. **Light records**: notes, posts and drawings.
How a session reaches it: the knowledge base's tools reach every session through the mesh MCP server, and one
shared instruction section, approved by ADR 0274's route, tells every session to ask the knowledge base what
holds before reading files. A session needs this repository's folder only to write a record's reasoning.
Proved by the acceptance sentence at the top: the merge window question, asked from a session outside this
repository, answered in one call under 4 KiB, naming ADR 0276's rules in force and the one ADR 0282 changed.
## 11. What needs the operator
Decided by the operator on 2026-10-11: the store is the truth for structure and this repository for reasoning
(section 1); light records come last; issues live whole in the tracker (section 9).
Still open:
1. **Drafted rules (section 7)**: the 226 prose decisions get their rules drafted by the agent's judgement and
confirmed a topic at a time. Who confirms: the operator, or an independent reviewing agent with the operator
seeing a summary?
2. **Issues' public narrative (section 6)**: once issues leave this repository, is a resolved design or
governance issue ever exported here publicly, or does it stay the mesh's alone?
## Words
The glossary has **decision record**, **design**, **playbook** and **the record** (the records module). This
effort proposes, for a decision to settle through the glossary's own rules: **knowledge base** for the module
and its seat; **rule** for one statement a decision makes, with its own status; **governing record** for a kind
reviewed before it holds; and **note**, **post** and **drawing** for the light kinds. *Policy* stays the word
for a module's upgrade policy, and *guideline* is not proposed, since section 2 finds no record of that kind.