hivectl: rename hivectl agents to hivectl agent <name> <verb>
This commit is contained in:
parent
f48ba3ca0d
commit
03afbd1316
19 changed files with 367 additions and 525 deletions
|
|
@ -26,7 +26,7 @@ above was spawned by `alice`), and the operator can reparent any agent. The
|
||||||
bootstrap container (`ruth`) is just another root. Re-parenting is
|
bootstrap container (`ruth`) is just another root. Re-parenting is
|
||||||
operator-driven:
|
operator-driven:
|
||||||
|
|
||||||
- CLI: `hivectl agents set-parent <child> --parent <new>` (or `--root` to
|
- CLI: `hivectl agent <child> set-parent --parent <new>` (or `--root` to
|
||||||
promote). Exactly one of `--parent` / `--root` is required.
|
promote). Exactly one of `--parent` / `--root` is required.
|
||||||
- Dashboard: `POST /api/topology/set-parent` (form fields `child`,
|
- Dashboard: `POST /api/topology/set-parent` (form fields `child`,
|
||||||
optional `new_parent` — absent / empty ⇒ promote to root).
|
optional `new_parent` — absent / empty ⇒ promote to root).
|
||||||
|
|
|
||||||
|
|
@ -119,7 +119,7 @@ kind-specific payload carrier.
|
||||||
- `Spawn` — direct container creation from the agent's config repo.
|
- `Spawn` — direct container creation from the agent's config repo.
|
||||||
`commit_ref` is empty. Submitted via `HostRequest::RequestSpawn`
|
`commit_ref` is empty. Submitted via `HostRequest::RequestSpawn`
|
||||||
(operator-gated, the `◆ R3QU3ST SP4WN` dashboard button +
|
(operator-gated, the `◆ R3QU3ST SP4WN` dashboard button +
|
||||||
`hivectl agents request-spawn` CLI). The host-level `HostRequest::Spawn`
|
`hivectl agent <name> request-spawn` CLI). The host-level `HostRequest::Spawn`
|
||||||
variant bypasses the approval queue entirely — privileged-context use
|
variant bypasses the approval queue entirely — privileged-context use
|
||||||
only (operator on the host shell, test scripts, one-off recoveries).
|
only (operator on the host shell, test scripts, one-off recoveries).
|
||||||
This is the **canonical first-spawn**: a new agent's `InitConfig`
|
This is the **canonical first-spawn**: a new agent's `InitConfig`
|
||||||
|
|
|
||||||
|
|
@ -212,7 +212,7 @@ bind-mount, and that's the whole design: the harness stats it
|
||||||
in-container via `hive_sh4re::paths::paused_marker`, while hive-c0re
|
in-container via `hive_sh4re::paths::paused_marker`, while hive-c0re
|
||||||
stats it on the host (`Coordinator::is_paused`) to populate the
|
stats it on the host (`Coordinator::is_paused`) to populate the
|
||||||
`paused` field on the agent card, and creates/removes it
|
`paused` field on the agent card, and creates/removes it
|
||||||
(`Coordinator::set_paused`) for `hivectl agents pause|resume` and the
|
(`Coordinator::set_paused`) for `hivectl agent <name> pause|resume` and the
|
||||||
dashboard toggle. Because the file itself is the only shared state
|
dashboard toggle. Because the file itself is the only shared state
|
||||||
there's no protocol between them, no round-trip into the container, and
|
there's no protocol between them, no round-trip into the container, and
|
||||||
pause keeps working when the harness is wedged or the container is
|
pause keeps working when the harness is wedged or the container is
|
||||||
|
|
@ -339,7 +339,7 @@ step would re-fire).
|
||||||
`R3V1V3` queues a Spawn approval that reuses the kept state on
|
`R3V1V3` queues a Spawn approval that reuses the kept state on
|
||||||
approve (no re-login).
|
approve (no re-login).
|
||||||
- `PURG3` (opt-in via the dashboard button or
|
- `PURG3` (opt-in via the dashboard button or
|
||||||
`hivectl agents destroy --purge <name>`) — DESTR0Y plus wipes
|
`hivectl agent <name> destroy --purge`) — DESTR0Y plus wipes
|
||||||
`/var/lib/hyperhive/{agents,applied}/<name>/`. Config history,
|
`/var/lib/hyperhive/{agents,applied}/<name>/`. Config history,
|
||||||
claude creds, /state/ notes, and the harness dir are all gone.
|
claude creds, /state/ notes, and the harness dir are all gone.
|
||||||
No undo.
|
No undo.
|
||||||
|
|
|
||||||
|
|
@ -79,13 +79,13 @@ See [`approvals.md`](approvals.md) for the full flow.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Roster: all agents, status, rev, parent, pending reminders
|
# Roster: all agents, status, rev, parent, pending reminders
|
||||||
hivectl agents list
|
hivectl list-agents
|
||||||
|
|
||||||
# Restart a stuck container (no rebuild)
|
# Restart a stuck container (no rebuild)
|
||||||
hivectl agents restart <agent>
|
hivectl agent <agent> restart
|
||||||
|
|
||||||
# Open a Claude session inside an agent's container
|
# Open a Claude session inside an agent's container
|
||||||
hivectl choom <agent>
|
hivectl agent <agent> choom
|
||||||
|
|
||||||
# Open hive web surfaces in a browser (or just print the URLs)
|
# Open hive web surfaces in a browser (or just print the URLs)
|
||||||
hivectl open # operator dashboard
|
hivectl open # operator dashboard
|
||||||
|
|
|
||||||
|
|
@ -20,29 +20,29 @@ This document contains the help content for the `hivectl` command-line program.
|
||||||
* [`hivectl gateway create-user`↴](#hivectl-gateway-create-user)
|
* [`hivectl gateway create-user`↴](#hivectl-gateway-create-user)
|
||||||
* [`hivectl gateway delete-user`↴](#hivectl-gateway-delete-user)
|
* [`hivectl gateway delete-user`↴](#hivectl-gateway-delete-user)
|
||||||
* [`hivectl gateway list-users`↴](#hivectl-gateway-list-users)
|
* [`hivectl gateway list-users`↴](#hivectl-gateway-list-users)
|
||||||
* [`hivectl agents`↴](#hivectl-agents)
|
* [`hivectl agent`↴](#hivectl-agent)
|
||||||
* [`hivectl agents list`↴](#hivectl-agents-list)
|
* [`hivectl agent restart`↴](#hivectl-agent-restart)
|
||||||
* [`hivectl agents restart`↴](#hivectl-agents-restart)
|
* [`hivectl agent pause`↴](#hivectl-agent-pause)
|
||||||
* [`hivectl agents restart-all`↴](#hivectl-agents-restart-all)
|
* [`hivectl agent resume`↴](#hivectl-agent-resume)
|
||||||
* [`hivectl agents pause`↴](#hivectl-agents-pause)
|
* [`hivectl agent spawn`↴](#hivectl-agent-spawn)
|
||||||
* [`hivectl agents resume`↴](#hivectl-agents-resume)
|
* [`hivectl agent request-spawn`↴](#hivectl-agent-request-spawn)
|
||||||
* [`hivectl agents spawn`↴](#hivectl-agents-spawn)
|
* [`hivectl agent kill`↴](#hivectl-agent-kill)
|
||||||
* [`hivectl agents request-spawn`↴](#hivectl-agents-request-spawn)
|
* [`hivectl agent destroy`↴](#hivectl-agent-destroy)
|
||||||
* [`hivectl agents kill`↴](#hivectl-agents-kill)
|
* [`hivectl agent rebuild`↴](#hivectl-agent-rebuild)
|
||||||
* [`hivectl agents destroy`↴](#hivectl-agents-destroy)
|
* [`hivectl agent set-parent`↴](#hivectl-agent-set-parent)
|
||||||
* [`hivectl agents rebuild`↴](#hivectl-agents-rebuild)
|
* [`hivectl agent set-limits`↴](#hivectl-agent-set-limits)
|
||||||
* [`hivectl agents set-parent`↴](#hivectl-agents-set-parent)
|
* [`hivectl agent choom`↴](#hivectl-agent-choom)
|
||||||
* [`hivectl agents set-limits`↴](#hivectl-agents-set-limits)
|
* [`hivectl agent quota`↴](#hivectl-agent-quota)
|
||||||
* [`hivectl agents quota`↴](#hivectl-agents-quota)
|
* [`hivectl agent quota show`↴](#hivectl-agent-quota-show)
|
||||||
* [`hivectl agents quota enable`↴](#hivectl-agents-quota-enable)
|
* [`hivectl agent quota set`↴](#hivectl-agent-quota-set)
|
||||||
* [`hivectl agents quota show`↴](#hivectl-agents-quota-show)
|
* [`hivectl agent subvol`↴](#hivectl-agent-subvol)
|
||||||
* [`hivectl agents quota set`↴](#hivectl-agents-quota-set)
|
* [`hivectl agent subvol upgrade`↴](#hivectl-agent-subvol-upgrade)
|
||||||
* [`hivectl agents subvol`↴](#hivectl-agents-subvol)
|
* [`hivectl agent subvol snapshot`↴](#hivectl-agent-subvol-snapshot)
|
||||||
* [`hivectl agents subvol upgrade`↴](#hivectl-agents-subvol-upgrade)
|
* [`hivectl agent subvol snapshot create`↴](#hivectl-agent-subvol-snapshot-create)
|
||||||
* [`hivectl agents subvol snapshot`↴](#hivectl-agents-subvol-snapshot)
|
* [`hivectl agent subvol snapshot delete`↴](#hivectl-agent-subvol-snapshot-delete)
|
||||||
* [`hivectl agents subvol snapshot create`↴](#hivectl-agents-subvol-snapshot-create)
|
* [`hivectl agent subvol snapshot send`↴](#hivectl-agent-subvol-snapshot-send)
|
||||||
* [`hivectl agents subvol snapshot delete`↴](#hivectl-agents-subvol-snapshot-delete)
|
* [`hivectl list-agents`↴](#hivectl-list-agents)
|
||||||
* [`hivectl agents subvol snapshot send`↴](#hivectl-agents-subvol-snapshot-send)
|
* [`hivectl quota-enable`↴](#hivectl-quota-enable)
|
||||||
* [`hivectl approvals`↴](#hivectl-approvals)
|
* [`hivectl approvals`↴](#hivectl-approvals)
|
||||||
* [`hivectl approvals pending`↴](#hivectl-approvals-pending)
|
* [`hivectl approvals pending`↴](#hivectl-approvals-pending)
|
||||||
* [`hivectl approvals approve`↴](#hivectl-approvals-approve)
|
* [`hivectl approvals approve`↴](#hivectl-approvals-approve)
|
||||||
|
|
@ -52,7 +52,6 @@ This document contains the help content for the `hivectl` command-line program.
|
||||||
* [`hivectl wg peer`↴](#hivectl-wg-peer)
|
* [`hivectl wg peer`↴](#hivectl-wg-peer)
|
||||||
* [`hivectl wg status`↴](#hivectl-wg-status)
|
* [`hivectl wg status`↴](#hivectl-wg-status)
|
||||||
* [`hivectl peer-config`↴](#hivectl-peer-config)
|
* [`hivectl peer-config`↴](#hivectl-peer-config)
|
||||||
* [`hivectl choom`↴](#hivectl-choom)
|
|
||||||
* [`hivectl stop`↴](#hivectl-stop)
|
* [`hivectl stop`↴](#hivectl-stop)
|
||||||
* [`hivectl start`↴](#hivectl-start)
|
* [`hivectl start`↴](#hivectl-start)
|
||||||
* [`hivectl restart`↴](#hivectl-restart)
|
* [`hivectl restart`↴](#hivectl-restart)
|
||||||
|
|
@ -71,11 +70,12 @@ Sibling to the `hive-c0re` daemon binary. Covers host-side admin operations that
|
||||||
* `matrix` — matrix-tuwunel user provisioning
|
* `matrix` — matrix-tuwunel user provisioning
|
||||||
* `github` — GitHub account provisioning
|
* `github` — GitHub account provisioning
|
||||||
* `gateway` — Gateway htpasswd user management
|
* `gateway` — Gateway htpasswd user management
|
||||||
* `agents` — Agent container management
|
* `agent` — Lifecycle actions on ONE managed agent container. Needs the hive-c0re daemon running
|
||||||
|
* `list-agents` — Show all managed agents with their status and technical state
|
||||||
|
* `quota-enable` — Enable btrfs qgroup accounting on the agent-state filesystem
|
||||||
* `approvals` — Operator approval queue: list, approve, or deny pending requests
|
* `approvals` — Operator approval queue: list, approve, or deny pending requests
|
||||||
* `wg` — WireGuard inter-hive mesh setup helpers
|
* `wg` — WireGuard inter-hive mesh setup helpers
|
||||||
* `peer-config` — Generate the federation peer-config block for THIS hive
|
* `peer-config` — Generate the federation peer-config block for THIS hive
|
||||||
* `choom` — Open an interactive Claude session inside an agent container
|
|
||||||
* `stop` — Stop containers hive-wide in one operator action
|
* `stop` — Stop containers hive-wide in one operator action
|
||||||
* `start` — Start containers hive-wide — the inverse of `hivectl stop`
|
* `start` — Start containers hive-wide — the inverse of `hivectl stop`
|
||||||
* `restart` — Restart containers hive-wide — `stop` then `start` over one scope
|
* `restart` — Restart containers hive-wide — `stop` then `start` over one scope
|
||||||
|
|
@ -84,7 +84,7 @@ Sibling to the `hive-c0re` daemon binary. Covers host-side admin operations that
|
||||||
|
|
||||||
###### **Options:**
|
###### **Options:**
|
||||||
|
|
||||||
* `--socket <SOCKET>` — Path to the hive-c0re host admin socket, used by the daemon-assisted verbs (`agents`, `stop`, `start`). Global: accepted before or after the subcommand. Verbs that don't talk to the daemon ignore it
|
* `--socket <SOCKET>` — Path to the hive-c0re host admin socket, used by the daemon-assisted verbs (`agent`, `list-agents`, `stop`, `start`). Global: accepted before or after the subcommand. Verbs that don't talk to the daemon ignore it
|
||||||
|
|
||||||
Default value: `/run/hyperhive/host.sock`
|
Default value: `/run/hyperhive/host.sock`
|
||||||
|
|
||||||
|
|
@ -327,54 +327,41 @@ List all gateway htpasswd usernames, one per line
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## `hivectl agents`
|
## `hivectl agent`
|
||||||
|
|
||||||
Agent container management.
|
Lifecycle actions on ONE managed agent container. Needs the hive-c0re daemon running.
|
||||||
|
|
||||||
Lifecycle actions on managed agent containers. Needs the hive-c0re daemon running.
|
Everything here targets a single named agent (`hivectl agent foo restart`, `hivectl agent foo choom`, …) — anything that acts hive-wide lives at the top level instead (`list-agents`, `restart`/`stop`/`start` with a scope, `quota-enable`).
|
||||||
|
|
||||||
**Usage:** `hivectl agents <COMMAND>`
|
**Usage:** `hivectl agent <NAME> <COMMAND>`
|
||||||
|
|
||||||
###### **Subcommands:**
|
###### **Subcommands:**
|
||||||
|
|
||||||
* `list` — Show all managed agents with their status and technical state
|
* `restart` — Stop and start this agent container without rebuilding config
|
||||||
* `restart` — Stop and start a single agent container without rebuilding config
|
* `pause` — Park this agent's turn loop, leaving the container running
|
||||||
* `restart-all` — Restart all managed agent containers
|
* `resume` — Resume this paused agent — it drains whatever queued up while parked
|
||||||
* `pause` — Park an agent's turn loop, leaving the container running
|
* `spawn` — Spawn this agent container directly, bypassing the approval queue
|
||||||
* `resume` — Resume a paused agent — it drains whatever queued up while parked
|
|
||||||
* `spawn` — Spawn a new agent container directly, bypassing the approval queue
|
|
||||||
* `request-spawn` — Queue a spawn request for operator approval
|
* `request-spawn` — Queue a spawn request for operator approval
|
||||||
* `kill` — Stop a managed container (graceful)
|
* `kill` — Stop this managed container (graceful)
|
||||||
* `destroy` — Tear down a sub-agent container, keeping its state by default. No undo
|
* `destroy` — Tear down this sub-agent container, keeping its state by default. No undo
|
||||||
* `rebuild` — Apply pending config to a managed container
|
* `rebuild` — Apply pending config to this managed container
|
||||||
* `set-parent` — Move an agent in the topology tree — under a new parent, or to root
|
* `set-parent` — Move this agent in the topology tree — under a new parent, or to root
|
||||||
* `set-limits` — Declare an agent's CPU/memory limits, overriding the hive-wide defaults
|
* `set-limits` — Declare this agent's CPU/memory limits, overriding the hive-wide defaults
|
||||||
* `quota` — Per-agent disk accounting + optional quotas via btrfs qgroups
|
* `choom` — Open an interactive Claude session inside this agent's container
|
||||||
* `subvol` — btrfs subvolume management for agent state dirs
|
* `quota` — This agent's disk accounting + optional quota via btrfs qgroups
|
||||||
|
* `subvol` — btrfs subvolume management for this agent's state dir
|
||||||
|
|
||||||
|
|
||||||
## `hivectl agents list`
|
|
||||||
|
|
||||||
Show all managed agents with their status and technical state
|
|
||||||
|
|
||||||
**Usage:** `hivectl agents list [OPTIONS]`
|
|
||||||
|
|
||||||
###### **Options:**
|
|
||||||
|
|
||||||
* `--json` — Emit the raw JSON rows instead of the padded table (for scripting). The table is the default human-readable shape
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## `hivectl agents restart`
|
|
||||||
|
|
||||||
Stop and start a single agent container without rebuilding config
|
|
||||||
|
|
||||||
**Usage:** `hivectl agents restart [OPTIONS] <NAME>`
|
|
||||||
|
|
||||||
###### **Arguments:**
|
###### **Arguments:**
|
||||||
|
|
||||||
* `<NAME>` — Agent name (e.g. `damocles`, `ruth`)
|
* `<NAME>` — Agent name (e.g. `damocles`, `iris`)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## `hivectl agent restart`
|
||||||
|
|
||||||
|
Stop and start this agent container without rebuilding config
|
||||||
|
|
||||||
|
**Usage:** `hivectl agent restart [OPTIONS]`
|
||||||
|
|
||||||
###### **Options:**
|
###### **Options:**
|
||||||
|
|
||||||
|
|
@ -382,91 +369,55 @@ Stop and start a single agent container without rebuilding config
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## `hivectl agents restart-all`
|
## `hivectl agent pause`
|
||||||
|
|
||||||
Restart all managed agent containers
|
Park this agent's turn loop, leaving the container running.
|
||||||
|
|
||||||
**Usage:** `hivectl agents restart-all [OPTIONS]`
|
|
||||||
|
|
||||||
###### **Options:**
|
|
||||||
|
|
||||||
* `--no-wait` — Return immediately after the restart DAGs are queued
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## `hivectl agents pause`
|
|
||||||
|
|
||||||
Park an agent's turn loop, leaving the container running.
|
|
||||||
|
|
||||||
The harness stops driving turns but keeps serving its web UI and MCP daemons, so the container, its mounts and its warm caches stay up while it burns no tokens. Inbox messages queue unacked and the backlog drains on `resume`. Sticky: it survives a restart, and pausing a stopped agent makes it come up paused.
|
The harness stops driving turns but keeps serving its web UI and MCP daemons, so the container, its mounts and its warm caches stay up while it burns no tokens. Inbox messages queue unacked and the backlog drains on `resume`. Sticky: it survives a restart, and pausing a stopped agent makes it come up paused.
|
||||||
|
|
||||||
**Usage:** `hivectl agents pause <NAME>`
|
**Usage:** `hivectl agent pause`
|
||||||
|
|
||||||
###### **Arguments:**
|
|
||||||
|
|
||||||
* `<NAME>` — Agent name
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## `hivectl agents resume`
|
## `hivectl agent resume`
|
||||||
|
|
||||||
Resume a paused agent — it drains whatever queued up while parked
|
Resume this paused agent — it drains whatever queued up while parked
|
||||||
|
|
||||||
**Usage:** `hivectl agents resume <NAME>`
|
**Usage:** `hivectl agent resume`
|
||||||
|
|
||||||
###### **Arguments:**
|
|
||||||
|
|
||||||
* `<NAME>` — Agent name
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## `hivectl agents spawn`
|
## `hivectl agent spawn`
|
||||||
|
|
||||||
Spawn a new agent container directly, bypassing the approval queue.
|
Spawn this agent container directly, bypassing the approval queue.
|
||||||
|
|
||||||
Operator-on-the-host only; use `request-spawn` for an approval-gated spawn.
|
Operator-on-the-host only; use `request-spawn` for an approval-gated spawn.
|
||||||
|
|
||||||
**Usage:** `hivectl agents spawn <NAME>`
|
**Usage:** `hivectl agent spawn`
|
||||||
|
|
||||||
###### **Arguments:**
|
|
||||||
|
|
||||||
* `<NAME>` — Agent name (e.g. `iris`)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## `hivectl agents request-spawn`
|
## `hivectl agent request-spawn`
|
||||||
|
|
||||||
Queue a spawn request for operator approval
|
Queue a spawn request for operator approval
|
||||||
|
|
||||||
**Usage:** `hivectl agents request-spawn <NAME>`
|
**Usage:** `hivectl agent request-spawn`
|
||||||
|
|
||||||
###### **Arguments:**
|
|
||||||
|
|
||||||
* `<NAME>` — Agent name
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## `hivectl agents kill`
|
## `hivectl agent kill`
|
||||||
|
|
||||||
Stop a managed container (graceful)
|
Stop this managed container (graceful)
|
||||||
|
|
||||||
**Usage:** `hivectl agents kill <NAME>`
|
**Usage:** `hivectl agent kill`
|
||||||
|
|
||||||
###### **Arguments:**
|
|
||||||
|
|
||||||
* `<NAME>` — Agent name
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## `hivectl agents destroy`
|
## `hivectl agent destroy`
|
||||||
|
|
||||||
Tear down a sub-agent container, keeping its state by default. No undo
|
Tear down this sub-agent container, keeping its state by default. No undo
|
||||||
|
|
||||||
**Usage:** `hivectl agents destroy [OPTIONS] <NAME>`
|
**Usage:** `hivectl agent destroy [OPTIONS]`
|
||||||
|
|
||||||
###### **Arguments:**
|
|
||||||
|
|
||||||
* `<NAME>` — Agent name
|
|
||||||
|
|
||||||
###### **Options:**
|
###### **Options:**
|
||||||
|
|
||||||
|
|
@ -474,46 +425,34 @@ Tear down a sub-agent container, keeping its state by default. No undo
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## `hivectl agents rebuild`
|
## `hivectl agent rebuild`
|
||||||
|
|
||||||
Apply pending config to a managed container
|
Apply pending config to this managed container
|
||||||
|
|
||||||
**Usage:** `hivectl agents rebuild <NAME>`
|
**Usage:** `hivectl agent rebuild`
|
||||||
|
|
||||||
###### **Arguments:**
|
|
||||||
|
|
||||||
* `<NAME>` — Agent name
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## `hivectl agents set-parent`
|
## `hivectl agent set-parent`
|
||||||
|
|
||||||
Move an agent in the topology tree — under a new parent, or to root
|
Move this agent in the topology tree — under a new parent, or to root
|
||||||
|
|
||||||
**Usage:** `hivectl agents set-parent [OPTIONS] <CHILD>`
|
**Usage:** `hivectl agent set-parent [OPTIONS]`
|
||||||
|
|
||||||
###### **Arguments:**
|
|
||||||
|
|
||||||
* `<CHILD>` — Agent to move
|
|
||||||
|
|
||||||
###### **Options:**
|
###### **Options:**
|
||||||
|
|
||||||
* `--parent <PARENT>` — New parent agent name. Mutually exclusive with `--root`
|
* `--parent <PARENT>` — New parent agent name. Mutually exclusive with `--root`
|
||||||
* `--root` — Promote `child` to root (no parent)
|
* `--root` — Promote this agent to root (no parent)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## `hivectl agents set-limits`
|
## `hivectl agent set-limits`
|
||||||
|
|
||||||
Declare an agent's CPU/memory limits, overriding the hive-wide defaults.
|
Declare this agent's CPU/memory limits, overriding the hive-wide defaults.
|
||||||
|
|
||||||
Replaces the agent's whole override entry rather than merging into it: any limit you don't pass returns to the hive-wide default. To change one and keep the other, pass both. Disk is a separate resource with its own group — see `agents quota`.
|
Replaces the agent's whole override entry rather than merging into it: any limit you don't pass returns to the hive-wide default. To change one and keep the other, pass both. Disk is a separate resource with its own group — see `quota`.
|
||||||
|
|
||||||
**Usage:** `hivectl agents set-limits [OPTIONS] <NAME>`
|
**Usage:** `hivectl agent set-limits [OPTIONS]`
|
||||||
|
|
||||||
###### **Arguments:**
|
|
||||||
|
|
||||||
* `<NAME>` — Agent name
|
|
||||||
|
|
||||||
###### **Options:**
|
###### **Options:**
|
||||||
|
|
||||||
|
|
@ -523,85 +462,77 @@ Replaces the agent's whole override entry rather than merging into it: any limit
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## `hivectl agents quota`
|
## `hivectl agent choom`
|
||||||
|
|
||||||
Per-agent disk accounting + optional quotas via btrfs qgroups.
|
Open an interactive Claude session inside this agent's container.
|
||||||
|
|
||||||
Opt-in: enable qgroup accounting, then report per-agent usage or cap an agent. No-op on non-btrfs hosts.
|
A fresh session by default, or resume a prior one. Requires root and a running container.
|
||||||
|
|
||||||
**Usage:** `hivectl agents quota <COMMAND>`
|
**Usage:** `hivectl agent choom [OPTIONS]`
|
||||||
|
|
||||||
|
###### **Options:**
|
||||||
|
|
||||||
|
* `--resume <SESSION>` — Resume a prior claude session by its session id, passed through as `claude --resume <value>` (claude's `--continue` takes no value — it resumes the cwd's latest session, which is the harness's, so choom never uses it; this flag matches the claude flag it maps to). Omit for a fresh blank session. A value is required when the flag is given
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## `hivectl agent quota`
|
||||||
|
|
||||||
|
This agent's disk accounting + optional quota via btrfs qgroups.
|
||||||
|
|
||||||
|
Needs `hivectl quota-enable` run once hive-wide first. No-op on non-btrfs hosts.
|
||||||
|
|
||||||
|
**Usage:** `hivectl agent quota <COMMAND>`
|
||||||
|
|
||||||
###### **Subcommands:**
|
###### **Subcommands:**
|
||||||
|
|
||||||
* `enable` — Enable btrfs qgroup accounting on the agent-state filesystem
|
* `show` — Report this agent's disk usage from btrfs qgroups
|
||||||
* `show` — Report per-agent disk usage from btrfs qgroups (all agents, or one by name)
|
* `set` — Set or clear this agent's disk-usage quota
|
||||||
* `set` — Set or clear an agent's disk-usage quota
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## `hivectl agents quota enable`
|
## `hivectl agent quota show`
|
||||||
|
|
||||||
Enable btrfs qgroup accounting on the agent-state filesystem.
|
Report this agent's disk usage from btrfs qgroups
|
||||||
|
|
||||||
Run once before `show` / `limit`. No-op on non-btrfs hosts.
|
**Usage:** `hivectl agent quota show`
|
||||||
|
|
||||||
**Usage:** `hivectl agents quota enable`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## `hivectl agents quota show`
|
## `hivectl agent quota set`
|
||||||
|
|
||||||
Report per-agent disk usage from btrfs qgroups (all agents, or one by name)
|
Set or clear this agent's disk-usage quota
|
||||||
|
|
||||||
**Usage:** `hivectl agents quota show [NAME]`
|
**Usage:** `hivectl agent quota set <SIZE>`
|
||||||
|
|
||||||
###### **Arguments:**
|
###### **Arguments:**
|
||||||
|
|
||||||
* `<NAME>` — Agent to show (omit for all agents with a state subvolume)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## `hivectl agents quota set`
|
|
||||||
|
|
||||||
Set or clear an agent's disk-usage quota.
|
|
||||||
|
|
||||||
Named `set` rather than `set-quota` because the enclosing `quota` group already carries the noun — `agents quota set iris 5G`. The `set-<noun>` spelling stays for the flat verbs (`set-parent`, `set-limits`), which have no group to inherit it from.
|
|
||||||
|
|
||||||
**Usage:** `hivectl agents quota set <NAME> <SIZE>`
|
|
||||||
|
|
||||||
###### **Arguments:**
|
|
||||||
|
|
||||||
* `<NAME>` — Agent whose state subvolume to limit
|
|
||||||
* `<SIZE>` — Size cap (`5G`, `500M`, `1073741824`) or `none` to clear
|
* `<SIZE>` — Size cap (`5G`, `500M`, `1073741824`) or `none` to clear
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## `hivectl agents subvol`
|
## `hivectl agent subvol`
|
||||||
|
|
||||||
btrfs subvolume management for agent state dirs.
|
btrfs subvolume management for this agent's state dir.
|
||||||
|
|
||||||
Upgrade an existing plain-dir agent's state into a btrfs subvolume so it gains snapshots and per-subvol usage/quota.
|
Upgrade an existing plain-dir agent's state into a btrfs subvolume so it gains snapshots and per-subvol usage/quota.
|
||||||
|
|
||||||
**Usage:** `hivectl agents subvol <COMMAND>`
|
**Usage:** `hivectl agent subvol <COMMAND>`
|
||||||
|
|
||||||
###### **Subcommands:**
|
###### **Subcommands:**
|
||||||
|
|
||||||
* `upgrade` — Convert a plain-dir agent state root into a btrfs subvolume in place, so it gains snapshots and per-subvol usage/quota
|
* `upgrade` — Convert a plain-dir agent state root into a btrfs subvolume in place, so it gains snapshots and per-subvol usage/quota
|
||||||
* `snapshot` — Read-only snapshots of an agent's state subvolume
|
* `snapshot` — Read-only snapshots of this agent's state subvolume
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## `hivectl agents subvol upgrade`
|
## `hivectl agent subvol upgrade`
|
||||||
|
|
||||||
Convert a plain-dir agent state root into a btrfs subvolume in place, so it gains snapshots and per-subvol usage/quota.
|
Convert a plain-dir agent state root into a btrfs subvolume in place, so it gains snapshots and per-subvol usage/quota.
|
||||||
|
|
||||||
Bounces the agent to migrate its state, so it requires `--yes`.
|
Bounces the agent to migrate its state, so it requires `--yes`.
|
||||||
|
|
||||||
**Usage:** `hivectl agents subvol upgrade [OPTIONS] <NAME>`
|
**Usage:** `hivectl agent subvol upgrade [OPTIONS]`
|
||||||
|
|
||||||
###### **Arguments:**
|
|
||||||
|
|
||||||
* `<NAME>` — Agent name (e.g. `damocles`, `iris`)
|
|
||||||
|
|
||||||
###### **Options:**
|
###### **Options:**
|
||||||
|
|
||||||
|
|
@ -609,11 +540,11 @@ Bounces the agent to migrate its state, so it requires `--yes`.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## `hivectl agents subvol snapshot`
|
## `hivectl agent subvol snapshot`
|
||||||
|
|
||||||
Read-only snapshots of an agent's state subvolume
|
Read-only snapshots of this agent's state subvolume
|
||||||
|
|
||||||
**Usage:** `hivectl agents subvol snapshot <COMMAND>`
|
**Usage:** `hivectl agent subvol snapshot <COMMAND>`
|
||||||
|
|
||||||
###### **Subcommands:**
|
###### **Subcommands:**
|
||||||
|
|
||||||
|
|
@ -623,15 +554,11 @@ Read-only snapshots of an agent's state subvolume
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## `hivectl agents subvol snapshot create`
|
## `hivectl agent subvol snapshot create`
|
||||||
|
|
||||||
Create a read-only snapshot (agent must already be a subvolume)
|
Create a read-only snapshot (agent must already be a subvolume)
|
||||||
|
|
||||||
**Usage:** `hivectl agents subvol snapshot create --label <LABEL> <NAME>`
|
**Usage:** `hivectl agent subvol snapshot create --label <LABEL>`
|
||||||
|
|
||||||
###### **Arguments:**
|
|
||||||
|
|
||||||
* `<NAME>` — Agent name (e.g. `damocles`, `iris`)
|
|
||||||
|
|
||||||
###### **Options:**
|
###### **Options:**
|
||||||
|
|
||||||
|
|
@ -639,28 +566,26 @@ Create a read-only snapshot (agent must already be a subvolume)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## `hivectl agents subvol snapshot delete`
|
## `hivectl agent subvol snapshot delete`
|
||||||
|
|
||||||
Delete a snapshot created by `subvol snapshot create`
|
Delete a snapshot created by `subvol snapshot create`
|
||||||
|
|
||||||
**Usage:** `hivectl agents subvol snapshot delete <NAME> <LABEL>`
|
**Usage:** `hivectl agent subvol snapshot delete <LABEL>`
|
||||||
|
|
||||||
###### **Arguments:**
|
###### **Arguments:**
|
||||||
|
|
||||||
* `<NAME>` — Agent name the snapshot belongs to
|
|
||||||
* `<LABEL>` — Snapshot label passed to `subvol snapshot create --label`
|
* `<LABEL>` — Snapshot label passed to `subvol snapshot create --label`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## `hivectl agents subvol snapshot send`
|
## `hivectl agent subvol snapshot send`
|
||||||
|
|
||||||
Export a snapshot to a local file via `btrfs send` (the local-file half of inter-hive migration transport; the cross-hive `ssh ... btrfs receive` leg isn't wired up yet). Also useful standalone as a point-in-time backup: a full send with no `--parent` produces a self-contained archive of the snapshot
|
Export a snapshot to a local file via `btrfs send` (the local-file half of inter-hive migration transport; the cross-hive `ssh ... btrfs receive` leg isn't wired up yet). Also useful standalone as a point-in-time backup: a full send with no `--parent` produces a self-contained archive of the snapshot
|
||||||
|
|
||||||
**Usage:** `hivectl agents subvol snapshot send [OPTIONS] --dest <DEST> <NAME> <LABEL>`
|
**Usage:** `hivectl agent subvol snapshot send [OPTIONS] --dest <DEST> <LABEL>`
|
||||||
|
|
||||||
###### **Arguments:**
|
###### **Arguments:**
|
||||||
|
|
||||||
* `<NAME>` — Agent name the snapshot belongs to
|
|
||||||
* `<LABEL>` — Snapshot label passed to `subvol snapshot create --label`
|
* `<LABEL>` — Snapshot label passed to `subvol snapshot create --label`
|
||||||
|
|
||||||
###### **Options:**
|
###### **Options:**
|
||||||
|
|
@ -670,6 +595,30 @@ Export a snapshot to a local file via `btrfs send` (the local-file half of inter
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## `hivectl list-agents`
|
||||||
|
|
||||||
|
Show all managed agents with their status and technical state.
|
||||||
|
|
||||||
|
Global — not scoped to one agent, so it lives at the top level rather than under `hivectl agent <name>`. Needs the hive-c0re daemon running.
|
||||||
|
|
||||||
|
**Usage:** `hivectl list-agents [OPTIONS]`
|
||||||
|
|
||||||
|
###### **Options:**
|
||||||
|
|
||||||
|
* `--json` — Emit the raw JSON rows instead of the padded table (for scripting). The table is the default human-readable shape
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## `hivectl quota-enable`
|
||||||
|
|
||||||
|
Enable btrfs qgroup accounting on the agent-state filesystem.
|
||||||
|
|
||||||
|
Global one-shot toggle (not per-agent), hence top-level rather than under `hivectl agent <name>`. Idempotent — safe to re-run. Once enabled, `hivectl agent <name> quota show`/`quota set` work.
|
||||||
|
|
||||||
|
**Usage:** `hivectl quota-enable`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## `hivectl approvals`
|
## `hivectl approvals`
|
||||||
|
|
||||||
Operator approval queue: list, approve, or deny pending requests.
|
Operator approval queue: list, approve, or deny pending requests.
|
||||||
|
|
@ -787,24 +736,6 @@ Prints the nix a peer operator pastes into their swarm config to trust and reach
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## `hivectl choom`
|
|
||||||
|
|
||||||
Open an interactive Claude session inside an agent container.
|
|
||||||
|
|
||||||
A fresh session by default, or resume a prior one. Requires root and a running container.
|
|
||||||
|
|
||||||
**Usage:** `hivectl choom [OPTIONS] <NAME>`
|
|
||||||
|
|
||||||
###### **Arguments:**
|
|
||||||
|
|
||||||
* `<NAME>` — Agent name (e.g. `damocles`, `iris`)
|
|
||||||
|
|
||||||
###### **Options:**
|
|
||||||
|
|
||||||
* `--resume <SESSION>` — Resume a prior claude session by its session id, passed through as `claude --resume <value>` (claude's `--continue` takes no value — it resumes the cwd's latest session, which is the harness's, so choom never uses it; this flag matches the claude flag it maps to). Omit for a fresh blank session. A value is required when the flag is given
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## `hivectl stop`
|
## `hivectl stop`
|
||||||
|
|
||||||
Stop containers hive-wide in one operator action.
|
Stop containers hive-wide in one operator action.
|
||||||
|
|
|
||||||
|
|
@ -132,18 +132,19 @@ username updates the password hash in place.
|
||||||
## Agents
|
## Agents
|
||||||
|
|
||||||
Container lifecycle shortcuts that go through the host admin socket.
|
Container lifecycle shortcuts that go through the host admin socket.
|
||||||
Requires the `hive-c0re` daemon to be running.
|
Requires the `hive-c0re` daemon to be running. Everything scoped to a
|
||||||
|
single agent lives under `hivectl agent <name> <verb>` — the name is
|
||||||
|
hoisted onto the parent command, so none of the verbs below repeat it.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
hivectl agents list # roster: every agent's status + technical state
|
hivectl list-agents # roster: every agent's status + technical state
|
||||||
hivectl agents list --json # same data as raw JSON rows (for scripting)
|
hivectl list-agents --json # same data as raw JSON rows (for scripting)
|
||||||
hivectl agents restart iris # stop + start the `iris` container (no rebuild)
|
hivectl agent iris restart # stop + start the `iris` container (no rebuild)
|
||||||
hivectl agents restart-all # stop + start every managed agent container in sequence
|
hivectl agent iris pause # park iris's turn loop, leave the container running
|
||||||
hivectl agents pause iris # park iris's turn loop, leave the container running
|
hivectl agent iris resume # let it drive turns again, draining what queued up
|
||||||
hivectl agents resume iris # let it drive turns again, draining what queued up
|
|
||||||
```
|
```
|
||||||
|
|
||||||
`list` prints a padded table with one row per managed agent —
|
`list-agents` prints a padded table with one row per managed agent —
|
||||||
`NAME STATUS REV PARENT REMIND`. STATUS collapses the health flags
|
`NAME STATUS REV PARENT REMIND`. STATUS collapses the health flags
|
||||||
(`running` / `stopped`, plus ` paused` / ` needs-login` / ` needs-update`
|
(`running` / `stopped`, plus ` paused` / ` needs-login` / ` needs-update`
|
||||||
when set — `paused` is orthogonal to running, see below);
|
when set — `paused` is orthogonal to running, see below);
|
||||||
|
|
@ -153,10 +154,13 @@ pending reminders. It reuses the same per-agent aggregation the dashboard
|
||||||
renders, so the CLI roster and the web UI never drift. `--json` emits the
|
renders, so the CLI roster and the web UI never drift. `--json` emits the
|
||||||
raw rows instead of the table.
|
raw rows instead of the table.
|
||||||
|
|
||||||
`restart` is the manual equivalent of the MCP `restart` tool — useful
|
`agent <name> restart` is the manual equivalent of the MCP `restart`
|
||||||
when you need to kick a container from the host without going through
|
tool — useful when you need to kick a container from the host without
|
||||||
the agent hierarchy. Failures on `restart-all` are collected and
|
going through the agent hierarchy. For more than one agent at once, use
|
||||||
reported at the end rather than aborting mid-run.
|
the top-level `hivectl restart --agents` (or `--agent <name>` repeated) —
|
||||||
|
it rides a single DAG and reports per-target failures at the end rather
|
||||||
|
than aborting mid-run, instead of shelling out to `agent <name> restart`
|
||||||
|
in a loop.
|
||||||
|
|
||||||
`pause` / `resume` are the "stop burning tokens without losing the
|
`pause` / `resume` are the "stop burning tokens without losing the
|
||||||
container" pair. Pausing writes a marker file into the agent's harness
|
container" pair. Pausing writes a marker file into the agent's harness
|
||||||
|
|
@ -175,15 +179,15 @@ resume drains the backlog rather than dropping it. Points worth knowing:
|
||||||
handshake is skipped for a paused agent (it would never answer), which
|
handshake is skipped for a paused agent (it would never answer), which
|
||||||
is safe precisely because the pause check sits at the top of the loop:
|
is safe precisely because the pause check sits at the top of the loop:
|
||||||
a paused agent has no turn in flight to checkpoint.
|
a paused agent has no turn in flight to checkpoint.
|
||||||
- Visible as ` paused` in `agents list`'s STATUS column, as a `paused`
|
- Visible as ` paused` in `list-agents`'s STATUS column, as a `paused`
|
||||||
field on the JSON rows, and as a badge on the dashboard card.
|
field on the JSON rows, and as a badge on the dashboard card.
|
||||||
|
|
||||||
### Per-agent resource limits
|
### Per-agent resource limits
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
hivectl agents set-limits sock --cpu-quota 400% --memory-max 8G
|
hivectl agent sock set-limits --cpu-quota 400% --memory-max 8G
|
||||||
hivectl agents set-limits sock --memory-max 8G # CPU falls back to the hive default
|
hivectl agent sock set-limits --memory-max 8G # CPU falls back to the hive default
|
||||||
hivectl agents set-limits sock --reset # drop all overrides
|
hivectl agent sock set-limits --reset # drop all overrides
|
||||||
```
|
```
|
||||||
|
|
||||||
Overrides the hive-wide `services.hyperhive.agentCpuQuota` /
|
Overrides the hive-wide `services.hyperhive.agentCpuQuota` /
|
||||||
|
|
@ -224,8 +228,8 @@ tells you it needs root, instead of reporting a permission problem with
|
||||||
the state dir.
|
the state dir.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
hivectl choom iris # fresh blank Claude session in iris's container
|
hivectl agent iris choom # fresh blank Claude session in iris's container
|
||||||
hivectl choom iris --resume <session-id> # rejoin a prior session by id
|
hivectl agent iris choom --resume <session-id> # rejoin a prior session by id
|
||||||
```
|
```
|
||||||
|
|
||||||
Bare `choom` starts a fresh blank session. `--resume <value>` passes
|
Bare `choom` starts a fresh blank session. `--resume <value>` passes
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ agents) runs:
|
||||||
losing it; reminders and todo wakes buffer in their channels. The
|
losing it; reminders and todo wakes buffer in their channels. The
|
||||||
check runs before the self-continue slot is consumed, so a pending
|
check runs before the self-continue slot is consumed, so a pending
|
||||||
`request_next_turn` survives the pause. Set it with
|
`request_next_turn` survives the pause. Set it with
|
||||||
`hivectl agents pause <name>` or the dashboard toggle; see
|
`hivectl agent <name> pause` or the dashboard toggle; see
|
||||||
[persistence](persistence.md#-harnesspaused-per-agent).
|
[persistence](persistence.md#-harnesspaused-per-agent).
|
||||||
1. Long-poll `Recv` on its socket. The host-side broker
|
1. Long-poll `Recv` on its socket. The host-side broker
|
||||||
(`broker.rs::recv_blocking_batch`) returns immediately if there's
|
(`broker.rs::recv_blocking_batch`) returns immediately if there's
|
||||||
|
|
|
||||||
|
|
@ -295,7 +295,7 @@ pub async fn graceful_restart(
|
||||||
/// `Reconcile` (nothing to stop). Restart never writes `wanted`, so the
|
/// `Reconcile` (nothing to stop). Restart never writes `wanted`, so the
|
||||||
/// tail `Reconcile` converges each agent to its EXISTING intent — a
|
/// tail `Reconcile` converges each agent to its EXISTING intent — a
|
||||||
/// deliberately-stopped agent stays down. The whole hive-wide
|
/// deliberately-stopped agent stays down. The whole hive-wide
|
||||||
/// `hivectl restart` / `restart-all` is one DAG.
|
/// `hivectl restart` is one DAG.
|
||||||
pub async fn restart_many(
|
pub async fn restart_many(
|
||||||
coord: &Arc<Coordinator>,
|
coord: &Arc<Coordinator>,
|
||||||
agents: &[String],
|
agents: &[String],
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,6 @@ async fn dispatch(req: &HostRequest, coord: Arc<Coordinator>) -> HostResponse {
|
||||||
HostRequest::SetPaused { name, paused } => {
|
HostRequest::SetPaused { name, paused } => {
|
||||||
handle_set_paused(&coord, name, *paused).await
|
handle_set_paused(&coord, name, *paused).await
|
||||||
}
|
}
|
||||||
HostRequest::RestartAll => handle_restart_all(&coord).await?,
|
|
||||||
HostRequest::RestartScoped { scope, graceful } => {
|
HostRequest::RestartScoped { scope, graceful } => {
|
||||||
handle_restart_scoped(&coord, scope, *graceful).await?
|
handle_restart_scoped(&coord, scope, *graceful).await?
|
||||||
}
|
}
|
||||||
|
|
@ -447,7 +446,7 @@ async fn handle_set_resource_limits(
|
||||||
"{name}: CPUQuota={cpu} MemoryMax={mem} — the cgroup cap itself is live now (restart \
|
"{name}: CPUQuota={cpu} MemoryMax={mem} — the cgroup cap itself is live now (restart \
|
||||||
the container if it's running and needs the new cap immediately), but the derived \
|
the container if it's running and needs the new cap immediately), but the derived \
|
||||||
Claude/JSC heap ceiling is baked in at build time, so it needs a REBUILD \
|
Claude/JSC heap ceiling is baked in at build time, so it needs a REBUILD \
|
||||||
(`hivectl agents rebuild {name}`) to actually track this change"
|
(`hivectl agent {name} rebuild`) to actually track this change"
|
||||||
)]))
|
)]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -612,7 +611,7 @@ async fn handle_quota_show(name: Option<&str>) -> Result<HostResponse> {
|
||||||
// varies across btrfs-progs versions).
|
// varies across btrfs-progs versions).
|
||||||
if msg.to_ascii_lowercase().contains("quota not enabled") {
|
if msg.to_ascii_lowercase().contains("quota not enabled") {
|
||||||
return Ok(HostResponse::error(
|
return Ok(HostResponse::error(
|
||||||
"btrfs quota not enabled — run `hivectl agents quota enable` first",
|
"btrfs quota not enabled — run `hivectl quota-enable` first",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
// A plain-dir agent (no subvolume) has no qgroup; note it
|
// A plain-dir agent (no subvolume) has no qgroup; note it
|
||||||
|
|
@ -787,41 +786,11 @@ async fn submit_single(coord: &Arc<Coordinator>, name: &str, verb: Verb) -> Host
|
||||||
HostResponse::queued(vec![id])
|
HostResponse::queued(vec![id])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Restart every container in **one** DAG — a per-agent restart subgraph
|
|
||||||
/// each, running concurrently on their own leases (so unrelated agents'
|
|
||||||
/// restarts overlap while nothing races an in-flight rebuild). Returns
|
|
||||||
/// once queued; per-node progress surfaces on the single DAG.
|
|
||||||
async fn handle_restart_all(coord: &Arc<Coordinator>) -> Result<HostResponse> {
|
|
||||||
tracing::info!("restart-all");
|
|
||||||
let containers = lifecycle::list().await?;
|
|
||||||
let agents: Vec<String> = containers
|
|
||||||
.iter()
|
|
||||||
.filter_map(|a| a.strip_prefix(lifecycle::AGENT_PREFIX).map(str::to_owned))
|
|
||||||
.collect();
|
|
||||||
let queued = if agents.is_empty() {
|
|
||||||
Vec::new()
|
|
||||||
} else {
|
|
||||||
vec![
|
|
||||||
crate::job_queue::submit::restart_many(
|
|
||||||
coord,
|
|
||||||
&agents,
|
|
||||||
false,
|
|
||||||
crate::job_queue::Source::Manual,
|
|
||||||
"manual restart via hivectl restart-all".to_owned(),
|
|
||||||
)
|
|
||||||
.await,
|
|
||||||
]
|
|
||||||
};
|
|
||||||
let mut resp = HostResponse::list(agents);
|
|
||||||
resp.queued_dags = Some(queued);
|
|
||||||
Ok(resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stop the given `agents` (resolved logical names) then `infra` containers
|
/// Stop the given `agents` (resolved logical names) then `infra` containers
|
||||||
/// (`hivectl stop`). Agents go down before infra so they're not mid-request
|
/// (`hivectl stop`). Agents go down before infra so they're not mid-request
|
||||||
/// against a forge/matrix that's already gone. Per-target failures are
|
/// against a forge/matrix that's already gone. Per-target failures are
|
||||||
/// aggregated rather than aborting on the first error, mirroring
|
/// aggregated rather than aborting on the first error, mirroring
|
||||||
/// `handle_restart_all`. Callers resolve the [`LifecycleScope`] to these
|
/// `finish_lifecycle` below. Callers resolve the [`LifecycleScope`] to these
|
||||||
/// explicit name lists up front — this never sees the "all" flag.
|
/// explicit name lists up front — this never sees the "all" flag.
|
||||||
///
|
///
|
||||||
/// Every agent rides the job queue: a `graceful` stop submits the
|
/// Every agent rides the job queue: a `graceful` stop submits the
|
||||||
|
|
@ -1105,7 +1074,7 @@ fn scoped_infra(scope: &LifecycleScope) -> Vec<InfraContainer> {
|
||||||
|
|
||||||
/// Build the aggregated lifecycle response: `ok` with the touched names when
|
/// Build the aggregated lifecycle response: `ok` with the touched names when
|
||||||
/// every target succeeded, otherwise `ok: false` with the joined errors and
|
/// every target succeeded, otherwise `ok: false` with the joined errors and
|
||||||
/// the partial success list (matches `handle_restart_all`).
|
/// the partial success list.
|
||||||
fn finish_lifecycle(ok_items: Vec<String>, errors: &[String]) -> HostResponse {
|
fn finish_lifecycle(ok_items: Vec<String>, errors: &[String]) -> HostResponse {
|
||||||
if errors.is_empty() {
|
if errors.is_empty() {
|
||||||
HostResponse::list(ok_items)
|
HostResponse::list(ok_items)
|
||||||
|
|
|
||||||
|
|
@ -112,10 +112,6 @@ pub enum HostRequest {
|
||||||
/// `true` pauses, `false` resumes. Idempotent either way.
|
/// `true` pauses, `false` resumes. Idempotent either way.
|
||||||
paused: bool,
|
paused: bool,
|
||||||
},
|
},
|
||||||
/// Stop and restart all managed containers in sequence. Convenience
|
|
||||||
/// wrapper for `hivectl agents restart-all`; iterates the live
|
|
||||||
/// container list and restarts each one.
|
|
||||||
RestartAll,
|
|
||||||
/// Restart containers hive-wide (`hivectl restart`), scoped like
|
/// Restart containers hive-wide (`hivectl restart`), scoped like
|
||||||
/// `Stop`/`Start`. Each targeted agent rides exactly one DAG
|
/// `Stop`/`Start`. Each targeted agent rides exactly one DAG
|
||||||
/// server-side — the `Restart` template (mechanical stop + reconcile),
|
/// server-side — the `Restart` template (mechanical stop + reconcile),
|
||||||
|
|
@ -146,7 +142,7 @@ pub enum HostRequest {
|
||||||
/// persistent state dir under the agents root. Answered daemon-side
|
/// persistent state dir under the agents root. Answered daemon-side
|
||||||
/// because that root is `0700 hive-core`: a client stat-ing it
|
/// because that root is `0700 hive-core`: a client stat-ing it
|
||||||
/// without root gets EACCES, so the pre-flight "does this agent
|
/// without root gets EACCES, so the pre-flight "does this agent
|
||||||
/// exist?" guard in front of `hivectl choom` / `agents subvol` used
|
/// exist?" guard in front of `hivectl agent <name> choom` / `subvol` used
|
||||||
/// to fail with a permission error instead of an answer. The daemon
|
/// to fail with a permission error instead of an answer. The daemon
|
||||||
/// already runs as the owning user and does the same check for its
|
/// already runs as the owning user and does the same check for its
|
||||||
/// own provisioning paths. Result: [`HostResponse::agent_exists`].
|
/// own provisioning paths. Result: [`HostResponse::agent_exists`].
|
||||||
|
|
@ -157,7 +153,7 @@ pub enum HostRequest {
|
||||||
AgentExists { name: Ident },
|
AgentExists { name: Ident },
|
||||||
/// List managed agents with their full status + technical state
|
/// List managed agents with their full status + technical state
|
||||||
/// (running / needs-login / needs-update / deployed sha / parent /
|
/// (running / needs-login / needs-update / deployed sha / parent /
|
||||||
/// pending reminders) — the `hivectl agents list` roster view.
|
/// pending reminders) — the `hivectl list-agents` roster view.
|
||||||
/// Reuses the dashboard's per-agent `ContainerView` aggregation.
|
/// Reuses the dashboard's per-agent `ContainerView` aggregation.
|
||||||
AgentStatus,
|
AgentStatus,
|
||||||
/// Report this hive's canonical DNS domain
|
/// Report this hive's canonical DNS domain
|
||||||
|
|
@ -309,43 +305,46 @@ pub enum HostRequest {
|
||||||
/// the agent's `gh` wrapper / git credential helper — no rebuild needed.
|
/// the agent's `gh` wrapper / git credential helper — no rebuild needed.
|
||||||
SetAgentGithubToken { agent: Ident, token: String },
|
SetAgentGithubToken { agent: Ident, token: String },
|
||||||
/// Turn on btrfs qgroup accounting on the agent-state filesystem, via
|
/// Turn on btrfs qgroup accounting on the agent-state filesystem, via
|
||||||
/// the privileged helper. Daemon-side equivalent of `hivectl agents quota
|
/// the privileged helper. Daemon-side equivalent of `hivectl
|
||||||
/// enable`. Returns advisory lines in [`HostResponse::messages`].
|
/// quota-enable`. Returns advisory lines in [`HostResponse::messages`].
|
||||||
QuotaEnable,
|
QuotaEnable,
|
||||||
/// Set (or, with `limit = None`, clear) an agent's btrfs disk quota via
|
/// Set (or, with `limit = None`, clear) an agent's btrfs disk quota via
|
||||||
/// the privileged helper. Daemon-side equivalent of `hivectl agents quota
|
/// the privileged helper. Daemon-side equivalent of `hivectl agent
|
||||||
/// set`; the size string is parsed to bytes client-side. Returns a
|
/// <name> quota set`; the size string is parsed to bytes client-side.
|
||||||
/// bare success — the client prints the confirmation from the value it
|
/// Returns a bare success — the client prints the confirmation from
|
||||||
/// sent.
|
/// the value it sent.
|
||||||
QuotaLimit {
|
QuotaLimit {
|
||||||
name: Ident,
|
name: Ident,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
limit: Option<u64>,
|
limit: Option<u64>,
|
||||||
},
|
},
|
||||||
/// Report per-agent btrfs qgroup usage (`hivectl agents quota show [name]`).
|
/// Report btrfs qgroup usage (`hivectl agent <name> quota show`, or all
|
||||||
/// The daemon resolves the agent set (all kept state dirs when `name`
|
/// kept state dirs when `name` is absent — no CLI path reaches the
|
||||||
/// is absent) and reads each subvolume's referenced/exclusive usage via
|
/// absent case today, but the daemon still supports it). The daemon
|
||||||
/// the privileged helper. Result rows land in [`HostResponse::quota`];
|
/// resolves the agent set and reads each subvolume's referenced/
|
||||||
/// a "btrfs quota not enabled" error short-circuits the whole sweep as a
|
/// exclusive usage via the privileged helper. Result rows land in
|
||||||
/// plain [`HostResponse::error`] so the client can print the enable hint.
|
/// [`HostResponse::quota`]; a "btrfs quota not enabled" error
|
||||||
|
/// short-circuits the whole sweep as a plain [`HostResponse::error`]
|
||||||
|
/// so the client can print the enable hint.
|
||||||
QuotaShow {
|
QuotaShow {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
name: Option<Ident>,
|
name: Option<Ident>,
|
||||||
},
|
},
|
||||||
/// Migrate an agent's plain state dir to a btrfs subvolume via the
|
/// Migrate an agent's plain state dir to a btrfs subvolume via the
|
||||||
/// privileged helper (`hivectl agents subvol upgrade`). The agent MUST already
|
/// privileged helper (`hivectl agent <name> subvol upgrade`). The agent
|
||||||
/// be stopped — the client orchestrates stop → this → start. Returns a
|
/// MUST already be stopped — the client orchestrates stop → this →
|
||||||
/// bare success; the client prints its own progress lines.
|
/// start. Returns a bare success; the client prints its own progress
|
||||||
|
/// lines.
|
||||||
UpgradeSubvolume { name: Ident },
|
UpgradeSubvolume { name: Ident },
|
||||||
/// Create a read-only btrfs snapshot of an agent's state subvolume
|
/// Create a read-only btrfs snapshot of an agent's state subvolume
|
||||||
/// (`hivectl agents subvol snapshot create`). `label` is validated client-side
|
/// (`hivectl agent <name> subvol snapshot create`). `label` is
|
||||||
/// AND by hive-priv. Returns the snapshot's host path in
|
/// validated client-side AND by hive-priv. Returns the snapshot's
|
||||||
/// [`HostResponse::messages`].
|
/// host path in [`HostResponse::messages`].
|
||||||
SnapshotSubvolume { name: Ident, label: String },
|
SnapshotSubvolume { name: Ident, label: String },
|
||||||
/// Delete a snapshot created by `SnapshotSubvolume` (`hivectl agents subvol
|
/// Delete a snapshot created by `SnapshotSubvolume` (`hivectl agent <name> subvol
|
||||||
/// snapshot delete`). Bare success; the client prints the confirmation.
|
/// snapshot delete`). Bare success; the client prints the confirmation.
|
||||||
DeleteSnapshot { name: Ident, label: String },
|
DeleteSnapshot { name: Ident, label: String },
|
||||||
/// Export a snapshot to a local file via `btrfs send` (`hivectl agents subvol
|
/// Export a snapshot to a local file via `btrfs send` (`hivectl agent <name> subvol
|
||||||
/// snapshot send`). `dest` is a bare filename (hive-priv rejects paths);
|
/// snapshot send`). `dest` is a bare filename (hive-priv rejects paths);
|
||||||
/// `parent` names an optional parent snapshot for an incremental send.
|
/// `parent` names an optional parent snapshot for an incremental send.
|
||||||
/// Returns the written file's host path in [`HostResponse::messages`].
|
/// Returns the written file's host path in [`HostResponse::messages`].
|
||||||
|
|
|
||||||
|
|
@ -1273,7 +1273,7 @@ async fn snapshot_agent_subvolume(
|
||||||
let agent_root = PathBuf::from(AGENT_STATE_ROOT).join(agent_name);
|
let agent_root = PathBuf::from(AGENT_STATE_ROOT).join(agent_name);
|
||||||
if !is_btrfs_subvolume(&agent_root) {
|
if !is_btrfs_subvolume(&agent_root) {
|
||||||
bail!(
|
bail!(
|
||||||
"{} is not a btrfs subvolume — nothing to snapshot (run `hivectl agents subvol upgrade` first)",
|
"{} is not a btrfs subvolume — nothing to snapshot (run `hivectl agent <name> subvol upgrade` first)",
|
||||||
agent_root.display()
|
agent_root.display()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -364,10 +364,10 @@ pub struct ContainerInfo {
|
||||||
/// One row in a `HostRequest::AgentStatus` result — the operator-CLI
|
/// One row in a `HostRequest::AgentStatus` result — the operator-CLI
|
||||||
/// projection of the dashboard's per-agent `ContainerView`. Carries the
|
/// projection of the dashboard's per-agent `ContainerView`. Carries the
|
||||||
/// agent's running/health flags plus the technical state an operator
|
/// agent's running/health flags plus the technical state an operator
|
||||||
/// wants in a roster overview (`hivectl agents list`).
|
/// wants in a roster overview (`hivectl list-agents`).
|
||||||
//
|
//
|
||||||
// Four orthogonal, independently-observed facts about one agent, each
|
// Four orthogonal, independently-observed facts about one agent, each
|
||||||
// rendered as its own column/token by `hivectl agents list` and read
|
// rendered as its own column/token by `hivectl list-agents` and read
|
||||||
// individually by `--json` consumers. Any combination is meaningful
|
// individually by `--json` consumers. Any combination is meaningful
|
||||||
// (a stopped agent can be paused and need an update), so folding them
|
// (a stopped agent can be paused and need an update), so folding them
|
||||||
// into a state machine or nested flag structs would only add
|
// into a state machine or nested flag structs would only add
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,18 @@
|
||||||
//! `hivectl agents` — everything scoped to a managed agent: container
|
//! `hivectl agent <name>` — everything scoped to ONE managed agent:
|
||||||
//! lifecycle over the host admin socket
|
//! container lifecycle over the host admin socket (restart/pause/resume/
|
||||||
//! (list/restart/restart-all/pause/resume/spawn/kill/destroy/rebuild/
|
//! spawn/kill/destroy/rebuild/set-parent/set-limits/choom), plus the
|
||||||
//! set-parent/set-limits), plus the `quota` and `subvol` groups, whose
|
//! `quota` and `subvol` groups, whose handlers live in their own modules.
|
||||||
//! handlers live in their own modules.
|
//! `agents_list` (`hivectl list-agents`) is the one genuinely hive-wide
|
||||||
|
//! read that lives in this module too since it shares the same daemon
|
||||||
|
//! request as everything else here, even though it's dispatched from a
|
||||||
|
//! top-level `Cmd` variant, not `AgentCmd`.
|
||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use anyhow::{Context as _, Result, bail};
|
use anyhow::{Context as _, Result, bail};
|
||||||
use hive_host_sock::HostRequest;
|
use hive_host_sock::HostRequest;
|
||||||
|
|
||||||
use crate::cli::{AgentsCmd, QuotaCmd};
|
use crate::cli::{AgentCmd, AgentQuotaCmd};
|
||||||
use crate::dag_progress::wait_for_dags;
|
use crate::dag_progress::wait_for_dags;
|
||||||
use crate::util::render;
|
use crate::util::render;
|
||||||
|
|
||||||
|
|
@ -33,11 +36,11 @@ async fn agents_restart(socket: &Path, name: &str, no_wait: bool) -> Result<()>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `hivectl agents list` — fetch the per-agent status roster from the
|
/// `hivectl list-agents` — fetch the per-agent status roster from the
|
||||||
/// daemon (`HostRequest::AgentStatus`) and render it as a padded table,
|
/// daemon (`HostRequest::AgentStatus`) and render it as a padded table,
|
||||||
/// or the raw JSON rows with `--json`. Reuses the dashboard's
|
/// or the raw JSON rows with `--json`. Reuses the dashboard's
|
||||||
/// `ContainerView` aggregation, so the CLI and the web UI never drift.
|
/// `ContainerView` aggregation, so the CLI and the web UI never drift.
|
||||||
async fn agents_list(socket: &Path, json: bool) -> Result<()> {
|
pub(crate) async fn agents_list(socket: &Path, json: bool) -> Result<()> {
|
||||||
let resp = crate::client::request(socket, hive_host_sock::HostRequest::AgentStatus)
|
let resp = crate::client::request(socket, hive_host_sock::HostRequest::AgentStatus)
|
||||||
.await
|
.await
|
||||||
.with_context(|| format!("connect to daemon socket {}", socket.display()))?;
|
.with_context(|| format!("connect to daemon socket {}", socket.display()))?;
|
||||||
|
|
@ -112,28 +115,7 @@ async fn agents_list(socket: &Path, json: bool) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn agents_restart_all(socket: &Path, no_wait: bool) -> Result<()> {
|
/// `hivectl agent <name> pause|resume` — flip the agent's pause marker. Not a
|
||||||
let resp = crate::client::request(socket, hive_host_sock::HostRequest::RestartAll)
|
|
||||||
.await
|
|
||||||
.with_context(|| format!("connect to daemon socket {}", socket.display()))?;
|
|
||||||
let agents = resp.agents.as_deref().unwrap_or(&[]);
|
|
||||||
if agents.is_empty() {
|
|
||||||
println!("restart-all: no managed containers found");
|
|
||||||
} else {
|
|
||||||
for a in agents {
|
|
||||||
println!("restart queued: {a}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !resp.ok {
|
|
||||||
bail!(
|
|
||||||
"restart-all: {}",
|
|
||||||
resp.error.as_deref().unwrap_or("unknown error")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
wait_for_dags(socket, resp.queued_dags.unwrap_or_default(), no_wait).await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `hivectl agents pause|resume` — flip the agent's pause marker. Not a
|
|
||||||
/// DAG, so there's nothing to wait on: the daemon writes the marker and
|
/// DAG, so there's nothing to wait on: the daemon writes the marker and
|
||||||
/// the harness picks it up on its next poll.
|
/// the harness picks it up on its next poll.
|
||||||
async fn set_paused(socket: &Path, name: &str, paused: bool) -> Result<()> {
|
async fn set_paused(socket: &Path, name: &str, paused: bool) -> Result<()> {
|
||||||
|
|
@ -159,41 +141,36 @@ async fn set_paused(socket: &Path, name: &str, paused: bool) -> Result<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Dispatch `hivectl agents <verb>` — container lifecycle over the host
|
/// Dispatch `hivectl agent <name> <verb>` — container lifecycle over the
|
||||||
/// admin socket.
|
/// host admin socket, all scoped to the single `name` hoisted from the
|
||||||
pub(crate) async fn run_agents(socket: &Path, cmd: AgentsCmd) -> Result<()> {
|
/// parent command.
|
||||||
|
pub(crate) async fn run_agent(socket: &Path, name: &str, cmd: AgentCmd) -> Result<()> {
|
||||||
match cmd {
|
match cmd {
|
||||||
AgentsCmd::List { json } => agents_list(socket, json).await,
|
AgentCmd::Restart { no_wait } => agents_restart(socket, name, no_wait).await,
|
||||||
AgentsCmd::Restart { name, no_wait } => agents_restart(socket, &name, no_wait).await,
|
AgentCmd::Pause => set_paused(socket, name, true).await,
|
||||||
AgentsCmd::RestartAll { no_wait } => agents_restart_all(socket, no_wait).await,
|
AgentCmd::Resume => set_paused(socket, name, false).await,
|
||||||
AgentsCmd::Pause { name } => set_paused(socket, &name, true).await,
|
AgentCmd::Spawn => {
|
||||||
AgentsCmd::Resume { name } => set_paused(socket, &name, false).await,
|
let name = crate::util::parse_ident(name)?;
|
||||||
AgentsCmd::Spawn { name } => {
|
|
||||||
let name = crate::util::parse_ident(&name)?;
|
|
||||||
render(crate::client::request(socket, HostRequest::Spawn { name }).await?)
|
render(crate::client::request(socket, HostRequest::Spawn { name }).await?)
|
||||||
}
|
}
|
||||||
AgentsCmd::RequestSpawn { name } => {
|
AgentCmd::RequestSpawn => {
|
||||||
let name = crate::util::parse_ident(&name)?;
|
let name = crate::util::parse_ident(name)?;
|
||||||
render(crate::client::request(socket, HostRequest::RequestSpawn { name }).await?)
|
render(crate::client::request(socket, HostRequest::RequestSpawn { name }).await?)
|
||||||
}
|
}
|
||||||
AgentsCmd::Kill { name } => {
|
AgentCmd::Kill => {
|
||||||
let name = crate::util::parse_ident(&name)?;
|
let name = crate::util::parse_ident(name)?;
|
||||||
render(crate::client::request(socket, HostRequest::Kill { name }).await?)
|
render(crate::client::request(socket, HostRequest::Kill { name }).await?)
|
||||||
}
|
}
|
||||||
AgentsCmd::Destroy { name, purge } => {
|
AgentCmd::Destroy { purge } => {
|
||||||
let name = crate::util::parse_ident(&name)?;
|
let name = crate::util::parse_ident(name)?;
|
||||||
render(crate::client::request(socket, HostRequest::Destroy { name, purge }).await?)
|
render(crate::client::request(socket, HostRequest::Destroy { name, purge }).await?)
|
||||||
}
|
}
|
||||||
AgentsCmd::Rebuild { name } => {
|
AgentCmd::Rebuild => {
|
||||||
let name = crate::util::parse_ident(&name)?;
|
let name = crate::util::parse_ident(name)?;
|
||||||
render(crate::client::request(socket, HostRequest::Rebuild { name }).await?)
|
render(crate::client::request(socket, HostRequest::Rebuild { name }).await?)
|
||||||
}
|
}
|
||||||
AgentsCmd::SetParent {
|
AgentCmd::SetParent { parent, root } => {
|
||||||
child,
|
let child = crate::util::parse_ident(name)?;
|
||||||
parent,
|
|
||||||
root,
|
|
||||||
} => {
|
|
||||||
let child = crate::util::parse_ident(&child)?;
|
|
||||||
let new_parent = if root {
|
let new_parent = if root {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -204,16 +181,15 @@ pub(crate) async fn run_agents(socket: &Path, cmd: AgentsCmd) -> Result<()> {
|
||||||
.await?,
|
.await?,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
AgentsCmd::SetLimits {
|
AgentCmd::SetLimits {
|
||||||
name,
|
|
||||||
cpu_quota,
|
cpu_quota,
|
||||||
memory_max,
|
memory_max,
|
||||||
reset,
|
reset,
|
||||||
} => {
|
} => {
|
||||||
let name = crate::util::parse_ident(&name)?;
|
let name = crate::util::parse_ident(name)?;
|
||||||
// `--reset` is the only way to reach an all-`None` request;
|
// `--reset` is the only way to reach an all-`None` request;
|
||||||
// clap rejects a bare `set-limits <name>` with neither flag,
|
// clap rejects a bare `set-limits` with neither flag, so a
|
||||||
// so a forgotten value can't silently clear the overrides.
|
// forgotten value can't silently clear the overrides.
|
||||||
let (cpu_quota, memory_max) = if reset {
|
let (cpu_quota, memory_max) = if reset {
|
||||||
(None, None)
|
(None, None)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -231,13 +207,16 @@ pub(crate) async fn run_agents(socket: &Path, cmd: AgentsCmd) -> Result<()> {
|
||||||
.await?,
|
.await?,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// `quota` and `subvol` keep their own modules — this arm is just
|
AgentCmd::Choom { resume_session } => {
|
||||||
// the reparenting glue that moved them under `agents`.
|
crate::choom::choom(socket, name, resume_session.as_deref()).await
|
||||||
AgentsCmd::Quota { cmd } => match cmd {
|
}
|
||||||
QuotaCmd::Enable => crate::quota::quota_enable(socket).await,
|
// `quota` and `subvol` keep their own modules — these arms are
|
||||||
QuotaCmd::Show { name } => crate::quota::quota_show(socket, name.as_deref()).await,
|
// just the reparenting glue that hoists `name` in from the
|
||||||
QuotaCmd::Set { name, size } => crate::quota::quota_limit(socket, &name, &size).await,
|
// parent `agent <name>` command.
|
||||||
|
AgentCmd::Quota { cmd } => match cmd {
|
||||||
|
AgentQuotaCmd::Show => crate::quota::quota_show(socket, name).await,
|
||||||
|
AgentQuotaCmd::Set { size } => crate::quota::quota_limit(socket, name, &size).await,
|
||||||
},
|
},
|
||||||
AgentsCmd::Subvol { cmd } => crate::subvol::dispatch_subvol(socket, cmd).await,
|
AgentCmd::Subvol { cmd } => crate::subvol::dispatch_subvol(socket, name, cmd).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
//! `hivectl choom <agent>` — drop into an interactive Claude session inside
|
//! `hivectl agent <name> choom` — drop into an interactive Claude session inside
|
||||||
//! an agent container by exec-ing `machinectl shell` running claude as the
|
//! an agent container by exec-ing `machinectl shell` running claude as the
|
||||||
//! agent user, mirroring the harness's per-turn claude invocation.
|
//! agent user, mirroring the harness's per-turn claude invocation.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,9 @@ recovery / debugging verbs.\
|
||||||
)]
|
)]
|
||||||
pub struct Cli {
|
pub struct Cli {
|
||||||
/// Path to the hive-c0re host admin socket, used by the daemon-assisted
|
/// Path to the hive-c0re host admin socket, used by the daemon-assisted
|
||||||
/// verbs (`agents`, `stop`, `start`). Global: accepted before or after
|
/// verbs (`agent`, `list-agents`, `stop`, `start`). Global: accepted
|
||||||
/// the subcommand. Verbs that don't talk to the daemon ignore it.
|
/// before or after the subcommand. Verbs that don't talk to the daemon
|
||||||
|
/// ignore it.
|
||||||
#[arg(long, global = true, default_value = DEFAULT_HOST_SOCKET)]
|
#[arg(long, global = true, default_value = DEFAULT_HOST_SOCKET)]
|
||||||
pub(crate) socket: PathBuf,
|
pub(crate) socket: PathBuf,
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
|
|
@ -61,14 +62,36 @@ pub enum Cmd {
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
cmd: GatewayCmd,
|
cmd: GatewayCmd,
|
||||||
},
|
},
|
||||||
/// Agent container management.
|
/// Lifecycle actions on ONE managed agent container. Needs the
|
||||||
|
/// hive-c0re daemon running.
|
||||||
///
|
///
|
||||||
/// Lifecycle actions on managed agent containers. Needs the hive-c0re
|
/// Everything here targets a single named agent (`hivectl agent foo
|
||||||
/// daemon running.
|
/// restart`, `hivectl agent foo choom`, …) — anything that acts
|
||||||
Agents {
|
/// hive-wide lives at the top level instead (`list-agents`,
|
||||||
|
/// `restart`/`stop`/`start` with a scope, `quota-enable`).
|
||||||
|
Agent {
|
||||||
|
/// Agent name (e.g. `damocles`, `iris`).
|
||||||
|
name: String,
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
cmd: AgentsCmd,
|
cmd: AgentCmd,
|
||||||
},
|
},
|
||||||
|
/// Show all managed agents with their status and technical state.
|
||||||
|
///
|
||||||
|
/// Global — not scoped to one agent, so it lives at the top level
|
||||||
|
/// rather than under `hivectl agent <name>`. Needs the hive-c0re
|
||||||
|
/// daemon running.
|
||||||
|
ListAgents {
|
||||||
|
/// Emit the raw JSON rows instead of the padded table (for
|
||||||
|
/// scripting). The table is the default human-readable shape.
|
||||||
|
#[arg(long)]
|
||||||
|
json: bool,
|
||||||
|
},
|
||||||
|
/// Enable btrfs qgroup accounting on the agent-state filesystem.
|
||||||
|
///
|
||||||
|
/// Global one-shot toggle (not per-agent), hence top-level rather than
|
||||||
|
/// under `hivectl agent <name>`. Idempotent — safe to re-run. Once
|
||||||
|
/// enabled, `hivectl agent <name> quota show`/`quota set` work.
|
||||||
|
QuotaEnable,
|
||||||
/// Operator approval queue: list, approve, or deny pending requests.
|
/// Operator approval queue: list, approve, or deny pending requests.
|
||||||
///
|
///
|
||||||
/// Needs the hive-c0re daemon running.
|
/// Needs the hive-c0re daemon running.
|
||||||
|
|
@ -98,22 +121,6 @@ pub enum Cmd {
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
wg_endpoint: Option<String>,
|
wg_endpoint: Option<String>,
|
||||||
},
|
},
|
||||||
/// Open an interactive Claude session inside an agent container.
|
|
||||||
///
|
|
||||||
/// A fresh session by default, or resume a prior one. Requires root
|
|
||||||
/// and a running container.
|
|
||||||
Choom {
|
|
||||||
/// Agent name (e.g. `damocles`, `iris`).
|
|
||||||
name: String,
|
|
||||||
/// Resume a prior claude session by its session id, passed
|
|
||||||
/// through as `claude --resume <value>` (claude's `--continue`
|
|
||||||
/// takes no value — it resumes the cwd's latest session, which
|
|
||||||
/// is the harness's, so choom never uses it; this flag matches
|
|
||||||
/// the claude flag it maps to). Omit for a fresh blank session.
|
|
||||||
/// A value is required when the flag is given.
|
|
||||||
#[arg(long = "resume", value_name = "SESSION")]
|
|
||||||
resume_session: Option<String>,
|
|
||||||
},
|
|
||||||
/// Stop containers hive-wide in one operator action.
|
/// Stop containers hive-wide in one operator action.
|
||||||
///
|
///
|
||||||
/// Bare `hivectl stop` stops everything; scope flags narrow it to
|
/// Bare `hivectl stop` stops everything; scope flags narrow it to
|
||||||
|
|
@ -435,128 +442,68 @@ pub enum WgCmd {
|
||||||
Status,
|
Status,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
|
||||||
pub enum QuotaCmd {
|
|
||||||
/// Enable btrfs qgroup accounting on the agent-state filesystem.
|
|
||||||
///
|
|
||||||
/// Run once before `show` / `limit`. No-op on non-btrfs hosts.
|
|
||||||
Enable,
|
|
||||||
/// Report per-agent disk usage from btrfs qgroups (all agents, or one
|
|
||||||
/// by name).
|
|
||||||
Show {
|
|
||||||
/// Agent to show (omit for all agents with a state subvolume).
|
|
||||||
name: Option<String>,
|
|
||||||
},
|
|
||||||
/// Set or clear an agent's disk-usage quota.
|
|
||||||
///
|
|
||||||
/// Named `set` rather than `set-quota` because the enclosing `quota`
|
|
||||||
/// group already carries the noun — `agents quota set iris 5G`. The
|
|
||||||
/// `set-<noun>` spelling stays for the flat verbs (`set-parent`,
|
|
||||||
/// `set-limits`), which have no group to inherit it from.
|
|
||||||
Set {
|
|
||||||
/// Agent whose state subvolume to limit.
|
|
||||||
name: String,
|
|
||||||
/// Size cap (`5G`, `500M`, `1073741824`) or `none` to clear.
|
|
||||||
size: String,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default host admin socket path. Shared with `hive-c0re`'s `main.rs`
|
// Default host admin socket path. Shared with `hive-c0re`'s `main.rs`
|
||||||
// default via `hive_host_sock::HOST_SOCKET` — the daemon binds there
|
// default via `hive_host_sock::HOST_SOCKET` — the daemon binds there
|
||||||
// and `hivectl agents` connects to it.
|
// and `hivectl` connects to it.
|
||||||
pub(crate) use hive_host_sock::HOST_SOCKET as DEFAULT_HOST_SOCKET;
|
pub(crate) use hive_host_sock::HOST_SOCKET as DEFAULT_HOST_SOCKET;
|
||||||
|
|
||||||
|
/// Verbs under `hivectl agent <name> <verb>` — every one of these targets
|
||||||
|
/// the single agent named on the parent command, so none of them carry
|
||||||
|
/// their own `name` field.
|
||||||
#[derive(Subcommand)]
|
#[derive(Subcommand)]
|
||||||
pub enum AgentsCmd {
|
pub enum AgentCmd {
|
||||||
/// Show all managed agents with their status and technical state.
|
/// Stop and start this agent container without rebuilding config.
|
||||||
List {
|
|
||||||
/// Emit the raw JSON rows instead of the padded table (for
|
|
||||||
/// scripting). The table is the default human-readable shape.
|
|
||||||
#[arg(long)]
|
|
||||||
json: bool,
|
|
||||||
},
|
|
||||||
/// Stop and start a single agent container without rebuilding config.
|
|
||||||
Restart {
|
Restart {
|
||||||
/// Agent name (e.g. `damocles`, `ruth`).
|
|
||||||
name: String,
|
|
||||||
/// Return immediately after the restart DAG is queued.
|
/// Return immediately after the restart DAG is queued.
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
no_wait: bool,
|
no_wait: bool,
|
||||||
},
|
},
|
||||||
/// Restart all managed agent containers.
|
/// Park this agent's turn loop, leaving the container running.
|
||||||
RestartAll {
|
|
||||||
/// Return immediately after the restart DAGs are queued.
|
|
||||||
#[arg(long)]
|
|
||||||
no_wait: bool,
|
|
||||||
},
|
|
||||||
/// Park an agent's turn loop, leaving the container running.
|
|
||||||
///
|
///
|
||||||
/// The harness stops driving turns but keeps serving its web UI and
|
/// The harness stops driving turns but keeps serving its web UI and
|
||||||
/// MCP daemons, so the container, its mounts and its warm caches stay
|
/// MCP daemons, so the container, its mounts and its warm caches stay
|
||||||
/// up while it burns no tokens. Inbox messages queue unacked and the
|
/// up while it burns no tokens. Inbox messages queue unacked and the
|
||||||
/// backlog drains on `resume`. Sticky: it survives a restart, and
|
/// backlog drains on `resume`. Sticky: it survives a restart, and
|
||||||
/// pausing a stopped agent makes it come up paused.
|
/// pausing a stopped agent makes it come up paused.
|
||||||
Pause {
|
Pause,
|
||||||
/// Agent name.
|
/// Resume this paused agent — it drains whatever queued up while parked.
|
||||||
name: String,
|
Resume,
|
||||||
},
|
/// Spawn this agent container directly, bypassing the approval queue.
|
||||||
/// Resume a paused agent — it drains whatever queued up while parked.
|
|
||||||
Resume {
|
|
||||||
/// Agent name.
|
|
||||||
name: String,
|
|
||||||
},
|
|
||||||
/// Spawn a new agent container directly, bypassing the approval queue.
|
|
||||||
///
|
///
|
||||||
/// Operator-on-the-host only; use `request-spawn` for an approval-gated
|
/// Operator-on-the-host only; use `request-spawn` for an approval-gated
|
||||||
/// spawn.
|
/// spawn.
|
||||||
Spawn {
|
Spawn,
|
||||||
/// Agent name (e.g. `iris`).
|
|
||||||
name: String,
|
|
||||||
},
|
|
||||||
/// Queue a spawn request for operator approval.
|
/// Queue a spawn request for operator approval.
|
||||||
RequestSpawn {
|
RequestSpawn,
|
||||||
/// Agent name.
|
/// Stop this managed container (graceful).
|
||||||
name: String,
|
Kill,
|
||||||
},
|
/// Tear down this sub-agent container, keeping its state by default.
|
||||||
/// Stop a managed container (graceful).
|
/// No undo.
|
||||||
Kill {
|
|
||||||
/// Agent name.
|
|
||||||
name: String,
|
|
||||||
},
|
|
||||||
/// Tear down a sub-agent container, keeping its state by default. No
|
|
||||||
/// undo.
|
|
||||||
Destroy {
|
Destroy {
|
||||||
/// Agent name.
|
|
||||||
name: String,
|
|
||||||
/// Also wipe the agent's state dirs (config + creds + notes).
|
/// Also wipe the agent's state dirs (config + creds + notes).
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
purge: bool,
|
purge: bool,
|
||||||
},
|
},
|
||||||
/// Apply pending config to a managed container.
|
/// Apply pending config to this managed container.
|
||||||
Rebuild {
|
Rebuild,
|
||||||
/// Agent name.
|
/// Move this agent in the topology tree — under a new parent, or to
|
||||||
name: String,
|
/// root.
|
||||||
},
|
|
||||||
/// Move an agent in the topology tree — under a new parent, or to root.
|
|
||||||
SetParent {
|
SetParent {
|
||||||
/// Agent to move.
|
|
||||||
child: String,
|
|
||||||
/// New parent agent name. Mutually exclusive with `--root`.
|
/// New parent agent name. Mutually exclusive with `--root`.
|
||||||
#[arg(long, conflicts_with = "root", required_unless_present = "root")]
|
#[arg(long, conflicts_with = "root", required_unless_present = "root")]
|
||||||
parent: Option<String>,
|
parent: Option<String>,
|
||||||
/// Promote `child` to root (no parent).
|
/// Promote this agent to root (no parent).
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
root: bool,
|
root: bool,
|
||||||
},
|
},
|
||||||
/// Declare an agent's CPU/memory limits, overriding the hive-wide defaults.
|
/// Declare this agent's CPU/memory limits, overriding the hive-wide
|
||||||
|
/// defaults.
|
||||||
///
|
///
|
||||||
/// Replaces the agent's whole override entry rather than merging into
|
/// Replaces the agent's whole override entry rather than merging into
|
||||||
/// it: any limit you don't pass returns to the hive-wide default. To
|
/// it: any limit you don't pass returns to the hive-wide default. To
|
||||||
/// change one and keep the other, pass both. Disk is a separate
|
/// change one and keep the other, pass both. Disk is a separate
|
||||||
/// resource with its own group — see `agents quota`.
|
/// resource with its own group — see `quota`.
|
||||||
SetLimits {
|
SetLimits {
|
||||||
/// Agent name.
|
|
||||||
name: String,
|
|
||||||
/// systemd `CPUQuota=` value, e.g. `400%` (100% = one full core).
|
/// systemd `CPUQuota=` value, e.g. `400%` (100% = one full core).
|
||||||
#[arg(long, conflicts_with = "reset")]
|
#[arg(long, conflicts_with = "reset")]
|
||||||
cpu_quota: Option<String>,
|
cpu_quota: Option<String>,
|
||||||
|
|
@ -573,15 +520,29 @@ pub enum AgentsCmd {
|
||||||
)]
|
)]
|
||||||
reset: bool,
|
reset: bool,
|
||||||
},
|
},
|
||||||
/// Per-agent disk accounting + optional quotas via btrfs qgroups.
|
/// Open an interactive Claude session inside this agent's container.
|
||||||
///
|
///
|
||||||
/// Opt-in: enable qgroup accounting, then report per-agent usage or
|
/// A fresh session by default, or resume a prior one. Requires root
|
||||||
/// cap an agent. No-op on non-btrfs hosts.
|
/// and a running container.
|
||||||
|
Choom {
|
||||||
|
/// Resume a prior claude session by its session id, passed
|
||||||
|
/// through as `claude --resume <value>` (claude's `--continue`
|
||||||
|
/// takes no value — it resumes the cwd's latest session, which
|
||||||
|
/// is the harness's, so choom never uses it; this flag matches
|
||||||
|
/// the claude flag it maps to). Omit for a fresh blank session.
|
||||||
|
/// A value is required when the flag is given.
|
||||||
|
#[arg(long = "resume", value_name = "SESSION")]
|
||||||
|
resume_session: Option<String>,
|
||||||
|
},
|
||||||
|
/// This agent's disk accounting + optional quota via btrfs qgroups.
|
||||||
|
///
|
||||||
|
/// Needs `hivectl quota-enable` run once hive-wide first. No-op on
|
||||||
|
/// non-btrfs hosts.
|
||||||
Quota {
|
Quota {
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
cmd: QuotaCmd,
|
cmd: AgentQuotaCmd,
|
||||||
},
|
},
|
||||||
/// btrfs subvolume management for agent state dirs.
|
/// btrfs subvolume management for this agent's state dir.
|
||||||
///
|
///
|
||||||
/// Upgrade an existing plain-dir agent's state into a btrfs subvolume
|
/// Upgrade an existing plain-dir agent's state into a btrfs subvolume
|
||||||
/// so it gains snapshots and per-subvol usage/quota.
|
/// so it gains snapshots and per-subvol usage/quota.
|
||||||
|
|
@ -591,6 +552,17 @@ pub enum AgentsCmd {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
pub enum AgentQuotaCmd {
|
||||||
|
/// Report this agent's disk usage from btrfs qgroups.
|
||||||
|
Show,
|
||||||
|
/// Set or clear this agent's disk-usage quota.
|
||||||
|
Set {
|
||||||
|
/// Size cap (`5G`, `500M`, `1073741824`) or `none` to clear.
|
||||||
|
size: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
/// Operator approval queue: list, approve, or deny pending requests.
|
/// Operator approval queue: list, approve, or deny pending requests.
|
||||||
#[derive(Subcommand)]
|
#[derive(Subcommand)]
|
||||||
pub enum ApprovalsCmd {
|
pub enum ApprovalsCmd {
|
||||||
|
|
@ -615,14 +587,12 @@ pub enum SubvolCmd {
|
||||||
///
|
///
|
||||||
/// Bounces the agent to migrate its state, so it requires `--yes`.
|
/// Bounces the agent to migrate its state, so it requires `--yes`.
|
||||||
Upgrade {
|
Upgrade {
|
||||||
/// Agent name (e.g. `damocles`, `iris`).
|
|
||||||
name: String,
|
|
||||||
/// Confirm: this stops the agent, migrates its state dir, and
|
/// Confirm: this stops the agent, migrates its state dir, and
|
||||||
/// restarts it. Required — the command refuses without it.
|
/// restarts it. Required — the command refuses without it.
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
yes: bool,
|
yes: bool,
|
||||||
},
|
},
|
||||||
/// Read-only snapshots of an agent's state subvolume.
|
/// Read-only snapshots of this agent's state subvolume.
|
||||||
Snapshot {
|
Snapshot {
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
cmd: SnapshotCmd,
|
cmd: SnapshotCmd,
|
||||||
|
|
@ -633,8 +603,6 @@ pub enum SubvolCmd {
|
||||||
pub enum SnapshotCmd {
|
pub enum SnapshotCmd {
|
||||||
/// Create a read-only snapshot (agent must already be a subvolume).
|
/// Create a read-only snapshot (agent must already be a subvolume).
|
||||||
Create {
|
Create {
|
||||||
/// Agent name (e.g. `damocles`, `iris`).
|
|
||||||
name: String,
|
|
||||||
/// Snapshot label. Mandatory, and must start with `hive-` — the
|
/// Snapshot label. Mandatory, and must start with `hive-` — the
|
||||||
/// prefix doubles as an allow-list hive-priv checks so only
|
/// prefix doubles as an allow-list hive-priv checks so only
|
||||||
/// hivectl-issued snapshot names can reach the `btrfs subvolume
|
/// hivectl-issued snapshot names can reach the `btrfs subvolume
|
||||||
|
|
@ -644,8 +612,6 @@ pub enum SnapshotCmd {
|
||||||
},
|
},
|
||||||
/// Delete a snapshot created by `subvol snapshot create`.
|
/// Delete a snapshot created by `subvol snapshot create`.
|
||||||
Delete {
|
Delete {
|
||||||
/// Agent name the snapshot belongs to.
|
|
||||||
name: String,
|
|
||||||
/// Snapshot label passed to `subvol snapshot create --label`.
|
/// Snapshot label passed to `subvol snapshot create --label`.
|
||||||
label: String,
|
label: String,
|
||||||
},
|
},
|
||||||
|
|
@ -655,8 +621,6 @@ pub enum SnapshotCmd {
|
||||||
/// point-in-time backup: a full send with no `--parent` produces a
|
/// point-in-time backup: a full send with no `--parent` produces a
|
||||||
/// self-contained archive of the snapshot.
|
/// self-contained archive of the snapshot.
|
||||||
Send {
|
Send {
|
||||||
/// Agent name the snapshot belongs to.
|
|
||||||
name: String,
|
|
||||||
/// Snapshot label passed to `subvol snapshot create --label`.
|
/// Snapshot label passed to `subvol snapshot create --label`.
|
||||||
label: String,
|
label: String,
|
||||||
/// Optional parent snapshot label for an incremental send
|
/// Optional parent snapshot label for an incremental send
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,16 @@
|
||||||
//!
|
//!
|
||||||
//! A thin client for the `hive-c0re` daemon: it speaks the host admin
|
//! A thin client for the `hive-c0re` daemon: it speaks the host admin
|
||||||
//! socket protocol (`hive-host-sock`) and does NOT link the daemon crate.
|
//! socket protocol (`hive-host-sock`) and does NOT link the daemon crate.
|
||||||
//! Container lifecycle + the approval queue (`agents <spawn|kill|rebuild|
|
//! Container lifecycle + the approval queue (`agent <name> <spawn|kill|
|
||||||
//! restart|…>`, `approvals <pending|approve|deny>`, `stop` / `start`) and
|
//! rebuild|restart|choom|…>`, `list-agents`, `approvals <pending|approve|
|
||||||
//! provisioning (`forge` / `matrix` / `github` / `gateway`) all forward to
|
//! deny>`, `stop` / `start`) and provisioning (`forge` / `matrix` /
|
||||||
//! the daemon, which owns the broker, the credentials, and the provisioning
|
//! `github` / `gateway`) all forward to the daemon, which owns the broker,
|
||||||
//! logic — a running daemon is required for those. A couple of verbs work
|
//! the credentials, and the provisioning logic — a running daemon is
|
||||||
//! off local host state directly instead (`wg` / `peer-config` read the mesh
|
//! required for those. A couple of verbs work off local host state
|
||||||
//! key + TLS CA), so they don't need the socket. `choom` execs into a
|
//! directly instead (`wg` / `peer-config` read the mesh key + TLS CA), so
|
||||||
//! container rather than asking the daemon to do anything, but still uses the
|
//! they don't need the socket. `agent <name> choom` execs into a container
|
||||||
//! socket for its "is this an agent?" pre-flight — that answer lives in a
|
//! rather than asking the daemon to do anything, but still uses the socket
|
||||||
|
//! for its "is this an agent?" pre-flight — that answer lives in a
|
||||||
//! directory only the daemon's user can read.
|
//! directory only the daemon's user can read.
|
||||||
//!
|
//!
|
||||||
//! One module per subcommand family (see the `mod` list below); `main` is
|
//! One module per subcommand family (see the `mod` list below); `main` is
|
||||||
|
|
@ -40,13 +41,12 @@ use open::open_url;
|
||||||
mod wg;
|
mod wg;
|
||||||
use wg::{peer_config, require_hive_domain, wg_init, wg_peer, wg_status};
|
use wg::{peer_config, require_hive_domain, wg_init, wg_peer, wg_status};
|
||||||
mod choom;
|
mod choom;
|
||||||
use choom::choom;
|
|
||||||
mod github;
|
mod github;
|
||||||
use github::github_set_token;
|
use github::github_set_token;
|
||||||
mod forge;
|
mod forge;
|
||||||
use forge::{forge_create_user, forge_reconcile_config};
|
use forge::{forge_create_user, forge_reconcile_config};
|
||||||
mod agents;
|
mod agents;
|
||||||
use agents::run_agents;
|
use agents::{agents_list, run_agent};
|
||||||
mod power;
|
mod power;
|
||||||
use power::{restart, start, stop};
|
use power::{restart, start, stop};
|
||||||
mod approvals;
|
mod approvals;
|
||||||
|
|
@ -93,7 +93,9 @@ async fn main() -> Result<()> {
|
||||||
GatewayCmd::DeleteUser { username } => gateway_delete_user(&socket, &username).await,
|
GatewayCmd::DeleteUser { username } => gateway_delete_user(&socket, &username).await,
|
||||||
GatewayCmd::ListUsers => gateway_list_users(&socket).await,
|
GatewayCmd::ListUsers => gateway_list_users(&socket).await,
|
||||||
},
|
},
|
||||||
Cmd::Agents { cmd } => run_agents(&socket, cmd).await,
|
Cmd::Agent { name, cmd } => run_agent(&socket, &name, cmd).await,
|
||||||
|
Cmd::ListAgents { json } => agents_list(&socket, json).await,
|
||||||
|
Cmd::QuotaEnable => quota::quota_enable(&socket).await,
|
||||||
Cmd::Approvals { cmd } => run_approvals(&socket, cmd).await,
|
Cmd::Approvals { cmd } => run_approvals(&socket, cmd).await,
|
||||||
Cmd::Wg { cmd } => match cmd {
|
Cmd::Wg { cmd } => match cmd {
|
||||||
WgCmd::Init { address } => wg_init(&socket, address.as_deref()).await,
|
WgCmd::Init { address } => wg_init(&socket, address.as_deref()).await,
|
||||||
|
|
@ -123,10 +125,6 @@ async fn main() -> Result<()> {
|
||||||
} => stop(&socket, scope.to_scope(), graceful, no_wait).await,
|
} => stop(&socket, scope.to_scope(), graceful, no_wait).await,
|
||||||
Cmd::Start { scope, no_wait } => start(&socket, scope.to_scope(), no_wait).await,
|
Cmd::Start { scope, no_wait } => start(&socket, scope.to_scope(), no_wait).await,
|
||||||
Cmd::Restart { scope, graceful } => restart(&socket, scope.to_scope(), graceful).await,
|
Cmd::Restart { scope, graceful } => restart(&socket, scope.to_scope(), graceful).await,
|
||||||
Cmd::Choom {
|
|
||||||
name,
|
|
||||||
resume_session,
|
|
||||||
} => choom(&socket, &name, resume_session.as_deref()).await,
|
|
||||||
Cmd::MarkdownDocs => {
|
Cmd::MarkdownDocs => {
|
||||||
print!("{}", clap_markdown::help_markdown::<Cli>());
|
print!("{}", clap_markdown::help_markdown::<Cli>());
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
//! `hivectl agents quota` — per-agent disk accounting + optional quotas via
|
//! `hivectl quota-enable` + `hivectl agent <name> quota` — per-agent disk
|
||||||
//! btrfs qgroups. The daemon holds the privileged helper that reads /
|
//! accounting + optional quotas via btrfs qgroups. The daemon holds the
|
||||||
//! sets qgroups; hivectl relays the request and formats the reply.
|
//! privileged helper that reads / sets qgroups; hivectl relays the
|
||||||
|
//! request and formats the reply.
|
||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
|
|
@ -13,20 +14,20 @@ pub(crate) async fn quota_enable(socket: &Path) -> Result<()> {
|
||||||
daemon_request(socket, hive_host_sock::HostRequest::QuotaEnable, "quota").await
|
daemon_request(socket, hive_host_sock::HostRequest::QuotaEnable, "quota").await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `quota show [name]` — report per-agent disk usage from btrfs qgroups.
|
/// `agent <name> quota show` — report one agent's disk usage from btrfs
|
||||||
/// The daemon resolves the agent set + reads each subvolume's usage (it
|
/// qgroups. The daemon reads that subvolume's usage (it holds the
|
||||||
/// holds the privileged helper); the client just formats the returned rows.
|
/// privileged helper); the client just formats the returned row(s).
|
||||||
pub(crate) async fn quota_show(socket: &Path, name: Option<&str>) -> Result<()> {
|
pub(crate) async fn quota_show(socket: &Path, name: &str) -> Result<()> {
|
||||||
let resp = crate::client::request(
|
let resp = crate::client::request(
|
||||||
socket,
|
socket,
|
||||||
hive_host_sock::HostRequest::QuotaShow {
|
hive_host_sock::HostRequest::QuotaShow {
|
||||||
name: name.map(crate::util::parse_ident).transpose()?,
|
name: Some(crate::util::parse_ident(name)?),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.with_context(|| format!("connect to daemon socket {}", socket.display()))?;
|
.with_context(|| format!("connect to daemon socket {}", socket.display()))?;
|
||||||
if !resp.ok {
|
if !resp.ok {
|
||||||
// Carries the "btrfs quota not enabled — run `hivectl agents quota enable`
|
// Carries the "btrfs quota not enabled — run `hivectl quota-enable`
|
||||||
// first" hint when qgroups are off.
|
// first" hint when qgroups are off.
|
||||||
bail!("{}", resp.error.as_deref().unwrap_or("quota show failed"));
|
bail!("{}", resp.error.as_deref().unwrap_or("quota show failed"));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
//! `hivectl agents subvol` — btrfs state-subvolume ops: migrate a plain-dir agent
|
//! `hivectl agent <name> subvol` — btrfs state-subvolume ops: migrate a
|
||||||
//! state root to a subvolume (`upgrade`), and snapshot create/delete/send.
|
//! plain-dir agent state root to a subvolume (`upgrade`), and snapshot
|
||||||
|
//! create/delete/send.
|
||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
|
|
@ -28,25 +29,21 @@ fn single_agent_scope(name: &str) -> hive_host_sock::LifecycleScope {
|
||||||
/// migration via hive-priv, then restart it. The restart is attempted
|
/// migration via hive-priv, then restart it. The restart is attempted
|
||||||
/// regardless of the migration outcome so a failed migration never leaves
|
/// regardless of the migration outcome so a failed migration never leaves
|
||||||
/// the agent down; the migration error (if any) is surfaced afterwards.
|
/// the agent down; the migration error (if any) is surfaced afterwards.
|
||||||
/// Route a `hivectl agents subvol …` subcommand. Split out of `main`'s top-level
|
/// Route a `hivectl agent <name> subvol …` subcommand. Split out of
|
||||||
/// match so the CLI router stays within the clippy line budget and the
|
/// `main`'s top-level match so the CLI router stays within the clippy
|
||||||
/// subvolume-op subcommands are dispatched in one place.
|
/// line budget and the subvolume-op subcommands are dispatched in one
|
||||||
pub(crate) async fn dispatch_subvol(socket: &Path, cmd: SubvolCmd) -> Result<()> {
|
/// place. `name` is hoisted in from the parent `agent <name>` command.
|
||||||
|
pub(crate) async fn dispatch_subvol(socket: &Path, name: &str, cmd: SubvolCmd) -> Result<()> {
|
||||||
match cmd {
|
match cmd {
|
||||||
SubvolCmd::Upgrade { name, yes } => subvol_upgrade(socket, &name, yes).await,
|
SubvolCmd::Upgrade { yes } => subvol_upgrade(socket, name, yes).await,
|
||||||
SubvolCmd::Snapshot { cmd } => match cmd {
|
SubvolCmd::Snapshot { cmd } => match cmd {
|
||||||
SnapshotCmd::Create { name, label } => {
|
SnapshotCmd::Create { label } => subvol_snapshot_create(socket, name, label).await,
|
||||||
subvol_snapshot_create(socket, &name, label).await
|
SnapshotCmd::Delete { label } => subvol_snapshot_delete(socket, name, &label).await,
|
||||||
}
|
|
||||||
SnapshotCmd::Delete { name, label } => {
|
|
||||||
subvol_snapshot_delete(socket, &name, &label).await
|
|
||||||
}
|
|
||||||
SnapshotCmd::Send {
|
SnapshotCmd::Send {
|
||||||
name,
|
|
||||||
label,
|
label,
|
||||||
parent,
|
parent,
|
||||||
dest,
|
dest,
|
||||||
} => subvol_snapshot_send(socket, &name, &label, parent.as_deref(), &dest).await,
|
} => subvol_snapshot_send(socket, name, &label, parent.as_deref(), &dest).await,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -210,9 +210,9 @@ in
|
||||||
# hive-wide `services.hyperhive.agentMemoryMax`) — see
|
# hive-wide `services.hyperhive.agentMemoryMax`) — see
|
||||||
# `resource_limits::effective_memory_bytes_from`. Not meant to be set
|
# `resource_limits::effective_memory_bytes_from`. Not meant to be set
|
||||||
# directly in an agent.nix, same convention as `hyperhive.otel.*`
|
# directly in an agent.nix, same convention as `hyperhive.otel.*`
|
||||||
# above; the host option (or `hivectl agents set-resource-limits`) is
|
# above; the host option (or `hivectl agent <name> set-limits`) is
|
||||||
# the real operator knob, and this only reflects the value baked in at
|
# the real operator knob, and this only reflects the value baked in at
|
||||||
# the agent's *last rebuild* — `set-resource-limits` still applies the
|
# the agent's *last rebuild* — `set-limits` still applies the
|
||||||
# cgroup cap live via a drop-in reload, but this derived heap ceiling
|
# cgroup cap live via a drop-in reload, but this derived heap ceiling
|
||||||
# needs a rebuild to pick up a new value.
|
# needs a rebuild to pick up a new value.
|
||||||
options.hyperhive.claudeMemoryMaxBytes = lib.mkOption {
|
options.hyperhive.claudeMemoryMaxBytes = lib.mkOption {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue