Skip to content

clawnify/clawflow

Repository files navigation

ClawFlow

ClawFlow

npm License Reddit Website

The n8n for agents. A declarative, AI-native workflow format that agents can read, write, and run — without infrastructure.


Install

npm install @clawnify/clawflow

Or as an OpenClaw plugin:

openclaw plugins install @clawnify/clawflow

From source:

git clone https://github.com/clawnify/clawflow.git
cd clawflow && npm install && npm run build

Why ClawFlow

Workflows 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:

  1. 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.

  2. The format is the asset, not the runtime. Write once, run as an OpenClaw plugin today, run in a standalone server tomorrow.

  3. AI nodes are first-class citizens. do: ai and do: agent are core primitives with structured output, model selection, and schema validation — not HTTP calls with extra steps.


Features

AI-Native

  • 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

Control Flow

  • do: branch — multi-way routing with inline sub-flows per path
  • do: condition — if/else with automatic reconvergence
  • do: loop — iterate over arrays
  • do: parallel — concurrent execution with all or race modes

Durability

  • Memoized state — completed nodes aren't re-run on resume
  • Approval gatesdo: wait pauses for human review, resumes with a token
  • External eventswaitForEvent blocks until an external system pushes data
  • Per-node retry — exponential, linear, or constant backoff on any node

Portability

  • 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

Quick Example

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" }
    }
  ]
}

Node Types

11 node types. This is intentional — the constraint is the feature. An LLM can reliably generate valid flows because there's nothing to hallucinate.

do: ai — LLM call

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.


do: agent — delegate to a real agent

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, extraction
  • do: agent = open-ended, multi-step, uses tools — use for scraping, research, file operations

do: branch — multi-way routing

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.


do: condition — if/else with reconvergence

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"

do: loop — iterate over a list

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.


do: parallel — concurrent execution

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.


do: http — external API call

{
  "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.


do: memory — persistent key/value store

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.


do: wait — pause for human or external event

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.


do: sleep — time-based pause

{ "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.


do: code — inline JavaScript

{
  "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.


Templates

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.


Retry Policy

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.


Flow State and Durability

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.


Runtime Targets

The format is the spec. The runtime is swappable.

1. OpenClaw Plugin (current)

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"] }
    }]
  }
}

2. Standalone Node.js Runner (coming soon)

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

3. Flow Registry (coming soon)

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)

How Agents Build Workflows

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.

System prompt for flow authorship

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

Comparison

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

What's Next

  • Standalone HTTP runner (self-hosted, no OpenClaw dependency)
  • Observability: structured traces, token tracking
  • Flow registry: shareable, reusable community flows
  • Cloudflare Workers transpiler
  • Visual canvas

Architecture

┌─────────────────────────────────────────────────────────────────┐
│                        .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                │
         └─────────────────────────────────────┘

Contributing

The most valuable contributions right now:

  1. Real-world flow definitions — try to describe a workflow you actually run, generate the flow JSON, and report where the format breaks down
  2. Node type proposals — what real workflow pattern can't be expressed with the current 11 node types?
  3. Runtime implementations — a Python runner, a Go runner, a Rust runner — anything that proves portability
  4. Bug reports — file issues at github.com/clawnify/clawflow

License

MIT


Built by Clawnify — AI agent hosting and orchestration.

About

The n8n for agents. A declarative, AI-native workflow format that agents can read, write, and run.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors