SWIRLS_
Language

Failure policies

Configure retry, skip, and fallback behavior on any node using the optional failurePolicy field.

By default, when a node throws, the entire graph execution fails. A failurePolicy on that node changes this: the engine can retry the node, skip it, or substitute a fallback value and continue.

failurePolicy is optional and can be set on any node.

Shape

failurePolicy: {
  strategy: "fail" | "retry" | "skip" | "fallback"
  maxRetries: <number>      // used with "retry"
  backoffMs: <number>       // used with "retry"
  fallbackValue: <any>      // used with "fallback"
}

Strategies

StrategyBehavior
failNode failure errors the entire execution. This is the default when no policy is set.
retryRe-run the node up to maxRetries times, waiting backoffMs milliseconds between each attempt. If the node still fails after all retries, the execution errors.
skipMark the node as skipped and continue. Downstream nodes that read context.nodes.<name>.output will see undefined.
fallbackReplace the node's output with fallbackValue and continue as if the node succeeded.

When to use each strategy

retry is the right choice for transient failures: rate limits, network timeouts, or flaky third-party APIs. Set maxRetries to a small number (2 to 5) and backoffMs to give the external service time to recover.

fallback is the right choice when the node's output is optional. An enrichment step that sometimes fails should not block the rest of the workflow. Provide a fallback value that downstream nodes can handle gracefully.

skip is the right choice when the node's output is truly optional and downstream nodes can run without it. Skipped nodes produce undefined output, so downstream nodes must handle that case.

fail (the default) is the right choice when there is no safe way to continue without the node's output.

Examples

Retry on a flaky HTTP endpoint

node fetch_data {
  type: http
  label: "Fetch from API"
  url: @ts { return "https://api.example.com/data" }
  failurePolicy: {
    strategy: "retry"
    maxRetries: 3
    backoffMs: 2000
  }
}

The engine tries fetch_data up to four times total (the first attempt plus three retries), waiting two seconds between each. If all four attempts fail, the execution errors.

Fallback on an AI enrichment node

node classify {
  type: ai
  label: "Classify intent"
  kind: object
  model: "anthropic/claude-3.5-sonnet"
  prompt: @ts {
    return "Classify this message: " + context.nodes.root.input.message
  }
  schema: @json {
    {
      "type": "object",
      "required": ["intent"],
      "properties": { "intent": { "type": "string" } }
    }
  }
  failurePolicy: {
    strategy: "fallback"
    fallbackValue: { intent: "unknown" }
  }
}

If the AI call fails (model unavailable, timeout, or invalid response), the node outputs { intent: "unknown" } and the execution continues. Downstream nodes always have a valid intent to work with.

Skip an optional enrichment step

node enrich {
  type: scrape
  label: "Enrich from web"
  url: @ts { return "https://example.com/" + context.nodes.root.output.slug }
  failurePolicy: {
    strategy: "skip"
  }
}

If the web scrape fails, the enrich node is marked as skipped. Any downstream node that reads context.nodes.enrich.output receives undefined. The execution continues to completion.

Retry with a short backoff on a rate-limited service

node scrape {
  type: scrape
  label: "Scrape page"
  url: @ts { return context.nodes.root.output.url }
  failurePolicy: {
    strategy: "retry"
    maxRetries: 2
    backoffMs: 5000
  }
}

Downstream behavior after skip or fallback

Nodes downstream of a skipped or fallback node still execute. They receive output from context.nodes.<name>.output:

  • After skip: the output is undefined. Use optional chaining (?.) in @ts blocks that read skipped node output.
  • After fallback: the output is the fallbackValue you specified.

Both cases flow through the normal edge graph. There is no branching on failure; you handle the difference in downstream node code.

node format_result {
  type: code
  label: "Format result"
  code: @ts {
    const intent = context.nodes.classify.output?.intent ?? "unknown"
    return { formatted: "Intent: " + intent }
  }
}

Further reading

On this page