The parent's copy is for reading a child's config; a change to it is a PR
on the child's repo, made from a clone and merged after review. A
writable mount is a second path to the same file that skips that review,
which makes the boundary a convention rather than a permission.
Confirmed with ruth before flipping: it clones from the forge and opens a
PR, including for a brand-new child's first config.
The prose was the larger half. docs/approvals.md did not merely describe
the old mount, it *instructed* agents to use it ("can therefore edit,
commit, and submit changes for any of its direct children directly inside
its container"), and the doc comment in host_config.rs asserted a
dependency that never existed: the InitConfig seed runs as hive-c0re
against the host path, and read_only on a bind constrains writers inside
the container only. That comment is what produced issue #3206, now closed
as invalid.
bind_child_agent_dirs looped over state, harness and config alike and
mounted all three read-write, while the doc comment above it defended
only state. The rationale covered one dir, the loop covered three — the
uniformity is what erased the fact that the three have three different
answers.
harness holds the child's own runtime material (bash-tasks, the
turn-stats and event sqlite dbs) and nothing argues for a parent
touching it. The only other reader is stats::hive_stats, which reads the
host path directly and needs no mount into anyone.
config stays read-write here on purpose. The ruling is that it becomes
read-only, but request_init_config still has the manager seed a new
child's config in place, so flipping the mount before relocating that
step breaks agent creation hive-wide. That ordering now lives in the doc
comment, where someone about to finish the job in one line will see it.
docs/persistence.md justified all three dirs as RW; it now states the
boundary as three answers and names the right source file.
Seven places described an authoring flow that does not exist: the
manager editing and committing a child's config in the bind-mounted
config dir. mara, on the snapshot issue: "the pr workflow is the main
one, anything else is legacy … the config dir in agent container is
read only copy to see own config … changes need to go through local
clone and via forge".
Two of the seven are the request_init_config MCP tool description and
its args struct, which are rendered into the system prompt of every
agent holding the approvals tool group. A wrong tool description is not
a stale comment; it is an instruction the whole hive reads as fact,
which is why the claim kept being repeated back.
The init tool creates the repo and seeds it. Nothing else. Tailoring
the seeded template is not a separate mechanism - it is the ordinary
config-change flow, a PR reviewed like any other.
Prose only; the code already implements the PR flow (an approval's
commit_ref is a PR number). Where a doc justified the parent's
read-write mount on the child's config dir with the authoring model,
the justification is removed rather than replaced: that mount is a
defect tracked on its own issue, and it should not read as intentional
while it waits.
The disk_watch todo producer isn't turn-loop-shaped -- it's disk-space
state tracked in persistence.md's State dirs (per agent) section, right
next to hyperhive-todos.sqlite's own description. Leave a lean pointer
in the boot-wiring narrative instead of duplicating the detail.
Fixes#2727
Part of hyperhive#1898 (b): every docs subdir should have a top-level
README.md link, achieved by moving/renaming where an existing file
already fits the role.
docs/turn-loop.md already served as the hub + index for the three
sub-pages under turn-loop/ (claude-invocation.md, config.md, mcp.md),
so it moves wholesale rather than leaving a redundant top-level
pointer stub. Fixes every inbound/relative link across the repo
(top-level README.md, CLAUDE.md, docs/persistence.md,
docs/tools/scheduling.md, the sub-pages own back-link, hive-agent
README + doc comments, hive-agent/Cargo.toml, .prettierignore per-file
exemption entry) - grepped the whole tree for both turn-loop.md and
turn-loop/ to find every reference rather than trusting a partial
list.
nix fmt clean, cargo check -p hive-agent clean.
persistence.md is exhaustive schema/impl reference (sqlite table
columns, systemd unit internals, marker files, btrfs subvolume
mechanics) with the one thing an operator actually needs - what
happens to my stuff when I destroy or purge an agent - scattered
across several sections rather than answered up front.
Adds a short synthesis at the top: destroy keeps everything
(revivable, no re-login), purge wipes it all (no undo), approvals and
questions never age out, message history vacuums acked rows at 30
days, an agent's own /state/ and claude login survive every restart/
rebuild, and the root agent auto-recreates if it's ever destroyed.
Every claim checked against the existing detail further down in this
same file before writing it.
Part of hyperhive#1898.
Phase 4 (repoint every container onto `meta#<n>`) and phase 5 (rename
the `root` container to `h-root`) were marker-guarded one-shots for
layouts no live hive still has: containers are rendered onto `meta#<n>`
at creation, and the `h-` prefix has been the naming for far longer than
any deployment predates. A one-shot nobody can still trigger is dead
weight, so both are gone along with `repoint_container`,
`rename_manager_container`, `CONTAINER_TIMEOUT` and the two marker paths.
Phase 6 was not obsolete, only misplaced. Ruth's tool groups are now
seeded by `ensure_root_agent` on the one path that creates her, rather
than re-asserted on every hive-c0re boot. The skip-if-already-set guard
survives the move: a destroy+recreate under the same name must not reset
an operator's chosen group set back to MANAGER_DEFAULT.
That also settles a latent bug. Phase 4's marker check was a `return`,
not a skip, so on any hive carrying the marker phases 5 and 6 never ran
at all — the tool-group backfill, whose whole job was preventing a silent
privilege downgrade, has not executed here in a long time. Moving it to
create-time removes the question rather than answering it.
What stays is convergence: three unguarded, idempotent phases that re-run
each boot and no-op once their state is right. The module doc now names
the three categories so the next person can tell which kind they're
adding.
The PR added CPUWeight=/IOWeight= to the drop-in but left the prose
docs describing a two-setting file. Covers the cap-vs-share
distinction, the hive-wide-only scope (no resource-limits.json
override), and the iocost/BFQ caveat that makes IOWeight= inert on
most hosts.
The poller was a `tokio::spawn` inside the `hive-agent` serve loop. It
never needed anything from that loop except a socket path, so being
in-process bought nothing and cost two things: a harness restart took
forge notifications down with it, and the whole forge/HTTP dependency
tree was linked into the serve-loop binary.
It is now `hive-forge-notify`, a per-agent daemon with its own systemd
unit, a sibling of `hive-bash-daemon` and `hive-matrix-daemon`. Same
contract as those two: it reaches the harness only by upserting todos on
the in-agent socket, and nowhere else.
The module moves verbatim (`notify.rs`) — the formatters, the activation
gates, the dedupe map and all 33 tests are unchanged. Only the socket
call sites are rewritten, onto a small local `todo_client` rather than
the harness's. That mirrors what both sibling daemons already do, and
the etiquette differs on purpose: the harness's client carries a 60s
backoff schedule sized to ride out a hive-c0re restart, which its
callers need because they have no retry of their own. This poller's two
call sites both sit inside the 30s poll loop and both treat a failure as
"leave the thread unread, try next tick", so the poll interval already
is the retry; a second backoff would only stack sleeps and delay the
rest of the batch.
The unit is `Restart=on-failure`, not `always`. An agent with no forge
account is a supported configuration and the poller reports it by
logging why and exiting 0 — under `always` that clean exit would be a
restart loop on every forge-less agent.
`forgejo-api`, `url` and `time` drop out of `hive-agent`'s dependencies
with the module.
Also corrects docs that outlived the code they described: the persisted
`forge_cursor` field is long gone (forge's own read-state is the durable
record of what has been delivered), but `docs/persistence.md` and the
`harness_state` module docs still documented it as live.
The hive applies one `agentCpuQuota` / `agentMemoryMax` to every
container. That's the right default and the wrong ceiling: a build-heavy
agent needs headroom the other twelve don't, and raising the hive-wide
value to suit it hands that headroom to everyone.
Adds a per-agent override, persisted host-side and resolved per-field
against the hive defaults.
Follows the existing `meta/*.json` pattern (`capabilities.json`,
`tool-groups.json`): a host-side map read by `hive-c0re`, staged and
committed in the meta repo so every change lands in the audit trail.
```json
{ "sock": { "cpu_quota": "400%", "memory_max": "8G" } }
```
Fallback is **per field**, not per agent: an entry with only
`memory_max` leaves that agent on the hive-wide CPU quota. Absent file,
absent agent and absent field all resolve to the hive default, so the
feature is inert until someone opts an agent in.
Unlike the other meta files this one is **not** injected into the
container — a limit is something done *to* an agent, not something it
reads about itself.
```
hivectl agents set-limits sock --cpu-quota 400% --memory-max 8G
hivectl agents set-limits sock --reset
```
Values are validated before they're persisted: they go into a systemd
drop-in verbatim, and a typo there makes the unit fail to *start* —
turning a fat-fingered quota into a container that won't come back.
The command is declarative: each call replaces the agent's whole entry.
That makes a forgotten flag a silent revert, so a bare `set-limits
<name>` is rejected at the clap layer and clearing needs an explicit
`--reset`.
`ContainerView` gains `cpu_quota` / `memory_max`, both always populated:
there's no "unset" state to render, only "same as everyone else". They
reflect what the drop-in *says* — what the next start will enforce — not
a live cgroup reading.
The write goes through `meta::commit_resource_limits` rather than the
bare setter, so it's staged and committed under `META_LOCK`. Writing
without committing would leave the meta working tree dirty for the next
`prepare_deploy` to trip over.
Docs: `persistence.md` (the new meta file, and why it isn't injected),
`tools/hivectl.md` (the prose guide), `tools/hivectl-cli.md`
(regenerated clap dump).
Closes: internal/requests issue 25
A paused agent keeps its container, its claude session and its
dashboard/todo servers up, but stops driving turns. Messages queue
unacked and are drained on resume.
The whole protocol is a single marker file, `<harness>/paused`. That
directory is already a bind-mount shared between host and container, so
both sides just stat the same path: the harness reads it to decide
whether to drive a turn, hive-c0re reads it to render the badge and
writes/removes it for `hivectl pause|resume`. No new wire protocol, no
container round-trip, and it is sticky across restarts by construction.
Not calling `recv_next` while paused *is* the queueing semantic, so
there is no fencing to get wrong: reminders buffer in their unbounded
channel, the todo `Notify` permit coalesces, and a `request_next_turn`
that raced the pause survives because the gate sits above
`self_continue.take()`.
Graceful stop is handled host-side rather than in the harness: a paused
agent provably has no turn in flight, so `run_signal` skips the fence
entirely instead of eating the full `GRACEFUL_STOP_TIMEOUT` waiting for
a checkpoint turn that will never run.
`paused` is reported on `ContainerView` / `AgentStatusRow` for the
dashboard, orthogonal to `running` and reported for stopped containers
too.
Closes: hyperhive/hyperhive issue 2271
coordinator.md rewrites the queue section (node inventory, DAG shapes,
resources, desired-state reconciliation, boot reconcile); approvals.md
+ persistence.md + hivectl --graceful help updated to match. agent_power
lives in broker.sqlite like approvals/questions (own connection + busy
timeout) instead of a separate db file.
The 'non-destroyable (declarative; would fight with host NixOS config)'
claim was stale: the root/bootstrap container is imperative
infrastructure, managed end-to-end by hive-c0re (auto_update::
ensure_root_agent recreates it on startup if absent). The actions::
destroy bail is a soft policy guard, and destroying the container is
transient anyway — it's recreated on the next hive-c0re startup.
- Drop the redundant 'including the root/bootstrap container' /
'sub-agents + root' enumerations in the port-range + state-dir
uniformity statements — 'every agent' / 'all agents' is enough (and
calling out '+ root' reintroduces the special-case framing).
- Stop describing the root agent's privileged (manager-flavour) socket
as a current thing (it's being removed): drop the conventions.md
capability clause, the network.md per-agent-privileged-socket list
entry, and the persistence.md /run/hyperhive/manager/mcp.sock
runtime-dir bullet.
Config-repo editor, meta RO-mount, non-destroyable container, and the
topology.json example now refer to the root agent / bootstrap container
rather than 'the manager'. The /run/hyperhive/manager/mcp.sock path is a
real runtime path, kept (clarified as the root agent's manager-flavour
socket).
Progressive enhancement: a brand-new agent's state root under
/var/lib/hyperhive/agents is created as a btrfs subvolume when the host
filesystem is btrfs, otherwise it falls back to a plain directory. No
existing agent is auto-migrated — the new path only fires when the root
does not yet exist, so plain-dir agents are left untouched until an
explicit opt-in upgrade.
Two new privileged ops (subvolume create/delete are root-only):
EnsureAgentSubvolume statfs-gates on btrfs, creates the subvolume, and
chowns it to the hive-core user so the normal state/claude/harness
mkdirs succeed inside it; DeleteAgentSubvolume btrfs-subvolume-deletes
the root iff it is actually a subvolume. hive-c0re calls Ensure before
the per-agent dirs are created (spawn/rebuild/InitConfig) and Delete on
the purge path only — destroy keeps the subvolume for revival, matching
plain-dir semantics. btrfs-progs added to the hive-priv unit PATH.
Per-subvolume usage accounting + optional quota is a separate
follow-up.
persistence.md: bash-tasks/ says "persist until container purge" but
bash_tasks_vacuum now runs hourly and deletes terminal task trios older
than 48 hours. Update the description to reflect actual retention policy.
matrix.md: add step 6 to the Provisioning flow section documenting that
hive-c0re restarts hive-matrix-daemon immediately after writing the
access_token (fix for issue #1329). Previously the doc stopped at token
write; the restart + .path-trigger fallback interplay was undocumented.
The meta/ section only described the flake. Added entries for
topology.json, tool-groups.json, and capabilities.json — the three
system-level config files also committed there — with their writers,
readers, and injected env vars.
Two new sections housing rationale that was inline in
`harness-base.nix`:
- `docs/conventions.md::Best-effort oneshot services` — shape
contract shared by `tea-login`, `forge-avatar-sync`, and
`matrix-avatar-sync`: always exit 0, no `set -e`, skip silently
on missing prerequisites, wired to multi-user.target,
re-runnable. Plus the artefact-under-agent-home + service-stays-
root posture and the operator-visible-via-journalctl trade-off.
- `docs/persistence.md::matrix-avatar-sync` — the two-step `media
upload` → `set avatar_url` matrix-spec dance + why
`RemainAfterExit = false` (so the `.path` watcher's re-fire on
token appearance actually re-executes the unit).
In-code comments trim to one-liner purpose + pointers; the script
bodies stay (they're the actual implementation) but their inline
`#` rationale comments collapse where the docs cover them.
`description = ''…''` blocks (operator-facing options docs)
preserved per iris #718.
`nix flake check` clean; `nix fmt` clean.
iris's #718 scope: move substantive design context from `#` comment
blocks in `nix/` to corresponding `docs/` files, leave short
references in code. iris handed it back to me on #10114 since
nix/ is my lane + #775 established the pattern.
First pass — three highest-density blocks in harness-base.nix:
1. **First-boot agent-user migration** (~70 lines → `~20 lines code +
short ref` in the activation script). Substantive prose moves to
new `docs/persistence.md::First-boot agent-user migration (post-#658)`
section explaining the 4 steps the script performs + the eventual
removability of the marker-guarded body.
2. **nix-daemon `sandbox-fallback = true`** (10-line block → 5-line
ref). New `docs/gotchas.md::Containerized nix-daemon needs
sandbox-fallback = true` section covers the user-namespaces
rationale + nixpkgs-default override.
3. **Matrix daemon + token-arrival trigger** (~50 lines across two
systemd units → ~10 lines code + short refs). New
`docs/persistence.md::Matrix per-agent daemon + token-arrival
trigger` covers the socket-path rationale, the runtime-dir
ownership story, and the first-boot ordering pattern.
Net: harness-base.nix -84 lines, docs +74 lines. Substantive design
context moves to durable docs; in-code refs follow iris's pattern
from her #712 batches (`see docs/<file>::<section>`).
Follow-ups: hive-c0re.nix, hive-forge.nix, hive-matrix.nix (already
trimmed via #775 but a couple of remaining blocks could go), and
the smaller files in #718's scope table. Shipping this first to get
the pattern reviewed before larger batches.
Verified: `nix eval` on agent-base toplevel still resolves.