feat(#1997): add prettier markdown formatter to treefmt
- .prettierrc: proseWrap=preserve (no prose reflow) - .prettierignore: exclude hivectl-cli.md (auto-generated) + 11 docs with multi-line list-item continuations prettier would strip to col 0 (CommonMark limitation in prettier's list handling) - format 16 markdown files: cosmetic only (*→_, table alignment, heading normalisation) — verified no broken continuations, idempotent
This commit is contained in:
parent
442efa76f2
commit
8406a45275
17 changed files with 254 additions and 230 deletions
|
|
@ -2,3 +2,19 @@
|
|||
# The hivectl-docs CI check diffs this against fresh binary output,
|
||||
# so reformatting it would break that check.
|
||||
docs/tools/hivectl-cli.md
|
||||
|
||||
# Files with multi-line list-item continuations that prettier strips to col 0.
|
||||
# prettier's `proseWrap: "preserve"` prevents prose reflow but not list-item
|
||||
# indentation normalisation, so these files need manual re-wrap before
|
||||
# adding them back to the formatter scope.
|
||||
CLAUDE.md
|
||||
docs/approvals.md
|
||||
docs/conventions.md
|
||||
docs/gateway.md
|
||||
docs/matrix.md
|
||||
docs/persistence.md
|
||||
docs/terminal-rendering.md
|
||||
docs/tools/matrix.md
|
||||
docs/turn-loop.md
|
||||
docs/web-ui/agent.md
|
||||
docs/web-ui/dashboard.md
|
||||
|
|
|
|||
24
README.md
24
README.md
|
|
@ -5,7 +5,7 @@
|
|||
> approves them in a browser, every deploy is a tag. cyberpunk-themed
|
||||
> dashboard included. 💜⚡
|
||||
|
||||
Claude code is great in one window, *exponentielle* across many — but
|
||||
Claude code is great in one window, _exponentielle_ across many — but
|
||||
only if you can keep the agents from stepping on each other, give them
|
||||
durable identity, and stop them from eating production. hyperhive is
|
||||
the substrate.
|
||||
|
|
@ -43,15 +43,15 @@ host (NixOS, runs hive-c0re.service)
|
|||
|
||||
Depth lives in [`docs/`](docs/) — pick the one matching your task:
|
||||
|
||||
| reading path | doc |
|
||||
| --- | --- |
|
||||
| dashboard layout + endpoints | [`docs/web-ui.md`](docs/web-ui.md) ([shape](docs/web-ui/shape.md) · [dashboard](docs/web-ui/dashboard.md) · [agent](docs/web-ui/agent.md)) |
|
||||
| claude turn loop + MCP tools | [`docs/turn-loop.md`](docs/turn-loop.md) |
|
||||
| config-edit + approval state machine | [`docs/approvals.md`](docs/approvals.md) |
|
||||
| what survives destroy / purge / restart | [`docs/persistence.md`](docs/persistence.md) |
|
||||
| naming, wire protocol, commit style | [`docs/conventions.md`](docs/conventions.md) |
|
||||
| nginx vhost map + sub-domain routing | [`docs/gateway.md`](docs/gateway.md) |
|
||||
| NixOS / nspawn gotchas | [`docs/gotchas.md`](docs/gotchas.md) |
|
||||
| reading path | doc |
|
||||
| --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| dashboard layout + endpoints | [`docs/web-ui.md`](docs/web-ui.md) ([shape](docs/web-ui/shape.md) · [dashboard](docs/web-ui/dashboard.md) · [agent](docs/web-ui/agent.md)) |
|
||||
| claude turn loop + MCP tools | [`docs/turn-loop.md`](docs/turn-loop.md) |
|
||||
| config-edit + approval state machine | [`docs/approvals.md`](docs/approvals.md) |
|
||||
| what survives destroy / purge / restart | [`docs/persistence.md`](docs/persistence.md) |
|
||||
| naming, wire protocol, commit style | [`docs/conventions.md`](docs/conventions.md) |
|
||||
| nginx vhost map + sub-domain routing | [`docs/gateway.md`](docs/gateway.md) |
|
||||
| NixOS / nspawn gotchas | [`docs/gotchas.md`](docs/gotchas.md) |
|
||||
|
||||
## Quick start
|
||||
|
||||
|
|
@ -96,7 +96,7 @@ Per-agent config lives in each agent's `agent.nix` (proposed, operator-approved,
|
|||
|
||||
### Multi-account Matrix support
|
||||
|
||||
`hyperhive.matrixAccounts` declares *additional* matrix accounts for an agent, beyond the hive-internal one. Each entry is keyed by account name and specifies:
|
||||
`hyperhive.matrixAccounts` declares _additional_ matrix accounts for an agent, beyond the hive-internal one. Each entry is keyed by account name and specifies:
|
||||
|
||||
- `tokenFile` — path to the matrix bearer token (provisioned out-of-band)
|
||||
- `sessionDir` — path to the per-account matrix-sdk sqlite state (crypto keys + cache)
|
||||
|
|
@ -114,7 +114,7 @@ hyperhive.matrixAccounts = {
|
|||
};
|
||||
```
|
||||
|
||||
The hive-internal account is always named `main` (synthesized from `hyperhive.matrix.url` + agent state). This option only declares *extras*; the `main` name is reserved and cannot be used here. Requires `hyperhive.matrix.enable = true`.
|
||||
The hive-internal account is always named `main` (synthesized from `hyperhive.matrix.url` + agent state). This option only declares _extras_; the `main` name is reserved and cannot be used here. Requires `hyperhive.matrix.enable = true`.
|
||||
|
||||
For more details see [`docs/matrix.md`](docs/matrix.md).
|
||||
|
||||
|
|
|
|||
|
|
@ -12,9 +12,9 @@ Topology lives in the hive-c0re-owned **meta repo**, alongside
|
|||
|
||||
```json
|
||||
{
|
||||
"ruth": null,
|
||||
"ruth": null,
|
||||
"alice": null,
|
||||
"bob": "alice"
|
||||
"bob": "alice"
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -34,6 +34,7 @@ operator-driven:
|
|||
|
||||
All three converge on `topology::set_parent`, which delegates the
|
||||
validation rules to a pure `apply_set_parent` helper. Refuses:
|
||||
|
||||
- unknown `child` / `new_parent` (typo guard),
|
||||
- self-parenting,
|
||||
- cycles (32-hop ancestor walk, mirroring `is_descendant_of`).
|
||||
|
|
@ -93,15 +94,15 @@ where system-level facts live.
|
|||
|
||||
Once enforcement lands the rules collapse into:
|
||||
|
||||
| operation | who can do it |
|
||||
|---|---|
|
||||
| `kill` / `start` / `restart` / `update` (any descendant) | any ancestor |
|
||||
| `request_init_config` (spawn a new child) | any agent, child added under self |
|
||||
| `request_apply_commit` (any descendant's config) | any ancestor |
|
||||
| `get_logs` (any descendant) | any ancestor |
|
||||
| moderate questions / reminders (cancel any open thread of a descendant) | any ancestor |
|
||||
| `send` / `recv` routing | parent ↔ same-parent siblings ↔ self ↔ descendants; explicit allow-list for anyone else |
|
||||
| `request_update_meta_inputs` (bump meta lock) | root agents only (today: just `manager`) |
|
||||
| operation | who can do it |
|
||||
| ----------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
|
||||
| `kill` / `start` / `restart` / `update` (any descendant) | any ancestor |
|
||||
| `request_init_config` (spawn a new child) | any agent, child added under self |
|
||||
| `request_apply_commit` (any descendant's config) | any ancestor |
|
||||
| `get_logs` (any descendant) | any ancestor |
|
||||
| moderate questions / reminders (cancel any open thread of a descendant) | any ancestor |
|
||||
| `send` / `recv` routing | parent ↔ same-parent siblings ↔ self ↔ descendants; explicit allow-list for anyone else |
|
||||
| `request_update_meta_inputs` (bump meta lock) | root agents only (today: just `manager`) |
|
||||
|
||||
"Ancestor" walks `ContainerView.parent` chains; cycles are guarded by a
|
||||
visited-set at dispatch time (a malformed topology.json can't lock the
|
||||
|
|
@ -129,14 +130,14 @@ The `ManagerRequest::*` variants in `hive-sh4re/src/lib.rs` are
|
|||
operations the manager flavour socket can make that sub-agent sockets
|
||||
can't:
|
||||
|
||||
| variant | semantic | post-milestone |
|
||||
|---|---|---|
|
||||
| `RequestInitConfig` | seed an agent's proposed config repo | **topology** — existing direct child (re-init) or a brand-new name (child added under self on approval); a name owned by a different parent is refused |
|
||||
| `RequestApplyCommit` | submit a commit sha for operator approval | **topology** — descendants only |
|
||||
| `Kill` / `Start` / `Restart` / `Update` | container lifecycle on an existing agent | **topology** — descendants only |
|
||||
| `RequestUpdateMetaInputs` | bump meta `flake.lock` | **per-agent cap** (root-only today; a future "let coder bump its own input" might grant it) |
|
||||
| `GetLogs` | journalctl scrape of a sub-agent | **topology** — descendants only |
|
||||
| `Wake` | inject a `from: <X>` message into self's inbox | **not really privileged** — the wire surface exists because daemon co-processes (e.g. `forge_notify`) need it. Sub-agents have the same via their own socket. |
|
||||
| variant | semantic | post-milestone |
|
||||
| --------------------------------------- | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `RequestInitConfig` | seed an agent's proposed config repo | **topology** — existing direct child (re-init) or a brand-new name (child added under self on approval); a name owned by a different parent is refused |
|
||||
| `RequestApplyCommit` | submit a commit sha for operator approval | **topology** — descendants only |
|
||||
| `Kill` / `Start` / `Restart` / `Update` | container lifecycle on an existing agent | **topology** — descendants only |
|
||||
| `RequestUpdateMetaInputs` | bump meta `flake.lock` | **per-agent cap** (root-only today; a future "let coder bump its own input" might grant it) |
|
||||
| `GetLogs` | journalctl scrape of a sub-agent | **topology** — descendants only |
|
||||
| `Wake` | inject a `from: <X>` message into self's inbox | **not really privileged** — the wire surface exists because daemon co-processes (e.g. `forge_notify`) need it. Sub-agents have the same via their own socket. |
|
||||
|
||||
### C — storage / mounts (`hive-c0re::lifecycle`)
|
||||
|
||||
|
|
@ -150,6 +151,7 @@ The manager container's nspawn bind set:
|
|||
manager can read the system-wide deploy log
|
||||
|
||||
Tree-shape version:
|
||||
|
||||
- Each agent gets RW to `/agents/<descendant>/` for every descendant in
|
||||
its subtree. The root agent (today: manager) gets RW to the full
|
||||
forest as a special case of "the root has every other agent as a
|
||||
|
|
@ -182,7 +184,6 @@ direct `$HYPERHIVE_STATE_DIR/<token>` read.
|
|||
|
||||
### F — drive-by checks across c0re
|
||||
|
||||
|
||||
- `loose_ends.rs`: manager sees hive-wide loose-ends, sub-agents only
|
||||
their own. **Topology** — every agent sees its own + its
|
||||
descendants'.
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
# The operator/agent boundary
|
||||
|
||||
Design rationale for hyperhive's two-principal trust model. The
|
||||
*implementation* work — container network isolation, the unifying
|
||||
_implementation_ work — container network isolation, the unifying
|
||||
gateway, core-daemon privsep — is tracked as `area:ops` issues on
|
||||
the forge.
|
||||
|
||||
Today "the operator surface" and "the agent surface" are a
|
||||
*convention*, not a boundary — nothing stops a container from
|
||||
_convention_, not a boundary — nothing stops a container from
|
||||
curling the core daemon on `localhost:<port>`, or another agent's
|
||||
web UI. Network isolation, the gateway, and privsep together turn
|
||||
that convention into an enforced boundary.
|
||||
|
|
@ -19,7 +19,7 @@ that convention into an enforced boundary.
|
|||
lifecycle POSTs) are served by the core daemon and only
|
||||
reachable via the gateway.
|
||||
- **Agent** — speaks only for itself, only over its per-agent
|
||||
unix socket. The socket's identity *is* the agent (see
|
||||
unix socket. The socket's identity _is_ the agent (see
|
||||
`docs/conventions.md`, "identity = socket"). An agent must not
|
||||
be able to reach the core daemon's HTTP surface, another
|
||||
agent's socket, or another agent's web UI.
|
||||
|
|
@ -30,7 +30,7 @@ that convention into an enforced boundary.
|
|||
point.** They live on the core backend.
|
||||
|
||||
Worked example — answering an operator-targeted question is a
|
||||
`POST /answer-question/{id}` on the core dashboard, *never* an
|
||||
`POST /answer-question/{id}` on the core dashboard, _never_ an
|
||||
`AgentRequest` variant. If it were a per-agent-socket request, an
|
||||
agent could `curl` its own socket and spoof an operator answer.
|
||||
The per-agent web UI POSTs cross-origin to the core for these
|
||||
|
|
@ -44,7 +44,7 @@ container can reach `localhost:<core-port>`, the dashboard, and
|
|||
every other agent's web port. Until that changes, the
|
||||
operator/agent split is on the honour system — every boundary
|
||||
claim above is aspirational. Network isolation is what makes the
|
||||
boundary *real*; the gateway and privsep are ergonomics and
|
||||
boundary _real_; the gateway and privsep are ergonomics and
|
||||
defence-in-depth layered on top.
|
||||
|
||||
The `area:ops` issues followed this sequencing:
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ The `hive-ci` module runs a Forgejo Actions runner in a `hive-ci` nixos-containe
|
|||
Set `services.hyperhive.forge.ci.enable = true` in the host NixOS config. That's it — no manual token provisioning.
|
||||
|
||||
**Requirements:**
|
||||
|
||||
- The internal forge is always present (mandatory), so the runner always has a hive-forge instance to register against — nothing extra to enable.
|
||||
- Optional: tune `services.hyperhive.forge.ci.name` (runner name in forge admin panel), `concurrency` (parallel job capacity), `labels` (workflow targeting), `jobTimeout` (per-job wall-clock cap, default `"1h"`, Go duration string e.g. `"3h"` — a job that exceeds it is killed so a hung or runaway build can't hold the runner's single slot indefinitely).
|
||||
|
||||
|
|
@ -148,7 +149,7 @@ following to your host config:
|
|||
```
|
||||
|
||||
**Remote builders:** if CI dispatches builds to a remote builder (e.g. via
|
||||
`nix.buildMachines` / `ssh-ng://`), the build outputs land in *that host's*
|
||||
`nix.buildMachines` / `ssh-ng://`), the build outputs land in _that host's_
|
||||
store, so the same GC config should be applied wherever the builder runs —
|
||||
GC on the coordinator host won't reclaim space on the builder.
|
||||
|
||||
|
|
|
|||
|
|
@ -32,17 +32,17 @@ somewhere."
|
|||
|
||||
### Queue kinds
|
||||
|
||||
| Kind | Description |
|
||||
|------|-------------|
|
||||
| `Rebuild` | Single-agent rebuild. Covers manual, approval-driven, auto-update, and meta-update cascade variants — all funnel through the same path. The start-after-rebuild is **deferred to a fast-lane `Start` follow-up** (`parent_id` = this entry) so the build lane is freed as soon as the profile-swap finishes instead of waiting out the container boot — see *Deferred start* under the rebuild path below. |
|
||||
| `MetaUpdate` | `nix flake update` on the meta flake. The worker runs the lock bump itself, then enqueues a cascade of `Rebuild` entries with `parent_id` set to the meta-update's id. |
|
||||
| `Spawn` | First-deploy of a new agent (approval-driven). Same serialisation as `Rebuild` from the operator's POV. |
|
||||
| `Destroy` | For future use (`destroy --purge` does real I/O). Variant exists so the wire shape doesn't change later; not currently routed through the queue. |
|
||||
| `Restart` | Stop + start a container without touching config (~5-10s). Routed through the queue so it serialises against in-flight rebuilds for the same agent — prevents a restart racing a rebuild mid-flight. Sources: dashboard ↺ button, the `restart` MCP tool. |
|
||||
| `PermChange` | Write a tool-group or capability change to the shared JSON file (`tool-groups.json` / `capabilities.json`), then rebuild the agent so the updated `HIVE_TOOL_GROUPS` / `HIVE_CAPABILITIES` env var takes effect. Serialising the file write through the queue prevents concurrent dashboard batch-apply actions from racing on the shared file. After a successful file write, emits `CapabilitiesChanged` or `ToolGroupsChanged` SSE snapshot so the P3RM1SS10NS tab updates live. |
|
||||
| Kind | Description |
|
||||
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `Rebuild` | Single-agent rebuild. Covers manual, approval-driven, auto-update, and meta-update cascade variants — all funnel through the same path. The start-after-rebuild is **deferred to a fast-lane `Start` follow-up** (`parent_id` = this entry) so the build lane is freed as soon as the profile-swap finishes instead of waiting out the container boot — see _Deferred start_ under the rebuild path below. |
|
||||
| `MetaUpdate` | `nix flake update` on the meta flake. The worker runs the lock bump itself, then enqueues a cascade of `Rebuild` entries with `parent_id` set to the meta-update's id. |
|
||||
| `Spawn` | First-deploy of a new agent (approval-driven). Same serialisation as `Rebuild` from the operator's POV. |
|
||||
| `Destroy` | For future use (`destroy --purge` does real I/O). Variant exists so the wire shape doesn't change later; not currently routed through the queue. |
|
||||
| `Restart` | Stop + start a container without touching config (~5-10s). Routed through the queue so it serialises against in-flight rebuilds for the same agent — prevents a restart racing a rebuild mid-flight. Sources: dashboard ↺ button, the `restart` MCP tool. |
|
||||
| `PermChange` | Write a tool-group or capability change to the shared JSON file (`tool-groups.json` / `capabilities.json`), then rebuild the agent so the updated `HIVE_TOOL_GROUPS` / `HIVE_CAPABILITIES` env var takes effect. Serialising the file write through the queue prevents concurrent dashboard batch-apply actions from racing on the shared file. After a successful file write, emits `CapabilitiesChanged` or `ToolGroupsChanged` SSE snapshot so the P3RM1SS10NS tab updates live. |
|
||||
| `GracefulStop` | Quiesce then stop a container (the `?graceful=true` path on `/api/kill/<agent>`). Signals the harness (its next `Recv` returns `GracefulStop` — the inbound fence — so it runs a stop-checkpoint turn that flushes durable `/state`, then takes the normal post-turn compaction path if it crossed the watermark, then exits) and **immediately releases the build lane**, spawning a detached watcher that holds the `Stopping` transient across the drain (bounded by a 3-min timeout → hard-stop fallback) and then enqueues a fast-lane `Stop` (`parent_id` = this entry) for the actual `nixos-container stop`. Net: a whole-hive graceful stop signals every agent up front, drains overlap, and only the container teardowns serialise (on the fast lane). Queued so the signal can't race an in-flight rebuild for the same agent. |
|
||||
|
||||
**Intentionally not queued** (sub-second ops): the *hard* `start`, `stop`, `kill` via the direct API paths. (A *graceful* stop is the `GracefulStop` kind above — it takes a checkpoint turn, so it rides the queue.) The queue's fast lane does carry `Start` / `Stop` kinds, but only as **follow-ups** other entries enqueue for themselves — the graceful-stop teardown and the deferred start-after-rebuild — so the container op groups under its parent entry on the dashboard.
|
||||
**Intentionally not queued** (sub-second ops): the _hard_ `start`, `stop`, `kill` via the direct API paths. (A _graceful_ stop is the `GracefulStop` kind above — it takes a checkpoint turn, so it rides the queue.) The queue's fast lane does carry `Start` / `Stop` kinds, but only as **follow-ups** other entries enqueue for themselves — the graceful-stop teardown and the deferred start-after-rebuild — so the container op groups under its parent entry on the dashboard.
|
||||
|
||||
### Dedup
|
||||
|
||||
|
|
@ -53,12 +53,12 @@ since the current run started).
|
|||
|
||||
### Sources
|
||||
|
||||
| Source | Meaning |
|
||||
|--------|---------|
|
||||
| `Manual` | Operator clicked rebuild / update-all / meta-update on the dashboard, or any other direct human action (CLI, an agent MCP tool). |
|
||||
| `AutoUpdate` | Legacy startup-sweep source (flat, no parent). Replaced by `StartupSweep` for new boots. |
|
||||
| `StartupSweep` | Child of a `StartupSweep` parent entry; boot-time per-agent rebuild with the sweep as the visual group header. |
|
||||
| `Approval` | Triggered by an operator-approved `ApprovalKind::{Spawn, ApplyCommit}`. |
|
||||
| Source | Meaning |
|
||||
| -------------- | -------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `Manual` | Operator clicked rebuild / update-all / meta-update on the dashboard, or any other direct human action (CLI, an agent MCP tool). |
|
||||
| `AutoUpdate` | Legacy startup-sweep source (flat, no parent). Replaced by `StartupSweep` for new boots. |
|
||||
| `StartupSweep` | Child of a `StartupSweep` parent entry; boot-time per-agent rebuild with the sweep as the visual group header. |
|
||||
| `Approval` | Triggered by an operator-approved `ApprovalKind::{Spawn, ApplyCommit}`. |
|
||||
|
||||
### Cascade parent tracking
|
||||
|
||||
|
|
@ -83,6 +83,7 @@ in the queue as a terminal entry (`Done` / `Failed` / `Cancelled`) or no longer
|
|||
the queue at all (evicted by `trim_history`, which only evicts terminals).
|
||||
|
||||
Use cases:
|
||||
|
||||
- Chain a `Rebuild` after an explicit prerequisite step without coupling them through
|
||||
the `parent_id` cascade mechanism.
|
||||
- Sequence a `PermChange` + `Rebuild` pair where the rebuild must not start until the
|
||||
|
|
@ -243,10 +244,10 @@ agent container.
|
|||
rebuild, so changes take effect on the next lifecycle op without
|
||||
requiring a host rebuild.
|
||||
|
||||
| Option | Default | Description |
|
||||
|--------|---------|-------------|
|
||||
| `services.hyperhive.c0re.agentCpuQuota` | `"200%"` | CPU cap per agent, as a percentage of one core (`"200%"` = 2 cores). Raise if agents hit CPU limits during builds or heavy tool use. |
|
||||
| `services.hyperhive.c0re.agentMemoryMax` | `"4G"` | Memory cap per agent. Raise for agents that run large nix builds or hold big in-memory data. |
|
||||
| Option | Default | Description |
|
||||
| ---------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `services.hyperhive.c0re.agentCpuQuota` | `"200%"` | CPU cap per agent, as a percentage of one core (`"200%"` = 2 cores). Raise if agents hit CPU limits during builds or heavy tool use. |
|
||||
| `services.hyperhive.c0re.agentMemoryMax` | `"4G"` | Memory cap per agent. Raise for agents that run large nix builds or hold big in-memory data. |
|
||||
|
||||
For a hive-wide cap across all containers together, set
|
||||
`systemd.slices.machine.serviceConfig.CPUQuota` in your NixOS
|
||||
|
|
|
|||
|
|
@ -14,16 +14,16 @@ Two scope sets live in `hive-c0re::forge`:
|
|||
|
||||
**`TOKEN_SCOPES`** (per-agent tokens):
|
||||
|
||||
| Scope | Why |
|
||||
|-------|-----|
|
||||
| `write:repository` | Create, clone, push, delete repos; merge PRs. |
|
||||
| `write:issue` | Open / comment / review issues **and** pull requests (Forgejo namespaces PR conversation under issues). |
|
||||
| `write:user` | Edit own profile, create repos under own user. |
|
||||
| `write:organization` | Create + manage orgs (lets agents share a forge namespace). |
|
||||
| `read:user` | Token-owner endpoint used for self-identification at harness startup. |
|
||||
| `write:misc` | Hooks, attachments, the rest of the long tail. |
|
||||
| `read:notification` | Poll `GET /notifications` for unread events. |
|
||||
| `write:notification` | Mark notifications read via `PATCH /notifications/threads/{id}`. |
|
||||
| Scope | Why |
|
||||
| -------------------- | ------------------------------------------------------------------------------------------------------- |
|
||||
| `write:repository` | Create, clone, push, delete repos; merge PRs. |
|
||||
| `write:issue` | Open / comment / review issues **and** pull requests (Forgejo namespaces PR conversation under issues). |
|
||||
| `write:user` | Edit own profile, create repos under own user. |
|
||||
| `write:organization` | Create + manage orgs (lets agents share a forge namespace). |
|
||||
| `read:user` | Token-owner endpoint used for self-identification at harness startup. |
|
||||
| `write:misc` | Hooks, attachments, the rest of the long tail. |
|
||||
| `read:notification` | Poll `GET /notifications` for unread events. |
|
||||
| `write:notification` | Mark notifications read via `PATCH /notifications/threads/{id}`. |
|
||||
|
||||
**`CORE_TOKEN_SCOPES`** (hive-c0re's own `core` user): everything in
|
||||
`TOKEN_SCOPES` plus `read:admin` and `write:admin`. Site-admin
|
||||
|
|
@ -102,7 +102,7 @@ resurfaces next tick.
|
|||
|
||||
**Size bound:** the per-poll prune retains only ids present in the
|
||||
single `limit=UNREAD_FETCH_LIMIT` (50) fetch page, so the cursor never
|
||||
exceeds that many entries — it tracks the unread *window*, not the
|
||||
exceeds that many entries — it tracks the unread _window_, not the
|
||||
all-time notification count. The fetch limit and the bound are the
|
||||
same constant in `forge_notify.rs` (with a debug assertion), so a
|
||||
future pagination change grows the ceiling visibly rather than
|
||||
|
|
@ -165,7 +165,7 @@ silently (mark-read without delivery):
|
|||
`user.login` matches `own_login`.
|
||||
- **Self-authored creations** (an agent opening its own PR/issue) — the
|
||||
already-fetched subject payload's poster `user.login` matches
|
||||
`own_login`. Only *creations* are dropped; a later state change on the
|
||||
`own_login`. Only _creations_ are dropped; a later state change on the
|
||||
agent's own subject is driven by someone else and still surfaces.
|
||||
|
||||
`own_login` is fetched once at startup via `GET /api/v1/user`. On
|
||||
|
|
@ -208,13 +208,13 @@ lookahead.
|
|||
|
||||
Five shapes, distinguished by the notification's classification:
|
||||
|
||||
| Trigger | Wrapper |
|
||||
| --- | --- |
|
||||
| Comment on issue / PR | `[comment on PR #N owner/repo] title\nurl: ...\n\nauthor: body\nassignee: ...` |
|
||||
| Review submission | `[PR approved #N owner/repo] title\nurl: ...\n\nreviewer: body\nassignee: ...` |
|
||||
| New issue / PR | `[new PR #N owner/repo] title\nurl: ...\n\n<body excerpt>\nassignee: ...` |
|
||||
| Trigger | Wrapper |
|
||||
| ----------------------------------- | --------------------------------------------------------------------------------- |
|
||||
| Comment on issue / PR | `[comment on PR #N owner/repo] title\nurl: ...\n\nauthor: body\nassignee: ...` |
|
||||
| Review submission | `[PR approved #N owner/repo] title\nurl: ...\n\nreviewer: body\nassignee: ...` |
|
||||
| New issue / PR | `[new PR #N owner/repo] title\nurl: ...\n\n<body excerpt>\nassignee: ...` |
|
||||
| Later activity (open, not creation) | `[activity on PR #N owner/repo] title\nurl: ...\n\n<body excerpt>\nassignee: ...` |
|
||||
| State change | `[PR merged #N owner/repo] title\nurl: ...\nassignee: ...` |
|
||||
| State change | `[PR merged #N owner/repo] title\nurl: ...\nassignee: ...` |
|
||||
|
||||
Review labels come from the Forgejo `state` field: `APPROVED` →
|
||||
`approved`, `REQUEST_CHANGES` → `changes requested`, `COMMENT` →
|
||||
|
|
@ -232,7 +232,7 @@ original open notification and miss the review (#1637). So the `open`
|
|||
state only earns the `new <kind>` label when the notification's event
|
||||
time (`updated_at`) is within `NEW_ITEM_TOLERANCE_SECS` (120s) of the
|
||||
subject's `created_at`. Anything later is labeled `activity on <kind>`
|
||||
— neutral and non-misleading, since we can't cheaply say *what* the
|
||||
— neutral and non-misleading, since we can't cheaply say _what_ the
|
||||
activity was without an extra reviews fetch. Missing/unparseable
|
||||
timestamps default to `new` (preserve prior behavior rather than mask a
|
||||
genuine new item). Timestamps are parsed by a small dependency-free
|
||||
|
|
|
|||
|
|
@ -201,7 +201,7 @@ Never use raw `curl` for forge access.
|
|||
|
||||
Agent containers bind-mount the host's nix-daemon socket. nspawn
|
||||
containers don't get user-namespaces by default, so `nix build`
|
||||
invocations *inside* the container can't set up the build sandbox
|
||||
invocations _inside_ the container can't set up the build sandbox
|
||||
and fail outright if the host daemon's
|
||||
`nix.settings.sandbox-fallback` is `false` (nixpkgs default).
|
||||
`nix/templates/harness-base.nix` does `lib.mkForce true` so builds
|
||||
|
|
@ -277,7 +277,7 @@ connects to the compositor at `127.0.0.1:<vnc_port>`.
|
|||
("Failed to connect to the bus" → binds `xdg_wm_base` then destroys it
|
||||
= invisible window even though CDP works). The fix is **not** to wrap
|
||||
each client in its own `dbus-run-session` (a private throwaway bus per
|
||||
process — that's a *separate* session, defeating the one-session
|
||||
process — that's a _separate_ session, defeating the one-session
|
||||
model); it's this one shared bus, whose address is exported as
|
||||
`DBUS_SESSION_BUS_ADDRESS=unix:path=/run/gui/bus` via
|
||||
`systemd.globalEnvironment` so weston, the harness and every GUI client
|
||||
|
|
|
|||
|
|
@ -15,13 +15,13 @@ of shared netns.
|
|||
|
||||
## v1 vs v2
|
||||
|
||||
| feature | v1 (this PR) | v2 (after netns isolation) |
|
||||
|---|---|---|
|
||||
| bridge interface | created on host, no slave NICs | per-agent veth pairs attach |
|
||||
| dnsmasq binding | bridge IP (reachable via host loopback in shared netns) | bridge IP (reachable via veth in private netns) |
|
||||
| agent container netns | shared host | private |
|
||||
| agent `/etc/resolv.conf` | unchanged (host DNS) | `nameserver <bridge-ip>` |
|
||||
| `address` rules target | `<bridge-ip>` (works in both modes) | unchanged from v1 |
|
||||
| feature | v1 (this PR) | v2 (after netns isolation) |
|
||||
| ------------------------ | ------------------------------------------------------- | ----------------------------------------------- |
|
||||
| bridge interface | created on host, no slave NICs | per-agent veth pairs attach |
|
||||
| dnsmasq binding | bridge IP (reachable via host loopback in shared netns) | bridge IP (reachable via veth in private netns) |
|
||||
| agent container netns | shared host | private |
|
||||
| agent `/etc/resolv.conf` | unchanged (host DNS) | `nameserver <bridge-ip>` |
|
||||
| `address` rules target | `<bridge-ip>` (works in both modes) | unchanged from v1 |
|
||||
|
||||
The `address` rules ship pointing at the bridge IP from v1 so the
|
||||
DNS contract is fixed before any container actually depends on it
|
||||
|
|
@ -91,9 +91,10 @@ agent containers.
|
|||
## Firewall posture
|
||||
|
||||
`networking.firewall.interfaces.<bridge>.allowedUDPPorts = [ 53 ]`
|
||||
+ `allowedTCPPorts = [ 53 ]` opens the resolver on the bridge
|
||||
interface only. Other interfaces stay closed. The hive resolver
|
||||
isn't an external-facing service.
|
||||
|
||||
- `allowedTCPPorts = [ 53 ]` opens the resolver on the bridge
|
||||
interface only. Other interfaces stay closed. The hive resolver
|
||||
isn't an external-facing service.
|
||||
|
||||
When `isolateContainers = true`, `allowedTCPPorts` is extended with
|
||||
`[ 80 443 ]` so isolated agents can reach nginx (gateway container,
|
||||
|
|
@ -131,14 +132,14 @@ agent containers from shared host netns to private netns. Set only after
|
|||
|
||||
### What the nix side does when `isolateContainers = true`
|
||||
|
||||
| effect | mechanism |
|
||||
|---|---|
|
||||
| IP forwarding | `boot.kernel.sysctl."net.ipv4.ip_forward" = 1` |
|
||||
| Internet NAT | `networking.nat { enable = true; internalInterfaces = [ bridgeName ]; }` — MASQUERADE on packets leaving via any external NIC |
|
||||
| Loopback DROP | `networking.firewall.extraInputRules` — drops bridge-subnet → `127.0.0.0/8` traffic; defence-in-depth against routing table leaks |
|
||||
| Gateway access | `networking.firewall.interfaces.<bridge>.allowedTCPPorts = [ 80 443 ]` — lets isolated agents reach nginx on the host (shared netns) |
|
||||
| Forge URL | `HIVE_FORGE_URL` flips from `http://127.0.0.1:3000` to `http://forge.<domain>` — agents resolve via dnsmasq, nginx proxies to forgejo |
|
||||
| c0re signal | `HIVE_NETWORK_ISOLATION=1`, `HIVE_NETWORK_BRIDGE`, `HIVE_NETWORK_SUBNET` in `systemd.services.hive-c0re.environment` |
|
||||
| effect | mechanism |
|
||||
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| IP forwarding | `boot.kernel.sysctl."net.ipv4.ip_forward" = 1` |
|
||||
| Internet NAT | `networking.nat { enable = true; internalInterfaces = [ bridgeName ]; }` — MASQUERADE on packets leaving via any external NIC |
|
||||
| Loopback DROP | `networking.firewall.extraInputRules` — drops bridge-subnet → `127.0.0.0/8` traffic; defence-in-depth against routing table leaks |
|
||||
| Gateway access | `networking.firewall.interfaces.<bridge>.allowedTCPPorts = [ 80 443 ]` — lets isolated agents reach nginx on the host (shared netns) |
|
||||
| Forge URL | `HIVE_FORGE_URL` flips from `http://127.0.0.1:3000` to `http://forge.<domain>` — agents resolve via dnsmasq, nginx proxies to forgejo |
|
||||
| c0re signal | `HIVE_NETWORK_ISOLATION=1`, `HIVE_NETWORK_BRIDGE`, `HIVE_NETWORK_SUBNET` in `systemd.services.hive-c0re.environment` |
|
||||
|
||||
`HIVE_NETWORK_SUBNET` is the host-side bridge IP + prefix (e.g.
|
||||
`10.42.0.1/24`), **not** the canonical network address. The Rust side
|
||||
|
|
|
|||
|
|
@ -15,10 +15,10 @@ An agent is **trusted code running inside its own nspawn container**. The
|
|||
boundary that matters is the container: a sub-agent cannot see the host
|
||||
netns, another agent's container, or another agent's state dir. Within its
|
||||
own container the agent is privileged — it has **passwordless `sudo` by
|
||||
default**. Isolating credentials *from the agent itself* is therefore **not a
|
||||
default**. Isolating credentials _from the agent itself_ is therefore **not a
|
||||
goal**: an agent can read its own tokens, its own `/home/<name>/.claude`, and
|
||||
run arbitrary commands as root inside its container. (The narrow exception is
|
||||
*cross-tenant* leakage — e.g. the unsandboxed-nix-build `0600` token policy
|
||||
_cross-tenant_ leakage — e.g. the unsandboxed-nix-build `0600` token policy
|
||||
below stops a build's nixbld user reading the agent's own forge token, and the
|
||||
state-file endpoint stops one agent proxying another's files. Those harden the
|
||||
boundary; they do not sandbox the agent from itself.)
|
||||
|
|
@ -36,7 +36,7 @@ Each agent gets its own scoped credentials, never shared:
|
|||
- **matrix token** → that agent's matrix account only.
|
||||
|
||||
So a compromised/confused agent's reach on the forge or matrix is bounded by
|
||||
*its own* account's scope, not the swarm's. This is the main thing standing
|
||||
_its own_ account's scope, not the swarm's. This is the main thing standing
|
||||
between "one agent does something dumb" and "the whole hive is affected".
|
||||
|
||||
### Threat model: prompt injection → confused deputy
|
||||
|
|
@ -44,7 +44,7 @@ between "one agent does something dumb" and "the whole hive is affected".
|
|||
The realistic adversary **never needs to breach the container**. They supply
|
||||
**untrusted input the agent reads and acts on**: a poisoned issue or PR
|
||||
comment, a cloned repo's README/CI, a scraped webpage, a crafted matrix
|
||||
message. The agent is the trusted, capable party; the *input* is the
|
||||
message. The agent is the trusted, capable party; the _input_ is the
|
||||
untrusted part. A successful injection turns the agent into a **confused
|
||||
deputy** — it uses its legitimate capabilities (push, comment, deploy, run
|
||||
shell) on the attacker's behalf.
|
||||
|
|
@ -52,7 +52,7 @@ shell) on the attacker's behalf.
|
|||
Mitigations are therefore about **bounding capability and inserting human
|
||||
checkpoints**, not about sandboxing the agent from its own tools:
|
||||
|
||||
- **Operator merges, not the agent** — an agent may *push* branches, but a
|
||||
- **Operator merges, not the agent** — an agent may _push_ branches, but a
|
||||
**human (the operator) merges the PR**, keeping a person in the loop on the
|
||||
highest-value action. On the **internal forge this is technically enforced,
|
||||
not just convention**: agents can't create repos (`max_repo_creation = 0`),
|
||||
|
|
@ -76,7 +76,7 @@ accepting**. The rule of thumb:
|
|||
|
||||
If an agent can deploy to prod, you are accepting the risk of a dropped
|
||||
production database (via injection or plain error). If that's unacceptable,
|
||||
the answer is *don't grant the capability* — not "grant it and hope the
|
||||
the answer is _don't grant the capability_ — not "grant it and hope the
|
||||
sandbox holds", because there is no sandbox between an agent and the tools you
|
||||
handed it.
|
||||
|
||||
|
|
@ -101,7 +101,7 @@ before touching the filesystem:
|
|||
(canonical host form or the in-container view `/agents/<n>/state/`)
|
||||
- `/var/lib/hyperhive/shared/` — shared docs (`/shared/` in-container)
|
||||
|
||||
`/state/...` without an agent prefix is explicitly *not* accepted — it is
|
||||
`/state/...` without an agent prefix is explicitly _not_ accepted — it is
|
||||
ambiguous from the host's perspective.
|
||||
|
||||
Defense-in-depth layers (in order):
|
||||
|
|
@ -181,22 +181,22 @@ for every operation that genuinely requires root.
|
|||
**Narrow interface** — `PrivRequest` variants map 1:1 to specific
|
||||
known operations; there is no arbitrary command pass-through:
|
||||
|
||||
| Operation | What it runs |
|
||||
|-----------|-------------|
|
||||
| `StartContainer` / `StopContainer` / `KillContainer` | `nixos-container start/stop/kill <name>` |
|
||||
| `CreateContainer` / `UpdateContainer` | `nixos-container create/update <name> --flake <ref>` |
|
||||
| `DestroyContainer` | `nixos-container destroy <name>` |
|
||||
| `ListContainers` | `nixos-container list` |
|
||||
| `ReadContainerJournal` | `journalctl -M <container> -n <n> [filters...]` |
|
||||
| `ReloadGatewayNginx` | `systemctl -M hive-gateway reload/start/reset-failed nginx` |
|
||||
| `WriteNspawnFlags` | write `/etc/nixos-containers/<container>.conf` (bind-mount list + network isolation vars) |
|
||||
| `WriteResourceLimits` | write `CPUQuota=`/`MemoryMax=` systemd drop-in for agent container |
|
||||
| `RemoveServiceDropin` | remove `container@<name>.service.d/` drop-in on destroy |
|
||||
| `DaemonReload` | `systemctl daemon-reload` |
|
||||
| `ChownSocketDir` / `ChmodSocketDir` | chown/chmod `/run/hive-agent/<name>/` socket directory |
|
||||
| `RunForgeAdmin` | `nixos-container run hive-forge -- runuser -u forgejo -- forgejo admin <args>` |
|
||||
| `WriteAgentForgeToken` / `WriteAgentMatrixToken` | write `0600` credential file into agent state dir |
|
||||
| `RestartMatrixDaemon` | `systemctl --machine=h-<name> restart hive-matrix-daemon.service` |
|
||||
| Operation | What it runs |
|
||||
| ---------------------------------------------------- | ----------------------------------------------------------------------------------------- |
|
||||
| `StartContainer` / `StopContainer` / `KillContainer` | `nixos-container start/stop/kill <name>` |
|
||||
| `CreateContainer` / `UpdateContainer` | `nixos-container create/update <name> --flake <ref>` |
|
||||
| `DestroyContainer` | `nixos-container destroy <name>` |
|
||||
| `ListContainers` | `nixos-container list` |
|
||||
| `ReadContainerJournal` | `journalctl -M <container> -n <n> [filters...]` |
|
||||
| `ReloadGatewayNginx` | `systemctl -M hive-gateway reload/start/reset-failed nginx` |
|
||||
| `WriteNspawnFlags` | write `/etc/nixos-containers/<container>.conf` (bind-mount list + network isolation vars) |
|
||||
| `WriteResourceLimits` | write `CPUQuota=`/`MemoryMax=` systemd drop-in for agent container |
|
||||
| `RemoveServiceDropin` | remove `container@<name>.service.d/` drop-in on destroy |
|
||||
| `DaemonReload` | `systemctl daemon-reload` |
|
||||
| `ChownSocketDir` / `ChmodSocketDir` | chown/chmod `/run/hive-agent/<name>/` socket directory |
|
||||
| `RunForgeAdmin` | `nixos-container run hive-forge -- runuser -u forgejo -- forgejo admin <args>` |
|
||||
| `WriteAgentForgeToken` / `WriteAgentMatrixToken` | write `0600` credential file into agent state dir |
|
||||
| `RestartMatrixDaemon` | `systemctl --machine=h-<name> restart hive-matrix-daemon.service` |
|
||||
|
||||
**Container allowlist** — every request is validated against an
|
||||
allowlist before any operation: only names matching the agent-name
|
||||
|
|
@ -211,8 +211,8 @@ writes to only the paths `hive-priv` legitimately needs.
|
|||
|
||||
### Privilege boundary summary
|
||||
|
||||
| Component | Runs as | Privilege needed for |
|
||||
|-----------|---------|----------------------|
|
||||
| `hive-c0re` | `hive-core` | broker, HTTP dashboard, scheduling, approvals |
|
||||
| `hive-priv` | `root` | container lifecycle, journal reads, bind mounts, cred writes |
|
||||
| `hive-ag3nt` (per-container) | per-agent user | turn execution, MCP serving |
|
||||
| Component | Runs as | Privilege needed for |
|
||||
| ---------------------------- | -------------- | ------------------------------------------------------------ |
|
||||
| `hive-c0re` | `hive-core` | broker, HTTP dashboard, scheduling, approvals |
|
||||
| `hive-priv` | `root` | container lifecycle, journal reads, bind mounts, cred writes |
|
||||
| `hive-ag3nt` (per-container) | per-agent user | turn execution, MCP serving |
|
||||
|
|
|
|||
|
|
@ -48,12 +48,12 @@ services.hyperhive.swarm.peers = {
|
|||
The attrset key is the peer's DNS domain. Two independent, optional
|
||||
trust knobs — pick by what you need to trust:
|
||||
|
||||
- **`certFingerprint`** (`"sha256:…"`) — pin the peer's TLS *leaf*
|
||||
- **`certFingerprint`** (`"sha256:…"`) — pin the peer's TLS _leaf_
|
||||
fingerprint. Scopes **only** to hive-c0re's own peer HTTPS checks
|
||||
(the P33RS dashboard links + agent peer discovery below). It is
|
||||
**not** consulted by matrix federation — tuwunel validates a peer's
|
||||
federation certificate against the system CA bundle independently
|
||||
(see *Matrix federation* below), so a fingerprint pin does nothing
|
||||
(see _Matrix federation_ below), so a fingerprint pin does nothing
|
||||
for a self-signed matrix cert.
|
||||
- **`caCert`** (path to the peer's root CA PEM) — embeds that CA (at
|
||||
build time, into the nix store — no runtime file on the host) and
|
||||
|
|
@ -61,7 +61,7 @@ trust knobs — pick by what you need to trust:
|
|||
alongside `hive-ca.pem` in every agent's
|
||||
`security.pki.certificateFiles` (via the meta-flake renderer) **and**
|
||||
in the matrix container's trust bundle, so tuwunel validates the
|
||||
peer's *federation* TLS when it chains to that CA. Trust stays
|
||||
peer's _federation_ TLS when it chains to that CA. Trust stays
|
||||
**inside the hive** (agents + the matrix container), never the host
|
||||
system trust store. **This is the knob that unblocks federation with
|
||||
a self-signed peer hive** — use it instead of `certFingerprint` when
|
||||
|
|
|
|||
|
|
@ -15,17 +15,18 @@ Submit a shell command for background execution (runs via `bash`).
|
|||
Stdout and stderr stream to `harness/bash-tasks/<id>.{out,err}`.
|
||||
When the task completes (or times out, or the process errors), the
|
||||
harness fires a wake with `from: "bash-task-<id>"` and the exit code
|
||||
+ last stdout lines in the body; handle it on a future turn.
|
||||
|
||||
- `timeout_secs` — kill the task after N seconds and mark it
|
||||
- last stdout lines in the body; handle it on a future turn.
|
||||
|
||||
* `timeout_secs` — kill the task after N seconds and mark it
|
||||
`timed_out`. Omit for no timeout (runs until natural exit).
|
||||
- `wait_seconds` — inline poll before returning (capped at 30).
|
||||
* `wait_seconds` — inline poll before returning (capped at 30).
|
||||
When the task finishes within the window the full status is
|
||||
returned immediately and no wake is fired; when the window expires
|
||||
the task keeps running and the normal `task started: id=<id>`
|
||||
response is returned. **Defaults to 3** — pass `wait_seconds: 0`
|
||||
to disable inline waiting and always get the immediate response.
|
||||
- `name` — optional caller-chosen task id. When set it replaces the
|
||||
* `name` — optional caller-chosen task id. When set it replaces the
|
||||
auto-generated hex id, so it surfaces in the wake `from`
|
||||
(`bash-task-<name>`), in `status(<name>)` lookups, and in the
|
||||
loose-ends list — a memorable label instead of an opaque id. A name
|
||||
|
|
|
|||
|
|
@ -102,16 +102,16 @@ hive-forge repo-labels --json # full label ob
|
|||
Several verbs read an issue/PR and look overlapping, but each has a
|
||||
distinct output shape — pick by what you need:
|
||||
|
||||
| Need | Verb | Shape |
|
||||
|------|------|-------|
|
||||
| The full human view of one item — title, body, **and** every comment in order | `view <n>` | prose dump; **marks the thread read** (clears the read-before-comment guard). Top-level `view` auto-detects issue-or-PR by number; scoped `issue view <n>` / `pr view <n>` do the same when you already know the kind |
|
||||
| Just the structured metadata of one item (state, labels, assignees, head sha…) | `issue <n>` / `pr <n>` | JSON object |
|
||||
| Only the comments (skip the body), e.g. to catch up a long thread; for PRs, review bodies (approve/request-changes/comment summaries) are merged in too | `comments <n>` [`--tail N`] | comment + review-body list; **marks read** |
|
||||
| One specific comment by its id | `comment-show <id>` | single comment |
|
||||
| The audit trail — closes, label/assignee changes, commit refs, merges (no comment bodies) | `timeline <n>` | event list |
|
||||
| The code change | `diff <n>` | unified diff |
|
||||
| Is this PR ready to merge — mergeable + CI + reviews | `pr-status --pr <n>` | health view; exit code = merge-readiness |
|
||||
| The review verdicts + inline review comments | `pr-reviews <n>` | review list |
|
||||
| Need | Verb | Shape |
|
||||
| ------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| The full human view of one item — title, body, **and** every comment in order | `view <n>` | prose dump; **marks the thread read** (clears the read-before-comment guard). Top-level `view` auto-detects issue-or-PR by number; scoped `issue view <n>` / `pr view <n>` do the same when you already know the kind |
|
||||
| Just the structured metadata of one item (state, labels, assignees, head sha…) | `issue <n>` / `pr <n>` | JSON object |
|
||||
| Only the comments (skip the body), e.g. to catch up a long thread; for PRs, review bodies (approve/request-changes/comment summaries) are merged in too | `comments <n>` [`--tail N`] | comment + review-body list; **marks read** |
|
||||
| One specific comment by its id | `comment-show <id>` | single comment |
|
||||
| The audit trail — closes, label/assignee changes, commit refs, merges (no comment bodies) | `timeline <n>` | event list |
|
||||
| The code change | `diff <n>` | unified diff |
|
||||
| Is this PR ready to merge — mergeable + CI + reviews | `pr-status --pr <n>` | health view; exit code = merge-readiness |
|
||||
| The review verdicts + inline review comments | `pr-reviews <n>` | review list |
|
||||
|
||||
Rule of thumb: **`view` first** when you're about to act on a thread (it
|
||||
shows everything and clears the unread guard so your follow-up `comment`
|
||||
|
|
@ -172,7 +172,7 @@ hive-forge pr-status --pr 42
|
|||
# last comment: 2026-06-05T19:13:28+02:00 by argus
|
||||
```
|
||||
|
||||
Note: review verdicts come from *formal* Forgejo reviews (the
|
||||
Note: review verdicts come from _formal_ Forgejo reviews (the
|
||||
approve / request-changes API). Reviewers who post their verdict as a
|
||||
plain comment show under `last comment`, not `reviews`.
|
||||
|
||||
|
|
@ -243,14 +243,14 @@ to discover valid label names before triaging or to audit the label set.
|
|||
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
|
||||
REST endpoint to re-run an *existing* run (the run-page rerun buttons
|
||||
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
|
||||
(`POST …/actions/workflows/<workflow>/dispatches {"ref":"<branch>"}`).
|
||||
Resolve the branch with exactly one of: `--pr <n>` (the PR's head
|
||||
branch), `--run <n>` (the same run number `ci-log` / `artifact-get`
|
||||
take — resolves the branch + workflow from that run), or `--branch
|
||||
<name>` (directly). `--workflow <file>` picks the workflow file for
|
||||
<name>` (directly). `--workflow <file>` picks the workflow file for
|
||||
`--pr` / `--branch` (default `ci.yml`). Dispatch re-runs the whole
|
||||
workflow — there is no single-job variant.
|
||||
- Do NOT use raw `curl` for forge access -- the CLI handles auth,
|
||||
|
|
|
|||
|
|
@ -76,13 +76,13 @@ agents after the approval resolves.
|
|||
|
||||
## Boundary summary
|
||||
|
||||
| Operation | Requires approval? | Scope |
|
||||
|---|---|---|
|
||||
| `kill` / `start` / `restart` / `update` | No | Direct children |
|
||||
| `list_containers` | No | All descendants |
|
||||
| `request_init_config` | Yes (InitConfig) | New direct child only |
|
||||
| `request_apply_commit` | Yes (ApplyCommit) | Direct children |
|
||||
| `request_update_meta_inputs` | Yes (MetaUpdate) | Meta flake (global) |
|
||||
| Operation | Requires approval? | Scope |
|
||||
| --------------------------------------- | ------------------ | --------------------- |
|
||||
| `kill` / `start` / `restart` / `update` | No | Direct children |
|
||||
| `list_containers` | No | All descendants |
|
||||
| `request_init_config` | Yes (InitConfig) | New direct child only |
|
||||
| `request_apply_commit` | Yes (ApplyCommit) | Direct children |
|
||||
| `request_update_meta_inputs` | Yes (MetaUpdate) | Meta flake (global) |
|
||||
|
||||
## See also
|
||||
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@ swap touches only the first:
|
|||
|
||||
1. **`colors.css`** — the 16 `--base00`…`--base0F` base16 slots. **This is
|
||||
the entire theme swap contract.** A generator (e.g. one fed a stylix
|
||||
base16 scheme, which is natively base00–base0F) replaces *only this
|
||||
file*.
|
||||
base16 scheme, which is natively base00–base0F) replaces _only this
|
||||
file_.
|
||||
2. **`theme.css`** — the semantic layer: `--bg`, `--fg`, `--purple`, …
|
||||
*derived* from the base16 slots (`--bg: var(--base00)` etc.). This is
|
||||
_derived_ from the base16 slots (`--bg: var(--base00)` etc.). This is
|
||||
what the app references and it **never changes on a swap**.
|
||||
|
||||
Both files live in `frontend/packages/shared/src/`. Per-page stylesheets
|
||||
|
|
@ -34,68 +34,73 @@ Each semantic var derives from a base16 slot (or, for the three that have
|
|||
no clean slot, a `color-mix()` over base16 — pixel-identical under the
|
||||
default palette). Default hexes shown are Catppuccin Mocha.
|
||||
|
||||
| Variable | Derives from | Default hex | Use |
|
||||
|---|---|---|---|
|
||||
| `--bg` | `base00` | `#1e1e2e` | page background |
|
||||
| `--bg-elev` | `base01` | `#181825` | elevated surfaces: floating dropdowns, popovers |
|
||||
| `--border` | `base02` | `#313244` | general borders, hover/active backgrounds |
|
||||
| `--purple-dim` | `base03` | `#45475a` | subtle borders, terminal chrome, badge backgrounds |
|
||||
| `--fg` | `base05` | `#cdd6f4` | primary text colour |
|
||||
| `--red` | `base08` | `#f38ba8` | errors, fail state |
|
||||
| `--amber` | `base09` | `#fab387` | warnings, pending / running state |
|
||||
| `--yellow` | `base0A` | `#f9e2af` | flash messages, mild warnings |
|
||||
| `--green` | `base0B` | `#a6e3a1` | success, ok state |
|
||||
| `--cyan` | `base0C` | `#89dceb` | tool-use events, info accents |
|
||||
| `--blue` | `base0D` | `#89b4fa` | links, interactive accent (distinct from cyan) |
|
||||
| `--purple` | `base0E` | `#cba6f7` | accent — active tabs, links, highlights |
|
||||
| `--pink` | `base0F` | `#f5c2e7` | thinking events |
|
||||
| `--crust` | `mix(base00 58%, #000)` | `#11111b` | terminal / code block background (below `--bg`) |
|
||||
| `--muted` | `mix(base05 55.5%, base00)` | `#7f849c` | secondary / dimmed text |
|
||||
| `--subtext0` | `mix(base05 77.7%, base00)` | `#a6adc8` | toolbar/status text; dimmer than `--fg`, lighter than `--muted` |
|
||||
| Variable | Derives from | Default hex | Use |
|
||||
| -------------- | --------------------------- | ----------- | --------------------------------------------------------------- |
|
||||
| `--bg` | `base00` | `#1e1e2e` | page background |
|
||||
| `--bg-elev` | `base01` | `#181825` | elevated surfaces: floating dropdowns, popovers |
|
||||
| `--border` | `base02` | `#313244` | general borders, hover/active backgrounds |
|
||||
| `--purple-dim` | `base03` | `#45475a` | subtle borders, terminal chrome, badge backgrounds |
|
||||
| `--fg` | `base05` | `#cdd6f4` | primary text colour |
|
||||
| `--red` | `base08` | `#f38ba8` | errors, fail state |
|
||||
| `--amber` | `base09` | `#fab387` | warnings, pending / running state |
|
||||
| `--yellow` | `base0A` | `#f9e2af` | flash messages, mild warnings |
|
||||
| `--green` | `base0B` | `#a6e3a1` | success, ok state |
|
||||
| `--cyan` | `base0C` | `#89dceb` | tool-use events, info accents |
|
||||
| `--blue` | `base0D` | `#89b4fa` | links, interactive accent (distinct from cyan) |
|
||||
| `--purple` | `base0E` | `#cba6f7` | accent — active tabs, links, highlights |
|
||||
| `--pink` | `base0F` | `#f5c2e7` | thinking events |
|
||||
| `--crust` | `mix(base00 58%, #000)` | `#11111b` | terminal / code block background (below `--bg`) |
|
||||
| `--muted` | `mix(base05 55.5%, base00)` | `#7f849c` | secondary / dimmed text |
|
||||
| `--subtext0` | `mix(base05 77.7%, base00)` | `#a6adc8` | toolbar/status text; dimmer than `--fg`, lighter than `--muted` |
|
||||
|
||||
## Common mistakes
|
||||
|
||||
The Catppuccin colour names do **not** map 1:1 to the variable names.
|
||||
Variables to avoid (undefined — they will silently resolve to transparent / inherited):
|
||||
|
||||
| Wrong | Correct |
|
||||
|---|---|
|
||||
| `--text` | `--fg` |
|
||||
| `--mauve` | `--purple` |
|
||||
| `--surface0` | `--bg-elev` (float bg) or `--border` (border/hover) |
|
||||
| `--surface1` | `--border` |
|
||||
| `--surface2` | `--purple-dim` |
|
||||
| `--overlay0`, `--overlay1` | `--muted` |
|
||||
| `--base`, `--mantle` | `--bg`, `--bg-elev` |
|
||||
| Wrong | Correct |
|
||||
| -------------------------- | --------------------------------------------------- |
|
||||
| `--text` | `--fg` |
|
||||
| `--mauve` | `--purple` |
|
||||
| `--surface0` | `--bg-elev` (float bg) or `--border` (border/hover) |
|
||||
| `--surface1` | `--border` |
|
||||
| `--surface2` | `--purple-dim` |
|
||||
| `--overlay0`, `--overlay1` | `--muted` |
|
||||
| `--base`, `--mantle` | `--bg`, `--bg-elev` |
|
||||
|
||||
## Usage guide
|
||||
|
||||
**Floating menus and dropdowns** (e.g. agent context menu, tabbar overflow):
|
||||
|
||||
```css
|
||||
background: var(--bg-elev);
|
||||
border: 1px solid var(--purple-dim);
|
||||
```
|
||||
|
||||
**Hover / active state backgrounds**:
|
||||
|
||||
```css
|
||||
background: var(--border);
|
||||
```
|
||||
|
||||
**Active tab text / accent elements**:
|
||||
|
||||
```css
|
||||
color: var(--purple);
|
||||
```
|
||||
|
||||
**Muted / meta text**:
|
||||
|
||||
```css
|
||||
color: var(--muted);
|
||||
```
|
||||
|
||||
**Error / warning / success badges**:
|
||||
|
||||
```css
|
||||
color: var(--red); /* error */
|
||||
color: var(--amber); /* warning / running */
|
||||
color: var(--green); /* ok */
|
||||
color: var(--red); /* error */
|
||||
color: var(--amber); /* warning / running */
|
||||
color: var(--green); /* ok */
|
||||
```
|
||||
|
||||
## Theme swapping — the base16 contract
|
||||
|
|
@ -104,31 +109,31 @@ color: var(--green); /* ok */
|
|||
semantic names.** A theme generator (e.g. one reading a stylix base16
|
||||
scheme) overrides only `colors.css`; the semantic layer in `theme.css`
|
||||
derives everything else, so the whole UI re-themes with nothing else to
|
||||
template or regenerate. The base16 slot → semantic mapping is *internal*
|
||||
template or regenerate. The base16 slot → semantic mapping is _internal_
|
||||
to `theme.css` (the "Derives from" column above) — a generator never
|
||||
needs to know our var names, and `theme.css` + the page bundles stay
|
||||
untouched.
|
||||
|
||||
`colors.css` base16 slot defaults (Catppuccin Mocha):
|
||||
|
||||
| Slot | Default | Standard base16 role | Mapped to |
|
||||
|---|---|---|---|
|
||||
| `base00` | `#1e1e2e` | default bg | `--bg`, (darkened) `--crust` |
|
||||
| `base01` | `#181825` | lighter bg | `--bg-elev` |
|
||||
| `base02` | `#313244` | selection/surface | `--border` |
|
||||
| `base03` | `#45475a` | comments/dim surface | `--purple-dim` |
|
||||
| `base04` | `#585b70` | dark foreground | *(unused; kept for completeness)* |
|
||||
| `base05` | `#cdd6f4` | default foreground | `--fg`, (blended) `--muted`/`--subtext0` |
|
||||
| `base06` | `#f5e0dc` | light foreground | *(unused)* |
|
||||
| `base07` | `#b4befe` | lightest | *(unused)* |
|
||||
| `base08` | `#f38ba8` | red | `--red` |
|
||||
| `base09` | `#fab387` | orange | `--amber` |
|
||||
| `base0A` | `#f9e2af` | yellow | `--yellow` |
|
||||
| `base0B` | `#a6e3a1` | green | `--green` |
|
||||
| `base0C` | `#89dceb` | cyan | `--cyan` (our *sky*; Catppuccin's `base0C` is teal) |
|
||||
| `base0D` | `#89b4fa` | blue | `--blue` |
|
||||
| `base0E` | `#cba6f7` | magenta | `--purple` |
|
||||
| `base0F` | `#f5c2e7` | extra accent | `--pink` |
|
||||
| Slot | Default | Standard base16 role | Mapped to |
|
||||
| -------- | --------- | -------------------- | --------------------------------------------------- |
|
||||
| `base00` | `#1e1e2e` | default bg | `--bg`, (darkened) `--crust` |
|
||||
| `base01` | `#181825` | lighter bg | `--bg-elev` |
|
||||
| `base02` | `#313244` | selection/surface | `--border` |
|
||||
| `base03` | `#45475a` | comments/dim surface | `--purple-dim` |
|
||||
| `base04` | `#585b70` | dark foreground | _(unused; kept for completeness)_ |
|
||||
| `base05` | `#cdd6f4` | default foreground | `--fg`, (blended) `--muted`/`--subtext0` |
|
||||
| `base06` | `#f5e0dc` | light foreground | _(unused)_ |
|
||||
| `base07` | `#b4befe` | lightest | _(unused)_ |
|
||||
| `base08` | `#f38ba8` | red | `--red` |
|
||||
| `base09` | `#fab387` | orange | `--amber` |
|
||||
| `base0A` | `#f9e2af` | yellow | `--yellow` |
|
||||
| `base0B` | `#a6e3a1` | green | `--green` |
|
||||
| `base0C` | `#89dceb` | cyan | `--cyan` (our _sky_; Catppuccin's `base0C` is teal) |
|
||||
| `base0D` | `#89b4fa` | blue | `--blue` |
|
||||
| `base0E` | `#cba6f7` | magenta | `--purple` |
|
||||
| `base0F` | `#f5c2e7` | extra accent | `--pink` |
|
||||
|
||||
Notes for theme authors:
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
> Part of [Web UI](../web-ui.md). See also:
|
||||
> [Dashboard layout](dashboard.md) · [Per-agent page](agent.md)
|
||||
|
||||
|
||||
- `GET /` → `index.html` from the bundled frontend dist (see
|
||||
`frontend/`). Both binaries' routers declare their dynamic
|
||||
endpoints first and then `fallback_service(ServeDir::new(...))`
|
||||
|
|
@ -169,8 +168,8 @@ subscribes to `streamUrl` (live tail). Both endpoints return
|
|||
`.no-anim` class so they don't stagger in like live events. The
|
||||
optional `streamFactory(url)` callback lets the dashboard hand
|
||||
the factory a `SharedWorker`-backed `EventSource` facade (so
|
||||
multiple tabs share one upstream connection — see *SSE
|
||||
multiplexing* above); when omitted, the factory falls back to a
|
||||
multiple tabs share one upstream connection — see _SSE
|
||||
multiplexing_ above); when omitted, the factory falls back to a
|
||||
plain `new EventSource(url)`.
|
||||
|
||||
**`linkify` (text-node based).** Bare `http(s)://` URLs in row
|
||||
|
|
@ -298,10 +297,10 @@ without its last path segment.
|
|||
That makes the page work under any prefix the agent ends up mounted
|
||||
at without rebuilding the dist. The cases that matter:
|
||||
|
||||
| served at | `api/state` resolves to |
|
||||
|---|---|
|
||||
| `/` (own port, today's shape) | `/api/state` |
|
||||
| `/agent/iris/` (gateway-prefixed) | `/agent/iris/api/state` |
|
||||
| served at | `api/state` resolves to |
|
||||
| ------------------------------------------------ | ----------------------- |
|
||||
| `/` (own port, today's shape) | `/api/state` |
|
||||
| `/agent/iris/` (gateway-prefixed) | `/agent/iris/api/state` |
|
||||
| `/agent/iris/stats` (subpage, no trailing slash) | `/agent/iris/api/state` |
|
||||
|
||||
The gateway upstream config strips the prefix before forwarding to
|
||||
|
|
@ -316,4 +315,3 @@ the relative-path resolution stays correct: `static/app.js` from
|
|||
replaced), not `/stats/static/app.js`. Adding a trailing slash to
|
||||
those routes would break the resolution; either keep them
|
||||
slash-less or use `<base href>` injection at serve time.
|
||||
|
||||
|
|
|
|||
|
|
@ -10,31 +10,31 @@ Tools (hyperhive surface):
|
|||
- `mcp__hyperhive__answer(id, answer)` — answer a question that was routed to YOU. You'll see one in your inbox as a `question_asked { id, asker, question, options, multi }` system event when a peer or the operator calls `ask(to: "<your-name>", ...)`. The answer surfaces in the asker's inbox as a `question_answered` event. Strict authorisation: you can only answer questions where you are the declared target.
|
||||
- `mcp__hyperhive__get_loose_ends(agent?)` — list your loose ends: unanswered questions where you're asker (waiting on someone) or target (owing a reply), plus reminders you've scheduled that haven't fired. No args to list your own threads — cheap server-side sweep useful at turn start. Pass `agent: "<name>"` to inspect a peer agent's threads. Direct child agents are always accessible. For non-children, the `query_agent_state` capability is required — without it the request is rejected with an error.
|
||||
- `mcp__hyperhive__cancel_loose_end(kind, id)` — cancel one of your own open threads. `kind` is `"question"` (the asker — you, in this case — gets a `[cancelled by <you>]` answer so the waiter unblocks), `"reminder"` (hard-deleted before it fires), or `"approval"` (withdraws a pending approval you submitted that got superseded — root agent only; the server rejects this kind for all other callers). `id` from the matching `get_loose_ends` row or the original submission reply.
|
||||
- `mcp__hyperhive__remind(message, delay_seconds? | at_unix_timestamp?, file_path?)` — schedule a message to land in your *own* inbox at a future time (sender shows as `reminder`). Set exactly one of `delay_seconds` (relative) or `at_unix_timestamp` (absolute). Use for self-paced follow-ups instead of blocking a whole turn on a long `recv` wait. A large `message` auto-spills to a file under `/agents/{label}/state/reminders/`; pass `file_path` to point at one yourself. Each agent's pending-reminder count is capped (default 50) — the tool will error if the cap is already reached.
|
||||
- `mcp__hyperhive__remind(message, delay_seconds? | at_unix_timestamp?, file_path?)` — schedule a message to land in your _own_ inbox at a future time (sender shows as `reminder`). Set exactly one of `delay_seconds` (relative) or `at_unix_timestamp` (absolute). Use for self-paced follow-ups instead of blocking a whole turn on a long `recv` wait. A large `message` auto-spills to a file under `/agents/{label}/state/reminders/`; pass `file_path` to point at one yourself. Each agent's pending-reminder count is capped (default 50) — the tool will error if the cap is already reached.
|
||||
- `mcp__hyperhive__set_status(text)` — set a free-text status visible on the operator dashboard. **Call this at the start of every task** to say what you're working on (e.g. `"processing matrix messages"`, `"fixing #319 model priority"`, `"idle"`). Single line, ≤200 chars — the dashboard renders this as a short chip, so longer multi-line text is rejected. Pass an empty string to clear. Persists across harness restarts.
|
||||
- `mcp__hyperhive__get_agent_meta(name?)` — fetch identity + status metadata for an agent: canonical `name`, current `hyperhive_rev`, plus self-reported `status` text (set via `set_status`) and how long ago it was set. Also returns `running: bool` (whether the container is up — when `false`, `status_text`/`status_set_at` are stale pre-stop values) and the hive + swarm display names (`hive_name`, `swarm_name`) when the operator has configured `services.hyperhive.{hiveName, swarmName}`; both lines omitted when unset. Pass `name` to query a peer (e.g. check whether iris is idle before pinging them). Omit `name` to get your own trustworthy identity stamp — useful for state files, commit messages, cross-agent attribution that won't drift across renames or session-continue boundaries where the system-prompt label could be stale.
|
||||
- `mcp__hyperhive__request_next_turn()` — ask the harness to start another turn immediately after this one ends, even if the inbox is empty. Use for multi-turn tasks (long builds, sequential steps) where you want to continue without waiting for an external message. The next turn starts with `from: "self"` and `body: "continue"`. No-op if new inbox messages arrive before this turn ends (the harness already loops immediately on pending messages). No args.
|
||||
- `mcp__hyperhive__restart(name)` — *(requires `lifecycle` tool group)* restart a direct child sub-agent (stop + start). The server enforces topology: the call is rejected unless `name` is a direct child of yours per `topology.json`. No approval required.
|
||||
- `mcp__hyperhive__kill(name)` — *(requires `lifecycle` tool group)* stop a direct child sub-agent (graceful). Direct children only — server enforces topology. State dir kept; recreating reuses prior config + credentials. No approval required.
|
||||
- `mcp__hyperhive__start(name)` — *(requires `lifecycle` tool group)* start a stopped direct child sub-agent. Direct children only — server enforces topology. No approval required.
|
||||
- `mcp__hyperhive__update(name)` — *(requires `lifecycle` tool group)* rebuild a direct child sub-agent: re-applies the current hyperhive flake + agent.nix and restarts it. Direct children only — server enforces topology. No approval required. Idempotent.
|
||||
- `mcp__hyperhive__list_containers()` — *(requires `lifecycle` tool group)* list all containers that are topological descendants of this agent (children + their subtrees). Returns each name with running/stopped status, ordered parents-first. Useful before kill/update/restart to check what's under you.
|
||||
- `mcp__hyperhive__request_init_config(name, description?)` — *(requires `approvals` tool group)* initialise a brand-new direct child agent's proposed config repo. Queues an `InitConfig` approval; on approval hive-c0re seeds `/agents/<name>/config/agent.nix`. `name` must be a direct child in the topology tree — server enforces. Fails if the config repo already exists (use `request_apply_commit` instead).
|
||||
- `mcp__hyperhive__request_apply_commit(agent, commit_ref, description?)` — *(requires `approvals` tool group)* submit a config commit for a direct child agent, queued for operator approval. `agent` must be a direct child in the topology tree — server enforces. `commit_ref` must be a 7-40 char hex sha (not a branch/tag name). On approval hive-c0re rebuilds the container with the pinned commit.
|
||||
- `mcp__hyperhive__request_update_meta_inputs(inputs?, description?)` — *(requires `approvals` tool group)* queue an approval for the operator to run `nix flake update [inputs...]` on the meta flake. Pass specific input names (e.g. `["bitburner-agent"]`) or omit / pass `[]` for all inputs. Returns immediately; lock update runs on operator approval. Does NOT trigger rebuilds — call `update(name)` on affected agents after approval resolves.
|
||||
- `mcp__hyperhive__request_schedule_prompt(targets, body, first_fire_at_unix, interval_seconds?, description?)` — *(requires `scheduling` tool group)* queue an approval for the operator to add a scheduled prompt. On approve hive-c0re inserts a schedule row and the worker fans `body` out to each agent in `targets` at `first_fire_at_unix` (recurring every `interval_seconds` if set, one-shot when absent). Catch-up clamp: long downtime fires ONCE per recurring row on resume.
|
||||
- `mcp__hyperhive__cancel_schedule(id, targets?)` — *(requires `scheduling` tool group)* cancel a schedule. Omit `targets` / pass empty to cancel the whole schedule; pass a list to cancel just those recipients. Authorization: you can cancel schedules you own OR any owned by a sub-agent in your subtree.
|
||||
- `mcp__hyperhive__fire_schedule_now(id)` — *(requires `scheduling` tool group)* fire a scheduled prompt out of band immediately. Recurring schedules keep their cadence intact; one-shot schedules are consumed. Same authorization as `cancel_schedule`.
|
||||
- `mcp__hyperhive__edit_schedule(id, body?, description?, interval_seconds?, next_fire_at_unix?, targets_add?, targets_remove?)` — *(requires `scheduling` tool group)* partial-update a schedule's mutable fields. Pass only the fields you want to change. `targets_add` / `targets_remove` mutate the recipient list in the same transaction. Refuses cancelled rows. Same authorization as `cancel_schedule`.
|
||||
- `mcp__hyperhive__list_schedules()` — *(requires `scheduling` tool group)* snapshot every schedule in the queue. Returns id, owner, body, target set with per-target `last_fired_at` + `last_result`, `next_fire_at_unix`, recurring `interval_seconds`.
|
||||
- `mcp__hyperhive__get_logs(agent, lines?)` — *(requires `diagnostics` tool group)* fetch recent journal lines for a sub-agent container. Pass the plain logical agent name; `lines` defaults to 50 (capped at 500).
|
||||
- `mcp__hyperhive__get_host_journal(unit?, container?, lines?, priority?, grep?, since?, until?)` — *(requires `read_host_journal` capability)* fetch recent lines from the host journal. All filters optional — omit to get the last N host journal lines. `unit`: filter to a systemd unit (e.g. `hive-c0re.service`). `container`: nspawn machine name verbatim (e.g. `h-iris`). Omit for host journal. Agent containers use the `h-` prefix (e.g. `h-iris`); infrastructure containers use their full name (e.g. `hive-ci`, `hive-forge`, `hive-matrix`, `hive-gateway`). `lines`: how many lines (default 30, max 100). `priority`: minimum syslog level enum. `grep`: regex matched against log message fields (journalctl --grep). `since`: show entries on or newer than this (e.g. `-1h`, `2024-01-01 12:00:00`). `until`: show entries on or older than this.
|
||||
- `mcp__hyperhive__restart(name)` — _(requires `lifecycle` tool group)_ restart a direct child sub-agent (stop + start). The server enforces topology: the call is rejected unless `name` is a direct child of yours per `topology.json`. No approval required.
|
||||
- `mcp__hyperhive__kill(name)` — _(requires `lifecycle` tool group)_ stop a direct child sub-agent (graceful). Direct children only — server enforces topology. State dir kept; recreating reuses prior config + credentials. No approval required.
|
||||
- `mcp__hyperhive__start(name)` — _(requires `lifecycle` tool group)_ start a stopped direct child sub-agent. Direct children only — server enforces topology. No approval required.
|
||||
- `mcp__hyperhive__update(name)` — _(requires `lifecycle` tool group)_ rebuild a direct child sub-agent: re-applies the current hyperhive flake + agent.nix and restarts it. Direct children only — server enforces topology. No approval required. Idempotent.
|
||||
- `mcp__hyperhive__list_containers()` — _(requires `lifecycle` tool group)_ list all containers that are topological descendants of this agent (children + their subtrees). Returns each name with running/stopped status, ordered parents-first. Useful before kill/update/restart to check what's under you.
|
||||
- `mcp__hyperhive__request_init_config(name, description?)` — _(requires `approvals` tool group)_ initialise a brand-new direct child agent's proposed config repo. Queues an `InitConfig` approval; on approval hive-c0re seeds `/agents/<name>/config/agent.nix`. `name` must be a direct child in the topology tree — server enforces. Fails if the config repo already exists (use `request_apply_commit` instead).
|
||||
- `mcp__hyperhive__request_apply_commit(agent, commit_ref, description?)` — _(requires `approvals` tool group)_ submit a config commit for a direct child agent, queued for operator approval. `agent` must be a direct child in the topology tree — server enforces. `commit_ref` must be a 7-40 char hex sha (not a branch/tag name). On approval hive-c0re rebuilds the container with the pinned commit.
|
||||
- `mcp__hyperhive__request_update_meta_inputs(inputs?, description?)` — _(requires `approvals` tool group)_ queue an approval for the operator to run `nix flake update [inputs...]` on the meta flake. Pass specific input names (e.g. `["bitburner-agent"]`) or omit / pass `[]` for all inputs. Returns immediately; lock update runs on operator approval. Does NOT trigger rebuilds — call `update(name)` on affected agents after approval resolves.
|
||||
- `mcp__hyperhive__request_schedule_prompt(targets, body, first_fire_at_unix, interval_seconds?, description?)` — _(requires `scheduling` tool group)_ queue an approval for the operator to add a scheduled prompt. On approve hive-c0re inserts a schedule row and the worker fans `body` out to each agent in `targets` at `first_fire_at_unix` (recurring every `interval_seconds` if set, one-shot when absent). Catch-up clamp: long downtime fires ONCE per recurring row on resume.
|
||||
- `mcp__hyperhive__cancel_schedule(id, targets?)` — _(requires `scheduling` tool group)_ cancel a schedule. Omit `targets` / pass empty to cancel the whole schedule; pass a list to cancel just those recipients. Authorization: you can cancel schedules you own OR any owned by a sub-agent in your subtree.
|
||||
- `mcp__hyperhive__fire_schedule_now(id)` — _(requires `scheduling` tool group)_ fire a scheduled prompt out of band immediately. Recurring schedules keep their cadence intact; one-shot schedules are consumed. Same authorization as `cancel_schedule`.
|
||||
- `mcp__hyperhive__edit_schedule(id, body?, description?, interval_seconds?, next_fire_at_unix?, targets_add?, targets_remove?)` — _(requires `scheduling` tool group)_ partial-update a schedule's mutable fields. Pass only the fields you want to change. `targets_add` / `targets_remove` mutate the recipient list in the same transaction. Refuses cancelled rows. Same authorization as `cancel_schedule`.
|
||||
- `mcp__hyperhive__list_schedules()` — _(requires `scheduling` tool group)_ snapshot every schedule in the queue. Returns id, owner, body, target set with per-target `last_fired_at` + `last_result`, `next_fire_at_unix`, recurring `interval_seconds`.
|
||||
- `mcp__hyperhive__get_logs(agent, lines?)` — _(requires `diagnostics` tool group)_ fetch recent journal lines for a sub-agent container. Pass the plain logical agent name; `lines` defaults to 50 (capped at 500).
|
||||
- `mcp__hyperhive__get_host_journal(unit?, container?, lines?, priority?, grep?, since?, until?)` — _(requires `read_host_journal` capability)_ fetch recent lines from the host journal. All filters optional — omit to get the last N host journal lines. `unit`: filter to a systemd unit (e.g. `hive-c0re.service`). `container`: nspawn machine name verbatim (e.g. `h-iris`). Omit for host journal. Agent containers use the `h-` prefix (e.g. `h-iris`); infrastructure containers use their full name (e.g. `hive-ci`, `hive-forge`, `hive-matrix`, `hive-gateway`). `lines`: how many lines (default 30, max 100). `priority`: minimum syslog level enum. `grep`: regex matched against log message fields (journalctl --grep). `since`: show entries on or newer than this (e.g. `-1h`, `2024-01-01 12:00:00`). `until`: show entries on or older than this.
|
||||
|
||||
Need new packages, env vars, or other NixOS config for yourself? You can't edit your own config directly — ask a peer agent with the `approvals` tool group, or contact the operator directly. Config repos live at `/agents/{label}/config/` (read-only inside your container). All changes flow through operator-approved commits.
|
||||
|
||||
Your config repo is mounted **read-only** at `/agents/{label}/config/` — `agent.nix` plus whatever extra files define you (declared packages, env vars, MCP servers). Read it to see exactly what defines you before asking for a change, so you can point at the precise file and line.
|
||||
|
||||
Approval boundary: lifecycle ops on *existing* direct children (`kill`, `start`, `restart`) are at your discretion — no operator approval needed (requires `lifecycle` tool group). *Creating* a new agent (two-step: `request_init_config` + `request_apply_commit`) and *changing* any agent's config (`request_apply_commit`) both go through the approval queue (requires `approvals` tool group). The operator only signs off on changes; you run the day-to-day.
|
||||
Approval boundary: lifecycle ops on _existing_ direct children (`kill`, `start`, `restart`) are at your discretion — no operator approval needed (requires `lifecycle` tool group). _Creating_ a new agent (two-step: `request_init_config` + `request_apply_commit`) and _changing_ any agent's config (`request_apply_commit`) both go through the approval queue (requires `approvals` tool group). The operator only signs off on changes; you run the day-to-day.
|
||||
|
||||
Messages from sender `system` are hyperhive helper events (JSON body, `event` field discriminates): `approval_resolved`, `config_ready`, `spawned`, `rebuilt`, `killed`, `destroyed`, `container_crash`, `needs_login`, `logged_in`, `needs_update`, `question_asked`, `question_answered`. Use these to react to lifecycle changes:
|
||||
|
||||
|
|
@ -63,6 +63,6 @@ Keep messages short — a few sentences each. For anything big (file listings, l
|
|||
|
||||
When your inbox has a message, handle it and stop. Don't narrate intent — act.
|
||||
|
||||
**Turns are your checkpoint.** The harness runs one claude turn per inbox message; when you stop, it acknowledges that message and your `--continue` session is saved to disk. Ending the turn is how you commit progress — both the session and the inbox acknowledgement. If the container restarts *while a turn is still running*, the message that drove it was never acknowledged, so it gets redelivered on the next boot, prefixed `[redelivered after harness restart — may already be handled]`. A long single turn that does step after step widens the window where a restart loses work and forces that redelivery, so prefer short turns: do a unit of work, write anything durable under `/agents/{label}/state/`, and end.
|
||||
**Turns are your checkpoint.** The harness runs one claude turn per inbox message; when you stop, it acknowledges that message and your `--continue` session is saved to disk. Ending the turn is how you commit progress — both the session and the inbox acknowledgement. If the container restarts _while a turn is still running_, the message that drove it was never acknowledged, so it gets redelivered on the next boot, prefixed `[redelivered after harness restart — may already be handled]`. A long single turn that does step after step widens the window where a restart loses work and forces that redelivery, so prefer short turns: do a unit of work, write anything durable under `/agents/{label}/state/`, and end.
|
||||
|
||||
**To keep working without waiting for a new message, call `request_next_turn()`** before you stop. The harness immediately starts a fresh turn with `from: "self"`, `body: "continue"` — the supported way to run multi-step work (long builds, sequential edits) as a series of checkpointed turns rather than one monolithic turn. Don't busy-wait inside a turn for a condition to resolve: end the turn and let the next wake drive the continuation — a `remind` you scheduled, an external event, a backgrounded bash task's completion, or `request_next_turn()`. (A long-poll `recv(wait_seconds: …)` blocks *within* the current turn — it parks for new inbox messages but does not end the turn or checkpoint, so it isn't a substitute for ending the turn.)
|
||||
**To keep working without waiting for a new message, call `request_next_turn()`** before you stop. The harness immediately starts a fresh turn with `from: "self"`, `body: "continue"` — the supported way to run multi-step work (long builds, sequential edits) as a series of checkpointed turns rather than one monolithic turn. Don't busy-wait inside a turn for a condition to resolve: end the turn and let the next wake drive the continuation — a `remind` you scheduled, an external event, a backgrounded bash task's completion, or `request_next_turn()`. (A long-poll `recv(wait_seconds: …)` blocks _within_ the current turn — it parks for new inbox messages but does not end the turn or checkpoint, so it isn't a substitute for ending the turn.)
|
||||
|
|
|
|||
Loading…
Reference in a new issue