Skip to content

Commit e74f402

Browse files
committed
Install the select-copilot-pat action and integrate into agent
1 parent e3664af commit e74f402

3 files changed

Lines changed: 291 additions & 1 deletion

File tree

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
# Select Copilot PAT
2+
3+
Selects a random Copilot PAT from a numbered pool of secrets. This addresses limitations that arise from having a single PAT shared across all agentic workflows, such as rate-limiting.
4+
5+
**This is a stop-gap workaround.** As soon as organization/enterprise billing is offered for agentic workflows, this approach will be removed from our workflows.
6+
7+
## Repository Onboarding
8+
9+
To use Agentic Workflows in a repository:
10+
11+
1. Follow the instructions for [Configuring Your Repository | Agentic Authoring | GitHub Agentic Workflows][configure-repo].
12+
2. Copy this `select-copilot-pat` folder into the repository under `.github/actions/select-copilot-pat`, including both the `README.md` and `action.yml`.
13+
3. Merge those additions into the repository and then follow the instructions for the PAT Creation and Usage below.
14+
15+
> **Optional:** If you plan to manage secrets or workflows from the command line (e.g., `gh aw secrets set`), [install the `gh aw` CLI extension][cli-setup]:
16+
>
17+
> ```sh
18+
> gh extension install github/gh-aw
19+
> ```
20+
21+
## PAT Management
22+
23+
Team members provide PATs into the pools for the repository by adding them as repository secrets with secret names matching the pattern of `<pool_name>_<0-9>`, such as `COPILOT_PAT_0`.
24+
25+
[Use this link to prefill the PAT creation form with the required settings][create-pat]:
26+
27+
1. **Resource owner** is your **user account**, not an organization.
28+
2. **Copilot Requests (Read)** must be granted.
29+
3. **Repository access** set to **Public repositories** only — or, if the consuming workflow's agent needs to read private repositories, add those repos explicitly.
30+
4. Any additional **read-only** permissions the consuming workflow's agent requires (e.g., reading issues or metadata from other repositories) should also be included in the PAT scope. Consult the workflow's documentation for its specific requirements.
31+
32+
The **Token Name** _does not_ need to match the secret name and is only visible to the owner of the PAT. It's recommended to use a token name indicating the PAT is used for agentic workflows in this repository. The **Description** is also only used for your own reference.
33+
34+
Team members providing PATs for workflows should set recurring reminders to regenerate and update their PATs in the repository secrets before they expire.
35+
36+
PATs are added to repositories through the **Settings > Secrets and variables > Actions** UI, saved as **Repository secrets** and matching the `<pool_name>_<0-9>` naming convention. This can also be done using the GitHub CLI.
37+
38+
```sh
39+
gh aw secrets set "<pool_name>_<0-9>" --value "<your-github-pat>" --repo <owner/repo>
40+
```
41+
42+
## Workflow Output Attribution
43+
44+
Team members' PATs are _only_ used for the Copilot requests from within the agentic portion of the workflow. All outputs from the workflow use the `github-actions[bot]` account token. Issues, PRs, comments, and all other content generated by the workflow will be attributed to `github-actions[bot]`--not the team member's account or token.
45+
46+
## Usage
47+
48+
Add the following frontmatter at the top-level of an agentic workflow. These elements are not supported through [imports][imports], so they must be copied into all workflows.
49+
50+
Up to 10 `SECRET_#` environment variables can be passed to the action, numbered 0-9. Different workflows can use different pools of PATs if desired. Change the `secrets.COPILOT_PAT_0` through `secrets.COPILOT_PAT_9` secret names in both the `select-copilot-pat` step `env` values and in the `case` expression under the `engine: env` configuration.
51+
52+
```yml
53+
on:
54+
# Add the pre-activation step of selecting a random PAT from the supplied secrets
55+
steps:
56+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
57+
name: Checkout the select-copilot-pat action folder
58+
with:
59+
persist-credentials: false
60+
sparse-checkout: .github/actions/select-copilot-pat
61+
sparse-checkout-cone-mode: true
62+
fetch-depth: 1
63+
64+
- id: select-copilot-pat
65+
name: Select Copilot token from pool
66+
uses: ./.github/actions/select-copilot-pat
67+
env:
68+
# If the secret names are changed here, they must also be changed
69+
# in the `engine: env` case expression
70+
SECRET_0: ${{ secrets.COPILOT_PAT_0 }}
71+
SECRET_1: ${{ secrets.COPILOT_PAT_1 }}
72+
SECRET_2: ${{ secrets.COPILOT_PAT_2 }}
73+
SECRET_3: ${{ secrets.COPILOT_PAT_3 }}
74+
SECRET_4: ${{ secrets.COPILOT_PAT_4 }}
75+
SECRET_5: ${{ secrets.COPILOT_PAT_5 }}
76+
SECRET_6: ${{ secrets.COPILOT_PAT_6 }}
77+
SECRET_7: ${{ secrets.COPILOT_PAT_7 }}
78+
SECRET_8: ${{ secrets.COPILOT_PAT_8 }}
79+
SECRET_9: ${{ secrets.COPILOT_PAT_9 }}
80+
81+
# Add the pre-activation output of the randomly selected PAT
82+
jobs:
83+
pre-activation:
84+
outputs:
85+
copilot_pat_number: ${{ steps.select-copilot-pat.outputs.copilot_pat_number }}
86+
87+
# Override the COPILOT_GITHUB_TOKEN expression used in the activation job
88+
# Consume the PAT number from the pre-activation step and select the corresponding secret
89+
engine:
90+
id: copilot
91+
env:
92+
# We cannot use line breaks in this expression as it leads to a syntax error in the compiled workflow
93+
# If none of the `COPILOT_PAT_#` secrets were selected, then the default COPILOT_GITHUB_TOKEN is used
94+
COPILOT_GITHUB_TOKEN: ${{ case(needs.pre_activation.outputs.copilot_pat_number == '0', secrets.COPILOT_PAT_0, needs.pre_activation.outputs.copilot_pat_number == '1', secrets.COPILOT_PAT_1, needs.pre_activation.outputs.copilot_pat_number == '2', secrets.COPILOT_PAT_2, needs.pre_activation.outputs.copilot_pat_number == '3', secrets.COPILOT_PAT_3, needs.pre_activation.outputs.copilot_pat_number == '4', secrets.COPILOT_PAT_4, needs.pre_activation.outputs.copilot_pat_number == '5', secrets.COPILOT_PAT_5, needs.pre_activation.outputs.copilot_pat_number == '6', secrets.COPILOT_PAT_6, needs.pre_activation.outputs.copilot_pat_number == '7', secrets.COPILOT_PAT_7, needs.pre_activation.outputs.copilot_pat_number == '8', secrets.COPILOT_PAT_8, needs.pre_activation.outputs.copilot_pat_number == '9', secrets.COPILOT_PAT_9, secrets.COPILOT_GITHUB_TOKEN) }}
95+
```
96+
97+
## Design / Security
98+
99+
There are several details of this implementation that keep our workflows and repositories safe.
100+
101+
1. **Secrets adhere to existing trust boundaries.** The pool of PAT secrets is
102+
provided to the `select-copilot-pat` action within the `pre_activation`
103+
job, which is a deterministic and trusted portion of the workflow. No
104+
untrusted context or input is within scope during this job. The action step
105+
runs within that job, and the secrets do not get passed across contexts. The
106+
`select-copilot-pat` action only references the secret values to determine
107+
which values are non-empty, filtering the secret numbers to those with
108+
values.
109+
1. **The `select-copilot-pat` action does not require any permissions.** It
110+
merely selects a random number from the pool of non-empty secrets and
111+
returns the _number_ (**not the secret**). The consuming workflow uses the
112+
returned secret number to provide the corresponding PAT to the agent job.
113+
1. **The implementation uses existing extensibility hooks in Agentic
114+
Workflows.** Everything is supported by `gh aw compile` in this approach,
115+
and no hand-editing of the compiled output is required. The `pre_activation`
116+
job is designed for this type of extensibility, and the
117+
[secret override][secret-override] capability was added to support using a
118+
secret with a name different from the default `COPILOT_GITHUB_TOKEN`.
119+
120+
Each of the references below contributed to the design and implementation to ensure a secure and reliable design.
121+
122+
## References
123+
124+
- [Agentic Workflows CLI Extension][cli-setup]
125+
- [Agentic Authoring][configure-repo]
126+
- [Authentication][authentication]
127+
- [Agentic Workflow Imports][imports]
128+
- [Custom Steps][steps]
129+
- [Custom Jobs][jobs]
130+
- [Job Outputs][job-outputs]
131+
- [Engine Configuration][engine]
132+
- [Engine Environment Variables][engine-vars]
133+
- [Case Function in Workflow Expressions][case-expression]
134+
- [Update agentic engine token handling to use user-provided secrets (github/gh-aw#18017)][secret-override]
135+
136+
[cli-setup]: https://github.github.com/gh-aw/setup/cli/
137+
[configure-repo]: https://github.github.com/gh-aw/guides/agentic-authoring/#configuring-your-repository
138+
[authentication]: https://github.github.com/gh-aw/reference/auth/
139+
[create-pat]: https://github.com/settings/personal-access-tokens/new?name=Agentic%20Workflows%20PAT&description=GitHub+Agentic+Workflows+-+Copilot+engine+authentication.+Must+be+configured+with+Copilot+Requests+permission+and+user+account+as+resource+owner.&user_copilot_requests=read
140+
[imports]: https://github.github.com/gh-aw/reference/imports/
141+
[steps]: https://github.github.com/gh-aw/reference/frontmatter/#custom-steps-steps
142+
[jobs]: https://github.github.com/gh-aw/reference/frontmatter/#custom-jobs-jobs
143+
[job-outputs]: https://github.github.com/gh-aw/reference/frontmatter/#job-outputs
144+
[engine]: https://github.github.com/gh-aw/reference/frontmatter/#ai-engine-engine
145+
[engine-vars]: https://github.github.com/gh-aw/reference/engines/#engine-environment-variables
146+
[case-expression]: https://docs.github.com/en/actions/reference/workflows-and-actions/expressions#case
147+
[secret-override]: https://github.com/github/gh-aw/pull/18017
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
---
2+
name: "Select Copilot PAT from Pool"
3+
description: >-
4+
Selects a random Copilot PAT from a numbered pool of secrets. Secrets are
5+
passed as environment variables SECRET_0 through SECRET_9 by the calling
6+
workflow step.
7+
8+
inputs:
9+
random-seed:
10+
description: >-
11+
A seed number to use for the random PAT selection, for deterministic
12+
selection if needed.
13+
required: false
14+
default: ""
15+
16+
outputs:
17+
copilot_pat_number:
18+
description: >-
19+
The 0-9 secret number selected from the pool of specified secrets
20+
value: ${{ steps.select-pat-number.outputs.copilot_pat_number }}
21+
22+
runs:
23+
using: composite
24+
steps:
25+
- id: select-pat-number
26+
shell: bash
27+
env:
28+
RANDOM_SEED: ${{ inputs.random-seed }}
29+
run: |
30+
# Collect numbers with non-empty secrets from SECRET_0..SECRET_9.
31+
PAT_NUMBERS=()
32+
for i in $(seq 0 9); do
33+
var="SECRET_${i}"
34+
val="${!var}"
35+
if [ -n "$val" ]; then
36+
PAT_NUMBERS+=(${i})
37+
fi
38+
done
39+
40+
# If none of the secrets in the pool have values, emit a warning
41+
# and do not set an output value. The consumer can then fall back
42+
# to using COPILOT_GITHUB_TOKEN.
43+
if [ ${#PAT_NUMBERS[@]} -eq 0 ]; then
44+
warning_message="::warning::None of the specified secrets had values "
45+
warning_message+="(checked SECRET_0 through SECRET_9)"
46+
echo "$warning_message"
47+
exit 0
48+
fi
49+
50+
# Select a random index using the seed if specified
51+
if [ -n "$RANDOM_SEED" ]; then
52+
RANDOM=$RANDOM_SEED
53+
fi
54+
55+
PAT_INDEX=$(( RANDOM % ${#PAT_NUMBERS[@]} ))
56+
PAT_NUMBER="${PAT_NUMBERS[$PAT_INDEX]}"
57+
selection_message="Selected token ${PAT_NUMBER}"
58+
selection_details="(index: ${PAT_INDEX}; pool size: ${#PAT_NUMBERS[@]})"
59+
echo "${selection_message} ${selection_details}"
60+
61+
# Set the PAT number as the output
62+
echo "copilot_pat_number=${PAT_NUMBER}" >> "$GITHUB_OUTPUT"

.github/agents/agentic-workflows.agent.md

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ This is a **dispatcher agent** that routes your request to the appropriate speci
1313

1414
- **Creating new workflows**: Routes to `create` prompt
1515
- **Updating existing workflows**: Routes to `update` prompt
16-
- **Debugging workflows**: Routes to `debug` prompt
16+
- **Debugging workflows**: Routes to `debug` prompt
1717
- **Upgrading workflows**: Routes to `upgrade-agentic-workflows` prompt
1818
- **Creating report-generating workflows**: Routes to `report` prompt — consult this whenever the workflow posts status updates, audits, analyses, or any structured output as issues, discussions, or comments
1919
- **Creating shared components**: Routes to `create-shared-agentic-workflow` prompt
@@ -176,3 +176,84 @@ gh aw compile --validate
176176
- Follow security best practices: minimal permissions, explicit network access, no template injection
177177
- **Network configuration**: Use ecosystem identifiers (`node`, `python`, `go`, etc.) or explicit FQDNs in `network.allowed`. Bare shorthands like `npm` or `pypi` are **not** valid. See https://github.com/github/gh-aw/blob/v0.66.1/.github/aw/network.md for the full list of valid ecosystem identifiers and domain patterns.
178178
- **Single-file output**: When creating a workflow, produce exactly **one** workflow `.md` file. Do not create separate documentation files (architecture docs, runbooks, usage guides, etc.). If documentation is needed, add a brief `## Usage` section inside the workflow file itself.
179+
180+
## Repository-Specific Requirements: Copilot PAT Pool
181+
182+
**⚠️ MANDATORY**: Every agentic workflow in this repository **must** include the `select-copilot-pat` integration. This selects a random Copilot PAT from a numbered pool of secrets to avoid rate-limiting from a single shared PAT. This is a stop-gap workaround until organization-level billing is offered for agentic workflows.
183+
184+
These elements are **not supported through imports**, so they must be copied into the frontmatter of every agentic workflow. See `.github/actions/select-copilot-pat/README.md` for full documentation and `.github/workflows/copilot-echo.md` for a working example.
185+
186+
When **creating** or **updating** an agentic workflow, always include the following three frontmatter blocks. These go between the `---` markers alongside the workflow's other frontmatter (permissions, safe-outputs, etc.).
187+
188+
### Comment block (placed before the steps, inside the `on:` block)
189+
190+
Always include this comment block before the steps to explain the PAT pool override:
191+
192+
```yaml
193+
# ###############################################################
194+
# Override the COPILOT_GITHUB_TOKEN secret usage for the workflow
195+
# with a randomly-selected token from a pool of secrets.
196+
#
197+
# As soon as organization-level billing is offered for Agentic
198+
# Workflows, this stop-gap approach will be removed.
199+
#
200+
# See: /.github/actions/select-copilot-pat/README.md
201+
# ###############################################################
202+
```
203+
204+
### Block 1: Pre-activation steps (nested under `on:`)
205+
206+
Add this under the `on:` key, at the same level as the trigger configuration, immediately after the comment block above:
207+
208+
```yaml
209+
# Add the pre-activation step of selecting a random PAT from the supplied secrets
210+
steps:
211+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
212+
name: Checkout the select-copilot-pat action folder
213+
with:
214+
persist-credentials: false
215+
sparse-checkout: .github/actions/select-copilot-pat
216+
sparse-checkout-cone-mode: true
217+
fetch-depth: 1
218+
219+
- id: select-copilot-pat
220+
name: Select Copilot token from pool
221+
uses: ./.github/actions/select-copilot-pat
222+
env:
223+
SECRET_0: ${{ secrets.COPILOT_PAT_0 }}
224+
SECRET_1: ${{ secrets.COPILOT_PAT_1 }}
225+
SECRET_2: ${{ secrets.COPILOT_PAT_2 }}
226+
SECRET_3: ${{ secrets.COPILOT_PAT_3 }}
227+
SECRET_4: ${{ secrets.COPILOT_PAT_4 }}
228+
SECRET_5: ${{ secrets.COPILOT_PAT_5 }}
229+
SECRET_6: ${{ secrets.COPILOT_PAT_6 }}
230+
SECRET_7: ${{ secrets.COPILOT_PAT_7 }}
231+
SECRET_8: ${{ secrets.COPILOT_PAT_8 }}
232+
SECRET_9: ${{ secrets.COPILOT_PAT_9 }}
233+
```
234+
235+
### Block 2: Pre-activation job outputs (top-level `jobs:`)
236+
237+
```yaml
238+
jobs:
239+
pre-activation:
240+
outputs:
241+
copilot_pat_number: ${{ steps.select-copilot-pat.outputs.copilot_pat_number }}
242+
```
243+
244+
### Block 3: Engine configuration with PAT override (top-level `engine:`)
245+
246+
```yaml
247+
engine:
248+
id: copilot
249+
env:
250+
# We cannot use line breaks in this expression as it leads to a syntax error in the compiled workflow
251+
# If none of the `COPILOT_PAT_#` secrets were selected, then the default COPILOT_GITHUB_TOKEN is used
252+
COPILOT_GITHUB_TOKEN: ${{ case(needs.pre_activation.outputs.copilot_pat_number == '0', secrets.COPILOT_PAT_0, needs.pre_activation.outputs.copilot_pat_number == '1', secrets.COPILOT_PAT_1, needs.pre_activation.outputs.copilot_pat_number == '2', secrets.COPILOT_PAT_2, needs.pre_activation.outputs.copilot_pat_number == '3', secrets.COPILOT_PAT_3, needs.pre_activation.outputs.copilot_pat_number == '4', secrets.COPILOT_PAT_4, needs.pre_activation.outputs.copilot_pat_number == '5', secrets.COPILOT_PAT_5, needs.pre_activation.outputs.copilot_pat_number == '6', secrets.COPILOT_PAT_6, needs.pre_activation.outputs.copilot_pat_number == '7', secrets.COPILOT_PAT_7, needs.pre_activation.outputs.copilot_pat_number == '8', secrets.COPILOT_PAT_8, needs.pre_activation.outputs.copilot_pat_number == '9', secrets.COPILOT_PAT_9, secrets.COPILOT_GITHUB_TOKEN) }}
253+
```
254+
255+
**Important notes about the engine block:**
256+
257+
- The `COPILOT_GITHUB_TOKEN` `case()` expression **must** remain on a single line — line breaks cause syntax errors in the compiled workflow.
258+
- If no `COPILOT_PAT_#` secrets are configured, the expression falls back to the default `COPILOT_GITHUB_TOKEN` secret.
259+
- Do **not** specify `engine: copilot` as a simple string — use the object form shown above so the `env:` override can be included.

0 commit comments

Comments
 (0)