Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

YAML Recipe Format Reference

Complete schema reference for amplihack recipe runner YAML files.

Top-Level Fields

FieldTypeRequiredDefaultDescription
namestringyes—Recipe name
versionstringno"1.0"Semantic version
descriptionstringno""Human-readable description
authorstringno""Author name
tagslist of stringsno[]Recipe tags for categorisation
contextmapno{}Default variable values for templates
extendsstringno—Parent recipe name (for inheritance). Note: only single-level inheritance is supported; extended recipes cannot themselves use extends.
recursionRecursionConfignosee belowSub-recipe recursion limits
hooksRecipeHooksno—Lifecycle hooks — bash commands, run by the resolved bash interpreter
stepslist of Stepyes—Ordered list of steps to execute

RecursionConfig

Controls sub-recipe nesting limits.

FieldTypeDefaultDescription
max_depthint6Maximum sub-recipe recursion depth
max_total_stepsint200Maximum total steps across all sub-recipes

RecipeHooks

Shell commands executed at lifecycle boundaries.

FieldTypeDescription
pre_stepstringShell command to run before each step
post_stepstringShell command to run after each step
on_errorstringShell command to run on step failure

Hook commands receive context variables via template substitution. Each runs as a bash step with a fixed 30-second timeout, under the same resolved bash interpreter as ordinary command: steps. A failing hook is logged as a warning and does not fail the step.


Step Fields

FieldTypeRequiredDefaultDescription
idstringyes—Unique step identifier
typestringnoinferred"bash", "agent", or "recipe" (see inference rules)
commandstringno—Shell command (bash steps), run by the resolved bash interpreter
agentstringno—Agent reference (agent steps)
promptstringno—Prompt template (agent steps)
outputstringno—Variable name to store step output in context
conditionstringno—Expression that must be truthy to execute
parse_jsonboolnofalseExtract JSON from step output
parse_json_requiredboolnofalseFail the step if JSON extraction fails (see below)
modestringno—Execution mode
working_dirstringno—Override working directory for this step
timeoutintno—Step timeout in seconds
auto_stageboolnotrueGit auto-stage after agent steps
modelstringno—Model override for agent steps (e.g., “haiku”, “sonnet”)
recipestringno—Sub-recipe name (recipe steps)
recovery_on_failureboolnofalseAttempt agentic recovery if sub-recipe fails (see below)
contextmapno—Context overrides passed to sub-recipe
continue_on_errorboolnofalseContinue execution if this step fails
fatalboolnotruefatal: false is shorthand for continue_on_error: true
nonfatalboolnofalseAlias for continue_on_error
when_tagslist of stringsno[]Step only runs when these tags match active tag filters
parallel_groupstringno—Group name for parallel execution

Note: The context field provides step-specific variables that are passed as overrides to sub-recipes. In YAML you write context:.

Unknown and retired step fields

A step key that isn’t in the table above is ignored, with a warning. The recipe still parses and runs, so an existing recipe never breaks because of an unknown key, but a typo or an unsupported field can’t silently do nothing.

The runner prints one warning per unknown key when it parses the recipe:

recipe 'deploy': step 'build' has unknown field 'comand' (ignored)

A field that used to mean something but is now retired gets a specific message that says what to do. The only retired field is max_env_value_bytes:

recipe 'workflow-tdd': step 'checkpoint-3': field 'max_env_value_bytes' is retired and ignored — the runner now spills oversized env values to private files automatically; remove it from the recipe

max_env_value_bytes was a per-step setting to avoid Argument list too long errors. The runner now handles oversized values automatically without truncating them (see Per-value env spill), so you can remove the field.

Details:

  • A step without an id is shown as <index N> (zero-based). A recipe without a name is shown as <unnamed>. A non-string key is shown as <non-string key>.
  • Recipe names, step ids and keys are escaped (\n, \u{1b}, …) and cut to 128 characters, so a warning is always a single line.
  • A recipe gives at most 100 field warnings, followed by … and N more unknown step fields.
  • Warnings go through the logger at warn level when it’s enabled (for example RUST_LOG=warn). Otherwise they go to stderr prefixed with warning: .
  • Each distinct warning is printed once per process, so a sub-recipe that runs several times in one recipe run is reported once.
  • --validate-only lists the same messages in its own report and doesn’t print them twice. Unknown fields are warnings, so it still exits 0. See --validate-only.

Library users who parse recipes themselves can turn the parse-time warnings off:

#![allow(unused)]
fn main() {
use std::path::Path;
use recipe_runner_rs::parser::RecipeParser;

let parser = RecipeParser::new().without_field_warnings();
let recipe = parser.parse_file(Path::new("recipes/deploy.yaml"))?;
}

RecipeParser::new() (and RecipeParser::default()) print the warnings. RecipeParser is a struct with a private field, so build it with new() or default(); the unit-struct expression RecipeParser doesn’t compile.


Type Inference Rules

When type is omitted, the effective step type is inferred in this order:

  1. recipe field present → recipe type
  2. agent field present → agent type
  3. prompt present without command → agent type
  4. Otherwise → bash type (default)

An explicit type value always takes precedence.

# Inferred as bash (has command, no agent/recipe/prompt)
- id: build
  command: cargo build --release

# Inferred as agent (agent field present)
- id: review
  agent: code-reviewer
  prompt: "Review {{file}}"

# Inferred as agent (prompt without command)
- id: summarise
  prompt: "Summarise the changes in {{diff}}"

# Inferred as recipe (recipe field present)
- id: deploy
  recipe: deploy-production
  context:
    env: staging

# Explicit type overrides inference
- id: special
  type: bash
  prompt: "This prompt is ignored because type is bash"
  command: echo "explicit wins"

Template Syntax

Variables are substituted using {{variable_name}} syntax. Variable names may contain letters, digits, underscores, hyphens, and dots.

context:
  project: my-app
  branch: main

steps:
  - id: greet
    command: echo "Building {{project}} on {{branch}}"

Dot Notation

Nested context values are accessed with dot notation:

context:
  deploy:
    target: production
    region: us-east-1

steps:
  - id: deploy
    command: ./deploy.sh --target {{deploy.target}} --region {{deploy.region}}

Shell Escaping

When templates are rendered for shell commands, values are shell-escaped automatically via shell_escape to prevent injection. Undefined variables resolve to an empty string.


Condition Syntax

The condition field accepts an expression that is evaluated against the current context. Steps with a falsy condition are skipped.

See conditions.md for the full reference. Supported operators and built-in functions include:

  • Comparisons: ==, !=, <, <=, >, >=
  • Logical: and, or, not
  • Membership: in, not in
  • Functions: int(), str(), len(), bool(), float(), min(), max()
  • Methods: strip(), lower(), upper(), startswith(), endswith(), replace(), split(), join(), count(), find()
- id: deploy
  condition: "branch == 'main' and tests_passed == 'true'"
  command: ./deploy.sh

JSON Extraction (parse_json)

When parse_json: true, the runner attempts to extract structured JSON from step output using three strategies in order:

  1. Direct parse — the entire trimmed output is valid JSON.
  2. Markdown fence extraction — JSON inside ```json ... ``` fences.
  3. Balanced bracket detection — locates the first {…} or […] block with proper depth tracking, string awareness, and escape handling.

If all strategies fail a warning is logged and the raw output is stored.

- id: get-config
  command: curl -s https://api.example.com/config
  output: api_config
  parse_json: true

- id: use-config
  command: echo "Region is {{api_config.region}}"

Strict Mode (parse_json_required)

By default, JSON extraction failure degrades the step (status becomes degraded) but the recipe continues. Set parse_json_required: true to make extraction failure a hard error that stops the recipe.

- id: must-be-json
  command: curl -s https://api.example.com/data
  parse_json: true
  parse_json_required: true  # fails the recipe if output isn't valid JSON
  output: api_data
parse_json_requiredOn extraction failure
false (default)Step marked degraded, raw output stored, recipe continues
trueStep marked failed, recipe stops immediately

Sub-Recipe Recovery (recovery_on_failure)

When a sub-recipe step fails, set recovery_on_failure: true to trigger an agentic recovery attempt. The runner sends the failure details to an agent, which attempts to complete the remaining work.

- id: deploy
  recipe: deploy-to-staging
  recovery_on_failure: true  # agent attempts recovery if deploy fails

If the agent’s recovery output contains “STATUS: COMPLETE” or “recovered”, the step is marked as recovered and the recipe continues. Otherwise, the original failure propagates.


Model Override (model)

Agent steps can override the default model using the model field. The value is passed to the selected provider adapter. For Codex, it is forwarded explicitly as --model; omission leaves selection to native Codex configuration. Codex never uses an implicit auto fallback. Choose a provider-supported identifier; the Claude examples below are not portable Codex model names. See Codex agent steps.

- id: quick-check
  agent: reviewer
  prompt: "Quick lint check on {{file_path}}"
  model: haiku  # fast, cheap model for simple tasks

- id: deep-review
  agent: reviewer
  prompt: "Thorough security review of {{file_path}}"
  model: sonnet  # more capable model for complex analysis

Complete Examples

1. Simple Bash-Only Recipe

name: build-and-test
version: "1.0"
description: Build the project and run tests
author: dev-team
tags: [ci, build]

context:
  build_mode: release

steps:
  - id: clean
    command: cargo clean

  - id: build
    command: cargo build --{{build_mode}}

  - id: test
    command: cargo test --{{build_mode}}
    output: test_results

  - id: report
    command: echo "Tests complete. Results {{test_results}}"

2. Agent-Based Workflow

name: code-review-workflow
version: "1.0"
description: Automated code review with AI agents

context:
  target_branch: main

steps:
  - id: get-diff
    command: git diff {{target_branch}} --stat
    output: diff_summary

  - id: review
    agent: code-reviewer
    prompt: |
      Review the following changes against {{target_branch}}:
      {{diff_summary}}
      Focus on correctness, security, and performance.
    output: review_result
    parse_json: true

  - id: check-approved
    condition: "review_result.approved == true"
    command: echo "Review passed"

  - id: request-changes
    condition: "review_result.approved != true"
    command: echo "Changes requested — see review_result.comments"

3. Sub-Recipe Composition

name: full-pipeline
version: "2.0"
description: End-to-end pipeline composing smaller recipes

context:
  environment: staging

steps:
  - id: lint
    recipe: lint-check

  - id: build
    recipe: build-project
    context:
      build_mode: release
      target: "{{environment}}"

  - id: deploy
    recipe: deploy-service
    context:
      env: "{{environment}}"
      version: "{{build.version}}"
    condition: "environment != 'local'"

4. Recipe with Hooks, Tags, and Recursion Limits

name: guarded-pipeline
version: "1.0"
description: Pipeline with lifecycle hooks and safety limits
author: platform-team
tags: [production, safe]

recursion:
  max_depth: 3
  max_total_steps: 50

hooks:
  pre_step: echo "[$(date -Iseconds)] Starting step"
  post_step: echo "[$(date -Iseconds)] Finished step"
  on_error: |
    echo "FAILED — sending alert"
    curl -s -X POST https://alerts.example.com/hook \
      -d '{"step": "failed", "recipe": "guarded-pipeline"}'

context:
  notify: true

steps:
  - id: preflight
    command: ./scripts/preflight-check.sh

  - id: migrate
    command: ./scripts/migrate.sh
    when_tags: [database]

  - id: deploy
    command: ./scripts/deploy.sh
    when_tags: [deploy]

  - id: smoke-test
    command: ./scripts/smoke-test.sh
    timeout: 120
    when_tags: [deploy]

  - id: notify
    condition: "notify == 'true'"
    command: echo "Pipeline complete"

5. Recipe with continue_on_error and Conditions

name: resilient-checks
version: "1.0"
description: Run multiple checks, collecting results even on failures

context:
  strict: false

steps:
  - id: lint
    command: cargo clippy -- -D warnings
    output: lint_result
    continue_on_error: true

  - id: test
    command: cargo test 2>&1
    output: test_result
    continue_on_error: true

  - id: audit
    command: cargo audit
    output: audit_result
    continue_on_error: true

  - id: gate
    condition: "strict == 'true'"
    command: |
      echo "Lint: {{lint_result}}"
      echo "Test: {{test_result}}"
      echo "Audit: {{audit_result}}"
      # Fail the pipeline in strict mode if any check failed
      exit 1

  - id: summary
    condition: "strict != 'true'"
    command: |
      echo "=== Check Summary ==="
      echo "Lint:  {{lint_result}}"
      echo "Test:  {{test_result}}"
      echo "Audit: {{audit_result}}"
      echo "Non-strict mode — pipeline continues"