Skip to content

Extending ado-aw

This guide shows the usual workflow for adding a new capability to the compiler.

Use it when you want to add a feature such as:

  • a new CLI command
  • a new compile target
  • a new front-matter field
  • a new template marker
  • a new safe-output tool
  • a new first-class tool
  • a new runtime

Start in src/main.rs.

  1. Add a new variant to the Commands enum.
  2. Define the arguments with clap derive attributes.
  3. Handle the new command in the main dispatch logic.
  4. Add tests for parsing and behavior.

Use this when you want a new top-level command such as ado-aw my-command.

Compile targets live under src/compile/.

A typical workflow is:

  1. create a new target module such as src/compile/my_target.rs
  2. implement the compiler behavior for that target
  3. register the target so the front matter can select it
  4. add tests that compile representative inputs and verify generated output

Use this when the project needs a different kind of generated Azure DevOps template.

Front-matter types live in src/compile/types.rs.

To add a field:

  1. add the new field to the relevant Rust type
  2. update parsing and validation
  3. thread the value into compilation where needed
  4. add tests for valid and invalid input

If the change is breaking, also add a codemod under src/compile/codemods/.

Use a codemod for changes such as:

  • renaming a field
  • removing a field
  • changing a field’s shape
  • adding a new required field

Template markers are placeholders in the pipeline templates under src/data/.

To add one:

  1. add the marker to the relevant template file
  2. update the target compiler that replaces markers
  3. ensure the replacement is correct for every target that needs it
  4. add tests that assert the generated YAML contains the expected expansion

This is the right approach when a feature needs new generated YAML in the final pipeline.

Safe-output tools live in src/safeoutputs/.

Typical workflow:

  1. add a new file in src/safeoutputs/
  2. define the tool’s input and validation rules
  3. implement Stage 3 execution behavior
  4. register the tool in the safe-output module wiring
  5. expose it through the MCP server and execution path
  6. add compile-time and execution tests

Use a safe output when the agent should propose an action that is validated and executed later, instead of performing the action directly.

First-class tools live under src/tools/<name>/.

A common pattern is:

  1. create src/tools/<name>/mod.rs
  2. create src/tools/<name>/extension.rs
  3. add execute.rs if the tool also needs Stage 3 runtime behavior
  4. extend the front-matter config types in src/compile/types.rs
  5. register the tool in extension collection
  6. add tests

Use a first-class tool when the feature is user-configured under tools: and needs compiler-managed setup.

Runtimes live under src/runtimes/<name>/.

To add one:

  1. create src/runtimes/<name>/mod.rs for config types and helpers
  2. create src/runtimes/<name>/extension.rs for compiler integration
  3. extend RuntimesConfig in src/compile/types.rs
  4. register the runtime in extension collection
  5. add tests for pipeline generation, validation, and runtime-specific behavior

Use a runtime when the feature installs or configures a language or execution environment before the agent runs.

The key abstraction for tools and runtimes is CompilerExtension in src/compile/extensions/mod.rs.

Implement this trait when your feature needs to contribute things like:

  • required network hosts
  • required bash commands
  • prompt supplements
  • prepare or setup steps
  • MCP gateway server entries
  • allowed Copilot tools
  • compile-time validation
  • pipeline environment mappings
  • AWF mounts or PATH updates

In practice, the workflow is:

  1. implement CompilerExtension for your runtime or tool
  2. return the pieces your feature needs
  3. let the compiler collect and merge those requirements

This keeps new features composable instead of scattering special-case logic across the compiler.

When adding a new feature:

  1. decide whether it belongs under safe-outputs, tools, runtimes, or target compilation
  2. add or update front-matter types in src/compile/types.rs
  3. implement the behavior in the colocated module
  4. register the feature in the compiler’s collection and dispatch points
  5. add tests for parsing, validation, and generated YAML
  6. run cargo test and cargo clippy

Choose the extension point that matches the job:

  • CLI command: new end-user command
  • compile target: new output shape for generated pipelines
  • front-matter field: new author-facing configuration
  • template marker: new generated YAML insertion point
  • safe output: validated deferred write action
  • first-class tool: agent capability configured under tools:
  • runtime: installed language or execution environment

If you place the feature in the right extension point from the start, the rest of the implementation tends to stay much simpler.