---
name: runfabric
description: Author, publish, trigger, run, and inspect durable Runfabric workflows through its CLI, SDK, or MCP tools, including explicit integration profiles and local workers.
---

# Runfabric

Use Runfabric for a durable, provider-neutral process graph. Discover the deployed contract first with `runfabric doctor`, `runfabric schema`, and `runfabric templates`, or MCP `workflow_schema` and `workflow_templates`. Treat the service response and local validator as authoritative.

## Author the graph

A definition has `formatVersion: 1`, stable `id`, string `version`, `entry`, `nodes`, and `edges`. Node IDs and branch names match `^[A-Za-z][A-Za-z0-9_-]{0,63}$`. Edges name the source node's result port.

| Node | Required outgoing ports |
|---|---|
| `condition` | exactly `true`, `false` |
| `switch` | every declared case port, plus `default` |
| `approval` | exactly `approved`, `rejected` |
| `activity` | `success`; optional `failure` |
| `end` | none |
| `transform`, `foreach`, `parallel`, `repeat`, `wait` | exactly `next` |

Graphs must be acyclic and every node must be reachable. Put repetition inside `foreach` or `repeat`; never draw a cycle. `foreach` requires `maxItems`, preserves item order, and runs a nested `body`. `repeat` requires `maxIterations` and a nested `body`. `parallel` runs every named nested branch and joins only after all branches complete; its result is an object keyed by branch name. A failed child fails the join. Bounds also apply inside nested graphs.

An `activity` requires `activityKind`, `input`, and `mode` (`read`, `idempotent`, or `write`). A write activity cannot retry; `maxAttempts` is bounded for the other modes. `timeoutMs` defaults to 900000 and cannot exceed 3600000.

Expressions are JSON AST, not code:

- Primitives (`null`, boolean, number, string) are literal expressions.
- References are `{ "$ref": "input.customer.id" }`. Roots are `input`, `context`, `item`, and frame-local `steps`.
- Use `{ "op": "literal", "value": {...} }` for a literal JSON object or array.
- Build evaluated data with `{ "op": "object", "entries": {...} }` or `{ "op": "array", "items": [...] }`.
- Operators are `not`, `and`, `or`, `eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `exists`, and `length`.
- Do not put JavaScript, shell, URLs to execute, credentials, or provider configuration in an expression.

This valid example branches, loops over a bounded collection, and pauses for approval:

```json
{
  "formatVersion": 1,
  "id": "review-items",
  "version": "1",
  "name": "Review selected items",
  "entry": "should_process",
  "nodes": [
    {
      "id": "should_process",
      "type": "condition",
      "condition": { "op": "eq", "left": { "$ref": "input.process" }, "right": true }
    },
    {
      "id": "each_item",
      "type": "foreach",
      "items": { "$ref": "input.items" },
      "maxItems": 10,
      "body": {
        "entry": "shape",
        "nodes": [
          { "id": "shape", "type": "transform", "value": { "op": "object", "entries": { "item": { "$ref": "item" }, "reviewed": true } } },
          { "id": "item_done", "type": "end", "output": { "$ref": "steps.shape" } }
        ],
        "edges": [{ "source": "shape", "target": "item_done", "port": "next" }]
      }
    },
    { "id": "review", "type": "approval", "prompt": { "op": "object", "entries": { "items": { "$ref": "steps.each_item" } } } },
    { "id": "accepted", "type": "end", "output": { "op": "literal", "value": { "accepted": true } } },
    { "id": "skipped", "type": "end", "output": { "op": "literal", "value": { "accepted": false, "reason": "skipped" } } },
    { "id": "rejected", "type": "end", "output": { "op": "literal", "value": { "accepted": false, "reason": "rejected" } } }
  ],
  "edges": [
    { "source": "should_process", "target": "each_item", "port": "true" },
    { "source": "should_process", "target": "skipped", "port": "false" },
    { "source": "each_item", "target": "review", "port": "next" },
    { "source": "review", "target": "accepted", "port": "approved" },
    { "source": "review", "target": "rejected", "port": "rejected" }
  ]
}
```

Validate before saving. Draft creation does not publish. Fetch the current record before update or publish and pass its exact `revision`; reconcile a conflict instead of overwriting it. A published version is immutable, and runs start from published versions.

## Use the shipped interfaces

The installed package exposes `runfabric` and `runfabric-mcp`. The earlier `workflows` and `workflows-mcp` names remain aliases.

```bash
npm install https://runfabric.dev/downloads/runfabric-0.2.0.tgz
runfabric auth login
RUNFABRIC_EXAMPLES=./node_modules/@coralbeat/workflows/examples
runfabric integration create --file "$RUNFABRIC_EXAMPLES/quickstart-http-profile.json"
runfabric validate "$RUNFABRIC_EXAMPLES/quickstart-http-workflow.json"
runfabric workflow create --file "$RUNFABRIC_EXAMPLES/quickstart-http-workflow.json"
runfabric workflow publish quickstart-http --revision 1
printf '%s\n' '{"id":1}' | runfabric run start quickstart-http --input - --idempotency-key quickstart-http-1
runfabric run inspect RUN_ID
```

The API defaults to `https://dev.runfabric.dev`. CI and unattended agents may set `RUNFABRIC_API_KEY`; `WORKFLOW_URL` and `WORKFLOW_API_KEY` remain compatibility aliases. CLI definitions accept a file or `-` for stdin; JSON goes to stdout and structured errors go to stderr. Mutations accept `--idempotency-key`; when omitted the CLI creates a collision-resistant key for the new intent.

The SDK uses the same HTTP API. Import `RunfabricClient` from `@coralbeat/workflows/runfabric`; it defaults to the dev endpoint. `WorkflowClient` remains available with an explicit `baseUrl`. Both cover workflows, runs, keys, integration profiles, schedules, webhooks, and the redacted activity journal. Browser surfaces use `RunfabricBrowserClient` with an authorized `projectId`. Remote calls have finite time and response-size bounds and accept an `AbortSignal`.

Start `runfabric-mcp` after browser login or with `RUNFABRIC_API_KEY`. Hosted MCP is `https://dev.runfabric.dev/mcp`; discover it through `https://dev.runfabric.dev/.well-known/oauth-protected-resource/mcp` and request the exact OAuth resource `https://dev.runfabric.dev/mcp`. API tokens use the separate `https://dev.runfabric.dev/v1` resource. Its canonical tools cover workflow create/get/list/update/publish/export, run start/list/inspect/approve/cancel/rerun, profiles, schedules, webhooks, and keys. MCP mutations use exact revisions or idempotency keys; omitted run-command keys are generated. It has no eval, arbitrary URL, or arbitrary-shell tool.

Create hosted profiles and triggers from JSON files:

```bash
runfabric integration create --file ./profile.json
runfabric integration test PROFILE_ID --input ./probe.json
runfabric schedule create --file ./schedule.json
runfabric webhook create --file ./webhook.json
```

Webhook create and rotate return a secret once; store it immediately. List/get and errors never return credentials. A user-owned local worker loads reviewed profiles and optional secrets, polls real fenced tasks, heartbeats them during execution, and stops cleanly on `SIGINT` or `SIGTERM`:

```bash
runfabric work start --profiles ./profiles.json --secrets ./secrets.json
runfabric work start --profiles ./profiles.json --once
```

Only profile-owned executable and argument arrays reach local CLI or stdio MCP execution; no shell is used and workflow input cannot select a command. Hosted profiles support HTTP, remote MCP, and bounded agent loops. Inspect hosted tool activity with `runfabric run journal RUN_ID`.

## Run and recover safely

Use a fresh idempotency key for each intended run start or command; reuse it only to retry the identical request. Inspect current approvals, activities, waits, events, revision, and status before commanding a run. Approval and rejection commands require an attributed `actor`. Show the concrete prompt and effect before requesting consent unless the user already authorized it. This skill never authorizes signup, purchases, billing changes, provider credentials, or cloud resources.

Activity nodes create durable intents; the core does not call providers. An operator-owned `ActivityWorker` claims by activity kind, heartbeats a server-issued fenced lease, and completes or fails with that lease token. Keep credentials and allowed origins in worker configuration. The optional trusted HTTP adapter uses only operator-configured origins, routes, hosts, and headers; graph input is only its JSON body. If no handler exists, leave the activity parked for an explicit worker or manual completion. Never claim external effects are exactly once: an expired write can become `unknown` and must be reconciled against the provider before retry.

Report failed, rejected, cancelled, timed-out, and `unknown` states accurately. `run watch` should stop at a terminal state or the user's requested condition.

Runfabric does not claim n8n import compatibility, arbitrary code execution, billing, or model-vendor lock-in.
