Keep a machine's discovery answer under the bus's message limit
Every tool's schema in one $SRV.INFO reply outgrew max_payload on machines serving 137-206 tools, and the refused reply was dropped silently, so search and a machine's view went empty. Module tools now announce without schema; describe and tools/list ask the module for it. An answer still too large has its descriptions cut to a line, and a failed reply is logged.
This commit is contained in:
@@ -691,6 +691,10 @@ func (s *Surface) discover(name string, args map[string]any) map[string]any {
|
||||
if err != nil {
|
||||
return failure(err.Error())
|
||||
}
|
||||
if !t.Seat && len(t.Tool.Input) == 0 {
|
||||
// Discovery carries a seat's schema, not a module tool's: asked of the runtime serving it.
|
||||
t.Tool.Input = inputOf(s.conn, t.Tool.Module, t.Tool.Name)
|
||||
}
|
||||
description := t.Tool.Description
|
||||
if description == "" {
|
||||
description = t.Name
|
||||
@@ -728,3 +732,29 @@ func (s *Surface) discover(name string, args map[string]any) map[string]any {
|
||||
}
|
||||
return failure("no discovery verb " + name)
|
||||
}
|
||||
|
||||
// inputOf is a module tool's input schema, asked of a runtime that serves the module — its `tools` verb,
|
||||
// answered on the module's plain subject by any instance — because discovery no longer carries it: every
|
||||
// tool's schema made a machine's one discovery answer outgrow the bus (2026-10-04). Nil when nothing
|
||||
// answers, which describes the tool as taking nothing rather than failing the description.
|
||||
func inputOf(conn *bus.Conn, module, tool string) json.RawMessage {
|
||||
got, err := conn.Ask(module+".tools", map[string]any{}, "")
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
var answer struct {
|
||||
Tools []struct {
|
||||
Name string `json:"name"`
|
||||
Input json.RawMessage `json:"input"`
|
||||
} `json:"tools"`
|
||||
}
|
||||
if json.Unmarshal(got.Result, &answer) != nil {
|
||||
return nil
|
||||
}
|
||||
for _, t := range answer.Tools {
|
||||
if t.Name == tool {
|
||||
return t.Input
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -178,11 +178,20 @@ func TestTheMeshsToolsAreFoundByAddress(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Discovery itself asked nothing but the bus.
|
||||
if n := asked.Load(); n != 0 {
|
||||
t.Errorf("discovery asked a roster or a module's tools %d time(s); it asks the bus", n)
|
||||
}
|
||||
|
||||
// Described without `node`: the address carries the machine.
|
||||
described, isErr := call(t, endpoint, "mesh_describe", map[string]any{"address": "desk/beta.three"})
|
||||
if isErr || strings.Contains(described, `"node"`) || !strings.Contains(described, `"address": "desk/beta.three"`) {
|
||||
t.Errorf("describe: %s", described)
|
||||
}
|
||||
// Its schema, which discovery no longer carries for a module's tool, asked of the runtime serving it.
|
||||
if !strings.Contains(described, `"verbose"`) {
|
||||
t.Errorf("describe lost the tool's arguments: %s", described)
|
||||
}
|
||||
|
||||
// Search finds across the layers; a module that starts serving after the first answer is found.
|
||||
if got, _ := call(t, endpoint, "mesh_search", map[string]any{"query": "shelf"}); !strings.Contains(got, "desk/node-shelf.list") {
|
||||
@@ -212,8 +221,10 @@ func TestTheMeshsToolsAreFoundByAddress(t *testing.T) {
|
||||
t.Errorf("a module that arrived later was not found: %s", found)
|
||||
}
|
||||
|
||||
if n := asked.Load(); n != 0 {
|
||||
t.Errorf("discovery asked a roster or a module's tools %d time(s); it asks the bus", n)
|
||||
// Only describing a module's tool asks its runtime — once, for the schema discovery no longer carries
|
||||
// (2026-10-04: every tool's schema made a machine's discovery answer outgrow the bus).
|
||||
if n := asked.Load(); n != 1 {
|
||||
t.Errorf("asked a module's tools %d time(s); describing one tool asks once, and discovery never", n)
|
||||
}
|
||||
|
||||
// The old names still answer, unannounced.
|
||||
|
||||
@@ -125,7 +125,15 @@ func (s *Surface) Handle(r Request) *Reply {
|
||||
return refuse(r.ID, -32603, "the mesh's discovery failed: "+err.Error())
|
||||
}
|
||||
tools := make([]map[string]any, 0, len(l.Tools))
|
||||
for _, t := range l.Tools {
|
||||
inputs := map[string]map[string]json.RawMessage{} // module → tool → input, asked once per module
|
||||
for i, t := range l.Tools {
|
||||
if !t.Seat && len(t.Input) == 0 {
|
||||
if inputs[t.Module] == nil {
|
||||
inputs[t.Module] = toolInputs(s.conn, t.Module)
|
||||
}
|
||||
l.Tools[i].Input = inputs[t.Module][t.Name]
|
||||
t = l.Tools[i]
|
||||
}
|
||||
var schema map[string]any
|
||||
switch {
|
||||
case t.Seat && t.Scope != "node":
|
||||
@@ -295,3 +303,24 @@ func (s *Surface) instructions() string {
|
||||
"arguments; mesh_call calls it. " + grammar + " What may be called was fixed when this account " +
|
||||
"was issued, so a refusal means the account, not the tool."
|
||||
}
|
||||
|
||||
// toolInputs is every tool's input schema of one module, asked of a runtime that serves it.
|
||||
func toolInputs(conn *bus.Conn, module string) map[string]json.RawMessage {
|
||||
out := map[string]json.RawMessage{}
|
||||
got, err := conn.Ask(module+".tools", map[string]any{}, "")
|
||||
if err != nil {
|
||||
return out
|
||||
}
|
||||
var answer struct {
|
||||
Tools []struct {
|
||||
Name string `json:"name"`
|
||||
Input json.RawMessage `json:"input"`
|
||||
} `json:"tools"`
|
||||
}
|
||||
if json.Unmarshal(got.Result, &answer) == nil {
|
||||
for _, t := range answer.Tools {
|
||||
out[t.Name] = t.Input
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user