# Subagent daemon `hive-subagent-daemon` (crate `hive-subagent-mcp`) spawns nested headless `claude` sessions on request. Own process, own systemd unit, own MCP server (`subagent`, not `hyperhive`) — independent of `hive-bash-daemon`: a subagent is a full nested claude process, a materially heavier capability than a background shell command, so it gets its own deployable/restartable unit rather than living inside the bash daemon. Shipped default-on for every agent — `nix/agent-modules/mcp.nix` injects `subagent` into `hyperhive.extraMcpServers` via `lib.mkDefault` (`allowedTools = ["*"]`), same as `bash`. Default-on rather than unconditional: an `agent.nix` can override or drop the entry, which is what `mkDefault` is there for. The operator's own framing: default-on for now, a real opt-in capability later. For what the tools do and when an agent should reach for them, see the `subagent` MCP server's own tool descriptions and the `base:claude-subagents` skill — this page covers the daemon as deployed infrastructure, not the agent-facing API. ## Tools Served under the `subagent` MCP server (`mcp__subagent__`): `start`, `continue`, `status`, `interrupt`. ## State In-memory only: what's running now, where each name's session lives, how each name's last turn ended, and when each running turn last produced output. All of it lives only as long as the daemon process does. A daemon restart stops whatever was running rather than adopting it. The durable record of a subagent's existence is claude's own on-disk session (`hive_claude::SessionStore`), which `continue` reattaches to independent of the daemon's own lifetime — a restart loses the _in-flight turn_, not the subagent's history. Because the remembered directory goes with the rest of it, a `continue` after a restart has to re-supply `dir` when the session lives anywhere other than the daemon's own working directory — and a restart is the situation you reach for `continue` in most often. ## Is it working, or is it wedged? `status` reporting **running** says a process is tracked, which a wedged subagent satisfies as fully as a busy one. A running answer therefore carries the age of that turn's last event too: seconds means it's working, an age climbing into the minutes with no end-of-turn todo means it's stuck. That one number replaces inferring the same thing from `ps` output and CPU-time deltas. Every line the subagent's `claude` process writes bumps the timestamp — stream-json events, plain stdout chatter and stderr alike — and nothing about the content is inspected. The record says the child is alive, not what it's doing. The clock starts at the spawn, so a subagent that wedged before it ever emitted anything still reports a climbing age rather than no age at all. It's dropped when the turn ends, since a finished turn has no progress left to describe. ## A `continue` that finds no session `continue` doesn't check for the session before spawning. claude's own `--resume` is the authority, and it exits non-zero rather than quietly starting a fresh session, so the check could only duplicate the lookup the driver was about to do — while answering as though the session were gone. The usual truth is that the session exists somewhere else. `continue` returns as soon as the process is confirmed running, same as always, so the failure lands where every other failed turn lands: that turn's end-of-turn todo, carrying claude's own message plus the location this daemon searched. ``` claude error: no session matched the requested id or title (searched /home/agent/.claude for cwd /home/agent/work; if the session was started elsewhere, pass `dir`) ``` The directory is the part claude's own message never names, and the part that resolves the confusion: pass `dir` to point `continue` at the directory the session was started in. ## A killed turn A subagent whose `claude` process dies on a signal — the kernel's OOM killer under memory pressure, a stopped unit, an `interrupt` from the owning agent — didn't finish its turn, and the daemon says so rather than letting it settle back into `idle`: - `status` reports the session **killed**, naming the signal, instead of the `idle` it reports for a turn that ended on its own; - the end-of-turn todo the daemon pushes without being asked says the subagent was killed mid-turn, not that it finished; - `continue` still resumes such a session — often what you want — but its reply says the previous turn was killed, so nobody carries on from cut-off work believing it was complete. The record is per-name, in memory with the rest of this daemon's state, and the next confirmed spawn under that name clears it. A daemon restart loses it along with everything else — the daemon has to outlive the kill to report it, which is what `OOMPolicy=continue` on the unit is for. ## Compaction trade-off Built on `hive_claude::Claude::spawn` + `RunningClaude::wait` directly rather than `InfiniteSession::run`, since only the low-level driver exposes a cancel handle to stop a turn mid-flight — that's what makes `interrupt` genuinely stop a running turn rather than only cancelling a still-pending one. The cost: a turn that overflows the context window surfaces as an error rather than self-healing via reactive compaction. Subagents are meant to be bounded, single-batch work, not sessions long-lived enough to need in-place compaction — a real follow-up if that assumption stops holding. ## Configuration `hyperhive.mcp.subagentHttpPort` — the daemon's streamable-http listen port. Same pattern as `bashHttpPort`/`matrixHttpPort`: a per-agent default assigned by `nix/agent-modules/mcp.nix`, only worth overriding for an agent that needs a stable or non-default port. Own systemd unit, defined alongside the other per-agent MCP daemons in `nix/agent-modules/mcp.nix`. ## MCP servers available to a subagent A subagent runs with `--strict-mcp-config` and no `--mcp-config` by default — zero MCP servers, full stop; it falls back to claude's own native tools (`Bash`, `WebFetch`, etc.), not the parent's `mcp__bash__*` / `mcp__hyperhive__*` surface. Nothing implicit reaches it: the built-in hyperhive surface (todos/messaging) isn't an `extraMcpServers` entry at all, and the automatically injected `bash`/`subagent` entries default to excluded too (a subagent can't spawn hive-bash tasks or its own nested subagents unless an operator opts them in explicitly, same as anything else). Set `hyperhive.extraMcpServers..availableToSubagents = true` on a specific entry to hand that one server to subagents as well — useful for, say, a read-only lookup or scraper MCP a subagent's bounded, single-batch task might need. `hive-subagent-mcp`'s `mcp_config` module renders the opted-in subset into its own `--mcp-config` file per turn; an entry left at the default `false` never appears there.