Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 4 additions & 10 deletions .github/aw/subagents.md
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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.

---

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
19 changes: 19 additions & 0 deletions pkg/cli/codemod_inline_agents.go
Original file line number Diff line number Diff line change
@@ -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,
})
}
70 changes: 70 additions & 0 deletions pkg/cli/codemod_inline_agents_test.go
Original file line number Diff line number Diff line change
@@ -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")
}
})
}
}
1 change: 1 addition & 0 deletions pkg/cli/fix_codemods.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions pkg/cli/fix_codemods_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
}

Expand Down Expand Up @@ -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",
Expand Down
10 changes: 0 additions & 10 deletions pkg/constants/feature_constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
17 changes: 7 additions & 10 deletions pkg/workflow/compiler_activation_job_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
8 changes: 3 additions & 5 deletions pkg/workflow/compiler_yaml_main_job.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
9 changes: 9 additions & 0 deletions pkg/workflow/features.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
36 changes: 36 additions & 0 deletions pkg/workflow/features_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
})
}
}
5 changes: 2 additions & 3 deletions pkg/workflow/sub_agent_step.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
Loading