Watch
0
0
Fork
You've already forked hyperhive
0

docs: the hive has no approval queue

config-changes.md (was approvals.md) describes config PRs, the meta flake
and helper events; the approval queue, its kinds, the withdraw path, the
job-queue dispatch and ApprovalResolved go. Every other page drops the
approvals table, endpoints, event, tool group, hivectl verbs, dashboard
section and the loose-end/cancel kind, and links to the renamed page.
This commit is contained in:
atlas 2026-10-04 23:55:14 +02:00
commit e4b170f8c6
18 changed files with 122 additions and 327 deletions

View file

@ -1,15 +1,8 @@
# Approvals + helper events
# Config changes + helper events
The approval queue is where an agent asks the operator for something it
can't do itself. Nothing submits a new approval today: the one kind
left, a meta-input bump, only reads back from existing rows. Config
changes don't go through this queue: they're pull requests
on the agent's config repo, which an operator merges on the forge, and
that merge deploys them. The submitting agent — any agent with the
`approvals` tool group, which manages the config of its **direct
children** (the root agent for top-level agents; a sub-manager for its
own subtree) — is the policy gate in front of both; helper events are how
it stays informed about what happens after a decision lands.
Config changes are pull requests on the agent's config repo, which an
operator merges on the forge; that merge deploys them. Helper events are
how agents stay informed about lifecycle changes that need a reaction.
## For operators
@ -17,17 +10,6 @@ it stays informed about what happens after a decision lands.
config (or its own, via a sub-manager) as a pull request on
`agent-configs/<agent>`. Review it on the forge like any other PR, and
merge it there to deploy it. See [Config changes](#config-changes).
- **Meta/flake update** (`UpdateMetaInputs`) — an existing row reads
back and you can approve it. Approving runs the update and commits
the lock change; it doesn't rebuild anything by itself.
Don't want to approve something? **Deny it** (`DENY` on the dashboard
card, or `hivectl approvals deny <id>`) — nothing runs. Either way the
submitting agent is always notified that the operator denied their request; what's
optional is only the reason text, which you can add on the dashboard's
prompt (cancelling that prompt aborts the whole deny, not just the
reason) but not from the CLI. Denying is final: a denied approval
can't be re-approved later, the agent has to submit a fresh request.
Everything below this point is the implementation detail behind that
flow.
@ -38,8 +20,7 @@ Config changes flow through a **forge pull request** on the agent's
`agent-configs/<name>` repo — the same surface agents use for code PRs.
No MCP tool exists for config changes: opening the PR IS the request.
1. The submitting agent (the child's parent, holding the `approvals`
tool group) **clones** `agent-configs/<name>`, edits it there (any
1. The submitting agent **clones** `agent-configs/<name>`, edits it there (any
tracked path, but `agent.nix` is the contract entry point), commits
with its own git identity, and pushes a branch + opens a PR with
`hive-forge` — the same way it would change any other repo.
@ -77,46 +58,17 @@ failed rebuild shows on the hive like any other and isn't commented on
the PR. A merge whose webhook delivery never reaches the controller
deploys nothing.
## Approval queue
### Withdrawing a pending approval
The submitting agent can call `cancel_loose_end(kind: "approval", id)` to
withdraw an approval the operator hasn't acted on yet.
The row transitions to `ApprovalStatus::Cancelled` (distinct from
`Denied`/`Failed`), the dashboard pulls the card out of the
pending pane, and `ApprovalResolved { status: "cancelled" }` fires
on the root agent + dashboard channels. Approvals that the operator
(or a lifecycle failure) has already approved, denied, or failed return an error — the resolution is
final once acted on.
The socket refuses the `approval` kind with a clear error for any
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.
Creating a brand-new agent isn't an approval: it's swarm-level
(`swarmctl agent create`, `POST /api/agents` on the swarm controller),
and no hive can originate an agent. The swarm controller's
`InitAgentConfigRepo` job seeds the agent's config repo with a default
`agent.nix` template, then asks the target hive to deploy it.
Creating a brand-new agent is swarm-level (`swarmctl agent create`,
`POST /api/agents` on the swarm controller), and no hive can originate
an agent. The swarm controller's `InitAgentConfigRepo` job seeds the
agent's config repo with a default `agent.nix` template, then asks the
target hive to deploy it.
Changing what the template seeded isn't a special case: like every
later change, it's a PR on that config repo, made from a clone and
merged by an operator on the forge (see [Config changes](#config-changes)).
### Approval kinds (wire shapes)
`ApprovalKind` carries one variant, and it overloads `commit_ref` as
its payload carrier.
<!-- vale write-good.Passive = NO -->
- `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.
On approve hive-c0re runs `nix flake update [inputs...]` on the
meta flake and commits the resulting lock changes.
<!-- vale write-good.Passive = YES -->
## Agent lifecycle
### Reminder delivery: file-path semantics
@ -144,10 +96,10 @@ body strings before calling it.
### Destroy semantics
`HostRequest::Destroy { name, purge }` is the lifecycle tear-down,
not an approval. Stops + removes the nspawn container, drops the
systemd drop-in, fails any pending approvals. Persistent state
(proposed/applied repos, claude credentials, `/state/` notes) is
`HostRequest::Destroy { name, purge }` is the lifecycle tear-down.
Stops + removes the nspawn container and drops the systemd drop-in.
Persistent state (proposed/applied repos, claude credentials, `/state/`
notes) is
**kept by default** — recreating the agent with the same name
reuses prior config + login. With `purge = true` the agent's
`/var/lib/hyperhive/{agents,applied}/<name>/` trees are also
@ -252,41 +204,6 @@ forge and meta's lock commits record it. A config PR nobody merges
carries no extra state on the forge side: the PR stays open, and
the submitter pushes again (or closes it) to retry.
### Dispatch via the job queue
Long-running approval work — `UpdateMetaInputs` — runs as a DAG on the
global job queue (`docs/scheduler/coordinator.md::Job queue`), submitted
by the approval handler rather than run inline:
| `ApprovalKind` | DAG submitted | source |
|---|---|---|
| `UpdateMetaInputs` | `meta_update` (`MetaLock` + rebuild fan-out) | `approval` |
The DAG carries the originating `approval_id`. **Every** queued kind
resolves through `actions::resolve_approval_dag` when its DAG settles
terminal: the DAG's own terminal state is the authoritative outcome.
That hook fires the matching `HelperEvent::*` via `finish_approval`.
Two visible consequences:
- **Operator dashboard**: after selecting APPR0VE the work-in-progress
shows up on the *rebuild queue* card (`GET /api/jobq/graph`, refetched
on every `rebuild_queue_changed` tick), not on the approvals panel
(which already moved the row to "approved"). A long meta-update
cascade renders as a parent DAG with one child rebuild per affected
agent — see `docs/web-ui/dashboard.md` for the layout.
- **Cancellation**: the dashboard's *× cancel* button on a still-queued
DAG calls `POST /api/rebuild-queue/{id}/cancel`, which flips it to
`Cancelled` before any node runs (and fails the approval row instead
of leaving it dangling). Returns `{"cancelled": true}` on success,
`{"cancelled": false}` once any node started — terminal states can't
be retroactively rewritten.
The `approval` source + `approval_id` mean a tail-end build failure
surfaces back as a failed approval row, not just a silent queue
entry. `manual` (dashboard ↻ R3BU1LD) and `auto_update` (boot
reconcile) DAGs use the same queue but skip the approval plumbing.
### Forge mirror
The bundled `hive-forge` container runs on the swarm's forge host
@ -323,10 +240,10 @@ calls `bind_child_agent_dirs` for each entry in
current config — not an editing surface. An agent without the
capability sees no other agent's config at all.
An agent with the `approvals` tool group submits a change the same way
it makes any other change: **clone the target agent's config repo from the
An agent submits a change to another agent's config the same way it
makes any other change: **clone the target agent's config repo from the
forge into its own state dir, commit on a branch, open a PR**, and let
the operator review and approve it. By design, no second, mount-shaped
the operator review and merge it. By design, no second, mount-shaped
path reaches the same file without the review.
Agents holding the `manage_root_agent` capability (granted per agent in
@ -401,7 +318,7 @@ hive-c0re creates it. The root agent's flake lives at
`/var/lib/hyperhive/applied/ruth/`; its proposed config at
`/var/lib/hyperhive/agents/ruth/config/`. The root agent can edit its own
`agent.nix` (visible inside the container at `/agents/ruth/config/`)
and open a config PR on `agent-configs/ruth` for operator approval,
and open a config PR on `agent-configs/ruth` for operator review,
same as any other agent.
Differences from sub-agents:
@ -415,8 +332,6 @@ Differences from sub-agents:
and `/var/lib/hyperhive/applied` → `/applied` (RO) so the root agent
can `git fetch` deployed/failed/denied tags from any agent's
authoritative applied repo (see "Root-agent view of applied" below).
- First-deploy spawn bypasses the approval queue (the root agent is
required infrastructure).
- `socket_server::start_manager` binds the root agent's socket,
pure transport with no dedicated helpers — it uses the same
per-agent runtime dir as any other agent (`/run/hyperhive/agents/ruth/`),
@ -432,26 +347,19 @@ The system prompt (`hive-agent/prompts/system.md`, rendered by
`hive-agent/src/prompt.rs`) is the **same for every agent**; what
varies is which MCP tools it surfaces (gated by tool groups and
capabilities in `agent.nix`). No `role:manager` block renders only
for the root agent. The root agent's approval-gating
for the root agent. The root agent's role-specific
behaviour comes from its CLAUDE.md / agent-specific instructions, not
the system prompt template.
## Helper events to the submitting agent
## Helper events
`Coordinator::notify_submitter(approval_id, &HelperEvent)` routes the
event to the agent that originally submitted the approval (looked up from
the `submitter` column on the `approvals` table). The harness delivers it
as a regular `system` inbox message so it drives a normal claude turn.
`finish_approval` fires an `ApprovalResolved` HelperEvent this way for
**every** approval kind's terminal state. A
"FYI, check when convenient" event doesn't need a message — hive-c0re publishes those as swarm
lifecycle notices instead (`swarm_notices::notify`). Legacy
approval rows that predate the submitter column fall back to the
root agent. Variants (`hive_sh4re::manager::HelperEvent`):
`Coordinator::notify_agent(agent, &HelperEvent)` pushes the event into
the agent's inbox, and the harness delivers it as a regular `system`
inbox message so it drives a normal claude turn. A "FYI, check when
convenient" event doesn't need a message — hive-c0re publishes those as
swarm lifecycle notices instead (`swarm_notices::notify`). Variants
(`hive_sh4re::manager::HelperEvent`):
- `ApprovalResolved { id, agent, commit_ref, status, note }` —
fired by `actions::approve` + `actions::deny` whenever an
approval transitions to its terminal state.
- `ContainerCrash { agent, note }` — `crash_watch`: a previously-
running container went away with no operator-initiated transient
state (Stopping / Restarting / Destroying / Rebuilding) AND nothing