Commit graph hyperhive/docs
Author SHA1 Message Date
atlas
6a8a729f58 fix(#3020): K3PT ST4T3 says what it shows, and stops reading the job graph
`build_tombstone_views` folded `transient_snapshot`'s keys into its `live`
set, so an agent with in-flight transient work was treated as not-a-tombstone.
Since #3010 that set is derived from the running job graph, which made a
page about on-disk state a function of the scheduler.

Dropping the filter exposes what was always true underneath: nothing records
a destroy. Every definition-side artifact — state subvolume, proposed +
applied repos, `deployed/0`, meta registration, topology entry — is written by
`Provision` before the container exists and survives `lifecycle::destroy`. So
a mid-spawn agent is byte-identical on disk to a tombstone.

Per mara on #3020: remove the filter, warn on the page, keep the issue open
for the swarm-controller / snapshot-storage rework where the problem shape
changes anyway.

- dashboard/tombstones.rs: drop the param + the chain; document the real
  semantics
- core.js: amber caveat banner above the rows; row badge `destroyed` ->
  `offline`, which is what an absent container actually proves
- dashboard.css: `.tombstone-warn`, modelled on `.port-conflict` but amber and
  without the pulse — a permanent banner that pulses trains you to ignore it
- docs/web-ui/dashboard.md: the pane was described as "destroyed-but-state-kept
  agents", now the exact wrong claim
2026-08-03 18:07:04 +02:00
iris
e41417d00e docs: add a top-level docs/README.md index, point README.md at it
Fixes hyperhive#3014.

docs/README.md is a genuine hand-written index for the docs/ tree -
task-oriented reading paths grouped by topic, covering every top-level
doc plus the three subdirectories that already have their own landing
page (web-ui/, tools/, turn-loop/). Adapted from CLAUDE.md's existing
"Reading paths" section (already curated and kept current) rather than
written from scratch, reorganized into headed groups since this is a
landing page, not a flat reference list. Explicitly covers
docs/tools/matrix.md and docs/github.md, per the note on hyperhive#3014
about the content that moved out of README.md's deleted section on
PR #3006 not going undiscoverable.

Top-level README.md's reading-path table (enumerating every doc)
replaced with a single prominent docs-site link, per mara's "replace
docs links table in readme md with a prominent link to the docs
(public host and git relative path)" - both forms present (rendered
site URL, git-relative docs/ path).

Verified every link in docs/README.md resolves to a real file (33
files checked). nix fmt clean, tracker-tag grep clean.

Companion to hyperhive/website#47/#48 - this is what that PRs "index.html
half" needs to exist before it can render, per atlas's note on #48.
2026-08-03 17:27:43 +02:00
iris
3993f635d5 docs: add tools/ landing page
Part of hyperhive#1898 (b), the other missing docs subdir. Unlike
turn-loop/ this one has no single existing file that already covers
the whole directory - hivectl.md/hivectl-cli.md are genuinely
operator-facing (the operator's own host CLI), while bash.md/forge.md/
lifecycle.md/matrix.md/scheduling.md document the agents' own MCP tool
surface (a different audience: what the agent can do, not what the
operator does). Writes a new README.md rather than moving one,
splitting the link list along that line so the operator-relevant half
leads.
2026-08-03 12:57:00 +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
iris
c6af5a2abf docs: trim impl detail out of the top-level README
Per mara on hyperhive#1898: the top-level README should not go into
details about impl details or specific subsystems. Trims the
Multi-account Matrix support and GitHub account sections down to a
one-line pointer each.

The matrixAccounts option detail that was only ever documented in the
README (checked: docs/matrix.md is entirely about the host hive-matrix
container, not this per-agent option) moves to docs/tools/matrix.md
instead, next to the account parameter every matrix tool already
takes - the natural home, not a link to a page that never covered it.
2026-08-03 12:48:37 +02:00
damocles
8db1cbd204 hive-forge: add pr edit, reusing issue edit's shared /issues endpoint 2026-08-03 01:54:28 +02:00
iris
aa149a7a62 builds: mount <hive-jobq-graph> directly, drop the hand-rolled queue renderer
Per mara's explicit steer on hyperhive#2812 ("also replace the build
queue tab with this component" + "graph fetching should live in the
component, not build.js" + "dont replicate the grouping by dag"):
R3BU1LD QU3U3 is now a mounted <hive-jobq-graph endpoint="/api/jobq/graph">
element. builds.js no longer renders the queue itself, does its own
fetch, or hand-rolls a per-root tree/roll-up/cancel-button — all of
buildNodeTree/topoSort/entryFingerprint/renderQueueEntry/
firstFailedNode/rebuildQueueRowCache/QUEUE_STATE_GLYPH/rollupState is
gone.

builds.js's remaining job is listening for the component's
hive-jobq-graph-update event (added to the component in the prior
commit) to keep a flat jobqNodes array in sync, and using that for the
two things the generic view doesn't render: the count-pill and the
live-log panel. On the rebuild_queue_changed SSE tick, calls the
mounted element's .refresh() instead of doing its own fetch — that
event still carries its own queue payload on the wire (tabs.js/SW4RM
still reads it for the badges, untouched), this page just ignores it
now.

Also removed, now genuinely dead: the two elapsed/finished-time
tickers (nothing produces the .rqe-when spans they targeted anymore),
stateSlug and isoToSecs (no callers left), fmtElapsed import (no
callers left).

New @hive/shared/jobq-graph.js export entry in packages/shared's
package.json, alongside the existing hive-tab-strip.js/hive-menu.js/
etc. pattern.

docs/web-ui/dashboard.md's R3BU1LD QU3U3 section rewritten to match:
mounted-component shape, no source/reason/cancel-button/deep-link on
rows (generic component has none), settled entries show their full
step tree (Done nodes aren't filtered off this wire, unlike the old
DagView projection).

Verified against real production data again (this hive's own live
/api/jobq/graph, now settled — no in-flight build at test time) plus
a synthetic running-build case to exercise findLiveBuild's happy path:
correct live-node detection (build_log_id gate), correct in-flight
root count. Confirmed the built dist bundle actually registers
customElements.define("hive-jobq-graph", ...) — the new shared
package export resolves correctly through esbuild.

Branch reused per mara's explicit "dont rework #3000 - continue
working on _this_ pr [#2996], it already has the component that
replaces 90% of build.js" — this ships as part of PR #2996, not a
separate PR.
2026-08-03 01:53:39 +02:00
iris
3512e4b019 dashboard: hide forge links instead of guessing <hostname>:3000
Adds services.hyperhive.forge.publicUrl (defaults to the gateway vhost
URL when behindGateway=true, null otherwise). HIVE_FORGE_PUBLIC_URL is
now sourced from it instead of hardcoding https://${forge.domain}
whenever behindGateway is on.

The 4 frontend call sites that built a forge link from
state.forge_public_url now hide the link when that's absent, rather
than guessing http://<browser-hostname>:3000 — a guess that's only
correct by accident once the operator isn't on plain localhost. Fixes
the dashboard H0M3 tile, per-agent-row forge links + agent menu, the
approval-queue PR link, and the per-agent page's own meta-nav forge
link (found during this pass, same defect, not in the original
3-site inventory).

Docs + doc-comments updated to match.
2026-08-03 01:21:11 +02:00
damocles
68560215bd hive-c0re: deliver scheduled prompts via push_todo, not a broker message 2026-08-03 01:08:24 +02:00
iris
7fba2d6919 docs(ci): add a For operators section
ci.md mixes a genuinely short operator bootstrap step with deep
auto-registration/container-design internals and a full security
writeup an operator does need to read once, but not first.

Adds a short synthesis at the top: enabling is one nix option, the
unsandboxed-build trust tradeoff (fine for a trusted team, gate it if
you take fork PRs), and the disk-maintenance recipe (CI shares the
host nix store with no auto-GC of its own). Every claim checked
against the existing detail further down in this same file.

Part of hyperhive#1898.
2026-08-03 00:30:57 +02:00
iris
7892304827 docs(persistence): add a For operators section
persistence.md is exhaustive schema/impl reference (sqlite table
columns, systemd unit internals, marker files, btrfs subvolume
mechanics) with the one thing an operator actually needs - what
happens to my stuff when I destroy or purge an agent - scattered
across several sections rather than answered up front.

Adds a short synthesis at the top: destroy keeps everything
(revivable, no re-login), purge wipes it all (no undo), approvals and
questions never age out, message history vacuums acked rows at 30
days, an agent's own /state/ and claude login survive every restart/
rebuild, and the root agent auto-recreates if it's ever destroyed.
Every claim checked against the existing detail further down in this
same file before writing it.

Part of hyperhive#1898.
2026-08-03 00:23:16 +02:00
damocles
601cd295e5 docs: describe the push_todo lifecycle-notice migration 2026-08-03 00:04:24 +02:00
iris
2fb0d08274 docs(approvals): fix deny-notification conditionality
argus review: the submitting agent is always notified when its request
is denied (HelperEvent::ApprovalResolved fires unconditionally); only
the reason text is optional, and only from the dashboard prompt (not
the CLI, which has no reason argument at all). Also note that
cancelling the dashboard prompt aborts the whole deny, not just the
reason.
2026-08-02 23:59:53 +02:00
iris
23ff147953 docs(approvals): add a For operators section
approvals.md (655 lines) is genuinely dense implementation reference
(webhook mechanics, DAG phase names, db column names, git-ref rollback
internals) with nothing written for an operator deciding whether to
click approve. Unlike web-ui/, this is a single top-level file with no
subdirectory to give a separate landing page to, so the fix here is
in-file: a "For operators" section right after the intro, covering
what actually shows up on the dashboard (or hivectl CLI) and what each
approval kind does when you click approve or deny, before the existing
implementation detail.

Every claim in the new section was checked against the rest of this
file plus dashboard.md and hivectl-cli.md rather than assumed - caught
one real error before pushing (hivectl approvals list doesn't exist,
the verb is `pending`) and one incomplete claim (denial reasons are
dashboard-only with an optional prompt; the CLI deny has no reason
argument).
2026-08-02 23:59:53 +02:00
iris
052767c25b docs(github): move impl detail below the operator-facing sections
github.md mixed operator content (enabling, provisioning, security)
with deep implementation detail (the gh wrapper/credential-helper
mechanics, the notification poller's internals) in file order, so an
operator reading top-to-bottom hits internals before finishing the
part they actually need.

Pure reorder, no rewrite: Enabling -> Provisioning -> Security (all
operator-facing) now come first: How the agent uses it and
Notifications (both pure impl detail) move to the end, with a one-line
marker between them. Every word of existing content is unchanged, only
section order moved - lowest-risk shape for a file like this with no
subdirectory to split into (see hyperhive#1898).
2026-08-02 23:54:55 +02:00
iris
53ce83ff56 docs(web-ui): fix Stats/Peers/Settings tab-strip miscategorization
argus review on hyperhive#2986: the README listed Stats/Peers/Settings
alongside Permissions/Schedules as dashboard tabs. Checked the actual
frontend rather than trusting dashboard.md prose (which is internally
inconsistent on this - some section headers say "tab" for things that
turned out not to be):

- stats.html and settings.html are real separate bundles
  (frontend/packages/dashboard/src/{stats,settings}.{html,js}) - same
  shape as builds.html/core.html/logs.html, moved to the "own page"
  list.
- Peers is neither a tab nor a page - swarm.js::renderPeerHives
  confirms it is a card list rendered inside the SW4RM tab
  (#peers-block/#peers-section), gated on state.peer_hives being
  non-empty. Folded into the SW4RM bullet instead of listing it as its
  own item anywhere.

The dashboard tab strip is genuinely just four: SW4RM, Y3R C4LL,
P3RM1SS10NS, SCH3DUL3S.
2026-08-02 23:30:39 +02:00
iris
025cc4f18d docs(web-ui): add an operator-facing README as the subdir landing page
docs/web-ui/ had four pages, all written as deep implementation
reference (dashboard.md alone is 1466 lines of wire shapes and DOM
mechanics) - there was nothing here written for an operator actually
using the dashboard day to day.

Add docs/web-ui/README.md: genuinely task-oriented content (what the
dashboard is, where the tabs are, the things you'd actually do -
checking an agent, answering a pending question, approving a config
change, granting a permission, reading logs) with pointers into the
existing pages for implementation depth. Point docs/web-ui.md at it as
the operator starting point.

Deliberately not touching dashboard.md/agent.md/shape.md/css-vars.md -
reflowing 1466 lines of dense, working reference content in place
turned out to be a much bigger and more error-prone task than "lead
with the user fact" suggested at a glance (see hyperhive#1898). The
subdir's landing page carries the user-facing content; the existing
pages stay exactly as they are, as the depth layer underneath it.

Verified the new page actually renders as the subdir's index via the
website repo's new subdir-landing-page support (hyperhive/website
PR #46): a real build of the prose-docs derivation confirms
web-ui/index.html now exists, the root index links straight to it, and
every internal link resolves correctly.
2026-08-02 23:26:29 +02:00
damocles
d77f81cd1e hive-c0re: push HelperEvent::Spawned as a todo, not a broker message
Follow-up from #2955 (mara: 'make core able to give agent a todo').
First migration slice: Spawned was pure FYI-check-when-convenient
material, not something needing an immediate turn.

Coordinator::push_todo/push_todo_submitter do a best-effort live dial
of the target agent's own hive-agent-sock (hive_host_sock::agent_todo_
socket), sending the exact UpsertTodo request in-container producers
(matrix/bash/forge-notify) already send. Push, not queue: agent
offline (socket absent) or dial failure is a silent no-op, no retry,
no fallback delivery -- matches mara's 'not available if offline'
call exactly.

HelperEvent::Spawned removed entirely (enum variant + all 3 call
sites migrated: handle_spawn's two arms, finish_approval's Spawn
approval-kind arm) rather than kept alongside a translation layer --
per mara's correction on the first design attempt, migrating the
producer means deleting the old path, not bridging it.

Verified: cargo build/clippy/test -p hive-c0re -p hive-host-sock
-p hive-sh4re clean (318 tests), nix fmt clean.
2026-08-02 23:00:14 +02:00
damocles
6d22b57a6d hivectl: regenerate docs for the start --paused flag 2026-08-02 20:04:37 +02:00
damocles
3421925442 hivectl: add start/stop agent verbs, rename spawn to create 2026-08-02 19:42:59 +02:00
iris
e6ecd8db60 docs: trim implementation detail from the OpenAPI/Swagger doc
mara, PR review: 'less impl details in user facing docs'. Dropped the
utoipa crate link, the ApiDoc source-file pointer, and the
#[utoipa::path(...)] annotation-syntax mention from the Dashboard
endpoints intro paragraph -- kept only what a reader needs (where to
find it, that it's a growing subset not yet the full list). Trimmed
the H0M3-page mention the same way.
2026-08-02 19:13:41 +02:00
iris
3f2fdeac70 docs: document the OpenAPI spec + Swagger UI, add H0M3 API tile
Closes #2965.

hive-c0re auto-generates an OpenAPI 3 spec via utoipa
(hive-c0re/src/dashboard/mod.rs's ApiDoc), served raw at
/api/openapi.json and browsable as a Swagger UI at /api/docs, but
docs/ never mentioned either — genuinely zero hits grepping the whole
docs/ tree. Documented both in docs/web-ui/dashboard.md's Dashboard
endpoints + H0M3 page sections.

Also added the H0M3 hub's API tile mara suggested ("maybe also add
home page app that opens swagger ui") -- a plain static link to
/api/docs, no gating needed since the endpoint always exists
(unlike Forge/Matrix, which are conditionally enabled).
2026-08-02 19:13:41 +02:00
iris
da3fc9bf95 move terminal-verbosity toggle from agent overflow menu to /settings.html
Per mara: 'i wanted you to put this in .../settings.html' — the toggle
belongs with the other operator-local browser preferences, not buried
in each agent's own overflow menu.

Extracted the get/set + localStorage key into @hive/shared/prefs.js so
settings.html (writer) and every per-agent app.js (reader, via
HiveTerminal.create's expandDetails option) agree on the exact same key
without two independently-typed copies that could drift. Removed the
now-unused overflow-menu toggle + its agent.css rules from the agent
page. Docs moved from docs/web-ui/agent.md's overflow-button section to
docs/web-ui/dashboard.md's S3TT1NGS section, next to the existing
browser-notifications preference.
2026-08-02 18:34:40 +02:00
iris
198db326b3 agent web UI: add terminal verbosity setting (expand tool output by default)
Adds a browser-local (localStorage only, no backend field) toggle in
the per-agent overflow menu's new settings section: whether otherwise-
collapsed <details> rows in the live terminal (long tool-results,
Write/Edit diffs, ...) default open. Message-bearing rows that already
default open (send/ask/answer/recv) are unaffected either way.

The shared terminal factory (frontend/packages/shared/src/terminal/terminal.js)
gains an optional expandDetails option (boolean or zero-arg function),
read live on every details()/detailsDiff() call rather than captured
once, so flipping the toggle mid-session applies to the next rendered
row without a reload. Unused by the dashboard's own terminal pane, so
its default-closed behaviour is unchanged.

Closes #2961.
2026-08-02 18:25:45 +02:00
atlas
10dbdb444d job_queue: declare a node's resources where the node is constructed
Resources were derived from the node's kind: `templates::node` called
`NodeKind::resource_deps()`, which fanned out to `needs_build_slot` /
`needs_lease` / `needs_meta_window`. That made the requirement a property
of the *kind*, so a kind that happened to run under an ancestor already
holding the resource could get away with declaring nothing.

Three did. `Start`, `Stop` and `PostSwap` appear in none of the three
predicates, and that was only safe because one construction site fans
them out from inside a lease-holding `Reconcile` — a fact about today's
DAG shape, not about the nodes.

Each of the 41 construction sites now says what it holds. `Start` /
`Stop` / `PostSwap` declare the agent lease; per the contract that is a
re-entrant borrow, which a new test pins rather than argues.

`running_transients` reads the node's declared deps instead of
re-deriving from the kind. That closes the blank-pill gap: the pill went
blank during container start, stop and the post-swap tail because the
declaration was missing, not because the filter was wrong.

The deleted predicates carried the only written record of three design
decisions; each moved to the `Resource` variant it constrains rather than
dying with its function.
2026-08-02 16:29:06 +02:00
atlas
ec16b80415 docs(coordinator): a DAG is declared, not described
The submit-time petgraph `toposort` this described is gone — a cycle
needs an edge pointing at a node declared later, and a handle only
exists for a node already declared. Say why the validation pass is
absent rather than leaving a description of one that isn't there.

`templates.rs`'s module doc was 35 lines and over the comment-block
lint's max; it now points here for the reasoning instead of restating
it, and drops the power-op paragraph that `submit.rs` already owns.
2026-08-02 15:32:05 +02:00
damocles
cf5d65c369 docs: drop history/rationale framing from recv docs, state current behavior only 2026-08-02 04:08:14 +02:00
damocles
ceff662d25 recv: drop wait_seconds from the MCP tool, always an immediate peek 2026-08-02 04:08:14 +02:00
damocles
9e7158f593 docs: describe the knowledge-change broadcast in the sync-mechanism section 2026-08-02 03:13:04 +02:00
iris
94fb42f2b6 docs/agent.md: fix stale 3-flyout description; drop dead buildLooseEndsList
docs/web-ui/agent.md still described a "loose-ends" and "tasks" flyout
that were superseded by the todos flyout when loose-ends-v2 landed —
GET /api/loose-ends and GET /api/bash-tasks are both gone server-side.
Replaced with an accurate description of the todos flyout (including
the mark-done bulk action from #2919), and noted that the ask->operator
inline-answer binding this doc also describes is currently
non-functional (its data source was the same removed endpoint) —
tracked separately as #2922, not fixed here.

buildLooseEndsList in app.js rendered the old loose-ends flyout and had
zero call sites left; removed it. buildAnswerForm stays — reconcileAskBinds
still calls it, even though that path is currently dead per #2922.

Fixes #2920
2026-08-02 02:40:01 +02:00
atlas
e02ac1e86e refactor(#2916): drop the two obsolete startup migrations
Phase 4 (repoint every container onto `meta#<n>`) and phase 5 (rename
the `root` container to `h-root`) were marker-guarded one-shots for
layouts no live hive still has: containers are rendered onto `meta#<n>`
at creation, and the `h-` prefix has been the naming for far longer than
any deployment predates. A one-shot nobody can still trigger is dead
weight, so both are gone along with `repoint_container`,
`rename_manager_container`, `CONTAINER_TIMEOUT` and the two marker paths.

Phase 6 was not obsolete, only misplaced. Ruth's tool groups are now
seeded by `ensure_root_agent` on the one path that creates her, rather
than re-asserted on every hive-c0re boot. The skip-if-already-set guard
survives the move: a destroy+recreate under the same name must not reset
an operator's chosen group set back to MANAGER_DEFAULT.

That also settles a latent bug. Phase 4's marker check was a `return`,
not a skip, so on any hive carrying the marker phases 5 and 6 never ran
at all — the tool-group backfill, whose whole job was preventing a silent
privilege downgrade, has not executed here in a long time. Moving it to
create-time removes the question rather than answering it.

What stays is convergence: three unguarded, idempotent phases that re-run
each boot and no-op once their state is right. The module doc now names
the three categories so the next person can tell which kind they're
adding.
2026-08-02 01:41:37 +02:00
atlas
6458c039a0 docs(#2815): the transient pill's vocabulary is open, not a fixed set
argus flagged on !2910 that the dashboard doc still described the pill's
old shape. It did, in two ways that now teach the wrong thing:

- it listed a fixed vocabulary, where the label is now the running
  node's own wire tag — the `NodeKind::as_str` strings `NodeView.kind`
  already carries. A client that switches on specific values is now
  wrong, and `restarting` in particular no longer exists at all.
- it described the transient as operator-initiated ("set the moment the
  operator clicks"), which was the distinction between it and the
  rebuild-queue fallback. That is no longer true: the transient is
  derived from the running node, so worker-driven work the operator
  never clicked lights the same pill.

The queue-`kind` half of that section is untouched — that path did not
change and its vocabulary is still fixed.

Also notes on the wire-event list that `transient_kind` is a display
string to render, not an enum to branch on, since that is the property a
client would otherwise have to infer from a now-open set.

Docs only, no code change.
2026-08-01 16:23:10 +02:00
atlas
f3569160d3 docs(#2860): forge.url defaults to null, not a loopback address
The connectivity-overrides section still documented
`hyperhive.forge.url = "http://localhost:3000"` as the default and said
the empty string was a valid value. Both stopped being true when the
option became `nullOr str` with `default = null`.

Also says what `null` means and why, since "the default is null" alone
reads like an oversight rather than the point: a loopback default is
only correct when the forge shares the agent's netns, and the units that
would consume the URL are simply not generated without one.

Refs #2860
2026-08-01 01:33:43 +02:00
atlas
7b23b53b75 docs(#2862): document the push side and regenerate the CLI reference
docs/tools/hivectl-cli.md is generated by `hivectl markdown-docs` and
diffed against a fresh run by the hivectl-docs flake check, so adding
the push verb without regenerating it would have failed CI. The
regeneration also retires two copies of a sentence describing the
cross-hive leg as an ssh pipe that "isn't wired up yet" -- ssh was
dropped when the WireGuard mesh became the authentication, and the leg
is wired up now.

snapshot-store.md documented only the receiving host, so a reader had
no way to learn how a pushing hive is told where the store is. It now
covers services.hyperhive.swarm.snapshotStore, including why address
has no default and port does: an address is a deployment fact that
cannot be guessed, a port is a convention both ends read from the same
docs. It also states the namespace rule the two options illustrate --
swarm.* describes the swarm as seen from here, a bare
services.hyperhive.<service> describes a role this host performs.

swarm.md never mentioned the store even though the option lives in its
namespace, so a reader configuring swarm.peers had no signal it exists.
2026-07-31 22:15:37 +02:00
atlas
6a6266cd5e refactor(#2862): keep the option at services.hyperhive.snapshotStore
Reverting the namespace move from the previous commit — mara's reason
is better than mine was.

I grouped it with swarm.peers and swarm.wireguard because the module
serves the swarm tier. But those two describe THE SWARM: who is in it,
how it is meshed. snapshotStore describes THIS HOST'S ROLE. On a
standalone store box the operator enables one service, and nesting it
under `swarm` implies they are configuring a swarm when they are not.

The swarm- prefix on the file and units stands: the name says which
tier the component serves, the option path says what you are turning
on. Those are different questions and they are allowed different
answers.
2026-07-31 19:03:24 +02:00
atlas
70bcdb5463 refactor(#2862): swarm- prefix for the snapshot store
mara, in preparation for the swarm tier: the store is a swarm-level
role, not a hive one, so hive- was misleading about which tier it
belongs to. Module, units, syslog identifier, log lines and docs all
move to swarm-snapshot-store.

Also moved the option under services.hyperhive.swarm.snapshotStore, to
sit with swarm.peers and swarm.wireguard rather than dangling off the
top level. That is a judgement call beyond the literal rename — flagged
on the PR, and cheap precisely now: the option has never shipped, so
there is no deployment to migrate, whereas doing it after a release
would be a breaking change for no new benefit.
2026-07-31 19:03:24 +02:00
atlas
c051cd9717 docs(#2862): document the snapshot store, drop the dedicated option
mara: the option was the wrong shape for the concern. "this host runs
nothing else" is a deployment expectation, not something a module
should assert about its own host — and asserting it made co-location
look like a config toggle rather than what it is.

Replaced with docs/snapshot-store.md, which the module had no docs
page at all before: enabling it, why the mesh is the authentication
(cryptokey routing already binds source address to pubkey, so certs
would authenticate the same fact twice and add an expiry), why the
destination is keyed per agent (a per-hive prefix splits an agent's
chain the first time it migrates), what the sender may and may not
choose, why the firewall rule is interface-scoped, what a snapshot
does and does not contain, and what the pull side still needs.

The dedicated-host expectation is stated there as an operational
assumption with its own failure mode — true on day one, quietly false
the day someone notices the box has spare disk — rather than as an
assertion someone flips to false to make the build proceed.

Linked from CLAUDE.md's reading paths.
2026-07-31 19:03:24 +02:00
atlas
0db83c40a0 feat(#2642): a github.com notification poller alongside the forge one
hive-forge-notify grows a second binary, hive-github-notify. The two
share the notification half of the job — tolerant parse, classification,
formatting, dedupe, todo delivery — and nothing else: each binary owns
its host's protocol outright.

Two binaries rather than one multi-source daemon, and rather than a
cargo feature. A feature would unify across the workspace and cost every
crate its build cache. Two binaries keep the decision in nix: forge.nix
installs the forge unit, github.nix installs the github one under
hyperhive.github.enable, so a hive built without that module has no
github poller in its closure at all — GitHub access is separable (a
tier, a policy boundary), not merely switched off. Both binaries ship
from the existing derivation, so packages.nix is untouched.

The split is real at the code level too, not just at the unit level.
source.rs is a trait; the impls live in the binaries that use them, so
neither binary links the other's protocol code and the library names no
host at all. The forge-only assigned-issue rollup moves into the forge
binary for the same reason: it asks the forge what is assigned to this
agent, which is not a notification-protocol concern.

At runtime the github unit needs a PAT at <state>/github-token, the same
dashboard-provisioned token the gh wrapper and the git credential helper
already use. No PAT: it logs why and exits 0, which is why the unit is
Restart=on-failure and not always.

Forgejo's notifications API is modelled on GitHub's, so one tolerant
parse serves both — the differences (string thread ids, PullRequest vs
Pull) are absorbed by lenient deserializers rather than a second parse
path. Thread ids normalise to String at the parse boundary; they are
only ever opaque keys. Todo keys gain a per-source prefix so the two
hosts cannot collide, and the forge's is deliberately empty to keep
existing forge todo keys stable across the deploy that lands this.

The github loop honours the server's X-Poll-Interval, re-arming only
when the server asks for a slower cadence than ours; the hint is read
before the status check, because it arrives on error and empty pages too
and that is exactly when it matters. Reading the notification stream
needs the notifications scope on the PAT, which a token minted for push
access typically lacks; the failure mode is silence, so docs/github.md
says so explicitly.
2026-07-31 17:23:18 +02:00
damocles
f034ffb9d8 hive-forge: infer active repo from cwd's git remote, demote HIVE_FORGE_REPO 2026-07-29 18:37:37 +02:00
damocles
2c11a437b4 hive-agent: enable the Skill built-in tool so installed skills are invokable 2026-07-27 22:28:28 +02:00
damocles
2ab3c92a11 docs: drop removal-history framing, document current state only 2026-07-27 22:15:36 +02:00
damocles
fffe0a2c29 remove request_next_turn: same-turn continuation is always worse than an external wake 2026-07-27 22:15:36 +02:00
damocles
74d52a7be2 hivectl: trim cli-help doc comment for agent watch (no impl details) 2026-07-27 21:04:00 +02:00
damocles
9d5c7a7f7e hivectl: add agent <name> watch to follow live events from the CLI 2026-07-27 21:04:00 +02:00
damocles
9b29c6a172 hive-forge: lint no-reviewer checks actual requested-reviewers, not a text mention 2026-07-27 19:07:36 +02:00
damocles
03afbd1316 hivectl: rename hivectl agents to hivectl agent <name> <verb> 2026-07-27 19:07:18 +02:00
damocles
abde71b0ed hive-forge: add --label to issue-create and pr-create 2026-07-27 16:19:36 +02:00
atlas
e8e6998ac5 refactor(#2756): replace the DAG terminal hook with real tail nodes
The queue carried a per-DAG `HookKind` that fired an inline side effect
from outside the graph when a container rolled up terminal. mara asked
three times why this could not be an ordinary node; the answer in the
code was a doc-comment claiming a node could not work, and it was wrong.

`DepWhen::AfterAny` already existed with two live users, and a weak edge
is satisfied by a `Cancelled` dep, so a tail node runs on success,
failure and cancel alike. What was genuinely missing was smaller than a
hook: a node had no way to learn how the work it followed ended.

So: `Claim` now carries `deps: Vec<DepOutcome>`, snapshotted at claim
time from the graph the scheduler already holds (no `hive-jobq` change).
`Claim::deps_state()` / `deps_error()` roll that up, and two new kinds
consume it — `ResolveApproval { approval_id }` and `EmitRebuilt { agent }`.
Templates append one as a group-root with `AfterAny` edges onto the DAG's
other group roots; a root's state is its subtree's roll-up, so that
covers every node without fanning out to each of them.

Deleted: `HookKind`, `DagSpec.hook`, `NodeKind::Dag.hook`, `DagMeta.hook`,
`TerminalDag`, `terminal_dag()`, `terminal_summary()`, `dag_agents()`,
`dag_rollup()`, `fire_terminal_hook()`, `run_terminal_hook()`,
`emit_rebuilt()`. `complete_node` returns `()`.

Load-bearing details:

- `JobQueue::cancel` spares tail nodes instead of cancelling the whole
  subtree, and returns `bool`. Without this a cancelled approval DAG
  would dangle its approval forever — the hazard `tests.rs` already
  named. The spared tail's deps are `Cancelled`, which satisfies its weak
  edge, so the scheduler claims it and it resolves the row as cancelled.
  `hive-jobq` anticipated exactly this: `cancel_node`'s doc already says
  to settle afterwards so "a weak-edge terminal node observing the
  cancellation" can advance.
- The existing `complete(container)` call after cancelling is kept and is
  deliberately a no-op when a tail was spared (a non-terminal child parks
  the container back in `Finishing`), so power ops still settle
  synchronously with no branch.
- `DeployTail` is NOT `is_tail()`: it does real compensating work, and a
  cancelled DAG has nothing to compensate.
- `exec::failure_reason` falls back to `first_error(dag_id)` because a
  group root that rolled up `Failed` from a child carries no error of its
  own — without it every tail-reported failure would lose its reason.
- `EmitRebuilt` is per agent, so a multi-agent DAG reports each agent's
  own outcome rather than painting all of them with the DAG roll-up.
- `ResolveApproval` is agentless: the approval row already names its
  agent, and that is also what lets one tail close a multi-agent DAG.

Transients-derived-from-running-nodes and the frontend's node-kind
strings stay out of this change; they touch iris's slice and review
better next to their own diff.
2026-07-27 15:06:27 +02:00
atlas
b08176f089 feat(#2693): let the operator pin the claude-code every agent runs
Agents run whatever `claude-code` the meta flake's `nixpkgs` resolves
to, and that is normally a release channel. This one package moves fast
enough that stable trails unstable by weeks — 26.05 is on 2.1.187 while
unstable carries 2.1.220 — and an agent cannot fix it for itself: it
only ever sees the single nixpkgs hive-c0re injects, so an `agent.nix`
has no other tree to reach for.

New host option `services.hyperhive.c0re.claudeCodePackage` takes the
package directly and rides the existing `hyperhiveDocs` threading path —
serveConfigJson -> HiveEnv -> render_flake — to reach each agent as
`hyperhive.claudeCodePath`. Null (the default) is today's behaviour.

What travels is the store *path*, as a plain string literal, not a flake
input: containers share the host's `/nix/store`, so the build is already
reachable inside them with its whole closure and has nothing to travel.
An input would be worse than useless — a `path:/nix/store/<pkg>` input
is re-copied as a reference-less `-source`, which strips exactly the
closure the binary needs.

The catch is that a path written into a generated flake is text, so
nothing in the container's closure keeps the binary alive. The host does
that instead, and gets it for free: the package is interpolated into
`/etc/hyperhive/serve.json`, `builtins.toJSON` preserves string context,
so the /etc entry references it and the system closure gc-roots it for
as long as that generation is the one the agents were rendered from. An
assertion pins that property, because losing the context is invisible at
eval and at deploy — it would surface only as every agent failing to
spawn `claude` whenever the next gc ran.

Container side wraps the path in a symlink farm rather than putting it
on PATH directly: `systemd.services.<name>.path` and
`environment.systemPackages` both coerce a store-path *string* through
`lib.toDerivation`, i.e. `builtins.storePath`, which pure evaluation
rejects. Interpolating the path into a builder is just text and
evaluates anywhere. `claude-code` drops out of systemPackages when a
pin is set, so there is exactly one claude in the container.

Refs #2693
2026-07-27 13:56:28 +02:00
atlas
f28a1e33d3 docs(#2754): document the container weights in coordinator/security/persistence
The PR added CPUWeight=/IOWeight= to the drop-in but left the prose
docs describing a two-setting file. Covers the cap-vs-share
distinction, the hive-wide-only scope (no resource-limits.json
override), and the iocost/BFQ caveat that makes IOWeight= inert on
most hosts.
2026-07-27 11:01:36 +02:00