Watch
0
0
Fork
You've already forked hyperhive
0

docs: rephrase the vale warnings that have a better wording

Words the alex and Microsoft styles flag where a plainer one reads as well:
disabled/special/invalid as a state, primitive, trap, hang, several, click,
first-person FAQ questions, hyphenated adverb pairs. Left as they are: Let's
Encrypt, quoted UI labels and phrases, the generated forge CLI reference, and
the middle-click gesture.
This commit is contained in:
iris 2026-10-26 18:11:03 +01:00 • committed by mara
commit 5db38a596e
29 changed files with 62 additions and 62 deletions

View file

@ -13,7 +13,7 @@ declarations.
- **Bringing a fresh swarm online?** → [`getting-started/setup.md`](getting-started/setup.md) - **Bringing a fresh swarm online?** → [`getting-started/setup.md`](getting-started/setup.md)
(the hand steps after the first switch). (the hand steps after the first switch).
- **What does the dashboard look like, and how do I use it?** → - **What does the dashboard look like, and how to use it?** →
[`web-ui/`](web-ui/README.md) — the operator-facing starting point; [`web-ui/`](web-ui/README.md) — the operator-facing starting point;
its own sub-pages ([`shape`](web-ui/shape.md), its own sub-pages ([`shape`](web-ui/shape.md),
[`dashboard`](web-ui/dashboard.md), [`agent`](web-ui/agent.md), [`dashboard`](web-ui/dashboard.md), [`agent`](web-ui/agent.md),
@ -57,9 +57,9 @@ declarations.
- **How do I give an agent a GitHub account (`gh` + `git push`)? how's - **How do I give an agent a GitHub account (`gh` + `git push`)? how's
the PAT injected?** → [`integrations/github.md`](integrations/github.md) the PAT injected?** → [`integrations/github.md`](integrations/github.md)
(operator content up top; the `gh`/git-push + notification-poller (operator content up top; the `gh`/git-push + notification-poller
mechanics are in a collapsed "Implementation" section at the bottom). mechanics are in a collapsed section on how it is implemented at the bottom).
- **What's `/knowledge`? How does the swarm-wide knowledge repo sync, - **What's `/knowledge`? How does the swarm-wide knowledge repo sync,
and how do I contribute a document?** → [`integrations/knowledge.md`](integrations/knowledge.md). and how to contribute a document?** → [`integrations/knowledge.md`](integrations/knowledge.md).
- **What does `hivectl` do? Provisioning, gateway users, container - **What does `hivectl` do? Provisioning, gateway users, container
shells?** → [`tools/hivectl.md`](tools/hivectl.md) (the curated guide); shells?** → [`tools/hivectl.md`](tools/hivectl.md) (the curated guide);
[`tools/hivectl-cli.md`](tools/hivectl-cli.md) for the exhaustive, [`tools/hivectl-cli.md`](tools/hivectl-cli.md) for the exhaustive,
@ -71,7 +71,7 @@ declarations.
discovery work?** → [`networking/gateway.md`](networking/gateway.md). discovery work?** → [`networking/gateway.md`](networking/gateway.md).
- **How does DNS resolution work in agent containers? What's the - **How does DNS resolution work in agent containers? What's the
bridge network for?** → [`networking/network.md`](networking/network.md). bridge network for?** → [`networking/network.md`](networking/network.md).
- **How do I connect two hives into a swarm?** → [`swarm/`](swarm/README.md) - **How are two hives connected into a swarm?** → [`swarm/`](swarm/README.md)
(peer hives, TLS trust). (peer hives, TLS trust).
- **Where do agent snapshots go? How does the swarm's `btrfs receive` - **Where do agent snapshots go? How does the swarm's `btrfs receive`
endpoint authenticate a pushing hive?** → endpoint authenticate a pushing hive?** →
@ -86,18 +86,18 @@ declarations.
sealed?** → [`swarm/bao.md`](swarm/bao.md). sealed?** → [`swarm/bao.md`](swarm/bao.md).
- **How does an agent get a prompt at set times, and why doesn't it fire - **How does an agent get a prompt at set times, and why doesn't it fire
twice after downtime?** → [`swarm/schedules.md`](swarm/schedules.md). twice after downtime?** → [`swarm/schedules.md`](swarm/schedules.md).
- **How do I set an agent's model or effort from the swarm, and what happens - **How to set an agent's model or effort from the swarm, and what happens
to its own pick?** → [`swarm/inference.md`](swarm/inference.md). to its own pick?** → [`swarm/inference.md`](swarm/inference.md).
- **How will a PR get checked against its repo's own yes/no rules before - **How will a PR get checked against its repo's own yes/no rules before
a full review?** → [`swarm/review-classifier.md`](swarm/review-classifier.md) a full review?** → [`swarm/review-classifier.md`](swarm/review-classifier.md)
(planned, not implemented yet). (planned, not implemented yet).
- **Does an agent config PR build before it's merged?** → - **Does an agent config PR build before it's merged?** →
[`swarm/builder.md`](swarm/builder.md). [`swarm/builder.md`](swarm/builder.md).
- **How do I create an agent from a ready-made set of permissions and - **How to create an agent from a ready-made set of permissions and
settings?** → [`swarm/archetypes.md`](swarm/archetypes.md). settings?** → [`swarm/archetypes.md`](swarm/archetypes.md).
- **How long do CI runs take, per repo and PR, and where does the board get - **How long do CI runs take, per repo and PR, and where does the board get
it?** → [`swarm/ci-metrics.md`](swarm/ci-metrics.md). it?** → [`swarm/ci-metrics.md`](swarm/ci-metrics.md).
- **How do I log an agent in to claude from the swarm UI, and why doesn't a - **How to log an agent in to claude from the swarm UI, and why doesn't a
stale stored copy log it out again?** → stale stored copy log it out again?** →
[`swarm/claude-login.md`](swarm/claude-login.md). [`swarm/claude-login.md`](swarm/claude-login.md).
- **How does an agent get a todo when someone assigns it an issue, and lose it - **How does an agent get a todo when someone assigns it an issue, and lose it
@ -111,7 +111,7 @@ declarations.
queue sources, scheduler internals?** → [`scheduler/coordinator.md`](scheduler/coordinator.md). queue sources, scheduler internals?** → [`scheduler/coordinator.md`](scheduler/coordinator.md).
- **How does the CI runner work? How does it register?** → - **How does the CI runner work? How does it register?** →
[`scheduler/ci.md`](scheduler/ci.md). [`scheduler/ci.md`](scheduler/ci.md).
- **How do I export Claude Code metrics (tokens, cost, tool calls) to - **How are Claude Code metrics (tokens, cost, tool calls) exported to
Prometheus/Grafana?** → [`scheduler/observability.md`](scheduler/observability.md). Prometheus/Grafana?** → [`scheduler/observability.md`](scheduler/observability.md).
## Crate reference ## Crate reference

View file

@ -66,7 +66,7 @@ system-level facts live.
degrades safely to "no agents" (covers a fresh install that hasn't degrades safely to "no agents" (covers a fresh install that hasn't
synced yet). synced yet).
- **Reconcile** — runs alongside the periodic meta/flake regeneration. - **Reconcile** — runs alongside the periodic meta/flake regeneration.
Adds newly-spawned agents, drops removed ones. Reconcile keeps agents Adds newly spawned agents, drops removed ones. Reconcile keeps agents
whose config repo exists but that haven't spawned yet, so the gap until whose config repo exists but that haven't spawned yet, so the gap until
the container appears doesn't churn the file. the container appears doesn't churn the file.
@ -118,7 +118,7 @@ state dirs work as for every other agent.
permissions alone, the same mechanism for every agent permissions alone, the same mechanism for every agent
([`docs/turn-loop/mcp.md`](../turn-loop/mcp.md)). Ruth's permissions ([`docs/turn-loop/mcp.md`](../turn-loop/mcp.md)). Ruth's permissions
are the default plus `manage_root_agent`, stored for her like any grant. are the default plus `manage_root_agent`, stored for her like any grant.
- **State dirs** — _not_ special-cased: `HYPERHIVE_STATE_DIR` is - **State dirs** — _not_ treated as an exception: `HYPERHIVE_STATE_DIR` is
injected uniformly via `systemd.globalEnvironment` for every injected uniformly via `systemd.globalEnvironment` for every
container including the manager, so all token/state paths resolve container including the manager, so all token/state paths resolve
through it the same way everywhere. through it the same way everywhere.

View file

@ -68,7 +68,7 @@ target hive to deploy it.
An agent created from an [archetype](../swarm/archetypes.md) gets no An agent created from an [archetype](../swarm/archetypes.md) gets no
config repo: its hive builds it from the archetype's module. config repo: its hive builds it from the archetype's module.
Changing what the template seeded isn't a special case: like every Changing what the template seeded isn't an exception: like every
later change, it's a PR on that config repo, made from a clone and later change, it's a PR on that config repo, made from a clone and
merged by an operator on the forge (see [Config changes](#config-changes)). merged by an operator on the forge (see [Config changes](#config-changes)).
@ -376,7 +376,7 @@ Differences from sub-agents:
authoritative applied repo (see "Root-agent view of applied" below). authoritative applied repo (see "Root-agent view of applied" below).
- Its socket comes from the generic per-agent registration, in the same - Its socket comes from the generic per-agent registration, in the same
per-agent runtime dir as any other agent (`/run/hyperhive/agents/ruth/`), per-agent runtime dir as any other agent (`/run/hyperhive/agents/ruth/`),
not a special manager-only path. not a separate manager-only path.
**Migration note** (for older hosts): drop any `containers.root = **Migration note** (for older hosts): drop any `containers.root =
{ ... }` block from your host NixOS config, and any `services.hyperhive.ruthless` { ... }` block from your host NixOS config, and any `services.hyperhive.ruthless`
@ -406,7 +406,7 @@ and nothing in the tree constructs it; this removal dropped the push path
genuinely needs to interrupt an agent's turn loop, not because genuinely needs to interrupt an agent's turn loop, not because
anything live uses it today. anything live uses it today.
**A previously-running container crashing** used to be exactly that **A previously running container crashing** used to be exactly that
case (`ContainerCrash`, routed to the root agent via case (`ContainerCrash`, routed to the root agent via
`Coordinator::notify_manager`) — it no longer drives any agent turn at `Coordinator::notify_manager`) — it no longer drives any agent turn at
all. `crash_watch` now only `tracing::warn!`s `"container crash all. `crash_watch` now only `tracing::warn!`s `"container crash

View file

@ -4,7 +4,7 @@ Where state lives, what survives what, and how it's bounded.
## For operators ## For operators
The short answer to "will I lose anything": **destroying an agent The short answer to "does anything disappear": **destroying an agent
keeps its state, purging it doesn't.** keeps its state, purging it doesn't.**
- **`DESTR0Y`** (the default action) stops and removes the container - **`DESTR0Y`** (the default action) stops and removes the container

View file

@ -57,7 +57,7 @@ What the granter is, why it's root-equivalent, and the store's TLS:
## 2 · Your SSO account ## 2 · Your SSO account
_On the host running authelia._ Until this runs, `auth.<swarm.domain>` serves _On the host running authelia._ Until this runs, `auth.<swarm.domain>` serves
a login page that refuses everyone: its only subject is a disabled placeholder. a login page that refuses everyone: its only subject is an inactive placeholder.
```bash ```bash
swarmctl user add mara --display-name Mara --email mara@example.com --group admins swarmctl user add mara --display-name Mara --email mara@example.com --group admins
@ -95,7 +95,7 @@ settings. Leave it empty and agents refer to you by name only.
## 4 · Ruth ## 4 · Ruth
hive-c0re creates no agent at startup. You create ruth, the manager agent, hive-c0re creates no agent at startup. You create ruth, the manager agent,
like any other: open the swarm UI start page and click the paperclip, which like any other: open the swarm UI start page and select the paperclip, which
opens the create form with `ruth` filled in and the `manage_root_agent` opens the create form with `ruth` filled in and the `manage_root_agent`
permission checked: that permission mounts the other agents into her permission checked: that permission mounts the other agents into her
container so she can recover them. The form declares her, creates her container so she can recover them. The form declares her, creates her

View file

@ -204,7 +204,7 @@ read directly without a delivery — same `mark_read` call, no wake.
### Activation gates (graceful no-ops) ### Activation gates (graceful no-ops)
The poller starts disabled and stays that way for any of: The poller starts switched off and stays that way for any of:
- `HIVE_FORGE_URL` not set or not parseable as a URL (a hand-run - `HIVE_FORGE_URL` not set or not parseable as a URL (a hand-run
harness; hive-c0re sets it from `swarm.forge.domain` on every hive). harness; hive-c0re sets it from `swarm.forge.domain` on every hive).
@ -215,7 +215,7 @@ The poller starts disabled and stays that way for any of:
best-effort enrichment fetches of server-provided subject URLs; best-effort enrichment fetches of server-provided subject URLs;
both 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 Switched off = the spawned task returns immediately. All other failure
modes (HTTP errors, parse errors, mark-read failures) are modes (HTTP errors, parse errors, mark-read failures) are
best-effort: logged at debug/warn and retried next tick. best-effort: logged at debug/warn and retried next tick.

View file

@ -400,7 +400,7 @@ surprising behaviour:
- **`services.hyperhive.swarm.matrix.gatewayHost != ""`** — same footgun as `forge.domain`: - **`services.hyperhive.swarm.matrix.gatewayHost != ""`** — same footgun as `forge.domain`:
empty string renders `.<hive>`-shaped garbage in both nginx empty string renders `.<hive>`-shaped garbage in both nginx
`server_name` (treated as wildcard catch-all, surprising) and `server_name` (treated as wildcard catch-all, surprising) and
`/etc/hosts` (invalid entry). `null` is the right opt-out shape; `/etc/hosts` (malformed entry). `null` is the right opt-out shape;
a config assertion rejects empty string explicitly. a config assertion rejects empty string explicitly.
SSO is unconditional, so the three below are requirements of running a SSO is unconditional, so the three below are requirements of running a
homeserver at all rather than of a setting: homeserver at all rather than of a setting:

View file

@ -42,7 +42,7 @@ The catch-all `_` vhost answers any other `Host` with `444` (connection closed,
Only the host that **runs** authelia declares `auth.<swarm>`; a hive that merely uses SSO knows `swarm.authelia.url` but doesn't answer for that name. The server name must be exactly `swarm.authelia.domain` — authelia checks that `authelia_url` sits inside its session cookie domain at startup, and refuses to boot otherwise. The vhost carries no `auth_request` (that would put the login page behind the login it serves) and passes `X-Forwarded-{Proto,Host,Uri,For}`, because authelia decides by the original request. Only the host that **runs** authelia declares `auth.<swarm>`; a hive that merely uses SSO knows `swarm.authelia.url` but doesn't answer for that name. The server name must be exactly `swarm.authelia.domain` — authelia checks that `authelia_url` sits inside its session cookie domain at startup, and refuses to boot otherwise. The vhost carries no `auth_request` (that would put the login page behind the login it serves) and passes `X-Forwarded-{Proto,Host,Uri,For}`, because authelia decides by the original request.
⚠️ `auth.<swarm>` answering with the **sso unavailable** page means authelia isn't answering at all — check `journalctl -M swarm-authelia -u authelia-swarm`. While the user set is empty, a disabled placeholder user keeps authelia's user store non-empty, so it boots fine and serves a login page that refuses everyone; add the first account with `swarmctl user add …` ([`setup.md`](../getting-started/setup.md)). ⚠️ `auth.<swarm>` answering with the **sso unavailable** page means authelia isn't answering at all — check `journalctl -M swarm-authelia -u authelia-swarm`. While the user set is empty, an inactive placeholder user keeps authelia's user store non-empty, so it boots fine and serves a login page that refuses everyone; add the first account with `swarmctl user add …` ([`setup.md`](../getting-started/setup.md)).
Per-agent UIs stay sub-path; forge and matrix get sub-domains → [Sub-domain shape](#sub-domain-shape-rationale). Per-agent UIs stay sub-path; forge and matrix get sub-domains → [Sub-domain shape](#sub-domain-shape-rationale).

View file

@ -183,7 +183,7 @@ Which makes it the highest-value target in the swarm by a wide margin,
and means it should get the treatment a backup host gets --- restricted and means it should get the treatment a backup host gets --- restricted
access, and a decision (rather than an omission) on encryption at rest. access, and a decision (rather than an omission) on encryption at rest.
The trap is the label: this box holds backup-grade data while nobody The pitfall is the label: this box holds backup-grade data while nobody
calls it a backup, so it can end up with backup-grade _exposure_ and calls it a backup, so it can end up with backup-grade _exposure_ and
non-backup-grade _controls_. Nobody puts a migration staging area on non-backup-grade _controls_. Nobody puts a migration staging area on
the access-review list. the access-review list.

View file

@ -88,7 +88,7 @@ namespace, not the wire surface.
## Recipient sentinels ## Recipient sentinels
The broker reserves a few recipient names, which have special The broker reserves a few recipient names, which have a fixed
meaning that ordinary agent labels can never collide with — agent meaning that ordinary agent labels can never collide with — agent
name validation rejects any character outside `[a-z0-9_-]`, so the name validation rejects any character outside `[a-z0-9_-]`, so the
angle-bracket and asterisk shapes below are structurally safe. angle-bracket and asterisk shapes below are structurally safe.

View file

@ -156,7 +156,7 @@ an input only because a docs tree has no runtime dependencies.
<!-- vale write-good.Passive = NO --> <!-- vale write-good.Passive = NO -->
The `storePath` trap is worth spelling out, because it's not confined The `storePath` pitfall is worth spelling out, because it's not confined
to options the operator writes: **any** option of type `package` fed a to options the operator writes: **any** option of type `package` fed a
store-path _string_ coerces through `lib.toDerivation`, i.e. store-path _string_ coerces through `lib.toDerivation`, i.e.
`builtins.storePath`. `environment.systemPackages` and `builtins.storePath`. `environment.systemPackages` and
@ -465,7 +465,7 @@ connects to the compositor at `127.0.0.1:<vnc_port>`.
Same reasoning as the best-effort oneshots in `docs/process/conventions.md`. Same reasoning as the best-effort oneshots in `docs/process/conventions.md`.
- **`[core] idle-time=0`**: disables weston's 300-second idle - **`[core] idle-time=0`**: disables weston's 300-second idle
timeout. Without it the VNC desktop fades to black and timeout. Without it the VNC desktop fades to black and
desktop-shell shows its click-to-unlock screen — useless for an desktop-shell shows its unlock screen — useless for an
agent desktop viewed over `/screen`. `idle-time=0` updates the agent desktop viewed over `/screen`. `idle-time=0` updates the
idle timer with a 0ms delay, which idle timer with a 0ms delay, which
`wl_event_source_timer_update` treats as "disarm," so the `wl_event_source_timer_update` treats as "disarm," so the

View file

@ -40,7 +40,7 @@ internals, container design, and the full security writeup.
<!-- vale write-good.Passive = NO --> <!-- vale write-good.Passive = NO -->
Several jobs run on every PR (and on `workflow_dispatch` for manual The jobs run on every PR (and on `workflow_dispatch` for manual
re-triggers), defined in re-triggers), defined in
[`.forgejo/workflows/ci.yml`](../../.forgejo/workflows/ci.yml). The first four [`.forgejo/workflows/ci.yml`](../../.forgejo/workflows/ci.yml). The first four
below are required checks (forge branch protection) — a hit on any of them below are required checks (forge branch protection) — a hit on any of them
@ -270,7 +270,7 @@ A malicious `default.nix` or build script in a PR can therefore:
No forge admin token enters the container: swarm-controller holds it and publishes only the registration token, which only `swarm-ci-runner-register.service` loads, so a job step can't read it. A build process can still reach the forge over the network, and it runs as the runner's own user, so it can read `.runner` and act as this runner; it can't issue admin API calls. No forge admin token enters the container: swarm-controller holds it and publishes only the registration token, which only `swarm-ci-runner-register.service` loads, so a job step can't read it. A build process can still reach the forge over the network, and it runs as the runner's own user, so it can read `.runner` and act as this runner; it can't issue admin API calls.
Note: `nix flake check --no-build` (eval-only) reduces the attack surface but doesn't eliminate it — `builtins.fetchGit`, `builtins.fetchurl`, and import-from-derivation can reach the network and filesystem during evaluation. The CI workflow's `nix flake check` job builds every check derivation, which is the higher-risk path. Note: `nix flake check --no-build` (eval-only) reduces the exposure but doesn't eliminate it — `builtins.fetchGit`, `builtins.fetchurl`, and import-from-derivation can reach the network and filesystem during evaluation. The CI workflow's `nix flake check` job builds every check derivation, which is the higher-risk path.
### Mitigation ### Mitigation

View file

@ -16,11 +16,11 @@ resource/lease semantics).
hive-c0re submits every container operation (rebuild, first-spawn, power hive-c0re submits every container operation (rebuild, first-spawn, power
changes) to the global job-DAG queue (`hive-c0re/src/job_queue/`) changes) to the global job-DAG queue (`hive-c0re/src/job_queue/`)
as a **DAG of primitive nodes**. One scheduler task drives all DAGs; as a **DAG of basic nodes**. One scheduler task drives all DAGs;
concurrency comes from the resource classes below, not from multiple workers. concurrency comes from the resource classes below, not from multiple workers.
Special cases like graceful stop, deferred starts, and the boot sweep Exceptions like graceful stop, deferred starts, and the boot sweep
need no bespoke code paths — each takes the shape of a DAG built need no bespoke code paths — each takes the shape of a DAG built
from the same primitive nodes. from the same basic nodes.
### Two levels: DAG and node ### Two levels: DAG and node

View file

@ -273,13 +273,13 @@ X" string to set, so mapping every level takes one rule each. A level button
then filters on `level` **or** the matching `severity_text`, and the store then filters on `level` **or** the matching `severity_text`, and the store
answers the second half. Each enabled rule appends an `OR answers the second half. Each enabled rule appends an `OR
<field>:<op>"<value>"` term to the query a level button emits, beside the <field>:<op>"<value>"` term to the query a level button emits, beside the
`level:…` term that matches nothing here. Clicking "info" goes from `level:…` term that matches nothing here. Selecting "info" goes from
``` ```
level:contains_common_case("info","information","informational","notice") level:contains_common_case("info","information","informational","notice")
``` ```
to that OR `severity_text:="INFO"`, which our rows do match. to that OR `severity_text:="INFO"`, which the rows here do match.
`Unspecified` is deliberately left out of those rules. It's VictoriaLogs' own `Unspecified` is deliberately left out of those rules. It's VictoriaLogs' own
rendering of an absent severity, and it's what the **Log rows with no rendering of an absent severity, and it's what the **Log rows with no
@ -684,6 +684,6 @@ unaffected either way; gauges have no temporality. The hive-tier collector
runs a `deltatocumulative` processor ahead of export, so a delta runs a `deltatocumulative` processor ahead of export, so a delta
`hive-metric` counter still lands in VictoriaMetrics as a cumulative `hive-metric` counter still lands in VictoriaMetrics as a cumulative
series — the standard `rate()`/`increase()` idioms work on it exactly like series — the standard `rate()`/`increase()` idioms work on it exactly like
any other counter in this system, no special query needed. any other counter in this system, no extra query needed.
<!-- vale write-good.Passive = YES --> <!-- vale write-good.Passive = YES -->

View file

@ -436,7 +436,7 @@ them, and the notification router, not the controller, decides what they mean.
A `config-pr` delivery reporting a PR merged into `main` queues a deploy A `config-pr` delivery reporting a PR merged into `main` queues a deploy
of its `merge_commit_sha` on the one hive whose wanted state places the of its `merge_commit_sha` on the one hive whose wanted state places the
agent; with no such hive, or several, the controller deploys nothing. See agent; with no such hive, or more than one, the controller deploys nothing. See
[config-changes.md § Config changes](../agent-lifecycle/config-changes.md#config-changes). [config-changes.md § Config changes](../agent-lifecycle/config-changes.md#config-changes).
A merge whose `closed` delivery never arrives deploys nothing either; the A merge whose `closed` delivery never arrives deploys nothing either; the
operator recovers by redelivering that delivery from the forge hook page, operator recovers by redelivering that delivery from the forge hook page,

View file

@ -91,7 +91,7 @@ constrain what a policy says. What bounds it:
restart, with the keys `init` printed. restart, with the keys `init` printed.
⚠️ **A sealed store still answers.** The container is up and the port ⚠️ **A sealed store still answers.** The container is up and the port
responds while every read times out — the failure looks like a hang, not like responds while every read times out — the failure looks like a stall, not like
a store that was never initialised. a store that was never initialised.
## TLS ## TLS

View file

@ -72,7 +72,7 @@ A flow is a graph of nodes joined by edges: one `source` node naming the forge
event types it starts from, `match` nodes that pass when every condition event types it starts from, `match` nodes that pass when every condition
holds, `todo` nodes that emit one todo op per recipient, and `request_reviewer` holds, `todo` nodes that emit one todo op per recipient, and `request_reviewer`
nodes that ask for a review. A match node can have more than one child, and nodes that ask for a review. A match node can have more than one child, and
each one runs, so one event can reach several leaves. A flow must be a tree: each one runs, so one event can reach more than one leaf. A flow must be a tree:
one source, one parent for every other node, every node reachable, no children one source, one parent for every other node, every node reachable, no children
under a `todo` or a `request_reviewer`. The most is 64 nodes, 16 under a `todo` or a `request_reviewer`. The most is 64 nodes, 16
levels deep, and 100 custom flows. A `todo` node sends to the agents an event levels deep, and 100 custom flows. A `todo` node sends to the agents an event

View file

@ -128,7 +128,7 @@ publishes it as
It answers only swarm-controller's OIDC client It answers only swarm-controller's OIDC client
(`swarm.controller.queueClientId`) and refuses every other token `403`. (`swarm.controller.queueClientId`) and refuses every other token `403`.
On first boot this module seeds the users database with a disabled placeholder subject, so authelia has a non-empty store to start against before any real account exists (`swarm-authelia.nix:873`). Authelia generates session and storage keys in the container on On first boot this module seeds the users database with an inactive placeholder subject, so authelia has a non-empty store to start against before any real account exists (`swarm-authelia.nix:873`). Authelia generates session and storage keys in the container on
first boot and never rotates them automatically; replacing one first boot and never rotates them automatically; replacing one
invalidates data already written (sessions, the encrypted store), so invalidates data already written (sessions, the encrypted store), so
that's an operator action. that's an operator action.

View file

@ -13,10 +13,10 @@ sign-out pages; the hive dashboard and the per-agent page have the same menu.
Every dropdown (a form's select, the refresh interval, the history window) opens a Every dropdown (a form's select, the refresh interval, the history window) opens a
list in the swarm UI's own theme, not the browser's, and works the same in every list in the swarm UI's own theme, not the browser's, and works the same in every
current browser. Open it with a click, or with the arrow keys, **Enter** or current browser. Open it with a press, or with the arrow keys, **Enter** or
**Space** while it has focus. Then the arrow keys, **Home**, **End**, **Page Up** **Space** while it has focus. Then the arrow keys, **Home**, **End**, **Page Up**
and **Page Down** move through the options, and **Enter**, **Space** or a click take and **Page Down** move through the options, and **Enter**, **Space** or a press take
the highlighted one. **Escape**, **Tab** or a click elsewhere shut the list without the highlighted one. **Escape**, **Tab** or a press elsewhere shut the list without
changing anything. Typing the start of an option's name picks it, also with the list changing anything. Typing the start of an option's name picks it, also with the list
shut. A dropdown inside a dialog opens above the dialog and the dialog doesn't cut it off. shut. A dropdown inside a dialog opens above the dialog and the dialog doesn't cut it off.
@ -29,7 +29,7 @@ shut. A dropdown inside a dialog opens above the dialog and the dialog doesn't c
| `/alerts` | the alerts the swarm's Grafana holds now: the firing ones, with a button for the pending, no-data and error ones and a link to Grafana's own alert list; read through the controller with a Viewer service-account token | | `/alerts` | the alerts the swarm's Grafana holds now: the firing ones, with a button for the pending, no-data and error ones and a link to Grafana's own alert list; read through the controller with a Viewer service-account token |
| `/jobs` | the controller's job graph — where agent creation and credential mints show progress | | `/jobs` | the controller's job graph — where agent creation and credential mints show progress |
| `/schedules` | the scheduled prompts: add, edit, pause, resume and remove | | `/schedules` | the scheduled prompts: add, edit, pause, resume and remove |
| `/messages` | the swarm-wide message feed: every message between agents, and to them from the operator, reminders and compaction, with a history window and a filter on sender or recipient; a folded **broadcast** card above the feed sends one message to several agents at once | | `/messages` | the swarm-wide message feed: every message between agents, and to them from the operator, reminders and compaction, with a history window and a filter on sender or recipient; a folded **broadcast** card above the feed sends one message to more than one agent at once |
| `/notifications` | the notification router's flows: a switch for each built-in, every flow drawn as a graph, and an editor for custom flows | | `/notifications` | the notification router's flows: a switch for each built-in, every flow drawn as a graph, and an editor for custom flows |
| `/issues` | a cross-repo issue report | | `/issues` | a cross-repo issue report |
| `/review` | the review classifier: its recent runs, a try-out on a real PR, and its settings | | `/review` | the review classifier: its recent runs, a try-out on a real PR, and its settings |
@ -488,7 +488,7 @@ its newest 100 builds, so a link to an older one shows a note instead.
The messages page (`/messages`) has a **broadcast** card above the message list. It The messages page (`/messages`) has a **broadcast** card above the message list. It
starts folded; select its header to open it. The card holds a composer that sends one message starts folded; select its header to open it. The card holds a composer that sends one message
to several agents at once, and the messages show up in the list below as they to more than one agent at once, and the messages show up in the list below as they
go. Folding it again keeps what you typed and ticked. Pick the go. Folding it again keeps what you typed and ticked. Pick the
agents from a grid of their icons. **running** shows the agents that report agents from a grid of their icons. **running** shows the agents that report
themselves running and **all** shows every agent; the browser keeps the choice. themselves running and **all** shows every agent; the browser keeps the choice.

View file

@ -146,7 +146,7 @@ distinct output shape — pick by what you need:
Rule of thumb: **`view` first** when you're about to act on a thread (it Rule of thumb: **`view` first** when you're about to act on a thread (it
shows everything and clears the unread guard so your follow-up `comment` shows everything and clears the unread guard so your follow-up `comment`
isn't rejected); the JSON verbs (`issue`/`pr`) when a script needs one isn't rejected); the JSON verbs (`issue`/`pr`) when a script needs one
field; `pr status` when the only question is "can I merge it." field; `pr status` when the only question is whether it can merge.
### Contributing to a read-only repo (`clone` + `pr create --agit`) ### Contributing to a read-only repo (`clone` + `pr create --agit`)

View file

@ -109,7 +109,7 @@ The summary depends on the room's unread state:
- **One unread message**: `[matrix] <sender> in <room>: <body> — - **One unread message**: `[matrix] <sender> in <room>: <body> —
use read_room to view, mark_read to clear` use read_room to view, mark_read to clear`
- **Several unread messages**: `[matrix] N unread in <room> — - **Multiple unread messages**: `[matrix] N unread in <room> —
use read_room to view, mark_read to clear` use read_room to view, mark_read to clear`
**Multi-account prefix**: when the daemon serves more than one account **Multi-account prefix**: when the daemon serves more than one account

View file

@ -200,7 +200,7 @@ renders it says so. Read the diff and the gate output regardless.
parent that polls `status` sees the block without reading anything else. parent that polls `status` sees the block without reading anything else.
`continue` is how you answer it. `continue` is how you answer it.
## Is it working, or is it wedged? ## Telling working from wedged
`status` reporting **running** says the daemon tracks a process, which a `status` reporting **running** says the daemon tracks a process, which a
wedged subagent satisfies as fully as a busy one. A running answer therefore wedged subagent satisfies as fully as a busy one. A running answer therefore
@ -209,7 +209,7 @@ an age climbing into the minutes means it's stuck. That one number
replaces inferring the same thing from `ps` output and CPU-time deltas. replaces inferring the same thing from `ps` output and CPU-time deltas.
It resets at each turn's spawn, so on a goal run it describes the turn in It resets at each turn's spawn, so on a goal run it describes the turn in
flight rather than the run — which is what you want, since a run that's flight rather than the run — which is what you want, since a run that's
making progress spends several perfectly healthy minutes. making progress spends a few perfectly healthy minutes.
Every line the subagent's `claude` process writes bumps the timestamp — Every line the subagent's `claude` process writes bumps the timestamp —
stream-json events, plain stdout chatter and stderr alike — and what the stream-json events, plain stdout chatter and stderr alike — and what the
@ -432,7 +432,7 @@ worktree switching.
One rule worth knowing before editing any of this: **the daemon never One rule worth knowing before editing any of this: **the daemon never
emits an empty `--tools` value**, and asserts rather than doing so. Not emits an empty `--tools` value**, and asserts rather than doing so. Not
because of what an empty value does — that's exactly the point. Our own because of what an empty value does — that's exactly the point. The hive's own
measurement and the installed `claude --help` disagree about whether measurement and the installed `claude --help` disagree about whether
`--tools ""` means "no tools" or reads as the flag being absent, and the `--tools ""` means "no tools" or reads as the flag being absent, and the
answer belongs to whichever claude release the system has installed answer belongs to whichever claude release the system has installed

View file

@ -45,7 +45,7 @@ Each agent gets its own scoped credentials, never shared:
_Its own_ account's scope bounds a compromised/confused agent's reach on the _Its own_ account's scope bounds a compromised/confused agent's reach on the
forge or matrix, not the swarm's. This is the main thing standing forge or matrix, not the swarm's. This is the main thing standing
between "one agent does something dumb" and "the whole hive is affected." between "one agent does something unwise" and "the whole hive is affected."
<!-- vale write-good.Passive = YES --> <!-- vale write-good.Passive = YES -->

View file

@ -221,7 +221,7 @@ services.hyperhive.agent.claudePluginsAutoUpdate = false; # defaul
- **`claudePluginsAutoUpdate`** — when `true`, runs - **`claudePluginsAutoUpdate`** — when `true`, runs
`claude plugin marketplace update` before installing plugins to pull `claude plugin marketplace update` before installing plugins to pull
the latest index. Disabled by default to keep boot times short and the latest index. Off by default to keep boot times short and
plugin versions pinned. plugin versions pinned.
## `cargo.shortMessages` ## `cargo.shortMessages`
@ -265,7 +265,7 @@ is a no-op without the other:
consolidated harness state file so the operator dashboard also stops reading this consolidated harness state file so the operator dashboard also stops reading this
agent's empty `~/.claude/` as "needs login." An api-key agent that hits agent's empty `~/.claude/` as "needs login." An api-key agent that hits
a real 401 (the key itself is bad) still surfaces `needs_login` — the a real 401 (the key itself is bad) still surfaces `needs_login` — the
harness skips only the boot-time "have I ever logged in" check, not harness skips only the boot-time check for an earlier login, not
the auth-failure path. the auth-failure path.
- **`backendEnvironmentFile`** points at an operator-managed file - **`backendEnvironmentFile`** points at an operator-managed file
(outside the nix store, one `KEY=value` per line, systemd (outside the nix store, one `KEY=value` per line, systemd

View file

@ -92,7 +92,7 @@ JS renders — no full-page reloads.
motion, theming, error-UX, and component-first principles for the motion, theming, error-UX, and component-first principles for the
swarm-level Preact app specifically (not this page's dashboard/agent swarm-level Preact app specifically (not this page's dashboard/agent
UIs). `/components` on a running swarm-ui is the companion living UIs). `/components` on a running swarm-ui is the companion living
demo of every primitive it references. demo of every building block it references.
### Implementation reading paths ### Implementation reading paths
@ -115,6 +115,6 @@ touching the code rather than using the UI:
[`agent.md`](agent.md) (Terminal-embedded prompt). [`agent.md`](agent.md) (Terminal-embedded prompt).
- **"What are the per-agent HTTP endpoints?"** → - **"What are the per-agent HTTP endpoints?"** →
[`agent.md`](agent.md) (Per-agent endpoints). [`agent.md`](agent.md) (Per-agent endpoints).
- **"Which CSS variable do I use / where are colours defined?"** → - **"Which CSS variable to use / where are colours defined?"** →
[`css-vars.md`](css-vars.md) (Palette, single-source `base.css` [`css-vars.md`](css-vars.md) (Palette, single-source `base.css`
rule). rule).

View file

@ -128,7 +128,7 @@ and scrolls behind the fixed header + footer.
`.agent-main`**, not in `log.parentElement = .terminal-wrap`: `.agent-main`**, not in `log.parentElement = .terminal-wrap`:
`.terminal-wrap` applies `backdrop-filter: blur` for the frost `.terminal-wrap` applies `backdrop-filter: blur` for the frost
effect, which creates a CSS stacking context — anchoring the effect, which creates a CSS stacking context — anchoring the
pill inside that context would trap its `z-index` below the pill inside that context would confine its `z-index` below the
fixed composer in the root stacking context, and it'd never fixed composer in the root stacking context, and it'd never
float. `.agent-main` has no backdrop-filter (no stacking-context float. `.agent-main` has no backdrop-filter (no stacking-context
creators), so the pill's `z-index` reaches the root and properly creators), so the pill's `z-index` reaches the root and properly
@ -259,8 +259,8 @@ Unknown `/foo` shows an error row instead of being silently sent.
## Per-agent endpoints ## Per-agent endpoints
Successful POSTs return 200 (no 303 redirects). Error responses Successful POSTs return 200 (no 303 redirects). Error responses
use semantic status codes: **400** for missing/invalid input use semantic status codes: **400** for missing/malformed input
(`body` required, unknown model name, invalid effort level), (`body` required, unknown model name, unrecognised effort level),
**409** for retryable state conflicts (turn in flight when **409** for retryable state conflicts (turn in flight when
`/compact` fires), **500** only for genuine server/transport `/compact` fires), **500** only for genuine server/transport
failures. The matching failures. The matching
@ -358,7 +358,7 @@ shaped).
dimensions; enabled once the server advertises the dimensions; enabled once the server advertises the
`ExtendedDesktopSize` pseudo-encoding (`-308` rect in the header). `ExtendedDesktopSize` pseudo-encoding (`-308` rect in the header).
Fit-mode state persists in `localStorage` (`screen-fit`); default Fit-mode state persists in `localStorage` (`screen-fit`); default
is on. `sendPointer` rescales pointer coordinates so clicks is on. `sendPointer` rescales pointer coordinates so presses
land on the right pixel regardless of CSS scale. land on the right pixel regardless of CSS scale.
- `GET /screen/ws` — raw RFB byte relay: proxies WebSocket - `GET /screen/ws` — raw RFB byte relay: proxies WebSocket
frames to the weston VNC server at `127.0.0.1:<vnc_port>`. frames to the weston VNC server at `127.0.0.1:<vnc_port>`.

View file

@ -449,7 +449,7 @@ on the `openDialog` core:
button takes focus). Selecting the backdrop and `Esc` both cancel. button takes focus). Selecting the backdrop and `Esc` both cancel.
- `themedToast(message, { type, ... })` — a non-blocking toast (top-right, - `themedToast(message, { type, ... })` — a non-blocking toast (top-right,
`info` / `error` / `ok`) for transient validation + action `info` / `error` / `ok`) for transient validation + action
failures, so an error doesn't trap the operator behind a modal. failures, so an error doesn't lock the operator behind a modal.
Single-action errors autodismiss; bulk / partial-failure Single-action errors autodismiss; bulk / partial-failure
summaries are sticky (select to dismiss) so they aren't missed. summaries are sticky (select to dismiss) so they aren't missed.
@ -493,7 +493,7 @@ frosted-mauve bar slides up from the bottom of the viewport
<!-- vale write-good.Passive = NO --> <!-- vale write-good.Passive = NO -->
- **Count + names** — "N agents selected · name1, name2, …" - **Count + names** — "N agents selected · name1, name2, …"
- **Bulk action buttons** — only enabled when ALL selected agents - **Bulk action buttons** — only enabled when ALL selected agents
support the action; disabled with a tooltip naming the blockers support the action; greyed out with a tooltip naming the blockers
when the selection is mixed: when the selection is mixed:
- `↺ R3ST4RT` — running agents only - `↺ R3ST4RT` — running agents only
- `■ ST0P` — running agents only - `■ ST0P` — running agents only

View file

@ -5,7 +5,7 @@ dashboard, which has its own older visual language). This doc is the
_why_: the principles behind how swarm-ui looks and behaves, and the _why_: the principles behind how swarm-ui looks and behaves, and the
concrete rules that follow from them. It deliberately doesn't show what concrete rules that follow from them. It deliberately doesn't show what
things look like — that's `ComponentsPage` (`/components`), the living, things look like — that's `ComponentsPage` (`/components`), the living,
always-current demo of every primitive in `src/ui/`. Code can't go stale always-current demo of every building block in `src/ui/`. Code can't go stale
the way a doc's screenshots can, so treat `ComponentsPage` as the source the way a doc's screenshots can, so treat `ComponentsPage` as the source
of truth for appearance and this doc as the source of truth for intent. of truth for appearance and this doc as the source of truth for intent.
Colour variables specifically are `docs/web-ui/css-vars.md`'s job, not Colour variables specifically are `docs/web-ui/css-vars.md`'s job, not
@ -145,7 +145,7 @@ just bare column headers floating over nothing.
## Component-first design ## Component-first design
Build the `src/ui/` primitive before or alongside the first real page Build the `src/ui/` building block before or alongside the first real page
that needs it, not after a contributor styles it inline and leaves it that needs it, not after a contributor styles it inline and leaves it
for later — a page reaching for a component that's already there has ready-made for later — a page reaching for a component that's already there has ready-made
blocks to build with, instead of the next contributor duplicating blocks to build with, instead of the next contributor duplicating
@ -157,10 +157,10 @@ option in a form uses `CheckboxField`, which sizes its row like the other
fields, rather than a bare `<input type="checkbox">`. fields, rather than a bare `<input type="checkbox">`.
**Every new `ui/` component gets a demo section on `/components` the **Every new `ui/` component gets a demo section on `/components` the
same day it lands** — no primitive without a place to see it. One same day it lands** — no building block without a place to see it. One
deliberate exception exists today: `FormField`, the internal label+ deliberate exception exists today: `FormField`, the internal label+
control wrapper `TextField`/`SelectField` share, isn't itself a control wrapper `TextField`/`SelectField` share, isn't itself a
primitive a page reaches for directly, so it has no demo of its own — building block a page reaches for directly, so it has no demo of its own —
a considered exception, not an oversight this rule missed. a considered exception, not an oversight this rule missed.
## Attention ## Attention

View file

@ -128,7 +128,7 @@ regardless, for example message-bearing tool_use, are unaffected either way).
**Sticky-bottom + snap animation.** `stickToBottom` is the **Sticky-bottom + snap animation.** `stickToBottom` is the
operator's intent: true means "keep snapping to bottom on every operator's intent: true means "keep snapping to bottom on every
mutation," false means "I scrolled up, leave me alone." The flag mutation," false means "the operator scrolled up, leave them alone." The flag
flips when a scroll event lands further than `NEAR_BOTTOM_PX = 48` flips when a scroll event lands further than `NEAR_BOTTOM_PX = 48`
from the bottom. New rows then either snap to bottom (when sticky) from the bottom. New rows then either snap to bottom (when sticky)
or bump the unseen-count and surface the "↓ N new" pill. The snap or bump the unseen-count and surface the "↓ N new" pill. The snap