SWIRLS_
Workflows

Workflows

Build DAGs of nodes connected by edges to define workflow logic.

What it is. A deterministic set of instructions: a standard operating procedure you can declare, version, and reuse.

Use it when you already have a procedure and want it to run the same way every time. Workflows are also available to agents as tools, so the reasoning layer can invoke the exact one.

Works with triggers (what starts a run), node types (the steps), reviews (human approval), and streams (where output persists).

A workflow is a directed acyclic graph (DAG) of nodes connected by edges. Each node performs a discrete unit of work. The flow block defines the edges between nodes. Workflows span multiple .swirls files: declarations reference each other by name across the whole workspace, with no imports.

Workflow structure

FieldTypeRequiredDescription
labelstringNoDisplay name. Defaults to the workflow name.
descriptionstringNoDescription. Required (non-empty) when the workflow is used as an agent tool.

Cycles are not allowed in the outer flow block. For in-workflow iteration, use type: map or type: while nodes (child workflows), or trigger separate runs via a schedule.

Root node

Every workflow has exactly one root node. The root node is the entry point for the workflow run: it receives the trigger payload on context.nodes.root.input. It defines:

  • inputSchema: the shape of data the workflow accepts from a trigger
  • outputSchema: the data passed to downstream nodes

Use the root { ... } syntax (not node root). The root can be any node type. inputSchema and outputSchema are root-only; using them on other nodes is a parse error.

Regular nodes

Additional nodes use node name { ... }. Each node has a type and type-specific configuration. Downstream nodes define only schema (the node’s output shape). Their input is derived from the outputs of upstream nodes, accessed via context.nodes.

Flow block

The flow { ... } block defines edges between nodes.

  • source -> target: a simple edge
  • source -["label"]-> target: a labeled edge, used with switch nodes

The validator enforces the DAG rules: no cycles, no self-edges, every edge names a declared node, and exactly one node with no incoming edges (the root; a parentless stream read counts as a second root and fails validation). Fan-out and fan-in are fine: a node can feed several branches and several branches can converge.

Ready nodes run concurrently in stable dependency order. A workflow-level concurrency: field caps active nodes from 1 through 64 and defaults to 8. Each completed node's output is durably checkpointed, so completed nodes do not re-execute on resume. See How execution works.

Example: multi-node workflow

This workflow normalizes form input, summarizes it with an LLM, and sends a confirmation email.

schema contact_payload {
  label: "Contact message"
  schema: @json {
    {
      "type": "object",
      "required": ["name", "email", "message"],
      "properties": {
        "name": { "type": "string" },
        "email": { "type": "string" },
        "message": { "type": "string" }
      },
      "additionalProperties": false
    }
  }
}

workflow process_form {
  label: "Process Form"
  description: "Normalize input, summarize with LLM, send email"

  root {
    type: code
    label: "Entry"
    inputSchema: contact_payload
    outputSchema: contact_payload
    code: @ts {
      const { name, email, message } = context.nodes.root.input
      return {
        name: name.trim(),
        email: email.trim().toLowerCase(),
        message: message.trim(),
      }
    }
  }

  node summarize {
    type: ai
    kind: object
    label: "Summarize"
    schema: @json {
      { "type": "object", "required": ["text"], "properties": { "text": { "type": "string" } } }
    }
    model: "google/gemini-2.5-flash"
    prompt: @ts {
      const { name, message } = context.nodes.root.output
      return `Summarize this message from ${name}: ${message}`
    }
  }

  node notify {
    type: email
    label: "Send confirmation"
    from: @ts { return "[email protected]" }
    to: @ts { return context.nodes.root.output.email }
    subject: @ts { return "We received your message" }
    text: @ts {
      const summary = context.nodes.summarize.output.text ?? ""
      return `Thanks for reaching out. Summary: ${summary}`
    }
  }

  flow {
    root -> summarize
    summarize -> notify
  }
}

Branching with switch nodes

A switch node routes execution to one of several labeled branches. The router function returns the name of the branch to follow. Each branch is then connected using a labeled edge in the flow block.

schema score_payload {
  label: "Score input/output"
  schema: @json {
    {
      "type": "object",
      "required": ["score"],
      "properties": { "score": { "type": "number" } },
      "additionalProperties": false
    }
  }
}

schema route_branch_result {
  label: "Switch branch result"
  schema: @json {
    {
      "type": "object",
      "required": ["result"],
      "properties": { "result": { "type": "string" } },
      "additionalProperties": false
    }
  }
}

workflow route_by_score {
  label: "Route by Score"

  root {
    type: code
    label: "Entry"
    inputSchema: score_payload
    outputSchema: score_payload
    code: @ts {
      return { score: Number(context.nodes.root.input.score) || 0 }
    }
  }

  node route {
    type: switch
    label: "Route"
    cases: ["high", "low"]
    router: @ts {
      return context.nodes.root.output.score > 50 ? "high" : "low"
    }
  }

  node handle_high {
    type: code
    label: "High path"
    schema: route_branch_result
    code: @ts { return { result: "High priority" } }
  }

  node handle_low {
    type: code
    label: "Low path"
    schema: route_branch_result
    code: @ts { return { result: "Low priority" } }
  }

  flow {
    root -> route
    route -["high"]-> handle_high
    route -["low"]-> handle_low
  }
}

Further reading

  • Node types: all available node types and their configuration
  • Context: type-safe access to node outputs via context.nodes
  • Multi-file workflows: workspace layout and cross-file references
  • Streams: typed workflow output persisted for other workflows and integrations

On this page