Swirl Background - Animated Topographic Lines
Now in alpha release

AGENTIC WORKFLOWS.
SECURE BY CONSTRUCTION.

Durable execution for AI workflows. Agents only access what the workflow authorizes. Define it in one .swirls file. Deploy it in one command.

The workflow definition IS the security policy. 13 node types. Resumable steps. Auditable execution. Free to run locally.

Durable execution engineFree local runtimeCloud from $30/mo
contact-form.swirls
form contact {
  label: "Contact Form"
  enabled: true
  schema: @json {
    {
      "type": "object",
      "required": ["name", "email", "message"],
      "properties": {
        "name": { "type": "string" },
        "email": { "type": "string" },
        "message": { "type": "string" }
      }
    }
  }
}

graph process_contact {
  label: "Process Contact"

  root {
    type: code
    label: "Normalize"
    code: @ts {
      const { name, email, message } = context.nodes.root.input
      return {
        name: String(name ?? "").trim(),
        email: String(email ?? "").toLowerCase().trim(),
        message: String(message ?? "").trim(),
      }
    }
  }

  node summarize {
    type: ai
    label: "Summarize"
    model: "google/gemini-2.5-flash"
  }

  node notify {
    type: resend
    label: "Send confirmation"
  }

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

trigger on_contact {
  form:contact -> process_contact
  enabled: true
}
The Problem

AGENTS ACT ON YOUR BEHALF. WHO CONTROLS WHAT THEY CAN DO?

AI agents call APIs, move data, and spend money. Most runtimes give every agent full access to everything. Swirls scopes permissions automatically from your workflow definition. Each agent only reaches the secrets, tools, and endpoints you authorize. Nothing more.

Other Runtimes
WorkspaceFULL ACCESS
DeploymentFULL ACCESS
ExecutionFULL ACCESS
NodeFULL ACCESS
ToolFULL ACCESS

Flat token. Every node gets access to every secret and every endpoint.

Swirls
Workspace
workspace=acme-corp
Deployment
graph_hash=sha256:a1b2...
Execution
nonce=e7d2, ttl=1h
Node
secrets=PARTNER_KEY
Tool
endpoints=api.partner.com

Permissions narrow at every step. The workflow definition controls it all.

The Runtime

DURABLE. REVIEWABLE. AUDITABLE.

Workflows survive failures, pause for human review, and produce tamper-evident execution logs. Three properties of one runtime. Watch one workflow flow through all three.

Durable Execution

Every step is saved. Workflows resume exactly where they stopped. No re-execution. No lost state.

EXECUTION · run_8f3a
http/enrich340ms
ai/classify1.2s
review/approvepaused
resend/notify120ms

Human-in-the-Loop

Workflows pause for human review when stakes are high. Approve, reject, or edit. Built into the language.

REVIEW GATE

Tamper-Evident Logs

Every execution is recorded and verifiable. If an agent did it, you can prove it.

AUDIT LOG
trigger.received#001
Get Started

THREE WAYS IN.

Write workflows by hand, generate them with an LLM, or fork a recipe from the cookbook. Every path gets you to a running workflow.

Full LSP support in VS Code

  • IntelliSense and error checking as you type
  • 13 node types, 3 resource types
  • Run locally with swirls worker start
  • Hot reload on save
swirls worker start
/**
 * Scores incoming leads and persists results for reporting.
 * High-scoring leads trigger an alert to the sales team.
 */

webhook new_lead {
  label: "New Lead Webhook"
  schema: @json {
    {
      "type": "object",
      "required": ["email", "company", "source"],
      "properties": {
        "email": { "type": "string", "format": "email" },
        "company": { "type": "string" },
        "source": { "type": "string" }
      }
    }
  }
}

graph score_lead {
  label: "Score Lead"

  root {
    type: code
    label: "Extract lead"
    code: @ts { return context.nodes.root.input }
  }

  node score {
    type: ai
    label: "Score the lead"
    kind: object
    model: "google/gemini-2.5-flash"
    prompt: @ts {
      const lead = context.nodes.root.output
      return "Score this B2B lead 0-100.\n\nEmail: "
        + lead.email + "\nCompany: " + lead.company
    }
    schema: @json {
      {
        "type": "object",
        "required": ["score", "reasoning"],
        "properties": {
          "score": { "type": "number" },
          "reasoning": { "type": "string" },
          "tier": { "type": "string", "enum": ["hot", "warm", "cold"] }
        }
      }
    }
  }

  node route {
    type: switch
    label: "Route by score"
    cases: ["hot", "other"]
    router: @ts {
      if (context.nodes.score.output.tier === "hot") return "hot"
      return "other"
    }
  }

  node alert_sales {
    type: resend
    label: "Alert sales team"
    from: @ts { return "[email protected]" }
    to: @ts { return "[email protected]" }
    subject: @ts {
      return "Hot lead: " + context.nodes.root.output.company
        + " (Score: " + context.nodes.score.output.score + ")"
    }
    text: @ts {
      const lead = context.nodes.root.output
      const result = context.nodes.score.output
      return "New hot lead!\n\nCompany: " + lead.company
        + "\nScore: " + result.score
        + "\nReason: " + result.reasoning
    }
  }

  flow {
    root -> score
    score -> route
    route -["hot"]-> alert_sales
  }
}

stream scored_leads {
  label: "Scored leads"
  graph: score_lead
  schema: @json {
    {
      "type": "object",
      "required": ["score", "reasoning", "tier"],
      "properties": {
        "score": { "type": "number" },
        "reasoning": { "type": "string" },
        "tier": { "type": "string" }
      }
    }
  }
  condition: @ts { return true }
  prepare: @ts {
    return context.nodes.score.output
  }
}

trigger on_lead {
  webhook:new_lead -> score_lead
  enabled: true
}
Alpha release

DEPLOY YOUR FIRST AGENTIC WORKFLOW

Install the CLI. Write a .swirls file. Run it locally in under five minutes. When it's ready, swirls cloud deploy.

Shape the future of Swirls by joining our community.

JOIN THE DISCORD