The n8n for agents. A declarative, AI-native workflow format that agents can read, write, and run — without infrastructure.
npm install @clawnify/clawflowOr as an OpenClaw plugin:
openclaw plugins install @clawnify/clawflowFrom source:
git clone https://github.com/clawnify/clawflow.git
cd clawflow && npm install && npm run buildWorkflows today are written for agents, not by them. Visual canvas tools require humans to click nodes. Code-first orchestration frameworks have too much surface area for LLMs to generate reliably.
ClawFlow is a workflow format designed from first principles for agents. Three rules drove every design decision:
-
An LLM must be able to write a valid workflow in a single turn. If the format is too complex, agents hallucinate. If it's too simple, real workflows can't be expressed.
-
The format is the asset, not the runtime. Write once, run as an OpenClaw plugin today, run in a standalone server tomorrow.
-
AI nodes are first-class citizens.
do: aianddo: agentare core primitives with structured output, model selection, and schema validation — not HTTP calls with extra steps.
do: ai— structured LLM calls with schema validation and model selection (fast,smart,best)do: agent— delegate to real agents with full tool access (browser, exec, memory, MCP, CLI)- Agent-writable — any LLM can generate a valid flow from a natural language description
do: branch— multi-way routing with inline sub-flows per pathdo: condition— if/else with automatic reconvergencedo: loop— iterate over arraysdo: parallel— concurrent execution withallorracemodes
- Memoized state — completed nodes aren't re-run on resume
- Approval gates —
do: waitpauses for human review, resumes with a token - External events —
waitForEventblocks until an external system pushes data - Per-node retry — exponential, linear, or constant backoff on any node
- OpenClaw plugin — run flows as agent tools today
- Standalone runner — self-hosted Node.js server (coming soon)
- Static validation — catch bad references and missing fields before execution
- Draft/publish versioning — edit safely, publish when ready, run any version
A flow is JSON. No custom syntax, no new language — just structured data that any LLM can generate from a description.
{
"flow": "support-triage",
"description": "Classify a ticket, draft a reply, get approval, send it",
"trigger": { "on": "webhook", "from": "helpdesk" },
"nodes": [
{
"name": "classify",
"do": "ai",
"prompt": "Classify this ticket as billing, technical, or general",
"input": "trigger.body",
"schema": {
"category": "billing | technical | general",
"urgency": "low | medium | high",
"summary": "string"
},
"model": "fast",
"output": "classification"
},
{
"name": "route",
"do": "branch",
"on": "classification.category",
"paths": {
"billing": [
{
"name": "handle-billing",
"do": "agent",
"task": "Draft a billing support reply for: {{ trigger.body }}",
"output": "draft"
}
],
"technical": [
{
"name": "handle-technical",
"do": "agent",
"task": "Draft a technical support reply for: {{ trigger.body }}",
"output": "draft"
}
]
},
"default": [
{
"name": "handle-general",
"do": "agent",
"task": "Draft a general support reply for: {{ trigger.body }}",
"output": "draft"
}
]
},
{
"name": "approve",
"do": "wait",
"for": "approval",
"prompt": "Send this reply?\n\n{{ draft }}"
},
{
"name": "send",
"do": "http",
"url": "https://helpdesk.example.com/api/reply",
"method": "POST",
"body": { "message": "{{ draft }}", "ticketId": "{{ trigger.id }}" },
"retry": { "limit": 3, "delay": "2s", "backoff": "exponential" }
}
]
}11 node types. This is intentional — the constraint is the feature. An LLM can reliably generate valid flows because there's nothing to hallucinate.
The most important node. A single LLM call that returns structured or freeform output.
{
"name": "classify",
"do": "ai",
"prompt": "Classify this support ticket",
"input": "trigger.body",
"schema": {
"category": "billing | technical | general",
"confidence": "number",
"summary": "string"
},
"model": "fast",
"output": "classification"
}| Field | Description |
|---|---|
prompt |
The instruction to the model. Supports {{ templates }}. |
input |
Dotted path to a value in flow state passed as context |
schema |
Output shape. When set, enforces JSON mode. Keys are type hints. |
model |
fast (Gemini 3 Flash), smart (Claude Sonnet 4.6), best (Minimax M2.5), or any model string |
temperature |
0–1, default 0 for deterministic workflow steps |
Why schema matters: downstream nodes reference classification.category as a reliable string. Without schema, the output is freeform text and you're back to parsing.
Runs a task through a real OpenClaw agent with full tool access (browser, exec, memory, MCP, CLI). The agent decides its own path to a result.
{
"name": "scrape",
"do": "agent",
"task": "Navigate to https://example.com and extract the pricing table as JSON",
"agent": "main",
"timeout": "120s",
"output": "data"
}On OpenClaw, this delegates to openclaw agent --agent <id> --message "...". The agent gets full tool access — browser, shell, file system, memory, MCP. Falls back to a single AI call if the CLI is unavailable (standalone mode).
| Field | Description |
|---|---|
task |
The instruction to the agent. Supports {{ templates }}. |
agent |
OpenClaw agent ID (e.g. "main", "ops"). Uses config defaultAgent or "main" if omitted. |
input |
Dotted path to context passed with the task |
tools |
Hint for non-OpenClaw runtimes (OpenClaw agents have their own tool policy) |
The distinction between ai and agent is intentional:
do: ai= deterministic, one-shot, structured output — use for classification, drafting, extractiondo: agent= open-ended, multi-step, uses tools — use for scraping, research, file operations
Routes the flow to a sub-flow based on a value in state. Each path is an array of nodes that executes as a self-contained block, then reconverges back into the main flow.
{
"name": "route",
"do": "branch",
"on": "classification.category",
"paths": {
"billing": [
{ "name": "lookup-invoice", "do": "http", "url": "https://api.example.com/invoice/{{ trigger.id }}", "output": "invoice" },
{ "name": "draft-reply", "do": "ai", "prompt": "Draft billing reply for: {{ invoice }}", "output": "draft" }
],
"technical": [
{ "name": "draft-reply", "do": "agent", "task": "Research and draft technical reply for: {{ trigger.body }}", "output": "draft" }
]
},
"default": [
{ "name": "draft-reply", "do": "ai", "prompt": "Draft a general reply for: {{ trigger.body }}", "output": "draft" }
]
}Each path runs its full node sequence and merges state back. The default path handles any value not explicitly listed. No default + no matching path = runtime error (intentional — fail loudly).
Use branch for multi-way value matching, condition for boolean if/else logic. Both support full sub-flows and reconverge automatically.
Runs inline sub-node blocks based on a boolean condition, then merges back into the main flow. Use condition for true/false logic, branch for multi-way value matching.
{
"name": "check-transport",
"do": "condition",
"if": "extractOrder.transport_type == 'CLIENTE'",
"then": [
{ "name": "pickup-note", "do": "code", "run": "'Client picks up'", "output": "note" }
],
"else": [
{ "name": "delivery-note", "do": "code", "run": "'We deliver'", "output": "note" }
]
}| Field | Description |
|---|---|
if |
JS expression evaluated against flow state. Dotted paths are resolved. |
then |
Nodes to run when condition is true |
else |
Nodes to run when condition is false (optional — skipped if absent) |
Supports comparison and logical operators:
"classification.priority == 'urgent'"
"validation.valid && items.length > 0"
"trigger.amount > 1000 || trigger.vip == true"
Runs a set of sub-nodes for each item in an array.
{
"name": "process-tickets",
"do": "loop",
"over": "inbox.tickets",
"as": "ticket",
"nodes": [
{
"name": "summarize",
"do": "ai",
"prompt": "Summarize this ticket: {{ ticket }}",
"output": "summary"
}
],
"output": "processed"
}Iterations run sequentially. For concurrent execution, use do: parallel.
Runs multiple nodes at the same time. Waits for all to complete (mode: "all") or the first to finish (mode: "race").
{
"name": "research",
"do": "parallel",
"mode": "all",
"nodes": [
{
"name": "web-search",
"do": "agent",
"task": "Search the web for recent cases of {{ topic }}",
"output": "web_results"
},
{
"name": "memory-lookup",
"do": "memory",
"action": "read",
"key": "knowledge-{{ topic }}",
"output": "memory_results"
}
],
"output": "research"
}mode: "race" mirrors Cloudflare's Promise.race() pattern — the first branch to complete wins and the others are discarded. Useful for: try-cache-before-fetch, multi-model racing, fallback strategies.
{
"name": "notify-slack",
"do": "http",
"url": "https://hooks.slack.com/services/{{ trigger.slackWebhook }}",
"method": "POST",
"body": { "text": "Ticket resolved: {{ classification.summary }}" },
"retry": { "limit": 3, "delay": "1s", "backoff": "exponential" },
"output": "slack_response"
}All fields support {{ templates }}. Retry is strongly recommended for any outbound call.
Read, write, or delete values that persist across flow runs.
{ "name": "save-result", "do": "memory", "action": "write", "key": "ticket-{{ trigger.id }}", "value": "{{ classification.category }}" }
{ "name": "load-history", "do": "memory", "action": "read", "key": "ticket-{{ trigger.id }}", "output": "previous_category" }
{ "name": "cleanup", "do": "memory", "action": "delete", "key": "ticket-{{ trigger.id }}" }In the OpenClaw plugin, memory persists to ~/.openclaw/flow-memory/. In Cloudflare, it maps to KV or D1. Keys support templates.
Two modes:
Approval gate — pauses the flow and returns a resumeToken. A human reviews and approves or denies via flow_resume.
{
"name": "approve-send",
"do": "wait",
"for": "approval",
"prompt": "Send this reply to the customer?\n\n{{ draft }}"
}External event — blocks until an external system pushes the matching event via flow_send_event. Learned from Cloudflare's step.waitForEvent().
{
"name": "await-payment",
"do": "wait",
"for": "event",
"event": "stripe-payment-confirmed",
"timeout": "24h",
"output": "payment"
}When a Stripe webhook arrives, the calling code does:
{ "tool": "flow_send_event", "instanceId": "...", "eventType": "stripe-payment-confirmed", "payload": { "amount": 4900 } }The flow resumes with payment.amount = 4900 in state.
{ "name": "cool-down", "do": "sleep", "duration": "5m" }Duration syntax: 30s, 5m, 2h, 1d. Maps directly to Cloudflare's step.sleep(). Does not count towards step limits.
{
"name": "format-date",
"do": "code",
"input": "trigger.timestamp",
"run": "new Date(input).toLocaleDateString('en-GB')",
"output": "formatted_date"
}Single expressions are returned automatically. Multi-statement bodies (containing ; or newlines) require an explicit return:
{
"name": "calc",
"do": "code",
"input": "trigger",
"run": "const total = input.price * input.qty;\nconst tax = total * 0.22;\nreturn { total, tax, grand: total + tax };",
"output": "invoice"
}No imports, no async, no filesystem access. state and input are frozen — return new values instead of mutating. For scripts or complex logic, use do: exec to run shell commands. For external APIs, use do: http.
Any string field supports {{ path.to.value }} interpolation resolved against flow state:
{{ trigger.body }} # initial input
{{ classification.category }} # node with output: "classification" → access .category
{{ trigger.user.email }} # nested dotted path
{{ research.web_results }} # array or object (serialized to JSON string)
Important: templates reference the output key, not the node name. If a node has "name": "get_data", "output": "api", reference it as {{ api }} — not {{ get_data }}.
Flow state starts as { trigger: <input> } and grows as nodes complete.
Any node can define per-node retry behavior. Learned directly from Cloudflare's WorkflowStepConfig:
{
"retry": {
"limit": 3,
"delay": "2s",
"backoff": "exponential"
}
}| Field | Values | Default |
|---|---|---|
limit |
integer | 1 (no retry) |
delay |
duration string or ms | "0" |
backoff |
constant, linear, exponential |
constant |
Strongly recommended on do: http and do: ai nodes. Do not use on do: wait — retrying approval gates makes no sense.
Every flow run gets a unique instanceId. The runner persists state after every completed node to ~/.openclaw/flow-state/<instanceId>.json.
What this means in practice:
- Gateway restarts mid-flow? Already-completed nodes are not re-run. The flow resumes from the last checkpoint.
- A node that took 30 seconds to run won't run again on resume — its memoized output is loaded from disk.
- An approval-gated flow can stay paused for days. The state survives indefinitely.
This is the lightweight equivalent of Cloudflare Durable Objects' memoization. Cloudflare does it at the infrastructure level with global durability. We do it at the file system level for local/self-hosted use.
The format is the spec. The runtime is swappable.
Eleven tools registered in OpenClaw:
| Tool | Does |
|---|---|
flow_create |
Create a new flow definition and save to file |
flow_delete |
Soft-delete a flow (moves to .clawflow/bin/) |
flow_restore_from_bin |
List bin contents or restore a deleted flow |
flow_run |
Execute a flow (uses latest published version by default) |
flow_resume |
Resume after an approval gate |
flow_send_event |
Push an event into a waiting flow |
flow_status |
Inspect any running or completed instance |
flow_list |
List all flows with metadata, expected inputs, and version info |
flow_read |
Read a flow definition (draft or specific version), inspect single nodes |
flow_publish |
Publish current draft as a new numbered version |
flow_edit |
Edit nodes in a flow definition (set, update, add, remove, move, wrap, revert, list) |
Config:
{
"plugins": {
"entries": {
"clawflow": {
"enabled": true,
"config": { "defaultModel": "smart" }
}
}
},
"agents": {
"list": [{
"id": "main",
"tools": { "alsoAllow": ["flow_create", "flow_delete", "flow_restore_from_bin", "flow_run", "flow_resume", "flow_send_event", "flow_status", "flow_list", "flow_read", "flow_publish", "flow_edit"] }
}]
}
}A small HTTP server wrapping the runner. Expose flows as endpoints, receive webhooks, manage instances via REST API. Self-hosted alternative to Cloudflare.
POST /flows/:name/run # start a flow instance
POST /flows/resume # resume paused flow
POST /flows/event # send event to waiting flow
GET /flows/instances # list all instances
GET /flows/instances/:id # get instance status
A community library of reusable, shareable .flow definitions. Think npm for workflows — but agent-writable.
clawflow install support-triage
clawflow install github-pr-review
clawflow install invoice-processing
clawflow install lead-enrichment
Every flow in the registry is:
- Parameterized (inputs declared in
trigger) - Runtime-agnostic (runs on OpenClaw or Cloudflare)
- LLM-editable (agents can fork and modify them)
This is the core loop that makes this different from every other workflow tool:
User: "When a new GitHub PR is opened, have an AI review the diff,
check if all tests pass, and if the review is positive
post an approval — otherwise request changes with specific feedback"
Agent: [calls flow_run with an inline flow definition it just generated]
→ flow runs
→ AI reviews diff (do: ai)
→ checks CI status (do: http → GitHub API)
→ branches on review result (do: branch)
→ posts comment (do: http → GitHub API)
The agent doesn't need a visual canvas. It doesn't need to learn a DSL. It reads the node type descriptions from the tool definition and generates valid JSON in one turn.
Add this to a skill or system prompt to enable an agent to write flows:
You can design and run workflows using flow_run.
Flows are JSON with a "flow" name and "nodes" array.
Node types: ai, agent, branch, condition, loop, parallel, http, memory, wait, sleep, code
Rules:
- Every node needs a unique "name"
- Use "output" to name a node's result — other nodes reference it via the output key: {{ outputKey.field }}
- Always add "schema" to ai nodes when downstream nodes need typed fields
- Use "retry" on all http nodes: { "limit": 3, "delay": "2s", "backoff": "exponential" }
- Use "do: agent" for open-ended research/tasks, "do: ai" for structured extraction
- "do: wait" with "for: approval" pauses for human review before side effects
- "do: parallel" runs nodes concurrently; use when steps are independent
- Prefer "model": "fast" for classification, "smart" for drafting, "best" for complex reasoning
| Visual canvas tools | Code-first orchestration | ClawFlow | |
|---|---|---|---|
| AI nodes first-class | ✗ | partial | ✓ |
| Agent delegation | ✗ | partial | ✓ |
| LLM can write it | ✗ | ✗ | ✓ |
| Human readable | ✓ | ✗ | ✓ |
| Durable execution | ✗ | ✓ | ✓ |
| Per-step retry | ✓ | ✓ | ✓ |
| waitForEvent | ✗ | ✓ | ✓ |
| Parallel branches | ✓ | ✓ | ✓ |
| Runtime portable | ✗ | ✗ | ✓ |
| Self-hostable | ✓ | ✗ | ✓ |
- Standalone HTTP runner (self-hosted, no OpenClaw dependency)
- Observability: structured traces, token tracking
- Flow registry: shareable, reusable community flows
- Cloudflare Workers transpiler
- Visual canvas
┌─────────────────────────────────────────────────────────────────┐
│ .flow definition │
│ (JSON — the portable format spec) │
└──────────────────────────────┬──────────────────────────────────┘
│
┌──────────────┼──────────────┐
│ │
┌──────▼──────┐ ┌────────▼───────┐
│ OpenClaw │ │ Standalone │
│ Plugin │ │ Node Server │
│ │ │ (coming soon) │
│ 11 tools │ │ │
│ versioning │ │ REST API │
│ webhooks │ │ Webhook recv │
└──────┬──────┘ └────────────────┘
│
┌──────▼──────────────────────────────┐
│ FlowRunner │
│ │
│ ┌─────────┐ ┌─────────────────┐ │
│ │ State │ │ Event Bus │ │
│ │ Store │ │ (waitForEvent) │ │
│ │ │ │ │ │
│ │ memoize │ │ sendEvent() │ │
│ │ resume │ │ per instanceId │ │
│ └─────────┘ └─────────────────┘ │
│ │
│ Node executors: │
│ execAi · execAgent · execBranch │
│ execCondition · execLoop │
│ execParallel · execHttp │
│ execMemory · execWait · execSleep │
│ execCode · execExec │
└─────────────────────────────────────┘
The most valuable contributions right now:
- Real-world flow definitions — try to describe a workflow you actually run, generate the flow JSON, and report where the format breaks down
- Node type proposals — what real workflow pattern can't be expressed with the current 11 node types?
- Runtime implementations — a Python runner, a Go runner, a Rust runner — anything that proves portability
- Bug reports — file issues at github.com/clawnify/clawflow
MIT
Built by Clawnify — AI agent hosting and orchestration.
