Watch
0
0
Fork
You've already forked hyperhive
0

hive-agent: re-send the turn-state header once a minute

The header on `$SWARM.agent-state.<agent>` went out only when a bus event
changed it, and the core subject keeps nothing for a late subscriber. An
idle agent therefore said nothing to a reader that attached after its
last change: swarm-controller after a restart showed `effective: null`
for that agent until its next turn, which can be hours.

The publish loop now also wakes every 60 s and re-sends the current
header through the same `publish` path (clearing the dedup state first).
Transitions still go out as they happen. Same subject, same grant.

An agent whose queue connection is down now logs `swarm agent state:
publish skipped` once a minute even while idle, so the
`hh-swarm-agent-state-publish-skipped` alert also fires for idle
disconnected agents, not only busy ones.

Refs #5034
This commit is contained in:
atlas 2026-10-10 00:25:13 +02:00 • committed by mara
commit 19a045a401
8 changed files with 152 additions and 59 deletions

View file

@ -8,11 +8,11 @@
//! the agent's current state.
//!
//! **That difference is the one thing worth reading twice.** `hive-agent`
//! publishes a header on transition, to a core subject — so a client that
//! attaches mid-idle sees nothing until the next change, which for an agent
//! parked overnight can be hours. A renderer therefore has to open with
//! whatever `GET /api/agents/status` already gives it and let this stream
//! sharpen it, rather than treating an empty stream as an empty agent. The
//! publishes a header on transition and re-sends it once a minute, to a core
//! subject — so a client that attaches mid-idle sees nothing for up to a
//! minute. A renderer therefore has to open with whatever
//! `GET /api/agents/status` already gives it and let this stream sharpen
//! it, rather than treating an empty stream as an empty agent. The
//! alternative would be a KV bucket written on every turn-state flip, which
//! is the write rate this feature exists to avoid.
//!
@ -40,8 +40,8 @@ use crate::AppState;
turn-state header, JSON as hive-agent published it (opaque to this relay) — \
`turn_state`, `turn_state_since` (ISO 8601 UTC), `agent_state`, `model`, `effort`, \
`model_source`, `effort_source`, `resolved_model`, `context_window_tokens`, \
`ctx_usage`, `cost_usage`. Sent on \
transition only, live, no replay: an idle agent emits nothing until it changes",
`ctx_usage`, `cost_usage`. Sent on transition and re-sent once a minute, live, \
no replay",
body = String, content_type = "text/event-stream"),
(status = 400, description = "the agent name is not shaped like an identifier \
(problem+json)", body = String),

View file

@ -8,8 +8,9 @@
//!
//! What the agent runs on comes from its turn-state header
//! (`$SWARM.agent-state.<agent>`), which [`Reported`] keeps the latest of.
//! Agents publish a header only when it changes and the queue keeps none, so
//! an agent that has not changed since this daemon started has no entry.
//! Agents publish a header when it changes and re-send it once a minute, and
//! the queue keeps none, so an agent has no entry for up to a minute after
//! this daemon starts.
use std::collections::{BTreeSet, HashMap};
use std::sync::{Arc, Mutex, PoisonError};