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

@ -10,12 +10,12 @@ when you need depth on a subsystem. This file is the index.
the env var — inside an agent container `localhost` is the *agent*, not
the forge, so a hardcoded loopback address fails to connect.
- Operator/agent trust-boundary design:
**[docs/boundary.md](docs/boundary.md)** (`area/ops` issues
**[docs/trust-boundary/boundary.md](docs/trust-boundary/boundary.md)** (`area/ops` issues
for the deployment/gateway/privsep work — the separator is a **slash**,
and `--label area:ops` now fails with `did you mean "area/ops"?`).
- Agent trust model (trust boundary, prompt-injection threat model,
capability = accepted risk), credential isolation + sandbox threat model:
**[docs/security.md](docs/security.md)**.
**[docs/trust-boundary/security.md](docs/trust-boundary/security.md)**.
## Repo map
@ -38,7 +38,7 @@ hand-maintained per-file tree drifts out of sync with the code.
- **`hivectl/`** — standalone operator CLI (`hivectl` binary). Talks to
the `hive-c0re` daemon over the host admin socket (`hive-host-sock`
wire types) — does NOT link `hive-c0re`. Full, always-current verb
reference (CI-enforced against the clap tree, see `docs/conventions.md`):
reference (CI-enforced against the clap tree, see `docs/process/conventions.md`):
[`docs/tools/hivectl-cli.md`](docs/tools/hivectl-cli.md).
- **`hive-agent/`**, **`hive-agent-mcp/`** —
in-container harness, two sibling crates for every agent (not a
@ -80,7 +80,7 @@ hand-maintained per-file tree drifts out of sync with the code.
- **`hive-priv/`** — minimal root privileged-helper, socket-activated at
`/run/hive/priv.sock`; performs the few root operations (bind-mount
edits, nsenter) the unprivileged `hive-c0re` delegates to it. See
`docs/boundary.md`.
`docs/trust-boundary/boundary.md`.
- **`hive-forge/`** — `hive-forge` Forgejo CLI wrapper; one module per
verb under `src/verbs/`.
- **`hive-forge-notify/`** — per-agent notification poller daemons; turns
@ -156,7 +156,7 @@ hand-maintained per-file tree drifts out of sync with the code.
written in place** — one file, shared with `swarm-authelia-bridge`; see
that crate's README for what both writers must uphold. Full, always-current
verb reference (CI-enforced against the clap tree, same pattern as
`hivectl`'s — see `docs/conventions.md`):
`hivectl`'s — see `docs/process/conventions.md`):
[`docs/tools/swarmctl-cli.md`](docs/tools/swarmctl-cli.md).
### External dependencies with no directory here
@ -191,91 +191,17 @@ hand-maintained per-file tree drifts out of sync with the code.
## Reading paths
Pick the doc that matches your task. None depend on the others —
read them à la carte.
- **"How do I bring a fresh hive online (first-run hivectl
bootstrap)?"** → [`docs/setup.md`](docs/setup.md).
- **"What does the dashboard look like?"** →
[`docs/web-ui.md`](docs/web-ui.md) (index; sub-pages:
[`shape`](docs/web-ui/shape.md),
[`dashboard`](docs/web-ui/dashboard.md),
[`agent`](docs/web-ui/agent.md)).
- **"How does the per-agent terminal classify + colour
events?"** → [`docs/terminal-rendering.md`](docs/terminal-rendering.md).
- **"How does claude get its prompt and what tools does it have?"** →
[`docs/turn-loop/`](docs/turn-loop/README.md) (index: the loop, binary shape,
turn outcomes; sub-pages:
[`claude-invocation`](docs/turn-loop/claude-invocation.md),
[`config`](docs/turn-loop/config.md), [`mcp`](docs/turn-loop/mcp.md)).
- **"How do config changes flow from manager to operator to
container?"** → [`docs/approvals.md`](docs/approvals.md).
- **"What state survives destroy / purge / restart?"** →
[`docs/persistence.md`](docs/persistence.md).
- **"Naming, commit style, wire protocol, the `data-async`
pattern."** → [`docs/conventions.md`](docs/conventions.md).
- **"Why does the nspawn flag look like that?"** →
[`docs/gotchas.md`](docs/gotchas.md).
- **"What nginx vhosts does the gateway serve? How does matrix
discovery work?"** → [`docs/gateway.md`](docs/gateway.md).
- **"How do per-agent forge accounts work? What does forge_notify
poll + how does it format wake messages?"** →
[`docs/forge.md`](docs/forge.md).
- **"What verbs does `hive-forge` support? How do I post a comment,
upload an attachment, manage subscriptions?"** →
[`docs/tools/forge.md`](docs/tools/forge.md).
- **"What does `hivectl` do? How do I provision a forge/matrix account,
manage gateway users, restart containers, or drop into an agent shell?"** →
[`docs/tools/hivectl.md`](docs/tools/hivectl.md).
- **"How does the matrix-tuwunel container work? What about
fluffychat-web and per-agent matrix accounts?"** →
[`docs/matrix.md`](docs/matrix.md).
- **"How do I give an agent a GitHub account (`gh` + `git push`)?
How is the PAT injected?"** → [`docs/github.md`](docs/github.md).
- **"How does DNS resolution work in agent containers? What's the
bridge network for?"** → [`docs/network.md`](docs/network.md).
- **"How do I connect two hives into a swarm? How do I declare peer
hives and configure TLS trust?"** →
[`docs/swarm/`](docs/swarm/README.md).
- **"Which deployment shapes are supported — all-local, services on the
controller host, fully spread out?"** →
[`docs/swarm/services.md`](docs/swarm/services.md#deployment-shapes).
- **"Where do agent snapshots go? How does the swarm's `btrfs receive`
endpoint authenticate a pushing hive, and what does a snapshot
actually contain?"** →
[`docs/snapshot-store.md`](docs/snapshot-store.md).
- **"What is the job queue, as a general idea?"** →
[`docs/jobq.md`](docs/jobq.md) (operator-facing, no hive-c0re specifics).
- **"How does the rebuild queue work? What are the concrete step kinds,
queue sources, scheduler internals?"** →
[`docs/coordinator.md`](docs/coordinator.md).
- **"How does the CI runner work? What's the auto-registration flow?"** →
[`docs/ci.md`](docs/ci.md).
- **"What is `/knowledge`? How does the hive-wide knowledge repo sync,
and how do I contribute a document?"** →
[`docs/knowledge.md`](docs/knowledge.md).
- **"How do I export Claude Code metrics (tokens, cost, tool calls) to
a Prometheus/Grafana collector? What OTEL options are available?"** →
[`docs/observability.md`](docs/observability.md).
- **"What does a PR review verdict actually gate? Should a reviewer
wait for CI before approving? What does arming auto-merge commit an
operator to?"** → [`docs/pr-review-gate.md`](docs/pr-review-gate.md).
**[`docs/README.md`](docs/README.md) is the single index** — grouped by topic
(getting started, agent lifecycle, trust boundary, integrations, networking,
scheduler, process). Read it instead of a second copy here; this file used to
carry its own parallel "Reading paths" list, and the two drifted out of sync
with each other.
## Conventions & process
The docs below own the details — this section just points at them.
- **Commit style, naming, identity, reconcile verb:**
[`docs/conventions.md`](docs/conventions.md).
- **Never add `#[allow(clippy::…)]`** — fix the lint instead (extract a
helper, add backticks, etc.). Details + worked examples: →
[`docs/conventions.md`](docs/conventions.md#building--local-checks).
- **NixOS / nspawn quirks** (bind mounts, conf flags, etc.): →
[`docs/gotchas.md`](docs/gotchas.md).
- **Turn loop, sentinels (rate-limit, auth-failed), context
window:** → [`docs/turn-loop/`](docs/turn-loop/README.md).
- **Two-step spawn, approval flow, flake.lock validation:**
[`docs/approvals.md`](docs/approvals.md).
[`docs/process/conventions.md`](docs/process/conventions.md#building--local-checks).
- **Pre-push lint hook** (catches tracker-tag and comment-block failures
before CI does — install once per clone):
`ln -sf ../../scripts/pre-push .git/hooks/pre-push`

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

View file

@ -321,7 +321,7 @@ pub(super) struct MatrixRoomUnread {
/// on (`hyperhive.mcp.matrixHttpPort`'s nix default). Overridable via
/// `HIVE_MATRIX_HTTP_PORT` for parity with the port options nix already
/// exposes; unset in practice since a single fixed port is safe (each
/// agent container is its own network namespace — see docs/network.md).
/// agent container is its own network namespace — see docs/networking/network.md).
const DEFAULT_MATRIX_HTTP_PORT: u16 = 8792;
/// Short request timeout for the local status query below — this must

View file

@ -69,7 +69,7 @@ pub(crate) fn persist_effort(level: &str) -> std::io::Result<()> {
// (`hyperhive-rate-limited`, `hyperhive-needs-login`) that grew organically
// and had no shared schema. A single JSON file is self-documenting, atomic
// to write, and cheaper for hive-c0re to read on each sweep (one fopen vs
// two stat calls). See `docs/persistence.md::Harness state files`.
// two stat calls). See `docs/agent-lifecycle/persistence.md::Harness state files`.
//
// Legacy sentinel files written by older harness builds are still honoured
// by `read_harness_state` so in-place upgrades don't lose state (the new

View file

@ -1,7 +1,7 @@
//! Agent identity helpers — short label + hive-qualified long name +
//! human display names for the hive and swarm. Full env var surface +
//! domain-vs-name distinction documented in
//! `docs/conventions.md::Hive identity (label + domain + display names)`.
//! `docs/process/conventions.md::Hive identity (label + domain + display names)`.
use std::env;

View file

@ -37,7 +37,7 @@ pub const ALLOWED_BUILTIN_TOOLS: &[&str] = &["Edit", "Glob", "Grep", "Read", "Sk
/// Env var written by the meta renderer with a comma-separated list of
/// `hive_sh4re::permissions::ToolGroup` `snake_case` names (e.g. `"messaging,inbox,meta"`).
/// When present, the harness expands the groups into per-tool allow entries
/// instead of using the hardcoded flavor default. See `docs/conventions.md::Tool groups`.
/// instead of using the hardcoded flavor default. See `docs/process/conventions.md::Tool groups`.
const TOOL_GROUPS_ENV: &str = "HIVE_TOOL_GROUPS";
/// `HIVE_CAPABILITIES` env var injected by `meta::render_flake` when the
@ -164,7 +164,7 @@ mod extra_server_gate_tests {
/// MCP tools claude is allowed to call without prompting, derived from
/// the supplied tool groups. Adding a new `#[tool]` fn to a server impl
/// requires updating the matching `ToolGroup::tools()` slice in hive-sh4re
/// (single source of truth). See `docs/conventions.md::Tool groups`.
/// (single source of truth). See `docs/process/conventions.md::Tool groups`.
#[must_use]
pub fn allowed_mcp_tools(groups: &[hive_sh4re::permissions::ToolGroup]) -> Vec<String> {
// Collect all tool names, deduplicating while preserving order.

View file

@ -1,6 +1,6 @@
//! Per-turn OTEL metric export for the fields Claude Code's own built-in
//! OTEL integration cannot know about. Claude's own export already covers
//! token usage, cost, and tool-call counts (see `docs/observability.md`) —
//! token usage, cost, and tool-call counts (see `docs/scheduler/observability.md`) —
//! duplicating those here under different metric names would just give a
//! collector two disagreeing series for the same number. This module emits
//! only the harness-only concepts: wall-clock turn duration as *this harness*
@ -149,7 +149,7 @@ fn build_provider(interval: Duration) -> anyhow::Result<SdkMeterProvider> {
// OTLP client is required — `PeriodicReader` drives export from a
// background thread with no Tokio reactor), and same rationale as
// `hive-metric` for the resource (the SDK reads `OTEL_RESOURCE_ATTRIBUTES`
// — set container-wide, see `docs/observability.md::Built-in resource
// — set container-wide, see `docs/scheduler/observability.md::Built-in resource
// labels` — on its own; passing an explicit `Resource` here would need to
// duplicate `agent`/`hive`/`swarm` this crate has no other reason to know).
let exporter = MetricExporter::builder()

View file

@ -7,7 +7,7 @@
//! Claude credentials live at `$HOME/.claude` (resolves to
//! `/home/<agent-name>/.claude` because the harness service runs as a
//! non-root unix user matching the agent label — see
//! `docs/persistence.md::First-boot agent-user migration`).
//! `docs/agent-lifecycle/persistence.md::First-boot agent-user migration`).
//!
//! All three paths can be overridden via env vars (`HYPERHIVE_STATE_DIR`,
//! `HYPERHIVE_HARNESS_DIR`, `HYPERHIVE_CLAUDE_DIR`) for dev / test scenarios.

View file

@ -11,7 +11,7 @@
//! Trade-off (accepted — issue tracker "perms borked"): a misbehaving harness
//! could skip its own cleanup, which the host-side version was meant to
//! prevent. But a compromised harness is already inside the container trust
//! boundary (`docs/security.md`), and these are ephemeral local artifacts — so
//! boundary (`docs/trust-boundary/security.md`), and these are ephemeral local artifacts — so
//! the honest fix is to clean them up where they live.
use std::path::Path;

View file

@ -72,7 +72,7 @@ struct AppState {
/// `HIVE_WEB_SOCKET` opt-in selects unix-socket vs TCP binding; the
/// dual-mode transition + gateway-side consumer live in
/// [`docs/web-ui/shape.md::Listener bind`](../../../docs/web-ui/shape.md) and
/// [`docs/gateway.md::Per-agent unix-socket upstream`](../../../docs/gateway.md).
/// [`docs/networking/gateway.md::Per-agent unix-socket upstream`](../../../docs/networking/gateway.md).
///
/// # Errors
///
@ -146,7 +146,7 @@ pub async fn serve(
// `UnixListener` at the given path. Empty string treated as
// unset so a stray `HIVE_WEB_SOCKET=` doesn't trap us into an
// un-bindable empty path. Falls through to the TCP path below
// otherwise. See docs/gateway.md::Per-agent unix-socket upstream
// otherwise. See docs/networking/gateway.md::Per-agent unix-socket upstream
// for the gateway-side consumer.
if let Some(socket_path) = std::env::var_os("HIVE_WEB_SOCKET")
&& !socket_path.is_empty()
@ -176,7 +176,7 @@ pub async fn serve(
/// to ask.
///
/// Marker-gating + the gateway-side consumer: see
/// [`docs/gateway.md::Per-agent unix-socket upstream`](../../../docs/gateway.md).
/// [`docs/networking/gateway.md::Per-agent unix-socket upstream`](../../../docs/networking/gateway.md).
fn bind_unix(path: &Path) -> Result<tokio::net::UnixListener> {
use std::os::unix::fs::PermissionsExt;
if let Some(parent) = path.parent() {

View file

@ -26,7 +26,7 @@ to track it:
- **`dashboard/`** — the operator dashboard (containers, approvals,
schedules, logs, topology).
- **`job_queue/`** — the job-DAG queue + desired-state reconciliation
(`docs/coordinator.md`).
(`docs/scheduler/coordinator.md`).
- **`lifecycle/`** — `nixos-container` lifecycle + per-agent config
flake generation.
- **`stores/`** — sqlite-backed stores (broker, queues, audit, power).
@ -37,7 +37,7 @@ to track it:
- **`stats/`** — dashboard metrics aggregation + OTEL export.
- **`socket_server/`** — the unix-socket request server shared by
per-agent + manager sockets.
- **`forge/`** — optional Forgejo wiring (`docs/forge.md`).
- **`forge/`** — optional Forgejo wiring (`docs/integrations/forge.md`).
- **`coordinator.rs`** — top-level wiring for `serve`.
- **`meta.rs`**, **`migrate.rs`** — the meta flake + schema/state
migrations.

View file

@ -9,9 +9,9 @@
//! `lifecycle::set_nspawn_flags` for the consumer.
//!
//! Format, rationale, read/reconcile/inject/surface flow, and target
//! enforcement semantics: `docs/agent-hierarchy.md::Current state`.
//! enforcement semantics: `docs/agent-lifecycle/agent-hierarchy.md::Current state`.
//! `<parent>` sentinel resolution (delivered by [`resolve_recipient`]):
//! `docs/conventions.md::Recipient sentinels`.
//! `docs/process/conventions.md::Recipient sentinels`.
//!
//! ## Graph representation
//!
@ -109,7 +109,7 @@ pub fn top_level_agents_in(topo: &BTreeMap<String, Option<String>>) -> Vec<Strin
/// straight into [`crate::broker::Broker::send`] without
/// borrow-juggling around the temporary lookup.
///
/// Rules + rationale: `docs/conventions.md::Recipient sentinels`.
/// Rules + rationale: `docs/process/conventions.md::Recipient sentinels`.
/// Fast path: ordinary recipient names short-circuit before any
/// disk read — only `<parent>` triggers `read()` on `topology.json`.
#[must_use]
@ -262,7 +262,7 @@ pub fn default_seed(agent_names: &[String]) -> BTreeMap<String, Option<String>>
/// powers come from the privileged MCP socket, not its tree
/// position. The cycle walk below covers "moving X under its own
/// descendant" for the manager as much as any other agent.
/// `docs/agent-hierarchy.md::Current state` has the rationale.
/// `docs/agent-lifecycle/agent-hierarchy.md::Current state` has the rationale.
pub fn apply_set_parent(
topo: &BTreeMap<String, Option<String>>,
child: &str,

View file

@ -53,7 +53,7 @@ pub struct ContainerView {
/// indentation. Sourced from `meta/topology.json` (single source of
/// truth, hive-c0re-owned) — NOT from per-agent agent.nix, because
/// an agent shouldn't be able to unilaterally declare its own place
/// in the tree. See `docs/agent-hierarchy.md::Current state`.
/// in the tree. See `docs/agent-lifecycle/agent-hierarchy.md::Current state`.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub parent: Option<String>,
/// The Claude model the agent's harness is currently using, read from

View file

@ -867,7 +867,7 @@ impl Coordinator {
/// `_or_root` resolves to the literal string `"<root>"` when the
/// slot is `None`, keeping the wording consistent with the
/// `<parent>` sentinel's "root → operator" routing (see
/// `docs/conventions.md::Recipient sentinels`). The
/// `docs/process/conventions.md::Recipient sentinels`). The
/// notifications fire as ordinary broker messages with
/// `from = hive_sh4re::manager::SYSTEM_SENDER` so the dashboard renders
/// them under the existing system-source styling.

View file

@ -214,7 +214,7 @@ pub async fn serve(
webhook_secret,
});
// Binds loopback-only; external access via gateway.
// Rationale: docs/gateway.md::Firewall posture.
// Rationale: docs/networking/gateway.md::Firewall posture.
let addr = SocketAddr::from(([127, 0, 0, 1], port));
let listener = bind_with_retry(addr).await?;
tracing::info!(%addr, "dashboard listening");

View file

@ -2,7 +2,7 @@
//!
//! `GET /api/state-file?path=…` serves an allow-listed file (per-agent
//! `state/` or `shared/`) with defense-in-depth symlink + traversal checks
//! (see `docs/security.md::State-file endpoint`); raster images are served
//! (see `docs/trust-boundary/security.md::State-file endpoint`); raster images are served
//! with their real content-type, everything else as truncated text.
//! `scan_validated_paths` runs the same allow-list at broker-message ingest
//! so dashboard events carry a pre-verified file-ref set.
@ -19,7 +19,7 @@ use crate::paths::{AGENTS_ROOT, SHARED_ROOT};
#[derive(Deserialize, IntoParams)]
pub(super) struct StateFileQuery {
/// Absolute path under an agent's `state/` dir or under `shared/`;
/// checked against the allow-list (`docs/security.md::State-file
/// checked against the allow-list (`docs/trust-boundary/security.md::State-file
/// endpoint`).
path: String,
}
@ -27,7 +27,7 @@ pub(super) struct StateFileQuery {
/// Resolve a caller-supplied path against the allow-listed roots
/// (`agents/<n>/state/` and `shared/`). Applies defense-in-depth
/// symlink + traversal checks before serving. Security model and
/// all five layers: `docs/security.md::State-file endpoint`.
/// all five layers: `docs/trust-boundary/security.md::State-file endpoint`.
fn resolve_state_path(
raw: &str,
) -> std::result::Result<(std::path::PathBuf, std::fs::Metadata), String> {

View file

@ -89,7 +89,7 @@ pub(super) struct StateSnapshot {
/// runtime-generated `agents.conf` include file — see
/// `gateway_nginx.rs`). The `false` branch (direct
/// `http://<hostname>:<port>/` TCP links) is retained as a defensive
/// fallback for the env being unset. See `docs/gateway.md::Vhost map`.
/// fallback for the env being unset. See `docs/networking/gateway.md::Vhost map`.
gateway_enabled: bool,
/// Public URL of the forge (e.g.
/// `"https://forge.pr1ma.darkest.space"`). Sourced from the

View file

@ -2,7 +2,7 @@
//! config-repo mirroring, meta read-access grants. Also seeds
//! `internal/docs` — a private repo every agent gets read-only
//! collaborator access to for operator-curated shared content.
//! No-op when `hive-forge` isn't running. Full design: `docs/forge.md`.
//! No-op when `hive-forge` isn't running. Full design: `docs/integrations/forge.md`.
mod ci_runner;
pub mod config_pr_poll;

View file

@ -59,7 +59,7 @@ fn config_org_avatar_png_path() -> std::path::PathBuf {
))
}
/// Per-agent token scopes (broad-but-not-admin) for tokens hive-c0re
/// mints itself on the **internal** forge. See `docs/forge.md::Token
/// mints itself on the **internal** forge. See `docs/integrations/forge.md::Token
/// scopes` for the per-scope rationale. Not `pub(super)` — external
/// forges (`dashboard/extra_forges.rs`) take an operator-pasted token
/// verbatim, so their scope is whatever the operator's remote account
@ -70,7 +70,7 @@ const TOKEN_SCOPES: &str = "read:user,write:user,read:notification,write:notific
/// top of `TOKEN_SCOPES` so the host daemon can drive
/// `/api/v1/admin/*`. Site-admin membership alone isn't enough: the
/// token's own scope gate runs before the user-permission check.
/// See `docs/forge.md::Token scopes`.
/// See `docs/integrations/forge.md::Token scopes`.
const CORE_TOKEN_SCOPES: &str = "read:admin,write:admin,read:user,write:user,read:notification,write:notification,write:repository,write:issue,write:organization,write:misc";
/// Pull the access token out of forgejo's success message. Format
@ -242,7 +242,7 @@ pub(super) async fn ensure_user_email(name: &str) {
}
Err(e) if is_forbidden(&e) => {
// Core token missing admin scope — see
// `docs/forge.md::Token scopes` migration note.
// `docs/integrations/forge.md::Token scopes` migration note.
tracing::warn!(
%name, %email, error = %e,
"forge: PATCH user email forbidden — core token likely missing admin scope. \

View file

@ -2,7 +2,7 @@
//! `/agent/<name>/` location blocks. Writes
//! `/var/lib/hive-gateway/conf/agents.conf` on every topology change.
//! UDS upstream selection, the reload trigger, and idempotency:
//! `docs/gateway.md::Per-agent unix-socket upstream`.
//! `docs/networking/gateway.md::Per-agent unix-socket upstream`.
use anyhow::{Context, Result};
use std::fmt::Write as _;
@ -173,7 +173,7 @@ fn render(names: &[String], frontend_dir: Option<&str>) -> String {
/// It stays an explicit trigger rather than a systemd path
/// unit watching the file: the write already knows it changed something,
/// and a watcher would turn one causal edge into a race with the writer's
/// own rename (see `docs/gateway.md`).
/// own rename (see `docs/networking/gateway.md`).
///
/// The priv call is best-effort — a failed sync is logged but not fatal.
/// `reload_if_pending` retries on the next `spawn_poll` tick so a

View file

@ -24,7 +24,7 @@
//! The queue is runtime-only (no persistence): an empty graph on boot; desired
//! state is re-derived by the reconcile sweep. A single scheduler task
//! ([`scheduler::run_worker`]) drives it; concurrency comes from the build-slot
//! capacity, not multiple workers. Design: `docs/coordinator.md::Job queue`.
//! capacity, not multiple workers. Design: `docs/scheduler/coordinator.md::Job queue`.
pub mod exec;
pub mod model;

View file

@ -9,7 +9,7 @@
//! execution, build-log, cancel, and the dashboard group (a group root's
//! subtree *is* the group). A DAG used to be a second level above it, with
//! its own store and its own id; there is no container node any more, so a
//! job is exactly the nodes it declared. See `docs/coordinator.md::Job queue`
//! job is exactly the nodes it declared. See `docs/scheduler/coordinator.md::Job queue`
//! for the full design.
pub use hive_host_sock::jobs::{PermPayload, State};
@ -206,7 +206,7 @@ pub enum NodeKind {
///
/// Its children declare no resources and borrow these grants, which is what
/// lets `Prebuild` run beside the `Signal` → `Drain` window. Why braces
/// exist and what they cost: `docs/coordinator.md`, _Braces_.
/// exist and what they cost: `docs/scheduler/coordinator.md`, _Braces_.
AgentWindow { agent: String },
/// Deploy phase 1 — **verify only, mutates nothing.** Drift-gate the
/// approval's PR head, fetch it into the applied repo, and eval-verify the

View file

@ -8,12 +8,12 @@
//! **The one sanctioned exception is a brace** — a pure-resource-holder root
//! ([`NodeKind::AgentWindow`], [`NodeKind::DeployWindow`]) declaring for a
//! coordinated subtree whose members then declare nothing. See
//! `docs/coordinator.md`, _Braces_ — which also carries the per-operation DAG
//! `docs/scheduler/coordinator.md`, _Braces_ — which also carries the per-operation DAG
//! shapes, so they are not restated here.
//!
//! Nodes are **named, not counted** — a template holds the handle
//! [`JobBuilder::node`] hands back, so an edge says which node it waits on. Why that
//! removes submit-time cycle validation: `docs/coordinator.md`.
//! removes submit-time cycle validation: `docs/scheduler/coordinator.md`.
//!
//! The hive-wide **power ops** (`stop` / `start` / `restart`) are NOT here:
//! their per-agent shape depends on live running state (an async
@ -203,7 +203,7 @@ impl<'a> RebuildRoots<'a> {
/// The rebuild node subtree — three group roots (`MetaSync`, the `AgentWindow`
/// brace, `Reconcile`). `after`, when given, is the node it chains behind. The
/// shape itself is in `docs/coordinator.md`; the code below is the source of
/// shape itself is in `docs/scheduler/coordinator.md`; the code below is the source of
/// truth for it, so only the three choices a reader would otherwise undo are
/// called out here:
///

View file

@ -579,7 +579,7 @@ fn rebuild_chain_declares_its_resources_on_the_brace() {
// ⚠️ **The rename is a reversal, not tidying.** The old name asserted "the
// slot follows the nix work and the lease follows the container" — each node
// declaring what it personally needed. The brace inverts that for a
// coordinated subtree; see `docs/coordinator.md`, _Braces_.
// coordinated subtree; see `docs/scheduler/coordinator.md`, _Braces_.
//
// (`hive_jobq` owns slot *fairness*, pinned there by
// `a_contended_resource_goes_to_the_oldest_waiter`. What is c0re's is

View file

@ -85,7 +85,7 @@ fn config_bind_source(name: &str) -> PathBuf {
}
/// Append bind flags for `child`'s state and config dirs into `binds`.
/// See docs/persistence.md ("Parent access to child state") for what a
/// See docs/agent-lifecycle/persistence.md ("Parent access to child state") for what a
/// parent may touch and why. Creates missing host-side directories so
/// nspawn doesn't refuse to start; missing dirs are non-fatal.
///
@ -134,7 +134,7 @@ fn bind_child_agent_dirs(child: &str, binds: &mut Vec<BindMount>) {
/// that hive-c0re owns: `PRIVATE_NETWORK` (always 1), `HOST_ADDRESS` (the
/// bridge gateway IP) and `EXTRA_NSPAWN_FLAGS` (the runtime-dir bind). What
/// those network vars mean and why isolation is unconditional:
/// `docs/network.md` § *What the Rust side does*.
/// `docs/networking/network.md` § *What the Rust side does*.
///
/// ⚠️ The start script expands `$EXTRA_NSPAWN_FLAGS` unquoted into the
/// `systemd-nspawn` command.

View file

@ -94,7 +94,7 @@ pub fn agent_web_port(name: &str) -> u16 {
///
/// `HIVE_NETWORK_SUBNET` carries the host-side bridge address verbatim
/// (e.g. `10.42.0.1/24`), **not** the canonical network address — see
/// the note in `set_nspawn_flags` + `docs/network.md`. The IP part is
/// the note in `set_nspawn_flags` + `docs/networking/network.md`. The IP part is
/// therefore the bridge IP itself: the host end of the bridge, the
/// default-route target for isolated containers, and the address the
/// hive dnsmasq resolver binds. Returns the dotted-decimal IP with the
@ -522,7 +522,7 @@ pub async fn converge_start_preamble(
/// activation-error shape), retry once via stop + kill + start before
/// giving up. Used by the queue's fast-lane `Start` handler and the
/// inline start-after-rebuild path.
/// See `docs/coordinator.md::Cold-start fallback`.
/// See `docs/scheduler/coordinator.md::Cold-start fallback`.
///
/// Requires a [`StartableAgent`] token from [`converge_start_preamble`]
/// to prove the preamble ran. For internal use within this module (where
@ -701,8 +701,8 @@ pub async fn destroy(name: &str) -> Result<()> {
/// Pre-build `system.build.toplevel` against `meta#<name>` so the
/// subsequent `nixos-container update` finds the result cached and
/// skips straight to the profile-swap. Store-warming only — container
/// is untouched. See `docs/coordinator.md::Rebuild path` for why
/// the prebuild happens before stop, and `docs/coordinator.md::Prebuild
/// is untouched. See `docs/scheduler/coordinator.md::Rebuild path` for why
/// the prebuild happens before stop, and `docs/scheduler/coordinator.md::Prebuild
/// attr path` for why the explicit nixosConfigurations attr is required.
///
/// `node_id` is the queue node this build belongs to, when there is one —

View file

@ -528,7 +528,7 @@ async fn cmd_serve(
// Also retries any pending gateway nginx reload that failed on
// the previous tick. write() is idempotent so steady-state cost
// is one stat per agent per tick.
// See `docs/gateway.md::Per-agent unix-socket upstream`.
// See `docs/networking/gateway.md::Per-agent unix-socket upstream`.
agent_sockets::spawn_poll();
// MCP socket listener startup sync: one-shot sweep that re-registers any
// running agent container whose MCP listener was lost when hive-c0re

View file

@ -4,7 +4,7 @@
//! haven't flipped `services.hyperhive.deploy.matrix.enable = true` pay
//! nothing.
//!
//! See `docs/matrix.md::Provisioning flow (registration token)` for
//! See `docs/integrations/matrix.md::Provisioning flow (registration token)` for
//! the full UIAA round-trip, token-file shape, and host/container
//! bind-mount layout.
@ -744,7 +744,7 @@ pub async fn ensure_user_for(
tracing::info!(%name, "matrix: provisioned access token");
// Kick the daemon so it picks up the new token without waiting for a
// full container restart — see docs/matrix.md::Provisioning flow.
// full container restart — see docs/integrations/matrix.md::Provisioning flow.
if let Err(e) = crate::priv_client::restart_matrix_daemon(name).await {
tracing::warn!(%name, error = ?e, "matrix: could not restart hive-matrix-daemon (token written; daemon will reload on next container start)");
} else {

View file

@ -2,7 +2,7 @@
//! exports one `nixosConfiguration` per agent and drives the system-wide
//! deploy audit trail. Flow (`sync_agents`, two-phase `prepare_deploy` /
//! `finalize_deploy` / `abort_deploy`, `lock_update_hyperhive`):
//! `docs/approvals.md::Meta flake`.
//! `docs/agent-lifecycle/approvals.md::Meta flake`.
use std::path::Path;
@ -162,7 +162,7 @@ pub async fn sync_agents(hive: &HiveEnv, agents: &[AgentSpec]) -> Result<()> {
// (`HIVE_WEB_SOCKET`); the gateway routes via this map, falling back
// to a computed TCP loopback port (`lifecycle::agent_web_port`) only
// while an agent's socket marker is absent. Best-effort + non-fatal.
// See `docs/gateway.md::Per-agent unix-socket upstream`.
// See `docs/networking/gateway.md::Per-agent unix-socket upstream`.
if let Err(e) = crate::agent_sockets::write(&agent_names) {
tracing::warn!(error = ?e, "agent_sockets::write failed (non-fatal)");
}
@ -249,7 +249,7 @@ pub async fn prepare_deploy(name: &str, node_id: Option<u64>) -> Result<()> {
let input = format!("agent-{name}");
// Re-lock the agent input against the LOCAL applied mirror, not the
// persistent forge URL declared in the meta flake (see the `## Meta flake`
// note in docs/approvals.md): the deploy must build the exact reviewed
// note in docs/agent-lifecycle/approvals.md): the deploy must build the exact reviewed
// config that `verify_commit` gated and `applied/<n>/main` was
// fast-forwarded to, and it must keep working when the forge is
// unreachable (rebuilds fire on crash-restart / meta bumps too, not just

View file

@ -16,7 +16,7 @@
//! from birth does not need re-asserting each morning.
//!
//! Kill-switch: `HIVE_SKIP_META_MIGRATION=1`. Full sequence and phase
//! details: `docs/approvals.md::Migration from the pre-tag`.
//! details: `docs/agent-lifecycle/approvals.md::Migration from the pre-tag`.
use std::path::Path;
use std::sync::Arc;

View file

@ -1073,7 +1073,7 @@ pub(crate) fn handle_send(
// Resolve magic-recipient sentinels (`<parent>`) against topology.json;
// no-op for ordinary names. Lets agents address structural roles without
// learning the label — runtime reparenting propagates for free. See
// `docs/conventions.md::Recipient sentinels`.
// `docs/process/conventions.md::Recipient sentinels`.
let resolved = crate::topology::resolve_recipient(agent, to);
// Validate that the resolved recipient is a known local agent or the
// special "operator" recipient. Without this check a typo in `to`

View file

@ -12,7 +12,7 @@
//! No network: agents share the host network namespace
//! (`privateNetwork = false`), so there is no per-container net
//! counter to read. Per-agent network only becomes meaningful with the
//! netns-isolation roadmap (`docs/network.md`).
//! netns-isolation roadmap (`docs/networking/network.md`).
//!
//! CPU is cumulative (`usage_usec` is monotonic), so a single read is
//! meaningless — we sample every machine's counter, sleep one short

View file

@ -1,7 +1,7 @@
//! Sqlite-backed full build-log capture — stdout + stderr per
//! `nixos-container` / `nix build` invocation, accumulated live.
//! Schema, indices, retention, and the rationale for replacing
//! the old ring buffer: `docs/persistence.md::/var/lib/hyperhive/db/build_logs.sqlite`.
//! the old ring buffer: `docs/agent-lifecycle/persistence.md::/var/lib/hyperhive/db/build_logs.sqlite`.
use std::path::Path;
use std::sync::{Arc, Mutex, OnceLock};

View file

@ -4,13 +4,13 @@
//! `interval_seconds` and re-arm `next_fire_at` on delivery;
//! one-shots are reaped.
//!
//! Schema + retention: `docs/persistence.md::/var/lib/hyperhive/db/broker.sqlite`
//! Schema + retention: `docs/agent-lifecycle/persistence.md::/var/lib/hyperhive/db/broker.sqlite`
//! (the `scheduled_prompts` / `scheduled_prompt_targets` table bullets).
//! Submit paths (operator-direct vs `ApprovalKind::SchedulePrompt`,
//! plus why even agent-self schedules go through approval):
//! `docs/approvals.md::Scheduled prompts (submit paths)`.
//! `docs/agent-lifecycle/approvals.md::Scheduled prompts (submit paths)`.
//! Catch-up clamp on resume + per-target tombstoning:
//! `docs/approvals.md::Scheduled prompt worker (catch-up clamp)`.
//! `docs/agent-lifecycle/approvals.md::Scheduled prompt worker (catch-up clamp)`.
use std::path::Path;
use std::sync::Mutex;

View file

@ -6,7 +6,7 @@
//!
//! Full mechanism — per-agent subdir bind-mount, `hyperhive-socket-bound`
//! marker gate, gateway UDS upstream, 10s poll loop:
//! `docs/gateway.md::Per-agent unix-socket upstream`.
//! `docs/networking/gateway.md::Per-agent unix-socket upstream`.
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};

View file

@ -14,7 +14,7 @@
//! path are skipped — nothing changed, no nix work to do.
//!
//! Booting with no config change performs no meta commit — only
//! reconciles. See `docs/coordinator.md::Boot reconcile`.
//! reconciles. See `docs/scheduler/coordinator.md::Boot reconcile`.
use std::path::Path;
use std::sync::Arc;

View file

@ -1,7 +1,7 @@
//! Per-container crash and login-state watcher. Polls every managed
//! container on a 10s interval. Fires `ContainerCrash`, `LoggedIn`,
//! and `NeedsLogin` helper events. Event semantics and the
//! `RECENT_TRANSIENT_GRACE` window: `docs/approvals.md::Helper events`.
//! `RECENT_TRANSIENT_GRACE` window: `docs/agent-lifecycle/approvals.md::Helper events`.
use std::collections::HashSet;
use std::sync::Arc;

View file

@ -1,7 +1,7 @@
//! Background loop that drains due `scheduled_prompts` rows and fans
//! the body to each active target. 5s poll cadence, shutdown-aware.
//! Catch-up clamp, missing-target handling, and broker-error retry
//! semantics: `docs/approvals.md::Scheduled prompt worker`.
//! semantics: `docs/agent-lifecycle/approvals.md::Scheduled prompt worker`.
use std::sync::Arc;
use std::time::Duration;

View file

@ -18,4 +18,4 @@ This is the **host-served** protocol: the harness talks *out* to `hive-c0re`
over `/run/hive/mcp.sock` (broker sends, approvals, lifecycle).
`hive-agent-sock` is the separate *in-container* socket the harness serves to
its own local producers — that one never leaves the container. See
`docs/boundary.md` for the socket topology.
`docs/trust-boundary/boundary.md` for the socket topology.

View file

@ -38,7 +38,7 @@ pub enum Request {
},
/// Pop pending messages from this agent's inbox.
/// Delivery + ack cycle: see
/// `docs/conventions.md::Broker delivery + ack cycle`.
/// `docs/process/conventions.md::Broker delivery + ack cycle`.
Recv {
#[serde(default)]
wait_seconds: Option<u64>,
@ -55,7 +55,7 @@ pub enum Request {
OperatorMsg { body: String },
/// Wake-up event injected from inside the container. Recipient is
/// implicit (this agent); `from` is caller-chosen. See
/// `docs/conventions.md::Wake injection` for the trust model and
/// `docs/process/conventions.md::Wake injection` for the trust model and
/// typical callers. The wake is persisted in the sqlite broker
/// like any other message — the agent can ack it via `AckUntil`.
Wake { from: String, body: String },
@ -68,7 +68,7 @@ pub enum Request {
/// `query_agent_state` capability — rejected with an error otherwise;
/// `"*"` is always rejected (use the manager socket). On the manager
/// socket: `None` = manager self, `"*"` = hive-wide, any name =
/// that agent. See `docs/conventions.md::Loose-ends wire shape`.
/// that agent. See `docs/process/conventions.md::Loose-ends wire shape`.
GetLooseEnds {
#[serde(default, skip_serializing_if = "Option::is_none")]
agent: Option<String>,
@ -80,14 +80,14 @@ pub enum Request {
SetStatus { text: String },
/// Fetch identity + status for an agent. `name = None` =
/// self-introspection; `Some(<agent>)` = target query. See
/// `docs/conventions.md::Agent metadata`.
/// `docs/process/conventions.md::Agent metadata`.
GetAgentMeta {
#[serde(default, skip_serializing_if = "Option::is_none")]
name: Option<Ident>,
},
/// Cancel an open thread the agent owns. Authorisation +
/// per-kind semantics in
/// `docs/conventions.md::Loose-ends wire shape`.
/// `docs/process/conventions.md::Loose-ends wire shape`.
CancelLooseEnd { kind: CancelLooseEndKind, id: i64 },
/// Create a git repo *through hive-c0re*. Agents can't create
/// repos with their own forge token (`max_repo_creation = 0`); this is
@ -98,7 +98,7 @@ pub enum Request {
CreateRepo { repo: String },
/// Mark every message popped since the last `AckTurn` as handled.
/// Harness↔broker pairing fired after `TurnOutcome::Ok`. See
/// `docs/conventions.md::Broker delivery + ack cycle`.
/// `docs/process/conventions.md::Broker delivery + ack cycle`.
AckTurn,
/// Mark every inbox message with broker row id `<= up_to` as
/// handled (`acked_at` set), whether still pending or already
@ -107,12 +107,12 @@ pub enum Request {
/// re-reading dozens of already-handled messages one turn at a
/// time, the agent acks everything up to the id it has seen.
/// Recipient-scoped — an agent can only ack its own rows. See
/// `docs/conventions.md::Broker delivery + ack cycle`.
/// `docs/process/conventions.md::Broker delivery + ack cycle`.
AckUntil { up_to: i64 },
/// Requeue every popped-but-unacked message back into the inbox.
/// Harness fires this once at boot to recover from
/// crashed-mid-turn sessions. See
/// `docs/conventions.md::Broker delivery + ack cycle`.
/// `docs/process/conventions.md::Broker delivery + ack cycle`.
RequeueInflight,
/// Harness → c0re: "I saw the `GracefulStop` signal, ran my
/// stop-checkpoint turn (durable `/state` flushed) and am exiting my
@ -267,7 +267,7 @@ pub enum Response {
/// this agent. Ordered newest-first within each kind.
LooseEnds { loose_ends: Vec<LooseEnd> },
/// `GetAgentMeta` result. Per-field semantics + serde defaults
/// live in `docs/conventions.md::Agent metadata`.
/// live in `docs/process/conventions.md::Agent metadata`.
AgentMeta {
name: String,
#[serde(default = "default_true")]

View file

@ -14,7 +14,7 @@ self-echo filter, the comment / review / new-item / state-change
wrapper formats, body-excerpt truncation, and the assigned-issue
rollup all live in `notify.rs`. The behaviour contract — activation
gates, filtering rules, the review-request override — is documented in
[`docs/forge.md`](../docs/forge.md), "Notification poller".
[`docs/integrations/forge.md`](../docs/integrations/forge.md), "Notification poller".
## Shape

View file

@ -5,7 +5,7 @@
//!
//! Takes no arguments. `HYPERHIVE_STATE_DIR` holds the PAT
//! (`github-token`, provisioned from the dashboard credentials tab — see
//! `docs/github.md`) and `HIVE_AGENT_SOCKET` is the harness's todo socket.
//! `docs/integrations/github.md`) and `HIVE_AGENT_SOCKET` is the harness's todo socket.
//! Having a PAT *is* the opt-in: with no token the poller logs why and
//! exits 0, so deploying this unit to an agent that never gets one costs a
//! settled process rather than a restart loop.

View file

@ -11,7 +11,7 @@ use hive_forge_notify::notify::UNREAD_FETCH_LIMIT;
use hive_forge_notify::source::Source;
use tracing::{debug, warn};
/// GitHub's REST base. `docs/github.md` scopes the integration to
/// GitHub's REST base. `docs/integrations/github.md` scopes the integration to
/// github.com only — the same constraint the `gh` wrapper and the git
/// credential helper already carry — so this is a constant rather than
/// another operator-entered URL.

View file

@ -18,12 +18,12 @@
//! **Multi-source**: always the internal Forgejo, plus github.com when the
//! agent has a PAT. Each source polls independently behind
//! [`Source`]; everything below is shared. Rationale
//! + host differences: [`docs/forge.md::Sources`](../../../docs/forge.md).
//! + host differences: [`docs/integrations/forge.md::Sources`](../../../docs/integrations/forge.md).
//!
//! Activation gates, self-notification filtering, body excerpt +
//! truncation + heading escape, wrapper formats (comment / review /
//! new-item / state-change), meta suffix, and review-request override
//! all live in [`docs/forge.md::Notification poller`](../../../docs/forge.md).
//! all live in [`docs/integrations/forge.md::Notification poller`](../../../docs/integrations/forge.md).
use std::collections::{HashMap, HashSet};
use std::fmt::Write as _;
@ -63,7 +63,7 @@ pub const TOKEN_RETRY_MAX: u32 = 20;
/// a `new <kind>` label. Later activity that lands on the state-change
/// path because it carries no `latest_comment_url` (e.g. a bodiless
/// review submission) fires well outside this window, so we must not
/// claim it's "new" — see docs/forge.md, "new vs activity on".
/// claim it's "new" — see docs/integrations/forge.md, "new vs activity on".
const NEW_ITEM_TOLERANCE_SECS: i64 = 120;
/// Fetch the account's own login for self-notification filtering.
@ -111,7 +111,7 @@ fn notif_type_label(t: Option<NotifySubjectType>) -> &'static str {
/// Escape strict-ATX markdown headings in a body before embedding it
/// inside the forge-notify wrapper, so a leading `## title` line
/// doesn't blow into an h2 in the dashboard render. See
/// `docs/forge.md::Body excerpt + truncation + heading escape` for
/// `docs/integrations/forge.md::Body excerpt + truncation + heading escape` for
/// the strict-ATX-vs-`#tag` rationale and the `split_inclusive`
/// trailing-newline contract.
fn escape_md_headings(body: &str) -> String {
@ -196,7 +196,7 @@ fn is_username_byte(b: u8) -> bool {
/// `@username` mention AND aren't already present (as a substring) in
/// `included_excerpt`. Surfaces tags that fell outside the truncation
/// window so addressed agents never silently miss a mention on a long
/// body. See `docs/forge.md::Body excerpt + truncation + heading
/// body. See `docs/integrations/forge.md::Body excerpt + truncation + heading
/// escape` for the truncate-before-escape ordering rule.
fn extract_truncated_mention_lines<'a>(full_body: &'a str, included_excerpt: &str) -> Vec<&'a str> {
full_body
@ -253,7 +253,7 @@ fn review_state_label(state: &str) -> Option<&str> {
/// Build a human-readable wake message for one Forgejo notification,
/// or `None` for a self-echo the caller should mark-read without
/// delivery. Wrapper format table + meta-suffix shape + number/repo
/// extraction live in `docs/forge.md::Wrapper format` +
/// extraction live in `docs/integrations/forge.md::Wrapper format` +
/// `::Meta suffix`.
async fn format_notification<S: Source>(
client: &reqwest::Client,
@ -305,7 +305,7 @@ async fn format_notification<S: Source>(
// Always fetch subject detail for assignee/reviewer metadata so
// the meta suffix can show current ownership without a follow-up
// fetch (see `docs/forge.md::Meta suffix`).
// fetch (see `docs/integrations/forge.md::Meta suffix`).
let subject = if subject_api_url.is_empty() {
None
} else {
@ -361,7 +361,7 @@ async fn format_notification<S: Source>(
// that comment was genuinely posted AFTER the close — a comment
// racing the merge inside the state-change tolerance window — append
// it so it isn't lost (best of both worlds; see
// `docs/forge.md::Merge racing a comment`). The ordinary pre-merge
// `docs/integrations/forge.md::Merge racing a comment`). The ordinary pre-merge
// last comment (created before `closed_at`) is left off.
let comment_tail = if has_comment {
fresh_post_close_comment_tail(
@ -413,7 +413,7 @@ struct NotifMeta<'a> {
/// Build the `\nassignee: ...` (and optionally `\nreviewer: ...`)
/// suffix appended to every wrapper. Shape + presence rules live in
/// `docs/forge.md::Meta suffix`.
/// `docs/integrations/forge.md::Meta suffix`.
fn build_meta_suffix(subject: Option<&serde_json::Value>, is_pr: bool) -> String {
let assignees: Vec<&str> = subject
.and_then(|s| s["assignees"].as_array())
@ -675,7 +675,7 @@ fn format_state_change_notification(
// time. A review submitted with no body carries no
// `latest_comment_url`, so it lands here instead of on the comment
// path — and its event time is well after `created_at`. Labeling
// that "new PR" is misleading (see docs/forge.md, "new vs activity
// that "new PR" is misleading (see docs/integrations/forge.md, "new vs activity
// on"): agents dismiss it as a
// duplicate of the original open notification. When we can't confirm
// creation, fall back to a neutral "activity on" label.
@ -707,7 +707,7 @@ fn format_state_change_notification(
// Review-request override: Forgejo doesn't reliably set
// `reason == "review_requested"` (often null), so we check the
// subject payload's `requested_reviewers` list directly. See
// `docs/forge.md::Review-request override`.
// `docs/integrations/forge.md::Review-request override`.
let is_review_request = is_new
&& *is_pr
&& !own_login.is_empty()
@ -751,7 +751,7 @@ fn format_state_change_notification(
/// `created_at`: within `NEW_ITEM_TOLERANCE_SECS` ⇒ creation. When
/// either timestamp is missing or unparseable we default to `true`,
/// preserving the prior "new" behavior rather than masking a genuine
/// new item behind the neutral fallback. See docs/forge.md, "new vs
/// new item behind the neutral fallback. See docs/integrations/forge.md, "new vs
/// activity on".
fn notification_is_creation(
event: Option<OffsetDateTime>,

View file

@ -27,7 +27,7 @@
//!
//! Dispatch re-runs the whole workflow (no single-job variant). ⚠️ `--pr`
//! verifies the code but doesn't reliably move the PR's own status
//! checks — see `docs/ci.md`'s "CI checks" for why; re-check `pr-status`.
//! checks — see `docs/scheduler/ci.md`'s "CI checks" for why; re-check `pr-status`.
use anyhow::{Context as _, Result, bail};
use clap::Args as ClapArgs;

View file

@ -25,6 +25,6 @@ needs (`Source`, `State`, `PermPayload`, `NodeId`) is protocol-local. The typed
served as a generic graph (`hive-jobq-wire`), not as a second hand-written
view.
See `docs/boundary.md` (host admin socket access) for the trust model around who
See `docs/trust-boundary/boundary.md` (host admin socket access) for the trust model around who
may connect to the socket, and `hive-priv-sock` for the sibling split on the
privileged-helper socket.

View file

@ -13,7 +13,7 @@
//! `GET /api/jobq/graph` — so there is no second shape to keep in
//! agreement with the first. Queue internals (node kinds, edges,
//! scheduling) live in `hive-c0re::job_queue`.
//! Semantics: `docs/coordinator.md::Job queue`.
//! Semantics: `docs/scheduler/coordinator.md::Job queue`.
use serde::{Deserialize, Serialize};

View file

@ -68,7 +68,7 @@ pub const AGENT_SOCKET_DIR: &str = "/run/hive-agent";
/// Per-agent web UI unix socket path — `AGENT_SOCKET_DIR/<name>/web.sock`.
/// Same socket the gateway's nginx upstream and the harness's
/// `HIVE_WEB_SOCKET` bind both derive from; see
/// `docs/gateway.md::Per-agent unix-socket upstream`.
/// `docs/networking/gateway.md::Per-agent unix-socket upstream`.
#[must_use]
pub fn agent_web_socket(name: &Ident) -> PathBuf {
PathBuf::from(AGENT_SOCKET_DIR)
@ -121,14 +121,14 @@ pub enum HostRequest {
/// first-time provisioning: proposed/applied repos, state subvolume,
/// meta-flake sync, `nixos-container create`), bypassing the approval
/// queue. Privileged-context only. Exposed on the CLI as `hivectl
/// agent <name> create`. See `docs/approvals.md::Approval kinds
/// agent <name> create`. See `docs/agent-lifecycle/approvals.md::Approval kinds
/// (wire shapes)`. Wire name kept as `Spawn` (unrenamed underneath
/// the CLI-verb rename — `hivectl agent <name> start` reuses the
/// existing scope-based [`HostRequest::Start`] below instead of a
/// new per-agent variant, see its doc comment).
Spawn { name: Ident },
/// Submit a first-creation request for the operator to approve. See
/// `docs/approvals.md::Approval kinds (wire shapes)` (`Spawn`).
/// `docs/agent-lifecycle/approvals.md::Approval kinds (wire shapes)` (`Spawn`).
/// Exposed on the CLI as `hivectl agent <name> request-create`.
RequestSpawn { name: Ident },
/// Hard stop a managed container. Exposed on the CLI as `hivectl
@ -139,7 +139,7 @@ pub enum HostRequest {
/// `Kill`.
Kill { name: Ident },
/// Tear down a sub-agent container, optionally purging state.
/// See `docs/approvals.md::Destroy semantics`.
/// See `docs/agent-lifecycle/approvals.md::Destroy semantics`.
Destroy {
name: Ident,
#[serde(default)]
@ -234,7 +234,7 @@ pub enum HostRequest {
/// Move an agent in the topology tree. `new_parent = None`
/// promotes the agent to root, `Some(name)` sets a new parent.
/// Validation rules + bind-mount caveat documented in
/// `docs/agent-hierarchy.md::Current state`.
/// `docs/agent-lifecycle/agent-hierarchy.md::Current state`.
SetParent {
child: Ident,
new_parent: Option<Ident>,

View file

@ -22,5 +22,5 @@ Standard OTEL environment variables are read automatically by the SDK:
- `OTEL_RESOURCE_ATTRIBUTES` — resource labels (`k=v,...`)
The harness populates all of these per-agent when
`services.hyperhive.otel.enable = true`. See `docs/observability.md` for the
`services.hyperhive.otel.enable = true`. See `docs/scheduler/observability.md` for the
collector setup and the metrics hyperhive exports.

View file

@ -16,6 +16,6 @@ admin socket.
## Shape
Serde-derived request/response types only — no server or client logic. Both
sides import them so the shapes stay in sync. See `docs/boundary.md` +
`docs/security.md` for the privilege boundary these types sit on, and
sides import them so the shapes stay in sync. See `docs/trust-boundary/boundary.md` +
`docs/trust-boundary/security.md` for the privilege boundary these types sit on, and
`hive-priv/README` for the helper itself.

View file

@ -277,7 +277,7 @@ pub struct CredentialMount {
/// Network isolation parameters for `WriteNspawnFlags`. hive-priv writes
/// `PRIVATE_NETWORK=1` + veth bridge wiring from these; every container
/// is isolated, so they are required rather than a mode selector.
/// No static address is pre-assigned here — see `docs/network.md`
/// No static address is pre-assigned here — see `docs/networking/network.md`
/// § *Container isolation* for how a container gets its address.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NetworkIsolation {
@ -288,7 +288,7 @@ pub struct NetworkIsolation {
/// container-side setup installs a default route (`default via <gw>`)
/// before DHCP completes: without it the container has no route off
/// the bridge subnet until the DHCP lease arrives. The same IP runs the
/// hive dnsmasq resolver — see `docs/network.md` § *How the isolated
/// hive dnsmasq resolver — see `docs/networking/network.md` § *How the isolated
/// container gets its resolver*.
pub gateway_ip: String,
}

View file

@ -12,7 +12,7 @@ coordination logic (broker, HTTP, scheduling) stays in the *unprivileged*
Privsep. `hive-c0re` runs as the unprivileged `hive-core` user so a bug or a
prompt-injection in the large daemon can't directly wield root. The few root
operations it needs are funnelled through this small, auditable helper instead.
See `docs/boundary.md` and `docs/security.md` for the privilege boundary.
See `docs/trust-boundary/boundary.md` and `docs/trust-boundary/security.md` for the privilege boundary.
## Security model

View file

@ -66,7 +66,7 @@ async fn main() -> Result<()> {
fn socket_listener() -> Result<UnixListener> {
// hive-priv is ALWAYS socket-activated by the `hive-priv.socket` unit
// (fd 3 via LISTEN_FDS). There is intentionally no self-bind fallback,
// so dev and prod take the same path; see docs/boundary.md.
// so dev and prod take the same path; see docs/trust-boundary/boundary.md.
let listen_fds: Option<i32> = std::env::var("LISTEN_FDS")
.ok()
.and_then(|s| s.parse().ok());
@ -2889,7 +2889,7 @@ fn write_nspawn_flags(
// marker carries the gateway IP so the container need not re-derive it.
// Always written — every container is isolated, so there is no mode in
// which the marker should be absent. Why the rewrite is needed at all,
// and which unit does it: `docs/network.md` § *How the isolated
// and which unit does it: `docs/networking/network.md` § *How the isolated
// container gets its resolver*.
write_bridge_dns_marker(container, isolation)?;
Ok(())

View file

@ -8,7 +8,7 @@ use hive_types::Ident;
use serde::{Deserialize, Serialize};
/// One row in the approval queue. `commit_ref` is overloaded per
/// `kind` — see `docs/approvals.md::Approval kinds (wire shapes)`
/// `kind` — see `docs/agent-lifecycle/approvals.md::Approval kinds (wire shapes)`
/// for the encoding table and lifecycle.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Approval {
@ -39,7 +39,7 @@ pub struct Approval {
/// What action the approval, when granted, will trigger.
/// Variant-specific payload encoding + flow lives in
/// `docs/approvals.md::Approval kinds (wire shapes)`.
/// `docs/agent-lifecycle/approvals.md::Approval kinds (wire shapes)`.
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ApprovalKind {
@ -63,7 +63,7 @@ pub enum ApprovalKind {
/// sole config-change flow — a manager opens a PR on its
/// `agent-configs/<agent>` repo and the operator reviews + approves it.
/// `commit_ref` = PR number; `fetched_sha` = the reviewed PR head
/// pinned at submit. See `docs/approvals.md`.
/// pinned at submit. See `docs/agent-lifecycle/approvals.md`.
#[default]
MergeConfigPr,
}
@ -95,7 +95,7 @@ pub enum ApprovalStatus {
/// Manager withdrew the request before the operator acted on it.
/// Distinct from `Denied` (operator decision) and `Failed`
/// (post-approval lifecycle error). See
/// `docs/approvals.md::Withdrawing a pending approval`.
/// `docs/agent-lifecycle/approvals.md::Withdrawing a pending approval`.
Cancelled,
}

View file

@ -86,7 +86,7 @@ pub struct InboxRow {
}
/// One delivered message in a `Recv` response.
/// See `docs/conventions.md::Broker delivery + ack cycle` for the
/// See `docs/process/conventions.md::Broker delivery + ack cycle` for the
/// full delivery/ack/requeue story.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeliveredMessage {
@ -121,7 +121,7 @@ pub enum ReminderTiming {
/// thread kinds can land without breaking existing handlers.
/// Per-flavour scoping + per-variant fields + clock-anomaly
/// saturation behaviour live in
/// `docs/conventions.md::Loose-ends wire shape`.
/// `docs/process/conventions.md::Loose-ends wire shape`.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum LooseEnd {
@ -190,7 +190,7 @@ pub enum LooseEnd {
/// Kind discriminator for `CancelLooseEnd`. Per-kind store +
/// authorisation rules live in
/// `docs/conventions.md::Loose-ends wire shape`.
/// `docs/process/conventions.md::Loose-ends wire shape`.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum CancelLooseEndKind {

View file

@ -60,7 +60,7 @@ pub fn trusted_sender(s: &str) -> Ident {
/// Out-of-band events the host-side daemon pushes to the manager's inbox.
/// Serialised as JSON in `Message::body` (sender = `SYSTEM_SENDER`).
/// Per-variant triggers + the optional `sha`/`tag` semantics live in
/// `docs/approvals.md::Helper events to the manager`.
/// `docs/agent-lifecycle/approvals.md::Helper events to the manager`.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "event", rename_all = "snake_case")]
pub enum HelperEvent {

View file

@ -11,7 +11,7 @@ use serde::{Deserialize, Serialize};
/// `snake_case` group names written by the meta renderer from per-agent
/// config) and expands it to the matching tool names for `--allowedTools`.
/// When the env var is absent the harness falls back to `AGENT_DEFAULT`.
/// See `docs/conventions.md::Tool groups`.
/// See `docs/process/conventions.md::Tool groups`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ToolGroup {
@ -200,7 +200,7 @@ impl ToolGroup {
/// Injected into containers as `HIVE_CAPABILITIES` (comma-separated
/// `snake_case`) via `meta::render_flake`. The harness reads this to
/// conditionally register capability-gated MCP tools so claude only
/// sees tools it can actually invoke. See `docs/conventions.md::Capabilities`.
/// sees tools it can actually invoke. See `docs/process/conventions.md::Capabilities`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Capability {

View file

@ -14,5 +14,5 @@ Foundational shared **newtypes** for the hyperhive workspace. A zero-dependency
instead of flowing into a handler that has to re-validate by hand.
The crate is deliberately a leaf with the smallest possible dependency footprint
so anything in the workspace can depend on it. See `docs/conventions.md`
so anything in the workspace can depend on it. See `docs/process/conventions.md`
("identity = socket") for how agent identity is modelled across the sockets.

View file

@ -90,7 +90,7 @@ pub(crate) async fn watch(socket: &Path, name: &str) -> Result<()> {
/// Render one SSE frame (everything up to the blank-line separator,
/// already stripped) as a single compact terminal line. Not an attempt at
/// the web UI's full row taxonomy (`docs/terminal-rendering.md`) — that's
/// the web UI's full row taxonomy (`docs/web-ui/terminal-rendering.md`) — that's
/// presentation for a browser, not a `tail -f`. Falls back to the raw
/// payload for anything unrecognised rather than silently dropping it, so
/// a shape this doesn't know about is still visible.

View file

@ -153,7 +153,7 @@ in
not `state/` `harness/` survives container rebuilds exactly like
`state/` does, but is never bind-mounted into a parent agent's
container (unlike `state/`, which a parent gets read-write for child
recovery see `docs/persistence.md`'s "Parent access to child
recovery see `docs/agent-lifecycle/persistence.md`'s "Parent access to child
state"), so this credential is reachable by nothing but this agent
and the host. Permissions should be `0600`, owned by the agent's
unix user. Loaded with a leading `-` (optional `EnvironmentFile`),
@ -232,8 +232,8 @@ in
# Harness systemd unit. Unit shape (PATH wrapper-dir trick, env vars,
# RuntimeDirectory, User=, standalone-eval fallbacks):
# docs/agent-hierarchy.md::Harness systemd unit shape. PATH /bin
# auto-append behaviour: docs/gotchas.md::systemd.services.*.path
# docs/agent-lifecycle/agent-hierarchy.md::Harness systemd unit shape. PATH /bin
# auto-append behaviour: docs/process/gotchas.md::systemd.services.*.path
# appends /bin to every entry.
systemd.services.hive-agent =
let

View file

@ -46,7 +46,7 @@
# Write declared dashboardLinks to the state dir so hive-c0re can
# read them without accessing the container's /etc/ from the host.
# Best-effort oneshot (always exit 0):
# docs/conventions.md::Best-effort oneshot services.
# docs/process/conventions.md::Best-effort oneshot services.
systemd.services.hive-dashboard-links = lib.mkIf (config.hyperhive.dashboardLinks != [ ]) {
description = "write declarative dashboardLinks to agent state dir";
wantedBy = [ "multi-user.target" ];

View file

@ -151,8 +151,8 @@
# `lib.mkForce` overrides nixpkgs's normal-priority `false` so
# in-container `nix build` invocations fall back to unsandboxed
# local builds rather than failing on the missing user-namespace.
# See `docs/gotchas.md::Containerized nix-daemon needs
# sandbox-fallback = true` + `docs/security.md` for the rationale.
# See `docs/process/gotchas.md::Containerized nix-daemon needs
# sandbox-fallback = true` + `docs/trust-boundary/security.md` for the rationale.
#
# Note: with NIX_REMOTE=daemon below this becomes a no-op for the
# common case — daemon-routed builds run on the host where sandboxing
@ -208,7 +208,7 @@
# to be on PATH too. Only this one is actually looked up on PATH
# by claude/shell code inside the container:
# `hive-metric` (agent-emitted custom metrics CLI,
# docs/observability.md).
# docs/scheduler/observability.md).
environment.systemPackages = [
config.hyperhive.packages.hive-metric
]

View file

@ -180,7 +180,7 @@ in
# One-shot: tea config.yml from the seeded forge token. Shape
# contract (always exit 0, no set -e, skip-silently, re-runnable):
# docs/conventions.md::Best-effort oneshot services.
# docs/process/conventions.md::Best-effort oneshot services.
# Not generated at all when no forge is configured: an absent
# integration rather than one pointed at a guessed address.
systemd.services.tea-login = lib.mkIf (config.hyperhive.forge.url != null) {
@ -251,7 +251,7 @@ in
# service fires too early and exits with "no forge-token found".
# Without this path unit, RemainAfterExit=true would prevent systemd
# from ever re-running the service. See
# docs/persistence.md::forge-avatar-sync.
# docs/agent-lifecycle/persistence.md::forge-avatar-sync.
systemd.paths.forge-avatar-sync = lib.mkIf (config.hyperhive.icon != null) {
description = "trigger forge-avatar-sync when forge-token appears";
wantedBy = [ "multi-user.target" ];
@ -259,7 +259,7 @@ in
};
# One-shot: hyperhive.icon → Forgejo profile avatar. Shape contract:
# docs/conventions.md::Best-effort oneshot services.
# docs/process/conventions.md::Best-effort oneshot services.
# RemainAfterExit = false so the .path trigger above can re-fire
# this unit when the forge-token arrives after boot. The PNG is
# rasterized at build time (`iconPng`, shared shape with the matrix

Some files were not shown because too many files have changed in this diff Show more