This release is a pre-release and may not be stable for production use.
Get Started with Microsoft Agent Framework Durable Task
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:
LegacyDurableAgentStatefor exact1.0.0,1.1.0, and1.2.0inputsSharedAgentStateReaderfor exact2.0.0inputs
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_bytesenables pressure eviction, including withkeep_all. Athigh_watermark=0.85, the runtime plans removal of oldest eligible atomic message groups towardlow_watermark=0.70, or the protected floor if it is higher but still below the high watermark. Watermarks must be finite and satisfy0 < low_watermark < high_watermark <= 1. - Explicit
max_state_bytes=Nonedisables 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)
| 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)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| 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.