Repository navigation
feat(subagent): replace the computed batch ceiling with a default and a setting - #350
Merged
Merged
Conversation
… a setting A parallel subagent batch derived its ceiling from the host CPU count (cpu-2, capped at 16, applied twice) and an env var, so the effective concurrency was impossible to reason about and the env var could only ever lower it. The ceiling is now a plain default of 8 plus one optional setting: [subagent] max_concurrency (StepCode JSON: subagent.maxConcurrency), read on every call so a config reload takes effect without a restart. Any positive integer is accepted, so the configured value may raise the limit above the default; invalid values are reported as settings warnings and ignored, falling back to the option and then the default. The maxParallelTasks batch-size cap, the shared workflow admission window, and background-lane behavior are unchanged. STEP_WORKFLOW_CONCURRENCY_LIMIT still caps workflow agent() fan-out; it no longer caps subagent batches. The old test asserting that env cap is replaced by one asserting its removal, and new tests cover the resolver, the default, a configured value above the default, the env var, invalid values, and the settings paths.
liuedcson
requested review from
MelodyVAR,
Uking-xxx and
ZouR-Ma
as code owners
November 7, 2026 09:33
…hain The configured value is already validated at the settings boundary (settings-manager / stepcode-config reject anything that is not a positive integer), so the execution path no longer re-validates it. The ceiling is now `configured ?? option ?? default`, and the extension factory stores the host option as-is. This drops the double resolution (creation time plus per call) and the `resolveSubagentConcurrency` helper whose silent fallbacks made an invalid host option look valid two layers later. Invalid values are still rejected with a settings warning at load time.
…d window
The previous commit removed the numeric guard along with the CPU-derived
ceiling. `maxConcurrency` is an exported extension option, so a host that
passed 0, a negative number, or NaN reached `acquireWorkflowAdmission` as the
process-wide capacity: the pool admits nothing, every queued child waits with
no error and no timeout, and concurrent workflows are wedged with it. NaN was
worse still — `Array.from({ length: NaN })` starts zero workers, so the batch
reports `0/N succeeded` as a successful result.
Resolution is now `resolveSubagentConcurrency(configured, option)` again: the
settings boundary validates the configured value, and the option is validated
here so an invalid one cannot become a capacity. The factory rejects an
invalid option outright instead of silently clamping it, and
`WorkflowSemaphore.setCeiling` clamps as the last line of defense, since the
pool's capacity is the smallest ceiling among its members.
Also fixes what the change left behind:
- The StepCode JSON path reports `subagent.maxConcurrency` instead of the
TOML key, and `subagent.backgroundTimeoutSeconds` now reports its ignored
value the same way its neighbour does, instead of vanishing silently.
- `reload()` drops diagnostics from the previous snapshot so a warning about
a value the user already fixed does not resurface.
- CHANGELOG and both settings documents describe the new default, the
removed env coupling, and the shared window's minimum-of-members capacity;
the subagent settings section is no longer titled "Background subagents"
even though it governs every parallel batch.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The parallel subagent batch ceiling was "computed": the factory derived it from the host CPU count (
clampWorkflowConcurrency:cpus-2, capped at 16, and it clamped even an explicitly configured value down), and execution took the min withSTEP_WORKFLOW_CONCURRENCY_LIMITon top. The effective concurrency was impossible to reason about, the env var could only ever lower it, and the subagent path was coupled to the workflow provider-limit concept.Solution
The ceiling is now a plain default of 8 plus one optional setting:
[subagent] max_concurrency(StepCode JSON:subagent.maxConcurrency) →StepSettings.subagentMaxConcurrency→ the bootstrap accessor →resolveSubagentConcurrency(configured, option)→min(result, maxParallelTasks)→ the batch mapper plus the shared workflow admission window.maxParallelTasksbatch-size cap, the shared workflow admission window (including 429 backoff), and background-lane behavior.STEP_WORKFLOW_CONCURRENCY_LIMITstill caps workflowagent()fan-out; it no longer caps subagent batches.Testing
test/subagent-concurrency.test.ts: resolver defaults, precedence, and invalid fallback; with CPUs mocked to 2, the default peak is 8, a configured 12 reaches peak 12, the env var no longer caps the batch, invalid values fall back, oversized batches are rejected, and tasks beyond the ceiling queue.drainErrors()warning carrying the config path.subagent-chain-result.test.tsthat asserted the env var lowered subagent concurrency; it now asserts the removal.tsgo --noEmitand the rootcheck(biome plus all contract checks) pass; the affected test suites pass (75/75).Review
A subagent review pass was run over the diff. Its three findings were all fixed before submitting: a
drainErrors()diagnostic for invalid values on the JSON load path, a load-time warning that no longer claims a fixed fallback value, and tests that actually cover themaxParallelTasksrejection and queueing paths.