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.
96 lines
6.6 KiB
Markdown
96 lines
6.6 KiB
Markdown
# mesh-tools
|
|
|
|
Two modules in one repository (novox/hq ADR 0069), one piece of software:
|
|
|
|
- **`node-tools`** (`node-tools/`) — the node's **tool runtime** as a module (ADR 0175, to-be 38
|
|
WP3): one process per machine the host runs from this bundle, serving every assigned module's tools
|
|
and every held seat's verbs on the bus, and answering MCP on the machine's loopback — the console
|
|
(design 34). The code, its tests and the `mesh` client all live there.
|
|
- **`mesh-tools`** (this directory) — the two images TypeScript bundles are compiled in and a module's
|
|
own *service* may still run in. Built from the same code; no longer how tools reach a node.
|
|
|
|
The runtime:
|
|
|
|
1. connects the mesh bus on the node's credential — a concrete implementation of the sdk's `Broker`
|
|
contract;
|
|
2. reads one membership per module it serves — what the mesh issued that module on this machine
|
|
(ADR 0160): where its tools are answered, which seats it holds — and follows each live;
|
|
3. imports each module's compiled tool entrypoints, each of which registers its tools as it loads,
|
|
guarded: a bundle that throws is named, in the log and in what `tools` answers for its module,
|
|
and the others serve;
|
|
4. serves every module's tools on that module's subjects and every held seat's verbs on the seat's.
|
|
|
|
A bundle that is not plain JavaScript — a Go or Rust binary, a Python script, or a JavaScript file
|
|
marked executable — is **launched** rather than imported (novox/hq ADR 0188): the runtime starts it
|
|
as a child with its own environment and speaks MCP over stdio to it, `tools/list` once and
|
|
`tools/call` per call. A tool it lists as `<seat>.<verb>` is the seat's implementation. A child that
|
|
exits is named in the log and started again on its next call. So a tools bundle may be written in
|
|
any language; the mesh's SDK for each is the stdio loop and nothing more (`node-tools/src/launch.ts`
|
|
is the runtime's side of it).
|
|
|
|
Everything hard — dispatch, collection, duplicate-name safety — is the sdk's. This is the thin
|
|
wrapper that binds the bus and loads the modules. Keeping the bus client here, out of the sdk, is
|
|
deliberate: a bus-client change never rebuilds a module (ADR 0039). The runtime is module-agnostic:
|
|
it knows bundles and subjects, nothing of what any module does. A tool that needs root escalates
|
|
itself — root is the module's concern, not the runtime's.
|
|
|
|
## Running it
|
|
|
|
```
|
|
MESH_BROKER_FILE the node's sealed credential, as the mesh delivered it
|
|
MESH_TOOL_MODULES alpha=/…/alpha/tools/index.js,beta=/…/beta/dist/index.js,…
|
|
the modules to serve and their compiled entrypoints; several entries may
|
|
name one module. A bare path is an entrypoint of the credential's own module
|
|
— the one-module form a per-module container still sets.
|
|
MESH_OPERATOR_ACCOUNT whose machine this is, and MESH_OPERATOR_HOME where their home is; set by
|
|
the mesh when the node has an account, read by tools from their environment
|
|
MESH_BROKER_URL a plain URL instead of the credential, for the bootstrap case
|
|
```
|
|
|
|
`node dist/main.js`. On a node the controller composes the variables and the host supervises the
|
|
process like any other host-side workload (novox/hq to-be 38). As `node-tools` the same process is
|
|
the console: MCP on `127.0.0.1:4270` (or `MESH_CONSOLE_LISTEN`). The container (`Dockerfile`) is how
|
|
a module's own *service* may still be built; it is no longer how tools reach a node.
|
|
|
|
## `mesh` — the tools for whoever is on a machine
|
|
|
|
The same package carries the client (novox/hq design 25 §7, design 34): `mesh tools`, `mesh call
|
|
<module>.<tool> [json]`, `mesh mcp` (an MCP server over stdio for a program a person starts) and
|
|
`mesh serve` (the **console**: MCP over HTTP on a machine's loopback, started by the mesh as the
|
|
`mesh-console` module on the credential in `MESH_BROKER_FILE` — novox/hq ADR 0152). `mesh serve`
|
|
refuses to bind anything but loopback. With `--console <url>`, `tools` and `call` go through a console
|
|
already on the machine and need no credential.
|
|
|
|
The console announces six tools and reaches everything else by address (novox/hq ADR 0195):
|
|
`mesh_overview` (the mesh's seats and machines), `mesh_machine` (one machine's seats and modules),
|
|
`mesh_search` (a tool by words), `mesh_describe` (one tool's arguments), `mesh_call` (call one by
|
|
address) and `mesh_runtimes` (which runtimes answered discovery: per runtime its machine, how long its
|
|
answer took, its size in bytes, how many modules and tools it announced, whether it was shortened to
|
|
fit the bus, when it was last heard — and who was expected and not heard).
|
|
|
|
`mesh_search` also finds a tool by the shell command it replaces (novox/hq ADR 0242): each seat verb and
|
|
module tool says what it replaces in the controller's records (`replaces`, read from the controller's
|
|
`tools` and `modules` answers), so `journalctl -u x`, `systemctl status x` or `docker ps` puts the verb
|
|
for it first, and says the command it matched as `instead_of`. A word also matches through a short table
|
|
of the words agents use for the mesh's (`logs` finds the journal). Best first: a replaced command, then
|
|
words in a name or a replaced command, then words in a description; seats before modules on a tie.
|
|
|
|
Discovery asks the bus (ADR 0197): every runtime answers the NATS services protocol's `$SRV.PING` and
|
|
`$SRV.INFO` with what it serves at that moment, and the console reads where the controller's records
|
|
place each module. The console waits at least 750 ms, and up to 5 s for every runtime that answered
|
|
PING to send what it serves — a large runtime's answer crossing the bus to a broker on another machine
|
|
can arrive well after the rest. A runtime that said it is there and did not say what it serves, or
|
|
that answered earlier and not now, is named: the console never calls a module missing, or on another
|
|
machine, while a runtime that might serve it was not heard. A runtime whose answer outgrows the bus
|
|
announces first-line descriptions, then none, and says so. A module may not name a tool of its own
|
|
`tools`; the runtime refuses it at load.
|
|
|
|
## Verified
|
|
|
|
`npm test` runs against a real NATS server with JetStream (`MESH_TEST_NATS`, see any test's header
|
|
for the one-line `docker run`) and proves the whole path over the wire: the runtime serves a
|
|
registered tool, a separate connection invokes it by name and gets the result, an unknown tool is
|
|
refused, a runtime serves exactly the subjects it is issued and re-serves on a new membership, and
|
|
the node's runtime serves three modules' bundles on one credential — one of them broken, named and
|
|
not fatal — with every seat verb answering where the membership put it.
|