Skip to content

feat: add top-level model field, deprecate engine.model#46570

Merged
pelikhan merged 4 commits into
mainfrom
copilot/add-top-level-model-field
Jul 19, 2026
Merged

feat: add top-level model field, deprecate engine.model#46570
pelikhan merged 4 commits into
mainfrom
copilot/add-top-level-model-field

Conversation

Copilot AI commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Introduces a canonical top-level model field for LLM model selection, deprecating engine.model which required nesting a single scalar inside the engine object.

Changes

  • Top-level model support (engine.go, frontmatter_types.go)

    • New model: field parsed from frontmatter; populates EngineConfig.Model across all engine formats (string, object, inline, model-only)
    • Takes precedence over engine.model when both are present
  • Deprecation warning (engine.go)

    • Emits a stderr warning at compile time when engine.model is encountered: 'engine.model' is deprecated. Use top-level 'model' instead. Run 'gh aw fix' to automatically migrate.
  • Codemod (codemod_engine_model.go)

    • engine-model-to-top-level automatically migrates engine.model → top-level model via gh aw fix
    • Skips inline-map syntax (engine: { id: copilot, model: gpt-4 }); removes engine.model if top-level model already present
  • Schema (main_workflow_schema.json)

    • Adds top-level model property
    • Marks engine.model as deprecated with migration message in both object-form engine_config variants

Example

# Before
engine:
  id: copilot
  model: gpt-5.4

# After (gh aw fix)
model: gpt-5.4
engine:
  id: copilot

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI requested a review from pelikhan July 19, 2026 07:30
@pelikhan
pelikhan marked this pull request as ready for review July 19, 2026 07:35
Copilot AI review requested due to automatic review settings July 19, 2026 07:35
@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot remove Go Engine.Model to avoid duplication, apply fix to this repo

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds canonical top-level model configuration and deprecates engine.model.

Changes:

  • Parses and prioritizes top-level model.
  • Adds schema deprecation metadata and warnings.
  • Adds a gh aw fix migration codemod and tests.
Show a summary per file
File Description
pkg/workflow/frontmatter_types.go Adds the typed top-level model field.
pkg/workflow/engine.go Applies model precedence and emits deprecation warnings.
pkg/parser/schemas/main_workflow_schema.json Defines top-level model and deprecation metadata.
pkg/cli/fix_codemods.go Registers the codemod.
pkg/cli/fix_codemods_test.go Updates registry expectations.
pkg/cli/codemod_engine_model.go Implements model migration.
pkg/cli/codemod_engine_model_test.go Tests codemod behavior.
.changeset/engine-model-to-top-level.md Documents the feature.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 8/8 changed files
  • Comments generated: 5
  • Review effort level: Medium

Comment thread pkg/workflow/engine.go
if topLevelMaxRuns == 0 {
topLevelMaxRuns = parseMaxRunsValue(frontmatter["max-runs"])
}
topLevelModel, _ := frontmatter["model"].(string)
Description: "Moves deprecated 'engine.model' to the top-level 'model' field. The top-level field is the canonical location for LLM model configuration and takes precedence over engine.model.",
IntroducedIn: "0.78.0",
Apply: func(content string, frontmatter map[string]any) (string, bool, error) {
return migrateEngineFieldToTopLevel(
Comment on lines +12258 to +12260
"description": "Optional specific LLM model to use (e.g., 'claude-3-5-sonnet-20241022', 'gpt-4'). Has sensible defaults and can typically be omitted.",
"deprecated": true,
"x-deprecation-message": "'engine.model' is deprecated. Use top-level 'model' instead. Run 'gh aw fix' to automatically migrate."
Comment thread pkg/workflow/engine.go
if model, hasModel := engineObj["model"]; hasModel {
if modelStr, ok := model.(string); ok {
config.Model = modelStr
fmt.Fprintln(os.Stderr, console.FormatWarningMessage("'engine.model' is deprecated. Use top-level 'model' instead. Run 'gh aw fix' to automatically migrate."))

- New top-level `model` field for specifying the LLM model used by the agentic engine. Takes precedence over `engine.model` when both are set.
- Deprecation warning emitted at compile time when `engine.model` is used.
- `gh aw fix` codemod (`engine-model-to-top-level`) automatically migrates `engine.model` to top-level `model`.
@github-actions

github-actions Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

@github-actions

github-actions Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

@github-actions

github-actions Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

@github-actions

github-actions Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: feat: add top-level model field, deprecate engine.model

Overall the implementation is clean and well-tested. Existing inline comments capture the blocking issues.

Key concerns (already flagged inline):

  1. engine.go line 208topLevelModel is not propagated in the import-accumulator path; workflows using imported models would silently get no model.
  2. engine.go line 329 — Deprecation warning bypasses the compiler's warning counter, and the migration message overpromises for inline-map engine syntax.
  3. codemod_engine_model.go line 15 — Comment-bearing engine.model lines may leave a dangling engine: key after migration.
  4. .changeset line 9 — Release note doesn't disclose the inline-map limitation.
  5. Schema line 12260 — Existing examples in engine_config still show the deprecated engine.model.

Items 1–3 are correctness/reliability issues worth addressing before merge. Items 4–5 are polish.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · 14.9 AIC · ⌖ 5.03 AIC · ⊞ 5K

@github-actions

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 81/100 — Excellent

Analyzed 7 test(s): 6 design, 1 implementation, 0 violation(s).

📊 Metrics (7 tests)
Metric Value
Analyzed 7 (Go: 7, JS: 0)
✅ Design 6 (86%)
⚠️ Implementation 1 (14%)
Edge/error coverage 4 (57%)
Duplicate clusters 0
Inflation Justified
🚨 Violations 0

Test Breakdown:

Test File Classification Key Assertion
TestEngineModelToTopLevelCodemod_Metadata codemod_engine_model_test.go:12 Implementation-detail Validates struct fields (ID, Name, Description, IntroducedIn, Apply function)
TestEngineModelToTopLevelCodemod_NoOp codemod_engine_model_test.go:22 Behavioral contract Non-mutation: workflows without engine.model remain unchanged
TestEngineModelToTopLevelCodemod_IdempotentWhenAlreadyMigrated codemod_engine_model_test.go:44 Behavioral contract Idempotency: already-migrated workflows not re-processed
TestEngineModelToTopLevelCodemod_MigratesField codemod_engine_model_test.go:66 Behavioral contract Core migration: engine.model → top-level model
TestEngineModelToTopLevelCodemod_PreservesCommentsAndBody codemod_engine_model_test.go:100 Behavioral contract Content integrity: YAML comments and body preserved
TestEngineModelToTopLevelCodemod_RespectsExistingTopLevel codemod_engine_model_test.go:136 Behavioral contract Conflict resolution: top-level model takes precedence
TestEngineModelToTopLevelCodemod_InlineEngineMapNoOp codemod_engine_model_test.go:162 Behavioral contract Safe failure: inline maps skipped (not corrupted)

Verdict

Approved. 14% implementation tests (threshold: 30%). No guideline violations.

Strengths:

  • Exceptional edge-case coverage: non-mutation, idempotency, conflict resolution, safe failure modes
  • All build tags present (//go:build !integration)
  • No mock library violations
  • Content preservation explicitly tested
  • Registry integration tests updated

Test inflation (6:1 ratio) is justified: Codemods are transformation engines requiring comprehensive YAML/frontmatter testing. Production code (30 lines) delegates to shared helper; test suite provides necessary behavioral specification.

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • awmgmcpg

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "awmgmcpg"

See Network Configuration for more information.

🧪 Test quality analysis by Test Quality Sentinel · 16.2 AIC · ⌖ 6.47 AIC · ⊞ 7K ·
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Test Quality Sentinel: 81/100. 14% implementation tests (threshold: 30%). No guideline violations.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skills-Based Review 🧠

Applied /tdd and /codebase-design — requesting changes on correctness and consistency issues.

📋 Key Themes & Highlights

Key Themes

  • Silent model loss on import (engine.go ~line 208): topLevelModel is never read in the import-accumulator path, so imported workflows lose their top-level model: setting.
  • Out-of-band deprecation warning (engine.go line 329): using fmt.Fprintln(os.Stderr) bypasses the compiler's warning pipeline — not counted, not testable.
  • Misleading changeset (.changeset/engine-model-to-top-level.md): claims gh aw fix always migrates engine.model, but the codemod deliberately no-ops for inline-map syntax.
  • Comment-trailing model-only engine (codemod_engine_model.go line 15): migrating engine:\n # comment\n model: x leaves a dangling engine: key with an orphaned comment.
  • Schema examples still show deprecated form (main_workflow_schema.json ~line 12168): deprecated engine.model examples remain in the engine_config variants.

Positive Highlights

  • ✅ Comprehensive test suite in codemod_engine_model_test.go covering no-op, idempotent, migration, and inline-map cases
  • ✅ Clean precedence logic: top-level model consistently wins over engine.model across all engine parse paths
  • ✅ Schema deprecation annotations added in both engine_config variants

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · 22 AIC · ⌖ 4.61 AIC · ⊞ 6.7K
Comment /matt to run again

Comment thread pkg/workflow/engine.go
if topLevelMaxRuns == 0 {
topLevelMaxRuns = parseMaxRunsValue(frontmatter["max-runs"])
}
topLevelModel, _ := frontmatter["model"].(string)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/tdd] topLevelModel is not propagated through the import-accumulator path (around line 208), so a workflow that sets top-level model: and is imported by another workflow will silently lose its model override.

💡 Suggested fix

After extracting the sub-workflow frontmatter in the import accumulator, read topLevelModel and assign it to config.Model — the same pattern used on line 302 for the object-engine path.

Add a test that imports a workflow with model: gpt-5.4 and asserts the compiled output carries the model.

@copilot please address this.

Comment thread pkg/workflow/engine.go
if model, hasModel := engineObj["model"]; hasModel {
if modelStr, ok := model.(string); ok {
config.Model = modelStr
fmt.Fprintln(os.Stderr, console.FormatWarningMessage("'engine.model' is deprecated. Use top-level 'model' instead. Run 'gh aw fix' to automatically migrate."))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/codebase-design] The deprecation warning is emitted via fmt.Fprintln(os.Stderr, ...) while the rest of the compiler surfaces warnings through its structured warning pipeline. This bypasses the warning count and makes the message invisible in tests.

💡 Suggested fix

Use the existing compiler warning mechanism (e.g. c.Warnings = append(c.Warnings, ...) or equivalent) so the deprecation warning is counted, testable, and routed through the same output channel as all other compiler diagnostics.

@copilot please address this.

@github-actions

Copy link
Copy Markdown
Contributor

Design Decision Gate - ADR Required

This PR makes significant changes to core business logic (245 new lines in pkg/) but does not have a linked Architecture Decision Record (ADR).

Draft ADR committed: docs/adr/46570-add-top-level-model-field.md — review and complete it before merging.

This PR cannot merge until an ADR is linked in the PR body.

What to do next
  1. Review the draft ADR committed to your branch - it was generated from the PR diff

  2. Complete the missing sections - add context the AI could not infer, refine the decision rationale, and list real alternatives you considered

  3. Commit the finalized ADR to docs/adr/ on your branch

  4. Reference the ADR in this PR body by adding a line such as:

    ADR: ADR-46570: Add Top-Level model Field

Once an ADR is linked in the PR body, this gate will re-run and verify the implementation matches the decision.

Why ADRs Matter

ADRs create a searchable, permanent record of why the codebase looks the way it does. Future contributors (and your future self) will thank you.

Michael Nygard ADR Format Reference

An ADR must contain these four sections to be considered complete:

  • Context - What is the problem? What forces are at play?
  • Decision - What did you decide? Why?
  • Alternatives Considered - What else could have been done?
  • Consequences - What are the trade-offs (positive and negative)?

All ADRs are stored in docs/adr/ as Markdown files numbered by PR number.

🏗️ ADR gate enforced by Design Decision Gate 🏗️ · 49.5 AIC · ⌖ 10.4 AIC · ⊞ 4.3K ·
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

REQUEST_CHANGES

The feature direction is sound, but several correctness and test-coverage gaps must be addressed before merging.

Dual parsing path for modelFrontmatterConfig.Model is populated by JSON unmarshal in ParseFrontmatterConfig but ExtractEngineConfig re-reads the same field directly from the raw frontmatter map, creating two independent representations. Any normalization logic added to the struct path will silently not apply to the engine path.

No tests for the new primary code path — the topLevelModel-only branch (engine.go lines 607–615) is the canonical new UX (model: without an engine: key) and is entirely untested.

Pre-existing review comments also flag: deprecation warning not counted in compiler warning total; misleading codemod promises in the changeset; import-path model propagation gap; schema examples still showing deprecated form; codemod edge case with model-only engine syntax.

🔎 Code quality review by PR Code Quality Reviewer · 53 AIC · ⌖ 4.67 AIC · ⊞ 5.6K
Comment /review to run again

Comments that could not be inline-anchored

pkg/workflow/frontmatter_types.go:382

Dual parsing path for model field creates silent divergence risk: FrontmatterConfig.Model is populated correctly by JSON unmarshal, but engine.go ignores it and re-reads from the raw frontmatter map (frontmatter[&quot;model&quot;].(string)) independently.

<details>
<summary>💡 Details</summary>

ParseFrontmatterConfig (frontmatter_parsing.go) already unmarshals all top-level fields into FrontmatterConfig, including the new Model string field. But ExtractEngineConfig (engine.go line 3…

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Jul 19, 2026

Copy link
Copy Markdown
Contributor Author

@copilot remove Go Engine.Model to avoid duplication, apply fix to this repo

Done in commit feat: remove EngineConfig.Model, add WorkflowData.Model, run gh aw fix:

  • Removed Model from EngineConfig struct — ExtractEngineConfig now returns the model as a separate 3rd return value
  • Added Model string to WorkflowData as the canonical location; all engine implementations (copilot, claude, codex, pi, gemini, etc.) now read workflowData.Model
  • Added Model string to ThreatDetectionConfig for threat-detection-specific model overrides
  • Updated the import accumulator to prefer top-level model from imports over deprecated engine.model
  • Ran gh aw fix --write which migrated 52 workflow files in this repo from deprecated engine.model to top-level model, then recompiled all 258 workflows

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
@pelikhan
pelikhan merged commit 3688433 into main Jul 19, 2026
23 checks passed
@pelikhan
pelikhan deleted the copilot/add-top-level-model-field branch July 19, 2026 09:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants