From 2c4a3c2fa731fed3de7b8915e93697d944dcd0c2 Mon Sep 17 00:00:00 2001 From: atlas Date: Sat, 10 Oct 2026 22:28:59 +0200 Subject: [PATCH] hive-agent: a failed turn-end /compact keeps the turn's own compacted flag The turn-end path returned the /compact's success as the turn's outcome, so a turn that had already compacted (reactively or per policy) was recorded as not compacted when its queued /compact then failed. The outcome now folds the two with `||`, via `with_turn_end_compact`, which the tests pin. --- hive-agent/src/turn.rs | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/hive-agent/src/turn.rs b/hive-agent/src/turn.rs index f90d07dc..4828d753 100644 --- a/hive-agent/src/turn.rs +++ b/hive-agent/src/turn.rs @@ -461,7 +461,7 @@ pub async fn drive_turn( // Reflect `Compacting` in the UI like the idle path (`run_pending_compact`) // does; the serve loop resets to `Idle` once this turn returns. bus.set_state(crate::events::TurnState::Compacting); - let compacted = report_compact(bus, session.compact(&config, &sink).await); + let compact_ok = report_compact(bus, session.compact(&config, &sink).await); // If the compact call asked to be woken (the agent's own `compact` // tool with a `wake_prompt`), stash it — the serve loop reads it // back after this turn returns and drives a synthetic follow-up @@ -470,7 +470,7 @@ pub async fn drive_turn( if let Some(prompt) = request.wake_prompt { bus.set_post_compact_wake(prompt); } - return Ok(compacted); + return with_turn_end_compact(outcome, compact_ok); } outcome } @@ -495,6 +495,13 @@ fn report_compact(bus: &Bus, result: Result<(), E>) -> boo } } +/// Fold a turn-end `/compact` into the turn's own outcome. A compaction that +/// ran during the turn (`Ok(true)`) stays recorded even when the turn-end +/// `/compact` failed; a failed `/compact` never turns it into `Ok(false)`. +fn with_turn_end_compact(outcome: TurnOutcome, compact_ok: bool) -> TurnOutcome { + outcome.map(|turn_compacted| turn_compacted || compact_ok) +} + /// Pre-turn auto-reset check. If context is large AND the prompt cache has /// gone cold (idle time >= cache TTL), archive the current session so the /// next wake-up turn's `--resume ` misses and self-heals into a fresh @@ -835,7 +842,7 @@ fn archive_session(bus: &Bus, session: &AgentSession) { mod tests { use super::{ PermissionAsk, TurnError, acp_permits, nonzero_or, parse_u64, report_compact, - runtime_error_to_turn, + runtime_error_to_turn, with_turn_end_compact, }; use crate::events::{Bus, LiveEvent}; use hive_runtime::{AcpError, Error}; @@ -1001,4 +1008,20 @@ mod tests { assert!(compacted); assert_eq!(notes, ["/compact done"]); } + + #[test] + fn a_failed_turn_end_compact_keeps_the_turns_own_compaction() { + assert!(matches!(with_turn_end_compact(Ok(true), false), Ok(true))); + } + + #[test] + fn a_failed_turn_end_compact_leaves_a_plain_turn_uncompacted() { + assert!(matches!(with_turn_end_compact(Ok(false), false), Ok(false))); + } + + #[test] + fn a_successful_turn_end_compact_marks_the_turn_compacted() { + assert!(matches!(with_turn_end_compact(Ok(false), true), Ok(true))); + assert!(matches!(with_turn_end_compact(Ok(true), true), Ok(true))); + } }