MCP surface
The harness ships an embedded MCP server (rmcp 1.7). The built-in
hyperhive surface is served over streamable HTTP by a persistent
hive-mcp-http daemon (loopback, 127.0.0.1:<hyperhive.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 (hyperhive.extraMcpServers) pick their own transport per
entry (type = "stdio" | "http", default "stdio"): matrix stays a
stdio bridge, bash runs its own persistent streamable-http listener
(hive-bash-daemon) — same reasoning as the built-in surface. The
server name is hyperhive, so the tools land in claude as
mcp__hyperhive__<tool>.
Tool access is gated by tool groups (HIVE_TOOL_GROUPS). The default
preset (AGENT_DEFAULT) includes messaging, meta, inbox, and
execution. Privileged groups (lifecycle, approvals, scheduling,
diagnostics) are opt-in via the P3RM1SS10NS tab.
Core tools (always available)
Messaging (messaging group): send(to, body, in_reply_to?),
recv(wait_seconds?, max?), ask(question, options?, multi?, ttl_seconds?, to?), answer(id, answer), ack_until(up_to).
send— message a peer (logical name) or the operator (to: "operator"). Useto: "<parent>"to address the topology parent without hardcoding the label; the broker resolves the sentinel at delivery time. Optionalin_reply_to: i64links the message to a prior id for thread rendering. Per-agenthyperhive.allowedRecipients(default: empty = unrestricted) limits which namessendaccepts — useful for sandboxing: set[ "operator" ]to restrict a sub-agent to operator messages only (the topology parent is always reachable regardless of this list — that carve-out is structural, keyed on parent relationship, not name).recv— drain inbox. Withoutwait_seconds(or0) returns immediately. Positive value parks the turn up to that many seconds (cap 180) — incoming messages wake instantly.max(default 1, cap 5) drains up to N rows;wait_secondsapplies to the first, then drains up tomaxtotal. Each returned row is prefixed with[msg #<id>](broker row id; note the highest id seen, then pass it toack_untilto bulk-triage the batch). Graceful shutdown: when the harness receives a stop signal, the inbox becomes fenced andrecvreturns an explicitfrom: "graceful-stop"message instead of an empty inbox. This unmissably directs the agent to flush durable state (/statefiles) 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 huge transcript.ask— surface a structured question to the operator (default) or a peer agent (to: "<agent>"). Non-blocking — returns a question id; the answer arrives as aquestion_answeredsystem event in the asker's inbox.optionsis advisory;multi=truerenders as checkboxes;ttl_secondsauto-cancels with answer[expired].answer— respond to aquestion_askedevent routed to this agent. Strict authorisation: only the declared target can answer.ack_until(up_to)— bulk-mark inbox rows handled: every row with broker id<= up_tois stamped 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, thenack_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): lifecycle and Q&A events
delivered as regular inbox messages (same recv path; body is a JSON
object with an event discriminant field). The submitting agent
(the root agent for top-level containers; an agent with the approvals
tool group for its own subtree) receives lifecycle events (spawned,
rebuilt, killed, destroyed, container_crash, needs_login,
logged_in, config_ready, needs_update, approval_resolved). Any
agent receives Q&A events when it is the declared target
(question_asked) or the asker (question_answered). Full payload
shapes and routing logic in
docs/approvals.md § Helper events.
Inbox (inbox group): get_loose_ends(agent?),
cancel_loose_end(kind, id), remind(message, delay_seconds? | at_unix_timestamp?), request_next_turn().
get_loose_ends(agent?)— list pending questions (asked/owed), scheduled reminders, and active local tasks published by external MCP daemons (e.g. running bash tasks fromhive-bash-daemon). Each row carries an id + kind forcancel_loose_end. Omitagentto list your own threads. Passagent: "<name>"to inspect a direct child agent (always accessible per topology enforcement); non-children require thequery_agent_statecapability. The"*"hive-wide query is not available on the agent socket.cancel_loose_end— withdraw aquestion(posts[cancelled by <self>]), hard-delete areminder, or cancel a pendingapprovalrow. Agents may only cancel rows they own; theapprovalkind 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).request_next_turn— ask the harness to start another turn immediately after this one ends, even if the inbox is empty. Next turn fires withfrom: "self"andbody: "continue".
Meta (meta group): set_status(text), get_agent_meta(name?).
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.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 tool group (like set_status): compact().
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.
Privileged tools (by tool group)
- Bash execution (
execution) — background shell tasks. Seedocs/tools/bash.md. - Lifecycle + config (
lifecycle,approvals) — manage child agents, spawn new ones, apply config commits. Seedocs/tools/lifecycle.md. - Scheduling + diagnostics (
scheduling,diagnostics) — scheduled prompts,get_logs. Seedocs/tools/scheduling.md. - Forge repos (
forge) —create_repo— the only agent path to create a repo under theagents/org (direct forge token creation is disabled for agents). The repo is created in the c0re-ownedagentsorg; the calling agent gets write collaborator access; the default branch is branch-protected (operator-team must approve merges, so the agent cannot self-merge). Opt-in; not in any default preset. Seedocs/tools/forge.md — Repo management. - Web egress (
web_tools) — enables Claude's built-inWebFetchandWebSearchtools (not MCP tools; added directly to the--allowedToolslist). Off by default; add the group in the P3RM1SS10NS tab and rebuild to enable. - Capability-gated —
get_host_journal(requiresread_host_journalcapability set via the P3RM1SS10NS tab; orthogonal to tool groups). Full list of capabilities and their effects indocs/conventions.md#capabilities. Also documented indocs/tools/scheduling.md. - Matrix MCP + extra servers —
mcp__matrix__*tools and per-agent extra MCP config. Seedocs/tools/matrix.md.
Waking the agent from inside the container
External MCP servers (and any other in-container process) can
inject a wake-up event into the agent's inbox via the per-agent
socket at /run/hive/mcp.sock. Speak the wire protocol directly —
JSON-line over the unix socket: {"cmd":"wake","from":"matrix","body": "new dm from @alice"}\n. Same shape as any other AgentRequest; see
hive-sh4re::AgentRequest::Wake. (An earlier hive-agent-wake CLI
wrapper existed for this but was removed — no shipped co-process
daemon actually shelled out to it; every one that wakes the harness
(matrix, bash) dials the socket directly, so the raw protocol is the
only path now.)
The wake event lands in the broker as {from:<label>, to:<agent>, body}, waking whatever recv call the harness
is currently blocked on. The next turn fires with the wake
prompt formed from that message.
Identity = socket: anything that can connect to
/run/hive/mcp.sock is implicitly trusted to inject these —
the bind-mount is the agent's own container only.
Authoritative state
hive_ag3nt::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 moved to the wake prompt + UI header.
Tool whitelist (mcp_config::ALLOWED_BUILTIN_TOOLS)
- Allowed built-ins:
Edit,Glob,Grep,Read,Write. - Tool-group-gated built-ins:
WebFetch,WebSearch(added when theweb_toolstool group is enabled — 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.Bashis not in the managed deny — see below. - Allowed MCP tools: as listed above (by tool group).
Bash is disallowed for the autonomous harness — 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 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 (#2356); choom is an operator (root) action, 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.