subagent: add status tool, cut docs down to operator-facing + no cli flags
This commit is contained in:
parent
e64639c4d0
commit
c2fb3c6e3e
7 changed files with 136 additions and 195 deletions
13
CLAUDE.md
13
CLAUDE.md
|
|
@ -100,12 +100,13 @@ hand-maintained per-file tree drifts out of sync with the code.
|
||||||
`bash_commands` stat into turn-stats.sqlite.
|
`bash_commands` stat into turn-stats.sqlite.
|
||||||
- **`hive-subagent-mcp/`** — per-agent claude-subagent runner daemon
|
- **`hive-subagent-mcp/`** — per-agent claude-subagent runner daemon
|
||||||
(`hive-subagent-daemon`); spawns nested claude sessions on request and
|
(`hive-subagent-daemon`); spawns nested claude sessions on request and
|
||||||
serves `start`/`continue`/`interrupt` directly over streamable-http (no
|
serves `start`/`continue`/`status`/`interrupt` directly over
|
||||||
stdio bridge). Independent of `hive-bash-mcp` (a subagent is a much
|
streamable-http (no stdio bridge). Independent of `hive-bash-mcp` (a
|
||||||
heavier capability than a bash command). No task files — the daemon's
|
subagent is a much heavier capability than a bash command). No task
|
||||||
only state is an in-memory map of currently-running processes, live
|
files — the daemon's only state is an in-memory map of currently-running
|
||||||
only as long as the process is; the actual claude session survives a
|
processes, live only as long as the process is; the actual claude
|
||||||
daemon restart independently (see `session.rs`'s module doc).
|
session survives a daemon restart independently (see `session.rs`'s
|
||||||
|
module doc).
|
||||||
- **`hive-sh4re/`** — shared wire types (Agent / Manager request +
|
- **`hive-sh4re/`** — shared wire types (Agent / Manager request +
|
||||||
response, `Message`, `Approval`, `HelperEvent`) used across the unix
|
response, `Message`, `Approval`, `HelperEvent`) used across the unix
|
||||||
sockets. Host-admin-socket and hive-priv-socket wire types have been
|
sockets. Host-admin-socket and hive-priv-socket wire types have been
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ description: Spin up a short-lived headless `claude` sub-instance to grind throu
|
||||||
|
|
||||||
A sub-instance you spawn inherits the same filesystem and credentials you
|
A sub-instance you spawn inherits the same filesystem and credentials you
|
||||||
have. This is a first-class tool for offloading a bounded, mechanical
|
have. This is a first-class tool for offloading a bounded, mechanical
|
||||||
batch - not a hack.
|
batch.
|
||||||
|
|
||||||
## When to use it
|
## When to use it
|
||||||
|
|
||||||
|
|
@ -20,72 +20,26 @@ batch - not a hack.
|
||||||
|
|
||||||
## The `subagent` MCP tools
|
## The `subagent` MCP tools
|
||||||
|
|
||||||
Your container's `subagent` MCP server (`hive-subagent-daemon`) is this
|
Your container's `subagent` MCP server (`hive-subagent-daemon`) runs this
|
||||||
skill's recipe as MCP calls — no manual shell-wrapping, no hand-rolled
|
skill's recipe for you:
|
||||||
backgrounding:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
start(name, prompt_file, model?, trigger?)
|
start(name, prompt_file, model?, trigger?)
|
||||||
continue(name, prompt, model?)
|
continue(name, prompt, model?)
|
||||||
|
status(name)
|
||||||
interrupt(name, force?)
|
interrupt(name, force?)
|
||||||
```
|
```
|
||||||
|
|
||||||
`name` ↔ `--name`, `model` ↔ `--model`, `prompt_file` ↔
|
`start` and `continue` return as soon as the process is confirmed
|
||||||
`--append-system-prompt-file`, `trigger`/`prompt` ↔ `-p`. `start` returns
|
running, not once it finishes — a completion lands as a todo
|
||||||
once the process is confirmed running, not once it finishes — a
|
(`get_loose_ends`), same as any other producer. Use `status` for a
|
||||||
completion lands as a todo (`get_loose_ends`), same as any other producer.
|
zero-cost "is it still going" check; reach for `continue` only once you
|
||||||
There's no `status` poll: check on a subagent by `continue`-ing it (refused
|
actually have a new instruction for it, since that spends a turn.
|
||||||
while it's still running, which is itself the "still going" signal) or by
|
`interrupt` genuinely stops a running turn (`force: true` for SIGKILL).
|
||||||
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
|
Model choice, prompt hygiene, splitting big batches, verify-then-report —
|
||||||
big batches, verify-then-report) applies unchanged; only the launch
|
everything else in this skill — applies exactly the same whether you're
|
||||||
mechanics differ from a raw shell invocation.
|
calling the tool or thinking through the recipe by hand.
|
||||||
|
|
||||||
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:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
claude --name <memorable> --model <cheaper-than-you> --dangerously-skip-permissions \
|
|
||||||
--append-system-prompt-file task-prompt.md \
|
|
||||||
-p "Carry out the task described in your instructions."
|
|
||||||
```
|
|
||||||
|
|
||||||
- **`--name <memorable>`** - names the session so you can `--resume` it
|
|
||||||
by name for follow-ups.
|
|
||||||
- **`--model <cheaper-than-you>`** - think about which model the task
|
|
||||||
actually needs; don't spend a bigger model's tokens than your own on
|
|
||||||
mechanical work a cheaper one handles fine. Never use a _bigger_ model
|
|
||||||
than yourself for a sub-agent - if the task needs that much capability,
|
|
||||||
it's not the "mechanical batch" case this skill is for.
|
|
||||||
- **`--dangerously-skip-permissions`** - required in headless (`-p`)
|
|
||||||
mode. Without it, tool calls that would normally prompt for approval
|
|
||||||
are auto-**denied** non-interactively, so the sub-agent silently does
|
|
||||||
nothing. Acceptable here because the sub-instance runs inside your
|
|
||||||
already-sandboxed environment, with your already-scoped credentials,
|
|
||||||
on a bounded task - don't reach for it when the task could touch
|
|
||||||
things outside its intended blast radius.
|
|
||||||
- **`--append-system-prompt-file <path>`** - read the recipe from a
|
|
||||||
file rather than inlining it in `-p` (which is bounded by `ARG_MAX` -
|
|
||||||
a long recipe passed as `-p "$(cat …)"` can blow past it and the exec
|
|
||||||
fails). Keeps Claude Code's default scaffolding and adds your recipe
|
|
||||||
on top.
|
|
||||||
- **`-p "<trigger>"`** - headless print mode. Keep this short: just
|
|
||||||
tell the sub-agent to act on its file-supplied instructions.
|
|
||||||
|
|
||||||
## Run it in the background, don't babysit
|
|
||||||
|
|
||||||
Wrap the launch in a background bash task and end your turn - your
|
|
||||||
bash-task runner already captures stdout/stderr and hands you a
|
|
||||||
completion pointer, and ending the turn keeps you reachable for other
|
|
||||||
work while the sub-agent grinds.
|
|
||||||
|
|
||||||
## Prompt hygiene - this is where batches succeed or fail
|
## Prompt hygiene - this is where batches succeed or fail
|
||||||
|
|
||||||
|
|
@ -121,10 +75,9 @@ running at once instead of handing the whole thing to one.
|
||||||
different files never race on the same working tree or index. For a
|
different files never race on the same working tree or index. For a
|
||||||
non-git batch (issue relabeling, API calls), independent items don't
|
non-git batch (issue relabeling, API calls), independent items don't
|
||||||
need filesystem isolation at all — just launch N in parallel.
|
need filesystem isolation at all — just launch N in parallel.
|
||||||
- **Launch all N in the background and don't babysit any single one** —
|
- **Launch all N and don't babysit any single one** — same as the
|
||||||
same as the one-subagent case, just N background bash tasks instead of
|
one-subagent case, just N `start` calls instead of one. Check on them
|
||||||
one. Check on them as a batch, not by polling each individually in a
|
as a batch, not by polling each individually in a loop.
|
||||||
loop.
|
|
||||||
- **Mind the container's memory cap before picking N.** Your whole
|
- **Mind the container's memory cap before picking N.** Your whole
|
||||||
container shares one `MemoryMax` (a few GB by default) with every
|
container shares one `MemoryMax` (a few GB by default) with every
|
||||||
subagent you spawn _and_ your own process. A `claude` process plus its
|
subagent you spawn _and_ your own process. A `claude` process plus its
|
||||||
|
|
@ -147,10 +100,10 @@ threaded — the coordination overhead isn't worth it below that size.
|
||||||
|
|
||||||
## Resume for follow-ups
|
## Resume for follow-ups
|
||||||
|
|
||||||
`claude --resume <name> -p "Now run the same procedure over the second batch."`
|
`continue` reuses the same session, so the sub-agent keeps every constant
|
||||||
reuses the same session, so the sub-agent keeps every constant and
|
and gotcha it already discovered instead of re-learning the surface from
|
||||||
gotcha it already discovered instead of re-learning the surface from a
|
a cold prompt. Reach for `start` under a new name only for a genuinely
|
||||||
cold prompt. Spawn a new `--name` only for a genuinely unrelated task.
|
unrelated task.
|
||||||
|
|
||||||
## Verify, then report
|
## Verify, then report
|
||||||
|
|
||||||
|
|
@ -160,14 +113,12 @@ anything the sub-agent left ambiguous or exempted for a human call.
|
||||||
|
|
||||||
## Pitfalls (all observed in practice)
|
## Pitfalls (all observed in practice)
|
||||||
|
|
||||||
- No `--dangerously-skip-permissions` → headless tool calls denied, the
|
|
||||||
sub-agent burns a run doing nothing.
|
|
||||||
- Using a model bigger than yourself → paying premium cost for
|
- Using a model bigger than yourself → paying premium cost for
|
||||||
mechanical work that didn't need it.
|
mechanical work that didn't need it.
|
||||||
- Skipping the one-item tuning pass → a systematic mistake smeared
|
- Skipping the one-item tuning pass → a systematic mistake smeared
|
||||||
across the whole batch.
|
across the whole batch.
|
||||||
- Spawning fresh instead of `--resume` for a follow-up → throws away
|
- `start`-ing fresh instead of `continue`-ing for a follow-up → throws
|
||||||
all the context the first pass earned.
|
away all the context the first pass earned.
|
||||||
- Baking an unverified assumption into the recipe - if a quick check
|
- Baking an unverified assumption into the recipe - if a quick check
|
||||||
suggests something "isn't possible" or "doesn't exist," confirm it
|
suggests something "isn't possible" or "doesn't exist," confirm it
|
||||||
before writing that conclusion into the prompt; a wrong assumption
|
before writing that conclusion into the prompt; a wrong assumption
|
||||||
|
|
|
||||||
|
|
@ -29,9 +29,9 @@ debug agent behavior.
|
||||||
- **[bash](bash.md)** — background shell execution (`mcp__bash__*`),
|
- **[bash](bash.md)** — background shell execution (`mcp__bash__*`),
|
||||||
available on every agent unconditionally.
|
available on every agent unconditionally.
|
||||||
- **[subagent](subagent.md)** — spawn nested headless claude sessions
|
- **[subagent](subagent.md)** — spawn nested headless claude sessions
|
||||||
(`mcp__subagent__{start,continue,interrupt}`), shipped default-on for
|
(`mcp__subagent__{start,continue,status,interrupt}`), shipped
|
||||||
every agent today alongside `bash` (expected to become a real opt-in
|
default-on for every agent today alongside `bash` (expected to become a
|
||||||
capability later).
|
real opt-in capability later).
|
||||||
- **[forge](forge.md)** — the `hive-forge` Forgejo CLI every agent has
|
- **[forge](forge.md)** — the `hive-forge` Forgejo CLI every agent has
|
||||||
for issues, PRs, and comments. Not an MCP tool — a binary agents
|
for issues, PRs, and comments. Not an MCP tool — a binary agents
|
||||||
shell out to instead of ad-hoc curl.
|
shell out to instead of ad-hoc curl.
|
||||||
|
|
|
||||||
|
|
@ -102,12 +102,6 @@ MCP tools directly over streamable-http on `hyperhive.mcp.bashHttpPort`
|
||||||
stdio child, so there's no per-turn MCP re-registration race and no
|
stdio child, so there's no per-turn MCP re-registration race and no
|
||||||
round-trip socket hop for tool calls.
|
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)
|
### Completion as a todo (loose-ends v2)
|
||||||
|
|
||||||
When a bash task changes state, `hive-bash-daemon` upserts a single keyed
|
When a bash task changes state, `hive-bash-daemon` upserts a single keyed
|
||||||
|
|
|
||||||
|
|
@ -1,102 +1,54 @@
|
||||||
# Subagent tools
|
# Subagent daemon
|
||||||
|
|
||||||
Spawns nested headless `claude` sessions via `hive-subagent-daemon`. Tools
|
`hive-subagent-daemon` (crate `hive-subagent-mcp`) spawns nested headless
|
||||||
land as `mcp__subagent__<tool>` (the MCP server name is `subagent`, not
|
`claude` sessions on request. Own process, own systemd unit, own MCP
|
||||||
`hyperhive`). Shipped default-on for every agent today —
|
server (`subagent`, not `hyperhive`) — independent of `hive-bash-daemon`:
|
||||||
`nix/agent-modules/mcp.nix` always injects `subagent` into
|
a subagent is a full nested claude process, a materially heavier
|
||||||
`hyperhive.extraMcpServers` (with `allowedTools = ["*"]`), same as `bash`.
|
capability than a background shell command, so it gets its own
|
||||||
The operator's own framing: shipped default-on for now, expected to become
|
deployable/restartable unit rather than living inside the bash daemon.
|
||||||
a real opt-in capability later (not built yet).
|
|
||||||
|
|
||||||
See the `base:claude-subagents` skill for _when_ to reach for this
|
Shipped default-on for every agent — `nix/agent-modules/mcp.nix` injects
|
||||||
(mechanical, bounded batches) versus doing the work inline.
|
`subagent` into `hyperhive.extraMcpServers` unconditionally (`allowedTools
|
||||||
|
= ["*"]`), same as `bash`. 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
|
## Tools
|
||||||
|
|
||||||
### `start(name, prompt_file, model?, trigger?)`
|
Served under the `subagent` MCP server (`mcp__subagent__<tool>`): `start`,
|
||||||
|
`continue`, `status`, `interrupt`.
|
||||||
|
|
||||||
Start a fresh subagent session under `name`, running in the background.
|
## State
|
||||||
Returns as soon as the process is confirmed running — not once it
|
|
||||||
finishes.
|
|
||||||
|
|
||||||
- `name` — session display name: claude's own `--name`/`--resume` session
|
In-memory only: a map of currently-running processes, live only as long
|
||||||
title, and this daemon's tracking key while the process is alive.
|
as the daemon process is. A daemon restart stops whatever was running
|
||||||
`[a-z0-9-]`, max 63 chars. Reusable once a prior session under that name
|
rather than adopting it. The durable record of a subagent's existence is
|
||||||
has _finished_; refused while one is still running.
|
claude's own on-disk session (`hive_claude::SessionStore`), which
|
||||||
- `prompt_file` — path to a file passed as `--append-system-prompt-file`,
|
`continue` reattaches to independent of the daemon's own lifetime — a
|
||||||
the subagent's actual task instructions. A file, not an inline string,
|
restart loses the *in-flight turn*, not the subagent's history.
|
||||||
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
|
## Compaction trade-off
|
||||||
`.jsonl.archived`, not deleted) — a real fresh start, not a silent resume
|
|
||||||
of old history.
|
|
||||||
|
|
||||||
Exposed as `mcp__subagent__start`.
|
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.
|
||||||
|
|
||||||
### `continue(name, prompt, model?)`
|
## Configuration
|
||||||
|
|
||||||
Give an existing named session a new turn — whether that means "the
|
`hyperhive.mcp.subagentHttpPort` — the daemon's streamable-http listen
|
||||||
previous turn finished, here's a follow-up instruction" or "the daemon
|
port. Same pattern as `bashHttpPort`/`matrixHttpPort`: a per-agent default
|
||||||
restarted, reattaching to a session that survived it independently."
|
assigned by `nix/agent-modules/mcp.nix`, only worth overriding for an
|
||||||
Returns as soon as confirmed running, same as `start`. Refused for a name
|
agent that needs a stable or non-default port.
|
||||||
with no session on disk at all, or one currently running.
|
|
||||||
|
|
||||||
- `name` — the existing session's name (from a prior `start`).
|
Own systemd unit, defined alongside the other per-agent MCP daemons in
|
||||||
- `prompt` — the new turn's prompt.
|
`nix/agent-modules/mcp.nix`.
|
||||||
- `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.
|
|
||||||
|
|
|
||||||
|
|
@ -15,20 +15,21 @@ use crate::session::{self, State};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, JsonSchema)]
|
#[derive(Debug, Deserialize, JsonSchema)]
|
||||||
struct StartArgs {
|
struct StartArgs {
|
||||||
/// Session name — becomes both this daemon's tracking key and claude's
|
/// Session name — this daemon's tracking key while it's alive, and the
|
||||||
/// own `--name`/`--resume` session title. Same identifier rules as the
|
/// identity to `continue`/`status`/`interrupt` it by afterward. Same
|
||||||
/// `bash` server's task names: lowercase, digits, hyphen, max 63 chars.
|
/// identifier rules as the `bash` server's task names: lowercase,
|
||||||
/// Reusable once a prior *finished* session under that name is done —
|
/// digits, hyphen, max 63 chars. Reusable once a prior *finished*
|
||||||
/// rejected while one under the same name is still running.
|
/// session under that name is done — rejected while one under the same
|
||||||
|
/// name is still running.
|
||||||
name: String,
|
name: String,
|
||||||
/// `--model` for the subagent's own claude invocation. Omit for
|
/// Which model the subagent's own session runs. Omit for claude's own
|
||||||
/// claude's own default. The `base:claude-subagents` skill's
|
/// default. The `base:claude-subagents` skill's "cheaper-than-you"
|
||||||
/// "cheaper-than-you" guidance still applies here.
|
/// guidance still applies here.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
model: Option<String>,
|
model: Option<String>,
|
||||||
/// Path to a file passed as `--append-system-prompt-file` — the
|
/// Path to a file holding the subagent's actual task instructions. A
|
||||||
/// subagent's actual task instructions. A file, not an inline string,
|
/// file, not an inline string, so a large recipe can't blow past a
|
||||||
/// to avoid `ARG_MAX` on a large recipe.
|
/// shell argument length limit.
|
||||||
prompt_file: String,
|
prompt_file: String,
|
||||||
/// Written to the subagent's stdin as its first turn's prompt. Default:
|
/// Written to the subagent's stdin as its first turn's prompt. Default:
|
||||||
/// a generic "carry out your instructions" nudge — the real task detail
|
/// a generic "carry out your instructions" nudge — the real task detail
|
||||||
|
|
@ -47,12 +48,18 @@ struct ContinueArgs {
|
||||||
name: String,
|
name: String,
|
||||||
/// The new turn's prompt, written to the subagent's stdin.
|
/// The new turn's prompt, written to the subagent's stdin.
|
||||||
prompt: String,
|
prompt: String,
|
||||||
/// `--model` for this turn. Omit to let claude fall back to its own
|
/// Which model this turn runs. Omit to let claude fall back to its own
|
||||||
/// default — this does not have to match whatever model `start` used.
|
/// default — this does not have to match whatever model `start` used.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
model: Option<String>,
|
model: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, JsonSchema)]
|
||||||
|
struct StatusArgs {
|
||||||
|
/// The subagent name to check.
|
||||||
|
name: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, JsonSchema)]
|
#[derive(Debug, Deserialize, JsonSchema)]
|
||||||
struct InterruptArgs {
|
struct InterruptArgs {
|
||||||
/// The running session's name to signal.
|
/// The running session's name to signal.
|
||||||
|
|
@ -73,13 +80,12 @@ impl SubagentMcp {
|
||||||
#[tool(
|
#[tool(
|
||||||
description = "Start a fresh claude subagent session under `name`, running in the \
|
description = "Start a fresh claude subagent session under `name`, running in the \
|
||||||
background. Returns as soon as the process is confirmed running — not once it \
|
background. Returns as soon as the process is confirmed running — not once it \
|
||||||
finishes; poll for completion via the todo this daemon pushes when the turn ends, \
|
finishes; this daemon pushes a todo when the turn ends, or use `continue` later to \
|
||||||
or use `continue` later to give it another turn. A prior *finished* session under \
|
give it another turn. A prior *finished* session under the same name is archived \
|
||||||
the same name is archived first (real fresh start, not a silent resume); a \
|
first (real fresh start, not a silent resume); a *currently running* one is \
|
||||||
*currently running* one is refused. Always runs with \
|
refused. Runs unattended — every tool-call permission prompt is pre-approved rather \
|
||||||
`--dangerously-skip-permissions --strict-mcp-config` (no `--mcp-config` override — \
|
than interactively confirmed — with its MCP server set fixed to what this daemon \
|
||||||
that's a safety property, not a knob). See the `base:claude-subagents` skill for \
|
configures for it. See the `base:claude-subagents` skill for when to reach for this."
|
||||||
when to reach for this."
|
|
||||||
)]
|
)]
|
||||||
fn start(&self, Parameters(args): Parameters<StartArgs>) -> String {
|
fn start(&self, Parameters(args): Parameters<StartArgs>) -> String {
|
||||||
match session::start(
|
match session::start(
|
||||||
|
|
@ -120,6 +126,19 @@ impl SubagentMcp {
|
||||||
Err(e) => format!("interrupt error: {e:#}"),
|
Err(e) => format!("interrupt error: {e:#}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tool(
|
||||||
|
description = "Report whether a subagent is currently running — a zero-cost check that \
|
||||||
|
never launches a process, unlike `continue`. Distinguishes running, idle (a session \
|
||||||
|
exists but nothing is in flight — `continue` to give it another turn), and no such \
|
||||||
|
session at all."
|
||||||
|
)]
|
||||||
|
fn status(&self, Parameters(args): Parameters<StatusArgs>) -> String {
|
||||||
|
match session::status(&self.state, &args.name) {
|
||||||
|
Ok(msg) => msg,
|
||||||
|
Err(e) => format!("status error: {e:#}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tool_handler]
|
#[tool_handler]
|
||||||
|
|
|
||||||
|
|
@ -235,6 +235,30 @@ fn spawn_and_track(
|
||||||
Ok(format!("subagent `{name}` started"))
|
Ok(format!("subagent `{name}` started"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Report whether `name` is currently running — a zero-cost check that
|
||||||
|
/// never launches a process, unlike `continue`. Distinguishes three
|
||||||
|
/// states: running, idle (a session exists but nothing is in flight), and
|
||||||
|
/// no such session at all.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// An invalid name, or no session — running or on disk — under `name`.
|
||||||
|
pub fn status(state: &State, name: &str) -> anyhow::Result<String> {
|
||||||
|
validate_name(name)?;
|
||||||
|
if state.is_running(name) {
|
||||||
|
return Ok(format!("subagent `{name}` is running"));
|
||||||
|
}
|
||||||
|
let config = build_config(name, None, None);
|
||||||
|
let store = build_store(&config)?;
|
||||||
|
if store.find_by_title(name).is_some() {
|
||||||
|
Ok(format!(
|
||||||
|
"subagent `{name}` is idle — its last turn finished; `continue` to give it another"
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
anyhow::bail!("no subagent named `{name}` exists — `start` creates one")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Signal `name`'s running process — `force` picks SIGKILL over SIGINT (see
|
/// Signal `name`'s running process — `force` picks SIGKILL over SIGINT (see
|
||||||
/// `hive_claude::Cancel::cancel`). Refuses a name with nothing running:
|
/// `hive_claude::Cancel::cancel`). Refuses a name with nothing running:
|
||||||
/// there's no queued/pending state to cancel pre-emptively any more (see
|
/// there's no queued/pending state to cancel pre-emptively any more (see
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue