Watch
0
0
Fork
You've already forked hyperhive
0

deploy the closure the swarm declares, read no forge head, drop the update badge

This commit is contained in:
atlas 2026-11-01 23:56:40 +01:00 • committed by mara
commit 47c2acb640
2 changed files with 647 additions and 2001 deletions

View file

@ -41,12 +41,12 @@ No MCP tool exists for config changes: opening the PR IS the request.
and publishes a deploy request carrying that commit on that hive's
deploy subject. With no such hive, or more than one, it deploys
nothing and logs a warning naming them.
5. If the commit already is the one the agent's last successful build pinned
(the `applied/.<n>.config-rev` marker), the hive does nothing. Otherwise it
queues a rebuild, which resolves its pins from `internal/meta` like any
rebuild. The request's commit only feeds that check: the hive keeps no copy
of the config. An agent with no container on that hive yet ignores the
commit; its first deploy builds whatever `internal/meta` pins.
5. If the closure the swarm declares for the agent is the one the hive last
applied (the `applied/.<n>.closure` marker), the hive does nothing.
Otherwise it queues a rebuild, which downloads the declared closure. The
request's commit plays no part: the hive keeps no copy of the config and
reads no forge head. An agent with no container on that hive yet gets its
first deploy from the declared closure.
This path runs **no eval-verify**. A merged config that doesn't
evaluate or build fails the rebuild, so the agent's rebuilds keep failing
@ -130,35 +130,24 @@ nginx include in line with the live agents. `provision_container`, `destroy`,
which means `nixos-container list` failed, and it fails when `HIVE_FORGE_URL`
is unset, since every agent would come up with no forge.
The dashboard's `deployed:<sha12>` chip per container row shows the config
commit the agent's last successful build pinned.
## The swarm's meta flake
swarm-controller commits `internal/meta`: one `flake.nix` and `flake.lock` for
every agent any hive places (the pins:
[`docs/swarm/builder.md`](../swarm/builder.md#meta-flake-lock)). The swarm
builder builds every agent from it, and a hive deploys that build. While the
repo has no commit that pins an agent's current config, that agent's deploy
fails and names the agent. The controller queues
swarm has recorded no build of an agent, that agent's deploy fails and names the
agent. The controller queues
its update of the repo before the deploy request for a merged config commit
and for a new agent, and sends the deploy only when that update succeeded and
the swarm builder built the agent from the updated repo (the
[deploy gate](../swarm/builder.md#deploy-gate)). A failed build skips the deploy, and the timer builds again.
- **Choosing the commit.** The rebuild's `MetaSync` node reads the repo's
default-branch head with the hive's read token (`forge::reader_token()`)
and takes it when its `flake.nix` builds the agent the way the agent needs now:
a custom agent from the config commit at the head of `agent-configs/<n>`
`main`, an archetype agent from the archetype it has now. An agent switched
between custom and archetype, or to another archetype, fails that test.
A repo with no commit, a head that lacks the agent and a head behind the
config each fail the deploy with an error that names the agent. `MetaSync` records the commit,
with the hyperhive and config commits the head pins, in `hive-c0re`'s own state,
`/var/lib/hyperhive/meta-rev/<n>`, which the agent can't write (the container mounts its runtime
directory read-write); `Prebuild` and `Swap` read that
record, so both expect the closure of one commit. A first deploy resolves the commit in its `Create`
node, since it has no `MetaSync`.
- **Choosing the closure.** The hive doesn't choose. It deploys the closure the
agent's record in `agent-closures` declares, whichever `internal/meta` commit
the builder built it from, and reads no forge head. The rebuild's `MetaSync`
node only prepares it (the agent's archetype, its runtime directories, the
agent set).
- **The closure.** The hive builds nothing. `Prebuild`, `Swap` and `Create` read
the agent's record in the swarm's `agent-closures` bucket (the controller
writes it from the builder's reply) and have `hive-priv` download the closure
@ -168,22 +157,20 @@ the swarm builder built the agent from the updated repo (the
that agent returned. On the wire the
`hive_priv_sock::PrivRequest` variants carry a required field:
`{"op":"update_container","name":"atlas","stream":true,"toplevel":"/nix/store/<hash>-nixos-system-atlas"}`.
The three cases when the swarm's closure isn't the one this hive expects
are in [`docs/swarm/builder.md`](../swarm/builder.md#rebuilds-download-the-closure).
A rebuild with no record, or none the builder pushed, fails with an error
that names the agent; see
[`docs/swarm/builder.md`](../swarm/builder.md#rebuilds-download-the-closure).
- **Hyperhive.** The commit pins one hyperhive rev for the swarm, so an agent
runs the hyperhive of the controller's deploy, not its hive's.
- **Markers.** After a successful build `hive-c0re` writes the hyperhive
commit the head pins to `applied/.<n>.hyperhive-rev`, and the config
commit to `applied/.<n>.config-rev` (none for an archetype agent). The boot
reconcile ([below](#autoupdate-on-startup)) and the dashboard's update chip
read them.
- **Marker.** After a successful swap or create `hive-c0re` writes the closure's
store path to `applied/.<n>.closure`. The autoupdate pass
([below](#autoupdate-on-startup)) compares it with the record.
## Deploy markers per agent
```
/var/lib/hyperhive/applied/ deploy markers - core-only
├── .<name>.hyperhive-rev # hyperhive commit of the last good build
└── .<name>.config-rev # config commit of the last good build
└── .<name>.closure # store path of the last closure applied
/var/lib/hyperhive/meta/ plain hive state - core
├── topology.json # the live agent set
@ -191,8 +178,11 @@ the swarm builder built the agent from the updated repo (the
```
hive-c0re keeps no copy of an agent's config: the forge's `agent-configs/<name>`
`main` is the only one, and a config change is a PR against it. The two markers
record what the agent's last successful build pinned, and nothing else.
`main` is the only one, and a config change is a PR against it. The marker
records the closure last applied, and nothing else. Hives that ran before the
marker existed have `.<name>.hyperhive-rev`, `.<name>.config-rev` and
`meta-rev/<name>` files instead. Nothing reads them any more, and you can
delete them.
Older hives also kept an *applied* repo at `applied/<name>` and a *proposed*
clone at `agents/<name>/config`. Nothing reads either any more, and you can
@ -210,9 +200,8 @@ The bundled `hive-forge` container runs on the swarm's forge host
(`deploy.forgejo.enable`, see [`../swarm/services.md`](../swarm/services.md)).
Every agent created through the swarm has a repo in the private
`agent-configs` Forgejo org, created and seeded by swarm-controller.
hive-c0re pushes nothing to it and keeps no clone: forge `main` advances only
by an operator's merge. The hive reads the head of `main`, to resolve the
commit `internal/meta` pins.
hive-c0re pushes nothing to it, keeps no clone and reads nothing from it: forge
`main` advances only by an operator's merge.
Each agent is a **write collaborator on its own** `agent-configs/<name>`
repo — so it can push a branch and open a config PR — but not a member
@ -356,29 +345,30 @@ swarm-level alert could carry the same signal).
`hive-c0re serve` runs `auto_update::run` in a background task right
after opening the coordinator. It enumerates managed containers and
rebuilds any whose recorded hyperhive rev differs from the one
`internal/meta` pins now. That's how a hive that missed the swarm's
fan-out catches up. Sub-agents and the root agent go through the same
graceful rebuild subgraph.
rebuilds any whose closure marker differs from the store path the agent's
record in `agent-closures` declares. That's how a hive that missed the
swarm's deploy request catches up, and it repeats every five minutes, which
covers a request lost while the hive is up. Sub-agents and the root agent go
through the same graceful rebuild subgraph.
"Rev" = the hyperhive commit pinned by the head of `internal/meta`, as
the hive reads it with its forge token. Marker
file: `/var/lib/hyperhive/applied/.<name>.hyperhive-rev`, written after
a successful build. If the hive can't read the pin (no forge token, the
forge down, `internal/meta` not published yet), the scan rebuilds
nothing; a rebuild would have no flake to build from either. A hive deploy
alone doesn't rebuild agents.
The comparison reads the record and the marker
(`/var/lib/hyperhive/applied/.<name>.closure`, written after a successful
swap or create) and nothing on the forge. It rebuilds nothing when the hive
can't tell: no connection to the swarm queue, no record, a closure the builder
didn't push, or an agent with no marker. The last case covers every agent on
the first boot after the marker arrived, so that boot rebuilds nothing; the
agent's next deploy request from the swarm writes the marker. The periodic pass
attempts a declared store path once per process, and skips an agent while a
rebuild of it waits or runs, so a closure that fails to deploy doesn't
restart its agent every five minutes. Restarting `hive-c0re`, or a rebuild from
the agent's menu, tries again.
A stopped agent gets its rebuild at its next start: the start path upgrades
the start of a stale, stopped agent to a rebuild followed by the start. In
the window after a config merge, before the swarm's update has pinned the new
config, that rebuild fails with the error that names the agent, and the
agent stays stopped until the update lands and you ask for the start again.
the window after a config merge, before the swarm's builder has recorded the
new closure, the rebuild deploys the previous one.
The dashboard surfaces pending config changes per agent: a clickable
"needs update ↻" badge appears whenever the config commit that `internal/meta`
pins for the agent's next build differs from the one its last build pinned. The badge POSTs `/api/rebuild/<name>`, which inserts the
same `job_queue::templates::rebuild` DAG so manual triggers and the
startup scan can't drift. When at least one container is stale, a
top-level `↻ UPD4TE 4LL` button appears that loops over every
stale container.
The dashboard shows no pending-update state: the hive applies a changed
declaration itself. The agent menu's rebuild queues the same
`job_queue::templates::rebuild` DAG, which downloads and applies the declared
closure again.

View file

@ -544,8 +544,8 @@ Isolation still holds by default: a container has its *own* dirs and,
unless it holds the permission, nothing else.
Under `/var/lib/hyperhive/applied/` — the hive-c0re-only deploy markers
`.<name>.hyperhive-rev` and `.<name>.config-rev`: the hyperhive and config
commits the agent's last successful build pinned. No config lives here; the
`.<name>.closure`: the store path of the closure the agent was last swapped to
or created from. No config lives here; the
forge's `agent-configs/<name>` is the only copy. A hive that predates this
finds per-agent `applied/<name>` repos here. Nothing reads them any more, and
you can delete them.