diff --git a/TODO.md b/TODO.md index a55d917..f254c52 100644 --- a/TODO.md +++ b/TODO.md @@ -27,10 +27,6 @@ Pick anything from here when relevant. Cross-cutting design notes live in ## UI / UX -- **State badge: napping state.** Idle / thinking / compacting - already ship from server-side `TurnState`. Add `napping ๐Ÿ˜ด` - once the `nap` tool exists โ€” it just adds a new `TurnState` - variant the harness flips into for the duration of the nap. - **Terminal: `/model` slash command.** Operator-typeable model override from the terminal. Depends on the model-override work above; once an override mechanism exists, wire a `/model ` @@ -81,14 +77,6 @@ Pick anything from here when relevant. Cross-cutting design notes live in ## Loop substance -- **`nap` tool.** Agent-side MCP tool `mcp__hyperhive__nap(seconds)` that - parks the turn loop for a short while before next-message processing. - Use cases: agent decides it has nothing useful to do, or wants to - throttle itself between rapid wake events. Implementation: harness - records a "wake-not-before" timestamp; `recv_blocking` skips the long - poll until that ts; the state badge reads `napping ยท MM:SS` during. - Operator can cancel via the same `/cancel` slash command or a - dashboard button. - **Notes compaction.** `/state/` is bind-mounted persistently and agents are told (in the system prompt) to keep `/state/notes.md` for durable knowledge โ€” but we don't currently nudge them to compact when notes diff --git a/hive-ag3nt/prompts/agent.md b/hive-ag3nt/prompts/agent.md index a722f08..724d673 100644 --- a/hive-ag3nt/prompts/agent.md +++ b/hive-ag3nt/prompts/agent.md @@ -2,7 +2,7 @@ You are hyperhive agent `{label}` in a multi-agent system. Tools (hyperhive surface): -- `mcp__hyperhive__recv()` โ€” drain one more message from your inbox (returns `(empty)` if nothing pending). +- `mcp__hyperhive__recv(wait_seconds?)` โ€” drain one more message from your inbox (returns `(empty)` if nothing pending after the wait). Without `wait_seconds` it long-polls 30s. To **wait** for work when you have nothing else useful to do this turn, call with a long wait (e.g. `wait_seconds: 180`, the max) โ€” you'll be woken instantly when a message arrives, otherwise return after the timeout. That is strictly better than calling `recv` repeatedly with short waits: lower latency on new work, fewer turns, no busy-loop. Never use a fixed `sleep` shell command for the same purpose. - `mcp__hyperhive__send(to, body)` โ€” message a peer (by their name) or the operator (recipient `operator`, surfaces in the dashboard). Need new packages, env vars, or other NixOS config for yourself? You can't edit your own config directly โ€” message the manager (recipient `manager`) describing what you need + why. The manager evaluates the request (it doesn't rubber-stamp), edits `/agents/{label}/config/agent.nix` on your behalf, commits, and submits an approval that the operator can accept on the dashboard; on approve hive-c0re rebuilds your container with the new config. diff --git a/hive-ag3nt/prompts/manager.md b/hive-ag3nt/prompts/manager.md index ca96cae..3cf86dc 100644 --- a/hive-ag3nt/prompts/manager.md +++ b/hive-ag3nt/prompts/manager.md @@ -2,7 +2,7 @@ You are the hyperhive manager `{label}` in a multi-agent system. You coordinate Tools (hyperhive surface): -- `mcp__hyperhive__recv()` โ€” drain one more message from your inbox. +- `mcp__hyperhive__recv(wait_seconds?)` โ€” drain one more message from your inbox. Without `wait_seconds` it long-polls 30s. To **wait** when you have nothing else to do, call with a long wait (e.g. `wait_seconds: 180`, the max) โ€” you'll wake instantly on new work, otherwise return after the timeout. Use this instead of ending the turn or sleeping in a Bash command. - `mcp__hyperhive__send(to, body)` โ€” message an agent (by name), another peer, or the operator (`operator` surfaces in the dashboard). - `mcp__hyperhive__request_spawn(name)` โ€” queue a brand-new sub-agent for operator approval (โ‰ค9 char name). - `mcp__hyperhive__kill(name)` โ€” graceful stop on a sub-agent. No approval required. diff --git a/hive-ag3nt/src/mcp.rs b/hive-ag3nt/src/mcp.rs index c610ccf..bd38c05 100644 --- a/hive-ag3nt/src/mcp.rs +++ b/hive-ag3nt/src/mcp.rs @@ -166,7 +166,10 @@ impl AgentServer { #[tool( description = "Pop one message from this agent's inbox. Returns the sender and body, \ or an empty marker if nothing is waiting. Optional `wait_seconds` long-polls \ - for that many seconds (capped at 60) before returning empty โ€” default 30." + for that many seconds (capped at 180) before returning empty โ€” default 30. \ + Use a long wait_seconds (e.g. 120 or 180) when you have nothing else to do โ€” \ + it parks the turn until either a message arrives or the timeout fires, which \ + is strictly better than a fixed sleep because incoming work wakes you instantly." )] async fn recv(&self, Parameters(args): Parameters) -> String { let log = format!("{args:?}"); @@ -314,8 +317,10 @@ impl ManagerServer { #[tool( description = "Pop one message from the manager inbox. Returns sender + body, or \ - empty. Optional `wait_seconds` long-polls (capped at 60, default 30) so the \ - manager can sit on Recv when there's nothing to do without burning turns." + empty. Optional `wait_seconds` long-polls (capped at 180, default 30) so the \ + manager can sit on Recv when there's nothing to do without burning turns โ€” \ + prefer a long wait (120 or 180) over ending a turn early; you'll wake \ + instantly when work arrives." )] async fn recv(&self, Parameters(args): Parameters) -> String { let log = format!("{args:?}"); diff --git a/hive-c0re/src/agent_server.rs b/hive-c0re/src/agent_server.rs index 3904959..c47e5b4 100644 --- a/hive-c0re/src/agent_server.rs +++ b/hive-c0re/src/agent_server.rs @@ -79,10 +79,12 @@ async fn serve(stream: UnixStream, agent: String, broker: Arc) -> Result /// Default and max long-poll window for `Recv`. Caller can request a /// shorter (or longer up to `RECV_LONG_POLL_MAX`) wait via the -/// `wait_seconds` field; values above the cap are clamped. Set well -/// below typical TCP / proxy idle limits. +/// `wait_seconds` field; values above the cap are clamped. 180s +/// max keeps us under typical TCP/proxy idle limits while letting +/// agents park their turn until a message lands instead of busy- +/// looping with short waits. const RECV_LONG_POLL_DEFAULT: std::time::Duration = std::time::Duration::from_secs(30); -const RECV_LONG_POLL_MAX: std::time::Duration = std::time::Duration::from_secs(60); +const RECV_LONG_POLL_MAX: std::time::Duration = std::time::Duration::from_secs(180); fn recv_timeout(wait_seconds: Option) -> std::time::Duration { match wait_seconds { diff --git a/hive-c0re/src/manager_server.rs b/hive-c0re/src/manager_server.rs index 8765c75..4050641 100644 --- a/hive-c0re/src/manager_server.rs +++ b/hive-c0re/src/manager_server.rs @@ -72,7 +72,7 @@ async fn serve(stream: UnixStream, coord: Arc) -> Result<()> { /// Default and max long-poll window for manager `Recv`. Caller can /// request a shorter or longer (up to MAX) wait via `wait_seconds`. const MANAGER_RECV_LONG_POLL_DEFAULT: std::time::Duration = std::time::Duration::from_secs(30); -const MANAGER_RECV_LONG_POLL_MAX: std::time::Duration = std::time::Duration::from_secs(60); +const MANAGER_RECV_LONG_POLL_MAX: std::time::Duration = std::time::Duration::from_secs(180); fn manager_recv_timeout(wait_seconds: Option) -> std::time::Duration { match wait_seconds {