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

@ -214,7 +214,9 @@ The second thing an agent publishes is its **turn-state header**, on
`$SWARM.agent-state.<agent>` — same shape of subject, same grant mechanics,
same lack of retention. It carries what a header bar wants: what the turn loop
is doing (`turn_state`, plus `turn_state_since` as an ISO 8601 UTC stamp),
which model (`model` and the resolved id the last turn actually ran on), the
which model and effort (`model`, `effort`, where each comes from as
`model_source` and `effort_source`, and the resolved id the last turn
actually ran on), the
context budget and the last turn's context and cost token blocks, and
`agent_state`.
@ -238,7 +240,9 @@ opens with the bucket's snapshot and lets this stream refine it.
Swarm-side, `GET /api/agents/<name>/state/stream` relays that subject as SSE,
resolving the agent's hive at request time exactly as the terminal stream does.
The payload passes through opaquely — the controller never parses a header.
The payload passes through opaquely. Separately, the controller keeps each
agent's latest `model`, `effort` and their sources for
`GET /api/agents/<name>/inference` ([`inference.md`](inference.md)).
The third thing an agent publishes is its **icon**, the same SVG its own
`GET /icon` serves. It goes into the `agent-icons` KV bucket under the key

View file

@ -21,11 +21,41 @@ swarmctl agent inference clear iris
`continue` calls may name, read on each call
([`subagent.md`](../tools/subagent.md#which-models-a-subagent-may-run-on)).
The web UI's model picker doesn't use it.
- `get` shows each field, naming the unset ones.
- `get` shows each field, naming the unset ones, and then the model and
effort the agent reports running on, each with where it comes from.
- `clear` removes every field; the agent goes back to its own values.
The full flag reference is [`swarmctl-cli.md`](../tools/swarmctl-cli.md).
## What an agent runs on
`get` shows what the agent runs on next to what the swarm sets:
```text
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
@ -55,7 +85,7 @@ Every field is optional, and `{}` reads the same as no key.
| method | path | body |
| -------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `GET` | `/api/agents/<agent>/inference` | —; answers the stored value, `{}` when none |
| `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 |
@ -72,4 +102,12 @@ 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`](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`.
</details>

View file

@ -152,7 +152,7 @@ A field the swarm sets wins over the agent's own value (from its `/model` or `/e
###### **Subcommands:**
* `get` — Show what the swarm sets for an agent, naming the fields it doesn't set
* `get` — Show what the swarm sets for an agent, naming the fields it doesn't set, and the model and effort the agent runs on, each with where it comes from
* `set` — Set fields for an agent. Only the flags given change; the others keep what's stored
* `clear` — Clear every field for an agent, so it goes back to its own values
@ -160,7 +160,7 @@ A field the swarm sets wins over the agent's own value (from its `/model` or `/e
## `swarmctl agent inference get`
Show what the swarm sets for an agent, naming the fields it doesn't set
Show what the swarm sets for an agent, naming the fields it doesn't set, and the model and effort the agent runs on, each with where it comes from
**Usage:** `swarmctl agent inference get [OPTIONS] <AGENT>`