hivectl: add start/stop agent verbs, rename spawn to create

This commit is contained in:
damocles 2026-08-02 18:30:55 +02:00 committed by mara
commit 3421925442
7 changed files with 127 additions and 35 deletions

View file

@ -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
/// daemon (`HostRequest::AgentStatus`) and render it as a padded table,
/// 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::Pause => set_paused(socket, name, true).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)?;
render(crate::client::request(socket, HostRequest::Spawn { name }).await?)
}
AgentCmd::RequestSpawn => {
AgentCmd::RequestCreate => {
let name = crate::util::parse_ident(name)?;
render(crate::client::request(socket, HostRequest::RequestSpawn { name }).await?)
}
AgentCmd::Stop => agents_stop(socket, name).await,
AgentCmd::Kill => {
let name = crate::util::parse_ident(name)?;
render(crate::client::request(socket, HostRequest::Kill { name }).await?)

View file

@ -468,14 +468,22 @@ pub enum AgentCmd {
Pause,
/// Resume this paused agent — it drains whatever queued up while parked.
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
/// spawn.
Spawn,
/// Queue a spawn request for operator approval.
RequestSpawn,
/// Stop this managed container (graceful).
/// Operator-on-the-host only; use `request-create` for an
/// approval-gated creation.
Create,
/// Queue a first-creation request for operator approval.
RequestCreate,
/// 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,
/// Tear down this sub-agent container, keeping its state by default.
/// No undo.