agents: remove send(operator) and the operator inbox; alert on turn failures
Agents reach the operator over matrix now (#5115). A `send` to `operator` is refused, by the broker and by the MCP allow-list check, with an error that names the matrix MCP tools. The dashboard's Y3R C4LL tab goes with it: the operator inbox, `GET /api/operator-inbox`, `Broker::unread_for_recipient`, and the browser notifications that only ever fired on operator-bound messages. The harness no longer posts turn failures and plugin-install failures to the operator inbox. Both are already logged at warn; a new Grafana rule, `hh-agent-turn-failed`, fires per agent on the harness's `turn failed` line. Dashboard-to-agent messaging (op-send, the agent web UI's message box, mark-all-read for agent inboxes) is unchanged. Decisions per mara on #5115: q1 a, q2 a plus a grafana alert, q3 keep dashboard-to-agent messaging and leave swarm-level code alone.
This commit is contained in:
parent
2bf809cbe5
commit
7e6a4c7727
37 changed files with 246 additions and 848 deletions
|
|
@ -51,14 +51,13 @@ in
|
|||
description = ''
|
||||
Names this agent is allowed to `send` to via
|
||||
`mcp__hyperhive__send`. Empty list (the default) means
|
||||
unrestricted — the agent can message any peer, the
|
||||
operator, or the manager. Non-empty list constrains the
|
||||
unrestricted — the agent can message any peer or the
|
||||
manager. Non-empty list constrains the
|
||||
surface: only the listed names + the manager (always
|
||||
allowed) get through; anything else returns an error
|
||||
string to claude without touching the broker. The
|
||||
operator (`operator`) needs to be in the list if the
|
||||
agent should be able to surface output on the
|
||||
dashboard.
|
||||
string to claude without touching the broker.
|
||||
`operator` is refused either way: agents reach the operator
|
||||
over matrix.
|
||||
|
||||
Useful for sandboxing untrusted sub-agents — set
|
||||
`[ "manager" ]` to scope them to manager-only chatter.
|
||||
|
|
|
|||
|
|
@ -996,6 +996,20 @@ in
|
|||
for = "5m";
|
||||
summary = "{{ $labels.hive }}/{{ $labels.agent }} finds nothing at its queue credential path and cannot connect to the queue";
|
||||
})
|
||||
# Logged once per failed turn, by `emit_turn_end` in
|
||||
# hive-agent's turn.rs; the window is how long the rule
|
||||
# stays firing after it. The exact `_msg` match and the
|
||||
# unit keep out the subagent daemon's `subagent: turn
|
||||
# failed` and the ACP backend's `ACP checkpoint turn
|
||||
# failed`, which a phrase match would also count.
|
||||
(logCountRule {
|
||||
uid = "hh-agent-turn-failed";
|
||||
title = "agent turn failed";
|
||||
windowSeconds = 900;
|
||||
expr = ''_time:15m _SYSTEMD_UNIT:="hive-agent.service" _msg:="turn failed" | stats by (hive, agent) count() as failures'';
|
||||
for = "0s";
|
||||
summary = "{{ $labels.hive }}/{{ $labels.agent }} had a claude turn fail; the reason is the `F_ERROR` field of the harness's `turn failed` log line";
|
||||
})
|
||||
# Same cadence as the forge rule: one pass every 5m
|
||||
# (`RECONCILE_INTERVAL` in swarm-controller's
|
||||
# agent_renewal.rs), one line per failed pass, so one
|
||||
|
|
|
|||
|
|
@ -288,6 +288,25 @@ let
|
|||
&& lib.all (r: r.field == "severity_text" && r.enabled == true && lib.elem r.level canonical) rules
|
||||
&& lib.all (text: lib.any (r: r.value == text) rules) emitted;
|
||||
}
|
||||
{
|
||||
# Harness turn failures reach the operator only through this rule, so
|
||||
# its absence is silent. Asserted on the query, not the title: the
|
||||
# exact `_msg` is what keeps subagent turn failures out, and a phrase
|
||||
# match would still provision and fire.
|
||||
name = "the agent turn-failure alert rule is provisioned against the harness's log line";
|
||||
ok =
|
||||
let
|
||||
groups =
|
||||
grafanaOldPath.containers.swarm-grafana.config.services.grafana.provision.alerting.rules.settings.groups;
|
||||
rules = lib.concatMap (g: g.rules) groups;
|
||||
rule = lib.findFirst (r: r.uid == "hh-agent-turn-failed") null rules;
|
||||
expr = (lib.head rule.data).model.expr;
|
||||
in
|
||||
rule != null
|
||||
&& lib.hasInfix ''_SYSTEMD_UNIT:="hive-agent.service"'' expr
|
||||
&& lib.hasInfix ''_msg:="turn failed"'' expr
|
||||
&& lib.hasInfix "stats by (hive, agent)" expr;
|
||||
}
|
||||
{
|
||||
# 🩸 The regression this guards: moving bao's metrics scrape into its
|
||||
# own in-container collector made that collector stamp every exported
|
||||
|
|
|
|||
|
|
@ -30,8 +30,8 @@
|
|||
# `./reserved-hive-fragments.nix` — read its header before merging the two.
|
||||
[
|
||||
# ---- message-layer senders -------------------------------------------
|
||||
# The human at the dashboard: a broker recipient (the T4LK box sends
|
||||
# `{from: "operator", ...}`) and the fallback attribution for an answered
|
||||
# The human at the dashboard: the sender of operator-authored messages
|
||||
# (`{from: "operator", ...}`) and the fallback attribution for an answered
|
||||
# question.
|
||||
"operator"
|
||||
# Helper events (`container_crash`, `needs_update`) — the sender an
|
||||
|
|
|
|||
Loading…
Reference in a new issue