diff --git a/docs/agent-lifecycle/persistence.md b/docs/agent-lifecycle/persistence.md index 0560360b..4e7d6813 100644 --- a/docs/agent-lifecycle/persistence.md +++ b/docs/agent-lifecycle/persistence.md @@ -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. -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//`: (`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#` at creation. A stale +hive-c0re renders containers onto `meta#` 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 diff --git a/docs/scheduler/coordinator.md b/docs/scheduler/coordinator.md index 25599287..60aae812 100644 --- a/docs/scheduler/coordinator.md +++ b/docs/scheduler/coordinator.md @@ -259,11 +259,11 @@ Two consequences worth knowing: -- **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. diff --git a/docs/swarm/bao.md b/docs/swarm/bao.md index bd721ce7..f2d63ef2 100644 --- a/docs/swarm/bao.md +++ b/docs/swarm/bao.md @@ -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. -
Upgrading a swarm set up with the older swarm-bootstrap policy - -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 -``` - -
- ### Residual risk The granter is root-equivalent. It may write any `swarm-*` policy with any diff --git a/docs/tools/bash.md b/docs/tools/bash.md index 43459219..07fcf124 100644 --- a/docs/tools/bash.md +++ b/docs/tools/bash.md @@ -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. diff --git a/docs/tools/forge.md b/docs/tools/forge.md index 0e437866..3a43c748 100644 --- a/docs/tools/forge.md +++ b/docs/tools/forge.md @@ -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 diff --git a/docs/tools/matrix.md b/docs/tools/matrix.md index 5cd8eeeb..4a80a14e 100644 --- a/docs/tools/matrix.md +++ b/docs/tools/matrix.md @@ -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. diff --git a/docs/tools/subagent.md b/docs/tools/subagent.md index 43ffab8a..392fd1fc 100644 --- a/docs/tools/subagent.md +++ b/docs/tools/subagent.md @@ -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 diff --git a/docs/trust-boundary/boundary.md b/docs/trust-boundary/boundary.md index 5c74302d..161192f3 100644 --- a/docs/trust-boundary/boundary.md +++ b/docs/trust-boundary/boundary.md @@ -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. diff --git a/docs/trust-boundary/security.md b/docs/trust-boundary/security.md index 3c391a0b..3d94440d 100644 --- a/docs/trust-boundary/security.md +++ b/docs/trust-boundary/security.md @@ -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 ` | | `ControlInfraContainer` | `systemctl 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 `//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 | diff --git a/docs/web-ui/agent.md b/docs/web-ui/agent.md index 5546a487..1a273d40 100644 --- a/docs/web-ui/agent.md +++ b/docs/web-ui/agent.md @@ -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** (`
`): 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: - **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. 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