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.
This commit is contained in:
iris 2026-09-07 16:47:54 +02:00 committed by mara
commit e82a735745
35 changed files with 99 additions and 99 deletions

View file

@ -178,8 +178,8 @@ nspawn agent. Open questions, not yet wired:
One harness serve binary (`hive-agent`, with its `hive-agent-mcp`
sibling), one shared `nix/agent-modules/` tree, one service unit
(`systemd.services.hive-agent`) for all agents. There is no separate
manager service name or role distinction in the harness — privilege
(`systemd.services.hive-agent`) for all agents. No separate manager
service name or role distinction exists in the harness — privilege
differences live server-side in the broker socket (which tool groups
and manager-surface calls each agent receives).

View file

@ -56,7 +56,7 @@ flow.
Config changes flow through a **forge pull request** on the agent's
`agent-configs/<name>` repo — the same surface agents use for code PRs.
There is no bespoke MCP tool for config changes: opening the PR IS the
No bespoke MCP tool exists for config changes: opening the PR IS the
request.
1. The submitting agent (the child's parent, holding the `approvals`
@ -349,7 +349,7 @@ per-hive config — is the idempotent reconciler called by `spawn`,
`destroy`, `rebuild`, and the startup migration. Renders `flake.nix`
from the agent list; if it differs from disk, runs
`nix flake lock` + commits as `regenerate meta flake` (or
`seed meta from N agent(s)` on the very first call).
`seed meta from N agent(s)` on the first call).
The root agent has `/meta` RO-bound inside its container:
`git -C /meta log --oneline` is the swarm-wide deploy log,
@ -499,8 +499,8 @@ current config — not an editing surface.
An agent with the `approvals` tool group submits a change the same way
any other change is made: **clone the child's config repo from the
forge into its own state dir, commit on a branch, open a PR**, and let
the operator review and approve it. There is deliberately no second,
mount-shaped path that reaches the same file without the review.
the operator review and approve it. By design, no second, mount-shaped
path reaches the same file without the review.
Agents holding the `can_manage_top_level_agents` topology role (see
`hive-c0re/src/agent_config/topology.rs`) get additional host-side
@ -593,8 +593,8 @@ updates the root agent itself.
The system prompt (`hive-agent/prompts/system.md`, rendered by
`hive-agent/src/prompt.rs`) is the **same for every agent**; what
varies is which MCP tools are surfaced (gated by tool groups and
capabilities in `agent.nix`). There is no `role:manager` block that
renders only for the root agent. The root agent's approval-gating
capabilities in `agent.nix`). No `role:manager` block renders only
for the root agent. The root agent's approval-gating
behaviour comes from its CLAUDE.md / agent-specific instructions, not
the system prompt template.

View file

@ -72,7 +72,7 @@ power-intent registry:
last_fired_at_unix / last_result`. `ON DELETE CASCADE` from
`scheduled_prompts(id)` — requires `PRAGMA foreign_keys = ON`
per connection (set at open).
- `agent_power` — one tiny row per agent: `agent PK / wanted (up |
- `agent_power` — one row per agent: `agent PK / wanted (up |
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
@ -158,7 +158,7 @@ host-side). Best-effort and created on first write
(`CREATE TABLE IF NOT EXISTS`), so it's absent until a bash
task runs.
turn-stats.sqlite has **no vacuum** — it's one tiny row per turn
turn-stats.sqlite has **no vacuum** — it's one small row per turn
(~hundreds of KB even over months), read directly by the `/stats` page
and the hive-wide stats view, so pruning it would only lose trend
history for no space gain.
@ -409,7 +409,7 @@ Contents:
The root agent has the meta dir RO-mounted at `/meta/`.
There is no longer a `.meta-migration-done` marker: the
The `.meta-migration-done` marker no longer exists: the
one-shot container repoint it guarded has been removed, since
containers are rendered onto `meta#<n>` at creation. A stale
marker file left over from an older hive is inert and can be
@ -516,7 +516,7 @@ marker-guarded so the substantive moves only happen once per
container lifetime:
1. **`${homeDir}` exists with the right ownership** — covers the
very first boot before `useradd`'s `createHome` has had a
the first boot before `useradd`'s `createHome` has had a
chance to chown. Also re-applies on every rebuild in case the
meta-flake's per-agent name evolves (rare).
2. **Migrate any leftover `/root/.claude` content into

View file

@ -117,7 +117,7 @@ read on forge straight away (`PATCH /notifications/threads/{id}`). The
broker inbox is the durable work queue now — each delivered wake is a
sqlite row with its own ack lifecycle — so the forge unread flag no
longer needs to track whether the agent has _processed_ a
notification. Clearing it on delivery keeps forge's unread set **tiny
notification. Clearing it on delivery keeps forge's unread set **small
by construction**: at rest it holds only threads that failed to
deliver plus whatever arrived since the last 30s poll.
@ -140,7 +140,7 @@ wake already carries the notification body, so _delivery is the read_.
An agent that wants the full thread still runs `hive-forge comments` /
`view`.
**In-process dedupe (tiny, ephemeral).** A single-process map (thread
**In-process dedupe (small, ephemeral).** A single-process map (thread
id → last-delivered `updated_at`) guards the narrow window where a
mark-read call _transiently fails_ and the thread reappears unread in
the next poll before its `updated_at` bumps — so a flaky PATCH doesn't
@ -165,7 +165,7 @@ The poller starts disabled and stays that way for any of:
- Initial client construction fails (the typed `forgejo-api` client
for the API calls, or the plain reqwest client kept for the
best-effort enrichment fetches of server-provided subject URLs;
both extremely unlikely; treated as fatal-to-the-task only).
both unlikely; treated as fatal-to-the-task only).
Disabled = the spawned task returns immediately. All other failure
modes (HTTP errors, parse errors, mark-read failures) are
@ -260,7 +260,7 @@ path. But a merged/closed subject **keeps** its `latest_comment_url` set,
so a just-merged PR that had any prior discussion would route to the
comment path and render `[comment on PR]` (with a stale pre-merge comment
body) instead of `[PR merged]` — the agent never learns its PR merged.
So when the notification IS the merge/close transition — its
When the notification IS the merge/close transition — its
event time (`updated_at`) is within `NEW_ITEM_TOLERANCE_SECS` of the
subject's `closed_at` (set for both `merged` and `closed`) — the
state-change path wins even with a comment url present
@ -281,7 +281,7 @@ its `created_at` is strictly **after** the subject's `closed_at`
(`comment_is_after_close`) — that is it raced the merge rather than being the
pre-merge last comment the subject keeps — append it as a
`comment by <author>: <excerpt>` block before the meta suffix
(`fresh_post_close_comment_tail`). So the wake carries **both** `[PR merged]`
(`fresh_post_close_comment_tail`). The wake carries **both** `[PR merged]`
and the racing comment. The kept pre-merge comment (created before
`closed_at`) is left off, a self-authored racing comment is dropped (don't
echo the agent's own write), and a missing/unparseable `created_at`/
@ -295,7 +295,7 @@ A review submitted with **no body** carries no `latest_comment_url`,
so it misses the comment path and lands on the state-change path with
`state == "open"` — exactly like a freshly opened PR. Labeling that
`new PR` is misleading: agents dismiss it as a duplicate of the
original open notification and miss the review. So the `open`
original open notification and miss the review. The `open`
state only earns the `new <kind>` label when the notification's event
time (`updated_at`) is within `NEW_ITEM_TOLERANCE_SECS` (120s) of the
subject's `created_at`. Anything later is labeled `activity on <kind>`

View file

@ -13,8 +13,8 @@ injected into the agent's state dir out of band.
## Enabling
The integration is **on by default** for every agent (`hyperhive.github.enable
= true`), inert until a PAT is provisioned. There is nothing per-agent to
declare — an agent gains GitHub by having a PAT written to its token
= true`), inert until a PAT is provisioned. No per-agent declaration is
needed — an agent gains GitHub by having a PAT written to its token
file.
To turn it off for the whole hive, set the host option:
@ -35,7 +35,7 @@ github.com only. The token **value** never touches nix — it's written to
The PAT is operator-supplied. The primary path is the **dashboard
credentials tab** (github sub-tab): paste the PAT for an agent and submit
(`POST /api/github-account`). There is also a CLI path for
(`POST /api/github-account`). A CLI path also exists for
recovery/scripting:
```sh

View file

@ -45,7 +45,7 @@ hive-c0re maintains the local clone at
pull`, so agents see the new content on their next turn.
A hive that's offline when the event is sent doesn't get it on
reconnect — the periodic pull below is what closes that gap. So one
reconnect — the periodic pull below is what closes that gap. One
hive briefly showing older `/knowledge` content than another is
expected, and resolves by itself within the fallback interval.

View file

@ -19,7 +19,7 @@ Single nginx in front of every hyperhive web surface. Runs on the **host**, next
The authelia vhost is declared only by the host that **runs** authelia, not by every hive that uses it — a client hive knows the swarm's `authelia.url` but must not answer for a name it doesn't serve. Its server name is exactly `swarm.authelia.domain`: authelia validates `authelia_url ⊂ session cookie domain` at startup, so a near-miss is a container that refuses to boot. It carries no `auth_basic` — the login page must not sit behind the login mechanism it replaces — and sets the four `X-Forwarded-{Proto,Host,Uri,For}` headers, since authelia decides by the *original* request rather than the hop it sees.
⚠️ **A `502` from this vhost usually means authelia has no users yet, not that the proxy is misconfigured.** Authelia treats an empty user store as a fatal startup error, so an enabled-but-unbootstrapped swarm crash-loops the container while the vhost in front of it works perfectly. Check `journalctl -M swarm-authelia -u authelia-swarm` before suspecting anything here; the bootstrap step is in [`swarm/sso.md`](../swarm/sso.md).
⚠️ **A `502` from this vhost typically means authelia has no users yet, not that the proxy is misconfigured.** Authelia treats an empty user store as a fatal startup error, so an enabled-but-unbootstrapped swarm crash-loops the container while the vhost in front of it works perfectly. Check `journalctl -M swarm-authelia -u authelia-swarm` before suspecting anything here; the bootstrap step is in [`swarm/sso.md`](../swarm/sso.md).
Per-agent UIs stay sub-path, forge and matrix get sub-domains — see
[Sub-domain shape (rationale)](#sub-domain-shape-rationale) below for why.
@ -34,7 +34,7 @@ Operator points client at `<hive>`. Sequence:
matrix-dart-sdk (FluffyChat etc.) hardcodes `https` for the well-known fetch regardless of input scheme, so the discovery endpoint MUST be https — see "Self-signed TLS" below for the cert generation that backs the default-on path.
Federation peers fetch `.well-known/matrix/server``{"m.server":"chat.<swarm>:<httpsPort>"}` (the federation delegation always carries an explicit port, even the HTTPS default 443 — the https-implies-443 elision only applies to the client base_url above). Gateway only listens on configured `port` (+ `httpsPort` when TLS on); cross-hive federation needs either an SRV record (`_matrix._tcp.chat.<swarm>` → port 80 / 443) OR `matrix.openFirewall = true` so peers reach tuwunel's federation port directly. Hyperhive is mostly closed/internal, so this rarely bites.
Federation peers fetch `.well-known/matrix/server``{"m.server":"chat.<swarm>:<httpsPort>"}` (the federation delegation always carries an explicit port, even the HTTPS default 443 — the https-implies-443 elision only applies to the client base_url above). Gateway only listens on configured `port` (+ `httpsPort` when TLS on); cross-hive federation needs either an SRV record (`_matrix._tcp.chat.<swarm>` → port 80 / 443) OR `matrix.openFirewall = true` so peers reach tuwunel's federation port directly. Hyperhive is closed/internal in most deployments, so this rarely bites.
## SPA fallback (Accept-header pattern)
@ -252,7 +252,7 @@ or make the key world-readable (`0644`) if your threat model allows it. nginx er
### Fronting with an external TLS terminator
There is no http-only mode (see [TLS modes](#tls-modes) above). Two paths
No http-only mode exists (see [TLS modes](#tls-modes) above). Two paths
for an operator who wants their own TLS terminator:
- give the gateway the real cert via `tls.certDir` (or `tls.acme`) so it

View file

@ -12,7 +12,7 @@ whenever hyperhive is enabled. Configured via
## Network map
One picture of the whole hive. There are two planes: **infra
One picture of the whole hive two planes: **infra
containers share the host netns** and bind host ports directly;
**compute containers (agents + CI) each get a private netns** behind
the bridge. The unix-socket control plane rides the VFS and is
@ -129,14 +129,14 @@ own resolvers: dnsmasq runs on the host and reads the host's
upstream — they query the bridge IP and dnsmasq does the right thing
per-name.
There is deliberately no fallback `server=`: dnsmasq queries all known
No fallback `server=` exists, by design: dnsmasq queries all known
upstreams in parallel, so a hardcoded public resolver would take a share
of normal traffic, not just cover the gap.
dnsmasq runs on the host and reads the host's `/etc/resolv.conf`
directly, so a network change (new router, new lease, laptop moving
networks) reaches it the moment openresolv rewrites the file. There is
nothing to synchronise and no unit watching for it.
networks) reaches it the moment openresolv rewrites the file. Nothing
needs synchronising, and no unit watches for it.
`bind-interfaces` + `interface = [ bridgeName "lo" ]` means the
listener only accepts queries from the bridge interface (plus lo for
@ -228,7 +228,7 @@ creating or updating containers. Both variables are validated **once at
daemon startup**, not per container: they're process-global, so a
missing or malformed value is a misconfigured daemon rather than one bad
container, and failing at boot gives a single diagnostic instead of one
per agent. There is no non-isolated mode to fall back to. `LOCAL_ADDRESS` is left empty so the
per agent. No non-isolated mode exists to fall back to. `LOCAL_ADDRESS` is left empty so the
container's dhcpcd acquires an address from the bridge dnsmasq pool
(`networking.useDHCP = true` in `nix/agent-modules/network.nix`). This applies uniformly
to all containers — agents and service containers alike.

View file

@ -71,14 +71,14 @@ role this host performs_. A store host sets both --- one to run the
receiver, one only if it also runs a hive that pushes.
With it set, `hivectl agent <name> subvol snapshot push <label>
[--parent <label>]` streams a snapshot straight into the store. There is
no destination argument, because a swarm has exactly one store (see
[--parent <label>]` streams a snapshot straight into the store. No
destination argument exists, because a swarm has exactly one store (see
[One subvolume per agent, not per hive](#one-subvolume-per-agent-not-per-hive)),
and no credential argument, because the mesh is the authentication.
## The mesh is the authentication
There are no certificates here, and no key material of its own. That's
No certificates live here, and no key material of its own. That's
deliberate rather than an omission.
WireGuard's cryptokey routing already binds a peer's source address to
@ -106,7 +106,7 @@ the case the store exists to serve.
## What the sender can and can't choose
A `btrfs send` stream carries no notion of _which agent_ it belongs to,
and the subvolume name inside it's chosen by the sender. So the
and the subvolume name inside it's chosen by the sender. The
protocol is one `agent <name>` header line, then the raw stream.
The rule that matters:

View file

@ -53,7 +53,7 @@ peer's name when the caller knows it's hive-local.
## Identity = socket
There are no auth tokens on the per-agent unix sockets. The socket
No auth tokens exist on the per-agent unix sockets. The socket
*path* identifies the principal; perms come from "who has the
bind-mount." A sub-agent only sees its own `/run/hive/mcp.sock`;
hive-c0re owns the host admin socket.
@ -490,7 +490,7 @@ check derivations they don't:
- **`hivectl-docs`** regenerates `docs/tools/hivectl-cli.md` from
hivectl's clap tree and **fails if the committed copy is stale**.
So **after any change to a hivectl verb or flag, regenerate it**:
**After any change to a hivectl verb or flag, regenerate it**:
```sh
nix develop -c cargo run --bin hivectl -- markdown-docs > docs/tools/hivectl-cli.md
```

View file

@ -1,7 +1,7 @@
# Gotchas
NixOS + nspawn quirks and lessons we hit the hard way. If something
here looks unmotivated in the code, there's usually a story underneath.
here looks unmotivated in the code, there's probably a story underneath.
Grouped by area — jump to the section that matches what you're
touching.
@ -64,7 +64,7 @@ path = [ "/run/wrappers" "/run/current-system/sw" ];
```
The bug is silent: `nix eval` succeeds, the unit starts, but PATH
contains a non-existent directory. The first symptom is usually
contains a non-existent directory. The first symptom is typically
`sudo: must be owned by uid 0 and have the setuid bit set` because
the setuid sudo wrapper lives at `/run/wrappers/bin/sudo` and
the path entry resolves to `/run/wrappers/bin/bin` instead.
@ -144,7 +144,7 @@ store-path _string_ coerces through `lib.toDerivation`, i.e.
`builtins.storePath`. `environment.systemPackages` and
`systemd.services.<name>.path` both do it (the latter takes plain
strings like `/run/wrappers` happily, but anything under
`builtins.storeDir` is treated as a package). So a path handed to the
`builtins.storeDir` is treated as a package). A path handed to the
container as text has to be wrapped in a real derivation — a symlink
farm built from the interpolated string — before it can go anywhere a
package is expected.

View file

@ -85,7 +85,7 @@ Cheap — no build slot:
| `KnowledgePull` | one-shot boot-time `/knowledge` pull (`knowledge::pull`), reconciling commits that landed while `hive-c0re` was down. Same rationale as `MatrixSweep`: the periodic hourly re-pull stays a background loop |
| `WantedPull` | one-shot boot-time pull of the agent set the swarm controller declares for this hive (`wanted::pull`), converging the agents it names. No background loop behind this one — boot is the whole cadence; the deploy event (`swarm_status`) is the fast path, this repairs a missed one. Agentless |
There is deliberately **no `GitCommit` node**: `meta.rs` fuses each mutation
Deliberately, **no `GitCommit` node** exists: `meta.rs` fuses each mutation
with its commit under its internal `META_LOCK` mutex, so a standalone commit
node would open a dirty-working-tree window between nodes.
@ -217,7 +217,7 @@ resources are free. Resources:
`Reconcile`, `WriteDropin`, `Create`) or at a **brace** (`AgentWindow`,
`DeployWindow`) on behalf of a whole coordinated subtree; held by the owning
DAG until it's terminal, so two DAGs never interleave container ops on the
same agent. A DAG touching several agents holds one lease per agent.
same agent. A DAG touching multiple agents holds one lease per agent.
(`SetWanted` is a store write, not a container op, but takes the lease anyway
so a power-op DAG's intent write + reconcile is atomic — two racing ops can't
clobber intent before either reconciles.) **Lease-exempt**: `MetaSync`,
@ -333,7 +333,7 @@ string) and free-form `data` for what only some kinds have (`agent`,
`approval_id`, `inputs`, `build_log_id`). Group roots ride as ordinary
nodes, so a group's state is just the root's own `state`.
There is **no group-level `agent`** — agent is per-node, so one group can
No group-level `agent` exists — agent is per-node, so one group can
span agents; consumers derive a group's agents from its nodes. Build logs
are likewise **per-node**: the dashboard renders the node tree and keys the
live-log panel off the running node.
@ -497,7 +497,7 @@ limits, build parallelism, and first-spawn latency.
`buildSlots` (default `1`) sets how many nix-heavy job-queue nodes
(prebuilds, profile swaps, first-spawn creates, meta lock bumps) run
concurrently. The default serializes all heavy nix work like the pre-DAG
rebuild queue did; raise it on hosts with the cores/RAM to build several
rebuild queue did; raise it on hosts with the cores/RAM to build multiple
agent toplevels at once. Per-agent correctness is independent of the count —
each agent's container-affecting ops serialize on its lifecycle lease
regardless.
@ -513,7 +513,7 @@ requiring a host rebuild.
The same drop-in carries `CPUWeight=` / `IOWeight=` from
`agentCpuWeight` / `agentIoWeight`. Those are a different kind of
setting: the quota and the memory max are **hard caps** that throttle
an agent even on a completely idle host, while the weights are cgroup
an agent even on an idle host, while the weights are cgroup
v2 **relative shares** that only decide who yields _under contention_.
A low-weight container still gets the whole machine when nothing else
wants it.
@ -560,7 +560,7 @@ trade-off:
binary are added to the host system closure (low single-digit GB
additional).
- **Off** (default): the host closure stays lean; the first spawn
does all the eval + fetch work at runtime (can take several
does all the eval + fetch work at runtime (can take a few
minutes on a fresh store).
**Note**: toplevels are pinned to `x86_64-linux`. Enabling on an

View file

@ -5,7 +5,7 @@ token usage, cost, tool call counts — to any OTLP-compatible collector via
Claude Code's built-in OpenTelemetry integration.
This is a **hive-wide** setting: one switch in the host NixOS config enables it
for every agent container simultaneously. There is no per-agent opt-in or opt-out.
for every agent container simultaneously. No per-agent opt-in or opt-out exists.
## Enabling export
@ -22,7 +22,7 @@ leaves the swarm — optional, because the swarm's own metrics store
telemetry goes to both. See
[`swarm/services.md`](../swarm/services.md#metrics-victoriametrics--grafana).
**There is exactly one way telemetry leaves a hive: through the collector that
**Telemetry leaves a hive exactly one way: through the collector that
`enable` starts on the host.** Agents never talk to `endpoint` themselves —
they export unauthenticated to a bridge address only their own containers can
reach. That collector forwards to the swarm's
@ -45,7 +45,7 @@ no longer survives independently of anything host-side.
**It has no application-level auth.** The receiver takes any OTLP that reaches
it; what bounds who can reach it's the firewall — `exposeHostPorts` opens the
port on the bridge interface only. So "unauthenticated to a bridge address"
port on the bridge interface only "unauthenticated to a bridge address"
means _reachable from an agent container_, not _presents a credential_.
The consequence, stated because it's a choice rather than an oversight: **any
@ -76,7 +76,7 @@ is getting a credential:
`hive=` from the connection it authenticated, so _that_ label becomes
unforgeable.
So a verified `hive` is reachable and a verified `agent` isn't — and that falls
A verified `hive` is reachable and a verified `agent` isn't — and that falls
out of the topology rather than being a gap someone forgot to close. The swarm
runs one collector, and the mechanism gives it no finer grain: a bearer-token
check never reveals _which_ token matched, and a receiver reads request metadata
@ -150,7 +150,7 @@ somehow — copy it across and name it:
services.hyperhive.otel.clientSecretFile = "/run/secrets/hive-telemetry.secret";
```
**There is no unauthenticated mode.** A hive always presents an identity, so a
**No unauthenticated mode exists.** A hive always presents an identity, so a
missing credential is a build error rather than a quieter fallback — the
collector has no anonymous route to accept samples on, and every path it serves
belongs to exactly one hive.

View file

@ -49,7 +49,7 @@ both once.**
`domain` is required too, but you no longer _write_ it: it's read from
this hive's own entry in the directory, whose `domain` defaults to
`<name>.<swarm.domain>`. So a conventional swarm states no addresses at
`<name>.<swarm.domain>`. A conventional swarm states no addresses at
all, and a hive addressed by something else states it in the one place
the other hives read — `swarm.hives.edge.domain = "edge.elsewhere.example";`.
@ -134,7 +134,7 @@ contain you derives every hive as a peer and you peer with yourself.
conventionally `<name>.<swarm.domain>`, but a wrong domain that
evaluates cleanly points at a real machine that isn't the one you meant.
> **There is no per-hive CA field, and no per-hive cert pinning.** Trust
> **No per-hive CA field exists, and no per-hive cert pinning.** Trust
> inside a swarm comes from the swarm root ([`ca.md`](ca.md)): every
> hive chains to it, so one anchor replaces per-hive pinning entirely.
> What that genuinely drops is trusting a hive whose root this swarm

View file

@ -48,7 +48,7 @@ swarm-services leaf can, and this host signs that one only under
`autoConfigure`. A hive with neither serves the hive leaf on those names
and every client sees a name mismatch.
So `hive-tls` emits an eval-time **warning** naming the uncovered names.
`hive-tls` emits an eval-time **warning** naming the uncovered names.
It's deliberately not an assertion: this module can see what _it_ is
able to issue, but not an operator-installed sub-CA in
`swarm.ca.stateDir`, an external ACME setup, or a certificate delivered

View file

@ -17,7 +17,7 @@ The rule is about what must stay secret, not about credentials generally.
`deploy.nats.calloutUserPublicKey`, is published to every client that connects,
so the store is a perfectly good place for it.
**The generator and the reader are usually in different containers.** They share
**The generator and the reader typically live in different containers.** They share
the host's network namespace, which makes them feel co-located, but their
filesystem roots are separate. That's why delivery is a **host-side copy rather
than a bind mount**: `nixos-container` refuses to start when a bind source is
@ -30,7 +30,7 @@ container that starts after it.
Every row below is read against one of these. This is a different cut
from the [deployment shapes](services.md#deployment-shapes) --- those
say _where services run_, these say _who is responsible for a secret
file being there_ --- so the two lists don't line up one-to-one, and
file being there_ --- the two lists don't line up one-to-one, and
neither is a renaming of the other.
| topology | what it means | who places secrets |
@ -63,7 +63,7 @@ because nothing outside that container ever reads them. **That's the test worth
applying to any secret added here** — and the client secret's plaintext half is
the one row that fails it, which is the entire reason a delivery step exists.
There are two telemetry collectors and they land on opposite sides of that test.
Two telemetry collectors exist, and they land on opposite sides of that test.
The **hive's** collector needs no delivery step. It authenticates to the swarm's
collector as its own hive, and it's a host unit rather than a container, so on
@ -136,7 +136,7 @@ when the system builds. The server names the offending file and refuses to run.
Both delivery units wait for authelia's first boot to mint the secret — a
bounded wait, 120s — and then **fail loudly** rather than skipping. A silent skip
produces a service whose login button always fails, which is a symptom several
produces a service whose login button always fails, which is a symptom many
layers from its cause.
The store's **first reader** is the matrix registration token, and it's worth
@ -170,7 +170,7 @@ everything else a hive needs does.
A hive CA carries `nameConstraints=permitted;DNS:<hive domain>`, and **a swarm
service name is a sibling of the hive domain rather than a child** — `forge.<swarm>`
next to `<hive>.<swarm>`. So a hive CA can't issue a certificate for a swarm
next to `<hive>.<swarm>`. A hive CA can't issue a certificate for a swarm
service. Not by policy: by construction, and openssl enforces it.
Whatever holds the swarm root is therefore what makes swarm-service certificates

View file

@ -143,11 +143,11 @@ Drilldown_ app is deliberately not installed: it only supports Loki, and
no setting here changes that, so Explore is the log browser for this
swarm.
| Option | When you'd touch it |
| ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `deploy.victorialogs.retentionPeriod` | Default `30d`, far shorter than the metrics store's — logs are bulkier per unit of value and are usually read within days of being written. Raise it if you need to answer questions about last quarter. |
| `swarm.victorialogs.domain` | Only to rename it. |
| `swarm.victorialogs.port` | Only if something else on the services host already claims `9428`. |
| Option | When you'd touch it |
| ------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `deploy.victorialogs.retentionPeriod` | Default `30d`, far shorter than the metrics store's — logs are bulkier per unit of value and are typically read within days of being written. Raise it if you need to answer questions about last quarter. |
| `swarm.victorialogs.domain` | Only to rename it. |
| `swarm.victorialogs.port` | Only if something else on the services host already claims `9428`. |
Like the metrics store, it binds loopback and takes no credential of its
own: the gateway vhost is the only way in, and the collector is the only
@ -169,7 +169,7 @@ port is a coin toss at runtime rather than an error at build time.
Every hive's own collector reaches this one by its gateway name,
`swarm.otel.domain` (default `otel.<swarm domain>`) — the same
by-domain-through-the-gateway shape every other swarm service uses, not a
loopback URL an operator has to redirect. There is nothing to set on a hive
loopback URL an operator has to redirect. Nothing needs setting on a hive
that doesn't run the swarm's services; the name resolves through the
gateway either way.

View file

@ -33,7 +33,7 @@ enabled but not bootstrapped shows a **crash-looping unit** and `502 Bad
Gateway` from the vhost — not a login page with nobody able to use it.
The gateway is working in that state; the upstream isn't up.
⚠️ So the step below is **required to finish the install**, not an
⚠️ The step below is **required to finish the install**, not an
optional first-login convenience. Run it before concluding anything is
wrong with the proxy: a 502 here means "no users yet" far more often
than it means a routing fault.
@ -67,7 +67,7 @@ operator typing one command once.
`user add` only ever adds: on a name that already exists it refuses,
rather than resurfacing as a second account or a silent overwrite.
Editing an existing subject is `user update`, and the flags compose, so
one call can change several things:
one call can change multiple things:
```console
# swarmctl user update mara --add-group admins --email mara@example.com
@ -99,7 +99,7 @@ Every secret in the swarm, with its generator and its path, is tabulated
in one place: [`secrets.md`](secrets.md), including authelia's own keys
(session, JWT, storage-encryption, OIDC HMAC, OIDC issuer) and the two
halves of each client secret. That page's two rules — a secret is always
a path, never a value, and the generator and the reader are usually in
a path, never a value, and the generator and the reader typically live in
different containers — are why the client secret's plaintext half needs
the delivery step below and the rest of authelia's keys don't.
@ -124,7 +124,7 @@ nixos-container refuses to start a container whose bind source is
missing, and this secret doesn't exist until authelia's first boot has
run. Binding it would make the service wait on a file that waits on a
container that starts after it — on a fresh hive, a permanent stall
presenting as "the forge is broken," several layers from its cause.
presenting as "the forge is broken," many layers from its cause.
### 2. Swarm-managed services
@ -149,7 +149,7 @@ services.hyperhive.swarm = {
**Both are asserted at eval.** A hive that boots with SSO
half-configured shows a login button that always fails — a symptom
several layers from its cause, and far worse to diagnose than an
many layers from its cause, and far worse to diagnose than an
evaluation error.
## Where each relying party differs
@ -218,7 +218,7 @@ HTML instead of the failure that actually happened:
- `/api/oidc/token`, `/.well-known/openid-configuration` — a client
parsing an error page as its JSON document
So authelia's `/api/` and `/.well-known/` prefixes are routed **without**
Authelia's `/api/` and `/.well-known/` prefixes are routed **without**
error interception. The split is by _audience_, not by an enumerated path
list: a human gets the page, every machine caller gets the status.
Enumerating endpoints individually would leave the next one added

View file

@ -84,8 +84,8 @@ Exposed as `mcp__bash__kill`.
## Namespace note
`run` and `status` live in the `bash` MCP server, not `hyperhive`. So
the tool names in claude are `mcp__bash__run` and `mcp__bash__status`.
`run` and `status` live in the `bash` MCP server, not `hyperhive`. The
tool names in claude are `mcp__bash__run` and `mcp__bash__status`.
The `Bash` built-in tool is blocked — all shell execution goes through
this structured path so tasks get task-id tracking and structured output.

View file

@ -1003,7 +1003,7 @@ Add a collaborator to the active repo (`-r`/`HIVE_FORGE_REPO`) with a permission
###### **Options:**
* `--permission <PERMISSION>` — Permission level to grant (default: write — a freshly added collaborator usually needs to push)
* `--permission <PERMISSION>` — Permission level to grant (default: write — a freshly added collaborator typically needs to push)
Default value: `write`

View file

@ -134,7 +134,7 @@ hive-forge repo-labels --json # full label ob
### Which read verb when
Several verbs read an issue/PR and look overlapping, but each has a
Multiple verbs read an issue/PR and look overlapping, but each has a
distinct output shape — pick by what you need:
| Need | Verb | Shape |

View file

@ -643,7 +643,7 @@ Stream a snapshot to the swarm's snapshot store over the WireGuard mesh — the
Nothing is staged locally: `btrfs send` writes straight into the connection, so a multi-gigabyte agent needs no scratch space on this host. The mesh is the authentication (cryptokey routing binds the sender's address to its key), so there is no credential to pass here.
There is no destination argument: a swarm has one store, read from `services.hyperhive.swarm.snapshotStore`.
No destination argument exists: a swarm has one store, read from `services.hyperhive.swarm.snapshotStore`.
**Usage:** `hivectl agent subvol snapshot push [OPTIONS] <LABEL>`

View file

@ -308,7 +308,7 @@ The URL is resolved from the running daemon (`HostRequest::Urls`), which
reads the per-surface public URLs from c0re's service env — so custom
forge / matrix domains resolve correctly instead of assuming
`forge.<domain>`. The URL is **always printed** (the reliable core, since
the host is usually headless / driven over SSH), then `xdg-open` is tried
the host is typically headless / driven over SSH), then `xdg-open` is tried
as a convenience — a missing or failing opener is reported as a note, not
an error.

View file

@ -67,7 +67,7 @@ Add a user, generating a password for them
Change an existing user's attributes.
Every flag is optional and they compose, so one call can set several things at once. Deliberately does **not** touch the password: regenerating a credential is a different intent from editing an attribute, and folded together an attribute edit can invalidate a login by accident.
Every flag is optional and they compose, so one call can set multiple things at once. Deliberately does **not** touch the password: regenerating a credential is a different intent from editing an attribute, and folded together an attribute edit can invalidate a login by accident.
**Usage:** `swarmctl user update [OPTIONS] <USERNAME>`

View file

@ -87,7 +87,7 @@ systemd unit. The unit binds `/run/hive/priv.sock` with
to the helper as fd 3 (`LISTEN_FDS`). The helper requires this and
bails if it isn't socket-activated.
⚠️ There is intentionally no self-bind fallback: if `hive-priv` bound
⚠️ Intentionally, no self-bind fallback exists: if `hive-priv` bound
the socket itself, it would create the file owned by root's primary
group rather than `hive-core`, and a `hive-core` client couldn't
connect the way the socket unit's `SocketGroup` grant intends.

View file

@ -34,7 +34,7 @@ Each agent gets its own scoped credentials, never shared:
collaborator grants; can't act as another agent or as `core`).
- **matrix token** → that agent's matrix account only.
So a compromised/confused agent's reach on the forge or matrix is bounded by
A compromised/confused agent's reach on the forge or matrix is bounded by
_its own_ account's scope, not the swarm's. This is the main thing standing
between "one agent does something dumb" and "the whole hive is affected."

View file

@ -141,8 +141,8 @@ window from `HIVE_TURN_IDLE_SECS` (`0` disables) and maps the driver's
After the outcome handler, the stats sink records a row. `handle_turn`
reports the result to `serve_loop` via `TurnControl { auth_failed }`
on auth failure the loop parks in `wait_for_login`; otherwise it loops
straight back to the idle wait (step 1). There is no same-turn
self-continue mechanism: every multi-step continuation rides an
straight back to the idle wait (step 1). No same-turn
self-continue mechanism exists: every multi-step continuation rides an
external wake instead — a new inbox message, a `remind`, or an
in-container todo wake (bash-task completion, forge notification,
matrix activity). Ending the turn and letting one of

View file

@ -25,7 +25,7 @@ to the bus.
**Which `claude` binary.** The bare name `claude`, resolved off the
harness unit's PATH. By default that's the `claude-code` in the agent's
own nixpkgs (the meta flake's `nixpkgs` input) via
`environment.systemPackages`. Since that's usually a release channel and
`environment.systemPackages`. Since that's typically a release channel and
this package moves fast, the operator can pin one hive-wide with
`services.hyperhive.c0re.claudeCodePackage`: its store path is written
into each agent's flake, and `claude` on PATH becomes a symlink to it
@ -117,7 +117,7 @@ into its `run`:
_still_ overflows, `run` surfaces `Error::PromptTooLong`; `drive_turn`
then archives the session (session lifecycle stays hive-side) and the
serve loop requeues the message so it redelivers into a fresh session
(see [Turn outcomes](README.md#turn-outcomes) — the wake prompt itself is tiny, so
(see [Turn outcomes](README.md#turn-outcomes) — the wake prompt itself is small, so
the overflow was the accumulated context the archive clears).
- **Proactive** — a turn finishes cleanly but the last inference's context
size crossed the policy watermark. While the session is still healthy it
@ -185,7 +185,7 @@ still applies.
exceeds the assumed prompt-cache TTL (`HIVE_CACHE_TTL_SECS`, default
`3600`). Claude's prompt cache goes cold after a while; once it's cold,
`--resume`-ing a large session pays the full re-upload cost with no
benefit over starting fresh. So `drive_turn` **archives** the current
benefit over starting fresh. `drive_turn` **archives** the current
session (same mechanism as the operator reset — rename `<uuid>.jsonl`
`.archived`) so the next turn's `--resume <title>` misses and starts
fresh. Unlike proactive compaction the session is dropped entirely, not

View file

@ -46,7 +46,7 @@ preset (`AGENT_DEFAULT`) includes `messaging`, `meta`, `inbox`, and
The graceful-stop turn takes the same post-turn compaction path as any
other turn: if the context crossed the watermark the harness runs a
notes-checkpoint turn and then `/compact`. Compacting before shutdown
keeps a later cold start cheap instead of re-uploading a huge transcript.
keeps a later cold start cheap instead of re-uploading a large transcript.
- `ack_until(up_to)` — bulk-mark inbox rows handled: every row with
broker id `<= up_to` is stamped as acked in a single UPDATE.
Recipient-scoped (agents can only ack their own rows). Use when a
@ -93,7 +93,7 @@ at_unix_timestamp?)`.
payloads spill to `/agents/<self>/state/reminders/`. Pending count
capped at 50 per agent (`HIVE_REMIND_MAX_PENDING_PER_AGENT`).
There is no same-turn self-continue tool — see
No same-turn self-continue tool exists — see
[Turn outcomes](README.md#turn-outcomes) for why. Multi-step work rides
`remind` for a durable self-wake, or an in-container todo wake
(bash-task completion, forge notification, matrix activity) for work

View file

@ -46,7 +46,7 @@ structurally rather than for one specific trigger. Two columns:
- **Pills cluster** (`.agent-header-pills`, right-aligned): status
badges (`StatusChips.tsx`) and flyout triggers together in one row —
a deliberate choice (not the historical default) so the header reads
as one identity zone + one status/actions zone rather than several
as one identity zone + one status/actions zone rather than multiple
separate clusters:
- **Alive badge**: `● alive` (green) / `⊘ rate limited` (red) /
`◌ needs login` / `◌ logging in` / `○ offline` / `… connecting`.
@ -83,7 +83,7 @@ structurally rather than for one specific trigger. Two columns:
(`GET /api/state`'s `links` field) also feeds
`DashboardState.links` for the dashboard card's icon strip —
`agent_links()` in hive-agent is the single source of truth for
both. There is no separate overflow (`⋯`) menu any more — it used
both. No separate overflow (`⋯`) menu exists any more — it used
to hold exactly this dashboard link plus a rebuild-container action
(mara, hyperhive#3704: "remove rebuild button, move link to
dashboards into links menu") — rebuild had no real discoverability

View file

@ -141,7 +141,7 @@ dirs; `POST /api/purge-tombstone/{name}`).
⚠️ **Not only *destroyed* agents.** Nothing records a destroy: every
definition-side artifact (state subvolume, proposed + applied repos,
meta registration, topology entry) is written by `Provision` *before*
the container exists and survives `lifecycle::destroy`. So an agent
the container exists and survives `lifecycle::destroy`. An agent
part-way through a spawn is byte-identical on disk to a tombstone, and
both are listed. The pane carries a standing warning to that effect, and
the row badge says `offline` rather than `destroyed` — the absence of a
@ -474,8 +474,8 @@ Both tables share the same visual shape: `.cap-table-wrap` /
and its checkbox cells, where each checkbox carries `data-baseline` (its
render-time state) and the row carries `data-agent` for dirty-tracking.
**Saving — one button for the whole page.** There are no per-row save
buttons. A single page-level `.perm-save-bar` with a **save all (N
**Saving — one button for the whole page.** No per-row save
buttons exist. A single page-level `.perm-save-bar` with a **save all (N
agents)** button sits at the bottom of the pane, enabled only when some
checkbox diverges from its baseline. Clicking it diffs every checkbox
across *both* matrices and POSTs one batch to `POST /api/permissions` as
@ -826,7 +826,7 @@ limited` (red, while the harness is parked after a 429), `needs
login`, `needs update` — plus **one `◐ pending-state…` pill per
active transient** (replaces buttons during operator-initiated
start / stop / restart / rebuild / destroy). An agent can carry
**several transients at once** — for example a lease-exempt `prebuild`
**multiple transients at once** — for example a lease-exempt `prebuild`
running alongside a `stop_for_update` on the same agent — and each
renders as its own independent badge rather than being collapsed
into one label, matching the existing multi-badge convention this
@ -1337,8 +1337,8 @@ payload):
every queue mutation (enqueue, state transition, dedup collapse,
terminal-history trim). Unlike the snapshot events below it ships
no state at all: the client re-fetches `GET /api/jobq/graph`, which
is where it reads the queue from cold too. There is no
`/api/state.rebuild_queue` — it went with the typed projection.
is where it reads the queue from cold too. No
`/api/state.rebuild_queue` exists — it went with the typed projection.
- `schedules_changed` (seq, schedules: `Vec<WireSchedule>`) —
full snapshot of all scheduled prompts. Emitted after every
operator mutation via the `/api/schedules` surface (new /

View file

@ -16,7 +16,7 @@ repeated here.
- **Frosted / tinted glass** where it fits — translucency over flat
panels, not a hard rule for every surface.
- **Playful use of the multiple accent colours**, without tipping into
visual noise — the base16 palette has several accent slots
visual noise — the base16 palette has multiple accent slots
(`--purple`, `--cyan`, `--pink`, …); use more than one where it adds
distinction, not decoration for its own sake.
- **Whimsy** — small, delightful touches are welcome (the per-hive
@ -110,7 +110,7 @@ UI later. Concretely: full untruncated `detail`, a copy button, no
"something went wrong, try reloading."
This follows from **power-user-first**, one of the standing principles:
swarm-ui instances are mostly self-hosted and operators are techies, so
swarm-ui instances are typically self-hosted and operators are techies, so
errors should give them what they need to actually diagnose a problem
rather than a friendly wall. Power-user-first doesn't mean
newcomer-hostile — the UI should still be self-explanatory, warn or ask

View file

@ -51,7 +51,7 @@ pub struct Args {
/// Collaborator's forge login to add.
user: String,
/// Permission level to grant (default: write — a freshly added
/// collaborator usually needs to push).
/// collaborator typically needs to push).
#[arg(long, value_enum, default_value_t = Permission::Write)]
permission: Permission,
}

View file

@ -668,7 +668,7 @@ pub enum SnapshotCmd {
/// binds the sender's address to its key), so there is no credential
/// to pass here.
///
/// There is no destination argument: a swarm has one store, read
/// No destination argument exists: a swarm has one store, read
/// from `services.hyperhive.swarm.snapshotStore`.
Push {
/// Snapshot label passed to `subvol snapshot create --label`.

View file

@ -146,7 +146,7 @@ enum UserVerb {
/// Change an existing user's attributes.
///
/// Every flag is optional and they compose, so one call can set
/// several things at once. Deliberately does **not** touch the
/// multiple things at once. Deliberately does **not** touch the
/// password: regenerating a credential is a different intent from
/// editing an attribute, and folded together an attribute edit can
/// invalidate a login by accident.