Skip to content

feat(#4026): support disabling agents via config.yaml enabled field#4049

Open
fullsend-ai-coder[bot] wants to merge 1 commit into
mainfrom
agent/4026-config-enabled-field
Open

feat(#4026): support disabling agents via config.yaml enabled field#4049
fullsend-ai-coder[bot] wants to merge 1 commit into
mainfrom
agent/4026-config-enabled-field

Conversation

@fullsend-ai-coder

@fullsend-ai-coder fullsend-ai-coder Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add Enabled *bool field to AgentEntry struct so operators can selectively disable agents (including built-in scaffold agents) without removing them from configuration
  • Filter disabled agents in MergedAgents() so they are excluded from the merged result set
  • Block disabled agents from falling through to agents-repo/disk fallback in resolveAgentSource
  • Gate dispatch on agents[].enabled in both scaffold and reusable dispatch workflows
  • Add pre-dispatch config validation to catch malformed entries before individual agent runs
  • Allow suppression-only entries (enabled: false with no source) that exist solely to disable scaffold defaults by name

Related Issue

Closes #4026

ADR Edits

This PR edits the Decision section of ADR 0058 (Agent Registration) (status: Accepted, 2026-06-29) to add an enabled: false example to the AgentEntry struct documentation. The edit extends the existing struct definition with the new field — it does not change the decision or its rationale.

Changes

internal/config/config.go

  • Added Enabled *bool field with yaml:"enabled,omitempty" tag to AgentEntry
  • Added IsEnabled() helper method that returns true when Enabled is nil (default) or explicitly true
  • Updated ValidateAgentEntries() to accept suppression-only entries (no source, enabled: false) as long as they have an explicit name

internal/config/agents.go

  • Updated MergedAgents() to skip disabled config entries and remove any scaffold entries they override
  • Added inOrder set to prevent duplicate entries when a disabled entry is followed by an enabled one with a different name
  • Added IsAgentExplicitlyDisabled() to distinguish "explicitly disabled" from "not in config"

internal/cli/run.go

  • Added disabled-agent check in resolveAgentSource before agents-repo/disk fallback to prevent disabled agents from resolving via other sources

internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml

  • Added "Check agent is enabled" step using yq to skip dispatch for disabled agents
  • Added "Validate agents config" step to catch malformed entries (missing name, duplicates) before dispatch
  • Error handling: captures yq stderr and emits ::warning:: on failure instead of silently swallowing errors

.github/workflows/reusable-dispatch.yml

  • Same agent-check and config validation steps as scaffold dispatch, with .fullsend/config.yaml path and file-existence guard

docs/guides/user/customizing-agents.md

  • Added "Disabling Agents" section with usage example
  • Documented that name must match agent/harness name (code, fix, etc.), not role name (coder)

docs/ADRs/0058-agent-registration.md

  • Added enabled: false example to Decision section (see ADR Edits above)

docs/plans/agent-registration.md, docs/plans/agent-extraction-to-agents-repo.md

  • Updated plan docs with Enabled *bool field and enabled: false example

Tests

  • internal/config/agents_test.go: 7 new tests — disabled scaffold/custom agents, suppression-only, enabled defaults, duplicate-name-rejected-by-validation, IsAgentExplicitlyDisabled (7 cases)
  • internal/config/config_test.go: Suppression validation, disabled-with-source, IsEnabled unit tests, YAML round-trip tests
  • internal/cli/run_test.go: 4 tests — disabled/suppression blocks fallback, enabled still resolves

Testing

  • All internal/config/... tests pass
  • All internal/cli/... resolveAgentSource tests pass
  • go vet ./... clean
  • go build ./... clean

Checklist

  • Code follows conventional commit format
  • Tests added for new functionality
  • Backward compatible (nil Enabled defaults to true)
  • ADR 0058 edit called out in PR description per policy
  • Dispatch-side gate uses downcase (yq), not ascii_downcase (jq)
  • Config validation catches malformed entries pre-dispatch

@fullsend-ai-coder fullsend-ai-coder Bot requested a review from a team as a code owner July 10, 2026 14:13
@github-actions

Copy link
Copy Markdown

E2E tests did not run

E2E tests run automatically for org/repo members and collaborators on pull requests.

For other contributors, a maintainer must add the ok-to-test label after the latest push.

See E2E testing guide for details.

@github-actions

github-actions Bot commented Jul 10, 2026

Copy link
Copy Markdown

Site preview

Preview: https://024c9b6d-site.fullsend-ai.workers.dev

Commit: 553b42fb525f032b713daccc37333939884eed93

@codecov

codecov Bot commented Jul 10, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@ascerra ascerra 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.

Review: enabled: false does not stop agents from running

Problem

This PR filters disabled agents out of MergedAgents(), but that is not enough to disable them at runtime. Two independent gates still allow the agent to run.

Gap 1 — resolveAgentSource treats “missing” as “fall back”

In internal/cli/run.go:

agent := config.LookupMergedAgent(merged, agentName)
if agent == nil || !agent.IsConfig {
    // tryAgentsRepoFallback → resolveHarnessPath
}

Deleting an agent from MergedAgents makes Lookup return nil, which takes the same path as a normal scaffold agent. First-party names (triage, code, fix, review, retro, prioritize) are in defaultAgentsRepoKnownAgents and will still resolve via agents-repo fallback (or disk).

So for the stated use case (disable built-in scaffold agents), filtering in MergedAgents alone is a no-op at runtime. The PR claim that no run.go changes are needed is incorrect.

Gap 2 — dispatch ignores agents: entirely

Org dispatch.yml gates only on defaults.roles (yq '.defaults.roles[]'). It never reads agents[].enabled. With retro still in defaults.roles, dispatch still fires even if config says enabled: false.

What works today vs what this PR needs

Goal Works with this PR?
Disable custom (non-first-party) agent Partially — may error on missing harness, not clean skip
Disable built-in (retro, etc.) No — fallback + dispatch still run it

Required fix (for another agent)

  1. resolveAgentSource: Distinguish “explicitly disabled” from “not in merged set.” If config has {name: X, enabled: false}, return a clear skip/error — do not call tryAgentsRepoFallback / resolveHarnessPath.
  2. Dispatch (scaffold dispatch.yml + reusable-dispatch.yml): Honor disabled agents (or document that operators must also remove the role from defaults.roles — but that contradicts the issue goal of keeping workflow infra).
  3. Tests: Add resolveAgentSource coverage proving enabled: false on retro/triage does not resolve a harness via fallback. Current tests only cover MergedAgents in isolation.

Minimal repro config

defaults:
  roles: [fullsend, triage, coder, review, retro, prioritize]
agents:
  - name: retro
    enabled: false

Expected after fix: retro does not dispatch / does not resolve a harness.
Actual with this PR: MergedAgents drops retro; dispatch + fallback still run it.

Refs: internal/cli/run.go (resolveAgentSource, defaultAgentsRepoKnownAgents), internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml (Check role is enabled).

@ggallen

ggallen commented Jul 10, 2026

Copy link
Copy Markdown
Member

/fs-fix rebase and fix the issues raised in #4049 (review) and also #4049 (comment)

@fullsend-ai-coder

fullsend-ai-coder Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Finished Fix · ✅ Success · Started 2:50 PM UTC · Completed 3:02 PM UTC
Commit: b8a817e · View workflow run →

@fullsend-ai-coder

Copy link
Copy Markdown
Contributor Author

🔧 Fix agent — iteration 1 (human-triggered)

Addressed 4 of 5 review findings. Fixed resolveAgentSource to block disabled agents from resolving via fallback paths, added IsAgentExplicitlyDisabled helper, and added 11 tests. Disagreed with dispatch workflow changes (deferred to follow-up PR since resolveAgentSource now enforces the safety guarantee).

Fixed (4):

  1. resolveAgentSource treats disabled agents same as not-found, allowing fallback resolution (internal/config/agents.go): Added IsAgentExplicitlyDisabled() helper that checks config entries for an agent with Enabled explicitly set to false, distinguishing 'explicitly disabled' from 'not in config at all'
  2. resolveAgentSource falls through to tryAgentsRepoFallback and resolveHarnessPath for disabled agents (internal/cli/run.go): Updated resolveAgentSource to call IsAgentExplicitlyDisabled before falling through to fallback paths. When an agent is explicitly disabled in config, returns a clear error instead of trying agents-repo fallback or disk lookup
  3. Missing resolveAgentSource coverage proving enabled:false blocks fallback (internal/cli/run_test.go): Added 4 test cases: TestResolveAgentSource_DisabledAgentBlocksFallback, TestResolveAgentSource_DisabledFirstPartyAgentBlocksFallback, TestResolveAgentSource_SuppressionOnlyEntryBlocksFallback, TestResolveAgentSource_EnabledAgentStillResolves
  4. Missing IsAgentExplicitlyDisabled unit tests (internal/config/agents_test.go): Added 7 test cases covering: explicit disable, case-insensitivity, enabled agent, nil Enabled, not-in-config, empty config, derived name

Disagreed (1):

  1. dispatch.yml and reusable-dispatch.yml do not check agents[].enabled: The dispatch workflow fix (checking agents[].enabled in YAML/bash) has a different blast radius than the resolveAgentSource fix and is better addressed in a follow-up PR. The resolveAgentSource fix already prevents disabled agents from running when dispatched — fullsend run will now exit with a clear error if the agent is disabled. The dispatch workflows will still trigger the run, but the run will fail-fast at resolution time. A follow-up PR should add the dispatch-side check to avoid wasting runner time, but the safety guarantee (agent does not run) is now enforced.

Tests: passed

Decision points
  • Chose to add IsAgentExplicitlyDisabled as a standalone function rather than modifying MergedAgents to retain disabled entries (alternatives: Keep disabled entries in MergedAgents with a Disabled flag, Check configAgents slice directly in resolveAgentSource; rationale: A standalone function is the smallest change, keeps MergedAgents semantics unchanged, and is independently testable. Callers that need the distinction can call IsAgentExplicitlyDisabled without changing MergedAgents consumers.)
  • Deferred dispatch workflow changes to a follow-up PR (alternatives: Add agents[].enabled check to both dispatch.yml and reusable-dispatch.yml; rationale: The resolveAgentSource fix provides the safety guarantee (disabled agents cannot run). Dispatch changes involve workflow YAML with different testing requirements and blast radius. The reviewer offered 'or document that operators must also remove the role' as an acceptable alternative.)

Updated by fullsend fix agent

fullsend-ai-coder Bot added a commit that referenced this pull request Jul 10, 2026
Add IsAgentExplicitlyDisabled() to distinguish 'explicitly disabled in
config' from 'not in config at all'. Update resolveAgentSource() to
check this before falling through to agents-repo fallback or disk
lookup — without this, setting enabled: false on a built-in agent
(triage, retro, etc.) was a no-op because the fallback paths still
resolved the harness.

Add unit tests for IsAgentExplicitlyDisabled and integration tests for
resolveAgentSource proving that enabled: false blocks all fallback
resolution paths.

Addresses review feedback on #4049
@ggallen

ggallen commented Jul 10, 2026

Copy link
Copy Markdown
Member

/fs-fix Rebase first.

Add a dispatch-side check for agents[].enabled in internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml.

After the "Check role is enabled" step (line 340), add a new step "Check agent is enabled" that reads agents[].enabled from config.yaml for the matched stage. If the agent for that stage has enabled: false, skip dispatch (set skipped=true the same way the role check does).

Use yq to check — something like: look up the agent entry whose name matches $STAGE and check if its enabled field is explicitly false. If there is no matching agent entry, or if enabled is omitted/true, proceed normally (default-enabled, backward compatible).

Gate subsequent steps on this new step's skipped output the same way they gate on role-check.outputs.skipped.

The resolveAgentSource check in run.go is correct and must stay — it's the safety net for direct fullsend run invocations. The dispatch check is the efficiency gate that prevents wasting a runner on an agent that will just fail at resolution time.

@fullsend-ai-coder

fullsend-ai-coder Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Finished Fix · ❌ Failure · Started 3:49 PM UTC · Completed 3:59 PM UTC
Commit: b8a817e · View workflow run →

@ggallen ggallen force-pushed the agent/4026-config-enabled-field branch from 553b42f to 5fd307b Compare July 10, 2026 17:23
@ggallen

ggallen commented Jul 10, 2026

Copy link
Copy Markdown
Member

/fs-review

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 10, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 5:32 PM UTC · Completed 5:49 PM UTC
Commit: b8a817e · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review

Re-review of same SHA df6d8b6. Prior review provenance: app-verified. Severity anchoring applied to unchanged code.

This PR adds an Enabled *bool field to AgentEntry so operators can selectively disable agents (including built-in scaffold agents) without removing them from configuration. The implementation spans three defense layers: config validation (ValidateAgentEntries), merge-set filtering (MergedAgents), and CLI fallback guarding (IsAgentExplicitlyDisabled in resolveAgentSource). Dispatch workflows gate on agents[].enabled via yq before dispatching. Test coverage is thorough — 30+ new test cases covering disabled scaffold agents, suppression-only entries, fallback blocking, validation edge cases, YAML round-trip parsing, and IsEnabled() semantics. Documentation has been updated across ADR 0058, the agent-registration plan, the agent-extraction plan, and the customizing-agents guide.

No code changes since prior review. All prior findings remain applicable at their anchored severities.

Findings

Medium

  • [protected-path] .github/workflows/reusable-dispatch.yml — This file is under .github/ (protected path). The changes add a "Validate agents config" step and a "Check agent is enabled" step for dispatch-level agent-enabled gating using downcase for case-insensitive name matching, and gate the stage output and downstream steps on agent-check.outputs.skipped. Human approval is required for protected-path changes regardless of context. The scaffold dispatch.yml (internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml) also receives the same agent-check step, maintaining routing logic parity per AGENTS.md.

Low

  • [fail-open] .github/workflows/reusable-dispatch.yml — The "Check agent is enabled" step fails open when yq encounters an error: it emits a ::warning:: and exits 0, allowing dispatch to proceed. The same pattern exists in the scaffold dispatch.yml. This is consistent with the existing "Check role is enabled" step pattern and appears intentional for operational resilience. The $(cat "$YQ_ERR") content in the ::warning:: command is not sanitized for workflow command sequences, but config.yaml is read from the trusted base branch (base.sha on pull_request_target), so the data source is not attacker-controlled.

  • [edge-case] internal/config/config.goDerivedName() on a suppression-only entry (Name="", Source="") returns "." (from path.Base("")) rather than an error. ValidateAgentEntries catches this by requiring Name for disabled entries without a source, but the defense is in validation only, not in the type system.


Labels: PR modifies agent configuration (internal/config/), dispatch workflows (.github/workflows/, internal/scaffold/), and documentation (docs/)

Previous run

Review

Re-review of force-push from c716b69df6d8b6. Prior review provenance: app-verified. Severity anchoring applied to unchanged code.

This PR adds an Enabled *bool field to AgentEntry so operators can selectively disable agents (including scaffold defaults) without removing them from configuration. The implementation spans three defense layers: config validation (ValidateAgentEntries), merge-set filtering (MergedAgents), and CLI fallback guarding (IsAgentExplicitlyDisabled in resolveAgentSource). Dispatch workflows gate on agents[].enabled via yq before dispatching. Test coverage is thorough — 30+ new test cases covering disabled scaffold agents, suppression-only entries, fallback blocking, validation edge cases, YAML round-trip parsing, and IsEnabled() semantics. Documentation has been updated across ADR 0058, the agent-registration plan, the agent-extraction plan, and the customizing-agents guide.

Improvement since prior review: The yq queries in both dispatch workflows now use | tail -1 to take the last matching entry's .enabled value, correctly implementing last-writer-wins semantics for the unusual enable-then-disable configuration pattern. This resolves the prior review's medium [logic-error] finding about multi-line yq output with duplicate entries.

Findings

Medium

  • [protected-path] .github/workflows/reusable-dispatch.yml — This file is under .github/ (protected path). The changes add a "Validate agents config" step and a "Check agent is enabled" step for dispatch-level agent-enabled gating using downcase for case-insensitive name matching, and gate the stage output and downstream steps on agent-check.outputs.skipped. Human approval is required for protected-path changes regardless of context. The scaffold dispatch.yml (internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml) also receives the same agent-check step, maintaining routing logic parity per AGENTS.md.

Low

  • [fail-open] .github/workflows/reusable-dispatch.yml — The "Check agent is enabled" step fails open when yq encounters an error: it emits a ::warning:: and exits 0, allowing dispatch to proceed. The same pattern exists in the scaffold dispatch.yml. This is consistent with the existing "Check role is enabled" step pattern and appears intentional for operational resilience.

  • [edge-case] internal/config/config.goDerivedName() on a suppression-only entry (Name="", Source="") returns "." (from path.Base("")) rather than an error. ValidateAgentEntries catches this by requiring Name for disabled entries without a source, but the defense is in validation only, not in the type system.


Labels: PR modifies agent configuration (internal/config/), dispatch workflows (.github/workflows/, internal/scaffold/), and documentation (docs/)


Labels: PR modifies agent configuration (internal/config/) and dispatch workflows.


Labels: PR modifies agent configuration (internal/config/) and dispatch workflows.


Labels: PR modifies agent configuration (internal/config/) and dispatch workflows.

Previous run (2)

Review

Re-review of force-push from f72c816c716b69. Prior review provenance: app-verified. Severity anchoring applied to unchanged code.

Findings

Medium

  • [protected-path] .github/workflows/reusable-dispatch.yml — This file is under .github/ (protected path). The changes add a "Validate agents config" step and a "Check agent is enabled" step for dispatch-level agent-enabled gating using downcase for case-insensitive name matching, and gate the stage output and downstream steps on agent-check.outputs.skipped. Human approval is required for protected-path changes regardless of context. The scaffold dispatch.yml (internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml) also receives the same agent-check step, maintaining routing logic parity per AGENTS.md.

  • [logic-error] internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml — The yq query in the "Check agent is enabled" step can produce multi-line output when the config contains duplicate entries for the same agent name with different enabled states (e.g., enable-then-disable pattern, which ValidateAgentEntries deliberately permits). The yq expression .agents[] | select((.name | downcase) == "$STAGE") | .enabled emits one value per matching entry. When there are two matching entries, the shell variable AGENT_ENABLED contains a newline-separated string (e.g., true\nfalse). The subsequent [[ "$AGENT_ENABLED" == "false" ]] check fails to match, so the workflow proceeds as if the agent is enabled — contradicting the Go-side last-writer-wins semantics. The same issue exists in .github/workflows/reusable-dispatch.yml. Impact is limited: this only triggers with the unusual enable-then-disable configuration pattern, and the Go-level IsAgentExplicitlyDisabled() check in resolveAgentSource() provides a second defense layer that correctly blocks the agent. The consequence is a wasted workflow dispatch with a clear error message.

Low

  • [fail-open] .github/workflows/reusable-dispatch.yml — The "Check agent is enabled" step fails open when yq encounters an error: it emits a ::warning:: and exits 0, allowing dispatch to proceed. The same pattern exists in the scaffold dispatch.yml. This is consistent with the existing "Check role is enabled" step pattern and appears intentional for operational resilience.

  • [edge-case] internal/config/config.goDerivedName() on a suppression-only entry (Name="", Source="") returns "." (from path.Base("")) rather than an error. ValidateAgentEntries catches this by requiring Name for disabled entries without a source, but the defense is in validation only, not in the type system.


Labels: PR modifies agent configuration (internal/config/) and dispatch workflows (.github/workflows/, internal/scaffold/)

Previous run (3)

Review

Re-review of force-push from 22d221af72c816. Prior review provenance: app-verified. Severity anchoring applied to unchanged code.

This PR adds an Enabled *bool field to AgentEntry so operators can selectively disable agents (including scaffold defaults) without removing them from configuration. The implementation is well-structured across three layers: config validation (ValidateAgentEntries), merge-set filtering (MergedAgents), and CLI fallback guarding (IsAgentExplicitlyDisabled in resolveAgentSource). Test coverage is thorough — 30+ new test cases covering disabled scaffold agents, suppression-only entries, fallback blocking, validation edge cases, YAML round-trip parsing, and IsEnabled() semantics. Documentation has been updated across ADR 0058, the agent-registration plan, the agent-extraction plan, and the customizing-agents guide.

No new issues since prior review. The force-push from 22d221af72c816 is a rebase onto updated main with no substantive content changes to the PR diff.

Findings

Medium

  • [protected-path] .github/workflows/reusable-dispatch.yml — This file is under .github/ (protected path). The changes add an agent-check step for dispatch-level agent-enabled gating with ascii_downcase for case-insensitive name matching, and gate the stage output and downstream steps on agent-check.outputs.skipped. Human approval is required for protected-path changes regardless of context. The scaffold dispatch.yml (internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml) also receives the same agent-check step, maintaining routing logic parity per AGENTS.md.
Previous run (4)

Review

Re-review of force-push from bd4137a22d221a. Prior review provenance: app-verified. Severity anchoring applied to unchanged code.

This PR adds an Enabled *bool field to AgentEntry so operators can selectively disable agents (including scaffold defaults) without removing them from configuration. The implementation is well-structured across three layers: config validation (ValidateAgentEntries), merge-set filtering (MergedAgents), and CLI fallback guarding (IsAgentExplicitlyDisabled in resolveAgentSource). Test coverage is thorough — 30+ new test cases covering disabled scaffold agents, suppression-only entries, fallback blocking, validation edge cases, YAML round-trip parsing, and IsEnabled() semantics. Documentation has been updated across ADR 0058, the agent-registration plan, the agent-extraction plan, and the customizing-agents guide.

No new issues since prior review. The force-push from bd4137a22d221a does not introduce correctness, security, or architectural concerns beyond the standing protected-path finding.

Findings

Medium

  • [protected-path] .github/workflows/reusable-dispatch.yml — This file is under .github/ (protected path). The changes add an agent-check step for dispatch-level agent-enabled gating with ascii_downcase for case-insensitive name matching, and gate the stage output and downstream steps on agent-check.outputs.skipped. Human approval is required for protected-path changes regardless of context. The scaffold dispatch.yml (internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml) also receives the same agent-check step, maintaining routing logic parity per AGENTS.md.
Previous run (5)

Review

Re-review of force-push from 43ec352bd4137a. Prior review provenance: app-verified. Severity anchoring applied to unchanged code.

This PR adds an Enabled *bool field to AgentEntry so operators can selectively disable agents (including scaffold defaults) without removing them from configuration. The implementation is well-structured across three layers: config validation (ValidateAgentEntries), merge-set filtering (MergedAgents), and CLI fallback guarding (IsAgentExplicitlyDisabled in resolveAgentSource). Test coverage is thorough — 30+ new test cases covering disabled scaffold agents, suppression-only entries, fallback blocking, validation edge cases, YAML round-trip parsing, and IsEnabled() semantics. Documentation has been updated across ADR 0058, the agent-registration plan, the agent-extraction plan, and the customizing-agents guide.

Improvement since prior review: The force-push addressed the prior review's case-sensitivity finding — both dispatch workflows now use ascii_downcase in the yq query for case-insensitive name matching. The reusable-dispatch.yml also adds a config.yaml existence check before invoking yq. Prior low-severity findings about the yq case-sensitivity gap and error message clarity are resolved.

Findings

Medium

  • [protected-path] .github/workflows/reusable-dispatch.yml — This file is under .github/ (protected path). The changes add an agent-check step for dispatch-level agent-enabled gating with ascii_downcase for case-insensitive matching. Human approval is required for protected-path changes regardless of context. The scaffold dispatch.yml (internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml) also receives the same agent-check step, maintaining routing logic parity per AGENTS.md.
Previous run

Review

Re-review of force-push from 99148e243ec352. Prior review provenance: app-verified. Severity anchoring applied to unchanged code.

This PR adds an Enabled *bool field to AgentEntry so operators can selectively disable agents (including scaffold defaults) without removing them from configuration. The implementation is well-structured across three layers: config validation (ValidateAgentEntries), merge-set filtering (MergedAgents), and CLI fallback guarding (IsAgentExplicitlyDisabled in resolveAgentSource). Test coverage is thorough — 30+ new test cases covering disabled scaffold agents, suppression-only entries, fallback blocking, validation edge cases, YAML round-trip parsing, and IsEnabled() semantics. Documentation has been updated across ADR 0058, the agent-registration plan, the agent-extraction plan, and the customizing-agents guide.

Findings

Medium

  • [protected-path] .github/workflows/reusable-dispatch.yml — This file is under .github/ (protected path). The changes add an agent-check step for dispatch-level agent-enabled gating. Human approval is required for protected-path changes regardless of context. The scaffold dispatch.yml (internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml) also receives the same agent-check step, maintaining routing logic parity per AGENTS.md.

Low

  • [logic-error] .github/workflows/reusable-dispatch.yml — The yq query in the agent-check step performs case-sensitive comparison (.name == "$STAGE") while the Go code uses case-insensitive matching (strings.ToLower). If a config entry specifies name: Triage (uppercase T) and the routed stage is triage (lowercase), the yq query would fail to match, allowing dispatch to proceed even though the Go-level resolveAgentSource would block it. This is mitigated at three levels: (1) stage names are always lowercase (enforced by the "Validate routed stage" regex ^[a-z][a-z0-9_-]*$), (2) ValidateAgentEntries requires explicit names on all disabled entries, and (3) the CLI-level IsAgentExplicitlyDisabled() check in resolveAgentSource() provides a second defense layer. The derived-name branch in the yq query (matching .source filename when .name is null) is functionally dead code for disabled entries since validation requires explicit names.

  • [edge-case] internal/config/config.go — An enabled entry (nil or true Enabled) with an empty Source correctly falls through to the existing "source must not be empty" validation error. The error message is accurate but could be more helpful (e.g., distinguishing "enabled entry must have a source" from the generic message). UX nit, not a correctness bug.

Previous run

Review

Re-review of force-push from 5fd307b99148e2. Prior review provenance: app-verified. Severity anchoring applied to unchanged code.

This PR adds an Enabled *bool field to AgentEntry so operators can selectively disable agents (including scaffold defaults) without removing them from configuration. The implementation is well-structured across three layers: config validation (ValidateAgentEntries), merge-set filtering (MergedAgents), and CLI fallback guarding (IsAgentExplicitlyDisabled in resolveAgentSource). Test coverage is thorough — 30+ new test cases covering disabled scaffold agents, suppression-only entries, fallback blocking, validation edge cases, YAML round-trip parsing, and IsEnabled() semantics.

Improvement since prior review: The force-push addressed four documentation findings from the prior review — ADR 0058 is now annotated with the enabled field, the implementation plan includes the Enabled *bool field, the agent-extraction plan has an enabled example, and the customizing-agents guide now has a "Disabling Agents" section.

Findings

Medium

  • [protected-path] .github/workflows/reusable-dispatch.yml — This file is under .github/ (protected path). The changes add an agent-check step for dispatch-level agent-enabled gating. Human approval is required for protected-path changes regardless of context. The scaffold dispatch.yml (internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml) also receives the same agent-check step, maintaining routing logic parity per AGENTS.md.

Low

  • [logic-error] .github/workflows/reusable-dispatch.yml — The yq query .agents[] | select(.name == "$STAGE") | .enabled only matches agents with an explicit YAML name field. An entry using a derived name from the source filename would not be matched. This gap is mitigated at two levels: (1) ValidateAgentEntries requires explicit name on all disabled entries (both suppression-only and with source), and (2) the CLI-level IsAgentExplicitlyDisabled() check in resolveAgentSource() provides the authoritative guard. The workflow step is a secondary defense layer. The $STAGE variable is validated upstream by the "Validate routed stage" step (regex ^[a-z][a-z0-9_-]*$), so there is no injection risk in the yq query or ::notice:: context.

  • [scope-creep] .github/workflows/reusable-dispatch.yml — Issue feat: support disabling agents via config.yaml enabled field #4026 scopes the feature to config.go, agents.go, agents_test.go, and run.go. Workflow changes and the run.go fallback guard (IsAgentExplicitlyDisabled) were not explicitly listed but are logical extensions: the workflow step avoids starting containers for disabled agents (performance), and the run.go guard prevents silently re-enabling a disabled agent via the agents-repo or disk fallback path.

Previous run

Review

Findings

Medium

  • [protected-path] .github/workflows/reusable-dispatch.yml — This file is under .github/ (protected path). The PR links to issue feat: support disabling agents via config.yaml enabled field #4026 and the changes implement a dispatch-level agent-enabled check as part of the feature. Human approval is always required for protected-path changes, regardless of context.

Low

  • [logic-error] .github/workflows/reusable-dispatch.yml — The yq query .agents[] | select(.name == "$STAGE") | .enabled only matches agents with an explicit YAML name field. Go's DerivedName() also derives names from the Source filename (e.g., source: harness/foo.yaml → name triage). An entry with source: harness/foo.yaml, enabled: false, and no explicit name would be correctly suppressed by Go code but missed by the workflow yq query. This is a narrow edge case — suppression-only entries (no source) are validated to require explicit names, and the CLI-level check in resolveAgentSource() provides a second defense layer via IsAgentExplicitlyDisabled(). Consider requiring explicit name on all disabled entries to close the gap.

  • [stale-doc] docs/ADRs/0058-agent-registration.md — ADR 0058 defines the AgentEntry schema without the new enabled field. A minor annotation noting the extension would keep readers informed. ADRs are architectural records rather than living API references, so this is informational.

  • [stale-doc] docs/plans/agent-registration.md — The implementation plan's AgentEntry struct definition does not include the Enabled *bool field. The plan is already completed (Phases 1–3 implemented), so this is retrospective staleness.

  • [missing-example] docs/plans/agent-extraction-to-agents-repo.md — Example config.yaml snippets do not demonstrate the enabled field or suppression-only entry pattern. Disabling agents is orthogonal to agent extraction, so this is a nice-to-have.

  • [missing-config-reference] docs/guides/user/customizing-agents.md — The customizing agents guide does not mention the enabled option for disabling specific agents. Adding a section here would be the most natural place for user-facing documentation of this feature.

  • [edge-case] internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml — The agent-check step's if condition includes steps.pr-check.outputs.skipped != 'true', which is not strictly necessary since downstream dispatch is already gated on pr-check. Harmless but adds coupling.

  • [scope-creep] .github/workflows/reusable-dispatch.yml — Issue feat: support disabling agents via config.yaml enabled field #4026 scopes the feature to config.go, agents.go, agents_test.go, and run.go. Workflow changes were not explicitly listed but are a logical extension of the agent-disabling feature.

Previous run (6)

Review

Re-review of force-push from 43ec352bd4137a. Prior review provenance: app-verified. Severity anchoring applied to unchanged code.

This PR adds an Enabled *bool field to AgentEntry so operators can selectively disable agents (including scaffold defaults) without removing them from configuration. The implementation is well-structured across three layers: config validation (ValidateAgentEntries), merge-set filtering (MergedAgents), and CLI fallback guarding (IsAgentExplicitlyDisabled in resolveAgentSource). Test coverage is thorough — 30+ new test cases covering disabled scaffold agents, suppression-only entries, fallback blocking, validation edge cases, YAML round-trip parsing, and IsEnabled() semantics. Documentation has been updated across ADR 0058, the agent-registration plan, the agent-extraction plan, and the customizing-agents guide.

Improvement since prior review: The force-push addressed the prior review's case-sensitivity finding — both dispatch workflows now use ascii_downcase in the yq query for case-insensitive name matching. The reusable-dispatch.yml also adds a config.yaml existence check before invoking yq. Prior low-severity findings about the yq case-sensitivity gap and error message clarity are resolved.

Findings

Medium

  • [protected-path] .github/workflows/reusable-dispatch.yml — This file is under .github/ (protected path). The changes add an agent-check step for dispatch-level agent-enabled gating with ascii_downcase for case-insensitive matching. Human approval is required for protected-path changes regardless of context. The scaffold dispatch.yml (internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml) also receives the same agent-check step, maintaining routing logic parity per AGENTS.md.
Previous run (7)

Review

Re-review of force-push from 99148e243ec352. Prior review provenance: app-verified. Severity anchoring applied to unchanged code.

This PR adds an Enabled *bool field to AgentEntry so operators can selectively disable agents (including scaffold defaults) without removing them from configuration. The implementation is well-structured across three layers: config validation (ValidateAgentEntries), merge-set filtering (MergedAgents), and CLI fallback guarding (IsAgentExplicitlyDisabled in resolveAgentSource). Test coverage is thorough — 30+ new test cases covering disabled scaffold agents, suppression-only entries, fallback blocking, validation edge cases, YAML round-trip parsing, and IsEnabled() semantics. Documentation has been updated across ADR 0058, the agent-registration plan, the agent-extraction plan, and the customizing-agents guide.

Findings

Medium

  • [protected-path] .github/workflows/reusable-dispatch.yml — This file is under .github/ (protected path). The changes add an agent-check step for dispatch-level agent-enabled gating. Human approval is required for protected-path changes regardless of context. The scaffold dispatch.yml (internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml) also receives the same agent-check step, maintaining routing logic parity per AGENTS.md.

Low

  • [logic-error] .github/workflows/reusable-dispatch.yml — The yq query in the agent-check step performs case-sensitive comparison (.name == "$STAGE") while the Go code uses case-insensitive matching (strings.ToLower). If a config entry specifies name: Triage (uppercase T) and the routed stage is triage (lowercase), the yq query would fail to match, allowing dispatch to proceed even though the Go-level resolveAgentSource would block it. This is mitigated at three levels: (1) stage names are always lowercase (enforced by the "Validate routed stage" regex ^[a-z][a-z0-9_-]*$), (2) ValidateAgentEntries requires explicit names on all disabled entries, and (3) the CLI-level IsAgentExplicitlyDisabled() check in resolveAgentSource() provides a second defense layer. The derived-name branch in the yq query (matching .source filename when .name is null) is functionally dead code for disabled entries since validation requires explicit names.

  • [edge-case] internal/config/config.go — An enabled entry (nil or true Enabled) with an empty Source correctly falls through to the existing "source must not be empty" validation error. The error message is accurate but could be more helpful (e.g., distinguishing "enabled entry must have a source" from the generic message). UX nit, not a correctness bug.

Previous run

Review

Re-review of force-push from 5fd307b99148e2. Prior review provenance: app-verified. Severity anchoring applied to unchanged code.

This PR adds an Enabled *bool field to AgentEntry so operators can selectively disable agents (including scaffold defaults) without removing them from configuration. The implementation is well-structured across three layers: config validation (ValidateAgentEntries), merge-set filtering (MergedAgents), and CLI fallback guarding (IsAgentExplicitlyDisabled in resolveAgentSource). Test coverage is thorough — 30+ new test cases covering disabled scaffold agents, suppression-only entries, fallback blocking, validation edge cases, YAML round-trip parsing, and IsEnabled() semantics.

Improvement since prior review: The force-push addressed four documentation findings from the prior review — ADR 0058 is now annotated with the enabled field, the implementation plan includes the Enabled *bool field, the agent-extraction plan has an enabled example, and the customizing-agents guide now has a "Disabling Agents" section.

Findings

Medium

  • [protected-path] .github/workflows/reusable-dispatch.yml — This file is under .github/ (protected path). The changes add an agent-check step for dispatch-level agent-enabled gating. Human approval is required for protected-path changes regardless of context. The scaffold dispatch.yml (internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml) also receives the same agent-check step, maintaining routing logic parity per AGENTS.md.

Low

  • [logic-error] .github/workflows/reusable-dispatch.yml — The yq query .agents[] | select(.name == "$STAGE") | .enabled only matches agents with an explicit YAML name field. An entry using a derived name from the source filename would not be matched. This gap is mitigated at two levels: (1) ValidateAgentEntries requires explicit name on all disabled entries (both suppression-only and with source), and (2) the CLI-level IsAgentExplicitlyDisabled() check in resolveAgentSource() provides the authoritative guard. The workflow step is a secondary defense layer. The $STAGE variable is validated upstream by the "Validate routed stage" step (regex ^[a-z][a-z0-9_-]*$), so there is no injection risk in the yq query or ::notice:: context.

  • [scope-creep] .github/workflows/reusable-dispatch.yml — Issue feat: support disabling agents via config.yaml enabled field #4026 scopes the feature to config.go, agents.go, agents_test.go, and run.go. Workflow changes and the run.go fallback guard (IsAgentExplicitlyDisabled) were not explicitly listed but are logical extensions: the workflow step avoids starting containers for disabled agents (performance), and the run.go guard prevents silently re-enabling a disabled agent via the agents-repo or disk fallback path.

Previous run

Review

Findings

Medium

  • [protected-path] .github/workflows/reusable-dispatch.yml — This file is under .github/ (protected path). The PR links to issue feat: support disabling agents via config.yaml enabled field #4026 and the changes implement a dispatch-level agent-enabled check as part of the feature. Human approval is always required for protected-path changes, regardless of context.

Low

  • [logic-error] .github/workflows/reusable-dispatch.yml — The yq query .agents[] | select(.name == "$STAGE") | .enabled only matches agents with an explicit YAML name field. Go's DerivedName() also derives names from the Source filename (e.g., source: harness/foo.yaml → name triage). An entry with source: harness/foo.yaml, enabled: false, and no explicit name would be correctly suppressed by Go code but missed by the workflow yq query. This is a narrow edge case — suppression-only entries (no source) are validated to require explicit names, and the CLI-level check in resolveAgentSource() provides a second defense layer via IsAgentExplicitlyDisabled(). Consider requiring explicit name on all disabled entries to close the gap.

  • [stale-doc] docs/ADRs/0058-agent-registration.md — ADR 0058 defines the AgentEntry schema without the new enabled field. A minor annotation noting the extension would keep readers informed. ADRs are architectural records rather than living API references, so this is informational.

  • [stale-doc] docs/plans/agent-registration.md — The implementation plan's AgentEntry struct definition does not include the Enabled *bool field. The plan is already completed (Phases 1–3 implemented), so this is retrospective staleness.

  • [missing-example] docs/plans/agent-extraction-to-agents-repo.md — Example config.yaml snippets do not demonstrate the enabled field or suppression-only entry pattern. Disabling agents is orthogonal to agent extraction, so this is a nice-to-have.

  • [missing-config-reference] docs/guides/user/customizing-agents.md — The customizing agents guide does not mention the enabled option for disabling specific agents. Adding a section here would be the most natural place for user-facing documentation of this feature.

  • [edge-case] internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml — The agent-check step's if condition includes steps.pr-check.outputs.skipped != 'true', which is not strictly necessary since downstream dispatch is already gated on pr-check. Harmless but adds coupling.

  • [scope-creep] .github/workflows/reusable-dispatch.yml — Issue feat: support disabling agents via config.yaml enabled field #4026 scopes the feature to config.go, agents.go, agents_test.go, and run.go. Workflow changes were not explicitly listed but are a logical extension of the agent-disabling feature.

Previous run (8)

Review

Re-review of force-push from 99148e243ec352. Prior review provenance: app-verified. Severity anchoring applied to unchanged code.

This PR adds an Enabled *bool field to AgentEntry so operators can selectively disable agents (including scaffold defaults) without removing them from configuration. The implementation is well-structured across three layers: config validation (ValidateAgentEntries), merge-set filtering (MergedAgents), and CLI fallback guarding (IsAgentExplicitlyDisabled in resolveAgentSource). Test coverage is thorough — 30+ new test cases covering disabled scaffold agents, suppression-only entries, fallback blocking, validation edge cases, YAML round-trip parsing, and IsEnabled() semantics. Documentation has been updated across ADR 0058, the agent-registration plan, the agent-extraction plan, and the customizing-agents guide.

Findings

Medium

  • [protected-path] .github/workflows/reusable-dispatch.yml — This file is under .github/ (protected path). The changes add an agent-check step for dispatch-level agent-enabled gating. Human approval is required for protected-path changes regardless of context. The scaffold dispatch.yml (internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml) also receives the same agent-check step, maintaining routing logic parity per AGENTS.md.

Low

  • [logic-error] .github/workflows/reusable-dispatch.yml — The yq query in the agent-check step performs case-sensitive comparison (.name == "$STAGE") while the Go code uses case-insensitive matching (strings.ToLower). If a config entry specifies name: Triage (uppercase T) and the routed stage is triage (lowercase), the yq query would fail to match, allowing dispatch to proceed even though the Go-level resolveAgentSource would block it. This is mitigated at three levels: (1) stage names are always lowercase (enforced by the "Validate routed stage" regex ^[a-z][a-z0-9_-]*$), (2) ValidateAgentEntries requires explicit names on all disabled entries, and (3) the CLI-level IsAgentExplicitlyDisabled() check in resolveAgentSource() provides a second defense layer. The derived-name branch in the yq query (matching .source filename when .name is null) is functionally dead code for disabled entries since validation requires explicit names.

  • [edge-case] internal/config/config.go — An enabled entry (nil or true Enabled) with an empty Source correctly falls through to the existing "source must not be empty" validation error. The error message is accurate but could be more helpful (e.g., distinguishing "enabled entry must have a source" from the generic message). UX nit, not a correctness bug.

Previous run (9)

Review

Re-review of force-push from 5fd307b99148e2. Prior review provenance: app-verified. Severity anchoring applied to unchanged code.

This PR adds an Enabled *bool field to AgentEntry so operators can selectively disable agents (including scaffold defaults) without removing them from configuration. The implementation is well-structured across three layers: config validation (ValidateAgentEntries), merge-set filtering (MergedAgents), and CLI fallback guarding (IsAgentExplicitlyDisabled in resolveAgentSource). Test coverage is thorough — 30+ new test cases covering disabled scaffold agents, suppression-only entries, fallback blocking, validation edge cases, YAML round-trip parsing, and IsEnabled() semantics.

Improvement since prior review: The force-push addressed four documentation findings from the prior review — ADR 0058 is now annotated with the enabled field, the implementation plan includes the Enabled *bool field, the agent-extraction plan has an enabled example, and the customizing-agents guide now has a "Disabling Agents" section.

Findings

Medium

  • [protected-path] .github/workflows/reusable-dispatch.yml — This file is under .github/ (protected path). The changes add an agent-check step for dispatch-level agent-enabled gating. Human approval is required for protected-path changes regardless of context. The scaffold dispatch.yml (internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml) also receives the same agent-check step, maintaining routing logic parity per AGENTS.md.

Low

  • [logic-error] .github/workflows/reusable-dispatch.yml — The yq query .agents[] | select(.name == "$STAGE") | .enabled only matches agents with an explicit YAML name field. An entry using a derived name from the source filename would not be matched. This gap is mitigated at two levels: (1) ValidateAgentEntries requires explicit name on all disabled entries (both suppression-only and with source), and (2) the CLI-level IsAgentExplicitlyDisabled() check in resolveAgentSource() provides the authoritative guard. The workflow step is a secondary defense layer. The $STAGE variable is validated upstream by the "Validate routed stage" step (regex ^[a-z][a-z0-9_-]*$), so there is no injection risk in the yq query or ::notice:: context.

  • [scope-creep] .github/workflows/reusable-dispatch.yml — Issue feat: support disabling agents via config.yaml enabled field #4026 scopes the feature to config.go, agents.go, agents_test.go, and run.go. Workflow changes and the run.go fallback guard (IsAgentExplicitlyDisabled) were not explicitly listed but are logical extensions: the workflow step avoids starting containers for disabled agents (performance), and the run.go guard prevents silently re-enabling a disabled agent via the agents-repo or disk fallback path.

Previous run

Review

Findings

Medium

  • [protected-path] .github/workflows/reusable-dispatch.yml — This file is under .github/ (protected path). The PR links to issue feat: support disabling agents via config.yaml enabled field #4026 and the changes implement a dispatch-level agent-enabled check as part of the feature. Human approval is always required for protected-path changes, regardless of context.

Low

  • [logic-error] .github/workflows/reusable-dispatch.yml — The yq query .agents[] | select(.name == "$STAGE") | .enabled only matches agents with an explicit YAML name field. Go's DerivedName() also derives names from the Source filename (e.g., source: harness/foo.yaml → name triage). An entry with source: harness/foo.yaml, enabled: false, and no explicit name would be correctly suppressed by Go code but missed by the workflow yq query. This is a narrow edge case — suppression-only entries (no source) are validated to require explicit names, and the CLI-level check in resolveAgentSource() provides a second defense layer via IsAgentExplicitlyDisabled(). Consider requiring explicit name on all disabled entries to close the gap.

  • [stale-doc] docs/ADRs/0058-agent-registration.md — ADR 0058 defines the AgentEntry schema without the new enabled field. A minor annotation noting the extension would keep readers informed. ADRs are architectural records rather than living API references, so this is informational.

  • [stale-doc] docs/plans/agent-registration.md — The implementation plan's AgentEntry struct definition does not include the Enabled *bool field. The plan is already completed (Phases 1–3 implemented), so this is retrospective staleness.

  • [missing-example] docs/plans/agent-extraction-to-agents-repo.md — Example config.yaml snippets do not demonstrate the enabled field or suppression-only entry pattern. Disabling agents is orthogonal to agent extraction, so this is a nice-to-have.

  • [missing-config-reference] docs/guides/user/customizing-agents.md — The customizing agents guide does not mention the enabled option for disabling specific agents. Adding a section here would be the most natural place for user-facing documentation of this feature.

  • [edge-case] internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml — The agent-check step's if condition includes steps.pr-check.outputs.skipped != 'true', which is not strictly necessary since downstream dispatch is already gated on pr-check. Harmless but adds coupling.

  • [scope-creep] .github/workflows/reusable-dispatch.yml — Issue feat: support disabling agents via config.yaml enabled field #4026 scopes the feature to config.go, agents.go, agents_test.go, and run.go. Workflow changes were not explicitly listed but are a logical extension of the agent-disabling feature.

Previous run (10)

Review

Re-review of force-push from 5fd307b99148e2. Prior review provenance: app-verified. Severity anchoring applied to unchanged code.

This PR adds an Enabled *bool field to AgentEntry so operators can selectively disable agents (including scaffold defaults) without removing them from configuration. The implementation is well-structured across three layers: config validation (ValidateAgentEntries), merge-set filtering (MergedAgents), and CLI fallback guarding (IsAgentExplicitlyDisabled in resolveAgentSource). Test coverage is thorough — 30+ new test cases covering disabled scaffold agents, suppression-only entries, fallback blocking, validation edge cases, YAML round-trip parsing, and IsEnabled() semantics.

Improvement since prior review: The force-push addressed four documentation findings from the prior review — ADR 0058 is now annotated with the enabled field, the implementation plan includes the Enabled *bool field, the agent-extraction plan has an enabled example, and the customizing-agents guide now has a "Disabling Agents" section.

Findings

Medium

  • [protected-path] .github/workflows/reusable-dispatch.yml — This file is under .github/ (protected path). The changes add an agent-check step for dispatch-level agent-enabled gating. Human approval is required for protected-path changes regardless of context. The scaffold dispatch.yml (internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml) also receives the same agent-check step, maintaining routing logic parity per AGENTS.md.

Low

  • [logic-error] .github/workflows/reusable-dispatch.yml — The yq query .agents[] | select(.name == "$STAGE") | .enabled only matches agents with an explicit YAML name field. An entry using a derived name from the source filename would not be matched. This gap is mitigated at two levels: (1) ValidateAgentEntries requires explicit name on all disabled entries (both suppression-only and with source), and (2) the CLI-level IsAgentExplicitlyDisabled() check in resolveAgentSource() provides the authoritative guard. The workflow step is a secondary defense layer. The $STAGE variable is validated upstream by the "Validate routed stage" step (regex ^[a-z][a-z0-9_-]*$), so there is no injection risk in the yq query or ::notice:: context.

  • [scope-creep] .github/workflows/reusable-dispatch.yml — Issue feat: support disabling agents via config.yaml enabled field #4026 scopes the feature to config.go, agents.go, agents_test.go, and run.go. Workflow changes and the run.go fallback guard (IsAgentExplicitlyDisabled) were not explicitly listed but are logical extensions: the workflow step avoids starting containers for disabled agents (performance), and the run.go guard prevents silently re-enabling a disabled agent via the agents-repo or disk fallback path.

Previous run (11)

Review

Findings

Medium

  • [protected-path] .github/workflows/reusable-dispatch.yml — This file is under .github/ (protected path). The PR links to issue feat: support disabling agents via config.yaml enabled field #4026 and the changes implement a dispatch-level agent-enabled check as part of the feature. Human approval is always required for protected-path changes, regardless of context.

Low

  • [logic-error] .github/workflows/reusable-dispatch.yml — The yq query .agents[] | select(.name == "$STAGE") | .enabled only matches agents with an explicit YAML name field. Go's DerivedName() also derives names from the Source filename (e.g., source: harness/triage.yaml → name triage). An entry with source: harness/foo.yaml, enabled: false, and no explicit name would be correctly suppressed by Go code but missed by the workflow yq query. This is a narrow edge case — suppression-only entries (no source) are validated to require explicit names, and the CLI-level check in resolveAgentSource() provides a second defense layer via IsAgentExplicitlyDisabled(). Consider requiring explicit name on all disabled entries to close the gap.

  • [stale-doc] docs/ADRs/0058-agent-registration.md — ADR 0058 defines the AgentEntry schema without the new enabled field. A minor annotation noting the extension would keep readers informed. ADRs are architectural records rather than living API references, so this is informational.

  • [stale-doc] docs/plans/agent-registration.md — The implementation plan's AgentEntry struct definition does not include the Enabled *bool field. The plan is already completed (Phases 1–3 implemented), so this is retrospective staleness.

  • [missing-example] docs/plans/agent-extraction-to-agents-repo.md — Example config.yaml snippets do not demonstrate the enabled field or suppression-only entry pattern. Disabling agents is orthogonal to agent extraction, so this is a nice-to-have.

  • [missing-config-reference] docs/guides/user/customizing-agents.md — The customizing agents guide does not mention the enabled option for disabling specific agents. Adding a section here would be the most natural place for user-facing documentation of this feature.

  • [edge-case] internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml — The agent-check step's if condition includes steps.pr-check.outputs.skipped != 'true', which is not strictly necessary since downstream dispatch is already gated on pr-check. Harmless but adds coupling.

  • [scope-creep] .github/workflows/reusable-dispatch.yml — Issue feat: support disabling agents via config.yaml enabled field #4026 scopes the feature to config.go, agents.go, agents_test.go, and run.go. Workflow changes were not explicitly listed but are a logical extension of the agent-disabling feature.

@fullsend-ai-review fullsend-ai-review Bot added the requires-manual-review Review requires human judgment label Jul 10, 2026
@ggallen ggallen force-pushed the agent/4026-config-enabled-field branch from 5fd307b to 99148e2 Compare July 10, 2026 17:56
@ggallen

ggallen commented Jul 10, 2026

Copy link
Copy Markdown
Member

/fs-review

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 10, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 6:02 PM UTC · Completed 6:17 PM UTC
Commit: b8a817e · View workflow run →

@fullsend-ai-review fullsend-ai-review Bot added requires-manual-review Review requires human judgment and removed requires-manual-review Review requires human judgment labels Jul 10, 2026
@ggallen ggallen force-pushed the agent/4026-config-enabled-field branch from 99148e2 to 43ec352 Compare July 10, 2026 18:27

@ascerra ascerra 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.

approve as long as testing passes

@ggallen

ggallen commented Jul 10, 2026

Copy link
Copy Markdown
Member

/fs-review

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 10, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 6:42 PM UTC · Completed 6:57 PM UTC
Commit: b8a817e · View workflow run →

@fullsend-ai-review fullsend-ai-review Bot added requires-manual-review Review requires human judgment component/harness Agent harness, config, and skills loading and removed requires-manual-review Review requires human judgment labels Jul 10, 2026
@ggallen

ggallen commented Jul 10, 2026

Copy link
Copy Markdown
Member

/fs-review

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 10, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 7:32 PM UTC · Completed 7:47 PM UTC
Commit: b8a817e · View workflow run →

@fullsend-ai-review fullsend-ai-review Bot added requires-manual-review Review requires human judgment and removed requires-manual-review Review requires human judgment labels Jul 10, 2026

@waynesun09 waynesun09 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Multi-agent review squad (Claude, Gemini, Codex reviewers) — posting the verified MEDIUM+ findings as inline comments. TL;DR: the Go layer is solid and well tested, but the new workflow agent-check gate is non-functional due to a jq-vs-yq builtin mixup (ascii_downcasedowncase), and the silent error-swallowing around the yq call is what masked it. One latent duplicate-entry bug in MergedAgents was also reproduced with a failing test. Details inline.

Comment thread .github/workflows/reusable-dispatch.yml Outdated
Comment thread .github/workflows/reusable-dispatch.yml Outdated
Comment thread internal/config/agents.go
@ggallen ggallen force-pushed the agent/4026-config-enabled-field branch from 22d221a to f72c816 Compare July 11, 2026 12:58
@ggallen

ggallen commented Jul 11, 2026

Copy link
Copy Markdown
Member

/fs-review

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 11, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 12:59 PM UTC · Completed 1:09 PM UTC
Commit: 2941769 · View workflow run →

@fullsend-ai-review fullsend-ai-review Bot added requires-manual-review Review requires human judgment and removed requires-manual-review Review requires human judgment labels Jul 11, 2026
@ggallen ggallen requested review from ggallen and waynesun09 July 11, 2026 13:13

@waynesun09 waynesun09 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Follow-up multi-agent review squad pass (4 agents dispatched: claude-coder, claude-researcher, gemini-code-review, cursor-code-review). Transparency note: the Gemini/Antigravity CLI leg failed outright (quota exhausted, ~46h reset) and the Codex/cursor-agent leg returned "no issues found" — in both cases the wrapping Claude agent did its own independent investigation instead of returning nothing. So this round is effectively 4 independent Claude review passes, not 3-model diversity.

TL;DR: the Go merge/validate/resolve logic and the dispatch-gating workflow step are correct for the core single-entry suppression case (the gaps from the earlier CHANGES_REQUESTED review and the ascii_downcase/fail-open/duplicate-entry issues above all look fixed). Four new findings below: one HIGH — validation actually rejects the disable-then-override-same-name pattern that this PR's own MergedAgents tests were written to support (related to, but distinct from, the duplicate-entries bug already fixed in the thread above) — plus three MEDIUM policy/docs gaps (an edit to the Decision section of the already-Accepted ADR 0058 without a PR-description callout, a coder-role vs code-agent-name mismatch that can make a disable entry silently no-op, and no pre-merge validation of config.yaml so a single bad enabled entry only surfaces as a whole-config failure at dispatch/run time). Details inline.

Comment thread internal/config/config.go
Comment thread docs/ADRs/0058-agent-registration.md
Comment thread docs/guides/user/customizing-agents.md
Comment thread internal/config/config.go
@waynesun09

Copy link
Copy Markdown
Member

/fs-fix #4049 (review)

@fullsend-ai-coder

fullsend-ai-coder Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Finished Fix · ✅ Success · Started 1:54 PM UTC · Completed 2:10 PM UTC
Commit: 2941769 · View workflow run →

@fullsend-ai-coder

Copy link
Copy Markdown
Contributor Author

🔧 Fix agent — iteration 1 (human-triggered)

Fixed 2 of 4 review findings. (1) Fixed HIGH-severity ValidateAgentEntries bug: validation now accepts disable-then-enable and enable-then-disable same-name patterns, matching MergedAgents semantics. Added two new validation-layer tests. (2) Fixed MEDIUM-severity docs gap: added note about agent/harness name vs role name distinction. Disagreed with 2 findings: ADR 0058 edit callout is a PR-description process issue the fix agent cannot address; config.yaml CI validation is a new feature out of scope for this PR.

Fixed (2):

  1. ValidateAgentEntries rejects disable-then-enable same-name pattern (internal/config/config.go): Changed the seen map in ValidateAgentEntries from a simple presence-check to tracking the disabled state of each entry. Same-name entries with different enabled states (disable-then-enable, enable-then-disable) are now accepted, matching MergedAgents last-writer-wins semantics. True duplicates (same name and same enabled state) are still rejected. Added two new validation tests exercising both patterns through OrgConfig.Validate().
  2. Disable name must match agent/harness name, not role name — undocumented (docs/guides/user/customizing-agents.md): Added documentation clarifying that the disabled entry name must match the agent/harness name (code, triage, review, fix, retro, prioritize), not the role name. Explicitly calls out the code vs coder distinction that can cause silent no-ops.

Disagreed (2):

  1. Edits Decision section of accepted ADR 0058 without PR-description callout: The reviewer's concern is a process issue (PR description should callout the ADR edit), not a code correctness issue. The fix agent cannot edit PR descriptions — that is a PR mutation reserved for the post-script or the PR author. The actual ADR change is a minor annotation adding one YAML example line and a comment to document the new enabled field, which is consistent with the ADR immutability policy's allowance for cross-references and short notes linking to newer decisions. The PR author should add a callout to the PR description per AGENTS.md policy.
  2. No pre-merge validation of config.yaml; bad enabled entry only surfaces at dispatch/run time: Adding a CI check or fullsend config validate subcommand is a new feature that is out of scope for this PR, which adds the enabled field to AgentEntry. This would require a new CLI command, CI workflow changes, and documentation — a separate work item. The reviewer's suggestion is valid as a follow-up but does not block this PR's correctness.

Tests: passed

Updated by fullsend fix agent

fullsend-ai-coder Bot added a commit that referenced this pull request Jul 11, 2026
ValidateAgentEntries shared a single seen map that rejected any
two entries with the same name, regardless of enabled state. This
contradicted MergedAgents which supports disable-then-enable and
enable-then-disable patterns (last-writer-wins).

Change the seen map to track the disabled state of each entry so
that same-name entries with different enabled states are accepted
(matching MergedAgents semantics), while true duplicates (two
entries with the same state) are still rejected.

Also document that the disable name must match the agent/harness
name (e.g. code), not the role name (e.g. coder), to prevent
silent no-ops.

Addresses review feedback on #4049
@ggallen ggallen force-pushed the agent/4026-config-enabled-field branch from 50eeb5d to c716b69 Compare July 11, 2026 20:48
@ggallen

ggallen commented Jul 11, 2026

Copy link
Copy Markdown
Member

/fs-review

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 11, 2026

Copy link
Copy Markdown

🤖 Finished Review · ❌ Failure · Started 8:48 PM UTC · Completed 9:05 PM UTC
Commit: 2941769 · View workflow run →

Add `Enabled *bool` to `AgentEntry` so operators can selectively disable
agents (including scaffold defaults) without removing them from config.
Nil defaults to enabled for backward compatibility.

- Filter disabled agents in `MergedAgents()` and block fallback in
  `resolveAgentSource`
- Gate dispatch on `agents[].enabled` in scaffold and reusable workflows
  with last-writer-wins semantics for enable-then-disable patterns
- Add pre-dispatch config validation to catch malformed entries
- Allow suppression-only entries (`enabled: false`, no source) to
  disable scaffold defaults by name
- Allow disable-then-enable and enable-then-disable patterns for the
  same agent name in validation

Closes #4026

Signed-off-by: Claude <noreply@anthropic.com>
Signed-off-by: Greg Allen <gallen@redhat.com>
@ggallen ggallen force-pushed the agent/4026-config-enabled-field branch from c716b69 to df6d8b6 Compare July 12, 2026 00:52
@ggallen

ggallen commented Jul 12, 2026

Copy link
Copy Markdown
Member

/fs-review

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 12, 2026

Copy link
Copy Markdown

🤖 Finished Review · ❌ Failure · Started 12:52 AM UTC · Completed 1:03 AM UTC
Commit: 2941769 · View workflow run →

@ggallen

ggallen commented Jul 12, 2026

Copy link
Copy Markdown
Member

/fs-review

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 12, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 1:04 AM UTC · Completed 1:16 AM UTC
Commit: 2941769 · View workflow run →

@fullsend-ai-review fullsend-ai-review Bot added requires-manual-review Review requires human judgment and removed requires-manual-review Review requires human judgment labels Jul 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

component/dispatch Workflow dispatch and triggers component/harness Agent harness, config, and skills loading requires-manual-review Review requires human judgment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: support disabling agents via config.yaml enabled field

3 participants