hive-c0re/hive-sh4re: remove the ask/answer wire protocol + core routing
This commit is contained in:
parent
46183795dd
commit
2850270829
23 changed files with 177 additions and 851 deletions
|
|
@ -2,7 +2,7 @@ You are hyperhive agent `{label}` (qualified: `{qualified_label}`){hive_identity
|
|||
|
||||
Tools (hyperhive surface). Full signature + behavior for each comes from the tool's own MCP description (you already received it via the MCP tool schema) — this is just the map of what exists and which ones are gated, so you know where to look:
|
||||
|
||||
- **Inbox / messaging** (always available): `mcp__hyperhive__recv`, `mcp__hyperhive__ack_until`, `mcp__hyperhive__send`, `mcp__hyperhive__ask`, `mcp__hyperhive__answer`, `mcp__hyperhive__get_loose_ends`, `mcp__hyperhive__cancel_loose_end`, `mcp__hyperhive__mark_todos_done`, `mcp__hyperhive__remind`, `mcp__hyperhive__set_status`, `mcp__hyperhive__get_agent_meta`. Two habits worth internalizing beyond the tool descriptions themselves: prefer ending the turn over repeatedly polling `recv` when idle (only turn-boundaries observe in-container todo wakes — bash-task completions, matrix unread, forge activity — and ending the turn is also your checkpoint); and `ask`/`answer` are async — `ask` returns immediately with a question id, the reply lands later as a `question_answered` system event, never block a turn waiting on it inline. For a large todo backlog (`get_loose_ends` caps at 40 rows), clear reviewed ids in bulk with `mark_todos_done` rather than cancelling one at a time — there's no blind range-clear, only ids you've actually looked at.
|
||||
- **Inbox / messaging** (always available): `mcp__hyperhive__recv`, `mcp__hyperhive__ack_until`, `mcp__hyperhive__send`, `mcp__hyperhive__get_loose_ends`, `mcp__hyperhive__cancel_loose_end`, `mcp__hyperhive__mark_todos_done`, `mcp__hyperhive__remind`, `mcp__hyperhive__set_status`, `mcp__hyperhive__get_agent_meta`. One habit worth internalizing beyond the tool descriptions themselves: prefer ending the turn over repeatedly polling `recv` when idle (only turn-boundaries observe in-container todo wakes — bash-task completions, matrix unread, forge activity — and ending the turn is also your checkpoint). For a large todo backlog (`get_loose_ends` caps at 40 rows), clear reviewed ids in bulk with `mark_todos_done` rather than cancelling one at a time — there's no blind range-clear, only ids you've actually looked at.
|
||||
- **Extra MCP tools** (some agents only): `mcp__<server>__<tool>` — agent-specific (matrix client, scraper, db connector, etc.) declared in your `agent.nix` under `hyperhive.extraMcpServers`. First-class tools, already operator-approved at deploy time.
|
||||
- **Lifecycle** (_requires `lifecycle` tool group_, direct children only, no approval needed): `restart`, `kill`, `start`, `update`, `list_containers`.
|
||||
- **Approvals** (_requires `approvals` tool group_, queues an operator approval): `request_init_config`, `request_apply_commit`, `request_update_meta_inputs`.
|
||||
|
|
@ -15,11 +15,11 @@ Your config repo is mounted **read-only** at `/agents/{label}/config/` — `agen
|
|||
|
||||
Approval boundary: lifecycle ops on _existing_ direct children (`kill`, `start`, `restart`) are at your discretion — no operator approval needed (requires `lifecycle` tool group). _Creating_ a new agent (two-step: `request_init_config` + `request_apply_commit`) and _changing_ any agent's config (`request_apply_commit`) both go through the approval queue (requires `approvals` tool group). The operator only signs off on changes; you run the day-to-day.
|
||||
|
||||
Messages from sender `system` are hyperhive helper events (JSON body, `event` field discriminates): `approval_resolved`, `container_crash`, `needs_update`, `question_asked`, `question_answered`. Use these to react to lifecycle changes:
|
||||
Messages from sender `system` are hyperhive helper events (JSON body, `event` field discriminates): `approval_resolved`, `container_crash`, `needs_update`. Use these to react to lifecycle changes:
|
||||
|
||||
- `needs_update` — agent's flake rev is stale. Call `update(name)` to rebuild — it's idempotent and doesn't need approval.
|
||||
- `container_crash` — restart with `start(name)`. If it crashes again, ask the operator.
|
||||
- otherwise pick up answers to questions you asked.
|
||||
- `approval_resolved` — one of your own submitted approvals (`request_init_config`, `request_apply_commit`, `request_update_meta_inputs`, a scheduled prompt, …) was approved, denied, or failed; the body carries the resolution.
|
||||
|
||||
Lifecycle notices that don't need an immediate turn — a new agent spawned, its config repo seeded, a container rebuilt/killed/destroyed, or its login state changing — surface as todos instead of messages now. Call `get_loose_ends` to see them.
|
||||
|
||||
|
|
|
|||
|
|
@ -104,9 +104,9 @@ async fn main() -> Result<()> {
|
|||
// ---------- shared turn helpers ----------
|
||||
|
||||
/// Surface a `SYSTEM_SENDER` message in the live event bus + tracing
|
||||
/// log. Both agents and the manager receive `QuestionAnswered`,
|
||||
/// `ContainerCrash`, reparent notifications, and friends; the parse
|
||||
/// and log path is identical. Quiet no-op when `from` isn't
|
||||
/// log. Both agents and the manager receive `ContainerCrash`,
|
||||
/// reparent notifications, and friends; the parse and log path is
|
||||
/// identical. Quiet no-op when `from` isn't
|
||||
/// `SYSTEM_SENDER`.
|
||||
fn log_system_event(bus: &Bus, from: &str, body: &str) {
|
||||
if from != SYSTEM_SENDER {
|
||||
|
|
|
|||
|
|
@ -255,7 +255,7 @@ shared closer
|
|||
assert!(!rendered.contains("<!-- /role:"));
|
||||
// Shared tools appear.
|
||||
assert!(rendered.contains("mcp__hyperhive__recv"));
|
||||
assert!(rendered.contains("mcp__hyperhive__ask"));
|
||||
assert!(rendered.contains("mcp__hyperhive__send"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -254,14 +254,7 @@ fn enrich_tool_use_entry(entry: &mut Value) {
|
|||
/// `_category: "rich"` so the client can distinguish without re-implementing
|
||||
/// the tool name list.
|
||||
fn is_rich_tool(name: &str) -> bool {
|
||||
matches!(
|
||||
name,
|
||||
"Edit"
|
||||
| "mcp__bash__run"
|
||||
| "mcp__hyperhive__send"
|
||||
| "mcp__hyperhive__ask"
|
||||
| "mcp__hyperhive__answer"
|
||||
)
|
||||
matches!(name, "Edit" | "mcp__bash__run" | "mcp__hyperhive__send")
|
||||
}
|
||||
|
||||
/// Pre-compute the expandable body for rich tool entries.
|
||||
|
|
@ -271,7 +264,7 @@ fn is_rich_tool(name: &str) -> bool {
|
|||
/// - `"diff"` → `api.detailsDiff` (colour-coded `+`/`-` lines)
|
||||
/// - `"plain"` → `api.details` (plain `<pre>` block)
|
||||
/// - `"markdown"` → `api.detailsOpenMd` (markdown rendered via marked + `DOMPurify`,
|
||||
/// default-open; used for message-bearing tools: send, ask, answer)
|
||||
/// default-open; used for message-bearing tools: `send`)
|
||||
///
|
||||
/// Returns `None` for tools that have no body at all.
|
||||
///
|
||||
|
|
@ -318,9 +311,7 @@ fn rich_tool_body(name: &str, input: &Value) -> Option<(String, &'static str)> {
|
|||
Some((format!("$ {cmd}"), "plain"))
|
||||
}
|
||||
}
|
||||
// Message-bearing tools: body is markdown text rendered by the client.
|
||||
// The ask form (operator reply slot) is still mounted client-side;
|
||||
// only the raw body text moves to the backend here.
|
||||
// Message-bearing tool: body is markdown text rendered by the client.
|
||||
"mcp__hyperhive__send" => {
|
||||
let body = input.get("body").and_then(Value::as_str).unwrap_or("");
|
||||
if body.is_empty() {
|
||||
|
|
@ -329,22 +320,6 @@ fn rich_tool_body(name: &str, input: &Value) -> Option<(String, &'static str)> {
|
|||
Some((body.to_owned(), "markdown"))
|
||||
}
|
||||
}
|
||||
"mcp__hyperhive__ask" => {
|
||||
let q = input.get("question").and_then(Value::as_str).unwrap_or("");
|
||||
if q.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some((q.to_owned(), "markdown"))
|
||||
}
|
||||
}
|
||||
"mcp__hyperhive__answer" => {
|
||||
let a = input.get("answer").and_then(Value::as_str).unwrap_or("");
|
||||
if a.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some((a.to_owned(), "markdown"))
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
@ -354,8 +329,6 @@ fn tool_icon(name: &str) -> &'static str {
|
|||
match name {
|
||||
"mcp__hyperhive__send" => "📤",
|
||||
"mcp__hyperhive__recv" => "📥",
|
||||
"mcp__hyperhive__ask" => "❓",
|
||||
"mcp__hyperhive__answer" => "✍️",
|
||||
"mcp__hyperhive__remind" => "⏰",
|
||||
"mcp__hyperhive__set_status" => "🏷️",
|
||||
"mcp__hyperhive__get_loose_ends" => "🪢",
|
||||
|
|
@ -470,10 +443,10 @@ fn fmt_builtin_tool(name: &str, short: &str, input: &Value) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
/// Summary for message-bearing hyperhive tools (send / ask / answer).
|
||||
/// Summary for `send`, the one message-bearing hyperhive tool.
|
||||
///
|
||||
/// Format: `"{short} → {recipient}"` or `"{short} #{id}"` with ` · NL`
|
||||
/// appended when the body spans multiple lines.
|
||||
/// Format: `"{short} → {recipient}"` with ` · NL` appended when the body
|
||||
/// spans multiple lines.
|
||||
fn fmt_hyperhive_message_tool(name: &str, short: &str, input: &Value) -> String {
|
||||
match name {
|
||||
"mcp__hyperhive__send" => {
|
||||
|
|
@ -485,30 +458,6 @@ fn fmt_hyperhive_message_tool(name: &str, short: &str, input: &Value) -> String
|
|||
format!("{short} → {to}")
|
||||
}
|
||||
}
|
||||
"mcp__hyperhive__ask" => {
|
||||
let to = input
|
||||
.get("to")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or("operator");
|
||||
let lines = sv(input, "question").lines().count();
|
||||
if lines > 1 {
|
||||
format!("{short} → {to} · {lines}L")
|
||||
} else {
|
||||
format!("{short} → {to}")
|
||||
}
|
||||
}
|
||||
"mcp__hyperhive__answer" => {
|
||||
let id = input
|
||||
.get("id")
|
||||
.and_then(Value::as_u64)
|
||||
.map_or_else(|| "?".to_owned(), |n| n.to_string());
|
||||
let lines = sv(input, "answer").lines().count();
|
||||
if lines > 1 {
|
||||
format!("{short} #{id} · {lines}L")
|
||||
} else {
|
||||
format!("{short} #{id}")
|
||||
}
|
||||
}
|
||||
_ => fmt_args_generic(short, input),
|
||||
}
|
||||
}
|
||||
|
|
@ -516,9 +465,7 @@ fn fmt_hyperhive_message_tool(name: &str, short: &str, input: &Value) -> String
|
|||
/// `mcp__hyperhive__*` tools.
|
||||
fn fmt_hyperhive_tool(name: &str, short: &str, input: &Value) -> String {
|
||||
match name {
|
||||
"mcp__hyperhive__send" | "mcp__hyperhive__ask" | "mcp__hyperhive__answer" => {
|
||||
fmt_hyperhive_message_tool(name, short, input)
|
||||
}
|
||||
"mcp__hyperhive__send" => fmt_hyperhive_message_tool(name, short, input),
|
||||
"mcp__hyperhive__recv" => {
|
||||
let mut parts = Vec::new();
|
||||
if let Some(w) = input.get("wait_seconds").and_then(Value::as_u64) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue