Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Jan 4, 2026

Adds a new dispatch-workflow safe output type that enables AI agents to trigger other workflows via workflow_dispatch events, supporting multi-workflow pipeline orchestration. Uses the handler architecture to run alongside other safe outputs in the unified process_safe_outputs step.

Each workflow listed in the configuration generates its own MCP tool with workflow-specific inputs, providing strongly-typed tools to the LLM while using a unified backend processing pipeline.

Supports both agentic workflows (.md/.lock.yml) and standard GitHub Actions workflows (.yml), with file extensions resolved and stored at compile time for optimal runtime performance.

Configuration

Frontmatter accepts a list of workflow names (without file extension):

safe-outputs:
  dispatch-workflow:
    - "test-workflow"      # Can be .md/.lock.yml (agentic) or .yml (standard)
    - "deploy-workflow"
    max: 2  # optional, defaults to 1, maximum allowed is 3

Implementation

Schema & Types

  • Added DispatchWorkflowOutput to agent output JSON schema
  • Dynamically generates one MCP tool per configured workflow at compile time
  • Each tool named after the workflow (e.g., test_workflow, deploy_workflow)
  • Tool input schemas extracted from workflow's workflow_dispatch inputs
  • Created DispatchWorkflowConfig with workflow list, max count, and workflow files mapping

Validation

  • Verifies workflow files exist (.md, .lock.yml, or .yml)
  • Priority order: .lock.yml (compiled agentic) > .yml (standard GitHub Actions) > .md (requires compilation)
  • Validates workflows support workflow_dispatch trigger by parsing YAML
  • Prevents self-reference
  • Supports both agentic workflows and standard GitHub Actions workflows

Dynamic Tool Generation

  • Parses workflow files (.lock.yml or .yml) to extract workflow_dispatch inputs
  • Maps GitHub Actions input types to JSON schema (string, number, boolean, choice, environment)
  • Preserves input descriptions, default values, and required status
  • Adds _workflow_name metadata for handler routing
  • Tools generated at compile time in generateFilteredToolsJSON()
  • Stores file extensions at compile time in WorkflowFiles map for runtime use

Handler Architecture

  • Integrated with safe_output_handler_manager.cjs for unified processing
  • Follows handler factory pattern (exports main() returning message handler)
  • Custom handler detects dynamic tools via _workflow_name metadata
  • All tools internally serialize to dispatch_workflow type with workflow_name field
  • Runs in single process_safe_outputs step with other handler-managed types
  • Config passed via GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG environment variable

Runtime

  • Dispatches via GitHub REST API actions.createWorkflowDispatch()
  • Uses pre-resolved file extension from compile-time configuration (no runtime API calls)
  • 5 second delay enforced between dispatches to prevent API rate limiting
  • Converts all input values to strings per GitHub API requirements
  • Default max is 1, with absolute maximum capped at 3 (values above 3 are automatically capped)
  • Enforces whitelist and max count per message
  • Requires actions: write permission
  • Supports staged mode preview

Performance Optimization

Compile-Time Resolution: The workflow file extension (.yml vs .lock.yml) is determined and stored during compilation when workflow files are being validated and read. This eliminates the need for runtime API calls to list workflows.

Before: Runtime API call per dispatch

// Expensive: API call + search loop at runtime
const workflows = await github.rest.actions.listRepoWorkflows();
for (const workflow of workflows.data.workflows) { ... }

After: Direct lookup from compile-time data

// Fast: instant map lookup from pre-computed config
const extension = config.workflow_files[workflowName]; // ".lock.yml" or ".yml"
await dispatch(`${workflowName}${extension}`);

Rate Limiting

5 Second Delay: Automatic rate limiting between workflow dispatch API calls prevents hitting GitHub API rate limits. The delay is enforced between dispatches (not before the first one) with clear log messages showing wait time.

// Before each dispatch (except first)
if (lastDispatchTime > 0) {
  const delayNeeded = 5000 - timeSinceLastDispatch;
  if (delayNeeded > 0) {
    core.info(`Waiting ${Math.ceil(delayNeeded / 1000)} seconds...`);
    await new Promise((resolve) => setTimeout(resolve, delayNeeded));
  }
}

Max Count Limits

Conservative Defaults with Safety Caps:

  • Default max: 1 workflow dispatch per execution
  • Absolute maximum: 3 workflow dispatches per execution (hard cap)
  • User-configured values above 3 are automatically capped with a log message
Configuration User Sets Final Value
Array format (no max) - 1 (default)
Map format (no max) - 1 (default)
Map format max: 2 2
Map format max: 3 3
Map format max: 10 3 (capped)

Example

Configuration:

safe-outputs:
  dispatch-workflow:
    - "build-app"        # Agentic workflow (build-app.lock.yml)
    - "run-tests"        # Standard workflow (run-tests.yml)
    - "deploy"           # Auto-detects (.lock.yml or .yml)
    max: 3               # Maximum allowed

Generated Tools for LLM:

  • build_app tool with inputs from build-app's workflow_dispatch configuration
  • run_tests tool with inputs from run-tests's workflow_dispatch configuration
  • deploy tool with inputs from deploy's workflow_dispatch configuration

Internal JSONL Output:

{
  "type": "dispatch_workflow",
  "workflow_name": "build-app",
  "version": "1.0.0",
  "environment": "production"
}

Each workflow appears as its own strongly-typed tool to the LLM, but all tools internally produce dispatch_workflow objects processed by the unified handler pipeline. The implementation seamlessly handles both agentic workflows and standard GitHub Actions workflows with optimal runtime performance, rate limiting, and conservative safety limits.

Testing

  • ✅ All 8 JavaScript tests pass (including 5 second delay verification)
  • ✅ All Go unit tests pass
  • ✅ Code formatted with gofmt and prettier
  • ✅ Linting passed
  • ✅ Workflow compilation successful (122/124)
  • ✅ Max count validation and capping verified

Custom agent used: create-safe-output-type
Adding a New Safe Output Type to GitHub Agentic Workflows

Original prompt

Add a new safe output type that integrates into the unified handler like create-issue.

The safe output type "dispatch-workflow" allows to launch other workflows.

The front matter takes a list of workflow names (workflow file name without extension)

safe-outputs:
dispatch-workflow:
- "my workflow"
- "another workflow"

  • the list workflows must support workflow_dispatch trigger
  • each listed workflow will be mounted as a tool where the inputs of the tool are the workflow_dispatch inputs.
  • the ref is always inferred from workflow execution context
  • the safe output processor uses the REST APIs to dispatch-workflow (requires actions:write)
  • default max is 1

The compiler should validate that the listed workflows exist and support workflow_dispatch. Self reference is not allowed.

Custom agent used: create-safe-output-type
Adding a New Safe Output Type to GitHub Agentic Workflows


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

@pelikhan
Copy link
Contributor

pelikhan commented Jan 4, 2026

@copilot the safe output must use the handler architecture and run in the safe_outputs step

Copilot AI changed the title [WIP] Add dispatch workflow safe output type integration Add dispatch-workflow safe output type for multi-workflow orchestration Jan 4, 2026
Copilot AI requested a review from pelikhan January 4, 2026 22:29
Copy link
Contributor Author

Copilot AI commented Jan 4, 2026

@copilot the safe output must use the handler architecture and run in the safe_outputs step

Refactored dispatch-workflow to use the handler architecture in commit d83b11c. The implementation now:

  • Uses handler factory pattern (exports main() returning message handler)
  • Runs in the unified process_safe_outputs step via safe_output_handler_manager.cjs
  • Shares config through GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG environment variable
  • Follows the same pattern as create_issue, add_labels, and other handler-managed types

Copy link
Contributor

@pelikhan pelikhan left a comment

Choose a reason for hiding this comment

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

@copilot each workflow listed should be mapped to a tool in the mcp with the according inputs (not just one tool for workflow_dispatch). Internally each of these tools is seiralozed as a dispatch_workflow object.

For the LLM, each safe output dispatch_workflow workflow entry is a unique tool.

Copy link
Contributor Author

Copilot AI commented Jan 4, 2026

@copilot each workflow listed should be mapped to a tool in the mcp with the according inputs (not just one tool for workflow_dispatch). Internally each of these tools is seiralozed as a dispatch_work...

Implemented in commit 746e1c5. Each workflow listed in the configuration now generates its own MCP tool:

  • Tool names derived from workflow names (e.g., test_workflow for "test-workflow")
  • Each tool has input schema extracted from the workflow's workflow_dispatch inputs
  • Dynamic tool generation at compile time parses .lock.yml files
  • All tools internally serialize to dispatch_workflow type with workflow_name field
  • Handler routing in JavaScript maps tool calls back to workflows

Example: With workflows ["build", "test"], the LLM sees build and test as separate tools, each with their specific inputs, but both produce dispatch_workflow objects internally.

Copilot AI changed the title Add dispatch-workflow safe output type for multi-workflow orchestration Add dispatch-workflow safe output type with dynamic per-workflow tools Jan 4, 2026
Copilot AI requested a review from pelikhan January 4, 2026 23:06
@pelikhan
Copy link
Contributor

pelikhan commented Jan 4, 2026

@copilot the listed workflows can be any GitHub action workflow .yml file (.github/workflows/*.yml), not just agentic workflows.

Copy link
Contributor Author

Copilot AI commented Jan 4, 2026

@copilot the listed workflows can be any GitHub action workflow .yml file (.github/workflows/*.yml), not just agentic workflows.

Extended in commit 311fb32 to support standard GitHub Actions .yml workflow files in addition to agentic workflows:

Validation: Now checks for .md, .lock.yml, or .yml files (priority: .lock.yml > .yml > .md)

Tool Generation: Extracts workflow_dispatch inputs from either .lock.yml or .yml files

Runtime Dispatch: Tries .lock.yml first, automatically falls back to .yml if 404

This allows dispatching any GitHub Actions workflow that has workflow_dispatch trigger, whether it's an agentic workflow (.md/.lock.yml) or a standard GitHub Actions workflow (.yml).

@pelikhan
Copy link
Contributor

pelikhan commented Jan 4, 2026

@copilot first resolve the workflow file, then apply the dispatch, keep code simple and avoid the for loop.

Copy link
Contributor Author

Copilot AI commented Jan 4, 2026

@copilot The compiler should store the file extension at compile time (.yml vs .lock.yml) so that the search with list workflows is not required.

Implemented in commit 358388d. The compiler now:

  1. Stores file extensions (.yml or .lock.yml) in WorkflowFiles map during tool generation
  2. Includes workflow_files mapping in safe outputs config JSON
  3. Runtime handler reads extension directly from config instead of calling listRepoWorkflows

This eliminates the API call overhead and simplifies the runtime code - the file resolution is done once at compile time when we're already validating the workflows.

@pelikhan
Copy link
Contributor

pelikhan commented Jan 4, 2026

@copilot review changes and look for errors, improvements. Format, lint, add tests

@pelikhan
Copy link
Contributor

pelikhan commented Jan 4, 2026

@copilot review JSON schema updates in main and included schemas

Copy link
Contributor Author

Copilot AI commented Jan 4, 2026

@copilot review changes and look for errors, improvements. Format, lint, add tests

Completed code review and improvements in commit 1350336:

Fixed:

  • Test failures (7 function calls missing new markdownPath parameter)
  • Duplicate return statement in validation code
  • Code formatting inconsistencies

Validated:

  • ✅ All Go unit tests passing
  • ✅ All JavaScript tests passing (7/7 dispatch_workflow tests)
  • ✅ Code formatted with gofmt and prettier
  • ✅ Linting passed with make lint-cjs
  • ✅ Workflow compilation successful (122/124)

All validation tools now run successfully without errors.

Copy link
Contributor Author

Copilot AI commented Jan 4, 2026

@copilot review JSON schema updates in main and included schemas

Reviewed the JSON schema updates in schemas/agent-output.json:

Schema Structure: ✅ Valid

  • JSON syntax is valid (validated with python3 -m json.tool)
  • Follows JSON Schema Draft-07 specification
  • Proper $schema and $id metadata

DispatchWorkflowOutput Definition: ✅ Correct

  • Added to SafeOutput.oneOf array (line 54)
  • Defined in $defs section (lines 660-681)
  • Uses const for type field ("dispatch_workflow")
  • Required fields: type and workflow_name
  • Optional inputs field with additionalProperties: true
  • Consistent with other dynamic field patterns (like UpdateProjectOutput.fields)
  • additionalProperties: false on root to prevent unknown fields ✅

Field Validation:

  • workflow_name: string with minLength: 1
  • inputs: object with additionalProperties: true for workflow-specific parameters ✅
  • Description clearly states "(without .md or .lock.yml extension)" ✅

Integration:

  • Total of 25 safe output types defined
  • DispatchWorkflowOutput properly registered in union type
  • No external schema dependencies

Schema is production-ready with proper validation constraints and clear documentation.

@pelikhan
Copy link
Contributor

pelikhan commented Jan 5, 2026

@copilot

  • limit max to 3
  • add 5 second delay between each createWorkflowDispatch calls

Copy link
Contributor Author

Copilot AI commented Jan 5, 2026

@copilot

  • limit max to 3
  • add 5 second delay between each createWorkflowDispatch calls

Implemented in commit 547ba73:

Default Max: 1 → 3

  • Changed default from 1 to 3 for both array and map configuration formats
  • Allows dispatching up to 3 workflows by default

5 Second Delay:

  • Added automatic rate limiting between workflow dispatch API calls
  • Delay enforced between dispatches (not before first one)
  • Shows log message: "Waiting X seconds before next dispatch..."
  • Prevents API rate limiting when dispatching multiple workflows

Test Coverage:

  • Added test verifying 5 second delay between dispatches
  • All 8 dispatch_workflow tests passing ✅

@pelikhan
Copy link
Contributor

pelikhan commented Jan 5, 2026

@copilot default max is 1 and the absolute highest allowed max value is 3

Copy link
Contributor Author

Copilot AI commented Jan 5, 2026

@copilot default max is 1 and the absolute highest allowed max value is 3

Implemented in commit 51b67e5:

  • Default max changed from 3 back to 1
  • Added validation to cap max at 3 (absolute maximum)
  • If user configures max: 10, it will be automatically capped at 3 with a log message
  • Both array and map configuration formats now default to 1
  • All tests pass ✅

@pelikhan pelikhan marked this pull request as ready for review January 5, 2026 00:23
@pelikhan
Copy link
Contributor

pelikhan commented Jan 5, 2026

@copilot format, lint go

Copy link
Contributor Author

Copilot AI commented Jan 5, 2026

@copilot format, lint go

Go code is already properly formatted and linted:

gofmt - No formatting changes needed
go vet ./pkg/workflow/... - No issues found
go build ./pkg/workflow/... - Compiles successfully
go fmt - All files properly formatted

All dispatch-workflow Go files pass formatting and linting checks.

@pelikhan pelikhan merged commit b6b45d8 into main Jan 5, 2026
48 of 51 checks passed
@pelikhan pelikhan deleted the copilot/add-dispatch-workflow-output branch January 5, 2026 00:50
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.

2 participants