diff --git a/.github/aw/subagents.md b/.github/aw/subagents.md index 3bc1f9d498b..0b41ca959a0 100644 --- a/.github/aw/subagents.md +++ b/.github/aw/subagents.md @@ -1,5 +1,5 @@ --- -description: Guide for defining inline sub-agents in workflow markdown files — syntax, feature flag, engine placement, frontmatter fields, and best practices. +description: Guide for defining inline sub-agents in workflow markdown files — syntax, engine placement, frontmatter fields, and best practices. --- # Inline Sub-Agents @@ -12,17 +12,15 @@ Inline sub-agents let you define specialised agents directly inside a workflow m ## Enabling the Feature -Inline sub-agent extraction and restoration steps are **only compiled in when the `inline-agents` feature flag is set**: +Inline sub-agent extraction and restoration steps are enabled by default: ```yaml --- engine: copilot -features: - inline-agents: true --- ``` -Without this flag the `## agent:` sections are stripped from the prompt at compile time but no upload or restore steps are generated, so the sub-agent files will not be available during the agent job. +> `features.inline-agents` is deprecated and no longer needed. Existing workflows may still include it, but it has no effect. --- @@ -99,8 +97,6 @@ For sub-agents to perform useful work they typically need access to the file sys ```yaml --- engine: copilot -features: - inline-agents: true tools: github: mode: gh-proxy @@ -172,8 +168,6 @@ Extract a repetitive sub-task (file summarisation, commit-message generation, co ```markdown --- engine: copilot -features: - inline-agents: true tools: github: mode: gh-proxy @@ -205,5 +199,5 @@ changes. Return a bulleted list, one bullet per file. - Sub-agents do not support `engine:`, `tools:`, `network:`, or `mcp-servers:` fields — those are stripped at runtime. - Sub-agents cannot define their own safe-output jobs. -- The feature requires `features.inline-agents: true` — without it the upload/restore steps are not generated. +- `features.inline-agents` is deprecated and has no effect; inline sub-agent upload/restore is always generated. - Sub-agent blocks must appear in the main workflow file body; they are not resolved inside imported shared files. diff --git a/pkg/cli/codemod_inline_agents.go b/pkg/cli/codemod_inline_agents.go new file mode 100644 index 00000000000..7e7f539aeb9 --- /dev/null +++ b/pkg/cli/codemod_inline_agents.go @@ -0,0 +1,19 @@ +package cli + +import "github.com/github/gh-aw/pkg/logger" + +var inlineAgentsCodemodLog = logger.New("cli:codemod_inline_agents") + +// getInlineAgentsFeatureRemovalCodemod removes deprecated features.inline-agents. +func getInlineAgentsFeatureRemovalCodemod() Codemod { + return newFieldRemovalCodemod(fieldRemovalCodemodConfig{ + ID: "features-inline-agents-removal", + Name: "Remove deprecated features.inline-agents", + Description: "Removes deprecated features.inline-agents. Inline sub-agents are now enabled by default.", + IntroducedIn: "1.0.0", + ParentKey: "features", + FieldKey: "inline-agents", + LogMsg: "Removed deprecated features.inline-agents", + Log: inlineAgentsCodemodLog, + }) +} diff --git a/pkg/cli/codemod_inline_agents_test.go b/pkg/cli/codemod_inline_agents_test.go new file mode 100644 index 00000000000..6322b0a7479 --- /dev/null +++ b/pkg/cli/codemod_inline_agents_test.go @@ -0,0 +1,70 @@ +//go:build !integration + +package cli + +import ( + "testing" + + "github.com/github/gh-aw/pkg/parser" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestInlineAgentsFeatureRemovalCodemod(t *testing.T) { + codemod := getInlineAgentsFeatureRemovalCodemod() + + tests := []struct { + name string + input string + expectApply bool + }{ + { + name: "removes inline-agents when true", + input: `--- +name: Test Workflow +features: + inline-agents: true + mcp-gateway: true +--- +# Test workflow`, + expectApply: true, + }, + { + name: "removes inline-agents when false", + input: `--- +name: Test Workflow +features: + inline-agents: false +--- +# Test workflow`, + expectApply: true, + }, + { + name: "does not modify when inline-agents is absent", + input: `--- +name: Test Workflow +features: + mcp-gateway: true +--- +# Test workflow`, + expectApply: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := parser.ExtractFrontmatterFromContent(tt.input) + require.NoError(t, err, "Failed to parse test input frontmatter") + + output, applied, err := codemod.Apply(tt.input, result.Frontmatter) + require.NoError(t, err, "Codemod apply should not error") + assert.Equal(t, tt.expectApply, applied, "Applied status mismatch") + + if tt.expectApply { + assert.NotContains(t, output, "inline-agents:", "Codemod should remove deprecated inline-agents flag") + } else { + assert.Equal(t, tt.input, output, "Output should be unchanged when codemod does not apply") + } + }) + } +} diff --git a/pkg/cli/fix_codemods.go b/pkg/cli/fix_codemods.go index c7b46b5106d..ff6273ed8e8 100644 --- a/pkg/cli/fix_codemods.go +++ b/pkg/cli/fix_codemods.go @@ -58,6 +58,7 @@ func GetAllCodemods() []Codemod { getDependabotPermissionsCodemod(), // Add vulnerability-alerts: read when dependabot toolset is used getGitHubReposToAllowedReposCodemod(), // Rename deprecated tools.github.repos to tools.github.allowed-repos getByokCopilotFeatureRemovalCodemod(), // Remove deprecated features.byok-copilot (Copilot BYOK is default) + getInlineAgentsFeatureRemovalCodemod(), // Remove deprecated features.inline-agents (inline sub-agents now default) getCliProxyFeatureToGitHubModeCodemod(), // Migrate features.cli-proxy: true to tools.github.mode: gh-proxy getDIFCProxyToIntegrityProxyCodemod(), // Migrate deprecated features.difc-proxy to tools.github.integrity-proxy getMountAsCLIsToCLIProxyCodemod(), // Rename tools.mount-as-clis to tools.cli-proxy and remove features.mcp-cli diff --git a/pkg/cli/fix_codemods_test.go b/pkg/cli/fix_codemods_test.go index ff554582249..9e1173e5dad 100644 --- a/pkg/cli/fix_codemods_test.go +++ b/pkg/cli/fix_codemods_test.go @@ -88,6 +88,7 @@ func TestGetAllCodemods_ContainsExpectedCodemods(t *testing.T) { "pull-request-target-checkout-false", "dependabot-toolset-permissions", "features-byok-copilot-removal", + "features-inline-agents-removal", "mount-as-clis-to-cli-proxy", } @@ -149,6 +150,7 @@ func TestGetAllCodemods_InExpectedOrder(t *testing.T) { "dependabot-toolset-permissions", "github-repos-to-allowed-repos", "features-byok-copilot-removal", + "features-inline-agents-removal", "features-cli-proxy-to-tools-github-mode", "features-difc-proxy-to-tools-github", "mount-as-clis-to-cli-proxy", diff --git a/pkg/constants/feature_constants.go b/pkg/constants/feature_constants.go index 68c8b5bea36..318a330ecdf 100644 --- a/pkg/constants/feature_constants.go +++ b/pkg/constants/feature_constants.go @@ -72,14 +72,4 @@ const ( // features: // integrity-reactions: true IntegrityReactionsFeatureFlag FeatureFlag = "integrity-reactions" - // InlineAgentsFeatureFlag enables the generation of inline sub-agent extraction and - // restoration steps in the compiled workflow. When enabled, the compiler adds a runtime - // step to extract `## agent: \`name\`` sections from the workflow markdown and write them - // to the engine-specific agents directory after the base branch restore step. - // - // Workflow frontmatter usage: - // - // features: - // inline-agents: true - InlineAgentsFeatureFlag FeatureFlag = "inline-agents" ) diff --git a/pkg/workflow/compiler_activation_job_builder.go b/pkg/workflow/compiler_activation_job_builder.go index 5238aa59c8c..dc393d39adc 100644 --- a/pkg/workflow/compiler_activation_job_builder.go +++ b/pkg/workflow/compiler_activation_job_builder.go @@ -463,17 +463,14 @@ func (c *Compiler) addActivationArtifactUploadStep(ctx *activationJobBuildContex ctx.steps = append(ctx.steps, " /tmp/gh-aw/aw-prompts/prompt-import-tree.json\n") ctx.steps = append(ctx.steps, " /tmp/gh-aw/"+constants.GithubRateLimitsFilename+"\n") ctx.steps = append(ctx.steps, " /tmp/gh-aw/base\n") - // Include the engine-specific sub-agents staging directory when inline-agents is enabled - // so inline sub-agent files written during the activation job are available to the agent job. - if v, ok := ctx.data.Features[string(constants.InlineAgentsFeatureFlag)]; ok { - if enabled, isBool := v.(bool); isBool && enabled { - engineID := "" - if ctx.data.EngineConfig != nil { - engineID = ctx.data.EngineConfig.ID - } - subAgentDir := parser.GetEngineSubAgentDir(engineID) - ctx.steps = append(ctx.steps, fmt.Sprintf(" /tmp/gh-aw/%s\n", subAgentDir)) + // Include the engine-specific sub-agents staging directory (inline sub-agents are enabled by default). + if isFeatureEnabled(constants.FeatureFlag("inline-agents"), ctx.data) { + engineID := "" + if ctx.data.EngineConfig != nil { + engineID = ctx.data.EngineConfig.ID } + subAgentDir := parser.GetEngineSubAgentDir(engineID) + ctx.steps = append(ctx.steps, fmt.Sprintf(" /tmp/gh-aw/%s\n", subAgentDir)) } ctx.steps = append(ctx.steps, " if-no-files-found: ignore\n") ctx.steps = append(ctx.steps, " retention-days: 1\n") diff --git a/pkg/workflow/compiler_yaml_main_job.go b/pkg/workflow/compiler_yaml_main_job.go index fc8633df3ae..2ebc7bfe602 100644 --- a/pkg/workflow/compiler_yaml_main_job.go +++ b/pkg/workflow/compiler_yaml_main_job.go @@ -418,11 +418,9 @@ func (c *Compiler) generateEngineInstallAndPreAgentSteps(yaml *strings.Builder, // Restore inline sub-agents written during the activation job. // This step runs AFTER the base-branch restore so the engine-specific agent directory - // is not clobbered. It is guarded by the features.inline-agents flag. - if v, ok := data.Features[string(constants.InlineAgentsFeatureFlag)]; ok { - if enabled, isBool := v.(bool); isBool && enabled { - generateRestoreInlineSubAgentsStep(yaml, data) - } + // is not clobbered. Inline sub-agents are enabled by default. + if isFeatureEnabled(constants.FeatureFlag("inline-agents"), data) { + generateRestoreInlineSubAgentsStep(yaml, data) } // Add pre-agent-steps (if any) after base-branch restore but before MCP setup. diff --git a/pkg/workflow/features.go b/pkg/workflow/features.go index bcee019ed62..26ef914f6f4 100644 --- a/pkg/workflow/features.go +++ b/pkg/workflow/features.go @@ -22,6 +22,15 @@ func isFeatureEnabled(flag constants.FeatureFlag, workflowData *WorkflowData) bo featuresLog.Printf("Checking if feature is enabled: %s", flagLower) } + // Inline sub-agents are now enabled by default and the corresponding + // frontmatter flag is deprecated/no-op. + if flagLower == "inline-agents" { + if logEnabled { + featuresLog.Printf("Feature %s is deprecated and always enabled", flagLower) + } + return true + } + // First, check if the feature is explicitly set in frontmatter. // Frontmatter values always take precedence. if enabled, found := getFeatureValueFromFrontmatter(flagLower, workflowData, logEnabled); found { diff --git a/pkg/workflow/features_test.go b/pkg/workflow/features_test.go index 4e58576c46b..8b4c8a1ff6a 100644 --- a/pkg/workflow/features_test.go +++ b/pkg/workflow/features_test.go @@ -293,3 +293,39 @@ func TestMergedFeaturesTopLevelPrecedence(t *testing.T) { t.Errorf("isFeatureEnabled(\"import-only\") = %v, want true (from import)", importOnlyResult) } } + +func TestInlineAgentsFeatureAlwaysEnabled(t *testing.T) { + t.Setenv("GH_AW_FEATURES", "") + + tests := []struct { + name string + features map[string]any + }{ + { + name: "enabled when feature absent", + features: map[string]any{}, + }, + { + name: "enabled when explicitly true", + features: map[string]any{ + "inline-agents": true, + }, + }, + { + name: "enabled when explicitly false", + features: map[string]any{ + "inline-agents": false, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + workflowData := &WorkflowData{Features: tt.features} + result := isFeatureEnabled("inline-agents", workflowData) + if !result { + t.Errorf("isFeatureEnabled(%q, %+v) = %v, want true", "inline-agents", tt.features, result) + } + }) + } +} diff --git a/pkg/workflow/sub_agent_step.go b/pkg/workflow/sub_agent_step.go index 96ac15318fa..a8535a89b2d 100644 --- a/pkg/workflow/sub_agent_step.go +++ b/pkg/workflow/sub_agent_step.go @@ -17,9 +17,8 @@ var subAgentStepLog = logger.New("workflow:sub_agent_step") // during the activation job and uploaded as part of the activation artifact. // This step restores them so the engine CLI can discover them. // -// The step is only generated when features.inline-agents is set in the workflow -// frontmatter. The shell logic lives in restore_inline_sub_agents.sh for -// maintainability and testability. +// Inline sub-agents are enabled by default. The shell logic lives in +// restore_inline_sub_agents.sh for maintainability and testability. func generateRestoreInlineSubAgentsStep(yaml *strings.Builder, data *WorkflowData) { engineID := "" if data.EngineConfig != nil { diff --git a/pkg/workflow/testdata/TestWasmGolden_CompileFixtures/basic-copilot.golden b/pkg/workflow/testdata/TestWasmGolden_CompileFixtures/basic-copilot.golden index 7cc18b3d0c2..6cc07338b13 100644 --- a/pkg/workflow/testdata/TestWasmGolden_CompileFixtures/basic-copilot.golden +++ b/pkg/workflow/testdata/TestWasmGolden_CompileFixtures/basic-copilot.golden @@ -244,6 +244,7 @@ jobs: /tmp/gh-aw/aw-prompts/prompt-import-tree.json /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/base + /tmp/gh-aw/.github/agents if-no-files-found: ignore retention-days: 1 @@ -346,6 +347,11 @@ jobs: GH_AW_AGENT_FOLDERS: ".agents .claude .codex .crush .gemini .github .opencode .pi" GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh" + - name: Restore inline sub-agents from activation artifact + env: + GH_AW_SUB_AGENT_DIR: ".github/agents" + GH_AW_SUB_AGENT_EXT: ".agent.md" + run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_sub_agents.sh" - name: Download container images run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.41 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41 ghcr.io/github/gh-aw-firewall/squid:0.25.41 ghcr.io/github/gh-aw-mcpg:v0.3.6 ghcr.io/github/github-mcp-server:v1.0.3 - name: Start MCP Gateway diff --git a/pkg/workflow/testdata/TestWasmGolden_CompileFixtures/playwright-cli-mode.golden b/pkg/workflow/testdata/TestWasmGolden_CompileFixtures/playwright-cli-mode.golden index 90f8bb482a7..e413d667db0 100644 --- a/pkg/workflow/testdata/TestWasmGolden_CompileFixtures/playwright-cli-mode.golden +++ b/pkg/workflow/testdata/TestWasmGolden_CompileFixtures/playwright-cli-mode.golden @@ -255,6 +255,7 @@ jobs: /tmp/gh-aw/aw-prompts/prompt-import-tree.json /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/base + /tmp/gh-aw/.github/agents if-no-files-found: ignore retention-days: 1 @@ -360,6 +361,11 @@ jobs: GH_AW_AGENT_FOLDERS: ".agents .claude .codex .crush .gemini .github .opencode .pi" GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh" + - name: Restore inline sub-agents from activation artifact + env: + GH_AW_SUB_AGENT_DIR: ".github/agents" + GH_AW_SUB_AGENT_EXT: ".agent.md" + run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_sub_agents.sh" - name: Download container images run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.41 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41 ghcr.io/github/gh-aw-firewall/squid:0.25.41 ghcr.io/github/gh-aw-mcpg:v0.3.6 ghcr.io/github/github-mcp-server:v1.0.3 - name: Start MCP Gateway diff --git a/pkg/workflow/testdata/TestWasmGolden_CompileFixtures/smoke-copilot.golden b/pkg/workflow/testdata/TestWasmGolden_CompileFixtures/smoke-copilot.golden index 1e45d326fff..b98e4b9d20f 100644 --- a/pkg/workflow/testdata/TestWasmGolden_CompileFixtures/smoke-copilot.golden +++ b/pkg/workflow/testdata/TestWasmGolden_CompileFixtures/smoke-copilot.golden @@ -356,6 +356,7 @@ jobs: /tmp/gh-aw/aw-prompts/prompt-import-tree.json /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/base + /tmp/gh-aw/.github/agents if-no-files-found: ignore retention-days: 1 @@ -499,6 +500,11 @@ jobs: GH_AW_AGENT_FOLDERS: ".agents .claude .codex .crush .gemini .github .opencode .pi" GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh" + - name: Restore inline sub-agents from activation artifact + env: + GH_AW_SUB_AGENT_DIR: ".github/agents" + GH_AW_SUB_AGENT_EXT: ".agent.md" + run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_sub_agents.sh" - name: Download container images run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.41 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41 ghcr.io/github/gh-aw-firewall/squid:0.25.41 ghcr.io/github/gh-aw-mcpg:v0.3.6 ghcr.io/github/github-mcp-server:v1.0.3 ghcr.io/github/serena-mcp-server:latest mcr.microsoft.com/playwright/mcp - name: Install gh-aw extension diff --git a/pkg/workflow/testdata/TestWasmGolden_CompileFixtures/with-imports.golden b/pkg/workflow/testdata/TestWasmGolden_CompileFixtures/with-imports.golden index 7e3d8c2f9a3..00769b25d61 100644 --- a/pkg/workflow/testdata/TestWasmGolden_CompileFixtures/with-imports.golden +++ b/pkg/workflow/testdata/TestWasmGolden_CompileFixtures/with-imports.golden @@ -245,6 +245,7 @@ jobs: /tmp/gh-aw/aw-prompts/prompt-import-tree.json /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/base + /tmp/gh-aw/.github/agents if-no-files-found: ignore retention-days: 1 @@ -347,6 +348,11 @@ jobs: GH_AW_AGENT_FOLDERS: ".agents .claude .codex .crush .gemini .github .opencode .pi" GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh" + - name: Restore inline sub-agents from activation artifact + env: + GH_AW_SUB_AGENT_DIR: ".github/agents" + GH_AW_SUB_AGENT_EXT: ".agent.md" + run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_sub_agents.sh" - name: Download container images run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.41 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41 ghcr.io/github/gh-aw-firewall/squid:0.25.41 ghcr.io/github/gh-aw-mcpg:v0.3.6 ghcr.io/github/github-mcp-server:v1.0.3 - name: Start MCP Gateway