Rank a search the same whatever order discovery heard things in
mesh/merge-gate pass: builds mesh-tools, node-tools → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 of …
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery delivered

A tie went to whichever tool discovery listed first, so docker logs found docker_secrets_in_logs
before docker_logs on some runs. A name the query says whole wins, then the shorter name.
This commit is contained in:
jochen
2026-10-07 20:27:26 +02:00
parent 49cb06f6c5
commit aa209313c2
5 changed files with 37 additions and 12 deletions
+4 -4
View File
@@ -131,7 +131,7 @@ type index struct {
// what it serves in time, or it answered before and not now. An address it might answer is never
// called missing while it is here (2026-10-05).
Unheard []unheard
// Replaces is what each verb and tool replaces, from the controller's records (novox/hq ADR 0242):
// Replaces is what each verb and tool replaces, from the controller's records (novox/hq ADR 0245):
// `seat:<seat>.<verb>` and `<module>.<tool>`.
Replaces map[string][]string
}
@@ -225,7 +225,7 @@ type recordedModule struct {
Module string `json:"module"`
On []string `json:"on"`
Tools bool `json:"tools"`
// Replaces is what each of its own tools replaces (novox/hq ADR 0242).
// Replaces is what each of its own tools replaces (novox/hq ADR 0245).
Replaces map[string][]string `json:"replaces,omitempty"`
}
@@ -244,7 +244,7 @@ func indexOn(conn *bus.Conn) (*index, error) {
wg.Add(3)
go func() { defer wg.Done(); nodesOut, _ = controllerOutput(conn, "nodes") }()
go func() { defer wg.Done(); modulesOut, _ = controllerOutput(conn, "modules") }()
// The seats' verbs as the records define them, for what each replaces (novox/hq ADR 0242): answered
// The seats' verbs as the records define them, for what each replaces (novox/hq ADR 0245): answered
// in-process from the records, never a command.
go func() {
defer wg.Done()
@@ -832,7 +832,7 @@ func (s *Surface) discover(name string, args map[string]any) map[string]any {
if len(hits) == 0 {
out["matches"] = []hit{}
out["hint"] = "nothing matched every word; try fewer words, or mesh_overview and mesh_machine to browse. " +
"If no tool does it, one is created in the module that owns it, on its seat — never worked around (novox/hq ADR 0242)"
"If no tool does it, one is created in the module that owns it, on its seat — never worked around (novox/hq ADR 0245)"
}
if len(x.Unheard) > 0 {
out["incomplete"] = "discovery did not hear in full from " + sayUnheard(x.Unheard) +
+30 -5
View File
@@ -1,6 +1,6 @@
package console
// Finding a tool by the words an agent already has (novox/hq ADR 0242).
// Finding a tool by the words an agent already has (novox/hq ADR 0245).
//
// An agent that wants a unit's log reaches for `journalctl`, and one that wants a container's for `docker
// logs`; it searches with those words, or with `logs`. Matched against names and descriptions alone, none
@@ -12,10 +12,11 @@ package console
// every word of the replaced command, in order, among the query's words. The most specific command wins.
// 2. **Every word is in the tool's name, its replaced commands or its description**, a word also matching
// through a short table of the words agents use for the mesh's (logs ⇄ journal, ps ⇄ list). A word in
// the name or a replaced command weighs more than one in the description.
// the name or a replaced command weighs more than one in the description, and a name the query says
// whole more than one it says in part.
// 3. Nothing else: a search that matches nothing says so.
//
// Ties keep the listing's order — seats first, the place tools belong — then the modules by name.
// Ties go to a seat — the place tools belong — then to the shorter name, then by address.
import (
"encoding/json"
@@ -149,6 +150,19 @@ func scoreOf(query string, qwords []string, name, description string, replaces [
score += found
}
}
// A name the query says whole — `docker logs` for docker_logs, not docker_secrets_in_logs — first.
own := name[strings.LastIndex(name, ".")+1:]
whole := true
for _, part := range strings.FieldsFunc(strings.ToLower(own), func(r rune) bool { return r == '_' || r == '-' }) {
in := false
for _, w := range qwords {
in = in || w == part
}
whole = whole && in
}
if whole {
score += 2
}
return score, ""
}
@@ -208,11 +222,22 @@ func (x *index) search(query string) []hit {
hits = append(hits, h)
}
}
sort.SliceStable(hits, func(i, j int) bool { return hits[i].score > hits[j].score })
// Best first; on a tie the shorter name — the plainer tool — then the address, so an answer never
// depends on the order discovery heard things in.
sort.SliceStable(hits, func(i, j int) bool {
if hits[i].score != hits[j].score {
return hits[i].score > hits[j].score
}
a, b := hits[i].Address[strings.LastIndex(hits[i].Address, ".")+1:], hits[j].Address[strings.LastIndex(hits[j].Address, ".")+1:]
if len(a) != len(b) {
return len(a) < len(b)
}
return hits[i].Address < hits[j].Address
})
return hits
}
// replacesOf is what every verb and tool replaces, from the controller's records (novox/hq ADR 0242):
// replacesOf is what every verb and tool replaces, from the controller's records (novox/hq ADR 0245):
// a seat's verbs from its `tools` answer, keyed `seat:<seat>.<verb>`, and a module's own tools from
// its `modules` answer, keyed `<module>.<tool>`. A controller older than the field answers neither,
// and search matches names and descriptions as before.
+1 -1
View File
@@ -55,7 +55,7 @@ func meshToSearch(t *testing.T) *index {
return x
}
// **The words an agent has find the verb first** (novox/hq ADR 0242): the command it would have run, or
// **The words an agent has find the verb first** (novox/hq ADR 0245): the command it would have run, or
// the word it would have used, puts the mesh's tool for it at the top.
func TestSearchFindsTheToolThatReplacesACommandFirst(t *testing.T) {
x := meshToSearch(t)