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

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 in docs/5-project/process/conventions.md § Loose-ends wire shape.
  • cancel_loose_end — hard-delete a reminder or clear a todo row (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_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 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 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_ending 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 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/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 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 (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. 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.
  • Permission-gated built-ins: WebFetch, WebSearch (added once the agent holds web_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. Bash isn'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.