agents: the send(operator) refusal also covers agents without matrix
An agent with no matrix account has no `mcp__matrix__*` tools, so a refusal that only says "use the matrix MCP tools" points it at something it cannot call. `OPERATOR_SEND_REFUSAL` now names matrix for an agent that has it, and tells one without it that there is no direct channel: the operator reads its `set_status` line, its turn output on the dashboard, and its logs. The allow-list refusal, the `send` tool description, the MCP server instructions and the system prompt say "over matrix if you have it" to match, and the helper-event bullets go back to "ask the operator" without naming a channel.
This commit is contained in:
parent
7e6a4c7727
commit
80e8361da9
7 changed files with 25 additions and 17 deletions
|
|
@ -36,9 +36,11 @@ is opt-in via the P3RM1SS10NS tab.
|
|||
`in_reply_to: i64` links the message to a prior id for thread
|
||||
rendering. Per-agent `services.hyperhive.agent.allowedRecipients`
|
||||
(default: empty = unrestricted) limits which names `send` accepts —
|
||||
useful for sandboxing. The broker refuses `to: "operator"` with an error
|
||||
that points at the matrix MCP tools: agents reach the operator over
|
||||
matrix ([matrix](../tools/matrix.md)).
|
||||
useful for sandboxing. The broker refuses `to: "operator"`. The error
|
||||
points an agent that has the matrix MCP tools at matrix
|
||||
([matrix](../tools/matrix.md)), and tells one without them that the
|
||||
operator reads its `set_status` line, its turn output on the dashboard,
|
||||
and its logs.
|
||||
- `recv` — drain inbox. Always an immediate peek, never blocks. `max`
|
||||
(default 1, cap 5) drains up to N rows. `recv` prefixes each returned row
|
||||
with `[msg #<id>]` (broker row id; note the highest id seen, then pass
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@ use rmcp::schemars;
|
|||
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
|
||||
pub struct SendArgs {
|
||||
/// Logical agent name to deliver the message to (e.g. `"manager"`,
|
||||
/// `"alice"`). `"operator"` is refused: reach the operator over matrix.
|
||||
/// `"alice"`). `"operator"` is refused: reach the operator over matrix
|
||||
/// if you have it.
|
||||
pub to: String,
|
||||
/// Message body. Plain text; the broker doesn't parse it.
|
||||
pub body: String,
|
||||
|
|
|
|||
|
|
@ -142,7 +142,7 @@ impl AgentServer {
|
|||
impl AgentServer {
|
||||
#[tool(
|
||||
description = "Send a message to another hyperhive agent on this hive. \
|
||||
Not for the operator: reach them over matrix with the matrix MCP tools."
|
||||
Not for the operator: reach them over matrix if you have the matrix MCP tools."
|
||||
)]
|
||||
async fn send(&self, Parameters(args): Parameters<SendArgs>) -> String {
|
||||
let log = format!("{args:?}");
|
||||
|
|
@ -500,7 +500,7 @@ impl AgentServer {
|
|||
|
||||
#[tool_handler(
|
||||
instructions = "You are a hyperhive agent. Use `send` to talk to peers (by their logical \
|
||||
name); reach the operator over matrix. Use `recv` to drain your inbox one \
|
||||
name); reach the operator over matrix if you have it. Use `recv` to drain your inbox one \
|
||||
message at a time. Use `remind` to schedule a future wake-up message for yourself."
|
||||
)]
|
||||
impl ServerHandler for AgentServer {}
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ pub fn check_send_allowed(to: &str) -> Result<(), String> {
|
|||
Err(format!(
|
||||
"send refused: recipient '{to}' not in services.hyperhive.agent.allowedRecipients \
|
||||
(configured in agent.nix). Allowed: {allow:?}. To reach the operator, use \
|
||||
the matrix MCP tools."
|
||||
the matrix MCP tools if you have them."
|
||||
))
|
||||
}
|
||||
|
||||
|
|
@ -53,9 +53,10 @@ mod tests {
|
|||
use super::check_send_allowed;
|
||||
|
||||
#[test]
|
||||
fn operator_is_refused_with_the_matrix_pointer() {
|
||||
fn operator_is_refused_naming_matrix_and_the_no_matrix_fallback() {
|
||||
let refusal = check_send_allowed(hive_sh4re::manager::OPERATOR_RECIPIENT)
|
||||
.expect_err("operator must be refused");
|
||||
assert!(refusal.contains("mcp__matrix__"), "{refusal}");
|
||||
assert!(refusal.contains("set_status"), "{refusal}");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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__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`. `send` reaches peers on this hive only; **reach the operator over matrix** (the matrix MCP tools, `mcp__matrix__send_dm` or `mcp__matrix__send_message`) — `send(to: "operator")` is refused. 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.
|
||||
- **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`. `send` reaches peers on this hive only; **reach the operator over matrix** if you have the matrix MCP tools (`mcp__matrix__send_dm` or `mcp__matrix__send_message`); without them the operator reads your `set_status` line and turn output on the dashboard, and your logs. `send(to: "operator")` is refused. 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.
|
||||
|
||||
Need new packages, env vars, or other NixOS config for yourself? You can't edit your own config directly — open a PR on your `agent-configs/<name>` repo, or contact the operator directly. Config repos live at `/agents/{label}/config/` (read-only inside your container). All changes flow through operator-approved commits.
|
||||
|
|
@ -13,8 +13,8 @@ Operator boundary: starting, stopping and rebuilding containers is the operator'
|
|||
|
||||
Messages from sender `system` are hyperhive helper events (JSON body, `event` field discriminates): `container_crash`, `needs_update`. Use these to react to lifecycle changes:
|
||||
|
||||
- `needs_update` — agent's flake rev is stale. Ask the operator over matrix to rebuild it.
|
||||
- `container_crash` — ask the operator over matrix to start it again; if it keeps crashing, say so with what you saw.
|
||||
- `needs_update` — agent's flake rev is stale. Ask the operator to rebuild it.
|
||||
- `container_crash` — ask the operator to start it again; if it keeps crashing, say so with what you saw.
|
||||
|
||||
Lifecycle notices that don't need an immediate turn — a new agent spawned, a container rebuilt/killed/destroyed, or its login state changing — surface as todos instead of messages now. Call `get_loose_ends` to see them.
|
||||
|
||||
|
|
|
|||
|
|
@ -667,7 +667,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn send_to_operator_is_refused_with_the_matrix_pointer() {
|
||||
fn send_to_operator_is_refused_naming_matrix_and_the_no_matrix_fallback() {
|
||||
let (_dir, coord) = coordinator();
|
||||
let resp = handle_send(
|
||||
&coord,
|
||||
|
|
@ -677,7 +677,8 @@ mod tests {
|
|||
None,
|
||||
);
|
||||
assert!(
|
||||
matches!(&resp, Response::Err { message } if message.contains("mcp__matrix__")),
|
||||
matches!(&resp, Response::Err { message }
|
||||
if message.contains("mcp__matrix__") && message.contains("set_status")),
|
||||
"{resp:?}"
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,11 +14,14 @@ pub const MANAGER_AGENT: &str = "ruth";
|
|||
/// [`OPERATOR_SEND_REFUSAL`].
|
||||
pub const OPERATOR_RECIPIENT: &str = "operator";
|
||||
|
||||
/// The error a `send` to [`OPERATOR_RECIPIENT`] returns. Agents reach the
|
||||
/// operator over matrix, so the text names the matrix tools to use.
|
||||
/// The error a `send` to [`OPERATOR_RECIPIENT`] returns. One text for every
|
||||
/// agent, so it covers both an agent with the matrix MCP tools and one
|
||||
/// without them.
|
||||
pub const OPERATOR_SEND_REFUSAL: &str = "send failed: `operator` is not a send recipient. \
|
||||
Reach the operator over matrix with the matrix MCP tools \
|
||||
(`mcp__matrix__send_dm`, or `mcp__matrix__send_message` to a room the operator is in).";
|
||||
If you have the matrix MCP tools, reach the operator over matrix \
|
||||
(`mcp__matrix__send_dm`, or `mcp__matrix__send_message` to a room the operator is in). \
|
||||
Without them there is no direct channel to the operator: they read your `set_status` \
|
||||
line and turn output on the dashboard, and your logs.";
|
||||
|
||||
/// Sender hive-c0re uses for events it pushes into the manager's inbox.
|
||||
/// Manager harness recognises this and parses the body as a `HelperEvent`.
|
||||
|
|
|
|||
Loading…
Reference in a new issue