Find a tool by the shell command it replaces, best first (hq ADR 0245)

The agent searched with journalctl, logs and docker ps and found nothing, so it went over ssh. Search
now reads what each verb and tool replaces from the controller's records, matches a command line
against it, ranks the matches, and says the command it matched.
This commit is contained in:
jochen
2026-10-07 19:21:13 +02:00
parent 26a58c7a79
commit 49cb06f6c5
5 changed files with 443 additions and 72 deletions
+35 -70
View File
@@ -67,9 +67,10 @@ func discovery() []map[string]any {
{"name": verbMachine, "inputSchema": obj(map[string]any{"node": str("the machine, as mesh_overview names it")}, "node"),
"description": "One machine: the seats it holds with their verbs, and the modules assigned to it with their tools — " +
"each with the address to describe or call it by. " + grammar},
{"name": verbSearch, "inputSchema": obj(map[string]any{"query": str("words to find in tool names and descriptions, e.g. `postgres databases`")}, "query"),
{"name": verbSearch, "inputSchema": obj(map[string]any{"query": str("words to find in tool names and descriptions, e.g. `postgres databases` — or the shell command you would have run, e.g. `journalctl -u x`, `docker ps`")}, "query"),
"description": "Find tools anywhere in the mesh by words: every match's address and a line of what it does, across the " +
"mesh's seats, the machines' seats and every module on every machine. " + grammar},
"mesh's seats, the machines' seats and every module on every machine, best first. A shell command finds the tool " +
"that replaces it — search before reaching for ssh, journalctl, systemctl or docker. " + grammar},
{"name": verbDescribe, "inputSchema": obj(map[string]any{"address": str("the tool's address")}, "address"),
"description": "What one tool does and the arguments it takes, as a JSON schema. The machine is in the address, " +
"never an argument. " + grammar},
@@ -130,6 +131,9 @@ 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):
// `seat:<seat>.<verb>` and `<module>.<tool>`.
Replaces map[string][]string
}
// unheard is one runtime discovery did not hear in full, and why.
@@ -221,6 +225,8 @@ 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 map[string][]string `json:"replaces,omitempty"`
}
// recordedMachine is a machine as the controller's records hold it (`node list --json`).
@@ -234,9 +240,18 @@ type recordedMachine struct {
func indexOn(conn *bus.Conn) (*index, error) {
var wg sync.WaitGroup
var nodesOut, modulesOut string
wg.Add(2)
var toolsAnswer json.RawMessage
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
// in-process from the records, never a command.
go func() {
defer wg.Done()
if got, err := conn.Ask("seat:mesh-controller.tools", map[string]any{}, ""); err == nil {
toolsAnswer = got.Result
}
}()
d, err := announce.Gather(conn)
wg.Wait()
if err != nil {
@@ -258,7 +273,9 @@ func indexOn(conn *bus.Conn) (*index, error) {
// What should have answered: every assignment of a module that declares tools. Silence is named;
// a module with no tools is never a name here.
var recorded []recordedModule
if json.Unmarshal([]byte(jsonIn(modulesOut)), &recorded) == nil {
recordedOK := json.Unmarshal([]byte(jsonIn(modulesOut)), &recorded) == nil
x.Replaces = replacesOf(toolsAnswer, recorded)
if recordedOK {
for _, m := range recorded {
if !m.Tools {
continue
@@ -803,70 +820,10 @@ func (s *Surface) discover(name string, args map[string]any) map[string]any {
case verbSearch:
query := strings.ToLower(str("query"))
if query == "" {
return failure("mesh_search needs `query`: words to find, e.g. `postgres databases`")
}
words := strings.Fields(query)
type hit struct {
Address string `json:"address"`
Does string `json:"does"`
Also string `json:"also,omitempty"`
}
var hits []hit
matches := func(parts ...string) bool {
hay := strings.ToLower(strings.Join(parts, " "))
for _, w := range words {
if !strings.Contains(hay, w) {
return false
}
}
return true
}
for _, st := range x.Seats {
for _, v := range st.Verbs {
if !matches(st.Seat, v.Name, v.Description) {
continue
}
if st.Scope == "node" {
on := nodesOf(st.Holders)
first := "<node>"
also := ""
if len(on) > 0 {
first = on[0]
if len(on) > 1 {
also = "also on " + strings.Join(on[1:], ", ")
}
}
hits = append(hits, hit{first + "/" + st.Seat + "." + v.Name, firstLine(v.Description), also})
} else {
hits = append(hits, hit{st.Seat + "." + v.Name, firstLine(v.Description), ""})
}
}
}
names := make([]string, 0, len(x.Modules))
for n := range x.Modules {
names = append(names, n)
}
sort.Strings(names)
for _, n := range names {
m := x.Modules[n]
for _, t := range m.Tools {
if !matches(m.Module, t.Name, t.Description) {
continue
}
switch {
case m.Interchangeable:
hits = append(hits, hit{m.Module + "." + t.Name, firstLine(t.Description), "any instance; on " + orNobody(m.On)})
case len(m.On) == 0:
hits = append(hits, hit{"<node>/" + m.Module + "." + t.Name, firstLine(t.Description), "the mesh places it on no machine"})
default:
also := ""
if len(m.On) > 1 {
also = "also on " + strings.Join(m.On[1:], ", ")
}
hits = append(hits, hit{m.On[0] + "/" + m.Module + "." + t.Name, firstLine(t.Description), also})
}
}
return failure("mesh_search needs `query`: words to find, e.g. `postgres databases`, or the command you would " +
"have run, e.g. `journalctl` or `docker ps`")
}
hits := x.search(query)
out := map[string]any{"matches": hits}
if len(hits) > searchCap {
out["matches"] = hits[:searchCap]
@@ -874,7 +831,8 @@ 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"
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 len(x.Unheard) > 0 {
out["incomplete"] = "discovery did not hear in full from " + sayUnheard(x.Unheard) +
@@ -895,8 +853,15 @@ func (s *Surface) discover(name string, args map[string]any) map[string]any {
if description == "" {
description = t.Name
}
return answerText(map[string]any{"address": t.Address, "description": description,
"arguments": schemaAsPassed(t)})
described := map[string]any{"address": t.Address, "description": description, "arguments": schemaAsPassed(t)}
key := t.Tool.Module + "." + t.Tool.Name
if t.Seat {
key = "seat:" + key
}
if r := x.Replaces[key]; len(r) > 0 {
described["instead_of"] = r
}
return answerText(described)
case verbCall:
t, err := resolve(x, str("address"))