102 lines
4.6 KiB
Markdown
102 lines
4.6 KiB
Markdown
# Subagent tools
|
|
|
|
Spawns nested headless `claude` sessions via `hive-subagent-daemon`. Tools
|
|
land as `mcp__subagent__<tool>` (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.
|