From e4b170f8c66abe2ea23d585eeba3274d09b41910 Mon Sep 17 00:00:00 2001 From: atlas Date: Sun, 4 Oct 2026 23:55:14 +0200 Subject: [PATCH] docs: the hive has no approval queue config-changes.md (was approvals.md) describes config PRs, the meta flake and helper events; the approval queue, its kinds, the withdraw path, the job-queue dispatch and ApprovalResolved go. Every other page drops the approvals table, endpoints, event, tool group, hivectl verbs, dashboard section and the loose-end/cancel kind, and links to the renamed page. --- CLAUDE.md | 12 +- README.md | 6 +- docs/README.md | 4 +- docs/agent-lifecycle/config-changes.md | 146 +++++-------------------- docs/agent-lifecycle/persistence.md | 29 ++--- docs/getting-started/setup.md | 3 +- docs/integrations/forge.md | 4 +- docs/networking/gateway.md | 2 +- docs/process/conventions.md | 41 ++----- docs/process/gotchas.md | 9 -- docs/scheduler/coordinator.md | 18 +-- docs/swarm/README.md | 2 +- docs/trust-boundary/boundary.md | 4 +- docs/trust-boundary/security.md | 7 +- docs/turn-loop/mcp.md | 32 ++---- docs/web-ui/README.md | 14 +-- docs/web-ui/dashboard.md | 103 ++++------------- docs/web-ui/shape.md | 7 +- 18 files changed, 119 insertions(+), 324 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 2a227390..41104e5f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -31,11 +31,11 @@ hand-maintained per-file tree drifts out of sync with the code. user). `src/main.rs` is the `hive-c0re` binary — **daemon-only** (`serve` + the periodic vacuum/sweep loops); the operator CLI lives in the separate `hivectl` crate, which talks to the daemon over the host - admin socket. Owns the sqlite broker, approval + reminder - queues, the meta flake, lifecycle (`nixos-container` - shellouts), gateway / forge / matrix wiring (agent forge and matrix - accounts are `swarm-controller`'s), per-container stats, and the axum - operator dashboard (`dashboard/`). Largest crate. + admin socket. Owns the sqlite broker, the reminder queue, the meta + flake, lifecycle (`nixos-container` shellouts), gateway / forge / + matrix wiring (agent forge and matrix accounts are + `swarm-controller`'s), per-container stats, and the axum operator + dashboard (`dashboard/`). Largest crate. - **`hivectl/`** — standalone operator CLI (`hivectl` binary). Talks to the `hive-c0re` daemon over the host admin socket (`hive-host-sock` wire types) — does NOT link `hive-c0re`. Full, always-current verb @@ -128,7 +128,7 @@ hand-maintained per-file tree drifts out of sync with the code. the process is; the actual claude session survives a daemon restart independently (see `session.rs`'s module doc). - **`hive-sh4re/`** — shared wire types (Agent / Manager request + - response, `Message`, `Approval`, `HelperEvent`) used across the unix + response, `Message`, `HelperEvent`) used across the unix sockets. Host-admin-socket and hive-priv-socket wire types have been split out into their own crates (below) so `hivectl` and `hive-priv` don't need to pull in the rest of `hive-sh4re`. diff --git a/README.md b/README.md index f4d1f9e5..e8a8cded 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ the swarm — one of each, each on whichever host you choose the hives — the substrate, as many as you like (deploy.hive-controller.enable) │ -├── hive-c0re runs the agent containers: lifecycle, approvals, broker, +├── hive-c0re runs the agent containers: lifecycle, broker, │ auto-rebuild, the per-hive dashboard; unprivileged ├── hive-priv root helper, socket-activated, does bind-mounts + nsenter ├── hive-gateway nginx + dnsmasq on :80/:443, fronts every agent's web UI @@ -180,7 +180,7 @@ newer channel breaks in ways hyperhive's CI doesn't catch. - **`swarmctl`** — the swarm's CLI, on the swarm-controller host: create agents and mint their identities, manage SSO users, make forge admins. → [reference](docs/tools/swarmctl-cli.md) -- **per-hive** — each hive's dashboard (approvals, container state, +- **per-hive** — each hive's dashboard (container state, rebuild queue) and `hivectl` for host-level administration and container shells. → [`docs/web-ui/`](docs/web-ui/README.md) · [hivectl](docs/tools/hivectl.md) @@ -197,7 +197,7 @@ pick the page for your task rather than reading front to back. Some common entry points: - connecting hives into a swarm → [`swarm/`](docs/swarm/README.md) -- how config changes flow → [`agent-lifecycle/approvals.md`](docs/agent-lifecycle/approvals.md) +- how config changes flow → [`agent-lifecycle/config-changes.md`](docs/agent-lifecycle/config-changes.md) - trust boundary + threat model → [`trust-boundary/`](docs/trust-boundary/boundary.md) - what an agent's turn looks like → [`turn-loop/`](docs/turn-loop/README.md) - every `services.hyperhive.*` option → [options reference](https://hyperhive.darkest.space/options/) diff --git a/docs/README.md b/docs/README.md index 04fc5445..dd8217e4 100644 --- a/docs/README.md +++ b/docs/README.md @@ -26,8 +26,8 @@ declarations. ## Agent lifecycle - **How do config changes flow from manager to operator to container?** → - [`agent-lifecycle/approvals.md`](agent-lifecycle/approvals.md) (approval kinds, approval - state machine, `flake.lock` validation). + [`agent-lifecycle/config-changes.md`](agent-lifecycle/config-changes.md) + (config PRs, the meta flake, helper events). - **What state survives destroy / purge / restart?** → [`agent-lifecycle/persistence.md`](agent-lifecycle/persistence.md). - **Who can do what to whom — the agent roster and privilege?** → diff --git a/docs/agent-lifecycle/config-changes.md b/docs/agent-lifecycle/config-changes.md index 4fb25291..6d43e64a 100644 --- a/docs/agent-lifecycle/config-changes.md +++ b/docs/agent-lifecycle/config-changes.md @@ -1,15 +1,8 @@ -# Approvals + helper events +# Config changes + helper events -The approval queue is where an agent asks the operator for something it -can't do itself. Nothing submits a new approval today: the one kind -left, a meta-input bump, only reads back from existing rows. Config -changes don't go through this queue: they're pull requests -on the agent's config repo, which an operator merges on the forge, and -that merge deploys them. The submitting agent — any agent with the -`approvals` tool group, which manages the config of its **direct -children** (the root agent for top-level agents; a sub-manager for its -own subtree) — is the policy gate in front of both; helper events are how -it stays informed about what happens after a decision lands. +Config changes are pull requests on the agent's config repo, which an +operator merges on the forge; that merge deploys them. Helper events are +how agents stay informed about lifecycle changes that need a reaction. ## For operators @@ -17,17 +10,6 @@ it stays informed about what happens after a decision lands. config (or its own, via a sub-manager) as a pull request on `agent-configs/`. Review it on the forge like any other PR, and merge it there to deploy it. See [Config changes](#config-changes). -- **Meta/flake update** (`UpdateMetaInputs`) — an existing row reads - back and you can approve it. Approving runs the update and commits - the lock change; it doesn't rebuild anything by itself. - -Don't want to approve something? **Deny it** (`DENY` on the dashboard -card, or `hivectl approvals deny `) — nothing runs. Either way the -submitting agent is always notified that the operator denied their request; what's -optional is only the reason text, which you can add on the dashboard's -prompt (cancelling that prompt aborts the whole deny, not just the -reason) but not from the CLI. Denying is final: a denied approval -can't be re-approved later, the agent has to submit a fresh request. Everything below this point is the implementation detail behind that flow. @@ -38,8 +20,7 @@ Config changes flow through a **forge pull request** on the agent's `agent-configs/` repo — the same surface agents use for code PRs. No MCP tool exists for config changes: opening the PR IS the request. -1. The submitting agent (the child's parent, holding the `approvals` - tool group) **clones** `agent-configs/`, edits it there (any +1. The submitting agent **clones** `agent-configs/`, edits it there (any tracked path, but `agent.nix` is the contract entry point), commits with its own git identity, and pushes a branch + opens a PR with `hive-forge` — the same way it would change any other repo. @@ -77,46 +58,17 @@ failed rebuild shows on the hive like any other and isn't commented on the PR. A merge whose webhook delivery never reaches the controller deploys nothing. -## Approval queue - -### Withdrawing a pending approval - -The submitting agent can call `cancel_loose_end(kind: "approval", id)` to -withdraw an approval the operator hasn't acted on yet. -The row transitions to `ApprovalStatus::Cancelled` (distinct from -`Denied`/`Failed`), the dashboard pulls the card out of the -pending pane, and `ApprovalResolved { status: "cancelled" }` fires -on the root agent + dashboard channels. Approvals that the operator -(or a lifecycle failure) has already approved, denied, or failed return an error — the resolution is -final once acted on. - -The socket refuses the `approval` kind with a clear error for any -agent that lacks the `approvals` tool group: only an agent with that -group submits approvals (for its direct children), so an agent -without it has nothing of its own to withdraw. - -Creating a brand-new agent isn't an approval: it's swarm-level -(`swarmctl agent create`, `POST /api/agents` on the swarm controller), -and no hive can originate an agent. The swarm controller's -`InitAgentConfigRepo` job seeds the agent's config repo with a default -`agent.nix` template, then asks the target hive to deploy it. +Creating a brand-new agent is swarm-level (`swarmctl agent create`, +`POST /api/agents` on the swarm controller), and no hive can originate +an agent. The swarm controller's `InitAgentConfigRepo` job seeds the +agent's config repo with a default `agent.nix` template, then asks the +target hive to deploy it. Changing what the template seeded isn't a special case: like every later change, it's a PR on that config repo, made from a clone and merged by an operator on the forge (see [Config changes](#config-changes)). -### Approval kinds (wire shapes) - -`ApprovalKind` carries one variant, and it overloads `commit_ref` as -its payload carrier. - - -- `UpdateMetaInputs` — `commit_ref` stores the JSON-encoded inputs - array (`"[]"` = all inputs, `"[\"nixpkgs\"]"` = just nixpkgs, - etc.). hive-c0re sets the `agent` field to the requesting root agent. - On approve hive-c0re runs `nix flake update [inputs...]` on the - meta flake and commits the resulting lock changes. - +## Agent lifecycle ### Reminder delivery: file-path semantics @@ -144,10 +96,10 @@ body strings before calling it. ### Destroy semantics -`HostRequest::Destroy { name, purge }` is the lifecycle tear-down, -not an approval. Stops + removes the nspawn container, drops the -systemd drop-in, fails any pending approvals. Persistent state -(proposed/applied repos, claude credentials, `/state/` notes) is +`HostRequest::Destroy { name, purge }` is the lifecycle tear-down. +Stops + removes the nspawn container and drops the systemd drop-in. +Persistent state (proposed/applied repos, claude credentials, `/state/` +notes) is **kept by default** — recreating the agent with the same name reuses prior config + login. With `purge = true` the agent's `/var/lib/hyperhive/{agents,applied}//` trees are also @@ -252,41 +204,6 @@ forge and meta's lock commits record it. A config PR nobody merges carries no extra state on the forge side: the PR stays open, and the submitter pushes again (or closes it) to retry. -### Dispatch via the job queue - -Long-running approval work — `UpdateMetaInputs` — runs as a DAG on the -global job queue (`docs/scheduler/coordinator.md::Job queue`), submitted -by the approval handler rather than run inline: - -| `ApprovalKind` | DAG submitted | source | -|---|---|---| -| `UpdateMetaInputs` | `meta_update` (`MetaLock` + rebuild fan-out) | `approval` | - -The DAG carries the originating `approval_id`. **Every** queued kind -resolves through `actions::resolve_approval_dag` when its DAG settles -terminal: the DAG's own terminal state is the authoritative outcome. -That hook fires the matching `HelperEvent::*` via `finish_approval`. - -Two visible consequences: - -- **Operator dashboard**: after selecting APPR0VE the work-in-progress - shows up on the *rebuild queue* card (`GET /api/jobq/graph`, refetched - on every `rebuild_queue_changed` tick), not on the approvals panel - (which already moved the row to "approved"). A long meta-update - cascade renders as a parent DAG with one child rebuild per affected - agent — see `docs/web-ui/dashboard.md` for the layout. -- **Cancellation**: the dashboard's *× cancel* button on a still-queued - DAG calls `POST /api/rebuild-queue/{id}/cancel`, which flips it to - `Cancelled` before any node runs (and fails the approval row instead - of leaving it dangling). Returns `{"cancelled": true}` on success, - `{"cancelled": false}` once any node started — terminal states can't - be retroactively rewritten. - -The `approval` source + `approval_id` mean a tail-end build failure -surfaces back as a failed approval row, not just a silent queue -entry. `manual` (dashboard ↻ R3BU1LD) and `auto_update` (boot -reconcile) DAGs use the same queue but skip the approval plumbing. - ### Forge mirror The bundled `hive-forge` container runs on the swarm's forge host @@ -323,10 +240,10 @@ calls `bind_child_agent_dirs` for each entry in current config — not an editing surface. An agent without the capability sees no other agent's config at all. -An agent with the `approvals` tool group submits a change the same way -it makes any other change: **clone the target agent's config repo from the +An agent submits a change to another agent's config the same way it +makes any other change: **clone the target agent's config repo from the forge into its own state dir, commit on a branch, open a PR**, and let -the operator review and approve it. By design, no second, mount-shaped +the operator review and merge it. By design, no second, mount-shaped path reaches the same file without the review. Agents holding the `manage_root_agent` capability (granted per agent in @@ -401,7 +318,7 @@ hive-c0re creates it. The root agent's flake lives at `/var/lib/hyperhive/applied/ruth/`; its proposed config at `/var/lib/hyperhive/agents/ruth/config/`. The root agent can edit its own `agent.nix` (visible inside the container at `/agents/ruth/config/`) -and open a config PR on `agent-configs/ruth` for operator approval, +and open a config PR on `agent-configs/ruth` for operator review, same as any other agent. Differences from sub-agents: @@ -415,8 +332,6 @@ Differences from sub-agents: and `/var/lib/hyperhive/applied` → `/applied` (RO) so the root agent can `git fetch` deployed/failed/denied tags from any agent's authoritative applied repo (see "Root-agent view of applied" below). -- First-deploy spawn bypasses the approval queue (the root agent is - required infrastructure). - `socket_server::start_manager` binds the root agent's socket, pure transport with no dedicated helpers — it uses the same per-agent runtime dir as any other agent (`/run/hyperhive/agents/ruth/`), @@ -432,26 +347,19 @@ The system prompt (`hive-agent/prompts/system.md`, rendered by `hive-agent/src/prompt.rs`) is the **same for every agent**; what varies is which MCP tools it surfaces (gated by tool groups and capabilities in `agent.nix`). No `role:manager` block renders only -for the root agent. The root agent's approval-gating +for the root agent. The root agent's role-specific behaviour comes from its CLAUDE.md / agent-specific instructions, not the system prompt template. -## Helper events to the submitting agent +## Helper events -`Coordinator::notify_submitter(approval_id, &HelperEvent)` routes the -event to the agent that originally submitted the approval (looked up from -the `submitter` column on the `approvals` table). The harness delivers it -as a regular `system` inbox message so it drives a normal claude turn. -`finish_approval` fires an `ApprovalResolved` HelperEvent this way for -**every** approval kind's terminal state. A -"FYI, check when convenient" event doesn't need a message — hive-c0re publishes those as swarm -lifecycle notices instead (`swarm_notices::notify`). Legacy -approval rows that predate the submitter column fall back to the -root agent. Variants (`hive_sh4re::manager::HelperEvent`): +`Coordinator::notify_agent(agent, &HelperEvent)` pushes the event into +the agent's inbox, and the harness delivers it as a regular `system` +inbox message so it drives a normal claude turn. A "FYI, check when +convenient" event doesn't need a message — hive-c0re publishes those as +swarm lifecycle notices instead (`swarm_notices::notify`). Variants +(`hive_sh4re::manager::HelperEvent`): -- `ApprovalResolved { id, agent, commit_ref, status, note }` — - fired by `actions::approve` + `actions::deny` whenever an - approval transitions to its terminal state. - `ContainerCrash { agent, note }` — `crash_watch`: a previously- running container went away with no operator-initiated transient state (Stopping / Restarting / Destroying / Rebuilding) AND nothing diff --git a/docs/agent-lifecycle/persistence.md b/docs/agent-lifecycle/persistence.md index d5544161..9d02c92b 100644 --- a/docs/agent-lifecycle/persistence.md +++ b/docs/agent-lifecycle/persistence.md @@ -20,8 +20,6 @@ keeps its state, purging it doesn't.** Beyond that: -- **hive-c0re keeps approvals forever** — they're an audit trail, not a - cache. Nothing about them ever ages out. - **Broker messages**: acked ones vacuum after 30 days; anything undelivered or delivered-but-not-yet-acked is always kept, however old. @@ -40,8 +38,8 @@ schemas, file layouts, and internal migration mechanics. ### `/var/lib/hyperhive/db/broker.sqlite` (host) -Six tables, all in one file — two queues, a small key/value table, -the read-only schedule header/targets split, and the per-agent +Five tables, all in one file — the message queue, a small key/value +table, the read-only schedule header/targets split, and the per-agent power-intent registry: - `messages` — every inter-agent / operator-bound message. @@ -56,8 +54,6 @@ power-intent registry: per-agent store — see [`/harness/` contents below](#state-dirs-per-agent) for where reminders (and todos) live. -- `approvals` — the queue. `agent / kind (update_meta_inputs) / - commit_ref / requested_at / status / resolved_at / note`. - `scheduled_prompts` — the scheduled prompts left from before they moved to swarm level ([`swarm/schedules.md`](../swarm/schedules.md)). `owner / body / interval_seconds (NULL = one-shot) / @@ -86,8 +82,6 @@ Retention: rows are always kept — the harness `ack_turn`s only after a successful turn, so `requeue_inflight` can still requeue an unacked row on a crash. -- hive-c0re keeps approvals indefinitely — an audit trail. `actions::destroy` - rows stay visible to anything that queries by id. - Scheduled prompts: nothing deletes them; the rows stay as they are. - `agent_power` rows live until the operator destroys the agent (one row per agent — nothing to vacuum). @@ -362,10 +356,10 @@ unless it holds the capability, nothing else. Under `/var/lib/hyperhive/applied//` — the hive-c0re-only applied repo. Tracks `flake.nix` (module-only boilerplate; never -edited after first spawn) + `agent.nix` (the actual config; the -root agent's edits land here via the approval flow) + any other -files committed via the approval flow. `.git/` carries the proposal / -approved / building / deployed / failed / denied tag history. +edited after first spawn) + `agent.nix` (the actual config; edits land +here from merged config PRs) + any other files those PRs commit. +`.git/` carries the proposal / approved / building / deployed / failed / +denied tag history. Under `/var/lib/hyperhive/meta/` — the swarm-wide deploy flake plus system-level config files. Single git repo for the whole host; hive-c0re @@ -411,18 +405,17 @@ hive-c0re renders containers onto `meta#` at creation. See [For operators](#for-operators) above for what each action does to an agent's state. The mechanics, for completeness: -- `DESTR0Y` also drops the systemd drop-in and fails any pending - approvals; reviving the agent is `swarmctl agent create` with the same - name and hive, which reuses the kept state. +- `DESTR0Y` also drops the systemd drop-in; reviving the agent is + `swarmctl agent create` with the same name and hive, which reuses the + kept state. - `PURG3` wipes `/var/lib/hyperhive/{agents,applied}//` — the union of everything `DESTR0Y` left behind. `actions::destroy` implements the root/bootstrap agent's specialness as a soft policy guard that refuses to destroy it, backstopped by `auto_update::ensure_root_agent`, which recreates it on the next -hive-c0re startup if it's ever absent (bypassing the approval queue, -as required infrastructure) — so even without the guard, destroying it -would only be transient. +hive-c0re startup if it's ever absent — so even without the guard, +destroying it would only be transient. ### btrfs subvolumes for `/var/lib/hyperhive/agents/` diff --git a/docs/getting-started/setup.md b/docs/getting-started/setup.md index c62f9e2e..e852963a 100644 --- a/docs/getting-started/setup.md +++ b/docs/getting-started/setup.md @@ -129,7 +129,7 @@ swarm UI's job view for progress. Later config changes are PRs on `agent-configs/iris`; you merge them on the forge, and the merge deploys them. → -[`agent-lifecycle/approvals.md`](../agent-lifecycle/approvals.md#config-changes) +[`agent-lifecycle/config-changes.md`](../agent-lifecycle/config-changes.md#config-changes) ## Optional · Lock the hive dashboard @@ -150,7 +150,6 @@ hivectl list-agents # this hive's agents, status, rev hivectl agent restart # stop + start, no rebuild hivectl agent watch # follow its live event stream hivectl agent choom # interactive claude session in its container -hivectl approvals pending # what's waiting on you hivectl open # this hive's dashboard (or: forge, matrix) ``` diff --git a/docs/integrations/forge.md b/docs/integrations/forge.md index 9657311e..143f8f37 100644 --- a/docs/integrations/forge.md +++ b/docs/integrations/forge.md @@ -67,7 +67,7 @@ Two things live in the `agent-configs` Forgejo organization: allowlists are the `operators` team, and the agent can neither push `main` directly nor self-merge. An operator's merge in the Forgejo UI deploys the merged commit (see - [approvals.md § Config changes](../agent-lifecycle/approvals.md#config-changes)). + [config-changes.md § Config changes](../agent-lifecycle/config-changes.md#config-changes)). hive-c0re never force-pushes: the `push_config` mirror pushes the add-only status tags and `main` without force, and treats a refused `main` push as expected. @@ -75,7 +75,7 @@ Two things live in the `agent-configs` Forgejo organization: agent's config. (Agents remain read-only collaborators on `core/meta`.) hive-c0re also references this repo as the agent's **persistent meta flake input** (`agent-.url = git+http:///agent-configs/.git`; - see [approvals.md § Meta flake](../agent-lifecycle/approvals.md)), fetching it as the `core` + see [config-changes.md § Meta flake](../agent-lifecycle/config-changes.md#meta-flake)), fetching it as the `core` user via a git credential helper that reads the live forge-core token — so the config lives on the forge, not a hand-synced local checkout. - The dashboard links each container's "config" anchor to this diff --git a/docs/networking/gateway.md b/docs/networking/gateway.md index ac47d455..3298e386 100644 --- a/docs/networking/gateway.md +++ b/docs/networking/gateway.md @@ -141,7 +141,7 @@ A failed or missing login gets `401` with a styled `unauthorized.html` naming th nginx is the one external entry point. The per-agent web-port range (`8100`–`8999`) stays closed: agents serve their UI on a unix socket ([Per-agent unix-socket upstream](#per-agent-unix-socket-upstream)), and the hashed TCP port (`lifecycle::agent_web_port`) is only a fallback bind for an agent missing `HIVE_WEB_SOCKET` — the gateway never proxies through it. -The dashboard port (`services.hyperhive.c0re.dashboardPort`, default 7000) binds `127.0.0.1` only, so remote dashboard access goes through the gateway. The dashboard can approve, deny and destroy; don't expose it without a reverse proxy in front. +The dashboard port (`services.hyperhive.c0re.dashboardPort`, default 7000) binds `127.0.0.1` only, so remote dashboard access goes through the gateway. The dashboard can stop, rebuild and destroy agents; don't expose it without a reverse proxy in front. ## `HIVE_FORGE_URL`: agents reach the forge via the gateway by domain diff --git a/docs/process/conventions.md b/docs/process/conventions.md index 87c2bec6..daaff426 100644 --- a/docs/process/conventions.md +++ b/docs/process/conventions.md @@ -163,27 +163,18 @@ the dashboard's "mark all read" (unbounded, per-agent). ### Loose-ends wire shape `LooseEnd` is the per-row response shape for `GetLooseEnds` — one -request shape, no per-socket flavours. Tagged enum so -new thread kinds (forge PRs, long-running approvals from a -privileged bot, etc.) can land later without breaking existing -handlers. Each row carries enough context that the caller renders +request shape, no per-socket flavours. Tagged enum so new thread kinds +(forge PRs, etc.) can land later without breaking existing handlers. +Each row carries enough context that the caller renders it directly as a bulleted list, no follow-up fetch needed. `GetLooseEnds` takes no target — it always returns the caller's own rows, on both the agent and manager sockets alike (the manager, `ruth`, is a normal agent here, just deployed automatically with different -default capabilities). `Approval` rows only appear when the calling agent is -the manager (sub-agents don't submit approvals). `Reminder` rows always -carry `owner == self`. +default capabilities). `Reminder` rows always carry `owner == self`. Per-variant fields: -- `Approval { id, agent, commit_ref, description?, age_seconds }` - — `agent` is the affected agent (target of the spawn / config - commit), not the asker. `description` is the manager's free-text - blurb shown on the dashboard card. `commit_ref` is the - kind-specific payload (see `docs/agent-lifecycle/approvals.md::Approval kinds - (wire shapes)`). - `Reminder { id, owner, message, due_at, age_seconds }` — `due_at` is the absolute time the scheduler is targeting (RFC 3339 on the wire, see *Timestamps on the wire* below); clients @@ -205,16 +196,9 @@ Per-variant fields: unsynchronised wall clock, etc.) so the bulleted list never shows nonsense ages. -`CancelLooseEnd { kind, id }` is the matching write surface. The -`kind` enum (`Reminder` / `Approval`) selects which underlying store -the dispatcher reaches into. `Reminder` cancels from either surface -subject to an ownership check (the scheduling agent). -`Approval` is manager-only — sub-agents don't submit approvals -so they have nothing of their own to withdraw; their wire -surface returns a clear error if they try. Cancelling an approval -transitions the row to `ApprovalStatus::Cancelled` and fires -`ApprovalResolved { status: "cancelled" }` so the dashboard pulls -the card out of the pending pane. +`cancel_loose_end` is the matching write surface, handled in the +harness without a hive-c0re round-trip: `reminder` deletes one of the +caller's own pending reminders, `todo` clears one of its todos. ### Agent metadata @@ -296,7 +280,6 @@ binary flavor. | `inbox` | `get_loose_ends`, `cancel_loose_end`, `remind` | | `execution` | vestigial — `mcp__bash__run` / `mcp__bash__status` are always available unconditionally via `extraMcpServers`; this group's entries expand to non-existent `mcp__hyperhive__run` / `mcp__hyperhive__status` and have no effect. See `docs/tools/bash.md`. | | `lifecycle` | none | -| `approvals` | none — gates `cancel_loose_end`'s approval-cancel arm server-side. | | `forge` | none | | `web_tools` | none (gates the Claude built-ins `WebFetch`/`WebSearch`, not an MCP tool) | @@ -316,7 +299,7 @@ clear them. meta repo alongside `topology.json`). Format: `{ "alice": ["messaging", "meta", "inbox", "lifecycle"], "bob": ["messaging", "meta", "inbox"] }`. An absent entry means "use role default." Tool permissions are intentionally NOT configurable -from `agent.nix` — that file goes through the manager's approval flow, so +from `agent.nix` — that file changes through config PRs the manager opens, so letting it declare its own groups would let the manager grant itself any tool by submitting a config commit, bypassing the operator gate. @@ -385,9 +368,7 @@ dashboard and `app.js` on the per-agent UI) intercepts, shows a spinner, POSTs `application/x-www-form-urlencoded` (axum's `Form` extractor rejects multipart), calls `refreshState()` on success. New mutating forms should add `data-async` and optionally `data-confirm` (for a -JS-side `confirm()` prompt) or `data-prompt="…"` (for a -`window.prompt()` whose answer goes into a hidden input named by -`data-prompt-field`, default `note`). +JS-side `confirm()` prompt). `refreshState` defers automatically when `document.activeElement` sits inside a managed section so the operator's typing isn't lost; @@ -404,7 +385,7 @@ into the `Swap` node, then runs `nixos-container update` + stop + start across the `StopForUpdate → Swap → RebuildBookkeeping` brace and the tail `Reconcile` node. `flake.nix` itself lives in the agent's proposed/applied repos and rides along on every fetch (see -`docs/agent-lifecycle/approvals.md::Two repos per agent`). +`docs/agent-lifecycle/config-changes.md::Two repos per agent`). Anything that changes per-container state on the host should be re-applied here so a manual `↻ R3BU1LD` from the dashboard is @@ -412,7 +393,7 @@ sufficient to recover. ## Actions live in one place -`approve` / `deny` / `destroy` (and the lifecycle helper) live in +`destroy` (and the lifecycle helper) live in `actions.rs` / `hive-c0re/src/dashboard/`. The admin socket and the dashboard POST handlers both call into them so the two surfaces never drift. diff --git a/docs/process/gotchas.md b/docs/process/gotchas.md index 65709894..c3e4c7ae 100644 --- a/docs/process/gotchas.md +++ b/docs/process/gotchas.md @@ -218,15 +218,6 @@ overlap" case. REUSEADDR does **not** allow two simultaneous `LISTEN` sockets on the same port (that would be `SO_REUSEPORT`, not used here) — it preserves exclusivity. -## Approvals - -### Orphan approvals - -If test scripts or a manual `rm -rf` wipe state dirs out from under a -pending approval, the dashboard's next render marks them -`failed` with note `"agent state dir missing"` so they fall out of -`pending`. They stay in sqlite for audit. - ## Gateway / SPA serving ### SPA fallback: use `Accept` header map, not `try_files ... /index.html` diff --git a/docs/scheduler/coordinator.md b/docs/scheduler/coordinator.md index 1636d72f..eed67688 100644 --- a/docs/scheduler/coordinator.md +++ b/docs/scheduler/coordinator.md @@ -24,7 +24,7 @@ from the same primitive nodes. ### Two levels: DAG and node -The **DAG** is the unit of cancel / approval-resolution and the +The **DAG** is the unit of cancel and the dashboard group; the **node** is the unit of scheduling / execution / build-log. Deps are intra-DAG edges only (`AfterOk` by default: the dep must succeed, a failed/cancelled dep cancels the dependent — @@ -69,9 +69,8 @@ Cheap — no build slot: | `PauseDrain` | await the harness reporting `PauseAcknowledged`, bounded timeout; best-effort like `Drain` | | `DestroyContainer` | `nixos-container destroy` + un-registration (drop from the roster, clear the ephemeral runtime dir). Runs downstream of a `Stop`, so deliberately excluded from `takes_container_down` — the container is already down by the time it claims | | `PurgeState` | the `purge = true` half of a destroy: delete the agent's state subvolume (via hive-priv) plus its state/applied dirs. Own node because it's conditional and the irreversible step | -| `DestroyBookkeeping` | the post-destroy tail — meta sync, fail pending approvals, drop the power intent, notify the manager, rescan, re-emit the tombstone. Same split rationale as `RebuildBookkeeping`/`Swap`. Its `purge` flag only selects the wording of the approval-failure reason and the manager notification — the destructive work is `PurgeState`'s | +| `DestroyBookkeeping` | the post-destroy tail — meta sync, drop the power intent, notify the manager, rescan, re-emit the tombstone. Same split rationale as `RebuildBookkeeping`/`Swap`. The destructive work is `PurgeState`'s | | `SetWanted` | write the durable power intent (`wanted = Up`/`Offline`) as the head node of a power-op DAG. Takes the agent lease even though it's a store write, so the intent write and the tail `Reconcile` are atomic per-agent — two racing power ops can't clobber each other's intent before either reconciles | -| `ResolveApproval` | tail of an approval-carrying DAG — resolve the approval row from how the work ended (`AfterAny`, one node emitted per outcome). Agentless: the approval row already names its agent | | `EmitRebuilt` | tail of a rebuild/perm-change — emit the agent's `Rebuilt` manager event (ok/fail per outcome, nothing on cancel). One node per agent _and_ per outcome | | `WriteDropin` | `set_nspawn_flags` + `set_resource_limits` + daemon-reload | | `WritePermFile` | commit `tool-groups.json` / `capabilities.json` (single git commit under `META_LOCK`) + emit the P3RM1SS10NS snapshots | @@ -139,7 +138,7 @@ stop(a..): online a: SetWanted(a,Off) → [Signal→Drain→ if graceful] Re restart(a..): online a: [Signal→Drain→ if graceful] StopForUpdate(a) → Reconcile(a) (no SetWanted) offline a: Reconcile(a) (nothing to stop; Reconcile converges to existing wanted) start(a..): a: SetWanted(a,Up) → Reconcile(a) (down+stale ⇒ SetWanted(a,Up) → «rebuild subgraph») -spawn(a): [wanted=Up at approve] Provision(a) → Create(a) → WriteDropin(a) → Reconcile(a) +spawn(a): [wanted=Up at deploy] Provision(a) → Create(a) → WriteDropin(a) → Reconcile(a) perm-change(a): WritePermFile(a) → «rebuild subgraph» meta-update(inp): MetaLock(inp) →(in-DAG) «rebuild subgraph» per affected agent boot: (if any rev marker stale) MetaLock(hyperhive) →(in-DAG) «rebuild subgraph» per stale agent; @@ -280,13 +279,6 @@ Roll-up state: `Failed` if any node failed, else `Running` / `Queued` / DAGs — a flat cap over the whole sorted list, not per template, since the dashboard renders one recent-builds list and one number bounds it. -### Approvals - -`UpdateMetaInputs` approvals map onto the ordinary `meta-update` shapes. -The scheduler fires `actions::resolve_approval_dag` exactly once when -**any** approval-carrying DAG settles terminal (including -cancelled-while-queued, which fails the approval instead of dangling it). - ### Wire shape `RebuildQueueChanged { seq }` (event name kept) — **a bare trigger, no @@ -297,7 +289,7 @@ That endpoint serves the graph generically (`hive-jobq-wire`): every node carries `id`, `parent`, `deps`, `state`, `label` (the node kind's own wire string — the kind _is_ the phase label, there is no separate sub-step string) and free-form `data` for what only some kinds have (`agent`, -`approval_id`, `inputs`, `build_log_id`). Group roots ride as ordinary +`inputs`, `build_log_id`). Group roots ride as ordinary nodes, so a group's state is just the root's own `state`. No group-level `agent` exists — agent is per-node, so one group can @@ -521,7 +513,7 @@ which is almost never desired. Leave off on non-x86 hosts. ## See also -- `docs/agent-lifecycle/approvals.md` — approval flow +- `docs/agent-lifecycle/config-changes.md` — config PRs, meta flake, helper events - `docs/agent-lifecycle/persistence.md` — SQLite schema, state-dir layout - `docs/process/conventions.md` — wire protocol, recipient sentinels - `docs/agent-lifecycle/agent-roster.md` — the agent roster and privilege diff --git a/docs/swarm/README.md b/docs/swarm/README.md index 2d320a8d..d3c3982d 100644 --- a/docs/swarm/README.md +++ b/docs/swarm/README.md @@ -292,7 +292,7 @@ decision, not an event to adjudicate. A `config-pr` delivery reporting a PR merged into `main` queues a deploy of its `merge_commit_sha` on the one hive whose wanted state places the agent; with no such hive, or several, the controller deploys nothing. See -[approvals.md § Config changes](../agent-lifecycle/approvals.md#config-changes). +[config-changes.md § Config changes](../agent-lifecycle/config-changes.md#config-changes). A merge whose `closed` delivery never arrives deploys nothing either; the operator recovers by redelivering that delivery from the forge hook page, which re-enters the same handler and re-queues the deploy. diff --git a/docs/trust-boundary/boundary.md b/docs/trust-boundary/boundary.md index 30fc9f0b..dfc81a67 100644 --- a/docs/trust-boundary/boundary.md +++ b/docs/trust-boundary/boundary.md @@ -18,8 +18,8 @@ always on), the gateway proxies all operator-facing traffic, and - **Operator** — reaches every UI (the dashboard + every per-agent page) through the gateway, on one origin. - The core daemon serves operator-authority actions (approve / deny, - answer-as-operator, lifecycle POSTs), reachable only via the + The core daemon serves operator-authority actions + (answer-as-operator, lifecycle POSTs), reachable only via the gateway. - **Agent** — speaks only for itself, only over its per-agent unix socket. The socket's identity _is_ the agent (see diff --git a/docs/trust-boundary/security.md b/docs/trust-boundary/security.md index 4b299b62..da648a5a 100644 --- a/docs/trust-boundary/security.md +++ b/docs/trust-boundary/security.md @@ -118,8 +118,9 @@ checkpoints**, not about sandboxing the agent from its own tools: settings nor merge its own PR. It's **not** set up for external VCS (GitHub etc.), though — there, operator-merge is process + accepted risk, not a technical control. -- **Approvals** — config changes are config PRs an operator merges on the - forge (see [`approvals.md`](../agent-lifecycle/approvals.md)), and +- **Operator sign-off** — config changes are config PRs an operator merges + on the forge (see + [`config-changes.md`](../agent-lifecycle/config-changes.md)), and scheduled prompts are operator-only, set at swarm level (see [`schedules.md`](../swarm/schedules.md)). @@ -314,6 +315,6 @@ writes to only the paths `hive-priv` legitimately needs. | Component | Runs as | Privilege needed for | | ---------------------------- | -------------- | ------------------------------------------------------------ | -| `hive-c0re` | `hive-core` | broker, HTTP dashboard, approvals | +| `hive-c0re` | `hive-core` | broker, HTTP dashboard | | `hive-priv` | `root` | container lifecycle, journal reads, bind mounts, cred writes | | `hive-ag3nt` (per-container) | per-agent user | turn execution, MCP serving | diff --git a/docs/turn-loop/mcp.md b/docs/turn-loop/mcp.md index 045086a9..73d42c5d 100644 --- a/docs/turn-loop/mcp.md +++ b/docs/turn-loop/mcp.md @@ -24,8 +24,8 @@ for what reaches a subagent's own `--mcp-config` and what doesn't. Tool groups (`HIVE_TOOL_GROUPS`) gate tool access. The default preset (`AGENT_DEFAULT`) includes `messaging`, `meta`, `inbox`, and -`execution`. Privileged groups (`lifecycle`, `approvals`) -are opt-in via the P3RM1SS10NS tab. +`execution`. The privileged `lifecycle` group +is opt-in via the P3RM1SS10NS tab. ## Core tools (always available) @@ -61,14 +61,10 @@ are opt-in via the P3RM1SS10NS tab. Transient pings (sentinel id 0) have nothing to ack and show no marker. **System messages** (from sender `system`): the broker delivers -`hive_sh4re::manager::HelperEvent` (three variants: `ApprovalResolved`, -`ContainerCrash`, `NeedsUpdate` — declared, but no call site -constructs it) as regular inbox messages (same `recv` -path; body is a JSON object with an `event` discriminant field). -`ApprovalResolved` goes to whichever agent actually submitted the -approval (`Coordinator::notify_submitter`, looked up from the -authenticated socket caller at submit time; a row with no -recorded submitter falls back to the manager, `ruth`). +`hive_sh4re::manager::HelperEvent` (two variants: `ContainerCrash`, +`NeedsUpdate` — declared, but no call site constructs it) as regular +inbox messages (same `recv` path; body is a JSON object with an `event` +discriminant field). `ContainerCrash` always goes to `ruth` (`Coordinator::notify_manager`, hardcoded — `hive-c0re/src/workers/crash_watch.rs`). Lifecycle transitions the job-queue scheduler or crash watcher drive directly — @@ -84,15 +80,11 @@ at_unix_timestamp?)`. - `get_loose_ends()` — list this agent's own open threads: pending reminders, open todos pushed by an in-container subsystem (matrix, bash, forge, or any user-configured MCP server), an undelivered-inbox - count, and unread matrix notifications; `approval` rows only appear - when the caller is the manager (`ruth`) — sub-agents don't submit - approvals. Always the caller's own rows. Full per-variant wire shape in + count, and unread matrix notifications. Always the caller's own rows. Full per-variant wire shape in [`docs/process/conventions.md` § Loose-ends wire shape](../process/conventions.md#loose-ends-wire-shape). -- `cancel_loose_end` — hard-delete a `reminder`, cancel a pending - `approval` row, or clear a `todo` row (loose-ends-v2). Agents may - only cancel rows they own; the `approval` kind is further restricted - to the root agent (`ruth`) server-side. +- `cancel_loose_end` — hard-delete a `reminder` or clear a `todo` row + (loose-ends-v2). Agents may only cancel rows they own. - `remind` — schedule a reminder in this agent's own inbox. Large payloads spill to `/agents//state/reminders/`. Pending count capped at 50 per agent (`HIVE_REMIND_MAX_PENDING_PER_AGENT`). @@ -144,11 +136,9 @@ hive_name?, swarm_name?, matrix_accounts? }`. `matrix_accounts` is a (claude or ACP, via `hive-runtime`) as background tasks, shipped default-on like bash execution; no tool group gates it. See [`docs/tools/subagent.md`](../tools/subagent.md). -- **Lifecycle + config** (`lifecycle`, `approvals`) — `approvals` - gates `cancel_loose_end`'s approval-cancel arm server-side; - `lifecycle` gates nothing. +- **Lifecycle + config** (`lifecycle`) — gates nothing. Config changes go through a forge PR on `agent-configs/` — see - [`docs/agent-lifecycle/approvals.md`](../agent-lifecycle/approvals.md). + [`docs/agent-lifecycle/config-changes.md`](../agent-lifecycle/config-changes.md). - **Forge repos** (`forge`) — gates nothing. - **Web egress** (`web_tools`) — enables Claude's built-in `WebFetch` and `WebSearch` tools (not MCP tools; added directly to the diff --git a/docs/web-ui/README.md b/docs/web-ui/README.md index 39cbb25c..061308fa 100644 --- a/docs/web-ui/README.md +++ b/docs/web-ui/README.md @@ -1,6 +1,6 @@ # Dashboard & Web UI -The per-hive dashboard: approvals, container state and the rebuild +The per-hive dashboard: container state and the rebuild queue for one hive. For swarm-wide day to day, see [the swarm UI](../swarm/ui.md) instead. If you want implementation detail — wire formats, DOM structure, event plumbing — see [Dashboard @@ -20,8 +20,8 @@ of your time. It's a single page with exactly four tabs: - **SW4RM** — every agent, live. This is the default tab and the one you'll check most. -- **Y3R C4LL** — anything waiting on _you_: pending approvals. If an - agent needs a decision from you, it's here. +- **Y3R C4LL** — anything waiting on _you_: messages agents sent to + the operator. - **P3RM1SS10NS** — what tools and system-level access each agent has. - **SCH3DUL3S** — a read-only list of the scheduled prompts left in the hive, due for removal; operators set schedules at swarm level now. @@ -48,14 +48,14 @@ whether it's running, what it's currently doing (a live status pill — and quick links (stats, screen, forge profile). Select the name to open its terminal and watch it work in real time. -**Approve something an agent is waiting on.** Y3R C4LL is the one tab -worth checking regularly — it's everything that needs _you_. The tab's -count pill tells you at a glance if anything's pending. +**Read what agents sent you.** Y3R C4LL is the one tab worth checking +regularly — it's everything that needs _you_. The tab's count pill +tells you at a glance if anything's unread. **Review a config change.** Agent config changes (new packages, env vars, MCP servers) are pull requests on the agent's `agent-configs` repo. Review and merge them on the forge; the merge deploys the change. -See [`approvals.md`](../agent-lifecycle/approvals.md#config-changes). +See [`config-changes.md`](../agent-lifecycle/config-changes.md#config-changes). **Start, stop, restart, or rebuild an agent.** Select one or more agents on SW4RM (select the icon) and use the selection bar, or use the diff --git a/docs/web-ui/dashboard.md b/docs/web-ui/dashboard.md index bbffe8fb..b24a97bd 100644 --- a/docs/web-ui/dashboard.md +++ b/docs/web-ui/dashboard.md @@ -24,7 +24,7 @@ from the dashboard tab strip. standalone **C0R3** page (`/core.html`), and FL0W / L0GS / ST4TS live on their own pages too, all reachable from the H0M3 hub (not the tab strip). Count pills on SW4RM - (container count), Y3R C4LL (pending approvals + unread + (container count), Y3R C4LL (unread operator messages), and SCH3DUL3S (active schedules left to copy). - **Banner-thin** (`░▒▓█▓▒░ HYPERHIVE / HIVE-C0RE / WE ARE THE WIRED ░▒▓█▓▒░`) — sits below the tab strip. @@ -46,13 +46,12 @@ from the dashboard tab strip. snapshot. Hidden when there are no warnings. - **Browser tab title** — `hyperhive // h1ve-c0re` by default; updated to ` / // h1ve-c0re` once `hive_name` / `swarm_name` - arrive in the state snapshot. When there are pending approvals, the - page prepends a `(N)` prefix — `(3) pr1ma // h1ve-c0re` — so the + arrive in the state snapshot. When there are unread operator messages, + the page prepends a `(N)` prefix — `(3) pr1ma // h1ve-c0re` — so the operator can see the call count in an unfocused browser tab without - opening the dashboard. The page sets the prefix on the initial - `/api/state` cold-load and updates it live via - `approval_resolved` SSE events; it's preserved when `hive_name` / - `swarm_name` later replace the raw title. + opening the dashboard. The page sets the prefix on the operator-inbox + cold-load and updates it live as broker `sent` events arrive; it's + preserved when `hive_name` / `swarm_name` later replace the raw title. The FL0W and L0GS pages use a slim header (a `← home` back-link + the page title) rather than the dashboard tab strip — they're standalone @@ -74,12 +73,7 @@ agent is stale. ## Y3R C4LL tab -Things blocked on operator decision — the approval queue. - -**P3NDING APPR0VALS** — the queue (see "Approval card" below). A -`pending · N` / `history · N` tab pair switches between the live -queue and the last 30 resolved approvals (see "Approval card" for -the history row shape). +Things blocked on operator decision — the operator inbox. **0PER4T0R 1NB0X** — messages agents have sent to `to="operator"` but the operator hasn't read yet. Cold-loaded from `/api/operator-inbox` on @@ -332,7 +326,7 @@ named buckets of MCP tools; each agent starts with a role default (sub-agents: `messaging`, `meta`, `inbox`, `execution` — `ToolGroup::AGENT_DEFAULT`; hive-c0re seeds the root agent to `ToolGroup::MANAGER_DEFAULT` — `messaging`, `meta`, `inbox`, -`lifecycle`, `approvals`, `execution`, +`lifecycle`, `execution`, that is, every group except `forge` and `web_tools`). Checking / unchecking stages which groups are active for the agent; the page-level **save all** button (below) commits it. Columns come from @@ -341,8 +335,7 @@ takes effect. The current tool groups are: `messaging`, `meta`, `inbox`, `lifecycle`, -`approvals`, `forge`, `execution`, -`web_tools`. All listed in `ToolGroup::ALL` in `hive-sh4re`. The +`forge`, `execution`, `web_tools`. All listed in `ToolGroup::ALL` in `hive-sh4re`. The `web_tools` group is special: it carries no MCP tools; instead it adds Claude's built-in `WebFetch` and `WebSearch` to `--tools` / `--allowedTools` for that agent session. @@ -739,19 +732,17 @@ that wants step-level counts instead, unused here. ### Themed dialogs -All confirmations, prompts, and transient error notices use an -in-app themed dialog system (`@hive/shared/modal.js`) rather than the -browser's native `confirm()` / `prompt()` / `alert()` chrome, so -they match the Catppuccin palette and the OS can't style them away. -Three primitives, all built on the `openDialog` core: +All confirmations and transient error notices use an in-app themed +dialog system (`@hive/shared/modal.js`) rather than the browser's +native `confirm()` / `alert()` chrome, so they match the Catppuccin +palette and the OS can't style them away. Two primitives, both built +on the `openDialog` core: - `themedConfirm({ message, danger, confirmLabel, checkboxes })` — a modal confirm that resolves to `null` on cancel or an object of checkbox states on confirm. Destructive actions pass `danger: true` (the confirm button turns red and the cancel button takes focus). Selecting the backdrop and `Esc` both cancel. -- `themedPrompt(...)` — a modal with a text input, resolving to the - entered string or `null`. - `themedToast(message, { type, ... })` — a non-blocking toast (top-right, `info` / `error` / `ok`) for transient validation + action failures, so an error doesn't trap the operator behind a modal. @@ -816,51 +807,18 @@ frosted-mauve bar slides up from the bottom of the viewport Every render prunes stale selections (agents destroyed while selected) before the bar appears. -## Approval card - -Each pending approval renders as a card (`assets/call.js:: -renderApprovals`) with three stacked sections: - -- **identity header** — glyph, `#id`, agent, kind chip, and a - right-aligned `requested ago` relative time from - `ApprovalView.requested_at`. Glyph and chip vary by kind: - - | kind | glyph | chip | - |---|---|---| - | `update_meta_inputs` | `↻` | `meta-update` | - - - The chip ticks live every second via a `data-requested-at` - attribute + client-side interval (no re-render). Turns amber once - the request has been pending ≥ 1h so a stale approval stands out; - the `.stale` class flips precisely at the 3600s boundary rather - than at the next `renderApprovals` call. -- **what-changed body** — the submitting agent's description, then the - kind's payload: the inputs to bump (`update_meta_inputs`). -- **decision actions** — `◆ APPR0VE` and `DENY`. Deny pops a - `prompt()` for an optional reason carried to the submitting agent as - `HelperEvent::ApprovalResolved.note`. - - -A `pending · N` / `history · N` tab pair switches the section -between the live queue and the last 30 resolved approvals. - ## Browser notifications -Pure frontend (`Notification` API). Two signals trigger them: - -- new pending approval (per id, delta on `/api/state`) -- new broker message sent `to: "operator"` (live via SSE) +Pure frontend (`Notification` API), triggered by a new broker message +sent `to: "operator"` (live via SSE). The toggle controls live in the Y3R C4LL tab's ◆ PR3F3R3NC3S ◆ section; see that section above for the user-facing shape. Dispatch logic lives in `common.js::NOTIF`. -First `/api/state` after page load seeds "seen" sets without -firing — only items that arrive while the page is open count. -Per-event tags (`hyperhive:approval:`, -`hyperhive:msg::`) so distinct events stack in the OS +Only messages that arrive while the page is open count. Per-event tags +(`hyperhive:msg::`) so distinct events stack in the OS notification center instead of overwriting each other. `console.debug` logs at every block point (unsupported, permission ungranted, muted) for in-browser debugging. Selecting it @@ -880,17 +838,6 @@ the H0M3 hub's **API** tile), with the raw spec at `/api/openapi.json`. It's a growing supplement, not yet a full replacement for the list below — some endpoints aren't in it yet. -- `POST /api/approve/{id}` — approve a pending approval. Fires - `ApprovalResolved` on the dashboard event channel; client - updates derived approvals state from the event. -- `POST /api/deny/{id}` (`note=`, optional) — deny a pending - approval with an optional operator-supplied reason. The reason - travels to the submitting agent as `HelperEvent::ApprovalResolved.note` - and also rides on the dashboard's `ApprovalResolved` event. - Dashboard prompts via the themed `themedPrompt()` dialog on - selection — a resizable `