docs(persistence): dedupe destroy/purge + agent_power against other sections, trim historical framing

This commit is contained in:
iris 2026-08-15 11:52:29 +02:00 committed by mara
commit c14d932687

View file

@ -79,16 +79,13 @@ header/targets split, and the per-agent power-intent registry:
`scheduled_prompts(id)` — requires `PRAGMA foreign_keys = ON`
per connection (set at open).
- `agent_power` — one tiny row per agent: `agent PK / wanted (up |
offline) / updated_at` — the durable power *intent* behind the job
queue's desired-state reconciliation
(`docs/coordinator.md::Job queue`; owner: `hive-c0re/src/stores/power.rs`).
Written synchronously by every operator/agent power action
(dashboard start/stop, `hivectl stop`, the MCP kill/start tools,
spawn approval); read by `Reconcile` nodes and the boot reconcile.
Intent survives hive-c0re restarts — in-flight queue work
deliberately does not. Agents without a row are seeded from
observed state on first touch (running ⇒ `up`); destroy deletes
the row.
offline) / updated_at`, owned by `hive-c0re/src/stores/power.rs`.
This is the durable power *intent* the job queue reconciles the
observed container state against; intent survives hive-c0re
restarts even though in-flight queue work doesn't. See
[`docs/coordinator.md`'s Desired-state
section](coordinator.md#desired-state-spec-vs-status) for who
writes and reads it and how reconciliation works.
Retention:
@ -190,12 +187,10 @@ Shape:
The turn loop is the only writer today, but it still goes
read-modify-write under a shared in-process lock and merges into the
existing object rather than reconstructing it — so a second writer
preserves fields it doesn't own, and the lock closes the lost-update
window between a writer's read and its rename. (The forge notification
poller used to be that second writer, for a delivery-dedupe cursor. It
persists nothing now — forge's own read-state is the durable record of
what has been delivered — and it is a separate process, which an
in-process lock could not have serialised anyway.)
would preserve fields it doesn't own, and the lock closes the
lost-update window between a writer's read and its rename. The lock
is in-process only, so it wouldn't serialise a writer running as a
separate process; none of today's writers are.
hive-c0re reads this file on each `build_all` sweep (~10s) via
`container_view::read_harness_flags`. Falls back to the legacy individual
@ -209,11 +204,12 @@ Full stdout + stderr capture for every `nixos-container` / `nix
build` invocation the lifecycle layer fires. One row per invocation;
the row accumulates lines as the child runs.
Replaces the legacy 32-line stderr ring buffer that `lifecycle::run`
kept. The ring tail routinely truncated real eval errors ("tried
alternatives" blocks alone are often 30+ lines), so failures bailed
with an arbitrary tail whose full stream only lived in the host
journal. With this table the dashboard can surface the entire log.
Capturing the full stream (rather than a short tail buffer) matters
because real eval errors routinely run long — "tried alternatives"
blocks alone are often 30+ lines — so a truncated tail would cut off
the actual failure and leave only the host journal holding the
complete output. With this table the dashboard can surface the entire
log.
Two indices:
- `(agent, started_at)` — backs the per-agent latest-N lookup used
@ -323,11 +319,9 @@ notes, clearing a stuck sentinel) as well as reading it.
**`harness` is not mounted at all.** It holds the child's own runtime
material — `bash-tasks/`, the turn-stats and event sqlite dbs — and
nothing argues for a parent reading it, let alone writing it. It used to
be mounted RW for "the same management reasons" as `state`, which was
never an argument so much as the side-effect of one loop treating all
three dirs alike. hive-c0re reads a child's harness dir **directly on the
host** when it wants those stats, which needs no mount into the parent.
nothing argues for a parent reading it, let alone writing it. hive-c0re
reads a child's harness dir **directly on the host** when it wants
those stats, which needs no mount into the parent.
**`config` is read-only, including for the parent.** A config change is
a PR on the child's config repo, made from a clone and merged after
@ -339,8 +333,9 @@ 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
constrains writers *inside* a container only. The two are unrelated, and
reading them as the same thing is what kept this mount writable.
constrains writers *inside* a container only. The two are unrelated —
conflating them is an easy way to reason your way into thinking this
mount should be writable when it shouldn't.
Per-child isolation still holds: a container only ever has its *own*
dirs plus its direct children's bind-mounted, never a sibling's.
@ -396,25 +391,21 @@ deleted.
## Destroy vs purge
- `DESTR0Y` (default) — stops + removes the nspawn container,
drops the systemd drop-in, fails any pending approvals. State
dirs stay put; the agent appears in the dashboard's K3PT ST4T3
section as a tombstone with `⊕ R3V1V3` and `PURG3` actions.
`R3V1V3` queues a Spawn approval that reuses the kept state on
approve (no re-login).
- `PURG3` (opt-in via the dashboard button or
`hivectl agent <name> destroy --purge`) — DESTR0Y plus wipes
`/var/lib/hyperhive/{agents,applied}/<name>/`. Config history,
claude creds, /state/ notes, and the harness dir are all gone.
No undo.
See [For operators](#for-operators) above for what each action does to
an agent's state. The mechanics, for completeness:
The root/bootstrap container is **imperative** infrastructure — managed
end-to-end by hive-c0re, not declared in the host's NixOS config.
`auto_update::ensure_root_agent` recreates it on the next hive-c0re
startup if it's absent (bypassing the approval queue, as required
infrastructure). A soft policy guard in `actions::destroy` currently
refuses to destroy it; even without that guard, destroying it would only
be transient — hive-c0re brings it back on the next startup.
- `DESTR0Y` also drops the systemd drop-in and fails any pending
approvals; the tombstone's `⊕ R3V1V3` button queues a Spawn approval
that reuses the kept state on approve.
- `PURG3` wipes `/var/lib/hyperhive/{agents,applied}/<name>/` — the
union of everything `DESTR0Y` left behind.
The root/bootstrap agent's specialness is implemented as a soft policy
guard in `actions::destroy` that refuses to destroy it, backstopped by
`auto_update::ensure_root_agent`, which recreates it on the next
hive-c0re startup if it's ever absent (bypassing the approval queue,
as required infrastructure) — so even without the guard, destroying it
would only be transient.
### btrfs subvolumes for `/var/lib/hyperhive/agents/<name>`
@ -443,6 +434,11 @@ until an explicit opt-in upgrade.
Per-subvolume disk-usage accounting and optional quotas are a
follow-up (the qgroup work), not part of the base migration.
This is the same subvolume `hivectl agent <name> subvol snapshot push`
sends to the swarm's snapshot store — see
[`docs/snapshot-store.md`](snapshot-store.md) for what a pushed
snapshot contains and how the store authenticates a sender.
## Run-time dirs
`/run/hyperhive/` is tmpfs-backed (systemd `RuntimeDirectory=`) but