Visual workflow orchestration on Ray
Project description
Rayflow
Build distributed systems by drawing them: connect nodes, switch them on, and they stay running.
Rayflow is a visual editor for building backends on top of Ray. You connect nodes — each one a piece of Python logic — with two kinds of wire: one sets the order of execution, the other carries data. Load the graph and its nodes come up as live processes that stay available — not a task that runs once and exits: call them like an API, trigger them with events, or let them keep state between calls, a service you designed by drawing it.
Status: early (alpha). The execution model and the editor are functional; the API may change.
Two ways to build a flow
- Draw it — open the visual editor, wire nodes together, run it. See Quickstart below.
- Have an agent build it — install the MCP tools (
rayflow install claude-tools) and let an LLM agent like Claude Code design, validate, and test the flow for you, talking to the same API the editor uses. See Path B: an LLM agent, via MCP below.
Both paths produce the exact same flow JSON, running on the exact same engine — pick whichever fits how you work today.
Installation
pip install rayflow
Requires Python 3.10+. Ray, FastAPI, Uvicorn, and FastMCP are installed as dependencies.
Quickstart
Launch the server (visual editor + REST API + MCP):
rayflow serve --port 8000
- Visual editor: http://localhost:8000/editor
- REST API: http://localhost:8000/flows
- Health check: http://localhost:8000/health
Building an actual flow — by hand in Python, or by having an agent do it — is covered next.
Building a flow — two paths
Path A: the Python API
A flow's inputs live on its entry node — there's no flow-level inputs
field, so a flow that wants named typed inputs (like a/b below, as
opposed to the raw HTTP envelope OnStart gives you) declares its own
@entry_node:
import rayflow
from rayflow.nodes.decorators import entry_node, Input, ExecOutput
from rayflow.nodes.registry import get_catalog
@entry_node
class SumaEntry:
a = Input("int", default=0)
b = Input("int", default=0)
exec_out = ExecOutput()
get_catalog().register(SumaEntry)
# A flow is plain data — here defined inline.
suma = {
"name": "suma",
"outputs": {"result": "int"},
"nodes": [
{"id": "entry", "type": "SumaEntry"},
{"id": "add", "type": "Add", "exec_in": "entry",
"inputs": {"a": "entry.a", "b": "entry.b"}},
{"id": "exit", "type": "FlowOutput", "exec_in": "add",
"inputs": {"result": "add.result"}},
],
}
rayflow.load(suma)
for event in rayflow.execute("suma", {"a": 3, "b": 4}):
if event["event"] == "flow_done":
print(event["result"]) # {"result": 7}
rayflow.unload("suma")
Path B: an LLM agent, via MCP
Rayflow ships an MCP server alongside the REST API, so an LLM agent (Claude Code or any other MCP client) can design, validate, save, and run flows through the same operations the visual editor uses — no hand-written flow JSON, no separate SDK.
Install the agent-facing tooling into your project:
rayflow install claude-tools
This drops into your working directory:
.mcp.json— registers Rayflow's MCP server (served at/mcpbyrayflow serve), so an MCP-aware client picks it up automatically.- Two Claude Code skills (
.claude/skills/):rayflow-flow(design, validate, and test a flow end-to-end) andrayflow-node(create or edit a custom node). Both bundle real, runnable scripts — e.g.validate_flow.pyandbatch_test.py— so an agent can iterate locally against the same validation logic the server uses, instead of paying a network round-trip per attempt. - A
rayflow-debuggersubagent for tracing a flow node-by-node when a run gives the wrong result and it's not obvious which node is at fault.
With the server running (rayflow serve --port 8000), an agent has tools
like get_guide, list_nodes/get_node, validate_flow, create_flow/
update_flow, run_flow/test_flow, and the custom-node equivalents
(create_custom_node, update_custom_node_source, hot-reloaded — no
restart needed). A typical exchange:
You: "Build a flow that adds two numbers and doubles the result."
Agent: reads
get_guideandlist_nodes, drafts a flow wiringAdd→Multiply, callsvalidate_flowuntil it reportsvalid: true, thencreate_flowto save it andtest_flowto confirm the output — all without you writing or reading a line of flow JSON.
This is the same node catalog, the same validation, and the same execution engine the visual editor drives — a flow built by an agent opens in the editor exactly like one built by hand, and vice versa.
How a flow is triggered
The same graph can be started in several ways:
- Programmatically —
load()once,execute()any number of times (each call streamsnode_start/node_done/flow_doneevents and returns the flow's outputs inflow_done),unload()when done. - Served as an API —
load()once andexecute()many times; exposed over HTTP with event streaming. The system stays resident and is invoked repeatedly. - With a built-in UI — use the
ChatTriggerentry node (or any custom entry node declaringfrontend) andrayflow serve --file flow.jsonmounts a static web UI atGET /flows/{name}/ui. The page talks to the flow over the same/flows/{name}/runendpoint — no separate transport. Useful for chat-style flows, forms, or any flow that wants a human-facing surface without writing a frontend app. - By event —
serve_events()plus anOnEventnode: the flow stays resident and is triggered by an event, with no direct call. - As a component of another flow — the
CallFlownode runs one flow inside another.
Across all of these, a flow can be stateless or stateful — with or without memory between invocations (graph variables and state in resident nodes). With EmitEvent, a flow can also trigger others and chain reactions.
Concepts
A flow is a graph of nodes connected by two kinds of pin:
- Exec pins — define the order of execution (control).
- Data pins — carry values between nodes.
Each node is a decorated Python class. The decorator decides where it runs, not what it does:
from rayflow.nodes.decorators import engine_node, ExecInput, ExecOutput, Input, Output, ExecContext
@engine_node # runs locally inside the engine
class Add:
exec_in = ExecInput()
a = Input("int", default=0)
b = Input("int", default=0)
result = Output("int")
exec_out = ExecOutput()
async def run(self, ctx: ExecContext, a: int, b: int) -> None:
ctx.set_output("result", a + b)
await ctx.fire("exec_out")
@engine_noderuns locally, inside the engine (best for lightweight control logic).@ray_noderuns distributed on Ray (with exec pins it is a persistent, stateful actor).
The run() contract is identical for both — only the deployment differs.
A flow is stored as JSON — see the suma flow in Path A
above for a complete example.
Python API
import rayflow
rayflow.load(source) # pre-validate, spawn actors, register as served
rayflow.execute(name, inputs) # run an already-loaded flow (event stream)
rayflow.unload(name) # unload the flow
rayflow.serve_events(source) # keep the flow resident, subscribed to the event bus
rayflow.stop(graph_id, event_names) # unsubscribe and unload
Documentation
- Agent-facing skills and MCP tooling:
rayflow/claude_tools/— installed into your project viarayflow install claude-tools(see Path B above). - Contributing guide:
CONTRIBUTING.md
Development
pip install -e ".[dev]"
pytest tests/
The editor frontend (React + Vite) lives in rayflow/editor/frontend/:
cd rayflow/editor/frontend
npm install
npm run build
See CONTRIBUTING.md for the full workflow and the
Contributor License Agreement.
License
Rayflow is dual-licensed:
- Open source: GNU AGPL-3.0-or-later. Free to use, modify, and distribute under the AGPL's terms (note its network-use copyleft).
- Commercial: for use without the AGPL obligations (e.g. embedding Rayflow
in a closed-source or hosted product), a separate commercial license is
required. See
COMMERCIAL-LICENSE.md.
Personal, educational, research, and non-profit use is fully covered by the AGPL at no cost. For commercial terms, contact Manuel Alejandro Cuartas (alejandro.cuartas@yahoo.com).
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file rayflow-1.1.0.tar.gz.
File metadata
- Download URL: rayflow-1.1.0.tar.gz
- Upload date:
- Size: 493.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
aa80291053dbd759c57aca98a86da5ffb1f7963d225072ab612a412d5be2950e
|
|
| MD5 |
2475e0403dbdbf064f3e67da297fda24
|
|
| BLAKE2b-256 |
98bb83a878f793a63dea4a2019bd7047d642dbb4ddb1620ce816a3facf9e91ee
|
Provenance
The following attestation bundles were made for rayflow-1.1.0.tar.gz:
Publisher:
pypi.yaml on alejoair/rayflow
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rayflow-1.1.0.tar.gz -
Subject digest:
aa80291053dbd759c57aca98a86da5ffb1f7963d225072ab612a412d5be2950e - Sigstore transparency entry: 2081505020
- Sigstore integration time:
-
Permalink:
alejoair/rayflow@cad22014dac72b62fda4f63f0eb62bfd557f0698 -
Branch / Tag:
refs/tags/1.1.0 - Owner: https://github.com/alejoair
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi.yaml@cad22014dac72b62fda4f63f0eb62bfd557f0698 -
Trigger Event:
release
-
Statement type:
File details
Details for the file rayflow-1.1.0-py3-none-any.whl.
File metadata
- Download URL: rayflow-1.1.0-py3-none-any.whl
- Upload date:
- Size: 473.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
37688d35bd4b43b760a29a0be62236cf30bf392ede2bbba7b444597afc29f472
|
|
| MD5 |
b2a00045b500b99ece46e62ec5be3208
|
|
| BLAKE2b-256 |
57b1d218af5e4886fb0a02e88b0544a90e102a3c704ef75e704c3808bd7df51a
|
Provenance
The following attestation bundles were made for rayflow-1.1.0-py3-none-any.whl:
Publisher:
pypi.yaml on alejoair/rayflow
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rayflow-1.1.0-py3-none-any.whl -
Subject digest:
37688d35bd4b43b760a29a0be62236cf30bf392ede2bbba7b444597afc29f472 - Sigstore transparency entry: 2081505044
- Sigstore integration time:
-
Permalink:
alejoair/rayflow@cad22014dac72b62fda4f63f0eb62bfd557f0698 -
Branch / Tag:
refs/tags/1.1.0 - Owner: https://github.com/alejoair
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi.yaml@cad22014dac72b62fda4f63f0eb62bfd557f0698 -
Trigger Event:
release
-
Statement type: