Watch
0
0
Fork
You've already forked hyperhive
0

drop the hyperhive send/recv/ack_until tools: agents message over matrix

This commit is contained in:
damocles 2026-11-07 11:55:45 +01:00 • committed by mara
commit bffb2c07a8
3 changed files with 52 additions and 397 deletions

View file

@ -1,17 +1,15 @@
You are hyperhive agent `{label}` (qualified: `{qualified_label}`){hive_identity}{swarm_identity} in a multi-agent system.
The operator is the human at the dashboard; say "the operator" when you mention the operator in third person (e.g. when relaying to a peer or the manager).
When you're talking to or about a peer on a different hive, use the qualified form (`name@hive`) in text, never as a `send` target, so the operator + peers can disambiguate.
When you're talking to or about a peer on a different hive, use the qualified form (`name@hive`) in text, never as a `send_dm` recipient, so the operator + peers can disambiguate.
Within your own hive the short form is fine.{operators}
Tools: the hyperhive MCP tools and any extra MCP servers describe themselves in their own tool schema, so this prompt does not repeat them.
What the schema doesn't tell you:
- `send` is the hive-local broker: system notices, and peers on this hive.
- **Talk to peers and the operator over matrix** if you have the matrix MCP tools (`mcp__matrix__send_dm` or `mcp__matrix__send_message`).
- A peer's matrix user is the `user_id` in `matrix_accounts` from `get_agent_meta(name)`; a peer with no matrix account is reached with `send`.
- Without matrix tools, use `send` for peers; the operator reads your `set_status` line and turn output on the dashboard, and your logs.
- `send(to: "operator")` is refused.
- Prefer ending the turn over repeatedly polling `recv` when idle.
- **Talk to peers and the operator over matrix** (`mcp__matrix__send_dm`, or `mcp__matrix__send_message` for a room).
- `send_dm` takes a peer's agent name (`iris`) or a full user id (`@iris:server`); a bare name means that agent on your own homeserver.
- An agent without matrix tools is cut off from peers on purpose; the operator reads your `set_status` line and turn output on the dashboard, and your logs.
- Prefer ending the turn over repeatedly polling for new messages 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.
@ -93,7 +91,7 @@ Keep messages short — a few sentences each.
For anything big (file listings, long diffs, transcripts, analysis), don't hand anyone a filesystem path — not your state dir; the operator can't open it and peers can't read your state.
If it's about an issue or PR, attach it there (`hive-forge attach-issue` / `attach-comment`) or inline it in the comment.
If it's a non-forge matter for the operator, send it over matrix (inline it; `send_file` for attachments).
For a peer, inline it in the matrix message (the `send` if you have no matrix tools) or put it on the relevant forge thread, then keep the message to a short headline ("cluster audit attached to the PR, headline: 3 nodes over 80% mem").
For a peer, inline it in the matrix message or put it on the relevant forge thread, then keep the message to a short headline ("cluster audit attached to the PR, headline: 3 nodes over 80% mem").
Name any issue or PR to the operator as a full clickable URL, never a bare number.
When your inbox has a message, handle it and stop.
@ -107,5 +105,5 @@ A long single turn that does step after step widens the window where a restart l
For multi-step work (long builds, sequential edits) that spans more than one turn: end the turn and let an external wake drive the next one — a new inbox message, a `remind` you scheduled, or a backgrounded bash task's completion.
Ending the turn is never a same-turn continuation: it's the checkpoint itself, and the only place an in-container todo wake (bash-task completion, matrix unread, forge activity) can reach you.
Polling `recv` in a loop happens _within_ the current turn instead and misses exactly those wakes, so it isn't a substitute for ending the turn.
Polling for messages in a loop happens _within_ the current turn instead and misses exactly those wakes, so it isn't a substitute for ending the turn.
**Keep `set_status` current whenever the work changes** — a stale status is what actually loses context across a restart, not turn length; a status set at the start of a task and never touched again is a bug, not a shortcut.

View file

@ -994,7 +994,6 @@ async fn handle_turn<S: Surface>(
let from = first.from;
let body = first.body;
let redelivered = first.redelivered;
let msg_id = first.id;
log_system_event(bus, &from, &body);
tracing::info!(%from, %body, %redelivered, "inbox");
let unread = S::inbox_unread(socket).await;
@ -1011,14 +1010,7 @@ async fn handle_turn<S: Surface>(
// the "you were interrupted" banner, then the flag resets so a later
// ordinary turn doesn't repeat a stale notice.
let was_interrupted = interrupted.swap(false, std::sync::atomic::Ordering::Relaxed);
let prompt = serve_common::format_wake_prompt(
msg_id,
&from,
&body,
unread,
redelivered,
was_interrupted,
);
let prompt = serve_common::format_wake_prompt(&from, &body, redelivered, was_interrupted);
let outcome = turn::drive_turn(&prompt, files, bus, session).await;
turn::emit_turn_end(bus, &outcome);
bus.set_state(TurnState::Idle);

View file

@ -9,23 +9,13 @@ use crate::turn::{TurnError, TurnOutcome};
use crate::turn_stats::TurnStatRow;
/// Assemble the per-turn wake prompt string. The role/tools/etc. live in the
/// system prompt; this is just the wake signal body. `id` is the broker row
/// id, rendered as a `[msg #<id>]` marker so the agent can reference it in
/// `ack_until`. `unread` is the inbox depth after this message was popped.
/// `redelivered` prepends a "may already be handled" banner; `interrupted`
/// system prompt; this is just the wake signal body. `redelivered` prepends a "may already be handled" banner; `interrupted`
/// prepends a "previous turn was /cancel'd" banner instead (the two are
/// mutually exclusive in practice — a redelivered message means the harness
/// itself restarted, which also clears the in-memory interrupted flag — so
/// `redelivered` takes priority if both were somehow set).
#[must_use]
pub fn format_wake_prompt(
id: i64,
from: &str,
body: &str,
unread: u64,
redelivered: bool,
interrupted: bool,
) -> String {
pub fn format_wake_prompt(from: &str, body: &str, redelivered: bool, interrupted: bool) -> String {
let banner = if redelivered {
hive_sh4re::inbox::REDELIVERY_HINT
} else if interrupted {
@ -33,13 +23,7 @@ pub fn format_wake_prompt(
} else {
""
};
let tag = if id > 0 {
format!("[msg #{id}] ")
} else {
String::new()
};
let pending = hive_sh4re::inbox::pending_hint(unread);
format!("{banner}{tag}Incoming message from `{from}`:\n---\n{body}\n---{pending}")
format!("{banner}Incoming message from `{from}`:\n---\n{body}\n---")
}
/// Field-named args for [`build_row`]. Mirrors the turn-stats row