Watch
0
0
Fork
You've already forked hyperhive
0

swarm: per-agent inference settings (model, effort, subagent_models) in KV

swarm-controller keeps each agent's inference settings in a new
`agent-inference` KV bucket, one key per agent, and is its only writer
(`GET`/`PUT`/`DELETE /api/agents/{name}/inference`; `swarmctl agent
inference get|set|clear`). Every field is optional. Each agent reads its
own key every 10 s through a direct-get grant on that key alone, and a
set `model` or `effort` replaces the Bus value, so the next turn runs on
it and the badge follows.

The swarm value is applied without persisting it: `Bus::set_model` and
`set_effort` are split into a persisting setter and a non-persisting
`apply_*`, and the poller uses the latter. Otherwise the swarm value
would overwrite `hyperhive-model`/`hyperhive-effort`, and clearing it, or
a restart, would no longer find the agent's own value. An unset field
changes nothing; a field cleared after being applied goes back to the
local value (`harness_state::local_model`/`local_effort`, the same
resolution `Bus::new` uses), read again at the clear.

`subagent_models` is validated, stored and shown by `get`; no agent
applies it yet.

Refs #5034
This commit is contained in:
atlas 2026-10-09 23:11:58 +02:00
commit 916fdb35b8
24 changed files with 1432 additions and 34 deletions

View file

@ -19,9 +19,8 @@ use serde::{Deserialize, Serialize};
use tokio::sync::broadcast;
use crate::harness_state::{
DEFAULT_EFFORT, DEFAULT_MODEL, configured_effort, configured_model, context_window_tokens,
load_effort, load_model, persist_effort, persist_model, read_harness_state,
write_harness_state,
configured_effort, configured_model, context_window_tokens, local_effort, local_model,
persist_effort, persist_model, read_harness_state, write_harness_state,
};
const CHANNEL_CAPACITY: usize = 256;
@ -394,21 +393,8 @@ impl Bus {
}
};
let (tx, _) = broadcast::channel(CHANNEL_CAPACITY);
// Priority: HIVE_DEFAULT_MODEL (from services.hyperhive.agent.model in agent.nix) >
// persisted runtime override > compiled-in DEFAULT_MODEL.
// The nix config always wins on rebuild; the persisted file is kept
// for within-session tracking only (see persist_model / set_model).
let initial_model = configured_model().map_or_else(
|| load_model().unwrap_or_else(|| DEFAULT_MODEL.to_owned()),
str::to_owned,
);
// Effort precedence is the inverse of model: a persisted operator
// pick wins over the nix `HIVE_DEFAULT_EFFORT` baseline so the
// runtime choice survives harness restart (it only resets on
// `--purge`). nix is the default for a never-touched agent.
let initial_effort = load_effort()
.or_else(|| configured_effort().map(str::to_owned))
.unwrap_or_else(|| DEFAULT_EFFORT.to_owned());
let initial_model = local_model(configured_model());
let initial_effort = local_effort(configured_effort());
// Restore rate_limited (and needs_login) from the consolidated
// harness state file so the dashboard shows the correct status
// on cold load if the harness crashed while parked.
@ -590,10 +576,21 @@ impl Bus {
/// Panics if the internal lock is poisoned.
pub fn set_model(&self, name: impl Into<String>) {
let value: String = name.into();
self.model.lock().unwrap().clone_from(&value);
if let Err(e) = persist_model(&value) {
tracing::warn!(error = ?e, "model: persist failed");
}
self.apply_model(value);
}
/// Switch the model for future turns without persisting it, so
/// `hyperhive-model` keeps the agent's local value. For a value the
/// swarm sets (`crate::swarm_inference`).
///
/// # Panics
///
/// Panics if the internal lock is poisoned.
pub fn apply_model(&self, value: String) {
self.model.lock().unwrap().clone_from(&value);
// Mirror the resolved model into hyperhive-harness.json so
// hive-c0re can surface it on the dashboard badge without reading
// the hyperhive-model override file directly.
@ -624,10 +621,21 @@ impl Bus {
/// Panics if the internal lock is poisoned.
pub fn set_effort(&self, level: impl Into<String>) {
let value: String = level.into();
self.effort.lock().unwrap().clone_from(&value);
if let Err(e) = persist_effort(&value) {
tracing::warn!(error = ?e, "effort: persist failed");
}
self.apply_effort(value);
}
/// Switch the effort level without persisting it, so `hyperhive-effort`
/// keeps the agent's local value. For a value the swarm sets
/// (`crate::swarm_inference`).
///
/// # Panics
///
/// Panics if the internal lock is poisoned.
pub fn apply_effort(&self, value: String) {
self.effort.lock().unwrap().clone_from(&value);
self.emit(LiveEvent::EffortChanged { effort: value });
}