Skip to content

Commit 275a19e

Browse files
jeffhandleyCopilot
andauthored
Set Up GitHub Agentic Workflows (dotnet#10336)
* Initialize Agentic Workflows agent * Set up the select-copilot-pat integration into agentic workflows * Move labeler workflow readme to avoid it being recognized as a workflow * Fix markdown lint in agentic workflow docs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix more lint issues. Exclude skills and generated workflows from lint. * Add a Design/Security section to the select-copilot-pat doc * Fix new lint errors --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 0eb8854 commit 275a19e

7 files changed

Lines changed: 530 additions & 6 deletions

File tree

.checkov.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,8 @@ skip-check:
88
# Actions workflow_dispatch inputs MUST be empty."
99
# Suppressed as issue-labeler workflows require `workflow_dispatch` inputs.
1010
- CKV_GHA_7
11+
12+
skip-path:
13+
# Exclude GitHub skills and generated workflows
14+
- .github/skills/*
15+
- .github/workflows/copilot-setup-steps.yml
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 dotnet org 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 the only permission granted.
29+
3. **8-day expiration** must be used, which enforces a weekly renewal.
30+
4. **Repository access** set to **Public repositories** only.
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 dotnet org agentic workflows. The **Description** is also only used for your own reference.
33+
34+
Team members providing PATs for workflows should set weekly recurring reminders to regenerate and update their PATs in the repository secrets. With an 8-day expiration, renewal can be done on the same day each week.
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 dotnet/<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=dotnet%20org%20agentic%20workflows&description=GitHub+Agentic+Workflows+-+Copilot+engine+authentication.++Used+for+dotnet+org+workflows.+MUST+be+configured+with+only+Copilot+Requests+permissions+and+user+account+as+resource+owner.+Weekly+expiration+and+required+renewal.&user_copilot_requests=read&expires_in=8
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"

0 commit comments

Comments
 (0)