Kamu has been the testing bed for the initial implementation of the task and flow systems. Current design is presented in Appendix A.The current prototype system has a few design issues:
Flows are closely coupled with datasets - we’ll need them to work with many other types of resources
It exposes an ever growing GQL API surface that has to be updated when adding new flow types
It is not extensible and would not allow custom plug-in flow types
Flows consist of one workload only and cannot chain multiple steps (e.g. compact then GC)
We propose to define a new Tasks & Flows system on the core ODF level that builds on the Resource Framework and allows defining and scheduling workloads in a generic, extensible way.
Building in a bottom-up order, we define the Task resource. Task represents a single unit of work, from intent through planning and execution to commit.Task resource spec captures the intent: the target resource and the operation kind with high-level parameters. This is what a human operator or a FlowRun controller writes when creating a task. Spec is stable, human-readable, and never rewritten.The execution plan - with fully resolved paths, offsets, schemas, and all other inputs needed by the worker - is computed by the node’s planner and written into TaskPlan status condition.The TaskStatus lifecycle proceeds through the following phases:
Pending — task created, waiting for the planner
Planning — node planner is resolving the full execution plan into TaskPlan
Ready — TaskPlan is populated; task is queued for a worker
Running — worker is executing the plan
Committing — worker reported its result; node is validating output and writing the metadata block
Finished — terminal; TaskOutcome condition is either Success, Failed, NoOp or Cancelled; the resource enters a TTL period before deletion.
Task resources are retained for a configurable TTL period after completion (e.g. 10 days) before being deleted. This allows the UI and operators to inspect recent runs directly from the resource store without querying the event store. After TTL expires, the resource is deleted, but the full history of any task remains recoverable from the event sourcing store by resource ID.Note that the separation of planning, execution, and commit phase allows to run certain parts of the task on the node (with access to metadata and storage state), and heavy computational tasks on a separate worker.Example of a task as created by a FlowRun controller (spec describes only the intent, no plan yet):
$schema: https://opendatafabric.org/schemas/flow/v1alpha1/Taskheaders: ownerReferences: - FlowRun:c27331ce-ce88-4ff9-8c5a-4ce8107cc03f # ResourceRef of the FlowRun that spawned this taskspec: kind: Transform target: Dataset:sergiimk/foo # ResourceRefstatus: phase: Pending
Example of the same task after planning and execution:
FlowRun resources are a set of tasks to be executed in a sequence.Example:
$schema: https://opendatafabric.org/schemas/flow/v1alpha1/FlowRunheaders: ownerReferences: - Flow:f47ac10b-58cc-4372-a567-0e02b2c3d479 # ResourceRef of the Flow that spawned this runspec: target: Dataset:sergiimk/foo tasks: - kind: Transform # Implicit name: task-0-transform - kind: GarbageCollection # Implicit name: task-1-garbage-collectstatus: phase: Ready # NOTE: FlowRun resource is retained for a TTL period, then deleted conditions: # Tracks the overall status and the tasks that were spawned during the execution https://opendatafabric.org/schemas/flow/v1alpha1/FlowRunStatus: status: Running # Waiting / Running / Retrying / Finished tasks: - name: task-0-transform # Corresponds to spec.tasks[0] task: Task:a1b2c3d4-1111-2222-3333-444444444444 # ResourceHandle status: Finished outcome: kind: Success lastUpdatedAt: 2026-09-11T02:00:00Z - name: task-1-garbage-collect # Corresponds to spec.tasks[1] task: Task:e5f6a7b8-5555-6666-7777-888888888888 # ResourceHandle status: Finished outcome: kind: Success lastUpdatedAt: 2026-09-11T02:02:00Z # Explains what led to execution of this flow # In case of a retry - the causes of the original run are preserved https://opendatafabric.org/schemas/flow/v1alpha1/FlowRunActivationCauses: activationCauses: - activationTime: 2026-09-11T02:00:00Z initiator: system # AccountHandle trigger: # Copy of the trigger's configuration in the parent Flow kind: Manual lateActivationCauses: [] # Links to the previous FlowRun, if this is a retry https://opendatafabric.org/schemas/flow/v1alpha1/FlowRunRetry: retryOf: FlowRun:9b2e4f1a-3c7d-4e8b-a1f2-6d5e7c8b9a0d # ResourceHandle
The spec.target on the FlowRun level is used as the default target for tasks in spec.tasks list to avoid duplication.
Note: Although retryOf and activationCauses are immutable and known at FlowRun creation, they are part of status rather than spec because they carry information that can only be reliably set by the controller, not by a user.
Flow resources act as templates for instantiating FlowRuns and define triggers that decide when to instantiate them.Example:
$schema: https://opendatafabric.org/schemas/flow/v1alpha1/Flowheaders: name: compact-and-gc-rootsspec: target: # Selector matching all Root datasets under `sergiimk` type: Dataset account: sergiimk name: % labels: datasetKind: Root triggers: - kind: Event events: type: dataset.ref.updated # TODO: Spec for event types / groups cooldown: 10min tasks: - kind: Compaction params: minUncompactedRows: 100 # TODO: Take a look at compaction RFC maxSliceSize: 100MiB maxSliceRecords: 10000 onNoOp: Break # Break / Continue: Break finishes the flow run early without error onFailure: Fail # Fail / Continue - kind: GarbageCollection retryPolicy: maxAttempts: 3 minDelay: 1min backoff: Exponential # None / Linear / Exponential recentBindingsRetention: # Controls retention for `FlowStatus.recentBindings` maxBindings: 10 recentRunsRetention: # Controls retention for `FlowStatus.recentRuns` maxRuns: 100 # Also capped by the controller settings maxAge: 30dstatus: phase: Ready conditions: https://opendatafabric.org/schemas/flow/v1alpha1/FlowStatus: status: Active # Active / Paused recentBindings: - target: Dataset:sergiimk/foo boundAt: 2026-01-01T00:00:00Z bindingsTotal: 1 recentRuns: - flowRun: FlowRun:f47ac10b-1111-2222-3333-444444444444 # ResourceRef status: Finished outcome: Success finishedAt: 2026-09-11T02:00:00Z - flowRun: FlowRun:9b2e4f1a-5555-6666-7777-888888888888 # ResourceRef status: Finished outcome: Failed finishedAt: 2026-09-10T02:00:00Z
The spec.target on the Flow level is used as the default target for triggers in spec.triggers list and tasks in spec.tasks list to avoid duplication while still allowing overrides (e.g. triggering a flow on dataset A based on events in dataset B).
When a Flow’s target selector matches a resource, the flow controller sets up triggers for that resource automatically. As resources are created or deleted, the controller subscribes to those events and updates its set of active targets accordingly.The association between a Flow and its matched resources is purely a derived state that the controller maintains. The FlowStatus.recentBindigs may reflect last N bindings that were created for debugging purposes. To show the full paginated list of bindings, the flow controller may provide a dedicated API for querying the index, e.g. in REST:
GET /flow/v1alpha1/flow/_/inverseSearch?target={ResourceRef} - list flows by target
GET /flow/v1alpha1/flow/{id}/targets - list targets by flow
Flows specify a set of triggers that decide when to instantiate a FlowRun.Examples:
# Reacts to an API call or a UI button presskind: Manual# Fires on specified Cron schedule# Guaranteed to fire if node was down when the next tick was supposed to happen# Fires only once for all missed tickskind: Schedulecron: "@daily"# Fires at regular intervals# Guaranteed to fire if node was down when the next tick was supposed to happen# Fires only once for all missed tickskind: Intervalinterval: 15m# Reacts to events on the event bus# This is a very low-level trigger that should be used sparinglykind: Eventtarget: Dataset:sergiimk/fooevents: type: dataset.ref.updated # Domain event filter (TODO: Spec for event types)# Fires when inputs of a derivative dataset have updateskind: InputsUpdatedtarget: Dataset:sergiimk/barminRecordsToAwait: 100 # optional batchingmaxAwaitInterval: 1h # run at least once an hour if minNewRecords have not been reached# Fires when specified source is updatedkind: SourceUpdatedsource: Source:sensor.temp.httpminRecordsToAwait: 100 # optional batchingmaxAwaitInterval: 1h # run at least once an hour if minNewRecords have not been reached
The following properties are common across all trigger types:
enabled: true # Allows to pause an individual triggercooldown: 10m # Don't fire more often than every 10 minutes (batches multiple activations into one)