hive-c0re/hivectl/hive-agent: pause as a job-queue DAG node (closes #3056)

This commit is contained in:
damocles 2026-08-11 21:59:25 +02:00 committed by mara
commit 20a7a21053
13 changed files with 316 additions and 27 deletions

View file

@ -329,24 +329,43 @@ async fn handle_spawn(coord: &Arc<Coordinator>, name: &str) -> Result<HostRespon
/// `hivectl pause|resume` / the dashboard toggle: write or remove the
/// agent's pause marker.
///
/// Deliberately not a lifecycle DAG. There's no container operation to
/// sequence — it's one marker file, and the harness picks it up on its
/// next poll — so queueing it would only add latency and a lease. That
/// also means it works on a stopped agent: the marker is sticky, so the
/// agent comes up paused.
/// **Resume** stays synchronous, direct marker removal — there's
/// nothing to acknowledge (a resumed agent just starts driving turns
/// again on its own next poll, no handshake needed), and nobody has
/// asked resume to wait.
///
/// **Pause** rides the job queue (`PauseSignal → PauseDrain`, see
/// `job_queue::power::pause_many`) instead of writing the marker
/// synchronously here: pausing wants the same "confirmed, not just
/// requested" signal a graceful stop gets from its `Signal → Drain`
/// pair, and the DAG's agent lease is what stops a pause from racing
/// an in-flight rebuild/stop of the same agent — a synchronous write
/// had no such protection. Still works on a stopped agent (the queued
/// `PauseSignal` writes the same sticky marker either way, container
/// running or not).
async fn handle_set_paused(
coord: &std::sync::Arc<Coordinator>,
name: &hive_types::Ident,
paused: bool,
) -> HostResponse {
if let Err(e) = Coordinator::set_paused(name, paused).await {
return HostResponse::error(format!("set paused={paused} for {name}: {e}"));
if !paused {
if let Err(e) = Coordinator::set_paused(name, false).await {
return HostResponse::error(format!("set paused=false for {name}: {e}"));
}
tracing::info!(%name, "agent pause marker cleared");
// Refresh the dashboard's view so the paused badge flips without
// waiting for the next periodic rescan.
coord.rescan_containers_and_emit().await;
return HostResponse::success();
}
match crate::job_queue::power::pause_many(coord, std::slice::from_ref(&name.to_string())).await
{
Ok(ids) => {
tracing::info!(%name, "agent pause queued");
HostResponse::queued(ids.into_iter().map(hive_jobq::NodeId::get).collect())
}
Err(e) => HostResponse::error(format!("queue pause for {name}: {e}")),
}
tracing::info!(%name, paused, "agent pause marker updated");
// Refresh the dashboard's view so the paused badge flips without
// waiting for the next periodic rescan.
coord.rescan_containers_and_emit().await;
HostResponse::success()
}
/// Collect per-agent status rows for `hivectl status` and the dashboard.