Launch plans, schedules, and fixed inputs
When deploying workflows to Flyte, registering the bare workflow logic is often not enough. Workflows frequently require different configurations depending on the environment—such as running with locked production parameters on a weekly cron schedule, running nightly with debug notifications, or running with bounded concurrency.
A LaunchPlan in flytekit encapsulates a workflow alongside pre-configured inputs, execution schedules, notification rules, security credentials, and runtime constraints without modifying the underlying workflow definition.
Defining and Creating Launch Plans
A workflow definition specifies logic and interface types. A LaunchPlan provides an execution specification for that workflow.
Default Launch Plans
Every workflow automatically has a default launch plan. A default launch plan has no custom name, carries no schedule or notifications, and inherits any default parameter values defined in the workflow function signature.
To retrieve or register the default launch plan for a workflow, call LaunchPlan.get_or_create() with only the workflow argument:
from flytekit import task, workflow
from flytekit.core.launch_plan import LaunchPlan
@task
def process_data(dataset: str, retries: int) -> str:
return f"Processed {dataset} with {retries} retries"
@workflow
def data_pipeline(dataset: str, retries: int = 3) -> str:
return process_data(dataset=dataset, retries=retries)
# Retrieve or create the default launch plan
default_lp = LaunchPlan.get_or_create(workflow=data_pipeline)
If you try to pass custom configuration (such as default_inputs, fixed_inputs, or schedule) to LaunchPlan.get_or_create() without providing a name, flytekit raises a ValueError:
ValueError: Only named launchplans can be created that have other properties. Drop the name if you want to create a default launchplan. Default launchplans cannot have any other associations
Named Launch Plans
To configure schedules, notifications, or input bindings, supply a unique name to LaunchPlan.get_or_create() or LaunchPlan.create():
from flytekit.core.launch_plan import LaunchPlan
staging_lp = LaunchPlan.get_or_create(
name="data_pipeline_staging",
workflow=data_pipeline,
default_inputs={"dataset": "s3://my-bucket/staging-data"},
fixed_inputs={"retries": 1},
)
In-Memory Caching and Name Uniqueness
LaunchPlan maintains an internal dictionary cache (LaunchPlan.CACHE) keyed by the launch plan name (or the workflow name for default launch plans).
- When you call
LaunchPlan.get_or_create()with a name that already exists inCACHE, flytekit checks whether all parameters match the cached instance. - If the workflow or any configuration attributes differ from the cached version, flytekit raises an
AssertionErrorto prevent silent misconfiguration. - Calling
LaunchPlan.create()directly on an existing name will always raise anAssertionErrorif that name is already inLaunchPlan.CACHE.
Parameterization: Default vs. Fixed Inputs
Launch plans support two mechanisms for customizing workflow arguments: default_inputs and fixed_inputs.
Workflow Signature: data_pipeline(dataset: str, retries: int)
├── default_inputs={"dataset": "s3://staging"} --> In ParameterMap (caller can override at launch)
└── fixed_inputs={"retries": 1} --> In LiteralMap (stripped from ParameterMap, locked)
Default Inputs
Inputs defined in default_inputs provide fallback values at launch time. When triggering an execution via the Flyte UI, CLI, or API, users can override these values.
- Values supplied in
default_inputstake precedence over default argument values specified in the Python workflow signature. - Internally,
LaunchPlan.create()transformsdefault_inputsinto parameter defaults in the launch plan'sParameterMap.
Fixed Inputs
Inputs defined in fixed_inputs are immutable. Once set on a launch plan, they cannot be modified or overridden when triggering an execution.
- Internally, flytekit converts
fixed_inputsinto aLiteralMapusingtranslate_inputs_to_literals(). - The
LaunchPlan.__init__()constructor strips all keys found infixed_inputsfrom the publicParameterMap, ensuring callers cannot supply values for those arguments during execution requests. - Merged values from
default_inputsandfixed_inputsare saved to the launch plan's_saved_inputsdictionary for local executions.
from flytekit.core.launch_plan import LaunchPlan
production_lp = LaunchPlan.get_or_create(
name="data_pipeline_prod_locked",
workflow=data_pipeline,
default_inputs={"dataset": "s3://my-bucket/production-data"},
fixed_inputs={"retries": 5},
)
Calling Launch Plans Locally and in Workflows
You can invoke a launch plan directly in Python code, either during local testing or inside another workflow.
Launch plans enforce keyword arguments only. Passing positional arguments raises an AssertionError:
# Valid invocation
result = production_lp(dataset="s3://my-bucket/custom-data")
# Invalid: raises AssertionError("Only Keyword Arguments are supported for launch plan executions")
# result = production_lp("s3://my-bucket/custom-data")
Under the hood, LaunchPlan.__call__() handles two execution modes:
- Local Execution (
ctx.compilation_state is None): The call merges the launch plan's_saved_inputs(which contains default and fixed inputs) with any explicit keyword arguments passed to__call__(), forwarding execution directly toself.workflow(*args, **inputs). - Compilation / Workflow Embedding (
ctx.compilation_state is not None): The call links a sub-launch plan node into the workflow directed acyclic graph (DAG) viacreate_and_link_node(ctx, entity=self, **inputs).
Scheduling and Automated Triggers
Flytekit provides schedule constructs that bind directly to launch plans: CronSchedule and FixedRate.
Cron Schedules
CronSchedule schedules recurring executions using standard 5-field cron expressions or standard cron aliases.
from datetime import datetime
from flytekit import task, workflow
from flytekit.core.launch_plan import LaunchPlan
from flytekit.core.schedule import CronSchedule
@task
def process_partition(timestamp: datetime) -> str:
return f"Processing window ending at {timestamp.isoformat()}"
@workflow
def hourly_workflow(kickoff_time: datetime) -> str:
return process_partition(timestamp=kickoff_time)
hourly_lp = LaunchPlan.get_or_create(
name="hourly_partition_processor",
workflow=hourly_workflow,
schedule=CronSchedule(
schedule="0 * * * *",
offset="PT15M",
kickoff_time_input_arg="kickoff_time",
),
auto_activate=True,
)
Key features of CronSchedule:
- 5-field cron expression or aliases: Pass standard cron syntax (e.g.
"0 0 * * *") or aliases (e.g."@daily","hourly","weekly","monthly"). Flytekit validates these strings withcroniter. The legacy AWS 6-fieldcron_expressionparameter is deprecated; passing it raises anAssertionError. - Offset parameter: An optional ISO 8601 duration string (such as
"PT15M"or"P1D") matched against the regex([-+]?)P([-+0-9YMWD]+)?(T([-+0-9HMS.,]+)?)?. This shifts the firing time by the specified duration. - Kickoff time injection: Set
kickoff_time_input_argto the name of adatetimeargument in your workflow signature (e.g."kickoff_time"). Flyte will inject the scheduled execution timestamp into that argument when the run triggers.
Fixed Rate Intervals
FixedRate schedules executions at fixed, recurring intervals specified as a datetime.timedelta.
from datetime import timedelta
from flytekit.core.launch_plan import LaunchPlan
from flytekit.core.schedule import FixedRate
interval_lp = LaunchPlan.get_or_create(
name="heartbeat_every_thirty_minutes",
workflow=hourly_workflow,
schedule=FixedRate(
duration=timedelta(minutes=30),
kickoff_time_input_arg="kickoff_time",
),
)
FixedRate._translate_duration() evaluates the timedelta and converts it to integer quantities of FixedRateUnit.DAY, FixedRateUnit.HOUR, or FixedRateUnit.MINUTE. Resolutions smaller than 1 minute or values with non-zero microseconds raise an AssertionError:
AssertionError: Granularity of less than a minute is not supported for FixedRate schedules.
Triggers and Auto-Activation
triggervsschedule: Flytekit supports passing a trigger implementingLaunchPlanTriggerBase(such asOnSchedule(schedule=...)) to thetriggerparameter.auto_activate: By default,auto_activate=False. Settingauto_activate=Trueinstructs FlyteAdmin to activate the schedule automatically when the launch plan is registered, without requiring an explicit activation command.
Notifications, Security, and Advanced Configuration
LaunchPlan.create() and LaunchPlan.get_or_create() accept additional metadata and infrastructure policies.
Email, Slack, and PagerDuty Notifications
Use notification classes from flytekit.core.notification to trigger external alerts upon workflow completion:
from flytekit.core.launch_plan import LaunchPlan
from flytekit.core.notification import Email, PagerDuty, Slack
from flytekit.models.core.execution import WorkflowExecutionPhase
notified_lp = LaunchPlan.get_or_create(
name="monitored_pipeline_run",
workflow=data_pipeline,
default_inputs={"dataset": "s3://my-bucket/live-feed"},
fixed_inputs={"retries": 3},
notifications=[
Email(
phases=[WorkflowExecutionPhase.FAILED, WorkflowExecutionPhase.TIMED_OUT],
recipients_email=["ops-team@example.com"],
),
Slack(
phases=[WorkflowExecutionPhase.SUCCEEDED],
recipients_email=["alerts-channel@company.slack.com"],
),
PagerDuty(
phases=[WorkflowExecutionPhase.FAILED],
recipients_email=["pagerduty-ingest@company.pagerduty.com"],
),
],
)
Notifications only accept terminal phases: SUCCEEDED, FAILED, ABORTED, and TIMED_OUT. Specifying non-terminal states raises an AssertionError.
Security Context and Identity
Specify IAM roles or Kubernetes Service Accounts for workflow runs launched by the plan using SecurityContext:
from flytekit.models.security import Identity, SecurityContext
secure_lp = LaunchPlan.get_or_create(
name="secure_etl_launch_plan",
workflow=data_pipeline,
security_context=SecurityContext(
run_as=Identity(
iam_role="arn:aws:iam::123456789012:role/DataPipelineExecutionRole",
k8s_service_account="etl-service-account",
)
),
)
Note on
AuthRole: The legacyauth_roleparameter is deprecated. If bothauth_roleandsecurity_contextare passed, flytekit raises aValueError. If onlyauth_roleis passed, flytekit automatically translates it toSecurityContext(run_as=Identity(...)).
Parallelism, Caching, and Output Storage
Additional runtime settings include:
max_parallelism: An integer capping the maximum number of task nodes that can run concurrently within an execution started by this launch plan.overwrite_cache: A boolean flag (True/False) indicating whether executions from this launch plan should force re-execution and overwrite existing task caches.raw_output_data_config: ARawOutputDataConfig(output_dataset_name=...)object setting target cloud storage prefixes for intermediate and raw task data offloading.
from flytekit.models.common import RawOutputDataConfig
configured_lp = LaunchPlan.get_or_create(
name="custom_configured_pipeline",
workflow=data_pipeline,
max_parallelism=10,
overwrite_cache=True,
raw_output_data_config=RawOutputDataConfig(output_dataset_name="s3://my-bucket/custom-outputs/"),
)
Dynamic Workflows and Reference Launch Plans
Dynamic Workflows (node_dependency_hints)
When a dynamic task (@dynamic) returns sub-launch plan invocations, FlyteAdmin must know about the launch plan entity beforehand. Pass the launch plan object to node_dependency_hints:
from typing import List
from flytekit import dynamic, workflow
from flytekit.core.launch_plan import LaunchPlan
@workflow
def leaf_workflow(x: int) -> int:
return x * 2
leaf_lp = LaunchPlan.get_or_create(workflow=leaf_workflow, name="leaf_workflow_lp")
@dynamic(node_dependency_hints=[leaf_lp])
def generate_dynamic_runs(count: int) -> List[int]:
results = []
for i in range(count):
results.append(leaf_lp(x=i))
return results
Reference Launch Plans
When interacting with a launch plan registered in another Flyte project, domain, or version, use @reference_launch_plan or ReferenceLaunchPlan. This constructs an interface pointer without making network calls to FlyteAdmin during compilation:
from flytekit import workflow
from flytekit.core.launch_plan import reference_launch_plan
@reference_launch_plan(
project="shared_analytics",
domain="production",
name="aggregate_metrics_lp",
version="v2.1",
)
def aggregate_metrics_lp(dataset: str, retries: int) -> str:
...
@workflow
def orchestration_workflow(source_path: str) -> str:
return aggregate_metrics_lp(dataset=source_path, retries=2)
If the parameter types or names specified in the reference function signature mismatch the remote launch plan's registered interface, FlyteAdmin returns a validation error when registering the outer workflow.