DEPLOY AGENTIC AI.
ONE FILE. ONE COMMAND.
Write workflows in .swirls files. Run locally with zero config. When it's ready swirls deploy.
A compact workflow language for agents, APIs, forms, email, and human steps—framework-agnostic, diff-friendly, and easy to sketch in plain English before you refine it in code.
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: email
label: "Send confirmation"
}
flow {
root -> summarize
summarize -> notify
}
}
trigger on_contact {
form:contact -> process_contact
enabled: true
}DESCRIBE IT. BUILD IT. SHIP IT.
You don't need to be a developer to create a Swirls workflow. Describe what you want and get working code back.
Write it yourself
.swirls files have full LSP support: IntelliSense, error checking, go-to-definition in VS Code. 12 node types, 3 resource types, one flow block.
Let an LLM write it
Describe your workflow in plain English. Claude, GPT, or any LLM can generate a valid .swirls file. The format is small, declarative, and well-documented. Ideal for AI code generation.
Start from a cookbook
Copy a working example (lead enrichment, support triage, content pipelines, blog generation) and customize it.
THREE CONCEPTS. ONE FILE.
FULL WORKFLOWS.
Every .swirls file is made of resources, graphs, and triggers. One file, three concepts.
Your workflow entry points
Forms, webhooks, and schedules. Each one generates a UI in the Portal and an API endpoint automatically.
form contact {
label: "Contact Form"
enabled: true
schema: @json {
{ "type": "object", "additionalProperties": true }
}
}
webhook inbound {
label: "Inbound Webhook"
enabled: true
}
schedule daily_report {
cron: "0 9 * * *"
timezone: "America/New_York"
}DAGs of typed nodes
Code, AI, HTTP, email, switch, human review. 12 node types connected by explicit edges. Every input and output is typed.
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"
prompt: @ts {
const { message } = context.nodes.root.output
return `Summarize this inquiry in two sentences for the team. Message: ${message}`
}
}
node notify {
type: email
label: "Send confirmation"
}
flow {
root -> summarize
summarize -> notify
}
}Connect a resource to a graph
One line. When the resource fires, the graph runs.
trigger on_contact {
form:contact -> process_contact
enabled: true
}
trigger on_webhook {
webhook:inbound -> enrich_lead
enabled: true
}
trigger daily {
schedule:daily_report -> generate_report
enabled: true
}12 PRIMITIVES. INFINITE WORKFLOWS.
Every node in a graph has a type. Each type does one thing well. Combine them to build anything.
Run TypeScript
LLMs, structured output, images, embeddings
Call any API
Send email
Route by condition
Persist to queryable storage
Call another graph
Extract web content
Pause for a duration
File storage
Process documents
If your workflow needs it, there's a node for it.
PAUSE. REVIEW. RESUME.
Any node can require human approval before continuing. The workflow serializes its state, shuts down, and resumes exactly where it stopped when approved. Built into the language, not bolted on.
node draft_email {
type: ai
label: "Draft Email"
model: "anthropic/claude-sonnet-4-20250514"
kind: object
prompt: @ts {
return `Draft a response to: ${context.nodes.root.output.message}`
}
schema: @json {
{
"type": "object",
"required": ["subject", "body"],
"properties": {
"subject": { "type": "string" },
"body": { "type": "string" }
}
}
}
review: {
enabled: true
label: "Review draft before sending"
}
}EVERY EXECUTION, ON THE RECORD
When a Swirls workflow runs, every node execution is recorded: inputs, outputs, duration, status. Queryable. Replayable.
Debug step-by-step. Roll back to a previous graph version. See exactly which node failed and why.
This is what “auditable AI” looks like.
SECRETS GO WHERE THE WORKFLOW SAYS, AND NOWHERE ELSE.
Swirls derives the minimum set of secrets authorized by each node's token. Enforced by the runtime.
The SHA-256 hash of your compiled workflow definition is embedded in every authorization token. Modify the workflow, the token breaks. Permissions stay in sync with the code, always.
Only what you declare on the node enters context.secrets
For any node, the executor merges values from your project vault only for the keys listed in that node's secrets: declaration. Nothing else from the vault is exposed to TypeScript at runtime.
// The runtime injects project vault values only for keys listed on this node.
// Anything else in the vault never appears on context.secrets here.
graph call_partner_api {
label: "Partner API (least-privilege secrets)"
root {
type: code
label: "Fetch with scoped credentials"
// Only these names are authorized for this node; the executor builds context.secrets from this list alone.
secrets: [PARTNER_API_KEY, PARTNER_WEBHOOK_SECRET]
inputSchema: @json {
{
"type": "object",
"required": ["query"],
"properties": { "query": { "type": "string" } },
"additionalProperties": false
}
}
code: @ts {
// PARTNER_API_KEY and PARTNER_WEBHOOK_SECRET are in scope; other vault keys are not.
const key = context.secrets.PARTNER_API_KEY
const signingSecret = context.secrets.PARTNER_WEBHOOK_SECRET
return {
query: context.nodes.root.input.query,
ready: Boolean(key && signingSecret),
}
}
}
}START LOCAL. DEPLOY WHEN READY.
Develop on your machine with the same execution engine that runs in production. When you're ready, push to the cloud with one command.
Local
- SQLite-backed execution
- Full Portal access
- Hot reload
Cloud
- $30/mo with 50k credits
- Managed infrastructure
- Auto-scaling
Self-hosted
- Your infrastructure
- Full control
- Air-gapped support
DEPLOY YOUR FIRST AGENT
Install the CLI, write a .swirls file, and run it locally in under 5 minutes. When it's ready, swirls deploy and it's live.