All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
ADDED
- Added a pluggable
DataConverter(durabletask.serialization) accepted byTaskHubGrpcWorker,TaskHubGrpcClient, andAsyncTaskHubGrpcClientvia adata_converterargument. Every payload boundary (inputs, outputs, events, custom status, entity state) routes through it, so one converter controls how Python values become JSON and how they are reconstructed. The defaultJsonDataConverterpreserves existing behavior, so a custom converter (for example one backed by pydantic) is fully opt-in. - Custom objects can participate in serialization by exposing a
to_json()method and afrom_json(value)classmethod. Both are honored recursively, so nested custom objects round-trip through their own hooks. - Payloads are reconstructed into a caller-supplied type — dataclasses
(including nested fields),
from_json()-capable types, andenum.Enummembers, recursing throughlist,dict,tuple, andOptional/Unionhints. The type comes from a function's annotations, from an explicitreturn_typeoncall_activity/call_sub_orchestrator/call_entity(ordata_typeonwait_for_external_event), or from the typed accessorsget_input()/get_output()/get_custom_status()onclient.OrchestrationStateandEntityMetadata.get_typed_state(...). It is never inferred from the payload. Which annotated types are eligible is decided by the converter via the overridableDataConverter.can_reconstruct(...); a custom converter can override it to recognize its own types (for examplepydantic.BaseModelsubclasses).
CHANGED
- Custom objects (dataclasses,
SimpleNamespace, namedtuples) are now serialized as plain JSON. Decoding such a payload without a type hint now yields a plaindict(previously aSimpleNamespace; a namedtuple now round-trips as a JSON array). To get the original type back, supply a type via one of the mechanisms above. Payloads produced by older SDK versions still deserialize — including into aSimpleNamespacewhen no type is supplied — so in-flight orchestrations continue to replay across an upgrade. - JSON serialization failures now raise a
TypeErrorthat chains the original error (__cause__) and names the offending type. EntityContext.get_state()/DurableEntity.get_state()now return a freshly reconstructed value on every call rather than a reference to a single cached object. This changes v1.6.0 behavior: mutating the returned value in place no longer affects persisted state — write it back withset_state(). State is also serialized eagerly atset_state()time, so a non-serializable value fails inside the operation (which rolls back) instead of after the batch has run.
FIXED
- Falsy entity states (
0,"",[],{}) are no longer dropped when an entity batch is persisted. Previously a falsy current state was treated as "no state" and written asNone, effectively deleting it; only an actualNonestate now clears the persisted entity state.
DEPRECATED
durabletask.internal.shared.to_jsonanddurabletask.internal.shared.from_jsonare deprecated and now emit aDeprecationWarning. Use adurabletask.serialization.DataConverter(for example the defaultJsonDataConverter) instead. The functions continue to work for backwards compatibility.
BREAKING CHANGES (no runtime impact for typical users)
Most of these are type-level only: because the package ships py.typed,
consumers running strict type checkers (pyright/mypy) — or subclassing the
public abstract types — may need to update their code. The constructor change
below also affects callers who directly construct the named classes, which is
uncommon since they are normally handed to you by the SDK.
OrchestrationContext.call_activity,call_sub_orchestrator,call_entity, andwait_for_external_eventgained new keyword-only parameters (return_type/data_type). Subclasses overriding these methods should add the parameter to match the base signature.EntityContextandEntityMetadata(and itsfrom_entity_metadata/from_entity_responsefactories) now require adata_converterargument. These objects are normally constructed by the SDK — you receive anEntityContextin an entity function and anEntityMetadatafrom the client — so this only affects code that constructs them directly.
ADDED
- Added overridable activity-dispatch hooks
_on_activity_execution_startedand_on_activity_execution_completedonTaskHubGrpcWorker, invoked immediately before each activity runs and in afinallyafter it completes or fails. Subclasses can override these to observe in-flight activity execution (for example, to track the number of activities currently running). - Added
durabletask.extensions.history_exportfor exporting the event history of terminal orchestrations to an external destination. IncludesExportHistoryClient, a per-jobExportHistoryJobClientreturned byget_job_client(...), andlist_jobs(...)for enumerating jobs by status or last-modified window. Ships with a bundledAzureBlobHistoryExportWriter(installed withpip install durabletask[history-export-azure]) and aHistoryWriterprotocol for plugging in custom destinations. Supports bothExportMode.BATCH(export a window and complete) andExportMode.CONTINUOUS(tail terminal instances indefinitely until stopped viadelete_job). Exported blobs are self-describing: each blob carries an explicitschema_version, the orchestration'sOrchestrationStatemetadata, and the full ordered event list. Blob names are a lowercase-hex SHA-256 of{last_updated_at}|{instance_id}with the format extension appended (matches the .NETExportInstanceHistoryActivitynaming scheme), so re-exporting an instance after a later terminal update lands at a new blob path rather than overwriting the previous one, and instance IDs that differ only by/no longer collide. Each exported blob also carries{"instance_id": <id>}as destination-side metadata (the Azure writer persists this as Azure Blob metadata) so consumers can scan a container without parsing each blob body. The export workflow retries each instance up to 3 times with exponential backoff (15s/30s/60s), retries failed batches up to 3 times, caps in-flight exports viamax_parallel_exports(default 32), continues-as-new every 5 page cycles to bound orchestrator history while preserving cumulative totals across continue-as-new segments, and re-fetches entity state at the top of every page loop so external delete or mark-failed signals stop the orchestrator cleanly. Empty-page BATCH checkpoints no longer reset the persisted resume cursor, and duplicatemark_failedsignals are now idempotent no-ops when a job is already failed to reduce transition-noise logs.delete_jobactively tears the job down: it clears the entity state, terminates the driving orchestrator, waits briefly for it to settle, and purges its orchestration history so a re-created job with the same ID starts from a clean slate. Per-instance exports refuse to write a blob when the target instance has been purged or has re-entered a non-terminal state, surfacing the skipped instance as a per-batch failure. Job state lives in a durable entity with an explicit state-transition matrix (ACTIVE / COMPLETED / FAILED); invalid transitions raiseExportJobInvalidTransitionError. Persisted entity state uses a versioned, schema-stable JSON shape (STATE_SCHEMA_VERSION) with no embedded Python type metadata. Each export job's driving orchestrator uses a deterministic instance ID (export-job-{job_id}, exposed viaorchestrator_instance_id_for(...)) so callers can correlate a job ID with its orchestrator for logging, monitoring, and restart.
BREAKING CHANGES (type-level only — no runtime impact for typical users)
These changes do not alter runtime behavior for clients or activity/orchestrator
authors, but because the package ships py.typed, consumers running strict type
checkers (pyright/mypy) against their own code — or subclassing the public
abstract types — may see new type-check errors and need to update their
overrides:
OrchestrationContext.create_timernow returns the specificTimerTasktype (wasCancellableTask) (#93).OrchestrationContext.wait_for_external_eventnow returnsCancellableTask[Any](was a bareCancellableTask).WhenAnyTaskis now generic;when_any(tasks: Sequence[Task[T]])returnsWhenAnyTask[T]for better static inference of the completing child task (#94).CompositeTask.on_child_completednow takesTask[Any].TaskHubGrpcWorker.add_activity/add_entity(and the internal registry methods) now requireActivity[Any, Any]/Entity[Any, Any]instead of the bareActivity/Entityaliases.OrchestrationContext.call_entity/signal_entityinputparameter widened fromTInput | NonetoAny(Liskov-safe for callers; subclass overrides using the old narrower type will be flagged).- gRPC client interceptors now use the public
grpc.ClientCallDetails/grpc.aio.ClientCallDetailstypes instead of private internal namedtuples; custom interceptor subclasses should retype their override parameters. - These changes also broadly improve generic type-safety hints throughout the SDK (#92).
ADDED
- Added context-manager support (
__enter__/__exit__) toTaskHubGrpcClientso it can be used withwithstatements, mirroring the existingAsyncTaskHubGrpcClientasync-context-manager support and theTaskHubGrpcWorkerpattern.DurableTaskSchedulerClientinherits this behavior automatically.__exit__delegates toclose(), so the resiliency-aware teardown (in-flight recreate thread join, retired-channel timer cancellation, and SDK-owned channel cleanup) runs unchanged through the newwithpath. - Added
ReplaySafeLoggerandOrchestrationContext.create_replay_safe_logger()for suppressing duplicate log messages during orchestrator replay - Added
GrpcChannelOptionsandGrpcRetryPolicyOptionsfor configuring gRPC transport behavior, including message-size limits, keepalive settings, and channel-level retry policy service configuration. - Added optional
channelandchannel_optionsparameters toTaskHubGrpcClient,AsyncTaskHubGrpcClient, andTaskHubGrpcWorkerto support pre-configured channel passthrough and low-level gRPC channel customization. - Added
GrpcWorkerResiliencyOptionsandGrpcClientResiliencyOptions, plusresiliency_optionsconstructor parameters onTaskHubGrpcClient,AsyncTaskHubGrpcClient, andTaskHubGrpcWorker, to configure hello deadlines, silent-disconnect detection, reconnect backoff, and channel recreation thresholds for SDK-managed gRPC connections. - Added
get_orchestration_history()andlist_instance_ids()to the sync and async gRPC clients. - Added in-memory backend support for
StreamInstanceHistoryandListInstanceIdsso local orchestration tests can retrieve history and page terminal instance IDs by completion window.
CHANGED
when_anynow copies its input into a new list (WhenAnyTask(list(tasks))). Previously the task aliased the caller's list, so mutating it after construction was visible inside the task; that side effect no longer occurs.
FIXED
- Fixed
EntityInstanceId.__lt__infinite recursion when compared against a non-EntityInstanceIdoperand. It now returnsNotImplemented, so mixed-type comparisons raiseTypeErrorcleanly instead of recursing. - Improved
TaskHubGrpcWorkerrecovery from stale or disconnected gRPC streams so configured hello timeouts apply on fresh connections, received work resets failure tracking, SDK-owned channels are refreshed and cleaned up safely, and caller-owned channels are never recreated or closed during reconnects. - Fixed
TaskHubGrpcWorkerso in-flight and queued work item completions keep draining across graceful gRPC stream resets and worker shutdown before the worker retires an SDK-owned channel. - Improved sync and async gRPC clients so repeated transport failures recreate SDK-owned channels, while long-poll deadlines, successful replies, and application-level RPC errors do not trigger unnecessary channel replacement.
- Fixed
TaskHubGrpcClient.close()so explicit sync client shutdown now closes any previously retired SDK-owned gRPC channels immediately instead of waiting for the delayed cleanup timer.
ADDED
- Added large payload externalization support for automatically
offloading oversized orchestration payloads to Azure Blob Storage.
Install with
pip install durabletask[azure-blob-payloads]. Pass aBlobPayloadStoreto the worker and client via thepayload_storeparameter. - Added
durabletask.extensions.azure_blob_payloadsextension package withBlobPayloadStoreandBlobPayloadStoreOptions - Added
PayloadStoreabstract base class indurabletask.payloadfor custom storage backends - Added
durabletask.testingmodule withInMemoryOrchestrationBackendfor testing orchestrations without a sidecar process - Added
AsyncTaskHubGrpcClientfor asyncio-based applications usinggrpc.aio - Added
DefaultAsyncClientInterceptorImplfor async gRPC metadata interceptors - Added
get_async_grpc_channelhelper for creating async gRPC channels - Added orchestration restart client support
- Added batch client actions for purge and query operations across orchestrations and entities
- Added worker work item filtering support
- Added new
work_item_filteringsample - Improved distributed tracing support with full span coverage for orchestrations, activities, sub-orchestrations, timers, and events
CHANGED
- Improved timer scheduling behavior for orchestrator timers
FIXED:
- Fix unbound variable in entity V1 processing
- Fixed
compute_next_delayreturningNonewhenmax_retry_intervalis not set - Fixed multiple entity-related bugs across ID parsing and failure handling
ADDED
- Allow entities with custom names
CHANGED
- Allow task.fail() to be called with Exceptions
- Update type-hinting for Task return sub-types
- Add/update type-hinting for various worker methods
ADDED:
- Added new_uuid method to orchestration clients allowing generation of replay-safe UUIDs.
- Added ProtoTaskHubSidecarServiceStub class to allow passing self-generated stubs to worker
- Added support for new event types needed for specific durable backend setups:
- orchestratorCompleted
- eventSent
- eventRaised modified to support entity events
CHANGED:
- Added py.typed marker file to durabletask module
- Updated type hinting on EntityInstanceId.parse() to reflect behavior
- Entity operations now use UUIDs generated with new_uuid
FIXED:
- Mismatched parameter names in call_entity/signal_entity from interface
ADDED:
- Allow retrieving entity metadata from the client, with or without state
ADDED:
- Allow calling sub-orchestrators by name
- Abandon workitems if unhandled exception occurs in client
CHANGED:
- Improve execution logging
- Supported Python versions are now 3.10- 3.14. Python 3.9 is end of life and has been removed.
FIXED:
- Reduce exposure of Entity context internally
- Added support for Durable Entities
- Fixed an issue where orchestrations would still throw non-determinism errors even when versioning logic should have prevented it
- Added support for orchestration and activity tags
- Added support for orchestration versioning and versioning logic in the worker
ADDED
- Added
ConcurrencyOptionsclass for fine-grained concurrency control with separate limits for activities and orchestrations. The thread pool worker count can also be configured.
FIXED
- Fixed an issue where a worker could not recover after its connection was interrupted or severed
ADDED
- Added
set_custom_statusorchestrator API (#31) - contributed by @famarting - Added
purge_orchestrationclient API (#34) - contributed by @famarting - Added new
durabletask-azuremanagedpackage for use with the Durable Task Scheduler
CHANGED
- Protos are compiled with gRPC 1.62.3 / protobuf 3.25.X instead of the latest release. This ensures compatibility with a wider range of grpcio versions for better compatibility with other packages / libraries (#36) - by @berndverst
- Http and grpc protocols and their secure variants are stripped from the host name parameter if provided. Secure mode is enabled if the protocol provided is https or grpcs (#38) - by @berndverst
- Improve ProtoGen by downloading proto file directly instead of using submodule (#39 - by @berndverst
CHANGED
- Updated
durabletask-protobufsubmodule reference to latest
ADDED
- Add recursive flag in terminate_orchestration to support cascade terminate (#27) - contributed by @shivamkm07
ADDED
- Retry policies for activities and sub-orchestrations (#11) - contributed by @DeepanshuA
FIXED
- Fix try/except in orchestrator functions not being handled correctly (#21) - by @cgillum
- Updated
durabletask-protobufsubmodule reference to latest distributed tracing commit - by @cgillum
ADDED
- Adds support for secure channels (#18)
- contributed by @elena-kolevska
FIXED
- Fix zero argument values sent to activities as None (#13) - contributed by @DeepanshuA
ADDED
- Add gRPC metadata option (#16) - contributed by @DeepanshuA
CHANGED
- Removed Python 3.7 support due to EOL (#14) - contributed by @berndverst
ADDED
- Continue-as-new (#9)
- Support for Python 3.7+ (#10) - contributed by @DeepanshuA
Initial release, which includes the following features:
- Orchestrations and activities
- Durable timers
- Sub-orchestrations
- Suspend, resume, and terminate client operations