Watch
0
0
Fork
You've already forked hyperhive
0
hyperhive/docs/4-implementation/agent-tools/bash.md

114 lines
6.4 KiB
Markdown

# 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__<tool>` (the MCP server name is `bash`, not `hyperhive`).
`nix/agent-modules/mcp.nix` registers bash into `services.hyperhive.agent.extraMcpServers`
by default (`lib.mkDefault`, with `allowedTools = ["*"]`) — an `agent.nix`
can override or drop the entry, so this is a default, not an
unconditional grant. Even where the entry is present, the server is only
actually rendered into an agent's claude config when the agent also
holds the **`execution`** permission: `extra_server_required_permission` in
`hive-agent/src/mcp_config.rs` suppresses it entirely without that
permission, so an agent lacking `execution` gets no `mcp__bash__*` tools at
all, not just an unused grant. The harness rewrites the config each turn,
so a change applies at the next one. See "Relationship to the
`execution` permission" below.
## Tools
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.
- **`run`** submits a shell command (through `bash`) as a background task. Its
stdout and stderr stream to `harness/bash-tasks/<id>.{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
`run` and `status` live in the `bash` MCP server, not `hyperhive`. The
tool names in claude are `mcp__bash__run` and `mcp__bash__status`.
hyperhive blocks the `Bash` built-in tool — all shell execution goes through
this structured path so tasks get task-id tracking and structured output.
## Architecture
`hive-bash-daemon` is a single long-running process (one per agent
container, systemd service in `nix/agent-modules/mcp.nix`) — no stdio
bridge. It owns subprocess management, output file writing, todo delivery
on the harness's in-agent socket, **and** serves the `run`/`status`/`kill`
MCP tools directly over streamable-http on `services.hyperhive.agent.mcp.bashHttpPort`
(declared in `services.hyperhive.agent.extraMcpServers.bash` as `{ type = "http"; url =
...; }`). Same shape as the built-in `hyperhive` surface (`hive-mcp-http`)
— claude reconnects to the stable URL every turn instead of respawning a
stdio child, so there's no per-turn MCP re-registration race and no
round-trip socket hop for tool calls.
### Completion as a todo (loose-ends v2)
When a bash task changes state, `hive-bash-daemon` upserts a single keyed
todo (`key = task id`) on the harness's in-agent socket (`HIVE_AGENT_SOCKET`)
— "running" at start, then the completion summary when it finishes. The
summary change signals the harness turn loop directly (in-process, no broker
round-trip), so the harness drives a turn for the agent to handle it via
`get_loose_ends`, then clears the todo with `cancel_loose_end(kind: "todo", id: N)` (dials the
in-container socket directly — no bash task involved, so clearing doesn't
spawn another todo). Same mechanism the matrix daemon uses for
unread rooms. An inline `wait_seconds` / `status` observation that already
delivered the result instead clears the keyed todo, so no redundant
loose-end follows.
### Status and output the swarm sees
The daemon also tells the swarm about each task, so the swarm UI can list the
running ones and read their output without calling the agent
(`swarm_wire::bash_task`; the bucket, the stream and the grants are in
[`controller.md`](../swarm/controller.md)). It publishes as the agent, over the
connection `swarm_queue_client::agent_publisher` makes with the agent's own
queue credential, and publishes nothing in a container without a queue address
or a secret store. Everything is best-effort: a failed connect or publish is a
log line, and a task never waits on the queue.
- **Status.** The runner writes a task file at every state change, and each
write also publishes the task's state to `$KV.bash-task-status.<agent>.<task>`.
While a task is `pending` or `running`, the publish task repeats it every 60
seconds, so a daemon that died reads as stale. A finished task leaves that
repeat.
- **Output.** A task's output goes to files, so while it runs the daemon reads
what the files gain twice a second and publishes it as rows on
`$SWARM.term.<agent>.bash.<task>`, each with the `Swarm-Stream` header
(`stdout` or `stderr`). A chunk never splits a character, and one larger than
the queue's payload limit goes out in pieces. When the process exits, the daemon
publishes what the files still hold before it writes the final status.
## Relationship to the `execution` permission
`Permission::Execution` (in `Permission::DEFAULT`) is the real gate on bash:
`extra_server_required_permission` (in `hive-agent/src/mcp_config.rs`)
controls whether the `bash` MCP server reaches the agent's claude
config at all, suppressing every `mcp__bash__*` tool
when absent — not just leaving them unused. Its own `tools()` returns
`&[]`: `mcp__bash__run`/`status`/`kill` live on the separate `bash` MCP
server that `extra_server_required_permission` gates, not on the
`mcp__hyperhive__*` allowlist `tools()` builds. Removing `execution`
from an agent's permissions **does** remove bash availability.