Creating Custom Recipes

Build multi-step workflows that orchestrate agents.

Overview

Recipes let you:

  • Orchestrate agents - Chain specialists together
  • Create repeatable workflows - Run the same process consistently
  • Add approval gates - Human-in-the-loop checkpoints
  • Handle complex flows - Loops, conditions, parallel execution

Recipe Structure

# my-recipe.yaml
name: my-workflow
description: What this recipe does
version: "1.0.0"

# Input variables (flat dictionary)
context:
  input_var: ""           # Must be provided at execution time
  optional_var: "default" # Has a default value

# Workflow steps
steps:
  - id: step-one
    prompt: "Do the first thing with {{input_var}}"
    output: "first_result"

  - id: step-two
    agent: foundation:zen-architect
    prompt: |
      Based on: {{first_result}}
      Do the second thing.

Basic Recipe

Simple Two-Step Recipe

name: explain-and-improve
description: Explain code then suggest improvements
version: "1.0.0"

context:
  file: ""

steps:
  - id: explain
    prompt: "Explain what {{file}} does in plain language"
    output: "explanation"

  - id: improve
    prompt: |
      Based on this understanding:
      {{explanation}}

      Suggest 3 improvements for {{file}}

Run It

amp recipes execute explain-and-improve.yaml \
  --context '{"file": "src/auth.py"}'

Using Agents

Specify Agent for Step

steps:
  - id: design
    agent: foundation:zen-architect
    prompt: "Design a solution for {{problem}}"
    output: "design_result"

  - id: implement
    agent: foundation:modular-builder
    prompt: |
      Implement the design:
      {{design_result}}
    output: "implementation"

  - id: review
    agent: foundation:security-guardian
    prompt: "Review the implementation for security issues"

Bash Steps

Not every step needs an AI agent. Steps with type: bash execute shell commands directly and capture stdout/stderr as the step output. This is ideal for build tools, test runners, linters, and any deterministic operation.

Run Tests and Capture Results

name: test-and-report
description: Run tests then summarize results with an agent
version: "1.0.0"

context:
  project_dir: ""

steps:
  - id: run-tests
    type: bash
    command: "pytest tests/ --tb=short -q 2>&1"
    cwd: "{{project_dir}}"
    timeout: 180
    output: "test_output"

  - id: summarize
    prompt: |
      Here are the test results:
      {{test_output}}

      Summarize: how many passed, how many failed, and what
      the failures are about. Be concise.

Bash Step Fields

Field Purpose
type: bash Marks the step as a shell command
command The shell command to execute
cwd Directory to run the command in (optional)
timeout Seconds before the command is killed (optional)
output Variable name to store stdout/stderr (optional)

The command's combined stdout and stderr become the step output, accessible to later steps via the variable name specified in output.

Build, Lint, and Report

steps:
  - id: install-deps
    type: bash
    command: "pip install -r requirements.txt"
    cwd: "{{project_dir}}"
    timeout: 120

  - id: lint
    type: bash
    command: "ruff check src/ --output-format text"
    cwd: "{{project_dir}}"
    on_error: continue
    output: "lint_output"

  - id: typecheck
    type: bash
    command: "pyright src/"
    cwd: "{{project_dir}}"
    on_error: continue
    output: "typecheck_output"

  - id: quality-report
    prompt: |
      Lint results:
      {{lint_output}}

      Type check results:
      {{typecheck_output}}

      Create a prioritized list of issues to fix.

Context Variables

Input Variables

context:
  # Must be provided at execution time (empty default)
  target_file: ""

  # Has a default value (optional to override)
  depth: "standard"
  include_tests: "true"

Access Variables

prompt: |
  Review {{target_file}} with {{depth}} depth.

Step Results

Each step with an output field stores its result as a named variable. Later steps reference that variable name:

steps:
  - id: analyze
    prompt: "Analyze the code"
    output: "analysis"

  - id: report
    prompt: |
      Previous analysis: {{analysis}}
      Create a summary report.

Approval Gates

Approval gates require staged recipes. Stages group related steps together with optional approval checkpoints between them.

Require Human Approval

name: migration-workflow
description: Database migration with approval gate
version: "1.0.0"

stages:
  - name: planning
    steps:
      - id: plan
        prompt: "Create a migration plan"
        output: "migration_plan"
    approval:
      required: true
      prompt: "Review the migration plan before proceeding."
      timeout: 3600
      default: "deny"

  - name: execution
    steps:
      - id: execute
        prompt: "Execute the migration: {{migration_plan}}"

Approval Commands

# Check pending approvals
amp recipes approvals

# Approve a pending stage
amp recipes approve --session-id <id> --stage-name planning

# Deny with reason
amp recipes deny --session-id <id> --stage-name planning \
  --reason "Need changes"

Advanced Features

Foreach Loops

Use foreach to repeat a step for each item in a list. The foreach field takes a variable reference, as names the loop variable, and parallel controls concurrency.

steps:
  - id: get-files
    type: bash
    command: "find src/ -name '*.py' -type f"
    output: "file_list"

  - id: review-each
    foreach: "{{file_list}}"
    as: current_file
    parallel: 3
    agent: foundation:explorer
    prompt: "Review {{current_file}} for code quality issues"
    collect: "file_reviews"

Without parallel (or with parallel: false), iterations run one at a time. Setting it to an integer (as above) allows that many iterations to execute concurrently -- useful when iterations are independent of each other. Setting it to true runs all iterations simultaneously.

Foreach: Complete Working Example

name: bulk-file-review
description: Review all Python files in a directory
version: "1.0.0"

context:
  src_dir: ""

steps:
  - id: discover
    type: bash
    command: "find {{src_dir}} -name '*.py' -not -path '*/__pycache__/*'"
    output: "discovered_files"

  - id: review-files
    foreach: "{{discovered_files}}"
    as: file_path
    parallel: 4
    agent: foundation:zen-architect
    prompt: |
      Review {{file_path}} for:
      - Complexity issues
      - Naming clarity
      - Separation of concerns
      Return a short summary with a 1-10 quality score.
    collect: "file_reviews"

  - id: aggregate
    prompt: |
      Combine all file reviews:
      {{file_reviews}}

      Create a single prioritized report of issues across the codebase.

Conditional Steps

steps:
  - id: check
    prompt: "Does {{file}} have tests? Return JSON: {\"has_tests\": true/false}"
    output: "check_result"
    parse_json: true

  - id: write-tests
    condition: "{{check_result.has_tests}} == 'false'"
    prompt: "Write tests for {{file}}"

Error Handling

steps:
  - id: risky-step
    prompt: "Try something that might fail"
    on_error: continue  # Don't stop on failure
    output: "risky_output"

  - id: fallback
    condition: "{{risky_output}} == ''"
    prompt: "The previous step may have failed. Investigate and recover."

While Loops (Convergence)

Use while_condition to repeat a step until a condition becomes false. This is the right tool when you need iterative refinement -- keep improving something until it meets a quality bar.

Key fields:

Field Purpose
while_condition Expression evaluated before each iteration; loop continues while true
break_when Early exit condition (safety valve to prevent runaway loops)
update_context Variables to update after each iteration
max_while_iterations Hard safety limit (default: 100)

Note: Condition expressions currently support ==, !=, and, and or operators. Numeric comparisons (<, >, >=, <=), not, and parentheses are planned but not yet implemented. Structure your conditions around string equality checks.

While Loop: Working Example

This recipe iterates on a document draft until an evaluator deems it sufficient:

name: iterative-refinement
description: Refine a document until quality threshold is met
version: "1.0.0"

context:
  topic: ""
  quality_sufficient: "false"

steps:
  - id: initial-draft
    prompt: |
      Write a first draft of a technical document about {{topic}}.
      Keep it concise and well-structured.
    output: "current_draft"

  - id: refine
    prompt: |
      Current draft:
      {{current_draft}}

      Evaluate the draft for clarity, accuracy, and completeness.
      If it needs improvement, revise it. Return JSON:
      {"draft": "<revised text>", "sufficient": "true" or "false"}
    output: "refinement"
    parse_json: true
    while_condition: "{{quality_sufficient}} != 'true'"
    max_while_iterations: 5
    update_context:
      current_draft: "{{refinement.draft}}"
      quality_sufficient: "{{refinement.sufficient}}"

  - id: final-output
    prompt: |
      Polish and format the final draft:
      {{current_draft}}

The step re-executes after each iteration with updated context. Once quality_sufficient becomes "true", the loop exits. The max_while_iterations field prevents runaway loops if convergence is slow.

While Loop: Retry Until Success

Another common pattern is retrying an operation until it succeeds:

steps:
  - id: deploy-attempt
    type: bash
    command: "curl -sf https://api.example.com/deploy --data '{\"version\": \"{{version}}\"}'"
    on_error: continue
    output: "deploy_output"
    while_condition: "{{deploy_succeeded}} != 'true'"
    max_while_iterations: 3

Staged Recipes with Approval Gates

For workflows that require human checkpoints, staged recipes divide work into stages separated by approval gates. Each stage groups multiple steps into a logical phase, and an approval block on the stage defines whether execution pauses after that stage completes.

Staged Recipe Structure

name: release-workflow
description: Build, test, and release with human checkpoints
version: "1.0.0"

context:
  version: ""
  repo_dir: ""

stages:
  - name: build-and-test
    steps:
      - id: build
        type: bash
        command: "make build VERSION={{version}}"
        cwd: "{{repo_dir}}"
        timeout: 300
        output: "build_output"

      - id: test
        type: bash
        command: "make test"
        cwd: "{{repo_dir}}"
        timeout: 600
        output: "test_output"

      - id: test-summary
        prompt: |
          Summarize the build and test results:
          Build output: {{build_output}}
          Test output: {{test_output}}
        output: "summary"

  - name: staging-deploy
    approval:
      required: true
      prompt: |
        Build and tests complete:
        {{summary}}

        Approve to deploy {{version}} to staging?
      timeout: 3600
      default: "deny"
    steps:
      - id: deploy-staging
        type: bash
        command: "make deploy ENV=staging VERSION={{version}}"
        cwd: "{{repo_dir}}"
        output: "staging_result"

      - id: smoke-test
        type: bash
        command: "make smoke-test ENV=staging"
        cwd: "{{repo_dir}}"
        output: "smoke_results"

  - name: production-release
    approval:
      required: true
      prompt: |
        Staging deployment and smoke tests complete:
        {{smoke_results}}

        Approve production release of {{version}}?
      timeout: 3600
      default: "deny"
    steps:
      - id: deploy-prod
        type: bash
        command: "make deploy ENV=production VERSION={{version}}"
        cwd: "{{repo_dir}}"
        output: "prod_result"

      - id: verify-prod
        prompt: |
          Production deployment complete.
          Approval decision was: {{_approval_message}}
          Verify the deployment is healthy.

Managing Staged Approvals

# See what is waiting for approval
amp recipes approvals

# Approve with an optional message (available as {{_approval_message}})
amp recipes approve --session-id <id> --stage-name staging-deploy \
  --message "looks good, proceed"

# Deny and halt the recipe
amp recipes deny --session-id <id> --stage-name production-release \
  --reason "Smoke tests show elevated error rate"

The _approval_message variable is useful when the approver needs to communicate a decision downstream -- for example, whether to merge directly or create a PR.

Recipe Composition

Complex workflows can be assembled from smaller, focused recipes using type: recipe steps. Each sub-recipe runs to completion and its output becomes the calling step's result.

Why Compose?

  • Reuse: A lint recipe used in both CI and code-review workflows
  • Testing: Test each sub-recipe independently
  • Clarity: Keep individual recipe files focused on one job

Composition: Working Example

Suppose you have two standalone recipes:

lint-recipe.yaml:

name: lint
description: Run linting and format checks
version: "1.0.0"

context:
  target: ""

steps:
  - id: lint-check
    type: bash
    command: "ruff check {{target}} --output-format text"
    on_error: continue
    output: "lint_results"

  - id: format-check
    type: bash
    command: "ruff format --check {{target}}"
    on_error: continue
    output: "format_results"

  - id: lint-report
    prompt: |
      Lint results: {{lint_results}}
      Format results: {{format_results}}
      Summarize issues found.

test-recipe.yaml:

name: test
description: Run tests with coverage
version: "1.0.0"

context:
  test_dir: ""

steps:
  - id: run-tests
    type: bash
    command: "pytest {{test_dir}} --cov --tb=short -q"
    timeout: 300
    output: "test_results"

  - id: test-report
    prompt: |
      Test results: {{test_results}}
      Summarize pass/fail counts and coverage percentage.

A parent recipe composes them:

code-quality.yaml:

name: code-quality
description: Full code quality check using sub-recipes
version: "1.0.0"

context:
  src_dir: ""
  test_dir: "tests/"

steps:
  - id: lint
    type: recipe
    recipe: "./lint-recipe.yaml"
    context:
      target: "{{src_dir}}"
    output: "lint_report"

  - id: tests
    type: recipe
    recipe: "./test-recipe.yaml"
    context:
      test_dir: "{{test_dir}}"
    output: "test_report"

  - id: combined-report
    prompt: |
      Combine the quality reports:

      ## Lint Report
      {{lint_report}}

      ## Test Report
      {{test_report}}

      Create an overall quality assessment with
      a prioritized list of action items.

Run the composed recipe exactly like any other:

amp recipes execute code-quality.yaml \
  --context '{"src_dir": "src/", "test_dir": "tests/"}'

Each sub-recipe runs in its own context. The parent recipe only sees the final output from each sub-recipe step, keeping the context clean.

Complete Examples

Code Review Recipe

name: code-review
description: Comprehensive code review with multiple specialists
version: "1.0.0"

context:
  file_path: ""
  review_depth: "standard"

steps:
  - id: design-review
    agent: foundation:zen-architect
    prompt: |
      Review {{file_path}} for design issues:
      - Architecture patterns
      - Code organization
      - Complexity

      Depth: {{review_depth}}
    output: "design_findings"

  - id: security-review
    agent: foundation:security-guardian
    prompt: |
      Review {{file_path}} for security issues:
      - Input validation
      - Authentication/authorization
      - Data handling
    output: "security_findings"

  - id: test-review
    agent: foundation:test-coverage
    prompt: |
      Analyze test coverage for {{file_path}}:
      - Existing tests
      - Missing coverage
      - Test quality
    output: "test_findings"

  - id: final-report
    prompt: |
      Create a code review report combining:

      ## Design Review
      {{design_findings}}

      ## Security Review
      {{security_findings}}

      ## Test Coverage
      {{test_findings}}

      Include:
      - Summary of findings
      - Priority-ordered action items
      - Overall assessment

Deployment Recipe with Approval

name: deploy
description: Deploy to staging with approval gate
version: "1.0.0"

context:
  environment: "staging"
  version: ""

stages:
  - name: preparation
    steps:
      - id: build
        prompt: "Build version {{version}}"
        output: "build_result"

      - id: test
        prompt: "Run full test suite"
        output: "test_result"

      - id: pre-deploy-check
        prompt: |
          Verify deployment readiness:
          - Build: {{build_result}}
          - Tests: {{test_result}}

          List any blockers.
        output: "deploy_readiness"
    approval:
      required: true
      prompt: |
        Ready to deploy {{version}} to {{environment}}.

        Pre-check results:
        {{deploy_readiness}}
      timeout: 3600
      default: "deny"

  - name: deployment
    steps:
      - id: deploy
        prompt: "Deploy {{version}} to {{environment}}"
        output: "deploy_result"

      - id: verify
        prompt: "Verify deployment health"

Recipe Validation

Check your recipe before running:

amp recipes validate my-recipe.yaml

Common validation errors:

Error Solution
Invalid YAML syntax Check indentation, quotes
Unknown agent Verify agent name
Missing context var Add to context section
Circular reference Check step dependencies

Recipe Author Agent

Get help creating recipes:

> Help me create a recipe for database migration

The recipe-author agent will: - Ask clarifying questions - Suggest best structure - Generate valid YAML - Add error handling

Try It Yourself

Exercise 1: Simple Recipe

# hello-recipe.yaml
name: hello
description: Simple greeting recipe
version: "1.0.0"

context:
  name: ""

steps:
  - id: greet
    prompt: "Say hello to {{name}}"
amp recipes execute hello-recipe.yaml --context '{"name": "World"}'

Exercise 2: Multi-Step Recipe

Create a recipe that: 1. Reads a file 2. Explains it 3. Suggests improvements

Exercise 3: Add Approval Gate

Convert a flat recipe into a staged recipe with an approval gate before making changes.

Best Practices

  1. Clear step IDs - Use descriptive names like analyze-deps not step1
  2. One responsibility per step - Keep steps focused
  3. Use appropriate agents - Match agent specialty to task
  4. Add approval gates - For destructive or important operations (use staged recipes)
  5. Handle errors - Use on_error for steps that might fail
  6. Name your outputs - Use the output field so results are available downstream
  7. Document context - Make required inputs clear