refactor(hive-c0re): drop the request_init_config tool and InitConfig approval

swarm-controller's `InitAgentConfigRepo` node already covers config-repo
creation, so this deletes a duplicate rather than a capability; old
`init_config` rows are skipped by `collect_lenient` with no migration, by
operator decision.

Refs #4398
This commit is contained in:
atlas 2026-09-14 18:50:24 +02:00
commit a3b672d1d5
31 changed files with 134 additions and 601 deletions

View file

@ -26,7 +26,7 @@ declarations.
## Agent lifecycle
- **How do config changes flow from manager to operator to container?**
[`agent-lifecycle/approvals.md`](agent-lifecycle/approvals.md) (two-step spawn, approval
[`agent-lifecycle/approvals.md`](agent-lifecycle/approvals.md) (approval kinds, approval
state machine, `flake.lock` validation).
- **What state survives destroy / purge / restart?**
[`agent-lifecycle/persistence.md`](agent-lifecycle/persistence.md).

View file

@ -21,11 +21,10 @@ Topology lives in the hive-c0re-owned **meta repo**, alongside
`null` = root-level agent. New agents **default to root** — there is no
structural manager that everything hangs under. Hierarchy is built
explicitly: an agent that requests a sub-agent gets a
requester-as-parent edge written at its `init_config` approval (so
`alice` spawned `bob` above), and the operator can reparent any
agent, including the bootstrap container (`ruth`) — it's just another
root. The manager is reparentable like any other agent; there's no
explicitly: an agent gets a parent edge written before its first spawn,
or the operator reparents it afterwards (so `bob` above sits under
`alice`). Any agent is reparentable, the bootstrap container (`ruth`)
included — it's just another root. The manager is reparentable like any other agent; there's no
"structurally root" carve-out. Its privileges live on its MCP socket,
not its tree position (see _Manager special-casing today_ below).
@ -63,10 +62,11 @@ where system-level facts live.
install that hasn't synced yet).
- **Reconcile** — runs alongside the periodic meta/flake regeneration.
New agents default to root unless they already carry an explicit
parent edge from an `init_config` approval; Reconcile preserves existing entries
(including operator overrides); removed agents drop.
Agents that are approved but not yet spawned keep their edge too, so
it survives the gap until the container actually appears.
parent edge written before their first spawn; Reconcile preserves
existing entries (including operator overrides); removed agents drop.
Agents whose config repo exists but that haven't spawned yet keep
their edge too, so it survives the gap until the container actually
appears.
- **Inject** — hive-c0re exposes each container's parent (if any) to its own
environment as `HIVE_PARENT`, so the harness / system-prompt
renderer can see it.
@ -90,7 +90,6 @@ umount-old / mount-new / restart-cascade step.
| operation | who can do it |
| ----------------------------------------------------------- | --------------------------------------------------------------------------------------- |
| `kill` / `start` / `restart` / `update` (any descendant) | any ancestor |
| `request_init_config` (spawn a new child) | any agent, child added under self |
| config change via forge PR (any descendant's config) | any ancestor |
| `get_logs` (any descendant) | any ancestor |
| moderate reminders (cancel any open thread of a descendant) | any ancestor |
@ -110,15 +109,15 @@ other agents don't:
- **Naming/bootstrap** — the manager's broker recipient name, state-dir
key, and nixos-container name are all `ruth` (container `h-ruth`).
`hive-c0re` spawns it directly at boot if missing, with no operator
approval step — every other agent goes through `request_init_config`
→ approval. Topology-wise, `ruth` is still just another root agent.
approval step — every other agent goes through a `Spawn` approval.
Topology-wise, `ruth` is still just another root agent.
- **Wire-protocol** — the privileged `Request` variants
(`RequestInitConfig`; `Kill` / `Start` / `Restart` / `Update`;
(`Kill` / `Start` / `Restart` / `Update`;
`GetLogs`; `RequestUpdateMetaInputs`) — marked `*(privileged)*` in
`hive-core-agent-sock`'s unified `Request` enum — are reachable only
from the manager's socket flavour today. Planned rule for each is in the
table above ("any agent, child added under self" for init-config,
"any ancestor" for lifecycle/logs); `RequestUpdateMetaInputs` stays
table above ("any ancestor" for lifecycle/logs);
`RequestUpdateMetaInputs` stays
a root-only capability even post-milestone, not a topology rule.
One exception: `Wake` (inject a `from: <X>` message into the
caller's own inbox) isn't really privileged — every per-agent daemon

View file

@ -24,12 +24,14 @@ CLI) before it takes effect. What you'll see, and what to do with it:
anything in that chain fails, the change rolls back automatically —
the agent stays on its last-good config, no recovery action needed
from you.
- **New agent** (`InitConfig` then `Spawn`) — creating a brand-new
agent is two approvals. `InitConfig` creates the config repo and
seeds it from a template; `Spawn` creates the container from that
config. Tailoring the template first isn't a separate mechanism —
it's the config-change flow above, a PR you review like any other. Every later change goes through the config-change flow
above — there's no repeat "spawn" for an existing agent.
- **New agent** (`Spawn`) — the one approval in creating a brand-new
agent. Its config repo is scaffolded first, outside the approval
queue, by the swarm controller's `InitAgentConfigRepo` job
(`POST /api/agents`); `Spawn` then creates the container from that
config. Tailoring what the template seeded isn't a separate
mechanism — it's the config-change flow above, a PR you review like
any other. Every later change goes through that flow — there's no
repeat "spawn" for an existing agent.
- **Meta/flake update** (`UpdateMetaInputs`) — an agent asked to bump
one or more Nix flake inputs (or all of them). Approving runs the
update and commits the lock change; it doesn't rebuild anything by
@ -131,11 +133,10 @@ agent that lacks the `approvals` tool group: only an agent with that
group submits approvals (for its direct children), so an agent
without it has nothing of its own to withdraw.
`InitConfig` approvals create a brand-new agent's config repo. On
approve, hive-c0re seeds it with a default `agent.nix` template and
pushes a todo (`push_todo_submitter`) into the submitting agent's
in-container store. The operator then **spawns** the agent (the
`Spawn` approval / `◆ R3QU3ST SP4WN` button), which creates the
A brand-new agent's config repo is created outside this queue, by the
swarm controller's `InitAgentConfigRepo` job, which seeds it with a
default `agent.nix` template. The operator then **spawns** the agent
(the `Spawn` approval / `◆ R3QU3ST SP4WN` button), which creates the
container from that config.
Changing what the template seeded isn't a special case: like every
@ -146,7 +147,7 @@ through the web UI or the forge.
### Approval kinds (wire shapes)
`ApprovalKind` carries five variants; each maps to a different
`ApprovalKind` carries four variants; each maps to a different
`commit_ref` encoding because that field is overloaded as the
kind-specific payload carrier.
@ -169,13 +170,11 @@ kind-specific payload carrier.
`hivectl agent <name> request-create` CLI). The host-level `HostRequest::Spawn`
variant bypasses the approval queue entirely — privileged-context use
only (operator on the host shell, test scripts, one-off recoveries;
`hivectl agent <name> create`). This is the **canonical first-spawn**: a new agent's `InitConfig`
seeds its config repo, the submitting agent customises it, then the
operator spawns to create the container. Subsequent config changes go
through a `MergeConfigPr` PR.
- `InitConfig``commit_ref` is empty; the variant just gates
"seed the proposed repo with the default template" against
operator approval. Step 1 of the two-step spawn flow above.
`hivectl agent <name> create`). This is the **canonical first-spawn**: the
swarm controller's `InitAgentConfigRepo` job seeds the agent's config
repo, it gets customised through a PR, then the operator spawns to
create the container. Subsequent config changes go through a
`MergeConfigPr` PR.
- `UpdateMetaInputs``commit_ref` stores the JSON-encoded inputs
array (`"[]"` = all inputs, `"[\"nixpkgs\"]"` = just nixpkgs,
etc.). hive-c0re sets the `agent` field to the requesting root agent.
@ -421,7 +420,6 @@ rather than run inline:
| `MergeConfigPr` | `rebuild` (`DeployWindow` root + `MergeVerify → DeployApply` + `DeployTail`) | `approval` |
| `UpdateMetaInputs` | `meta_update` (`MetaLock` + rebuild fan-out) | `approval` |
| `Spawn` | `spawn` (`Create → WriteDropin → Reconcile`) | `approval` |
| `InitConfig` | — runs inline (sub-second git seed) | — |
| `SchedulePrompt` | — runs inline (single sqlite insert) | — |
The DAG carries the originating `approval_id`, surfaced on the node that
@ -604,7 +602,7 @@ as a regular `system` inbox message so it drives a normal claude turn.
through `Coordinator::push_todo`/`push_todo_submitter` instead, a direct
live dial of the target agent's in-container todo socket (same
`UpsertTodo` request in-container producers use); `finish_approval` fires
one of these too for `InitConfig`/`Spawn`/`MergeConfigPr`, *in addition to*
one of these too for `Spawn`/`MergeConfigPr`, *in addition to*
the `ApprovalResolved` HelperEvent above, not instead of it. Legacy
approval rows that predate the submitter column fall back to the
root agent. Variants (`hive_sh4re::manager::HelperEvent`):
@ -625,7 +623,7 @@ root agent. Variants (`hive_sh4re::manager::HelperEvent`):
no approval required.
The remaining lower-urgency lifecycle notices — `Rebuilt`, `Killed`,
`Destroyed`, `NeedsLogin`, `LoggedIn`, `ConfigReady` — are "FYI, check
`Destroyed`, `NeedsLogin`, `LoggedIn` — are "FYI, check
when convenient" events with no reason to drive an immediate turn, so
they deliver via `push_todo`/`push_todo_submitter` (see above) instead
of `HelperEvent`: an `agent_todo_socket` push instead of a broker
@ -638,7 +636,7 @@ hive-c0re-vouched commit sha. Optional `tag` carries the deploy
bookkeeping tag — `deployed/<id>` on a successful build or
`failed/<id>` on a failed one, planted by the `MergeConfigPr` deploy.
Both fields are `Option`: `None` on the paths that don't deploy a new
commit (spawn / init_config / meta-update / deny, and the autoupdate
commit (spawn / meta-update / deny, and the autoupdate
sweep's `job_queue::templates::rebuild` reapplying the existing main,
or the dashboard `↻ R3BU1LD` button when the lock didn't move). When set,
`git show <sha>` against `/applied/<n>/.git` inside the

View file

@ -58,7 +58,7 @@ power-intent registry:
below](#state-dirs-per-agent) for where reminders (and todos)
actually live now.
- `approvals` — the queue. `agent / kind (merge_config_pr | spawn |
init_config | update_meta_inputs | schedule_prompt) /
update_meta_inputs | schedule_prompt) /
commit_ref / requested_at / status / resolved_at / note`.
- `scheduled_prompts` — recurring + one-shot prompt queue.
`owner / body / interval_seconds (NULL = one-shot) /
@ -353,9 +353,9 @@ tree anyone edits in place. Mounting it writable would leave a second
path to the same file that skips the review entirely, which makes the
boundary a convention rather than a permission.
⚠️ Not to be confused with the seeding done when an `InitConfig`
approval resolves: that writes the child's initial config repo as
**hive-c0re, against the host path**, and `read_only` on a bind
⚠️ Not to be confused with the config-repo seeding hive-c0re does at
spawn (`lifecycle::setup_proposed`): that writes the child's initial
config repo as **hive-c0re, against the host path**, and `read_only` on a bind
constrains writers *inside* a container only. The two are unrelated —
conflating them can lead you to reason your way into thinking this
mount should be writable when it shouldn't.
@ -443,7 +443,7 @@ nothing is automigrated: existing agents keep their plain dirs
until an explicit opt-in upgrade.
- **Creation:** `lifecycle::ensure_agent_state_subvolume` runs before
hive-c0re creates the per-agent subdirs (spawn / rebuild / InitConfig).
hive-c0re creates the per-agent subdirs (spawn / rebuild).
It skips the work when the root already exists; otherwise it asks
hive-priv (`EnsureAgentSubvolume`) to `btrfs subvolume create` the
root when the FS is btrfs (`statfs` magic gate) and chown it to the

View file

@ -247,18 +247,17 @@ a manual `hivectl` step — see _Swarm SSO_ above (`swarmctl user add`).
### 7 · Spawn sub-agents
Sub-agent creation goes through the approval queue — ruth proposes, the
operator approves, the container builds. From ruth's own turn (inside
the container, via MCP tools):
Sub-agent creation is an operator action — agents have no tool for it.
Two steps:
```
# Step 1: initialise a new agent's config repo
request_init_config(name: "iris")
# → operator approves → config_ready event lands in the inbox
# Step 1: scaffold the new agent's config repo. The swarm controller's
# InitAgentConfigRepo job does this (POST /api/agents), seeding
# /agents/iris/config/agent.nix from the default template.
# Step 2: edit /agents/iris/config/agent.nix and commit it. Then the
# operator spawns iris (dashboard ◆ R3QU3ST SP4WN / Spawn approval),
# which builds + starts the container from that config.
# Step 2: edit /agents/iris/config/agent.nix and commit it. Then spawn
# iris from the dashboard (◆ R3QU3ST SP4WN / Spawn approval), which
# builds + starts the container from that config.
# Later config changes: open a PR on agent-configs/iris (hive-forge);
# the operator reviews + approves it — no MCP tool call.

View file

@ -311,7 +311,7 @@ binary flavor.
| `inbox` | `get_loose_ends`, `cancel_loose_end`, `remind` |
| `execution` | vestigial — `mcp__bash__run` / `mcp__bash__status` are always available unconditionally via `extraMcpServers`; this group's entries expand to non-existent `mcp__hyperhive__run` / `mcp__hyperhive__status` and have no effect. See `docs/tools/bash.md`. |
| `lifecycle` | `kill`, `start`, `restart`, `update`, `list_containers` *(privileged)* |
| `approvals` | `request_init_config`, `request_update_meta_inputs` *(privileged)* |
| `approvals` | `request_update_meta_inputs` *(privileged)* |
| `scheduling` | `request_schedule_prompt`, `fire_schedule_now`, `cancel_schedule`, `edit_schedule`, `list_schedules` *(privileged)* |
| `diagnostics` | `get_logs` *(privileged)* |
| `forge` | `create_repo` — create git repos through hive-c0re (operator-gated merge) |

View file

@ -39,38 +39,20 @@ 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.
Meta-flake input bumps route through the operator approval queue.
### `request_init_config(name, description?)`
Creating a new agent is **not** in this group — agents have no tool for
it. A new agent's config repo is scaffolded by the swarm controller's
`InitAgentConfigRepo` job (`POST /api/agents`, see
`swarm-controller/`), and the operator spawns the container from the
dashboard (`◆ R3QU3ST SP4WN` / `Spawn` approval, routed via
`HostRequest::RequestSpawn`).
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
Config changes on an existing agent 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
@ -83,15 +65,14 @@ 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) |
| Operation | Requires approval? | Scope |
| --------------------------------------- | ------------------ | ---------------------------- |
| `kill` / `start` / `restart` / `update` | No | Own subtree |
| `list_containers` | No | Own subtree, caller included |
| `request_update_meta_inputs` | Yes (MetaUpdate) | Meta flake (global) |
## See also
- [`docs/agent-lifecycle/approvals.md`](../agent-lifecycle/approvals.md) — full approval flow, kinds,
helper events (`config_ready`, `approval_resolved`), flake.lock
helper events (`approval_resolved`), flake.lock
validation.

View file

@ -66,7 +66,7 @@ object with an `event` discriminant field). The **submitting agent**
tool group for its own subtree) receives `container_crash`,
`needs_update`, and `approval_resolved` this way. The remaining, lower-urgency lifecycle
notices — `spawned`, `rebuilt`, `killed`, `destroyed`, `needs_login`,
`logged_in`, `config_ready` — skip the inbox entirely: they land as
`logged_in` — skip the inbox entirely: they land as
todos on the submitting agent's in-container todo socket instead
(`Coordinator::push_todo`/`push_todo_submitter`, `subsystem = "core"`),
which still wakes a turn (the todo-wake path — see [Turn

View file

@ -1060,7 +1060,6 @@ renderApprovals`) with three stacked sections:
| `merge_config_pr` | `⇒` | `merge-pr` | PR-head sha (`sha_short`) |
| `update_meta_inputs` | `↻` | `meta-update` | — |
| `schedule_prompt` | `⏱` | `schedule` | — |
| `init_config` | `⊕` | `init` | — |
| `spawn` | `⊕` | `spawn` | — |
The chip ticks live every second via a `data-requested-at`
@ -1074,8 +1073,7 @@ renderApprovals`) with three stacked sections:
config PR into `agent-configs/<agent>/pulls/<pr_number>` (shown
only when `forge_present` and `pr_number` is set). The config diff
lives on the forge PR itself — no inline diff side-panel.
- `init_config` / `spawn`: a one-line "container will be created"
note instead.
- `spawn`: a one-line "container will be created" note instead.
- **decision actions**`◆ APPR0VE` and `DENY`. Deny pops a
`prompt()` for an optional reason carried to the submitting agent as
`HelperEvent::ApprovalResolved.note`.