Extract 11 private const string fields for vector store property names
(Key, Role, MessageId, AuthorName, ApplicationId, AgentId, UserId,
SessionId, Content, CreatedAt, ContentEmbedding) and replace all inline
usages across the collection definition, store dictionary, search result
access, and filter expressions.
Fixes#3801
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add Azure AI Foundry Memory Context Provider with unit tests
* Add FoundryMemory integration tests and sample application
* Fix ClearStoredMemoriesAsync to handle 404 gracefully and rename to EnsureStoredMemoriesDeletedAsync
* Refactor FoundryMemory: simplify architecture and add memory store creation
- Remove IFoundryMemoryOperations interface (was only for test mocking)
- Remove AIProjectClientMemoryOperations wrapper class
- Provider now directly uses AIProjectClient with internal extension methods
- Extension methods return actual response models instead of extracted values
- Remove WaitForUpdateCompletionAsync from provider (sample uses delay)
- Simplify EnsureMemoryStoreCreatedAsync to return Task instead of Task<bool>
- Add memory store creation with chat_model and embedding_model
- Add UpdateMemoriesResponse with SupersededBy and Error fields
- Simplify unit tests to focus on constructor validation and serialization
- Update sample to use simple delay for memory processing wait
* Add waiting operation for memory store updates
* Fix UTF-8 BOM encoding for FoundryMemory csproj files
* Update copilot instructions for UTF-8 BOM and fix sample API rename
* Fix UTF-8 BOM encoding for TestableAIProjectClient.cs
* Add missing response headers for TS
* Changing default embedding
* Using the SDK Models
* Program update
* Remove debugging code from sample
* Adapt FoundryMemoryProvider to new AIContextProvider API and add UTF-8 BOM instruction
- Override ProvideAIContextAsync/StoreAIContextAsync instead of removed virtual InvokingAsync/InvokedAsync
- Use ProviderSessionState<State> for session-scoped state management (matching Mem0Provider pattern)
- Replace constructor-based scope with stateInitializer delegate
- Remove Serialize method (no longer on base class)
- Add SearchInputMessageFilter, StorageInputMessageFilter, StateKey to options
- Update sample to use AIContextProviders list instead of AIContextProviderFactory
- Update unit and integration tests for new API
- Add UTF-8 BOM encoding and --tl:off instructions to dotnet/AGENTS.md
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Use DefaultAzureCredential in Foundry Memory sample
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address PR review comments for FoundryMemoryProvider
- Move memoryStoreName from options to required constructor parameter
- Make FoundryMemoryProviderScope require non-null/whitespace scope in constructor
- Make Scope property read-only (getter only)
- Replace ConcurrentQueue with single last update ID to fix memory leak
- Only clear pending update ID after successful completion
- Add delete success logging
- Mark FoundryMemoryProvider with [Experimental] attribute
- Update unit tests for new API signatures
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Use Throw.IfNullOrWhitespace for scope and memoryStoreName validation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor: Normalize Run/RunStreaming with AIAgent
* refactor: Clarify Session vs. Run -level concepts
* Rename RunId to SessionId to better match Run/Session terminology in AIAgent
* [BREAKING]: Will break existing checkpointed sessions in CosmosDb due to field rename
* refactor: Rename and simplify interface around getting typed data out of ExternalRequest/Response
* Also adds hints around using value types in PortableValue
* refactor: Rename AddFanInEdge to AddFanInBarrierEdge
This will prevent a breaking change later when we introduce a programmable FanIn edge, analogous to the FanOut edge's EdgeSelector.
The goal, in the long run is to support a number of different FanIn scenarios, with naive FanIn (no barrier) by default, similar to FanOut.
* refactor: AsAgent(this Workflow, ...) => AsAIAgent(...)
* misc - part1: SwitchBuilder internal
---------
Co-authored-by: Dmytro Struk <13853051+dmytrostruk@users.noreply.github.com>
* fix: strip function_call and text_reasoning from cross-agent workflow handoff
When a reasoning model (e.g. gpt-5-mini) runs as Agent 1 in a workflow, its
response includes text_reasoning items (with server-scoped IDs like rs_XXXX)
and function_call items. Forwarding these to Agent 2 in a fresh conversation
caused API errors because the reasoning/call IDs are scoped to the original
stored response context.
Changes:
- Strip 'function_call', 'text_reasoning', 'function_approval_request', and
'function_approval_response' from handoff messages in _agent_executor.py
- Keep 'function_result' so the actual tool output content is preserved for
the next agent's context
- Update unit tests to reflect that function_result messages survive handoff
(messages grow from 2→3: user, tool(result), assistant(summary))
- Fix incorrect test assertions in test_function_invocation_stop_clears_*
that assumed the client layer updates session.service_session_id
- Also fixed _extract_function_calls to search all messages with call_id
deduplication, and the error-limit stop path to submit function_call_output
items before halting (via tool_choice=none cleanup call)
Relates to: https://github.com/microsoft/agent-framework/issues/4047
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: reasoning model workflow handoff and history serialization
Fixes multiple related issues when using reasoning models (gpt-5-mini,
gpt-5.2) in multi-agent workflows that chain agents via from_response
or replay full conversation history via AgentExecutorRequest.
## Reasoning items always emitted on output_item.added
When a reasoning model produces encrypted or hidden reasoning (no
visible text), the Responses API still fires a reasoning output item
without any reasoning_text.delta events. Previously no text_reasoning
Content was emitted in that case, making it invisible to downstream
logic. Both the non-streaming (_parse_response_from_openai) and
streaming (output_item.added) paths now always emit at least one
text_reasoning Content — with empty text if no content is available —
so co-occurrence detection and serialization guards work reliably.
## Reasoning items only serialized when paired with a function_call
The Responses API only accepts reasoning items in input when they
directly preceded a function_call in the original response. Sending a
reasoning item that preceded a text response (no tool call) causes:
"reasoning was provided without its required following item"
_prepare_message_for_openai now checks has_function_call per message
and skips text_reasoning serialization when there is no accompanying
function_call.
## summary field is an array, not an object
The reasoning item summary field sent to the Responses API must be an
array of objects ([{"type": "summary_text", "text": ...}]), not a
single object. Fixed _prepare_content_for_openai accordingly.
## service_session_id cleared when explicit history is provided
When a workflow coordinator replays a full conversation (including
function calls from a previous agent run) back to an executor via
AgentExecutorRequest or from_response, the executor's session still
held a service_session_id (previous_response_id) from the prior run.
The API then received the same function-call items twice — once from
previous_response_id (server-stored) and once from the explicit input —
causing: "Duplicate item found with id fc_...".
AgentExecutor.run (when should_respond=True) and from_response now
reset self._session.service_session_id = None before running so that
explicit input is the sole source of conversation context.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* small improvements in text reasoning
* refactor: add reset_service_session to AgentExecutorRequest for explicit history replay
Replace the implicit 'always clear service_session_id when should_respond=True'
with an explicit opt-in field on AgentExecutorRequest.
The old approach used should_respond=True as a proxy for 'full history replay',
but that conflates two distinct intents:
- Orchestrations group chat sends should_respond=True with an empty/single-message
list (not a full replay) — unnecessarily clearing service_session_id.
- HITL / feedback coordinators send the full prior conversation and truly need
a fresh service session ID to avoid duplicate-item API errors.
Changes:
- Add AgentExecutorRequest.reset_service_session: bool = False
- AgentExecutor.run only clears service_session_id when this flag is True
- AgentExecutor.from_response unchanged (always clears; always full conversation)
- Set reset_service_session=True in all full-history-replay call sites:
agents_with_HITL.py, azure_chat_agents_tool_calls_with_feedback.py,
autogen-migration round-robin coordinator, tau2 runner
- Update _FullHistoryReplayCoordinator test helper to pass the flag
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* comment update
* fixes from feedback
* fix test
* reverted changes to agent executor
* fix: remove reset_service_session from tau2 runner
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* two other reverts
* fix sample
---------
Co-authored-by: Giles Odigwe <79032838+giles17@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix handoff orchestration not passing user message to handoff target agent (#3161)
Filter out internal handoff function call and tool result messages before
passing conversation history to the target agent's LLM. These messages
confused the model into ignoring the original user question.
* Add handoff tool call filtering behavior and enhance workflow builder
- Introduced HandoffToolCallFilteringBehavior enum to specify filtering behavior for tool call contents in handoff workflows.
- Updated HandoffsWorkflowBuilder to support customizable handoff instructions and tool call filtering behavior.
- Enhanced HandoffAgentExecutor to utilize new filtering options for improved message handling during agent handoffs.
* Enhance handoff message filtering logic and add unit tests for filtering behaviors
* Refactor HandoffMessagesFilter to remove unused handoff function names and enhance filtering logic for non-handoff function calls
* Refactor HandoffMessagesFilter to streamline FilterCandidateState initialization and improve clarity
* Refactor HandoffMessagesFilter to improve filtering logic and add integration tests for handoff workflows
* fix: HandoffAgentExecutor tests
* [BREAKING] refactor: Decouple Checkpointing and Execution APIs
With this change, Checkpointing becomes an property of an IWorkflowExecutionEnvironment. This lets environments that are tightly-coupled to their CheckpointManager avoid needing to present APIs that would not work (e.g. taking in an InMemory CheckpointManager for Durable Tasks, for example)
* refactor: Normalize IsCheckpointingEnabled naming
- Rename UserNameProvider → UserMemoryProvider
- Use session state (state dict) instead of instance variables
- Use context.extend_instructions() instead of context.instructions.append()
- Use DEFAULT_SOURCE_ID class attribute
- Fix imports to use public agent_framework API
- Add session state inspection at end of sample
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Track the last CheckpointInfo in InProcessRunner so that newly created
checkpoints reference their parent. When resuming from a checkpoint,
the resumed-from checkpoint becomes the parent of the next checkpoint.
Adds tests verifying:
- First checkpoint has null parent
- Subsequent checkpoints chain parents correctly
- Checkpoint after resume references the resumed-from checkpoint
* feat: Implement Polymorphic Routing
* feat: Add support for Send/Yield annotations with basic Executor
* Adds annotations to Declarative workflow executors
* fix: Address PR Comments
* Implicit filter in collection loops
* Remove debug / usused / superfluous code
* Fix ProtocolBuilder implicit output registrations
* Fix logic error in ExecuteRouteGeneratorTests.ClassWithManualConfigureProtocol_DoesNotGenerate
* fix: Solidify type checks and send/yield type registrations
* fix: Suppress generation of TurnTokens out of AggregateTurnMessagesExecutor
* Fixes an issue where ConcurrentEndExecutor is not expecting TurnTokens.
* fix: Add ProtocolBuilder support for chained-delegation
* Updates Declarative pacakge to rely on chained-delegation Send/Yield registration
* Renames DeclarativeActionExectuor's new ExecuteAsync to ExecuteActionAsync to avoid colliding with Executor.ExecutoeAsync
* fix: Address PR Comments
* Fixes type mapping in FanInEdgeRunner
* Fixes and expalins send/yield type registration in FunctionExecutor
* fixup: build-break
* fix: Add missing SendsMesage declaration to InvokeAzureAgentExecutor
* Fix FoundryAgents_Step15_ComputerUse sample for Azure Agents API
The Azure Agents API rejects previous_response_id alongside computer_call_output
items, unlike the vanilla OpenAI Responses API. This fix:
- Send all prior response output items (reasoning, computer_call, etc.) as input
items in follow-up calls so the API has full conversation context
- Create a fresh session per call to avoid ConversationId/previous_response_id
- Use currentCallId instead of initialCallId for computer_call_output
- Clear ContinuationToken after polling to prevent stale tokens
- Remove unused initialCallId tracking variable
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address comments
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Initial Implementation of InvokeFunctionTool
* Added unit test for InvokeFunctionTool executor.
* Implemented unit and integration tests for InvokeFunctionTool.
* Add sample for InvokeFunctionTool in declarative workflows.
* Remove unused sample and updated comments.
* Updating to official OM release with InvokeFunctionTool
* Fix formatting issues.
* Updated PowerFx version
* Update test fixture
* Cleanup - Removed unused method in InvokeFunctionToolExecutor
* Update test based on PR feedback.
* Update based on PR comments
* Rename WorkflowOutputEvent.SourceId to ExecutorId for Python consistency
- Rename SourceId property to ExecutorId in WorkflowOutputEvent
- Add [Obsolete] SourceId property for backward compatibility
- Update all test usages to use ExecutorId
Resolves part of #2938
* Unify AgentResponse events with WorkflowOutputEvent (#2938)
- Change AgentResponseEvent and AgentResponseUpdateEvent to inherit from
WorkflowOutputEvent instead of ExecutorEvent
- Update AIAgentHostExecutor and HandoffAgentExecutor to use YieldOutputAsync()
instead of AddEventAsync() for agent outputs
- Add special-casing in InProcessRunnerContext.YieldOutputAsync() to create
specific event types for AgentResponse and AgentResponseUpdate, bypassing
OutputFilter for backwards compatibility
- Update TestRunContext and TestWorkflowContext with same special-casing
- Add regression tests in AgentEventsTests
* refactor: Seal AgentResponse events
- Update ModelContextProtocol NuGet package from 0.4.0-preview.3 to 0.8.0-preview.1
- Update System.Net.ServerSentEvents from 10.0.1 to 10.0.3
- Fix OAuth config to use DynamicClientRegistration in Agent_MCP_Server_Auth
- Fix incorrect sample name references in README files
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Initial plan
* Add Foundry evaluation samples for Red Teaming and Self-Reflection
Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com>
* Refactor evaluation samples with real implementations in local functions
Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com>
* Uncomment function signatures and bodies, keep only invocations commented
Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com>
* Update Foundry evaluation samples with observability support
* Restructure evaluation samples to follow FoundryAgents naming convention
- Rename Evaluation/Evaluation_StepXX to FoundryAgents_Evaluations_StepXX
- Add evaluation projects to slnx
- Fix var usage, apply dotnet format, use DefaultAzureCredential
- Add try/finally for agent cleanup
- Fix evaluator deployment name separation in Step02
- Update README references
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Rewrite Step01 to use Azure.AI.Projects RedTeam API and address review comments
- Replace safety evaluator sample with actual Red Teaming using AIProjectClient.RedTeams
- Use AttackStrategy (Easy, Moderate, Jailbreak) and RiskCategory from Azure.AI.Projects
- Remove Microsoft.Extensions.AI.Evaluation.Safety dependency from Step01
- Add DefaultAzureCredential warning comments to Step02
- Remove unused bestResponse variable in Step02
- Add session isolation comments in self-reflection loop
- Fix stale directory references in READMEs
- Fix misleading evaluation overview link in main README
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add note about agent-targeted red teaming limitations in README
The .NET RedTeam API currently only supports model deployment targets
via AzureOpenAIModelConfiguration. Agent-targeted red teaming with
AzureAIAgentTarget is documented in concept docs but not yet available
in the SDK's RedTeam constructor. Results appear in classic portal view.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add classic Foundry disclaimer to red teaming sample README
Clarify that this sample uses the classic Azure AI Foundry red teaming
API (/redTeams/runs). The new Foundry portal uses a separate evaluation-
based API not yet available in the .NET SDK. AzureAIAgentTarget exists
in the SDK but is consumed by the Evaluation Taxonomy API, not RedTeam.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address PR review comments on Step02 SelfReflection
- Pass full prompt (with context) to evaluator messages instead of just
the question, so evaluator input matches what the agent received
- Include previous response text in self-reflection refinement prompt
so the LLM can meaningfully improve its answer across iterations
- Inline CreateKnowledgeAgent helper (single use, single statement)
- Add comment clarifying why RunCombinedQualityAndSafetyEvaluation
intentionally passes only the question (no context)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: improve .env precedence and observability samples
- Switch load_settings to explicit precedence: overrides -> explicit .env -> environment -> defaults\n- Raise when env_file_path is provided but missing\n- Update settings docs and tests for new behavior\n- Refresh observability samples and README guidance for env loading options\n\nCloses #3864\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fixed some imports
* Fix load_settings CI regressions
Allow explicit env_file_path values that exist but are not regular files (for example /dev/null) by checking path existence before dotenv parsing, and restore a dict accumulator with typed return cast to satisfy mypy.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Avoid implicit dotenv in observability
Only load dotenv in observability helpers when env_file_path is explicitly provided, and remove test os.devnull workarounds that are no longer necessary.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix streaming branch in weather override middleware sample
The streaming branch of weather_override_middleware only prefixed the
original weather data via a transform hook instead of replacing the
content with the 'perfect weather' override like the non-streaming
branch does. Replace with a new ResponseStream that yields the override
content as ChatResponseUpdate chunks.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fixed exception handling middleware sample
* Fixed runtime context delegation middleware example
* Fixed multimodal input examples
* Small update
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add workflow support for Azure Functions
* fix compatability with latest framework changes and add integration tests
* refactor code
* remove white space
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* align help text with actual port used
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* replace instance id with a place holder
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* remove unused import
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* remove redundant typing import and fix SIM115
* fix latest breaking changes
* fix mypy issues
* clean up imports
* define source marker strings as constants
* fix json module name
* refactor _extract_message_content_from_dict
* refactor serialization
* add helper method for error response construction and remove _extract_message_content_from_dict since it is not needed
* use strict tpe checking for edges
* change how duplicate agent registrations are handled
* cancel approval_task on HITL timeout
* update docstring
* fix: align azurefunctions package with core API changes after rebase
- State.import_state/export_state are now sync (removed await)
- Add State.commit() before export_state() in activity execution
- Rename executor parameter shared_state -> state
- Rename ctx.set_shared_state/get_shared_state -> set_state/get_state (sync)
- WorkflowBuilder now takes start_executor as constructor kwarg
- Update WorkflowOutputEvent -> WorkflowEvent with type='output'
- Update RequestInfoEvent -> WorkflowEvent[Any]
- Update SharedState -> State in test imports
- Update duplicate agent name tests to match new warning behavior
- Update sample README API references
* fix sample check errors
* fix mypy issues
* fix trailing white spaces
* fix test imports
* feat: add durable workflow samples and adapt to main branch changes
- Add workflow samples 09-12 to 04-hosting/azure_functions/
- Adapt to ChatMessage -> Message rename from main
- Adapt to pickle-based checkpoint encoding from main
- Simplify _serialization.py to delegate to core encode/decode
- Fix Message -> WorkflowMessage disambiguation in _context.py
- Remove non-existent _checkpoint_summary import
* fix: update create_checkpoint signature to match superclass
* fix: correct relative link in HITL sample README
* fix: resolve import breakage after rebase (State, DurableAgentThread, get_logger)
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Dmytro Struk <13853051+dmytrostruk@users.noreply.github.com>
Enable automatic synchronization with the active item in VS Code for better
developer experience when working with .NET projects.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat: Inject OpenTelemetry trace context into MCP requests and update documentation
* Update python/samples/getting_started/observability/README.md
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update python/packages/core/tests/core/test_mcp.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* refactor: move opentelemetry import to module level
OpenTelemetry is a hard dependency of agent-framework-core (per
pyproject.toml), so the try/except ImportError guard was dead code.
Move the import to the top of the file to fail fast on missing
dependencies instead of silently hiding installation issues.
---------
Co-authored-by: Pete Roden <Pete.Roden@microsoft.com>
Co-authored-by: Mark Wallace <127216156+markwallace-microsoft@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Fix#3600: Pass JSON schemas through without Pydantic conversion
This change optimizes FunctionTool and MCP flows by passing JSON schemas
directly to providers without converting them to Pydantic models first.
Key changes:
- Store JSON schema as-is when supplied to FunctionTool
- Skip Pydantic model_validate for schema-supplied tools in invoke()
- Return MCP tool schemas directly without conversion
- Add comprehensive tests for schema passthrough behavior
Performance benefits:
- Eliminates expensive Pydantic model creation for supplied schemas
- Preserves exact schema structure (additionalProperties, custom fields, etc.)
- Reduces memory overhead and initialization time
Maintains backward compatibility:
- Function signature inference still uses Pydantic models
- Explicit Pydantic models passed as input_model work as before
- All existing tests pass
* Fix schema passthrough validation and remove helper
* Simplify FunctionTool without generic model dependency
* Fix FunctionTool typing fallout in 3600
* Remove FunctionTool[Any] compatibility shim
* Use serializable kwargs in OTEL tool args
* .NET: [BREAKING] Add session statebag to use for state storage instead of inside providers (#3737)
* Add a StateBag to AgentSession and pass Agent and AgentSession to AIContextProvider and ChatHistoryProviders
* Convert all AIContextProviders to use the statebag
* Update InMemoryChatHistoryProvider to use StateBag
* Update Comsos and Workflow ChatHistoryProviders
* Update 3rd party chat history storage sample.
* Remove serialize method from providers
* Replacing provider factories with properties
* Remove Providers from Session and flatten state bag serialization
* Update samples to use getservice on agent
* Updated additional session types to serialize statebag
* Fix regression
* Address PR comments
* Address PR comments.
* Fix formatting
* Fix unit tests
* Remove InMemoryAgentSession since it is not required anymore.
* Address PR comments
* Convert sessions for A2AAgent, ChatClientAgent, CopilotStudioAgent and GithubCopilotAgent to use regular json serialization.
* Fix durable agent session jso usgae
* Add jso to InMemory and Workflow ChatHistoryProviders
* Update InMemoryChatHistoryProvider to use an options class for it's many optional settings.
* Apply suggestions from code review
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Address PR feedback
* Fix verification bug.
* Improve state bag thread safety
* Address PR comments and fix unit tests
* Address PR comments
* Fix unit test
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Add a public StateKey property to providers (#3810)
* .NET: [BREAKING] Update providers in such a way that they can participate in a pipeline (#3846)
* Make providers pipeline capable
* Fix unit tests
* Move source stamping to providers from base class
* Also update samples.
* Address PR comments
* Rename AsAgentRequestMessageSourcedMessage to WithAgentRequestMessageSource
* .NET: [BREAKING] Add consistent message filtering to all providers. (#3851)
* Add consistent message filtering to all providers.
* Remove old chat history filtering classes
* Fix merge issues
* Fix unit test
* Enforce non-nullable property
* Fix merging bug and make troubleshooting source info easier by adding tostring implementation
* .NET: [BREAKING] Add support for multiple AIContextProviders on a ChatClientAgent (#3863)
* Add support for multiple AIContextProviders on a ChatClientAgent
* Address PR comments and fix tests
* Address PR comments.
* .NET: [BREAKING]Delay AIContext Materialization until the end of the pipeline is reached. (#3883)
* Delay AIContext Materialization until the end of the pipeline is reached.
* Address PR comments.
* Address PR comments
* Modify InvokedContext to be immutable (#3888)
* .NET: Address Feedback on StateBag feature branch PR (#3910)
* Address Feedback on statebag feature branch PR
* Update dotnet/src/Microsoft.Agents.AI.DurableTask/CHANGELOG.md
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Address PR comments
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Python: Replace wildcard imports with explicit imports
- Replace all 'from ... import *' with explicit symbol imports
- Add __all__ declarations to namespace packages for re-exports
- Update CODING_STANDARD.md to prohibit wildcard imports
- Maintain exported API and preserve all functionality
fixes#3605
* Refine wildcard guidance example text
* Simplify explicit exports without self-aliases
* fix: prevent repeating instructions in continued Responses API conversations
- Instructions are now only prepended to messages on the first turn
- When conversation_id/response_id exists (continuation), instructions are skipped
- Covers OpenAI and Azure Responses API paths
- Adds regression tests for all continuation scenarios
Fixes#3498
* Apply lint fixes to continuation tests
* Consolidate responses continuation tests
* PR2: Wire context provider pipeline and update all internal consumers
- Replace AgentThread with AgentSession across all packages
- Replace ContextProvider with BaseContextProvider across all packages
- Replace context_provider param with context_providers (Sequence)
- Replace thread= with session= in run() signatures
- Replace get_new_thread() with create_session()
- Add get_session(service_session_id) to agent interface
- DurableAgentThread -> DurableAgentSession
- Remove _notify_thread_of_new_messages from WorkflowAgent
- Wire before_run/after_run context provider pipeline in RawAgent
- Auto-inject InMemoryHistoryProvider when no providers configured
* fix: update all tests for context provider pipeline, fix lazy-loaders, remove old test files
* refactor: update all sample files for context provider pipeline (AgentThread→AgentSession, ContextProvider→BaseContextProvider)
* fix: update remaining ag-ui references (client docstring, getting_started sample)
* fix: make get_session service_session_id keyword-only to avoid confusion with session_id
* refactor: rename _RunContext.thread_messages to session_messages
* refactor: remove _threads.py, _memory.py, and old provider files; migrate devui to use plain message lists
* rename: remove _new_ prefix from test files
* refactor: rewrite SlidingWindowChatMessageStore as SlidingWindowHistoryProvider(InMemoryHistoryProvider)
* fix: read full history from session state directly instead of reaching into provider internals
* fix: update stale .pyi stubs, sample imports, and README references for new provider types
* fix: remove stale message_store, _notify_thread_of_new_messages, and session_id.key references in samples
* refactor: merge context_providers and sessions sample folders into sessions, remove aggregate_context_provider
* refactor: UserInfoMemory stores state in session.state instead of instance attributes
* feat: add Pydantic BaseModel support to session state serialization
Pydantic models stored in session.state are now automatically serialized
via model_dump() and restored via model_validate() during to_dict()/from_dict()
round-trips. Models are auto-registered on first serialization; use
register_state_type() for cold-start deserialization.
Also export register_state_type as a public API.
* fix mem0
* Update sample README links and descriptions for session terminology
- Replace 'thread' with 'session' in sample descriptions across all READMEs
- Update file links for renamed samples (mem0_sessions, redis_sessions, etc.)
- Fix Threads section → Sessions section in main samples/README.md
- Update tools, middleware, workflows, durabletask, azure_functions READMEs
- Update architecture diagrams in concepts/tools/README.md
- Update migration guides (autogen, semantic-kernel)
* Fix broken Redis README link to renamed sample
* Fix Mem0 OSS client search: pass scoping params as direct kwargs
AsyncMemory (OSS) expects user_id/agent_id/run_id as direct kwargs,
while AsyncMemoryClient (Platform) expects them in a filters dict.
Adds tests for both client types.
Port of fix from #3844 to new Mem0ContextProvider.
* Fix rebase issues: restore missing _conversation_state.py and checkpoint decode logic
- Add back _conversation_state.py (encode/decode_chat_messages) lost in rebase
- Fix on_checkpoint_restore to decode cache/conversation with decode_chat_messages
- Fix on_checkpoint_restore to use decode_checkpoint_value for pending requests
- Add tests/workflow/__init__.py for relative import support
- Fix test_agent_executor checkpoint selection (checkpoints[1] not superstep)
* Add STORES_BY_DEFAULT ClassVar to skip redundant InMemoryHistoryProvider injection
Chat clients that store history server-side by default (OpenAI Responses API,
Azure AI Agent) now declare STORES_BY_DEFAULT = True. The agent checks this
during auto-injection and skips InMemoryHistoryProvider unless the user
explicitly sets store=False.
* Fix broken markdown links in azure_ai and redis READMEs
* Fix getting-started samples to use session API instead of removed thread/ContextProvider API
* updates to workflow as agent
* fix group chat import
* Rename Thread→Session throughout, fix service_session_id propagation, remove stale AGUIThread
- Fix: Propagate conversation_id from ChatResponse back to session.service_session_id
in both streaming and non-streaming paths in _agents.py
- Rename AgentThreadException → AgentSessionException
- Remove stale AGUIThread from ag_ui lazy-loader
- Rename use_service_thread → use_service_session in ag-ui package
- Rename test functions from *_thread_* to *_session_*
- Rename sample files from *_thread* to *_session*
- Update docstrings and comments: thread → session
- Update _mcp.py kwargs filter: add 'session' alongside 'thread'
- Fix ContinuationToken docstring example: thread=thread → session=session
- Fix _clients.py docstring: 'Agent threads' → 'Agent sessions'
* Fix broken markdown links after thread→session file renames
* fix azure ai test
* Update GitHub.Copilot.SDK to 0.1.23 and copy new session config properties
- Bump GitHub.Copilot.SDK from 0.1.18 to 0.1.23
- Add new SessionConfig properties: ReasoningEffort, Hooks, OnUserInputRequest,
WorkingDirectory, ConfigDir, InfiniteSessions
- Add missing ResumeSessionConfig properties: Model, SystemMessage,
AvailableTools, ExcludedTools, ReasoningEffort, Hooks, OnUserInputRequest,
WorkingDirectory, ConfigDir, InfiniteSessions
- Fix UserMessageDataAttachmentsItem -> UserMessageDataAttachmentsItemFile
for new polymorphic attachment API
- Add unit tests for new session config properties
* Address PR review: centralize config mapping and improve test coverage
- Extract CopySessionConfig/CopyResumeSessionConfig as internal static helpers
to eliminate duplicated mapping logic between RunCoreStreamingAsync and
CreateResumeConfig (addresses reviewer comment on drift risk)
- Add InternalsVisibleTo for unit test project
- Replace shallow constructor tests with comprehensive property-verification
tests that validate every config property is correctly copied, including
OnUserInputRequest (addresses reviewer comments on test coverage)
* Remove accidentally committed git-lfs hooks
* restructure: Python samples into progressive 01-05 layout
- 01-get-started/: 6 numbered steps (hello agent → hosting)
- 02-agents/: all agent concept samples (tools, middleware, providers, etc.)
- 03-workflows/: ALL existing workflow samples preserved as-is
- 04-hosting/: azure-functions, durabletask, a2a
- 05-end-to-end/: demos, evaluation, hosted agents
- Old files moved to _to_delete/ for review
- Added AGENTS.md with structure documentation
- autogen-migration/ and semantic-kernel-migration/ preserved at root
* fix: switch to AzureOpenAI Foundry, fix CI failures
- Switch all 01-get-started samples to AzureOpenAIResponsesClient with
Azure AI Foundry project endpoint (AZURE_AI_PROJECT_ENDPOINT +
AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME + AzureCliCredential)
- Add _to_delete/ and 05-end-to-end/ to pyrightconfig.samples.json excludes
- Fix test paths in packages/ that referenced old getting_started/ dirs:
durabletask conftest + streaming test, azurefunctions conftest,
devui conftest + capture_messages + openai_sdk_integration
- Fix workflow_as_agent_human_in_the_loop.py import (sibling import)
- Update hosting READMEs and tool comment paths
- Replace root README.md with new structure overview
- Update AGENTS.md to document Azure OpenAI Foundry as default provider
* cleanup: remove _to_delete folder, copy resource files to active dirs
All files in _to_delete/ were either:
- Exact duplicates of files in the new structure (240 files)
- Same file with only comment path updates (100 files)
- One import-fix diff (workflow_as_agent_human_in_the_loop.py)
- One superseded minimal_sample.py
Resource files (sample.pdf, countries.json, employees.pdf, weather.json)
copied to 02-agents/sample_assets/ and 02-agents/resources/ since active
samples reference them.
* fix: address PR review comments, centralize resources, remove root duplicates
- Fix type annotation in 04_memory.py (string union -> proper types)
- Fix old sample paths in observability files
- Fix grammar/spelling in observability samples
- Move sample_assets/ and resources/ to shared/ folder
- Remove 8 duplicate observability files from 02-agents root
- Update resource path references in multimodal_input and provider samples
* fix: update broken links from old getting_started paths to new structure
- Update relative paths in READMEs: getting_started/ → 01-get-started/,
02-agents/, 03-workflows/, 04-hosting/, 05-end-to-end/
- Fix absolute GitHub URLs in package READMEs
- Fix broken link in ollama package README
* fix: convert absolute GitHub URLs to relative paths for link checker
Absolute URLs to python/samples/ on main branch 404 until PR merges.
Converted to relative paths that linkspector can verify locally.
* fix: update link for handoff sample moved to orchestrations/
* fix: update chatkit-integration README path from demos/ to 05-end-to-end/
* fix: update broken links in orchestrations README to match flat directory structure
* Centralize tool result parsing in FunctionTool.invoke()
- Add parse_result static method to FunctionTool that converts raw
function return values to strings at invocation time
- Add result_parser parameter to FunctionTool and @tool decorator
for custom parsing
- Remove prepare_function_call_results from all 9 consumer files
and from the public API
- Update MCPTool to parse MCP types directly to strings via
_parse_tool_result_from_mcp and _parse_prompt_result_from_mcp
- Change MCPTool parse_tool_results/parse_prompt_results type from
Literal[True] | Callable | None to Callable | None
- Remove ReturnT type parameter from FunctionTool (now single
generic ArgsT since invoke() always returns str)
- Update all subclass signatures and docstrings
Fixes#1147
* Fix test_mcp_tool_call_tool_with_meta_integration for string results
The test was still accessing result[0].additional_properties but
invoke() now returns a string, not a list of Content objects.
* Fix SIM108 lint: use binary operator for output assignment
* Fix bedrock: use FunctionTool.parse_result instead of str() fallback
str(result) turns None into literal 'None' and dicts into Python reprs
with single quotes, breaking JSON parsing. Use the shared parse_result
which handles None as '' and serializes via json.dumps.
* updated lock
* updates from feedback
* Replace Pydantic Settings with TypedDict + load_settings()
- Remove pydantic-settings dependency, add python-dotenv
- Delete _pydantic.py (AFBaseSettings, HTTPsUrl)
- Add _settings.py with generic load_settings() function, SecretString,
type coercion, and Required field validation (SettingNotFoundError)
- Convert all 13 settings classes from AFBaseSettings subclasses to
TypedDict definitions with load_settings() calls
- Update all consumers from attribute access to dict access
- Add 20 unit tests for load_settings() covering basic loading, dotenv,
SecretString, type coercion, and required field validation
- Update all existing tests for new settings patterns
* Fix mypy type errors from settings conversion
- Fix str | None attribute access in responses_client (walrus operator)
- Fix SecretString | None narrowing in bedrock (type: ignore after guard)
- Convert _context_provider.py attribute access to dict access (missed file)
- Fix endpoint type narrowing in search_provider and context_provider
- Fix purview: str | None .rstrip(), int | None defaults, urlparse bytes
* Address PR review: required_fields param, type validation, fixes
- Move required field validation from TypedDict annotations (Required)
to a required_fields parameter on load_settings(), enabling runtime
decisions about which fields are required
- Remove Required imports and restore from __future__ import annotations
in ollama and foundry_local
- Add _check_override_type() for deterministic ServiceInitializationError
on invalid override types (e.g. dict passed for str field)
- Fix all multi-exception test catches back to single exception type
- Fix Ollama host=None: use .get() so None is passed through to SDK default
- Fix Purview processor: use explicit is-None checks instead of or operator
- Remove unused BaseModel import from openai/_shared.py
- Add 4 new tests (24 total): required_fields param, type validation
* Fix type validation: allow int for float fields
_check_override_type now permits int values for float-typed fields,
matching Python's standard numeric promotion behavior.
* fix: wrap urlparse arg with str() to fix mypy bytes endswith error
* Initial plan
* feat: extend AzureOpenAIResponsesClient to support Foundry project endpoints
Add project_client and project_endpoint parameters to allow creating
the client via an Azure AI Foundry project. When provided, the client
uses AIProjectClient.get_openai_client() to obtain the OpenAI client.
The azure-ai-projects package is imported lazily and only required
when using the project endpoint path.
Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>
* fix: address code review - remove duplicate MagicMock imports in tests
Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>
* fix: add type field to Responses API input items and add Foundry sample
- Add 'type: message' to input items in _prepare_message_for_openai
to comply with the Responses API schema requirement
- Filter out empty dicts from unsupported content types to prevent
sending items with invalid empty type values
- Add azure_responses_client_with_foundry.py sample demonstrating
AzureOpenAIResponsesClient with project_endpoint
- Update README and pyrightconfig.samples.json accordingly
* updates to response format and setup
* fix: patch AIProjectClient at correct module path in test
Patch agent_framework.azure._responses_client.AIProjectClient instead of
azure.ai.projects.aio.AIProjectClient since the import is at module level.
* docs: add Foundry sample to READMEs and document AZURE_AI_PROJECT_ENDPOINT env var
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>
Co-authored-by: eavanvalkenburg <github@vanvalkenburg.eu>
* Initial plan
* Add comprehensive unit tests for EditTableV2Executor
- Test AddItemOperation with record and scalar values
- Test ClearItemsOperation
- Test RemoveItemOperation
- Test TakeLastItemOperation (with items and empty table)
- Test TakeFirstItemOperation (with items and empty table)
- Test error cases (null ItemsVariable, non-table variable)
- Include ExecuteTestAsync and CreateModel helper methods
- All 10 tests passing
Co-authored-by: crickman <66376200+crickman@users.noreply.github.com>
* Add comprehensive unit tests for EditTableV2Executor - complete with 100% coverage
- Added 13 comprehensive tests covering all code paths
- Test AddItemOperation with record and scalar values
- Test ClearItemsOperation
- Test RemoveItemOperation (including non-table value case)
- Test TakeLastItemOperation (with items and empty table)
- Test TakeFirstItemOperation (with items and empty table)
- Test error cases (null ItemsVariable, non-table variable, null operation values)
- Include ExecuteTestAsync and CreateModel helper methods
- 100% line and branch coverage achieved
Co-authored-by: crickman <66376200+crickman@users.noreply.github.com>
* Update tests / refine product code
* Checkpoint
* Updated
* Update dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/ObjectModel/SetTextVariableExecutorTest.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Address code review feedback
- Fix typo: rename metadataExpresssion to metadataExpression
- Fix test name in AddMessageWithMetadataAsync (was using wrong test name)
- Fix test name in ClearGlobalScopeAsync (was using wrong test name)
- Remove pre-population in SetTextVariableExecutorTest that made tests ineffective
- Use explicit .Where() filter in SetMultipleVariablesExecutorTest foreach loop
Co-authored-by: crickman <66376200+crickman@users.noreply.github.com>
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: crickman <66376200+crickman@users.noreply.github.com>
Co-authored-by: Chris Rickman <crickman@microsoft.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* PR1: Add core context provider types and tests
New types in _sessions.py (no changes to existing code):
- SessionContext: per-invocation state with extend_messages/get_messages/
extend_instructions/extend_tools and read-only response property
- _ContextProviderBase: base class with before_run/after_run hooks
- _HistoryProviderBase: storage base with load/store flags, abstract
get_messages/save_messages, default before_run/after_run
- AgentSession: lightweight session with state dict, to_dict/from_dict
- InMemoryHistoryProvider: built-in provider storing in session.state
35 unit tests covering all classes and configuration flags.
* feat: keyword-only params, stateless InMemoryHistoryProvider, deep serialization
- Make before_run/after_run parameters keyword-only
- InMemoryHistoryProvider stores ChatMessage objects directly (no per-cycle serialization)
- Deep serialization via to_dict/from_dict only at session boundary
- State type registry for automatic deserialization of registered types
- Updated tests for new serialization approach
* feat: add new-pattern provider implementations for external packages
- _RedisContextProvider(BaseContextProvider) - Redis search/vector context
- _RedisHistoryProvider(BaseHistoryProvider) - Redis-backed message storage
- _Mem0ContextProvider(BaseContextProvider) - Mem0 semantic memory
- _AzureAISearchContextProvider(BaseContextProvider) - Azure AI Search (semantic + agentic)
All use temporary _ prefix names for side-by-side coexistence with existing providers.
Will be renamed in PR2 when old ContextProvider/ChatMessageStore are removed.
* test: add tests for new-pattern provider implementations
- 32 tests for _RedisContextProvider and _RedisHistoryProvider
- 29 tests for _Mem0ContextProvider
- 17 tests for _AzureAISearchContextProvider
* fix: address PR review comments and CI failures
- Move module docstring before imports in _sessions.py (review comment)
- Import TYPE_CHECKING unconditionally in Redis _context_provider.py (NameError on Python <3.12)
- Fix Mem0 test_init_auto_creates_client_when_none to patch at class level
* feat: add source attribution to extend_messages
Set attribution marker in additional_properties for each message
added via extend_messages(), matching the tool attribution pattern.
Uses setdefault to preserve any existing attribution.
* refactor: make attribution value a dict with source_id key
* add attribution and use sets for filters
* Add source_type to message attribution and copy messages in extend_messages
- SessionContext.extend_messages now accepts source as str or object with
source_id attribute; when an object is passed, its class name is recorded
as source_type in the attribution dict
- Messages are shallow-copied before attribution is added so callers'
original objects are never mutated
- Filter framework-internal keys (attribution) from A2A wire metadata
to prevent leaking internal state over the wire
* fix: correct mypy type: ignore comment from union-attr to attr-defined
* set attribution to _attribution
* adjusted naming of bools
* Python: Add long-running agents and background responses support
- Add ContinuationToken TypedDict to core types
- Add continuation_token field to ChatResponse, ChatResponseUpdate,
AgentResponse, and AgentResponseUpdate
- Add background and continuation_token options to OpenAIResponsesOptions
- Implement polling via responses.retrieve() and streaming resumption
in RawOpenAIResponsesClient
- Propagate continuation tokens through agent run() and
map_chat_to_agent_update
- Fix streaming telemetry 'Failed to detach context' error in both
ChatTelemetryLayer and AgentTelemetryLayer by avoiding
trace.use_span() context attachment for async-managed spans
- Add 14 unit tests for continuation token types and background flows
- Add background_responses sample showing polling and stream resumption
Fixes#2478
* Python: Add A2A long-running task support via ContinuationToken
- Make ContinuationToken provider-agnostic (total=False, optional task_id/context_id fields)
- Add background param to A2AAgent.run() controlling token emission
- Add poll_task() for single-request task state retrieval
- Add resubscribe support via continuation_token param on run()
- Extract _updates_from_task() and _map_a2a_stream() for cleaner code
- Streamline run()/streaming by removing intermediate _stream_updates wrapper
- Update A2A sample to show background=False (default) with link to background_responses sample
- Remove stale BareAgent from __all__
- Add 12 new A2A continuation token tests
* fix logic for overriding continuation token when done
* refactored ContinuationToken setup
* Update message source code to match python.
* Apply suggestion from @Copilot
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Address PR comment
* Move setting of source information to extension method
* Add underscore for attribution key to indicate internal usage
* Stick to version 102 of the SDK since 103 is causing issues.
* Revert global.json change
* Fix unit test
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
RunCoreStreamingAsync was passing inputMessagesForProviders (which lacks
chat history) to GetStreamingResponseAsync instead of
inputMessagesForChatClient (which includes chat history). This caused
streaming runs to lose conversation context on subsequent calls.
The non-streaming path (RunCoreAsync) already correctly used
inputMessagesForChatClient. This aligns the streaming path to match.
Also adds a unit test that validates chat history is included in
messages sent to the chat client during streaming on subsequent calls.
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>
* Python: fix prek runner running fmt/lint in all packages on core change
When a core package file changed, run_tasks_in_changed_packages.py ran
fmt, lint, and pyright in ALL 22 packages (66 tasks). Only type-checking
tasks (pyright, mypy) need to propagate to all packages since type
changes in core affect downstream packages. File-local tasks (fmt, lint)
only need to run in packages with actual file changes.
This reduces a core-only change from 66 tasks to 24 tasks (2 local +
22 pyright).
Also adds no-commit-to-branch builtin hook to protect the main branch
from direct commits.
* Python: add agent skills extracted from AGENTS.md and coding standards
Add 5 skills to python/.github/skills/ following the Agent Skills format:
- python-development: coding standards, type annotations, docstrings, logging
- python-testing: test structure, fixtures, running tests, async mode
- python-code-quality: linting, formatting, type checking, prek hooks, CI
- python-package-management: monorepo structure, lazy loading, versioning
- python-samples: sample structure, PEP 723, documentation guidelines
* Python: deduplicate AGENTS.md and instructions with agent skills
* updated skills
* fixes from review
* Python: increase timeout for web search integration test
* Add ADR for Python ContextMiddleware unification
* Add session serialization/deserialization design to ADR
* Add Related Issues section mapping to ADR
* Update session management: create_session, get_session_by_id, agent.serialize_session
* ADR: Add hooks alternative, context compaction discussion, and PR feedback
- Add Option 3: ContextHooks with before_run/after_run pattern
- Add detailed pros/cons for both wrapper and hooks approaches
- Add Open Discussion section on context compaction strategies
- Clarify response_messages is read-only (use AgentMiddleware for modifications)
- Add SimpleRAG examples showing input-only filtering
- Clarify default storage only added when NO middleware configured
- Add RAGWithBuffer examples for self-managed history
- Rename hook methods to before_run/after_run
* ADR: Restructure and add .NET comparison
- Add class hierarchy clarification for both options
- Merge detailed design sections (side-by-side comparison)
- Move detailed design before decision outcome
- Move compaction discussion after decision
- Add .NET implementation comparison (feature equivalence)
- Update .NET method names to match actual implementation
- Rename hook methods to before_run/after_run
- Fix storage context table for injected context
* tweaks
* fix smart load
* ADR: Add naming discussion note for ContextHooks
- Note that class and method names are open for discussion
- Add alternative method naming options table
- Include invoking/invoked as option matching current Python and .NET
* Update context middleware design: remove smart mode, add attribution filtering
- Remove smart mode for load_messages (now explicit bool, default True)
- Add attribution marker in additional_properties for message filtering
- Update validation to warn on multiple or zero storage loaders
- Add note about ChatReducer naming from .NET
- Note that attribution should not be propagated to storage
* Add Decision 2: Instance Ownership (instances in session vs agent)
- Option A: Instances in Session (current proposal)
- Option B: Instances in Agent, State in Session
- B1: Simple dict state with optional return
- B2: SessionState object with mutable wrapper
- Updated examples to use Hooks pattern (before_run/after_run)
- Added open discussion on hook factories in Option B model
* Update ADR: Choose ContextPlugin with before_run/after_run and Option B1
Decision outcomes:
- Option 3 (Hooks pattern) with ContextPlugin class name
- Methods: before_run/after_run
- Option B1: Instances in Agent, State in Session (simple dict)
- Whole state dict passed to plugins (mutable, no return needed)
- Added trust note: plugins reason over messages, so they're trusted by default
Status changed from proposed to accepted.
* Add agent and session params to before_run/after_run methods
Signature now: before_run(agent, session, context, state)
* Remove ContextPluginRunner, store plugins directly on agent
Simpler design: agent stores Sequence[ContextPlugin] and calls
before_run/after_run directly in the run method.
* Update workplan to 2 PRs for simpler review
* updated doc
* Refine ADR: serialization, ownership, decorators, session methods, exports
- Add to_dict()/from_dict() on AgentSession with 'type' discriminator
- Present serialization as Option A (direct) vs Option B (through agent)
- Rewrite ownership section as 2x2 matrix (orthogonal decision)
- Move Instance Ownership Options before Decision Outcome
- Fix get_session to use service_session_id, split from create_session
- Add decorator-based provider convenience API (@before_run/@after_run)
- Add _ prefix naming strategy for all PR1 types (core + external)
- Constructor compatibility table for existing providers
- Add load_messages=False skip logic to all agent run loops
- Clarify abstract vs non-abstract in execution pattern samples
- Update auto-provision: trigger on conversation_id or store=True
- Document root package exports (ContextProvider, HistoryProvider, etc.)
- Rename section heading to 'Key Design Considerations'
* Rename ADR to 0016-python-context-middleware.md
* Fix broken link: #3-unified-storage-middleware → #3-unified-storage
* feat(workflows): Make telemetry opt-in via WithOpenTelemetry()
- Add WorkflowTelemetryOptions class with EnableSensitiveData property
- Add WorkflowTelemetryContext to manage ActivitySource lifecycle
- Add WithOpenTelemetry() extension method on WorkflowBuilder
- Update all workflow components to use telemetry context:
- WorkflowBuilder, Workflow, Executor
- InProcessRunnerContext, InProcessRunner
- LockstepRunEventStream, StreamingRunEventStream
- All edge runners (Direct, FanIn, FanOut, Response)
- Telemetry is now disabled by default
- Users must call WithOpenTelemetry() to enable spans/activities
BREAKING CHANGE: Workflow telemetry is now opt-in. Users who relied on
automatic telemetry must add .WithOpenTelemetry() to their workflow builder.
* refactor: Pass telemetry context as parameter instead of via interface
- Remove IWorkflowContextWithTelemetry interface
- Add internal ExecuteAsync overload that accepts WorkflowTelemetryContext
- Public ExecuteAsync delegates with WorkflowTelemetryContext.Disabled
- InProcessRunner passes TelemetryContext when calling ExecuteAsync
- BoundContext now implements IWorkflowContext (not the removed interface)
* Add optional ActivitySource parameter to WithOpenTelemetry
Allow users to provide their own ActivitySource when enabling telemetry,
giving them better control over the ActivitySource lifecycle. When not
provided, the framework creates one internally (existing behavior).
Changes:
- Add optional activitySource parameter to WithOpenTelemetry() extension
- Update WorkflowTelemetryContext to accept external ActivitySource
- Add unit test for user-provided ActivitySource scenario
* Add component-level telemetry control with disable flags
Allow users to selectively disable specific activity types via
WorkflowTelemetryOptions. All activities are enabled by default.
New disable flags:
- DisableWorkflowBuild: Disables workflow.build activities
- DisableWorkflowRun: Disables workflow_invoke activities
- DisableExecutorProcess: Disables executor.process activities
- DisableEdgeGroupProcess: Disables edge_group.process activities
- DisableMessageSend: Disables message.send activities
Added helper methods to WorkflowTelemetryContext for each activity type
and updated all activity creation sites to use them.
* Implement EnableSensitiveData to log executor input/output
When EnableSensitiveData is true in WorkflowTelemetryOptions, executor
input and output are logged as JSON-serialized attributes in the
executor.process activity.
New activity tags:
- executor.input: JSON serialized input message
- executor.output: JSON serialized output result (non-void only)
Added suppression attributes for AOT/trimming warnings since this is
an opt-in feature for debugging/diagnostics.
* Refactor activity start methods to centralize tagging logic
Move tagging logic into WorkflowTelemetryContext methods:
- StartExecutorProcessActivity now accepts executorId, executorType,
messageType, and message; sets all tags including executor.input
when EnableSensitiveData is true
- Added SetExecutorOutput method to set executor.output after execution
- StartMessageSendActivity now accepts sourceId, targetId, and message;
sets all tags including message.content when EnableSensitiveData is true
Simplified Executor.cs and InProcessRunnerContext.cs by removing
inline tagging code. Added message.content tag constant.
* Revert Python changes
* Update samples and code cleanup
* Fix file formatting
* Add comment
* Add telemetry configuration to declarative workflow
* Remove delays in tests
* Address comments
* python: replace pre-commit with prek, add PEP 723 script deps, clean up dev dependencies
- Replace pre-commit with prek (Rust-native, faster pre-commit alternative)
- Move supported hooks to repo: builtin for zero-clone speed
- Add new builtin hooks: trailing-whitespace, check-merge-conflict, detect-private-key, check-added-large-files
- Update all hook versions to latest (pre-commit-hooks v6, pyupgrade v3.21.2, bandit 1.9.3, uv-pre-commit 0.10.0)
- Add PEP 723 inline script metadata to 34 samples with external deps
- Remove autogen-agentchat/autogen-ext from dev deps (now declared per-sample)
- Remove unused dev deps: pytest-env, tomli-w
- Add agent-framework-core>=1.0.0b260130 lower bound to all 21 packages
- Update CI workflow to use j178/prek-action
- Update docs: DEV_SETUP.md, AGENTS.md, CODING_STANDARD.md, SAMPLE_GUIDELINES.md
* updated lock
* python: fix prek config paths for local execution and CI workflow
Remove global 'files: ^python/' filter and strip python/ prefix from all path patterns in .pre-commit-config.yaml so prek finds files when run from the python/ directory. Update CI workflow to use --cd python instead of --config path. Include trailing whitespace fixes and dev dependency cleanup.
* python: move helper scripts to scripts/ folder and exclude from checks
* python: exclude AGENTS.md from prek markdown code lint
* python: exclude AGENTS.md and azure_ai_search sample from markdown lint
* fix m365 sample
* python: ignore CPY rule for samples with PEP 723 headers
* fix in dev_setup
* python: replace aiofiles with regular open in samples
* python: suppress reportUnusedImport in markdown code block checker
* python: use samples pyright config for markdown code block checker
Write a temp pyrightconfig.json matching pyrightconfig.samples.json rules (typeCheckingMode=off, only reportMissingImports and reportAttributeAccessIssue). Filter output to only fail on these rules since syntax-level errors (top-level await, undefined vars) are expected in README documentation snippets.
* python: use markdown-code-lint with fixed globs instead of prek file list
The prek-markdown-code-lint task received all changed files including non-README markdown and files with pre-existing broken imports. Replace with the standard markdown-code-lint task which uses the correct glob patterns (README.md, packages/**/README.md, samples/**/*.md).
* python: exclude READMEs with pre-existing broken imports from markdown lint
* python: fix broken README code snippets instead of excluding them
- ag-ui: replace TextContent (removed) with content.type == 'text'
- durabletask: fix import path to durabletask.worker.TaskHubGrpcWorker
- orchestrations: use constructor params instead of .participants() method
- observability: mark deprecated code blocks as plain text, filter
reportMissingImports to agent_framework modules only
- remove README excludes from markdown-code-lint task
* add revision to gaia download
* feat(python): parallelize checks across packages
Run (package × task) cross-product in parallel using ThreadPoolExecutor
and subprocesses. Key changes:
- Add scripts/task_runner.py with shared parallel execution engine
- Update run_tasks_in_packages_if_exists.py to accept multiple tasks
- Update run_tasks_in_changed_packages.py with --files flag and parallel support
- Add check-packages poe task (fmt+lint+pyright+mypy in parallel)
- Add prek-markdown-code-lint and prek-samples-check with change detection
- Split CI code quality workflow into parallel prek and mypy jobs
- Update DEV_SETUP.md to document new parallel behavior
Core package changes still trigger checks on all packages.
* feat(ci): split code quality into 4 parallel jobs
Split the single prek job into parallel jobs:
- pre-commit-hooks: lightweight hooks (SKIP=poe-check)
- package-checks: fmt/lint/pyright/mypy via check-packages
- samples-markdown: samples-lint, samples-syntax, markdown-code-lint
- mypy: change-detected mypy checks
All 4 jobs run concurrently (×2 Python versions = 8 runners).
* feat(ci): use only Python 3.10 for code quality checks
* refactor(python): add future annotations and remove quoted types
Add `from __future__ import annotations` to 93 package files that
used quoted string annotations, then run pyupgrade --py310-plus to
remove the now-unnecessary quotes.
Fixes https://github.com/microsoft/agent-framework/issues/3578
* Add ability to mark the source of Agent request messages and use that for filtering
* Add support for source, in addition to source type, and add unit tests for automatic stamping
* Address PR comments.
* Add merge fixes
* Address PR comments
* Add samples syntax checking with pyright
- Add pyrightconfig.samples.json with relaxed type checking but import validation
- Add samples-syntax poe task to check samples for syntax and import errors
- Add samples-syntax to check and pre-commit-check tasks
- Fix 78 sample errors:
- Update workflow builder imports to use agent_framework_orchestrations
- Change content type isinstance checks to content.type comparisons
- Use Content factory methods instead of removed content type classes
- Fix TypedDict access patterns for Annotation
- Fix various API mismatches (normalize_messages, ChatMessage.text, role)
* fixed a bunch of samples and tweaks to pre-commit
* updated lock
* updated lock
* fixes
* added lint to samples
* WIP
* big update to new ResponseStream model
* fixed tests and typing
* fixed tests and typing
* fixed tools typevar import
* fix
* mypy fix
* mypy fixes and some cleanup
* fix missing quoted names
* and client
* fix imports agui
* fix anthropic override
* fix agui
* fix ag ui
* fix import
* fix anthropic types
* fix mypy
* refactoring
* updated typing
* fix 3.11
* fixes
* redid layering of chat clients and agents
* redid layering of chat clients and agents
* Fix lint, type, and test issues after rebase
- Add @overload decorators to AgentProtocol.run() for type compatibility
- Add missing docstring params (middleware, function_invocation_configuration)
- Fix TODO format (TD002) by adding author tags
- Fix broken observability tests from upstream:
- Replace non-existent use_instrumentation with direct instantiation
- Replace non-existent use_agent_instrumentation with AgentTelemetryLayer mixin
- Fix get_streaming_response to use get_response(stream=True)
- Add AgentInitializationError import
- Update streaming exception tests to match actual behavior
* Fix AgentExecutionException import error in test_agents.py
- Replace non-existent AgentExecutionException with AgentRunException
* Fix test import and asyncio deprecation issues
- Add 'tests' to pythonpath in ag-ui pyproject.toml for utils_test_ag_ui import
- Replace deprecated asyncio.get_event_loop().run_until_complete with asyncio.run
* Fix azure-ai test failures
- Update _prepare_options patching to use correct class path
- Fix test_to_azure_ai_agent_tools_web_search_missing_connection to clear env vars
* Convert ag-ui utils_test_ag_ui.py to conftest.py
- Move test utilities to conftest.py for proper pytest discovery
- Update all test imports to use conftest instead of utils_test_ag_ui
- Remove old utils_test_ag_ui.py file
- Revert pythonpath change in pyproject.toml
* fix: use relative imports for ag-ui test utilities
* fix agui
* Rename Bare*Client to Raw*Client and BaseChatClient
- Renamed BareChatClient to BaseChatClient (abstract base class)
- Renamed BareOpenAIChatClient to RawOpenAIChatClient
- Renamed BareOpenAIResponsesClient to RawOpenAIResponsesClient
- Renamed BareAzureAIClient to RawAzureAIClient
- Added warning docstrings to Raw* classes about layer ordering
- Updated README in samples/getting_started/agents/custom with layer docs
- Added test for span ordering with function calling
* Fix layer ordering: FunctionInvocationLayer before ChatTelemetryLayer
This ensures each inner LLM call gets its own telemetry span, resulting in
the correct span sequence: chat -> execute_tool -> chat
Updated all production clients and test mocks to use correct ordering:
- ChatMiddlewareLayer (first)
- FunctionInvocationLayer (second)
- ChatTelemetryLayer (third)
- BaseChatClient/Raw...Client (fourth)
* Remove run_stream usage
* Fix conversation_id propagation
* Python: Add BaseAgent implementation for Claude Agent SDK (#3509)
* Added ClaudeAgent implementation
* Updated streaming logic
* Small updates
* Small update
* Fixes
* Small fix
* Naming improvements
* Updated imports
* Addressed comments
* Updated package versions
* Update Claude agent connector layering
* fix test and plugin
* Store function middleware in invocation layer
* Fix telemetry streaming and ag-ui tests
* Remove legacy ag-ui tests folder
* updates
* Remove terminate flag from FunctionInvocationContext, use MiddlewareTermination instead
- Remove terminate attribute from FunctionInvocationContext
- Add result attribute to MiddlewareTermination to carry function results
- FunctionMiddlewarePipeline.execute() now lets MiddlewareTermination propagate
- _auto_invoke_function captures context.result in exception before re-raising
- _try_execute_function_calls catches MiddlewareTermination and sets should_terminate
- Fix handoff middleware to append to chat_client.function_middleware directly
- Update tests to use raise MiddlewareTermination instead of context.terminate
- Add middleware flow documentation in samples/concepts/tools/README.md
- Fix ag-ui to use FunctionMiddlewarePipeline instead of removed create_function_middleware_pipeline
* fix: remove references to removed terminate flag in purview tests, add type ignore
* fix: move _test_utils.py from package to test folder
* fix: call get_final_response() to trigger context provider notification in streaming test
* fix: correct broken links in tools README
* docs: clarify default middleware behavior in summary table
* fix: ensure inner stream result hooks are called when using map()/from_awaitable()
* Fix mypy type errors
* Address PR review comments on observability.py
- Remove TODO comment about unconsumed streams, add explanatory note instead
- Remove redundant _close_span cleanup hook (already called in _finalize_stream)
- Clarify behavior: cleanup hooks run after stream iteration, if stream is not
consumed the span remains open until garbage collected
* Remove gen_ai.client.operation.duration from span attributes
Duration is a metrics-only attribute per OpenTelemetry semantic conventions.
It should be recorded to the histogram but not set as a span attribute.
* Remove duration from _get_response_attributes, pass directly to _capture_response
Duration is a metrics-only attribute. It's now passed directly to _capture_response
instead of being included in the attributes dict that gets set on the span.
* Remove redundant _close_span cleanup hook in AgentTelemetryLayer
_finalize_stream already calls _close_span() in its finally block,
so adding it as a separate cleanup hook is redundant.
* Use weakref.finalize to close span when stream is garbage collected
If a user creates a streaming response but never consumes it, the cleanup
hooks won't run. Now we register a weak reference finalizer that will close
the span when the stream object is garbage collected, ensuring spans don't
leak in this scenario.
* Fix _get_finalizers_from_stream to use _result_hooks attribute
Renamed function to _get_result_hooks_from_stream and fixed it to
look for the _result_hooks attribute which is the correct name in
ResponseStream class.
* Add missing asyncio import in test_request_info_mixin.py
* Fix leftover merge conflict marker in image_generation sample
* Update integration tests
* Fix integration tests: increase max_iterations from 1 to 2
Tests with tool_choice options require at least 2 iterations:
1. First iteration to get function call and execute the tool
2. Second iteration to get the final text response
With max_iterations=1, streaming tests would return early with only
the function call/result but no final text content.
* Fix duplicate function call error in conversation-based APIs
When using conversation_id (for Responses/Assistants APIs), the server
already has the function call message from the previous response. We
should only send the new function result message, not all messages
including the function call which would cause a duplicate ID error.
Fix: When conversation_id is set, only send the last message (the tool
result) instead of all response.messages.
* Add regression test for conversation_id propagation between tool iterations
Port test from PR #3664 with updates for new streaming API pattern.
Tests that conversation_id is properly updated in options dict during
function invocation loop iterations.
* Fix tool_choice=required to return after tool execution
When tool_choice is 'required', the user's intent is to force exactly one
tool call. After the tool executes, return immediately with the function
call and result - don't continue to call the model again.
This fixes integration tests that were failing with empty text responses
because with tool_choice=required, the model would keep returning function
calls instead of text.
Also adds regression tests for:
- conversation_id propagation between tool iterations (from PR #3664)
- tool_choice=required returns after tool execution
* Document tool_choice behavior in tools README
- Add table explaining tool_choice values (auto, none, required)
- Explain why tool_choice=required returns immediately after tool execution
- Add code example showing the difference between required and auto
- Update flow diagram to show the early return path for tool_choice=required
* Fix tool_choice=None behavior - don't default to 'auto'
Remove the hardcoded default of 'auto' for tool_choice in ChatAgent init.
When tool_choice is not specified (None), it will now not be sent to the
API, allowing the API's default behavior to be used.
Users who want tool_choice='auto' can still explicitly set it either in
default_options or at runtime.
Fixes#3585
* Fix tool_choice=none should not remove tools
In OpenAI Assistants client, tools were not being sent when
tool_choice='none'. This was incorrect - tool_choice='none' means
the model won't call tools, but tools should still be available
in the request (they may be used later in the conversation).
Fixes#3585
* Add test for tool_choice=none preserving tools
Adds a regression test to ensure that when tool_choice='none' is set but
tools are provided, the tools are still sent to the API. This verifies
the fix for #3585.
* Fix tool_choice=none should not remove tools in all clients
Apply the same fix to OpenAI Responses client and Azure AI client:
- OpenAI Responses: Remove else block that popped tool_choice/parallel_tool_calls
- Azure AI: Remove tool_choice != 'none' check when adding tools
When tool_choice='none', the model won't call tools, but tools should
still be sent to the API so they're available for future turns.
Also update README to clarify tool_choice=required supports multiple tools.
Fixes#3585
* Keep tool_choice even when tools is None
Move tool_choice processing outside of the 'if tools' block in OpenAI
Responses client so tool_choice is sent to the API even when no tools
are provided.
* Update test to match new parallel_tool_calls behavior
Changed test_prepare_options_removes_parallel_tool_calls_when_no_tools to
test_prepare_options_preserves_parallel_tool_calls_when_no_tools to reflect
that parallel_tool_calls is now preserved even when no tools are present,
consistent with the tool_choice behavior.
* Fix ChatMessage API and Role enum usage after rebase
- Update ChatMessage instantiation to use keyword args (role=, text=, contents=)
- Fix Role enum comparisons to use .value for string comparison
- Add created_at to AgentResponse in error handling
- Fix AgentResponse.from_updates -> from_agent_run_response_updates
- Fix DurableAgentStateMessage.from_chat_message to convert Role enum to string
- Add Role import where needed
* Fix additional ChatMessage API and method name changes
- Fix ChatMessage usage in workflow files (use text= instead of contents= for strings)
- Fix AgentResponse.from_updates -> from_agent_run_response_updates in workflow files
- Fix test files for ChatMessage and Role enum usage
* Fix remaining ChatMessage API usage in test files
* Fix more ChatMessage and Role API changes in source and test files
- Fix ChatMessage in _magentic.py replan method
- Fix Role enum comparison in test assertions
- Fix remaining test files with old ChatMessage syntax
* Fix ChatMessage and Role API changes across packages
- Add Role import where missing
- Fix ChatMessage signature: positional args to keyword args (role=, text=, contents=)
- Fix Role enum comparisons: .role.value instead of .role string
- Fix FinishReason enum usage in ag-ui event converters
- Rename AgentResponse.from_updates to from_agent_run_response_updates in ag-ui
Fixes API compatibility after Types API Review improvements merge
* Fix ChatMessage and Role API changes in github_copilot tests
* Fix ChatMessage and Role API changes in redis and github_copilot packages
- Fix redis provider: Role enum comparison using .value
- Fix redis tests: ChatMessage signature and Role comparisons
- Fix github_copilot tests: ChatMessage signature and Role comparisons
- Update docstring examples in redis chat message store
* Fix ChatMessage and Role API changes in devui package
- Fix executor: ChatMessage signature change
- Fix conversations: Role enum to string conversion in two places
- Fix tests: ChatMessage signatures and Role comparisons
* Fix ChatMessage and Role API changes in a2a and lab packages
- Fix a2a tests: Role comparisons and ChatMessage signatures
- Fix lab tau2 source: Role enum comparison in flip_messages, log_messages, sliding_window
- Fix lab tau2 tests: ChatMessage signatures and Role comparisons
* Remove duplicate test files from ag-ui/tests (tests are in ag_ui_tests)
* Fix ChatMessage and Role API changes across packages
After rebasing on upstream/main which merged PR #3647 (Types API Review
improvements), fix all packages to use the new API:
- ChatMessage: Use keyword args (role=, text=, contents=) instead of
positional args
- Role: Compare using .value attribute since it's now an enum
Packages fixed:
- ag-ui: Fixed Role value extraction bugs in _message_adapters.py
- anthropic: Fixed ChatMessage and Role comparisons in tests
- azure-ai: Fixed Role comparison in _client.py
- azure-ai-search: Fixed ChatMessage and Role in source/tests
- bedrock: Fixed ChatMessage signatures in tests
- chatkit: Fixed ChatMessage and Role in source/tests
- copilotstudio: Fixed ChatMessage and Role in tests
- declarative: Fixed ChatMessage in _executors_agents.py
- mem0: Fixed ChatMessage and Role in source/tests
- purview: Fixed ChatMessage in source/tests
* Fix mypy errors for ChatMessage and Role API changes
- durabletask: Use str() fallback in role value extraction
- core: Fix ChatMessage in _orchestrator_helpers.py to use keyword args
- core: Add type ignore for _conversation_state.py contents deserialization
- ag-ui: Fix type ignore comments (call-overload instead of arg-type)
- azure-ai-search: Fix get_role_value type hint to accept Any
- lab: Move get_role_value to module level with Any type hint
* Improve CI test timeout configuration
- Increase job timeout from 10 to 15 minutes
- Reduce per-test timeout to 60s (was 900s/300s)
- Add --timeout_method thread for better timeout handling
- Add --timeout-verbose to see which tests are slow
- Reduce retries from 3 to 2 and delay from 10s to 5s
This ensures individual test timeouts are shorter than the job
timeout, providing better visibility when tests hang.
With 60s timeout and 2 retries, worst case per test is ~180s.
* Fix ChatMessage API usage in docstrings and source
- Fix ChatMessage positional args in docstrings: _serialization.py, _threads.py, _middleware.py
- Fix ChatMessage in tau2 runner.py
- Fix role comparison in _orchestrator_helpers.py to use .value
- Fix role comparison in _group_chat.py docstring example
- Fix role assertions in test_durable_entities.py to use .value
* Revert tool_choice/parallel_tool_calls changes - must be removed when no tools
OpenAI API requires tool_choice and parallel_tool_calls to only be
present when tools are specified. Restored the logic that removes
these options when there are no tools.
- Restored check in _chat_client.py to remove tool_choice and
parallel_tool_calls when no tools present
- Restored same logic in _responses_client.py
- Reverted test to expect the correct behavior
* fixed issue in tests
* fix: resolve merge conflict markers in ag-ui tests
* fix: restructure ag-ui tests and fix Role/FinishReason to use string types
* fix: streaming function invocation and middleware termination
- Refactor streaming function invocation to use get_final_response() on inner streams
- Fix MiddlewareTermination to accept result parameter for passing results
- Fix _AutoHandoffMiddleware to use MiddlewareTermination instead of context.terminate
- Fix AgentMiddlewareLayer.run() to properly forward function/chat middleware
- Remove duplicate middleware registration in AgentMiddlewareLayer.__init__
- Fix exception handling in _auto_invoke_function to properly capture termination
- Fix mypy errors in core package
- Update tests to use stream=True parameter for unified run API
* fix all tests command
* Refactor integration tests to use pytest fixtures
- Merge testutils.py into conftest.py for azurefunctions integration tests
- Merge dt_testutils.py into conftest.py for durabletask integration tests
- Convert all integration tests to use fixtures instead of direct imports
(fixes ModuleNotFoundError with --import-mode=importlib)
- Add sample_helper fixture for azurefunctions tests
- Add agent_client_factory and orchestration_helper fixtures for durabletask
- Integration tests now skip with descriptive messages when services unavailable
- Restructure devui tests into tests/devui/ with proper conftest.py
- Add test organization guidelines to CODING_STANDARD.md
- Remove __init__.py from test directories per pytest best practices
* Fix pytest_collection_modifyitems to only skip integration tests
The hook was skipping all tests in the test session, not just
integration tests. Now it only skips items in the integration_tests
directory.
* Fix mem0 tests failing on Python 3.13
Use patch.object on the imported module instead of @patch with string
path to ensure the mock takes effect regardless of import timing.
* fix mem0
* another attempt for mem0
* fix for mem0
* fix mem0
* Increase worker initialization wait time in durabletask tests
Increase from 2 to 8 seconds to allow time for:
- Python startup and module imports
- Azure OpenAI client creation
- Agent registration with DTS worker
- Worker connection to DTS
This helps prevent test failures in CI where the first tests may run
before the worker is fully ready to process requests.
* Fix streaming test to use ResponseStream with finalizer
The _consume_stream method now expects a ResponseStream that can provide
a final AgentResponse via get_final_response(). Update the test to use
ResponseStream with AgentResponse.from_updates as the finalizer.
* Fix MockToolCallingAgent to use new ResponseStream API and update samples
* small updates to run_stream to run
* fix sub workflow
* temp fix for az func test
---------
Co-authored-by: Dmytro Struk <13853051+dmytrostruk@users.noreply.github.com>
* Add a StateBag to AgentSession and pass Agent and AgentSession to AIContextProvider and ChatHistoryProviders
* Remove statebag code from this branch, to get the refactoring out of the way first
* Apply suggestion from @rogerbarreto
Co-authored-by: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com>
* Apply suggestion from @westey-m
* Apply suggestion from @westey-m
---------
Co-authored-by: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com>
2026-02-05 15:58:41 +00:00
Roger BarretoGitHubcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
* Initial plan
* Fix issue #3195: Handle empty Version and ID in Azure AI agent responses
This fix addresses the issue where hosted MCP agents (like AgentWithHostedMCP)
fail with "ID cannot be null or empty (Parameter 'id')" error when deployed
to Azure AI Foundry.
Changes:
- Add CreateAgentReference helper method in AzureAIProjectChatClient that defaults
empty version to "latest"
- Update CreateChatClientAgentOptions to generate a fallback ID from name and version
when AgentVersion.Id is null or empty
- Add GetAgentVersionResponseJsonWithEmptyVersion and GetAgentResponseJsonWithEmptyVersion
test data methods
- Add unit tests for empty version handling scenarios
Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com>
* Address code review feedback: improve documentation and test comments
Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com>
* Address PR review: Use IsNullOrWhiteSpace and add whitespace unit tests
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
* Add an AsyncLocal AgentRunContext
* Update AgentRunContext session naming
* Make AgentRunContext readonly and add ADR
* Make session nullable and add unit tests
* Add unit tests for setting the context in AIAgent
* Apply suggestions from code review
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Fix sample in ADR
* Fix broken unit test
* Add unit test for checking if middleware can access AgentRunContext
* Fix build error after merge.
* Fix AgentRunContextTests after merge from main
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* WIP: with_output_from
* Add with_output_from to other modules; next: workflow as agent
* WIP: remove agent run events
* orchestrations
* WIP: update samples; next start at guessing_game_With_human_input.py
* Update all samples
* WIP: consolidate workflow as agent streaming vs non-streaming
* Consolidate workflow as agent streaming vs non-streaming
* Move request info event processing to a share method
* Final pass on the samples
* Fix mypy
* Fix mypy
* Comments
---------
Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
* Initial working version with tests.
* Updates to validate class data once instead of for each handler method. Also updated Diagnostics Ids to format of MAFGENWF{NUM}
* Formatting and trying to fix generation project pack.
* Another atempt at getting the genrators project to build.
* More attempts to fix generator build and pack.
* Fixing file encodings.
* Initail round of cleanup.
* Trying to fix packing.
* Still trying to fix pipeline pack.
* Remove obsolescence markers, sample updates, and docs from generator branch.
This commit separates the generator core functionality from the
deprecation of ReflectingExecutor. The removed changes will be
re-added in a dependent branch (wf-obsolete-reflector).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Mark ReflectingExecutor and IMessageHandler as obsolete.
This commit deprecates the reflection-based handler discovery approach
in favor of the new [MessageHandler] attribute with source generation.
Changes:
- Add [Obsolete] to ReflectingExecutor<T>, IMessageHandler<T>, IMessageHandler<T,R>
- Add #pragma to suppress warnings in internal reflection code
- Update Concurrent sample to use new [MessageHandler] pattern
- Add Directory.Build.props for samples to include generator
- Add documentation files explaining the migration
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Obsoleteing Reflector-based workflow code generation in favor of Source Generators and updating some samples to use new pattern.
This commit deprecates the reflection-based handler discovery approach
in favor of the new [MessageHandler] attribute with source generation.
Changes:
- Add [Obsolete] to ReflectingExecutor<T>, IMessageHandler<T>, IMessageHandler<T,R>
- Add #pragma to suppress warnings in internal reflection code
- Update Concurrent sample to use new [MessageHandler] pattern
- Add Directory.Build.props for samples to include generator
- Add documentation files explaining the migration
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Cleaning up temporary design and progress files.
---------
Co-authored-by: alliscode <bentho@microsoft.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Chris <66376200+crickman@users.noreply.github.com>
* Initial plan
* Add unit tests to improve coverage for Microsoft.Agents.AI.Abstractions
Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com>
* Fix file encoding and naming rule violation in new test files
Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com>
* Remove ChatMessageStoreExtensionsTests.cs to avoid duplication with Wesley's work
Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com>
* Fix AgentThread to AgentSession rename in unit tests
Update MockAgentWithName in AIAgentTests.cs and DelegatingAIAgentTests.cs
to use the renamed AgentSession class and corresponding methods:
- AgentThread -> AgentSession
- GetNewThreadAsync -> GetNewSessionAsync
- DeserializeThreadAsync -> DeserializeSessionAsync
- thread parameter -> session parameter
* Fix: Rename GetNewSessionAsync to CreateSessionAsync to match API changes
* Fix: Add SerializeSession override and remove async from DeserializeSessionAsync
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com>
* Move AgentSession.Serialize to AIAgent
* Address PR comments.
* Improve code and fix unit test
* Update test agents to return a default json element instead of throwing where the the result of the serialization is never used.
* Update further tests to actually serialize the session
* Replace Role and FinishReason classes with NewType + Literal
- Remove EnumLike metaclass from _types.py
- Replace Role class with NewType('Role', str) + RoleLiteral
- Replace FinishReason class with NewType('FinishReason', str) + FinishReasonLiteral
- Update all usages across codebase to use string literals
- Remove .value access patterns (direct string comparison now works)
- Add backward compatibility for legacy dict serialization format
- Update tests to reflect new string-based types
Addresses #3591, #3615
* Simplify ChatResponse and AgentResponse type hints (#3592)
- Remove overloads from ChatResponse.__init__
- Remove text parameter from ChatResponse.__init__
- Remove | dict[str, Any] from finish_reason and usage_details params
- Remove **kwargs from AgentResponse.__init__
- Both now accept ChatMessage | Sequence[ChatMessage] | None for messages
- Update docstrings and examples to reflect changes
- Fix tests that were using removed kwargs
- Fix Role type hint usage in ag-ui utils
* Remove text parameter from ChatResponseUpdate and AgentResponseUpdate (#3597)
- Remove text parameter from ChatResponseUpdate.__init__
- Remove text parameter from AgentResponseUpdate.__init__
- Remove **kwargs from both update classes
- Simplify contents parameter type to Sequence[Content] | None
- Update all usages to use contents=[Content.from_text(...)] pattern
- Fix imports in test files
- Update docstrings and examples
* Rename from_chat_response_updates to from_updates (#3593)
- ChatResponse.from_chat_response_updates → ChatResponse.from_updates
- ChatResponse.from_chat_response_generator → ChatResponse.from_update_generator
- AgentResponse.from_agent_run_response_updates → AgentResponse.from_updates
* Remove try_parse_value method from ChatResponse and AgentResponse (#3595)
- Remove try_parse_value method from ChatResponse
- Remove try_parse_value method from AgentResponse
- Remove try_parse_value calls from from_updates and from_update_generator methods
- Update samples to use try/except with response.value instead
- Update tests to use response.value pattern
- Users should now use response.value with try/except for safe parsing
* Add agent_id to AgentResponse and clarify author_name documentation (#3596)
- Add agent_id parameter to AgentResponse class
- Document that author_name is on ChatMessage objects, not responses
- Update ChatResponse docstring with author_name note
- Update AgentResponse docstring with author_name note
* Simplify ChatMessage.__init__ signature (#3618)
- Make contents a positional argument accepting Sequence[Content | str]
- Auto-convert strings in contents to TextContent
- Remove overloads, keep text kwarg for backward compatibility with serialization
- Update _parse_content_list to handle string items
- Update all usages across codebase to use new format: ChatMessage("role", ["text"])
* Allow Content as input on run and get_response
- Update prepare_messages and normalize_messages to accept Content
- Update type signatures in _agents.py and _clients.py
- Add tests for Content input handling
* Fix ChatMessage usage across packages and samples
Update all remaining ChatMessage(role=..., text=...) to use new
ChatMessage('role', ['text']) signature.
* Fix Role string usage and response format parsing
- Fix redis provider: remove .value access on string literals
- Fix durabletask ensure_response_format: set _response_format before accessing .value
* Fix ollama .value and ai_model_id issues, handle None in content list
- Fix ollama _chat_client: remove .value on string literals
- Fix ollama _chat_client: rename ai_model_id to model_id
- Fix _parse_content_list: skip None values gracefully
* Fix A2AAgent type signature to include Content
* Fix Role/FinishReason NewType dict annotations and improve test coverage to 95%
* Fix mypy errors for Role/FinishReason NewType usage
* Fix Role.TOOL and Role.ASSISTANT usage in _orchestrator_helpers.py
* Fix Role NewType usage in durabletask _models.py
2026-02-04 10:13:23 +00:00
Evan MattsonGitHubCopilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
* fix(claude): preserve $defs in JSON schema for nested Pydantic models
- Preserve $defs section from Pydantic JSON schema when converting FunctionTool to SDK MCP tool
- This fixes tools with nested Pydantic models that use $ref references
- Add test for nested type schema preservation
Fixes#3654
* Adjust shared state import
* Fix MCP tool kwargs serialization bug
---------
Co-authored-by: Evan Mattson <evan.mattson@microsoft.com>
* Support specifying types via handler and executor decorators
* Add handling for string types
* Fix typing
* Address PR feedback
* All or nothing for handler typing approach
* Fix mypy issues
* type support for request info
* Fix naming issue
* Fix mypy
In _prepare_options(), the 'instructions' key was excluded from run_options
but never re-added. This caused instructions passed via as_agent(instructions=...)
to be silently dropped, making agents in sequential workflows ignore their
configured instructions.
Fixes#3507
* Builds locally and tests pass
* Fix typo
* Updated
* Updated
* Fixed tests failing on net472 but not on dotnet10
---------
Co-authored-by: Chris Rickman <crickman@microsoft.com>
* Python: Add coverage threshold gate for PR checks (#3392)
- Add python-check-coverage.py script to enforce coverage threshold on specific modules
- Modify python-test-coverage.yml to run coverage check after tests
- Initial enforced module: agent_framework_azure_ai at 85% threshold
- Other modules are reported for visibility but don't block merges
* Fail if module not found
* Force unit test job to run
* Comment 1
* Fix coverage check to use full package paths for submodule support
* Update report format
* Add core utilities unit tests to improve coverage (#3356)
* Address PR comments: remove redundant imports and fix misleading test
* Refactor tests to use module-level mock class instead of inline classes
* Remove unnecessary tests for trivial base class implementations
* Restore base class tests with module-level helper class
* Builds locally and tests pass
* Fix typo
* Reverted nuget config change to remove internal feed and map to new public object model package with renames.
* Renaming Bot object model in additional sample.
---------
Co-authored-by: Peter Ibekwe <peibekwe@microsoft.com>
* changed AIFunction to FunctionTool and @ai_function to @tool
* test and mypy fixes
* mypy fix
* switch function tool to always_require
* fix noop
* fix github copilot imports
* test fixes
* fix ollama test
* fixes for tests
* fix tests
* reverted change to always_require and extended timeout
* fix test
Adds tests documenting current shared state behavior in subworkflows:
- State works correctly within a subworkflow
- State is isolated across parent/subworkflow boundaries
Related to #2419
2026-01-27 21:45:27 +00:00
2046 changed files with 113123 additions and 71983 deletions
All python code resides under the `python/` directory.
All C# code resides under the `dotnet/` directory.
Microsoft Agent Framework - a multi-language framework for building, orchestrating, and deploying AI agents.
The purpose of the code is to provide a framework for building AI agents.
## Repository Structure
When contributing to this repository, please follow these guidelines:
-`python/` - Python implementation → see [python/AGENTS.md](../python/AGENTS.md)
-`dotnet/` - C#/.NET implementation → see [dotnet/AGENTS.md](../dotnet/AGENTS.md)
-`docs/` - Design documents and architectural decision records
## C# Code Guidelines
## Architectural Decision Records (ADRs)
Here are some general guidelines that apply to all code.
ADRs in `docs/decisions/` capture significant design decisions and their rationale. They document considered alternatives, trade-offs, and the reasoning behind choices.
- The top of all *.cs files should have a copyright notice: `// Copyright (c) Microsoft. All rights reserved.`
-All public methods and classes should have XML documentation comments.
-After adding, modifying or deleting code, run `dotnet build`, and then fix any reported build errors.
- After adding or modifying code, run `dotnet format` to automatically fix any formatting errors.
**Templates:**
-`adr-template.md` - Full template with detailed sections
-`adr-short-template.md` - Abbreviated template for simpler decisions
### C# Sample Code Guidelines
Sample code is located in the `dotnet/samples` directory.
When adding a new sample, follow these steps:
- The sample should be a standalone .net project in one of the subdirectories of the samples directory.
- The directory name should be the same as the project name.
- The directory should contain a README.md file that explains what the sample does and how to run it.
- The README.md file should follow the same format as other samples.
- The csproj file should match the directory name.
- The csproj file should be configured in the same way as other samples.
- The project should preferably contain a single Program.cs file that contains all the sample code.
- The sample should be added to the solution file in the samples directory.
- The sample should be tested to ensure it works as expected.
- A reference to the new samples should be added to the README.md file in the parent directory of the new sample.
The sample code should follow these guidelines:
- Configuration settings should be read from environment variables, e.g. `var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");`.
- Environment variables should use upper snake_case naming convention.
- Secrets should not be hardcoded in the code or committed to the repository.
- The code should be well-documented with comments explaining the purpose of each step.
- The code should be simple and to the point, avoiding unnecessary complexity.
- Prefer inline literals over constants for values that are not reused. For example, use `new ChatClientAgent(chatClient, instructions: "You are a helpful assistant.")` instead of defining a constant for "instructions".
- Ensure that all private classes are sealed
- Use the Async suffix on the name of all async methods that return a Task or ValueTask.
- Prefer defining variables using types rather than var, to help users understand the types involved.
- Follow the patterns in the samples in the same directories where new samples are being added.
- The structure of the sample should be as follows:
- The top of the Program.cs should have a copyright notice: `// Copyright (c) Microsoft. All rights reserved.`
- Then add a comment describing what the sample is demonstrating.
- Then add the necessary using statements.
- Then add the main code logic.
- Finally, add any helper methods or classes at the bottom of the file.
### C# Unit Test Guidelines
Unit tests are located in the `dotnet/tests` directory in projects with a `.UnitTests.csproj` suffix.
Unit tests should follow these guidelines:
- Use `this.` for accessing class members
- Add Arrange, Act and Assert comments for each test
- Ensure that all private classes, that are not subclassed, are sealed
- Use the Async suffix on the name of all async methods
- Use the Moq library for mocking objects where possible
- Validate that each test actually tests the target behavior, e.g. we should not have tests that creates a mock, calls the mock and then verifies that the mock was called, without the target code being involved. We also shouldn't have tests that test language features, e.g. something that the compiler would catch anyway.
- Avoid adding excessive comments to tests. Instead favour clear easy to understand code.
- Follow the patterns in the unit tests in the same project or classes to which new tests are being added
When proposing architectural changes, create an ADR to capture options considered and the decision rationale. See [docs/decisions/README.md](../docs/decisions/README.md) for the full process.
@@ -53,7 +53,7 @@ Still have questions? Join our [weekly office hours](./COMMUNITY.md#public-commu
### ✨ **Highlights**
- **Graph-based Workflows**: Connect agents and deterministic functions using data flows with streaming, checkpointing, human-in-the-loop, and time-travel capabilities
This folder contains sample agent definitions that can be run using the declarative agent support, for python see the [declarative agent python sample folder](../python/samples/getting_started/declarative/).
This folder contains sample agent definitions that can be run using the declarative agent support, for python see the [declarative agent python sample folder](../python/samples/02-agents/declarative/).
Chosen option: **"Option 2: TypedDict with Generic Type Parameters"**, because it provides full type safety, excellent IDE support with autocompletion, and allows users to extend provider-specific options for their use cases. Extended this Generic to ChatAgents in order to also properly type the options used in agent construction and run methods.
See [typed_options.py](../../python/samples/getting_started/chat_client/typed_options.py) for a complete example demonstrating the usage of typed options with custom extensions.
See [typed_options.py](../../python/samples/02-agents/typed_options.py) for a complete example demonstrating the usage of typed options with custom extensions.
During an agent run, various components involved in the execution (middleware, filters, tools, nested agents, etc.) may need access to contextual information about the current run, such as:
1. The agent that is executing the run
2. The session associated with the run
3. The request messages passed to the agent
4. The run options controlling the agent's behavior
Additionally, some components may need to modify this context during execution, for example:
- Replacing the session with a different one
- Modifying the request messages before they reach the agent core
- Updating or replacing the run options entirely
Currently, there is no standardized way to access or modify this context from arbitrary code that executes during an agent run, especially from deeply nested call stacks where the context is not explicitly passed.
## Sample Scenario
When using an Agent as an AIFunction developers may want to pass context from the parent agent run to the child agent run. For example, the developer may want to copy chat history to the child agent, or share the same session across both agents.
To enable these scenarios, we need a way to access the parent agent run context, including e.g. the parent agent itself, the parent agent session, and the parent run options from function tool calls.
- Components executing during an agent run need access to run context without explicit parameter passing through every layer
- Context should flow naturally across async calls without manual propagation
- The design should allow modification of context properties by agent decorators (e.g., replacing options or session)
- Solution should be consistent with patterns used in similar frameworks (e.g., `FunctionInvokingChatClient.CurrentContext``HttpContext.Current`, `Activity.Current`)
## Considered Options
- **Option 1**: Pass context explicitly through all method signatures
- **Option 2**: Use `AsyncLocal<T>` to provide ambient context accessible anywhere during the run
- **Option 3**: Use a combination of explicit parameters for `RunCoreAsync` and `AsyncLocal<T>` for ambient access
## Decision Outcome
Chosen option: **Option 3** - Combination of explicit parameters and AsyncLocal ambient access.
This approach provides the best of both worlds:
1.**Explicit parameters are passed to `RunCoreAsync`**: The core agent implementation receives the parameters explicitly, making it clear what data is available and enabling easy unit testing. Any modification of these in a decorator will require calling `RunAsync` on the inner agent with the updated parameters, which would result in the inner agent creating a new `AgentRunContext` instance.
```csharp
public async Task<AgentResponse> RunAsync(
IEnumerable<ChatMessage> messages,
AgentSession? session = null,
AgentRunOptions? options = null,
CancellationToken cancellationToken = default)
{
CurrentRunContext = new(this, session, messages as IReadOnlyCollection<ChatMessage> ?? messages.ToList(), options);
2. **`AsyncLocal<AgentRunContext?>` for ambient access**: The context is stored in an `AsyncLocal<T>` field, making it accessible from any code executing during the agent run via a static property.
The main scenario for this is to allow deeply nested components (e.g., tools, chat client middleware) to access the context without needing to pass it through every method signature. These are external components that cannot easily be modified to accept additional parameters. For internal components, we prefer passing any parameters explicitly.
```csharp
public static AgentRunContext? CurrentRunContext
{
get => s_currentContext.Value;
protected set => s_currentContext.Value = value;
}
```
### AgentRunContext Design
The `AgentRunContext` class encapsulates all run-related state:
```csharp
public class AgentRunContext
{
public AgentRunContext(
AIAgent agent,
AgentSession? session,
IReadOnlyCollection<ChatMessage> requestMessages,
AgentRunOptions? agentRunOptions)
public AIAgent Agent { get; }
public AgentSession? Session { get; }
public IReadOnlyCollection<ChatMessage> RequestMessages { get; }
public AgentRunOptions? RunOptions { get; }
}
```
Key design decisions:
- **All properties are read-only**: While some of the sub-properties on the provided properties (like `AgentRunOptions.AllowBackgroundResponses`) may be mutable, the `AgentRunContext` itself is immutable and we want to discourage anyone modifying the values in the context. Modifying the context is unlikely to result in the desired behavior, as the values will typically already have been used by the time any custom code accesses them.
### Benefits
1. **Ambient Access**: Any code executing during the run can access context via `AIAgent.CurrentRunContext` without needing explicit parameters
2. **Async Flow**: `AsyncLocal<T>` automatically flows across async/await boundaries
3. **Modifiability**: Components can modify or replace session, messages, or options as needed
4. **Testability**: The explicit parameter to `RunCoreAsync` makes unit testing straightforward
Structured output is a valuable aspect of any agent system, since it forces an agent to produce output in a required format that may include required fields.
This allows easily turning unstructured data into structured data using a general-purpose language model.
## Context and Problem Statement
Structured output is currently supported only by `ChatClientAgent` and can be configured in two ways:
**Approach 1: ResponseFormat + Deserialize**
Specify the SO type schema via the `ChatClientAgent{Run}Options.ChatOptions.ResponseFormat` property at agent creation or invocation time, then use `JsonSerializer.Deserialize<T>` to extract the structured data from the response text.
Note: `RunAsync<T>` is an instance method of `ChatClientAgent` and not part of the `AIAgent` base class since not all agents support structured output.
Approach 1 is perceived as cumbersome by the community, as it requires additional effort when using primitive or collection types - the SO schema may need to be wrapped in an artificial JSON object. Otherwise, the caller will encounter an error like _Invalid schema for response_format 'Movie': schema must be a JSON Schema of 'type: "object"', got 'type: "array"'_.
This occurs because OpenAI and compatible APIs require a JSON object as the root schema.
Approach 1 is also necessary in scenarios where (a) agents can only be configured with SO at creation time (such as with `AIProjectClient`), (b) the SO type is not known at compile time, or (c) the JSON schema is represented as text (for declarative agents) or as a `JsonElement`.
Approach 2 is more convenient and works seamlessly with primitives and collections. However, it requires the SO type to be known at compile time, making it less flexible.
Additionally, since the `RunAsync<T>` methods are instance methods of `ChatClientAgent` and are not part of the `AIAgent` base class, applying decorators like `OpenTelemetryAgent` on top of `ChatClientAgent` prevents users from accessing `RunAsync<T>`, meaning structured output is not available with decorated agents.
Given the different scenarios above in which structured output can be used, there is no one-size-fits-all solution. Each approach has its own advantages and limitations,
and the two can complement each other to provide a comprehensive structured output experience across various use cases.
## Approaches Overview
1. SO usage via `ResponseFormat` property
2. SO usage via `RunAsync<T>` generic method
## 1. SO usage via `ResponseFormat` property
This approach should be used in the following scenarios:
- 1.1 SO result as text is sufficient as is, and deserialization is not required
- 1.2 SO for inter-agent collaboration
- 1.3 SO can only be configured at agent creation time (such as with `AIProjectClient`)
- 1.4 SO type is not known at compile time and represented by System.Type
- 1.5 SO is represented by JSON schema and there's no corresponding .NET type either at compile time or at runtime
- 1.6 SO in streaming scenarios, where the SO response is produced in parts
**Note: Primitives and arrays are not supported by this approach.**
When a caller provides a schema via `ResponseFormat`, they are explicitly telling the framework what schema to use. The framework passes that schema through as-is and
is not responsible for transforming it. Because the framework does not own the schema, it cannot wrap primitives or arrays into a JSON object to satisfy API requirements,
nor can it unwrap the response afterward - the caller controls the schema and is responsible for ensuring it is compatible with the underlying API.
This is in contrast to the `RunAsync<T>` approach (section 2), where the caller provides a type `T` and says "make it work." In that case, the caller does not
dictate the schema - the framework infers the schema from `T`, owns the end-to-end pipeline (schema generation, API invocation, and deserialization), and can
therefore wrap and unwrap primitives and arrays transparently.
Additionally, in streaming scenarios (1.6), the framework cannot reliably unwrap a response it did not wrap, since it has no way of knowing whether the caller wrapped the schema.Wrapping and unwrapping can only be done safely when the framework owns the entire lifecycle - from schema creation through deserialization — which is only the case with `RunAsync<T>`.
If a caller needs to work with primitives or arrays via the `ResponseFormat` approach, they can easily create a wrapper type around them:
```csharp
public class MovieListWrapper
{
public List<string> Movies { get; set; }
}
```
### 1.1 SO result as text is sufficient as is, and deserialization is not required
In this scenario, the caller only needs the raw JSON text returned by the model and does not need to deserialize it into a .NET type.
The SO schema is specified via `ResponseFormat` at agent creation or invocation time, and the response text is consumed directly from the `AgentResponse`.
In this scenario, the SO schema can only be configured at agent creation time (such as with `AIProjectClient`) and cannot be changed on a per-run basis.
The caller specifies the `ResponseFormat` when creating the agent, and all subsequent invocations use the same schema.
```csharp
AIProjectClient client = ...;
AIAgent agent = await client.CreateAIAgentAsync(model: "<model>", new ChatClientAgentOptions()
### 1.4 SO type not known at compile time and represented by System.Type
In this scenario, the SO type is not known at compile time and is provided as a `System.Type` at runtime. This is useful for dynamic scenarios where the schema is determined programmatically,
such as when building tooling or frameworks that work with user-defined types.
```csharp
Type soType = GetStructuredOutputTypeFromConfiguration(); // e.g., typeof(PersonInfo)
### 1.5 SO represented by JSON schema with no corresponding .NET type
In this scenario, the SO schema is represented as raw JSON schema text or a `JsonElement`, and there is no corresponding .NET type available at compile time or runtime.
This is typical for declarative agents or scenarios where schemas are loaded from external configuration.
```csharp
// JSON schema provided as a string, e.g., loaded from a configuration file
// Consume the SO result as text since there's no .NET type to deserialize into
Console.WriteLine(response.Text);
```
### 1.6 SO in streaming scenarios
In this scenario, the SO response is produced incrementally in parts via streaming. The caller specifies the `ResponseFormat` and consumes the response chunks as they arrive.
Deserialization is performed after all chunks have been received.
IAsyncEnumerable<AgentResponseUpdate> updates = agent.RunStreamingAsync("Please provide information about John Smith, who is a 35-year-old software engineer.");
This approach provides a convenient way to work with structured output on a per-run basis when the target type is known at compile time and a typed instance of the result
is required.
### Decision Drivers
1. Support arrays and primitives as SO types
2. Support complex types as SO types
3. Work with `AIAgent` decorators (e.g., `OpenTelemetryAgent`)
4. Enable SO for all AI agents, regardless of whether they natively support it
### Considered Options
1. `RunAsync<T>` as an instance method of `AIAgent` class delegating to virtual `RunCoreAsync<T>`
2. `RunAsync<T>` as an extension method using feature collection
3. `RunAsync<T>` as a method of the new `ITypedAIAgent` interface
4. `RunAsync<T>` as an instance method of `AIAgent` class working via the new `AgentRunOptions.ResponseFormat` property
### 1. `RunAsync<T>` as an instance method of `AIAgent` class delegating to virtual `RunCoreAsync<T>`
This option adds the `RunAsync<T>` method directly to the `AIAgent` base class.
throw new NotSupportedException($"The agent of type '{this.GetType().FullName}' does not support typed responses.");
}
}
```
Agents with native SO support override the `RunCoreAsync<T>` method to provide their implementation. If not overridden, the method throws a `NotSupportedException`.
Users will call the generic `RunAsync<T>` method directly on the agent:
```csharp
AIAgent agent = chatClient.AsAIAgent(name: "HelpfulAssistant", instructions: "You are a helpful assistant.");
AgentResponse<PersonInfo> response = await agent.RunAsync<PersonInfo>("Please provide information about John Smith, who is a 35-year-old software engineer.");
```
Decision drivers satisfied:
1. Support arrays and primitives as SO types
2. Support complex types as SO types
3. Work with `AIAgent` decorators (e.g., `OpenTelemetryAgent`)
4. Enable SO for all AI agents, regardless of whether they natively support it
Pros:
- The `AIAgent.RunAsync<T>` method is easily discoverable.
- Both the SO decorator and `ChatClientAgent` have compile-time access to the type `T`, allowing them to use the native `IChatClient.GetResponseAsync<T>` API, which handles primitives and collections seamlessly.
Cons:
- Agents without native SO support will still expose `RunAsync<T>`, which may be misleading.
- `ChatClientAgent` exposing `RunAsync<T>` may be misleading when the underlying chat client does not support SO.
- All `AIAgent` decorators must override `RunCoreAsync<T>` to properly handle `RunAsync<T>` calls.
### 2. `RunAsync<T>` as an extension method using feature collection
This option uses the Agent Framework feature collection (implemented via `AgentRunOptions.AdditionalProperties`) to pass a `StructuredOutputFeature` to agents, signaling that SO is requested.
Agents with native SO support check for this feature. If present, they read the target type, build the schema, invoke the underlying API, and store the response back in the feature.
```csharp
public class StructuredOutputFeature
{
public StructuredOutputFeature(Type outputType)
{
this.OutputType = outputType;
}
[JsonIgnore]
public Type OutputType { get; set; }
public JsonSerializerOptions? SerializerOptions { get; set; }
public AgentResponse? Response { get; set; }
}
```
The `RunAsync<T>` extension method for `AIAgent` adds this feature to the collection.
```csharp
public static async Task<AgentResponse<T>> RunAsync<T>(
((options ??= new AgentRunOptions()).AdditionalProperties ??= []).Add(typeof(StructuredOutputFeature).FullName!, structuredOutputFeature);
var response = await agent.RunAsync(messages, session, options, cancellationToken).ConfigureAwait(false);
if (structuredOutputFeature.Response is not null)
{
return new StructuredOutputResponse<T>(structuredOutputFeature.Response, response, serializerOptions);
}
throw new InvalidOperationException("No structured output response was generated by the agent.");
}
```
Users will call the `RunAsync<T>` extension method directly on the agent:
```csharp
AIAgent agent = chatClient.AsAIAgent(name: "HelpfulAssistant", instructions: "You are a helpful assistant.");
AgentResponse<PersonInfo> response = await agent.RunAsync<PersonInfo>("Please provide information about John Smith, who is a 35-year-old software engineer.");
```
Decision drivers satisfied:
1. Support arrays and primitives as SO types
2. Support complex types as SO types
3. Work with `AIAgent` decorators (e.g., `OpenTelemetryAgent`)
4. Enable SO for all AI agents, regardless of whether they natively support it
Pros:
- The `RunAsync<T>` extension method is easily discoverable.
- The `AIAgent` public API surface remains unchanged.
- No changes required to `AIAgent` decorators.
Cons:
- Agents without native SO support will still expose `RunAsync<T>`, which may be misleading.
- `ChatClientAgent` exposing `RunAsync<T>` may be misleading when the underlying chat client does not support SO.
### 3. `RunAsync<T>` as a method of the new `ITypedAIAgent` interface
This option defines a new `ITypedAIAgent` interface that agents with SO support implement. Agents without SO support do not implement it, allowing users to check for SO capability via interface detection.
The interface:
```csharp
public interface ITypedAIAgent
{
Task<AgentResponse<T>> RunAsync<T>(
IEnumerable<ChatMessage> messages,
AgentSession? session = null,
JsonSerializerOptions? serializerOptions = null,
AgentRunOptions? options = null,
CancellationToken cancellationToken = default);
...
}
```
Agents with SO support implement this interface:
```csharp
public sealed partial class ChatClientAgent : AIAgent, ITypedAIAgent
{
public async Task<AgentResponse<T>> RunAsync<T>(
IEnumerable<ChatMessage> messages,
AgentSession? session = null,
JsonSerializerOptions? serializerOptions = null,
AgentRunOptions? options = null,
CancellationToken cancellationToken = default)
{
...
}
}
```
However, `ChatClientAgent` presents a challenge: it can work with chat clients that either support or do not support SO. Implementing the interface does not guarantee
the underlying chat client supports SO, which undermines the core idea of using interface detection to determine SO capability.
Additionally, to allow users to access interface methods on decorated agents, all decorators must implement `ITypedAIAgent`. This makes it difficult for users to
determine whether the underlying agent actually supports SO, further weakening the purpose of this approach.
Furthermore, users would have to probe the agent type to check if it implements the `ITypedAIAgent` interface and cast it accordingly to access the `RunAsync<T>` methods.
This adds friction to the user experience. A `RunAsync<T>` extension method for `AIAgent` could be provided to alleviate that.
Given these drawbacks, this option is more complex to implement than the others without providing clear benefits.
Decision drivers satisfied:
1. Support arrays and primitives as SO types
2. Support complex types as SO types
3. Work with `AIAgent` decorators (e.g., `OpenTelemetryAgent`)
4. Enable SO for all AI agents, regardless of whether they natively support it
Pros:
- Both the SO decorator and `ChatClientAgent` have compile-time access to the type `T`, allowing them to use the native `IChatClient.GetResponseAsync<T>` API, which handles primitives and collections seamlessly.
Cons:
- `ChatClientAgent` implementing `ITypedAIAgent` may be misleading when the underlying chat client does not support SO.
- All `AIAgent` decorators must implement `ITypedAIAgent` to handle `RunAsync<T>` calls.
- Decorators implementing the interface may mislead users into thinking the underlying agent natively supports SO.
- Agents must implement all members of `ITypedAIAgent`, not just a core method.
- Users must check the agent type and cast to `ITypedAIAgent` to access `RunAsync<T>`.
### 4. `RunAsync<T>` as an instance method of `AIAgent` class working via the new `AgentRunOptions.ResponseFormat` property
This option adds a `ResponseFormat` property of type `ChatResponseFormat` to `AgentRunOptions`. Agents that support SO check for the presence of
this property in the options passed to `RunAsync` to determine whether structured output is requested. If present, they use the schema from `ResponseFormat`
to invoke the underlying API and obtain the SO response.
```csharp
public class AgentRunOptions
{
public ChatResponseFormat? ResponseFormat { get; set; }
}
```
Additionally, a generic `RunAsync<T>` method is added to `AIAgent` that initializes the `ResponseFormat` based on the type `T` and delegates to the non-generic `RunAsync`.
return new AgentResponse<T>(response, serializerOptions);
}
}
```
Users call the generic `RunAsync<T>` method directly on the agent:
```csharp
AIAgent agent = chatClient.AsAIAgent(name: "HelpfulAssistant", instructions: "You are a helpful assistant.");
AgentResponse<PersonInfo> response = await agent.RunAsync<PersonInfo>("Please provide information about John Smith, who is a 35-year-old software engineer.");
```
Decision drivers satisfied:
1. Support arrays and primitives as SO types
2. Support complex types as SO types
3. Work with `AIAgent` decorators (e.g., `OpenTelemetryAgent`)
4. Enable SO for all AI agents, regardless of whether they natively support it
Pros:
- The `AIAgent.RunAsync<T>` method is easily discoverable.
- No changes required to `AIAgent` decorators
Cons:
- Agents without native SO support will still expose `RunAsync<T>`, which may be misleading.
- `ChatClientAgent` exposing `RunAsync<T>` may be misleading when the underlying chat client does not support SO.
| Decorator changes | ❌ All decorators must override `RunCoreAsync<T>` | ✅ No changes required | ❌ All decorators must implement `ITypedAIAgent` | ✅ No changes required to decorators |
| Primitives/collections handling | ✅ Native support via `IChatClient.GetResponseAsync<T>` | ❌ Must wrap/unwrap internally | ✅ Native support via `IChatClient.GetResponseAsync<T>` | ❌ Must wrap/unwrap internally |
| Misleading API exposure | ❌ Agents without SO still expose `RunAsync<T>` | ❌ Agents without SO still expose `RunAsync<T>` | ❌ Interface on `ChatClientAgent` may be misleading | ❌ Agents without SO still expose `RunAsync<T>` |
| Implementation burden | ❌ Decorators must override method | ❌ Must handle schema wrapping | ❌ Agents must implement all interface members | ✅ Delegates to existing `RunAsync` via `ResponseFormat` |
## Cross-Cutting Aspects
1. **The `useJsonSchemaResponseFormat` parameter**: The `ChatClientAgent.RunAsync<T>` method has this parameter to enable structured output on LLMs that do not natively support it.
It works by adding a user message like "Respond with a JSON value conforming to the following schema:" along with the JSON schema. However, this approach has not been reliable historically. The recommendation is not to carry this parameter forward, regardless of which option is chosen.
2. **Primitives and array types handling**: There are a few options for how primitive and array types can be handled in the Agent Framework:
1. **Never wrap**, regardless of whether the schema is provided via `ResponseFormat` or `RunAsync<T>`.
- Pro: No changes needed; user has full control.
- Pro: No issues with unwrapping in streaming scenarios.
- Con: User must wrap manually.
2. **Always wrap**, regardless of whether the schema is provided via `ResponseFormat` or `RunAsync<T>`.
- Pro: Consistent wrapping behavior; no manual wrapping needed.
- Con: Inconsistent unwrapping behavior; it may be unexpected to have SO result wrapped when schema is provided via `ResponseFormat`.
- Con: Impossible to know if SO result is wrapped to unwrap it in streaming scenarios.
3. **Wrap only for `RunAsync<T>`** and do not wrap the schema provided via `ResponseFormat`.
- Pro: No unexpectedly wrapped result when schema is provided via `ResponseFormat`.
- Pro: Solves the problem with unwrapping in streaming scenarios.
4. **User decides** whether to wrap schema provided via `ResponseFormat` using a new `wrapPrimitivesAndArrays` property of `ChatResponseFormatJson`. For SO provided via `RunAsync<T>`, AF always wraps.
- Pro: No manual wrapping needed; just flip a switch.
- Pro: Solves the problem with unwrapping in streaming scenarios.
- Con: Extends the public API surface.
3. **Structured output for agents without native SO support**: Some AI agents in AF do not support structured output natively. This is either because it is not part of the protocol (e.g., A2A agent) or because the agents use LLMs without structured output capabilities.
To address this gap, AF can provide the `StructuredOutputAgent` decorator. This decorator wraps any `AIAgent` and adds structured output support by obtaining the text response from the decorated agent and delegating it to a configured chat client for JSON transformation.
```csharp
public class StructuredOutputAgent : DelegatingAIAgent
{
private readonly IChatClient _chatClient;
public StructuredOutputAgent(AIAgent innerAgent, IChatClient chatClient)
return new StructuredOutputAgentResponse(soResponse, textResponse);
}
}
```
The decorator preserves the original response from the decorated agent and surfaces it via the `OriginalResponse` property on the returned `StructuredOutputAgentResponse`.
This allows users to access both the original unstructured response and the new structured response when using this decorator.
```csharp
public class StructuredOutputAgentResponse : AgentResponse
AIAgent baseAgent = meaiChatClient.AsAIAgent(name: "HelpfulAssistant", instructions: "You are a helpful assistant.");
// Register the StructuredOutputAgent decorator during agent building
AIAgent agent = baseAgent
.AsBuilder()
.UseStructuredOutput(meaiChatClient)
.Build();
AgentResponse<PersonInfo> response = await agent.RunAsync<PersonInfo>("Please provide information about John Smith, who is a 35-year-old software engineer.");
It was decided to keep both approaches for structured output - via `ResponseFormat` and via `RunAsync<T>` since they serve different scenarios and use cases.
For the `RunAsync<T>` approach, option 4 was selected, which adds a generic `RunAsync<T>` method to `AIAgent` that works via the new `AgentRunOptions.ResponseFormat` property.
This was chosen for its simplicity and because no changes are required to existing `AIAgent` decorators.
For cross-cutting aspects, the `useJsonSchemaResponseFormat` parameter will not be carried forward due to reliability issues.
For handling primitives and array types, option 3 was selected: wrap only for `RunAsync<T>` and do not wrap the schema provided via `ResponseFormat`.
This avoids the issues described in the Approach 1 section note.
Finally, it was decided not to include the `StructuredOutputAgent` decorator in the framework, since the reliability of producing structured output via an additional
LLM call may not be sufficient for all scenarios. Instead, this pattern is provided as a sample to demonstrate how structured output can be achieved for agents without native support,
giving users a reference implementation they can adapt to their own requirements.
- Official docs (Microsoft Learn): <https://learn.microsoft.com/agent-framework/integrations/azure-functions>
## Document structure
| File | Purpose |
| --- | --- |
| `README.md` | Main technical overview: architecture, hosting models, orchestration patterns, and links to samples. |
| `durable-agents-ttl.md` | Deep-dive on session Time-To-Live (TTL) configuration and behavior. |
Add new sibling documents when a topic is too detailed for the README (e.g., a new feature like reliable streaming or MCP tool exposure). Keep the README focused on orientation and link out to siblings for depth.
## Writing guidelines
- **Audience**: Developers already familiar with the Microsoft Agent Framework who want to understand what durability adds and how to use it.
- **Host-agnostic first**: Durable agents work in console apps, Azure Functions, and any Durable Task–compatible host. Show host-agnostic patterns (plain orchestration functions, `IServiceCollection` registration) before Azure Functions–specific patterns. Avoid giving the impression that Azure Functions is the only hosting option.
- **Both languages**: Always include C# and Python examples side by side. Keep them equivalent in functionality.
- **Callout syntax**: Use GitHub-flavored callouts (`> [!NOTE]`, `> [!IMPORTANT]`, `> [!WARNING]`) rather than bold-text callouts (`> **Note:** ...`).
- **Line length**: Do not wrap long lines. Rely on text viewers / renderers for line wrapping.
- **Tables**: Use spaces around pipes in separator rows (`| --- |` not `|---|`).
- **Code snippets**: Keep them minimal and self-contained. Omit boilerplate (using statements, environment variable reads) unless the snippet is specifically about setup.
- **Cross-references**: Link to Microsoft Learn for conceptual background (Durable Entities, Durable Task Scheduler, Azure Functions). Link to sibling docs within this directory for feature deep-dives.
## Linting
Run markdownlint on all documents before committing, with line-length checks disabled:
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.
| 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 |
| 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 |
> [!NOTE]
> For a step-by-step tutorial and deployment guidance, see [Azure Functions (Durable)](https://learn.microsoft.com/agent-framework/integrations/azure-functions) on Microsoft Learn.
## 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:
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.
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.
### Agent session identity
Every durable agent session is identified by an `AgentSessionId`, which has two components:
- **Name** – the registered name of the agent (case-insensitive).
- **Key** – a unique session key (case-sensitive), typically a GUID.
The session ID is mapped to an underlying Durable Task entity ID with a `dafx-` prefix (e.g., `dafx-joker`). This naming convention is consistent across both .NET and Python implementations.
## Architecture
### .NET
The .NET implementation consists of two NuGet packages:
| Package | Purpose |
| --- | --- |
| `Microsoft.Agents.AI.DurableTask` | Core durable agent types: `DurableAIAgent`, `AgentEntity`, `DurableAgentSession`, `AgentSessionId`, `DurableAgentsOptions`, and the state model. |
| `Microsoft.Agents.AI.Hosting.AzureFunctions` | Azure Functions hosting integration: auto-generated HTTP endpoints, MCP tool triggers, entity function triggers, and the `ConfigureDurableAgents` extension method on `FunctionsApplicationBuilder`. |
Key types:
- **`DurableAIAgent`** – A subclass of `AIAgent` used *inside orchestrations*. Obtained via `context.GetAgent("agentName")`, it routes `RunAsync` calls through the orchestration's entity APIs so that each call is checkpointed.
- **`DurableAIAgentProxy`** – A subclass of `AIAgent` used *outside orchestrations* (e.g., from HTTP triggers or console apps). It signals the entity via `DurableTaskClient` and polls for the response.
- **`AgentEntity`** – The `TaskEntity<DurableAgentState>` that hosts the real agent. It loads the registered `AIAgent` by name, wraps it in an `EntityAgentWrapper`, feeds it the full conversation history, and persists the result.
- **`DurableAgentSession`** – An `AgentSession` subclass that carries the `AgentSessionId`.
- **`DurableAgentsOptions`** – Builder for registering agents and configuring TTL.
### Python
The core Python implementation is in the `agent-framework-durabletask` package (`python/packages/durabletask`). Azure Functions hosting (including `AgentFunctionApp`) is in the separate `agent-framework-azurefunctions` package (`python/packages/azurefunctions`).
Key types:
- **`DurableAIAgent`** – A generic proxy (`DurableAIAgent[TaskT]`) implementing `SupportsAgentRun`. Returns a `TaskT` from `run()` — either an `AgentResponse` (client context) or a `DurableAgentTask` (orchestration context, must be `yield`ed).
- **`DurableAIAgentWorker`** – Wraps a `TaskHubGrpcWorker` and registers agents as durable entities via `add_agent()`.
- **`DurableAIAgentClient`** – Wraps a `TaskHubGrpcClient` for external callers. `get_agent()` returns a `DurableAIAgent[AgentResponse]`.
- **`DurableAIAgentOrchestrationContext`** – Wraps an `OrchestrationContext` for use inside orchestrations. `get_agent()` returns a `DurableAIAgent[DurableAgentTask]`.
- **`AgentEntity`** – Platform-agnostic agent execution logic that manages state, invokes the agent, handles streaming, and calls response callbacks.
## Hosting models
### Azure Functions
The recommended production hosting model. A single call to `ConfigureDurableAgents` (C#) or `AgentFunctionApp` (Python) automatically:
- Registers agent entities with the Durable Task worker.
- Generates HTTP endpoints at `/api/agents/{agentName}/run` for each registered agent.
- Supports `thread_id` query parameter / JSON field and the `x-ms-thread-id` response header for session continuity.
- Supports fire-and-forget via the `x-ms-wait-for-response: false` header (returns HTTP 202).
For self-hosted or non-serverless scenarios, register durable agents via `IServiceCollection.ConfigureDurableAgents` (.NET) or `DurableAIAgentWorker` (Python) with explicit Durable Task worker and client configuration.
Durable agents can be composed into deterministic, checkpointed workflows using Durable Task orchestrations. The orchestration framework replays orchestrator code on failure, so completed agent calls are not re-executed.
### Patterns
| Pattern | Description |
| --- | --- |
| **Sequential (chaining)** | Call agents one after another, passing outputs forward. |
| **Parallel (fan-out/fan-in)** | Run multiple agents concurrently and aggregate results. |
| **Conditional** | Branch orchestration logic based on structured agent output. |
| **Human-in-the-loop** | Pause for external events (approvals, feedback) with optional timeouts. |
### Using agents in orchestrations
Inside an orchestration function, obtain a `DurableAIAgent` via the orchestration context. Each agent gets its own session (created with `CreateSessionAsync` / `create_session`), and you can call the same agent multiple times on the same session to maintain conversation context across sequential invocations.
# Get a durable agent reference — works in any host (standalone worker, Azure Functions, etc.)
writer=agent_ctx.get_agent("WriterAgent")
# Create a session to maintain conversation context across multiple calls
session=writer.create_session()
# First call: generate an initial draft
draft=yieldwriter.run(
messages="Write a concise inspirational sentence about learning.",
session=session,
)
# Second call: refine the draft — the agent sees the full conversation history
refined=yieldwriter.run(
messages=f"Improve this further while keeping it under 25 words: {draft.text}",
session=session,
)
returnrefined.text
```
> [!IMPORTANT]
> In .NET, `DurableAIAgent.RunAsync<T>` deliberately avoids `ConfigureAwait(false)` because the Durable Task Framework uses a custom synchronization context — all continuations must run on the orchestration thread.
## Streaming and response callbacks
Durable agents do not support true end-to-end streaming because entity operations are request/response. However, **reliable streaming** is supported via response callbacks:
- **`IAgentResponseHandler`** (.NET) or **`AgentResponseCallbackProtocol`** (Python) – Implement this interface to receive streaming updates as the underlying agent generates them (e.g., push tokens to a Redis Stream for client consumption).
- The entity still returns the complete `AgentResponse` after the stream is fully consumed.
- Clients can reconnect and resume reading from a cursor-based stream (e.g., Redis Streams) without losing messages.
See the **Reliable Streaming** samples for a complete implementation using Redis Streams.
## Session TTL (Time-To-Live)
Durable agent sessions support automatic cleanup via configurable TTL. See [Session TTL](durable-agents-ttl.md) for details on configuration, behavior, and best practices.
## Observability
When using the [Durable Task Scheduler](https://learn.microsoft.com/azure/azure-functions/durable/durable-task-scheduler/durable-task-scheduler) as the durable backend, you get built-in observability through its dashboard:
- **Conversation history** – View complete chat history for each agent session.
- **Orchestration visualization** – See multi-agent execution flows, including parallel branches and conditional logic.
dotnet test tests/Microsoft.Agents.AI.<Package>.UnitTests
dotnet format src/Microsoft.Agents.AI.<Package>
# Run a single test
dotnet test --filter "FullyQualifiedName~Namespace.TestClassName.TestMethodName"
# Run unit tests only
dotnet test --filter FullyQualifiedName\~UnitTests
```
Use `--tl:off` when building to avoid flickering when running commands in the agent.
## Speeding Up Builds and Testing
The full solution is large. Use these shortcuts:
| Change type | What to do |
|-------------|------------|
| Isolated/Internal logic | Build only the affected project and its `*.UnitTests` project. Fix issues, then build the full solution and run all unit tests. |
| Public API surface | Build the full solution and run all unit tests immediately. |
Example: Building a single code project for all target frameworks
Example: Running tests for a single project using .NET 10.
```bash
# From dotnet/ directory
dotnet test ./tests/Microsoft.Agents.AI.Abstractions.UnitTests -f net10.0
```
Example: Running a single test in a specific project using .NET 10.
Provide the full namespace, class name, and method name for the test you want to run:
```bash
# From dotnet/ directory
dotnet test ./tests/Microsoft.Agents.AI.Abstractions.UnitTests -f net10.0 --filter "FullyQualifiedName~Microsoft.Agents.AI.Abstractions.UnitTests.AgentRunOptionsTests.CloningConstructorCopiesProperties"
```
### Multi-target framework tip
Most projects target multiple .NET frameworks. If the affected code does **not** use `#if` directives for framework-specific logic, pass `-f net10.0` to speed up building and testing.
### Package Restore tip
`dotnet build` will try and restore packages for all projects on each build, which can be slow.
Unless packages have been changed, or it's the first time building the solution, add `--no-restore` to the build command to skip this step and speed up builds.
Just remember to run `dotnet restore` after pulling changes, making changes to project references, or when building for the first time.
### Testing on Linux tip
Unit tests target both .NET Framework as well as .NET Core. When running on Linux, only the .NET Core tests can be run, as .NET Framework is not supported on Linux.
To run only the .NET Core tests, use the `-f net10.0` option with `dotnet test`.
| `tests/` | Test projects — named `<Source-Code-Project>.UnitTests` or `<Source-Code-Project>.IntegrationTests` |
| `samples/` | Sample projects |
| `src/Shared`, `src/LegacySupport` | Shared code files included by multiple source code projects (see README.md files in these folders or their subdirectories for instructions on how to include them in a project) |
description:How to build, run and verify the .NET sample projects in the Agent Framework repository. Use this when a user wants to verify that the samples still function as expected.
---
# Verifying .NET Sample Projects
## Sample Pre-requisites
We should only support verifying samples that:
1. Use environment variables for configuration.
2. Have no complex setup requirements, e.g., where multiple applications need to be run together, or where we need to launch a browser, etc.
Always report to the user which samples were run and which were not, and why.
## Verifying a sample
Samples should be verified to ensure that they actually work as intended and that their output matches what is expected.
For each sample that is run, output should be produced that shows the result and explains the reasoning about what output
was expected, what was produced, and why it didn't match what the sample was expected to produce.
Steps to verify a sample:
1. Read the code for the sample
1. Check what environment variables are required for the sample
1. Check if each environment variable has been set
1. If there are any missing, give the user a list of missing environment variables to set and terminate
1. Summarize what the expected output of the sample should be
1. Run the sample
1. Show the user any output from the sample run as it gets produced, so that they can see the run progress
1. Check the output of the run against expectations
1. After running all requested samples, produce output for each sample that was verified:
1. If expectations were matched, output the following:
```text
[Sample Name] Succeeded
```
1. If expectations were not matched, output the following:
```text
[Sample Name] Failed
Actual Output:
[What the sample produced]
Expected Output:
[Explanation of what was expected and why the actual output didn't match expectations]
```
## Environment Variables
Most samples use environment variables to configure settings.
```csharp
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
```
To run a sample, the environment variables should be set first.
Before running a sample, check whether each environment variable in the sample has a value and
then give the user a list of environment variables to set.
You can provide the user some examples of how to set the variables like this:
Instructions for AI coding agents working in the .NET codebase.
## Build, Test, and Lint Commands
See `./.github/skills/build-and-test/SKILL.md` for detailed instructions on building, testing, and linting projects.
## Project Structure
See `./.github/skills/project-structure/SKILL.md` for an overview of the project structure.
### Core types
-`AIAgent`: The abstract base class that all agents derive from, providing common methods for interacting with an agent.
-`AgentSession`: The abstract base class that all agent sessions derive from, representing a conversation with an agent.
-`ChatClientAgent`: An `AIAgent` implementation that uses an `IChatClient` to send messages to an AI provider and receive responses.
-`IChatClient`: Interface for sending messages to an AI provider and receiving responses. Used by `ChatClientAgent` and implemented by provider-specific packages.
-`FunctionInvokingChatClient`: Decorator for `IChatClient` that adds function invocation capabilities.
-`AITool`: Represents a tool that an agent/AI provider can use, with metadata and an execution delegate.
-`AIFunction`: A specific type of `AITool` that represents a local function the agent/AI provider can call, with parameters and return types defined.
-`ChatMessage`: Represents a message in a conversation.
-`AIContent`: Represents content in a message, which can be text, a function call, tool output and more.
### External Dependencies
The framework integrates with `Microsoft.Extensions.AI` and `Microsoft.Extensions.AI.Abstractions` (external NuGet packages)
using types like `IChatClient`, `FunctionInvokingChatClient`, `AITool`, `AIFunction`, `ChatMessage`, and `AIContent`.
## Key Conventions
- **Encoding**: All new files must be saved with UTF-8 encoding with BOM (Byte Order Mark). This is required for `dotnet format` to work correctly.
- **Copyright header**: `// Copyright (c) Microsoft. All rights reserved.` at top of all `.cs` files
- **XML docs**: Required for all public methods and classes
- **Async**: Use `Async` suffix for methods returning `Task`/`ValueTask`
- **Private classes**: Should be `sealed` unless subclassed
- **Config**: Read from environment variables with `UPPER_SNAKE_CASE` naming
- **Tests**: Add Arrange/Act/Assert comments; use Moq for mocking
## Key Design Principles
When developing or reviewing code, verify adherence to these key design principles:
- **DRY**: Avoid code duplication by moving common logic into helper methods or helper classes.
- **Single Responsibility**: Each class should have one clear responsibility.
- **Encapsulation**: Keep implementation details private and expose only necessary public APIs.
- **Strong Typing**: Use strong typing to ensure that code is self-documenting and to catch errors at compile time.
## Sample Structure
Samples (in `./samples/` folder) should follow this structure:
1. Copyright header: `// Copyright (c) Microsoft. All rights reserved.`
2. Description comment explaining what the sample demonstrates
3. Using statements
4. Main code logic
5. Helper methods at bottom
Configuration via environment variables (never hardcode secrets). Keep samples simple and focused.
When adding a new sample:
- Create a standalone project in `samples/` with matching directory and project names
- Include a README.md explaining what the sample does and how to run it
- Add the project to the solution file
- Reference the sample in the parent directory's README.md
@@ -19,6 +19,9 @@ string endpoint = builder.Configuration["AZURE_OPENAI_ENDPOINT"] ?? throw new In
stringdeploymentName=builder.Configuration["AZURE_OPENAI_DEPLOYMENT_NAME"]??thrownewInvalidOperationException("AZURE_OPENAI_DEPLOYMENT_NAME is not set.");
// Create the AI agent with tools
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
@@ -19,6 +19,9 @@ string endpoint = builder.Configuration["AZURE_OPENAI_ENDPOINT"] ?? throw new In
stringdeploymentName=builder.Configuration["AZURE_OPENAI_DEPLOYMENT_NAME"]??thrownewInvalidOperationException("AZURE_OPENAI_DEPLOYMENT_NAME is not set.");
// Create the AI agent
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.