Wait for every runtime discovery hears from, and name the ones it misses

The console gathered $SRV.INFO answers for a fixed 750 ms. The laptop's
runtime (48 modules, 341 endpoints, 164 kB) answers last every time: its
answer crosses to the broker on another machine and back, a median of
365 ms on a quiet link and 813 ms in one of 25 rounds measured. When it
missed the window the console said its modules ran nowhere ("nothing in
the mesh is called slack") or only on another machine.

Discovery now asks $SRV.PING alongside $SRV.INFO and waits, past the
window and up to 5 s, for every instance that answered PING. A runtime
that never sends what it serves, or answered before and not now, is
named in every answer that might concern it instead of the module being
called missing. An answer still too large after first-line descriptions
drops them, and says so in its metadata.

mesh_runtimes reports, per runtime, its machine, how long its answer
took, its size, its modules and tools, whether it was shortened and when
it was last heard, and the runtimes and machines not heard.
This commit is contained in:
jochen
2026-10-05 15:36:10 +02:00
parent 8b789578c1
commit 379d19d907
9 changed files with 882 additions and 71 deletions
+118 -13
View File
@@ -2,8 +2,8 @@ package console
// The mesh's tools found by address, not announced whole (novox/hq ADR 0195, to-be 34 §3a).
//
// The console announces five tools. Everything the mesh answers is reached through them by one
// address per layer:
// The console announces six tools — five to find and call, and mesh_runtimes to say which runtimes
// discovery heard. Everything the mesh answers is reached through them by one address per layer:
//
// <seat>.<verb> a seat held once for the mesh — its holder answers
// <node>/<seat>.<verb> a seat held once per machine — that machine's holder answers
@@ -32,13 +32,14 @@ import (
// one agent turn's search, describe and call ask once, short enough that nothing goes stale.
var IndexKept = 5 * time.Second
// The five tools the console announces. Names of the API's kind — letters, digits, `_`, `-`.
// The six tools the console announces. Names of the API's kind — letters, digits, `_`, `-`.
const (
verbOverview = "mesh_overview"
verbMachine = "mesh_machine"
verbSearch = "mesh_search"
verbDescribe = "mesh_describe"
verbCall = "mesh_call"
verbRuntimes = "mesh_runtimes"
)
// searchCap is how many matches a search answers before it says how many more there were.
@@ -49,7 +50,7 @@ const grammar = "Addresses: `<seat>.<verb>` for a seat held once for the mesh (e
"`<node>/<module>.<tool>` for a module on one machine (e.g. `novox/postgres.postgres_list_databases`); " +
"and `<module>.<tool>` also for a module whose instances are interchangeable."
// discovery is the five tools as tools/list announces them.
// discovery is the six tools as tools/list announces them.
func discovery() []map[string]any {
str := func(desc string) map[string]any { return map[string]any{"type": "string", "description": desc} }
obj := func(props map[string]any, required ...string) map[string]any {
@@ -77,12 +78,17 @@ func discovery() []map[string]any {
"arguments": map[string]any{"type": "object", "description": "the tool's arguments, as mesh_describe gives its schema"},
}, "address"),
"description": "Call one tool by its address with its arguments; the answer says which machine gave it. " + grammar},
{"name": verbRuntimes, "inputSchema": obj(map[string]any{}),
"description": "Which runtimes answered discovery, asked now: for each, its machine, how long its answer took to " +
"arrive, its size in bytes, how many modules and tools it announced, whether it was shortened to fit the " +
"bus, and when it was last heard — and every runtime or machine expected that was not heard. Read-only; " +
"for when an address is said to be missing or on another machine."},
}
}
func isDiscovery(name string) bool {
switch name {
case verbOverview, verbMachine, verbSearch, verbDescribe, verbCall:
case verbOverview, verbMachine, verbSearch, verbDescribe, verbCall, verbRuntimes:
return true
}
return false
@@ -117,6 +123,44 @@ type index struct {
Modules map[string]*moduleInfo
NotAnswering []string
Listing *Listing // the flat catalogue the call path resolves subjects with
Discovery announce.Discovery
// Recorded is where the controller's records place each module that declares tools.
Recorded map[string][]string
// Unheard is every runtime whose answer this index lacks: it said it was there and did not say
// 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
}
// unheard is one runtime discovery did not hear in full, and why.
type unheard struct {
Runtime string `json:"runtime"`
Machine string `json:"machine,omitempty"`
Why string `json:"why"`
}
// unheardOn is what is unheard on one machine; with no machine, everything unheard.
func (x *index) unheardOn(node string) []unheard {
var out []unheard
for _, u := range x.Unheard {
if node == "" || u.Machine == node || u.Machine == "" {
out = append(out, u)
}
}
return out
}
// sayUnheard is the unheard, as a sentence's end.
func sayUnheard(us []unheard) string {
var parts []string
for _, u := range us {
who := u.Runtime
if u.Machine != "" {
who += " on " + u.Machine
}
parts = append(parts, who+" ("+u.Why+")")
}
return strings.Join(parts, "; ")
}
func (x *index) seat(name string) *seatInfo {
@@ -193,20 +237,24 @@ func indexOn(conn *bus.Conn) (*index, error) {
wg.Add(2)
go func() { defer wg.Done(); nodesOut, _ = controllerOutput(conn, "nodes") }()
go func() { defer wg.Done(); modulesOut, _ = controllerOutput(conn, "modules") }()
infos, err := announce.Gather(conn)
d, err := announce.Gather(conn)
wg.Wait()
if err != nil {
return nil, err
}
l := &Listing{Tools: []Tool{}, NotAnswering: []string{}}
x := &index{Modules: map[string]*moduleInfo{}, Listing: l}
x := &index{Modules: map[string]*moduleInfo{}, Listing: l, Discovery: d, Recorded: map[string][]string{}}
for _, s := range d.Silent {
x.Unheard = append(x.Unheard, unheard{Runtime: s.Name, Machine: s.Machine,
Why: fmt.Sprintf("it answered PING; what it serves did not arrive within %s", announce.Patience)})
}
announced := map[string]map[string]bool{} // module → node → announced something
seats := map[string]*seatInfo{}
toolAt := map[string]int{} // <module>.<tool> or <seat>.<verb> → index in l.Tools
machines := map[string]bool{}
for _, info := range infos {
for _, e := range announce.Endpoints(info) {
for _, h := range d.Heard {
for _, e := range announce.Endpoints(h.Info) {
if e.Node != "" {
machines[e.Node] = true
}
@@ -298,6 +346,7 @@ func indexOn(conn *bus.Conn) (*index, error) {
if !m.Tools {
continue
}
x.Recorded[m.Module] = append([]string{}, m.On...)
for _, n := range m.On {
// A holder of a seat held once for the mesh announces no machine — the seat is the mesh's,
// not a machine's — so what it announced without one answers for wherever it is assigned.
@@ -309,6 +358,9 @@ func indexOn(conn *bus.Conn) (*index, error) {
} else {
l.NotAnswering = append(l.NotAnswering, "mesh-controller (its records of the modules did not answer, so what is missing cannot be said)")
}
for _, u := range x.Unheard {
l.NotAnswering = append(l.NotAnswering, "the runtime "+sayUnheard([]unheard{u}))
}
sort.Strings(l.NotAnswering)
x.NotAnswering = l.NotAnswering
@@ -336,9 +388,12 @@ func containsHolder(hs []holder, h holder) bool {
return false
}
func (s *Surface) index() (*index, error) {
func (s *Surface) index() (*index, error) { return s.indexAsked(false) }
// indexAsked is what the mesh answered, asked again when `fresh` or when the kept answer is old.
func (s *Surface) indexAsked(fresh bool) (*index, error) {
s.mu.Lock()
if s.idx != nil && time.Since(s.idxAt) <= IndexKept {
if !fresh && s.idx != nil && time.Since(s.idxAt) <= IndexKept {
x := s.idx
s.mu.Unlock()
return x, nil
@@ -349,6 +404,7 @@ func (s *Surface) index() (*index, error) {
return nil, err
}
s.mu.Lock()
s.remember(x)
s.idx, s.idxAt = x, time.Now()
s.mu.Unlock()
return x, nil
@@ -397,6 +453,18 @@ func resolve(x *index, address string) (target, error) {
m := x.Modules[prefix]
if m == nil {
if on := x.Recorded[prefix]; len(on) > 0 {
why := "it did not announce itself on the bus"
if us := x.unheardOnAny(on); len(us) > 0 {
why = "discovery did not hear in full from " + sayUnheard(us)
}
return target{}, fmt.Errorf("%s is assigned to %s in the mesh's records, but nothing that answered discovery serves it: %s. "+
"Ask again in a moment; mesh_runtimes shows which runtimes answered", prefix, strings.Join(on, ", "), why)
}
if len(x.Unheard) > 0 {
return target{}, fmt.Errorf("nothing that answered discovery is called %s, but not every runtime answered: %s — "+
"%s may be theirs. Ask again in a moment; mesh_runtimes shows which runtimes answered", prefix, sayUnheard(x.Unheard), prefix)
}
return target{}, fmt.Errorf("nothing in the mesh is called %s: no seat, and no module that answers tools. "+
"mesh_search finds a tool by words", prefix)
}
@@ -412,11 +480,30 @@ func resolve(x *index, address string) (target, error) {
return target{Address: rest, Key: rest, Name: rest, Tool: *tool}, nil
}
if len(m.On) > 0 && !contains(m.On, node) {
if us := x.unheardOn(node); len(us) > 0 || contains(x.Recorded[prefix], node) {
why := "it did not announce itself there"
if len(us) > 0 {
why = "discovery did not hear in full from " + sayUnheard(us)
}
return target{}, fmt.Errorf("%s on %s did not answer discovery: %s. It was heard on %s. "+
"Ask again in a moment; mesh_runtimes shows which runtimes answered", prefix, node, why, orNobody(m.On))
}
return target{}, fmt.Errorf("%s does not run on %s; it runs on %s", prefix, node, orNobody(m.On))
}
return target{Address: node + "/" + rest, Key: rest + "@" + node, Name: rest, Node: node, Tool: *tool}, nil
}
// unheardOnAny is what is unheard on any of these machines.
func (x *index) unheardOnAny(nodes []string) []unheard {
var out []unheard
for _, u := range x.Unheard {
if u.Machine == "" || contains(nodes, u.Machine) {
out = append(out, u)
}
}
return out
}
func contains(xs []string, s string) bool {
for _, x := range xs {
if x == s {
@@ -511,8 +598,11 @@ func failure(text string) map[string]any {
return map[string]any{"content": []map[string]any{{"type": "text", "text": text}}, "isError": true}
}
// discover answers one of the five.
// discover answers one of the discovery verbs.
func (s *Surface) discover(name string, args map[string]any) map[string]any {
if name == verbRuntimes {
return s.runtimesAnswer()
}
x, err := s.index()
if err != nil {
return failure("the mesh's discovery failed: " + err.Error())
@@ -606,7 +696,18 @@ func (s *Surface) discover(name string, args map[string]any) map[string]any {
}
modules = append(modules, entry)
}
return answerText(map[string]any{"machine": node, "seats": seats, "modules": modules})
out := map[string]any{"machine": node, "seats": seats, "modules": modules}
if seats == nil {
out["seats"] = []map[string]any{}
}
if modules == nil {
out["modules"] = []map[string]any{}
}
if us := x.unheardOn(node); len(us) > 0 {
out["incomplete"] = "discovery did not hear in full from " + sayUnheard(us) +
"; what is listed may be missing what it serves. Ask again in a moment; mesh_runtimes shows which runtimes answered"
}
return answerText(out)
case verbSearch:
query := strings.ToLower(str("query"))
@@ -684,6 +785,10 @@ func (s *Surface) discover(name string, args map[string]any) map[string]any {
out["matches"] = []hit{}
out["hint"] = "nothing matched every word; try fewer words, or mesh_overview and mesh_machine to browse"
}
if len(x.Unheard) > 0 {
out["incomplete"] = "discovery did not hear in full from " + sayUnheard(x.Unheard) +
"; what they serve is not searched. mesh_runtimes shows which runtimes answered"
}
return answerText(out)
case verbDescribe: