Watch
0
0
Fork
You've already forked hyperhive
0

a hive-side pause holds against the swarm's declared state until an operator resumes

the harness writes the reason into the pause marker when it pauses itself and reports it as paused_by_hive in its turn-state header. swarm-controller declares such an agent paused (only if declared up) and shows the reason on the agents status row and the swarm ui card.
This commit is contained in:
atlas 2026-11-09 23:09:14 +01:00 • committed by mara
commit e5c55257f8

View file

@ -945,6 +945,14 @@ async fn drive_turn_and_wake_chain<S: Surface>(
}
}
/// The pause marker's content for an auto-pause after `streak` missed todo
/// wakes. A non-empty marker is a pause by the harness itself, which the
/// turn-state header reports as `paused_by_hive`; the operator's pause writes
/// an empty one.
fn todo_miss_pause_reason(streak: u32) -> String {
format!("skipped get_loose_ends on {streak} consecutive todo wakes")
}
/// Apply a finished turn's `todo_wake_checked` signal to the miss-streak:
/// resets on a checked wake, increments (and auto-pauses at the threshold)
/// on a missed one, no-ops for a non-todo-wake turn. Split out of
@ -963,15 +971,19 @@ fn apply_todo_wake_checked(checked: Option<bool>, todo_miss_streak: &mut u32, bu
);
if streak >= TODO_MISS_PAUSE_THRESHOLD {
tracing::warn!("todo-miss streak hit the threshold — pausing the turn loop");
// Marker first: the Note triggers a turn-state header rebuild,
// which reads the reason back out of this file.
if let Err(e) =
std::fs::write(paths::paused_marker(), todo_miss_pause_reason(streak))
{
tracing::warn!(error = ?e, "failed to write pause marker");
}
bus.emit(LiveEvent::Note {
text: format!(
"auto-paused: skipped get_loose_ends on {streak} \
consecutive todo wakes — an operator needs to resume this agent"
),
});
if let Err(e) = std::fs::write(paths::paused_marker(), "") {
tracing::warn!(error = ?e, "failed to write pause marker");
}
// Fresh slate for whenever this agent gets resumed — see
// `serve_loop`'s `was_paused` reset for the mirror side.
*todo_miss_streak = 0;
@ -1254,4 +1266,31 @@ mod tests {
.await;
assert!(matches!(woke, RecvOutcome::Empty));
}
/// The third consecutive miss parks the agent with its reason in the
/// marker; an earlier miss, or a wake that did call `get_loose_ends`,
/// writes nothing.
#[test]
fn the_threshold_miss_writes_the_reason_into_the_pause_marker() {
crate::paths::with_harness_dir(|| {
let bus = crate::events::Bus::new();
let mut streak = 0;
for _ in 1..super::TODO_MISS_PAUSE_THRESHOLD {
super::apply_todo_wake_checked(Some(false), &mut streak, &bus);
assert!(
!crate::paths::paused_marker().exists(),
"below the threshold"
);
}
super::apply_todo_wake_checked(Some(true), &mut streak, &bus);
assert_eq!(streak, 0);
for _ in 0..super::TODO_MISS_PAUSE_THRESHOLD {
super::apply_todo_wake_checked(Some(false), &mut streak, &bus);
}
assert_eq!(
std::fs::read_to_string(crate::paths::paused_marker()).unwrap(),
super::todo_miss_pause_reason(super::TODO_MISS_PAUSE_THRESHOLD)
);
});
}
}