From 62a212280030cc0083cb943229cdd8c9375cb7ca Mon Sep 17 00:00:00 2001 From: atlas Date: Tue, 15 Sep 2026 16:37:05 +0200 Subject: [PATCH] subagent: drop the turn count from a NeedHelp status MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A NeedHelp status is waiting for an answer; `Turn 2 of 5` was competing with the thing the reader actually needs, which is what it asked for. Cut it at the describe_stopped call site (it already formats a distinct sentence per stop reason) rather than in describe_turns/describe_status, so TurnCap — where the count is the whole point — and the running-turn case are untouched. Refs #4415 Co-Authored-By: Claude Sonnet 5 --- hive-subagent-mcp/src/session.rs | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/hive-subagent-mcp/src/session.rs b/hive-subagent-mcp/src/session.rs index 37d2ff05..48230c58 100644 --- a/hive-subagent-mcp/src/session.rs +++ b/hive-subagent-mcp/src/session.rs @@ -1738,8 +1738,8 @@ fn describe_stopped(name: &str, stop: &StopReason, turns: &str) -> String { StopReason::NeedHelp(msg) => format!( "subagent `{name}` is BLOCKED and needs help: {msg}. It called `need_help`, which \ stopped its goal continuation, and nothing is in flight — it stays blocked until you \ - answer it.{turns} `continue` is how you answer: give it what it asked for as the \ - next turn's prompt." + answer it. `continue` is how you answer: give it what it asked for as the next \ + turn's prompt." ), StopReason::TurnCap { turns: spent } => format!( "subagent `{name}` ran out of turns — the harness limit of {spent} was reached and it \ @@ -2884,8 +2884,9 @@ mod tests { "the block and its reason must both be in the answer: {blocked}" ); assert!( - blocked.contains("Turn 2 of 5"), - "with the progress that says how far it got: {blocked}" + !blocked.contains("Turn 2 of 5"), + "a subagent waiting on an answer needs the answer, not a turn count competing for \ + attention: {blocked}" ); assert!( !blocked.contains("idle"), @@ -2893,6 +2894,23 @@ mod tests { ); } + #[test] + fn turn_cap_status_still_carries_the_turn_count() { + // The turn count is the whole point of a `TurnCap` stop — omitting + // it here (unlike `NeedHelp`) would be the regression a later + // refactor could silently reintroduce. + let capped = describe_status(&StatusFacts { + stop: Some(StopReason::TurnCap { turns: 5 }), + turns: Some((5, 5)), + ..StatusFacts::new("n") + }) + .expect("a capped session is a state, not an error"); + assert!( + capped.contains("Turn 5 of 5"), + "the turn count is the point of a TurnCap status: {capped}" + ); + } + #[test] fn a_reported_goal_never_reads_as_a_verified_one() { // The failure this is built against: `goal_reached` is self-reported