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.
This commit is contained in:
atlas 2026-07-15 23:36:35 +02:00 committed by mara
commit 9fdadb99c0
4 changed files with 136 additions and 25 deletions

View file

@ -139,15 +139,25 @@ async fn handle_completion(
coord.emit_rebuild_queue_snapshot();
}
/// Drain buffered terminal roll-ups: drop each DAG's lease-window
/// transient guard, then run the terminal hook (approval resolution,
/// Drain per-agent lease releases and buffered terminal roll-ups.
///
/// Per-agent first: an agent's subgraph within a DAG went terminal (its
/// lease was freed in `settle`), so drop that agent's `(dag, agent)`
/// transient pill now — ahead of whole-DAG terminal for a multi-agent
/// DAG. Then the whole-DAG terminals: drop any remaining transient the
/// DAG still held and run the terminal hook (approval resolution,
/// `Rebuilt` events, cancelled-power-op intent revert).
async fn process_terminals(
coord: &Arc<Coordinator>,
transients: &mut HashMap<(u64, String), crate::coordinator::TransientGuard>,
) {
for rel in coord.job_queue.drain_agent_releases() {
transients.remove(&(rel.dag_id, rel.agent));
}
for terminal in coord.job_queue.drain_terminal() {
// Drop every per-agent transient guard this DAG held.
// Drop any per-agent transient guard the DAG still held (the
// per-agent pass above already dropped the ones whose subgraphs
// settled early).
transients.retain(|(dag_id, _), _| *dag_id != terminal.dag_id);
exec::on_dag_terminal(coord, &terminal).await;
}