Watch
0
0
Fork
You've already forked hyperhive
0
Commit graph hyperhive/CLAUDE.md
Author SHA1 Message Date
atlas
e92f118bda add swarm-metrics, an agent CLI for the metrics store
swarm-metrics reads the swarm's VictoriaMetrics from inside an agent
container: query, query-range, series, labels and label-values, each one
GET against the store's Prometheus read API through the gateway, printing
the store's JSON. It authenticates the way swarm-logs does, with the
agent's own queue secret read from bao under the agent's leaf, sent as
a swarm-agent bearer.

On the metrics store's host the read API's auth map gains an agent arm
ahead of the Grafana one. It routes to swarm-metrics-agent-auth, a
swarm-logs-auth instance logged in under the existing swarm-metrics-auth
leaf, whose bao grant now includes swarm/agents/+/queue read. The check is
rate-limited per address like the log store's
(deploy.victoriametrics.agentAuth{Rate,Burst,VerdictTtl}).

nix/agent-modules/metrics.nix installs the wrapped binary when the hive
forwards HIVE_AGENT_METRICS_URL (from swarm.victoriametrics.domain) and a
store address, the same gate as swarm-logs. Adds the base:swarm-metrics
skill and the CLI reference with its freshness check.

Fixes #5395
2026-10-17 10:10:17 +02:00
atlas
1b60084bf0 build every agent from the agent-base template
Delete nix/templates/ruth.nix, the ruth nixosConfiguration, module and
toplevel, the hive-c0re managerToplevel option, and the meta flake's
isManager switch with lifecycle::MANAGER_NAME. Every agent, ruth
included, now builds from nix/templates/agent.nix. The template's only
content beyond agent.nix was docs.enable = mkDefault true; docs.enable
is off by default for every agent and ruth's own agent.nix turns it on.

Refs #5377
2026-10-17 09:24:52 +02:00
atlas
1f214f9f01 bound the request line on the host admin and in-agent sockets
hive-c0re's admin socket and hive-agent's in-agent socket (mode 0666) read
the request with an unbounded read_line: no length cap and no deadline, so
a peer that never sends a newline grows a buffer and pins a task forever.

The bounded reader (16 MiB cap) moves from hive-c0re's socket_server into
hive-sock-client, the one crate both daemons already share or can take, and
gains optional deadlines for the first byte and for the rest of the line.
Both sockets now use it and answer an over-long or stalled line with the
same error reply the bounded sockets give, then close. The admin socket
bounds only the started line (clients may idle between requests); the
in-agent socket is single-shot and bounds silence too. The per-agent
sockets keep their behaviour (no deadline).
2026-10-17 09:09:20 +02:00
atlas
81e77f306f claude.md: list swarm-ci-metrics and swarm-metrics-auth in the crate map 2026-10-16 20:43:11 +02:00
atlas
d5de1e4943 swarm UI: log an agent into claude from its card; the credentials go to bao and the agent picks them up
A new swarm service, `swarm-claude-login`, in its own container on the host
that sets `deploy.claude-login.enable` (defaulted on by `allSwarmServices`
where the host has the minter's store leaf, like the review classifier).

- swarm-ui: an agent card showing "login needed" gets a "log in" button.
  Its dialog starts the login, shows the URL claude printed, and takes the
  pasted code.
- swarm-controller: `POST/DELETE /api/agents/{name}/claude-login` and
  `POST /api/agents/{name}/claude-login/code`, behind the swarm UI's
  authelia-gated `/api/`, relay the steps to the minter as requests on
  `$SWARM.claude-login.request`.
- swarm-claude-login: runs `claude auth login` over piped stdio with HOME
  and CLAUDE_CONFIG_DIR in a per-login scratch dir, scrapes the first
  https URL, writes the code to stdin, and stores the `.credentials.json`
  at `swarm/agents/<agent>/claude-credentials` with a KV v2 write, a new
  version over any earlier one. The scratch dir goes on every exit path; a
  login waits 15 minutes for its code.
- hive-agent: while parked in `wait_for_login`, reads that leaf every 30 s
  under the agent's own cert and installs it only when its KV version is
  newer than the one recorded in `<harness>/claude-credentials-version`.
  An equal or older version is never put back, because claude rotates the
  refresh token and the stored copy is stale after the first refresh.
- swarm-secret-client: `claude_login` paths and `SecretStore::read_versioned`.
- Credentials: a `claude-login` store leaf minted by glue-bao-tls, a
  `swarm-claude-login` policy (create/update on
  `swarm/agents/+/claude-credentials`, read on its own queue client
  secret), an authelia machine client, and a `--claude-login-client`
  grant in the queue's auth-callout responder. The unit runs as a static
  system user.

The hive's own web-UI login is unchanged.

Refs #5268
2026-10-15 13:26:29 +02:00
müde
b99ce2b572 CLAUDE.md: build commands, the 8 missing crates, leaner entries
Adds a build & check section, maps every workspace member, links
gotchas.md, and trims entries down to purpose + warnings (the rest lives
in each crate's //! doc). Also drops the comment in turn.rs that repeated
the module doc.
2026-10-14 23:14:08 +02:00
atlas
b68b74d687 swarm-builder: build each agent config PR head and post a commit status
A new `swarm-builder` service, in its own container on the host that sets
`deploy.builder.enable` (defaulted on by `allSwarmServices` where the host
has the builder's store leaf, like the review classifier).

When an `agent-configs/<agent>` PR is opened, reopened or synchronized,
swarm-controller queues a `build_agent` jobq node. The node sets a
`swarm-builder` commit status to pending on the PR head, sends
`{agent, rev, pr}` to the builder over NATS request-reply
(`$SWARM.builder.build`), and writes success (store path), failure (last
line nix printed) or error (no builder / no reply) on the head. The node
fails when the build does, so the Jobs page matches the PR.

The builder builds nix/builder/check-flake's `check` with every input
overridden: hyperhive and nixpkgs to the host's own
`c0re.hyperhiveFlake`/`c0re.nixpkgsFlake` (what a hive there renders into
its meta flake), and the agent to the PR head on the forge. Evaluation is
pure and runs in the container; builds go to the host nix daemon as an
untrusted client. The private config repo is fetched with the token of a
new read-only `builder` forge account (in the `hives` team), which the
controller mints into `swarm/services/builder/forge/token`; git gets it
from a credential helper reading the child's environment, so it is in no
argument or URL.

Credentials: a `builder` store leaf minted by glue-bao-tls on the store
host, a `swarm-builder` reader policy (forge token + its own OIDC client
secret), an authelia machine client, and a `--builder-client` grant in the
queue's auth-callout responder. The controller gains a grant on the token
path and the secret publisher a deny on it.

This is the agent-config build check atlas scoped on #5081 (slice 3):
nothing is pushed to a cache, nothing is written to KV, and a red status
gates no deploy. The check composes agent-base + the config without the
values a hive adds (CA, docs, claude-code path, ports, input follows), so
it can disagree with a hive's own build.
2026-10-14 03:30:46 +02:00
atlas
1f4736e79b swarm-logs-auth: check agent tokens on the log store's query route
A new `auth_request` responder on the log store's host. nginx sends it
every `/select/logsql/` request whose `Authorization` is
`Bearer swarm-agent.<agent>.<secret>`; it answers 200 when the secret is
the one stored at `swarm/agents/<agent>/queue` (`swarm-agent-verify`),
401 otherwise. Every other bearer still goes to authelia, so agents
still presenting the hive's shared OIDC client keep working.

`auth_request` takes a fixed URI, so the split is a `map` on
`$http_authorization` plus an internal location that rewrites to the
map's pick. Checked against nginx 1.30.5 with stub upstreams: an agent
token only the stub validator accepts gets 200, a token only the stub
authelia accepts gets 200, every wrong one 401, and the internal
locations 404 from outside.

The responder serves a unix socket in its own traverse-only runtime
directory and reads the store under its own leaf
(`swarm-logs-auth`), whose policy is `swarm/agents/+/queue`, read-only.
glue-bao-tls mints the leaf where the store runs; elsewhere the
operator places it, and eval refuses a log store host without one.

Part of #4459.
2026-10-13 08:38:05 +02:00
atlas
6c9cb66dce docs: the review classifier's try-out service, its key and its subject
- review-classifier.md: claude runs spawn through hive-claude directly
  with no tools and no session (hive-runtime holds only TurnStop); the
  answer's fields, the 500-character reason cap and what a failure does;
  how the try-out page reaches the classifier; the settled parts of the
  open list (API key path, try-out subject, --tools "" on claude).
  The banner stays.
- services.md: a Review classifier section with the two operator steps.
- secrets.md, credentials.md: the classifier's leaf and the key row.
- network.md: the container's row. CLAUDE.md: the crate.
2026-10-12 13:11:21 +02:00
atlas
2df19e4899 swarmctl: log in with the operator's own SSO account and reach the controller through the swarm domain
`swarmctl --swarm https://<swarm> login` runs the OAuth 2.0
authorization-code flow with PKCE against the swarm's authelia as a new
public `swarmctl` client: a pushed authorization request, the browser login
and explicit consent, and the code delivered by form_post to a loopback
port. The access and refresh tokens are cached at mode 0600 under
$XDG_STATE_HOME/swarmctl/ and refreshed under a file lock, since authelia
rotates the refresh token and revokes the login when a used one is
presented again. With --swarm (or SWARMCTL_SWARM), the agent, schedule and
forge verbs send their requests to https://<swarm>/api/ with the bearer
token instead of over the controller's unix socket, which stays the default.

authelia registers the client on every authelia host with a swarm domain:
`kind = "native"`, public, no minted secret, scopes offline_access and
authelia.bearer.authz, audience https://<swarm>/api/ (matched as a URL
prefix), redirect http://127.0.0.1/callback (any loopback port). Every
field is one authelia 4.39.20's validator requires for a public client
holding the bearer scope.

The swarm apex's /api/ answers a refused caller that sent an
Authorization header with the 401 itself instead of the login redirect,
so the CLI can tell "log in again" apart from an answer; a browser still
gets the redirect. The bearer token is not forwarded to the controller.

Refs #4409 (mara: c94764, c94782).
2026-10-12 01:25:58 +02:00
atlas
e4b170f8c6 docs: the hive has no approval queue
config-changes.md (was approvals.md) describes config PRs, the meta flake
and helper events; the approval queue, its kinds, the withdraw path, the
job-queue dispatch and ApprovalResolved go. Every other page drops the
approvals table, endpoints, event, tool group, hivectl verbs, dashboard
section and the loose-end/cancel kind, and links to the renamed page.
2026-10-04 23:55:32 +02:00
atlas
b4a2aeb5ba docs: scheduled prompts are swarm-level; the hive page is a read-only list
Drops docs/tools/scheduling.md and every description of hive-level
schedule creation, the worker, the removed MCP tools, the scheduling tool
group and the removed endpoints/events, pointing at docs/swarm/schedules.md.
2026-10-04 21:23:31 +02:00
atlas
fd74cbd495 docs(turn-loop): facts + structure pass
Frame the turn loop runtime-neutrally: every turn runs through
hive-runtime, on claude (default) or an ACP agent. The loop steps,
harness binary shape and hive-agent README now say so; claude-only
failure detection gets its own heading; claude-invocation.md opens with
its scope and links the ACP side to hive-runtime/README.md.

Fact fixes: on-boot file paths (/run/hive-config, not /run/hive),
hive-claude is a crates.io dependency with no README here, hive-agent
has no client.rs or forge_notify.rs, the claude launch-config layer is
hive-agent's mcp_config.rs, agent forge/matrix accounts are
swarm-controller's, hive-c0re's dashboard is dashboard/, and the
deprecated hyperhive.gui.enable / hyperhive.extraMcpServers spellings.

Refs #3902
2026-10-02 07:52:13 +02:00
atlas
f189724a4c hive-runtime, hive-agent: model/effort picker for ACP agents from configOptions
An ACP agent's model and effort pickers now list what its session offers
(its `model` and `thought_level` config options) instead of the claude
model list and EFFORT_LEVELS. A pick goes through the same Bus::set_model /
Bus::set_effort -> Config.model / Config.effort path as on claude; before
each prompt the ACP runtime sets it with `session/set_config_option`, model
first, and only when the session offers that value. Options are re-read
from the set response and from `config_option_update`, so the effort picker
disappears when the chosen model offers no effort levels, and is hidden
while a newly picked model waits for the next turn.

The session's options reach the web UI through a `Choices` handle from a
new `Runtime::choices`, registered on the bus the way `canceller` is.
/api/model and /api/effort accept only offered values on ACP. The claude
path is unchanged.

Refs #4391
2026-09-30 10:53:53 +02:00
atlas
84d4808d54 hive-subagent-mcp: run an agent's subagents on its runtime
The subagent daemon now reads the parent agent's runtime at startup
(`hive_runtime::RuntimeSpec`, from the harness's `HIVE_RUNTIME` /
`HIVE_ACP_*`, which `mcp.nix` forwards onto its unit). On claude
nothing changes. On ACP, each run drives an `AcpRuntime` whose session
id is kept per name under the harness dir: `start` archives the old one,
`continue` loads it (and fails when none is recorded), `interrupt` sends
`session/cancel`, a role goes in front of the first prompt, and
permission requests get the answers a claude subagent's tool list
gives. The unit loads `backendEnvironmentFile` on ACP only, so the
agent can authenticate.

The end-of-turn handling moves out of the claude loop into `after_turn`
unchanged, so both loops share it.

Refs #4391
2026-09-30 07:41:12 +02:00
atlas
bcbeb8ac9c hive-runtime: cancel and idle watchdog for ACP turns
`Runtime` gets a fourth operation, `canceller()`: a handle that stops the
turn in flight from outside `run`. The ACP backend returns one; claude
returns `None`, because the harness stops a claude turn by signalling the
`claude` process, and that path is unchanged.

Both stops send the agent `session/cancel`:

- `Canceller::cancel()`, when asked from outside. The turn then ends
  normally, reported with stop reason `cancelled` whatever reason the agent
  gives. opencode 1.15.10, for one, answers a cancelled prompt with
  `end_turn` (`acp/agent.ts` `prompt()` always returns `end_turn`).
- The idle watchdog, once no `session/update` has arrived for
  `Config::idle_timeout`, the same field claude's watchdog reads. The turn
  fails with `AcpError::IdleTimeout`.

An agent that has not answered the prompt 10s after `session/cancel` is
killed (`IdleKilled` / `CancelIgnored`), and the next turn respawns it.

The watchdog is also what ends a turn stuck on a provider HTTP 429.
opencode 1.15.10 retries a retryable provider error with no attempt limit
(`session/retry.ts` `policy`, `session/processor.ts` `Effect.retry`) and
forwards neither `session.status` nor `session.error` over ACP (its
`handleEvent` only handles `permission.asked` and `message.part.*`). So the
ACP client sees nothing at all until the provider recovers. The
`IdleTimeout` message says a silently retried provider error looks like
this.

Refs #4391
2026-09-29 23:22:50 +02:00
atlas
f0110be76c hive-agent: drive turns through hive-runtime
AgentSession becomes hive_runtime::AgentRuntime, picked at startup from
the environment. Unset HIVE_RUNTIME keeps the claude backend, built
from the same title, store and PercentPolicy as before; drive_turn,
the 401 retry and the error mapping see the claude errors unchanged.

On acp the harness keeps the session id in the harness dir, answers
permission requests like the claude built-in allow-list (no built-in
shell, web fetch only with web_tools), maps runtime errors to Failed,
and reports an operator /compact as skipped instead of done.

Refs #4391
2026-09-29 22:29:36 +02:00
atlas
53d9ebdede nix: list nix/container-modules/ among the module trees
Refs #3773
2026-09-29 19:51:46 +02:00
atlas
8cc7f90c98 log: send records natively to journald, keep stdout off-unit
A record written to stdout carries no priority, so journald files the
whole stream at one level and the swarm log store shows `info` whatever
level `tracing` gave it. Under a systemd unit the process's stdout
already *is* the journal, so the fix is to speak the journal protocol
directly and let each record carry its own severity.

New `hive-log` crate holds the one sink chooser, called by `hive-c0re`,
`hive-agent` and `swarm-controller`. It builds the same `EnvFilter`
those binaries always built, then installs exactly one layer — never
both, since a journald layer stacked on the `fmt` layer under a unit
stores every record twice.

The choice is an fstat compare, not a presence test: a child inherits
`$JOURNAL_STREAM` even when its own stdout was redirected elsewhere, so
the variable existing proves nothing. The crate parses `dev:inode` out
of it and compares both numbers against an fstat of stdout, the
descriptor the `fmt` layer writes to by default. No match, unset, or
unparseable takes the `fmt` branch. A journald layer that fails to
construct despite a match falls back to `fmt` and warns through it —
a process must never fail to start because of its logger.
2026-09-21 15:52:57 +02:00
atlas
34129d776c subagent: give each run its own signal URL, and drop the name argument
`goal_reached`/`need_help` took the session name as a tool argument, so
identity was an assertion by the caller and the only guard on it was
`occupancy()` — "does that name have a turn in flight", which two
concurrently running siblings both satisfy for each other. A subagent
could stop its sibling's run by naming it.

Identity moves into the URL. Each spawned run is minted an unguessable
token (`Uuid::new_v4`, the OS CSPRNG), the URL carrying it goes into that
one subagent's own `--mcp-config`, and the route resolves it back to a
session before dispatching to a handler bound to that session. Neither
tool takes a `name` any more: a subagent has no field in which to name a
sibling, and a sibling's name — which a brief may well mention — is not a
token.

One route with a path parameter, not a route per session: the `Router` is
built once at startup and subagents come and go for the daemon's whole
life. An unminted or revoked token gets a bare 404, the same answer either
way, so nothing enumerates. A run's token is revoked when the run ends
(`finish_turn`) or when a call never reached a spawn.

Two things fall out of that:

- the config file becomes one per session. A single shared path was
  already a race between two `start`s; with a per-session URL in it, the
  loser would read the winner's identity.
- `occupancy()` stops being the identity guard and is gone from the signal
  path entirely rather than kept "just in case" — a revoked token can't
  reach it, and it never answered the question it was standing in for.
  It still backs `status`, which is what it was always actually for.

Refs #4403
Refs #4413
2026-09-14 22:24:51 +02:00
atlas
b18348bc9a subagent: give a run a goal, turns toward it, and a reason it stopped
`start` takes an optional `goal`. With one set a session stops being a
single turn: when a turn ends and nothing has said to stop, the daemon
spawns another turn re-prompting the subagent toward that goal, up to
`max_turns` (default 5, per-session). Without a goal nothing changes —
one turn, one todo, same as before.

Four things end a run, each recorded distinctly and reported by `status`:
the turn ending with no goal, `goal_reached`, `need_help`, and the turn
cap. The last says so out loud rather than stopping quietly — the todo
states the harness limit was reached and the goal was never reported
reached. Every stop extends the done message rather than replacing it,
and lands in the session's report file when it has one. The path is
never inferred: it comes from `start`'s `report_file` or from the
subagent naming where it wrote.

`goal_reached` and `need_help` are the subagent's own, served on a second
route (`/signal/mcp`) that carries those two tools and nothing else, so
reporting on a run can't become starting one. `goal_reached` is built as
a label, never a gate: it is self-reported by a subagent that has just
been re-prompted with "you haven't reached the goal", which is exactly
the incentive to claim it — the same failure class as a build report
asserting the tests pass. Every surface that renders it says so.
`need_help` is the blocking signal, and shows in `status` as its own
state so a parent polling it sees the block without reading a file.

`status` also carries `turn N of M`: with 4330's last-event age, that
separates working from wedged from out of turns off one answer.

Two bugs the new tests caught: a `tokio::fs::File` was dropped without
flushing, so the report line was written to nothing, and the plain idle
answer dropped the turn counter.

Also documents `await_resume`'s third case — a closed channel with no
send, which fails open the same as `Underway` — per argus on #4411.

Refs #4403
2026-09-14 21:46:59 +02:00
atlas
30fa54cbc6 feat(swarmctl): add agent create, queueing the swarm-controller creation DAG
`swarmctl agent create <name> --hive <hive>` POSTs `/api/agents` to
swarm-controller over the daemon's unix socket and prints the queued
job's node id.

It deliberately does not wait. The endpoint queues a DAG whose last node
*publishes* a deploy message; the hive's `hive-c0re` then converges on
its own clock, out of the controller's sight. So even a fully settled
graph would not mean the agent is up, and there is nothing this CLI
could wait for that would let it claim otherwise. Printing the id is
exactly what the response says and all of what it says.

Transport is a bare hyper HTTP/1.1 client handshaked onto a tokio
`UnixStream` via `hyper_util::rt::TokioIo` — the same crate family
`hivectl/src/watch.rs` and `hive-agent/src/web_ui/proxy.rs` already use,
all of it already workspace-pinned. The request/response shapes are a
local mirror rather than a shared crate: the controller's own types are
private to its binary and this crate does not link it, the same
separation `hivectl` keeps from `hive-c0re`.

Errors are reduced to one actionable line — the controller answers
RFC 9457 problem+json, so an unknown `--hive` reaches the operator as
the roster of hives that would have worked rather than a body dump.
Response `warnings` are printed when non-empty.

The nix module wraps the binary with `SWARM_CONTROLLER_SOCKET`, read
from the same `socketPath` the daemon binds.

Refs #4399
2026-09-14 19:40:23 +02:00
damocles
349f7096ba subagents: fold hive-extra-mcp into hive-agent-sock per mara's rescoping 2026-09-13 13:57:53 +02:00
damocles
c2fb3c6e3e subagent: add status tool, cut docs down to operator-facing + no cli flags 2026-09-09 23:45:12 +02:00
damocles
e64639c4d0 docs: document the independent subagent daemon (start/continue/interrupt) 2026-09-09 23:45:12 +02:00
atlas
f2713486a5 docs: the repo map gains the crate this branch adds
/knowledge/doc-standards.md lists "file added -> CLAUDE.md ## Repo map" under
always-update, and "CLAUDE.md after a file rename" under most-commonly-missed.
This branch added a workspace member and I missed it; a scheduled nudge to
re-read the hive rules is what caught it, not a gate.

The entry leads with the BAO_ vs VAULT_ env mismatch because that is the part
a reader cannot derive from the crate name: vaultrs' own defaults look for
VAULT_ADDR / VAULT_CLIENT_CERT / VAULT_CLIENT_KEY, no unit in this tree sets
those, and falling through to them produces a client with no identity whose
only symptom is a TLS handshake failure.

Measured while doing it: 6 of 28 workspace members were absent from the map.
Five predate this branch (hive-jobq-metrics, swarm-authelia-bridge,
swarm-authelia-bridge-sock, swarm-nats-auth, swarm-queue-client) and are
deliberately left alone here rather than widening this PR.

Refs #3726
2026-09-03 00:29:52 +02:00
iris
d7c171b805 CLAUDE.md: add the summary-line markdown gotcha to the doc-split note
argus's #3989 review caught it: a <details><summary> line isn't
markdown-processed, so backticks in it render as literal characters
rather than styled code. Worth banking now, before it gets rediscovered
per-file across the other ~19 docs #3902 still has to touch.
2026-09-02 21:42:59 +02:00
iris
67207ae32f docs: pilot split of github.md into operator-facing + collapsed implementation
hyperhive#3902, mara: option (a) - a content pass splitting mixed docs
into operator-facing content plus implementation detail. One file
first, to agree on the split pattern before doing the other ~19.

Went through 3 shapes on review before landing here: a sibling
-internals.md file (mara: clutters the navigation), then two
tree-precedent alternatives damocles raised (subdir+README like
web-ui/; or only split docs with a pre-existing boundary marker,
which would've covered 3-4 of the ~20 flagged docs and left the rest
untouched), then mara's own proposal - a collapsed <details> section
in the same file. Verified empirically (cmark-gfm --unsafe, the
website's own render pipeline) that markdown headings nested inside a
<details> block still parse as real headings with heading-id anchors
once separated from <summary> by a blank line, so anchor links into
the collapsed section keep working.

github.md keeps enabling/provisioning/security up top; its
'Implementation' section is now a <details> block holding what was
briefly a separate github-internals.md (deleted again) - how the
agent's gh/git-push actually authenticate, and the notification
poller's internals. Reverted the two cross-references + the
docs/README.md entry back to pointing at github.md now that the
content lives there again.

Added a short CLAUDE.md note recording the pattern per mara's ask,
including the one real caveat damocles flagged: <details> only
collapses in a rendered browser, a raw-text read (cat, the Read tool)
still sees everything, same as today.
2026-09-02 20:38:02 +02:00
iris
07b62612b0 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.
2026-09-02 01:55:37 +02:00
atlas
ff7897f9bc CLAUDE.md: the mistyped label separator errors now, it does not pass
This warned that `--label area:ops` "matches nothing and silently returns
unfiltered results". That stopped being true when hive-forge learned to suggest
the nearest name: it now exits non-zero with `unresolved label name(s): area:ops
(did you mean "area/ops"?)` and lists the available labels.

Measured, not assumed — the old text is what sent me looking for a silent
failure that no longer exists, which is the specific cost of a doc that
describes a fixed bug.
2026-08-31 18:13:33 +02:00
atlas
d6ca52535f docs: give the deployment shapes one home and link to it
The three supported shapes were described in pieces across the swarm
docs, so a reader assembling them got a different answer depending on
which page they landed on. They live in services.md now, and the other
pages point at it.

Split out of #3719 at mara's request: that PR also rewrites the
snapshot-store doc, which is a separate argument.
2026-08-30 14:46:57 +02:00
iris
5593fa76bb CLAUDE.md: cut the meta-commentary, just state the rule
mara: 'you did what the docs are trying to get rid of.' The prior
edit explained where the fuller guidance lives instead of just
being the two-sentence essence itself.
2026-08-30 14:20:57 +02:00
iris
c79b191b4f CLAUDE.md: drop the hive-specific /knowledge link
mara: hyperhive isn't tied to one hive/swarm's knowledge repo.
2026-08-30 14:20:57 +02:00
iris
b676cd4a3b CLAUDE.md: add comment/PR-description length pointer
mara, PR#3759: follow-up after internal/knowledge#82 merged.
2026-08-30 14:20:57 +02:00
atlas
d3b40da1c8 deploy: give every option an enable, and name the controller one
Two corrections from review, applied forward on this branch rather than
by rewriting it.

`deploy.<service>` was a bare bool, which makes
`deploy.forgejo = { enable; ci; }` unrepresentable -- the nested
CI-runner sub-option this namespace was designed around. Every entry is
now an attrset with an `enable`, so a second per-host deployment
decision becomes an ordinary addition rather than a migration.

`deploy.controller` is now `deploy.swarm-controller`, consistent with
`deploy.swarm-ui`, which was introduced in the same commit.

89 references rewritten across 24 files -- nix, Rust, docs, and the
repo's own CLAUDE.md.

The prefix-anchored sweep missed exactly one, and it was live code:
hive-tls.nix spells it `hyperhiveCfg.deploy.controller` -- the only
`hyperhiveCfg` prefix among 45 references. A suffix grep
(`\.deploy\.<name>`) finds it; a path-anchored one cannot, because the
head of a reference is whatever alias the reading file happens to bind.
2026-08-30 04:23:22 +02:00
atlas
0b7357d4b8 deploy: move the controller and swarm-ui toggles
One commit rather than two because they are not independent: the UI's
`enable` had the controller's as its literal default, so moving the
controller alone would leave the UI's default naming an option that no
longer exists.

The UI keeps that derivation in its new home — it is a view onto the
controller's state and reaches it over that daemon's unix socket, so the
host running the controller is the host that can serve it.

Three spellings had to move together for the UI, not one: the `default`,
the `defaultText` shown in the options doc, and the description prose
that names the old path in words. A grep for the option path finds the
first two.

The sweep also reached outside nix: `swarm-controller`'s crate README and
its `//!` module doc both named the option, as did this repo's own
CLAUDE.md and four pages under docs/. An option's name is API, and its
documentation lives wherever someone thought to write it down.
2026-08-30 04:23:22 +02:00
damocles
16ac84ca63 docs/hive-c0re: fix ask/answer removal doc gaps argus caught on #3741 2026-08-30 03:02:31 +02:00
iris
8363a459bf docs/jobq.md: drop the core-specific node table, keep only the abstract engine explanation
mara: "only the abstract jobq part was asked for in the first place" —
the previous revision still carried the full hive-c0re step-kind table
under a "core-specific nodes" heading; that catalogue belongs in
coordinator.md (where it already lived) alongside the rest of
hive-c0re's job-queue internals, not duplicated here.

jobq.md is now just the domain-agnostic model (graph of steps + shared
resource slots) plus the generic row/step/glyph framing for watching it
in the dashboard — no hive-c0re-specific step names anywhere.
Coordinator.md's job-queue section, docs/README.md, and the root
CLAUDE.md reading-paths index are updated to match.
2026-08-26 22:50:49 +02:00
iris
582aebc5eb docs: add operator-facing jobq page, dedup coordinator.md pointer
hyperhive/hyperhive#2999 asked for an operator-facing page explaining
the job queue's node kinds in the abstract, without the coordinator's
implementation detail (leases, resource windows, module layout).

Add docs/jobq.md: what a queue row/step means, a plain-language table
of every node label an operator actually sees in the dashboard
(BU1LDS/R3BU1LD QU3U3 and swarm-ui's /jobs page both render the same
graph), and a rough shape for a typical rebuild.

Wire it into docs/README.md and the root CLAUDE.md reading-paths index
alongside coordinator.md. Add a pointer from coordinator.md's job-queue
section to the new page so the two don't try to explain the same thing
twice at different levels.
2026-08-26 22:50:49 +02:00
atlas
6ca4887af4 docs(#3422): the user store is one file, not two
Six places asserted the old design as fact, and none of them mention the
change by name -- the class of doc breakage that is found by asking what
a diff made untrue, not by grepping for a feature:

- swarmctl/README.md and swarm-authelia-bridge/README.md both described
  their own private canonical store. The bridge's "known limitation"
  section described the seam as unsolved; it is what this fixes, so it
  becomes what both writers must uphold instead.
- docs/swarm/{sso,ui,secrets}.md described a rendered artifact.
- The repo CLAUDE.md entry for swarmctl said the same.
- docs/tools/swarmctl-cli.md is regenerated (CI diffs it against the
  clap tree), picking up the removed --store flag.

Operator-facing where it is read: the hand-editing consequence (values
survive a rewrite, comments do not) is stated in sso.md, where an
operator is being told to edit the file, rather than only in a module doc.
2026-08-18 10:34:00 +02:00
iris
7fc426b4dd swarmctl: add CLI reference docs, same pattern as hivectl
Adds swarmctl markdown-docs (a hidden Verb, same clap-markdown +
hide=true shape as hivectl markdown-docs) and generates
docs/tools/swarmctl-cli.md from it. Wires a swarmctl-docs freshness
check into nix/checks.nix, same shape as hivectl-docs, diffing against
packages.swarmctl.

One real gotcha: PathArgs::resolve() reads required
SWARMCTL_AUTHELIA_* deployment env vars and errors if unset -
swarmctl markdown-docs must not go through that path (it needs none of
those vars, and the docs build runs it outside any real deployment).
Restructured main() so resolve() only runs for the User arm, not
unconditionally before the match.

Also links the new doc from docs/tools/README.md (new 'for the swarm
operator' section), CLAUDE.md's swarmctl bullet, and
docs/conventions.md's flake-check list.

Verified: cargo check/clippy -D warnings/test/fmt -p swarmctl all
clean; swarmctl markdown-docs diffs clean against the committed doc
(checked against both a plain cargo build and the actual nix build.
#swarmctl output); scripts/check-issue-refs.sh clean.
2026-08-11 21:55:56 +02:00
iris
7d1b18d2c8 swarm-ui: bootstrap new swarm-level frontend (Preact + wouter + TS + JSX)
Project-bootstrap scope per the issue: static build as a nix pkg,
empty start page for now, functionality deferred until auth against
authelia is figured out. Stack (Preact + wouter + TypeScript + JSX)
matches the shell decision from the earlier framework-paths thread —
a real SPA shell with a router and deep links, distinct from the
per-hive dashboard's vanilla-JS + custom-element MPA.

- New npm workspace frontend/packages/swarm-ui: one route (/), a
  wouter Switch/Route shell, a 404 fallback. Reuses @hive/shared's
  colors.css/theme.css/base.css for visual consistency; no other
  shared JS (the vanilla-JS el()/dom.js helpers are superseded by
  Preact in this shell).
- nix/packages/swarm-ui.nix: its own buildNpmPackage derivation
  (scoped to just this workspace via an explicit buildPhase), not
  folded into nix/packages/frontend.nix's packages.default closure —
  same reasoning swarm-controller/swarmctl already use for staying out
  of daemonBins: a hive that doesn't run the swarm controller
  shouldn't carry swarm-ui bytes.
- npmDepsHash recomputed in both frontend.nix and swarm-ui.nix (same
  shared lockfile, new deps: preact, wouter-preact, typescript).
- Added swarm-ui to nix/checks.nix alongside frontend, for the same
  FOD-staleness reason plus being the only thing that actually builds
  it in CI (not in packages.default's closure like frontend is, so
  nix flake check wouldn't otherwise touch it).
- npm run typecheck (tsc --noEmit) is available locally; not yet wired
  into CI — esbuild transpiles TS without type-checking, so that's a
  real gap, left as a follow-up rather than growing this bootstrap PR
  with a new CI workflow step.

Verified: nix build .#swarm-ui and .#frontend both succeed; npm run
build (root, all workspaces) succeeds; tsc --noEmit clean;
scripts/check-issue-refs.sh clean.
2026-08-11 21:31:44 +02:00
atlas
9e44efa01f feat(#3089): add swarmctl and a user-add verb for the swarm's SSO
The swarm-authelia module states that its users database is written by
swarm-controller, but nothing ever granted the means. This adds the tool
that does it.

swarmctl runs as root on the controller's host and acts directly. The
rootless alternative was examined and does not work: relocating the users
file into a directory the controller owns only turns a write problem into
a read problem, because authelia must then reach across the same boundary
in the other direction. Making that read work needs either a hand-pinned
gid or world-readable password hashes.

The user store is two files, one authoritative: users.json is canonical,
users.yml is a rendered artifact. That split is what lets the crate work
without a YAML parser -- the workspace has none, and adding one costs a
crates.io fetch, a lock update and a vendor hash for a schema we fully
control and only ever emit.

Passwords are generated by authelia rather than passed to it: argv is
world-readable, so a password on a command line is readable by any local
process for the lifetime of the call.

The three derived facts swarmctl needs about the authelia container --
machine, unit and the host-side users path -- become readOnly options on
the authelia module rather than literals repeated at the call site.
2026-08-10 21:48:45 +02:00
atlas
b3b1ed19c6 docs: split swarm.md into a directory, starting with the services page
`docs/swarm.md` becomes `docs/swarm/README.md` and the shared-services
material moves to `docs/swarm/services.md`, following the shape
`docs/turn-loop/` and `docs/web-ui/` already use. The README keeps a
pointer so the reading path is unbroken.

Every referrer moved with it — five docs pages, two option descriptions
in swarm.nix, and CLAUDE.md's reading path. A pointer to a file that
moved is worse than one to a file that was deleted: the content still
exists, so the reader concludes the note is wrong rather than the path.
2026-08-05 18:07:04 +02:00
atlas
dde0e1a8c1 docs: let the crate README own the technical detail, point at it
Per review: crate READMEs will be served on the docs page, so the same
technical content in docs/swarm.md and the repo map is redundancy, not
thoroughness.

docs/swarm.md keeps only what is operator-facing and specific to it --
the option, and why enable is not derived from services.hyperhive.enable
-- and points at the README for the socket-directory constraint. The repo
map keeps the one-line warning and the pointer, not the argument.

This is the same correction as the AgentWindow comments: I had written
the socket rationale into five places and called it coverage. Correcting
every copy is what preserves the cause.
2026-08-05 14:59:37 +02:00
atlas
0fe2babbee docs: put swarm-controller in the repo map and the swarm doc
The crate was a workspace member with no entry in CLAUDE.md, which is the
index that auto-loads into every turn -- a member missing from it is
invisible to everyone who comes after.

Both entries carry the socket-directory constraint rather than just
naming the daemon, because that is the one thing about this service a
reader can get wrong from a position that looks correct.
2026-08-05 14:59:37 +02:00
argus
314aa77ee8 docs: the PR review gate — reviewers don't wait on CI, operators know what auto-merge commits them to
Prompted by hyperhive/hyperhive#3050: a reviewer held a formal
approve/request-changes verdict for CI to go green, which delayed a
merge that was only waiting on the review. CI, review approval, and
auto-merge arming are three independent gate conditions — a reviewer
is only responsible for the second one.

Documents the asymmetry that follows from auto-merge sometimes being
armed before a review lands: an unnecessary request-changes costs a
round-trip, but an approve given on lingering doubt can ship
immediately with no further human check. Default to holding when
unsure.
2026-08-04 17:09:43 +02:00
damocles
642377c5e0 docs: stop implying matrix/bash/forge is a closed todo-producer list 2026-08-04 00:06:19 +02:00
atlas
6e18bbc839 jobq: say the graph is runtime-only, because it is
The crate described itself as a persistent scheduler and NodeId promised
stability across restarts. Neither is true: hive-c0re constructs an empty
Graph on every boot and re-derives desired state with its reconcile sweep,
and nothing in the workspace writes or loads a graph. hive-c0re's own
job_queue module doc has said "runtime-only (no persistence)" all along —
only the extracted library's prose drifted.

Seven claims corrected across the module doc, NodeId, the id-counter error
and the Graph type, plus the repo map. The module doc now states the fact
positively rather than just dropping the word: serde exists so the graph can
be projected onto a wire and so a store could be added later, ids and
timestamps are stable within a run.

NodeId spells out the consequence, since that is the part that could mislead
someone: an id stored outside the process is a historical record, not a
handle that will resolve after a restart.
2026-08-03 17:28:09 +02:00
iris
04b274753b docs: give turn-loop/ a README.md landing page
Part of hyperhive#1898 (b): every docs subdir should have a top-level
README.md link, achieved by moving/renaming where an existing file
already fits the role.

docs/turn-loop.md already served as the hub + index for the three
sub-pages under turn-loop/ (claude-invocation.md, config.md, mcp.md),
so it moves wholesale rather than leaving a redundant top-level
pointer stub. Fixes every inbound/relative link across the repo
(top-level README.md, CLAUDE.md, docs/persistence.md,
docs/tools/scheduling.md, the sub-pages own back-link, hive-agent
README + doc comments, hive-agent/Cargo.toml, .prettierignore per-file
exemption entry) - grepped the whole tree for both turn-loop.md and
turn-loop/ to find every reference rather than trusting a partial
list.

nix fmt clean, cargo check -p hive-agent clean.
2026-08-03 12:55:18 +02:00