GitHub Agentic Workflows: Everything You Need to Know

GitHub Agentic Workflows went generally available on 28 October. You describe a job in a Markdown file ("summarise last week's activity", "explain why CI failed"), it gets compiled into a normal GitHub Actions workflow, and a coding agent does the work inside your runner without write access to your repo.
Let's take a look at how it works, build one small workflow, go through each safety layer, and figure out what it'll cost before you switch anything on.
What it is
An agentic workflow is a Markdown file that lives in .github/workflows/. The YAML frontmatter at the top holds everything Actions needs to know (the trigger, permissions, which agent to use, what it's allowed to write), and the body underneath is plain English instructions for the agent.
You run gh aw compile and it generates a .lock.yml file next to it, which is the actual Actions workflow that runs, and you commit both.
It's open source (MIT), built by GitHub Next, and available on github.com for Free, Team and Enterprise plans. The default engine is GitHub's Copilot CLI, which needs a Copilot licence, but you can swap in Claude, Codex and a few others with your own API key (more on that later).
Getting set up
First, install the gh CLI extension:
gh extension install github/gh-aw
If you've never used it before, the quickest way to see one working is to add one of GitHub's ready-made workflows from the agentics repo. From inside your repo, run:
gh aw add-wizard githubnext/agentics/daily-repo-status
The wizard is interactive. It asks you to pick an engine (Copilot is the default), helps you set up the secret that engine needs, and compiles the workflow. After that, it opens a pull request that adds the .md file and its .lock.yml to .github/workflows/ (or writes them locally, if you'd rather), and offers to run it straight away.
Merge that and you've got an agent writing a daily status report for your repo as an issue.
If you'd rather start from scratch, gh aw new gives you a template to fill in:
gh aw new stale-prs
That creates .github/workflows/stale-prs.md, which is what we'll write next.
Writing your first workflow
Let's build something small and useful: a weekly issue that lists pull requests nobody has touched in a while. It only reads from the repo and can only write one thing (a single issue), which makes it a nice safe first workflow.
Here's the whole file:
---
on:
schedule: weekly on monday
workflow_dispatch:
permissions:
contents: read
pull-requests: read
tools:
github:
toolsets: [repos, pull_requests]
network: defaults
safe-outputs:
staged: true
create-issue:
title-prefix: "[stale prs] "
labels: [report]
max: 1
---
# Stale pull request summary
Find every open pull request in this repository that hasn't had a commit,
comment or review in the last 14 days.
For each one, list:
- the title, with a link to it
- who opened it, and how many days since the last activity
- one sentence on what it's waiting for (a review, changes from the author,
a failing check)
Group them by what they're waiting for. If there aren't any stale pull
requests, don't create an issue.
Let's go through the frontmatter.
on
The on block works like any other Actions trigger, with a few extras.
schedule: weekly on monday is one of gh-aw's fuzzy schedules. Rather than you picking a cron time, the compiler picks one for you based on the repo and the file's path, so every workflow out there doesn't fire at midnight at the same time. The time stays the same each time you recompile.
I've also added workflow_dispatch, so we can run it by hand while we're testing.
permissions
These are normal Actions permission scopes, and everything here is read. The agent can look at code and pull requests, but it can't change anything directly. That's true even though the job ends up creating an issue, which we'll come to in a moment.
tools
github: gives the agent GitHub's MCP tools so it can list and read pull requests. On its own, github: turns on a default set of toolsets that includes issues, and the compiler will warn you that you're missing issues: read. This job doesn't need to read issues, so I've narrowed it down to the repos and pull_requests toolsets instead.
There are a bunch of other tools you can switch on here, like edit: for changing files, bash: for running commands (a short list of safe ones like ls, cat and grep by default), web-fetch: and playwright:. You'd only add what the job actually needs, and this one only needs to read GitHub.
network
defaults only lets the agent reach basic infrastructure, like certificate checks and Ubuntu's package mirrors. This is also what you get if you leave network out. If you needed it to reach an API or install Python packages, you'd list them under network.allowed, and anything not on that list is blocked.
safe-outputs
This is the only way the agent can change anything. We're allowing one issue, with a title prefix and a label so it's obvious where it came from.
The staged: true line means that for now it won't even create that issue. It'll write a preview of what it would have done to the Actions step summary instead, which is what you want for the first few runs.
The body
The Markdown body is the prompt. I'd keep it short and specific, and tell it what to do when there's nothing to report, otherwise you'll end up with an issue every Monday saying "no stale pull requests found".
Compile, commit and run
Once you're happy with the file, compile it:
gh aw compile
This validates the frontmatter and writes .github/workflows/stale-prs.lock.yml. Commit and push both files, since Actions runs the lock file and gh-aw needs the Markdown:
git add .github/workflows/stale-prs.md .github/workflows/stale-prs.lock.yml
git commit -m "Add stale PR summary workflow"
git push
Now trigger it, rather than waiting until Monday:
gh aw run stale-prs
You can check on it with gh aw status, and once it's finished, gh aw logs stale-prs downloads the logs along with how long each run took, how many tokens it used, what it cost and how many turns the agent took. For a deeper look at a single run, gh aw audit <run-id> gives you a full report, including a firewall section showing which domains the agent tried to reach and whether they were allowed or blocked.
Editing it later
When you change the frontmatter (the trigger, permissions, tools and so on), you need to run gh aw compile again and commit the new lock file.
Changes to the Markdown body don't need recompiling, though. The body gets loaded when the workflow runs, so you can tweak the prompt, push, and the next run picks it up.
What's in the lock file
It's worth opening the .lock.yml once, because it's a lot longer than the Markdown you wrote (ours compiles to around 2,000 lines). Behind the scenes, your one workflow becomes a chain of separate jobs, each with its own permissions.
activationprepares the prompt, checks the lock file still matches the Markdown, and checks you haven't gone over your daily AI budget.agentchecks out your repo, starts the MCP servers in their own containers, runs the agent, removes any secrets from its output, and uploads what the agent asked to do as an artifact. This job only gets the read permissions from your frontmatter.detectionreads that output and looks for anything suspicious.safe_outputsmakes the changes. In our example it's the only job that getsissues: write, apart from gh-aw's own housekeeping inactivationandconclusion, which can open an issue to tell you a run failed or that your compiled version needs updating.conclusionsums it all up. If your workflow is triggered by an event, like a new issue, you'll also get apre_activationjob at the start. It checks whether the person who triggered it has the right role before anything else runs.
The compiler pins every action it uses to a commit SHA and every container image to a digest, so nothing can change underneath you between compiles. You can also run security scanners over the result with gh aw compile --actionlint --zizmor --poutine. Don't edit the lock file by hand, since your changes will be gone the next time you compile.
The safety layers
Handing an agent your repo sounds a bit scary, so it's worth understanding what's stopping it from doing something you didn't ask for. GitHub's changelog lists five layers, so let's go through them one at a time.
Integrity filtering
This controls which GitHub content the agent can see at all, based on who wrote it. It's set with tools.github.min-integrity, and the options are merged, approved, unapproved and none.
On public repos, if you don't set anything, it defaults to approved, which filters out content from untrusted authors before the agent reads it. Private and internal repos don't get a filter applied automatically. (If you've seen lockdown: true in older examples, it's been replaced by this.)
Read-only permissions
As we saw above, the agent job runs with read permissions. Even if something convinces the agent to try and push code or close every issue, it doesn't have a token that can do it.
A sandbox and a firewall
The agent runs in a container on its own network behind the Agent Workflow Firewall, a proxy that only lets traffic through to the domains in your network block. Your model API keys sit in a separate proxy too, so the agent never sees them.
If you want more isolation than a container, there are newer micro-VM runtimes (Cloud Hypervisor and NVX), although they're still marked as preview in the docs.
Safe outputs
This is the one that does most of the work. The agent can't write to GitHub itself. It can only ask, in a structured format, for one of the actions you've listed under safe-outputs.
A separate job then checks the request against your rules (how many, which labels, what title prefix) and only then makes the API call with its own scoped token. So if you've allowed one issue, the most it can ever do is create one issue.
There are a load of output types beyond create-issue, like add-comment, add-labels, create-pull-request and push-to-pull-request-branch, and most have limits you can tighten.
For example, add-labels takes an allowed list so the agent can't invent its own labels. Pull requests it opens aren't merged automatically either, unless you opt in. There's an experimental merge-pull-request output and an --auto-merge-prs flag on gh aw run, but I'd leave both well alone.
Threat detection
Before the safe output job runs, a separate detection job uses AI to scan the agent's output and any code changes for prompt injection, leaked secrets and malicious code. It's switched on automatically whenever you have safe outputs.
By default it runs in warn mode. If it finds something, it flags outputs that a human will review anyway (like a pull request) and stops the ones nobody would see. To make it block everything instead, set continue-on-error: false under safe-outputs.threat-detection.
It's a useful extra check, but it's an AI reading the agent's output, so it can miss things. I wouldn't rely on it alone, and that's why the other four layers exist.
Untrusted input
Anything a stranger can type into your repo is untrusted, and that includes issue titles, issue bodies, PR descriptions and comments. If your workflow triggers on new issues, someone can write an issue that's really instructions for your agent.
Integrity filtering and safe outputs are what protect you here. A workflow that can only add labels from a short list can be tricked into adding the wrong label, which is annoying but not much worse. Pushing to branches is a much bigger deal, so be careful what you give a workflow that reads public input.
By default, only people with admin, maintainer or write access can trigger a workflow, and pull requests from forks are blocked. If you're triggering on pull requests, the quick start recommends pull_request over pull_request_target, since the latter runs with the base repo's permissions and secrets. GitHub is also blocking pull_request_target by default for public repos from 2 December, so it's worth checking any existing workflows that use it.
Engines
If you don't set an engine, you get Copilot. You can authenticate it with a COPILOT_GITHUB_TOKEN secret (a fine-grained personal access token with the Copilot Requests permission), or, if your org pays for Copilot centrally, by adding copilot-requests: write to the workflow's permissions so it uses the built-in Actions token.
To use Claude or Codex instead, set the engine and add your own API key as a secret:
---
engine:
id: claude
model: sonnet
---
gh aw secrets set ANTHROPIC_API_KEY --value "your-key"
For Codex, it's engine: codex with an OPENAI_API_KEY (or CODEX_API_KEY) secret. Claude and Codex also have keyless options using OIDC, so you don't need a long-lived key sitting in your secrets, and gh aw secrets bootstrap will look through your workflows and ask for any secrets that are missing.
GA also added model routing, where a router picks a model based on the type of task and how hard it is. I'd start with a single model you know, though, so you can tell whether a bad run was down to the prompt or the model.
What it costs
There are two costs. First, there are your Actions minutes, at standard Actions pricing. The agent job usually takes anywhere from one to 15 minutes, plus about a minute and a half of runner setup for each job.
Then there's the model itself. If you're on Copilot, that comes out of your Copilot plan, and with your own key, it's on your Anthropic or OpenAI bill.
gh-aw tracks this in AI Credits (1 AIC is $0.01), estimated from public model pricing, so treat it as a rough guide rather than your actual bill.
The detection job is a second AI run every time, with its own budget (400 AIC by default), so it adds to the cost too.
Limits you can set
A few limits are on by default, and you can tighten them in the frontmatter:
timeout-minutes: 20 minutes for the agent stepmax-ai-credits: 1,000 AIC (about $10) per runmax-daily-ai-credits: 5,000 AIC per workflow per day (once it's hit, the agent is skipped)max-turns: 500 agent turns For something like the stale PR summary, I'd setmax-ai-creditsmuch lower than the default. If the agent gets stuck going round in circles, you'd much rather it stopped after a dollar or two than ten.
Forecasting
Once a workflow has run a few times, gh aw forecast uses its run history to estimate what it'll cost over a week or a month:
gh aw forecast stale-prs
It projects a month by default (add --period week for a week), and gives you a low, typical and high estimate (P10, P50 and P90), since agent runs vary a fair bit. Since GA, it includes the threat detection credits too. It's worth running this after your staged runs and before you leave a schedule running, especially for anything triggered by events, where you don't control how often it runs.
The cost docs also suggest starting with schedule or workflow_dispatch triggers, and avoiding push triggers in busy repos, since every push would kick off an agent.
When not to use it
Agentic workflows are great when a job needs a bit of judgement. But plenty of jobs don't, and for those an agent just makes things slower, more expensive and less predictable.
Anything with a fixed right answer
Builds, tests, linting, releases and deploys belong in plain Actions. GitHub says as much: agentic workflows sit alongside your CI, they don't replace it. If you can write the steps down as a script, write the script.
Anything you need to behave the same every time
The same workflow can do something slightly different on each run. That's fine for a summary or a suggested label, but it's not what you want for something like a version bump or a changelog that has to follow a format.
Writes nobody will review
If a workflow is allowed to change things and nobody reads what it's done, a bad run can go unnoticed for weeks. Only give a workflow write access to things somebody actually looks at.
Wrapping up
There's a lot here, but you don't need all of it on day one. I'd start with one read-only workflow that can only open a single issue, set staged: true, and run it by hand a few times while you read what it would have done.
Once you're happy with it, check the forecast, remove staged, and let it run on its schedule. Only after you've watched it behave for a while would I give it anything more, like labels, comments or pull requests.