Skip to main content

fancy-flow (Python)

The Python runtime for fancy-flow workflow graphs — the third twin of its headless TypeScript engine, alongside fancy-flow-php.

A graph an agent or human authors in <FlowEditor> runs unchanged on Python. Same JSON in, same outputs out. The editor stays the one authoring surface; Python becomes a peer runtime alongside Node and PHP.

Zero runtime dependencies. Everything the built-in nodes reach for — HTTP, an LLM, a vector store, a queue — is an injected protocol with a deterministic offline default, so a workflow app that never calls a model does not inherit a provider SDK, and every test runs without a network.

from fancy_flow import FlowRunner, RunOptions, builtin, import_workflow

builtin.register()  # install the built-in kinds
result = import_workflow(schema_json)  # WorkflowSchema v1

run = FlowRunner().run(
    result.graph,
    builtin.executors(),  # or your own bindings
    options=RunOptions(initial_inputs={"trigger-1": {"payload": body}}),
)

run.ok  # bool
run.outputs  # {node_id: value}
run.error  # str | None

import_workflow(..., lenient=True) softens an unknown kind to a warning. It never softens the schema version: a document without version: 1 is refused in every mode, with an empty graph and result.refused true, exactly as the TypeScript and PHP runtimes refuse it. Check result.refused before running, or an empty graph runs and reports success.

Async, without two engines

Executors may be synchronous or async. The graph walk is written once and driven two ways, so branching, skipping and port routing cannot drift between them.

run = await FlowRunner().arun(graph, executors)  # awaits awaitable executors

The synchronous runner refuses an awaitable rather than storing it: a coroutine object in outputs looks like success and reaches every downstream node as a value nothing can read.

Custom nodes

Two halves, kept in sync — exactly the path every built-in takes.

from fancy_flow import ConfigField, NodeKind, ExecutorRegistry, default_registry

default_registry().register(
    NodeKind(
        name="@acme/send_invoice",
        category="io",
        label="Send invoice",
        aliases=("send_invoice",),
        config_schema=(ConfigField(type="text", key="to", label="To", required=True),),
        side_effects="unsafe-to-replay",  # a durable run gives this ONE attempt
    )
)


def send_invoice(ctx):
    return {"sent": ctx.option("to")}


executors = builtin.executors().bind("@acme/send_invoice", send_invoice)

An executor may be a callable, an object with .execute(ctx), or a class resolved through your container.

Durable runs, with no queue library

Durability is checkpoint-per-node, keyed by node id. The core owns the hard part — which node may run, and with what inputs — and a queue supplies transport and nothing else.

from fancy_flow.durable import Coordinator

flow = Coordinator(graph=graph, executors=executors, run=run_id, store=store)

ready = flow.advance()  # what is unblocked right now -> dispatch these
flow.run_node(node_id)  # claim, run through the real engine, checkpoint
flow.run_to_completion()  # or drive both, here, in this process

advance() and run_node() are the two operations a Celery / Dramatiq / Taskiq job wraps. Coordinator.run_to_completion() over a persistent NodeClaimStore is already a real durable runner: a crash resumes from the same place a crashed worker would, because the resume behaviour lives in the checkpoints rather than in the loop.

Human gates fail closed: user_input and human_approval pause because they are human nodes, not because their input port happens to be empty. Only a recorded answer for that node resumes the run.

Accepting a graph you did not write

import_workflow answers is this graph coherent? GraphPolicy answers is it safe to accept? — kind allowlists (resolved across every id a kind answers to), size caps, byte hygiene, structure, and host rules.

from fancy_flow.security import GraphPolicy

GraphPolicy.untrusted(allow=["manual_trigger", "transform", "output"]).assert_safe(schema)

Parity

The guarantee is asserted, not asserted-to. The suite runs the shared shared/expr and shared/satisfies-range tables from fancy-conformance, the 23 golden WorkflowSchema fixtures, and — because a queued run derives readiness from the opposite end — every one of those fixtures a second time through the per-node durable driver.

python -m pip install -e . --group dev
pytest

Status

Pre-1.0: breaking changes land in minor releases. See CHANGELOG.md and, for how the package is built and what is staged next, AGENTS.md.

MIT.

Metadata

Release files for fancy-flow 0.20.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for fancy-flow 0.20.1
File Size Uploaded
fancy_flow-0.20.1.tar.gz 201.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for fancy-flow 0.20.1
File Interpreter ABI Platform
fancy_flow-0.20.1-py3-none-any.whl Python 3 none any Details

Total release size: 343.8 kB

Release files / fancy_flow-0.20.1.tar.gz

Download URL fancy_flow-0.20.1.tar.gz
Size 201.6 kB
Tags Source
SHA-256 checksum
How to use checksums
9ebc5ce5719cd5bd42ee9219d2773c5238accd3b2f00b8ce86a5db2114d78e9a
BLAKE2b-256 checksum
How to use checksums
bcda063a43db9a147b6a1cea487d1a5e6b629813107e7cd4dac5ec606602fd52
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 14, 2026.

Transparency log

Release files / fancy_flow-0.20.1-py3-none-any.whl

Download URL fancy_flow-0.20.1-py3-none-any.whl
Size 142.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2e0dd9118b4d7f313fc40b9e48c615a3c1790a6ffadf732e2f445252bb7f89cf
BLAKE2b-256 checksum
How to use checksums
433379222062d6b75f50ce3525773e081d6471882133daaa999eeddbbd528f7c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 14, 2026.

Transparency log

Release history Release notifications | RSS feed

0.29.0

2 release files

0.28.0

2 release files

0.27.1

2 release files

0.27.0

2 release files

0.26.1

2 release files

0.26.0

2 release files

0.25.0

2 release files

0.24.0

2 release files

0.23.1

2 release files

0.23.0

2 release files

0.22.2

2 release files

0.22.1

2 release files

0.22.0

2 release files

0.21.0

2 release files

0.20.2

2 release files

This release

0.20.1 This release

2 release files

0.20.0

2 release files

0.19.0

2 release files

0.16.1

2 release files

0.16.0

2 release files

0.15.0

2 release files

0.11.0

2 release files

0.10.0

2 release files

0.9.0

2 release files

0.8.0

2 release files

0.7.0

2 release files

0.6.1

2 release files

0.6.0

2 release files

0.5.0

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page