fix(subagents): restore workflow-stage delegation and make the depth guard real - #2220
Merged
Conversation
Contributor
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
Contributor
There was a problem hiding this comment.
Pull request overview
Restores and hardens workflow-stage subagent delegation after regressions introduced by the in-process runner changeover, by fixing the child-policy gate semantics, making the depth guard enforceable from admitted policy (not env), and ensuring nested children resolve bundled resources against their admitted identity.
Changes:
- Fix subagent child-policy gating so read-only management actions (
list/get/status/doctor) work in fanout-denied children, while delegation and execution-continuing actions remain blocked. - Make the depth guard “real” by carrying admitted
depth/effectivemaxSubagentDepththrough typed policy and threadingparentDepththrough all execution modes (single/parallel/chain/async/resume). - Ensure nested in-process children load bundled package roots (and suppress workflow extension lifecycle inside stage-owned children), restoring workflow-stage delegation and nested tool availability.
Show a summary per file
| File | Description |
|---|---|
| test/unit/workflow-stage-bundled-resources.test.ts | Adds regression coverage for workflow-stage delegation via the registered subagent tool. |
| test/unit/wiring-adapters-01.test.ts | Updates stage session wiring expectation to allow fanout. |
| test/unit/subagents-inprocess-runner.test.ts | Adds admission/depth and builtin package path coverage for in-process children. |
| test/unit/subagents-inprocess-child-resources.test.ts | New end-to-end child session resource-loading regression tests (bundled tools, stage-owned child behavior, depth refusal). |
| test/unit/subagents-foreground-guard-propagation.test.ts | Expands coverage for per-agent max depth propagation across delegation modes and resume. |
| test/unit/subagents-depth-guard.test.ts | Rewrites tests to validate depth guard via admitted policy rather than env. |
| test/unit/subagents-child-policy-gate.test.ts | New regression suite for child-policy gating (fanout vs management) and depth enforcement. |
| packages/workflows/src/extension/atomic-stage-session.ts | Fixes workflow-stage subagent policy to permit delegation. |
| packages/workflows/CHANGELOG.md | Documents restored stage subagent behavior and nested stage child resource fix. |
| packages/subagents/src/shared/types-depth.ts | Replaces env-based depth logic with policy-based depth/max resolution helpers. |
| packages/subagents/src/shared/types-config.ts | Adds parentDepth to run options so admission sees correct nesting depth. |
| packages/subagents/src/shared/types-async.ts | Retains per-child effective max depth for reliable resume behavior. |
| packages/subagents/src/runs/inprocess/runner.ts | Carries effective max depth into admission policy and fixes child resource loader to include bundled package roots. |
| packages/subagents/src/runs/inprocess/background.ts | Threads parentDepth through async single params. |
| packages/subagents/src/runs/inprocess/background-single.ts | Propagates parentDepth into async single execution. |
| packages/subagents/src/runs/foreground/subagent-executor.ts | Fixes fanout gating ordering and makes resume/interrupt appropriately gated for non-fanout children. |
| packages/subagents/src/runs/foreground/subagent-executor-types.ts | Adds parentDepth to execution context data. |
| packages/subagents/src/runs/foreground/subagent-executor-status.ts | Stores per-child effective max depth alongside retained foreground results. |
| packages/subagents/src/runs/foreground/subagent-executor-single.ts | Threads parentDepth and records per-child max depth for retained runs. |
| packages/subagents/src/runs/foreground/subagent-executor-parallel.ts | Threads parentDepth and records per-child max depths for retained runs. |
| packages/subagents/src/runs/foreground/subagent-executor-parallel-task.ts | Passes parentDepth into each runSync task invocation. |
| packages/subagents/src/runs/foreground/subagent-executor-context.ts | Computes depth checks from admitted policy and records current depth as parentDepth. |
| packages/subagents/src/runs/foreground/subagent-executor-chain.ts | Threads parentDepth and records per-step effective max depth for retained runs. |
| packages/subagents/src/runs/foreground/subagent-executor-async.ts | Passes parentDepth through async dispatch. |
| packages/subagents/src/runs/foreground/inprocess-run-sync.ts | Uses parentDepth and preserves effective max depth on admitted child specs. |
| packages/subagents/src/runs/foreground/chain-execution-types.ts | Adds parentDepth to chain execution parameter shapes. |
| packages/subagents/src/runs/foreground/chain-execution-sequential-step.ts | Threads parentDepth into sequential chain step execution. |
| packages/subagents/src/runs/foreground/chain-execution-parallel-step.ts | Threads parentDepth into static parallel chain step execution. |
| packages/subagents/src/runs/foreground/chain-execution-parallel-runner.ts | Threads parentDepth into parallel chain runner execution. |
| packages/subagents/src/runs/foreground/chain-execution-dynamic-step.ts | Threads parentDepth into dynamic parallel chain step execution. |
| packages/subagents/skills/subagent/SKILL.md | Updates skill docs to reflect policy-based authority and parent-only skill behavior. |
| packages/subagents/README.md | Updates README to reflect policy-based authority and registered-tool vs authorization semantics. |
| packages/subagents/CHANGELOG.md | Documents fixed child-policy gate, restored depth guard, and restored nested child resources. |
| packages/coding-agent/src/core/extensions/context-types.ts | Extends SubagentChildPolicy to carry admitted depth and effective maxSubagentDepth. |
| packages/coding-agent/docs/workflows.md | Updates workflow docs to describe the new typed-policy depth enforcement mechanism. |
| packages/coding-agent/docs/subagents.md | Updates subagent docs to reflect registered-tool vs authority and policy-gated actions. |
| packages/coding-agent/CHANGELOG.md | Notes the new typed-policy enforcement for depth/max depth. |
Review details
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 37/37 changed files
- Comments generated: 3
- Review effort level: Lite
Comment on lines
+7
to
+10
| - Fixed every `subagent` action being refused with "Subagent fanout is not authorized for this child." for a child without fanout authorization. The fanout check ran before the management branch, so the observing actions — `list`, `get`, `status`, and `doctor` — were rejected with a message about delegation, which they do not perform. Those four now pass the gate. Fanout authorization gates delegation and every management action that can start or continue agent execution: `resume` revives a child and `interrupt` is privileged control over a running one, so both now receive the fanout refusal instead of reaching their handlers. Mutating management (`create`, `update`, `delete`) is still refused for a management-restricted child, by the narrower gate that this bug had made unreachable. Registration is not authority: a child loads bundled extensions through normal discovery and may therefore have the `subagent` tool registered, while typed admission policy decides which of its actions run. The bundled subagents skill stays parent-only and is stripped from every child prompt, including fanout-authorized children ([#2205](https://github.com/bastani-inc/atomic/pull/2205)). | ||
| - Fixed the in-process depth guard and nested workflow-stage children. Admission-issued child depth now travels in the typed policy into every single, parallel, chain, async, and resume path, so the executor can reject delegation at the configured limit while Rust admission keeps the hard five-level ceiling; the orphaned process-environment depth bridge and its self-fulfilling tests are gone. In-process children now load the bundled package resources needed to register `subagent`, so a nested child no longer starts with only the base built-in tools and no way to delegate, while workflow-stage children suppress only the workflow extension lifecycle ([#2205](https://github.com/bastani-inc/atomic/pull/2205)). | ||
| - Fixed an agent's `maxSubagentDepth` being dropped at the in-process admission door. A child admitted from an agent whose definition tightened the limit received a policy carrying no maximum, so it could keep delegating as if only the global five-level ceiling applied. The effective limit — the stricter of the parent's limit and the child agent's own — now travels on the admitted child spec and policy, is reissued unchanged by a cold reload, and is applied by the executor's depth check alongside the local configuration. Admission also derives the limit from the agent definition when a caller supplies a child spec without one, so the door no longer issues an unbounded policy for an agent that declared a limit ([#2205](https://github.com/bastani-inc/atomic/pull/2205)). | ||
| - Fixed a resumed foreground child losing the delegation limit its agent definition had narrowed. Retained resume re-derived the limit from the current stage or process configuration, so a child that ran under an agent maximum of 1 resumed with the configured maximum instead. The effective limit is now recorded per retained child — parallel and chain branches can each carry a different one — and reused on resume, so editing an agent definition between a run and its resume cannot widen that child's budget ([#2205](https://github.com/bastani-inc/atomic/pull/2205)). |
Comment on lines
+11
to
+12
| - Fixed workflow stages being unable to use the `subagent` tool at all. The stage policy set `managementActions: "full"` and `fanoutAuthorized: false` together, which contradict: stages could neither delegate nor, because of the companion subagents defect, run read-only management such as `subagent list`. Workflow stages are top-level sessions rather than subagent children, so the policy now sets `fanoutAuthorized: true`, restoring the delegation the workflow docs already describe. Nesting remains bounded by the typed in-process depth policy and Rust admission’s five-level ceiling ([#2205](https://github.com/bastani-inc/atomic/pull/2205)). | ||
| - Fixed nested subagents in workflow-stage sessions losing the bundled `subagent` extension. In-process child resource loading now carries the bundled package roots and disables only the workflow extension's repeated stage lifecycle, so a stage can delegate and its nested child receives the bundled tools instead of only the base built-in ones ([#2205](https://github.com/bastani-inc/atomic/pull/2205)). |
|
|
||
| ### Fixed | ||
|
|
||
| - Fixed in-process child sessions losing their admission-issued nesting depth and delegation limit. `SubagentChildPolicy` now carries both the admitted `depth` and the effective `maxSubagentDepth`, so the subagent executor can enforce the configured and inherited limits without relying on the removed process-environment bridge ([#2205](https://github.com/bastani-inc/atomic/pull/2205)). |
flora131
added a commit
that referenced
this pull request
Aug 6, 2026
Copilot review on #2220: the entries linked only to #2205, the regression source, so release-note readers could not trace the fixes to the PR that implemented them. All seven Unreleased bullets now lead with #2220 and keep #2205 as the named regression source. No released section is touched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The fanout authorization check ran before the management branch, so a child with `fanoutAuthorized: false` was refused every `subagent` action with "Subagent fanout is not authorized for this child." — including read-only management (list/get/status/doctor/interrupt/resume), which performs no delegation. That also made the narrower mutating-management gate unreachable dead code. Move the check to guard only the delegation path, and report the actual execution mode rather than a hardcoded "single". Workflow stages additionally shipped `managementActions: "full"` with `fanoutAuthorized: false`, which contradict. Stages are top-level sessions rather than subagent children, so the stage policy now sets `fanoutAuthorized: true`, restoring the delegation the workflow docs already describe. Nesting stays bounded by the unchanged five-level depth guard. Regression coverage in test/unit/subagents-child-policy-gate.test.ts; 7 of its 10 tests fail on the unfixed source.
…stration Adds the production door a workflow stage actually traverses: the full subagents extension resolving an executor from `ctx.subagentPolicy`. The existing case only covered the fanout-child registration door.
Two coupled defects from e4aa7ec (#2205), neither present in 0.9.12. WORKFLOW STAGES COULD NOT DELEGATE. WORKFLOW_STAGE_SUBAGENT_POLICY set `managementActions: "full"` and `fanoutAuthorized: false` in the same object. The two contradict, and the fanout gate discarded the "full" entirely. docs/workflows.md already promised stages the bundled subagent tool with the normal depth guard. Stages now carry fanoutAuthorized: true. THE TS DEPTH GUARD WAS DEAD CODE. checkSubagentDepth() read ATOMIC_SUBAGENT_DEPTH, which no production code ever wrote: #2205 deleted the env bridge and every OS child process, orphaning getSubagentDepthEnv() with zero production callers. So `blocked` was always false and checkDepthForExecution() could never block, at any depth, in any context. Its test stayed green only by writing the depth into process.env itself and asserting the reader read it back -- it simulated the propagation production no longer performed. The guard is now derived from the live admitted policy rather than an environment variable, via getCurrentSubagentDepth(ctx.subagentPolicy) and getInheritedMaxSubagentDepth(). The env machinery is deleted: SUBAGENT_DEPTH_ENV, getSubagentDepthEnv, hasWorkflowStageSubagentGuard, workflowSessionEnv, workflowSessionEnvFromContext, and the env-reading checkSubagentDepth. The Rust admission door in crates/atomic-natives remains the outer bound; the TS guard is now a real inner check rather than decoration. The depth tests no longer write process.env and instead assert against admitted policy depth, including the five-level limit, a stricter configured limit, the ceiling clamp, and inherited maximums in both directions. docs/subagents.md and docs/workflows.md are corrected to describe the mechanism that actually enforces the limit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fourth defect from #2205, observed live: a nested child inside a workflow stage came back with `status ok`, `toolCalls 0`, and an envelope of literally `undefined`, and appeared to lack a working subagent tool even though its stage granted the budget. In-process children resolved resources against the parent rather than their own admitted identity, so a nested child could be constructed without the tool set its policy allowed. Children now resolve their own resources and inherit the depth budget through the admitted policy, matching the guard rewritten in the previous commit. Covered by subagents-inprocess-child-resources.test.ts, plus updates to the runner, child-policy gate, guard-propagation, and workflow-stage bundled-resource suites. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Changelog entries under [Unreleased] for the four #2205 regressions, plus README and skill updates describing the real depth mechanism. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot review on #2220: the entries linked only to #2205, the regression source, so release-note readers could not trace the fixes to the PR that implemented them. All seven Unreleased bullets now lead with #2220 and keep #2205 as the named regression source. No released section is touched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
flora131
force-pushed
the
fix/2205-subagent-child-policy
branch
from
August 6, 2026 21:17
f133312 to
e970e04
Compare
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.
Four regressions from
e4aa7ec19(#2205). None are present in0.9.12—git merge-base --is-ancestor e4aa7ec19 0.9.12is false.1.
subagent listwas refused inside workflow stagesThe fanout check ran before the
params.actionbranch, so read-only management was rejected as "fanout", which it is not. The correct narrower gate already existed for mutating actions and was dead code.The gate now guards delegation only.
list,get,status, anddoctorpass for a fanout-denied child.resumeandinterruptdeliberately do not.resumerevives a child and drives further agent execution, so allowing it would hand a fanout-denied child exactly the capability the gate exists to deny. An earlier revision of this work did permit it; that was caught in review and is now covered by a regression test.2. Workflow stages could not delegate at all
WORKFLOW_STAGE_SUBAGENT_POLICYsetmanagementActions: "full"andfanoutAuthorized: falsein the same object. The two contradict, and defect 1 discarded the "full" entirely.docs/workflows.mdalready promised stages the bundled subagent tool with the normal depth guard.3. The TypeScript depth guard was dead code
checkSubagentDepth()readATOMIC_SUBAGENT_DEPTH, which no production code ever wrote — #2205 deleted the env bridge and every OS child process, orphaninggetSubagentDepthEnv()with zero production callers.blockedwas therefore alwaysfalse, andcheckDepthForExecution()could never block at any depth in any context.Its test stayed green by writing the depth into
process.envitself and asserting the reader read it back — it simulated the propagation production no longer performed.The guard now derives from the live admitted policy via
getCurrentSubagentDepth(ctx.subagentPolicy)andgetInheritedMaxSubagentDepth(). The env machinery is deleted:SUBAGENT_DEPTH_ENV,getSubagentDepthEnv,hasWorkflowStageSubagentGuard,workflowSessionEnv,workflowSessionEnvFromContext, and the env-readingcheckSubagentDepth.The Rust admission door in
crates/atomic-nativesremains the outer bound; the TS guard is now a real inner check rather than decoration.4. Nested children resolved resources against the parent
Observed live: a nested child inside a workflow stage returned
status ok,toolCalls 0, and an envelope of literallyundefined, and appeared to lack a working subagent tool despite its stage granting the budget. Children now resolve resources against their own admitted identity.Verification
npm run check— exit 0npm run test:unit— exit 0, 619 files, 5865 passed, 1 skippedprocess.env; they assert against admitted policy depth across the five-level limit, stricter configured limits, the ceiling clamp, and inherited maximums in both directionsdocs/subagents.mdanddocs/workflows.mdcorrected to describe the mechanism that actually enforces the limitNote for reviewers
This is four defects in one PR because 2 and 3 are genuinely coupled — enabling stage delegation while the depth guard was inert would have been irresponsible, and the guard rewrite touches the same call chain. The commit history separates them. Happy to split if preferred.
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.Greptile Summary
Workflow sessions can launch their allowed child while preserving the child’s delegation depth and limit. Children without fanout permission retain read-only inspection actions but cannot start, resume, or interrupt delegated work.
Confidence Score: 5/5
No blocking failure remains.
The exercised workflow delegation, depth propagation, and child permission paths behaved as intended.
What T-Rex did
Reviews (3): Last reviewed commit: "docs(changelog): attribute the policy fi..." | Re-trigger Greptile