Skip to content

fix(gh-aw): bind squad:{agent} labels to each issue's own plan row - #1863

Merged
bradygaster merged 1 commit into
devfrom
squad/1859-agent-binding-correspondence
Aug 24, 2026
Merged

fix(gh-aw): bind squad:{agent} labels to each issue's own plan row#1863
bradygaster merged 1 commit into
devfrom
squad/1859-agent-binding-correspondence

Conversation

@bradygaster

Copy link
Copy Markdown
Owner

Closes #1859
Closes #1860

The gap these two defects exposed

squad plan activate minted a certified but wrong label, then reported labels it never applied. Both slipped past every guard the workflow already had, because those guards verify membership and these were failures of correspondence.

  • Team Guard TG-2 certifies a vocabulary — the set of names eligible to become a squad:{agent} label.
  • Validation Check 10 asserts every plan Agent value belongs to that set.
  • Neither asks whether the label on task N is the agent the plan assigned to task N.

A label can be simultaneously certified and wrong.

Measured evidence

From the first full E4 run on octodemo/aspiregregator-squad-e2e (run 32778953402 — TG-2 green, Check 10 green, 12/12 values certified):

Issue Observed
#1859 Task 6 is assigned to McManus. Issue #17 was created with squad:kint — the owner of its parent epic. 11 of 12 bindings were correct; the twelfth was invisible because kint is a perfectly valid roster name.
#1860 The summary reported — squad:kint on epic #6 and squad:kint / squad:mcmanus on epic #7. Both issues carry only squad. The multi-owner epic was also never recorded under Non-roster agent values, so a run that silently dropped two bindings read as clean.

Root cause: ambiguity, not disobedience

Both create-issue steps read Labels: squad, squad:{agent} and never bound {agent} to a source — unlike the plan implement path at L928, which does qualify it ("{owner} is the Owner lowercased"). Reading tasks grouped beneath an epic, inheriting the epic's agent is the natural resolution of an unbound placeholder.

Changes to workflows/squad.md (17 lines)

  • Task rule now names this task's own Agent cell, keyed by the plan row whose # matches, and prohibits epic inheritance and carry-forward by name — a positive instruction alone was already present and did not prevent gh-aw: plan activate binds a task's squad:{agent} label from its epic owner, not the task's Agent cell #1859.
  • Epic rule now derives from the epic's own tasks: exactly one distinct agent mints that label; two or more resolve to bare squad plus a Non-roster agent values entry. An epic has no Agent column of its own, so any other reading is a guess.
  • Label Pre-flight gains two steps: correspondence (membership across the run is not evidence — verify per issue) and reporting (name a label only after that issue's create-issue call returned carrying it; the Non-roster agent values heading is required whenever a value did not become a label).

Tests

test/gh-aw-agent-binding-correspondence.test.ts — 7 tests, all passing.

These assert prompt text, which is a weaker instrument than the executed-shell assertions in gh-aw-activate-roster-binding.test.ts. Deterministic post-activation enforcement is #1801 and remains open. What these buy is that the specific ambiguity behind #1859 cannot silently return.

They are mutation-checked: restoring the task rule to its previous wording fails 2 of the 7. A prose assertion that cannot fail is decoration.

Validation

  • npx vitest run test/gh-aw-agent-binding-correspondence.test.ts — 7/7
  • gh-aw-quality, template-sync, gh-aw-activate-roster-binding, gh-aw-plan-lifecycle404 passing, no regressions
  • npm run lint clean; eslint clean
  • No changeset: no files under packages/*/src/

Not addressed here

Activation minted a certified-but-wrong label and then reported labels it
never applied. Both survived every existing guard because the guards check
membership, and these were failures of correspondence.

Team Guard TG-2 certifies the vocabulary of names that may become a
`squad:{agent}` label; validation Check 10 asserts every plan `Agent` is a
member of it. Neither asks whether the label on task N is the agent the plan
assigned to task N. A label can be simultaneously certified and wrong.

Measured on octodemo/aspiregregator-squad-e2e (run 32778953402, TG-2 green,
Check 10 green, 12/12 values certified):

  #1859  Task 6 is assigned to McManus; issue #17 was created with squad:kint,
         the owner of its parent epic. 11 of 12 bindings were correct, and the
         twelfth was invisible because `kint` is a valid roster name.
  #1860  The summary reported `squad:kint` on epic #6 and
         `squad:kint / squad:mcmanus` on epic #7. Both carry only `squad`.
         The multi-owner epic was also never recorded under `Non-roster agent
         values`, so a run that dropped two bindings read as clean.

The cause was ambiguity, not disobedience. Both create-issue steps said
`Labels: squad, squad:{agent}` without ever binding `{agent}` to a source --
unlike the `plan implement` path at L928, which qualifies it. Reading tasks
grouped under an epic, inheriting the epic's agent is the natural resolution.

  - Task rule now names the task's own `Agent` cell, keyed by the plan row
    whose `#` matches, and prohibits epic inheritance and carry-forward by name.
  - Epic rule now derives from the epic's own tasks: one distinct agent mints
    that label, two or more resolve to bare `squad` plus a `Non-roster agent
    values` entry. An epic has no Agent column, so any other reading is a guess.
  - Label Pre-flight gains a correspondence step stating that membership across
    the run is not evidence, and a reporting step requiring the summary to name
    a label only after that issue's create-issue call returned carrying it.

Tests assert prompt text, which is weaker than the executed-shell assertions in
gh-aw-activate-roster-binding.test.ts; deterministic post-activation enforcement
remains #1801. They are mutation-checked: restoring either rule to its previous
wording fails two of them.

Closes #1859
Closes #1860

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 20afe6d2-444e-414e-8a39-e67ab67ca6df
Copilot AI lite review requested due to automatic review settings August 24, 2026 22:04
@github-actions

Copy link
Copy Markdown
Contributor

🟡 Impact Analysis — PR #1863

Risk tier: 🟡 MEDIUM

📊 Summary

Metric Count
Files changed 2
Files added 1
Files modified 1
Files deleted 0
Modules touched 2

🎯 Risk Factors

  • 2 files changed (≤5 → LOW)
  • 2 modules touched (2-4 → MEDIUM)

📦 Modules Affected

root (1 file)
  • workflows/squad.md
tests (1 file)
  • test/gh-aw-agent-binding-correspondence.test.ts

This report is generated automatically for every PR. See #733 for details.

@github-actions

github-actions Bot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

🛫 PR Readiness Check

ℹ️ This comment updates on each push. Last checked: commit 526104b

PR Scope: 🔧 Infrastructure

⚠️ 2 item(s) to address before review

Status Check Details
Single commit 1 commit — clean history
Not in draft Ready for review
Branch up to date Up to date with dev
Copilot review No Copilot review yet — it may still be processing
Changeset present No source files changed — changeset not required
Scope clean No .squad/ or docs/proposals/ files
No merge conflicts No merge conflicts
Copilot threads resolved No Copilot review threads
CI passing 1 check(s) still running

Files Changed (2 files, +199 −2)

File +/−
test/gh-aw-agent-binding-correspondence.test.ts +184 −0
workflows/squad.md +15 −2

Total: +199 −2


This check runs automatically on every push. Fix any ❌ items and push again.
See CONTRIBUTING.md and PR Requirements for details.

@bradygaster
bradygaster merged commit bdd6a92 into dev Aug 24, 2026
18 checks passed

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Normalize and trim epic agent values before distinct-counting and label generation.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Updates gh-aw activation rules so issue labels correspond to their assigned plan agents and reporting reflects labels actually applied.

Changes:

  • Adds per-task and per-epic label binding rules.
  • Handles multi-owner epics and non-roster values.
  • Adds seven prompt-contract tests.
File summaries
File Description
workflows/squad.md Updates activation label binding and reporting rules.
test/gh-aw-agent-binding-correspondence.test.ts Tests the new correspondence requirements.
Review details

Suppressed comments (6)

workflows/squad.md:1338

  • This reporting condition cannot be satisfied for idempotent reruns: Step 2b says to deduplicate by an existing issue title, so no create-issue call returns for that issue. The summary would then omit labels that the existing issue actually carries. Permit either the create response or an existing-issue lookup to return the issue's actual labels before reporting them.
   `squad:{agent}` label for an issue only after that issue's `create-issue` call returned
   successfully carrying it. Never state a label that was skipped, omitted, deferred, or

workflows/squad.md:1376

  • The accepted Agent value @copilot is allowed by the pre-flight rule, but interpolating and lowercasing it here yields squad:@copilot. The repository's routing and label-sync contracts use squad:copilot (.squad/templates/routing.md:24, .github/workflows/sync-squad-labels.yml:175-181), so a Copilot-owned task would not receive the label that downstream automation recognizes. Add an explicit @copilotsquad:copilot mapping.
- Labels: `squad` (0075ca), `squad:{agent}` (e4e669) where `{agent}` is **this task's own `Agent` cell**, lowercased — read from the implementation-plan row whose `#` matches this task. Never inherit the parent epic's agent, and never carry the previous task's value forward: re-read the `Agent` cell for every task, because consecutive tasks under one epic routinely have different agents. No `size:*` labels unless policy says so.

workflows/squad.md:1362

  • @copilot is an explicitly permitted Agent value, but this singleton branch interpolates it as squad:@copilot. The repository's routing and heartbeat workflows consume squad:copilot (the triage mapper special-cases @copilot), and no squad:@copilot label is provisioned, so a copilot-owned epic will be created without a routable agent label. Handle the @copilotsquad:copilot mapping here and in the shared gate before interpolating the label.
- Labels: `squad` (0075ca), `squad:{agent}` (e4e669) where `{agent}` is **derived from this epic's own tasks**: collect the `Agent` values of every implementation-plan row whose `Epic` cell names this epic. Exactly one distinct value → mint `squad:{that agent}`. Two or more → multi-owner epic: apply only `squad` and record it under `Non-roster agent values`. Never mint a single agent label for a multi-owner epic, and never choose one of several.

workflows/squad.md:1341

  • An unavailable label is not a non-roster value: the Agent can be a certified ROSTER_MEMBER, with only the label resource missing. Requiring the Non-roster agent values heading for that case falsely classifies a valid roster member and conflicts with the next paragraph's prerequisite-gap reporting. Keep unavailable-label diagnostics in the prerequisite-gap section and reserve this heading for derived multi-owner or uncertified values.
   assumed. Whenever an `Agent` value did not become a label — multi-owner epic, uncertified
   name, unavailable label — the `Non-roster agent values` heading is **required**, and must
   name the value and the issue it applied to. Omitting the heading while omitting the label

workflows/squad.md:1362

  • The new suite exercises epic-source selection and the multi-owner refusal, but it never pins the singleton branch (Exactly one distinct value → mint squad:{that agent}). A regression that omits or downgrades labels for every single-owner epic would leave all seven tests green, while the existing completeness rule only requires one agent label somewhere in the run. Add a positive contract assertion for the exactly-one case.
- Labels: `squad` (0075ca), `squad:{agent}` (e4e669) where `{agent}` is **derived from this epic's own tasks**: collect the `Agent` values of every implementation-plan row whose `Epic` cell names this epic. Exactly one distinct value → mint `squad:{that agent}`. Two or more → multi-owner epic: apply only `squad` and record it under `Non-roster agent values`. Never mint a single agent label for a multi-owner epic, and never choose one of several.

workflows/squad.md:1376

  • This lookup now requires a # key, but the producer's own Step 4 structure at squad.md:1123 describes phase tables as Title|Size|Depends On|Agent|Epic and omits #. Although the imported ontology supplies that column, the local producer contract is contradictory; a model following the listed phase-table columns can emit no key, making the required per-task lookup impossible and reopening the drift this change targets. Make the producer contract explicitly include # or define a stable fallback before relying on it here.
- Labels: `squad` (0075ca), `squad:{agent}` (e4e669) where `{agent}` is **this task's own `Agent` cell**, lowercased — read from the implementation-plan row whose `#` matches this task. Never inherit the parent epic's agent, and never carry the previous task's value forward: re-read the `Agent` cell for every task, because consecutive tasks under one epic routinely have different agents. No `size:*` labels unless policy says so.
  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread workflows/squad.md
**2b. Create Epic Issues:** `create-issue` per epic (dedup by title `[Epic] {name}` if already exists from prior phase).
- Title: `[Epic] {name}`
- Labels: `squad` (0075ca), `squad:{agent}` (e4e669)
- Labels: `squad` (0075ca), `squad:{agent}` (e4e669) where `{agent}` is **derived from this epic's own tasks**: collect the `Agent` values of every implementation-plan row whose `Epic` cell names this epic. Exactly one distinct value → mint `squad:{that agent}`. Two or more → multi-owner epic: apply only `squad` and record it under `Non-roster agent values`. Never mint a single agent label for a multi-owner epic, and never choose one of several.
@bradygaster
bradygaster deleted the squad/1859-agent-binding-correspondence branch September 9, 2026 19:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants