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