Watch
0
0
Fork
You've already forked hyperhive
0

docs: state current behaviour, drop change-log wording

Refs #3902
This commit is contained in:
atlas 2026-10-02 08:22:09 +02:00 • committed by mara
commit 9545151b8d
10 changed files with 62 additions and 112 deletions

View file

@ -52,11 +52,10 @@ power-intent registry:
- `kv` — small persistent key/value store (`key PK / value`) for
host-side bookkeeping that doesn't warrant its own table.
⚠️ The `mcp__hyperhive__remind` queue is **not** here any more: it
moved to a harness-local, per-agent store as part of the
loose-ends-v2 migration — see [`/harness/` contents
⚠️ The `mcp__hyperhive__remind` queue lives in a harness-local,
per-agent store — see [`/harness/` contents
below](#state-dirs-per-agent) for where reminders (and todos)
actually live now.
live.
- `approvals` — the queue. `agent / kind (merge_config_pr | spawn |
update_meta_inputs | schedule_prompt) /
commit_ref / requested_at / status / resolved_at / note`.
@ -109,11 +108,11 @@ One table:
harness emits during turn loop execution.
<!-- vale write-good.Passive = NO -->
The harness both writes and vacuums it — this used to be a host-side
sweep, but hive-c0re runs as the unprivileged `hive-core` user under
The harness both writes and vacuums it. Cleanup runs in-container
because hive-c0re runs as the unprivileged `hive-core` user under
privsep and can't delete agent-owned files (host-side deletes hit
`PermissionDenied` on the bash-task trio and a readonly database error
here), so cleanup moved in-container. `hive-agent`'s `vacuum::run`
here). `hive-agent`'s `vacuum::run`
(`hive-agent/src/vacuum.rs`) sweeps hourly. Retention is
**type-scoped**: it deletes only the verbose `stream` rows (the raw
claude `stream-json` deltas — one per text chunk / tool use, the bulk
@ -192,10 +191,9 @@ is in-process only, so it wouldn't serialise a writer running as a
separate process; none of today's writers are.
hive-c0re reads this file on each `build_all` sweep (~10s) via
`container_view::read_harness_flags`. Falls back to the legacy individual
sentinel files (`hyperhive-rate-limited`, `hyperhive-needs-login`) if the
JSON is absent, so existing containers keep working through the transition
window before their next rebuild.
`container_view::read_harness_flags`. Falls back to the individual
sentinel files (`hyperhive-rate-limited`, `hyperhive-needs-login`) when the
JSON is absent.
### `/var/lib/hyperhive/db/build_logs.sqlite` (host)
@ -218,7 +216,7 @@ Three indices:
- `(node_id)` — added by a later migration so a build log row can be
looked up by the job-queue node it belongs to (a `hive_jobq` node is
immutable after insert, so hive-c0re records the link on the log row
instead); legacy rows predating the column keep `node_id IS NULL`.
instead); rows predating the column have `node_id IS NULL`.
Writes are best-effort: `append_stdout` / `append_stderr` / `finish`
log a warning on sqlite error and let the build continue. A failed
@ -293,22 +291,19 @@ Under `/var/lib/hyperhive/agents/<name>/`:
(`hive-agent`'s `vacuum::run`, same one that ages out `stream`
event rows above) deletes terminal task trios older than 48
hours; non-terminal (still-running) tasks are never deleted. This
used to be a host-side `hive-c0re` vacuum, moved in-container for
the same privsep-ownership reason as the events vacuum above.
sweep runs in-container, for the same privsep-ownership reason as
the events vacuum above.
- `hyperhive-state.sqlite` — consolidated loose-ends-v2 store: todos
and reminders, one small table each in a single file (in-container
daemons — `hive-bash-daemon`, `hive-matrix-daemon`,
`hive-forge-notify` — upsert keyed todos here over the harness's
in-agent socket, `HIVE_AGENT_SOCKET`; the harness merges them into
`get_loose_ends` output and clears a row on `mark_todo_done`).
Replaces three formerly separate files
(`hyperhive-todos.sqlite`, `hyperhive-reminders.sqlite`, and the
old file-based `mcp-loose-ends/` scanner before that) — a one-time
boot migration (`db_migrate::run`) folds the legacy files into this
path the first time a harness boots after the upgrade. Also backs
the `mcp__hyperhive__remind` queue, which moved from a host-side
`broker.sqlite` table to this per-agent store as part of the same
migration.
Consolidates todos and reminders into one file. A one-time boot
migration (`db_migrate::run`) folds any pre-existing files
(`hyperhive-todos.sqlite`, `hyperhive-reminders.sqlite`, the older
file-based `mcp-loose-ends/` scanner) into this path the first time
a harness boots. Also backs the `mcp__hyperhive__remind` queue.
The harness itself is also a producer, not just the socket server:
boot wiring's `spawn_todo_socket` starts `todo_server::run` (the
@ -344,9 +339,7 @@ The RW on `state` is deliberate, not an oversight: the holder recovers
other agents, which includes writing into their state (for example
seeding notes, clearing a stuck sentinel) as well as reading it.
This is the **only** cross-agent mount. Dropping the topology parent
field took with it the unconditional grant every agent used to
get over its own direct children — an agent holding no capability now
This is the **only** cross-agent mount. An agent holding no capability
sees its own dirs and nothing else.
**`harness` isn't mounted at all.** It holds that agent's own runtime
@ -395,8 +388,8 @@ Contents:
- `topology.json` — the agent roster (`["alice", "bob", "ruth"]`).
Written by `topology::reconcile` on every meta sync; read by
`topology::all_agents`, which is the set the `ManageRootAgent`
capability grants mounts over. Carried a `parent` per agent in the
legacy format; the reader still accepts that shape and keeps its keys.
capability grants mounts over. The reader also accepts a map-shaped
file carrying a `parent` per agent, and keeps its keys.
- `tool-groups.json` — per-agent MCP tool group grants
(`{ "alice": ["messaging", "inbox", "execution"] }`). Written by
`tool_groups::set_groups`; injected as `HIVE_TOOL_GROUPS` env
@ -420,9 +413,8 @@ Contents:
The root agent has the meta dir RO-mounted at `/meta/`.
The `.meta-migration-done` marker no longer exists: the
one-shot container repoint it guarded no longer exists either, since
hive-c0re renders containers onto `meta#<n>` at creation. A stale
hive-c0re renders containers onto `meta#<n>` at creation, so there is
no `.meta-migration-done` marker to guard a one-shot repoint. A stale
marker file left over from an older hive is inert and the operator can
delete it.
@ -519,7 +511,7 @@ reinstall.
The harness runs as a per-agent unix user inside the container
(`services.hyperhive.agent.user.name`, defaults to the agent's logical label so each
container has a uniquely named user). Operators with legacy root-owned
container has a uniquely named user). Operators with root-owned
state dirs need a one-time data shuffle so they don't lose their claude
session.
@ -533,8 +525,7 @@ container lifetime:
chance to chown. Also re-applies on every rebuild in case the
meta-flake's per-agent name evolves (rare).
2. **Migrate any leftover `/root/.claude` content into
`${homeDir}/.claude`** — legacy `claude` wrote to root's
empty home; the bind mount didn't exist yet. Marker
`${homeDir}/.claude`.** Marker
(`/var/lib/hive-agent-user-migrated`) guards single-shot; it's
only written once there's nothing left to migrate, so a `cp`
failure leaves it absent and the next boot retries.
@ -557,9 +548,7 @@ container lifetime:
there as the agent user. Why the mode matters:
[`boundary.md`](../trust-boundary/boundary.md#the-per-agent-socket-dir).
The activation script will eventually become unnecessary once no
operators have legacy root-owned state dirs left to migrate; drop
the body + marker check at that point.
The activation script migrates any root-owned state dir still present.
## Matrix per-agent daemon + token-arrival trigger

View file

@ -259,11 +259,11 @@ Two consequences worth knowing:
<!-- vale write-good.Passive = NO -->
- **Flattening a chain under a brace is safe.** The stop chain used to nest
`Signal` over `Drain` over `StopForUpdate` specifically so the lease stayed
continuous — as independent siblings each would acquire it separately and
leave a gap another DAG could claim the agent in, mid-bounce. A brace supplies
that continuity directly, so the nesting is no longer load-bearing.
- **Flattening a chain under a brace is safe.** A brace (`AgentWindow`)
holds the lease continuity for the stop chain's subtree: as independent
siblings, `Signal`, `Drain`, and `StopForUpdate` would each acquire the
lease separately and leave a gap another DAG could claim the agent in,
mid-bounce.
- **Observability is unaffected.** `running_transients` keys off a node's
_payload_ agent, not off a declared lease edge, so every child still lights its
own dashboard pill and still reports its own `takes_container_down` to the
@ -284,8 +284,8 @@ reconcile_), so there is no durable-recovery machinery to go wrong.
### Cancel, history
The agent-per-node move removed submit-time dedup (a multi-agent DAG
has no single agent to key a dedup on), so every submit enqueues a fresh DAG.
Every submit enqueues a fresh DAG; a multi-agent DAG has no single agent
to key a dedup on.
Cancel only applies to DAGs that are still fully queued (an in-flight nix build isn't
interruptible) — each op is one DAG now, so there are no child DAGs to cascade to.

View file

@ -53,19 +53,6 @@ wrapper carries an address, a CA and a client certificate but deliberately
**no token**, so that read answers `403` whether or not the role exists. Read
the unit's journal instead.
<details><summary>Upgrading a swarm set up with the older swarm-bootstrap policy</summary>
A store set up before the granter existed has every grant, but no `bao-granter`
policy or role. After the deploy that introduces it, each `swarm-bao-*-policy`
unit fails and logs the one-time step. Run the setup step as it stands. The
old policy can go, with the root token again:
```bash
bao policy delete swarm-bootstrap
```
</details>
### Residual risk
The granter is root-equivalent. It may write any `swarm-*` policy with any

View file

@ -129,9 +129,8 @@ loose-end follows.
controls whether the `bash` MCP server reaches the agent's claude
config at all, suppressing every `mcp__bash__*` tool
when absent — not just leaving them unused. Its own `tools()` returns
`&[]` and stays that way: `mcp__bash__run`/`status`/`kill` belong to a
`&[]`: `mcp__bash__run`/`status`/`kill` belong to a
different, out-of-process MCP server (`bash`, not `hyperhive`), so they
were never real entries in the `mcp__hyperhive__*` allowlist `tools()`
builds — the two dead `["run", "status"]` strings it used to return
matched nothing, so they're gone now. Removing `execution` from an
aren't entries in the `mcp__hyperhive__*` allowlist `tools()`
builds. Removing `execution` from an
agent's groups **does** remove bash availability.

View file

@ -281,8 +281,7 @@ to discover valid label names before triaging or to audit the label set.
falling back. `--job` selects the job (0-based, default 0);
`--attempt` picks the run attempt for the durable path (default 1;
re-runs increment it). `--json` wraps the output.
- `ci-rerun` re-runs CI without pushing an empty commit (the old
retrigger path, which littered PR history). Forgejo has no token-usable
- `ci-rerun` re-runs CI without pushing an empty commit. Forgejo has no token-usable
REST endpoint to re-run an _existing_ run (the run-page rerun buttons
are CSRF-gated web routes a token POST 404s), so this dispatches a
**fresh** run of the workflow via the workflow-dispatch API

View file

@ -98,9 +98,9 @@ route refuses to create an account by that name.
mechanism behind "accounts are the enable signal": an agent with no
homeserver and no account of its own has an empty set, so it gets no
daemon, no path watcher and no injected MCP entry. That's how you give
an agent no matrix tools — it replaces the removed
`services.hyperhive.agent.matrix.enable = false`, which now fails
evaluation with a message saying so. Declaring an external account with
an agent no matrix tools. Setting
`services.hyperhive.agent.matrix.enable` fails evaluation with a
message saying so. Declaring an external account with
its own `homeserver` is enough on its own; a hive homeserver isn't
required.

View file

@ -103,9 +103,7 @@ there is no such identity to hold a file open for, so the daemon omits
anything task-shaped.
⚠️ This applies to every dispatch, not just role-bearing ones — no agent
ships roles yet, so today it's the only path in real use. Before this fix
the no-role path put the task straight into the system prompt, same as
every `start` before roles existed at all.
ships roles yet, so today it's the only path in real use.
### A role with no file refuses the call
@ -356,10 +354,8 @@ Everything else in claude's built-in set is absent, in particular the
tools that let a session act outside the run for which it began: peer and
operator messaging, nested agents (including the stop verb, which takes
an _agent_ id rather than a session), schedule and webhook creation, and
worktree switching. Before the harness passed this flag, a subagent
reached all of them — `--dangerously-skip-permissions` had removed the
only thing that would have asked, and `--allowedTools` would not have
helped: it approves prompts in advance rather than restricting anything.
worktree switching. `--allowedTools` would not achieve this exclusion on
its own: it approves prompts in advance rather than restricting anything.
One rule worth knowing before editing any of this: **the daemon never
emits an empty `--tools` value**, and asserts rather than doing so. Not

View file

@ -123,7 +123,7 @@ the agent user and sets `0751`. The container has no user namespace, so
that uid is the host inode's owner. Don't add a host-side chown or chmod:
two owners of one path revert each other. Until the container activates,
the dir is `0751 root`: nothing but root can plant a socket in it, and a
legacy root-run harness can still bind.
root-run harness can still bind.
<!-- vale write-good.Passive = YES -->

View file

@ -100,10 +100,7 @@ its own store identity so credentials never pass through a hive at all.
agent and service stanzas above) because a hive's own path names it, so
scoping to the reader's own name costs nothing and drifts nowhere. That only
holds if a credential that must be one-per-hive is actually stored under the
hive kind — the matrix sender token was originally published to
`swarm/services/matrix/sender-token`, under the **service** kind, and so was
readable by every hive though it belonged to only one. Making it per-hive meant
moving its path under the hive kind, not narrowing the service stanza's grant.
hive kind.
**A credential that must be one-per-hive goes under `Kind::Hive`**; putting it
under `Kind::Service` and expecting the grant to scope it — that's the mistake
this note exists to stop.
@ -296,7 +293,7 @@ known operations; there is no arbitrary command pass-through:
| `DaemonReload` | `systemctl daemon-reload` |
| `RunForgeAdmin` | `nixos-container run hive-forge -- runuser -u forgejo -- forgejo admin <args>` |
| `ControlInfraContainer` | `systemctl <action> container@<container>.service` — the `InfraContainer` enum is the allowlist, and serde rejects unknown names at the wire boundary (`hive-c0re` has no variant, so no request can name it) |
| `SyncAgentTmpfiles` | legacy: unlink `/etc/tmpfiles.d/hyperhive-agents.conf` and return `Ok`; kept one release for an older hive-c0re |
| `SyncAgentTmpfiles` | unlink `/etc/tmpfiles.d/hyperhive-agents.conf` and return `Ok` |
| `SetAgentPaused` | create / remove the `<state>/<name>/harness/paused` marker that parks an agent's turn loop |
| `WriteAgentGithubToken` | write `0600` `github-token` into agent state dir |
| `RegisterCiRunner` | write `/run/hive-ci/runner-token` (host path, root-owned) then `systemctl --machine=hive-ci restart gitea-runner-hive.service`. Only the registration token crosses; the forge admin token never enters the container |

View file

@ -13,19 +13,16 @@ panel for flyouts and long content.
Preact component tree (`Header.tsx` + `StatusChips.tsx` +
`MetaNav.tsx` + `HeaderPill.tsx`, wired together in `Root.tsx`) — see
`frontend/packages/agent/src/components/`. This
section describes the rendered result, not the DOM ids the pre-Preact
page used (there are none any more — every element is component
output, not something a selector reaches by id).
section describes the rendered result, not DOM ids — every element is
Preact component output, not something a selector reaches by id.
**Fixed-overlay header** (`<header class="agent-header">`): frosted
glass — `backdrop-filter: blur` lets scrolled terminal rows show
through. Measures its own rendered height via `ResizeObserver`
(`Header.tsx`) and writes it to a CSS custom property the content
below reads for its offset — `agent.css` used to bake in a fixed `6em`
guess, which silently broke (content overlapping the header) the
moment any row of badges/pills wrapped onto an extra line at some
viewport width; measuring instead of guessing closes that bug class
structurally rather than for one specific trigger. Two columns:
below reads for its offset, so content never overlaps the header
regardless of how many rows of badges/pills wrap onto an extra line
at a given viewport width. Two columns:
<!-- vale write-good.Passive = NO -->
- **Agent icon** (`<img class="agent-icon">`): fixed-size square
@ -45,10 +42,9 @@ structurally rather than for one specific trigger. Two columns:
`ResizeObserver` measurement above exists to catch even when a
layout choice reintroduces it).
- **Pills cluster** (`.agent-header-pills`, right-aligned): status
badges (`StatusChips.tsx`) and flyout triggers together in one row —
a deliberate choice (not the historical default) so the header reads
as one identity zone + one status/actions zone rather than multiple
separate clusters:
badges (`StatusChips.tsx`) and flyout triggers together in one row,
so the header reads as one identity zone + one status/actions zone
rather than multiple separate clusters:
- **Alive badge**: `● alive` (green) / `⊘ rate limited` (red) /
`◌ needs login` / `◌ logging in` / `○ offline` / `… connecting`.
- **State badge**: `💤 idle` / `🧠 thinking` / `📦 compacting` /
@ -89,23 +85,14 @@ structurally rather than for one specific trigger. Two columns:
(`GET /api/state`'s `links` field) also feeds
`DashboardState.links` for the dashboard card's icon strip —
`agent_links()` in hive-agent is the single source of truth for
both. No separate overflow (`⋯`) menu exists any more — it used
to hold exactly this dashboard link plus a rebuild-container action
(mara: "remove rebuild button, move link to
dashboards into links menu") — rebuild had no real discoverability
need of its own (the dashboard's own R3BU1LD button already covers
it) so it's gone outright, and the dashboard link moved here,
leaving nothing to justify a separate menu. Everything else that
used to live in the old overflow menu (model/effort pickers,
new-session, logout) already had a better home before this: pickers
are real badges above, and `/new-session` / `/logout` are typed
slash commands with their own type-twice confirm (see below) — a
modal doesn't fit a text-input flow, and burying rare-but-important
actions in one flat menu was the design guide's own named
anti-example.
- No header cancel-turn button any more — `/cancel` (slash command,
below) is the only path; the turn-loop state badge already shows
`thinking` as the discoverability cue.
both. No overflow (`⋯`) menu exists: the dashboard link lives in
this 🔗 popover; the model/effort pickers are real badges above;
and `/new-session` / `/logout` are typed slash commands with their
own type-twice confirm (see below) — a modal doesn't fit a
text-input flow.
- `/cancel` (slash command, below) is the only cancel-turn path; the
turn-loop state badge already shows `thinking` as the
discoverability cue.
<!-- vale write-good.Passive = YES -->
Values throughout come from `GET /api/state`'s cold-load snapshot,
@ -179,11 +166,7 @@ from the harness-local store (same effect as `cancel_loose_end(kind:
"todo")`, just from the web UI instead of the agent's own tool calls).
The todos flyout is the only per-agent flyout — there is no separate
"loose-ends" or "tasks" list. There used to be a note here about the
`ask`/`answer` MCP tools having no inline answer form in this
terminal — that whole mechanism (the tools, the dashboard's questions
pane, the wire protocol) no longer exists, so there's
nothing left to render a form for.
"loose-ends" or "tasks" list.
## Live view