From e64639c4d0d102ced3335bedd8d79d9b8faad61b Mon Sep 17 00:00:00 2001 From: damocles Date: Wed, 9 Sep 2026 18:15:35 +0200 Subject: [PATCH] docs: document the independent subagent daemon (start/continue/interrupt) --- CLAUDE.md | 8 ++ .../base/skills/claude-subagents/SKILL.md | 39 ++++++- docs/tools/README.md | 4 + docs/tools/bash.md | 23 ++-- docs/tools/subagent.md | 102 ++++++++++++++++++ docs/turn-loop/mcp.md | 3 + 6 files changed, 165 insertions(+), 14 deletions(-) create mode 100644 docs/tools/subagent.md diff --git a/CLAUDE.md b/CLAUDE.md index fb26720c..1fac48ea 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -98,6 +98,14 @@ hand-maintained per-file tree drifts out of sync with the code. directly over streamable-http (no stdio bridge), writes task files under `/harness/bash-tasks/`, and records the favorite-tools `bash_commands` stat into turn-stats.sqlite. +- **`hive-subagent-mcp/`** — per-agent claude-subagent runner daemon + (`hive-subagent-daemon`); spawns nested claude sessions on request and + serves `start`/`continue`/`interrupt` directly over streamable-http (no + stdio bridge). Independent of `hive-bash-mcp` (a subagent is a much + heavier capability than a bash command). No task files — the daemon's + only state is an in-memory map of currently-running processes, live + only as long as the process is; the actual claude session survives a + daemon restart independently (see `session.rs`'s module doc). - **`hive-sh4re/`** — shared wire types (Agent / Manager request + response, `Message`, `Approval`, `HelperEvent`) used across the unix sockets. Host-admin-socket and hive-priv-socket wire types have been diff --git a/claude-plugins/plugins/base/skills/claude-subagents/SKILL.md b/claude-plugins/plugins/base/skills/claude-subagents/SKILL.md index 4305eae2..d5a11c5a 100644 --- a/claude-plugins/plugins/base/skills/claude-subagents/SKILL.md +++ b/claude-plugins/plugins/base/skills/claude-subagents/SKILL.md @@ -3,11 +3,11 @@ name: claude-subagents description: Spin up a short-lived headless `claude` sub-instance to grind through a well-scoped, mechanical batch (bulk relabeling, a repetitive find/replace, a mechanical migration) instead of burning your own context on it inline. Use this when a task has a clear, describable recipe and is either big enough to eat your context or long enough that you'd rather not babysit it. Not for judgement-heavy work, anything needing operator back-and-forth, or a task whose blast radius you can't bound up front. --- -# Ephemeral Sub-Agents via the Claude CLI +# Ephemeral Sub-Agents -`claude` is on `PATH` in your container, and a sub-instance you spawn -inherits the same filesystem and credentials you have. This is a -first-class tool for offloading a bounded, mechanical batch - not a hack. +A sub-instance you spawn inherits the same filesystem and credentials you +have. This is a first-class tool for offloading a bounded, mechanical +batch - not a hack. ## When to use it @@ -18,7 +18,36 @@ first-class tool for offloading a bounded, mechanical batch - not a hack. - **No:** judgement-heavy work, anything needing back-and-forth with a human, or a task whose blast radius you can't bound up front. -## The spawn command +## The `subagent` MCP tools + +Your container's `subagent` MCP server (`hive-subagent-daemon`) is this +skill's recipe as MCP calls — no manual shell-wrapping, no hand-rolled +backgrounding: + +``` +start(name, prompt_file, model?, trigger?) +continue(name, prompt, model?) +interrupt(name, force?) +``` + +`name` ↔ `--name`, `model` ↔ `--model`, `prompt_file` ↔ +`--append-system-prompt-file`, `trigger`/`prompt` ↔ `-p`. `start` returns +once the process is confirmed running, not once it finishes — a +completion lands as a todo (`get_loose_ends`), same as any other producer. +There's no `status` poll: check on a subagent by `continue`-ing it (refused +while it's still running, which is itself the "still going" signal) or by +reading its completion todo. `interrupt` genuinely stops a running turn +(`force: true` for SIGKILL) — unlike bash tasks' `kill`, this isn't +limited to a still-pending state. + +Everything else in this skill (model choice, prompt hygiene, splitting +big batches, verify-then-report) applies unchanged; only the launch +mechanics differ from a raw shell invocation. + +No `subagent` MCP server on this container? Fall back to the manual +command below — same recipe, you drive the backgrounding yourself. + +## The spawn command (manual fallback) Put the task recipe in a file and pass it as a path, with a short `-p` trigger: diff --git a/docs/tools/README.md b/docs/tools/README.md index fba7cc15..04fd50a1 100644 --- a/docs/tools/README.md +++ b/docs/tools/README.md @@ -28,6 +28,10 @@ debug agent behavior. - **[bash](bash.md)** — background shell execution (`mcp__bash__*`), available on every agent unconditionally. +- **[subagent](subagent.md)** — spawn nested headless claude sessions + (`mcp__subagent__{start,continue,interrupt}`), shipped default-on for + every agent today alongside `bash` (expected to become a real opt-in + capability later). - **[forge](forge.md)** — the `hive-forge` Forgejo CLI every agent has for issues, PRs, and comments. Not an MCP tool — a binary agents shell out to instead of ad-hoc curl. diff --git a/docs/tools/bash.md b/docs/tools/bash.md index c9ef0b52..9b373aaf 100644 --- a/docs/tools/bash.md +++ b/docs/tools/bash.md @@ -93,15 +93,20 @@ this structured path so tasks get task-id tracking and structured output. `hive-bash-daemon` is a single long-running process (one per agent container, systemd service in `nix/agent-modules/mcp.nix`) — no stdio -bridge, no separate bin. It owns subprocess management, output file -writing, todo delivery on the harness's in-agent socket, **and** serves -the `run`/`status`/`kill` MCP tools directly over streamable-http on -`hyperhive.mcp.bashHttpPort` (declared in `hyperhive.extraMcpServers.bash` -as `{ type = "http"; url = ...; }`). Same shape as the built-in -`hyperhive` surface (`hive-mcp-http`) — claude reconnects to the stable -URL every turn instead of respawning a stdio child, so there's no -per-turn MCP re-registration race and no round-trip socket hop for tool -calls. +bridge. It owns subprocess management, output file writing, todo delivery +on the harness's in-agent socket, **and** serves the `run`/`status`/`kill` +MCP tools directly over streamable-http on `hyperhive.mcp.bashHttpPort` +(declared in `hyperhive.extraMcpServers.bash` as `{ type = "http"; url = +...; }`). Same shape as the built-in `hyperhive` surface (`hive-mcp-http`) +— claude reconnects to the stable URL every turn instead of respawning a +stdio child, so there's no per-turn MCP re-registration race and no +round-trip socket hop for tool calls. + +Independent daemon (own crate, own process/systemd unit/MCP server +`subagent`): `hive-subagent-daemon` — see [`subagent.md`](subagent.md). +Spawns nested claude sessions rather than shell commands, so it keeps no +task files here or anywhere else; a subagent task never shows up in this +daemon's `status`/`kill` or the running-tasks panel. ### Completion as a todo (loose-ends v2) diff --git a/docs/tools/subagent.md b/docs/tools/subagent.md new file mode 100644 index 00000000..ff80c833 --- /dev/null +++ b/docs/tools/subagent.md @@ -0,0 +1,102 @@ +# Subagent tools + +Spawns nested headless `claude` sessions via `hive-subagent-daemon`. Tools +land as `mcp__subagent__` (the MCP server name is `subagent`, not +`hyperhive`). Shipped default-on for every agent today — +`nix/agent-modules/mcp.nix` always injects `subagent` into +`hyperhive.extraMcpServers` (with `allowedTools = ["*"]`), same as `bash`. +The operator's own framing: shipped default-on for now, expected to become +a real opt-in capability later (not built yet). + +See the `base:claude-subagents` skill for _when_ to reach for this +(mechanical, bounded batches) versus doing the work inline. + +## Tools + +### `start(name, prompt_file, model?, trigger?)` + +Start a fresh subagent session under `name`, running in the background. +Returns as soon as the process is confirmed running — not once it +finishes. + +- `name` — session display name: claude's own `--name`/`--resume` session + title, and this daemon's tracking key while the process is alive. + `[a-z0-9-]`, max 63 chars. Reusable once a prior session under that name + has _finished_; refused while one is still running. +- `prompt_file` — path to a file passed as `--append-system-prompt-file`, + the subagent's actual task instructions. A file, not an inline string, + to avoid `ARG_MAX` on a large recipe. +- `model` — `--model` for the subagent's own claude invocation. Omit for + claude's own default. +- `trigger` — written to the subagent's stdin as its first turn's prompt. + Defaults to a generic "carry out your instructions" nudge. + +A prior _finished_ session under the same name is archived first (renamed +`.jsonl.archived`, not deleted) — a real fresh start, not a silent resume +of old history. + +Exposed as `mcp__subagent__start`. + +### `continue(name, prompt, model?)` + +Give an existing named session a new turn — whether that means "the +previous turn finished, here's a follow-up instruction" or "the daemon +restarted, reattaching to a session that survived it independently." +Returns as soon as confirmed running, same as `start`. Refused for a name +with no session on disk at all, or one currently running. + +- `name` — the existing session's name (from a prior `start`). +- `prompt` — the new turn's prompt. +- `model` — `--model` for this turn; doesn't have to match whatever + `start` used. + +Exposed as `mcp__subagent__continue`. + +### `interrupt(name, force?)` + +Signal `name`'s currently-running process. Only works while it's actually +running — there's no queued/pending state to cancel pre-emptively. + +- `force: false` (default) — SIGINT, letting claude shut down cleanly if + it's mid-response. +- `force: true` — SIGKILL. + +Exposed as `mcp__subagent__interrupt`. + +Always runs with `--dangerously-skip-permissions --strict-mcp-config` (no +`--mcp-config` override — a safety property, not a knob). + +## Checking on a subagent + +There's no `status` tool. A subagent's own claude session — not a +parallel log this daemon writes — is the record of what it did; read that +the same way you'd catch up on any other agent, by asking it directly +(`continue` with a prompt). The daemon pushes exactly one todo per +session lifetime: when a turn finishes, its completion summary lands via +`get_loose_ends` like any other producer's todo. `continue` on a name +that's still running is refused (with that as the tell it's still going) +rather than queuing behind it. + +## Architecture + +`hive-subagent-daemon` (own crate, `hive-subagent-mcp`) is independent of +`hive-bash-daemon` — a subagent spawns a full nested `claude` process, a +materially heavier capability than a bash command, worth its own +deployable/restartable unit. Own systemd unit, own streamable-http +listener on `hyperhive.mcp.subagentHttpPort`. + +**No task files.** The daemon's only state is an in-memory map of +currently-running processes (`name -> Cancel`), live only as long as the +process is — a restart stops whatever's running rather than adopting it. +The durable record of a subagent's existence is claude's own on-disk +session, found again by name via `hive_claude::SessionStore`; that's what +`continue` reattaches to, restart or not. + +**No mid-turn compaction.** Built on `hive_claude::Claude::spawn` + +`RunningClaude::wait` directly, not `InfiniteSession::run` — the latter +has no cancel handle to reach in from the outside, which is what would +make `interrupt` a no-op. The trade: a turn that overflows the context +window surfaces as a plain error instead of self-healing via reactive +compaction. Subagents are meant to be bounded, single-batch work (see the +`base:claude-subagents` skill), not sessions long-lived enough to need +in-place compaction — a real follow-up if that assumption stops holding. diff --git a/docs/turn-loop/mcp.md b/docs/turn-loop/mcp.md index d1d63549..d3c59fe7 100644 --- a/docs/turn-loop/mcp.md +++ b/docs/turn-loop/mcp.md @@ -128,6 +128,9 @@ hive_name?, swarm_name?, matrix_accounts? }`. `matrix_accounts` is a - **Bash execution** (`execution`) — background shell tasks. See [`docs/tools/bash.md`](../tools/bash.md). +- **Subagent spawning** — headless claude sub-instances as background + tasks, shipped default-on like bash execution (no tool group gates it + yet). See [`docs/tools/subagent.md`](../tools/subagent.md). - **Lifecycle + config** (`lifecycle`, `approvals`) — manage child agents, spawn new ones, apply config commits. See [`docs/tools/lifecycle.md`](../tools/lifecycle.md).