hivectl: add start/stop agent verbs, rename spawn to create
This commit is contained in:
parent
e6ecd8db60
commit
3421925442
7 changed files with 127 additions and 35 deletions
|
|
@ -39,9 +39,10 @@ hand-maintained per-file tree drifts out of sync with the code.
|
||||||
- **`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`. Verbs: `list-agents`,
|
wire types) — does NOT link `hive-c0re`. Verbs: `list-agents`,
|
||||||
`agent <name> <spawn|kill|destroy|rebuild|restart|set-parent|choom|
|
`agent <name> <start|create|request-create|stop|kill|destroy|rebuild|
|
||||||
watch|…>`, `approvals <pending|approve|deny>`, `forge`/`matrix`/
|
restart|set-parent|choom|watch|…>`, `approvals <pending|approve|deny>`,
|
||||||
`github`/`gateway` provisioning, `stop`/`start`, `wg`/`peer-config`.
|
`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,
|
||||||
|
|
|
||||||
|
|
@ -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-spawn` CLI). The host-level `HostRequest::Spawn`
|
`hivectl agent <name> request-create` 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`
|
`hivectl agent <name> create`). 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.
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,10 @@ 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 spawn`↴](#hivectl-agent-spawn)
|
* [`hivectl agent start`↴](#hivectl-agent-start)
|
||||||
* [`hivectl agent request-spawn`↴](#hivectl-agent-request-spawn)
|
* [`hivectl agent create`↴](#hivectl-agent-create)
|
||||||
|
* [`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)
|
||||||
|
|
@ -342,9 +344,11 @@ 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
|
||||||
* `spawn` — Spawn this agent container directly, bypassing the approval queue
|
* `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
|
||||||
* `request-spawn` — Queue a spawn request for operator approval
|
* `create` — Create this agent container from scratch (full first-time provisioning), bypassing the approval queue
|
||||||
* `kill` — Stop this managed container (graceful)
|
* `request-create` — Queue a first-creation request for operator approval
|
||||||
|
* `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
|
||||||
|
|
@ -390,27 +394,43 @@ Resume this paused agent — it drains whatever queued up while parked
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## `hivectl agent spawn`
|
## `hivectl agent start`
|
||||||
|
|
||||||
Spawn this agent container directly, bypassing the approval queue.
|
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
|
||||||
|
|
||||||
Operator-on-the-host only; use `request-spawn` for an approval-gated spawn.
|
**Usage:** `hivectl agent start`
|
||||||
|
|
||||||
**Usage:** `hivectl agent spawn`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## `hivectl agent request-spawn`
|
## `hivectl agent create`
|
||||||
|
|
||||||
Queue a spawn request for operator approval
|
Create this agent container from scratch (full first-time provisioning), bypassing the approval queue.
|
||||||
|
|
||||||
**Usage:** `hivectl agent request-spawn`
|
Operator-on-the-host only; use `request-create` for an approval-gated creation.
|
||||||
|
|
||||||
|
**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`
|
||||||
|
|
||||||
Stop this managed container (graceful)
|
Hard-stop this managed container
|
||||||
|
|
||||||
**Usage:** `hivectl agent kill`
|
**Usage:** `hivectl agent kill`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -99,14 +99,26 @@ 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 sub-agent container directly, bypassing the
|
/// Create and start a brand-new sub-agent container directly (full
|
||||||
/// approval queue. Privileged-context only. See
|
/// first-time provisioning: proposed/applied repos, state subvolume,
|
||||||
/// `docs/approvals.md::Approval kinds (wire shapes)`.
|
/// meta-flake sync, `nixos-container create`), bypassing the approval
|
||||||
|
/// 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 spawn request for the operator to approve. See
|
/// Submit a first-creation 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 },
|
||||||
/// Stop a managed container (graceful).
|
/// Hard stop a managed container. Exposed on the CLI as `hivectl
|
||||||
|
/// 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`.
|
||||||
|
|
|
||||||
|
|
@ -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 (spawn/kill/rebuild/restart/…).
|
- **`agents.rs`** — container lifecycle (start/stop/create/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.
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,55 @@ 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
|
||||||
|
|
@ -149,14 +198,16 @@ 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::Spawn => {
|
AgentCmd::Start => agents_start(socket, name).await,
|
||||||
|
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::RequestSpawn => {
|
AgentCmd::RequestCreate => {
|
||||||
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?)
|
||||||
|
|
|
||||||
|
|
@ -468,14 +468,22 @@ 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,
|
||||||
/// Spawn this agent container directly, bypassing the approval queue.
|
/// 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.
|
||||||
|
Start,
|
||||||
|
/// Create this agent container from scratch (full first-time
|
||||||
|
/// provisioning), bypassing the approval queue.
|
||||||
///
|
///
|
||||||
/// Operator-on-the-host only; use `request-spawn` for an approval-gated
|
/// Operator-on-the-host only; use `request-create` for an
|
||||||
/// spawn.
|
/// approval-gated creation.
|
||||||
Spawn,
|
Create,
|
||||||
/// Queue a spawn request for operator approval.
|
/// Queue a first-creation request for operator approval.
|
||||||
RequestSpawn,
|
RequestCreate,
|
||||||
/// Stop this managed container (graceful).
|
/// Gracefully stop this agent container: signal → drain → reconcile.
|
||||||
|
/// 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.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue