12 KiB
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. rea (gated on
services.hyperhive.agent.rea.enable) is a second stdio entry, spawning rea mcp. 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/4-implementation/agent-tools/subagent.md
for what reaches a subagent's own --mcp-config and what doesn't.
An "http" entry that needs request headers (a bearer-authenticated remote
server) sets headersFile to an absolute path: a file holding a JSON object
of header name to value. It renders as claude's headersHelper (cat of the
file), so the secret never enters the nix store, and claude re-reads a rotated token
on its next reconnect. ACP agents don't read it.
The agent's permissions gate tool access
(docs/5-project/process/conventions.md#permissions).
The default (Permission::DEFAULT) is messaging, meta, inbox and
execution; swarmctl agent permissions set changes it, and the next turn
runs on the change.
Core tools (always available)
Messaging. There is no hyperhive messaging tool. Agents talk to peers and the
operator over matrix (matrix); send_dm takes a peer's agent
name (iris, meaning that agent on the same homeserver) or a full user id. An agent
without matrix tools is cut off from peers on purpose, and the operator reads its
set_status line, its turn output on the dashboard, and its logs. The messaging
permission remains an accepted value for stored permission sets, but grants no tool.
The inbox itself is the harness's: it pops one message per turn and acks it when the
turn ends, so there is no agent-facing recv or ack_until. Graceful shutdown:
when the harness receives a stop signal, the inbox becomes fenced and the harness
delivers an explicit from: "graceful-stop" message as the turn's prompt. It
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.
System messages (from sender system): the broker delivers them as regular inbox
messages (the same path as every other inbox message). A container crash never reaches an
agent — it only produces a log line + swarm-level grafana
alert, no agent turn at all (docs/4-implementation/scheduler/observability.md). 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. Always the caller's own rows. Full per-variant wire shape indocs/5-project/process/conventions.md§ Loose-ends wire shape.cancel_loose_end— hard-delete areminderor clear atodorow (loose-ends-v2). Agents may only cancel rows they own.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 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_accountsis a list of matrix identities the agent can act as (name,user_id?,homeserver); omitted for agents with no matrix provisioning. Omitnameto query self.
Always-on, no permission needed: 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 themetagroup — 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/compactbutton. 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 deferredcompact_pendingflag 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 — seehive-agent/src/todo_server.rs.mark_todos_done(ids)— bulk-clear specific loose-ends-v2 todo rows by id in one call, instead ofcancel_loose_ending each one. List-based: pass the idsget_loose_endsactually showed you reviewed; unknown or already-done ids are silently skipped. Dials the same in-agent todo socket ascancel_loose_end'stodokind.
Privileged tools (by permission)
- Bash execution (
execution) — background shell tasks. Seedocs/4-implementation/agent-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 permission gates it. Seedocs/4-implementation/agent-tools/subagent.md. - Config changes — no permission: they go through a forge PR on
agent-configs/<name>— seedocs/2-handbook/agents/config-changes.md. - Web egress (
web_tools) — enables Claude's built-inWebFetchandWebSearchtools (not MCP tools; added directly to the--allowedToolslist). Off by default. - Recovery mounts (
manage_root_agent) — no tool: every agent's state and config mounted into this agent's container. Seedocs/5-project/process/conventions.md#permissions. - Matrix MCP + extra servers —
mcp__matrix__*tools and per-agent extra MCP config. Seedocs/4-implementation/agent-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/4-implementation/agent-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 onRecvwaiting for mail.Thinking—claude --printis running for a turn.Compacting— operator-triggered/compactis 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 (the Permission enum itself is in hive-types), not in the
harness, because the
subagent daemon spawns its own claude and must resolve the same set —
see docs/4-implementation/agent-tools/subagent.md.
- Allowed built-ins:
Edit,Glob,Grep,Read,Skill,Write.Skillis what makes an installed plugin'sSKILL.mdinvokable — without it, a skill'sdescriptionfrontmatter never gets seen by the model no matter how well it matches the task. - Permission-gated built-ins:
WebFetch,WebSearch(added once the agent holdsweb_tools). - 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.Bashisn't in the managed deny — see below. - Allowed MCP tools: as listed above (by permission).
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 available whenever the agent holds execution.
WebFetch / WebSearch are off by default; grant web_tools
(swarmctl agent permissions set) to enable them from the next turn.