Scheduled Workflow Function Signatures#
Scheduled Workflow Function Signatures define the parameter contracts and type requirements for workflow functions that are invoked periodically by the DBOS scheduler in the DBOS Transact Python framework. These signatures specify how scheduled workflows receive execution timing information and custom context data. The framework currently supports two distinct signature patterns: the modern (datetime, Any) signature introduced for dynamic scheduling, and the legacy (datetime, datetime) signature used by decorator-based scheduling.
On February 12, 2026, PR #581 introduced a breaking change to the scheduled workflow API, transitioning from the legacy (datetime, datetime) signature to the more flexible (datetime, Any) signature. This change enabled dynamic, database-backed schedule management where workflows can receive arbitrary JSON-serializable context objects rather than just timing information. The new signature pattern supports both synchronous and asynchronous workflow implementations, providing developers with greater flexibility in how they structure periodic tasks.
The distinction between these signature types reflects a fundamental architectural evolution in DBOS's scheduling system—from static, decorator-defined schedules with limited parameterization to dynamic, database-persisted schedules with rich context passing capabilities. Understanding these signatures is essential for developers working with scheduled workflows in DBOS, particularly when migrating from older codebases or working with systems that utilize the decorator-based scheduling approach.
Function Signature Types#
Modern Signature (datetime, Any)#
The modern scheduled workflow signature follows the pattern Callable[[datetime, Any], None] or Callable[[datetime, Any], Coroutine[Any, Any, None]] for asynchronous workflows. This signature type is defined in dbos/_scheduler.py as:
ScheduledWorkflow = (
Callable[[datetime, Any], None]
| Callable[[datetime, Any], Coroutine[Any, Any, None]]
)
The first parameter receives the scheduled execution time as a datetime object representing when the workflow was intended to run according to the cron schedule. The second parameter accepts a context object of type Any, which can be any JSON-serializable value including dictionaries, strings, numbers, lists, or None. This context is specified when creating a schedule via DBOS.create_schedule() and is passed to every scheduled execution.
The modern signature supports both synchronous and asynchronous workflow implementations. When the scheduler invokes a workflow, it passes the scheduled execution time and the deserialized context object that was stored in the schedule configuration. This design enables workflows to maintain state and configuration across multiple executions without relying on global variables or external configuration management.
Legacy Signature (datetime, datetime)#
The legacy scheduled workflow signature follows the pattern Callable[[datetime, datetime], None] or its asynchronous equivalent. This signature type is defined in dbos/_scheduler_decorator.py as:
DecoratedScheduledWorkflow = (
Callable[[datetime, datetime], None]
| Callable[[datetime, datetime], Coroutine[Any, Any, None]]
)
In this signature pattern, the first parameter represents the scheduled execution time (when the workflow was supposed to run), while the second parameter represents the actual execution time (when the workflow is being invoked). The decorator scheduler passes datetime.now(timezone.utc) as the second parameter, providing workflows with information about execution delay.
This signature type is still used by the @DBOS.scheduled() decorator, which provides decorator-based scheduling where the cron schedule is specified at definition time. While functional, this approach offers limited flexibility compared to the modern signature, as it cannot pass custom context data and is restricted to timing information only. The legacy signature remains in the codebase for backward compatibility with existing decorator-based scheduled workflows.
Breaking Change History#
PR #581: Workflow Schedules#
On February 12, 2026, PR #581 "Workflow Schedules" introduced a breaking change to the scheduled workflow API, fundamentally redesigning how schedules are created and managed in DBOS Transact Python. The primary change was the transition from the (datetime, datetime) signature to the (datetime, Any) signature, replacing the second timing parameter with a flexible context parameter.
Previous Signature (before PR #581):
@DBOS.workflow()
def scheduled_task(scheduled_at: datetime, actual_time: datetime) -> None:
DBOS.logger.info(f"Scheduled for {scheduled_at}, running at {actual_time}")
New Signature (after PR #581):
@DBOS.workflow()
def scheduled_task(scheduled_time: datetime, context: Any) -> None:
DBOS.logger.info(f"Running task scheduled for {scheduled_time} with context {context}")
The motivation for this breaking change was to enable flexible context passing and support dynamic, database-backed schedule management. Rather than receiving redundant timing information (which can be obtained via datetime.now() if needed), workflows now receive meaningful application-specific context data that can drive their behavior. This context is serialized and stored in the database when a schedule is created, then deserialized on retrieval for each scheduled execution.
Evolution Timeline#
The breaking change in PR #581 was the culmination of several incremental improvements to DBOS's scheduling system:
-
PR #484 "Better Scheduler" (October 3, 2025): Enhanced the scheduler with jitter and duplicate prevention mechanisms to address thundering herd problems in distributed scheduling environments. This laid the groundwork for more sophisticated scheduling features.
-
PR #493 "Python Fixes" (October 20, 2025): Extended scheduler support to allow both synchronous and asynchronous workflows, introducing the union type pattern that would later be used in the modern signature.
-
PR #581 "Workflow Schedules" (February 12, 2026): Introduced the breaking signature change and implemented dynamic scheduling with database persistence, marking a fundamental shift in the scheduling architecture.
-
PR #589 "Support Classes in Scheduler" (February 18, 2026): Added support for class-based and static method workflows via the
workflow_class_nameparameter, completing the transition to the new scheduling system.
Context-Based Invocation#
Context Parameter#
The context parameter in the modern (datetime, Any) signature accepts any JSON-serializable value, providing workflows with application-specific data for each scheduled execution. Supported context types include dictionaries, strings, numbers, lists, booleans, and None. The context is specified when creating a schedule via DBOS.create_schedule() and defaults to None if not provided.
Context objects enable several important use cases:
- Configuration data: Passing runtime configuration parameters that control workflow behavior
- Identification: Distinguishing between multiple schedules of the same workflow with different purposes
- Parameterization: Providing input data that varies between different scheduled instances
- Filtering: Enabling workflows to selectively process different data sets based on context
The context is stored in the database alongside the schedule definition, ensuring it persists across application restarts and is available to all workers in a distributed deployment. This makes context-based invocation suitable for production environments where schedules must be durable and consistently applied.
Serialization Mechanics#
The DBOS scheduler uses a serialization pipeline to persist and retrieve context objects. When a schedule is created, the context is serialized using the DBOS serializer (typically DefaultSerializer) and stored in the workflow_schedules database table. When the schedule is loaded for execution, the serialized context is deserialized back into its original form.
The workflow invocation process constructs inputs as a tuple containing the scheduled execution time and the deserialized context:
inputs: WorkflowInputs = {"args": (scheduled_at, context), "kwargs": {}}
These inputs are then serialized again and stored in the workflow status record, ensuring full durability and recoverability of scheduled workflow executions. This double serialization—once for the schedule definition and once for each workflow execution—guarantees that scheduled workflows can be recovered and replayed even after system failures.
API Methods#
DBOS.create_schedule()#
The DBOS.create_schedule() method is the primary interface for creating dynamic schedules with the modern (datetime, Any) signature. Its signature is defined in dbos/_dbos.py:
@classmethod
def create_schedule(
cls,
*,
schedule_name: str,
workflow_fn: ScheduledWorkflow,
schedule: str,
context: Any = None,
automatic_backfill: bool = False,
cron_timezone: Optional[str] = None,
queue_name: Optional[str] = None,
) -> None:
Parameters:
schedule_name: A unique identifier for the scheduleworkflow_fn: The workflow function to invoke (must accept(datetime, Any)parameters)schedule: A cron expression supporting 6 fields (including seconds)context: An optional JSON-serializable object passed to every execution (defaults toNone)automatic_backfill: Enables automatic backfill of missed executions on startup (defaults toFalse). WhenTrue, the scheduler automatically executes any missed schedule instances when it starts up, catching up on executions that should have occurred while the service was down. This is useful for critical scheduled tasks that must not be missed, such as periodic data synchronization or billing operations. However, it should not be used if backfilling would cause problems, such as when workflows have side effects that should only occur once at the scheduled time.cron_timezone: IANA timezone name for evaluating the cron expression (defaults toNone, meaning UTC). When specified (e.g.,"America/New_York","Europe/London","Asia/Tokyo"), the cron schedule is evaluated in the given timezone instead of UTC. This allows schedules to respect local business hours and automatically adjust for daylight saving time changes. For example, a schedule of"0 9 * * *"withcron_timezone="America/New_York"will fire at 9 AM Eastern Time regardless of whether daylight saving time is in effect.queue_name: An optional name of a declared queue to enqueue scheduled workflows to (defaults toNone). IfNone, scheduled workflows use the internal queue (_dbos_sys_internal). The queue must be declared before use, otherwise aDBOSExceptionis raised. This parameter enables concurrency management for scheduled workflows by directing them to specific queues with controlled worker pools.
The method validates the cron expression, verifies the workflow is registered, and stores the schedule in the database. If called from within a workflow, the operation is recorded as a step for durability. The schedule becomes active immediately and begins firing according to the cron expression.
DBOS.scheduled() Decorator#
The @DBOS.scheduled() decorator provides decorator-based scheduling using the legacy (datetime, datetime) signature. It is defined in dbos/_dbos.py:
@classmethod
def scheduled(
cls, cron: str
) -> Callable[[DecoratedScheduledWorkflow], DecoratedScheduledWorkflow]:
This decorator is applied to workflow functions to schedule them at definition time. Unlike create_schedule(), the decorator approach does not support dynamic schedule creation or custom context passing. The decorated workflow receives the scheduled time as the first parameter and the current execution time as the second parameter.
While still functional, the decorator approach is less flexible than the modern API and is primarily maintained for backward compatibility. New code should prefer DBOS.create_schedule() for its enhanced capabilities and alignment with the current scheduling architecture.
Usage Examples#
Example 1: Dictionary Context#
This example demonstrates passing a dictionary as context:
@DBOS.workflow()
def workflow_a(scheduled_at: datetime, ctx: Any) -> None:
"""Process tasks with dictionary context."""
task_id = ctx["id"]
DBOS.logger.info(f"Running task {task_id} scheduled for {scheduled_at}")
# Process task using context data
DBOS.create_schedule(
schedule_name="every-second-a",
workflow_fn=workflow_a,
schedule="* * * * * *", # Every second
context={"id": "a", "priority": "high"},
)
Example 2: String Context#
This example shows using a simple string as context:
@DBOS.workflow()
def scheduled_workflow(scheduled_at: datetime, ctx: Any) -> None:
"""Process with string context identifier."""
DBOS.logger.info(f"Running workflow {ctx} at {scheduled_at}")
# Use context as a simple identifier
DBOS.create_schedule(
schedule_name="hourly-cleanup",
workflow_fn=scheduled_workflow,
schedule="0 * * * *", # Every hour
context="cleanup-task",
)
Example 3: Async Workflow#
This example demonstrates an asynchronous workflow with context:
@DBOS.workflow()
async def my_workflow(scheduled_at: datetime, ctx: Any) -> None:
"""Asynchronous processing with context."""
await DBOS.sleep_async(1)
DBOS.logger.info(f"Async task with context {ctx}")
# Perform async operations
await DBOS.create_schedule_async(
schedule_name="async-schedule",
workflow_fn=my_workflow,
schedule="*/5 * * * *", # Every 5 minutes
context={"async": True, "batch_size": 100},
)
Example 4: Timezone-Aware Schedule with Automatic Backfill#
This example demonstrates a schedule that runs at 9 AM New York time with automatic backfill enabled:
@DBOS.workflow()
def business_hours_task(scheduled_at: datetime, ctx: Any) -> None:
"""Process daily business operations at 9 AM Eastern Time."""
DBOS.logger.info(f"Running business task scheduled for {scheduled_at}")
# Process daily operations
DBOS.create_schedule(
schedule_name="daily-business-task",
workflow_fn=business_hours_task,
schedule="0 9 * * *", # 9 AM daily
context={"priority": "high"},
automatic_backfill=True, # Catch up if service was down
cron_timezone="America/New_York", # Respects Eastern Time and DST
)
Example 5: Legacy Decorator Pattern#
This example shows the legacy decorator-based approach:
@DBOS.scheduled("0 0 * * *") # Daily at midnight
@DBOS.workflow()
def daily_report(scheduled_time: datetime, actual_time: datetime) -> None:
"""Generate daily report using legacy signature."""
delay = (actual_time - scheduled_time).total_seconds()
DBOS.logger.info(f"Report delayed by {delay} seconds")
# Generate report
Relevant Code Files#
| File | Purpose | URL |
|---|---|---|
dbos/_scheduler.py | Modern scheduler implementation with (datetime, Any) signature and dynamic schedule management | View Source |
dbos/_scheduler_decorator.py | Legacy decorator scheduler with (datetime, datetime) signature for backward compatibility | View Source |
dbos/_dbos.py | Schedule management API methods including create_schedule(), delete_schedule(), pause_schedule(), and related operations | View Source |
tests/test_scheduler.py | Comprehensive test cases demonstrating signature usage patterns and scheduler functionality | View Source |
Related Topics#
- Dynamic Workflow Scheduling: DBOS's approach to creating and managing scheduled workflows with database persistence
- Workflow Context Management: Techniques for passing and managing custom data within workflow executions
- Database-Persisted Schedules: The architecture enabling schedules to survive application restarts and coordinate across multiple workers
- Cron Schedule Management: Working with cron expressions in DBOS, including the extended 6-field format with second-level precision
- Workflow Recovery and Persistence: How scheduled workflows participate in DBOS's durable execution and recovery mechanisms
- JSON Serialization: The serialization strategies used to persist context objects and workflow inputs