Rewrites the config-change flow around the forge merge and the
DeployRequest{rev} deploy, drops the MergeConfigPr approval, its deploy
DAG, the hive's `/webhook/` route and the `core` merge allowlist from
the docs, and states that operators join the `operators` team by hand.
Refs #4850
246 lines
13 KiB
Markdown
246 lines
13 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`). 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.
|