Watch
0
0
Fork
You've already forked hyperhive
0
Commit graph hyperhive/docs/agent-lifecycle/persistence.md
Author SHA1 Message Date
atlas
7f26238c87 hive-matrix-mcp: retry a secondary account that failed to come up
The daemon's two-minute refresh compared the store's linked account
names with the names it booted with. A secondary that was named but did
not come up - no token at bring-up, a token the homeserver rejected
(M_UNKNOWN_TOKEN), or a homeserver still down after the ~52 s boot
retries - kept its name in the set, so nothing ever noticed it again:
it stayed down until the daemon restarted for some other reason, and
an operator re-linking a rejected account changed no name.

Each such secondary is now tried again on the same two-minute period,
one per tick, after one refresh and then doubling waits capped at
sixteen minutes. One that comes up joins the registry (now behind a
lock) and its sync loop joins the running set; the primary and the
other accounts are not restarted. The store is still only read.

The startup todo wipe moves out of bring_up_account to run once before
any sync loop: run per account, a late secondary would have cleared the
todos the running accounts' sweeps had already deduped.

Part of #5017 (slice M).
2026-10-14 16:20:04 +02:00
atlas
45568f6e60 matrix: back up agents' room keys with a controller-minted recovery passphrase
An agent's matrix encryption keys lived only in its local crypto store, so
`destroy --purge` or a primary-account token reset lost every encrypted
room's history with no way back (#5022).

swarm-controller now mints a random passphrase (32 bytes from the kernel's
CSPRNG, base64url) for every account listed under each agent's `matrix/`
directory, at `swarm/agents/<agent>/matrix-recovery/<account>`, in a
five-minute pass. The write is a cas=0 create after a read; a path that
holds anything is never replaced, and a create that loses a race counts as
done. The controller's existing `data/swarm/agents/*` grant covers the
write and the agent's own `agents/<agent>/*` read grant covers the read, so
no policy changes.

hive-matrix-daemon reads the passphrase when it brings an account up, after
cross-signing: with no backup on the homeserver it enables recovery with it
(backup + secret storage); with one there it recovers, which imports the
backup key and the self-signing key, and room keys then download through
`BackupDownloadStrategy::OneShot`. No passphrase yet logs at INFO and the
account comes up without backup; any failure logs WARN and never stops the
daemon.

Recovery runs after `ensure_cross_signing`, not before: on a new account
cross-signing must exist locally for `enable` to export it into secret
storage, and on an existing identity `bootstrap_cross_signing_if_needed` is
a no-op, leaving `recover` to import and sign.

Part of #5022 (S1+S2, with the S4 docs).
2026-10-14 02:09:57 +02:00
atlas
78412b3d3b hive: remove the read-only schedules page and the old schedule store
Scheduled prompts live at swarm level (swarm-controller's
/api/schedules, `swarmctl schedule`, the swarm UI's /schedules page).
The hive dashboard's SCH3DUL3S tab only listed the rows left in
hive-c0re's `scheduled_prompts` table so operators could copy them
over; the operator confirmed everything is migrated.

Removed:
- the SCH3DUL3S tab, its pane, schedules.js, its CSS (including the
  `.rqe-source*` rules, whose only emitter was schedules.js) and the
  hub tile's "schedules" entry
- hive-c0re's GET /api/schedules and the `scheduled_prompts` store
  (with its v1 column migration and test)
- hive-sh4re's `schedule` wire types, which only that route used
- docs describing the tab, the route and the two tables

The rebuild-queue cancel route that shared the module moves to
dashboard/rebuild_queue.rs under an OpenAPI tag of its own.

The two sqlite tables are not dropped: nothing opens them any more,
and persistence.md says they can be dropped by hand.

Closes #4916
2026-10-13 01:45:12 +02:00
atlas
fccf2ab892 hive-c0re: read each agent's swarm caps from agent-limits
Every 30 seconds `workers::swarm_limits` reads the `agent-limits` key of
each agent this hive has and stores the result in
swarm-resource-limits.json, where the drop-in, the heap ceiling and the
dashboard read it. A cap therefore follows its agent to whichever hive
runs it.

Each value goes through the hive's own validate_cpu_quota /
validate_memory_max before it is stored, since hive-priv renders it
verbatim into the container's drop-in, and one bad value refuses the whole
set (argus on #4268, carried over from the wanted-state consumer this
replaces). A failed read, or a bucket that does not answer, leaves the
stored caps as they are; three failures in a row raise a banner.

swarm-nats-auth grants every hive `$JS.API.DIRECT.GET.KV_agent-limits.>`:
read-only, and every agent's key, not just the hive's own. Scoping it to
the hive's agents would need a roster in the auth responder, which
policy.rs argues against. mara chose this on #4268 (c95293); narrowing it
is a possible low-priority follow-up, not planned. A test pins that the
grant reads every key, writes none, and reaches no other bucket.

Contributes to #3865.
2026-10-12 20:22:21 +02:00
atlas
eaca3c4eb3 agents: always write the memory cap, infinity for none
The cap file was removed for a cap of `infinity` or a RAM percentage,
so the boot check warned on every start of such an agent, for a
legitimate config. The file is now written on every start:

- a size: its byte count, as before;
- `infinity`: the literal `infinity`. The generators set no JSC heap
  ceiling and no subagent MemoryHigh=, and the boot check is quiet;
- a percentage: resolved on the host the way systemd resolves
  MemoryMax=N% for the container@ unit. That is permyriad (at most two
  decimals, at most 100%) of sysconf(_SC_PHYS_PAGES) *
  sysconf(_SC_PAGESIZE), rounded down to a page;
- a value systemd ignores (zero, over 100%, more than two decimals,
  unparseable) leaves the unit uncapped, so it is `infinity`.

The file is missing only when hive-c0re can't tell the cap: the
limits file and the kept drop-in are both unreadable, or a percentage
meets an unknown host memory size. The boot check now warns only for
a missing file or a first line that is neither a byte count nor
`infinity`.

Part of #5081 (slice S0-2).
2026-10-12 10:59:12 +02:00
atlas
0d619e215e agents: read the memory cap at boot, not from the build
An agent's memory cap was rendered into its flake (`memoryMaxBytes` →
`services.hyperhive.agent.claudeMemoryMaxBytes`), so the closure depended
on it and a changed cap reached the JSC heap ceiling and the subagent
`MemoryHigh=` only after a rebuild.

hive-c0re now writes the effective `MemoryMax=` in bytes to the agent's
runtime dir (`/run/hive/memory-max-bytes` inside) in `write_dropins`,
from the same value the drop-in gets. The file is removed for
`infinity` or a RAM percentage, and left alone when the drop-in is kept.

Inside the container, `nix/agent-modules/memory-cap.nix` reads it at
boot: an environment generator sets `BUN_JSC_forceRAMSize` (75%) for
every unit and `machinectl shell`, `/etc/set-environment` exports it in
login shells, and a unit generator writes `MemoryHigh=` (2/3) as a
drop-in for `hive-subagent-daemon`.

The render drops `memoryMaxBytes`, the agent option is removed (both
spellings fail eval with a removal message), and
`effective_memory_bytes_from` goes with its only caller.

Part of #5081 (slice S0-2).
2026-10-12 10:59:11 +02:00
atlas
7e6a4c7727 agents: remove send(operator) and the operator inbox; alert on turn failures
Agents reach the operator over matrix now (#5115). A `send` to
`operator` is refused, by the broker and by the MCP allow-list check,
with an error that names the matrix MCP tools. The dashboard's Y3R C4LL
tab goes with it: the operator inbox, `GET /api/operator-inbox`,
`Broker::unread_for_recipient`, and the browser notifications that only
ever fired on operator-bound messages.

The harness no longer posts turn failures and plugin-install failures
to the operator inbox. Both are already logged at warn; a new Grafana
rule, `hh-agent-turn-failed`, fires per agent on the harness's
`turn failed` line.

Dashboard-to-agent messaging (op-send, the agent web UI's message box,
mark-all-read for agent inboxes) is unchanged.

Decisions per mara on #5115: q1 a, q2 a plus a grafana alert, q3 keep
dashboard-to-agent messaging and leave swarm-level code alone.
2026-10-12 08:50:06 +02:00
atlas
baefb32bd5 Merge forge/main into atlas/5034-s4-remove-hive-model 2026-10-11 03:42:22 +02:00
atlas
466e32b69f hive-c0re: close build-log rows a dead process left in flight
A build_logs row is opened with finished_at NULL and closed only by the
process that runs the build. If hive-c0re stopped in between, the row stayed
open for good: the dashboard listed it with no status, its log stream never
sent `done`, and the retention sweep, which skips open rows, never deleted it.

Coordinator::open now closes every open row as `fail` and appends a line
saying hive-c0re stopped. It runs before this process starts any build, and
only there: BuildLogs::open itself leaves rows alone so a second opener can't
close another process's live rows.
2026-10-11 02:37:08 +02:00
atlas
ed271e655f agents: remove the hive-level /effort control too
mara on #5034: "effort shares /models fate". The per-agent web UI no
longer sets effort: the `/effort` slash command, the effort picker,
`POST /api/effort`, `Bus::set_effort`, the `hyperhive-effort` writer,
`EFFORT_LEVELS`/`is_valid_effort` and the ACP effort-list payload are
gone, and `lib/modelEffort.ts` with them. The effort chip is
display-only and shows its source (`effort_source` in `/api/state`);
on ACP it stays hidden while the session offers no effort level.

The read side is unchanged: with no swarm value an agent runs an
existing `hyperhive-effort` file, else its agent.nix effort, else
medium. Existing files are not touched.

Refs #5034
2026-10-11 01:28:44 +02:00
atlas
594693f4c7 agents: remove the hive-level /model control; the swarm editor sets the model
The per-agent web UI no longer sets the model: the `/model` slash
command, the model picker, `POST /api/model`, `Bus::set_model` and the
`hyperhive-model` writer are gone. The model chip is display-only and
shows where the model comes from (`model_source` in `/api/state`). The
model is set on the swarm (swarm UI agents page, `swarmctl agent
inference set`).

The read side is unchanged: with no swarm value an agent runs its
agent.nix model, else an existing `hyperhive-model` file, else haiku.
Existing files are not touched.

With the picker gone, its model list goes too: `available_models` in
`/api/state`, the ACP model-list filter, and the harness's global
`HIVE_AVAILABLE_MODELS`. The subagent daemon keeps its own
`HIVE_AVAILABLE_MODELS` from mcp.nix, and the `availableModels`
assertion stays.

`/effort` (command, picker, route, file) is unchanged.

Refs #5034
2026-10-10 23:11:53 +02:00
atlas
d2ea50031e agents: work/ in the disk-metric docs, holder never sees work/, chown guard comment 2026-10-05 13:23:39 +02:00
atlas
89a5d65119 agents: persistent /agents/<n>/work dir outside state/
Agents keep git worktrees and cargo target dirs under /home/<agent>,
which lives in the container rootfs and is deleted on destroy. They
can't move them under state/, because a claude started there walks up
into state/CLAUDE.md and takes on the agent's identity.

Add a per-agent work/ dir next to state/ on the host
(/var/lib/hyperhive/agents/<n>/work), bind-mounted RW at
/agents/<n>/work. hive-c0re creates it in ensure_state_dir and in
set_nspawn_flags (the rebuild path reaches only the latter), the
agent's activation chowns it to the agent user like state/ and
harness/, and measure_agent_disk counts it. destroy never touches the
agent root, so work/ survives it; purge removes the whole root, so
work/ goes with it.

Closes #4977
2026-10-05 12:56:02 +02:00
atlas
962c1548eb swarm-controller: revoke matrix admin from former operators
The operator admin sync now also takes homeserver admin away. It records
each user id it sends `make-user-admin` for in
`operator-matrix-admins.json` under the controller's state directory
(written to a temporary file and renamed over it; a missing file is an
empty set). On each pass, a recorded account that is no longer in
authelia's `admins` group but is still joined to `#admins:<server>` is
sent `!admin users force-leave-room <user> <admin room id>` as `@swarm`,
over the same `/_matrix` path the grant uses. Leaving that room is what
ends tuwunel admin.

It never revokes an admin it didn't grant, `@swarm`, tuwunel's server
user (`@conduit:<server>`), an agent's account (the store's
`hive-agent-*` cert roles, the same roster the agent account mint uses),
or anyone in the new `services.hyperhive.swarm.matrix.adminSync.ignoreUsers`
list. That list reaches the daemon as
`SWARM_CONTROLLER_MATRIX_ADMIN_SYNC_IGNORE_USERS`. It also never sends a
revoke that would leave `#admins` with no member besides `@swarm` and the
server user.

A revoke is sent at most once per account per controller process, like a
grant. A recorded account that is out of both the group and the room is
dropped from the file.

Closes #4799
2026-10-05 12:39:02 +02:00
atlas
e4b170f8c6 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.
2026-10-04 23:55:32 +02:00
atlas
b4a2aeb5ba docs: scheduled prompts are swarm-level; the hive page is a read-only list
Drops docs/tools/scheduling.md and every description of hive-level
schedule creation, the worker, the removed MCP tools, the scheduling tool
group and the removed endpoints/events, pointing at docs/swarm/schedules.md.
2026-10-04 21:23:31 +02:00
atlas
a88ed9f24e docs: config changes are operator merges on the forge
Rewrites the config-change flow around the forge merge and the
DeployRequest{rev} deploy, drops the MergeConfigPr approval, its deploy
DAG, the hive's `/webhook/` route and the `core` merge allowlist from
the docs, and states that operators join the `operators` team by hand.

Refs #4850
2026-10-02 23:13:04 +02:00
atlas
7d352486cd docs: state current behaviour without change-log wording 2026-10-02 11:43:10 +02:00
atlas
788542ba75 docs: drop statements about absent things, state current behaviour 2026-10-02 11:43:10 +02:00
atlas
37b8ca20d1 docs: state current behaviour, drop remaining change-log wording
Refs #3902
2026-10-02 11:43:10 +02:00
atlas
9545151b8d docs: state current behaviour, drop change-log wording
Refs #3902
2026-10-02 11:43:10 +02:00
atlas
2c7e586f47 forge: external forge accounts live in swarm bao; the agent fetches them itself
An operator now links an agent's external forge account (label, base URL,
token) in the swarm UI. swarm-controller stores it at
swarm/agents/<agent>/forge/<label>. There is no index: the store's
listing of the agent's forge/ directory is the set of accounts.

In the agent, hive-agent-forge-accounts (oneshot + 2-minute timer, as
the agent user, under its own store certificate) lists
swarm/agents/<agent>/forge/ with the `list` #4866 grants an agent on its
own metadata subtree, reads each account, and writes
<state>/forge-<label>-token and forge-<label>.json in the names and shape
hive-forge -f already reads. An empty listing (a 404, which `bao kv list
-format=json` answers with `{}` and an empty stderr) is zero accounts; a
denial or an unreachable store fails the unit. It never deletes: files
for labels not listed, including ones the hive wrote, stay as they are.

Removed: the dashboard FORGES tab (credentials.js/html section and its
CSS), hive-c0re's extra_forges.rs and its routes, priv_client's
extra-forge calls, and hive-priv's WriteAgentExtraForgeAccount /
DeleteAgentExtraForgeAccount with their helpers. The GITHUB tab and
WriteAgentGithubToken stay.

Also: persistence.md's matrix avatar note names the exit-75 restart on a
changed account listing, not the dashboard, as what brings a linked
account up.

Refs #4348
2026-10-01 18:05:33 +02:00
atlas
97fb76ce99 matrix: the agent's daemon pulls its linked accounts from bao itself
hive-matrix-daemon now learns which external matrix accounts it has from
the swarm secret store, under the agent's own certificate, and the hive
push chain for matrix is gone.

The daemon lists swarm/agents/<agent>/matrix/ (the `list` its policy
grants on its own metadata subtree), reads each account's homeserver
from its credential, and brings the accounts up with their tokens from
the store. Every two minutes it lists again and exits with 75 when the
set of linked accounts changed; the unit restarts on 75 without counting
a failure. A listed name whose credential reads as absent is skipped and
logged once. At start it removes the matrix-token-<a> /
matrix-account-<a>.json pairs a hive delivered (a sidecar marks a pair
as delivered; a declared tokenFile keeps its token).

Removed: CredentialNotice and the $SWARM.credential.* subject and NATS
grant, the controller's publish and its queue precondition on the PUT
route, hive-c0re's credential subscription arm and workers/credential.rs,
priv_client::write_agent_matrix_token, hive-priv's WriteAgentMatrixToken
and its helpers, and the daemon's state-dir account discovery.

Kept: WriteAgentGithubToken and the external-forge path
(WriteAgentExtraForgeAccount, extra_forges.rs) are untouched, and a
declared matrixAccounts tokenFile is still read when the store has no
token for that account.

Refs #4348
2026-10-01 17:43:28 +02:00
atlas
5785c0024c Make agent creation swarm-only and refuse a name placed on another hive
swarm-controller's POST /api/agents now refuses (409) a name the swarm
has already placed on a different hive: a non-Destroyed declaration in
that hive's wanted state, or a SetAgentWanted node still queued for it.
The same name on the same hive is that agent being re-created and goes
through. A wanted state that cannot be read refuses (503/500) instead of
reading as "placed nowhere". Creations are serialised from that read to
the graph insert so two concurrent creations of one name cannot both
pass.

Hive-level creation is removed: hivectl `agent create` / `request-create`,
HostRequest::Spawn / RequestSpawn, the dashboard POST /api/request-spawn
route, and ApprovalKind::Spawn with its approve/resolve arms and the
approval-carrying `templates::spawn`. The swarm path (deploy request or
wanted-state sweep -> queue_first_deploy -> templates::first_deploy) used
none of them. Old `spawn` approval rows are skipped by collect_lenient,
as `init_config` rows were in a3b672d1.

policy.rs's comment on agent_object_name stated swarm-wide name
uniqueness as a fact; it now says where it is enforced and what that
check cannot see.

Refs #4396
2026-09-29 15:47:40 +02:00
atlas
2252c55df8 hive-priv: create agent socket dirs on start; drop hyperhive-agents.conf
/etc/tmpfiles.d/hyperhive-agents.conf was a boot-time backstop (#2290)
that pre-created every agent's bind sources. The start preamble already
creates them for every c0re-driven start, and on this host only hive-c0re
starts agent containers. The file was also the reason the socket dir's
owner had to be declared there, which is how it spent its life at
`0777 root root` whenever the uid could not be resolved (#4742).

- hive-priv gains `EnsureAgentSocketDir { name }`, called from
  `set_nspawn_flags` in every start path. It creates
  `/run/hive-agent/<name>` `0751 root:root` with mkdirat relative to an
  O_DIRECTORY|O_NOFOLLOW fd for the parent. An existing entry has to be a
  directory (fstatat AT_SYMLINK_NOFOLLOW); anything else is refused, and a
  directory is left alone. hive-c0re's own create_dir_all went: its /run
  is read-only under ProtectSystem=strict.
- The container's `hive-agent-user-migrate` activation chowns that dir to
  the agent user and sets 0751, the same way it already handles state/ and
  harness/. It refuses a symlink or non-directory there, since `test -d`
  and chmod follow links. No host-side passwd parse, and no window where
  the dir is world-writable.
- `/run/hyperhive/agents/<name>` stays created by hive-c0re itself
  (`ensure_agent_runtime_dir`). It holds the `mcp.sock` that hive-c0re
  binds as hive-core, so it must not become root- or agent-owned.
- The `/run/hive-agent` parent is declared in hive-priv.nix, `0755
  root:root`, instead of hive-gateway's hive-core rule. hive-priv is its
  only writer now, and hive-priv's ReadWritePaths needs it to exist.
- The manager start in `ensure_root_agent` now goes through
  `converge_start_preamble` + `start_with_fallback`. It was a bare start,
  so after a reboot the manager's bind sources existed only because of the
  tmpfiles file, and its limits drop-in did not exist at all.
- Removed: `sync_tmpfiles`, `agent_uid_gid` / `parse_passwd_uid_gid`,
  `priv_client::sync_agent_tmpfiles`, `AgentTmpfilesEntry`, the tmpfiles
  body builder and their tests, plus the three call sites.
- Legacy: hive-priv unlinks the file at every start, ignoring ENOENT.
  `SyncAgentTmpfiles` stays one release as a payload-ignoring variant that
  does the same unlink and returns Ok, for an older hive-c0re.

Salvaged from #4752: the boundary.md correction that nginx only dials,
because ProtectSystem=strict makes its /run read-only.

Behaviour change: a manual `nixos-container start h-<name>` right after a
reboot, before hive-c0re has started that agent, now fails on a missing
bind source instead of starting.

Closes #4742
2026-09-27 18:55:33 +02:00
atlas
c6a778f666 lint: tighten atomic-write-secret.nix's header comment; fix vale contractions in persistence.md
The header comment grew to 39 lines across two audit-driven rounds,
over the 30-line comment-block-lint max. Trimmed to 30: merged the
value-as-argument rationale with its /proc/cmdline justification into
one paragraph, cut the usage example to one call instead of two, and
condensed the args paragraph — no content dropped, just restatement.

persistence.md's first-boot-migration marker paragraph used "it is"
and "there is not" — vale's Microsoft.Contractions rule (this repo's
config) wants the contracted forms, and "is not" also collides with
"is nothing" as a literal substring, which is what actually tripped
the error. Reworded to "it's" / "there's nothing", no meaning change.

Lint-only: no script logic changed, gates re-run below are all lint
checks (no module-eval, no cargo).

Refs #4723
2026-09-26 21:50:03 +02:00
atlas
ed53e9abcc host-modules: write credential files atomically; agent-modules: retry a failed .claude migration
Four host glue units fetched a secret from swarm-bao and rendered it
with `> path; chmod`: a reader racing the write could see a truncated
file, and briefly one at the wrong mode before the chmod landed.
glue-matrix-bao-token.nix, glue-queue-agent-credential.nix (both
files), swarm-grafana.nix and swarm-otel.nix now write to a same-
directory temp file, set its final mode/owner, then `mv -f` it over
the target — a shared `atomic_write_secret` helper
(nix/host-modules/lib/atomic-write-secret.nix) so the five call sites
share one implementation.

The first-boot `/root/.claude` migration in nix/agent-modules/user.nix
wrote its done-marker unconditionally, so a failed `cp` (disk full,
permission error) left the marker behind and no boot ever retried the
copy. The marker is now written only when there was nothing to
migrate or the copy succeeded; `cp -an`'s no-clobber semantics already
make a retry after a partial copy safe.

Refs #4723
2026-09-26 21:50:03 +02:00
atlas
85ba45b2de docs: agents' matrix accounts come from the swarm
The integration, tool, persistence and setup pages described hive-c0re
minting each agent's token into its state dir. They now describe the
swarm's appservice, its admin sender, the controller's mint and pass, the
daemon's store read and re-start timer, and the two new credential rows.
ruth's matrix account comes from the same pass once she holds the store
identity the setup page already has the operator mint.
2026-09-25 08:31:01 +02:00
atlas
336ed5a010 docs+comments: say what changed instead of tagging the tracker item
The prose added by this branch named the tracker item in seventeen
places, which check-issue-refs.sh rejects: a `#N` tag is dead weight for
anyone reading the public mirror, where no issue data exists. Each one
now states the fact it was pointing at — the parent field is gone — so
the sentence stands on its own.

Two of those lines also carried a rustdoc break: `[`write`]` in
topology.rs is ambiguous between the module's own `write` fn and the
`write!` macro, which `-D rustdoc::broken-intra-doc-links` fails. Spelled
`[`write()`]`, per rustdoc's own suggestion.

The host_config.rs rewrite is two lines rather than three so the doc
block stays under check-comment-blocks.sh's 30-line ceiling.
2026-09-21 22:43:16 +02:00
atlas
179f873722 docs: retire the agent hierarchy from every page that described it
The topology doc keeps its filename and its second half (manager
special-casing, harness unit shape) — both are cross-referenced from
other pages and neither is about the parent field. Its first half is
rewritten: what topology.json is now, and a table of what the removal
took with it, so a reader who finds `<parent>` or `set-parent` in an old
issue thread learns it went away rather than moved.

The dashboard's tree-rendering section is marked dormant rather than
deleted: the walk is still in swarm.js and retiring it is the frontend
owner's call.
2026-09-21 22:08:47 +02:00
atlas
11097ed336 remove the get_host_journal MCP tool and its capability
swarm-logs covers every host-tier unit this tool could reach, so the
second, capability-gated path into host journald earns nothing and is
removed outright rather than disabled behind a flag.

Removed end to end: the MCP tool definition + handler, the
GetHostJournal/HostJournal wire variants, hive-c0re's
dispatch_host_journal handler, the ReadHostJournal capability, and the
harness-side capability->--allowedTools gate. get_host_journal was the
only capability that mapped to an MCP tool, so allowed_capability_tools
could only ever return an empty vec; it goes too rather than linger as a
function that provably does nothing.

capabilities::has_cap/caps_for stay: #4624 gave ManageRootAgent's
bind-mount enforcement (hive-c0re/src/lifecycle/host_config.rs) a
second caller of has_cap, so they're no longer callerless once this
lands on top of it.

hive-sh4re's journal module (JournalPriority) had no consumer outside
this tool and is deleted.

An existing capabilities.json still naming read_host_journal does not
error: capabilities::prune_unknown drops unrecognised names with a
warn!, and an agent left with no capabilities has its entry removed. No
migration step is needed.

Untouched: hive-c0re/src/dashboard/journal.rs's
read_host_journal_response, which matches the name but is the private
helper behind the operator-only GET /api/journal-host dashboard route
and carries no capability check.
2026-09-21 19:31:45 +02:00
iris
04e27c4fb6 docs: suppress reviewed write-good.Passive false positives
133 hits across 38 files, all previously classified during #4548's sweep
and deliberately left un-rewritten (predicate-adjective state/necessity
description, design-intent idiom, structural/type-description idiom,
no-single-actor topology claim, parallel-triple exception, vale
substring-match artifact — see hyperhive#4548's per-PR bodies for the
per-hit reasoning).

Wraps each one in a scoped <!-- vale write-good.Passive = NO/YES -->
pair (the supported mechanism — TokenIgnores has a known offset-drift
bug) rather than a blanket per-file or per-rule silence, so a *new*
passive-voice hit anywhere in these files still fails once the rule
gates CI (next commit). Table/list false positives (docs/swarm/credentials.md's
renewal-table cells) wrap the whole block, not each cell.

Part of #4546.
2026-09-20 16:24:11 +02:00
iris
18c5b5c5ad docs: clear write-good.Passive hits in matrix/persistence docs
Part of #4548 — second batch. This PR: docs/integrations/matrix.md
(18 hits), docs/agent-lifecycle/persistence.md (17 hits) = 35 more of
the 353 (99 total across the two PRs so far).

Same method as the first batch (#4551): minimal, meaning-preserving
active-voice rewrites only, no restructuring beyond what each flagged
sentence needed, no vale-off silencing.

Genuine false positives left in place, with per-file reasoning in the
PR body: predicate-adjective state descriptions ("is wedged", "is
stopped", "are unrelated", "is unaffected", "is needed"), one
design-intent idiom ("was meant to prevent" — same shape as earlier
docs-lint work), and one vale parsing artifact: "is read" flagged
inside the hyphenated compound adjective "read-only", not real passive
voice at all.
2026-09-20 04:10:41 +02:00
atlas
99b141f5f2 matrix: drop the per-agent matrix.enable; accounts are the enable signal
`services.hyperhive.agent.matrix.enable` was a second source of truth for
a fact the account set already carried: after ①-③ the hive-internal
`main` account is an ordinary `matrixAccounts` entry, so "does this agent
have matrix" and "does this agent have an account" were the same question
asked twice, with the boolean able to disagree.

The option is gone and a non-empty `matrixAccounts` now gates the daemon
unit, its token path-watcher and the injected `extraMcpServers.matrix`
entry.

That is only a real condition because `matrixAccounts.main` is itself
gated: it is declared when `matrix.url != null`, never unconditionally. A
`main` with no homeserver is an account the daemon can never log in as,
so declaring one always would have made the signal trivially true and
turned matrix on for every agent in every hive. With the URL gate, the
empty set is reachable exactly for an agent the hive gave no homeserver
and whose operator declared no account of its own — the state the old
`enable = false` expressed.

Assertions: "extras require enable" is deleted, having become the
definition of the thing it checked (an external-only account with its own
homeserver is now rendered rather than rejected). `main.tokenFile` stays
pinned, re-guarded on `? main` instead of on the flag, since `main` is
absent whenever the URL is null and an unguarded index would throw there.

Both spellings of the option get `mkRemovedOptionModule`, following
../host-modules/deploy.nix's registrationTokenFile pair rather than a
silent delete: the definition whose meaning changes is `false`, and left
undeclared it would be ignored and hand the agent the tools its operator
turned off. Failing the eval with the replacement spelling is the only
outcome that cannot.

module-eval gains the three arms — URL, nothing, external-only — with the
middle one carrying why it exists: it is the only thing in the suite that
would notice `main` becoming unconditional again.

Refs #4475
2026-09-18 10:35:16 +02:00
atlas
7570597190 docs: repoint agent-tier option paths to services.hyperhive.agent.*
Refs #4479
2026-09-18 03:05:43 +02:00
atlas
a3b672d1d5 refactor(hive-c0re): drop the request_init_config tool and InitConfig approval
swarm-controller's `InitAgentConfigRepo` node already covers config-repo
creation, so this deletes a duplicate rather than a capability; old
`init_config` rows are skipped by `collect_lenient` with no migration, by
operator decision.

Refs #4398
2026-09-14 19:03:44 +02:00
iris
f22791b7a4 docs: clear the remaining error-level vale lints
Per #4128 (mara: allow-everywhere false positives go in a central
list, otherwise fix in source). Testing surfaced better fixes than
the plan posted on the issue:

- 5x Microsoft.Contractions 'that is' idiom false positives: adding
  the missing comma ("that is, ...") both reads better and satisfies
  the rule's own negative-lookahead, so no suppression is needed at
  all. Fixed in docs/integrations/forge.md, docs/tools/forge.md,
  docs/tools/hivectl.md, docs/web-ui/dashboard.md, and
  swarmctl-cli.md's generated source (swarmctl/src/main.rs, doc
  comment regenerated via markdown-docs).
- persistence.md's 'is not' matching inside 'is nothing': reworded to
  'there'\''s nothing' rather than add any exception -- dodges the trap
  and is a genuine contraction besides.
- ca.md's 'it is' matching inside the already-correct 'it isn'\''t':
  tried a central .vale.ini TokenIgnores entry first per the
  allow-everywhere framing, but testing against the real file (not
  just a synthetic snippet) found it silently fails to suppress
  whenever markdown emphasis syntax appears earlier in the same
  file -- an offset-drift bug in how Vale applies TokenIgnores, not
  a config mistake. Reworded to "it'\''s not" instead, same fix
  shape as persistence.md.
- config.md's 3 genuine Microsoft.Avoid 'backend' exceptions (already
  flagged and accepted on #4139 -- an actually-pluggable LLM API
  provider, matching the nix option's own name, not one internal
  system to name): scoped inline vale suppression around just that
  section, since this one really is context-specific rather than a
  rule bug.

Verified: fresh 'vale docs/ --minAlertLevel=error' is 0 errors AND
0 warnings (was 10 errors). nix fmt 0 changed beyond the edits
themselves. pre-push lints (tracker-tag/comment-block/doc-pointer)
clean. cargo clippy -p swarmctl -- -D warnings clean. Diffed the
regenerated swarmctl-cli.md against the old copy to confirm only
the intended line moved.
2026-09-09 22:55:28 +02:00
damocles
a4f72365c7 check-issue-refs: catch full forge issue URLs too, drop internal links from docs entirely 2026-09-09 21:15:28 +02:00
damocles
e1e913015d check-issue-refs: blanket-ban tracker tags in markdown too, no exceptions 2026-09-09 21:02:48 +02:00
iris
29927b7dfe docs: name the specific daemon instead of the vague 'backend'
Per mara's ruling on hyperhive#4041 (Microsoft.Avoid): 'backend' never says
which system it means. Fixed all 21 fixable hits to name the actual daemon
(mostly hive-c0re; one hive-agent instance and one hive-matrix-daemon one,
both confirmed against surrounding context, not guessed). Also tightened
Weston's own 'VNC backend'/'RDP backend' plugin references to their literal
upstream names (vnc-backend/rdp-backend) rather than dropping the accurate
term entirely, and reworded one unrelated Microsoft.Avoid hit ('and so on').

Left alone (flagged on the issue, not guessed): docs/turn-loop/config.md's
3 remaining hits match the actual nix option name (backendEnvironmentFile)
and describe a genuinely pluggable concept (which LLM API provider), not
one specific internal system.
2026-09-09 17:59:10 +02:00
iris
0ad0376fe3 docs: fix genuine passive-voice hits in docs/agent-lifecycle
Ninth batch of the ongoing write-good.Passive pass (hyperhive#4042):
read all 75 hits across agent-hierarchy.md/persistence.md/approvals.md
in context and rewrote 46 with a clearly nameable actor -- mostly
hive-c0re, a specific fn/type named right there or a sentence or two
earlier (approvals.md's numbered "Reminder delivery" list all share
hive-c0re as the established actor across three consecutive bullets),
or "the worker"/"the daemon" reused from the section's own established
subject.

Left 29 alone. Recurring buckets: predicate-adjective state pairs
("is wedged"/"is stopped", "is unaffected", "are unrelated", "is
overloaded" -- matches the established "is trusted"/"is privileged"
family), negative-capability/invariant idioms ("no X is needed",
"can't be removed/started", "no such transient was cleared"),
forward-looking design-intent statements about not-yet-built
enforcement (agent-hierarchy.md's whole "Planned topology semantics"
section leans on this: "are meant to run", "once enforcement is
finished", "will be gated"), the "audit trail, not cache" policy
invariant restated twice verbatim ("Approvals are kept forever" /
"indefinitely" -- left both alone consistently), and a config-
conditional state idiom ("when interval_seconds is set"). One more
false-positive tokenization, same bug as knowledge.md's earlier case:
persistence.md's "`config` is read-only" trips vale's "is read" match
inside the compound adjective -- not a real passive at all.

One deliberately-conservative leave: agent-hierarchy.md's "Reminder
cancellation is handled fully in-agent" had an ambiguous actor (the
in-agent socket vs. the hive-c0re source file the doc points readers
to for detail) -- left alone rather than risk a wrong attribution,
unlike the higher-confidence rewrites elsewhere in the same batch.

Verified via vale before/after: 75 -> 29 write-good.Passive hits,
exactly the 29 left alone above; error count and other warning
categories unchanged. Re-read every changed line in full surrounding
context after editing before running the final vale check.
2026-09-08 13:30:26 +02:00
iris
e82a735745 docs: fix write-good.So/ThereIs/Weasel lint findings
Fixes the "obvious ones first" slice of #4042 (mara: do the obvious
ones first) -- 81 hits across write-good.So, write-good.ThereIs, and
write-good.Weasel, all in docs/. Each is a genuine sentence rewrite
(lead with the real subject instead of "There is/are", drop a
sentence-initial "So ", replace a vague intensifier), not a blind
regex substitution -- read every hit in its real file context before
touching it.

3 of the 81 hits were in CI-generated CLI docs (docs/tools/{hivectl,
swarmctl,forge}-cli.md) -- fixed at the clap #[arg(...)]/doc-comment
source in hivectl/src/cli.rs, swarmctl/src/main.rs, and
hive-forge/src/verbs/repo_add_collaborator.rs, then regenerated via
each crate's `markdown-docs` subcommand so CI's freshness check stays
green.

Verified: fresh vale re-run shows 0 remaining So/ThereIs/Weasel hits
and no new hits introduced (983->982, exactly the one incidental fix
this pass also picked up at docs/scheduler/observability.md:48).
cargo fmt --check and clippy clean on the three touched crates.

Remaining write-good backlog (Passive: 726, TooWordy: 207) is
judgment-heavy and left for a follow-up slice of #4042, not bulk-
rewritten here.
2026-09-07 17:49:27 +02:00
iris
3f41e7a09c docs: drop redundant hyphens from -ly adverb + adjective/participle compounds
Fixes 22 of 23 Microsoft.Hyphens hits: an adverb ending in -ly doesn't
need a hyphen to the word it modifies (minimally scoped, currently
running, deliberately stopped, ...) -- the -ly ending already
unambiguously signals it's modifying the following word, so the
hyphen is redundant per Microsoft style.

Left one hit alone: docs/web-ui/dashboard.md's 'checked-not-originally-
active' is a single 4-word compound designator (parallel structure
with 'unchecked-was-active' right before it in the same sentence,
describing a diffing algorithm's two possible states), not an
ordinary -ly-adverb modifier -- dropping just the one hyphen vale
flagged would have broken the compound into a dangling
'checked-not-originally active' fragment. Also rewrote
docs/scheduler/coordinator.md's 'still-fully-queued' as a predicate
('DAGs that are still fully queued') rather than leaving a similar
broken partial-hyphen chain, since 'still' and 'fully' are both plain
adverbs with no reason to hyphenate at all once dehyphenated
consistently.

Verified via vale: only the one deliberately-left hit remains.
2026-09-07 16:28:06 +02:00
iris
05cf8e8276 docs: remove hyphens from auto-X compounds per Microsoft.Auto style
Fixed 72 hyphenated 'auto-X' compounds across the docs by deleting
the hyphen to match Microsoft style guide (autogenerated, autoregistration,
autodiscovery, etc). Preserved original capitalization throughout.

Covered 26 documentation files in docs/, including README.md, agent-lifecycle,
integrations, scheduler, networking, process, tools, trust-boundary,
turn-loop, and web-ui sections.
2026-09-07 16:28:06 +02:00
iris
4f2d1d281a docs: fix Microsoft.Foreign hits (e.g./i.e. -> for example/that is)
Substitutes 'for example' for 'e.g.' and 'that is' for 'i.e.' per
Microsoft style, with no comma after the replacement -- this repo
never puts a comma after e.g./i.e. either, so the substituted phrase
matches that house style instead of vale's literal 'for example,'
suggestion.

Covers all 77 real hits (72 from the initial vale pass + 5 more of the
same pattern found on a follow-up vale run). None of the flagged
instances were sentence-initial, so there's no capitalization concern
here.

Verified via a fresh vale run: 0 remaining Microsoft.Foreign hits.
Full accounting in /agents/iris/state/haiku-batches/foreign-report.md.
2026-09-07 16:28:06 +02:00
iris
ff377b0dee docs: fix capitalization losses + one text corruption from the contractions pass
Review pass on top of the previous commit (85a94671) turned up three
real issues its own vale-only verification didn't catch:

- 'has not' -> 'hasn't' fired inside 'has nothing', corrupting
  docs/swarm/README.md into 'hasn'thing to say upward'. Restored
  'has nothing'.
- ~44 hits where the original text was genuinely sentence-initial
  ('It is', 'That is', 'They are', 'What is', 'Do not', ...) got
  contracted in lowercase, silently de-capitalizing the start of a
  sentence. Checked each one's real surrounding text and capitalized
  the ones that needed it.
- Confirmed (by reading the actual text, not just trusting vale) that
  the 3 remaining 'is not'/'has not'/'it is' hits after those fixes are
  vale's own false positives: it matches these as a bare substring with
  no trailing word boundary, so it also fires on 'is nothing', 'has
  nothing', and the already-correct 'it isn't'. Left those alone --
  there's no real issue in the text.

Re-verified with a fresh vale run: the remaining 17 hits are exactly
the union of the 14 legitimate ALL-CAPS/bold-emphasis skips from the
first commit and these 3 false positives. Full accounting in
/agents/iris/state/haiku-batches/contractions-report.md.
2026-09-07 16:28:06 +02:00
iris
78021ce982 docs: fix 329 Microsoft.Contractions hits with proper safety checks
Apply contraction fixes across ~40 doc files (setup, integrations, lifecycle,
networking, scheduler, swarm, tools, trust-boundary, UI, etc.). Skipped 14 hits:
- 10 where words appear in ALL CAPS for deliberate emphasis (is NOT, do NOT, etc.)
- 4 where text could not be safely located due to markdown formatting or column position

Applied via systematic scan with checks for fenced code blocks, inline code spans,
and intentional caps. Preserves sentence-initial capitalization throughout.
2026-09-07 16:28:06 +02:00
atlas
af5dce0648 matrix: scope the daemon's token watcher to this agent's own state dir
`hive-matrix-daemon.path` globbed `/agents/*/state/matrix-token*`. Every
agent's state dir is visible from inside every container, so the
condition is satisfied by a sibling's token.

That is reachable, not cosmetic. The daemon deliberately exits 0 when it
has no token of its own — `Restart = "on-failure"` therefore does not
restart it, and the unit sits inactive, which is the state the path unit
exists for. In that state a sibling's token keeps the glob satisfied:
the path fires, the daemon exits 0, the unit deactivates, the path
re-arms, the condition is still true. systemd.path(5) activates a
`PathExists`-family condition that already holds immediately on arming,
so it repeats until the start limit stops it.

Scoped to this agent, the condition is false exactly when the daemon
would have nothing to do.

The glob is quoted in four other places, all of which would otherwise
name a pattern that no longer exists — a doc, a Rust doc-comment in
hive-c0re, a nix comment, and an assertion message an operator reads.
Each is reworded to the basename (`matrix-token*` in this agent's state
dir), which is what the assertion actually enforces via `baseNameOf`, so
they stay true wherever the directory moves.

Refs #4030.
2026-09-07 13:37:09 +02:00
iris
cd252ca6b2 docs: fix alex whitelist + condescending-word findings
Fixes the 'genuine' bucket from #4040's triage: whitelist/allowlist
(6 hits across 5 files, including a heading rename in turn-loop/mcp.md
-- checked no anchor links reference it first) and the
simply/obvious/easy condescending-word cluster (10 of 13 hits, real
sentence-level rewrites rather than mechanical deletion so nothing
reads awkwardly).

Left 3 alex.Condescending hits untouched on purpose:
integrations/matrix.md:271 and process/conventions.md:442 both say
"non-obvious", swarm/ca.md:162 says "not obvious" -- alex matched
the substring "obvious" inside a negated phrase, the literal opposite
of condescending. Flagging these on the issue rather than silently
leaving them out.
2026-09-07 13:25:09 +02:00
atlas
9e76589be3 docs: 10 cited nix options that do not exist, two in pasteable blocks
An option name that is absent reads exactly like one that is present —
the reader has no way to tell, and the failure surfaces as a nixos eval
error at deploy time. Two namespace reorgs the prose never followed:
`forge.*` moved under `swarm.`, and several flat paths under `c0re.`.

  docs/networking/gateway.md:335   services.hyperhive.forge = { … }
  docs/networking/gateway.md:367   forge.rootUrl        → swarm.forge.rootUrl
  docs/networking/gateway.md:389   frontend             → c0re.frontend
  docs/tools/hivectl.md:193        agentCpuQuota        → c0re.agentCpuQuota
  docs/tools/hivectl.md:317        forge.behindGateway  → swarm.forge.behindGateway
  docs/agent-lifecycle/persistence.md:404   agentCpuQuota → c0re.agentCpuQuota
  docs/web-ui/dashboard.md:610     modelPrices          → c0re.modelPrices
  docs/web-ui/dashboard.md:789     forge.publicUrl      → swarm.forge.publicUrl

Both `gateway.md` blocks are fenced nix an operator is meant to paste.

Two sites needed more than a rename:

  dashboard.md:781 cited `gateway.enable` as a live opt-in. It was
  removed; `gateway.md:269` already says the gateway is unconditional,
  and `hive-c0re/environment.nix:208` sets HIVE_GATEWAY_ENABLED = "1"
  with no condition. Rewritten to say so, keeping the flag-absent
  fallback the dashboard still implements.

  turn-loop/config.md:4 attributed the page's knobs to
  `services.hyperhive.agents.<name>`. No such option exists — every
  knob the page documents is agent-side `hyperhive.*`, set in the
  agent's own agent.nix.

Measured against the module system rather than a grep: `nix build
.#docs` renders nixosOptionsDoc over the evaluated trees (279 option
paths). That arm alone is wrong — it omits `internal = true` options
and called `gateway.localNames` and `swarm.peerHives` dead, so a second
arm requires the literal path to appear in nix/ and rescues exactly
those. Four remaining unresolved citations are correct prose saying
`gateway.enable` / `network.{enable,isolateContainers,upstreamDns}`
were removed, and are left alone.

Closes #3948
2026-09-02 17:17:09 +02:00