Skip to content

feat: add package variables (${var:...}) for deployed primitives#736

Closed
nathantoews wants to merge 1 commit intomicrosoft:mainfrom
nathantoews:feat/package-variables
Closed

feat: add package variables (${var:...}) for deployed primitives#736
nathantoews wants to merge 1 commit intomicrosoft:mainfrom
nathantoews:feat/package-variables

Conversation

@nathantoews
Copy link
Copy Markdown

Hello! APM team. This a feature I think would be super helpful to make apm a little more modular for abstractions of agents for installing. Please let me know what you think would be happy to make some changes.

Summary

Adds support for declaring and resolving variables in APM packages. Package authors can define ${var:name} placeholders in their primitive files, declare variables with defaults in apm.yml, and consumers can override values per-dependency. Variables are resolved at install time and substituted into all deployed text-based primitives.

Motivation

Packages often need to be customizable without requiring consumers to fork or manually edit files after install. Variables provide a first-class mechanism for parameterization -- things like model names, API endpoints, project-specific paths, or behavioral toggles can be exposed as named variables with sensible defaults.

How it works

  1. Declaration -- Package authors add a variables section to apm.yml with names, defaults, and optional descriptions.
  2. Usage -- Authors place ${var:name} placeholders anywhere in their primitive text files (instructions, prompts, agents, skills, commands).
  3. Override -- Consumers specify per-package variable values in their own apm.yml under dependencies[].variables.
  4. Resolution -- At install time, APM resolves each variable (consumer override > package default) and substitutes placeholders in all deployed files.
  5. Lockfile -- Resolved values are recorded in apm-lock.yml for reproducibility.

Changes

  • New src/apm_cli/utils/variables.py -- substitution engine (pattern matching, resolution, file walking)
  • models/apm_package.py -- variables field on APMPackage
  • deps/lockfile.py -- resolved_variables on LockedDependency
  • integration/base_integrator.py -- set_variables() / apply_variable_substitution() on base class
  • All integrators (agent, instruction, prompt, command, skill) -- apply substitution during deploy
  • commands/install.py -- resolve variables per-package and thread through integration
  • 66 new unit tests in tests/unit/test_package_variables.py
  • Documentation: manifest schema reference, dependencies guide, package authoring skill, CHANGELOG

Add install-time variable substitution for deployed primitive files.
Package authors declare variables in apm.yml with defaults, and
consumers override them per-package. Placeholders (${var:name}) in
.agent.md, .instructions.md, SKILL.md, and other text files are
resolved during apm install.

- Add variables field to APMPackage and apm.yml parsing
- Add variable substitution engine (src/apm_cli/utils/variables.py)
- Integrate substitution into all integrators (agent, instruction,
  skill, prompt, command) via BaseIntegrator
- Record resolved variables in apm.lock.yaml for reproducibility
- Fail with clear error when required variables have no resolution
- 66 new tests covering parsing, resolution, substitution, and
  lockfile round-trip
- Update manifest-schema, dependencies guide, and package-authoring
  docs
Copilot AI review requested due to automatic review settings April 16, 2026 16:44
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

Adds install-time package variable support (${var:...}) so APM packages can ship parameterized primitives and have consumers override values via apm.yml, with resolved values recorded in the lockfile for repeatable installs.

Changes:

  • Introduces a variable parsing/resolution + substitution utility and threads resolved variables through the install/integration pipeline.
  • Updates integrators (agents/instructions/prompts/commands/skills) to apply substitution during deploy, and extends the lockfile model to persist resolved values.
  • Adds a dedicated unit test suite plus documentation/schema + changelog updates describing the new variable feature.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tests/unit/test_package_variables.py New unit tests covering parsing, resolution, substitution, lockfile round-trip, and BaseIntegrator hooks.
src/apm_cli/utils/variables.py New substitution engine: regex matching, parsing package declarations + consumer overrides, and directory-wide substitution for copied skills.
src/apm_cli/models/apm_package.py Adds variables field to APMPackage and loads it from apm.yml.
src/apm_cli/integration/base_integrator.py Adds set_variables() and apply_variable_substitution() shared by integrators.
src/apm_cli/integration/agent_integrator.py Applies variable substitution during agent copy and Codex MD->TOML transformation.
src/apm_cli/integration/instruction_integrator.py Applies variable substitution during instruction deployment (including Cursor/Claude conversions).
src/apm_cli/integration/prompt_integrator.py Applies variable substitution during prompt deployment.
src/apm_cli/integration/command_integrator.py Applies variable substitution to command content before writing targets.
src/apm_cli/integration/skill_integrator.py Applies variable substitution across copied skill directories (including promoted sub-skills) and ensures BaseIntegrator init runs.
src/apm_cli/deps/lockfile.py Adds resolved_variables to LockedDependency serialization/deserialization.
src/apm_cli/commands/install.py Resolves per-package variables during install, sets variables on integrators, and writes resolved values into the lockfile.
packages/apm-guide/.apm/skills/apm-usage/package-authoring.md Adds authoring guidance for declaring/overriding variables.
docs/src/content/docs/reference/manifest-schema.md Extends manifest schema reference with the new variables section and lockfile field.
docs/src/content/docs/guides/dependencies.md Documents variable usage for dependencies/consumers and clarifies schema references.
CHANGELOG.md Adds an Unreleased entry for package variables.

else:
warnings.append(
f"Variable '{var_name}' for package '{package_name}' is unresolved "
f"-- ${{{var_name}}} placeholders will be left as-is"
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

The unresolved-variable warning message renders the placeholder as ${<name>} (e.g. ${stack}) rather than the actual ${var:<name>} syntax that users need to fix. Update the message so it references ${var:<name>} to avoid confusing consumers reading diagnostics.

Suggested change
f"-- ${{{var_name}}} placeholders will be left as-is"
f"-- ${{var:{var_name}}} placeholders will be left as-is"

Copilot uses AI. Check for mistakes.
logger.error(err_msg)

if errors:
return {}
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

Missing required variables currently only add diagnostics errors and then return {}; the install continues and deploys files with unresolved ${var:...} placeholders. This contradicts the spec/docs that say required variables must fail apm install. Consider hard-failing when errors is non-empty (e.g., raise an exception / return a sentinel that causes the caller to abort and exit non-zero), or ensure the main install flow exits when diagnostics.error_count > 0 for this case.

Suggested change
return {}
raise click.ClickException(
f"Failed to resolve required variables for package '{pkg_name}'."
)

Copilot uses AI. Check for mistakes.
Comment on lines +1404 to +1408
if not package_vars and not (consumer_apm_package.variables or {}):
return {}

consumer_overrides = parse_consumer_overrides(consumer_apm_package.variables)
pkg_name = package_info.package.name
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

PR description mentions consumer overrides under dependencies[].variables, but the implementation reads overrides from the top-level apm.yml variables mapping keyed by package name. Please align the PR description (and any docs, if needed) with the actual schema, or adjust the implementation to support the documented dependencies[].variables form.

Copilot uses AI. Check for mistakes.
Comment thread CHANGELOG.md
### Added

- Package variables (`${var:...}`) for deployed primitives -- package authors declare variables in `apm.yml`, consumers override values, and placeholders are substituted at install time in `.agent.md`, `.instructions.md`, `SKILL.md`, and other text files
- `apm install` now automatically discovers and deploys local `.apm/` primitives (skills, instructions, agents, prompts, hooks, commands) to target directories, with local content taking priority over dependencies on collision (#626, #644)
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

The new Unreleased changelog entry does not follow the repo format: entries should be a single concise line ending with the PR number in parentheses (e.g. ... (#123)). Please update this line to match the established Keep a Changelog/SemVer conventions used throughout the file.

Suggested change
- `apm install` now automatically discovers and deploys local `.apm/` primitives (skills, instructions, agents, prompts, hooks, commands) to target directories, with local content taking priority over dependencies on collision (#626, #644)
- `apm install` now discovers and deploys local `.apm/` primitives, with local content taking priority over dependencies on collision (#626, #644)

Copilot uses AI. Check for mistakes.
@danielmeppiel
Copy link
Copy Markdown
Collaborator

There is some support for input parameters in agent workflows. Adding variables is akin to creating a language and compiler/interpreter. That's something that I recognize as valuable but it's too early and we need discussion on design before.

The place for variables should be the markdowns themselves and not the manifest I believe.

Let's move this to a discussion

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