From d348ce885f95e86166290a2baab12b8f1b3496bd Mon Sep 17 00:00:00 2001 From: iris Date: Wed, 20 May 2026 21:42:21 +0200 Subject: [PATCH] manager: add optional agent param to GetLooseEnds GetLooseEnds now takes agent: Option: - None = manager's own loose ends (default; the bug fix) - Some("*") = hive-wide view (every approval/question/reminder) - Some("name") = that agent's loose ends The get_loose_ends MCP tool exposes this as an optional agent arg, so the manager can still scan the whole swarm on demand. The web UI and post-turn counts pass None (manager's own). --- hive-ag3nt/src/bin/hive-m1nd.rs | 2 +- hive-ag3nt/src/mcp.rs | 29 +++++++++++++++++++++-------- hive-ag3nt/src/web_ui.rs | 4 +++- hive-c0re/src/manager_server.rs | 19 +++++++++++++------ hive-sh4re/src/lib.rs | 18 ++++++++++++------ 5 files changed, 50 insertions(+), 22 deletions(-) diff --git a/hive-ag3nt/src/bin/hive-m1nd.rs b/hive-ag3nt/src/bin/hive-m1nd.rs index 3f712a3..6ee1931 100644 --- a/hive-ag3nt/src/bin/hive-m1nd.rs +++ b/hive-ag3nt/src/bin/hive-m1nd.rs @@ -334,7 +334,7 @@ fn now_unix() -> i64 { async fn fetch_manager_post_turn_counts(socket: &Path) -> (Option, Option) { let threads = match client::request::<_, ManagerResponse>( socket, - &ManagerRequest::GetLooseEnds, + &ManagerRequest::GetLooseEnds { agent: None }, ) .await { diff --git a/hive-ag3nt/src/mcp.rs b/hive-ag3nt/src/mcp.rs index fb3fe50..0c9e893 100644 --- a/hive-ag3nt/src/mcp.rs +++ b/hive-ag3nt/src/mcp.rs @@ -756,6 +756,18 @@ pub struct CancelLooseEndArgs { pub id: i64, } +#[derive(Debug, serde::Deserialize, schemars::JsonSchema)] +pub struct GetLooseEndsArgs { + /// Whose loose ends to list. Omit (or `null`) for your own — the + /// manager's: approvals you submitted + questions where you are + /// asker/target + your own pending reminders. Pass `"*"` for a + /// hive-wide view of EVERY pending approval, unanswered question, + /// and reminder across the swarm. Pass a specific agent name to + /// inspect just that agent's threads. + #[serde(default)] + pub agent: Option, +} + #[derive(Debug, serde::Deserialize, schemars::JsonSchema)] pub struct RequestApplyCommitArgs { /// Agent whose config repo the commit lives in (use `"hm1nd"` for the @@ -1126,18 +1138,19 @@ impl ManagerServer { } #[tool( - description = "Hive-wide loose ends: EVERY pending approval + EVERY unanswered \ - question + EVERY pending reminder across the swarm. Use to scan for stalled \ - coordination — questions sub-agents asked each other that nobody's answering, \ - approvals stuck waiting on the operator, reminders piling up on an offline \ - agent, etc. No args. The sub-agent flavour only returns the agent's own \ - threads; the manager flavour is unfiltered. Cancel any question or reminder \ + description = "List loose ends. By default returns your OWN — the manager's: \ + pending approvals you submitted + unanswered questions where you are \ + asker/target + your own pending reminders. Pass `agent: \"*\"` for a \ + hive-wide scan (EVERY pending approval, unanswered question, and reminder \ + across the swarm) — use it to spot stalled coordination, e.g. questions \ + sub-agents asked each other that nobody's answering. Pass `agent: \ + \"\"` to inspect one agent's threads. Cancel any question or reminder \ row via `cancel_loose_end` (manager bypasses the owner check)." )] - async fn get_loose_ends(&self) -> String { + async fn get_loose_ends(&self, Parameters(args): Parameters) -> String { run_tool_envelope("get_loose_ends", String::new(), async move { let (resp, retries) = self - .dispatch(hive_sh4re::ManagerRequest::GetLooseEnds) + .dispatch(hive_sh4re::ManagerRequest::GetLooseEnds { agent: args.agent }) .await; annotate_retries(format_loose_ends(resp), retries) }) diff --git a/hive-ag3nt/src/web_ui.rs b/hive-ag3nt/src/web_ui.rs index bbd1e76..f7a1bb8 100644 --- a/hive-ag3nt/src/web_ui.rs +++ b/hive-ag3nt/src/web_ui.rs @@ -409,7 +409,9 @@ async fn api_loose_ends(State(state): State) -> Response { Flavor::Manager => { match client::request::<_, hive_sh4re::ManagerResponse>( &state.socket, - &hive_sh4re::ManagerRequest::GetLooseEnds, + // Manager's own loose ends — the web page is the + // manager's page, not a hive-wide console. + &hive_sh4re::ManagerRequest::GetLooseEnds { agent: None }, ) .await { diff --git a/hive-c0re/src/manager_server.rs b/hive-c0re/src/manager_server.rs index 5906e07..11c82ee 100644 --- a/hive-c0re/src/manager_server.rs +++ b/hive-c0re/src/manager_server.rs @@ -396,12 +396,19 @@ async fn dispatch(req: &ManagerRequest, coord: &Arc) -> ManagerResp }, } } - ManagerRequest::GetLooseEnds => match crate::loose_ends::for_agent(coord, MANAGER_AGENT) { - Ok(loose_ends) => ManagerResponse::LooseEnds { loose_ends }, - Err(e) => ManagerResponse::Err { - message: format!("{e:#}"), - }, - }, + ManagerRequest::GetLooseEnds { agent } => { + let result = match agent.as_deref() { + Some("*") => crate::loose_ends::hive_wide(coord), + Some(name) => crate::loose_ends::for_agent(coord, name), + None => crate::loose_ends::for_agent(coord, MANAGER_AGENT), + }; + match result { + Ok(loose_ends) => ManagerResponse::LooseEnds { loose_ends }, + Err(e) => ManagerResponse::Err { + message: format!("{e:#}"), + }, + } + } ManagerRequest::CountPendingReminders => { match coord.broker.count_pending_reminders_for(MANAGER_AGENT) { Ok(count) => ManagerResponse::PendingRemindersCount { count }, diff --git a/hive-sh4re/src/lib.rs b/hive-sh4re/src/lib.rs index 1417155..9f20c05 100644 --- a/hive-sh4re/src/lib.rs +++ b/hive-sh4re/src/lib.rs @@ -792,12 +792,18 @@ pub enum ManagerRequest { #[serde(default)] file_path: Option, }, - /// Hive-wide loose-ends view: EVERY pending approval + EVERY - /// unanswered question in the swarm. Used by the manager to scan - /// for stalled coordination — the per-agent equivalent on the - /// sub-agent surface is `AgentRequest::GetLooseEnds` which - /// only returns rows where the agent itself is asker / target. - GetLooseEnds, + /// Loose-ends view for the manager surface. The optional `agent` + /// field selects scope: + /// - `None` — the manager's own loose ends: approvals it + /// submitted + questions where it is asker/target + its own + /// pending reminders. This is the default. + /// - `Some("*")` — hive-wide: EVERY pending approval, unanswered + /// question, and pending reminder across the swarm. + /// - `Some("")` — that specific agent's loose ends. + GetLooseEnds { + #[serde(default)] + agent: Option, + }, /// Count of the manager's own pending reminders. Mirror of /// `AgentRequest::CountPendingReminders` on the manager surface. CountPendingReminders,