Compare commits

..
7 changed files with 36 additions and 126 deletions

View file

@ -38,9 +38,10 @@ hand-maintained per-file tree drifts out of sync with the code.
and the axum operator dashboard (`dashboard.rs`). Largest crate. and the axum operator dashboard (`dashboard.rs`). Largest crate.
- **`hivectl/`** — standalone operator CLI (`hivectl` binary). Talks to - **`hivectl/`** — standalone operator CLI (`hivectl` binary). Talks to
the `hive-c0re` daemon over the host admin socket (`hive-host-sock` the `hive-c0re` daemon over the host admin socket (`hive-host-sock`
wire types) — does NOT link `hive-c0re`. Full, always-current verb wire types) — does NOT link `hive-c0re`. Verbs: `list-agents`,
reference (CI-enforced against the clap tree, see `docs/conventions.md`): `agent <name> <spawn|kill|destroy|rebuild|restart|set-parent|choom|
[`docs/tools/hivectl-cli.md`](docs/tools/hivectl-cli.md). watch|…>`, `approvals <pending|approve|deny>`, `forge`/`matrix`/
`github`/`gateway` provisioning, `stop`/`start`, `wg`/`peer-config`.
- **`hive-agent/`**, **`hive-agent-mcp/`** — - **`hive-agent/`**, **`hive-agent-mcp/`** —
in-container harness, two sibling crates for every agent (not a in-container harness, two sibling crates for every agent (not a
single `hive-ag3nt/` dir — that's the runtime/binary-family nickname, single `hive-ag3nt/` dir — that's the runtime/binary-family nickname,

View file

@ -119,10 +119,10 @@ 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 agent <name> request-create` 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).
`hivectl agent <name> create`). This is the **canonical first-spawn**: a new agent's `InitConfig` This is the **canonical first-spawn**: a new agent's `InitConfig`
seeds its config repo, the submitting agent customises it, then the seeds its config repo, the submitting agent customises it, then the
operator spawns to create the container. Subsequent config changes go operator spawns to create the container. Subsequent config changes go
through a `MergeConfigPr` PR. through a `MergeConfigPr` PR.

View file

@ -24,10 +24,8 @@ This document contains the help content for the `hivectl` command-line program.
* [`hivectl agent restart`↴](#hivectl-agent-restart) * [`hivectl agent restart`↴](#hivectl-agent-restart)
* [`hivectl agent pause`↴](#hivectl-agent-pause) * [`hivectl agent pause`↴](#hivectl-agent-pause)
* [`hivectl agent resume`↴](#hivectl-agent-resume) * [`hivectl agent resume`↴](#hivectl-agent-resume)
* [`hivectl agent start`↴](#hivectl-agent-start) * [`hivectl agent spawn`↴](#hivectl-agent-spawn)
* [`hivectl agent create`↴](#hivectl-agent-create) * [`hivectl agent request-spawn`↴](#hivectl-agent-request-spawn)
* [`hivectl agent request-create`↴](#hivectl-agent-request-create)
* [`hivectl agent stop`↴](#hivectl-agent-stop)
* [`hivectl agent kill`↴](#hivectl-agent-kill) * [`hivectl agent kill`↴](#hivectl-agent-kill)
* [`hivectl agent destroy`↴](#hivectl-agent-destroy) * [`hivectl agent destroy`↴](#hivectl-agent-destroy)
* [`hivectl agent rebuild`↴](#hivectl-agent-rebuild) * [`hivectl agent rebuild`↴](#hivectl-agent-rebuild)
@ -344,11 +342,9 @@ Everything here targets a single named agent (`hivectl agent foo restart`, `hive
* `restart` — Stop and start this agent container without rebuilding config * `restart` — Stop and start this agent container without rebuilding config
* `pause` — Park this agent's turn loop, leaving the container running * `pause` — Park this agent's turn loop, leaving the container running
* `resume` — Resume this paused agent — it drains whatever queued up while parked * `resume` — Resume this paused agent — it drains whatever queued up while parked
* `start` — Start this EXISTING agent container. Fails immediately if `name` has no config/topology entry at all — it never attempts first-time creation. Use `create` for that * `spawn` — Spawn this agent container directly, bypassing the approval queue
* `create` — Create this agent container from scratch (full first-time provisioning), bypassing the approval queue * `request-spawn` — Queue a spawn request for operator approval
* `request-create` — Queue a first-creation request for operator approval * `kill` — Stop this managed container (graceful)
* `stop` — Gracefully stop this agent container: signal → drain → reconcile. Never escalates to a hard kill — use `kill` for that
* `kill` — Hard-stop this managed container
* `destroy` — Tear down this 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 this managed container * `rebuild` — Apply pending config to this managed container
* `set-parent` — Move this 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
@ -394,43 +390,27 @@ Resume this paused agent — it drains whatever queued up while parked
## `hivectl agent start` ## `hivectl agent spawn`
Start this EXISTING agent container. Fails immediately if `name` has no config/topology entry at all — it never attempts first-time creation. Use `create` for that Spawn this agent container directly, bypassing the approval queue.
**Usage:** `hivectl agent start` Operator-on-the-host only; use `request-spawn` for an approval-gated spawn.
**Usage:** `hivectl agent spawn`
## `hivectl agent create` ## `hivectl agent request-spawn`
Create this agent container from scratch (full first-time provisioning), bypassing the approval queue. Queue a spawn request for operator approval
Operator-on-the-host only; use `request-create` for an approval-gated creation. **Usage:** `hivectl agent request-spawn`
**Usage:** `hivectl agent create`
## `hivectl agent request-create`
Queue a first-creation request for operator approval
**Usage:** `hivectl agent request-create`
## `hivectl agent stop`
Gracefully stop this agent container: signal → drain → reconcile. Never escalates to a hard kill — use `kill` for that
**Usage:** `hivectl agent stop`
## `hivectl agent kill` ## `hivectl agent kill`
Hard-stop this managed container Stop this managed container (graceful)
**Usage:** `hivectl agent kill` **Usage:** `hivectl agent kill`

View file

@ -99,26 +99,14 @@ pub enum ReconcileDirection {
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "cmd", rename_all = "snake_case")] #[serde(tag = "cmd", rename_all = "snake_case")]
pub enum HostRequest { pub enum HostRequest {
/// Create and start a brand-new sub-agent container directly (full /// Create and start a sub-agent container directly, bypassing the
/// first-time provisioning: proposed/applied repos, state subvolume, /// approval queue. Privileged-context only. See
/// meta-flake sync, `nixos-container create`), bypassing the approval /// `docs/approvals.md::Approval kinds (wire shapes)`.
/// queue. Privileged-context only. Exposed on the CLI as `hivectl
/// agent <name> create`. See `docs/approvals.md::Approval kinds
/// (wire shapes)`. Wire name kept as `Spawn` (unrenamed underneath
/// the CLI-verb rename — `hivectl agent <name> start` reuses the
/// existing scope-based [`HostRequest::Start`] below instead of a
/// new per-agent variant, see its doc comment).
Spawn { name: Ident }, Spawn { name: Ident },
/// Submit a first-creation request for the operator to approve. See /// Submit a spawn request for the operator to approve. See
/// `docs/approvals.md::Approval kinds (wire shapes)` (`Spawn`). /// `docs/approvals.md::Approval kinds (wire shapes)` (`Spawn`).
/// Exposed on the CLI as `hivectl agent <name> request-create`.
RequestSpawn { name: Ident }, RequestSpawn { name: Ident },
/// Hard stop a managed container. Exposed on the CLI as `hivectl /// Stop a managed container (graceful).
/// agent <name> kill` — kept distinct from the new graceful-only
/// `hivectl agent <name> stop` (which reuses the scope-based
/// [`HostRequest::Stop`] below with `graceful: true`); `Kill`
/// escalates to a hard stop, `Stop` never does. Wire name kept as
/// `Kill`.
Kill { name: Ident }, Kill { name: Ident },
/// Tear down a sub-agent container, optionally purging state. /// Tear down a sub-agent container, optionally purging state.
/// See `docs/approvals.md::Destroy semantics`. /// See `docs/approvals.md::Destroy semantics`.

View file

@ -19,7 +19,7 @@ talk to the daemon over their own MCP tool surface
One module per subcommand family; `main.rs` is just the clap parse + One module per subcommand family; `main.rs` is just the clap parse +
dispatch: dispatch:
- **`agents.rs`** — container lifecycle (start/stop/create/kill/rebuild/restart/…). - **`agents.rs`** — container lifecycle (spawn/kill/rebuild/restart/…).
- **`approvals.rs`** — the config/init-config/meta-input approval queue. - **`approvals.rs`** — the config/init-config/meta-input approval queue.
- **`dag_progress.rs`** — rebuild-queue progress rendering. - **`dag_progress.rs`** — rebuild-queue progress rendering.
- **`power.rs`** — restart/start/stop at the container level. - **`power.rs`** — restart/start/stop at the container level.

View file

@ -36,55 +36,6 @@ async fn agents_restart(socket: &Path, name: &str, no_wait: bool) -> Result<()>
} }
} }
/// `hivectl agent <name> start` — start an EXISTING agent container.
/// Fails immediately (no request even sent) if `name` has no state dir
/// at all, rather than silently resolving to an empty scope. Reuses the
/// exact hive-wide `hivectl start` DAG (`HostRequest::Start`'s
/// scope-based path), just scoped to this one name — a separate
/// per-agent wire request would have duplicated logic this scope-based
/// one already covers.
async fn agents_start(socket: &Path, name: &str) -> Result<()> {
if !crate::util::agent_exists(socket, name).await? {
bail!(
"no such agent: '{name}' (no state dir under {}/) — use 'hivectl agent {name} create' to provision a brand-new agent",
hive_host_sock::AGENTS_ROOT
);
}
render(
crate::client::request(
socket,
HostRequest::Start {
scope: hive_host_sock::LifecycleScope {
agent_names: vec![name.to_owned()],
..Default::default()
},
},
)
.await?,
)
}
/// `hivectl agent <name> stop` — graceful-only stop (signal → drain →
/// reconcile), never escalating to a hard kill. Reuses the hive-wide
/// `hivectl stop --graceful` DAG (`HostRequest::Stop`'s scope-based
/// path), scoped to this one name. Distinct from `AgentCmd::Kill`, which
/// hard-stops via a separate DAG that does escalate.
async fn agents_stop(socket: &Path, name: &str) -> Result<()> {
render(
crate::client::request(
socket,
HostRequest::Stop {
scope: hive_host_sock::LifecycleScope {
agent_names: vec![name.to_owned()],
..Default::default()
},
graceful: true,
},
)
.await?,
)
}
/// `hivectl list-agents` — 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
@ -198,16 +149,14 @@ pub(crate) async fn run_agent(socket: &Path, name: &str, cmd: AgentCmd) -> Resul
AgentCmd::Restart { no_wait } => agents_restart(socket, name, no_wait).await, AgentCmd::Restart { no_wait } => agents_restart(socket, name, no_wait).await,
AgentCmd::Pause => set_paused(socket, name, true).await, AgentCmd::Pause => set_paused(socket, name, true).await,
AgentCmd::Resume => set_paused(socket, name, false).await, AgentCmd::Resume => set_paused(socket, name, false).await,
AgentCmd::Start => agents_start(socket, name).await, AgentCmd::Spawn => {
AgentCmd::Create => {
let name = crate::util::parse_ident(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?)
} }
AgentCmd::RequestCreate => { 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?)
} }
AgentCmd::Stop => agents_stop(socket, name).await,
AgentCmd::Kill => { 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?)

View file

@ -468,22 +468,14 @@ pub enum AgentCmd {
Pause, Pause,
/// Resume this paused agent — it drains whatever queued up while parked. /// Resume this paused agent — it drains whatever queued up while parked.
Resume, Resume,
/// Start this EXISTING agent container. Fails immediately if `name` /// Spawn this agent container directly, bypassing the approval queue.
/// has no config/topology entry at all — it never attempts
/// first-time creation. Use `create` for that.
Start,
/// Create this agent container from scratch (full first-time
/// provisioning), bypassing the approval queue.
/// ///
/// Operator-on-the-host only; use `request-create` for an /// Operator-on-the-host only; use `request-spawn` for an approval-gated
/// approval-gated creation. /// spawn.
Create, Spawn,
/// Queue a first-creation request for operator approval. /// Queue a spawn request for operator approval.
RequestCreate, RequestSpawn,
/// Gracefully stop this agent container: signal → drain → reconcile. /// Stop this managed container (graceful).
/// Never escalates to a hard kill — use `kill` for that.
Stop,
/// Hard-stop this managed container.
Kill, Kill,
/// Tear down this sub-agent container, keeping its state by default. /// Tear down this sub-agent container, keeping its state by default.
/// No undo. /// No undo.