# Runfabric

Runfabric is a portable durable-workflow engine with a TypeScript SDK, JSON CLI, MCP server, hosted integration profiles, schedules, webhooks, approvals, and user-owned activity workers. PostgreSQL holds immutable workflow versions and durable run state. Activity leases are fenced, and ambiguous external writes become `unknown` for reconciliation instead of being reported as successful.

Version 0.2.0 is a development release. It does not include billing, arbitrary code nodes, or n8n import compatibility. Local CLI and stdio MCP processes run only from explicit user-owned profiles without a shell. Hosted execution supports allowlisted HTTPS, remote MCP, and bounded OpenAI-compatible agent loops; workflow input cannot select destinations, commands, credentials, or tools.

## Install and authenticate

Node 24 or later is required. Install from the public release artifact without repository access:

```bash
npm install https://runfabric.dev/downloads/runfabric-0.2.0.tgz
npx runfabric auth login
npx runfabric auth status
```

`auth login` opens the browser and uses Authorization Code with PKCE S256 through a loopback callback. Credentials are stored under the platform config directory in a mode-0600 file. `auth logout` revokes the OAuth token before removing the local file. The API defaults to `https://dev.runfabric.dev`; set `RUNFABRIC_URL` only for another deployment. CI may use `RUNFABRIC_API_KEY`. The previous `workflows`, `workflows-mcp`, `WORKFLOW_URL`, and `WORKFLOW_API_KEY` names remain compatibility aliases.

## Complete hosted HTTP quickstart

The package ships a runnable workflow and an allowlisted public HTTP profile. The profile selects the destination; the workflow supplies only the path value `{ "id": 1 }`.

```bash
RUNFABRIC_EXAMPLES=./node_modules/@coralbeat/workflows/examples

npx runfabric integration create \
  --file "$RUNFABRIC_EXAMPLES/quickstart-http-profile.json"

npx runfabric validate "$RUNFABRIC_EXAMPLES/quickstart-http-workflow.json"
RUNFABRIC_WORKFLOW=$(npx runfabric workflow create \
  --file "$RUNFABRIC_EXAMPLES/quickstart-http-workflow.json")
RUNFABRIC_REVISION=$(printf '%s' "$RUNFABRIC_WORKFLOW" | node -e \
  'let s="";process.stdin.on("data",c=>s+=c).on("end",()=>console.log(JSON.parse(s).revision))')
npx runfabric workflow publish quickstart-http --revision "$RUNFABRIC_REVISION"

RUNFABRIC_RUN=$(printf '%s\n' '{"id":1}' | npx runfabric run start \
  quickstart-http --input - --idempotency-key quickstart-http-1)
RUNFABRIC_RUN_ID=$(printf '%s' "$RUNFABRIC_RUN" | node -e \
  'let s="";process.stdin.on("data",c=>s+=c).on("end",()=>console.log(JSON.parse(s).id))')
npx runfabric run watch "$RUNFABRIC_RUN_ID"
```

The final run output contains the remote JSON response. Reuse an idempotency key only to retry the same request. Use a new key for a new run. `runfabric workflow get`, `list`, `update`, and `export`, plus `runfabric run inspect`, `list`, `approve`, `reject`, `cancel`, `rerun`, and `journal`, cover the rest of the lifecycle.

## Integration profiles and triggers

Hosted profile responses include configuration and `hasCredentials`; they never return credentials or ciphertext. Create and test profiles from JSON:

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

Schedules accept either a cron expression or `intervalSeconds` with an IANA timezone. Webhook create and rotate responses contain a one-time secret. Store that value immediately; webhook list/get and error responses omit it. Repeated webhook deliveries with the same delivery ID and body resolve to one durable delivery; changing the body for that ID conflicts.

[hosted-agent-profile.json](examples/hosted-agent-profile.json) shows the bounded OpenRouter-compatible agent configuration. It is disabled and contains no credential. Supply a `broker_api_key` as write-only profile credentials, then enable the profile. Provider choice, model name, credentials, turn limits, tool-profile allowlist, and output limits remain project configuration.

## Local user-owned worker

Local profiles use the portable runtime schema: an array of records with `id`, `name`, `activityKind`, `version: 1`, `type`, and `config`. Combine reviewed profiles and start the real polling worker:

```bash
RUNFABRIC_EXAMPLES=./node_modules/@coralbeat/workflows/examples
node -e 'const fs=require("fs");const files=process.argv.slice(1);process.stdout.write(JSON.stringify(files.map(f=>JSON.parse(fs.readFileSync(f,"utf8")))))' \
  "$RUNFABRIC_EXAMPLES/integrations/http-jsonplaceholder.json" \
  > ./runfabric-profiles.json
npx runfabric work start --profiles ./runfabric-profiles.json
```

Use `--once` for one polling pass, `--secrets FILE` for a local secret-name map, and `--worker-id ID` for stable operator attribution. The worker claims only matching activity kinds, renews its fenced lease while a handler runs, and stops on `SIGINT` or `SIGTERM`. CLI and stdio MCP profiles use profile-owned executable and argument arrays with `shell: false`; workflow input cannot choose an executable.

## TypeScript SDK

`RunfabricClient` defaults to the development API. `WorkflowClient` remains the portable class with an explicit `baseUrl`.

```ts
import { RunfabricClient } from "@coralbeat/workflows/runfabric";

const runfabric = new RunfabricClient({ apiKey: process.env.RUNFABRIC_API_KEY });
const workflow = await runfabric.getWorkflow("quickstart-http");
const run = await runfabric.startRun({
  workflowId: workflow.id,
  version: workflow.publishedVersions.at(-1)?.version,
  input: { id: 1 },
  idempotencyKey: crypto.randomUUID(),
});
```

SDK methods cover workflows, runs, project keys, integration profiles, schedules, webhooks, and redacted activity journal entries. Every remote call has a finite deadline and response-size bound and accepts an `AbortSignal`. First-party browser surfaces use `RunfabricBrowserClient({ projectId })`, which sends the session cookie and selected project header. Import pure validation from `@coralbeat/workflows/validation`, hosted integration execution from `@coralbeat/workflows/integrations`, and the Node-only local runtime from `@coralbeat/workflows/integrations/local`.

## MCP

The native stdio server uses the same stored CLI login or `RUNFABRIC_API_KEY`:

```bash
npx runfabric-mcp
```

Hosted MCP is at `https://dev.runfabric.dev/mcp`. Its OAuth protected-resource metadata is at `https://dev.runfabric.dev/.well-known/oauth-protected-resource/mcp`, and the exact OAuth resource is `https://dev.runfabric.dev/mcp`. The API resource is separately bound to `https://dev.runfabric.dev/v1`. OAuth scopes are `read`, `write`, `run`, `work`, `approve`, and `admin`; an access token for one resource is not accepted by the other.

MCP exposes the same workflow, run, profile, schedule, webhook, journal, and project-key operations through the exported `runfabricTools` list and `dispatchRunfabricTool`. Approval tools require `approve` or `admin`. There is no eval, arbitrary URL, arbitrary shell, signup, or billing tool.

## Self-host the engine

The portable origin needs Node 24, PostgreSQL 16, and `psql`:

```bash
cd products/workflows
npm install
docker compose up -d postgres
export DATABASE_URL=postgres://workflow_runtime@127.0.0.1:54329/workflows
export WORKFLOWS_ADMIN_TOKEN=local-bootstrap-admin
export PORT=8089
npm run build
npm run migrate
npm start
```

The ordered SQL history lives in `sql/`. Run migrations explicitly before starting an existing database. Project bootstrap is an origin-only administrative operation and is outside the public SDK. Query `/v1/schema` and `/v1/capabilities` rather than assuming limits.

## License

This standalone directory is licensed under Apache License 2.0. That does not relicense other Coralbeat2 code.
