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:
parent
6982b2ec14
commit
d723fac0be
20 changed files with 655 additions and 113 deletions
|
|
@ -6,6 +6,8 @@
|
|||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use swarm_queue_client::agent_inference::Source;
|
||||
|
||||
/// Path to the persisted model file. Overridable via `HYPERHIVE_MODEL_FILE`
|
||||
/// for dev / tests; otherwise derived from the agent's harness dir.
|
||||
fn model_file_path() -> PathBuf {
|
||||
|
|
@ -53,25 +55,37 @@ pub(crate) fn load_effort() -> Option<String> {
|
|||
}
|
||||
}
|
||||
|
||||
/// The model this agent runs when the swarm sets none. Priority:
|
||||
/// `configured` (`HIVE_DEFAULT_MODEL`, from `services.hyperhive.agent.model`
|
||||
/// in agent.nix) > the persisted runtime override > [`DEFAULT_MODEL`].
|
||||
pub(crate) fn local_model(configured: Option<&str>) -> String {
|
||||
configured.map_or_else(
|
||||
|| load_model().unwrap_or_else(|| DEFAULT_MODEL.to_owned()),
|
||||
str::to_owned,
|
||||
)
|
||||
/// The model this agent runs when the swarm sets none, and where it comes
|
||||
/// from. Priority: `configured` (`HIVE_DEFAULT_MODEL`, from
|
||||
/// `services.hyperhive.agent.model` in agent.nix) > the persisted runtime
|
||||
/// override > [`DEFAULT_MODEL`].
|
||||
pub(crate) fn local_model(configured: Option<&str>) -> (String, Source) {
|
||||
model_from(configured, load_model())
|
||||
}
|
||||
|
||||
/// The effort this agent runs at when the swarm sets none. The inverse of
|
||||
/// [`local_model`]: a persisted operator pick wins over `configured`
|
||||
/// (`HIVE_DEFAULT_EFFORT`), so the runtime choice survives a harness restart
|
||||
/// (it only resets on `--purge`); nix is the default for a never-touched
|
||||
/// agent.
|
||||
pub(crate) fn local_effort(configured: Option<&str>) -> String {
|
||||
load_effort()
|
||||
.or_else(|| configured.map(str::to_owned))
|
||||
.unwrap_or_else(|| DEFAULT_EFFORT.to_owned())
|
||||
fn model_from(configured: Option<&str>, file: Option<String>) -> (String, Source) {
|
||||
match (configured, file) {
|
||||
(Some(nix), _) => (nix.to_owned(), Source::AgentNix),
|
||||
(None, Some(file)) => (file, Source::LocalFile),
|
||||
(None, None) => (DEFAULT_MODEL.to_owned(), Source::Default),
|
||||
}
|
||||
}
|
||||
|
||||
/// The effort this agent runs at when the swarm sets none, and where it
|
||||
/// comes from. The inverse of [`local_model`]: a persisted operator pick
|
||||
/// wins over `configured` (`HIVE_DEFAULT_EFFORT`), so the runtime choice
|
||||
/// survives a harness restart (it only resets on `--purge`); nix is the
|
||||
/// default for a never-touched agent.
|
||||
pub(crate) fn local_effort(configured: Option<&str>) -> (String, Source) {
|
||||
effort_from(configured, load_effort())
|
||||
}
|
||||
|
||||
fn effort_from(configured: Option<&str>, file: Option<String>) -> (String, Source) {
|
||||
match (file, configured) {
|
||||
(Some(file), _) => (file, Source::LocalFile),
|
||||
(None, Some(nix)) => (nix.to_owned(), Source::AgentNix),
|
||||
(None, None) => (DEFAULT_EFFORT.to_owned(), Source::Default),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn persist_effort(level: &str) -> std::io::Result<()> {
|
||||
|
|
@ -291,7 +305,10 @@ pub fn context_window_tokens(model: &str) -> u64 {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{DEFAULT_EFFORT, EFFORT_LEVELS, is_valid_effort};
|
||||
use super::{
|
||||
DEFAULT_EFFORT, DEFAULT_MODEL, EFFORT_LEVELS, Source, effort_from, is_valid_effort,
|
||||
model_from,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn effort_validation_accepts_only_known_levels() {
|
||||
|
|
@ -312,4 +329,38 @@ mod tests {
|
|||
assert!(EFFORT_LEVELS.contains(&DEFAULT_EFFORT));
|
||||
assert_eq!(DEFAULT_EFFORT, "medium");
|
||||
}
|
||||
|
||||
fn pick(value: &str, source: Source) -> (String, Source) {
|
||||
(value.to_owned(), source)
|
||||
}
|
||||
|
||||
/// agent.nix wins over the file for the model.
|
||||
#[test]
|
||||
fn each_model_source() {
|
||||
let file = || Some("sonnet".to_owned());
|
||||
assert_eq!(
|
||||
model_from(Some("opus"), file()),
|
||||
pick("opus", Source::AgentNix)
|
||||
);
|
||||
assert_eq!(model_from(None, file()), pick("sonnet", Source::LocalFile));
|
||||
assert_eq!(model_from(None, None), pick(DEFAULT_MODEL, Source::Default));
|
||||
}
|
||||
|
||||
/// The file wins over agent.nix for the effort.
|
||||
#[test]
|
||||
fn each_effort_source() {
|
||||
let file = || Some("low".to_owned());
|
||||
assert_eq!(
|
||||
effort_from(Some("high"), file()),
|
||||
pick("low", Source::LocalFile)
|
||||
);
|
||||
assert_eq!(
|
||||
effort_from(Some("high"), None),
|
||||
pick("high", Source::AgentNix)
|
||||
);
|
||||
assert_eq!(
|
||||
effort_from(None, None),
|
||||
pick(DEFAULT_EFFORT, Source::Default)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue