oleander-whirlwind
08/10/2026, 9:25 AMMarvin
08/10/2026, 9:25 AMMarvin
08/10/2026, 9:28 AMflow.serve() flow submit work and wait for the result?
Yes. A flow can submit a background task and then call .result() / wait on the returned future.
4. Is there a recommended native pattern for warm runtimes?
Yes: keep the flow lightweight and move the heavy imports / reusable state into the background task worker process.
5. Closest Prefect-native solution if you want zero re-imports everywhere?
The closest native solution is exactly what you described, but only for the heavy compute layer.
flow.serve() itself does not give you a permanently warm interpreter per flow run.
Important caveat
flow.serve() runs a long-lived runner process, but individual flow runs are still executed in isolated subprocesses. So:
- heavy imports inside the flow run process will still be re-imported per run
- heavy imports inside the background task worker process can stay warm across many task runs
So the architecture works if:
- Container A = orchestration only
- Container B = heavy compute with warm imports/state
That is the right mental model.
What this means for your proposed architecture
Your proposed setup:
- Container A: flow.serve() deployment receiver / orchestrator
- Container B: background task worker with xarray, zarr, rasterio, custom code loaded once
is the closest Prefect-native answer to “deployment-based orchestration + warm compute runtime + no per-request interpreter startup”.
What is supported well
- Prefect deployment triggers/schedules/webhooks start the flow
- the flow can submit background tasks
- the background task worker stays alive
- heavy libs can be imported once at worker startup
- results can be awaited from the flow
What is not changed by this pattern
- the flow run itself is still not a warm persistent interpreter
- if the orchestration flow imports heavy libraries too, you lose much of the benefit
So you should structure code so the flow imports only lightweight orchestration code, and the heavy libraries live only in the worker-side task module.
Recommended pattern
Something like this:
python
from prefect import flow, task
# Heavy imports should live in the worker process module
@task(persist_result=True)
def heavy_compute(payload):
import xarray
import zarr
import rasterio
# or initialize these once at module import time in the worker container
return {"status": "done", "payload": payload}
@flow
def orchestrator(payload):
future = heavy_compute.delay(payload)
result = future.result()
return result
if __name__ == "__main__":
orchestrator.serve(name="orchestrator")
And separately, in the worker container, serve the task:
python
from prefect.task_worker import serve
from my_module import heavy_compute
if __name__ == "__main__":
serve(heavy_compute)
Even better for your use case, preload the heavy libraries at module import time in the worker container:
```python
import xarray
import zarr
import rasterio
from prefect import task
# Warm globals / caches initialized once when worker starts
ANALYTICS_CONTEXT = {"ready": True}
@task(persist_result=True)
def heavy_compute(payload):Marvin
08/10/2026, 9:28 AMflow.serve() for orchestration and TaskWorker / background tasks for separately executed long-lived work is supported.
2. Do Background Task Workers actually execute multiple task runs in the same long-lived Python interpreter?
Yes. That is the key property that makes them useful for warm runtimes.
3. Can a flow running from flow.serve() submit work to a Background Task Worker and synchronously wait for the result?
Yes. Submit with .delay() and then wait with .result().
4. Are there any Prefect-native recommended patterns for combining these pieces?
Yes: lightweight orchestration flow + heavy background task worker. This is the cleanest native pattern if you want deployments plus warm compute.
5. If not supported, what is the closest Prefect-native solution?
It is supported, and the closest native solution is exactly:
- flow.serve() for deployment orchestration
- background task worker for warm computation
If you needed everything to stay warm, including the request/orchestration interpreter, Prefect alone does not make flow.serve() flow runs execute in the same persistent interpreter. In that case, the non-Prefect-native alternative would be:
- your own long-lived service (FastAPI/etc.)
- submit/track work with Prefect only where useful
Practical recommendation
For your case, I’d recommend:
- keep Container A minimal: parameter validation, orchestration, retries, state transitions
- move all xarray / zarr / rasterio / custom analytics imports into Container B
- preload worker-side global state at process start
- use persisted results so the flow can safely retrieve outputs
- test thread safety before increasing worker concurrency
Relevant Prefect docs / APIs
- Background tasks concepts: Prefect tasks concept docs
- Background task guide: Background tasks guide
- flow.serve() and serving flows: Run flows in local processes
- Python SDK reference root: Prefect Python API
If you want, I can next give you a concrete two-container reference implementation for this pattern, including:
- orchestrator flow container
- task worker container
- result persistence config
- Dockerfiles
- a minimal end-to-end example with .delay() and .result()oleander-whirlwind
08/10/2026, 9:32 AMMarvin
08/10/2026, 9:32 AMMarvin
08/10/2026, 9:33 AMpython
from prefect.task_worker import serve
You can also use:
python
from prefect.task_worker import TaskWorker
but serve(...) is the direct supported helper.
2. Exact syntax to serve a task worker
Yes:
python
serve(my_task)
or with options:
python
serve(my_task, limit=2, status_server_port=8081)
3. Exact syntax to submit background work from a flow
Yes:
python
future = my_task.delay(...)
4. Return type from .delay()
PrefectDistributedFuture
5. How to synchronously wait and get the result
Yes:
python
result = future.result()
Optional timeout:
python
result = future.result(timeout=300)
6. Can persisted task results be retrieved through the returned future?
Yes. That is the normal pattern. Use persist_result=True on the task, then call future.result().
---
Verified APIs used below
- from prefect import flow, task
- from prefect.task_worker import serve
- future = some_task.delay(...)
- result = future.result(timeout=...)
- my_flow.serve(name="...")
Relevant docs:
- Background tasks
- Tasks
- Run flows in local processes
- `prefect.task_worker` API
---
Important note before the files
Your requested RemoteFileSystem object is a Prefect filesystem block class. Loading it once at module import time is fine if you already have the block saved in Prefect and the worker can access the Prefect API.
To make the example executable without requiring pre-created block setup, I’m using this pattern:
- try to load a saved RemoteFileSystem block once
- if not found, continue with None
That keeps the files executable while still demonstrating the exact warm-start architecture.
---
File: tasks.py
```python
from future import annotations
import os
import threading
from typing import Any
from prefect import task
from prefect.filesystems import RemoteFileSystem
# Heavy libraries loaded exactly once when this module is imported
import xarray as xr
import zarr
import rasterio
# Module-level warm cache / singleton-style state
_CACHE_LOCK = threading.Lock()
_CACHE: dict[str, Any] = {}
# Load credentials once at import time
WORKER_CREDENTIALS = {
"api_token": os.getenv("EXAMPLE_API_TOKEN", "not-set"),
"data_root": os.getenv("EXAMPLE_DATA_ROOT", "/tmp/prefect-data"),
}
# Attempt to load a Prefect RemoteFileSystem block once at import time.
# To use a real block, create one in advance and set:
# PREFECT_REMOTE_FILESYSTEM_BLOCK=document/my-remote-filesystem
#
# Example expected value for the env var above:
# my-remote-filesystem
#
# This keeps the example executable even if no block exists yet.
REMOTE_FILESYSTEM_BLOCK_NAME = os.getenv("PREFECT_REMOTE_FILESYSTEM_BLOCK")
try:
if REMOTE_FILESYSTEM_BLOCK_NAME:
REMOTE_FS = RemoteFileSystem.load(REMOTE_FILESYSTEM_BLOCK_NAME)
else:
REMOTE_FS = None
except Exception:
REMOTE_FS = None
def _initialize_warm_state() -> None:
with _CACHE_LOCK:
if _CACHE.get("initialized"):
return
_CACHE["initialized"] = True
_CACHE["xarray_version"] = xr.version
_CACHE["zarr_version"] = zarr.version
_CACHE["rasterio_version"] = rasterio.version
_CACHE["credentials_loaded"] = True
_CACHE["remote_fs_loaded"] = REMOTE_FS is not None
_CACHE["request_count"] = 0
_initialize_warm_state()
@task(
name="warm-heavy-compute",
persist_result=True,
log_prints=True,
)Marvin
08/10/2026, 9:33 AM---
*File: `worker.py`*
```python
from prefect.task_worker import serve
from tasks import warm_heavy_compute
if __name__ == "__main__":
serve(
warm_heavy_compute,
limit=2,
status_server_port=8081,
)
---
File: orchestrator.py
python
from __future__ import annotations
from prefect import flow
from tasks import warm_heavy_compute
@flow(name="background-task-orchestrator", log_prints=True)
def orchestrate(dataset_name: str = "demo-dataset", value: int = 10) -> dict:
future = warm_heavy_compute.delay(dataset_name, value)
result = future.result(timeout=300)
print(f"Received result from background task: {result}")
return result
if __name__ == "__main__":
orchestrate.serve(
name="background-task-orchestrator-deployment",
)
---
How to run this example
1. Start your Prefect API/server or connect to Prefect Cloud.
2. Ensure both containers/processes point at the same Prefect API and same result storage settings.
3. Start the background task worker:
bash
python worker.py
4. Start the flow serve process:
bash
python orchestrator.py
5. Trigger the deployment from the UI or CLI.
If you want to trigger from CLI, verify command help first:
- deployment CLI docs
In practice you'd run something like:
bash
prefect deployment run 'background-task-orchestrator/background-task-orchestrator-deployment' --param dataset_name=test --param value=5
---
What proves the warm runtime is working
The key field is:
python
"request_count_seen_by_worker"
If the same worker process handles multiple requests, this value increases across executions without resetting, which demonstrates that:
- imports were not re-done in a fresh interpreter
- module globals persisted
- the worker process stayed warm
---
API summary
- Task definition:
python
from prefect import task
- Background task worker startup:
python
from prefect.task_worker import serve
serve(my_task)
- Submit from flow:
python
future = my_task.delay(...)
- Returned type:
PrefectDistributedFuture
- Wait for completion:
python
future.wait(timeout=300)
- Get result:
python
result = future.result(timeout=300)
---
If you want, I can next provide the matching Dockerfiles and environment variables for Container A and Container B so this exact example runs as a two-container setup.oleander-whirlwind
08/10/2026, 9:42 AMMarvin
08/10/2026, 9:42 AMMarvin
08/10/2026, 9:42 AMpython
from prefect.task_worker import serve
from tasks import heavy_task
if __name__ == "__main__":
serve(
heavy_task,
limit=2,
status_server_port=8081,
)
tasks.py
python
from __future__ import annotations
from prefect import task
@task(
name="heavy-task",
persist_result=True,
log_prints=True,
)
def heavy_task(x: int, y: int) -> dict:
result = x + y
print(f"Computed {x} + {y} = {result}")
return {
"x": x,
"y": y,
"sum": result,
}
def submit_heavy_task(x: int, y: int):
future = heavy_task.delay(x, y)
return future
orchestrator.py
python
from __future__ import annotations
from prefect import flow
from tasks import heavy_task
@flow(name="orchestrator", log_prints=True)
def orchestrator(x: int = 1, y: int = 2) -> dict:
future = heavy_task.delay(x, y)
result = future.result(timeout=300)
print(f"Received result: {result}")
return result
if __name__ == "__main__":
orchestrator.serve(
name="orchestrator-deployment",
)
What this uses
- Background task worker startup:
python
from prefect.task_worker import serve
- Background task definition:
python
from prefect import task
- Submit work:
python
future = heavy_task.delay(x, y)
- Wait for result:
python
result = future.result(timeout=300)
- Serve the flow:
python
orchestrator.serve(name="orchestrator-deployment")
Docs
- Background tasks
- Serving flows
- `prefect.task_worker` API
If you want, I can also give you the exact commands to run these three files together in a local Prefect 3.x setup.