Compatibility matrix¶
For a Prefect-centric map of features, see Prefect → IronFlow.
Compatibility Matrix¶
This document tracks compatibility targets against Prefect OSS.
Maintainers should use docs/compatibility_review_workflow.md before changing this matrix or choosing a new Prefect-alignment feature. The workflow keeps upstream comparison, gap selection, documentation, and implementation decisions tied together.
Baseline¶
- Upstream project:
prefecthq/prefect(self-hosted OSS context) - Baseline major/minor:
3.x - Initial validation target:
3.0.0
Python versions & PyPI (ironflow-prefect-compat)¶
requires-python:>=3.11(seepython-shim/pyproject.toml).- Prebuilt wheels: CI publishes manylinux (x86_64 + aarch64), Windows (
win_amd64), and macOS wheels for CPython 3.11 and 3.12. Confirm exact filenames on PyPI → Download files. - Other CPython versions (for example 3.13): may resolve to sdist or fail until wheels exist — build from a full checkout with
cargo buildand/or setIRONFLOW_RUST_LIBper the hosted Installation guide (see alsodocs/INSTALL.mdon GitHub).
Phase 1 runtime compatibility (current MVP target)¶
- Supported:
@flowand@taskdecorated functions (compatibility shim).task.submit()dependency chains; withThreadPoolTaskRunner(default), independent submits return immediately and run concurrently (bodies in a shared thread pool).SequentialTaskRunnerkeeps submit non-overlapping.ProcessPoolTaskRunner:submit()remains synchronous; usemap()for process concurrency.task.map()with moderate fan-out.@task(name=...)custom task names (runtime + static forecast when tasks are module-level or flow-closure visible).- retries / timeouts / cancellation intent propagation.
- Deployment concurrency (subset): per-deployment
concurrency_limitwith collision strategiesENQUEUE/CANCEL_NEW, enforced on the deployment-run claim / trigger path (Rust-preferred whenbind_dbis active). This caps concurrent deployment runs for one deployment — not the same as named global/tag slots below. - Global concurrency limits (subset): named slot ledger in SQLite (Rust-preferred); CRUD via control plane + HTTP
/api/concurrency-limits; syncconcurrency(...)context manager (occupy,strict,timeout_seconds, leases) andrate_limit(...)whenslot_decay_per_secondis set. Soft-missing defaults match Prefect (warn + proceed unlessstrict=True). Seedocs/how-to/concurrency-limits.md. - Tag-based concurrency limits (subset):
@task(tags=...); limits namedtag:{tag}(viacreate_tag_concurrency_limitor GCL CRUD); AND across tags on enterRunning; limit0aborts (CANCELLED+ error). Tag wait poll:IRONFLOW_TASK_TAG_SLOT_WAIT_SECONDS. Thread/processmapacquires per worker so fan-out respects the cap. - State transition hooks (IronFlow extension, not Prefect API names): pass
transition_hooks=to@flow/@taskas a sequence ofTransitionHookSpecfromon_transition(fn, from_state=..., to_state=...).Noneforfrom_stateorto_stateis a wildcard. Hooks run synchronously in-process after each successful control-plane transition (including the two edges produced by the batchedPENDING/RUNNINGstart path), without holding the control-plane lock. User hook bodies may block arbitrarily; IronFlow only guarantees low overhead when no hooks are registered. Hook exceptions are logged and do not fail the run. Prefect’s separateon_running/on_failure/ … style maps to explicit edges (e.g.PENDING→RUNNING, any→FAILED). - Deployment schedules (subset): interval schedules (
schedule_interval_seconds+schedule_next_run_at+schedule_enabled), optional cron schedules (schedule_cron), and a Rust-preferred RRule subset (schedule_rrule). Schedule types are mutually exclusive in deployment state. The RRule subset supportsFREQ=MINUTELY|HOURLY|DAILY|WEEKLY, optional positiveINTERVAL, and optionalUNTIL;COUNTand advanced calendar filters are intentionally unsupported for now. Comparisons use RFC3339 timestamps in UTC. When the nativerust-enginelibrary is loaded withbind_db, schedule ticks run in Rust (deployment_ops); the compat server prefers a Rust background scheduler thread and Rust-backed blocking claim waits when available. Python fallbacks cover interval and simple RRule schedules; cron schedules require the Rust path to compute ticks unlessschedule_next_run_atis explicitly managed externally. - CLI / YAML deploy (Tier 1 subset):
ironflow init,ironflow deploy,ironflow serve, andironflow worker start; manifest fileironflow.yaml(ironflow-version, optionalpull,deployments[]withentrypointorflow_name,parameters,work_pool.name,schedule). Pythonprefect_compat.deploy.deploy()andserve()mirror CLI upsert + worker behavior. Tier 1 pull steps:ironflow.deployments.steps.set_working_directoryonly. Standalone workers shareIRONFLOW_HISTORY_PATHwith the API (IRONFLOW_ENABLE_LOCAL_WORKER=0on the server). Not Prefectprefect deploy, blocks, or full work-pool/recipe parity — seedocs/how-to/deploy-with-cli.md. - Self-hosted basic auth (subset): optional
IRONFLOW_SERVER_API_AUTH_STRING/IRONFLOW_API_AUTH_STRINGHTTP Basic auth on/api/*(Prefect OSS-shaped; no RBAC). Seedocs/how-to/secure-self-hosted.md. - Server Docker image (Tier A):
deploy/docker/Dockerfile.server— single-container API with embedded worker/scheduler; seedocs/how-to/docker-quickstart.mdanddeploy/docker/README.md(PyPI wheel + uvicorn runtime). - Persistence backends (Tier B1 subset): default SQLite sidecar (file/
IRONFLOW_HISTORY_PATH); optional Postgres viaIRONFLOW_DATABASE_URL. Claim/lease hot paths bind in Rust for both backends; schedule ticks and most CRUD fall back to Python on Postgres until follow-ups. Seedocs/how-to/database-postgres.mdanddocs/plans/self-hosted-storage-rfc.md. - HTTP workers (Tier B2 subset):
IRONFLOW_WORKER_MODE=http+POST /api/workers/claim/…/runs/{id}/started|finishedso workers never open the control-plane DB (Prefect-shaped multi-host). Default remainsfile(shared history path). Seedocs/how-to/worker-http-mode.md. - Docker Compose + services (Tier B3/B5 subset, shipped):
deploy/docker/compose.yml— Postgres + API (ENABLE_SCHEDULER/LOCAL_WORKER=0) +ironflow server services start+ HTTP worker; GHAdocker-compose-smoke.yml. HA multi-services leader election and Redis deferred. Seedocs/how-to/docker-compose.md(Prefect compose guide is the structural reference). - Subflows (subset): two mechanisms only — (1) blocking inline via direct
child_flow(...)from an active parent@flow(same process; linked child flow run withexecution_mode=inline, parent/root/depth metadata); (2) deployment-backed subflow as task viadeployment_ref(name_or_id).submit(**params)returningSubflowFuture(surrogate parent taskkind=subflow, child deployment run + child flow run linkage).wait_for=[subflow_future]andwait([...])gate downstream tasks; fire-and-forget requiressubmit(..., detach=True)(or@flow(final_state="explicit")) so the child is excluded from parent final-state aggregation. Nesting of either mechanism inside either mechanism is supported (depth capped, currently 32). Parent cancel propagates to active deployment-backed children. UI: DAG node kindsinline_subflow/subflow_task; parent run detail exposeschildren[]and child-run navigation. User guide:docs/how-to/subflows.md. - Flow-run final state (IronFlow extension): default
@flow(final_state="wait_all")waits for all non-detached task / gate / subflow children, then resolves the flow terminal state in Rust (resolve_flow_terminal_state:CANCELLED>FAILED> allCOMPLETED). Unobserved failed concurrentsubmits fail the flow (FlowChildrenFailed). Escape hatches:submit(..., detach=True)and@flow(final_state="explicit")(body return/exception remains authoritative). Not Prefect return-value /State-object finalization. - Temporal gate tasks (IronFlow extension):
gate(name=..., max_wait=...)inside an active@flow; call.submit(until=datetime | after=timedelta, wait_for=[...])to insert a zero-op barrier task (kind=gate, real task-run UUID) that blocks downstreamwait_foruntilopen_at. Defaultmax_waitsafeguard istimedelta(days=1)(Python definitional default; override per gate). While waiting, the flow run may enterPAUSED; gate promotion ticks prefer Rust (task_tick_gate_tasks/ bundled indeployment_maintenance) with Python fallback. UI: DAG node kindgate_taskwithgate_open_at. Not Prefect API parity — Prefect has no first-class in-flow calendar gate. - Task resume / result persist (subset): On flow-run resume (deployment retry sets
resume_from_flow_run_id, or in-processprepare_resume), IronFlow may skipCOMPLETEDDAG nodes keyed by(resume_lineage_id, planned_node_id, map_index, input_fingerprint)when (a) the prior return wasNone(auto marker), or (b)@task(persist_result=True)stored a JSON-safe payload (bool/int/float/str/list/dict, size-capped). Resume skips require matching flow/deployment parameters and matching JSON-safe submit/mapinputs; otherwise the node recomputes. Applies tosubmitandmap(thread/process included). Non-persisted non-Noneresults recompute. Cache hits advance task FSM events but do not re-firetransition_hooks. Fresh runs never auto-hit. UI shows persisted results on the Task Runs / Artifacts tabs. Not Prefectcache_policyparity; no cross-flow cache by default. User guide:docs/how-to/task-resume-and-persist.md. Design:docs/plans/task-result-cache.md. - Runtime context (subset):
get_run_context()→ frozenRunContext(flow_run_id,flow_name, optionaltask_run_id/task_name, deployment fields when claimed, bound flowparameters). RaisesMissingContextErroroutside an active@flow. - Run logging (subset):
get_run_logger()returns a stdlib logger that appends to control-plane log rows (GET /api/flow-runs/{id}/logs+ UI Logs tab) with flow/task association. Outside a run, messages go to stderr (not persisted).log_prints=is not yet supported. Process-isolated task workers do not inherit ContextVars for task-scoped logs. - Operator lifecycle control (subset):
POST /api/flow-runs/{id}/pauserequires explicitmode=drain|terminate(InterruptMode) fromSCHEDULED|PENDING|RUNNINGonly (not gate-onlyPAUSED);POST …/resumeresumes operator pauses only. Cancel recordslifecycle_action=cancel/interrupt_mode=terminate. Drain blocks new task starts and settles toPAUSEDwhen in-flight tasks finish; in-process@flow()bodies that try to submit after the hold raiseFlowRunSchedulingHeld(not markedFAILED). Terminate pause / cancel: underProcessPoolTaskRunner, in-flight task bodies run in registered child processes and are SIGTERM→grace→SIGKILL'd (IRONFLOW_TASK_TERMINATE_GRACE_SECONDS, default2); RUNNING rows flip toCANCELLEDfirst so lateCOMPLETEDis fenced; new task starts are held as soon as terminate lifecycle is written (not only afterPAUSEDsettles). Thread-pool bodies remain cooperative-only (may continue until exit). After terminate pause,resumeuses P1prepare_resume/ deploymentretryso COMPLETED persisted results skip and interrupted work re-runs; in-process prior runs are terminalizedCANCELLED(superseded_by_terminate_resume) rather than left zombieRUNNING. User guide:docs/how-to/cancel-pause-resume.md. Design:docs/plans/flow-run-lifecycle-control.md. - Not yet supported (open gaps — not parity claims):
- full API parity for every Prefect state rule edge case.
- Prefect
SubflowTask/run_deploymentname parity, automatic deployment creation from@flow, or subflow parameter schema validation beyond deployment defaults. - Opt-in cross-run result cache (Goal B); Goal A resume-within-lineage is supported above.
@flow(log_prints=True)/@task(log_prints=True)print capture.- Hard terminate under pure
ThreadPoolTaskRunner(CPython cannot kill threads); useProcessPoolTaskRunnerfor SIGTERM/SIGKILL. UI pause-mode chooser still open (P3.2e). - UI pause chooser / lifecycle badges; CLI pause helpers.
- User-facing artifacts API (
create_markdown/ tables); internal result artifact rows + GET APIs only. - Variables JSON store; settings/profiles module.
- Events → automations / webhooks engine (events + SSE exist; no trigger actions).
- Async
concurrency/rate_limithelpers, CLIironflow gclparity, UI concurrency admin page, work-queue / work-pool concurrency. - Advanced RRule calendar filters /
COUNT. - Deliberate park / non-goals (do not treat as near-term backlog):
- advanced cloud/tenant features (RBAC, SSO, workspaces).
- all blocks, secrets managers, and integration packs.
task.delay()background tasks; Dask/Ray runners; non-process work pool types.- human-in-the-loop pause/input forms (temporal
gateand operatordrain/terminatepause are different mechanisms).
Phase 2 static planning compatibility¶
- Supported subset (current):
@flowfunction body analysis forsubmit/mapandwait_fordependencies.@task(name=...)custom names when task objects are module-level or flow-closure visible.- Repeated invocations of the same task in one flow (
task-0,task-1, … labels; distinctplanned_node_idper call). - Distinct task wrappers on a shared Python function body (separate graph nodes per wrapper).
- Bounded loops with static upper bounds (
for i in range(N)whereNis a constant). - Direct nested
@flowcalls anddeployment_ref(...).submit()recognized for forecast/DAG node kinds where statically visible. - Per-run manifest + forecast (task/edge counts, critical path, parallelism).
- Run DAG API and UI: Aggregated fan-out (
mode=logical) / Task runs (mode=expanded); layout: dependencies left→right, parallel top→bottom; zoom-pan, search, path highlight; subflow node kindsinline_subflow/subflow_task(seedocs/concepts/dag-and-forecast.md). - Fallback:
- Non-analyzable dynamic sections (
if,range(n)with runtimen, tasks not visible to the compiler) run via the runtime path; DAG may showsource: runtimewith runtime-inferred nodes.
Notes¶
- This is an independent project, not an official Prefect release.
- Compatibility is workload-driven and expanded incrementally.