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 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/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 (messaging group): send(to, body, in_reply_to?), recv(max?), ack_until(up_to).

System messages (from sender system): the broker delivers hive_sh4re::manager::HelperEvent (one variant, 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). A container crash used to be the other variant (ContainerCrash, routed to ruth) but this tree no longer has that path — a crash now only produces a log line + swarm-level grafana alert, no agent turn at all (docs/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?).

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?).

Always-on, no permission needed: set_status(text), compact(), mark_todos_done(ids).

Privileged tools (by permission)

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) 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:

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/tools/subagent.md.

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.