Skip to content

Surface sub-agent failures and replace unpriced kiwi workflow models - #69788

Merged
pelikhan merged 2 commits into
mainfrom
pelikhan-aw-health-review-383
Oct 17, 2026
Merged

pelikhan merged 2 commits into
mainfrom
pelikhan-aw-health-review-383

Conversation

@pelikhan

@pelikhan pelikhan commented Oct 17, 2026 •

Copy link
Copy Markdown
Collaborator

Why

A successful Actions run can hide failed sub-agents. Contribution Check run 39068401375 stayed green after its report-formatter sub-agent received a kiwi AI-credit pricing rejection.

Changes

  • Reuse fresh cached usage summaries to report failed sub-agent invocations in detailed and fleet health. Add subagent_failures_observed, invocation and affected-run counts, and run links with agent/model attribution.
  • Exclude affected workflows from healthy fleet counts without changing Actions conclusions or success rates. Ignore historical revisions, stale caches, duplicate runs, and mirrored declared-model records.
  • Keep raw error diagnostics out of health output and preserve intentional-failure exclusions.
  • Document cache-only evidence limits and distinguish pricing rejection from alias or endpoint resolution.
  • Replace all five active kiwi sub-agent selections with gpt-6-luna in Contribution Check, Daily Malicious Code Scan, and Dead Code Remover. Regenerate the three affected locks while preserving prompts, permissions, budgets, and workflow intent.

The original run contained a kiwi alias, but no verified pricing or canonical model mapping was found. Per the user's follow-up direction, use an advertised, priced model instead of inventing rates. GPT-6 Luna is present in the live Copilot model listing, gh-aw's embedded catalog, and the original run's firewall pricing metadata. Its published default-tier prices are $0.10/M input and $0.50/M output (GitHub pricing). Historical kiwi failure fixtures and alias compatibility remain intact. Unrelated red CI is outside this PR.

Validation

  • go test ./pkg/cli -run '^TestHealth' -count=1
  • python3 -m unittest discover -s .github/skills/agentic-workflows/tests -v
  • make fmt and make build
  • Impacted unit tests passed through make agent-report-progress. After resolving changed-file lint failures, the remaining lint, schema, and workflow-drift gates passed through make agent-report-progress-no-test, using modern Bash and an isolated temporary directory for lint locking. The no-test gates were rerun after the workflow replacements without repeating the PR's unit-test phase.
  • make recompile: all 344 workflows compiled successfully; only the three intended locks changed.
  • .github/skills/operational-value-designer/scripts/verify-operational-value-contract-change.sh origin/main
  • Verified all five selections, no remaining active model: kiwi, and correct generated sub-agent model metadata. Source changes are limited to model substitutions.
  • Replayed the original run against its cached usage: one failed report-formatter / kiwi invocation, subagent_failures_observed, and an unchanged 100% Actions success rate.

Health does not download artifacts; missing cached evidence is not proof of successful delegation.

Preserve Actions success rates while degrading current-revision health when fresh usage summaries record failed sub-agent invocations. Include affected agents, models, and runs without leaking raw diagnostics. Document the observed kiwi pricing rejection separately from alias resolution.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@pelikhan
pelikhan marked this pull request as ready for review October 17, 2026 22:11
Copilot AI max review requested due to automatic review settings October 17, 2026 22:11
@github-actions

github-actions Bot commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

✅ PR Code Quality Reviewer completed the code quality review.

PR Code Quality Reviewer

Outcome: reported_no_action

The agent completed and reported no action. A no-op output alone does not verify that all required work was completed or that publication was possible.

Agent messages are omitted from this comment. The category uses structured run status, not interpretation of agent prose.

Generated from PR Code Quality Reviewer · gpt54 · 88.1 AIC · ⌖ 6.16 AIC · ◇ 2.16 AIC · ⊞ 12.7K · ◷

🔎 Code quality review by PR Code Quality Reviewer

@github-actions

github-actions Bot commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer

Use the advertised lightweight gpt-6-luna model for all five active kiwi selections. Its token prices are present in the embedded catalog and provider metadata. Preserve workflow prompts, permissions, budgets, and historical audit fixtures, and regenerate the three affected locks.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

github-actions Bot commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

✅ Design Decision Gate 🏗️ completed the design decision gate check. See the comment below for the result and any generated ADR draft.

🏗️ ADR gate enforced by Design Decision Gate 🏗️

@github-actions

github-actions Bot commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

✅ Ponytail Reviewer completed successfully!

Ponytail Reviewer

Outcome: reported_no_action

The agent completed and reported no action. A no-op output alone does not verify that all required work was completed or that publication was possible.

Agent messages are omitted from this comment. The category uses structured run status, not interpretation of agent prose.

Generated from Ponytail Reviewer · gpt56 · 7.78 AIC · ⌖ 5.33 AIC · ⊞ 14.6K · ◷

Generated by Ponytail Reviewer for #69788

@github-actions

github-actions Bot commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

✅ Test Quality Sentinel completed test quality analysis.

Test Quality Sentinel

Outcome: reported_no_action

The agent completed and reported no action. A no-op output alone does not verify that all required work was completed or that publication was possible.

Agent messages are omitted from this comment. The category uses structured run status, not interpretation of agent prose.

Generated from Test Quality Sentinel · sonnet50 · 15.2 AIC · ⌖ 15 AIC · ◇ 6.62 AIC · ⊞ 12.6K · ◷

🧪 Test quality analysis by Test Quality Sentinel

@pelikhan pelikhan changed the title Surface cached sub-agent failures in workflow health Oct 17, 2026
@pelikhan
pelikhan merged commit 5bf5f93 into main Oct 17, 2026
37 checks passed
@pelikhan
pelikhan deleted the pelikhan-aw-health-review-383 branch October 17, 2026 22:17
@github-actions

Copy link
Copy Markdown
Contributor

🏗️ Design Decision Gate — ADR Required

This PR makes significant changes to core business logic (348 new lines under pkg/) but does not link an Architecture Decision Record (ADR).

📄 Draft ADR committed: docs/adr/69788-surface-cached-subagent-failures-in-health.md. Review and complete it before merging.

Decision captured: gh aw health reports failed sub-agent invocations from cached usage summaries as a separate signal. Affected workflows get the status subagent_failures_observed and are counted in degraded_workflows instead of healthy_workflows. Actions conclusions and success_rate stay unchanged, and health still does not download artifacts.

🔒 This PR cannot merge until an ADR is linked in the PR body.

📋 What to do next
  1. Review the draft ADR committed to your branch. It was generated from the PR diff (pkg/cli/health_subagents.go, health_metrics.go, health_command.go).
  2. Complete the missing sections: confirm the deciders (marked [TODO: verify]) and refine the alternatives. The draft lists folding failures into the success rate, downloading artifacts on demand, and relying only on gh aw audit.
  3. Commit the finalized ADR to docs/adr/ on your branch.
  4. Reference the ADR in this PR body by adding a line such as:

    ADR: ADR-69788: Surface Cached Sub-agent Failures as a Distinct Health Signal

Once an ADR is linked in the PR body, this gate will re-run and check that the implementation matches the decision.

❓ Why ADRs Matter

ADRs create a searchable, permanent record of why the codebase looks the way it does. Future contributors (and your future self) will thank you.

📋 Michael Nygard ADR Format Reference

An ADR must contain these four sections to be considered complete:

  • Context: What is the problem? What forces are at play?
  • Decision: What did you decide? Why?
  • Alternatives Considered: What else could have been done?
  • Consequences: What are the trade-offs (positive and negative)?

All ADRs are stored in docs/adr/ as Markdown files numbered by PR number.

🏗️ ADR gate enforced by Design Decision Gate 🏗️ · pi · opus55 · 34 AIC · ⌖ 40.5 AIC · ⊞ 1.9K · ◷
Comment /review to run again

@github-actions

Copy link
Copy Markdown
Contributor

Comment Memory

Peek at saved memory (pr-code-quality-reviewer)
reviewed_at: 2026-10-17T22:14:43Z
review_event: REQUEST_CHANGES
top_themes:
  - status overwriting hides stronger health states
  - case-sensitive repository validation drops valid cached evidence
files_reviewed:
  - .github/aw/debug-model-engine-misconfiguration.md
  - docs/src/content/docs/setup/cli.md
  - pkg/cli/health_command.go
  - pkg/cli/health_failure_clusters.go
  - pkg/cli/health_metrics.go
  - pkg/cli/health_revisions.go
  - pkg/cli/health_subagents.go
  - pkg/cli/health_subagents_test.go
comment_count: 2

Note

This comment is managed by comment memory.

Expand the saved memory block to view or edit the persistent context for this thread.
Edit only the text inside the backtick fences; workflow metadata and the footer are regenerated automatically.

Learn more about comment memory

🔎 Code quality review by PR Code Quality Reviewer · copilot · gpt54 · 88.1 AIC · ⌖ 6.16 AIC · ⊞ 12.7K · ◷
Comment /review to run again

@github-actions github-actions Bot 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.

Verdict

I didn't find a changed-line issue here that is strong enough to block merge.

Reviewed areas
  • status propagation for cached sub-agent failures
  • stale-cache guards and revision scoping
  • fleet summary / detailed output behavior
  • regression coverage for duplicate, stale, and historical runs

Static review only; I did not execute the code in this pass.

🔎 Code quality review by PR Code Quality Reviewer · copilot · gpt54 · 88.1 AIC · ⌖ 6.16 AIC · ⊞ 12.7K
Comment /review to run again

pr := ProcessedRun{
Run: cached, JobDetails: summary.JobDetails, MCPFailures: summary.MCPFailures,
MissingTools: summary.MissingTools, MissingData: summary.MissingData, FirewallAnalysis: summary.FirewallAnalysis,
cached.Repository != run.Repository || cached.Attempt != run.Attempt || !cached.UpdatedAt.Equal(run.UpdatedAt) {

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.

This repository check is case-sensitive, so --repo Owner/Repo can make a valid cache look stale and silently suppress the very health evidence this change is trying to surface.

💡 Why this matters

GitHub repository slugs are case-insensitive, and other cache-validation paths in this codebase already use strings.EqualFold for that reason. Using raw string equality here means a casing difference between the cached summary and the run source will drop both failure-cluster enrichment and sub-agent-failure reporting as false negatives.

Prefer normalizing or case-folding the repository comparison, for example:

!strings.EqualFold(cached.Repository, run.Repository)

That keeps the stale-cache guard strict without rejecting equivalent repository identities.

if health.SubagentFailureCount == 0 {
return
}
health.Status = "subagent_failures_observed"

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.

This unconditionally overwrites failures_observed and insufficient_evidence, so a workflow with real run failures or too little data now gets reported only as subagent_failures_observed.

💡 Why this blocks the change

calculateCurrentWorkflowHealth already derives a status from Actions outcomes and sample sufficiency. Replacing that status here throws away the primary signal whenever cached sub-agent failures exist, which means both the CLI and JSON can tell callers the wrong reason a workflow is unhealthy.

A safer pattern is to preserve the existing status and surface sub-agent failures as an additional dimension, or only promote to subagent_failures_observed from statuses like healthy / recovery_observed.

if health.SubagentFailureCount > 0 {
    switch health.Status {
    case "healthy", "recovery_observed":
        health.Status = "subagent_failures_observed"
    }
}

That keeps failed or insufficient revisions from being misclassified while still exposing failed delegation.

@github-actions

Copy link
Copy Markdown
Contributor

🎉 This pull request is included in a new release.

Release: v0.92.5

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

Contribution Check’s replacement sub-agents remain endpoint-incompatible when its CLI session selects a Claude parent.

3 open issues
What changed in this PR

Adds cached sub-agent failure reporting to gh-aw health, exposing failed delegation even when Actions runs succeed.

Changes:

  • Report failed invocations, affected runs, and agent/model attribution.
  • Validate cached evidence and document its limits.
  • Replace five kiwi selections with gpt-6-luna and refresh three locks.
File Description
pkg/​cli/​health_subagents.go Aggregates and renders cached delegation failures.
pkg/​cli/​health_subagents_test.go Tests counts, output, cache validation, and exclusions.
pkg/​cli/​health_revisions.go Integrates delegation evidence into revision health.
pkg/​cli/​health_metrics.go Adds workflow and fleet failure counters.
pkg/​cli/​health_failure_clusters.go Shares cache loading and identity checks.
pkg/​cli/​health_command.go Displays failure totals and warnings.
docs/​src/​content/​docs/​setup/​cli.md Documents health fields and cache limitations.
.github/​workflows/​dead-code-remover.md Updates the discovery sub-agent model.
.github/​workflows/​dead-code-remover.lock.yml Refreshes the source body hash.
.github/​workflows/​daily-malicious-code-scan.md Updates two scanning sub-agent models.
.github/​workflows/​daily-malicious-code-scan.lock.yml Refreshes model metadata and body hash.
.github/​workflows/​contribution-check.md Updates formatter and dispatcher models.
.github/​workflows/​contribution-check.lock.yml Refreshes model metadata and body hash.
.github/​aw/​debug-model-engine-misconfiguration.md Distinguishes pricing rejection from routing failures.

Review effort: Max

---
description: Groups PR verdict JSONs into Ready/Needs-look/Off-guidelines tables and returns the markdown body for the contribution check report issue
model: kiwi
model: gpt-6-luna
Comment thread pkg/cli/health_command.go
Comment on lines +251 to +259
if health.SubagentFailureCount > 0 {
fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("%d sub-agent invocation(s) failed across %d run(s); Actions success rate does not establish healthy delegation", health.SubagentFailureCount, health.SubagentFailureRuns)))
} else if health.BelowThresh {
fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Success rate (%.1f%%) is below threshold (%.1f%%)", health.SuccessRate, threshold)))
} else if health.Status == "healthy" || health.Status == "recovery_observed" {
fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Success rate (%.1f%%) is above threshold (%.1f%%)", health.SuccessRate, threshold)))
} else {
fmt.Fprintln(os.Stderr, console.FormatWarningMessage("Current revision has insufficient evidence for a healthy status"))
}
Comment on lines +38 to +47
health.Status = "subagent_failures_observed"
for i := range health.Revisions {
revision := &health.Revisions[i]
for _, id := range revision.Current.RunIDs {
revision.Current.SubagentFailures += failuresByRun[id]
}
if revision.Current.SubagentFailures > 0 {
revision.Status = health.Status
}
}

@github-actions github-actions Bot 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.

Skills-Based Review 🧠

Applied /tdd and /grill-with-docs (per pr-triage), with a /codebase-design lens on the shared cache-loading path. Overall: solid feature with strong test coverage and careful preservation of Actions truth; two non-blocking findings posted inline.

📋 Key Themes & Highlights

Key Themes

  • Duplicated cache reads (/codebase-design): health_subagents.go and health_failure_clusters.go each independently call loadHealthRunSummary for the same run set, re-reading/re-parsing run_summary.json twice per health check. Minor perf/architecture nit, not a bug.
  • Status precedence not documented (/grill-with-docs): applyHealthSubagentFailures unconditionally overwrites health.Status, discarding the revision-derived status (healthy/recovery_observed/insufficient_evidence). Likely intentional (subagent failures should dominate messaging) but worth a short comment explaining the precedence rule so future readers do not need to reverse-engineer it.

Positive Highlights

  • ✅ Excellent test coverage in health_subagents_test.go: 11-field stale-cache rejection matrix, missing/corrupt cache fallback, intentional-failure exclusion from fleet counts, and explicit assertion that historical-revision failures do not degrade the current revision.
  • ✅ Careful double-count avoidance — explicit comment and test proving DeclaredSubagentModels is not summed alongside SubagentModelRequests.
  • ✅ No raw diagnostic leakage — test explicitly asserts error text (e.g. "private diagnostic detail") never reaches JSON/table output.
  • ✅ Docs (cli.md) clearly explain the new subagent_failures_observed status, cache-only limitation, and new JSON fields — good /grill-with-docs hygiene.

Scope note

Triage ran successfully (pr-triage); no fallback heuristic was needed. Investigation stayed within budget — no open uncertainty beyond the two inline findings.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · copilot · sonnet50 · 211.7 AIC · ⌖ 14 AIC · ⊞ 10.1K
Comment /matt to run again

func applyHealthSubagentFailures(health *WorkflowHealth, runs []WorkflowRun, config HealthConfig) {
failuresByRun := make(map[int64]int)
for _, run := range runs {
summary, _ := loadHealthRunSummary(run, config)

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.

[/codebase-design] applyHealthSubagentFailures re-reads and re-parses the same run_summary.json via loadHealthRunSummary for every run, duplicating the load already done in enrichHealthFailureFromCache (health_failure_clusters.go) for the same run set during the same health check.

💡 Why this matters

Both buildHealthFailureClusters and calculateCurrentWorkflowHealth call loadHealthRunSummary(run, config) independently against overlapping run sets, so each cached summary file is read and JSON-unmarshalled twice per health check. It's not a correctness bug today, but as more health features consume the same cache (this is now the second consumer), the duplicated I/O will keep compounding and the two load paths can drift in validation rules over time.

Consider threading a shared map[int64]*RunSummary (or memoizing loadHealthRunSummary) through displayDetailedHealth/calculateHealthWorkflowReports so both consumers share one load per run.

@copilot please address this.

if health.SubagentFailureCount == 0 {
return
}
health.Status = "subagent_failures_observed"

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.

[/grill-with-docs] health.Status = "subagent_failures_observed" unconditionally overwrites whatever revision-derived status was just computed (healthy, recovery_observed, insufficient_evidence, etc.), discarding that information rather than composing it.

💡 Why this matters

calculateCurrentWorkflowHealth sets health.Status from revision classification, then applyHealthSubagentFailures clobbers it with a single flat string whenever any sub-agent failure is observed — even if the underlying revision evidence was insufficient_evidence or recovery_observed. The JSON/table output downstream (isHealthEvidenceInsufficient, displayDetailedHealthStatus) only sees the final string, so a caller can no longer tell “this workflow has insufficient run evidence AND a subagent failure” from “healthy but degraded by one subagent failure” — both collapse to the same status string. The per-revision loop does something closer to the right thing (if revision.Current.SubagentFailures > 0 { revision.Status = health.Status }), but the top-level WorkflowHealth.Status doesn't get the same nuance.

Worth confirming this is the intended design (it may be — subagent failures are meant to take precedence in messaging) and, if so, documenting it inline with a short comment explaining the precedence rule, since it's not obvious from the code alone.

@copilot please address this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants