Skip to content

Commit 94efd27

Browse files
Copilotpelikhan
andauthored
Enable inline sub-agents by default and deprecate features.inline-agents with auto-removal codemod (#30661)
* Enable inline-agents by default and add deprecation codemod Agent-Logs-Url: https://github.com/github/gh-aw/sessions/87668b1f-e80f-415b-b946-ad966d6a547c Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> * Plan remove inline-agents constant Agent-Logs-Url: https://github.com/github/gh-aw/sessions/7f0dd052-ff02-4dc5-be8b-13a30d9a9bd4 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> * Revert accidental lockfile-only update commit Agent-Logs-Url: https://github.com/github/gh-aw/sessions/7f0dd052-ff02-4dc5-be8b-13a30d9a9bd4 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> * Remove deprecated InlineAgentsFeatureFlag constant Agent-Logs-Url: https://github.com/github/gh-aw/sessions/7f0dd052-ff02-4dc5-be8b-13a30d9a9bd4 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> * Simplify inline-agents test flag literal Agent-Logs-Url: https://github.com/github/gh-aw/sessions/7f0dd052-ff02-4dc5-be8b-13a30d9a9bd4 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> Co-authored-by: Peli de Halleux <pelikhan@users.noreply.github.com>
1 parent fc8182d commit 94efd27

15 files changed

Lines changed: 177 additions & 38 deletions

.github/aw/subagents.md

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
description: Guide for defining inline sub-agents in workflow markdown files — syntax, feature flag, engine placement, frontmatter fields, and best practices.
2+
description: Guide for defining inline sub-agents in workflow markdown files — syntax, engine placement, frontmatter fields, and best practices.
33
---
44

55
# Inline Sub-Agents
@@ -12,17 +12,15 @@ Inline sub-agents let you define specialised agents directly inside a workflow m
1212

1313
## Enabling the Feature
1414

15-
Inline sub-agent extraction and restoration steps are **only compiled in when the `inline-agents` feature flag is set**:
15+
Inline sub-agent extraction and restoration steps are enabled by default:
1616

1717
```yaml
1818
---
1919
engine: copilot
20-
features:
21-
inline-agents: true
2220
---
2321
```
2422

25-
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.
23+
> `features.inline-agents` is deprecated and no longer needed. Existing workflows may still include it, but it has no effect.
2624
2725
---
2826

@@ -99,8 +97,6 @@ For sub-agents to perform useful work they typically need access to the file sys
9997
```yaml
10098
---
10199
engine: copilot
102-
features:
103-
inline-agents: true
104100
tools:
105101
github:
106102
mode: gh-proxy
@@ -172,8 +168,6 @@ Extract a repetitive sub-task (file summarisation, commit-message generation, co
172168
```markdown
173169
---
174170
engine: copilot
175-
features:
176-
inline-agents: true
177171
tools:
178172
github:
179173
mode: gh-proxy
@@ -205,5 +199,5 @@ changes. Return a bulleted list, one bullet per file.
205199

206200
- Sub-agents do not support `engine:`, `tools:`, `network:`, or `mcp-servers:` fields — those are stripped at runtime.
207201
- Sub-agents cannot define their own safe-output jobs.
208-
- The feature requires `features.inline-agents: true` — without it the upload/restore steps are not generated.
202+
- `features.inline-agents` is deprecated and has no effect; inline sub-agent upload/restore is always generated.
209203
- Sub-agent blocks must appear in the main workflow file body; they are not resolved inside imported shared files.

pkg/cli/codemod_inline_agents.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package cli
2+
3+
import "github.com/github/gh-aw/pkg/logger"
4+
5+
var inlineAgentsCodemodLog = logger.New("cli:codemod_inline_agents")
6+
7+
// getInlineAgentsFeatureRemovalCodemod removes deprecated features.inline-agents.
8+
func getInlineAgentsFeatureRemovalCodemod() Codemod {
9+
return newFieldRemovalCodemod(fieldRemovalCodemodConfig{
10+
ID: "features-inline-agents-removal",
11+
Name: "Remove deprecated features.inline-agents",
12+
Description: "Removes deprecated features.inline-agents. Inline sub-agents are now enabled by default.",
13+
IntroducedIn: "1.0.0",
14+
ParentKey: "features",
15+
FieldKey: "inline-agents",
16+
LogMsg: "Removed deprecated features.inline-agents",
17+
Log: inlineAgentsCodemodLog,
18+
})
19+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
//go:build !integration
2+
3+
package cli
4+
5+
import (
6+
"testing"
7+
8+
"github.com/github/gh-aw/pkg/parser"
9+
"github.com/stretchr/testify/assert"
10+
"github.com/stretchr/testify/require"
11+
)
12+
13+
func TestInlineAgentsFeatureRemovalCodemod(t *testing.T) {
14+
codemod := getInlineAgentsFeatureRemovalCodemod()
15+
16+
tests := []struct {
17+
name string
18+
input string
19+
expectApply bool
20+
}{
21+
{
22+
name: "removes inline-agents when true",
23+
input: `---
24+
name: Test Workflow
25+
features:
26+
inline-agents: true
27+
mcp-gateway: true
28+
---
29+
# Test workflow`,
30+
expectApply: true,
31+
},
32+
{
33+
name: "removes inline-agents when false",
34+
input: `---
35+
name: Test Workflow
36+
features:
37+
inline-agents: false
38+
---
39+
# Test workflow`,
40+
expectApply: true,
41+
},
42+
{
43+
name: "does not modify when inline-agents is absent",
44+
input: `---
45+
name: Test Workflow
46+
features:
47+
mcp-gateway: true
48+
---
49+
# Test workflow`,
50+
expectApply: false,
51+
},
52+
}
53+
54+
for _, tt := range tests {
55+
t.Run(tt.name, func(t *testing.T) {
56+
result, err := parser.ExtractFrontmatterFromContent(tt.input)
57+
require.NoError(t, err, "Failed to parse test input frontmatter")
58+
59+
output, applied, err := codemod.Apply(tt.input, result.Frontmatter)
60+
require.NoError(t, err, "Codemod apply should not error")
61+
assert.Equal(t, tt.expectApply, applied, "Applied status mismatch")
62+
63+
if tt.expectApply {
64+
assert.NotContains(t, output, "inline-agents:", "Codemod should remove deprecated inline-agents flag")
65+
} else {
66+
assert.Equal(t, tt.input, output, "Output should be unchanged when codemod does not apply")
67+
}
68+
})
69+
}
70+
}

pkg/cli/fix_codemods.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ func GetAllCodemods() []Codemod {
5858
getDependabotPermissionsCodemod(), // Add vulnerability-alerts: read when dependabot toolset is used
5959
getGitHubReposToAllowedReposCodemod(), // Rename deprecated tools.github.repos to tools.github.allowed-repos
6060
getByokCopilotFeatureRemovalCodemod(), // Remove deprecated features.byok-copilot (Copilot BYOK is default)
61+
getInlineAgentsFeatureRemovalCodemod(), // Remove deprecated features.inline-agents (inline sub-agents now default)
6162
getCliProxyFeatureToGitHubModeCodemod(), // Migrate features.cli-proxy: true to tools.github.mode: gh-proxy
6263
getDIFCProxyToIntegrityProxyCodemod(), // Migrate deprecated features.difc-proxy to tools.github.integrity-proxy
6364
getMountAsCLIsToCLIProxyCodemod(), // Rename tools.mount-as-clis to tools.cli-proxy and remove features.mcp-cli

pkg/cli/fix_codemods_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ func TestGetAllCodemods_ContainsExpectedCodemods(t *testing.T) {
8888
"pull-request-target-checkout-false",
8989
"dependabot-toolset-permissions",
9090
"features-byok-copilot-removal",
91+
"features-inline-agents-removal",
9192
"mount-as-clis-to-cli-proxy",
9293
}
9394

@@ -149,6 +150,7 @@ func TestGetAllCodemods_InExpectedOrder(t *testing.T) {
149150
"dependabot-toolset-permissions",
150151
"github-repos-to-allowed-repos",
151152
"features-byok-copilot-removal",
153+
"features-inline-agents-removal",
152154
"features-cli-proxy-to-tools-github-mode",
153155
"features-difc-proxy-to-tools-github",
154156
"mount-as-clis-to-cli-proxy",

pkg/constants/feature_constants.go

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,4 @@ const (
7272
// features:
7373
// integrity-reactions: true
7474
IntegrityReactionsFeatureFlag FeatureFlag = "integrity-reactions"
75-
// InlineAgentsFeatureFlag enables the generation of inline sub-agent extraction and
76-
// restoration steps in the compiled workflow. When enabled, the compiler adds a runtime
77-
// step to extract `## agent: \`name\`` sections from the workflow markdown and write them
78-
// to the engine-specific agents directory after the base branch restore step.
79-
//
80-
// Workflow frontmatter usage:
81-
//
82-
// features:
83-
// inline-agents: true
84-
InlineAgentsFeatureFlag FeatureFlag = "inline-agents"
8575
)

pkg/workflow/compiler_activation_job_builder.go

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -463,17 +463,14 @@ func (c *Compiler) addActivationArtifactUploadStep(ctx *activationJobBuildContex
463463
ctx.steps = append(ctx.steps, " /tmp/gh-aw/aw-prompts/prompt-import-tree.json\n")
464464
ctx.steps = append(ctx.steps, " /tmp/gh-aw/"+constants.GithubRateLimitsFilename+"\n")
465465
ctx.steps = append(ctx.steps, " /tmp/gh-aw/base\n")
466-
// Include the engine-specific sub-agents staging directory when inline-agents is enabled
467-
// so inline sub-agent files written during the activation job are available to the agent job.
468-
if v, ok := ctx.data.Features[string(constants.InlineAgentsFeatureFlag)]; ok {
469-
if enabled, isBool := v.(bool); isBool && enabled {
470-
engineID := ""
471-
if ctx.data.EngineConfig != nil {
472-
engineID = ctx.data.EngineConfig.ID
473-
}
474-
subAgentDir := parser.GetEngineSubAgentDir(engineID)
475-
ctx.steps = append(ctx.steps, fmt.Sprintf(" /tmp/gh-aw/%s\n", subAgentDir))
466+
// Include the engine-specific sub-agents staging directory (inline sub-agents are enabled by default).
467+
if isFeatureEnabled(constants.FeatureFlag("inline-agents"), ctx.data) {
468+
engineID := ""
469+
if ctx.data.EngineConfig != nil {
470+
engineID = ctx.data.EngineConfig.ID
476471
}
472+
subAgentDir := parser.GetEngineSubAgentDir(engineID)
473+
ctx.steps = append(ctx.steps, fmt.Sprintf(" /tmp/gh-aw/%s\n", subAgentDir))
477474
}
478475
ctx.steps = append(ctx.steps, " if-no-files-found: ignore\n")
479476
ctx.steps = append(ctx.steps, " retention-days: 1\n")

pkg/workflow/compiler_yaml_main_job.go

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -418,11 +418,9 @@ func (c *Compiler) generateEngineInstallAndPreAgentSteps(yaml *strings.Builder,
418418

419419
// Restore inline sub-agents written during the activation job.
420420
// This step runs AFTER the base-branch restore so the engine-specific agent directory
421-
// is not clobbered. It is guarded by the features.inline-agents flag.
422-
if v, ok := data.Features[string(constants.InlineAgentsFeatureFlag)]; ok {
423-
if enabled, isBool := v.(bool); isBool && enabled {
424-
generateRestoreInlineSubAgentsStep(yaml, data)
425-
}
421+
// is not clobbered. Inline sub-agents are enabled by default.
422+
if isFeatureEnabled(constants.FeatureFlag("inline-agents"), data) {
423+
generateRestoreInlineSubAgentsStep(yaml, data)
426424
}
427425

428426
// Add pre-agent-steps (if any) after base-branch restore but before MCP setup.

pkg/workflow/features.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,15 @@ func isFeatureEnabled(flag constants.FeatureFlag, workflowData *WorkflowData) bo
2222
featuresLog.Printf("Checking if feature is enabled: %s", flagLower)
2323
}
2424

25+
// Inline sub-agents are now enabled by default and the corresponding
26+
// frontmatter flag is deprecated/no-op.
27+
if flagLower == "inline-agents" {
28+
if logEnabled {
29+
featuresLog.Printf("Feature %s is deprecated and always enabled", flagLower)
30+
}
31+
return true
32+
}
33+
2534
// First, check if the feature is explicitly set in frontmatter.
2635
// Frontmatter values always take precedence.
2736
if enabled, found := getFeatureValueFromFrontmatter(flagLower, workflowData, logEnabled); found {

pkg/workflow/features_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,3 +293,39 @@ func TestMergedFeaturesTopLevelPrecedence(t *testing.T) {
293293
t.Errorf("isFeatureEnabled(\"import-only\") = %v, want true (from import)", importOnlyResult)
294294
}
295295
}
296+
297+
func TestInlineAgentsFeatureAlwaysEnabled(t *testing.T) {
298+
t.Setenv("GH_AW_FEATURES", "")
299+
300+
tests := []struct {
301+
name string
302+
features map[string]any
303+
}{
304+
{
305+
name: "enabled when feature absent",
306+
features: map[string]any{},
307+
},
308+
{
309+
name: "enabled when explicitly true",
310+
features: map[string]any{
311+
"inline-agents": true,
312+
},
313+
},
314+
{
315+
name: "enabled when explicitly false",
316+
features: map[string]any{
317+
"inline-agents": false,
318+
},
319+
},
320+
}
321+
322+
for _, tt := range tests {
323+
t.Run(tt.name, func(t *testing.T) {
324+
workflowData := &WorkflowData{Features: tt.features}
325+
result := isFeatureEnabled("inline-agents", workflowData)
326+
if !result {
327+
t.Errorf("isFeatureEnabled(%q, %+v) = %v, want true", "inline-agents", tt.features, result)
328+
}
329+
})
330+
}
331+
}

0 commit comments

Comments
 (0)