Workflow YAML

Complete schema reference for workflow definition files

YAML
format
{{}}
dynamic vars
16
step types
2
node modes

Top-Level Schema

Every workflow YAML file starts with top-level keys that configure the environment, nodes, and execution sequence.

# workflow.yaml
name: string             # Workflow display name
nodes: NodeConfig         # Node definition (count-based or explicit)
remote_nodes: dict        # Optional remote node declarations
steps: list[Step]        # Ordered sequence of workflow steps

# Lifecycle options
nuke_on_start: bool       # Clean slate before workflow (default: false)
nuke_on_end: bool         # Destroy data after completion (default: false)
force_pull_image: bool    # Force pull Docker images (default: false)

# Auth configuration
auth_service: bool        # Enable Traefik + auth stack (default: false)
auth_mode: string         # "embedded" or "proxy" (binary mode only)
auth_image: string        # Custom auth service Docker image
auth_use_cached: bool     # Use cached auth frontend (default: false)

# Image & frontend
image: string             # Custom merod Docker image
webui_use_cached: bool    # Use cached WebUI frontend (default: false)

# NEAR configuration
near_devnet: bool         # Use local NEAR Sandbox (default: true)
contracts_dir: string     # Path to Calimero NEAR context WASM contracts

# Node configuration
config_path: string       # Custom config.toml path for all nodes
log_level: string         # RUST_LOG level (default: debug)
bootstrap_nodes: list     # Multiaddr strings for DHT bootstrap peers

Node Configuration

Nodes can be defined using count-based (dynamic) or explicit (named) mode.

Count-Based Mode

Creates count nodes with auto-generated names using the prefix.

nodes:
  count: 3
  prefix: node     # → node-0, node-1, node-2
  ports:
    rpc_start: 2528
    swarm_start: 2428

Count mode does not support per-node config_path overrides.

Explicit Mode

Name each node individually with specific settings and optional per-node config.

nodes:
  config_path: ./default.toml  # shared
  alice:
    port: 2428
    rpc_port: 2528
  bob:
    port: 2429
    rpc_port: 2529
    config_path: ./special.toml  # override

Remote Nodes

Remote nodes are pre-existing merod instances outside the local environment. Auth fields support ${ENV_VAR} and ${ENV_VAR:-default} expansion.

remote_nodes:
  prod-node:
    url: https://prod.example.com
    auth:
      method: user_password    # user_password | api_key | none
      username: ${PROD_USERNAME}

  staging-node:
    url: https://staging.example.com
    auth:
      method: api_key
      key: ${STAGING_API_KEY}

  public-node:
    url: https://public.example.com
    auth:
      method: none

Dynamic Variables & Outputs

Workflows support dynamic variable substitution using {{variable_name}} syntax. Variables are resolved at execution time; missing variables cause workflow failure.

Variable Sources

  • Step outputs — variables exported by previous steps via outputs
  • Workflow context — global workflow variables
  • Environment${ENV_VAR} and ${ENV_VAR:-default} expansion
  • Embedded — variables can appear inside strings: user_{{user_id}}_data_{{iteration}}

Output Configuration

Each step can export variables for use in subsequent steps.

# Simple string — maps API field to name
outputs:
  my_var: result

# Dotted path (recommended)
  nested: result.output
  deep: result.data.user.name
  array: items.0.id

# Dict-based (legacy, still supported)
  my_value:
    field: result
    json: true
    path: output

Step Type Reference

Complete field reference for each step type. required fields must be present; all others are optional.

install / install_application

Installs a WASM application on a target node. Use path for local files or url for remote downloads. Remote nodes require url (not path).

- type: install               # or install_application
  name: string              # Step name for result reference  required
  node: string              # Target node name  required
  path: string              # Local path to .wasm file
  url: string               # URL to download .wasm
  dev: bool                 # Development mode install (local only)
  metadata: dict            # Application metadata
  outputs:
    applicationId: string    # Installed application ID

Example

- name: Install App
  type: install
  node: calimero-node-1
  path: ./application.wasm
  dev: true
  outputs:
    applicationId: app_id
context / create_context

Creates a new context binding an application to a group on a node.

- type: context              # or create_context
  name: string              required
  node: string              required
  application_id: string    # Or {{app_id}} from prior step  required
  params: dict              # Context initialization parameters
  outputs:
    contextId: string        # Created context ID
    memberPublicKey: string  # Creator's public key

Example

- name: Create Context
  type: context
  node: calimero-node-1
  application_id: "{{app_id}}"
  params:
    param1: value1
  outputs:
    contextId: context_id
    memberPublicKey: member_key
identity

Generates a cryptographic identity on a target node.

- type: identity
  name: string              required
  node: string              required
  outputs:
    publicKey: string        # Generated public key

Example

- name: Create Identity
  type: identity
  node: calimero-node-2
  outputs:
    publicKey: public_key
invite

Invites an identity to join a context. The invitation payload is used in the subsequent join step.

- type: invite
  name: string              required
  node: string              # Node that owns the context  required
  context_id: string        required
  grantee_id: string        # Public key of the identity to invite  required
  outputs:
    invitation: string       # Invitation payload for join step

Example

- name: Invite Identity
  type: invite
  node: calimero-node-1
  context_id: "{{context_id}}"
  grantee_id: "{{public_key}}"
  outputs:
    invitation: invitation_data
join

Joins a context using an invitation payload from a prior invite step.

- type: join
  name: string              required
  node: string              # Node joining the context  required
  context_id: string        required
  invitee_id: string        # Public key of the joining identity  required
  invitation: string        # Invitation payload from invite step  required

Example

- name: Join Context
  type: join
  node: calimero-node-2
  context_id: "{{context_id}}"
  invitee_id: "{{public_key}}"
  invitation: "{{invitation_data}}"
create_mesh

Creates a context and connects multiple nodes in a single compound step. Automatically creates the context on the specified node, generates identities for each target node, sends invitations, and joins all nodes.

- type: create_mesh
  name: string              required
  context_node: string      # Node where context is created  required
  application_id: string    required
  nodes: list[string]       # Nodes to join the context  required
  capability: string        # Invitation capability (default: "member")
  outputs:
    context_id: contextId
    member_public_key: memberPublicKey
    # Auto-exported: public_key_{node_name} for each joined node
    # If only one node: also exported as public_key

Example

- name: Create Mesh
  type: create_mesh
  context_node: calimero-node-1
  application_id: "{{app_id}}"
  nodes:
    - calimero-node-2
    - calimero-node-3
  capability: member
  outputs:
    context_id: contextId
    member_public_key: memberPublicKey

After execution, {{public_key_calimero-node-2}} and {{public_key_calimero-node-3}} are available for use in later steps.

call / execute

Invokes a smart contract method within a context. Supports dotted-path output extraction and negative testing via expected_failure.

- type: call                 # or execute
  name: string              required
  node: string              required
  context_id: string        required
  method: string            # Contract method name  required
  args: dict                # Method arguments (JSON serialized)
  executor_public_key: string# Signing key
  expected_failure: bool    # Enable negative testing (default: false)
  outputs:
    result: string           # Simple field access
    nested: result.output     # Dotted path with auto JSON parsing
    deep: result.data.user.name# Deep nesting

Negative Testing (expected_failure)

When expected_failure: true, the step continues on failure and exports error details for assertion.

- name: Expected Failure
  type: call
  node: calimero-node-1
  context_id: "{{context_id}}"
  executor_public_key: "{{member_public_key}}"
  method: invalid_method
  args: {}
  expected_failure: true
  outputs:
    error_code: error_code      # JSON-RPC error code
    error_type: error_type      # e.g. "FunctionCallError"
    error_message: error_message  # Error message text
    error: error              # Full error object

- name: Assert Error
  type: assert
  statements:
    - "is_set({{error_type}})"
    - "equal({{error_type}}, FunctionCallError)"
    - "contains({{error_message}}, not found)"

JSON-RPC errors: error fields extracted from response. Network errors: captured and exported. Unexpected success: warning shown, error fields exported as None.

wait

Simple delay between steps.

- type: wait
  name: string              required
  seconds: number           # Seconds to wait  required

Example

- name: Wait for propagation
  type: wait
  seconds: 5
wait_for_sync

Waits until all specified nodes reach consensus by verifying they share the same root hash. More reliable than fixed wait times because it only proceeds once nodes are actually synchronized.

- type: wait_for_sync
  name: string              required
  context_id: string        required
  nodes: list[string]       # Nodes to check (defaults to all)
  timeout: int              # Max seconds to wait (default: 30)
  check_interval: float      # Seconds between checks (default: 1.0)
  trigger_sync: bool        # Trigger sync before checking (default: true)
  outputs:
    root_hash: string         # Consensus root hash
    elapsed_seconds: string   # Time taken to sync

When to Use

  • After state-changing operations (set, delete, update)
  • After nodes join a context
  • Before reading data from different nodes
  • In critical workflows where consistency is required

Example

- name: Wait for Nodes to Sync
  type: wait_for_sync
  context_id: "{{context_id}}"
  nodes:
    - calimero-node-1
    - calimero-node-2
  timeout: 30
  check_interval: 1
  trigger_sync: true
  outputs:
    root_hash: synced_hash
    elapsed_seconds: sync_time
repeat

Loops a sequence of sub-steps a specified number of times. Exposes {{current_iteration}} (zero-based) inside the loop body.

- type: repeat
  name: string              required
  count: int                # Number of iterations  required
  steps: list[Step]         # Sub-steps to repeat  required
  outputs:
    iteration: current_iteration # Current loop index

Example

- name: Repeat Operations
  type: repeat
  count: 3
  steps:
    - name: Set Value
      type: call
      node: calimero-node-1
      context_id: "{{context_id}}"
      method: set
      args:
        key: "iteration_{{current_iteration}}"
        value: "value_{{current_iteration}}"
      executor_public_key: "{{member_key}}"
    - name: Wait
      type: wait
      seconds: 2
fuzzy_test

Long-duration randomized load testing with weighted operation patterns and assertion-based validation. Application-agnostic — works with any smart contract.

- type: fuzzy_test
  name: string              required
  duration_minutes: int     # How long to run  required
  context_id: string        required
  success_threshold: float   # Pass if N%+ assertions succeed (e.g. 95.0)
  nodes: list               # Node configs with executor keys  required
    - name: string
      executor_key: string
  operations: list           # Weighted operation patterns  required
    - name: string           # Operation pattern name
      weight: int            # Relative frequency (sums don't need to = 100)
      steps: list[Step]      # Steps in this pattern

Random Value Generators

Use these inside {{}} placeholders within fuzzy test steps:

{{random_int(min, max)}}      # Random integer in range
{{random_string(length)}}     # Random alphanumeric string
{{random_float(min, max)}}    # Random float in range
{{random_choice([a, b, c])}}  # Random pick from list
{{timestamp}}                # Current Unix timestamp
{{uuid}}                     # Random UUID
{{random_node}}              # Random node from nodes list
{{random_executor}}          # Random executor key from nodes list

Auto-Captured Arguments (fuzzy_ prefix)

When a call step executes inside a fuzzy test, its resolved arguments are automatically captured with a fuzzy_ prefix for use in subsequent assertion steps:

# If call step has args.key and args.value, then:
{{fuzzy_key}}      # resolved value of args.key
{{fuzzy_value}}    # resolved value of args.value
{{fuzzy_amount}}   # resolved value of args.amount (etc.)

Example

- name: Fuzzy Load Test
  type: fuzzy_test
  duration_minutes: 30
  context_id: "{{context_id}}"
  success_threshold: 95.0
  nodes:
    - name: calimero-node-1
      executor_key: "{{member_public_key}}"
    - name: calimero-node-2
      executor_key: "{{public_key_node2}}"
  operations:
    - name: set_and_verify
      weight: 40
      steps:
        - type: call
          node: "{{random_node}}"
          method: set
          context_id: "{{context_id}}"
          executor_public_key: "{{random_executor}}"
          args:
            key: "test_{{random_int(1, 1000)}}"
            value: "{{uuid}}_{{timestamp}}"
        - type: wait
          seconds: 1
        - type: call
          node: "{{random_node}}"
          method: get
          context_id: "{{context_id}}"
          executor_public_key: "{{random_executor}}"
          args:
            key: "{{fuzzy_key}}"
          outputs:
            retrieved: result
        - type: assert
          non_blocking: true
          statements:
            - statement: "contains({{retrieved}}, {{fuzzy_value}})"
              message: "Value should match"

Features: Weighted patterns, non-blocking assertions, live progress summaries every 60s, detailed final report with pass rates and failure analysis.

upload_blob

Uploads files to blob storage and captures blob IDs for use in subsequent contract calls.

- type: upload_blob
  name: string              required
  node: string              required
  file_path: string         # Path to local file  required
  context_id: string        # Optional context association
  outputs:
    blob_id: string          # Uploaded blob ID
    size: string             # Blob size in bytes

Example

- name: Upload WASM to Blob
  type: upload_blob
  node: calimero-node-1
  file_path: res/kv_store.wasm
  context_id: "{{context_id}}"
  outputs:
    blob_id: wasm_blob_id
    size: wasm_blob_size

- name: Register Blob in Contract
  type: call
  node: calimero-node-1
  context_id: "{{context_id}}"
  method: register_blob
  args:
    blob_id: "{{wasm_blob_id}}"
    size: "{{wasm_blob_size}}"
script

Runs an arbitrary script or inline command. The target field controls where execution happens.

- type: script
  name: string              required
  target: string            # "image" | "nodes" | "local"  required
  script: string            # Path to script file
  inline: string            # Inline script content (alternative to file)
  args: list[string]        # Arguments (supports {{var}} placeholders)

Targets

  • image — runs inside a temporary container from the node image (before nodes start)
  • nodes — copies and runs inside each running Calimero node container
  • local — runs on host machine via /bin/sh

Example

- name: Echo Exported Value
  type: script
  target: local
  inline: |
    echo "Value is: {{read_value}}"

- name: Run Script File
  type: script
  target: local
  script: ./scripts/setup.sh
  args:
    - "{{read_value}}"
assert

Statement-based assertions against exported variables and literals. Placeholders are resolved before evaluation.

- type: assert
  name: string              required
  statements: list          required
  non_blocking: bool        # Failures don't stop workflow (fuzzy tests)

Supported Forms

# Presence checks
is_set(A)              # A is not null/empty
is_empty(A)            # A is null/empty

# String checks
contains(A, B)         # A contains substring B
not_contains(A, B)     # A does not contain B
regex(A, PATTERN)       # A matches regex PATTERN

# Comparisons
A == B                 # Equality
A != B                 # Inequality
A >= B   A > B        # Greater than (or equal)
A <= B   A < B        # Less than (or equal)

# Equality helpers
equal(A, B)            # Same as ==
equals(A, B)           # Same as ==
not_equal(A, B)        # Same as !=
not_equals(A, B)       # Same as !=

Example

- name: Assert exported variables
  type: assert
  statements:
    - "is_set({{context_id}})"
    - "{{count}} >= 1"
    - "contains({{get_result}}, 'hello')"
    - "regex({{value}}, '^abc')"
    - "equal({{a}}, {{b}})"
json_assert

Compare JSON-like values (Python dict/list or JSON strings). For deep structural comparison beyond simple string matching.

- type: json_assert
  name: string              required
  statements: list          required

Supported Forms

json_equal(A, B)       # A equals B (deep comparison)
equal(A, B)            # Alias for json_equal
json_subset(A, B)      # B is a subset of A
subset(A, B)           # Alias for json_subset

Example

- name: Assert JSON equality
  type: json_assert
  statements:
    - "json_equal({{get_result}}, {'output': 'assert_value'})"
parallel

Executes multiple sub-steps concurrently.

- type: parallel
  name: string              required
  steps: list[Step]         # Steps to run concurrently  required
  fail_fast: bool           # Cancel remaining on first failure (default: true)

Example

- name: Parallel Calls
  type: parallel
  fail_fast: true
  steps:
    - name: Call Node 1
      type: call
      node: calimero-node-1
      context_id: "{{context_id}}"
      method: get_count
    - name: Call Node 2
      type: call
      node: calimero-node-2
      context_id: "{{context_id}}"
      method: get_count

Complete Example

A full workflow demonstrating installation, context mesh creation, contract calls, sync verification, and assertions across multiple nodes.

name: e2e-kv-store-test

nuke_on_start: true
nuke_on_end: true
log_level: "info,calimero_context::handlers::execute=debug"

nodes:
  count: 2
  prefix: node
  ports:
    rpc_start: 2528
    swarm_start: 2428

steps:
  # 1. Install the WASM application
  - type: install
    name: install_kv
    node: node-0
    path: ./res/kv_store.wasm
    outputs:
      applicationId: app_id

  # 2. Create context and mesh all nodes
  - type: create_mesh
    name: setup_mesh
    context_node: node-0
    application_id: "{{app_id}}"
    nodes:
      - node-1
    outputs:
      context_id: contextId
      member_public_key: memberPublicKey

  # 3. Write a value on node-0
  - type: call
    name: set_value
    node: node-0
    context_id: "{{context_id}}"
    method: set
    args:
      key: hello
      value: world
    executor_public_key: "{{member_public_key}}"

  # 4. Wait for nodes to sync
  - type: wait_for_sync
    name: sync_nodes
    context_id: "{{context_id}}"
    timeout: 30

  # 5. Read back on node-1 and verify
  - type: call
    name: get_value
    node: node-1
    context_id: "{{context_id}}"
    method: get
    args:
      key: hello
    executor_public_key: "{{public_key_node-1}}"
    outputs:
      read_value: result.output

  # 6. Assert the value propagated correctly
  - type: assert
    name: verify_sync
    statements:
      - "is_set({{read_value}})"
      - "contains({{read_value}}, world)"