swarm-ui: per-agent cap on concurrently running subagents
Nothing capped how many subagents an agent runs at once; the operator wants to set that per agent from swarm-ui (#5234, shape (b)). AgentInference gains `max_subagents: Option<u32>`. An absent field decodes as None, so every stored value still parses and means "no cap", which is today's behaviour. swarm-controller accepts 1..=32 on PUT. 0 would refuse every start; 32 is far more concurrent claude sessions than one agent container has memory for, so anything above it is a typo, not a cap. The subagent daemon already read the agent's agent-inference key for subagent_models; it now reads it on every start and continue (not only those naming a model) and refuses a new run once the cap's worth of names is tracked, counted under the same lock as the name reservation. Every tracked slot counts: running, starting, a goal run between turns, and an interrupted one still exiting. A goal re-prompt never goes through the reservation, so it is never refused. A failed read applies no cap, as it falls back for models. swarmctl's `inference set` does GET, merge, PUT; its mirror type now carries the field so a set from the CLI keeps a cap set in swarm-ui instead of dropping it, and `get` prints it.
This commit is contained in:
parent
6d7fc0dd99
commit
9a71d1e094
10 changed files with 393 additions and 94 deletions
|
|
@ -1,7 +1,8 @@
|
|||
# Per-agent inference settings
|
||||
or a model name with whitespace or a leading `-`, or a `max_subagents` outside 1 to 32 |# 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
|
||||
plus the models it may start subagents on and how many subagents it may run
|
||||
at once. 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.
|
||||
|
||||
|
|
@ -20,6 +21,10 @@ swarmctl agent inference clear iris
|
|||
agent's `availableModels` as the models its subagent `start` and
|
||||
`continue` calls may name, read on each call
|
||||
([`subagent.md`](../tools/subagent.md#which-models-a-subagent-may-run-on)).
|
||||
- You set the subagent cap, `max_subagents`, in the swarm UI's inference
|
||||
dialog; `set` has no flag for it and keeps the stored one. It's a whole
|
||||
number from 1 to 32; unset means no cap
|
||||
([`subagent.md`](../tools/subagent.md#how-many-subagents-may-run-at-once)).
|
||||
- `get` shows each field, naming the unset ones (or says the swarm sets
|
||||
nothing for the agent), and then the model and effort the agent reports
|
||||
running on, each with where it comes from.
|
||||
|
|
@ -39,6 +44,7 @@ command.
|
|||
model: opus
|
||||
effort: (not set; the agent's own)
|
||||
subagent_models: (not set; the agent's own)
|
||||
max_subagents: (not set; no cap)
|
||||
running model: opus (swarm)
|
||||
running effort: medium (local_file)
|
||||
```
|
||||
|
|
@ -83,7 +89,12 @@ The settings live in the `agent-inference` KV bucket, one key per agent,
|
|||
which swarm-controller alone writes:
|
||||
|
||||
```json
|
||||
{ "model": "opus", "effort": "high", "subagent_models": ["haiku", "sonnet"] }
|
||||
{
|
||||
"model": "opus",
|
||||
"effort": "high",
|
||||
"subagent_models": ["haiku", "sonnet"],
|
||||
"max_subagents": 3
|
||||
}
|
||||
```
|
||||
|
||||
Every field is optional. The agent runs the same with `{}` as with no key;
|
||||
|
|
@ -108,8 +119,8 @@ 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.
|
||||
`subagent_models` and `max_subagents` with the same direct get, under the
|
||||
same credential, on each `start` or `continue`.
|
||||
|
||||
`effective` comes from the agent's turn-state header on
|
||||
`$SWARM.agent-state.<agent>` ([`README.md`](README.md)), which carries
|
||||
|
|
|
|||
|
|
@ -221,11 +221,12 @@ live rows only.
|
|||
|
||||
The agent's detail panel shows its model and effort under **inference**, each
|
||||
with where it comes from: the swarm, its agent.nix, its own pick, or the
|
||||
default. Below them is the subagent model list the swarm sets, or "its own"
|
||||
when it sets none. **edit** opens a dialog with the three fields, filled with
|
||||
what the swarm stores; saving does a `PUT /api/agents/{agent}/inference` of
|
||||
default. Below them are the subagent model list the swarm sets, or "its own"
|
||||
when it sets none, and the subagent cap, which reads "no cap" when unset.
|
||||
**edit** opens a dialog with the four fields, filled with what the swarm
|
||||
stores; saving does a `PUT /api/agents/{agent}/inference` of
|
||||
the whole value. A field you leave empty stays out of it, so the agent keeps
|
||||
its own value for that field. **clear the swarm's setting** sends the `DELETE`.
|
||||
its own value for that field; an empty max subagents is no cap. **clear the swarm's setting** sends the `DELETE`.
|
||||
The panel reads `GET /api/agents/{agent}/inference`; before the agent's first
|
||||
report reaches the controller, it shows the swarm's stored values only.
|
||||
[inference](inference.md) describes the settings themselves.
|
||||
|
|
|
|||
|
|
@ -337,6 +337,26 @@ happens until the daemon's first connection to the swarm queue succeeds.
|
|||
The match ignores case. On an ACP agent the names are the agent's own, such
|
||||
as `<provider>/<model>` on `opencode`.
|
||||
|
||||
## How many subagents may run at once
|
||||
|
||||
When the swarm sets `max_subagents` for the agent
|
||||
([`inference.md`](../swarm/inference.md)), the daemon refuses a `start`, or a
|
||||
`continue` of a session that isn't running, once that many subagents are
|
||||
running, before anything spawns:
|
||||
|
||||
```text
|
||||
3 subagents running, cap is 3 (set in swarm-ui → this agent → inference) — wait for one to finish, or `interrupt` one
|
||||
```
|
||||
|
||||
Every subagent the daemon tracks counts, whatever its role: one running a
|
||||
turn, one starting, a goal run between turns, and an interrupted one whose
|
||||
process hasn't exited yet. A goal run's next turn isn't a new start, and the
|
||||
cap never stops it. Lowering the cap stops nothing already running.
|
||||
|
||||
The daemon reads the cap with the model list, on every `start` and
|
||||
`continue`, so a change applies to the next call. Without a swarm value, or
|
||||
when the read fails, no cap applies.
|
||||
|
||||
## Runtime
|
||||
|
||||
A subagent runs on its parent agent's runtime
|
||||
|
|
|
|||
Loading…
Reference in a new issue