From 325afdc5acfebd85a0456bddaf9ef479ae504b2c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 3 Jan 2026 05:03:45 +0000 Subject: [PATCH 1/3] Initial plan From 6181a16008abbdd29073d8a57151a98ec5e0c977 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 3 Jan 2026 05:17:05 +0000 Subject: [PATCH 2/3] Make tracker-label optional in campaign specs - Remove mandatory validation for tracker-label - Keep format validation when tracker-label is provided - Update schema description to emphasize optional nature - Update documentation to clarify project board is canonical source - All campaign tests pass Co-authored-by: mnkiefer <8320933+mnkiefer@users.noreply.github.com> --- docs/src/content/docs/guides/campaigns.md | 4 ++-- docs/src/content/docs/guides/campaigns/specs.md | 14 +++++++------- pkg/campaign/schemas/campaign_spec_schema.json | 2 +- pkg/campaign/spec.go | 6 ++++-- pkg/campaign/validation.go | 6 +++--- pkg/campaign/validation_test.go | 17 ++++------------- 6 files changed, 21 insertions(+), 28 deletions(-) diff --git a/docs/src/content/docs/guides/campaigns.md b/docs/src/content/docs/guides/campaigns.md index 64c211091f2..1625c2be1fb 100644 --- a/docs/src/content/docs/guides/campaigns.md +++ b/docs/src/content/docs/guides/campaigns.md @@ -23,13 +23,13 @@ Use a campaign when you need to manage an initiative—scope, progress, and outc A campaign gives you a dashboard (GitHub Project), a coordinating orchestrator workflow that keeps it in sync, and a spec file that captures the objective, KPIs, governance, and wiring. In the repo, the spec lives at `.github/workflows/.campaign.md` and is the source of truth. -When the spec includes orchestration, the tooling generates an orchestrator workflow and compiles it into a locked `.campaign.lock.yml` workflow. The spec defines what success means (objective), how progress is measured (KPIs, with exactly one marked primary), where progress is shown (GitHub Project URL), what participates (workflows), and what is tracked (the label applied to issues and pull requests, commonly `campaign:`). +When the spec includes orchestration, the tooling generates an orchestrator workflow and compiles it into a locked `.campaign.lock.yml` workflow. The spec defines what success means (objective), how progress is measured (KPIs, with exactly one marked primary), where progress is shown (GitHub Project URL), and what participates (workflows). Optionally, you can add a tracker label (commonly `campaign:`) to help discover issues and PRs, but the project board remains the canonical source of campaign membership. **Note:** During compilation, a `.campaign.g.md` file is generated locally as a debug artifact to help developers understand the orchestrator structure, but this file is not committed to git—only the source `.campaign.md` and compiled `.campaign.lock.yml` are tracked. ## How it works -Most campaigns follow the same shape. The GitHub Project is the human-facing status view. The orchestrator workflow discovers tracked items from the workers and updates the Project. Worker workflows do the real work, such as opening pull requests or applying fixes but they stay campaign-agnostic. If you want cross-run discovery of worker-created assets, workers can include a `tracker-id` marker which the orchestrator can search for. +Most campaigns follow the same shape. The GitHub Project is the human-facing status view and the canonical source of campaign membership. The orchestrator workflow discovers tracked items from the workers and updates the Project. Worker workflows do the real work, such as opening pull requests or applying fixes but they stay campaign-agnostic. If you want cross-run discovery of worker-created assets, workers can include a `tracker-id` marker which the orchestrator can search for. Optionally, you can configure a tracker label (e.g., `campaign:`) as an ingestion hint to help discover issues and PRs created by workers. ## Memory diff --git a/docs/src/content/docs/guides/campaigns/specs.md b/docs/src/content/docs/guides/campaigns/specs.md index 9e7568a67d9..142cb63baf5 100644 --- a/docs/src/content/docs/guides/campaigns/specs.md +++ b/docs/src/content/docs/guides/campaigns/specs.md @@ -7,12 +7,12 @@ Campaigns are defined as Markdown files under `.github/workflows/` with a `.camp ## What a campaign is (in gh-aw) -In GitHub Agentic Workflows, a campaign is not “a special kind of workflow.” The `.campaign.md` file is a specification: a reviewable contract that wires together agentic workflows around a shared initiative (a tracker label, a GitHub Project dashboard, and optional durable state). +In GitHub Agentic Workflows, a campaign is not “a special kind of workflow.” The `.campaign.md` file is a specification: a reviewable contract that wires together agentic workflows around a shared initiative (a GitHub Project dashboard as the canonical source of membership, optional tracker label for ingestion, and optional durable state). In a typical setup: - Worker workflows do the work. They run an agent and use safe-outputs (for example `create_pull_request`, `add_comment`, or `update_issues`) for write operations. -- A generated orchestrator workflow keeps the campaign coherent over time. It discovers items tagged with your tracker label, updates the Project board, and produces ongoing progress reporting. +- A generated orchestrator workflow keeps the campaign coherent over time. It discovers items from the project board (optionally using tracker labels), updates the Project board, and produces ongoing progress reporting. - Repo-memory (optional) makes the campaign repeatable. It lets you store a cursor checkpoint and append-only metrics snapshots so each run can pick up where the last one left off. ### Mental model @@ -23,7 +23,7 @@ flowchart TB compile["fa:fa-cogs gh aw compile"] debug["fa:fa-file .campaign.g.md
debug artifact
(not tracked)
"] lock["fa:fa-lock .campaign.lock.yml
compiled workflow
(tracked in git)
"] - orchestrator["fa:fa-sitemap Orchestrator workflow
discovers items via tracker-label
updates Project dashboard
reads/writes repo-memory
"] + orchestrator["fa:fa-sitemap Orchestrator workflow
discovers items from project
updates Project dashboard
reads/writes repo-memory
"] worker1["fa:fa-robot Worker workflow
agent + safe-outputs"] worker2["fa:fa-robot Worker workflow
agent + safe-outputs"] project["fa:fa-table GitHub Project board
campaign dashboard"] @@ -35,8 +35,8 @@ flowchart TB lock --> orchestrator orchestrator -->|triggers/coordinates| worker1 orchestrator -->|triggers/coordinates| worker2 - worker1 -->|creates/updates
Issues/PRs with
tracker-label| project - worker2 -->|creates/updates
Issues/PRs with
tracker-label| project + worker1 -->|creates/updates
Issues/PRs
(optional tracker-label)| project + worker2 -->|creates/updates
Issues/PRs
(optional tracker-label)| project orchestrator -.->|reads/writes| memory project -.->|dashboard view| orchestrator @@ -91,8 +91,8 @@ owners: ## Core fields (what they do) - `id`: stable identifier used for file naming, reporting, and (if used) repo-memory paths. -- `project-url`: the GitHub Project that acts as the campaign dashboard. -- `tracker-label`: the label applied to issues and pull requests that belong to the campaign (commonly `campaign:`). This is the key that lets the orchestrator discover work across runs. +- `project-url`: the GitHub Project that acts as the campaign dashboard and canonical source of campaign membership. +- `tracker-label` (optional): an ingestion hint label that helps discover issues and pull requests created by workers (commonly `campaign:`). When provided, the orchestrator can discover work across runs. The project board remains the canonical source of truth. - `objective`: a single sentence describing what “done” means. - `kpis`: the measures you use to report progress (exactly one should be marked `primary`). - `workflows`: the participating workflow IDs. These refer to workflows in the repo (commonly `.github/workflows/.md`), and they can be scheduled, event-driven, or long-running. diff --git a/pkg/campaign/schemas/campaign_spec_schema.json b/pkg/campaign/schemas/campaign_spec_schema.json index 1064a083aa3..b920180b2b3 100644 --- a/pkg/campaign/schemas/campaign_spec_schema.json +++ b/pkg/campaign/schemas/campaign_spec_schema.json @@ -150,7 +150,7 @@ }, "tracker-label": { "type": "string", - "description": "Label used to associate issues/PRs with this campaign (e.g., campaign:incident-response)", + "description": "Optional label used as an ingestion hint to associate issues/PRs with this campaign (e.g., campaign:incident-response). The project board is the canonical source of campaign membership.", "pattern": "^[^:]+:.+$", "minLength": 1 }, diff --git a/pkg/campaign/spec.go b/pkg/campaign/spec.go index 2372b0a9feb..344005660af 100644 --- a/pkg/campaign/spec.go +++ b/pkg/campaign/spec.go @@ -62,8 +62,10 @@ type CampaignSpec struct { // RiskLevel is an optional free-form field (e.g. low/medium/high). RiskLevel string `yaml:"risk-level,omitempty" json:"risk_level,omitempty" console:"header:Risk Level,omitempty"` - // TrackerLabel describes the label used to associate issues/PRs with - // this campaign (for example: campaign:incident-response). + // TrackerLabel is an optional label used as an ingestion hint to help + // discover and associate issues/PRs with this campaign (for example: + // campaign:incident-response). The GitHub Project board is the canonical + // source of campaign membership. TrackerLabel string `yaml:"tracker-label,omitempty" json:"tracker_label,omitempty" console:"header:Tracker Label,omitempty"` // State describes the lifecycle stage of the campaign definition. diff --git a/pkg/campaign/validation.go b/pkg/campaign/validation.go index 9a14a2093b2..6e18d4a6b1d 100644 --- a/pkg/campaign/validation.go +++ b/pkg/campaign/validation.go @@ -77,9 +77,9 @@ func ValidateSpec(spec *CampaignSpec) []string { } } - if strings.TrimSpace(spec.TrackerLabel) == "" { - problems = append(problems, "tracker-label should be set to link issues and PRs to this campaign") - } else if !strings.Contains(spec.TrackerLabel, ":") { + // TrackerLabel is optional - only validate format when provided. + // The campaign project board is the canonical source of campaign membership. + if strings.TrimSpace(spec.TrackerLabel) != "" && !strings.Contains(spec.TrackerLabel, ":") { problems = append(problems, "tracker-label should follow a namespaced pattern (for example: campaign:security-q1-2025)") } diff --git a/pkg/campaign/validation_test.go b/pkg/campaign/validation_test.go index dbd5b542db2..de26ac73545 100644 --- a/pkg/campaign/validation_test.go +++ b/pkg/campaign/validation_test.go @@ -124,6 +124,7 @@ func TestValidateSpec_MissingWorkflows(t *testing.T) { } func TestValidateSpec_MissingTrackerLabel(t *testing.T) { + // tracker-label is now optional - spec should pass validation without it spec := &CampaignSpec{ ID: "test-campaign", Name: "Test Campaign", @@ -132,19 +133,9 @@ func TestValidateSpec_MissingTrackerLabel(t *testing.T) { } problems := ValidateSpec(spec) - if len(problems) == 0 { - t.Fatal("Expected validation problems for missing tracker label") - } - - found := false - for _, p := range problems { - if strings.Contains(p, "tracker-label should be set") { - found = true - break - } - } - if !found { - t.Errorf("Expected tracker label validation problem, got: %v", problems) + // Should have no problems since tracker-label is optional + if len(problems) != 0 { + t.Errorf("Expected no validation problems for missing tracker label (it's optional), got: %v", problems) } } From ce49955c71483e826dce6b84226c6a48027b8a0a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 3 Jan 2026 05:33:50 +0000 Subject: [PATCH 3/3] Fix formatting - trim trailing whitespace in comments Co-authored-by: mnkiefer <8320933+mnkiefer@users.noreply.github.com> --- pkg/campaign/spec.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/campaign/spec.go b/pkg/campaign/spec.go index 344005660af..26f1ddee0c4 100644 --- a/pkg/campaign/spec.go +++ b/pkg/campaign/spec.go @@ -63,8 +63,8 @@ type CampaignSpec struct { RiskLevel string `yaml:"risk-level,omitempty" json:"risk_level,omitempty" console:"header:Risk Level,omitempty"` // TrackerLabel is an optional label used as an ingestion hint to help - // discover and associate issues/PRs with this campaign (for example: - // campaign:incident-response). The GitHub Project board is the canonical + // discover and associate issues/PRs with this campaign (for example: + // campaign:incident-response). The GitHub Project board is the canonical // source of campaign membership. TrackerLabel string `yaml:"tracker-label,omitempty" json:"tracker_label,omitempty" console:"header:Tracker Label,omitempty"`