Workflows | Solace Agent Mesh
Description: Workflows is currently an experimental feature. Expect this feature to change and improve in future releases.

Skip to main content

# Workflows

Experimental Feature

Workflows is currently an experimental feature. Expect this feature to change and improve in future releases.

Workflows orchestrate multiple agents through YAML configuration. Unlike the orchestrator, which uses AI to dynamically decide how to accomplish tasks, workflows follow explicit paths you define. Each step, branch, and iteration is specified in configuration.

Use workflows when you need:

- Predictable execution that follows the same path every time
- Business processes with compliance or audit requirements
- Visual representation of agent interactions in the UI
- Fine-grained control over error handling and retries

## Workflows vs. Orchestrator​

The orchestrator excels at open-ended tasks where the AI determines the best approach. Workflows are better when you know exactly what steps need to happen and in what order.

Aspect | Orchestrator | Workflows
--- | --- | ---
Control flow | AI decides | You define
Visibility | Emergent behavior | Explicit DAG
Best for | Exploratory tasks | Repeatable processes

## Configuration Overview​

A workflow is defined in the `workflow` section of your YAML configuration file:

    apps:
      - name: my_workflow
        app_module: solace_agent_mesh.workflow.app
        broker:
          # ... broker configuration

        app_config:
          namespace: ${NAMESPACE}
          name: "MyWorkflow"

          workflow:
            description: "Process incoming orders"
            version: "1.0.0"

            input_schema:
              type: object
              properties:
                order_id:
                  type: string
              required: [order_id]

            nodes:
              - id: validate_order
                type: agent
                agent_name: "OrderValidator"
                input:
                  order_id: "{{workflow.input.order_id}}"

              - id: process_payment
                type: agent
                agent_name: "PaymentProcessor"
                depends_on: [validate_order]
                input:
                  order_data: "{{validate_order.output}}"

            output_mapping:
              status: "{{process_payment.output.status}}"
              confirmation: "{{process_payment.output.confirmation_number}}"

### Top-Level Fields​

Field | Required | Description
--- | --- | ---
`description` | Yes | What the workflow does
`nodes` | Yes | The workflow steps
`output_mapping` | Yes | Maps node outputs to final workflow output
`version` | No | Semantic version (default: "1.0.0")
`input_schema` | No | JSON Schema for workflow input validation
`output_schema` | No | JSON Schema for workflow output validation
`skills` | No | Skills exposed in the agent card
`on_exit` | No | Exit handler for cleanup
`fail_fast` | No | Stop on first failure (default: true)
`max_call_depth` | No | Limit for nested workflows (default: 10)
`retry_strategy` | No | Default retry configuration for all nodes

### App-Level Settings​

These settings go in `app_config`, outside the `workflow` block:

Field | Default | Description
--- | --- | ---
`max_workflow_execution_time_seconds` | 1800 | Maximum total workflow runtime (30 minutes)
`default_node_timeout_seconds` | 300 | Default timeout per node (5 minutes)
`default_max_map_items` | 100 | Safety limit for map node iterations

## Node Types​

### Agent Node​

Invokes an agent and captures its output.

    - id: analyze_data
      type: agent
      agent_name: "DataAnalyzer"
      input:
        dataset: "{{workflow.input.dataset}}"
        parameters: "{{workflow.input.analysis_params}}"
      instruction: "Focus on anomalies in the {{workflow.input.target_field}} field"
      timeout: "10m"

Field | Required | Description
--- | --- | ---
`id` | Yes | Unique identifier for this node
`type` | Yes | Must be `agent`
`agent_name` | Yes | Name of the agent to call
`input` | No | Input data mapping (template expressions allowed)
`instruction` | No | Additional context for the agent
`depends_on` | No | Node IDs that must complete first
`timeout` | No | Override default timeout (e.g., "30s", "5m", "1h")
`retry_strategy` | No | Override default retry behavior
`input_schema_override` | No | Override agent's input schema
`output_schema_override` | No | Override agent's output schema

### Switch Node​

Routes execution based on conditions. Cases are evaluated in order; the first match wins.

    - id: route_by_priority
      type: switch
      depends_on: [classify_ticket]
      cases:
        - condition: "{{classify_ticket.output.priority}} == 'critical'"
          node: escalate_immediately
        - condition: "{{classify_ticket.output.priority}} == 'high'"
          node: assign_senior_agent
      default: add_to_queue

Field | Required | Description
--- | --- | ---
`id` | Yes | Unique identifier
`type` | Yes | Must be `switch`
`cases` | Yes | List of condition/node pairs
`default` | No | Node to execute if no case matches
`depends_on` | No | Node IDs that must complete first

Each case has:

- `condition`: Expression to evaluate (see Condition Expressions)
- `node`: ID of the node to execute if condition is true

Nodes in branches that aren't selected are skipped entirely.

### Map Node​

Executes a node for each item in a collection. Items are processed in parallel by default.

    - id: process_all_items
      type: map
      depends_on: [fetch_items]
      items: "{{fetch_items.output.item_list}}"
      node: process_single_item
      concurrency_limit: 5
      max_items: 50

    - id: process_single_item
      type: agent
      agent_name: "ItemProcessor"
      input:
        item: "{{_map_item}}"

Field | Required | Description
--- | --- | ---
`id` | Yes | Unique identifier
`type` | Yes | Must be `map`
`node` | Yes | Node ID to execute for each item
`items` | Yes* | Template expression resolving to an array
`depends_on` | No | Node IDs that must complete first
`concurrency_limit` | No | Max parallel executions (unlimited if not set)
`max_items` | No | Safety limit (default: 100)

*Or use `withItems` for a static list, or `withParam` for Argo-style syntax.

The target node accesses the current item via `{{_map_item}}` or `{{_map_item.field}}`. The current index is available as `{{_map_index}}`.

After all iterations complete, the map node's output contains a `results` array with each iteration's output in order.

### Loop Node​

Repeats a node until a condition becomes false. The first iteration always runs; the condition is checked before subsequent iterations.

    - id: poll_until_ready
      type: loop
      node: check_status
      condition: "{{check_status.output.ready}} == false"
      max_iterations: 30
      delay: "10s"

    - id: check_status
      type: agent
      agent_name: "StatusChecker"
      input:
        task_id: "{{workflow.input.task_id}}"

Field | Required | Description
--- | --- | ---
`id` | Yes | Unique identifier
`type` | Yes | Must be `loop`
`node` | Yes | Node ID to execute repeatedly
`condition` | Yes | Continue while this is true
`depends_on` | No | Node IDs that must complete first
`max_iterations` | No | Safety limit (default: 100)
`delay` | No | Wait between iterations (e.g., "5s", "1m")

The current iteration number (starting at 0) is available as `{{_loop_iteration}}`.

### Workflow Node​

Invokes another workflow as a sub-workflow.

    - id: run_validation_workflow
      type: workflow
      workflow_name: "ValidationWorkflow"
      input:
        data: "{{workflow.input.payload}}"
      timeout: "15m"

The fields match agent nodes, except `workflow_name` replaces `agent_name`. Workflows cannot call themselves directly.

The `max_call_depth` setting (default: 10) limits how deeply workflows can nest to prevent infinite recursion.

## Template Expressions​

Template expressions reference data from workflow input or node outputs using `{{...}}` syntax.

### Available References​

Expression | Description
--- | ---
`{{workflow.input.field}}` | Workflow input field
`{{node_id.output.field}}` | Output from a completed node
`{{_map_item}}` | Current item in a map iteration
`{{_map_item.field}}` | Field from current map item
`{{_map_index}}` | Current map iteration index (0-based)
`{{_loop_iteration}}` | Current loop iteration number (0-based)

### Operators​

coalesce returns the first non-null value:

    output_mapping:
      result:
        coalesce:
          - "{{primary_source.output.value}}"
          - "{{fallback_source.output.value}}"
          - "default_value"

concat joins values as strings:

    input:
      message:
        concat:
          - "Processing order "
          - "{{workflow.input.order_id}}"
          - " for customer "
          - "{{workflow.input.customer_name}}"

## Condition Expressions​

Switch conditions and loop conditions use a safe expression syntax:

Comparisons: `==`, `!=`, `<`, `<=`, `>`, `>=`, `in`, `not in`

Boolean: `and`, `or`, `not`

Literals: strings (quoted), numbers, `true`, `false`, `null`

Examples:

    # Equality
    "{{node.output.status}} == 'complete'"

    # Numeric comparison
    "{{node.output.count}} > 10"

    # Boolean logic
    "{{node.output.ready}} == true and {{node.output.errors}} == 0"

    # Membership
    "{{node.output.category}} in ['A', 'B', 'C']"

## Dependencies and Parallelism​

Nodes declare dependencies with `depends_on`. A node executes only after all its dependencies complete.

    nodes:
      - id: fetch_user
        type: agent
        agent_name: "UserService"

      - id: fetch_orders
        type: agent
        agent_name: "OrderService"

      # These two run in parallel (no dependencies between them)

      - id: generate_report
        type: agent
        agent_name: "ReportGenerator"
        depends_on: [fetch_user, fetch_orders]  # Waits for both

Nodes with no dependencies (or whose dependencies are all satisfied) run in parallel automatically.

## Error Handling​

### Retry Strategy​

Configure retries at the workflow level (applies to all nodes) or per-node:

    workflow:
      retry_strategy:
        limit: 3
        retry_policy: "OnFailure"
        backoff:
          duration: "2s"
          factor: 2.0
          max_duration: "30s"

Field | Default | Description
--- | --- | ---
`limit` | 3 | Maximum retry attempts
`retry_policy` | "OnFailure" | When to retry: "Always", "OnFailure", "OnError"
`backoff.duration` | "1s" | Initial wait before first retry
`backoff.factor` | 2.0 | Multiplier for each subsequent retry
`backoff.max_duration` | None | Cap on backoff duration

### Exit Handlers​

Run cleanup or notification nodes when the workflow completes:

    workflow:
      on_exit: cleanup_resources  # Always runs

Or specify different handlers for different outcomes:

    workflow:
      on_exit:
        on_success: send_success_notification
        on_failure: send_failure_alert
        on_cancel: cleanup_partial_state
        always: log_completion

Exit handler nodes must be defined in the `nodes` array like any other node.

### Fail-Fast Behavior​

By default (`fail_fast: true`), when a node fails, no new nodes are scheduled. Nodes already running continue to completion.

Set `fail_fast: false` to allow independent branches to continue executing even when one branch fails.

## Workflow Discovery​

Workflows register themselves as agents and publish agent cards for discovery. Other agents and the orchestrator can invoke workflows just like any other agent.

The workflow's input and output schemas appear in the agent card, along with any skills you define:

    workflow:
      skills:
        - id: "process_order"
          name: "Process Order"
          description: "Validates and processes a customer order"
          tags: ["orders", "processing"]

## Schema Reference​

The complete JSON Schema for workflow definitions is available at `src/solace_agent_mesh/common/schemas/workflow_schema.json`.

Use this schema with your IDE or YAML validator for autocompletion and validation while writing workflow configurations.

## Next Steps​

For a hands-on guide to building workflows, see Creating Workflows.