Provenance: AI-drafted (GitHub Copilot CLI / Claude Opus 4.6), human-reviewed and approved, AI-submitted.
Problem
The engine currently rejects any workflow that references its own file as a sub-workflow:
# src/conductor/engine/workflow.py
if current_path is not None and sub_path == current_path:
raise ExecutionError(
f"Circular sub-workflow reference: agent '{agent.name}' "
f"references its own workflow file '{agent.workflow}'.",
suggestion="A workflow cannot reference itself as a sub-workflow.",
)
This prevents recursive workflow patterns where the same logic applies at different levels of a hierarchy (e.g., planning at epic/issue/task level, or any divide-and-conquer pattern).
The depth-tracking infrastructure already exists (MAX_SUBWORKFLOW_DEPTH=10, _subworkflow_depth counter). The circular reference check is overly conservative — depth tracking already prevents infinite recursion.
Proposed solution
Remove the file-path equality check. Rely on MAX_SUBWORKFLOW_DEPTH for safety (already enforced). The depth counter already increments on each _execute_subworkflow call and raises ExecutionError at the limit.
Optional enhancement: Add a max_depth field to type: workflow agents so authors can set tighter bounds per-agent:
agents:
- name: plan_children
type: workflow
workflow: ./self.yaml
max_depth: 3 # override global MAX_SUBWORKFLOW_DEPTH
input_mapping:
work_item_id: "{{ item.id }}"
routes:
- to: aggregate
What about mutual recursion (A → B → A)?
The current check only catches direct self-reference. Mutual recursion is already handled by the depth limit. Removing the self-reference check makes the behavior consistent: all circular patterns (direct and mutual) are bounded by depth, not by static analysis.
Termination guarantee:
MAX_SUBWORKFLOW_DEPTH=10 is the hard ceiling
- Per-agent
max_depth provides author control
max_iterations on each sub-workflow provides a second safety net
- These compound: a 3-level recursive workflow with 200 iterations each can do at most 600 agent executions total
Example: fractal planning
With all three issues in this series implemented:
# plan.yaml — recursive planner
workflow:
name: fractal-planner
input:
work_item_id: { type: number }
depth: { type: number, default: 0 }
entry_point: planner
agents:
- name: planner
model: claude-opus-4.6-1m
prompt: |
Read work item #{{ workflow.input.work_item_id }}.
If it's an Epic, break it into Issues.
If it's an Issue, break it into Tasks.
If it's a Task, output the implementation steps.
output:
children: { type: array }
is_leaf: { type: boolean }
routes:
- to: plan_children
when: "{{ not output.is_leaf }}"
- to: $end
- name: plan_children
type: for_each # Issue #102
source: planner.output.children
as: child
max_concurrent: 1
agent:
type: workflow
workflow: ./plan.yaml # Issue #103 (this): self-reference
input_mapping: # Issue #101
work_item_id: "{{ child.id }}"
depth: "{{ workflow.input.depth + 1 }}"
routes:
- to: $end
output:
children: "{{ planner.output.children }}"
Implementation
The change is minimal — remove the 5-line path equality check in _execute_subworkflow(). The existing MAX_SUBWORKFLOW_DEPTH enforcement (already present 10 lines above the check being removed) provides the safety guarantee.
Optionally, add max_depth: int | None = None to AgentDef for per-agent control, enforced alongside the global limit.
Dependency
Relationship to other requests
This is Issue 3 of 3 in a series enabling recursive workflow composition:
Problem
The engine currently rejects any workflow that references its own file as a sub-workflow:
This prevents recursive workflow patterns where the same logic applies at different levels of a hierarchy (e.g., planning at epic/issue/task level, or any divide-and-conquer pattern).
The depth-tracking infrastructure already exists (
MAX_SUBWORKFLOW_DEPTH=10,_subworkflow_depthcounter). The circular reference check is overly conservative — depth tracking already prevents infinite recursion.Proposed solution
Remove the file-path equality check. Rely on
MAX_SUBWORKFLOW_DEPTHfor safety (already enforced). The depth counter already increments on each_execute_subworkflowcall and raisesExecutionErrorat the limit.Optional enhancement: Add a
max_depthfield totype: workflowagents so authors can set tighter bounds per-agent:What about mutual recursion (A → B → A)?
The current check only catches direct self-reference. Mutual recursion is already handled by the depth limit. Removing the self-reference check makes the behavior consistent: all circular patterns (direct and mutual) are bounded by depth, not by static analysis.
Termination guarantee:
MAX_SUBWORKFLOW_DEPTH=10is the hard ceilingmax_depthprovides author controlmax_iterationson each sub-workflow provides a second safety netExample: fractal planning
With all three issues in this series implemented:
Implementation
The change is minimal — remove the 5-line path equality check in
_execute_subworkflow(). The existingMAX_SUBWORKFLOW_DEPTHenforcement (already present 10 lines above the check being removed) provides the safety guarantee.Optionally, add
max_depth: int | None = NonetoAgentDeffor per-agent control, enforced alongside the global limit.Dependency
input_mapping) for passing different inputs per recursive callfor_eachwith workflows) for fanning out to childrenRelationship to other requests
This is Issue 3 of 3 in a series enabling recursive workflow composition:
input_mapping— foundation for parameterized sub-workflowstype: workflowinfor_eachgroups — dynamic fan-out