mesh-knowledge: review every proposal in a judgement of its own, and serve no review verb (hq issue 425, ADR 0290 as updated)
mesh/merge-gate pass: builds mesh-knowledge → novox; no bus step; every machine composes with the change as it did without (4 of 4 compose)
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

The mesh proves which machine called, not which agent on it (hq issue 365), so a review verb could not tell a
reviewer from the author. Each version proposed is now judged by claude-code.claude_code_judge in a separate
call that sees only the record, the records it links and the rules: approve accepts (or asks the operator for a
needs-operator record), changes keeps it proposed with the findings for its author, and a judgement that cannot
be had or read leaves it proposed, says so, and never counts as approval.
This commit is contained in:
2026-10-11 12:06:18 +02:00
parent 300406e0dd
commit ebba63d71f
14 changed files with 743 additions and 315 deletions
@@ -2,8 +2,8 @@
// bundle the control-node's runtime launches. It holds every decision record and research effort whole — its
// fields, its clauses each with a status and a source, its typed links read from both ends, its reasoning — with
// every version kept, in a database in the store; answers what holds in one bounded call; takes a record proposed,
// checked when it is written, reviewed by someone who did not write it and accepted, on the operator's proven
// word when it needs it; and imports hq's records at a commit. stdout is the MCP channel; what this module says,
// checked when it is written, reviewed by its own judgement and accepted, on the operator's proven word when it
// needs it; and imports hq's records at a commit. stdout is the MCP channel; what this module says,
// it says on stderr.
//
// mesh-knowledge the bundle: the seat's verbs and the health tool
@@ -37,7 +37,7 @@ import (
const Seat = "knowledge-base"
// Verbs are the seat's verbs, as the manifest declares and claims them (ADR 0290 rule 6).
var Verbs = []string{"ask", "get", "holds", "links", "history", "reserve", "propose", "review", "import", "return"}
var Verbs = []string{"ask", "get", "holds", "links", "history", "reserve", "propose", "import", "return"}
// HealthTool is the module's own tool the node-engine asks how it is.
const HealthTool = "knowledge_health"
@@ -95,6 +95,7 @@ func newKB(store Store) *KB {
// serve runs the bundle: the verbs, the operator's answers heard, and the background work.
func serve(k *KB, status string) {
k.kick = make(chan struct{}, 1)
go hear(k)
go background(k)
if err := stdio.Serve("", append(Tools(k, callerOf), healthTool(status, k.now))); err != nil {
@@ -128,11 +129,26 @@ func hear(k *KB) {
}
// background reads the router's record of every waiting ask once a minute, withdraws reservations never
// proposed, and drafts and confirms clauses one step at a time.
// proposed, reviews every proposal waiting for its review — at once when one is proposed — and drafts and
// confirms clauses one step at a time, a review always before the next drafting step: the agent judges one
// question at a time, and a proposal does not wait behind the import's drafting.
func background(k *KB) {
tick := time.NewTicker(time.Minute)
defer tick.Stop()
time.Sleep(5 * time.Second)
review := func() {
for {
said, err := k.ReviewStep()
if err != nil {
logf("[mesh-knowledge] %v", err)
return
}
if said == "" {
return
}
logf("[mesh-knowledge] %s", said)
}
}
for {
if said, err := k.Reconcile(); err != nil {
logf("[mesh-knowledge] reading the operator's answers: %v", err)
@@ -146,6 +162,7 @@ func background(k *KB) {
} else if n > 0 {
logf("[mesh-knowledge] withdrew %d reservation(s) never proposed", n)
}
review()
for {
said, err := k.DraftStep()
if err != nil {
@@ -156,8 +173,12 @@ func background(k *KB) {
break
}
logf("[mesh-knowledge] %s", said)
review()
}
select {
case <-tick.C:
case <-k.kick:
}
<-tick.C
}
}
@@ -794,7 +815,10 @@ func Tools(k *KB, caller func(map[string]any) string) []stdio.Tool {
"every reason at once and writing nothing, when it breaks one of the 29 rules that judge one record or its prose — a " +
"link to nothing, a clause without a bold lead, a superseded record cited alone, an unknown topic, a retired word. A " +
"proposal is revised by proposing it again; an accepted record changes only by a progressive insight (insight: what " +
"stood before). Nothing proposed is in force.",
"stood before). The knowledge base then reviews each version itself, in a judgement that sees only the record, the " +
"records it links and the rules: on approve it is accepted (a record that needs the operator waits for the operator's " +
"Approve), on changes it stays proposed with the findings for its author to revise, and when no judgement can be had " +
"it stays proposed and says so. Nothing proposed is in force.",
Input: schema([]string{"number", "title"}, map[string]any{
"kind": kindArg(), "number": integer("the number reserve gave"), "title": text("one line"),
"topic": text("for a decision: a topic of the reading order, such as the mesh"),
@@ -851,32 +875,6 @@ func Tools(k *KB, caller func(map[string]any) string) []stdio.Tool {
}
return k.Propose(caller(m), p)
}},
{Name: Seat + ".review",
Description: "Review a proposed record you did not write: approve, or ask for changes with your findings (name the section " +
"and clause). Approved, it is accepted and its clauses are in force, and every clause it changes or supersedes moves; a " +
"record that needs the operator is asked of the operator first, on a channel that proves who answered, and accepted only " +
"on their Approve. Refused: a review by the record's author, a record not proposed, changes without findings.",
Input: schema([]string{"number", "verdict"}, map[string]any{"kind": kindArg(), "number": integer("the record's number"),
"verdict": map[string]any{"type": "string", "enum": []string{VerdictApprove, VerdictChanges}},
"findings": text("what you found, by section and clause")}),
Run: func(m map[string]any) (any, error) {
a, err := readArgs("review", m, "kind", "number", "verdict", "findings")
if err != nil {
return nil, err
}
key, err := a.key()
if err != nil {
return nil, err
}
r := ReviewAsk{Kind: key.Kind, Number: key.Number}
if r.Verdict, err = a.str("verdict"); err != nil {
return nil, err
}
if r.Findings, err = a.str("findings"); err != nil {
return nil, err
}
return k.Review(caller(m), r)
}},
{Name: Seat + ".import",
Description: "Import hq's decision records and research efforts, read through the records module at the commit its checkout " +
"stands at, each whole at its own number as its first version naming that commit. It only creates a record that is not " +