Workflow YAML
Complete schema reference for workflow definition files
Top-Level Schema
Every workflow YAML file starts with top-level keys that configure the environment, nodes, and execution sequence.
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.
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.
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.
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.
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).
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
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.
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
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.
name: string required
node: string required
outputs:
publicKey: string # Generated public key
Example
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.
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
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.
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
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.
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
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.
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.
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.
name: string required
seconds: number # Seconds to wait required
Example
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.
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
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.
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
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.
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_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:
{{fuzzy_key}} # resolved value of args.key
{{fuzzy_value}} # resolved value of args.value
{{fuzzy_amount}} # resolved value of args.amount (etc.)
Example
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.
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
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.
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
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.
name: string required
statements: list required
non_blocking: bool # Failures don't stop workflow (fuzzy tests)
Supported Forms
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
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.
name: string required
statements: list required
Supported Forms
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
type: json_assert
statements:
- "json_equal({{get_result}}, {'output': 'assert_value'})"
parallel
Executes multiple sub-steps concurrently.
name: string required
steps: list[Step] # Steps to run concurrently required
fail_fast: bool # Cancel remaining on first failure (default: true)
Example
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.
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)"