Skip to content

fix(subagents): restore workflow-stage delegation and make the depth guard real - #2220

Merged
flora131 merged 6 commits into
mainfrom
fix/2205-subagent-child-policy
Aug 6, 2026
Merged

fix(subagents): restore workflow-stage delegation and make the depth guard real#2220
flora131 merged 6 commits into
mainfrom
fix/2205-subagent-child-policy

Conversation

@lavaman131

@lavaman131 lavaman131 commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

Four regressions from e4aa7ec19 (#2205). None are present in 0.9.12git merge-base --is-ancestor e4aa7ec19 0.9.12 is false.

1. subagent list was refused inside workflow stages

subagent list
Subagent fanout is not authorized for this child.

The fanout check ran before the params.action branch, 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, and doctor pass for a fanout-denied child.

resume and interrupt deliberately do not. resume revives 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_POLICY set managementActions: "full" and fanoutAuthorized: false in the same object. The two contradict, and defect 1 discarded the "full" entirely. docs/workflows.md already promised stages the bundled subagent tool with the normal depth guard.

3. The TypeScript 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. blocked was therefore always false, and checkDepthForExecution() could never block at any depth in any context.

Its test stayed green 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 now derives from the live admitted policy 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.

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 literally undefined, 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 0
  • npm run test:unit — exit 0, 619 files, 5865 passed, 1 skipped
  • Depth tests no longer touch process.env; they assert against admitted policy depth across the five-level limit, stricter configured limits, the ceiling clamp, and inherited maximums in both directions
  • docs/subagents.md and docs/workflows.md corrected to describe the mechanism that actually enforces the limit

Note 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.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with 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.

T-Rex T-Rex Logs

What T-Rex did

  • Ran the workflow-stage-nested-delegation-validate.ts script with STAGE_MAX_DEPTH=2 to exercise a two-hop delegation path, resulting in a child at depth one with max depth two and nested delegation executing once.
  • Ran the same validation script with STAGE_MAX_DEPTH=1; the admitted child had depth 1, maxSubagentDepth 1, and its nested delegation was refused before runtime with zero runtime calls.
  • Executed focused child-policy depth-guard and in-process-runner Vitest coverage; the policy suite passed all 15 tests and the connected coverage passed all 38 tests, confirming read-only management remains available while delegation, resume, and interrupt are refused for non-fanout children.
  • Authored validation source trex-artifacts/workflow-stage-nested-delegation-validate.ts to exercise in-process admission, verify propagated child policy, and execute the nested executor path.
  • Reviewed the two-hop control run log, the target-run log, and the focused Vitest outputs to verify depth constraints, runtime calls, and policy behavior.

View all artifacts

T-Rex Ran code and verified through T-Rex

Reviews (3): Last reviewed commit: "docs(changelog): attribute the policy fi..." | Re-trigger Greptile

Copilot AI lite review requested due to automatic review settings August 6, 2026 09:15
@mintlify

mintlify Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
bastani 🟢 Ready View Preview Aug 6, 2026, 9:16 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/effective maxSubagentDepth through typed policy and threading parentDepth through 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 thread packages/subagents/CHANGELOG.md Outdated
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 thread packages/workflows/CHANGELOG.md Outdated
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)).
Comment thread packages/coding-agent/CHANGELOG.md Outdated

### 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>
Copilot AI review requested due to automatic review settings August 6, 2026 17:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review is ineligible. To be eligible to request a review, you need a paid Copilot license, or your organization must enable Copilot code review.

flora131 and others added 6 commits August 6, 2026 14:12
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
flora131 force-pushed the fix/2205-subagent-child-policy branch from f133312 to e970e04 Compare August 6, 2026 21:17
Copilot AI review requested due to automatic review settings August 6, 2026 21:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review is ineligible. To be eligible to request a review, you need a paid Copilot license, or your organization must enable Copilot code review.

@flora131
flora131 merged commit cfef83a into main Aug 6, 2026
8 checks passed
@flora131
flora131 deleted the fix/2205-subagent-child-policy branch August 7, 2026 02:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants