docs: job-DAG queue model; fold agent_power table into broker.sqlite

coordinator.md rewrites the queue section (node inventory, DAG shapes,
resources, desired-state reconciliation, boot reconcile); approvals.md
+ persistence.md + hivectl --graceful help updated to match. agent_power
lives in broker.sqlite like approvals/questions (own connection + busy
timeout) instead of a separate db file.
This commit is contained in:
müde 2026-07-06 20:27:44 +02:00
commit 604e1c2557
8 changed files with 293 additions and 203 deletions

View file

@ -139,11 +139,11 @@ enum Cmd {
#[command(flatten)]
scope: ScopeArgs,
/// Gracefully quiesce each agent before stopping, instead of a
/// hard stop. Each agent is enqueued as a `GracefulStop` on the
/// rebuild queue: the harness is signalled, runs one
/// stop-checkpoint turn to flush durable `/state`, drains, then
/// the container is stopped (bounded by a 3-min timeout that
/// falls back to a hard stop). Applies to agents only.
/// hard stop. Each agent gets a graceful-stop DAG on the job
/// queue: the harness is signalled, runs one stop-checkpoint
/// turn to flush durable `/state`, drains, then the container
/// is stopped (bounded by a 3-min timeout that falls back to a
/// hard stop). All drains overlap. Applies to agents only.
#[arg(long)]
graceful: bool,
},

View file

@ -490,8 +490,7 @@ impl Coordinator {
let audit_log =
Arc::new(crate::audit_log::AuditLog::open(build_logs_dir).context("open audit_log")?);
crate::audit_log::install(audit_log.clone());
let power =
Arc::new(crate::power::PowerStore::open(build_logs_dir).context("open agent_power")?);
let power = Arc::new(crate::power::PowerStore::open(db_path).context("open agent_power")?);
let (dashboard_events, _) = broadcast::channel(DASHBOARD_CHANNEL);
let (shutdown_tx, _) = watch::channel(false);
Ok(Self {

View file

@ -130,7 +130,7 @@ async fn run_swap(coord: &Arc<Coordinator>, claim: &Claim, ctx: &Ctx<'_>) -> Res
let paths = Coordinator::agent_paths(name, agent_dir);
let result =
crate::lifecycle::swap_update(name, &hive, &paths, &|step| ctx.step(step), &|log_id| {
ctx.build_log(log_id)
ctx.build_log(log_id);
})
.await;
match &result {

View file

@ -3,8 +3,11 @@
//! `container_view` remains the observed *status*; the job queue's
//! `Reconcile` nodes are the mechanism that converges the two.
//!
//! Stored in `/var/lib/hyperhive/db/agent_power.sqlite` (one tiny row
//! per agent). Intent persists across hive-c0re restarts; in-flight
//! Stored as the `agent_power` table in the coordinator DB
//! (`/var/lib/hyperhive/db/broker.sqlite`, one tiny row per agent) —
//! same one-file-many-modules pattern as `approvals` /
//! `operator_questions` / `scheduled_prompts`, each with its own
//! connection. Intent persists across hive-c0re restarts; in-flight
//! queue work deliberately does not. Setting `wanted` is never a
//! queued node: operator/intent actions update the row synchronously
//! at request time, then submit the DAG whose terminal `Reconcile`
@ -80,12 +83,21 @@ pub struct PowerStore {
}
impl PowerStore {
pub fn open(db_dir: &Path) -> Result<Self> {
std::fs::create_dir_all(db_dir)
.with_context(|| format!("create agent_power db parent {}", db_dir.display()))?;
let path = db_dir.join("agent_power.sqlite");
let conn = Connection::open(&path)
.with_context(|| format!("open agent_power db {}", path.display()))?;
/// Open (a connection to) the shared coordinator DB and ensure the
/// `agent_power` table exists. `db_path` is the same sqlite file
/// the broker / approvals / questions stores open.
pub fn open(db_path: &Path) -> Result<Self> {
if let Some(parent) = db_path.parent() {
std::fs::create_dir_all(parent)
.with_context(|| format!("create agent_power db parent {}", parent.display()))?;
}
let conn = Connection::open(db_path)
.with_context(|| format!("open agent_power db {}", db_path.display()))?;
// Several modules hold their own connection to this file (the
// broker / approvals / questions pattern); wait out a
// concurrent writer instead of surfacing SQLITE_BUSY.
conn.busy_timeout(std::time::Duration::from_secs(5))
.context("set agent_power busy_timeout")?;
conn.execute_batch(SCHEMA)
.context("apply agent_power schema")?;
Ok(Self {