`require_descendant` (`socket_server/mod.rs:666`) authorises
kill/start/restart/update/get_logs with `topology::is_descendant_of` — the
caller's whole subtree, itself included. That has been true since `53b4e752`
(#1865), whose message says "a parent owns its whole subtree; the root covers
every agent as a consequence, no positional privilege", and two tests pin it
(`is_descendant_of_in_grandchild`, `is_descendant_of_in_self_is_true`).
The prose never followed. The four lifecycle tool descriptions, their
`// IMPORTANT:` comments, `docs/tools/lifecycle.md`, the tools README,
hive-agent-mcp's README and the system prompt every agent is rendered from all
still said "direct children only" — while `list_containers`, four tools away in
the same file, said "direct children + their subtrees".
`lifecycle.md` also taught the model #1865 deleted: "Privileged agents (for
example ruth) may operate on any sub-agent — the topology scope applies to all
others." There is no privileged class to belong to; ruth reaches every agent
because the check is transitive and everything sits under it.
Same drift on the state-query side: `resolve_agent_state_target` is
subtree-scoped by the same commit, so `get_loose_ends`' argument doc, the
`QueryAgentState` capability doc and `docs/turn-loop/mcp.md` were all telling a
parent it needs a capability to read a grandchild's threads.
Two smaller corrections found on the way:
* `list_containers` returns the caller itself. `is_descendant_of` is true for
`candidate == ancestor` and `handle_list_descendants` filters the topology
with it; called from a leaf agent it answers one row, that agent.
* `request_init_config` accepts any unused name — the requester becomes its
parent — or an existing agent already in the caller's subtree, not "a direct
child". The editing surface is narrower than the guard, though: only direct
children's config repos are bind-mounted, so re-seeding deeper in the subtree
leaves no local copy to edit. `lifecycle.md` now says so.
The prompt's other stale claim, the dead `request_apply_commit`, is #4226 and
was fixed independently by damocles in #4227 while this was being gated. This
branch keeps only the scope wording on that line.
Closes #4225.
3.8 KiB
Lifecycle and approvals tools
Two tool groups govern agent lifecycle management and config changes.
The server scopes both to the caller's own subtree (topology-enforced
per topology.json: a child, a child's child, every agent below them —
plus the caller itself). No privileged class exists to belong to; the
root agent reaches every agent purely because the check is transitive and
everything sits under it.
lifecycle tool group
No operator approval required. The caller's own subtree.
kill(name)
Graceful stop. Container state is preserved; recreating the agent reuses prior config and credentials.
start(name)
Start a stopped sub-agent.
restart(name)
Stop + start in one call.
update(name)
Rebuild: re-applies the current hyperhive flake + agent.nix,
then restarts. Idempotent — safe to call repeatedly. Used in response
to needs_update system events.
list_containers()
List the caller's whole subtree with running status — children, their children, every agent below them. The calling agent is part of its own subtree, so it appears in its own listing; a leaf agent gets a one-row answer naming itself.
approvals tool group
Config changes and new-agent spawns route through the operator approval queue. Topology-enforced the same way.
request_init_config(name, description?)
Step 1 of spawning a new sub-agent. Queues an InitConfig
approval; on operator approve, hive-c0re seeds the proposed config
repo at /agents/<name>/config/agent.nix with a default template and
delivers a config_ready system event. Then edit agent.nix, commit,
and the operator spawns the agent (the dashboard ◆ R3QU3ST SP4WN
button / Spawn approval, routed via HostRequest::RequestSpawn),
which creates the container from that config.
Subsequent config changes go through a forge PR on the agent's
agent-configs/<name> repo (queues a MergeConfigPr approval on
open/update — no MCP tool involved), not a tool call. See
docs/agent-lifecycle/approvals.md.
name must be either unused — in which case the caller becomes its
parent on approval — or an agent already in the caller's subtree whose
config is being re-seeded. The server refuses a name that exists outside
that subtree, so one agent can't hijack another's.
Only direct children's config repos are bind-mounted into a parent's
container, though (bind_child_agent_dirs, driven by
topology::children_of). The server accepts re-seeding an agent further
down the subtree, and that still leaves the caller without a local copy
to edit afterwards.
Fails if a proposed config repo for name already exists.
name is ≤ 9 characters.
request_update_meta_inputs(inputs?, description?)
Queue an approval to run nix flake update [inputs...] on the meta
flake. Pass specific input names (for example ["bitburner-agent"]) or omit
/ pass [] for all inputs. Returns immediately; the lock update runs
on operator approval.
Doesn't trigger container rebuilds — call update(name) on affected
agents after the approval resolves.
Boundary summary
| Operation | Requires approval? | Scope |
|---|---|---|
kill / start / restart / update |
No | Own subtree |
list_containers |
No | Own subtree, caller included |
request_init_config |
Yes (InitConfig) | Unused name, or one in own subtree |
request_update_meta_inputs |
Yes (MetaUpdate) | Meta flake (global) |
See also
docs/agent-lifecycle/approvals.md— full approval flow, kinds, helper events (config_ready,approval_resolved), flake.lock validation.