Watch
0
0
Fork
You've already forked hyperhive
0

docs/tools: the bash and matrix pages say what the tools do around the edges, not how to call them

Each page opens with its audience (the operator). The per-parameter tool
listings, which repeat the tools' own MCP descriptions, become short summaries
of the behaviour an operator needs: the completion todo, what status clears,
the process-group kill, the unread guard, how invites resolve. The wiring and
architecture sections are unchanged.
This commit is contained in:
iris 2026-10-26 16:47:28 +01:00 • committed by mara
commit 2d1b6a4e1a
2 changed files with 56 additions and 137 deletions

View file

@ -1,5 +1,8 @@
# Bash execution tools # Bash execution tools
For the operator: this page covers the daemon as deployed and what the tools do
around the edges. How an agent calls them is in the tools' own MCP descriptions.
Background shell execution via `hive-bash-daemon`. Tools land as Background shell execution via `hive-bash-daemon`. Tools land as
`mcp__bash__<tool>` (the MCP server name is `bash`, not `hyperhive`). `mcp__bash__<tool>` (the MCP server name is `bash`, not `hyperhive`).
`nix/agent-modules/mcp.nix` registers bash into `services.hyperhive.agent.extraMcpServers` `nix/agent-modules/mcp.nix` registers bash into `services.hyperhive.agent.extraMcpServers`
@ -16,78 +19,30 @@ so a change applies at the next one. See "Relationship to the
## Tools ## Tools
### `run(cmd, timeout_secs?, wait_seconds?, name?)` Each tool's parameters and return values are in its own MCP description, which
is what an agent reads at call time. This section says what the tools do around
the edges.
Submit a shell command for background execution (runs via `bash`). - **`run`** submits a shell command (through `bash`) as a background task. Its
Stdout and stderr stream to `harness/bash-tasks/<id>.{out,err}`. stdout and stderr stream to `harness/bash-tasks/<id>.{out,err}`. When the
When the task completes (or times out, or the process errors), it task finishes, times out or its process errors, it surfaces as a todo in the
surfaces as a todo in the agent's loose-ends (via `get_loose_ends`), agent's loose-ends, with the exit code and a pointer to the captured output.
carrying the exit code and a `Read(<path>)` pointer to the captured `run` waits up to 3 seconds (30 at most) for a result before it returns; a
output. Handle it on a future turn — unless `wait_seconds` already result delivered that way makes no todo. A caller-chosen `name` replaces the
delivered the terminal result inline, in which case the daemon creates generated id. The daemon accepts the name again once its task has finished,
no todo (see `status` below). and rejects it while the task is still pending or running.
- **`status`** polls a task: its state (`pending`, `running`, `done`,
- `timeout_secs` — kill the task after N seconds and mark it `timed_out`, `interrupted` or `killed`), the exit code, the run time and the last
`timed_out`. Omit for no timeout (runs until natural exit). 4 KiB of each stream. A call that sees a finished task clears its completion
- `wait_seconds` — inline poll before returning (capped at 30). todo, since the agent already has the result; in a narrow race both can still
When the task finishes within the window the full status is appear. A task marked `interrupted` had its process killed by a harness
returned immediately and the daemon creates no todo; when the window expires restart, and a best-effort todo is still made so the agent isn't blocked
the task keeps running and the daemon returns the normal silently.
`task started: id=<id>` response. **Defaults to 3** — pass `wait_seconds: 0` - **`kill`** stops a running or pending task and returns without waiting. The
to disable inline waiting and always get the immediate response. completion surfaces as a todo like any other. It signals the task's whole
- `name` — optional caller-chosen task id. When set it replaces the process group, so children the shell started stop too: SIGINT by default,
autogenerated hex id, so it surfaces in `status(<name>)` lookups and SIGKILL with `force`. The daemon cancels a task still pending before it
the loose-ends list — a memorable label instead of an opaque id. A name starts, and the task ends as `killed`.
is **reusable once its previous task has finished**; the daemon
rejects a name whose task is still `pending`/`running`. Allowed
characters: `[a-z0-9-]` (a valid identifier — lowercase, digits,
hyphen; max 63). Omit for the autogenerated id.
Exposed as `mcp__bash__run`.
### `status(id, wait_seconds?)`
Poll the status of a task submitted with `run`. Returns:
- `status` — `pending` / `running` / `done` / `timed_out` / `interrupted` / `killed`
- `exit_code` — set when done
- run duration
- last 4 KiB of stdout and stderr (full output in the `.out` / `.err` files)
`wait_seconds` — optional inline poll (capped at 30): when the task
finishes within the window the call returns the full status immediately.
Useful to avoid a separate round-trip when you expect the task to
finish soon.
Any `status` call (waited or not) that observes a terminal task clears
that task's completion todo — you already have the result in this
response, so no redundant loose-end follows. Narrow best-effort race: a
`status`/`run` inline wait that resolves in the same instant the task
actually finishes can still occasionally get both.
Tasks marked `interrupted` had their process killed by a harness
restart; a best-effort todo is still surfaced so the agent isn't
silently blocked.
Exposed as `mcp__bash__status`.
### `kill(id, force?)`
Stop a running or pending task by its ID (from `run`). Fire-and-forget:
sends the signal and returns without waiting — the completion surfaces
in the agent's loose-ends; handle it on a future turn.
- `force: false` (default) — SIGINT to the task's **process group**
(graceful; lets the process clean up). The whole process group is
signalled, so children spawned by the shell (cargo, nix, etc.) are
also stopped.
- `force: true` — SIGKILL.
If a SIGINT'd task doesn't exit, call `kill` again with `force: true`.
The daemon cancels a still-pending task before it starts. The task ends as
`killed` and surfaces in the loose-ends like any completion.
Exposed as `mcp__bash__kill`.
## Namespace note ## Namespace note

View file

@ -1,5 +1,9 @@
# Matrix MCP tools and extra MCP servers # Matrix MCP tools and extra MCP servers
For the operator: this page covers how the harness adds the matrix tools and what
they do around the edges. How an agent calls them is in the tools' own MCP
descriptions.
## Built-in matrix MCP (`mcp__matrix__*`) ## Built-in matrix MCP (`mcp__matrix__*`)
When the agent has at least one `services.hyperhive.agent.matrixAccounts` When the agent has at least one `services.hyperhive.agent.matrixAccounts`
@ -9,71 +13,31 @@ see Architecture below). No `matrix.enable` switch exists: the account
set *is* the switch, and the module declares the hive-internal `main` set *is* the switch, and the module declares the hive-internal `main`
account for you as soon as `services.hyperhive.agent.matrix.url` is account for you as soon as `services.hyperhive.agent.matrix.url` is
non-null — which hive-c0re fills in per agent whenever the operator non-null — which hive-c0re fills in per agent whenever the operator
configures the host-level matrix tuwunel. Tools land as `mcp__matrix__<name>`: configures the host-level matrix tuwunel. Tools land as `mcp__matrix__<name>`.
### Messaging Each tool's parameters and return values are in its own MCP description, which
is what an agent reads at call time. They fall into four groups:
**Unread guard**: `send_message`, `send_dm`, `send_file`, and - **Messaging:** `send_message`, `send_dm`, `send_reply`, `send_reaction`,
`send_reply` are all rejected if the room has unread messages — call `send_file`, `send_redact` and `open_dm`. A room is `!id:server` or
`read_room` then `mark_read` on the latest event before sending to a `#alias:server`, and the daemon resolves either. `open_dm` finds or creates the
room you haven't read yet. DM room and returns its id without sending anything, for a case where
`send_dm`'s body is inconvenient, such as a file attachment. `send_file` caps
an upload at 50 MiB. Redacting someone else's event needs moderator power.
- **Reading:** `read_room`, `download_file`, `list_rooms` and
`list_room_members`. `read_room` shows media as `[file: …]`, `[image: …]`,
`[audio: …]` or `[video: …]` markers, and the event id of one goes to
`download_file`.
- **Room membership:** `invite_user`, `list_invites`, `resolve_invite` and
`join_room`. `resolve_invite` accepts or rejects a pending invite and is the
path for invites; `join_room` is for a public room the agent wasn't invited to.
`list_invites` gives the inviter and the topic, so an agent can judge an
invite before it answers.
- **Receipts:** `mark_read` advances the read receipt.
- `send_message(room, body)` — send a markdown message; `room` **Unread guard.** `send_message`, `send_dm`, `send_file` and `send_reply` fail
accepts `!id:server` or `#alias:server`, daemon resolves either while the room has unread messages. The agent calls `read_room` and then
- `send_dm(user_id, body)` — open (or reuse) the DM room with `mark_read` on the latest event before sending to a room it hasn't read.
`user_id` and post `body` to it
- `open_dm(user_id)` — resolve (find-or-create) the DM room with
`user_id` and return its room id **without sending anything**.
Use the returned id with room-based tools (`send_message`,
`send_file`, …) to deliver into a DM when `send_dm`'s body
parameter is inconvenient (for example for file attachments)
- `send_reply(room, event_id, body)` — threaded reply to a specific
event
- `send_reaction(room, event_id, key)` — react to a message with an
emoji key
- `send_file(room, path, caption?)` — upload a local file and post it
as a room attachment, inferring the MIME type from the file extension
(50 MiB cap); an optional `caption` follows as a separate text message
- `send_redact(room, event_id, reason?)` — redact (delete) a specific
event; works on your own events, redacting others' requires moderator
power level
### Reading
- `read_room(room, limit?)` — recent timeline events; media attachments
appear inline as `[file: name]`, `[image: name]`, `[audio: name]`,
or `[video: name]` markers — pass the event id to `download_file`
to retrieve the attachment
- `download_file(room, event_id, dest_path?)` — download a media
attachment from a message event to a local file and return its path;
pair with `read_room` which surfaces attachments as the markers above
- `list_rooms()` — enumerate joined rooms
(`{ room_id, canonical_alias, name, member_count }` per room)
- `list_room_members(room)` — members of a room
### Room membership
<!-- vale write-good.Passive = NO -->
- `invite_user(room, user_id)` — invite `@user:server` into a room
you're already in; you must have a high enough power level.
The invitee sees a pending invite and resolves it via
`resolve_invite`.
- `resolve_invite(room, action)` — accept or reject a pending invite.
`action` is `"accept"` (join the room) or `"reject"` (decline and
leave). This is the path for invites; `join_room` is for joining a
public room you weren't invited to.
- `join_room(room)` — join a public room by id or alias. Also accepts
a pending invite if one exists, but prefer `resolve_invite` for
invites (it can reject too).
- `list_invites()` — rooms this agent has been invited to but not
yet joined (`{ room_id, canonical_alias, name, inviter, topic }` per room —
`inviter` and `topic` are there so an agent has enough to judge an
invite before `resolve_invite`ing it, not just a bare room id).
<!-- vale write-good.Passive = YES -->
### Receipts
- `mark_read(room, event_id)` — advance the read receipt
## Multiple accounts ## Multiple accounts