From 5b441b193f7dc2b75f667f04f908f98f3e70b65f Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 22 Jan 2026 17:43:45 +0000
Subject: [PATCH 1/5] Initial plan
From 6249db3639f80102ef775aa5788b1cca5f644d60 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 22 Jan 2026 17:48:43 +0000
Subject: [PATCH 2/5] Initial exploration of campaign documentation structure
Co-authored-by: mnkiefer <8320933+mnkiefer@users.noreply.github.com>
---
.github/workflows/code-simplifier.lock.yml | 117 +++++++++------------
1 file changed, 50 insertions(+), 67 deletions(-)
diff --git a/.github/workflows/code-simplifier.lock.yml b/.github/workflows/code-simplifier.lock.yml
index e96a36cc15..420aab3450 100644
--- a/.github/workflows/code-simplifier.lock.yml
+++ b/.github/workflows/code-simplifier.lock.yml
@@ -87,7 +87,7 @@ jobs:
GH_AW_ASSETS_BRANCH: ""
GH_AW_ASSETS_MAX_SIZE_KB: 0
GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs
- GH_AW_SAFE_OUTPUTS: /tmp/gh-aw/safeoutputs/outputs.jsonl
+ GH_AW_SAFE_OUTPUTS: /opt/gh-aw/safeoutputs/outputs.jsonl
GH_AW_SAFE_OUTPUTS_CONFIG_PATH: /opt/gh-aw/safeoutputs/config.json
GH_AW_SAFE_OUTPUTS_TOOLS_PATH: /opt/gh-aw/safeoutputs/tools.json
outputs:
@@ -143,19 +143,7 @@ jobs:
env:
COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
- name: Install GitHub Copilot CLI
- run: |
- # Download official Copilot CLI installer script
- curl -fsSL https://raw.githubusercontent.com/github/copilot-cli/main/install.sh -o /tmp/copilot-install.sh
-
- # Execute the installer with the specified version
- # Pass VERSION directly to sudo to ensure it's available to the installer script
- sudo VERSION=0.0.388 bash /tmp/copilot-install.sh
-
- # Cleanup
- rm -f /tmp/copilot-install.sh
-
- # Verify installation
- copilot --version
+ run: /opt/gh-aw/actions/install_copilot_cli.sh 0.0.389
- name: Install awf binary
run: bash /opt/gh-aw/actions/install_awf_binary.sh v0.10.0
- name: Determine automatic lockdown mode for GitHub MCP server
@@ -169,7 +157,7 @@ jobs:
const determineAutomaticLockdown = require('/opt/gh-aw/actions/determine_automatic_lockdown.cjs');
await determineAutomaticLockdown(github, context, core);
- name: Download container images
- run: bash /opt/gh-aw/actions/download_docker_images.sh ghcr.io/github/github-mcp-server:v0.29.0 ghcr.io/githubnext/gh-aw-mcpg:v0.0.74 node:lts-alpine
+ run: bash /opt/gh-aw/actions/download_docker_images.sh ghcr.io/github/github-mcp-server:v0.29.0 ghcr.io/githubnext/gh-aw-mcpg:v0.0.76 node:lts-alpine
- name: Write Safe Outputs Config
run: |
mkdir -p /opt/gh-aw/safeoutputs
@@ -350,10 +338,49 @@ jobs:
}
}
EOF
+ - name: Generate Safe Outputs MCP Server Config
+ id: safe-outputs-config
+ run: |
+ # Generate a secure random API key (360 bits of entropy, 40+ chars)
+ API_KEY=""
+ API_KEY=$(openssl rand -base64 45 | tr -d '/+=')
+ PORT=3001
+
+ # Register API key as secret to mask it from logs
+ echo "::add-mask::${API_KEY}"
+
+ # Set outputs for next steps
+ {
+ echo "safe_outputs_api_key=${API_KEY}"
+ echo "safe_outputs_port=${PORT}"
+ } >> "$GITHUB_OUTPUT"
+
+ echo "Safe Outputs MCP server will run on port ${PORT}"
+
+ - name: Start Safe Outputs MCP HTTP Server
+ id: safe-outputs-start
+ env:
+ GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }}
+ GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }}
+ GH_AW_SAFE_OUTPUTS_TOOLS_PATH: /opt/gh-aw/safeoutputs/tools.json
+ GH_AW_SAFE_OUTPUTS_CONFIG_PATH: /opt/gh-aw/safeoutputs/config.json
+ GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs
+ run: |
+ # Environment variables are set above to prevent template injection
+ export GH_AW_SAFE_OUTPUTS_PORT
+ export GH_AW_SAFE_OUTPUTS_API_KEY
+ export GH_AW_SAFE_OUTPUTS_TOOLS_PATH
+ export GH_AW_SAFE_OUTPUTS_CONFIG_PATH
+ export GH_AW_MCP_LOG_DIR
+
+ bash /opt/gh-aw/actions/start_safe_outputs_server.sh
+
- name: Start MCP gateway
id: start-mcp-gateway
env:
GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
+ GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }}
+ GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }}
GITHUB_MCP_LOCKDOWN: ${{ steps.determine-automatic-lockdown.outputs.lockdown == 'true' && '1' || '0' }}
GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
run: |
@@ -370,7 +397,7 @@ jobs:
# Register API key as secret to mask it from logs
echo "::add-mask::${MCP_GATEWAY_API_KEY}"
export GH_AW_ENGINE="copilot"
- export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e DEBUG="*" -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_LOCKDOWN -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/githubnext/gh-aw-mcpg:v0.0.74'
+ export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e DEBUG="*" -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_LOCKDOWN -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/githubnext/gh-aw-mcpg:v0.0.76'
mkdir -p /home/runner/.copilot
cat << MCPCONFIG_EOF | bash /opt/gh-aw/actions/start_mcp_gateway.sh
@@ -387,42 +414,10 @@ jobs:
}
},
"safeoutputs": {
- "type": "stdio",
- "container": "node:lts-alpine",
- "entrypoint": "node",
- "entrypointArgs": ["/opt/gh-aw/safeoutputs/mcp-server.cjs"],
- "mounts": ["/opt/gh-aw:/opt/gh-aw:ro", "/tmp/gh-aw:/tmp/gh-aw:rw", "${{ github.workspace }}:${{ github.workspace }}:rw"],
- "env": {
- "GH_AW_MCP_LOG_DIR": "\${GH_AW_MCP_LOG_DIR}",
- "GH_AW_SAFE_OUTPUTS": "\${GH_AW_SAFE_OUTPUTS}",
- "GH_AW_SAFE_OUTPUTS_CONFIG_PATH": "\${GH_AW_SAFE_OUTPUTS_CONFIG_PATH}",
- "GH_AW_SAFE_OUTPUTS_TOOLS_PATH": "\${GH_AW_SAFE_OUTPUTS_TOOLS_PATH}",
- "GH_AW_ASSETS_BRANCH": "\${GH_AW_ASSETS_BRANCH}",
- "GH_AW_ASSETS_MAX_SIZE_KB": "\${GH_AW_ASSETS_MAX_SIZE_KB}",
- "GH_AW_ASSETS_ALLOWED_EXTS": "\${GH_AW_ASSETS_ALLOWED_EXTS}",
- "GITHUB_REPOSITORY": "\${GITHUB_REPOSITORY}",
- "GITHUB_SERVER_URL": "\${GITHUB_SERVER_URL}",
- "GITHUB_SHA": "\${GITHUB_SHA}",
- "GITHUB_WORKSPACE": "\${GITHUB_WORKSPACE}",
- "DEFAULT_BRANCH": "\${DEFAULT_BRANCH}",
- "GITHUB_RUN_ID": "\${GITHUB_RUN_ID}",
- "GITHUB_RUN_NUMBER": "\${GITHUB_RUN_NUMBER}",
- "GITHUB_RUN_ATTEMPT": "\${GITHUB_RUN_ATTEMPT}",
- "GITHUB_JOB": "\${GITHUB_JOB}",
- "GITHUB_ACTION": "\${GITHUB_ACTION}",
- "GITHUB_EVENT_NAME": "\${GITHUB_EVENT_NAME}",
- "GITHUB_EVENT_PATH": "\${GITHUB_EVENT_PATH}",
- "GITHUB_ACTOR": "\${GITHUB_ACTOR}",
- "GITHUB_ACTOR_ID": "\${GITHUB_ACTOR_ID}",
- "GITHUB_TRIGGERING_ACTOR": "\${GITHUB_TRIGGERING_ACTOR}",
- "GITHUB_WORKFLOW": "\${GITHUB_WORKFLOW}",
- "GITHUB_WORKFLOW_REF": "\${GITHUB_WORKFLOW_REF}",
- "GITHUB_WORKFLOW_SHA": "\${GITHUB_WORKFLOW_SHA}",
- "GITHUB_REF": "\${GITHUB_REF}",
- "GITHUB_REF_NAME": "\${GITHUB_REF_NAME}",
- "GITHUB_REF_TYPE": "\${GITHUB_REF_TYPE}",
- "GITHUB_HEAD_REF": "\${GITHUB_HEAD_REF}",
- "GITHUB_BASE_REF": "\${GITHUB_BASE_REF}"
+ "type": "http",
+ "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT",
+ "headers": {
+ "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}"
}
}
},
@@ -445,7 +440,7 @@ jobs:
engine_name: "GitHub Copilot CLI",
model: process.env.GH_AW_MODEL_AGENT_COPILOT || "",
version: "",
- agent_version: "0.0.388",
+ agent_version: "0.0.389",
workflow_name: "Code Simplifier",
experimental: false,
supports_tools_allowlist: true,
@@ -463,7 +458,7 @@ jobs:
allowed_domains: [],
firewall_enabled: true,
awf_version: "v0.10.0",
- awmg_version: "v0.0.74",
+ awmg_version: "v0.0.76",
steps: {
firewall: "squid"
},
@@ -1379,19 +1374,7 @@ jobs:
env:
COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
- name: Install GitHub Copilot CLI
- run: |
- # Download official Copilot CLI installer script
- curl -fsSL https://raw.githubusercontent.com/github/copilot-cli/main/install.sh -o /tmp/copilot-install.sh
-
- # Execute the installer with the specified version
- # Pass VERSION directly to sudo to ensure it's available to the installer script
- sudo VERSION=0.0.388 bash /tmp/copilot-install.sh
-
- # Cleanup
- rm -f /tmp/copilot-install.sh
-
- # Verify installation
- copilot --version
+ run: /opt/gh-aw/actions/install_copilot_cli.sh 0.0.389
- name: Execute GitHub Copilot CLI
id: agentic_execution
# Copilot CLI tool arguments (sorted):
From deaed167cb7f7a9557e126ba2f49640ccd7d357d Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 22 Jan 2026 17:54:18 +0000
Subject: [PATCH 3/5] Update campaign documentation: fix specs and rename flow
to lifecycle
- Updated Campaign Spec page with accurate field descriptions
- Fixed discovery-repos/discovery-orgs vs allowed-repos/allowed-orgs confusion
- Added required fields section for discovery configuration
- Updated KPI section with all required fields (baseline, target, time-window-days)
- Added proper YAML frontmatter delimiters to examples
- Removed duplicate governance section
- Renamed "Campaign Flow & Lifecycle" to "Campaign Lifecycle"
- Updated all references from /flow/ to /lifecycle/
Co-authored-by: mnkiefer <8320933+mnkiefer@users.noreply.github.com>
---
docs/src/content/docs/examples/campaigns.md | 2 +-
.../docs/guides/campaigns/getting-started.md | 2 +-
.../content/docs/guides/campaigns/index.mdx | 4 +-
.../campaigns/{flow.md => lifecycle.md} | 2 +-
.../content/docs/guides/campaigns/specs.md | 106 ++++++++++++------
5 files changed, 79 insertions(+), 37 deletions(-)
rename docs/src/content/docs/guides/campaigns/{flow.md => lifecycle.md} (99%)
diff --git a/docs/src/content/docs/examples/campaigns.md b/docs/src/content/docs/examples/campaigns.md
index 116d41f6ef..77ef5c46fd 100644
--- a/docs/src/content/docs/examples/campaigns.md
+++ b/docs/src/content/docs/examples/campaigns.md
@@ -95,7 +95,7 @@ Workers are stored alongside regular workflows, not in campaign-specific folders
## Learn More
- [Campaign Guides](/gh-aw/guides/campaigns/) - Campaign setup and configuration
-- [Flow & lifecycle](/gh-aw/guides/campaigns/flow/) - How the orchestrator runs
+- [Campaign lifecycle](/gh-aw/guides/campaigns/lifecycle/) - How the orchestrator runs
- [Safe Outputs](/gh-aw/reference/safe-outputs/) - dispatch_workflow configuration
## Pattern Analysis
diff --git a/docs/src/content/docs/guides/campaigns/getting-started.md b/docs/src/content/docs/guides/campaigns/getting-started.md
index 3a063a5b91..dd3a8576b0 100644
--- a/docs/src/content/docs/guides/campaigns/getting-started.md
+++ b/docs/src/content/docs/guides/campaigns/getting-started.md
@@ -42,7 +42,7 @@ The orchestrator runs on a schedule (daily by default) and will:
- sync issues/PRs into the Project
- post a Project status update each run
-For details, see [Flow & lifecycle](/gh-aw/guides/campaigns/flow/).
+For details, see [Campaign lifecycle](/gh-aw/guides/campaigns/lifecycle/).
## Keep it simple (best practices)
diff --git a/docs/src/content/docs/guides/campaigns/index.mdx b/docs/src/content/docs/guides/campaigns/index.mdx
index 9aebf71a37..38ff18c905 100644
--- a/docs/src/content/docs/guides/campaigns/index.mdx
+++ b/docs/src/content/docs/guides/campaigns/index.mdx
@@ -81,11 +81,11 @@ governance:
The spec is validated and compiled to create the campaign's orchestrator workflow (`.campaign.lock.yml`) that GitHub Actions can execute.
Think of it like compiling code: you write a human-friendly campaign definition, and the compiler produces a hardened, machine-executable workflow.
-**What happens next**: Once merged, the campaign orchestrator runs on schedule (daily by default) to [dispatch worker workflows, discover items, update the Project board, and report status](/gh-aw/guides/campaigns/flow/). The campaign coordinates all work toward your objective while respecting governance limits.
+**What happens next**: Once merged, the campaign orchestrator runs on schedule (daily by default) to [dispatch worker workflows, discover items, update the Project board, and report status](/gh-aw/guides/campaigns/lifecycle/). The campaign coordinates all work toward your objective while respecting governance limits.
## Next steps
- [Getting started](/gh-aw/guides/campaigns/getting-started/) – create your first campaign
- [Campaign specs](/gh-aw/guides/campaigns/specs/) – fields you can configure
-- [Flow & Lifecycle](/gh-aw/guides/campaigns/flow/) – what the orchestrator does each run
+- [Campaign Lifecycle](/gh-aw/guides/campaigns/lifecycle/) – what the orchestrator does each run
- [CLI commands](/gh-aw/guides/campaigns/cli-commands/) – inspect and validate campaigns
diff --git a/docs/src/content/docs/guides/campaigns/flow.md b/docs/src/content/docs/guides/campaigns/lifecycle.md
similarity index 99%
rename from docs/src/content/docs/guides/campaigns/flow.md
rename to docs/src/content/docs/guides/campaigns/lifecycle.md
index 8b3e3feb09..dd76b2c3f8 100644
--- a/docs/src/content/docs/guides/campaigns/flow.md
+++ b/docs/src/content/docs/guides/campaigns/lifecycle.md
@@ -1,5 +1,5 @@
---
-title: Campaign Flow & Lifecycle
+title: Campaign Lifecycle
description: What happens when the campaign orchestrator runs, and how to pause or finish a campaign.
banner:
content: 'Do not use. Campaigns are still incomplete and may produce unreliable or unintended results.'
diff --git a/docs/src/content/docs/guides/campaigns/specs.md b/docs/src/content/docs/guides/campaigns/specs.md
index 9c8881a5e3..5d18f22313 100644
--- a/docs/src/content/docs/guides/campaigns/specs.md
+++ b/docs/src/content/docs/guides/campaigns/specs.md
@@ -13,6 +13,7 @@ The campaign spec is a reviewable configuration file that:
- names the campaign
- points to a GitHub Project for tracking
+- defines where to discover worker-created items
- lists the workflows the orchestrator should dispatch
- defines goals (objective + KPIs)
@@ -22,6 +23,7 @@ Most users should create specs via the [Getting started flow](/gh-aw/guides/camp
```yaml
# .github/workflows/framework-upgrade.campaign.md
+---
id: framework-upgrade
version: "v1"
name: "Framework Upgrade"
@@ -30,16 +32,22 @@ description: "Move services to Framework vNext"
project-url: "https://github.com/orgs/ORG/projects/1"
tracker-label: "campaign:framework-upgrade"
-# Optional: Custom GitHub token for Projects v2 operations
-# project-github-token: "${{ secrets.GH_AW_PROJECT_GITHUB_TOKEN }}"
-
-## Optional: Repositories this campaign can operate on
-## If omitted, defaults to the repository where the campaign spec lives.
-allowed-repos:
+# Discovery: Where to find worker-created issues/PRs
+discovery-repos:
- "myorg/service-a"
- "myorg/service-b"
+# Or use discovery-orgs for organization-wide discovery:
+# discovery-orgs:
+# - "myorg"
-# Optional: Organizations this campaign can operate on
+# Optional: Custom GitHub token for Projects v2 operations
+# project-github-token: "${{ secrets.GH_AW_PROJECT_GITHUB_TOKEN }}"
+
+# Optional: Restrict which repos this campaign can operate on
+# If omitted, defaults to the repository where the campaign spec lives.
+# allowed-repos:
+# - "myorg/service-a"
+# - "myorg/service-b"
# allowed-orgs:
# - "myorg"
@@ -48,12 +56,19 @@ kpis:
- id: services_upgraded
name: "Services upgraded"
priority: primary
- direction: "increase"
+ unit: count
+ baseline: 0
target: 50
+ time-window-days: 30
+ direction: "increase"
- id: incidents
name: "Incidents caused"
- direction: "decrease"
+ priority: supporting
+ unit: count
+ baseline: 5
target: 0
+ time-window-days: 30
+ direction: "decrease"
workflows:
- framework-upgrade
@@ -61,29 +76,48 @@ workflows:
state: "active"
owners:
- "platform-team"
+---
```
## Core fields (what they do)
### Required
-- `id`: stable identifier used for file naming and reporting.
-- `name`: human-friendly name.
-- `project-url`: GitHub Project used for tracking.
-- `objective`: one sentence describing what “done” means.
-- `kpis`: measures used in status updates.
-- `workflows`: workflow IDs the orchestrator can dispatch (via `workflow_dispatch`).
-- `state`: lifecycle state, typically `active`.
+- `id`: Stable identifier used for file naming and reporting (lowercase letters, digits, hyphens only).
+- `name`: Human-friendly name for the campaign.
+- `project-url`: GitHub Project URL used for tracking.
+
+### Required for discovery
+
+When your campaign uses `workflows` or `tracker-label`, you must specify where to discover worker-created items:
+
+- `discovery-repos`: List of repositories (in `owner/repo` format) where worker workflows create issues/PRs.
+- `discovery-orgs`: List of GitHub organizations where worker workflows operate (searches all repos in those orgs).
+
+At least one of `discovery-repos` or `discovery-orgs` is required when using workflows or tracker labels.
+
+### Commonly used
+
+- `objective`: One sentence describing what success means for this campaign.
+- `kpis`: List of 1-3 KPIs used to measure progress toward the objective.
+- `workflows`: Workflow IDs the orchestrator can dispatch via `workflow_dispatch`.
+- `tracker-label`: Label used to discover worker-created issues/PRs (commonly `campaign:`).
+- `state`: Lifecycle state (`planned`, `active`, `paused`, `completed`, or `archived`).
### Optional
-- `tracker-label`: label used to help discovery across runs (commonly `campaign:`).
-- `allowed-repos`: repositories the campaign can operate on (defaults to the repo containing the spec).
-- `allowed-orgs`: organizations the campaign can operate on.
-- `project-github-token`: token to use for Projects operations when `GITHUB_TOKEN` isn’t enough.
+- `allowed-repos`: Repositories this campaign can operate on (defaults to the repo containing the spec).
+- `allowed-orgs`: Organizations this campaign can operate on.
+- `project-github-token`: Token expression for Projects v2 operations (e.g., `${{ secrets.GH_AW_PROJECT_GITHUB_TOKEN }}`).
+- `description`: Brief description of the campaign.
+- `version`: Spec version (defaults to `v1`).
+- `owners`: Primary human owners for this campaign.
+- `governance`: Pacing and opt-out policies (see Governance section below).
> [!IMPORTANT]
-> Use `priority: primary` (not `primary: true`) to mark your primary KPI.
+> - Use `priority: primary` (not `primary: true`) to mark your primary KPI.
+> - The `discovery-*` fields control WHERE to search for worker outputs.
+> - The `allowed-*` fields control WHERE the campaign can operate.
## Strategic goals (objective + KPIs)
@@ -94,13 +128,25 @@ Use `objective` and `kpis` to define what “done” means and how progress shou
## KPIs (recommended shape)
-Keep KPIs small and crisp:
+Each KPI requires these fields:
-- Use 1 primary KPI + a few supporting KPIs.
-- Use `direction: increase|decrease|maintain` to describe the desired trend.
-- Use `target` when there is a clear threshold.
+- `name`: Human-readable KPI name.
+- `baseline`: Starting value.
+- `target`: Goal value.
+- `time-window-days`: Rolling window for measurement (e.g., 7, 14, 30 days).
-If you define `kpis`, also define `objective` (and vice versa). It keeps the spec reviewable and makes reports consistent.
+Optional fields:
+
+- `id`: Stable identifier (lowercase letters, digits, hyphens).
+- `priority`: `primary` or `supporting` (exactly one KPI should be primary).
+- `unit`: Unit of measurement (e.g., `count`, `percent`, `days`).
+- `direction`: `increase` or `decrease` (describes improvement direction).
+- `source`: Signal source (`ci`, `pull_requests`, `code_security`, or `custom`).
+
+Keep KPIs small and crisp:
+
+- Use 1 primary KPI + up to 2 supporting KPIs (maximum 3 total).
+- When you define `kpis`, also define `objective` (and vice versa).
## Unified tracking (GitHub Project)
@@ -115,11 +161,7 @@ Project updates are applied by the orchestrator using safe outputs; see [Update
Use `workflows` to list the dispatchable workflows (“workers”) the orchestrator can trigger via `workflow_dispatch`.
-For worker requirements and dispatch behavior, see [Dispatching worker workflows](/gh-aw/guides/campaigns/flow/#dispatching-worker-workflows).
-
-## Governance (pacing)
-
-Use `governance` to cap how much the orchestrator updates per run.
+For worker requirements and dispatch behavior, see [Dispatching worker workflows](/gh-aw/guides/campaigns/lifecycle/#dispatching-worker-workflows).
## Governance (pacing & safety)
@@ -147,5 +189,5 @@ governance:
## Next
-- See [Flow & lifecycle](/gh-aw/guides/campaigns/flow/) for what happens each run.
+- See [Campaign lifecycle](/gh-aw/guides/campaigns/lifecycle/) for what happens each run.
- See [CLI commands](/gh-aw/guides/campaigns/cli-commands/) to validate and inspect campaigns.
From 71c135e722d0bcff0c26ebb8bd6d763a6a8b9923 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 22 Jan 2026 17:55:10 +0000
Subject: [PATCH 4/5] Update navigation config to reference Campaign Lifecycle
page
---
docs/astro.config.mjs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs
index 645ee9bb8b..05097a202c 100644
--- a/docs/astro.config.mjs
+++ b/docs/astro.config.mjs
@@ -193,7 +193,7 @@ export default defineConfig({
{ label: 'About campaigns', link: '/guides/campaigns/' },
{ label: 'Getting Started', link: '/guides/campaigns/getting-started/' },
{ label: 'Campaign Specs', link: '/guides/campaigns/specs/' },
- { label: 'Flow & Lifecycle', link: '/guides/campaigns/flow/' },
+ { label: 'Campaign Lifecycle', link: '/guides/campaigns/lifecycle/' },
{ label: 'CLI Commands', link: '/guides/campaigns/cli-commands/' },
],
},
From b2d0c90deedc9f4d3c5ae219a0cf7046c7b8108a Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 22 Jan 2026 17:56:51 +0000
Subject: [PATCH 5/5] Address code review: rename "Minimal spec" to "Complete
spec example"
The example includes all commonly-used fields and represents a complete, working
campaign configuration rather than a truly minimal one. This is more useful for
users getting started.
Co-authored-by: mnkiefer <8320933+mnkiefer@users.noreply.github.com>
---
docs/src/content/docs/guides/campaigns/specs.md | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/docs/src/content/docs/guides/campaigns/specs.md b/docs/src/content/docs/guides/campaigns/specs.md
index 5d18f22313..54747dd06d 100644
--- a/docs/src/content/docs/guides/campaigns/specs.md
+++ b/docs/src/content/docs/guides/campaigns/specs.md
@@ -19,7 +19,9 @@ The campaign spec is a reviewable configuration file that:
Most users should create specs via the [Getting started flow](/gh-aw/guides/campaigns/getting-started/).
-## Minimal spec
+## Complete spec example
+
+This example shows a complete, working campaign spec with all commonly-used fields:
```yaml
# .github/workflows/framework-upgrade.campaign.md