Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
3be2a20
docs: add ADR 0032 for durable thread compaction
ahmedmuhsin Jul 28, 2026
63604bc
feat: back durable agent history with a core HistoryProvider (ADR 0032)
ahmedmuhsin Jul 31, 2026
9a2dfc3
feat: forward workflow conversation context to durable agents (ADR 00…
ahmedmuhsin Jul 30, 2026
d205416
feat: back registered agents with durable history automatically (ADR …
ahmedmuhsin Jul 30, 2026
406611d
fix: correct history ownership for external and service-managed agent…
ahmedmuhsin Jul 30, 2026
4639bd4
feat: extend prune_history and workflow context forwarding to the Fun…
ahmedmuhsin Jul 30, 2026
62b6ce3
refactor: remove duplicated host-adapter logic and enforce the contex…
ahmedmuhsin Jul 30, 2026
c3bf401
chore: ignore local .env files
ahmedmuhsin Jul 31, 2026
72fab1c
fix: honor explicit store option and give history providers a stable …
ahmedmuhsin Jul 31, 2026
af5798a
docs: record history-ownership rules and entity lifetime concerns in …
ahmedmuhsin Jul 31, 2026
3ddb589
test: add samples and integration coverage for durable history and co…
ahmedmuhsin Jul 31, 2026
29198bd
feat: persist the agent session so provider state survives across turns
ahmedmuhsin Jul 31, 2026
7e7a821
fix: restore session state values as their own types after a cold start
ahmedmuhsin Jul 31, 2026
0775b58
docs: sharpen ADR 0032's account of the store-side compaction gap
ahmedmuhsin Jul 31, 2026
cc74aff
docs: complete the record of deferred core gaps in ADR 0032
ahmedmuhsin Jul 31, 2026
36b7fda
docs: tighten ADR 0032 for coherence and length
ahmedmuhsin Jul 31, 2026
23f9aaa
docs: drop em dashes and prose semicolons from this branch's content
ahmedmuhsin Jul 31, 2026
c63cef7
docs: revert ADR 0032 to proposed and clear deciders/consulted
ahmedmuhsin Jul 31, 2026
b868498
test: cover the persisted session in integration, and fix two tests t…
ahmedmuhsin Jul 31, 2026
ebad2f0
fix: satisfy mypy on the tests, which CI runs and I did not
ahmedmuhsin Jul 31, 2026
56c29c0
fix: make the redis lrange result typing environment independent
ahmedmuhsin Jul 31, 2026
002b9ef
fix: keep compaction reconciliation correct when messages shift or re…
ahmedmuhsin Jul 31, 2026
1f38a6b
fix: derive synthesized message ids from persisted state, not object …
ahmedmuhsin Jul 31, 2026
65507c9
fix: put the new samples on Foundry, which is what CI provisions
ahmedmuhsin Jul 31, 2026
8a6de7e
fix: close four defects review found in the entity's context and sess…
ahmedmuhsin Jul 31, 2026
423d846
docs: rework ADR 0032 around capacity, and correct what review found …
ahmedmuhsin Aug 13, 2026
5587159
feat: bound durable entity state so an agent stops failing at the bac…
ahmedmuhsin Aug 14, 2026
c852c27
feat: make workflow duplicate detection survive retention, and declar…
ahmedmuhsin Aug 14, 2026
15f77b4
test: drive retention through the real entity, and document the modes
ahmedmuhsin Aug 14, 2026
428aef6
docs: settle whether blob offload can reach the Durable Functions Pyt…
ahmedmuhsin Aug 14, 2026
d2ef652
docs: tighten ADR 0032 around the actual decision
ahmedmuhsin Aug 14, 2026
b586219
refactor: remove the unshipped prune_history alias
ahmedmuhsin Aug 14, 2026
26cf838
fix: keep a turn alive when the service forgets the id it just issued
ahmedmuhsin Aug 18, 2026
8df0abd
fix: say why a turn failed instead of returning an empty reply
ahmedmuhsin Aug 18, 2026
9744f00
fix: let the Azure Functions app actually set the state budget
ahmedmuhsin Aug 18, 2026
5de3a55
perf: stop serializing the whole conversation twice per turn
ahmedmuhsin Aug 19, 2026
4348ef1
docs: give the Redis sample provider a way to close its connection pool
ahmedmuhsin Aug 19, 2026
21de1b4
fix: hand out a copy of stored annotations, not the stored dict
ahmedmuhsin Aug 19, 2026
fb0b649
docs: record why the state budget counts characters
ahmedmuhsin Aug 19, 2026
b2a67fe
docs: say why the orchestrator reads two private attributes
ahmedmuhsin Aug 19, 2026
1ceced7
fix: decide what an entry is by its type, not by a flag beside it
ahmedmuhsin Aug 24, 2026
840da64
fix: stop evicting a response before its caller has read it
ahmedmuhsin Aug 24, 2026
5b8d6fd
docs: describe the two new conversation entry kinds in the schema
ahmedmuhsin Aug 24, 2026
0358c50
fix: budget durable state by what it weighs, not by how much of it is…
ahmedmuhsin Aug 28, 2026
62cafd1
fix: decide who owns a conversation per run, not once at registration
ahmedmuhsin Aug 28, 2026
761f631
fix: stop keeping a second copy of a conversation somebody else stores
ahmedmuhsin Aug 28, 2026
d80da42
fix: ask again before resending a whole transcript
ahmedmuhsin Aug 28, 2026
f4ec229
feat: record in the state itself when retention has removed something
ahmedmuhsin Aug 28, 2026
ca58947
docs: say which store bounds what, and stop describing gaps as oversi…
ahmedmuhsin Aug 28, 2026
17e5f15
fix: let the service own its conversation, and stop insuring against it
ahmedmuhsin Aug 28, 2026
0b21934
feat: answer a request once, however many times it is delivered
ahmedmuhsin Aug 28, 2026
a72347f
docs: correct what the orchestration and the entity each record
ahmedmuhsin Aug 28, 2026
6c1dbda
docs: name context mode as the first answer to workflow capacity
ahmedmuhsin Aug 28, 2026
b80368f
docs: stop counting blob offload as portable, and state the upstream …
ahmedmuhsin Aug 28, 2026
ba80cf3
docs: state the purity contract a context_filter takes on under durable
ahmedmuhsin Aug 31, 2026
7419093
docs: point the context_filter contract at its tracking issue
ahmedmuhsin Aug 31, 2026
70197f2
docs: correct three docstrings that no longer matched the code
ahmedmuhsin Aug 31, 2026
1f7eb28
fix: make the entry kinds a contract rather than four unused definitions
ahmedmuhsin Aug 31, 2026
00173bb
fix: carry the session forward when a turn fails
ahmedmuhsin Aug 31, 2026
3b6810b
Merge origin/main into python/durable-thread-compaction
ahmedmuhsin Sep 2, 2026
c4582a1
Export the retention knobs from the durabletask package root
ahmedmuhsin Sep 2, 2026
1b70629
fix: align durable execution and history with ADR 0032
ahmedmuhsin Sep 9, 2026
e1b3166
docs: update durable samples and validation status
ahmedmuhsin Sep 9, 2026
1aac4fd
fix: preserve durable workflow and execution contracts
ahmedmuhsin Sep 10, 2026
3ad9d6c
docs: define isolated v2 rollout and validated behavior
ahmedmuhsin Sep 10, 2026
5b872d1
docs: separate prototype from ADR review
ahmedmuhsin Sep 10, 2026
9b4550d
fix: isolate integration launches and reject incompatible delivery state
ahmedmuhsin Sep 10, 2026
cf1657b
feat: retain completion outcomes and validate retention boundaries
ahmedmuhsin Sep 12, 2026
7926226
test: guard Windows process flags for Linux typing
ahmedmuhsin Sep 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -445,3 +445,7 @@ FodyWeavers.xsd
*.msix
*.msm
*.msp

# Local environment files with credentials (templates use .env.example and stay tracked).
# Must come after the '!python/packages/**' negation above so it wins for test .env files.
**/.env
23 changes: 13 additions & 10 deletions docs/features/durable-agents/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

## Overview

Durable agents extend the standard Microsoft Agent Framework with **durable state management** powered by the Durable Task framework. An ordinary Agent Framework agent runs in-process: its conversation history lives in memory and is lost when the process ends. A durable agent persists conversation history and execution state in external storage so that sessions survive process restarts, failures, and scale-out events.
Durable agents extend the standard Microsoft Agent Framework with **durable execution state** powered by the Durable Task framework. Ordinary agents can already use in-memory, external-provider or service-owned history. Durable hosting persists execution and session control so that compatible workers can continue sessions across process restarts and scale-out.

| Capability | Ordinary agent | Durable agent |
| --- | --- | --- |
| Conversation history | In-memory only | Durably persisted |
| Failure recovery | State lost on crash | Automatically resumed |
| Multi-instance scale-out | Not supported | Any worker can resume a session |
| Conversation history | Selected provider or model service | Selected owner, with durable-backed local history when configured |
| Failure recovery | Application-owned | Persisted orchestration and entity state; uncommitted external effects can repeat |
| Multi-instance scale-out | Application-owned coordination | Compatible workers serialize access to each entity |
| Multi-agent orchestrations | Manual coordination | Deterministic, checkpointed workflows |
| Human-in-the-loop | Must keep process alive | Can wait days/weeks with zero compute |
| Hosting | Any process | Console app, Azure Functions, or any Durable Task–compatible host |
Expand All @@ -18,12 +18,15 @@ Durable agents extend the standard Microsoft Agent Framework with **durable stat

## How durable agents work

Durable agents are implemented on top of [Durable Entities](https://learn.microsoft.com/azure/azure-functions/durable/durable-functions-entities) (also called "virtual actors"). Each **agent session** maps to one entity instance whose state contains the full conversation history. When you send a message to a durable agent, the following happens:
Durable agents are implemented on top of [Durable Entities](https://learn.microsoft.com/azure/azure-functions/durable/durable-functions-entities) (also called "virtual actors"). Each **agent session** maps to one entity instance. Transcript ownership and response storage depend on the runtime version and selected history provider. When you send a message to a durable agent, the following happens:

1. The message is dispatched to the entity identified by an `AgentSessionId` (a composite of the agent name and a unique session key).
2. The entity loads its persisted `DurableAgentState`, which includes the complete conversation history.
3. The entity invokes the underlying `AIAgent` with the full conversation history, collects the response, and appends both the request and the response to the state.
4. The updated state is persisted back to durable storage automatically.
2. The entity loads its persisted `DurableAgentState` and session control.
3. The underlying agent obtains context from its configured history path and executes the request.
4. Entity-local changes are persisted. External provider writes and tool effects are not part of a distributed transaction.

> [!WARNING]
> The [Python prototype in PR #59](https://github.com/microsoft/agent-framework-durable-extension/pull/59) uses schema `2.0.0`, independent response/completion storage and an explicit `isolated_v2` deployment gate. It does not require a local mirror of external/service-owned history. Existing .NET readers do not support this layout. Do not mix these writers or replay old workflow histories through the new Python engine. These are provisional [prototype deployment constraints](../../../python/packages/durabletask/README.md#version-2-deployment-warning). Design review belongs in [ADR PR #88](https://github.com/microsoft/agent-framework-durable-extension/pull/88); the agreed implementation will follow in stacked PRs after ADR approval.

Because the entity framework serializes access to each entity instance, concurrent messages to the same session are processed one at a time, eliminating race conditions.

Expand Down Expand Up @@ -110,7 +113,7 @@ Alternatively, `ConfigureDurableOptions` configures both from a single delegate
**Python example:**

```python
app = AgentFunctionApp(agents=[agent])
app = AgentFunctionApp(agents=[agent], deployment_mode="isolated_v2")
```

### Console apps / generic hosts
Expand All @@ -134,7 +137,7 @@ IHost host = Host.CreateDefaultBuilder(args)
**Python example:**

```python
worker = DurableAIAgentWorker(TaskHubGrpcWorker(host_address="localhost:4001"))
worker = DurableAIAgentWorker(TaskHubGrpcWorker(host_address="localhost:4001"), deployment_mode="isolated_v2")
worker.add_agent(agent)
worker.start()
```
Expand Down
20 changes: 20 additions & 0 deletions python/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Copyright (c) Microsoft. All rights reserved.

"""Declare isolated deployment mode for the local test hubs.

This pytest-only fixture explicitly acknowledges the tests' isolated mode. It does
not bypass the production default. Gate tests remove or replace the variable with
their function-scoped monkeypatch fixture to exercise missing and invalid modes.
"""

from collections.abc import Iterator

import pytest


@pytest.fixture(scope="session", autouse=True)
def isolated_test_deployment() -> Iterator[None]:
"""Declare isolated mode for tests and restore the environment at session end."""
with pytest.MonkeyPatch.context() as monkeypatch:
monkeypatch.setenv("DURABLE_AGENTS_DEPLOYMENT_MODE", "isolated_v2")
yield
Loading
Loading