parent
bf81241744
commit
9545151b8d
10 changed files with 62 additions and 112 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 -->
|
||||
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue