Skip to content

DSL Reference ​

Complete reference for the workflow YAML syntax.

Workflow Structure ​

yaml
# Required fields
id: workflow_id                    # Unique identifier (snake_case)
title: "Human-Readable Title"      # Display name

# Optional fields
description: "What this workflow does"

category:                          # Auto-detected if omitted
  level: primitive | task | process | workflow | pipeline
  domain: engineering | sales | support | marketing | personal
  reusable: true | false

params:                            # Input parameters
  param_name: string | number | boolean

policies:
  allowed_domains:                 # Domain restrictions
    - github.com
    - "*.atlassian.net"

embed:                             # Embed button configuration
  selector: ".target-element"
  # ... see Embed Buttons section

steps:                             # Execution steps (required)
  - type: step_type
    # step-specific parameters

Validation ​

A workflow is checked by one set of rules wherever it is loaded or saved: the agent runtime checks a file when it loads it, and the app checks with the same code — in the workflow editor as you type, on every save, and in the cloud MCP tools that write workflows. A workflow that passes on one side loads on the other.

RuleError code
The YAML parses into a mappingyaml_parse, not_mapping
id and title are non-empty stringsmissing_id, missing_title
steps is a liststeps_not_array
Every step is a mapping with a string typestep_not_object, step_missing_type
Every type is a step the engine runs, including steps nested in control.if (then, else_steps), control.retry and control.foreach (steps)unknown_step_type, unsupported_step_type

Every error names its place: the step path (indices count from 0), the line and column, and a message. A misspelled type names the step it is closest to:

steps.1.then.0.type: unknown step type "browser.clik" — did you mean "browser.click"? (line 9, col 15)

YAML anchors and << merge keys are supported, so steps can share fields:

yaml
defaults: &click
  type: browser.click
steps:
  - <<: *click
    selector: "#first"

To check without saving, the portal answers POST /api/workflows/validate with { "content": "<yaml>" } → { ok, errors, summary }, and the cloud MCP tool manage_workflow takes dry_run: true. A workflow drafted in the portal's chat passes the same check before its draft card is shown, and is saved only from that card.

Parameters ​

Define inputs that users must provide:

yaml
params:
  repo_name: string
  max_items: number
  dry_run: boolean

Use parameters with interpolation:

yaml
steps:
  - type: browser.navigate
    url: "https://github.com/{{repo_name}}"

Policies ​

Domain Restrictions ​

Limit which domains the workflow can access:

yaml
policies:
  allowed_domains:
    - github.com           # Exact match
    - "*.atlassian.net"    # Wildcard subdomain
    - "*.google.com"

If allowed_domains is set, browser steps will fail on other domains.

Category Levels ​

Categories help organize workflows. They're auto-detected if omitted:

LevelAuto-Detection RuleExample
pipeline2+ workflow.call stepsComplex orchestration
workflow1+ workflow.call stepsComposed automation
processHas LLM, extract, or retryAI-powered tasks
taskHas params OR 3+ stepsParameterized work
primitive1-2 steps, no special featuresSimple actions

Embed Configuration ​

Inject buttons into web pages:

yaml
embed:
  selector: ".target-element"      # Where to inject button
  position: prepend | append | after | before
  when: ".page-indicator"          # Only inject when this exists
  label: "Button Text"

  # Optional: Filter by parent element
  parent_filter:
    selector: ".parent-container"
    match:
      attribute: "textContent"
      contains: "Status Text"

  # Extract values from page on click
  extract:
    my_value:
      selector: ".child-element"   # Or "self" for target
      attribute: "textContent"     # Or "href", "data-id", etc.
      pattern: "regex(capture)"    # Optional regex

  # Map to workflow params
  param_map:
    my_value: "param_name"         # Extracted → param
    _domain: "domain_param"        # Magic key → param
    _path.0: "org"                 # Path segment → param

Magic Context Keys ​

Available in param_map:

KeyValueExample
_urlFull page URLhttps://github.com/org/repo/pulls
_domainHostnamegithub.com
_pathnameURL path/org/repo/pulls
_titlePage titlePull requests · org/repo
_pathPath segments array["org", "repo", "pulls"]
_path.NNth path segment_path.0 = "org"

Learn more about embed buttons →

Variable Interpolation ​

Use {{variable}} syntax anywhere:

yaml
steps:
  - type: browser.extract
    selector: ".username"
    as: user_name

  - type: browser.type
    selector: "#greeting"
    text: "Hello, {{user_name}}!"

  - type: shell.run
    cmd: "echo 'User: {{user_name}}'"

Variable Sources ​

SourceSyntaxExample
Extract steps{{var_name}}{{title}}
Workflow params{{param_name}}{{repo}}
Shell output{{as_name}}{{shell_output}}
Env contexts{{env.name}}{{env.github_path}}
Nested workflows{{namespace.var}}{{data.result}}

Env Contexts ​

Define reusable environment variables in Settings:

yaml
# In workflow
steps:
  - type: shell.run
    cmd: "cd {{env.github_base_path}}/{{repo}}"

Env contexts are automatically passed to nested workflows.

Nested Workflows ​

Call other workflows with workflow.call:

yaml
steps:
  - type: workflow.call
    workflow: extract_page_data
    params:
      url: "{{target_url}}"
    as: extracted  # Namespace for variables

  # Access child variables
  - type: shell.run
    cmd: "echo '{{extracted.title}}'"

Variable Scoping ​

  • Child workflows receive parent's variables + their own params
  • Variables from child flow back to parent
  • Use as to namespace and avoid conflicts
  • Circular calls are prevented

File Organization ​

Standalone Files ​

workflows/my_workflow.yaml

Directory Format ​

workflows/my_workflow/
├── workflow.yaml
└── assets/

User Workflows ​

Save personal workflows in actions/ directory:

actions/
├── my_custom_workflow.yaml
└── team_automation.yaml

Complete Example ​

yaml
id: github_pr_summary
title: "GitHub: Summarize PR"
description: "Extract PR details and generate a summary"

params:
  org: string
  repo: string
  pr_number: number

policies:
  allowed_domains:
    - github.com

embed:
  selector: ".gh-header-actions"
  position: prepend
  when: "[data-hpc]"
  label: "Summarize"
  extract:
    pr_num:
      selector: ".gh-header-number"
      attribute: "textContent"
      pattern: "#(\\d+)"
  param_map:
    _path.0: "org"
    _path.1: "repo"
    pr_num: "pr_number"

steps:
  - type: browser.navigate
    url: "https://github.com/{{org}}/{{repo}}/pull/{{pr_number}}"

  - type: browser.wait
    selector: ".js-discussion"
    timeout: 10000

  - type: browser.extract
    selector: ".markdown-body"
    as: pr_description

  - type: browser.extractAll
    selector: ".commit-message"
    as: commits

  - type: llm.generate
    prompt: |
      Summarize this pull request:

      Description:
      {{pr_description}}

      Commits:
      {{commits}}
    as: summary

  - type: control.stop
    message: "{{summary}}"