SWIRLS_
Language

Graphs

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

A graph 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.

Graph structure

FieldTypeRequiredDescription
labelstringYesDisplay name
descriptionstringNoDescription
persistenceblockNoStream persistence config

Cycles are not allowed. If you need to loop, model the iteration as separate graph runs or use a schedule.

Root node

Every graph has exactly one root node. The root node is the entry point for the graph run. It defines:

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

Use the root { ... } syntax (not node root).

Regular nodes

Additional nodes use node name { ... }. Each node has a type and type-specific configuration. Downstream nodes define only outputSchema. 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

Example: multi-node graph

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

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

  root {
    type: code
    label: "Entry"
    inputSchema: @json {
      {
        "type": "object",
        "required": ["name", "email", "message"],
        "properties": {
          "name": { "type": "string" },
          "email": { "type": "string" },
          "message": { "type": "string" }
        },
        "additionalProperties": false
      }
    }
    outputSchema: @json {
      {
        "type": "object",
        "required": ["name", "email", "message"],
        "properties": {
          "name": { "type": "string" },
          "email": { "type": "string" },
          "message": { "type": "string" }
        },
        "additionalProperties": false
      }
    }
    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"
    outputSchema: @json {
      { "type": "object", "required": ["text"], "properties": { "text": { "type": "string" } } }
    }
    model: "gpt-4o-mini"
    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.

graph route_by_score {
  label: "Route by Score"

  root {
    type: code
    label: "Entry"
    inputSchema: @json {
      {
        "type": "object",
        "required": ["score"],
        "properties": { "score": { "type": "number" } },
        "additionalProperties": false
      }
    }
    outputSchema: @json {
      {
        "type": "object",
        "required": ["score"],
        "properties": { "score": { "type": "number" } },
        "additionalProperties": false
      }
    }
    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"
    outputSchema: @json {
      {
        "type": "object",
        "required": ["result"],
        "properties": { "result": { "type": "string" } },
        "additionalProperties": false
      }
    }
    code: @ts { return { result: "High priority" } }
  }

  node handle_low {
    type: code
    label: "Low path"
    outputSchema: @json {
      {
        "type": "object",
        "required": ["result"],
        "properties": { "result": { "type": "string" } },
        "additionalProperties": false
      }
    }
    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
  • Persistence: configure stream persistence on a graph

On this page