Skip to content

Step Types Reference

Every playbook step has a step_type that determines what it does. Steps run sequentially; each can access outputs from all previous steps via {{.step_outputs.<output_key>}}.

Template syntax

Reference earlier step output:

yaml
config:
  instruction: "Summarise this: {{.step_outputs.raw_data}}"

Reference playbook inputs:

yaml
config:
  instruction: "The topic is: {{.inputs.topic}}"

Reference today's date:

yaml
config:
  instruction: "Today is {{.Date}}"

AI steps

ai_prompt

Send a prompt to an AI model and capture the text response.

yaml
- name: Write summary
  step_type: ai_prompt
  output_key: summary
  config:
    provider: anthropic          # anthropic | google | openai
    model: claude-sonnet-4-6
    max_tokens: 1024
    instruction: "Summarise: {{.step_outputs.data}}"

ai_transform

Transform structured data using an AI model. Good for classifying, enriching, or reshaping JSON.

yaml
- name: Classify tasks
  step_type: ai_transform
  output_key: classified
  config:
    input_key: raw_tasks
    provider: google
    model: gemini-2.0-flash
    max_tokens: 4096
    instruction: "Classify each task by urgency: overdue, this_week, upcoming."
    output_format: '{"tasks": [{"id": 0, "urgency": ""}]}'

llm_extract

Extract structured fields from free-form text using an AI model.

yaml
- name: Extract intake
  step_type: llm_extract
  output_key: intake
  config:
    model: claude-sonnet-4-6
    schema_ref: my-intake-schema-v1   # references a knowledge file tag

notes_fill

Fill a structured form (JSON Schema) from unstructured text or images. Merges values into existing form data.

yaml
- name: Fill form from notes
  step_type: notes_fill
  output_key: filled_form
  config:
    model: claude-sonnet-4-6
    schema_key: intake              # existing form data key
    notes_key: raw_notes            # step output with free-form text

vision_describe

Describe image(s) attached to the playbook run using a vision model.

yaml
- name: Describe site photos
  step_type: vision_describe
  output_key: photo_descriptions
  config:
    model: claude-sonnet-4-6
    instruction: "Describe what you see in this garden site photo."

sanity_review

Ask an AI to flag issues or inconsistencies in a previous step's output. Returns a structured review.

yaml
- name: Sanity check
  step_type: sanity_review
  output_key: review
  config:
    input_key: generated_text
    model: claude-sonnet-4-6
    criteria: "Check for factual errors, missing sections, and inconsistent numbers."

humanize

Rewrite AI-generated text to sound more natural and human.

yaml
- name: Humanize draft
  step_type: humanize
  output_key: human_text
  config:
    input_key: ai_draft
    model: claude-sonnet-4-6

classify_image

Classify an image against a set of labels.

yaml
- name: Classify product image
  step_type: classify_image
  output_key: image_category
  config:
    labels: ["garden", "interior", "exterior", "product"]
    model: claude-sonnet-4-6

generate_image

Generate an image from a text prompt.

yaml
- name: Generate hero image
  step_type: generate_image
  output_key: hero_image
  config:
    prompt: "A professional photo of {{.inputs.subject}}, high quality, natural light"
    provider: openai
    model: dall-e-3
    size: "1024x1024"

generate_content

Generate a full article or content piece using the Studio content pipeline.

yaml
- name: Write article
  step_type: generate_content
  output_key: article
  config:
    topic: "{{.inputs.topic}}"
    language: nl

generate_presentation

Generate a PPTX presentation from structured slide JSON.

yaml
- name: Build deck
  step_type: generate_presentation
  output_key: presentation
  config:
    input_key: slides_json
    template: "Taufinity Branded"

create_topic_ideas

Generate a list of content topic ideas.

yaml
- name: Generate topic ideas
  step_type: create_topic_ideas
  output_key: topics
  config:
    seed: "{{.inputs.category}}"
    count: 10

Data steps

odoo_read

Read records from Odoo using a domain filter.

yaml
- name: Pull open tasks
  step_type: odoo_read
  output_key: tasks
  config:
    credential_ref: "My Odoo"         # name of the credential in Studio
    model: project.task
    domain: '[["stage_id.fold","=",false]]'
    fields: [id, name, stage_id, date_deadline]
    limit: 200
    order: "date_deadline asc"

odoo_update

Update existing Odoo records.

yaml
- name: Mark tasks done
  step_type: odoo_update
  output_key: update_result
  config:
    credential_ref: "My Odoo"
    model: project.task
    ids_key: task_ids               # step output with list of IDs
    values:
      stage_id: 5

odoo_bulk_create

Create multiple Odoo records in one call.

yaml
- name: Create Odoo tasks
  step_type: odoo_bulk_create
  output_key: created_ids
  config:
    credential_ref: "My Odoo"
    model: project.task
    records_key: new_tasks          # step output with list of record objects

knowledge_lookup

Search the knowledge base for files matching a query.

yaml
- name: Find pricing files
  step_type: knowledge_lookup
  output_key: pricing_context
  config:
    query: "{{.inputs.category}} pricing"
    limit: 3

knowledge_match

Return the single best matching knowledge chunk for a query, scoped by tags and (optionally) chunk type.

yaml
- name: Get brand guidelines
  step_type: knowledge_match
  output_key: brand_context
  config:
    knowledge_tags: [brand-guidelines]
    query_template: "brand-guidelines"
    top_k: 1
    similarity_threshold: 0    # no similarity floor — see below
    fail_on_no_match: true     # error instead of writing "null"

Threshold semantics

similarity_threshold is the minimum cosine similarity a chunk must reach.

ConfigMeaning
omitteddefault floor of 0.5
similarity_threshold: 0no floor — return the best chunk found, whatever it scores
similarity_threshold: 0.8require similarity >= 0.8

An explicit 0 is not the same as omitting the field.

For a tag-scoped fetch, set similarity_threshold: 0. A tag-scoped fetch is the "just give me the file tagged X" pattern: knowledge_tags: [X], top_k: 1, and a query that is literally the tag name. The tag already selects the chunks; similarity only ranks them. A short tag-name query ("mt-cashflow") scores well below 0.5 against real prose, so with the default floor the step matches nothing.

No-match behaviour

By default a no-match is not an error: the step writes the string "null" to its output key and the playbook continues. This is quiet by design (an optional book quote that finds nothing shouldn't kill a run), but it hides real breakage — in the MT-deck playbook, 7 of 8 knowledge_match steps returned null for months without anyone noticing.

Two things make it visible now:

  • Every no-match logs a WARN starting with knowledge_match: no chunk matched, including org_id, output_key, query, knowledge_tags, chunk_type and the effective threshold actually applied. Grep for it.
  • fail_on_no_match: true makes the step return an error instead of writing "null". Set it on any step whose output is actually consumed downstream — a silent null there is worse than a loud failure. Default is false, so existing playbooks are unchanged.

knowledge_extract_quotes

Extract relevant quotes from knowledge files for a given topic.

yaml
- name: Extract relevant quotes
  step_type: knowledge_extract_quotes
  output_key: quotes
  config:
    query: "{{.inputs.topic}}"
    tag: source-material
    limit: 5

create_knowledge_file

Write a new knowledge file from a previous step's output.

yaml
- name: Save summary to KB
  step_type: create_knowledge_file
  output_key: kb_file_id
  config:
    input_key: summary
    name: "Summary: {{.inputs.topic}}"
    tags: [ai-summary, auto-generated]

sheets_read

Read data from a Google Sheets spreadsheet.

yaml
- name: Read price list
  step_type: sheets_read
  output_key: prices
  config:
    credential_ref: "Google Sheets"
    spreadsheet_id: "1abc..."
    range: "Sheet1!A1:D100"

sheets_write

Write data to a Google Sheets spreadsheet.

yaml
- name: Write results to sheet
  step_type: sheets_write
  output_key: write_result
  config:
    credential_ref: "Google Sheets"
    spreadsheet_id: "1abc..."
    range: "Results!A1"
    input_key: report_rows

ask_data

Query a BigQuery data source with a natural language question.

yaml
- name: Ask about revenue
  step_type: ask_data
  output_key: revenue_answer
  config:
    question: "What was total revenue last month by product category?"
    provider_ref: "My BQ Provider"

data_enrichment

Enrich records with additional data from an external source.

yaml
- name: Enrich company data
  step_type: data_enrichment
  output_key: enriched
  config:
    input_key: companies
    strategy: web_search

Integration steps

local_api_get

Make a GET request to a public HTTP endpoint. No auth — for public APIs only.

yaml
- name: Fetch weather
  step_type: local_api_get
  output_key: weather_data
  config:
    url: "https://api.open-meteo.com/v1/forecast?latitude=52.37&longitude=4.90&current_weather=true"

webhook_action

Call an external webhook with a POST request and a JSON payload.

yaml
- name: Notify Slack
  step_type: webhook_action
  output_key: webhook_result
  config:
    url: "https://hooks.slack.com/services/..."
    payload_key: notification_body

render_template

Render a Go template string using playbook data.

yaml
- name: Build HTML output
  step_type: render_template
  output_key: html_output
  config:
    template: "<h1>{{.step_outputs.title}}</h1><p>{{.step_outputs.body}}</p>"

export_output

Format the final output as HTML, Markdown, or JSON.

yaml
- name: Export as HTML
  step_type: export_output
  output_key: final_html
  config:
    input_key: summary
    format: html

sale_order_builder

Build a structured sale order from intake data.

yaml
- name: Build sale order
  step_type: sale_order_builder
  output_key: sale_order
  config:
    intake_key: intake
    credential_ref: "My Odoo"

Control & validation steps

gatekeeper

Stop the playbook early if a condition is not met. Validates required inputs before expensive steps run.

yaml
- name: Require topic
  step_type: gatekeeper
  output_key: gate_result
  config:
    condition: '{{.inputs.topic}} != ""'
    message: "topic is required"

validate_gate

Assert that a step's output matches an expected shape or constraint. Fails the run on mismatch.

yaml
- name: Validate output
  step_type: validate_gate
  output_key: validation_result
  config:
    input_key: summary
    min_length: 50
    max_length: 5000

assert

Write an inline assertion that a condition is true. Used for testing inside a playbook.

yaml
- name: Assert not empty
  step_type: assert
  output_key: assert_result
  config:
    condition: 'len(step_outputs["summary"]) > 0'
    message: "summary must not be empty"

request_approval

Pause the playbook and wait for a human to approve or reject before continuing.

yaml
- name: Review before sending
  step_type: request_approval
  output_key: approval_result
  config:
    prompt: "Does this email draft look correct?"
    input_key: email_draft

run_test_suite

Run a named test suite as part of the playbook. Useful for regression testing in CI-style flows.

yaml
- name: Run regression tests
  step_type: run_test_suite
  output_key: test_results
  config:
    suite_name: "My Playbook Tests"

apply_rules

Apply a set of business rules (if/then conditions) to derive new values from inputs.

yaml
- name: Apply pricing rules
  step_type: apply_rules
  output_key: rule_outputs
  config:
    rules_key: pricing_rules
    data_key: intake

formula

Evaluate mathematical formulas against input data. Used for pricing calculations.

yaml
- name: Calculate price
  step_type: formula
  output_key: price_total
  config:
    intake_key: intake
    price_list_tag: my-price-list
    categories:
      - name: Labour
        slug: labour
        rules:
          - { name: hours, template_type: fixed, formula: "hours * hourly_rate" }

image_asset_picker

Select the best image from a set based on criteria.

yaml
- name: Pick best image
  step_type: image_asset_picker
  output_key: chosen_image
  config:
    criteria: "Most professional and brand-aligned"
    candidates_key: generated_images