Provenance: AI-drafted (GitHub Copilot CLI / Claude Opus 4.6), human-reviewed and approved, AI-submitted.
Problem
Today, sub-workflows (type: workflow) always receive the parent's workflow.input.* values verbatim. The child's workflow.input.X equals the parent's workflow.input.X. There is no way to pass intermediate results (upstream agent outputs) into a sub-workflow.
This makes sub-workflows useful only for phases that need the same static inputs as the parent. You can't call a sub-workflow in a loop with different parameters each iteration — e.g., "plan issue A" then "plan issue B" — because the sub-workflow sees the same workflow.input.* both times.
Current code (src/conductor/engine/workflow.py):
workflow_ctx = context.get("workflow", {})
sub_inputs: dict[str, Any] = (
dict(workflow_ctx.get("input", {})) if isinstance(workflow_ctx, dict) else {}
)
return await child_engine.run(sub_inputs)
Proposed solution
Add an optional input_mapping field to type: workflow agents. Each key is a sub-workflow input name; each value is a Jinja2 expression evaluated in the parent's context.
agents:
- name: plan_issue
type: workflow
workflow: ./plan-and-review.yaml
input_mapping:
work_item_id: "{{ task_manager.output.current_issue_id }}"
title: "{{ task_manager.output.current_issue_title }}"
description: "{{ task_manager.output.current_issue_description }}"
output:
plan_path: { type: string }
plan_summary: { type: string }
routes:
- to: next_step
Resolution rules:
- If
input_mapping is present, render each value as a Jinja2 template against the parent's full context, then pass the resulting dict as the sub-workflow's inputs. Parent's workflow.input.* is NOT inherited.
- If
input_mapping is absent, current behavior is preserved (parent's workflow.input.* is forwarded).
Schema change (AgentDef):
input_mapping: dict[str, str] | None = None
"""Optional mapping of sub-workflow input names to Jinja2 expressions.
Evaluated in the parent context. Only valid for type='workflow'."""
Engine change — roughly:
if agent.input_mapping:
sub_inputs = {}
for key, template in agent.input_mapping.items():
sub_inputs[key] = self._render_template(template, agent_context)
else:
# existing behavior
workflow_ctx = context.get("workflow", {})
sub_inputs = dict(workflow_ctx.get("input", {}))
Validation:
input_mapping is only valid when type: workflow
- Each value must be a valid Jinja2 expression
- Keys should match the sub-workflow's declared input names (warning, not error)
Why this matters
This is the foundation for all advanced composition patterns. Without it, sub-workflows are limited to "same inputs as parent" which makes them suitable for one-shot phases but not for parameterized or iterative use.
Relationship to other requests
This is Issue 1 of 3 in a series enabling recursive workflow composition:
Problem
Today, sub-workflows (
type: workflow) always receive the parent'sworkflow.input.*values verbatim. The child'sworkflow.input.Xequals the parent'sworkflow.input.X. There is no way to pass intermediate results (upstream agent outputs) into a sub-workflow.This makes sub-workflows useful only for phases that need the same static inputs as the parent. You can't call a sub-workflow in a loop with different parameters each iteration — e.g., "plan issue A" then "plan issue B" — because the sub-workflow sees the same
workflow.input.*both times.Current code (
src/conductor/engine/workflow.py):Proposed solution
Add an optional
input_mappingfield totype: workflowagents. Each key is a sub-workflow input name; each value is a Jinja2 expression evaluated in the parent's context.Resolution rules:
input_mappingis present, render each value as a Jinja2 template against the parent's full context, then pass the resulting dict as the sub-workflow's inputs. Parent'sworkflow.input.*is NOT inherited.input_mappingis absent, current behavior is preserved (parent'sworkflow.input.*is forwarded).Schema change (
AgentDef):Engine change — roughly:
Validation:
input_mappingis only valid whentype: workflowWhy this matters
This is the foundation for all advanced composition patterns. Without it, sub-workflows are limited to "same inputs as parent" which makes them suitable for one-shot phases but not for parameterized or iterative use.
Relationship to other requests
This is Issue 1 of 3 in a series enabling recursive workflow composition:
Issue 1 (this):
input_mapping— foundation for parameterized sub-workflowsIssue 2: Allow
type: workflowinfor_eachgroups — dynamic fan-out (depends on this)Issue 3: Allow self-referential workflows — controlled recursion (depends on Issues 1 + 2)
Issue 2: feat(composition): allow sub-workflows in for_each groups #102 — Allow
type: workflowinfor_eachgroups — dynamic fan-out (depends on this)Issue 3: feat(composition): allow self-referential sub-workflows with depth tracking #103 — Allow self-referential workflows — controlled recursion (depends on Issues 1 + 2)