diff --git a/.github/aw/subagents.md b/.github/aw/subagents.md index 0b41ca959a0..ea854247e3e 100644 --- a/.github/aw/subagents.md +++ b/.github/aw/subagents.md @@ -6,8 +6,6 @@ description: Guide for defining inline sub-agents in workflow markdown files — Inline sub-agents let you define specialised agents directly inside a workflow markdown file. At runtime the sub-agent sections are extracted from the prompt (after `{{#runtime-import}}` macros are resolved) and written to the engine-specific agents directory so the engine CLI can discover and invoke them. -> **Experimental feature.** Compilation emits `⚠ Using experimental feature: inline-sub-agents` whenever a workflow contains at least one `## agent:` block. - --- ## Enabling the Feature @@ -21,6 +19,8 @@ engine: copilot ``` > `features.inline-agents` is deprecated and no longer needed. Existing workflows may still include it, but it has no effect. +> +> `inline-sub-agents: false` is not supported and fails compilation. Remove the field. --- @@ -200,4 +200,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. - `features.inline-agents` is deprecated and has no effect; inline sub-agent upload/restore is always generated. +- `inline-sub-agents: false` is rejected at compile time; inline sub-agents cannot be disabled. - Sub-agent blocks must appear in the main workflow file body; they are not resolved inside imported shared files. diff --git a/docs/src/content/docs/patterns/data-ops.md b/docs/src/content/docs/patterns/data-ops.md index d809b67d287..7ebad0fca8c 100644 --- a/docs/src/content/docs/patterns/data-ops.md +++ b/docs/src/content/docs/patterns/data-ops.md @@ -186,11 +186,9 @@ main agent: → orchestrates sub-agents, synthesizes final report (high-reas ### Enabling inline sub-agents -Add the `inline-agents` feature flag and the `cli-proxy` tool so sub-agents can make authenticated GitHub API calls: +Inline sub-agents are enabled by default. Add `cli-proxy` so sub-agents can make authenticated GitHub API calls: ```yaml -features: - inline-agents: true tools: cli-proxy: true ``` diff --git a/docs/src/content/docs/reference/inline-sub-agents.md b/docs/src/content/docs/reference/inline-sub-agents.md index 3b101699098..96f40e62924 100644 --- a/docs/src/content/docs/reference/inline-sub-agents.md +++ b/docs/src/content/docs/reference/inline-sub-agents.md @@ -7,6 +7,8 @@ sidebar: An inline sub-agent is a named agent definition embedded directly in a workflow markdown file. Instead of creating a separate file in `.github/agents/`, you define the agent's frontmatter and instructions in a dedicated section of the same workflow file. +Inline sub-agents are enabled by default. `features.inline-agents` is deprecated/no-op, and `inline-sub-agents: false` is rejected at compile time. + ## Syntax Start a sub-agent block with a level-2 heading in the following form: diff --git a/pkg/cli/codemod_inline_agents_test.go b/pkg/cli/codemod_inline_agents_test.go index 6322b0a7479..70f43fbcffa 100644 --- a/pkg/cli/codemod_inline_agents_test.go +++ b/pkg/cli/codemod_inline_agents_test.go @@ -12,6 +12,7 @@ import ( func TestInlineAgentsFeatureRemovalCodemod(t *testing.T) { codemod := getInlineAgentsFeatureRemovalCodemod() + assert.Equal(t, "1.0.0", codemod.IntroducedIn) tests := []struct { name string diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index 54ea8bb6792..dcdfe276e50 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -2720,6 +2720,11 @@ } ] }, + "inline-sub-agents": { + "type": "boolean", + "description": "Deprecated switch for inline sub-agent support. Inline sub-agents are enabled by default. Setting this to false is not supported and causes a compilation error.", + "examples": [true] + }, "features": { "description": "Feature flags and configuration options for experimental or optional features in the workflow. Each feature can be a boolean flag or a string value. The 'action-tag' feature (string) specifies the tag or SHA to use when referencing actions/setup in compiled workflows (for testing purposes only).", "type": "object", diff --git a/pkg/workflow/compiler_orchestrator_tools.go b/pkg/workflow/compiler_orchestrator_tools.go index acc074373d1..0fd0f112dd6 100644 --- a/pkg/workflow/compiler_orchestrator_tools.go +++ b/pkg/workflow/compiler_orchestrator_tools.go @@ -61,11 +61,7 @@ func (c *Compiler) processToolsAndMarkdown(result *parser.FrontmatterResult, cle return nil, fmt.Errorf("failed to extract inline sub-agents: %w", err) } orchestratorToolsLog.Printf("Effective markdown after stripping sub-agent sections: %d bytes", len(effectiveMarkdown)) - if len(subAgents) > 0 { - fmt.Fprintln(os.Stderr, console.FormatWarningMessage("Using experimental feature: inline-sub-agents")) - c.IncrementWarningCount() - } - + orchestratorToolsLog.Printf("Extracted inline sub-agents: count=%d", len(subAgents)) // Surface best-effort sub-agent frontmatter warnings collected during import BFS traversal. for _, w := range importsResult.Warnings { fmt.Fprintln(os.Stderr, console.FormatWarningMessage(w)) diff --git a/pkg/workflow/compiler_types.go b/pkg/workflow/compiler_types.go index 68e6d856df2..5260784a3a5 100644 --- a/pkg/workflow/compiler_types.go +++ b/pkg/workflow/compiler_types.go @@ -523,6 +523,7 @@ type WorkflowData struct { ActionMode ActionMode // action mode for workflow compilation (dev, release, script) HasExplicitGitHubTool bool // true if tools.github was explicitly configured in frontmatter InlinedImports bool // if true, inline all imports at compile time (from inlined-imports frontmatter field) + InlineSubAgentsDisabled bool // true when inline-sub-agents: false is set in frontmatter (rejected at compile time) CheckoutConfigs []*CheckoutConfig // user-configured checkout settings from frontmatter CheckoutDisabled bool // true when checkout: false is set in frontmatter HasDispatchItemNumber bool // true when workflow_dispatch has item_number input (generated by label trigger shorthand) diff --git a/pkg/workflow/compiler_validators.go b/pkg/workflow/compiler_validators.go index 89a1f4fc327..74ef2c7f08d 100644 --- a/pkg/workflow/compiler_validators.go +++ b/pkg/workflow/compiler_validators.go @@ -55,6 +55,12 @@ func (c *Compiler) validateFeatureConfig(workflowData *WorkflowData, markdownPat return formatCompilerError(markdownPath, "error", err.Error(), err) } + // Inline sub-agents are always enabled and can no longer be disabled. + if workflowData.InlineSubAgentsDisabled { + msg := "inline-sub-agents: false is not supported. Inline sub-agents are always enabled. Remove inline-sub-agents from your frontmatter." + return formatCompilerError(markdownPath, "error", msg, errors.New("inline-sub-agents cannot be set to false")) + } + // Check for action-mode feature flag override if workflowData.Features != nil { if actionModeVal, exists := workflowData.Features["action-mode"]; exists { diff --git a/pkg/workflow/compiler_validators_test.go b/pkg/workflow/compiler_validators_test.go index 91c34062189..6ab63d15178 100644 --- a/pkg/workflow/compiler_validators_test.go +++ b/pkg/workflow/compiler_validators_test.go @@ -67,33 +67,38 @@ func TestValidateFeatureConfig(t *testing.T) { tests := []struct { name string features map[string]any + inlineDisable bool shouldError bool errorContains string }{ { - name: "no features", - features: nil, - shouldError: false, + name: "no features", + features: nil, + inlineDisable: false, + shouldError: false, }, { name: "valid action-mode dev", features: map[string]any{ "action-mode": "dev", }, - shouldError: false, + inlineDisable: false, + shouldError: false, }, { name: "valid action-mode release", features: map[string]any{ "action-mode": "release", }, - shouldError: false, + inlineDisable: false, + shouldError: false, }, { name: "invalid action-mode", features: map[string]any{ "action-mode": "invalid-mode", }, + inlineDisable: false, shouldError: true, errorContains: "invalid action-mode feature flag", }, @@ -102,7 +107,15 @@ func TestValidateFeatureConfig(t *testing.T) { features: map[string]any{ "action-mode": "", }, - shouldError: false, + inlineDisable: false, + shouldError: false, + }, + { + name: "inline-sub-agents false is rejected", + features: nil, + inlineDisable: true, + shouldError: true, + errorContains: "inline-sub-agents: false is not supported", }, } @@ -113,10 +126,11 @@ func TestValidateFeatureConfig(t *testing.T) { compiler := NewCompiler() workflowData := &WorkflowData{ - Name: "Test", - MarkdownContent: "# Test", - AI: "copilot", - Features: tt.features, + Name: "Test", + MarkdownContent: "# Test", + AI: "copilot", + Features: tt.features, + InlineSubAgentsDisabled: tt.inlineDisable, } err := compiler.validateFeatureConfig(workflowData, markdownPath) diff --git a/pkg/workflow/frontmatter_serialization.go b/pkg/workflow/frontmatter_serialization.go index 1396d9af3a7..5c3a15f8726 100644 --- a/pkg/workflow/frontmatter_serialization.go +++ b/pkg/workflow/frontmatter_serialization.go @@ -162,6 +162,9 @@ func (fc *FrontmatterConfig) ToMap() map[string]any { if fc.Features != nil { result["features"] = fc.Features } + if fc.InlineSubAgents != nil { + result["inline-sub-agents"] = *fc.InlineSubAgents + } if fc.Env != nil { result["env"] = fc.Env } diff --git a/pkg/workflow/frontmatter_types.go b/pkg/workflow/frontmatter_types.go index 21b2a665a15..50805b9b3d4 100644 --- a/pkg/workflow/frontmatter_types.go +++ b/pkg/workflow/frontmatter_types.go @@ -274,9 +274,12 @@ type FrontmatterConfig struct { Sandbox *SandboxConfig `json:"sandbox,omitempty"` // Feature flags and other settings - Features map[string]any `json:"features,omitempty"` // Dynamic feature flags - Env map[string]string `json:"env,omitempty"` - Secrets map[string]any `json:"secrets,omitempty"` + Features map[string]any `json:"features,omitempty"` // Dynamic feature flags + // Deprecated: as of v1.1.0, inline sub-agents are always enabled. + // Remove this field from frontmatter. Setting false causes a compilation error. + InlineSubAgents *bool `json:"inline-sub-agents,omitempty"` + Env map[string]string `json:"env,omitempty"` + Secrets map[string]any `json:"secrets,omitempty"` // Workflow execution settings RunsOn string `json:"runs-on,omitempty"` diff --git a/pkg/workflow/frontmatter_types_test.go b/pkg/workflow/frontmatter_types_test.go index 2a32b455767..f5b1e522945 100644 --- a/pkg/workflow/frontmatter_types_test.go +++ b/pkg/workflow/frontmatter_types_test.go @@ -52,6 +52,24 @@ func TestParseFrontmatterConfig(t *testing.T) { } }) + t.Run("parses inline-sub-agents boolean", func(t *testing.T) { + frontmatter := map[string]any{ + "inline-sub-agents": false, + } + + config, err := ParseFrontmatterConfig(frontmatter) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if config.InlineSubAgents == nil { + t.Fatal("InlineSubAgents should not be nil") + } + if *config.InlineSubAgents { + t.Error("InlineSubAgents should be false") + } + }) + t.Run("parses complete workflow config", func(t *testing.T) { frontmatter := map[string]any{ "name": "full-workflow", diff --git a/pkg/workflow/workflow_builder.go b/pkg/workflow/workflow_builder.go index c076055a1bd..915b7deace7 100644 --- a/pkg/workflow/workflow_builder.go +++ b/pkg/workflow/workflow_builder.go @@ -121,6 +121,17 @@ func (c *Compiler) buildInitialWorkflowData( } } + // Populate inline-sub-agents disable flag: explicit false is rejected during validation. + if toolsResult.parsedFrontmatter != nil && toolsResult.parsedFrontmatter.InlineSubAgents != nil { + workflowData.InlineSubAgentsDisabled = !*toolsResult.parsedFrontmatter.InlineSubAgents + } else if rawVal, ok := result.Frontmatter["inline-sub-agents"]; ok { + // Fall back to raw frontmatter parsing when full ParseFrontmatterConfig fails + // (e.g. due to unrecognized config shapes in other frontmatter sections). + if boolVal, ok := rawVal.(bool); ok { + workflowData.InlineSubAgentsDisabled = !boolVal + } + } + // Populate stale-check flag: disabled when on.stale-check: false is set in frontmatter. if onVal, ok := result.Frontmatter["on"]; ok { if onMap, ok := onVal.(map[string]any); ok {