ADR 0300 was accepted with no home, so the knowledge base's whole-set rule fails and holds every rollout of mesh-knowledge.
24 KiB
layer, status, code, updated, decisions
| layer | status | code | updated | decisions | |||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| to-be | in-progress |
|
2026-10-11 |
|
53 — The issue tracker
The mesh keeps its issues in a module of its own, mesh-issues, one instance on the control-node, holding the seat
issue-tracker. It hands out every
issue's number from one counter that two callers can never both advance, holds each issue's status and links, refuses
a change the development cycle would refuse, and announces every change on the bus. An issue lives whole in the tracker, its report, diagnosis and
resolution included; hq keeps no issue content.
(ADR 0275,
from research 042;
ADR 0291 for an issue whole in the tracker.)
An issue is a question about the mesh that someone must answer. The issue tracker is the module that holds
issues, named mesh-issues; the seat it holds is issue-tracker. The glossary holds both words.
Acceptance. An agent opens an issue and gets the next number; two agents opening at the same moment never get the same one.
This document designs slice 1 in detail. The slices after it are listed in § 8, each with what the operator sees when it is done.
1. The parts
control-node
mesh-issues (seat issue-tracker, held once for the mesh)
├ its process, launched by the node runtime
├ bucket issues one key per issue, by number; a short history per key
├ bucket counters one key per sequence; slice 1 has `issue` only
├ bucket grooming the backlog pass of the duplicate check (§ 9)
├ setting owners the registered list `located-in` may name
├ verbs open, get, list, move, note, link, import, groom, set, tracking,
│ served by the control-node's tool runner
├ events opened, moved, noted, linked, imported, set, triaged, ready, ready-to-verify
└ consumes its own `opened` and `moved`: the triage (§ 9);
mesh-delivery's `transition`: when a fix is delivered (§ 10)
claude-code (on the same node)
└ claude_code_judge one question answered by the agent with no tools (§ 9)
any agent session, on any node
mesh MCP server ── mesh_search, mesh_describe, mesh_call ──▶ the verbs, over the bus
The module is written in Go against the SDK. It is built on the model of hits, a headless issue tracker on NATS, and carries none of its code; its README says so.
2. The seat and its verbs
The module declares the seat issue-tracker at mesh scope and claims it exclusively
(ADR 0126): there is one tracker per mesh. Its verbs are
the seat's tools, served through the tool runner and announced on the bus
(ADR 0197), so an
agent finds them with mesh_search ("open an issue") and calls them as issue-tracker.<verb>.
| Verb | Takes | Does |
|---|---|---|
open |
a title, the report as text, optionally located-in and a folder path |
checks the issue, then takes the next number and writes it; returns the number. Refuses an empty title or report, and an owner not on the registered list |
get |
a number | the issue whole: fields, notes, links |
list |
optionally a status, an owner, a phrase | compact rows, newest first, short enough to read in a chat: number, status, owner, title, and the worker with since and whether it is stale |
move |
a number, a status, and what that status needs; by for diagnosing and fixing, and take-over with a reason to take an issue over from another worker |
changes the status under the rules of § 5 |
note |
a number and a text | appends to the issue's notes; a note is never edited |
link |
a number, a kind and a target, or remove |
adds or removes a typed link |
set |
a number and any of severity, needs, suggested-owner |
changes them as a person decides (§ 10) |
tracking |
optionally condition keys | for every condition key an issue links, the issues that link it and where each stands: status, needs, worker, fix (§ 12) |
groom |
optionally confirm and a pass's id |
the duplicate check over the backlog (§ 9): without confirm, starts a dry run in the background and answers its id; with the id alone, its progress and findings; with confirm and the id, applies exactly that pass's findings, each checked again |
import (slice 3) |
optionally dry-run, a commit |
reads every issue folder of hq through the records module and writes each as an issue at its own number; creates what is absent, fills a status the tracker holds empty, never overwrites, never advances the counter; run again, changes nothing and says so |
Each verb's description says in plain words what it does and what it refuses, without the mesh's internal names, so
an agent picks the right one from mesh_search alone. A refusal says what was wrong and what would be accepted.
The caller is the bus credential, never an argument. The runtime hands the module the identity of each call's
caller; the module records that identity as who acted. No verb takes an actor. by on move is not one: it names
the issue's worker, who works on it, as the worker states it
(ADR 0284).
3. What one issue holds
number(its key),title,status(open,diagnosing,located,fixing,resolved,wontfix),opened,closed;worker, while it isdiagnosingorfixing: who works on it as who@node,sincewhen, andactive, the last time a caller did anything on it (§ 5);located-in: entries of the registered list of owners (repositories and modules), never free text;fixed-by(pull requests or commits),amended-design,replayorreplay-none, and a reason forwontfix;notes: appended, each with who wrote it, when, and the text;links, typed: to another issue (duplicates,relates-to), a pull request, a decision record, a design, a condition key (the issue tracks that condition, § 12) or a delivery;documents: the report, and a diagnosis and a resolution when written, each with its author and date, revised in place with its history kept (ADR 0291);record: the path of the issue's folder in hq, while one exists; none is opened any more, and the folders are imported intodocumentsand removed (§ 8, slice 6);severity(critical, major, minor),needs(none, decision, operator),suggested-owner,delivered(the pull requests offixed-bythe delivery said are delivered) andtriage(the triage's judgement as made), and the link kinddepends-on(§ 10).
The registered list of owners is a setting of the module, changed through the controller's settings verb like any
other module setting. Slice 1 starts it with the repositories in hq's 00-META/repos.md and the modules the catalogue
registers.
An issue that has a folder in hq kept its status in that folder's frontmatter until the import of slice 3 ran;
until then the tracker held its number, title and record, took its notes and links, and move refused it with the
reason: its status lives in its folder. Since the import, every issue is whole in the tracker, folder or not, and a
report in hq carries no status key. No status is written in two places for one issue.
4. State, the counter and events
Buckets the module declares, the controller creates
(ADR 0201).
The manifest's state names two buckets, issues and counters, with their options: issues keeps a short history
per key, counters one value per key, neither expires. The module never creates a bucket. One issue is one value; at
the 256 KiB a value may be, a note that would push an issue past it is refused with that reason (the largest issue
folder in hq is 25 KiB).
The counter is advanced by compare-and-set. Beside get, put, delete, keys and watch, the runtime's state
verbs hold two writes that are refused when another writer was first, in the runtime and the SDK's state interface
together (ADR 0259):
createwrites a key only if it does not exist;updatewrites a key only if its revision is still the one the caller read, and returns the new revision.
The SDK's Increment reads a counter and writes the next number with create or update at the revision it read,
trying again a bounded number of times when another writer was first. The counter is started once, by create at
the number before the first, so it never starts twice. open then runs in this order:
- validate the issue; a refused issue spends no number;
- read
counters/issueand its revision; updateit to the next number at that revision; if another caller moved it first, the write is refused, andopenreads again and retries, a bounded number of times;createthe issue's key; an existing key is a fault the module reports loudly and never overwrites.
So the counter does not rely on there being one instance: two instances, or two calls racing in one, still never hand
out one number twice. The counter starts above the highest issue number on main and on every open branch of hq
at the moment the module is switched on. Numbers already skipped stay unused, and a number never moves.
Every change is an event the module declares under emits
(ADR 0041): opened, moved, noted, linked. Each carries
the number, what changed, and who changed it, so the events are the audit trail, and anything watching the bucket or
the events sees a change as it happens. moved also carries the worker a move names, as by, with since, and who
acted as caller; on a take-over took-over-from and the reason, and released when a move ends the work. No
event carries a secret, and an issue holds none.
5. The cycle's rules, at the write
move refuses what cycle.py refuses at merge today, and the rules playbook 03 states:
| The issue moves to | Needs |
|---|---|
| any status | one of the six |
diagnosing or fixing |
by, the worker as who@node; refused while another worker is current, naming them, unless take-over gives a reason, which is noted; a worker with nothing done on the issue for worker-stale-after (24 hours) is stale and may be taken over from without one |
| any other status | no by and no take-over; the move ends the work |
located |
located-in names at least one registered owner |
resolved |
the issue is at fixing (or already resolved, to correct what it holds); located-in, and fixed-by saying what fixed it |
resolved, for a core issue (its located-in names the controller, the node-engine, the tool runner, the SDK, or the catalogue's bus, forge or build agent) |
replay (an id from mesh-lab's replays register) or replay-none saying why none is possible (ADR 0237) |
wontfix |
a reason |
A refused move changes nothing and says which rule refused it. The rows for diagnosing, fixing and resolved are
ADR 0284's:
resolved only from fixing is the check that an agent said it started the fix. cycle.py checked the folders that carried status until
slice 3; since then it refuses a status key in any report, and judges no issue's status.
6. How the operator and agents use it
Through the mesh's tools first. An agent opening an issue calls issue-tracker.open, gets the number, and, for a design
or governance issue, writes 04-ISSUES/NNN-slug/ under it with the number in the report. Playbook 03's first step
changes to that the day slice 1 is live; nothing else in playbook 03 changes in slice 1. list answers "what is open"
in one call, from any session, and the responder on a channel answers from it once the seat exists.
An agent says when it starts. Before it starts work on an issue, an agent moves it to diagnosing (to find where
it lives) or fixing (to fix it) with by, its own name and node. list then shows who works on what, and a second
agent is refused before it starts the same work. An agent that keeps working notes what it finds, which keeps it the
worker; one that stops moves the issue on, or is stale a day later.
7. How it is checked
| Rule | Checked by |
|---|---|
| Two opens at once never share a number | the module's test over a real bus: many concurrent open calls, from two instances, yield distinct consecutive numbers; the runtime's test that update at a stale revision is refused |
| A refused open spends no number | the module's test: an invalid open, then a valid one, gets the next number |
| An issue's key is never overwritten | the module's test: a create on an existing key is refused and reported |
| The cycle's rules hold at the write | the module's table test over § 5, one case per row, each refused case changing nothing |
| The module creates no bucket | the runtime refuses a module that tries (ADR 0201 §3); the controller's raise asserts both buckets |
| The caller is who acted | the module's test that the recorded actor is the call's identity, whatever the arguments say |
| A worker is named on a move to diagnosing or fixing, another is refused while it is current, a take-over is noted, a stale one may be taken over from, and resolved is reached only from fixing | the rows of ADR 0284's "How it is checked" |
| An issue's delivery line follows the delivery; ready to verify once; a stuck fix said once and cleared | the rows of ADR 0281's "How it is checked" |
| A linked pull request is a fix; a closed unmerged one is withdrawn; delivered and not resolved is said once; an abandoned issue is flagged | the rows of ADR 0294's "How it is checked" |
| The triage fills only what is empty, links no cycle, and an issue is ready only when its dependencies' fixes are delivered | the rows of ADR 0279's "How it is checked" |
| The duplicate check acts only on a clear duplicate, never on an older or a resolved issue, once per issue, loudly when it cannot judge | the rows of ADR 0278's "How it is checked" |
| An issue's documents are kept whole, with author, date and history, and the import of hq's folders loses nothing (ADR 0291) | the module's tests of documents and of the import, one case per document kind; the comparison of every issue with its folder before the folders are removed, written on issue 427. Tracked by issue 427 |
| Live | on the mesh, an agent calls issue-tracker.open and gets the next number; two sessions on two nodes open at the same moment and get two different numbers; list shows both |
8. The slices after the first
| Slice | Delivers | Done when the operator sees |
|---|---|---|
| 1 | The mesh-issues module: open, get, list, move, note, link; a counter by compare-and-set in the SDK; playbook 03 takes its numbers from it |
an agent opens an issue and gets the next number; two agents opening at the same moment never get the same one |
| 2 | Decision and research numbers from the tracker, each its own key in counters; a number is reserved when a branch starts; the knowledge base's reserve asks for them (to-be 55 § 5). Tracked by issue 424 |
two agents writing a decision at once get two numbers, and no hq pull request is renumbered |
| 3 | import: every issue folder in hq read through the records module and written as an issue with its own number, idempotent and showing what it would change before it does (built and run 2026-10-10; one pull request changed playbook 03 and cycle.py and added dated notes to ADR 0019, 0080 and 0275, and once every folder was checked against the tracker a second removed the status keys from every report, as § 3 says); then the four consolidation passes of research 042 (fixed, duplicates, owners, grouping; the duplicates pass is the duplicate check's groom, § 9, judged rather than by shared words), each a list confirmed before it is applied through move, link and note, and an audit of fixed-by against the forge |
the not-closed list comes from one call and shrinks by what was already fixed or duplicated, each with its reason |
| 4 | Conditions and pull requests linked: an issue links a condition by its key, and every message about the condition says the issue and where it stands (§ 12; the controller does not show the link, ADR 0285); a merged pull request notes every issue it names; after a week of that, the controller opens one issue per healer-wanted condition key by itself |
a condition's message names its issue, and a merge notes the issues it names |
| 5 | A read-only view onto the issues bucket over the bus |
the operator reads issues in a browser without an agent |
| 6 | An issue whole (ADR 0291): the documents of § 3; an import of the 361 folders of hq into them, repeatable, keeping each issue's number and dates; once every issue is compared with its folder, one pull request rewrites the 765 path mentions in hq to "issue N", removes the folders, makes records.py refuse a new path into 04-ISSUES/, and changes playbook 03; open loses record. Tracked by issue 427 |
issue-tracker.get answers an issue's report and diagnosis, and hq has no 04-ISSUES/ |
Once slice 1 runs, each slice after it is opened as an issue in the tracker, so the tracker carries its own work.
9. The duplicate check
When an agent opens an issue that repeats one not resolved, the tracker closes the new one as its duplicate within a few minutes, keeping its report on the older one; an issue that repeats nothing gains a note that it was checked (ADR 0278).
- Started by the tracker's own
openedevent, which the module consumes. Only an issue still atopenis checked, against every older issue atopen,diagnosing,locatedorfixing; resolved and wontfix issues are never offered or changed. - Judged in two questions to the agent module's
claude_code_judgeon the tracker's node: a shortlist of at most five by titles, then a verdict on the reports (duplicate,relatedornone, whether it is certain, and why). The judging session runs as the node's agent account under its licence, with no tools, in safe mode, and answers within 25 seconds. - A certain duplicate: the new issue is linked
duplicates, its title and report become a note on the older one, its verdict a note on itself, and it moves towontfix, "duplicate of N". Related, or not certain: arelates-tolink and a note on both. None: one note. The older issue never moves. Each step goes through the tracker's ownnote,linkandmove, and is recorded as byduplicate-check. - Once per issue. The verdict note is written first, in a fixed first line; an event delivered again does only the steps not yet done.
- Loud when it cannot judge. One note on the new issue, and the event is not taken: after five deliveries
the controller raises
max-deliverieson the tracker's consumer. - The backlog:
groomruns the same judgement over every issue not resolved, as a dry run kept in the stategrooming, then applies that pass's findings onconfirm. - Settings:
duplicate-check(on) andjudge-model(sonnet).
10. Triage, severity and readiness
An agent opening an issue need not know the backlog: the triage estimates its severity and what it needs, links what it depends on, and the issue is said ready when every fix it waits for is delivered (ADR 0279).
- Triage is the judgement of § 9 widened: its second question is always asked and also answers severity,
needs, a suggested owner when the issue names none, and the shortlisted issues it depends on or relates to, each
with a one-line reason. The judgement is kept on the issue first, then applied through the tracker's own verbs:
one note saying it all, each empty field filled, each link added (never one that closes a cycle, never to a
resolved or wontfix issue), the duplicate verdict last. It fills only what is empty; a value a person gave
stands, and a disagreement is a line in the note. Recorded as by
triage. - Severity: critical (something the operator relies on is broken, or data is at risk), major (a rule of the mesh does not hold, or people work around it), minor (a contained fix; nothing the operator sees changes). Needs: none, decision (a decision record first), operator (the operator's answer is required).
depends-on: refused when it would close a cycle;getshows what an issue depends on and what it is needed by.- Ready: not resolved or wontfix, and every dependency wontfix (one closed as a duplicate counts as its
original), or resolved with every pull request its
fixed-bynames delivered; said once per spell. The tracker consumesmesh-delivery.transitionand, at its ownmovedto resolved, asksmesh-delivery.checksfor what it has not heard; an issue made ready gains a note and the eventready. opentakes severity and needs;setchanges severity, needs and suggested owner;listfilters on severity, needs and ready.groomtriages the backlog, dry run first.- The values are optional keys of an issue's one JSON value: an issue kept before reads as having none, and the key-value store has no schema to migrate.
10a. The record is a log of operations
The tracker's record is its log of operations, and its bucket is that log's fold (ADR 0300, resting on ADR 0297):
every write is one operation appended under the issue's number before the bucket is written; the bucket is rebuilt
from the log on start; the events announce and the log is the audit trail; list answers from memory with a ranked
search and a before cursor. Built under issues 501 and 502.
11. Whether the fix is delivered
An issue knows whether its fix is on the machines, as the delivery says it; delivered is not resolved, and a fix
stuck after its merge is said (ADR 0281). Since ADR 0294, an issue's fixes are the pull requests fixed-by
names and every pull request linked to it; a pull request closed without merging is withdrawn; an issue left in
fixing an hour after its last fix is delivered is said once, "fixed and delivered, not resolved"; and a stale worker
with no pull request open is abandoned.
- For each pull request
fixed-bynames, the tracker keeps the statemesh-delivery.transitionlast said — merged, held and why, covered, delivered — or asksmesh-delivery.checksonce for one it has not heard of. Nothing sets it by hand. getand each row oflistcarry delivery, one line: "fix not delivered: …", "held: …", "fix merged, not delivered", "fix not merged", "nothing heard …", "delivered to every machine it should reach".- When every fix of an issue not yet resolved is delivered, it gains a note and the event
ready-to-verify, once; it is resolved after the acceptance is proven live. A fix is named while the issue staysfixing:moveto the same status, withby, changes what the issue holds. - A fix merged and not delivered after
stuck-after(two hours) is said once: a note on the issue and a warning on the operator's channel, cleared when it is delivered. It is not a controller condition: a module that runs only its bundle cannot raise one.
12. Which issue tracks a condition
Every message about a condition says which issue tracks it and where that issue stands, or that none does (ADR 0285).
- An issue tracks a condition through a link of kind
conditionnaming the condition's key exactly; set by whoever opens the issue or works on it, never by the triage or the controller. An agent that opens an issue for a condition links its key at once, and one that silences a condition because an issue tracks it links that issue first. trackinganswers, for every key an issue links or only the keys asked, the issues that link it, those not closed first, at most 10 per key: number, status, severity, needs, the worker with since, active and stale, and fix (not-merged,merged,held,failed,unheard,delivered, or empty), from the facts of § 11. It carries no title, report or note, and changes nothing.- The router (to-be 45 § 5) asks it over the bus, outside its work, at most once a minute, and says from it the tracking line in every condition's message; the words and the verdict it changes are its own (to-be 45 § 5). The controller learns nothing of issues.
What it leaves out
Priorities beyond severity, notifications beyond the ready and ready-to-verify events and the stuck-fix warning, full-text or semantic search, the forge's own issue lists, and an index of any kind:
none of them is in any slice above, and each needs a decision of its own. Who works on an issue, listed here until
2026-10-10, is settled by ADR 0284.