Repository navigation
feat(core): bound subagent concurrency - #1
Merged
Merged
Conversation
Caps how many of one session subagents run at once with experimental.subagent_concurrency (-1 unlimited, 0 none, N a limit) and chooses over-limit behavior with experimental.subagent_over_limit (fail or wait). OPENCODE_SUBAGENT_CONCURRENCY outranks every config document, and a cap of 0 hides the subagent tool. Slots live in SubagentJob per parent session, keyed by child session ID, so a rejected call leaves no orphan session.
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.
Summary
experimental.subagent_concurrencycaps how many of one session's subagents run at once. Omit it or set-1for unlimited (today's behavior),0for none, or any positive count for a real cap.experimental.subagent_over_limitdecides what an over-limit call does:"fail"(default) returns a tool error right away,"wait"queues it until a slot frees.OPENCODE_SUBAGENT_CONCURRENCYsets the same cap when OpenCode starts and outranks every config document, the wayOPENCODE_FLAGSoutranksexperimental.<flag>inFlag.resolve.0thesubagenttool leaves the advertised tool list, so the model is never offered a call that can only fail.How it works
SubagentJobservice as oneMap<parentID, {children, waiting}>;limit < 0short-circuits with no bookkeeping, so the default path stays inert.SubagentJob.limits()reads both keys with a per-keyfindLastscan instead of oneConfig.latest("experimental")block, so a global value survives a project's partialexperimentalblock.subagent_depthkeeps its whole-object read. Same choice asall_flagsinFlag.resolve.sessions.create, keyed by a pre-generatedSessionSchema.ID.create(), so a rejected call leaves no orphan session. Release is idempotent and owned either by the call fiber (Effect.ensuringwith ahandoffflag) or, for backgrounded work, by the completion observer.sessionIDpassed back in) keeps its existing slot instead of budgeting a second one.Visual
Two slots run, the third stays dashed until one frees.
Feature flag
None. The elected default (
-1, unlimited) reproduces today's behavior exactly, so nothing user-visible changes until a config sets a key. Both keys already sit in theexperimentalblock, andOPENCODE_SUBAGENT_CONCURRENCYis itself the per-process switch.Tests
packages/schema/test/config.test.ts— round-trips both keys, rejects-2,1.5,"2"and the"queue"spelling, and omits an unset key.packages/core/test/config/normalization.test.ts— a rejected leaf drops only itself, so its sibling still lands.packages/core/test/session-subagent-job.test.ts— purelimits()precedence (no setting, per-key from separate documents, a later partial block does not shadow an earlier cap, the override outranks documents and reports a rejected value) plus slot semantics: unlimited stays unbudgeted, per-parent allowance, idempotent acquire and release, cap of zero in both modes, a queuedwaitcall woken by a release, and a cancelled wait.packages/core/test/tool-subagent.test.ts— six new cases: cap of 1 fails a second call and frees the slot when the child settles, a rejected call creates no session, a follow-up needs no second slot, the tool stays listed at cap 2, no tool at cap 0,waitmode runs the queued call, unlimited fan-out, and the startup override outranks the config file.bun run script/test.tsfor the four files: 82 pass, 0 fail.bun run checkgreen except@opencode/app#typecheck, which fails only on this Windows checkout becausecore.symlinks=falseturnspackages/app/src/custom-elements.d.ts(git mode120000) into a text file.bun run check:generatedpasses in bothpackages/protocolandpackages/client.