Skip to content

humd: a peer's attendant serves a bloom here, when this humd trusts it - #96

Merged
adiled merged 1 commit into
mainfrom
humd/attendants-across-the-mesh
Oct 25, 2026
Merged

adiled merged 1 commit into
mainfrom
humd/attendants-across-the-mesh

Conversation

@adiled

@adiled adiled commented Oct 25, 2026

Copy link
Copy Markdown
Owner

humd: a peer's attendant serves a bloom here, when this humd trusts it

WIRE.md already said a reach goes "to an attendant serving it here or on a peer", but humd handed a bloom only its own attendants' tools and never sent a reach to a peer's. Now it does, behind a trust gate of its own, the way remote nurses are.

What a bloom is handed

  • Each name once, the first to claim it keeping it: bash when the bloom builds, this humd's attendants', the forager's own, then the tools of attendants on live peers.
  • A peer's attendant counts only when this humd trusts remote attendants and knows the peer bee's hive, as a remote nurse counts only for a hive installed here.

Where a reach goes

  • bash to the builder where the bloom builds; any other name to an attendant here, then back to the forager when it offered the name, then to the peer whose attendant serves it.
  • The peer route is the one bash already takes to a builder elsewhere, now shared (reach_peer); the result comes back the way it does for bash.

Trust, off by default

  • humd.trustRemoteAttendants in hum.json, or HUM_TRUST_REMOTE_ATTENDANTS, mirroring trustRemoteNurses: the peer sees each reach's input, and its tool descriptions reach every model here.
  • from now means a peer sent it: a reach from the ensemble with no origin is dropped, since nothing could answer it, and a local reach loses any from before it reaches an attendant. An attendant can then serve its own humd's blooms and refuse a peer's, which web2 does unless its config says servePeers.

sim

  • Trust { nurses, attendants } and spawn_humd_trusting; spawn_humd_not_trusting_remote_attendants beside the nurses' one.
  • peer_attendants: a peer's tool is offered and its reach answered from the peer, input and all; without the trust it is neither offered nor reached; nor for a hive this humd was never taught.

Docs

  • WIRE.md's Tendrils section says the order a bloom's tools are taken in, the route, and the trust.
  • hum.schema.json and man/hum.1 carry the new key and variable.

Verified

  • cargo test --locked -p ensemble -p sim -p humd -p hum -p humctl -p hum-paths
  • codegen --check, tools/speech.py, tools/paths.py, the CI clippy line.

WIRE.md said a reach goes to an attendant "here or on a peer", but humd
handed a bloom only its own attendants' tools and never sent a reach to
a peer's. Now it does, behind a trust gate of its own.

- A bloom is handed, each name once: `bash`, this humd's attendants',
  the forager's own, then those of attendants on live peers, when this
  humd trusts remote attendants and knows the peer bee's hive.
- A reach goes to an attendant here, then back to the forager when it
  offered the name, then to the peer whose attendant serves it, by the
  route `bash` already takes to a builder elsewhere, now `reach_peer`.
- `humd.trustRemoteAttendants` / `HUM_TRUST_REMOTE_ATTENDANTS`, off by
  default as remote nurses are: the peer sees each reach's input, and
  its tool descriptions reach every model here.
- `from` now means a peer sent it: a peer's reach with no origin is
  dropped, since nothing could answer it, and a local reach loses any
  `from`, so an attendant can refuse a peer's blooms.
- sim: `Trust { nurses, attendants }`, and `peer_attendants` for the
  trusted, untrusted and never-taught cases.
adiled added a commit that referenced this pull request Oct 25, 2026
A reach from a peer humd carries the peer's `from` (#96), and web2 refuses
it unless web2.json says `servePeers: true`: what web2 is signed in to is
this machine's, lent to a peer's blooms only when a person says so. With
the peer's humd trusting remote attendants, that is the two opt-ins a
peer's bloom needs.
@adiled
adiled merged commit 9c548f0 into main Oct 25, 2026
7 checks passed
adiled added a commit that referenced this pull request Oct 25, 2026
A reach from a peer humd carries the peer's `from` (#96), and web2 refuses
it unless web2.json says `servePeers: true`: what web2 is signed in to is
this machine's, lent to a peer's blooms only when a person says so. With
the peer's humd trusting remote attendants, that is the two opt-ins a
peer's bloom needs.
adiled added a commit that referenced this pull request Oct 25, 2026
web2 is an attendant serving one fixed tool, `wrepl`. A reach carries one command line, and every OpenAPI spec and MCP server web2.json names answers it as if it were a CLI. The thrum sees a tendril like any other: OpenAPI, MCP and OAuth stop at this hive, and a harness that takes tools only over MCP gets `wrepl` as one tool among `bash` and the forager's own.

```
help
github help pulls
github pulls/list rust-lang rust --state open --jq '.[].title'
sentry search-issues --query 'is:unresolved'
```

**wrepl**
- `help` lists the services, `<service> help [group | words]` a service's commands, and `<service> <command> --help` a command's flags.
- Flags come from each command's input schema, typed by it: `--name value`, `--name=value`, `--flag` / `--no-flag`, a flag repeated for an array. Words with no flag fill the required inputs in order. Spelling is forgiven across kebab, snake and camel case.
- `--json '{…}'` gives the whole input, flags laid over it winning; `--jq '<filter>'` narrows a JSON result (jaq).
- A mistake answers with what would have worked: did-you-mean for services, commands and flags, the missing required flags, the type a value should be.
- An answer past `maxOutput` (32 KiB) keeps its start and its end, as `bash` does.

**Services**
- OpenAPI 3 from a URL or a file beside web2.json, JSON or YAML: each operation a command named by its `operationId`, grouped by its first tag, its parameters and JSON body fields as flags, local `$ref`s followed. The commands made from a spec are kept for a day, or until `web2 refresh`.
- MCP over stdio or streamable HTTP, through rmcp 3.7: one connection per server, opened at first use, closed when idle, opened again when it drops, relisted when the server says its tools changed.
- An `mcpServers` block pasted from any harness works as it is. `web2 import` copies in the servers Claude Code, Claude Desktop, Cursor, VS Code and Codex already know here.
- web2.json is read again whenever it changes; a service added there is reached on the next call.

**Reading and writing**
- A service only reads until web2.json says otherwise: `writes`, `allow` (write commands by name or glob, `read-*`), `deny`.
- An OpenAPI operation reads when its method is GET, HEAD or OPTIONS. An MCP tool reads only when its annotations say `readOnlyHint: true`, as the MCP spec has it.
- A refused call answers with the key that would turn it on, and a service's help counts what is off.
- `web2 allow`, `web2 deny` and `web2 review` (each command that is off, turned on or left, one by one) edit web2.json in place, keeping its order, and refuse a change that would break it.

**Signing in**
- Tokens live in `secrets/credentials.json` in web2's state directory, mode 0600 in a 0700 directory, never in an environment, a log or an answer. `"env": "VAR"` reads one from web2's environment instead.
- `web2 login <service>`: a token at a prompt that does not show it, or `--token-stdin`; an OpenAPI `oauth` app through the authorization code flow with PKCE and a callback on 127.0.0.1, renewed before it expires; an MCP server's own OAuth, by its discovery and client registration, through rmcp.
- A spec that names a scheme, with nothing stored, is called without credentials, so anonymous reads still answer; a 401 or 403 says how to sign in.

**Not offered**
- No sampling, elicitation or roots. A tool that asks for input mid-call (`input_required`), or turns into a task, answers that wrepl does not follow it.
- Legacy SSE MCP servers and Swagger 2.0 are refused with what to do instead.

**The hive**
- Its own workspace, depending on thrum-core, hum-thrum and hum-paths only; humd is a dev-dependency, for the bee test.
- Orchfile, `build` and `install` as the bash hive has them. Nothing goes on the PATH; the install prints where `web2` is. `web2` alone is the same REPL a bloom reaches, for a person at this machine.
- One row in the hives catalogue.

**Peers**
- A peer's bloom reaches web2 only when both sides say so: the peer's humd trusts remote attendants (#96), and this web2.json says `servePeers: true`. Otherwise a peer's reach, which carries its `from`, is refused, saying so: what web2 is signed in to is this machine's.

**Known limit, with its own change to follow**
- The tokens are as safe as the user web2 runs as, and `bash` on a builder runs as that user. They sit in `secrets/` so the builder (soon `shell`) can keep its shells out of hum's secrets, bee keys included.

**Verified**
- `cargo test`: 37 unit, 6 end to end against the real binary (an OpenAPI mock, an MCP server over stdio, one over HTTP with a bearer), 1 through a real humd: forager → humd → a nurse reaching `wrepl` → humd → `web2 serve` → an MCP server, and back, a config change mid-run included.
- An OpenAPI OAuth sign-in and renewal against a mock token endpoint, the PKCE verifier checked against its challenge.
- An MCP server's OAuth end to end against an authorization server shaped as the spec has it (protected resource and server metadata, client registration, PKCE, the issuer), then a call with the stored token.
- `web2 allow` / `deny` / `review` against a real config, globs included.
- `cargo clippy --all-targets -D warnings`, `./build` (release, `--locked`), tools/speech.py.
- Live: the Swagger Petstore (a relative server URL), GitHub's REST spec (1,242 commands; an anonymous `repos/get` in under a second), DeepWiki's MCP over HTTP.
@adiled
adiled deleted the humd/attendants-across-the-mesh branch October 31, 2026 13:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant