SWIRLS_

Swirls Docs

Agentic systems as code. Write .swirls files locally and deploy to Swirls Cloud.

Swirls is the deployment target for agentic systems. Describe agents, workflows, triggers, and auth in .swirls files. You can author and inspect those files locally, then deploy them to Swirls Cloud.

Data has SQL. Infrastructure has Terraform. Agents have Swirls.

Example workflow

This workflow accepts a contact form submission, summarizes it with an LLM, and sends a confirmation email. Workflows can span multiple .swirls files. The compiler composes them into a single workflow.

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

form contact_form {
  label: "Contact Form"
  enabled: true
  schema: contact_payload
}

workflow process_form {
  label: "Process Form"

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

  node summarize {
    type: ai
    label: "Summarize"
    model: "google/gemini-2.5-flash"
    kind: object
    schema: @json {
      { "type": "object", "required": ["text"], "properties": { "text": { "type": "string" } } }
    }
    prompt: @ts {
      const { name, email, message } = context.nodes.root.output
      return `Summarize in one sentence:\n\nFrom: ${name} <${email}>\n\n${message}`
    }
  }

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

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

trigger on_contact {
  form:contact_form -> process_form
  enabled: true
}

Get running in 5 minutes

Five ideas

Sixteen top-level blocks, five ideas. Learn the five and the rest is reference:

  • Agents: actors that reason. Chat with them, give them tools, put them inside workflows.
  • Workflows: deterministic procedures. Triggered by forms, webhooks, and schedules; available to agents as tools.
  • Memory: streams for structured output, disks for files, the Postgres you already have.
  • Connections: how your system talks to the outside world, from secrets you hold to brokered OAuth with no keys at all.
  • Access: who may reach in. Roles derived from identity, grants declared in the file.

The Primitives page maps all of it on one screen, and From intent to primitives translates plain-language goals into blocks.

What else you get

  • Every node type: ai, agent, code, http, email, postgres, scrape, stream, bucket, disk, workflow, parallel, switch, wait, map, while
  • Inline TypeScript blocks (@ts { … }) with LSP-powered IntelliSense
  • Triggers: connect forms, webhooks, and schedules to workflows
  • Human-in-the-loop: add review: { enabled: true } to any node to pause for approval
  • Local authoring with validation, type generation, and visual inspection
  • Cloud deployment via git push or swirls deploy. No CI/CD required.

When to use Swirls

  • AI content pipelines: draft, review, and publish content with LLM nodes and human review gates
  • Lead enrichment and scoring: enrich form submissions via HTTP, score with an LLM, route to CRM
  • Support ticket triage: classify urgency with a switch node, draft responses with an LLM
  • Automated reporting: schedule workflows to query data streams and deliver formatted summaries
  • Form-driven workflows: collect structured input, process it with AI, notify via email or webhook

Quick reference

Code node: run TypeScript, read upstream outputs via context.nodes.

node normalize {
  type: code
  label: "Normalize email"
  code: @ts {
    const { email } = context.nodes.root.input
    return { email: email.trim().toLowerCase() }
  }
}

AI node: prompt an LLM and get a typed response.

node classify {
  type: ai
  label: "Classify intent"
  model: "google/gemini-2.5-flash"
  kind: object
  schema: @json {
    { "type": "object", "required": ["intent"], "properties": { "intent": { "type": "string" } } }
  }
  prompt: @ts {
    return `Classify the intent of: ${context.nodes.root.input.message}`
  }
}

Switch node: route execution based on a TypeScript expression.

node route {
  type: switch
  label: "Route by urgency"
  cases: ["urgent", "normal", "low"]
  router: @ts {
    const body = context.nodes.root.output.body.toLowerCase()
    if (body.includes("urgent")) return "urgent"
    if (body.length > 500) return "normal"
    return "low"
  }
}

flow {
  root -> route
  route -["urgent"]-> handle_urgent
  route -["normal"]-> handle_normal
  route -["low"]-> handle_low
}

Next steps

On this page