prompts+docs: the lifecycle tools reach the whole subtree, not just direct children

`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.
This commit is contained in:
atlas 2026-09-11 15:20:47 +02:00
commit 0a80f21003
10 changed files with 93 additions and 66 deletions

View file

@ -38,9 +38,9 @@ debug agent behavior.
- **[forge-cli](forge-cli.md)** — the exhaustive, autogenerated
flag-by-flag reference for `hive-forge`, kept in lockstep with the
binary by CI the same way `hivectl-cli.md` is.
- **[lifecycle](lifecycle.md)** — kill/start/restart/update for an
agent's own direct children, plus the approval-gated config-change
tools.
- **[lifecycle](lifecycle.md)** — kill/start/restart/update for the
agents in a caller's own subtree, plus the approval-gated
config-change tools.
- **[matrix](matrix.md)** — the matrix MCP tool surface
(`mcp__matrix__*`) for agents with a matrix account, multiple
accounts per agent, and declaring extra MCP servers generally.

View file

@ -1,14 +1,15 @@
# Lifecycle and approvals tools
Two tool groups govern agent lifecycle management and config changes.
The server scopes both to **direct children only** (topology-enforced: it
rejects any name that's not a direct child of the calling
agent per `topology.json`). Privileged agents (for example ruth) may operate
on any sub-agent — the topology scope applies to all others.
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. Direct children only.
No operator approval required. The caller's own subtree.
### `kill(name)`
@ -31,17 +32,19 @@ to `needs_update` system events.
### `list_containers()`
List all **descendant** containers (children + their subtrees) with
running status. Topology-scoped to descendants only.
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. Direct children only (topology-enforced).
approval queue. Topology-enforced the same way.
### `request_init_config(name, description?)`
Step 1 of spawning a new direct child agent. Queues an `InitConfig`
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,
@ -54,6 +57,17 @@ Subsequent config changes go through a **forge PR** on the agent's
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.
@ -69,12 +83,12 @@ agents after the approval resolves.
## Boundary summary
| Operation | Requires approval? | Scope |
| --------------------------------------- | ------------------ | --------------------- |
| `kill` / `start` / `restart` / `update` | No | Direct children |
| `list_containers` | No | All descendants |
| `request_init_config` | Yes (InitConfig) | New direct child only |
| `request_update_meta_inputs` | Yes (MetaUpdate) | Meta flake (global) |
| 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