Uh oh!
There was an error while loading. Please reload this page.
feat: add call-workflow safe output for workflow_call chaining - #20806
Conversation
Implements the call-workflow safe output type that enables orchestrator/ dispatcher patterns within workflow_call chains. Key components: - CallWorkflowConfig struct with array/map parsing (call_workflow.go) - Compile-time validation: checks workflow_call trigger, self-reference, file existence (call_workflow_validation.go) - Per-worker MCP tool generation with typed inputs from workflow_call schema (safe_outputs_call_workflow.go) - Conditional uses: fan-out jobs in compiled YAML with if: conditions checking safe_outputs outputs (compiler_safe_output_jobs.go) - Runtime handler that validates and sets call_workflow_name + call_workflow_payload step outputs (call_workflow.cjs) - JSON schema support for both array and map formats - Full integration: validation hook, MCP setup, imports, config generation - Tests: Go unit tests and JS tests Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
…t empty allowlist behavior Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
call-workflow safe output for workflow_call chainingpelikhan
commented
Mar 13, 2026
@copilot add tests that generate various AW markdown file configuration and test the compilation process. |
pelikhan
commented
Mar 13, 2026
@copilot add documentation using the technical-doc-writer subagent |
Adds call_workflow_compilation_test.go with 16 table-driven scenarios that:
- Write markdown gateway files and worker lock files to temp directories
- Call CompileWorkflow() and read the generated .lock.yml
- Assert the compiled YAML structure (fan-out jobs, if: conditions,
secrets: inherit, payload wiring, output declarations)
Scenarios covered:
- Array format config (call-workflow: [worker-a, worker-b])
- Map format config (call-workflow: { workflows: [...], max: 1 })
- Single worker
- Worker with typed inputs (choice, boolean, string, number)
- call-workflow alongside add-comment
- Gateway triggered via workflow_call (cross-repo scenario)
- safe_outputs job declares call_workflow_name/payload outputs
- Conclusion job depends on all call-* worker jobs
- Worker name with underscores → hyphenated job name
- Validation failure: worker missing workflow_call trigger
- Validation failure: self-reference
- Validation failure: worker not found
- Worker only as .md source (same-batch compilation)
- Config included in safe_outputs JSON
- If condition format verification
- Worker found in .github/workflows regardless of gateway location
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Added Scenarios covered:
|
- safe-outputs.md: add call-workflow to TOC and full reference section with config options, worker input declaration, compiled output example, comparison table vs dispatch-workflow, and validation/security notes - orchestration.md: add call-workflow as second orchestration mechanism with guidance on choosing between call-workflow and dispatch-workflow Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Added documentation in commit
|
pelikhan
commented
Mar 13, 2026
@copilot add a mini smoke-call-workflow agentic workflow orchestrator that tells the agent to call another workflow (find one with workflow_call). Uses gpt-5.1-codex-mini , codex and triggers on water. |
Creates .github/workflows/smoke-call-workflow.md (compiled to .lock.yml) - engine: codex with gpt-5.1-codex-mini model - triggers on workflow_dispatch and PR label "water" - uses call-workflow safe output to call smoke-workflow-call worker Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Added
|
There was a problem hiding this comment.
Pull request overview
Adds a new call-workflow safe output type to enable compile-time, allowlisted fan-out to reusable workflow_call worker workflows (typed MCP tools + conditional uses: jobs), plus docs and smoke coverage.
Changes:
- Introduces
CallWorkflowConfig, parsing, compile-time validation, and dynamic MCP tool generation from workerworkflow_call.inputs. - Emits conditional
call-*reusable-workflow jobs wired offsafe_outputsoutputs (call_workflow_name/call_workflow_payload) and adds them to conclusion dependencies. - Adds runtime safe-output handler (
call_workflow.cjs), tool loader routing, schema/docs updates, and extensive compilation/validation tests + smoke workflow.
Reviewed changes
Copilot reviewed 25 out of 25 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| pkg/workflow/safe_outputs_tools_filtering.go | Generates dynamic MCP tools for call-workflow workers (extracts workflow_call.inputs). |
| pkg/workflow/safe_outputs_config_generation.go | Serializes call_workflow settings into generated safe-outputs config JSON. |
| pkg/workflow/safe_outputs_config.go | Parses call-workflow from frontmatter into compiler config. |
| pkg/workflow/safe_outputs_call_workflow.go | Populates worker file mappings and generates typed MCP tools from workflow_call.inputs. |
| pkg/workflow/mcp_setup_generator.go | Ensures call-workflow file mappings are populated before config/tool generation. |
| pkg/workflow/imports.go | Enables call-workflow safe-output detection and import merging. |
| pkg/workflow/data/action_pins.json | Updates/removes some action pin entries (versions/aliases). |
| pkg/workflow/compiler_types.go | Adds CallWorkflow to SafeOutputsConfig. |
| pkg/workflow/compiler_safe_outputs_job.go | Exposes call_workflow_name / call_workflow_payload as safe_outputs job outputs when configured. |
| pkg/workflow/compiler_safe_outputs_core.go | Includes call-workflow in base safe-output config collection. |
| pkg/workflow/compiler_safe_output_jobs.go | Builds conditional call-* reusable workflow jobs and wires them into the job graph. |
| pkg/workflow/compiler.go | Runs compile-time call-workflow validation during compilation. |
| pkg/workflow/call_workflow_validation.go | Validates workers exist, declare workflow_call, and are not self-references; extracts workflow_call.inputs. |
| pkg/workflow/call_workflow.go | Defines CallWorkflowConfig and frontmatter parsing logic. |
| pkg/parser/schemas/main_workflow_schema.json | Adds schema support for safe-outputs.call-workflow (array/map forms, max bounds). |
| docs/src/content/docs/reference/safe-outputs.md | Documents call-workflow configuration, compiled output, and security notes. |
| docs/src/content/docs/patterns/orchestration.md | Expands orchestration pattern docs to cover call-workflow vs dispatch-workflow. |
| actions/setup/js/call_workflow.cjs | Implements runtime handler to validate allowlist, serialize inputs, and set step outputs (no API calls). |
| actions/setup/js/call_workflow.test.cjs | Adds unit tests for the call_workflow handler behavior. |
| actions/setup/js/safe_outputs_tools_loader.cjs | Routes dynamically generated call_workflow tools to the call_workflow handler. |
| .github/workflows/smoke-call-workflow.md | Adds a smoke orchestrator workflow that must call a worker via call-workflow. |
| .github/workflows/smoke-call-workflow.lock.yml | Compiled lockfile showing the new conditional call-* job wiring. |
| .github/workflows/mcp-inspector.lock.yml | Updates pinned astral-sh/setup-uv SHA. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
Uh oh!
There was an error while loading. Please reload this page.
| for _, workflowName := range config.Workflows { | ||
| // Build the job name: "call-{sanitized-workflow-name}" | ||
| // sanitizeJobName normalizes underscores to hyphens (NormalizeSafeOutputIdentifier + dash conversion) | ||
| sanitizedName := sanitizeJobName(workflowName) | ||
| jobName := "call-" + sanitizedName | ||
| // Determine the relative path to the worker workflow file | ||
| workflowPath, ok := config.WorkflowFiles[workflowName] | ||
| if !ok || workflowPath == "" { | ||
| // Fallback: construct path from name | ||
| workflowPath = fmt.Sprintf("./.github/workflows/%s.lock.yml", workflowName) | ||
| } | ||
| callJob := &Job{ | ||
| Name: jobName, | ||
| Needs: []string{"safe_outputs"}, | ||
| If: fmt.Sprintf("needs.safe_outputs.outputs.call_workflow_name == '%s'", workflowName), | ||
| Uses: workflowPath, | ||
| SecretsInherit: true, | ||
| With: map[string]any{ | ||
| "payload": "${{ needs.safe_outputs.outputs.call_workflow_payload }}", | ||
| }, | ||
| } | ||
| if err := c.jobManager.AddJob(callJob); err != nil { | ||
| return nil, fmt.Errorf("failed to add call-workflow job '%s': %w", jobName, err) | ||
| } | ||
| jobNames = append(jobNames, jobName) | ||
| compilerSafeOutputJobsLog.Printf("Added call-workflow job: %s (uses: %s)", jobName, workflowPath) | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
pelikhan
commented
Mar 13, 2026
@copilot review suggestions / comments and apply. |
Add early duplicate check in validateCallWorkflow() and a corresponding test TestCallWorkflowCompile_ValidationFails_DuplicateWorkflow to cover the case where the same workflow name appears more than once in the list. Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Applied the open review suggestion in commit
The two resolved threads (docs wording fix and |
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Adds a
call-workflowsafe output type enabling orchestrator/dispatcher patterns withinworkflow_callchains — without API calls, without extra tokens, preserving billing attribution andgithub.actor.Unlike
dispatch-workflow(runtime API),call-workflowis a compile-time fan-out: the compiler reads each allowed worker'sworkflow_call.inputs, generates typed per-worker MCP tools, and emits conditionaluses:jobs. At runtime, only the worker whose name the agent selected executes.Frontmatter
Compiled output (simplified)
Worker inputs are forwarded as a single JSON
payloadstring (same envelope pattern asdispatch-workflow), avoiding GitHub's 50 inputs+secrets+outputs limit perworkflow_call.Changes
call_workflow.go—CallWorkflowConfigstruct;parseCallWorkflowConfig()supporting array and map formats; max defaults to 1, capped at 50call_workflow_validation.go— compile-time validation: each workflow must exist, declareworkflow_call, and not be a self-reference; handles.mdsame-batch targetssafe_outputs_call_workflow.go—populateCallWorkflowFiles()resolves.lock.yml/.yml/.mdpriority;generateCallWorkflowTool()builds typed MCP tools fromworkflow_call.inputs(string, number, boolean, choice with enum)compiler_safe_output_jobs.go—buildCallWorkflowJobs()emits one conditionaluses:job per worker; all added to conclusion job'sneedscompiler_safe_outputs_job.go— addscall_workflow_name/call_workflow_payloadtosafe_outputsjob outputs when configuredcall_workflow.cjs— runtime handler: validates against allowlist, serialises inputs to JSON, sets step outputs — no GitHub API callssafe_outputs_tools_loader.cjs— registers_call_workflow_nametool routing alongside existing_workflow_namedispatch patterndispatch-workflowcall_workflow_compilation_test.go— 16 end-to-end compilation tests covering array/map formats, typed worker inputs, cross-repoworkflow_callgateway trigger, conclusion job dependencies, underscore→hyphen job naming, worker discovery across directories, and all three validation failure paths (missing trigger, self-reference, worker not found)docs/reference/safe-outputs.md— new TOC entry and full reference section with configuration options, worker input declaration, compiled output example, comparison table vsdispatch-workflow, and security notesdocs/patterns/orchestration.md— expanded to cover bothcall-workflowanddispatch-workflowas orchestration mechanisms, with guidance on choosing between the twosmoke-call-workflow.md— mini smoke test orchestrator usingcodexengine withgpt-5.1-codex-minimodel, triggering on PR labelwaterorworkflow_dispatch; callssmoke-workflow-callviacall-workflowto validate end-to-end fan-out💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.