GitHub Agentic Workflows

Deterministic & Agentic Patterns

GitHub Agentic Workflows combine deterministic computation with AI reasoning, enabling data preprocessing, custom trigger filtering, and post-processing patterns.

Combine deterministic steps with AI agents to precompute data, filter triggers, preprocess inputs, post-process outputs, or build multi-stage computation and reasoning pipelines.

Define deterministic jobs in frontmatter alongside agentic execution:

┌────────────────────────┐
│ Deterministic Jobs │
│ - Data fetching │
│ - Preprocessing │
└───────────┬────────────┘
│ artifacts/outputs
┌────────────────────────┐
│ Agent Job (AI) │
│ - Reasons & decides │
└───────────┬────────────┘
│ safe outputs
┌────────────────────────┐
│ Safe Output Jobs │
│ - GitHub API calls │
└────────────────────────┘
.github/workflows/release-highlights.md
---
on:
push:
tags: ['v*.*.*']
engine: copilot
safe-outputs:
update-release:
steps:
- run: |
gh release view "${GITHUB_REF#refs/tags/}" --json name,tagName,body > /tmp/gh-aw/agent/release.json
gh pr list --state merged --limit 100 --json number,title,labels > /tmp/gh-aw/agent/prs.json
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
---
# Release Highlights Generator
Generate release highlights for `${GITHUB_REF#refs/tags/}`. Analyze PRs in `/tmp/gh-aw/agent/prs.json`, categorize changes, and use update-release to prepend highlights to the release notes.

Files in /tmp/gh-aw/agent/ are automatically uploaded as artifacts and available to the AI agent.

.github/workflows/static-analysis.md
---
on:
schedule: daily
engine: claude
safe-outputs:
create-discussion:
jobs:
run-analysis:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- run: ./gh-aw compile --zizmor --poutine > /tmp/gh-aw/agent/analysis.txt
steps:
- uses: actions/download-artifact@v6
with:
name: analysis-results
path: /tmp/gh-aw/
---
# Static Analysis Report
Parse findings in `/tmp/gh-aw/agent/analysis.txt`, cluster by severity, and create a discussion with fix suggestions.

Pass data between jobs via artifacts, job outputs, or environment variables.

Inject deterministic steps directly into the pre-activation job using on.steps:. This saves one workflow job compared to the multi-job pattern and is the recommended approach for lightweight filtering:

.github/workflows/smart-responder.md
---
on:
issues:
types: [opened]
steps:
- id: check
env:
LABELS: ${{ toJSON(github.event.issue.labels.*.name) }}
run: echo "$LABELS" | grep -q '"bug"'
# exits 0 (outcome: success) if the label is found, 1 (outcome: failure) if not
engine: copilot
safe-outputs:
add-comment:
if: needs.pre_activation.outputs.check_result == 'success'
---
# Bug Issue Responder
Triage bug report: "${{ github.event.issue.title }}" and add-comment with a summary of the next steps.

Each step with an id gets an auto-wired output <id>_result set to ${{ steps.<id>.outcome }}success when the step’s exit code is 0, failure when non-zero. Gate the workflow by checking needs.pre_activation.outputs.<id>_result == 'success'.

To pass an explicit value rather than relying on exit codes, set a step output and re-expose it via jobs.pre-activation.outputs:

jobs:
pre-activation:
outputs:
has_bug_label: ${{ steps.check.outputs.has_bug_label }}
if: needs.pre_activation.outputs.has_bug_label == 'true'

When on.steps: need GitHub API access, use on.permissions: to grant the required scopes to the pre-activation job:

on:
schedule: every 30m
permissions:
issues: read
steps:
- id: search
uses: actions/github-script@v8
with:
script: |
const open = await github.rest.issues.listForRepo({ ...context.repo, state: 'open' });
core.setOutput('has_work', open.data.length > 0 ? 'true' : 'false');
jobs:
pre-activation:
outputs:
has_work: ${{ steps.search.outputs.has_work }}
if: needs.pre_activation.outputs.has_work == 'true'

See Pre-Activation Steps and Pre-Activation Permissions for full documentation.

Use a separate jobs: entry when filtering requires heavy tooling (checkout, compiled tools, multiple runners):

.github/workflows/smart-responder.md
---
on:
issues:
types: [opened]
engine: copilot
safe-outputs:
add-comment:
jobs:
filter:
runs-on: ubuntu-latest
outputs:
should-run: ${{ steps.check.outputs.result }}
steps:
- id: check
env:
LABELS: ${{ toJSON(github.event.issue.labels.*.name) }}
run: |
if echo "$LABELS" | grep -q '"bug"'; then
echo "result=true" >> "$GITHUB_OUTPUT"
else
echo "result=false" >> "$GITHUB_OUTPUT"
fi
if: needs.filter.outputs.should-run == 'true'
---
# Bug Issue Responder
Triage bug report: "${{ github.event.issue.title }}" and add-comment with a summary of the next steps.

The compiler automatically adds the filter job as a dependency of the activation job, so when the condition is false the workflow run is skipped (not failed), keeping the Actions tab clean.

For conditions that can be expressed directly with GitHub Actions context, use if: without a custom job:

---
on:
pull_request:
types: [opened, synchronize]
engine: copilot
if: github.event.pull_request.draft == false
---

For conditions based on GitHub search results, use skip-if-match: or skip-if-no-match: in the on: section — these accept standard GitHub search query syntax and are evaluated in the pre-activation job, producing the same skipped-not-failed behaviour:

---
on:
issues:
types: [opened]
# Skip if a duplicate issue already exists (GitHub search query syntax)
skip-if-match: 'is:issue is:open label:duplicate'
engine: copilot
---
.github/workflows/code-review.md
---
on:
pull_request:
types: [opened]
engine: copilot
safe-outputs:
jobs:
format-and-notify:
description: "Format and post review"
runs-on: ubuntu-latest
inputs:
summary: {required: true, type: string}
steps:
- ...
---
# Code Review Agent
Review the pull request and use format-and-notify to post your summary.

Define reusable guidance in shared files and import them:

.github/workflows/analysis.md
---
on:
schedule: daily
engine: copilot
imports:
- shared/reporting.md
safe-outputs:
create-discussion:
---
# Daily Analysis
Follow the report formatting guidelines from shared/reporting.md.

Use /tmp/gh-aw/agent/ to share data with AI agents. Files here are automatically uploaded as artifacts and accessible to the agent:

steps:
- run: |
gh api repos/${{ github.repository }}/issues > /tmp/gh-aw/agent/issues.json
gh api repos/${{ github.repository }}/pulls > /tmp/gh-aw/agent/pulls.json

Reference in prompts: “Analyze issues in /tmp/gh-aw/agent/issues.json and PRs in /tmp/gh-aw/agent/pulls.json.”