Skip to content

feat(shared/apm.md): expose target input for slim per-harness bundles#1184

Merged
danielmeppiel merged 2 commits into
mainfrom
feat/shared-apm-target-input
May 7, 2026
Merged

feat(shared/apm.md): expose target input for slim per-harness bundles#1184
danielmeppiel merged 2 commits into
mainfrom
feat/shared-apm-target-input

Conversation

@danielmeppiel
Copy link
Copy Markdown
Collaborator

TL;DR

shared/apm.md (the gh-aw shared workflow) hardcoded target: all on its apm-action call, forcing every consumer bundle to ship all 8 harness layouts (.github/, .claude/, .cursor/, .opencode/, .codex/, .gemini/, .windsurf/, .agents/) regardless of which engine actually consumed it. This PR exposes target as an import-schema input (default all) so consumers can opt into slim, single-harness bundles. Zero behavior change for any existing import.

Problem (WHY)

The shared workflow runs apm-action with isolated: 'true' and target: all baked into the Pack step (.github/workflows/shared/apm.md was at line 265). Two consequences:

  1. Bundle bloat. Every workflow that imports shared/apm.md packs every harness's deploy tree into the artifact, even when the agent step downstream only consumes one. For a single-engine gh-aw workflow this is roughly 8x more files than necessary.
  2. No consumer knob. Even consumers who knew exactly which harness they wanted had no way to express it through shared/apm.md's import-schema -- they would have had to fork the file.

The natural follow-up question -- "why not honor the consumer repo's apm.yml target:?" -- doesn't apply: isolated: 'true' makes apm-action ignore any consumer apm.yml entirely (it generates a synthetic one from the inline dependencies: input, working in /tmp/gh-aw/apm-workspace). There is no apm.yml to honor in this code path. The shared workflow is the sole signal, so it needs an input.

Approach (WHAT)

Add a single optional target input to import-schema, default all, and thread it into the Pack step via ${{ github.aw.import-inputs.target || 'all' }}.

  • Name: target (singular) -- matches the apm.yml key, the apm-action input, and the --target CLI flag.
  • Type: string (CSV when multiple targets are needed) -- matches apm-action's native format. Avoids forcing a YAML-array-to-CSV conversion in the workflow.
  • Default: all -- byte-for-byte back-compat with the previous hardcoded value. No existing import changes behavior.
  • Fallback expression: the || 'all' belt-and-suspenders covers gh-aw versions that emit empty strings rather than honoring default: in the schema. Keeps the contract deterministic regardless of which gh-aw version compiles the workflow.

Rejected alternatives

Option Rejected because
Default to empty + fail loudly Breaks every existing import. Bad DevX.
Auto-derive target by scanning the consumer checkout The apm job runs with permissions: {} and no checkout; adding one costs ~15s for a heuristic the consumer can replace with one line.
Auto-derive from a engine: sibling input Requires a gh-aw contract change (engine isn't currently passed through). Phase 2, separate issue in microsoft/gh-aw.
Per-app-group target: in the apps[] schema No user has asked. A single workflow-level target is sufficient.
Pre-validation step in shared/apm.md Duplicates CLI logic, will drift. The CLI is the single validator and produces a clear error on bad tokens.

Implementation (HOW)

Three surgical edits to .github/workflows/shared/apm.md:

  1. Add usage example Add ARM64 Linux support to CI/CD pipeline #4 to the header comment block illustrating slim-bundle opt-in.
  2. Add target to import-schema with description text enumerating valid tokens (copilot, claude, cursor, codex, opencode, gemini, windsurf, agent-skills, all) and noting the isolated-mode contract (apm.yml is intentionally ignored).
  3. Replace target: all in the Pack step with target: ${{ github.aw.import-inputs.target || 'all' }}.

Recompile via gh aw compile regenerates the two consuming lock workflows (pr-review-panel.lock.yml, triage-panel.lock.yml) so the new expression ships in CI. CHANGELOG entry added under [Unreleased].

Trade-offs

  • Default all keeps the bundle-bloat default. This is intentional for back-compat. Slim bundles are now an opt-in. Phase 2 (gh-aw side) makes them zero-config for single-engine workflows.
  • No checkout for auto-detect. The shared workflow stays cheap. Consumers who want auto-detection can write target: copilot (or whichever) explicitly -- one line.
  • The default: all in import-schema is documentation-grade. The expression-level || 'all' is what actually guarantees the runtime behavior. We accept the small redundancy because gh-aw's import-input default substitution semantics vary across versions.

Validation

  • uv run --extra dev ruff check src/ tests/ && uv run --extra dev ruff format --check src/ tests/ -- silent.
  • gh aw compile -- 5 workflows recompiled, 0 errors. The two lock files that import shared/apm.md now contain target: ${{ github.aw.import-inputs.target || 'all' }}. At runtime, undefined github.aw.import-inputs.target evaluates to empty in ${{ }} expressions, so the || fallback yields 'all' -- preserving the previous behavior exactly.

How to test

  1. Existing consumer (back-compat): the pr-review-panel.lock.yml / triage-panel.lock.yml workflows that import shared/apm.md without setting target: continue to receive target: all. No change in CI behavior expected.
  2. New consumer (opt-in): add target: copilot to a with: block and recompile -- the target: value flows through to apm-action and the resulting bundle should contain only .github/ (not the other 7 harness trees).

Follow-up

Open a sibling issue in microsoft/gh-aw: "Pass workflow engine as target to shared/apm.md" -- gh-aw's compile step knows the engine declared in the workflow front-matter and could inject target: <engine> automatically when the consumer omits the field. Makes slim bundles zero-config for single-engine workflows.

The gh-aw shared workflow at .github/workflows/shared/apm.md previously
hardcoded `target: all` on its apm-action call, forcing every consumer
bundle to include all eight harness layouts (.github/, .claude/, .cursor/,
.opencode/, .codex/, .gemini/, .windsurf/, .agents/) regardless of which
engine the workflow actually used.

Expose `target` as an import-schema input (string CSV; default `all`)
so consumers can opt into slim, single-harness bundles. Wire it into the
Pack step via `${{ github.aw.import-inputs.target || 'all' }}` -- the
fallback preserves byte-for-byte back-compat for every existing import
that omits the field.

Notes on precedence: shared/apm.md runs apm-action with
`isolated: 'true'`, which intentionally ignores any apm.yml in the
consumer repo. The `target` input is therefore the sole target signal --
there is no apm.yml to honor or override in this context. Consumers who
target a single engine (claude / copilot / etc.) should set
`target: <engine>` for materially smaller bundles and faster restores.

Phase 2 (separate, in microsoft/gh-aw): teach gh-aw's compile step to
auto-inject `target: <engine>` from the workflow's declared engine when
the consumer omits the field, so slim bundles become zero-config for
single-engine workflows.

Recompiles the two lock workflows that import shared/apm.md
(pr-review-panel.lock.yml, triage-panel.lock.yml) so the new expression
ships in CI.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 7, 2026 12:07
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@danielmeppiel danielmeppiel merged commit 818cef5 into main May 7, 2026
9 checks passed
@danielmeppiel danielmeppiel deleted the feat/shared-apm-target-input branch May 7, 2026 12:10
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR updates the gh-aw shared workflow shared/apm.md to let importing workflows choose an APM target (defaulting to all) so packed bundles can be slimmed to only the needed harness layout(s), instead of always packing every supported layout when running in isolated: true mode.

Changes:

  • Add a new optional target input to import-schema (default all) and thread it into the microsoft/apm-action Pack step.
  • Regenerate/update the compiled lock workflows to use the new target: ${{ github.aw.import-inputs.target || 'all' }} expression.
  • Add an Unreleased CHANGELOG entry for the workflow feature and update uv.lock’s editable package version.
Show a summary per file
File Description
.github/workflows/shared/apm.md Adds target as an import input and passes it to apm-action during pack.
.github/workflows/pr-review-panel.lock.yml Regenerated lock workflow to pass through the new target input with a fallback to all.
.github/workflows/triage-panel.lock.yml Regenerated lock workflow to pass through the new target input with a fallback to all.
CHANGELOG.md Adds an Unreleased entry describing the new shared workflow capability.
uv.lock Updates the editable apm-cli lock entry version metadata.

Copilot's findings

  • Files reviewed: 4/5 changed files
  • Comments generated: 2

Comment thread CHANGELOG.md

### Added

- `shared/apm.md` gh-aw shared workflow exposes a `target:` import input (default `all`) so consumer workflows can ship slim, single-harness bundles instead of always packing every layout. (#1184)
Comment on lines +154 to +159
list. Valid tokens: copilot, claude, cursor, codex, opencode, gemini,
windsurf, agent-skills, all. Default: all (every supported harness).
Set this to match the engine your gh-aw workflow targets for smaller,
faster bundles. The shared workflow runs apm-action in isolated mode,
so any apm.yml in the consumer repo is intentionally ignored -- this
input is the sole target signal.
danielmeppiel pushed a commit that referenced this pull request May 7, 2026
#1185)

The bare-expression regex in gh-aw's importInputsExprRegex
(pkg/parser/import_field_extractor.go) only matches
`${{ github.aw.import-inputs.X }}` -- the `|| 'all'` belt-and-suspenders
introduced in #1184 made the regex skip substitution entirely, so
consumer-supplied `with: target: copilot` was silently ignored and the
unresolved expression survived into the lock workflow (yielding empty
runtime env, which apm then treats as the default).

The schema default `default: all` declared in import-schema is already
applied by gh-aw's applyImportSchemaDefaultsFromFrontmatter BEFORE
substitution, so the fallback was redundant anyway. Dropping it lets
gh-aw substitute literal values into the lock workflow.

Verified: `gh aw compile` now emits `target: all` (literal) into the
two consuming lock workflows. With `with: target: copilot` set on the
import, gh-aw will substitute `target: copilot` instead.

Closes #1185.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
danielmeppiel pushed a commit that referenced this pull request May 7, 2026
#1185)

The bare-expression regex in gh-aw's importInputsExprRegex
(pkg/parser/import_field_extractor.go) only matches
`${{ github.aw.import-inputs.X }}`. The `|| 'all'` belt-and-suspenders
introduced in #1184 made the regex skip substitution entirely, so
consumer-supplied `with: target: copilot` was silently ignored -- the
unresolved expression survived into the lock workflow and the runtime
env ended up empty (which apm-action then treated as `all`).

The schema default `default: all` declared in import-schema is already
applied by gh-aw's applyImportSchemaDefaultsFromFrontmatter BEFORE
substitution, so the fallback was redundant anyway. Dropping it lets
gh-aw substitute literal values into consuming lock workflows.

Verified locally: the two consuming lock workflows
(pr-review-panel.lock.yml, triage-panel.lock.yml) now carry the literal
substituted value `target: all` after recompile (since neither import
sets `target:`). Consumers who pass `with: target: copilot` will see
`target: copilot` substituted into their lock.

Edits to the lock files are surgical (single line each) -- the
frontmatter_hash in the gh-aw-metadata header only covers the .md
frontmatter, not the body, so they remain valid.

Closes #1185.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
danielmeppiel pushed a commit that referenced this pull request May 7, 2026
#1185)

The bare-expression regex in gh-aw's importInputsExprRegex
(pkg/parser/import_field_extractor.go) only matches
`${{ github.aw.import-inputs.X }}`. The `|| 'all'` belt-and-suspenders
introduced in #1184 made the regex skip substitution entirely, so
consumer-supplied `with: target: copilot` was silently ignored: the
unresolved expression survived into the lock workflow and the runtime
env was empty (which apm-action then treated as `all`).

The schema default `default: all` declared in import-schema is already
applied by gh-aw's applyImportSchemaDefaultsFromFrontmatter BEFORE
substitution, so the fallback was redundant. Dropping it lets gh-aw
substitute literal values into consuming lock workflows.

Recompiled with gh-aw v0.71.5 (matching the version stamped in lock
metadata). The two consuming lock workflows now carry the literal
substituted value `target: all` since neither import sets `target:`.
Consumers who pass `with: target: copilot` will get `target: copilot`.

Closes #1185.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
danielmeppiel added a commit that referenced this pull request May 7, 2026
#1185) (#1186)

The bare-expression regex in gh-aw's importInputsExprRegex
(pkg/parser/import_field_extractor.go) only matches
`${{ github.aw.import-inputs.X }}`. The `|| 'all'` belt-and-suspenders
introduced in #1184 made the regex skip substitution entirely, so
consumer-supplied `with: target: copilot` was silently ignored: the
unresolved expression survived into the lock workflow and the runtime
env was empty (which apm-action then treated as `all`).

The schema default `default: all` declared in import-schema is already
applied by gh-aw's applyImportSchemaDefaultsFromFrontmatter BEFORE
substitution, so the fallback was redundant. Dropping it lets gh-aw
substitute literal values into consuming lock workflows.

Recompiled with gh-aw v0.71.5 (matching the version stamped in lock
metadata). The two consuming lock workflows now carry the literal
substituted value `target: all` since neither import sets `target:`.
Consumers who pass `with: target: copilot` will get `target: copilot`.

Closes #1185.

Co-authored-by: Daniel Meppiel <copilot-rework@github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
danielmeppiel added a commit that referenced this pull request May 7, 2026
…1192)

Both panels run on the gh-aw default Copilot CLI engine, so they only
need the 'copilot' layout from the apm package. Pinning target=copilot
(via the new shared/apm.md input from #1184) ships a slim per-harness
bundle instead of installing all seven harnesses on every panel run.

This also side-steps the cowork-in-all crash that hit the apm
self-check (run 25511043293) before #1191 landed; the panels now
explicitly request a single supported harness, so an experimental
target leaking into 'all' could not regress these workflows again.

Co-authored-by: Daniel Meppiel <copilot-rework@github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

2 participants