Skip to content
Closed
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
6 changes: 3 additions & 3 deletions docs/src/content/docs/reference/frontmatter.md
Original file line number Diff line number Diff line change
Expand Up @@ -423,15 +423,15 @@ Debug workflow using script mode for custom actions.

#### Copilot BYOK Mode (`features.byok-copilot`)

Enables Copilot offline Bring Your Own Key (BYOK) mode with a single flag, bundling three required behaviors: injecting a dummy `COPILOT_API_KEY` to trigger the AWF BYOK runtime path, implicitly enabling `cli-proxy`, and forcing the Copilot CLI to install at `latest` (ignoring any pinned `engine.version`).
`byok-copilot` is **enabled by default** for Copilot workflows. It enables Copilot offline Bring Your Own Key (BYOK) mode by bundling three behaviors: injecting a dummy `COPILOT_API_KEY` to trigger the AWF BYOK runtime path, implicitly enabling `cli-proxy`, and forcing the Copilot CLI to install at `latest` (ignoring any pinned `engine.version`).

```yaml wrap
engine: copilot
features:
byok-copilot: true
byok-copilot: false
```

Without this flag, BYOK mode requires manual composition of all three behaviors. With `byok-copilot: true`, the compiler handles the wiring automatically.
Set `features.byok-copilot: false` to opt out.

> [!IMPORTANT]
> `byok-copilot` is a gh-aw convenience extension point, not an enforcement boundary. gh-aw does not enforce Copilot BYOK usage.
Expand Down
2 changes: 1 addition & 1 deletion docs/src/content/docs/reference/glossary.md
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ See [Engines Reference](/gh-aw/reference/engines/).

### Feature Flags (`features:`)

A frontmatter section that enables experimental or optional compiler and runtime behaviors as key-value pairs. Feature flags provide controlled access to new capabilities before they become defaults or are fully stabilized. Common flags include `action-mode` (controls how custom action references are compiled), `copilot-requests` (enables GitHub Actions token authentication for Copilot; currently in **private preview** — will not work unless your account has been onboarded), `byok-copilot` (enables Copilot offline BYOK mode with dummy `COPILOT_API_KEY`, API proxy sidecar, implicit `cli-proxy`, and latest Copilot CLI install), `mcp-gateway` (enables the MCP gateway proxy), `integrity-reactions` (enables reaction-based integrity promotion and demotion), `cli-proxy` (enables CLI proxy mode for integrity enforcement at the network boundary), and `awf-diagnostic-logs` (enables AWF Docker operational diagnostics collection on failure). See [Frontmatter Reference](/gh-aw/reference/frontmatter/#feature-flags-features).
A frontmatter section that enables experimental or optional compiler and runtime behaviors as key-value pairs. Feature flags provide controlled access to new capabilities before they become defaults or are fully stabilized. Common flags include `action-mode` (controls how custom action references are compiled), `copilot-requests` (enables GitHub Actions token authentication for Copilot; currently in **private preview** — will not work unless your account has been onboarded), `byok-copilot` (enabled by default for Copilot workflows; set `features.byok-copilot: false` to disable; BYOK mode uses a dummy `COPILOT_API_KEY`, API proxy sidecar, implicit `cli-proxy`, and latest Copilot CLI install), `mcp-gateway` (enables the MCP gateway proxy), `integrity-reactions` (enables reaction-based integrity promotion and demotion), `cli-proxy` (enables CLI proxy mode for integrity enforcement at the network boundary), and `awf-diagnostic-logs` (enables AWF Docker operational diagnostics collection on failure). See [Frontmatter Reference](/gh-aw/reference/frontmatter/#feature-flags-features).

### Fuzzy Scheduling

Expand Down
9 changes: 8 additions & 1 deletion pkg/workflow/copilot_installer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ func TestGenerateCopilotInstallerSteps(t *testing.T) {
}

func TestCopilotInstallerCustomVersion(t *testing.T) {
// Test that custom version from engine config is used
// Test that custom version from engine config is used when byok-copilot is disabled.
engine := NewCopilotEngine()

customVersion := "1.0.0"
Expand All @@ -118,6 +118,9 @@ func TestCopilotInstallerCustomVersion(t *testing.T) {
EngineConfig: &EngineConfig{
Version: customVersion,
},
Features: map[string]any{
string(constants.ByokCopilotFeatureFlag): false,
},
}

steps := engine.GetInstallationSteps(workflowData)
Expand Down Expand Up @@ -205,6 +208,7 @@ func TestGenerateCopilotInstallerSteps_ExpressionVersion(t *testing.T) {

func TestCopilotInstallerExpressionVersion_ViaEngineConfig(t *testing.T) {
// Test that expression version from engine config uses env var injection
// when byok-copilot is disabled.
engine := NewCopilotEngine()

expressionVersion := "${{ inputs.engine-version }}"
Expand All @@ -213,6 +217,9 @@ func TestCopilotInstallerExpressionVersion_ViaEngineConfig(t *testing.T) {
EngineConfig: &EngineConfig{
Version: expressionVersion,
},
Features: map[string]any{
string(constants.ByokCopilotFeatureFlag): false,
},
}

steps := engine.GetInstallationSteps(workflowData)
Expand Down
11 changes: 11 additions & 0 deletions pkg/workflow/features.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ func isFeatureEnabled(flag constants.FeatureFlag, workflowData *WorkflowData) bo
// Fall back to checking the environment variable
features := os.Getenv("GH_AW_FEATURES")
if features == "" {
// byok-copilot is enabled by default and can be explicitly disabled
// via features.byok-copilot: false in frontmatter.
if flag == constants.ByokCopilotFeatureFlag {
featuresLog.Printf("Feature not found, GH_AW_FEATURES empty: %s=true (default)", flagLower)
return true
}
featuresLog.Printf("Feature not found, GH_AW_FEATURES empty: %s=false", flagLower)
return false
}
Expand All @@ -83,6 +89,11 @@ func isFeatureEnabled(flag constants.FeatureFlag, workflowData *WorkflowData) bo
}
}

if flag == constants.ByokCopilotFeatureFlag {
featuresLog.Printf("Feature not found in GH_AW_FEATURES: %s=true (default)", flagLower)
return true
}

featuresLog.Printf("Feature not found: %s=false", flagLower)
return false
}
48 changes: 48 additions & 0 deletions pkg/workflow/features_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,14 @@ func TestIsFeatureEnabledNoEnv(t *testing.T) {
}
}

func TestIsFeatureEnabledByokCopilotDefaultOn(t *testing.T) {
t.Setenv("GH_AW_FEATURES", "")
result := isFeatureEnabled(constants.ByokCopilotFeatureFlag, nil)
if result != true {
t.Errorf("isFeatureEnabled(%q, nil) with no env = %v, want true", constants.ByokCopilotFeatureFlag, result)
}
}

func TestIsFeatureEnabledWithData(t *testing.T) {
tests := []struct {
name string
Expand Down Expand Up @@ -181,6 +189,46 @@ func TestIsFeatureEnabledWithData(t *testing.T) {
expected: false,
description: "byok-copilot implication should only apply to engine=copilot",
},
{
name: "byok-copilot defaults to enabled for copilot workflows",
envValue: "",
frontmatter: map[string]any{},
engineID: string(constants.CopilotEngine),
flag: constants.ByokCopilotFeatureFlag,
expected: true,
description: "byok-copilot should default to enabled",
},
{
name: "byok-copilot can be disabled with explicit false (case-insensitive key)",
envValue: "",
frontmatter: map[string]any{
"BYOK-copilot": false,
},
engineID: string(constants.CopilotEngine),
flag: constants.ByokCopilotFeatureFlag,
expected: false,
description: "explicit frontmatter false should disable default byok-copilot behavior",
},
{
name: "byok default implies cli-proxy for copilot engine",
envValue: "",
frontmatter: map[string]any{},
engineID: string(constants.CopilotEngine),
flag: constants.CliProxyFeatureFlag,
expected: true,
description: "default byok-copilot should imply cli-proxy for engine=copilot",
},
{
name: "explicit byok false disables cli-proxy implication",
envValue: "",
frontmatter: map[string]any{
"BYOK-copilot": false,
},
engineID: string(constants.CopilotEngine),
flag: constants.CliProxyFeatureFlag,
expected: false,
description: "explicit byok-copilot false should disable implicit cli-proxy",
},
}

for _, tt := range tests {
Expand Down
6 changes: 6 additions & 0 deletions pkg/workflow/github_remote_mode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ permissions:
issues: read
engine: copilot
strict: false
features:
byok-copilot: false
tools:
github:
mode: remote
Expand All @@ -126,6 +128,8 @@ permissions:
issues: read
engine: copilot
strict: false
features:
byok-copilot: false
tools:
github:
mode: remote
Expand Down Expand Up @@ -387,6 +391,8 @@ permissions:
issues: read
engine: copilot
strict: false
features:
byok-copilot: false
tools:
github:
mode: remote
Expand Down
4 changes: 2 additions & 2 deletions pkg/workflow/model_env_vars_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ func TestCopilotFallbackModelMapsToNativeEnvVar(t *testing.T) {
name: "Agent job maps GH_AW_MODEL_AGENT_COPILOT to COPILOT_MODEL",
safeOutputs: &SafeOutputsConfig{},
expectedOrgVar: constants.EnvVarModelAgentCopilot,
expectedTail: "''",
expectedTail: "'" + constants.CopilotBYOKDefaultModel + "'",
},
{
name: "Agent job with byok-copilot uses non-empty COPILOT_MODEL fallback",
Expand All @@ -240,7 +240,7 @@ func TestCopilotFallbackModelMapsToNativeEnvVar(t *testing.T) {
name: "Detection job maps GH_AW_MODEL_DETECTION_COPILOT to COPILOT_MODEL",
safeOutputs: nil,
expectedOrgVar: constants.EnvVarModelDetectionCopilot,
expectedTail: "''",
expectedTail: "'" + constants.CopilotBYOKDefaultModel + "'",
},
}

Expand Down
13 changes: 5 additions & 8 deletions pkg/workflow/threat_detection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1021,10 +1021,9 @@ func TestBuildDetectionEngineExecutionStepStripsAgentField(t *testing.T) {
}
}

// TestCopilotDetectionDefaultModel verifies that the copilot engine uses the
// Copilot CLI's native default model for the detection step when no model is specified.
// Detection now matches main agent behavior: both use ${{ vars.* || ” }} so the
// Copilot CLI picks its native default (currently claude-sonnet-4.6).
// TestCopilotDetectionDefaultModel verifies copilot model resolution in detection steps.
// With default-on byok-copilot, workflows without an explicit model use a non-empty
// fallback: ${{ vars.* || '<default-byok-model>' }}.
func TestCopilotDetectionDefaultModel(t *testing.T) {
compiler := NewCompiler()

Expand All @@ -1043,9 +1042,7 @@ func TestCopilotDetectionDefaultModel(t *testing.T) {
},
},
shouldContainModel: true,
// Detection uses env var fallback (same pattern as main agent), allowing
// the Copilot CLI to pick its native default (currently claude-sonnet-4.6)
expectedModel: "${{ vars." + constants.EnvVarModelDetectionCopilot + " || '' }}",
expectedModel: "${{ vars." + constants.EnvVarModelDetectionCopilot + " || '" + constants.CopilotBYOKDefaultModel + "' }}",
},
{
name: "copilot engine with custom model uses specified model",
Expand Down Expand Up @@ -1091,7 +1088,7 @@ func TestCopilotDetectionDefaultModel(t *testing.T) {
},
},
shouldContainModel: true,
expectedModel: "${{ vars." + constants.EnvVarModelDetectionCopilot + " || '' }}",
expectedModel: "${{ vars." + constants.EnvVarModelDetectionCopilot + " || '" + constants.CopilotBYOKDefaultModel + "' }}",
},
{
name: "claude engine does not add model parameter",
Expand Down