Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

Get Started with Microsoft Agent Framework Durable Task

PyPI

Please install this package via pip:

pip install agent-framework-durabletask --pre

Optional Blob Payload Offloading

This extra is part of the current unreleased stack. The matching package release must include it before using the installation command against PyPI. Install the optional storage dependencies through the shared extra:

pip install "agent-framework-durabletask[azure-blob-payloads]" --pre

The Functions package's azure-blob-payloads extra forwards to this extra. The dependency list has one owner; installation alone does not enable offloading. The shared extra uses the same durabletask>=1.11.0,<2 baseline as both hosts, including non-blocking asynchronous Blob compression. Keep SDK versions locked across clients and workers. When upgrading from older SDKs, finish affected nested-race runs on pinned old workers or use a separate hub for new instances. Already-stuck runs may require deliberate recovery. Existing migration and fresh-instance requirements still apply; see the SDK upgrade guidance.

Configure a compatible payload_store on each SDK worker and client before wrapping them:

import os

from durabletask.client import TaskHubGrpcClient
from durabletask.extensions.azure_blob_payloads import BlobPayloadStore, BlobPayloadStoreOptions
from durabletask.worker import TaskHubGrpcWorker

from agent_framework_durabletask import DurableAIAgentClient, DurableAIAgentWorker

store = BlobPayloadStore(
  BlobPayloadStoreOptions(
    connection_string=os.environ["PAYLOAD_STORAGE_CONNECTION_STRING"],
    container_name="durable-payloads",
    threshold_bytes=256 * 1024,
  )
)
worker = TaskHubGrpcWorker(host_address=os.environ["DURABLE_TASK_ENDPOINT"], payload_store=store)
client = TaskHubGrpcClient(host_address=os.environ["DURABLE_TASK_ENDPOINT"], payload_store=store)
agent_worker = DurableAIAgentWorker(worker, deployment_mode="isolated_v2")
agent_client = DurableAIAgentClient(client)

DurableTaskSchedulerWorker and DurableTaskSchedulerClient accept the same payload_store argument alongside their DTS endpoint, task hub and credential settings. The MAF wrappers retain it. Separate processes can use different store objects but must agree on the backing storage and reference format. Keep each store open while its worker or client uses it. Blob offloading does not require Functions. Functions configures its root app instead of constructing these objects; see the Functions setup.

The configured credentials need permission to create the upload container and to read and write payload blobs. The SDK attempts container creation on the first upload, even if the container already exists. Pre-provisioning it does not bypass this request.

Supported serialized payloads above the threshold, including entity state and operation results, are stored in blobs and hydrated by configured SDK clients/workers. The default stored-payload limit is 10 MiB and is configurable. Offloading does not reduce decoded state or model context, and does not change MAF state budgets or retention. Reset, response expiry, entity deletion and orchestration purge do not clean up SDK payload blobs. Retain blobs while referenced state/history may be read or replayed, including across deployments, and manage lifecycle separately.

Store-recognized whole strings are reserved transport references rather than ordinary data. Wrap literal references in an object and retain that wrapper across durable boundaries. References do not authorize reads; the upload container does not constrain which containers credentials can read. Use least-privilege credentials and validate or reject references from untrusted callers before durable API calls. Blob transport failures and missing references can prevent execution or reads; storage retries are not a guarantee of durable recovery.

Durable Task Integration

The durable task integration lets you host Microsoft Agent Framework agents using the Durable Task framework so they can persist state, replay conversation history, and recover from failures automatically.

Upgrading the Durable Task SDK

Both Python hosts require durabletask>=1.11.0,<2; standalone still supports Python 3.10+. SDK 1.11 fixes nested races such as when_any([cancel, when_all(tasks)]). Replaying affected older histories can now select a different branch and fail with NonDeterminismError. Finish affected runs on the previous SDK, or keep their hub on pinned old workers and start new instances in a separate hub. Already-stuck runs need deliberate recovery and may not drain. Do not mix old and new workers on the same hub to achieve isolation. The framework's fresh instance and isolated-deployment requirements below still apply. See the SDK release warning.

Coordinate SDK locks across clients and workers. A minimum version does not enforce identical resolved versions. Failure type names are now qualified, for example builtins.ValueError. Use FailureDetails.is_caused_by(ValueError) to also recognize older recorded bare names.

Current Runtime Contract On This Unreleased Stack

This README describes the current unreleased schema-v2 runtime, not a released compatibility promise. It is the common runtime contract for both Python hosts. See the Azure Functions guide for host-specific routes and settings.

The public mutable state model is now canonical DurableAgentState schema 2.0.0. read_agent_state(raw) remains explicitly backward compatible with legacy 1.x payloads. It accepts a dictionary or JSON string and returns:

  • LegacyDurableAgentState for exact 1.0.0, 1.1.0, and 1.2.0 inputs
  • SharedAgentStateReader for exact 2.0.0 inputs

Other schema versions are rejected. Within an accepted 2.0.0 snapshot, unknown fields and unknown protocol content remain preserved in detached raw JSON until a caller explicitly asks for a narrower projection.

Named shared counters are bounded to signed Int64. Usage token counts and ingested positions accept 0..9223372036854775807. A present evictedMessageCount accepts 1..9223372036854775807. Readers and writers reject invalid counts, and eviction increments reject overflow before changing state. These bounds also apply when loading legacy snapshots. Legacy nullable usage fields remain readable, and nullable Core usage counts retain their existing metadata representation. Other invalid known counts cannot be moved into metadata to bypass validation.

Integral JSON numbers such as 1.0 remain accepted. Exact integer tokens are not rounded through floating point. These bounds do not restrict opaque user payloads, provider metadata, session data, or unknown fields.

The v2 reader preserves the original JSON, including unknown fields, shared-value metadata, session state, receipts, and protocol details. to_dict() returns detached original JSON. try_get_agent_response(correlation_id) reads canonical results and receipts without writing back lifecycle changes. If the response is expired or otherwise unavailable, polling consumers retain the known completion outcome instead of deleting or rewriting the receipt. Durable Task storage reads use bounded polling retries.

Known response snapshots preserve the explicit shared-value policy. Typed projections require a present value field and a JSON-preserving decode. Consumers do not infer values from text, coerce missing payloads, or discard unknown structured fields. The Core transport snapshot carries the versioned _durable_value_policy marker so the matching loader can preserve the same rules after JSON transport. It does not prove execution authority, duplicate the value, or identify a runtime-native class by itself.

Host construction requires an explicit deployment strategy. The opt-in two-phase entity migration path uses versioned names and DTS backend filtering, then permits in-place historical upgrades after old workers stop. For an isolated deployment, pass deployment_mode="isolated_v2" or set DURABLE_AGENTS_DEPLOYMENT_MODE=isolated_v2. There is no automatic probe. This is an operator acknowledgement that the task hub is isolated and the clients are upgraded. It is not runtime proof of isolation and it cannot detect peer workers. A new isolated hub with matching upgraded workers and clients is the recommended way to keep old workers and histories off this deployment.

The following inherited replay limitations apply to affected older workflow builds, not to entity address upgrades. Workflow protocol 2 is unchanged, but start admission is stricter and checkpointed HITL admission and mixed parent/child scheduling change the replay action graph, including generated child IDs. Existing v2 in-flight instances and recorded histories are not supported by this runtime and may fail. The version marker checks start-envelope admission, not feature or replay compatibility. Older v2 envelopes can still pass that check. There is no replay migration or compatibility fallback. If old runs must finish, keep them on their original workers and hub, including published histories with concatenated child IDs. Do not resume them here.

Migration

For on-demand upgrades at an existing address, see in-place entity migration. The export/import API below remains a separate explicit transfer operation, not the two-phase rollout path.

The current layer includes canonical transaction state, session capture, the workflow protocol boundary, and a privileged manual migration path. Migration is a backend entity operation only. There is no generated HTTP or MCP migration endpoint. The helper stages a detached result with no model, tool, provider, storage, or transcript I/O. The operator must quiesce the legacy owner, fence it, authorize ownership transfer, and commit the staged result exactly once into a separate empty isolated v2 destination. The migration evidence cannot prove that authority or completeness on its own.

The request contract is explicit and complete. migrate(request) requires source, sourceDigest, sourceSessionId, destinationSessionId, migrationId, and ownershipTransferId. It optionally accepts deliveryEvidence, completionEvidence, and requireKnownOutcomes. The destination must match the current entity, must be separately addressed, and must still be empty. This is never an in-place rewrite and never a replay into a used destination. The same exact request may be retried idempotently. The runtime records a requestDigest and accepts only an identical retry after commit, without rewriting results or refreshing their grace period. Changed requests are rejected, including after acknowledgement loss or a later host run. If the state setter raises, only local staged/cache state rolls back. The backend may have committed, so the next operation reloads authoritative state before proceeding.

source accepts only valid published shared legacy 1.0.0, 1.1.0, or 1.2.0 state. All version 2.x sources are rejected. There is no private prototype v2 converter. The migration path does not claim that nullable legacy fields are normalized, that lossy legacy data becomes canonical automatically, or that unversioned private shapes are accepted. An existing source.data.migration field is rejected, never overwritten. Any source.data.pythonIngestion field is rejected before legacy parsing, including null, empty, foreign and recognized profiles. It is neither relocated nor carried into v2 where it could become active bookkeeping. Other unknown application fields remain at their original locations.

Nonempty legacy ingestedPositions requires deliveryEvidence. When supplied, this evidence must contain sourceDigest matching the source snapshot, a nonblank evidenceId, complete=true, and canonical messages from Message.to_dict(). It must cover all accepted inputs, including evicted inputs and every accepted revision. The only additional field is messagePositions, required when legacy ingestedPositions is nonempty and optional otherwise. When present, it must align one-for-one with messages. Each entry is null for an unpositioned input or exactly producer (a nonblank string) and position (a nonnegative integer, not a boolean). Attributed producers and their maximum positions must match ingestedPositions. This checks consistency, not completeness. Positions are never inferred from public message IDs or naming heuristics.

A complete, independent completionEvidence journal is required for any retained history, a truncation field, nonempty session state or ingestedPositions, or nonempty supplied delivery messages. An empty delivery journal alone does not require it. The journal must contain exactly sourceDigest matching the source snapshot, a nonblank evidenceId, complete=true, and results, including pruned original terminal results. Each result needs correlationId, the original outcome, completedAt, and response with messages, plus error for failures. resultExpiresAt is forbidden. An empty results list asserts there were no completions and is valid only without retained responses. requireKnownOutcomes=false cannot waive these requirements. Transcript fragments, accepted inputs, and later acknowledgements do not establish original outcomes. Imported results receive a new bounded delivery grace period from the migration clock plus the configured delivery window. Their original completedAt values remain unchanged, and identical retries never extend the deadline.

Full request example for a fresh source. Before submitting, replace the sourceDigest placeholder with agent_framework_durabletask.state_snapshot_digest(source) computed from the unmodified source object.

{
  "source": {
    "schemaVersion": "1.1.0",
    "data": {
      "conversationHistory": []
    }
  },
  "sourceDigest": "<computed source digest>",
  "sourceSessionId": "legacy-provider:source-session",
  "destinationSessionId": "@agent@dest-session",
  "migrationId": "migration-1",
  "ownershipTransferId": "transfer-1"
}

The optional migration helper budget max_state_bytes is a neutral admission limit, not a pressure policy. If the fully staged destination exceeds that bound, migration fails with StateCapacityError. It does not prune transcript, receipts, session state, or unknown JSON to fit. Runtime transcript retention is configured separately below.

Session Restoration

If the legacy source carries external session state, its original session_id is preserved and used by later host runs only after the migration is committed into the destination. Migration is the boundary that transfers that destination binding. A nonblank saved session_id must match sourceSessionId. An absent, null or blank saved ID is filled from sourceSessionId. Within a session object, state must be an object when present. Only omission defaults to {}, not null, a list or a scalar. service_session_id may be absent, null, any string or a JSON object, matching Core 1.13 and 1.16's str | ServiceSessionId | None contract, where ServiceSessionId is Mapping[str, Any]. Empty strings and objects are accepted. Object keys must be strings, but no particular keys are required and values may be any strict JSON, including unknown metadata. Service IDs, state values (including registered-type JSON), and unknown session siblings are preserved without trimming, Core session deserialization, provider imports or provider calls during migration. Cold session restoration preserves structured IDs. Continuation still requires a compatible agent/provider, since Core's generic chat Agent requires a string service ID.

Workflow Starts and Child Identity

Workflow start input wrapping is not universal. DurableWorkflowClient and generated HTTP start routes wrap new inputs for host-generated workflows. Application-owned native orchestrators retain their original input contracts. Do not wrap every orchestration payload in this envelope.

Generated root starts take public application JSON, not internal checkpoint data. The workflow client and generated HTTP routes remove reserved child markers. Application input is sanitized before typed reconstruction. At generated workflow entries, internal __subworkflow_input__ and __subworkflow_address__ markers require an actual SDK-reported parent and a child address consistent with both SDK parent and current instance IDs. Missing or mismatched provenance is rejected before checkpoint decoding. A native application parent may still call a generated workflow with ordinary application JSON inside wrap_workflow_input(...). Without internal child markers, that payload remains application input, even though the SDK reports a parent.

SDK parent metadata authenticates only the immediate parent/child relationship, not the full ancestry or claimed root's authority. It does not protect against a malicious application parent supplying an otherwise consistent envelope. Trusted worker and application deployments remain required. The internal checkpoint codec still uses pickle and is not safe for arbitrary untrusted input.

Generated child-ID scheme 1 always produces a physical instance ID of exactly 74 ASCII characters, dafxsw_v1_ followed by the full 64-character SHA-256 hex digest. The digest covers a domain separator and length-framed UTF-8 values for the parent's actual instance ID, the exact executor ID and the dispatch ordinal in decimal. Dispatch and provenance helpers use this same derivation at every hop. The hash checks address consistency, not authentication. Bounded physical IDs do not imply unlimited nesting, as logical paths and payloads still grow with depth and their limits still apply.

Original executor IDs and workflow names, ~-qualified HITL paths and the root notification address remain unchanged. Clients follow actual child IDs in the parent's subworkflows status map at each hop. They must not parse physical child IDs or reconstruct addresses from their names.

For a known standalone DurableTaskSchedulerClient, an explicit root ID must be nonblank, contain 1-100 printable ASCII characters and not start with @. It is validated, never rewritten or hashed. Generic TaskHubGrpcClient behavior is preserved because its backend is unknown. The Functions generic route validator retains its existing 100-character limit and Unicode-aware rules, not an ASCII-only rule, because its provider is unknown. Application-owned native orchestrator calls are unchanged.

Invocation and State Boundaries

There is no automatic transcript recovery for a rejected service conversation ID. A specifically rejected parent response can receive up to three additional identical-request retries for a visibility delay, but only before any output, tool execution or session advance. The saved parent ID is not cleared.

Streaming fallback requires an immediate capability refusal, not awaited setup, iteration or finalization errors. Async streaming remains supported. Activity state deltas compare a detached receiving-worker encoding, not producer pickle bytes. Explicit writes retain their intent, and in-place JSON changes remain type-sensitive.

Requires Python 3.10+, durabletask>=1.11.0,<2 for scoped JSON decoding, SDK parent metadata and corrected nested task completion, agent-framework-core>=1.19.0,<2 and pydantic>=2.11,<3 for structured response handling.

History Provider Integration

Ownership is resolved per run from its store option, then the agent's default_options, then the client's default. On client-owned runs, the active primary's load and storage flags select history and appends. skip_excluded=True omits compaction exclusions from later model context.

Append staging is atomic in memory, undoing lazy internal-ID repairs and restoring the transcript, working buffers, position indexes and append ordinal if staging fails. Earlier successful saves and flushes, including the preliminary flush in save_messages(), remain intact. This does not commit to the backend or roll back external provider writes.

Requests and responses are checked as stored transcript JSON before publication. Failed-run finalization retains pending tool results if filtering or staging fails, allowing a retry.

Core sessions are loaded and staged through a private entity-backed SessionStore adapter. It uses the existing entity session field, not a separate backend or commit. The agent still creates its working session, and only the durable history adapter's temporary message/index buffers are omitted from its snapshot. Snapshot copies isolate saved state from failing custom decoders and retained working-session references. External history providers keep their own transcripts and provider state. This does not make external writes transactional or migrate history between providers. The adapter relies on Core's experimental store contract and is not a public storage configuration API.

The history bridge requires an exact 2.0.0 snapshot. Even get_messages() may repair missing or duplicate internal IDs, so it is not a read-only inspection path. Mutation-capable hooks reject legacy and unsupported versions before changing stored history or working state. Use read_agent_state() for legacy inspection instead.

External primary providers, service-owned history and store-only audit sinks keep separate roles. Preparation permits only one DurableHistoryProvider, including an injected or replaced primary. Different source IDs do not create separate durable transcripts. Multiple durable adapters are rejected before model execution, even when loading or all store flags are disabled, because loading and flushing can still update shared history. Use an ordinary store-only HistoryProvider with a distinct source ID for an independent audit store. A single load-disabled durable adapter may audit an external primary into the canonical transcript. Its stored messages and public IDs do not acknowledge input delivery. Only the active primary's acceptance evidence contributes ingestion receipts, so an audit cannot suppress a later retry. On a service-owned turn, the inactive external primary's custom hooks are also suppressed because they may load or persist history directly. Ownership-independent work belongs in a separate context provider or store-only sink. Client-owned turns retain the original primary's hooks and resources. External-primary observation uses its current storage flags without rebinding custom hooks. Compaction aimed at an inactive external history source does not run its stored-history after hook. Its before hook still operates on unrelated current context, as Core specifies. Ordinary input and response IDs are preserved. Newly generated compaction summary occurrences get unique IDs when a strategy reuses a candidate ID, so both summary revisions and their links survive. Reconciliation distinguishes loaded occurrence IDs from unallocated summary IDs and compares summary revisions with JSON-exact payloads. With no primary, history is injected before a matching before-compaction provider. Core runs before hooks forward and after hooks in reverse, so that provider's after hook sees the previously stored history. After-only compaction retains the existing append-then-compact order. Per-service-call history uses Core's middleware cadence and does not run compaction hooks per model call. Injection honors a single configured compaction history source. Conflicting sources require an explicit primary instead of silently selecting a source that cannot serve all configured hooks. All explicit primary providers retain the user's hook order, including a built-in in-memory provider replaced with durable history. With [compaction, history], before compaction cannot see history loaded later, while reverse after hooks append the current input and output before compaction. With [history, compaction], before compaction sees loaded history, while the ordinary per-run after compaction runs before the current turn is appended. Choose the order explicitly for the strategy's intended inputs. Preparation does not mutate the caller's agent or provider list. Provider namespaces must be unique, including store-only sinks, and conflicts with an injected history source raise an error rather than automatically reassigning a namespace.

Canonical media keeps its declared content kind rather than inferring it from the URI scheme. Literal Core input preserves absent versus explicit-null function results and error details on the first canonical write and later cold reads. Plain Core objects without an attached input envelope keep canonical nullable defaults. Existing shared JSON retains its raw field presence. New response timestamps without an offset are interpreted as UTC. Valid stored timestamps retain their original offset and fractional precision.

Retention and State Budgets

The retention default is retention="keep_all". When wrapping DurableTaskSchedulerWorker, an omitted max_state_bytes now enables its known 1 MiB pressure budget. Old eligible transcript groups are removed only under pressure, not merely because model compaction excluded them. Pass max_state_bytes=None to preserve the previous disabled-budget behavior. Generic workers and Azure Functions remain disabled by default and require an explicit budget.

This default change can delete stored history in existing standalone DTS applications that omit the budget. Eager pruning remains opt-in and independent of pressure eviction.

  • retention="follow_compaction" physically removes eligible compaction exclusions when durable history is flushed, even without a pressure budget. It does not configure a compaction strategy.
  • A positive integer max_state_bytes enables pressure eviction, including with keep_all. At high_watermark=0.85, the runtime plans removal of oldest eligible atomic message groups toward low_watermark=0.70, or the protected floor if it is higher but still below the high watermark. Watermarks must be finite and satisfy 0 < low_watermark < high_watermark <= 1.
  • Explicit max_state_bytes=None disables pressure eviction, not eager pruning. Booleans, zero and negative budgets are rejected.

The estimate covers the whole entity using Python's default JSON serialization with ASCII escaping, including non-text content, delivery records, session state, metadata and truncation records. It excludes transport framing and is not a backend acceptance guarantee. max_state_bytes="backend_limit" is a 1 MiB (1_048_576 bytes) convenience only when the wrapped worker is a DurableTaskSchedulerWorker. A generic TaskHubGrpcWorker cannot resolve it. AgentFunctionApp rejects it, even when Functions uses DTS. Use an explicit positive budget or None there.

The local emulator tests compare actual persisted JSON bytes with the estimate, exercise near-limit default/opt-out behavior, and verify incoming entity-operation payload rejection. The tested emulator did not reject oversized entity state with budgeting disabled. These tests do not establish Azure's hard state-rejection boundary, and a state budget cannot prevent an oversized input or output from exceeding its separate transport limit.

Pressure eviction leaves terminal results, completion and ingestion receipts, session/control state, opaque unknown entries and retained entry metadata intact. System messages and the latest exchange are protected, and linked tool, reasoning and persisted atomic groups are not split. Eager pruning also protects pending tool calls and groups with any included member. These protections can prevent the requested reduction. A protected floor at or above the high watermark, or an unreachable safe target, raises StateCapacityError without applying a partial pressure plan. The enclosing run restores its local staged/cache state on failure. This does not undo model, tool or external-provider side effects. If a host state write raises, backend acknowledgement is unknown, not proof of rollback. The next operation reloads authoritative state.

Registration and History Ownership

DurableAIAgentWorker sets host defaults. At construction, omission or INHERIT uses the known DTS budget when available, otherwise no budget. Explicit None always disables pressure eviction. add_agent() and configure_workflow() accept retention, max_state_bytes, both watermarks and response_delivery_window_seconds overrides. Workflow settings apply to its agent entities and nested workflows, not one aggregate workflow budget. For budget overrides, omission or INHERIT from agent_framework_durabletask uses the host default, while explicit None disables pressure eviction. For the other overrides, None inherits. Shared workflow registrations must have matching settings.

AgentRegistrationSettings retains its positional delivery-window and callback parameters. Its added retention and budget fields are keyword-only and default to non-evicting behavior.

A hand-configured DurableHistoryProvider(prune_excluded=...) keeps its explicit value, including False, ahead of the inherited eager-pruning policy. This does not disable a pressure budget. An exact built-in in-memory primary is replaced with durable history while preserving its source_id and storage settings. An external primary, including an in-memory subclass, stays primary without a second durable primary. More than one load-enabled primary is rejected. Local retention does not delete external-provider history.

History ownership is resolved per run. A service-owned conversation bypasses durable history loading and eager compaction flushes. A configured pressure budget still checks local entity state and can evict eligible older local transcript, but it neither compacts the remote conversation nor clears the saved service branch ID.

Delivery and Maintenance

Canonical terminalResults hold immutable original response envelopes by correlation ID, separately from transcript history. Matching completionReceipts retain correlationId, known outcome (succeeded or failed) and completedAt. Results include the full response, including messages, metadata and an optional explicit value, even null or falsey. Failures require error.code and error.message, while successes forbid error. resultState="available" requires a matching result. resultState="unavailable" forbids a result and requires resultUnavailableAt. Completion facts and any resultExpiresAt agree across the pair. See the shared wire invariants.

Staging rejects unsupported live response fields before Core serialization, without invoking their conversion hooks. Supported lazy values keep their existing policy. Duplicate completions remain no-ops without inspecting the replacement producer.

Response availability has a default response_delivery_window_seconds=60. Transcript deletion does not delete the independent result or its completion outcome. Response expiry removes delivery payloads without deleting completion or ingestion receipts. There is no bounded receipt cleanup, so receipt growth alone can exhaust a configured budget. Runs perform expiry checks, but idle entities need application-owned maintenance to invoke expire_responses(). Polling reads do not persist cleanup. reset clears local transcript and session state while preserving completion receipts and live results. External-primary reset is rejected without a provider-owned clear operation.

Generated agent entities on both Python hosts support only run, reset, expire_responses, migrate and delete. Raw state setters and implementation helpers are not entity operations. The deprecated run_agent operation has been removed; direct callers must use run.

reset returns no value. expire_responses returns the integer number of expired delivery payloads removed by that execution, or zero for a successful no-op. Failures use the SDK operation failure channel. A completed call acknowledges execution; a signal acknowledges only enqueueing. The expiry count is not an idempotent receipt: an execution can remove payloads and lose its reply, then a retry can return zero. Both hosts use these native results rather than host-specific wrappers.

delete is a privileged administrative operation that removes the entire entity, including conversation state, live results, completion receipts and ingestion evidence. It is not a reset or expiry operation. Reusing that entity key no longer benefits from its previous duplicate-run protection, and queued operations can recreate it. Operators must quiesce and fence callers when deletion must be final. Generated HTTP and MCP routes do not expose these maintenance operations.

At or after a delivery deadline, lookup reports completion with its retained outcome, even before physical cleanup. It never returns the expired payload, reports pending work, reruns the request or reconstructs an original from compacted or external history. Shared v2 has no unknown outcome.

Retention Metrics

The agent_framework.durabletask meter emits durable.retention.* metrics for evaluations, resolved budgets, before/after JSON size, staged message and entry removals, reclaimed bytes, capacity failures, write attempts and observed run operations. Labels are bounded categories, not agent, session, correlation or message identifiers, content or exception text.

Deletion measurements describe staged local state with commit_status="not_attempted". Write observations distinguish stage="serialization" from stage="set_state", each with outcome="returned" or "failed". Successful serialization and transition validation record "returned" with commit_status="not_attempted" before the host call. A serialization failure records only that stage. After a set_state attempt, write and operation observations use commit_status="unknown" even when the call returns. Neither status confirms a durable commit, and staged deletions may later roll back locally.

Instrumentation uses only the OpenTelemetry API. The package does not configure an SDK, metric reader or exporter. Applications own that setup, and telemetry failures do not replace the operation's result or error.

Workflow HITL and Mixed Parent/Child Execution

Non-agent request_info replies are reconstructed and validated inside the registered response activity against the request's recorded type. The activity result checkpoints accepted or invalidreply, so orchestration replay consumes that outcome instead of rerunning reply validators. Rejected replies leave the request pending for a correction. Handler and output-serialization failures remain activity failures, not invalid replies. Activity retries or redelivery can still rerun application code.

Response-type descriptors retain declared types and generic arguments for supported list, dict, tuple, set, union and Any annotations. Coercion and type checks follow the installed Core version, so nested model/dataclass and tuple/set reconstruction from JSON is Core-dependent. Custom types must already be loaded under their recorded module and qualified name. This does not make every Python annotation a supported reply type. Tuple descriptors preserve the declared argument shape, including Python 3.10's explicit empty-tuple argument. They do not assume all tuple aliases admit the same JSON or native tuple values.

Valid replies for known fixed request IDs can arrive before their waits are published and remain buffered by the service. Sending a reply acknowledges event delivery, not type validation or handler success. Nested replies still require an active child path recorded by the parent. Pending event waits survive other replies and mixed waves, including valid replies already buffered for them.

Parent and child HITL requests can remain active together. Ready parent handlers and downstream work proceed without waiting for paused children. A ready child's downstream work can answer an earlier paused child. Within a ready wave, results route in dispatch order and preserve each result's message order. Local tasks read the dispatched state snapshot, and their reported updates and deletes merge in dispatch order before downstream work or reply handlers run. Across waves, recorded readiness defines order, not original child invocation order. This preserves durable snapshots, not Core's in-process visibility of other executors' uncommitted writes.

JSON runtime boundary

The current runtime writes canonical 2.0.0 state and supports the explicit migration above. Construct DurableAIAgentWorker before starting the SDK worker. Generated entity state and run/migrate inputs, blocking framework agent results, generated workflow starts, child results and external-event values use plain JSON rather than SDK custom-object reconstruction. Native co-hosted work keeps its original converter behavior. The integrated JSON boundary guide details both hosts, custom-converter constraints and separate checkpoint trust requirements.

Basic Usage Example

from agent_framework import Agent
from agent_framework.openai import OpenAIChatCompletionClient
from agent_framework_durabletask import DurableAIAgentWorker
from durabletask.worker import TaskHubGrpcWorker

# Create the worker
worker = TaskHubGrpcWorker(host_address="localhost:4001")

# Only use isolated_v2 for a new isolated hub with upgraded clients.
# Do not point this host at an existing hub and replay historical runs after updating.
agent_worker = DurableAIAgentWorker(worker, deployment_mode="isolated_v2")

chat_client = OpenAIChatCompletionClient()
my_agent = Agent(client=chat_client, name="assistant")
agent_worker.add_agent(my_agent)

For more details, review the standalone Durable Task samples and the full Agent Framework Python documentation.

Metadata

Release files for agent-framework-durabletask 1.0.0b261029

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for agent-framework-durabletask 1.0.0b261029
File Size Uploaded Provenance Actions
agent_framework_durabletask-1.0.0b261029.tar.gz 224.0 kB Without provenance Details for agent_framework_durabletask-1.0.0b261029.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for agent-framework-durabletask 1.0.0b261029
File Interpreter ABI Platform Actions
agent_framework_durabletask-1.0.0b261029-py3-none-any.whl Python 3 none any Details for agent_framework_durabletask-1.0.0b261029-py3-none-any.whl

Total release size: 462.0 kB

Release files / agent_framework_durabletask-1.0.0b261029.tar.gz

Download URL agent_framework_durabletask-1.0.0b261029.tar.gz
Size 224.0 kB
Tags Source
SHA-256 checksum
How to use checksums
853fb7a91718fe6a3aa7220f35f41a97402150756b4f93a73e43cbfcbb88c73a
BLAKE2b-256 checksum
How to use checksums
8b6301681df1e68a811ec0721422727dc8a3043579c1386540ec2bc7feb6e9e7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via RestSharp/106.13.0.0

Release files / agent_framework_durabletask-1.0.0b261029-py3-none-any.whl

Download URL agent_framework_durabletask-1.0.0b261029-py3-none-any.whl
Size 238.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
9dd108b3b06de10ab32b82469d7588aea19e63b62cc2920d940b44f85e299c90
BLAKE2b-256 checksum
How to use checksums
d72eb8112184b9dc2bed4eb67d8f34f575a98377678dfc61a940099aa77858bf
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via RestSharp/106.13.0.0

Security for agent-framework-durabletask 1.0.0b261029

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page