Watch
0
0
Fork
You've already forked hyperhive
0

swarm: show each agent's effective model and effort, and where they came from

Each agent now reports the model and effort it runs on, and where each
comes from (`swarm`, `agent_nix`, `local_file` or `default`), in its
turn-state header on `$SWARM.agent-state.<agent>`: `effort`,
`model_source` and `effort_source` join `model`. The shared
`swarm_queue_client::agent_inference::{Effective, Source}` types spell
those fields for both ends.

The Bus keeps a source next to the model and effort. `Bus::new` takes it
from the local fallback chain (`harness_state::local_model` /
`local_effort`, which now return the source with the value), a `/model`
or `/effort` pick sets `local_file`, and the swarm poller sets `swarm`
or, on a clear, the local chain's source. A swarm value equal to the one
the agent already runs is now applied once, so the source moves to
`swarm` and the header is republished; before, `next()` skipped it as
unchanged.

swarm-controller subscribes to every agent's header with the reader
grant it already holds for the state relay, and keeps the latest
inference fields per agent in memory (`inference::Reported`).
`GET /api/agents/{name}/inference` now answers
`{configured, effective}`, `effective` being `null` until the agent's
first header with these fields since the controller started.
`swarmctl agent inference get` prints both; `set` reads `configured`.

Refs #5034
This commit is contained in:
atlas 2026-10-10 00:00:25 +02:00 • committed by mara
commit d723fac0be
20 changed files with 655 additions and 113 deletions

View file

@ -787,6 +787,10 @@ struct AppState {
/// connection, several consumers" rationale as `agent_status` above,
/// and `None` in exactly the state `status` is.
agent_icons: Option<Arc<agent_icon::AgentIconReader>>,
/// What each agent last reported running on, from its turn-state
/// headers on `status`'s connection. `None` in exactly the state
/// `status` is.
reported_inference: Option<Arc<inference::Reported>>,
/// The swarm-level job graph, wrapped in its
/// [`hive_jobq::scheduler::Scheduler`] now that something drives it
/// (`spawn_jobq_worker`) — the graph alone was enough for the
@ -1138,6 +1142,32 @@ fn agent_icon_reader(
Some(reader)
}
/// What each agent last reported running on, read on the status reader's
/// connection — same rationale as [`wanted_writer`].
fn reported_inference(
status: Option<&Arc<status::StatusReader>>,
) -> Option<Arc<inference::Reported>> {
Some(inference::Reported::spawn(status?.queue_client()))
}
/// Same "log and carry on" shape as the queue/bridge/forge wiring in `main`.
/// A controller that cannot hold a webhook secret still serves every other
/// route; the webhook endpoint answers 503, which is the honest answer
/// rather than a silent accept. Lifted out of `main` for
/// `clippy::too_many_lines`.
fn load_webhook_secret() -> Option<Arc<str>> {
match webhook::load_or_generate_secret() {
Ok(secret) => Some(Arc::from(secret)),
Err(e) => {
tracing::warn!(
error = %format!("{e:#}"),
"webhook secret unavailable; swarm-wide forge webhooks are off"
);
None
}
}
}
/// A hive name that is shaped like one and names a hive this swarm has.
///
/// Reports the status and the detail rather than a rendered
@ -3119,20 +3149,7 @@ async fn main() -> Result<()> {
let _jobq_metrics_provider =
hive_jobq_metrics::spawn_exporter(Arc::clone(&jobq), "swarm-controller", jobq_http_client);
// Same "log and carry on" shape as the queue/bridge/forge wiring above.
// A controller that cannot hold a webhook secret still serves every
// other route; the webhook endpoint answers 503, which is the honest
// answer rather than a silent accept.
let webhook_secret = match webhook::load_or_generate_secret() {
Ok(secret) => Some(Arc::from(secret)),
Err(e) => {
tracing::warn!(
error = %format!("{e:#}"),
"webhook secret unavailable; swarm-wide forge webhooks are off"
);
None
}
};
let webhook_secret = load_webhook_secret();
// Diagnostic only — `put_matrix_account` does not call this yet (see its
// own doc comment), so this cannot change any request's outcome. Logged
@ -3178,6 +3195,7 @@ async fn main() -> Result<()> {
wanted: wanted_writer(status.as_ref()),
agent_status: agent_status_reader(status.as_ref()),
agent_icons: agent_icon_reader(status.as_ref()),
reported_inference: reported_inference(status.as_ref()),
status,
jobq,
webhook_secret,
@ -3359,6 +3377,7 @@ mod tests {
wanted: None,
agent_status: None,
agent_icons: None,
reported_inference: None,
jobq: std::sync::Arc::clone(&sched),
webhook_secret: None,
config_prs: std::sync::Arc::default(),