Watch
0
0
Fork
You've already forked hyperhive
0

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:
atlas 2026-10-13 19:24:13 +02:00 • committed by mara
commit 9a71d1e094
10 changed files with 393 additions and 94 deletions

View file

@ -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