From 2d1b6a4e1a834f50bcd783e6bc268951db175606 Mon Sep 17 00:00:00 2001 From: iris Date: Mon, 26 Oct 2026 16:47:28 +0100 Subject: [PATCH] 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. --- docs/tools/bash.md | 97 ++++++++++++-------------------------------- docs/tools/matrix.md | 88 ++++++++++++---------------------------- 2 files changed, 52 insertions(+), 133 deletions(-) diff --git a/docs/tools/bash.md b/docs/tools/bash.md index 536e57f0..874e5cfb 100644 --- a/docs/tools/bash.md +++ b/docs/tools/bash.md @@ -1,5 +1,8 @@ # 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 `mcp__bash__` (the MCP server name is `bash`, not `hyperhive`). `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 -### `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`). -Stdout and stderr stream to `harness/bash-tasks/.{out,err}`. -When the task completes (or times out, or the process errors), it -surfaces as a todo in the agent's loose-ends (via `get_loose_ends`), -carrying the exit code and a `Read()` pointer to the captured -output. Handle it on a future turn — unless `wait_seconds` already -delivered the terminal result inline, in which case the daemon creates -no todo (see `status` below). - -- `timeout_secs` — kill the task after N seconds and mark it - `timed_out`. Omit for no timeout (runs until natural exit). -- `wait_seconds` — inline poll before returning (capped at 30). - When the task finishes within the window the full status is - returned immediately and the daemon creates no todo; when the window expires - the task keeps running and the daemon returns the normal - `task started: id=` response. **Defaults to 3** — pass `wait_seconds: 0` - to disable inline waiting and always get the immediate response. -- `name` — optional caller-chosen task id. When set it replaces the - autogenerated hex id, so it surfaces in `status()` lookups and - the loose-ends list — a memorable label instead of an opaque id. A name - 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`. +- **`run`** submits a shell command (through `bash`) as a background task. Its + stdout and stderr stream to `harness/bash-tasks/.{out,err}`. When the + task finishes, times out or its process errors, it surfaces as a todo in the + agent's loose-ends, with the exit code and a pointer to the captured output. + `run` waits up to 3 seconds (30 at most) for a result before it returns; a + result delivered that way makes no todo. A caller-chosen `name` replaces the + generated id. The daemon accepts the name again once its task has finished, + and rejects it while the task is still pending or running. +- **`status`** polls a task: its state (`pending`, `running`, `done`, + `timed_out`, `interrupted` or `killed`), the exit code, the run time and the last + 4 KiB of each stream. A call that sees a finished task clears its completion + todo, since the agent already has the result; in a narrow race both can still + appear. A task marked `interrupted` had its process killed by a harness + restart, and a best-effort todo is still made so the agent isn't blocked + silently. +- **`kill`** stops a running or pending task and returns without waiting. The + completion surfaces as a todo like any other. It signals the task's whole + process group, so children the shell started stop too: SIGINT by default, + SIGKILL with `force`. The daemon cancels a task still pending before it + starts, and the task ends as `killed`. ## Namespace note diff --git a/docs/tools/matrix.md b/docs/tools/matrix.md index 35c0a696..c3e8639f 100644 --- a/docs/tools/matrix.md +++ b/docs/tools/matrix.md @@ -1,5 +1,9 @@ # 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__*`) 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` account for you as soon as `services.hyperhive.agent.matrix.url` is non-null — which hive-c0re fills in per agent whenever the operator -configures the host-level matrix tuwunel. Tools land as `mcp__matrix__`: +configures the host-level matrix tuwunel. Tools land as `mcp__matrix__`. -### 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 -`send_reply` are all rejected if the room has unread messages — call -`read_room` then `mark_read` on the latest event before sending to a -room you haven't read yet. +- **Messaging:** `send_message`, `send_dm`, `send_reply`, `send_reaction`, + `send_file`, `send_redact` and `open_dm`. A room is `!id:server` or + `#alias:server`, and the daemon resolves either. `open_dm` finds or creates the + 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` - accepts `!id:server` or `#alias:server`, daemon resolves either -- `send_dm(user_id, body)` — open (or reuse) the DM room with - `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 - - -- `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). - - -### Receipts - -- `mark_read(room, event_id)` — advance the read receipt +**Unread guard.** `send_message`, `send_dm`, `send_file` and `send_reply` fail +while the room has unread messages. The agent calls `read_room` and then +`mark_read` on the latest event before sending to a room it hasn't read. ## Multiple accounts