subagent: hand a subagent its parent's built-in tools, and no others

`build_config` spawned a subagent with `--dangerously-skip-permissions`
and no `--tools` at all, so it got claude's entire built-in set —
`SendMessage` and `ListAgents` (message peers, or the operator, as its
parent), `Task*` including `TaskStop`, which takes an *agent* id and so
reaches clean outside the run, `Cron*`, `RemoteTrigger` and
`EnterWorktree`/`ExitWorktree`. None of that is part of "do this bounded
task in this directory", and none of it is something the parent agent
itself can do: the harness has always passed `--tools`.

Pass the same one. The value comes from
`hive_sh4re::permissions::builtin_tools_arg()` — literally the function
the harness resolves its own session with — so the subagent's set is the
parent's set, `HIVE_TOOL_GROUPS` and all. That inheritance is the
requirement, not an implementation detail: a hardcoded subagent list
would hand `WebFetch`/`WebSearch` to the subagent of an agent without the
`web_tools` group, which is a privilege escalation, and would drift from
the parent's list the first time anyone added a tool to either.

`--tools` is the real gate: it holds under
`--dangerously-skip-permissions`, unlike `--allowedTools`, which only
auto-approves prompts. It does not filter MCP tools, so the
`goal_reached`/`need_help` signal surface is deliberately unnamed in it
and survives on `--strict-mcp-config` alone.

`build_config`'s doc comment claimed `strict_mcp_config` was *the* safety
property and that a subagent got "nothing implicit and nothing more".
That was false for built-ins, and is what hid this gap for as long as it
did; it now says which flag covers which half and that neither
substitutes for the other.

An empty `--tools` value parses as *unset* and grants more than omitting
the flag, so an empty resolution can only be a bug — `build_config`
asserts against it and a test pins the non-emptiness alongside the
subset-of-parent property.

Refs #4416
This commit is contained in:
atlas 2026-09-15 16:46:38 +02:00 committed by mara
commit d6c8cd5a6f
4 changed files with 156 additions and 16 deletions

View file

@ -218,13 +218,50 @@ 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
## The tool surface a subagent gets
Two flags, each covering one half, and neither covering the other:
`--tools` governs claude's built-in tools, `--strict-mcp-config` governs
the MCP ones. Dropping either brings that half back in full; in
particular `--tools` does **not** filter `mcp__*` tools.
### Built-in tools (`--tools`)
**A subagent gets exactly the built-ins its parent agent has** — the same
list, resolved by the same function
(`hive_sh4re::permissions::builtin_tools_arg`) from the same
`HIVE_TOOL_GROUPS`: `Edit`, `Glob`, `Grep`, `Read`, `Skill`,
`Write`, plus `WebFetch`/`WebSearch` for an agent granted the `web_tools`
tool group and not otherwise. See
[the harness's own allowlist](../turn-loop/mcp.md#tool-allowlist-hive_sh4repermissionsallowed_builtin_tools)
for what that list contains and why.
Inheriting rather than listing is the point: a hardcoded subagent list
would hand web egress to the subagent of an agent that isn't allowed web
egress, and would diverge from the parent's on the first tool anyone adds
to either.
Everything else in claude's built-in set is absent, in particular the
tools that let a session act outside the run it was started for: peer and
operator messaging, nested agents (including the stop verb, which takes
an _agent_ id rather than a session), schedule and webhook creation, and
worktree switching. Before this flag was passed, a subagent reached all of
them — `--dangerously-skip-permissions` had removed the only thing that
would have asked, and `--allowedTools` would not have helped: it approves
prompts in advance rather than restricting anything.
One trap worth knowing before editing any of this: an empty `--tools`
value parses as _unset_ and grants **more** than omitting the flag, so
there is no way to spell "no built-in tools" — the daemon asserts rather
than emitting one.
### MCP servers (`--strict-mcp-config`)
A subagent runs with `--strict-mcp-config` and, by default, exactly one
MCP server: the two-tool `subagent_control` route above. It otherwise
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
MCP server: the two-tool `subagent_control` route above — not the
parent's `mcp__bash__*` / `mcp__hyperhive__*` surface. Those two signal
tools are deliberately unnamed in `--tools`, which doesn't govern them;
they survive on this flag alone. 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