`goal_reached`/`need_help` took the session name as a tool argument, so identity was an assertion by the caller and the only guard on it was `occupancy()` — "does that name have a turn in flight", which two concurrently running siblings both satisfy for each other. A subagent could stop its sibling's run by naming it. Identity moves into the URL. Each spawned run is minted an unguessable token (`Uuid::new_v4`, the OS CSPRNG), the URL carrying it goes into that one subagent's own `--mcp-config`, and the route resolves it back to a session before dispatching to a handler bound to that session. Neither tool takes a `name` any more: a subagent has no field in which to name a sibling, and a sibling's name — which a brief may well mention — is not a token. One route with a path parameter, not a route per session: the `Router` is built once at startup and subagents come and go for the daemon's whole life. An unminted or revoked token gets a bare 404, the same answer either way, so nothing enumerates. A run's token is revoked when the run ends (`finish_turn`) or when a call never reached a spawn. Two things fall out of that: - the config file becomes one per session. A single shared path was already a race between two `start`s; with a per-session URL in it, the loser would read the winner's identity. - `occupancy()` stops being the identity guard and is gone from the signal path entirely rather than kept "just in case" — a revoked token can't reach it, and it never answered the question it was standing in for. It still backs `status`, which is what it was always actually for. Refs #4403 Refs #4413
35 lines
1.8 KiB
Markdown
35 lines
1.8 KiB
Markdown
# hive-subagent-mcp
|
|
|
|
Per-agent daemon (`hive-subagent-daemon`) that spawns nested headless
|
|
`claude` sessions on request and serves the tool surface
|
|
(`start`/`continue`/`status`/`interrupt`, plus a separate
|
|
subagent-facing `goal_reached`/`need_help` route, one per-session URL)
|
|
directly over streamable-http. No stdio bridge, no per-turn respawn — an
|
|
agent's claude reconnects to the same stable URL every turn.
|
|
|
|
Independent of `hive-bash-mcp` — a subagent spawns a full nested
|
|
`claude` session, a much heavier capability than a bash command, worth
|
|
its own deployable/restartable unit.
|
|
|
|
## Shape
|
|
|
|
One bin (`hive-subagent-daemon`, `src/main.rs`) built from the crate's
|
|
own lib (`src/lib.rs`):
|
|
|
|
- **`session.rs`** — the actual claude-facing logic: `Claude::spawn` +
|
|
`RunningClaude::wait`/`cancel_handle` (not `InfiniteSession::run`,
|
|
which has no cancel handle to reach in — see the module doc for the
|
|
v1 scope this trades away), the turn-continuation loop a `goal`
|
|
switches on, and the in-memory maps that are the _only_ state this
|
|
daemon keeps (no task files — a restart stops whatever's running;
|
|
the actual claude session is the durable store, found again by name
|
|
via `hive_claude::SessionStore`).
|
|
- **`mcp.rs`** — the `rmcp` tool routers (the parent's
|
|
`start`/`continue`/`status`/`interrupt` on `/mcp`, the subagent's
|
|
`goal_reached`/`need_help` on `/signal/mcp/<token>`) + `serve_http`.
|
|
Neither signal tool takes a session name: the token in the path is
|
|
minted per run and resolved to a session before dispatch, so a subagent
|
|
has no way to name — and therefore no way to signal — a sibling. One
|
|
route with a path parameter, because the `Router` is built once at
|
|
startup and sessions come and go for the daemon's whole life.
|
|
- **`paths.rs`** — the in-agent todo-socket path.
|