metaflow-temporal
Run Metaflow flows as Temporal workflows.
metaflow-temporal generates a self-contained Temporal worker file from any Metaflow flow, letting
you schedule, deploy, and monitor your pipelines through Temporal while keeping all your existing
Metaflow code unchanged.
Install
pip install metaflow-temporal
Or from source:
git clone https://github.com/npow/metaflow-temporal.git
cd metaflow-temporal
pip install -e ".[dev]"
Quick start
python my_flow.py temporal create --output my_flow_worker.py
python my_flow_worker.py # start the worker
python my_flow_worker.py trigger # trigger a run
Usage
Generate and run a worker
python my_flow.py temporal create --output my_flow_worker.py
python my_flow_worker.py
All graph shapes are supported
# Linear
class SimpleFlow(FlowSpec):
@step
def start(self):
self.value = 42
self.next(self.end)
@step
def end(self): pass
# Split/join (branch)
class BranchFlow(FlowSpec):
@step
def start(self):
self.next(self.branch_a, self.branch_b)
...
# Foreach fan-out
class ForeachFlow(FlowSpec):
@step
def start(self):
self.items = ["alpha", "beta", "gamma"]
self.next(self.process, foreach="items")
...
Parametrised flows
Parameters defined with metaflow.Parameter are forwarded automatically:
python param_flow.py temporal create --output param_flow_worker.py
python param_flow_worker.py trigger greeting=world
Configuration
Metadata service and datastore
By default, metaflow-temporal uses whatever metadata and datastore backends are active in your
Metaflow environment. The generated worker file bakes in metadata_type and datastore_type
at creation time so every step subprocess uses the same backend.
To use a remote metadata service or object store, configure them before running temporal create:
# Remote metadata service + S3 datastore
python my_flow.py \
--metadata=service \
--datastore=s3 \
temporal create my_flow_worker.py
Or via environment variables (applied to all flows):
export METAFLOW_DEFAULT_METADATA=service
export METAFLOW_DEFAULT_DATASTORE=s3
python my_flow.py temporal create my_flow_worker.py
Compute backends and step decorators
Step decorators like @kubernetes, @batch, @conda, and third-party backends such as
@sandbox, @daytona, and @e2b are automatically forwarded to each step subprocess at
runtime. No extra configuration is needed — decorate your steps as usual:
from metaflow import FlowSpec, step, kubernetes
class MyFlow(FlowSpec):
@kubernetes(cpu=4, memory=8000)
@step
def train(self):
...
To inject a decorator at deploy time without modifying the flow source:
python my_flow.py --with=sandbox temporal create my_flow_worker.py
Scheduling
Decorate your flow with @schedule and the worker automatically registers a Temporal Schedule
when it starts. The schedule fires the workflow on the specified cron:
from metaflow import FlowSpec, schedule, step
@schedule(hourly=True) # or daily=True, weekly=True, cron="0 6 * * ? *"
class DailyFlow(FlowSpec):
@step
def start(self):
...
python daily_flow.py temporal create daily_flow_worker.py
python daily_flow_worker.py # registers the schedule and starts listening
Run tags
Tags are attached to every run produced by the worker:
python my_flow.py temporal create --output my_flow_worker.py --tag team:ml --tag env:prod
Project namespace isolation
Flows decorated with @project get full namespace isolation between branches. Different branches
use separate task queues and store runs under the project-aware name
(<project>.<branch>.<FlowName>), so user, staging, and production deployments never collide.
from metaflow import FlowSpec, project, step
@project(name="myproject")
class TrainFlow(FlowSpec):
...
# User branch (default): myproject.user.alice.TrainFlow
python train_flow.py temporal create --output train_worker.py
# Named test branch: myproject.test.staging.TrainFlow
python train_flow.py temporal create --output train_worker.py --branch staging
# Production: myproject.prod.TrainFlow
python train_flow.py temporal create --output train_worker.py --production
Project tags (project:myproject, project_branch:prod) are automatically added to every run.
Workflow execution timeout
Cap how long a single workflow execution may run before Temporal cancels it:
python my_flow.py temporal create --output my_flow_worker.py --workflow-timeout 7200
Saga Pattern (Automatic Compensation)
Long-running flows that touch external systems (hotel bookings, cloud provisioning, payment processing) need a way to undo completed work when a later step fails. The Saga pattern solves this: each compensatable step declares a compensation handler that runs automatically, in reverse order (LIFO), if the workflow fails.
Import step from the extension instead of Metaflow. It's a drop-in replacement that adds
an optional .compensate decorator for declaring compensation handlers inline:
from metaflow import FlowSpec
from metaflow_extensions.temporal.plugins.temporal import step # shadows metaflow.step
class BookingFlow(FlowSpec):
@step
def start(self):
self.next(self.book_hotel)
@step
def book_hotel(self):
self.hotel_id = reserve_hotel() # side effect
self.next(self.book_flight)
@book_hotel.compensate
def cancel_hotel(self):
cancel_reservation(self.hotel_id) # self.hotel_id injected from forward step
@step
def book_flight(self):
self.flight_id = reserve_flight() # side effect
self.next(self.confirm)
@book_flight.compensate
def cancel_flight(self):
cancel_reservation(self.flight_id)
@step
def confirm(self):
raise RuntimeError("Payment declined") # triggers compensations
@step
def end(self):
pass
When confirm raises, the workflow automatically runs cancel_flight then cancel_hotel
(reverse order), then re-raises the original error.
API
- Each compensatable step gets at most one
.compensatehandler — enforced structurally. - The handler receives the forward step's persisted artifacts as
self.<attr>. - The handler must not call
self.next(). - Non-saga steps use
@stepidentically to plain Metaflow — no change needed. - Compensation failures are logged but do not cascade (best-effort execution).
Compensations are tracked for all step types: linear steps, foreach body tasks, and parallel split/join branches.
Resume a failed run
If a workflow fails partway through, resume it from where it left off — completed steps are skipped and only unfinished steps re-execute:
python my_flow.py temporal resume temporal-myflow-abc123
The run ID is printed when you trigger a run or shown in the Temporal UI. The resumed run reuses the same Metaflow run ID so all artifacts remain under the same pathspec.
Graceful shutdown
The worker handles SIGTERM and SIGINT cleanly — in-progress activities finish before the process exits:
kill -TERM <worker_pid> # or Ctrl-C
# Worker shutting down gracefully.
Namespace
Forward a Metaflow namespace to every step subprocess:
python my_flow.py temporal create --output my_flow_worker.py --namespace production
Auto-trigger on upstream completion
Decorate your flow with @trigger_on_finish and the worker will poll for completed upstream
runs and automatically trigger this flow when one finishes:
from metaflow import FlowSpec, trigger_on_finish, step
@trigger_on_finish(flow="UpstreamFlow")
class DownstreamFlow(FlowSpec):
...
How it works
metaflow-temporal compiles your flow's DAG into a self-contained worker file. Each Metaflow
step becomes a Temporal Activity (@activity.defn). A single MetaflowWorkflow (@workflow.defn)
interprets the CONFIG dict to orchestrate them.
The worker file:
- runs each step as a subprocess via the standard
metaflow stepCLI - passes
--input-pathscorrectly for joins and foreach splits - records artifact names in each activity result with a ready-to-use retrieval snippet
Temporal UI: workflow list
Multiple Metaflow flows show up as MetaflowWorkflow executions, each labelled by flow name:
Temporal UI: run timeline
Foreach fan-outs and split/join branches appear as parallel Activity runs in the timeline:
Temporal UI: artifact names
After each step completes, the activity result contains the artifact names and a one-liner to fetch values from the Metaflow datastore:
# Fetch any artifact value — copy the snippet from the Temporal UI
import metaflow
task = metaflow.Task("ArtifactFlow/temporal-abc123/transform/temporal-transform-0")
value = task.data.doubled
Development
git clone https://github.com/npow/metaflow-temporal.git
cd metaflow-temporal
pip install -e ".[dev]"
pytest -v
For integration tests against a real Temporal server (in-process worker):
docker compose up -d
pytest -v -m integration
For true end-to-end tests (generates real worker files, starts them as subprocesses):
docker compose up -d
pytest -v -m e2e --timeout=300
The Temporal UI is available at http://localhost:8080.
License
Metadata
Release files for metaflow-temporal 0.4.9
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| metaflow_temporal-0.4.9.tar.gz | 51.7 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| metaflow_temporal-0.4.9-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 91.8 kB
Release files / metaflow_temporal-0.4.9.tar.gz
| Download URL | metaflow_temporal-0.4.9.tar.gz |
|---|---|
| Size | 51.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
98bf5747ae5d9d5d0d25bfe6f22e8ac5c0f9c9d9a3acd721f22cb98cb0837936
|
|
BLAKE2b-256 checksum How to use checksums |
ef9b35d2f7c03da00db557b8eede5bd4cb92bec7fc9044e131a1c54aaa37a5e9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
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 Mar 7, 2026.
Transparency logRelease files / metaflow_temporal-0.4.9-py3-none-any.whl
| Download URL | metaflow_temporal-0.4.9-py3-none-any.whl |
|---|---|
| Size | 40.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9747a9b2dedb87e70be2c3e81978ad9630f6dca7b8d0c2169fa27c0c1a108b1d
|
|
BLAKE2b-256 checksum How to use checksums |
6acc358f32a153cee2dfd5775f7710a435eb0dba73a0ec2983430f502baed0bf
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
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 Mar 7, 2026.
Transparency logSecurity for metaflow-temporal 0.4.9
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.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for metaflow_temporal-0.4.9.tar.gz
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 Mar 7, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.