Migrating from Temporal
A map from Temporal workflows, activities, and signals to Flux — with an honest acknowledgment that this is usually the wrong direction.
This page should open with a warning. Temporal is the most mature durable-execution engine in production today, with multi-language SDKs, replay testing, and a managed cloud offering. Moving from Temporal to Flux is usually the wrong call. If you arrived at this page expecting a green light, take a beat and read vs. Temporal first.
The narrow case where the move makes sense: a Python-only team, building AI agents or workflows that need to be callable by AI agents, who would benefit from a much smaller operational surface and is willing to trade Temporal’s replay-test maturity and multi-language support to get there. If that is not you, stay on Temporal.
For teams who do fit that profile, the translation below makes the move concrete.
Mental-model translation
| Temporal | Flux | Notes |
|---|---|---|
| Workflow | @workflow | Both are deterministic functions over an event log. |
| Activity | @task | Both perform I/O outside the workflow body. |
ActivityOptions | @task.with_options(...) | The option surface is similar in spirit, not identical in shape. |
| Signal | pause() + flux workflow resume | The resume payload is the signal payload. |
| Query | flux execution show <id> --detailed or REST /executions/{id} | Read-only inspection of recorded state. |
| Continue-As-New | Not a first-class primitive in Flux 0.56.0 | Long-running loops have to be modeled differently. |
| Task queues | Worker affinity via @workflow.with_options(requires=...) | Conceptually similar, different mechanism. |
| Workflow IDs | Execution IDs | Generated server-side; one workflow can have many executions. |
GetVersion | Auto-incrementing catalog version | Less expressive — no in-workflow branching. |
| Replay tests | None in Flux 0.56.0 | Flux’s replay path runs at execution time, not test time. |
| Temporal Cloud | None | Self-host only. |
Code-pattern translation
A workflow with an activity.
# Temporal — Python SDK
from temporalio import workflow, activity
@activity.defn
async def fetch_url(url: str) -> str:
async with httpx.AsyncClient() as c:
return (await c.get(url)).text
@workflow.defn
class FetchWorkflow:
@workflow.run
async def run(self, url: str) -> str:
return await workflow.execute_activity(
fetch_url,
url,
start_to_close_timeout=timedelta(seconds=30),
retry_policy=RetryPolicy(maximum_attempts=3),
)
# Flux
from flux import ExecutionContext
from flux.task import task
from flux.workflow import workflow
@task.with_options(timeout=30, retry_max_attempts=3)
async def fetch_url(url: str) -> str:
async with httpx.AsyncClient() as client:
response = await client.get(url)
return response.text
@workflow
async def fetch_workflow(ctx: ExecutionContext[str]):
return await fetch_url(ctx.input)
The workflow is a function, not a class with a run method. Activity options become task options, declared on the task rather than passed at the call site. Retry policy moves from a RetryPolicy object to keyword arguments on with_options.
A workflow waiting for a signal.
# Temporal
@workflow.defn
class ApprovalWorkflow:
def __init__(self):
self.approved = False
self.decision = None
@workflow.signal
def submit_decision(self, decision: str):
self.decision = decision
self.approved = True
@workflow.run
async def run(self, request: dict) -> str:
await workflow.wait_condition(lambda: self.approved)
return await workflow.execute_activity(
apply_decision, request, self.decision,
start_to_close_timeout=timedelta(seconds=30),
)
# Flux
from flux.tasks import pause
@workflow
async def approval_workflow(ctx: ExecutionContext[dict]):
decision = await pause(name="awaiting_approval")
return await apply_decision(ctx.input, decision)
Signals collapse into a single pause(name=...) call. The workflow halts at pause, the server persists it as PAUSED, and flux workflow resume approval_workflow <execution_id> '"approved"' injects the resume value (here, the string "approved") as the return of pause. There is no signal handler method; the resume payload is the signal payload.
Inspecting a running workflow.
# Temporal — query the workflow
temporal workflow query --workflow-id w-123 --type get_status
# Flux — read the event log
flux execution show <execution_id> --detailed
The Temporal pattern lets you compute a derived value from inside the workflow body. The Flux pattern reads the raw event log, which is sufficient for the common cases (“what step is it on, what was the last input/output, when did it pause”) but does not let you run arbitrary code inside the workflow process to compute a query response. If your operational tooling depends on rich queries, that is a real gap.
Continuing as new.
# Temporal
if len(events) > 10_000:
workflow.continue_as_new(args=[next_cursor])
# Flux — no direct equivalent in 0.56.0
# Options:
# 1. Split the workflow at a natural boundary and call the next instance with
# flux workflow run from inside a task.
# 2. Use a schedule that runs short, idempotent passes against a cursor stored
# in your own datastore.
# 3. Accept a larger event log if the volume is bounded.
This is one of the cleaner reasons to stay on Temporal. If your workflow is a long-lived loop that needs to garbage-collect history, Temporal’s continue_as_new is purpose-built for it and Flux 0.56.0 is not.
What you give up
Multi-language SDKs. Temporal supports Go, Java, TypeScript, Python, .NET, PHP, and Ruby. Flux 0.56.0 is Python only. If your platform has even one service written in another language that needs to be a workflow or an activity, Temporal is the only of the two that works.
Replay testing. Temporal’s replay-history workflow replayer is mature: you record a history, point a new build of the workflow code at it, and the SDK fails the test if your changes would have produced different decisions. It is the industry’s most-developed answer to “I changed the workflow code, will it break in-flight executions?” Flux has nothing equivalent in 0.56.0. Workflow changes against in-flight executions get found at replay time, not at test time.
Temporal Cloud. The managed offering removes the cluster-operator burden — namespace, point workers at it, pay per action. Flux has no managed service today. Self-hosting is the only option, and while the operational surface is much smaller than Temporal’s, it is not zero.
Production maturity. Temporal has been running large workflow estates at Uber, Stripe, Coinbase, Snap, and Netflix for years. Flux 0.56.0 is much younger software with a much smaller production track record. If you are operating at the scale Temporal was built for, that maturity gap is the deciding factor, not the feature surface.
The expressive ceiling. GetVersion plus build IDs gives you fine-grained control over in-flight versioning. continue_as_new gives you unbounded long-lived workflows. The visibility API plus Elasticsearch gives you indexed search across workflow types and custom attributes. Each of these has a workaround in Flux, none has a peer.
Why migrate to Flux from Temporal
Two reasons hold up under scrutiny:
You are Python-only and want a smaller surface to operate. Temporal Server is four internal services (frontend, history, matching, worker) plus a durable store (Cassandra, MySQL, or Postgres) plus Elasticsearch for visibility. Flux is one FastAPI process plus Postgres. If you do not need the throughput ceiling the Temporal decomposition gives you, you are paying for complexity you don’t use. A Python-only team running modest workflow volume can run Flux on a single VM plus a managed Postgres and be done.
You are building AI agents or workflows callable by AI agents. Flux ships a first-class agent layer (flux.agents, flux.tasks.ai) where agents are workflows with an LLM orchestrator; every LLM call and tool dispatch is recorded as a durable event on the same event log as the surrounding workflow. The MCP server (flux start mcp) exposes registered workflows as MCP tools, so an external agent can call your workflows by name. Temporal does not ship this. You can build it on top of Temporal, but you are building it.
If neither reason fits, do not migrate. The cost will be higher than the benefit.
Migration order
Only if you have a clear reason.
Port one workflow with a single activity first. Get comfortable with the option-surface differences and the absence of replay tests. Verify the durability properties against your actual failure modes — kill a worker mid-run, see the next worker resume, inspect the event log against your mental model. Two weeks of running the Flux version next to the Temporal version against the same inputs is the minimum credible bar.
If that pilot survives, port a workflow with a signal next. The pause/resume mapping is the largest mental-model jump from Temporal because the signal handler disappears — you must accept that the resume payload is the only thing that comes back.
Then stop and reassess. Three workflows ported is enough data to decide whether to continue or to declare the migration a failed experiment and roll back. Do not port your whole estate before you know.
Where to read more
- Concepts — vs. Temporal — the comparative analysis behind this guide.
- Build — Pause and resume — the signal equivalent.
- Concepts — Determinism — the rules workflow bodies must follow in Flux, similar in spirit to Temporal’s.
- Agents — The agent primitive — the first-class agent layer that is one of the only good reasons to migrate.
Compared against Temporal Server 1.24 and Temporal Cloud, as of July 2026. Flux 0.56.0.