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
5.5 KiB
Per-agent inference settings
The swarm can set the model and reasoning effort each agent's turns run on, plus the models it may start subagents on. A field you set wins over the agent's own value; a field you leave unset keeps the agent on its own value, so an agent you set nothing for runs as it does today.
Setting them
swarmctl agent inference set iris --model opus --effort high
swarmctl agent inference get iris
swarmctl agent inference clear iris
setchanges only the flags you pass; the other fields keep what's stored. A hive must declare the agent.--effortis one oflow,medium,high,xhigh,max.--subagent-modelstakes a comma-separated list. It replaces the agent'savailableModelsas the models its subagentstartandcontinuecalls may name, read on each call (subagent.md). The web UI's model picker doesn't use it.getshows each field, naming the unset ones, and then the model and effort the agent reports running on, each with where it comes from.clearremoves every field; the agent goes back to its own values.
The full flag reference is swarmctl-cli.md.
What an agent runs on
get shows what the agent runs on next to what the swarm sets:
model: opus
effort: (not set; the agent's own)
subagent_models: (not set; the agent's own)
running model: opus (swarm)
running effort: medium (local_file)
Each running value names where it comes from:
| source | meaning |
|---|---|
swarm |
the value set here |
agent_nix |
services.hyperhive.agent.model or effortLevel in the agent's agent.nix |
local_file |
a /model or /effort pick in the agent's web UI |
default |
the harness's built-in default (haiku, medium) |
When the agent starts without a swarm value, its agent.nix model wins over
its /model pick, and its /effort pick wins over its agent.nix effort.
The agent reports these whenever they change. swarm-controller keeps only
what it has received since it started, so after a controller restart get
shows running: not reported… for an agent until its next change (its next
turn, or a setting changing).
When an agent picks a change up
Each agent reads its settings every 10 seconds and switches on the next read: its next turn runs on the new model or effort, and its web UI badge shows it. A turn already running keeps what it started with.
- Its own value is the one it runs without the swarm, from a
/modelor/effortpick in its web UI or from its agent.nix. Setting a swarm value doesn't overwrite it. - Clearing a field (or the whole key) puts the agent back on its own value on the next read.
- A
/modelor/effortpick while the swarm sets that field changes the agent's own value, but the swarm value comes back on the next read. - After a restart, the agent runs its own values until its first read of its settings succeeds.
Implementation
The settings live in the agent-inference KV bucket, one key per agent,
which swarm-controller alone writes:
{ "model": "opus", "effort": "high", "subagent_models": ["haiku", "sonnet"] }
Every field is optional, and {} reads the same as no key.
| method | path | body |
|---|---|---|
GET |
/api/agents/<agent>/inference |
—; answers {"configured": <the stored value, {} when none>, "effective": <what the agent reported, or null>} |
PUT |
/api/agents/<agent>/inference |
the whole value, replacing the stored one; the controller refuses unknown fields, an unknown effort, or a model name with whitespace |
DELETE |
/api/agents/<agent>/inference |
—; answers 204 |
An agent's own queue credential grants it
$JS.API.DIRECT.GET.KV_agent-inference.$KV.agent-inference.<agent>, the
direct read of its own key, and nothing else in this bucket — the same shape
as its schedule key (schedules.md).
The harness (hive-agent's swarm_inference) applies a set field to the
in-memory model or effort without writing hyperhive-model or
hyperhive-effort, which keep the agent's own value.
The subagent daemon (hive-subagent-mcp's swarm_models) reads
subagent_models with the same direct get, under the same credential, on
each start or continue that names a model.
effective comes from the agent's turn-state header on
$SWARM.agent-state.<agent> (README.md), which carries
model, effort, model_source and effort_source. swarm-controller
subscribes to every agent's header with the reader grant it already holds for
the state relay and keeps the latest per agent in memory. A header without
those fields, from an agent that doesn't report them, leaves effective
null.