Watch
0
0
Fork
You've already forked hyperhive
0
hyperhive/docs/turn-loop/mcp.md

251 lines
14 KiB
Markdown

# MCP surface
The harness ships an embedded MCP server (rmcp 2). A persistent
`hive-mcp-http` daemon serves the built-in `hyperhive` surface over
streamable HTTP (loopback, `127.0.0.1:<services.hyperhive.agent.mcp.httpPort>`,
per-container private netns). Claude connects to its stable URL via
`--mcp-config` rather than respawning a stdio child each turn, so the
URL survives the per-turn claude re-spawn (and a host-side hive-c0re
restart) — there is no per-turn MCP re-registration race for the
built-in surface. HTTP is the sole transport for it (no stdio fallback).
Extra servers (`services.hyperhive.agent.extraMcpServers`) pick their own transport per
entry (`type = "stdio" | "http"`, default `"stdio"`): `matrix` and
`subagent` each run their own persistent streamable-http listener
(`hive-matrix-daemon`, `hive-subagent-daemon`), same reasoning as the
built-in surface and `bash`'s `hive-bash-daemon`. The GUI bridge
(`screen`, gated on `services.hyperhive.agent.gui.enable`) is the one
entry on the schema's stdio default — it spawns
`hive-screen-mcp` fresh each turn. The server name is `hyperhive`, so
the tools land in claude as
`mcp__hyperhive__<tool>`. Each entry also has an `availableToSubagents`
toggle (default `false`) — see
[`docs/tools/subagent.md`](../tools/subagent.md#mcp-servers-available-to-a-subagent)
for what reaches a subagent's own `--mcp-config` and what doesn't.
Tool groups (`HIVE_TOOL_GROUPS`) gate tool access. The default
preset (`AGENT_DEFAULT`) includes `messaging`, `meta`, `inbox`, and
`execution`. Privileged groups (`lifecycle`, `approvals`, `scheduling`)
are opt-in via the P3RM1SS10NS tab.
## Core tools (always available)
**Messaging** (`messaging` group): `send(to, body, in_reply_to?)`,
`recv(max?)`, `ack_until(up_to)`.
- `send` — message a peer (logical name) or the operator
(`to: "operator"`). Optional `in_reply_to: i64` links the
message to a prior id for thread rendering. Per-agent
`services.hyperhive.agent.allowedRecipients` (default: empty = unrestricted) limits
which names `send` accepts — useful for sandboxing: set
`[ "operator" ]` to restrict a sub-agent to operator messages only.
The operator stays reachable regardless of this list, so an agent can
always report a block.
- `recv` — drain inbox. Always an immediate peek, never blocks. `max`
(default 1, cap 5) drains up to N rows. `recv` prefixes each returned row
with `[msg #<id>]` (broker row id; note the highest id seen, then pass
it to `ack_until` to bulk-triage the batch). **Graceful shutdown**: when the harness
receives a stop signal, the inbox becomes fenced and `recv` returns an
explicit `from: "graceful-stop"` message instead of an empty inbox.
This unmissably directs the agent to flush durable state (`/state`
files) and end the turn — the container exits when the turn completes.
The graceful-stop turn takes the same post-turn compaction path as any
other turn: if the context crossed the watermark the harness runs a
notes-checkpoint turn and then `/compact`. Compacting before shutdown
keeps a later cold start cheap instead of re-uploading a large transcript.
- `ack_until(up_to)` — bulk-mark inbox rows handled: it stamps every row
with broker id `<= up_to` as acked in a single UPDATE.
Recipient-scoped (agents can only ack their own rows). Use when a
restart redelivers a large backlog of already-handled messages: read
the highest `[msg #N]` from the set you've actually processed, then
`ack_until(N)` to prevent re-pop. Acked rows never redeliver.
Transient pings (sentinel id 0) have nothing to ack and show no marker.
**System messages** (from sender `system`): the broker delivers
`hive_sh4re::manager::HelperEvent` (three variants: `ApprovalResolved`,
`ContainerCrash`, `NeedsUpdate` — declared, but no call site
constructs it) as regular inbox messages (same `recv`
path; body is a JSON object with an `event` discriminant field).
`ApprovalResolved` goes to whichever agent actually submitted the
approval (`Coordinator::notify_submitter`, looked up from the
authenticated socket caller at submit time; a row with no
recorded submitter falls back to the manager, `ruth`).
`ContainerCrash` always goes to `ruth` (`Coordinator::notify_manager`,
hardcoded — `hive-c0re/src/workers/crash_watch.rs`). A `MergeConfigPr`
approval's rebuild additionally pushes a `rebuilt:<agent>` todo to that
same submitter (`Coordinator::push_todo_submitter`, `subsystem =
"core"`), which wakes a turn (the todo-wake path — see [Turn
outcomes](README.md#turn-outcomes)) via a generic "call
`get_loose_ends`" prompt rather than the event body itself. Lifecycle
transitions the job-queue scheduler or crash watcher drive directly —
stop/kill, destroy, a flake-rev login or logout state change — reach
no individual agent: they publish onto a swarm-wide NATS
JetStream stream (`swarm_notices::notify`,
`hive-c0re/src/swarm_notices.rs`); every hive is swarm-controlled.
**Inbox** (`inbox` group): `get_loose_ends()`,
`cancel_loose_end(kind, id)`, `remind(message, delay_seconds? |
at_unix_timestamp?)`.
- `get_loose_ends()` — list this agent's own open threads: pending
reminders, open todos pushed by an in-container subsystem (matrix,
bash, forge, or any user-configured MCP server), an undelivered-inbox
count, and unread matrix notifications; `approval` rows only appear
when the caller is the manager (`ruth`) — sub-agents don't submit
approvals. Always the caller's own rows. Full per-variant wire shape in
[`docs/process/conventions.md` § Loose-ends wire
shape](../process/conventions.md#loose-ends-wire-shape).
- `cancel_loose_end` — hard-delete a `reminder`, cancel a pending
`approval` row, or clear a `todo` row (loose-ends-v2). Agents may
only cancel rows they own; the `approval` kind is further restricted
to the root agent (`ruth`) server-side.
- `remind` — schedule a reminder in this agent's own inbox. Large
payloads spill to `/agents/<self>/state/reminders/`. Pending count
capped at 50 per agent (`HIVE_REMIND_MAX_PENDING_PER_AGENT`).
No same-turn self-continue tool exists — see
[Turn outcomes](README.md#turn-outcomes) for why. Multi-step work rides
`remind` for a durable self-wake, or an in-container todo wake
(bash-task completion, forge notification, matrix activity) for work
already in flight.
**Meta** (`meta` group): `get_agent_meta(name?)`.
- `get_agent_meta` — fetch identity + status metadata for an agent:
`{ name, hyperhive_rev, running, status_text, status_set_at,
hive_name?, swarm_name?, matrix_accounts? }`. `matrix_accounts` is a
list of matrix identities the agent can act as (`name`, `user_id?`,
`homeserver`); omitted for agents with no matrix provisioning. Omit
`name` to query self.
**Always-on, no tool group**: `set_status(text)`, `compact()`,
`mark_todos_done(ids)`.
- `set_status` — set a free-text status string visible on the
dashboard. Single line, ≤ 200 chars. Persisted to
`{state_dir}/hyperhive-status`. Pass `""` to clear. Not gated by the
`meta` group — every agent needs to keep its dashboard status chip
current regardless of which optional groups it holds.
- `compact` — agent self-service equivalent of the operator dashboard's
`/compact` button. No args. Gated server-side on the last completed
turn's context usage: refused (with an explanation, no side effect)
unless usage is above 66% of the effective context window. On a
pass, queues the same deferred `compact_pending` flag the dashboard
button sets — consumed at the end of the current turn, so it never
races a live claude process, and the usual pre-compaction
notes-checkpoint turn still fires first. Dispatched through the
in-agent socket (`hive-agent-sock::Request::Compact`), not the
broker — see `hive-agent/src/todo_server.rs`.
- `mark_todos_done(ids)` — bulk-clear specific loose-ends-v2 todo rows
by id in one call, instead of `cancel_loose_end`ing each one.
List-based: pass the ids `get_loose_ends` actually showed you reviewed;
unknown or already-done ids are silently skipped. Dials the same
in-agent todo socket as `cancel_loose_end`'s `todo` kind.
## Privileged tools (by tool group)
- **Bash execution** (`execution`) — background shell tasks. See
[`docs/tools/bash.md`](../tools/bash.md).
- **Subagent spawning** — nested sessions on the agent's own runtime
(claude or ACP, via `hive-runtime`) as background tasks, shipped
default-on like bash execution; no tool group gates it. See
[`docs/tools/subagent.md`](../tools/subagent.md).
- **Lifecycle + config** (`lifecycle`, `approvals`) — `approvals`
gates `cancel_loose_end`'s approval-cancel arm server-side;
`lifecycle` gates nothing.
Config changes go through a forge PR on `agent-configs/<name>` — see
[`docs/agent-lifecycle/approvals.md`](../agent-lifecycle/approvals.md).
- **Scheduling** (`scheduling`) — scheduled prompts. See
[`docs/tools/scheduling.md`](../tools/scheduling.md).
- **Forge repos** (`forge`) — gates nothing.
- **Web egress** (`web_tools`) — enables Claude's built-in `WebFetch`
and `WebSearch` tools (not MCP tools; added directly to the
`--allowedTools` list). Off by default; add the group in the
P3RM1SS10NS tab and rebuild to enable.
- **Capabilities** — orthogonal to tool groups, set via the P3RM1SS10NS
tab. No capability registers an MCP tool today; the full list and their
effects are in
[`docs/process/conventions.md#capabilities`](../process/conventions.md).
- **Matrix MCP + extra servers** — `mcp__matrix__*` tools and
per-agent extra MCP config. See
[`docs/tools/matrix.md`](../tools/matrix.md).
## Waking the agent from inside the container
The built-in producers (matrix, bash, forge) upsert a todo on the
harness's in-agent socket (`HIVE_AGENT_SOCKET`, `UpsertTodo` — see
[`docs/tools/bash.md` § Completion as a todo
(loose-ends v2)](../tools/bash.md#completion-as-a-todo-loose-ends-v2)
for the full upsert/signal/clear mechanism): a new or changed summary
makes the harness signal its own turn loop, with no hive-c0re
round-trip. An external MCP server (or any other in-container process)
can push its own todo the same way — `subsystem` is any string.
The host-served per-agent socket at `/run/hive/mcp.sock` carries a
lower-level `Wake` request (`hive_core_agent_sock::Request::Wake {
from, body }` — JSON-line, `{"cmd":"wake","from":"matrix","body":"new dm
from @alice"}\n`) that drops the body straight into the broker inbox as
`{from:<label>, to:<agent>, body}`, bypassing the todo layer entirely.
`hive-c0re` handles it; matrix, bash and forge don't call it. Identity = socket: anything that can connect to
`/run/hive/mcp.sock` is implicitly trusted to inject one — the
bind-mount is the agent's own container only.
## Authoritative state
`hive-agent`'s `events::Bus` carries the current turn-loop state in
addition to the broadcast channel and the events history. Variants:
- `Idle` — sitting on `Recv` waiting for mail.
- `Thinking` — `claude --print` is running for a turn.
- `Compacting` — operator-triggered `/compact` is in flight.
The harness flips state at the relevant transitions
(`set_state(Thinking)` before `drive_turn`, `set_state(Idle)`
after; `set_state(Compacting)` around an idle operator compact in
`turn::run_pending_compact`). Exposed via `/api/state.turn_state` +
`turn_state_since` (unix seconds); the agent page renders this rather
than deriving from SSE events.
## Tool envelope
`mcp::run_tool_envelope`: every MCP tool handler logs the request,
runs the body, logs the result. Pre-/post-log only — the inbox
status hint lives in the wake prompt + UI header, not here.
## Tool allowlist (`hive_sh4re::permissions::ALLOWED_BUILTIN_TOOLS`)
The built-in list and the `--tools` value it resolves to live in
`hive-sh4re` alongside `ToolGroup`, not in the harness, because the
subagent daemon spawns its own `claude` and must resolve the same set —
see [`docs/tools/subagent.md`](../tools/subagent.md).
- Allowed built-ins: `Edit`, `Glob`, `Grep`, `Read`, `Skill`, `Write`.
`Skill` is what makes an installed plugin's `SKILL.md` invokable —
without it, a skill's `description` frontmatter never gets seen by
the model no matter how well it matches the task.
- Tool-group-gated built-ins: `WebFetch`, `WebSearch` (added once the
operator enables the `web_tools` tool group — see P3RM1SS10NS tab).
- Denied by omission (absent from the harness `--tools` /
`--allowedTools`, so they "literally don't exist" in a harness turn):
`Bash`, `Task`, `NotebookEdit`, `TodoWrite`.
- Additionally in the managed-settings deny list
(`/etc/claude-code/managed-settings.json`, un-overridable): `Task`,
`TodoWrite`. `Bash` **isn't** in the managed deny — see below.
- Allowed MCP tools: as listed above (by tool group).
The autonomous harness disallows `Bash` — shell execution goes
through `mcp__bash__run` (background tasks with structured output +
task-id tracking) instead of an interactive shell. The harness gate is
`--tools` / `--allowedTools` (Bash absent from `ALLOWED_BUILTIN_TOOLS`),
so Bash never exists in a harness turn regardless of managed settings.
`Bash` is deliberately **not** in the managed-settings deny so that the
operator-driven `hivectl agent <name> choom` session — which passes neither `--tools`
nor `--allowedTools` — gets claude's built-in synchronous `Bash` tool
(inline, human-approved). That sidesteps the async `mcp__bash__run`
completion wake landing in the wrong session (the harness inbox) for a
choom-started task; `choom` is an operator action (root or `hive-admin`),
so built-in shell there stays within the existing trust boundary. The bash MCP server
(`run` / `status` / `kill`) uses `allowedTools = ["*"]` so all
`mcp__bash__*` tools are always available regardless of tool groups.
`WebFetch` / `WebSearch` are off by default; enable the `web_tools`
tool group in the P3RM1SS10NS tab and rebuild the agent to enable them.