Appearance
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 tagnotes_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 textvision_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-6classify_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-6generate_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: nlgenerate_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: 10Data 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: 5odoo_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 objectsknowledge_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: 3knowledge_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.
| Config | Meaning |
|---|---|
| omitted | default floor of 0.5 |
similarity_threshold: 0 | no floor — return the best chunk found, whatever it scores |
similarity_threshold: 0.8 | require 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
WARNstarting withknowledge_match: no chunk matched, includingorg_id,output_key,query,knowledge_tags,chunk_typeand the effective threshold actually applied. Grep for it. fail_on_no_match: truemakes the step return an error instead of writing"null". Set it on any step whose output is actually consumed downstream — a silentnullthere is worse than a loud failure. Default isfalse, 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: 5create_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_rowsask_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_searchIntegration 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¤t_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_bodyrender_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: htmlsale_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: 5000assert
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_draftrun_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: intakeformula
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