docs: restructure into topic subdirectories, collapse duplicated index

Per mara's go-ahead on hyperhive#3902 ("getting started is good, but
terminal rendering does not go in there i think"):

Moved 21 top-level docs/*.md files into 7 new topic subdirectories
(existing web-ui/, turn-loop/, swarm/, tools/, crates/ untouched):
  getting-started/  setup.md
  agent-lifecycle/  agent-hierarchy.md, approvals.md, persistence.md
  trust-boundary/   boundary.md, security.md
  integrations/     forge.md, matrix.md, github.md, knowledge.md
  networking/       gateway.md, network.md, snapshot-store.md
  scheduler/        jobq.md, coordinator.md, ci.md, observability.md
  process/          conventions.md, gotchas.md, pr-review-gate.md
  web-ui/           terminal-rendering.md (moved into the EXISTING dir,
                    per mara's correction to the original getting-started
                    guess -- it's UI implementation detail, not onboarding)

The physical layout now matches docs/README.md's own topical headers,
which already amounted to this taxonomy -- see the scoping comment on
the issue for the two findings that motivated this (a genuine
duplication between CLAUDE.md's old "Reading paths" list and
docs/README.md's grouped one, since drifted out of sync with each
other; and the flat layout not matching the grouping we already had).

Fixed every cross-reference this moved across the whole repo (~120
files: docs/ internal links at every depth, Rust doc comments, nix
module option docs, crate READMEs) -- verified two ways: a grep sweep
confirming zero remaining references to any old path, and a script
that resolves every markdown link in docs/**/*.md + CLAUDE.md +
README.md against the filesystem and reports anything that doesn't
exist (zero broken links).

Collapsed CLAUDE.md's "Reading paths" section (the duplicate) down to
a pointer at docs/README.md, now the single index. Rewrote
docs/README.md itself to use the new subdirectory paths and added the
one doc it was missing that CLAUDE.md's old copy had (pr-review-gate.md).

Classified all 22 docs/*.md files first via a haiku subagent (mara's
suggestion) on two axes -- proposed grouping and operator-vs-
implementation focus -- before finalizing the taxonomy; spot-checked
the report and found internal inconsistencies (its classification
table disagreed with its own summary section for a few files), so this
taxonomy is my original proposal + the one correction mara gave
directly, not a blind application of the subagent's table. The
operator-focus data it gathered is still useful for a follow-up
content pass (docs skewing 'mixed' rather than pure operator-facing),
not addressed in this PR -- structure only.

nix fmt clean, both pre-push lints clean.
This commit is contained in:
iris 2026-09-02 01:47:05 +02:00 committed by mara
commit 07b62612b0
124 changed files with 301 additions and 377 deletions

View file

@ -11,55 +11,53 @@ declarations.
## Getting started
- **Bringing a fresh hive online?** → [`setup.md`](setup.md) (first-run
`hivectl` bootstrap).
- **Bringing a fresh hive online?** → [`getting-started/setup.md`](getting-started/setup.md)
(first-run `hivectl` bootstrap).
- **What does the dashboard look like, and how do I use it?**
[`web-ui/`](web-ui/README.md) — the operator-facing starting point;
its own sub-pages ([`shape`](web-ui/shape.md),
[`dashboard`](web-ui/dashboard.md), [`agent`](web-ui/agent.md),
[`css-vars`](web-ui/css-vars.md)) go deeper into implementation.
[`css-vars`](web-ui/css-vars.md), [`terminal-rendering`](web-ui/terminal-rendering.md))
go deeper into implementation.
- **What tools does an agent (or the operator) have available?**
[`tools/`](tools/README.md) — `hivectl` (yours) plus every agent's MCP
tool surface (bash, forge, lifecycle, matrix, scheduling).
## Dashboard & agent UI internals
- **How does the per-agent terminal classify + colour events?**
[`terminal-rendering.md`](terminal-rendering.md).
## Turn loop, config, approvals
## Agent lifecycle
- **How do config changes flow from manager to operator to container?**
[`agent-lifecycle/approvals.md`](agent-lifecycle/approvals.md) (two-step spawn, approval
state machine, `flake.lock` validation).
- **What state survives destroy / purge / restart?**
[`agent-lifecycle/persistence.md`](agent-lifecycle/persistence.md).
- **Who can do what to whom — agent hierarchy and privilege?**
[`agent-lifecycle/agent-hierarchy.md`](agent-lifecycle/agent-hierarchy.md).
- **How does claude get its prompt, and what tools does it have?**
[`turn-loop/`](turn-loop/README.md) — the loop, binary shape, turn
outcomes; sub-pages: [`claude-invocation`](turn-loop/claude-invocation.md),
[`config`](turn-loop/config.md), [`mcp`](turn-loop/mcp.md).
- **How do config changes flow from manager to operator to container?**
[`approvals.md`](approvals.md) (two-step spawn, approval state machine,
`flake.lock` validation).
- **What state survives destroy / purge / restart?**
[`persistence.md`](persistence.md).
## Trust boundary & security
- **What's the operator/agent trust boundary? What's a capability?**
[`boundary.md`](boundary.md).
[`trust-boundary/boundary.md`](trust-boundary/boundary.md).
- **Agent trust model, prompt-injection threat model, credential
isolation?** → [`security.md`](security.md).
- **Who can do what to whom — agent hierarchy and privilege?**
[`agent-hierarchy.md`](agent-hierarchy.md).
isolation?** → [`trust-boundary/security.md`](trust-boundary/security.md).
## Accounts & integrations
- **How do per-agent forge accounts work? What does `forge_notify` poll,
and how does it format wake messages?** → [`forge.md`](forge.md) (the
hive's own Forgejo); [`tools/forge.md`](tools/forge.md) for the
and how does it format wake messages?** → [`integrations/forge.md`](integrations/forge.md)
(the hive's own Forgejo); [`tools/forge.md`](tools/forge.md) for the
`hive-forge` CLI verbs agents actually call.
- **How does the matrix-tuwunel container work? Multiple accounts per
agent?** → [`matrix.md`](matrix.md) (the homeserver);
agent?** → [`integrations/matrix.md`](integrations/matrix.md) (the homeserver);
[`tools/matrix.md`](tools/matrix.md) for the MCP tool surface and
`hyperhive.matrixAccounts`.
- **How do I give an agent a GitHub account (`gh` + `git push`)? How is
the PAT injected?** → [`github.md`](github.md).
the PAT injected?** → [`integrations/github.md`](integrations/github.md).
- **What is `/knowledge`? How does the hive-wide knowledge repo sync,
and how do I contribute a document?** → [`integrations/knowledge.md`](integrations/knowledge.md).
- **What does `hivectl` do? Provisioning, gateway users, container
shells?** → [`tools/hivectl.md`](tools/hivectl.md) (the curated guide);
[`tools/hivectl-cli.md`](tools/hivectl-cli.md) for the exhaustive,
@ -68,25 +66,25 @@ declarations.
## Networking & swarms
- **What nginx vhosts does the gateway serve? How does matrix
discovery work?** → [`gateway.md`](gateway.md).
discovery work?** → [`networking/gateway.md`](networking/gateway.md).
- **How does DNS resolution work in agent containers? What's the
bridge network for?** → [`network.md`](network.md).
bridge network for?** → [`networking/network.md`](networking/network.md).
- **How do I connect two hives into a swarm?** → [`swarm/`](swarm/README.md)
(peer hives, TLS trust).
- **Where do agent snapshots go? How does the swarm's `btrfs receive`
endpoint authenticate a pushing hive?** →
[`snapshot-store.md`](snapshot-store.md).
[`networking/snapshot-store.md`](networking/snapshot-store.md).
## Scheduler, CI, observability
- **What is the job queue, as a general idea (not hive-c0re specifics)?**
[`jobq.md`](jobq.md) — operator-facing, no implementation detail.
[`scheduler/jobq.md`](scheduler/jobq.md) — operator-facing, no implementation detail.
- **How does the rebuild queue work? What are the concrete step kinds,
queue sources, scheduler internals?** → [`coordinator.md`](coordinator.md).
queue sources, scheduler internals?** → [`scheduler/coordinator.md`](scheduler/coordinator.md).
- **How does the CI runner work? What's the auto-registration flow?**
[`ci.md`](ci.md).
[`scheduler/ci.md`](scheduler/ci.md).
- **How do I export Claude Code metrics (tokens, cost, tool calls) to
Prometheus/Grafana?** → [`observability.md`](observability.md).
Prometheus/Grafana?** → [`scheduler/observability.md`](scheduler/observability.md).
## Crate reference
@ -98,8 +96,8 @@ declarations.
## Process & conventions
- **Naming, commit style, wire protocol, the `data-async` pattern?**
[`conventions.md`](conventions.md).
- **Why does the nspawn flag look like that?** → [`gotchas.md`](gotchas.md)
[`process/conventions.md`](process/conventions.md).
- **Why does the nspawn flag look like that?** → [`process/gotchas.md`](process/gotchas.md)
(bind mounts, conf flags, other NixOS/nspawn quirks).
- **What is `/knowledge`? How does the hive-wide knowledge repo sync,
and how do I contribute a document?** → [`knowledge.md`](knowledge.md).
- **What does a PR review verdict actually gate?**
[`process/pr-review-gate.md`](process/pr-review-gate.md).

View file

@ -212,7 +212,7 @@ path = [ "/run/wrappers" "/run/current-system/sw" ];
`/run/wrappers` (not `/run/wrappers/bin`) comes first so setuid
wrappers — notably `sudo` — resolve before bare nix-store binaries; see
[`docs/gotchas.md`](gotchas.md) ("`systemd.services.*.path` appends
[`docs/process/gotchas.md`](../process/gotchas.md) ("`systemd.services.*.path` appends
`/bin` to every entry") for why the trailing `/bin` matters in
general. It's load-bearing here because the harness runs as the
per-agent user: without the wrapper dir on `PATH`, `sudo` resolves to
@ -243,4 +243,4 @@ uid 0 and have the setuid bit set."
(`$HIVE_FORGE_URL/hyperhive/hyperhive/issues/363`)
- Audit table source: milestone comment
(`$HIVE_FORGE_URL/hyperhive/hyperhive/issues/361#issuecomment-3335`)
- Operator/agent trust boundary (orthogonal axis): [`boundary.md`](boundary.md)
- Operator/agent trust boundary (orthogonal axis): [`boundary.md`](../trust-boundary/boundary.md)

View file

@ -77,8 +77,8 @@ power-intent registry:
This is the durable power *intent* the job queue reconciles the
observed container state against; intent survives hive-c0re
restarts even though in-flight queue work doesn't. See
[`docs/coordinator.md`'s Desired-state
section](coordinator.md#desired-state-spec-vs-status) for who
[`docs/scheduler/coordinator.md`'s Desired-state
section](../scheduler/coordinator.md#desired-state-spec-vs-status) for who
writes and reads it and how reconciliation works.
Retention:
@ -126,7 +126,7 @@ history sooner than a quiet one. The trade-off (accepted): a
misbehaving harness could now skip its own cleanup, which the old
host-side sweep was meant to prevent — but a compromised harness is
already inside the container trust boundary
([`docs/security.md`](security.md)), and these are ephemeral local
([`docs/trust-boundary/security.md`](../trust-boundary/security.md)), and these are ephemeral local
artifacts, so cleaning them up where they live is the honest fix.
Path overridable via `HYPERHIVE_EVENTS_DB` (for dev / no-`/harness`
@ -236,7 +236,7 @@ Survives destroy/recreate, gone on `--purge`.
Empty marker file. Its presence parks the agent's turn loop: the
harness keeps serving its web UI and MCP daemons but drives no turns,
and inbox messages queue unacked until it's removed (see
[turn loop](turn-loop/README.md#the-loop)).
[turn loop](../turn-loop/README.md#the-loop)).
Unusually, it's read and written from **both** sides of the harness
bind-mount, and that's the whole design: the harness stats it
@ -383,7 +383,7 @@ Contents:
(`{ "alice": "root", "bob": "alice", "root": null }`).
Written by `topology::apply_set_parent` (the pure move-validating
transform) via `meta::bulk_commit_topology` (the committer — see the
`Reparent` node in [`docs/coordinator.md`](coordinator.md)); read by
`Reparent` node in [`docs/scheduler/coordinator.md`](../scheduler/coordinator.md)); read by
the dashboard, the renderer, and `<parent>` / `<children>` recipient
resolution.
- `tool-groups.json` — per-agent MCP tool group grants
@ -466,7 +466,7 @@ subvolume creation/deletion above.
This is the same subvolume `hivectl agent <name> subvol snapshot push`
sends to the swarm's snapshot store — see
[`docs/snapshot-store.md`](snapshot-store.md) for what a pushed
[`docs/snapshot-store.md`](../networking/snapshot-store.md) for what a pushed
snapshot contains and how the store authenticates a sender.
## `/var/lib/swarm-controller/` (swarm-controller host only)

View file

@ -11,7 +11,7 @@ inside an agent container); the `request_*` steps run from ruth's own
turn via the MCP tools.
**Bringing up a hive that does not host its own swarm services?** Read
[`swarm/secrets.md`](swarm/secrets.md) first. Everything below assumes
[`swarm/secrets.md`](../swarm/secrets.md) first. Everything below assumes
each credential is generated where it is read, which is true on an
all-local deploy and not otherwise — that page says which files an
operator has to place, and where.
@ -105,7 +105,7 @@ than amend — adding the group afterwards is `swarmctl user update mara
--add-group admins`.
Detail, including what the password is and why this stays manual:
[`swarm/sso.md`](swarm/sso.md).
[`swarm/sso.md`](../swarm/sso.md).
### 5 · Swarm UI (only when `deploy.swarm-ui`, on by default with the controller)
@ -121,7 +121,7 @@ decide whether you can actually open it:
anywhere else it needs a real DNS record like any other public name.
Detail, including why reachability is deliberately not the access
control: [`swarm/ui.md`](swarm/ui.md).
control: [`swarm/ui.md`](../swarm/ui.md).
### 6 · Matrix
@ -163,7 +163,7 @@ request_init_config(name: "iris")
# the operator reviews + approves it — no MCP tool call.
```
See [`approvals.md`](approvals.md) for the full flow.
See [`approvals.md`](../agent-lifecycle/approvals.md) for the full flow.
### 8 · Useful host commands
@ -183,7 +183,7 @@ hivectl open forge # Forgejo
hivectl open matrix # Matrix GUI (fluffychat)
```
See [`tools/hivectl.md`](tools/hivectl.md) for every `hivectl` verb.
See [`tools/hivectl.md`](../tools/hivectl.md) for every `hivectl` verb.
## Security notes
@ -192,12 +192,12 @@ See [`tools/hivectl.md`](tools/hivectl.md) for every `hivectl` verb.
creds (the core token, the matrix admin token) live on the host.
- All config changes (forge PRs on `agent-configs/<name>`) go through
operator approval — agents can't unilaterally rebuild containers, by design.
See [`boundary.md`](boundary.md) and [`security.md`](security.md).
See [`boundary.md`](../trust-boundary/boundary.md) and [`security.md`](../trust-boundary/security.md).
- **Telemetry ingest is authenticated per hive**, and the `hive` label comes
from which hive authenticated rather than from the payload — so no hive can
report metrics as another. A first-run all-local hive gets this with nothing
to configure; joining a swarm you don't host needs one secret copied across.
See [`observability.md`](observability.md#authenticated-ingest).
See [`observability.md`](../scheduler/observability.md#authenticated-ingest).
Once the hive is running, ruth records anything it needs to remember
across restarts in `/agents/ruth/state/notes.md`.

View file

@ -4,7 +4,7 @@ Private Forgejo instance running in a nixos-container, used as the
swarm's persistent code-collaboration surface (issues, PRs, reviews,
attachments). Configured via `services.hyperhive.swarm.forge.*`. Container
shape, ROOT_URL / sub-domain routing, and operator-vs-in-cluster URL
handling live in [`docs/gateway.md`](gateway.md); this file owns the
handling live in [`docs/networking/gateway.md`](../networking/gateway.md); this file owns the
per-agent integration story and the notification pump that wakes
each agent on relevant activity.
@ -69,7 +69,7 @@ Two things live in the `agent-configs` Forgejo organization:
agent's config. (Agents remain read-only collaborators on `core/meta`.)
hive-c0re also references this repo as the agent's **persistent meta
flake input** (`agent-<n>.url = git+http://<forge>/agent-configs/<n>.git`;
see [approvals.md § Meta flake](approvals.md)), fetching it as the `core`
see [approvals.md § Meta flake](../agent-lifecycle/approvals.md)), fetching it as the `core`
user via a git credential helper that reads the live forge-core token —
so the config lives on the forge, not a hand-synced local checkout.
- The dashboard links each container's "config" anchor to this
@ -332,4 +332,4 @@ delivers every unread notification it's handed. Bounding the
firehose (dropping broad repo watches an agent doesn't need) is done
explicitly via a hive-forge CLI subscription verb, not by the poller
guessing which watches to drop. See the `subscription` verb in
[`docs/tools/forge.md`](tools/forge.md).
[`docs/tools/forge.md`](../tools/forge.md).

View file

@ -46,7 +46,7 @@ hivectl github set-token <agent> --token <pat> # inline (visible in shell his
Either path has hive-c0re delegate the write to hive-priv, which stores the
file `0600` owned by the agent (so the container can read it) — the same
credential-injection path as forge/matrix tokens. See
[hivectl → GitHub](tools/hivectl.md#github).
[hivectl → GitHub](../tools/hivectl.md#github).
## Security
@ -55,7 +55,7 @@ credential-injection path as forge/matrix tokens. See
workflow needs. Agents have passwordless sudo, so a compromised or
hallucinating agent can act as the account within the token's scopes;
scope is the real blast-radius limiter, and the container boundary is
the enforcement. See [security.md](security.md).
the enforcement. See [security.md](../trust-boundary/security.md).
Everything below this point is implementation detail (how the agent
actually uses the token, and the notification poller's internals).

View file

@ -4,11 +4,11 @@ Private Matrix homeserver (matrix-tuwunel — the conduwuit
successor) wrapped in a nixos-container, plus optional fluffychat-web
client at `chat.<swarm-domain>/` (the `gatewayHost` vhost). Configured via
`services.hyperhive.swarm.matrix.*`; vhost routing lives in
[`gateway.md`](gateway.md).
[`gateway.md`](../networking/gateway.md).
## Container shape
Same shape as [`gateway.md::hive-forge container shape`](gateway.md):
Same shape as [`gateway.md::hive-forge container shape`](../networking/gateway.md):
- Container name `hive-matrix` (not `h-*`) so c0re's lifecycle
scanner ignores it; operator manages via the standard
@ -202,7 +202,7 @@ at the protocol level from first boot (`allow_federation = true`) but
no remote homeserver is trusted until listed here. For a closed
single-hive deployment the default empty list is correct — add peer
hive domains here when connecting hives into a swarm (see
[`docs/swarm/`](swarm/README.md)).
[`docs/swarm/`](../swarm/README.md)).
**`maxRequestSize`** (default `20_000_000` bytes = 20 MB) — maximum
size of a single matrix client request body. Matches the matrix-spec
@ -237,7 +237,7 @@ homeserver at all rather than of a setting:
`server_name`'s own bogus-value guard lives in `hive-network.nix`
(`services.hyperhive.domain != null`), not here — see
[`docs/network.md`](network.md).
[`docs/networking/network.md`](../networking/network.md).
## fluffychat-web build fixes

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 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).
Per-agent UIs stay sub-path, forge and matrix get sub-domains — see
[Sub-domain shape (rationale)](#sub-domain-shape-rationale) below for why.
@ -108,7 +108,7 @@ now set unconditionally for every agent. The mechanism:
nginx reaches `web.sock` through `o=--x` (traverse) and the socket's
own `0666`. The gateway is one of three principals sharing that dir
and does not own its ownership rules — see
[`docs/boundary.md`](boundary.md#the-per-agent-socket-dir).
[`docs/trust-boundary/boundary.md`](../trust-boundary/boundary.md#the-per-agent-socket-dir).
3. **Marker gate**. After successful `bind_unix`, the harness drops
`<dir>/hyperhive-socket-bound` next to the socket. c0re's
`agent_sockets::write` filters its JSON map by marker presence —
@ -216,11 +216,11 @@ swarm-services fallback below is for.
**Why a CA, not a bare leaf**: a bare self-signed leaf is its own trust anchor, so every regeneration is a new anchor every consumer must re-trust — and a runtime-generated leaf can't be wired into an agent's build-time trust store at all. With a stable CA, agents and federation peers trust it *once*; leaf rotation never re-breaks them.
**What consumers trust**: `trust-bundle.pem` in the same state dir, not `ca.pem`. The hive CA is itself issued under the swarm root ([`swarm/ca.md`](swarm/ca.md) has the hierarchy), and an intermediate is not a chain a verifier can terminate at — so the bundle carries the hive CA plus whatever it is rooted at. nginx is handed the leaf with the hive CA appended for the same reason. Everything that trusts the hive's TLS reads the bundle: agents (via `security.pki.certificateFiles`), the CI and forge containers, and a federating peer.
**What consumers trust**: `trust-bundle.pem` in the same state dir, not `ca.pem`. The hive CA is itself issued under the swarm root ([`swarm/ca.md`](../swarm/ca.md) has the hierarchy), and an intermediate is not a chain a verifier can terminate at — so the bundle carries the hive CA plus whatever it is rooted at. nginx is handed the leaf with the hive CA appended for the same reason. Everything that trusts the hive's TLS reads the bundle: agents (via `security.pki.certificateFiles`), the CI and forge containers, and a federating peer.
**Why on by default**: matrix-dart-sdk (FluffyChat's SDK) hardcodes `https://<host>/.well-known/matrix/client` for homeserver discovery and refuses to fall back to plain http. Without TLS the browser client cannot bootstrap.
**Cert shape**: leaf subject CN = bare hive domain; subjectAltName is `<hive>` plus wildcard `*.<hive>`, so all current and future sub-domain vhosts validate under the same leaf + the hive CA. A swarm service whose name is *not* under this hive's domain cannot be added here — the hive CA is name-constrained to `<hive>`, and a violating SAN invalidates the whole leaf, not just that name. Those names get the swarm-services leaf instead ([`swarm/ca.md`](swarm/ca.md)).
**Cert shape**: leaf subject CN = bare hive domain; subjectAltName is `<hive>` plus wildcard `*.<hive>`, so all current and future sub-domain vhosts validate under the same leaf + the hive CA. A swarm service whose name is *not* under this hive's domain cannot be added here — the hive CA is name-constrained to `<hive>`, and a violating SAN invalidates the whole leaf, not just that name. Those names get the swarm-services leaf instead ([`swarm/ca.md`](../swarm/ca.md)).
**Rotation**: `hive-tls-ca.service` is idempotent — it re-signs the leaf when it is missing or within 30 days of expiry, always under the same CA (so consumer trust is undisturbed). The CA itself is regenerated only if missing or already expired. To force a leaf rotation, delete `gateway.pem` under the state dir and restart the unit, then reload `nginx`.

View file

@ -70,7 +70,7 @@ The flows, end to end:
the host forwards + masquerades out its uplink.
- **Swarm** — peer hives connect over the `wg-hive` WireGuard mesh
and reach each other's gateway/forge across it
([`docs/swarm/`](swarm/README.md)).
([`docs/swarm/`](../swarm/README.md)).
- **Control plane (no network)** — per-agent broker socket
`/run/hive/mcp.sock`, privileged helper `/run/hive/priv.sock`,
operator admin `/run/hyperhive/host.sock`, and the per-agent UI

View file

@ -35,7 +35,7 @@ services.hyperhive.swarm.wireguard = {
The store host is a swarm member like any other: it gets an entry in
`services.hyperhive.swarm.hives`, the same directory every host holds. See
[swarm/](swarm/README.md) for the mesh itself.
[swarm/](../swarm/README.md) for the mesh itself.
Note that the mesh is gated on `swarm.wireguard.enable`, **not** on
`c0re.enable` --- a store host runs no hive and would otherwise get no
@ -185,7 +185,7 @@ the access-review list.
The snapshot covers an agent's **state subvolume**, which is the parent
of `state/`, `claude/` and `harness/` (see
[`docs/persistence.md`'s btrfs subvolume
section](persistence.md#btrfs-subvolumes-for-varlibhyperhiveagentsname)
section](../agent-lifecycle/persistence.md#btrfs-subvolumes-for-varlibhyperhiveagentsname)
for how and when that subvolume is created). Consequences:
- The Claude session (`claude/`) travels, so a restored agent keeps its

View file

@ -178,7 +178,7 @@ durable knowledge here (`notes.md`, anything else) — the harness's own
internal files (`hyperhive-events.sqlite`, `hyperhive-turn-stats.sqlite`,
`hyperhive-model`) live in the separate `harness` dir instead, so they
don't clutter what claude sees as "my notes dir" (see
[`docs/persistence.md`](persistence.md)). Survives `destroy`/recreate
[`docs/agent-lifecycle/persistence.md`](../agent-lifecycle/persistence.md)). Survives `destroy`/recreate
alongside the claude dir.
## Networking & ports
@ -361,7 +361,7 @@ own self-consistent object store and is immune to the issue.
### `hive-forge`: prefer over raw curl pipelines
Full CLI reference: [`docs/tools/forge.md`](tools/forge.md).
Full CLI reference: [`docs/tools/forge.md`](../tools/forge.md).
Never use raw `curl` for forge access.
## GUI (weston/VNC)

View file

@ -32,7 +32,7 @@ using the same engine for its own unrelated queue.
## Watching it happen
Each **row** you see in a queue view (the **BU1LDS** page's R3BU1LD QU3U3
— see [`web-ui/dashboard.md`](web-ui/dashboard.md) — and swarm-ui's
— see [`web-ui/dashboard.md`](../web-ui/dashboard.md) — and swarm-ui's
`/jobs` page both render the same underlying graph) is one job; the rows
nested under it are that job's steps, in order (occasionally a couple run
side by side). A step shows one of:

View file

@ -20,13 +20,13 @@ services.hyperhive.otel = {
leaves the swarm — optional, because the swarm's own metrics store
(`deploy.victoriametrics`) is a destination in its own right. With both,
telemetry goes to both. See
[`swarm/services.md`](swarm/services.md#metrics-victoriametrics--grafana).
[`swarm/services.md`](../swarm/services.md#metrics-victoriametrics--grafana).
**There is exactly one way telemetry leaves a hive: 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
([`swarm/services.md`](swarm/services.md#telemetry-collector-otel)), which is
([`swarm/services.md`](../swarm/services.md#telemetry-collector-otel)), which is
the single process holding the upstream credential and the only writer to the
swarm's store. No agent holds a copy, and neither does this hive.

View file

@ -75,7 +75,7 @@ domains can share one. `hiveName` surfaces in the same places but is
distinction — one names this hive, the other names the group it belongs
to.
See `docs/conventions.md` § Hive identity for the env-var chain
See `docs/process/conventions.md` § Hive identity for the env-var chain
and `qualify()` / `qualified_label()` semantics.
## Swarm CA
@ -166,7 +166,7 @@ evaluates cleanly points at a real machine that isn't the one you meant.
key must never enter the store), so there is no build-time name for
it. Bridging that needs a runtime mechanism and is tracked as its own
issue. Until then, federation needs CA-issued certs (ACME). See
`docs/matrix.md` for federation firewall + TLS requirements.
`docs/integrations/matrix.md` for federation firewall + TLS requirements.
3. **WireGuard mesh** (optional) — `swarm.wireguard.enable` reads each
entry's `wireguardPublicKey`/`wireguardEndpoint`/`wireguardAddress`
@ -279,7 +279,7 @@ port}` tells this hive where the swarm's `btrfs receive` endpoint is, so
It is genuinely swarm-scoped rather than per-peer — a swarm has exactly
one store, because the receiver keys destinations by *agent* so a
migrating agent keeps one unbroken incremental chain. See
[snapshot-store.md](../snapshot-store.md).
[snapshot-store.md](../networking/snapshot-store.md).
## Swarm controller
@ -385,7 +385,7 @@ Nothing to configure. The hooks are registered only when this host also
serves the swarm UI vhost — that is what publishes the endpoint, and a
hook the forge cannot reach would collect failed deliveries while
looking healthy. The HMAC secret is generated on first start and kept
(see [`docs/persistence.md`](../persistence.md)).
(see [`docs/agent-lifecycle/persistence.md`](../agent-lifecycle/persistence.md)).
To check it is working, push to `internal/knowledge` and look for
`webhook: verified delivery` in `journalctl -u swarm-controller`. A
@ -393,13 +393,13 @@ refused delivery logs `webhook: refused delivery` with the reason.
## Cross-references
- `docs/snapshot-store.md` — the swarm's `btrfs receive` endpoint, and
- `docs/networking/snapshot-store.md` — the swarm's `btrfs receive` endpoint, and
the `swarm.snapshotStore` option that points a hive at it
- `docs/conventions.md` § Hive identity — env vars, qualified labels
- `docs/matrix.md` — matrix federation, TLS cert auto-generation,
- `docs/process/conventions.md` § Hive identity — env vars, qualified labels
- `docs/integrations/matrix.md` — matrix federation, TLS cert auto-generation,
firewall posture
- `docs/swarm/ui.md` — the swarm-wide hive roster, now the operator
surface for "what hives exist" (superseded the per-hive dashboard's
old "peer hives" display)
- `docs/gateway.md` — nginx vhosts and the `.well-known/matrix/`
- `docs/networking/gateway.md` — nginx vhosts and the `.well-known/matrix/`
auto-discovery scheme

View file

@ -183,5 +183,5 @@ refused at eval — a tier that receives samples and drops them looks
healthy while losing data.
Agent-side configuration, and what a hive's own collector does, are in
[`../observability.md`](../observability.md).
[`../scheduler/observability.md`](../scheduler/observability.md).

View file

@ -16,7 +16,7 @@ authelia binds loopback only. The **gateway** on the host running it
publishes it as `auth.<swarm.domain>` — vhost, dnsmasq record and TLS
name all follow `deploy.authelia`, so there is nothing to turn on
separately. (Details, including why a client hive must not declare that
vhost: [`../gateway.md`](../gateway.md).)
vhost: [`../networking/gateway.md`](../networking/gateway.md).)
**Authelia does not start until at least one user exists.** The user
store is generated empty — deliberately, since seeding a default account

View file

@ -38,7 +38,7 @@ fine and still gets bounced.
swarmctl user add <you> --group admins
```
`admins` deliberately, not a new word: [`../setup.md`](../setup.md) has
`admins` deliberately, not a new word: [`../getting-started/setup.md`](../getting-started/setup.md) has
told every operator to create exactly that group since the bootstrap step
existed, so an account made by following the guide already passes. This
is the first rule that *consumes* a group name — inventing a second one
@ -111,4 +111,4 @@ popover.
## Cross-references
- [`sso.md`](sso.md) — the authelia instance itself, and the user store.
- [`../gateway.md`](../gateway.md) — the full vhost map and TLS modes.
- [`../networking/gateway.md`](../networking/gateway.md) — the full vhost map and TLS modes.

View file

@ -6,7 +6,7 @@ admin socket `/run/hyperhive/host.sock`, which is root-only by default —
so it needs `sudo` unless you grant sudoless access by listing your
login user in `services.hyperhive.c0re.adminUsers` (adds you to the
`hive-admin` group that owns the socket; see
[`docs/boundary.md`](../boundary.md#host-admin-socket-access-hivectl)).
[`docs/trust-boundary/boundary.md`](../trust-boundary/boundary.md#host-admin-socket-access-hivectl)).
Available via the `hive-c0re` package in the host NixOS config.
Unlike the `hive-c0re` daemon subcommands (which go through the broker),
@ -96,7 +96,7 @@ Write an operator-supplied GitHub personal access token (PAT) into an
agent's token file so its `gh` wrapper + git credential helper can act as
the bot account. Unlike forge/matrix there is no account creation — the PAT
is for an existing GitHub account. A CLI alternative to the dashboard
credentials tab; the [GitHub integration](../github.md) is on by default
credentials tab; the [GitHub integration](../integrations/github.md) is on by default
(`hyperhive.github.enable`), so no per-agent config is needed.
```bash
@ -108,7 +108,7 @@ hivectl github set-token damocles --token <pat> # inline (visible in shell hi
hive-priv — the same credential-injection path as forge/matrix tokens.
The `gh` wrapper / git credential helper read it live, so a freshly-set
or rotated PAT takes effect with no rebuild or restart. Refuses an empty
token. See [github.md](../github.md) for the full flow + security notes.
token. See [github.md](../integrations/github.md) for the full flow + security notes.
## Gateway
@ -193,7 +193,7 @@ hivectl agent sock set-limits --reset # drop all overrides
Overrides the hive-wide `services.hyperhive.agentCpuQuota` /
`agentMemoryMax` for one agent, persisted to
`meta/resource-limits.json` (see
[`persistence.md`](../persistence.md)). Values are systemd's
[`persistence.md`](../agent-lifecycle/persistence.md)). Values are systemd's
`CPUQuota=` / `MemoryMax=` syntax: a percentage (`400%` = four full
cores) for CPU; a size (`8G`), a percentage of physical RAM, or
`infinity` for memory. Both are validated before they're persisted —
@ -276,7 +276,7 @@ hivectl agent iris watch # tail iris's live events; Ctrl-C to stop
Dials the same unix socket the gateway's nginx `proxy_pass`es through
(`/run/hive-agent/<name>/web.sock` — see [Per-agent unix-socket
upstream](../gateway.md#per-agent-unix-socket-upstream)) directly and
upstream](../networking/gateway.md#per-agent-unix-socket-upstream)) directly and
speaks a bare HTTP/1.1 request for the agent's existing `/events/stream`
SSE endpoint over it. No gateway hop, no daemon round-trip for the
stream itself — the daemon socket is only used for the "does this agent
@ -289,7 +289,7 @@ Prints one compact line per event — reuses the `_icon`/`_summary` fields
the harness already stamps onto stream-json events for the web UI
(`stream_enrich.rs`), so tool calls and turn markers read as short
glyph-prefixed lines instead of raw JSON. Not an attempt at the web
UI's full collapsible-details rendering (`docs/terminal-rendering.md`)
UI's full collapsible-details rendering (`docs/web-ui/terminal-rendering.md`)
— that's presentation for a browser, this is a `tail -f`.
## Open

View file

@ -52,7 +52,7 @@ which creates the container from that config.
Subsequent config changes go through a **forge PR** on the agent's
`agent-configs/<name>` repo (queues a `MergeConfigPr` approval on
open/update — no MCP tool involved), not a tool call. See
`docs/approvals.md`.
`docs/agent-lifecycle/approvals.md`.
Fails if a proposed config repo for `name` already exists.
`name` is ≤ 9 characters.
@ -78,6 +78,6 @@ agents after the approval resolves.
## See also
- [`docs/approvals.md`](../approvals.md) — full approval flow, kinds,
- [`docs/agent-lifecycle/approvals.md`](../agent-lifecycle/approvals.md) — full approval flow, kinds,
helper events (`config_ready`, `approval_resolved`), flake.lock
validation.

View file

@ -130,7 +130,7 @@ pending invites, resolve_invite to accept or reject`. The keyed todo
is cleared when a `resolve_invite` (or `join_room`) call resolves the
invite.
See [`docs/matrix.md`](../matrix.md) for the homeserver setup,
See [`docs/integrations/matrix.md`](../integrations/matrix.md) for the homeserver setup,
provisioning flow, and federation config.
## Extra MCP servers (per-agent)

View file

@ -92,5 +92,5 @@ lifecycle events, or another container's boot log.
- `remind` (no-approval self-wake path) — documented in
[`docs/turn-loop/`](../turn-loop/README.md).
- [`docs/approvals.md`](../approvals.md) — approval flow for
- [`docs/agent-lifecycle/approvals.md`](../agent-lifecycle/approvals.md) — approval flow for
`request_schedule_prompt`.

View file

@ -77,7 +77,7 @@ The boundary rests on three layers:
3. **Privsep** — defence in depth on the core process; `hive-c0re`
runs as the unprivileged `hive-core` user and delegates root
operations to `hive-priv`, a narrow socket-activated helper. See
[`docs/security.md`](security.md) for the privilege boundary table.
[`docs/trust-boundary/security.md`](security.md) for the privilege boundary table.
### hive-priv socket activation
@ -130,7 +130,7 @@ them and the rest of the host. That costs no network isolation: nginx
binds the host's `:80`/`:443` and reaches `localhost` upstreams, which a
netns would have to be opened up for anyway.
🔑 It does mean nothing *implicitly* scopes the privileged reload verb —
see [`docs/security.md`](security.md#hive-c0re-privilege-separation) for
see [`docs/trust-boundary/security.md`](security.md#hive-c0re-privilege-separation) for
how `PrivRequest::ReloadGatewayNginx`'s containment works.
⚠️ Contrast `/shared`, which *is* sticky world-writable (`1777`): it has

View file

@ -71,7 +71,7 @@ checkpoints**, not about sandboxing the agent from its own tools:
technical control.
- **Approvals** — config changes, schedule additions, and other
blast-radius-y operations route through the operator approval queue
(see [`approvals.md`](approvals.md)).
(see [`approvals.md`](../agent-lifecycle/approvals.md)).
### Capability = accepted risk
@ -158,7 +158,7 @@ The blast radius also has a **network** dimension. hive-ci runs its unsandboxed
builds of untrusted PR code in its own private netns behind the hive bridge: a
build reaches the forge only through the gateway and cannot reach host-loopback
services — including the core dashboard at `127.0.0.1:<dashboard_port>`, which
has no application-layer auth of its own (see [`docs/ci.md`](ci.md)). The `0600`
has no application-layer auth of its own (see [`docs/scheduler/ci.md`](../scheduler/ci.md)). The `0600`
token policy bounds file reads; network isolation bounds network reach.
**What is NOT exposed**:

View file

@ -14,7 +14,7 @@ agents) runs:
queued and unacked, so a resume drains the backlog instead of
losing it; reminders and todo wakes buffer in their channels. Set
it with `hivectl agent <name> pause` or the dashboard toggle; see
[persistence](../persistence.md#-harnesspaused-per-agent).
[persistence](../agent-lifecycle/persistence.md#-harnesspaused-per-agent).
1. Long-poll `Recv` on its socket. The host-side broker
(`broker.rs::recv_blocking_batch`) returns immediately if there's
a pending message, otherwise waits up to 30 s for a broker `Sent`
@ -91,21 +91,21 @@ doc for the trait shape.
`serve_main` reads `HIVE_PORT` (default `DEFAULT_WEB_PORT`) +
`HIVE_LABEL` (default `"hive"` for standalone runs; the meta
flake sets it unconditionally for any container-deployed agent;
see `docs/conventions.md::Hive identity` for the env stack),
see `docs/process/conventions.md::Hive identity` for the env stack),
opens turn-stats sqlite, prepares the on-boot files (see
[claude-invocation](claude-invocation.md#on-boot-files)),
installs claude plugins, spawns `web_ui::serve` + `vacuum::run`,
and either drops into `serve_loop` directly (`Online`) or parks on
the login flow first (`NeedsLogin`). Forge notifications are polled by
their own process, not this loop — see `hive-forge-notify` in
[`forge.md`](../forge.md).
[`forge.md`](../integrations/forge.md).
Boot also opens the todos store and the socket in-container producers
dial. Matrix / bash / forge-notify daemons and the in-process
`disk_watch` todo producer (low state-disk space) are the built-in
producers, but the socket accepts any `subsystem` marker — a
user-configured MCP server can push its own todos the same way. See
[`docs/persistence.md`](../persistence.md#state-dirs-per-agent) for
[`docs/agent-lifecycle/persistence.md`](../agent-lifecycle/persistence.md#state-dirs-per-agent) for
what each built-in todo producer watches and how the store + `get_loose_ends`
merge work.

View file

@ -31,7 +31,7 @@ this package moves fast, the operator can pin one hive-wide with
into each agent's flake, and `claude` on PATH becomes a symlink to it
instead of the container's own `claude-code` — so there's only ever one
`claude` in the container. Agents pick up a new build on their
next rebuild, not live. See docs/gotchas.md::`claude-code` is unfree.
next rebuild, not live. See docs/process/gotchas.md::`claude-code` is unfree.
Hive-enforced settings ship at `/etc/claude-code/managed-settings.json`
(claude-code's canonical managed-settings path — precedence #1,

View file

@ -59,7 +59,7 @@ sudo rejection rather than silently succeeding — easier to audit.
`hyperhive.user.uid`, `hyperhive.user.gid`, and
`hyperhive.user.name` are the companion options; see
`docs/agent-hierarchy.md` — "Harness systemd unit shape" for the full
`docs/agent-lifecycle/agent-hierarchy.md` — "Harness systemd unit shape" for the full
`user.*` surface.
## Dashboard links
@ -284,7 +284,7 @@ KEYS
The file lives in the agent's bind-mounted **harness** dir, not `state/`
`harness/` survives container rebuilds exactly like `state/` does, but
(unlike `state/`) is never bind-mounted into a parent agent's container
(see `docs/persistence.md`'s "Parent access to child state"), so an API
(see `docs/agent-lifecycle/persistence.md`'s "Parent access to child state"), so an API
key placed here is reachable by nothing but this agent and the host.
⚠️ Verified end-to-end against OpenRouter has not happened as of this

View file

@ -70,7 +70,7 @@ which still wakes a turn (the todo-wake path — see [Turn
outcomes](README.md#turn-outcomes)) but via a generic "call
`get_loose_ends`" prompt rather than the event body itself. Full
payload shapes and routing logic in
[`docs/approvals.md` § Helper events](../approvals.md#helper-events-to-the-submitting-agent).
[`docs/agent-lifecycle/approvals.md` § Helper events](../agent-lifecycle/approvals.md#helper-events-to-the-submitting-agent).
**Inbox** (`inbox` group): `get_loose_ends(agent?)`,
`cancel_loose_end(kind, id)`, `remind(message, delay_seconds? |
@ -148,7 +148,7 @@ already in flight.
- **Capability-gated**`get_host_journal` (requires
`read_host_journal` capability set via the P3RM1SS10NS tab;
orthogonal to tool groups). Full list of capabilities and their
effects in [`docs/conventions.md#capabilities`](../conventions.md).
effects in [`docs/process/conventions.md#capabilities`](../process/conventions.md).
Also documented in [`docs/tools/scheduling.md`](../tools/scheduling.md).
- **Matrix MCP + extra servers**`mcp__matrix__*` tools and
per-agent extra MCP config. See

View file

@ -3,7 +3,7 @@
Two web surfaces share the same skeleton: the dashboard (port 7000)
and the per-agent UIs (each container's port is a deterministic hash
in :8100-8999 — see
[`gotchas.md#web-ui-ports-collide-on-hash`](gotchas.md#web-ui-ports-collide-on-hash)
[`gotchas.md#web-ui-ports-collide-on-hash`](process/gotchas.md#web-ui-ports-collide-on-hash)
for the mechanics and the collision caveat).
Both are SPAs — `GET /` returns a static shell, `/api/state`
returns JSON, JS renders. No full-page reloads.
@ -47,7 +47,7 @@ dashboard rather than how it's built, start at
[`web-ui/dashboard.md`](web-ui/dashboard.md) (Dashboard
endpoints, Dashboard event channel).
- **"How does the per-agent terminal render tool calls?"** →
[`terminal-rendering.md`](terminal-rendering.md) (full row
[`terminal-rendering.md`](web-ui/terminal-rendering.md) (full row
taxonomy + dispatch walkthrough); for a high-level summary see
[`web-ui/agent.md`](web-ui/agent.md) (Per-stream rendering).
- **"What slash commands does the agent accept?"** →

View file

@ -200,7 +200,7 @@ new" pill are the shared terminal-pane mechanics described in
[Shape](shape.md#shared-terminal-pane) — this page's log is one
instance of that same factory.
Per-stream rendering (see [`docs/terminal-rendering.md`](../terminal-rendering.md) for
Per-stream rendering (see [`docs/web-ui/terminal-rendering.md`](terminal-rendering.md) for
the full row taxonomy and dispatch logic):
- `Stream` `tool_use`

View file

@ -175,7 +175,7 @@ rides the same row but is fed by a separate ~5 min background `du` sampler
last-sampled value (`null` until the first sample). Network is intentionally
omitted — agents share the host netns, so there is no per-container net
counter (per-agent network needs the netns-isolation roadmap in
`docs/network.md`).
`docs/networking/network.md`).
Hive infrastructure services (`hive-ci`, `hive-forge`, `hive-gateway`,
`hive-matrix`) have no dashboard panel — `hivectl stop`/`start`/`restart`
@ -362,7 +362,7 @@ account appears only once it has been provisioned through the form.
### GITHUB tab
Provision a single per-agent GitHub personal access token (see
[`docs/github.md`](../github.md) for the injection + `gh`/git-push
[`docs/integrations/github.md`](../integrations/github.md) for the injection + `gh`/git-push
mechanics). No login flow — the operator pastes an existing PAT for a
dedicated bot account, with a security-warning banner (dedicated account +
minimally-scoped token) and a link to
@ -640,7 +640,7 @@ the matrix module exposes).
The unified nginx-front re-root to
`https://chat.${hyperhive.swarm.domain}` + `.well-known/matrix/client`
auto-discovery lives in `docs/gateway.md` (atlas's lane).
auto-discovery lives in `docs/networking/gateway.md` (atlas's lane).
## FL0W page (`/flow.html`)
@ -782,7 +782,7 @@ front (`StateSnapshot.gateway_enabled`, sourced from the
base URL is `/agent/<name>` (same origin, gateway proxies to the
per-agent harness — TCP or unix-domain depending on the agent's
`HIVE_WEB_SOCKET` opt-in, see
`docs/gateway.md::Per-agent unix-socket upstream`).
`docs/networking/gateway.md::Per-agent unix-socket upstream`).
Gateway-off (legacy / local dev): base URL is
`http://<host>:<container.port>` (direct TCP fallback). Forge
links resolve against `state.forge_public_url` (sourced from

View file

@ -301,8 +301,8 @@ The per-agent UI optionally binds a `UnixListener` instead of
TCP when `HIVE_WEB_SOCKET` is set — the unix-socket transition
mechanics (per-agent `/run/hive-agent/<name>/` bind-mount,
`.bound` marker filtering, `agent-sockets.json` consumer on the
gateway side) live in [`docs/gateway.md::Per-agent unix-socket
upstream`](gateway.md). The env var is opt-in per agent so the
gateway side) live in [`docs/networking/gateway.md::Per-agent unix-socket
upstream`](../networking/gateway.md). The env var is opt-in per agent so the
two modes coexist while sub-agents transition.
## Per-agent relative paths