Commit graph hyperhive/hive-c0re/src/job_queue
Author SHA1 Message Date
damocles
5bb5a88aa0 restart preserves wanted intent instead of forcing all agents up (#2540) 2026-07-17 01:46:12 +02:00
damocles
f2ff0deb6b split Swap's Ok-tail into a first-class PostSwap DAG node (#2390) 2026-07-17 01:06:33 +02:00
damocles
673aea4e50 fold hive-c0re module tree into the daemon binary + drop dead pub items surfaced by bin-only (#2513) 2026-07-16 10:43:08 +02:00
atlas
9fdadb99c0 feat(#2446): release a DAG's per-agent lease when that agent's subgraph is terminal
A per-agent lifecycle lease gates that agent's container globally across
concurrent DAGs, so it should be held for exactly as long as the agent's
work in a DAG is in flight, no longer. settle() previously freed every
lease a DAG held only at whole-DAG terminal, so a multi-agent DAG (a
hive-wide restart) kept agent A's container locked until B and C also
finished, blocking any other DAG wanting A.

Now free each agent's lease the moment its own subgraph within the DAG is
terminal (no live node still targets it), and drop that agent's dashboard
transient pill on the same edge via a new per-agent release channel. A
single-agent DAG is unaffected: its agent's subgraph goes terminal exactly
when the whole DAG does, so behaviour is identical.
2026-07-15 23:53:03 +02:00
damocles
c2bd7db998 refactor(#2416): remove the non-pr config-change flow (request_apply_commit / applycommit) 2026-07-15 21:03:52 +02:00
atlas
fbbd5d921c feat(#2485): remove vestigial Noop + StartupSweep residuals
Since the boot sweep (#2450) and meta-update cascade (#2476) became
single DAGs that grow subgraphs in-place, nothing constructs the old
fan-out anchors/parents anymore:

- NodeKind::Noop (the old boot_root grouping anchor) — no constructors.
- Template::StartupSweep / Source::StartupSweep (the old fan-out parent
  template + cascade-child source) — replaced by Template::Boot and
  Source::AutoUpdate/MetaUpdate respectively.

Drops the three variants + their as_str arms + the Noop executor arm, and
refreshes the stale fan-out/anchor doc comments (Boot/MetaUpdate/Source
docs, coordinator.md, dashboard.md). Frontend: the queue-kind glyph moves
from the dead startup_sweep to boot (which had none), and the dead
rqe-source-startup_sweep style is dropped.

No behaviour change — pure dead-variant removal.
2026-07-15 20:54:19 +02:00
atlas
b87eac0a61 feat(#2484): unify in-DAG growth on append_subgraph (drop append_node)
append_subgraph is the multi-node/multi-agent generalisation of the
single-node append_node, so the two in-DAG-growth channels collapse to
one: the Reconcile planner now emits its mechanical Start/Stop as a
single-node append_subgraph rooted on the reconcile node (stamping
claim.agent on the NodeSpec, which append_node inherited implicitly).

Removes NodeOutput.append_nodes + its scheduler drain loop and
JobQueue::append_node. No behaviour change — a channel unification.
2026-07-15 20:23:11 +02:00
atlas
edf9fd036e feat(#2453): remove DAG parent_id now that every op is one DAG
With the meta-update cascade (#2476) and startup sweep (#2450) folded
into single DAGs that grow per-agent subgraphs via append_subgraph,
nothing links parent/child DAGs anymore — parent_id is dead.

hive-c0re: drop parent_id from Dag/DagSpec (+ the DagView copy); delete
append_children and cancel_children (no callers); simplify trim_history
(no more terminal-parent-with-live-children guard — a one-big-DAG is
terminal only when its whole graph settles); drop the rebuild() parent_id
param; QueueDag returns just the polled DAG (no fan-out children to
gather). hive-sh4re: drop the DagView.parent_id wire field.

frontend: a multi-step op is one DAG now, so renderRebuildQueue drops the
childrenOf/orphans cross-DAG grouping and renders each entry flat; its
per-agent subgraphs render as nodes within the one row (split by deps).
Removed the dead rqe-child style + isChild plumbing.

Docs + the child-DAG queue tests updated/removed to match.
2026-07-15 19:50:04 +02:00
atlas
2b3130f63c feat(#2476): grow the meta-update cascade in-DAG instead of child DAGs
MetaLock's non-sweep completion now grows one rebuild subgraph per
affected agent into the same DAG (append_subgraph), replacing the
fan-out-child-DAGs + cancel_children dance. Drops NodeOutput.fanout and
scheduler's fanout_specs. meta_update DAG carries Rebuilding transient so
each cascade agent gets crash-watch suppression at Swap (the property the
old child Rebuild DAGs held via their own transient); MetaLock head needs
no lease so the pseudo-agent gets no pill.

append_children/parent_id and child-DAG tests are intentionally left for
the #2453 capstone.
2026-07-15 19:09:05 +02:00
atlas
4545dd312e feat(#2450): collapse the startup sweep into one inline DAG
The boot is now ONE DAG, assembled inline in submit_boot_tree — no boot_root
Noop anchor, no per-agent child DAGs, no display-only parent_id grouping: a
sweep MetaLock root (only when something is stale) that grows one rebuild
subgraph per stale agent into the same DAG (via append_subgraph, previous
commit), plus one Reconcile root per drifted agent (independent — a boot
reconcile needs no lock bump).

- submit_boot_tree builds the DagSpec inline; removed the single-use
  templates::{boot_root, startup_sweep} builders (inlined per the operator's
  "don't force single-use shapes into templates.rs" steer).
- fanout_specs simplified to the meta-update cascade path only — the startup
  sweep no longer fans out child DAGs, so its branch was dead.
- test: append_subgraph_roots_on_emitter_and_rebases_local_deps.

Vestigial after this (deliberately left as follow-ups, flagged in the PR):
NodeKind::Noop is now unconstructed (contained to hive-c0re, removable);
Template::StartupSweep is unconstructed but a hive-sh4re wire type
(frontend-coordinated removal).
2026-07-15 18:24:30 +02:00
atlas
b6defdeaaf feat(#2450): append_subgraph primitive + sweep MetaLock grows rebuilds in-DAG
First half of making the startup sweep one DAG. Adds the runtime
subgraph-append machinery and switches the sweep MetaLock from fanning out
child Rebuild DAGs to growing one rebuild subgraph per stale agent into the
same DAG:

- JobQueue::append_subgraph(dag_id, nodes, dep_on) — the multi-node,
  multi-agent generalisation of append_node: rebases a subgraph's local deps
  onto the DAG's id space and roots it on the emitting node.
- NodeOutput.append_subgraph: Vec<Vec<NodeSpec>> — the executor→scheduler
  channel for it; scheduler drains it before completing the emitting node
  (same ordering as append_nodes).
- run_meta_lock sweep branch returns the stale agents' rebuild_nodes
  subgraphs via append_subgraph instead of fanout.

Follow-up commit collapses submit_boot_tree (drop boot_root + per-agent
reconcile child DAGs) so the whole boot is one DAG built inline.
2026-07-15 18:24:30 +02:00
atlas
860484a193 refactor(#2439): build hive-wide stop/start/restart DAGs dynamically
Hive-wide `stop` / `start` / `restart` emit ONE DAG with a per-agent
subgraph each (concurrent on their own leases) instead of N DAGs — and each
subgraph is now built dynamically from the agent's live running state rather
than a fixed template shape:

- online agent: the full stop→reconcile (restart: stop-for-update→reconcile)
  chain; `graceful` prepends signal→drain.
- offline agent: just `SetWanted → Reconcile` (nothing to quiesce/stop; a
  restart of a down agent is really a start).

The head `SetWanted` (intent) and tail `Reconcile` (convergence guarantee)
are always present; only the mechanical `Signal`/`Drain`/`StopForUpdate`
nodes are state-conditional. Keeping `Reconcile` in every shape closes the
TOCTOU window — a race-up between the `is_running` read and node exec is
still converged in-DAG (with `StopForUpdate`-noop as the backstop) — with no
reliance on an external reconcile sweep.

The state-aware assembly needs an async `is_running` read, so it moves out
of the pure/sync `templates.rs` into `submit.rs`, layered as pure
`*_chain(running)` → pure `*_spec(targets)` (the unit-test seam) → async
`*_many` (reads live state + submits). `templates.rs` keeps only the shared
pure primitives (`node`/`after_ok`/`rebuild_nodes`).

Callers await the now-async submit fns (server, dashboard, socket_server).
Tests exercise both the online and offline shapes via the pure `*_spec`
seam. docs/coordinator.md shapes updated.
2026-07-15 00:29:54 +02:00
atlas
c8bd0f7180 test+docs(#2448): multi-agent restart test + doc shapes
- multi_agent_restart_is_one_dag_with_concurrent_per_agent_subgraphs:
  two agents restart in one DAG; both SetWanted heads are roots, each
  acquiring its own agent's lease concurrently. Existing single-agent
  shape/lease tests updated for the templates::restart(&[..], graceful)
  signature.
- coordinator.md + templates.rs module doc: restart takes an agent list;
  a hive-wide restart is one DAG with N per-agent subgraphs.
2026-07-14 23:11:02 +02:00
atlas
1739716fa2 feat(#2448): emit one multi-agent DAG for hivectl restart / restart-all
A hive-wide restart was N separate single-agent DAGs (one submit::restart
per agent). Now that agent is per-node (#2445), make it ONE DAG with a
per-agent restart subgraph each.

- templates::restart takes an agent list: each agent gets an independent
  subgraph (a head SetWanted(Up) root, then its restart chain), so the N
  subgraphs run concurrently on their own leases. One agent = the ordinary
  single-agent restart; unifies the old restart + graceful_restart fns.
- submit::restart / graceful_restart stay as single-agent wrappers over
  the new submit::restart_many(agents, graceful).
- server.rs handle_restart_all + handle_restart_scoped submit one
  restart_many call instead of looping per agent. Infra containers
  unchanged (no lease/DAG, synchronous).

Scope: restart + restart-all only. Broad stop+start is the same pattern
(stop/start templates take agent lists) — a follow-up increment.
2026-07-14 23:11:02 +02:00
atlas
5fe8008cce refactor(#2449): write power intent via a SetWanted DAG node, not a pre-submit side effect
The durable 'wanted' power intent was written by submit::{start,stop,
restart,graceful_restart,graceful_stop} as a synchronous pre-submit side
effect, then read by the DAG's tail Reconcile. That's not crash-safe
(a crash between the write and the enqueue loses it) and, with agent now
per-node, can't be per-agent in a DAG that spans agents.

Move it into the DAG as a head SetWanted node:
- NodeKind::SetWanted { up } + run_set_wanted executor (fails the node on
  a write error, unlike the old warn-and-continue, so a stale intent
  never reaches Reconcile).
- LEASE-NEEDING, not lease-exempt: it takes the agent lease so a power-op
  DAG's intent-write + reconcile is atomic per-agent. If it were exempt,
  two racing ops (restart vs stop) would run both intent-writes up front
  and clobber each other before either reconciled — defeating the point
  of moving the write into the DAG. (In stale_start the lease is thus held
  across the head Prebuild, but that's a no-op there: the agent is down so
  prebuild is skipped.)
- templates: explicit SetWanted node 0 on restart/graceful_restart/
  graceful_stop, plus dedicated start/stop templates (SetWanted -> Reconcile)
  and stale_start (SetWanted(Up) -> rebuild subgraph, reusing rebuild_nodes).
  No compose helper / rebuild variant. reconcile_only is now boot-only.
- submit.rs: drop the set_wanted side effect; the stale-rev shape decision
  (start vs stale_start) stays submit-side.

All 33 job_queue tests pass (shape/lease tests updated for the head node).
2026-07-14 22:34:44 +02:00
atlas
2a59f2f5fc refactor(#2441): move agent field from DAG onto Node; drop dedup
Agent was a single field on Dag/DagSpec, making a DAG structurally
one-agent — a multi-agent op could only ever be N separate DAGs. Move it
onto Node/NodeSpec (and the NodeView wire type), drop it from Dag/DagSpec
(and DagView): a DAG can now span agents.

- lifecycle lease keys on the node's agent, still globally exclusive per
  agent across all DAGs (Inner.leases unchanged in shape). A DAG holds one
  lease per distinct agent it touches; settle() frees each at DAG-terminal
  (per-agent-subgraph early release is a follow-up, only observable with
  multi-agent DAGs).
- transient guard keyed (dag_id, agent); cancel-revert + Rebuilt events
  walk TerminalDag.agents.
- submit-time dedup removed (a multi-agent DAG has no single agent to key
  on); every submit enqueues a fresh DAG. Whether dedup needs reintroducing
  is tracked in a follow-up sub-issue.
- templates gain a node(agent, kind, deps) helper stamping the agent onto
  every node; meta templates stamp "hyperhive".

Templates stay single-agent in this PR — behaviour is unchanged, only the
representation + wire shape. Multi-agent DAG emission (restart/restart-all/
broad stop+start as one DAG) and the SetWanted-as-a-node change are
follow-ups off #2439.
2026-07-14 21:58:40 +02:00
atlas
901ab6a779 fix(#2398): graceful restart as one atomic DAG, not compose-and-await
mara's review on #2436: no submit-await-submit composition, even
server-side. Adds Template::GracefulRestart (Signal -> Drain ->
StopForUpdate -> Reconcile, wanted=Up) mirroring how Restart already
does StopForUpdate -> Reconcile, plus submit::graceful_restart and
templates::graceful_restart. handle_restart_scoped now submits exactly
one DAG per agent up front for both the graceful and non-graceful
case -- no await_dags in the loop anymore.
2026-07-14 20:38:10 +02:00
damocles
4be2279485 chore(#2400): skip the prebuild warm-build when the container is down 2026-07-13 16:24:39 +02:00
damocles
436adf6fd0 refactor(#2390): split provision out of the create node in the spawn dag 2026-07-13 16:13:59 +02:00
damocles
16f69ca890 feat(#2392): group boot sweep + reconciles under one boot dag 2026-07-13 11:51:39 +02:00
damocles
79d4c345bb feat(#2349): fan reconcile's start/stop out as first-class dag nodes 2026-07-12 03:10:18 +02:00
damocles
556a213320 refactor(#2285): inline remaining 1:1 path wrappers (meta_dir, marker fns, host_conf_path) 2026-07-10 20:32:03 +02:00
damocles
cfb84b420a refactor(#2285): drop coordinator 1:1 path accessors, callers use paths:: directly 2026-07-10 20:32:03 +02:00
atlas
df44becd4a fix(#2290): update stale spawn_poll references in comments 2026-07-09 01:08:38 +02:00
atlas
73f1020a7e refactor(#2290): replace mcp_sockets poll with event-driven register_agent
mara: the background worker is redundant if c0re knows when its own
sockets go missing. damocles: 10s poll latency and redundancy are two
faces of the same issue — poll adds a reconnect window and does
redundant work when c0re could react directly.

design: c0re owns the MCP listener lifecycle, so the only time a
listener disappears without c0re knowing is when c0re itself restarts.

- replace spawn_poll (recurring 10s loop) with sync_on_start (one-shot
  sweep at daemon boot): re-registers all running agents on startup
  after /run/hyperhive/agents/ is cleared by the tmpfs reset.
- run_reconcile (reconcile-start path): add coord.register_agent(name)
  immediately after start_with_fallback — event-driven, no poll delay.
- run_create already calls register_agent eagerly; kill/destroy paths
  already call unregister_agent — no changes needed there.

tracker: #2290
2026-07-09 01:08:38 +02:00
atlas
afdd8c6c9f feat(#2290): converge unification cleanup — pull preamble into lifecycle
Collapse the scattered ensure_agent_runtime_dir calls into the lifecycle
functions themselves so callers have a single responsibility:

- lifecycle::spawn: calls ensure_agent_runtime_dir before write_dropins.
  Callers (handle_spawn, ensure_root_agent) no longer need a separate
  preamble step.

- lifecycle::rebuild_no_meta spawn path: calls ensure_agent_runtime_dir
  before write_dropins. apply_commit / merge_config_pr flows no longer
  need a manual ensure_agent_runtime_dir.

- run_create (job-queue): drops ensure_agent_runtime_dir + register_agent.
  The tail Reconcile's converge_start_preamble handles the runtime dir
  and mcp_sockets::spawn_poll handles the listener. Create stays purely
  'provision + create', not 'create + start'.

- handle_spawn (server.rs): drops manual preamble; lifecycle::spawn owns it.
  Drops unneeded unregister_agent on failure (supervisor handles listener).

- ensure_root_agent (auto_update.rs): drops manual ensure_agent_runtime_dir.

- actions.rs apply_commit / merge_config_pr: drop manual
  ensure_agent_runtime_dir; rebuild_no_meta's spawn path handles it.

Result: ensure_agent_runtime_dir lives in exactly two places —
lifecycle::spawn (direct spawn) and converge_start_preamble (start/reconcile
path). All other callers are clean call sites.
2026-07-09 01:08:38 +02:00
atlas
a45f65bd73 style: rustfmt 2026-07-09 01:08:38 +02:00
atlas
950a13bc69 feat(#2290): StartableAgent token — start_with_fallback requires preamble proof
- lifecycle::StartableAgent: opaque token produced only by
  converge_start_preamble. #[must_use] with a hint to call
  start_with_fallback(token).

- lifecycle::converge_start_preamble(name, hive, paths): runs
  ensure_agent_runtime_dir + write_dropins, returns StartableAgent.
  The only way to obtain a token.

- lifecycle::start_with_fallback(token: StartableAgent): public API
  now requires the token. Callers that skip the preamble get a compile
  error, not a runtime outage.

- lifecycle::start_with_fallback_inner(name): private; used internally
  by rebuild_no_meta where the preamble is already enforced structurally
  (write_dropins was called on the line above).

- exec.rs ReconcileAction::Start: migrated to converge_start_preamble
  + start_with_fallback(token). The write_dropins + start_with_fallback
  two-step is now a single typed pipeline.
2026-07-09 01:08:38 +02:00
atlas
3d919b596f feat(#2290): split ensure_runtime — dirs to lifecycle, listeners to mcp_sockets supervisor
- lifecycle::ensure_agent_runtime_dir(name): pure filesystem op, no
  Coordinator dep. Creates /run/hyperhive/agents/<name> without touching
  the MCP listener map.

- workers/mcp_sockets::spawn_poll(coord): 10 s reconcile loop (same shape
  as agent_sockets::spawn_poll). Converges 'agent running => MCP listener
  bound'. First tick is immediate so hive-c0re restarts re-register all
  running agents without waiting a full interval. Fixes the dead-listener-
  after-daemon-restart gap.

- All ensure_runtime() call sites updated:
  - Prebuild/Swap/WriteDropin: Coordinator::agent_dir() (pure, no IO)
  - Reconcile-Start: ensure_agent_runtime_dir + agent_dir (dir may be
    missing after reboot; listener deferred to supervisor)
  - run_create / handle_spawn: ensure_agent_runtime_dir + register_agent
    (eager on first spawn so socket ready before harness first turn)
  - apply_commit / merge_config_pr: ensure_agent_runtime_dir + agent_dir
  - Manager (auto_update): ensure_agent_runtime_dir + agent_dir
    (manager has no MCP listener; socket_server::start_manager owns it)

- ensure_runtime() retained in Coordinator with updated doc pointing at
  the preferred split form. No callers remain outside tests.
2026-07-09 00:58:51 +02:00
müde
c7c156e57b fix(hive-c0re): converge /run bind sources + limits drop-in before reconcile-start 2026-07-08 21:48:37 +02:00
müde
2486251b32 fix: close second review round on the queue-routed CLI
- subvol upgrade waits for the queued stop DAG before migrating (was
  snapshotting + swapping state under a live bind mount) and for the
  restart job after
- history trim gets a 5-min grace for fresh terminals so broad
  stop/start waits can't miss a failed DAG evicted by the per-template
  cap (cap still applies past the grace)
- restart-all returns its DAG ids so hivectl actually waits
- hard stops await their agent DAGs (bounded) before infra goes down,
  restoring the agents-before-infra invariant
- hivectl wait uses node-level terminality so the after-any recovery
  reconcile is watched to completion; infra render errors no longer
  skip watching already-queued agent DAGs
- fold hive-bash-mcp's last local now_unix into wire_time
2026-07-06 22:57:28 +02:00
müde
b489454dc2 feat(hivectl): queue-routed lifecycle verbs with wait + DAG progress
every agent lifecycle verb on the admin socket (rebuild / restart /
restart-all / kill / stop / start) now submits job-queue DAGs and
returns their ids; hivectl polls the new HostRequest::QueueDag and
prints a live node-chain progress line per DAG (fan-out children
included), exiting non-zero on failure — --no-wait opts out. DagView
and the queue wire enums move to hive_sh4re::jobs (wire types live in
the shared crate); the last fused rebuild path (lifecycle::rebuild)
is gone. tracker: #2166
2026-07-06 22:30:49 +02:00
müde
dc6a37b29a style: import ordering + blank lines after now_unix sweep (treefmt) 2026-07-06 22:08:40 +02:00
müde
c84028ddcf refactor: single now_unix in hive_sh4re::wire_time
replaces 15 per-module copies (now_unix/now_secs) across hive-c0re and
hive-ag3nt; wire_time already owns the epoch-seconds convention
2026-07-06 21:58:32 +02:00
müde
084e12503c fix(hive-c0re): close review findings on the job-DAG queue
- deploy-window gate (meta::exclusive) + path-limited meta commits:
  a perm/lock/topology commit can no longer sweep an ApprovalDeploy's
  staged flake.lock and neuter abort_deploy (regression test included)
- cancel surfaces now buffer terminal roll-ups the scheduler drains,
  so a queued approval DAG cancelled by the operator resolves its
  approval instead of dangling, and cancelled power ops revert their
  wanted flip to the observed state
- hivectl restart / restart-all ride the queue (lease serialization,
  transient guard) and restart sets wanted=Up like the old kill+start
- exactly one Rebuilt event per rebuild DAG, emitted at terminal
- StopForUpdate pre-seeds a missing agent_power row from the pre-stop
  observation so a rebuild can't strand an unknown agent offline
- history trim keeps terminal fan-out parents with live children
- audit_log back on db::open; swarm.js badge for reconcile DAGs
2026-07-06 21:44:43 +02:00
müde
604e1c2557 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.
2026-07-06 20:36:57 +02:00
müde
7946e03fde feat(hive-c0re): replace rebuild queue with generic job-DAG queue
jobs are now DAGs of primitive nodes (prebuild, stop-for-update, swap,
reconcile, signal, drain, ...) driven by one scheduler with N build
slots + per-agent lifecycle leases. per-agent power intent (wanted
up/offline) is durable in agent_power.sqlite; Reconcile nodes converge
observed state to it. kills the graceful-stop watcher thread, the
deferred-start follow-up, and the cascade pre-enqueue (fan-out on
MetaLock completion instead). tracker: #2166
2026-07-06 20:36:57 +02:00