* Fix Magentic to share agent replies across team
The per-round instruction was sent untargeted (fan-out delivered it to
every participant) and replies were never relayed, so a later speaker saw
the prior speaker's instruction but not its response - inverted from
GroupChatHost and the Python reference.
- Target the instruction at the selected speaker only.
- Broadcast each reply to the other participants (buffered, no TurnToken),
excluding the responder via _currentSpeakerExecutorId, mirroring
GroupChatHost.
- Persist _currentSpeakerExecutorId across checkpoints.
- Add a regression test.
* Address review feedback: null-guard, explicit checkpoint key, drop vacuous assertion
* Address review feedback: centralize checkpoint keys, clear current speaker
- Move CurrentSpeakerStateKey into MagenticConstants as
nameof(CurrentSpeakerStateKey)
- Clear _currentSpeakerExecutorId in ResetAndReplanAsync and
PrepareFinalAnswerAsync so a checkpoint taken in those windows does not
persist a stale speaker
- Add UTF-8 BOM to RecordingEchoAgent.cs to satisfy the format check.
* docs: clarify checkpoint storage security model and deserialization trust boundaries
Add Security Model documentation sections to the checkpoint encoding and
Azure Functions serialization modules explaining:
- Checkpoint storage is a trusted data source requiring access controls
- The RestrictedUnpickler allowlist is defense-in-depth, not a security boundary
- Developer responsibilities for securing storage backends
- Guidance on using allowed_types and strip_pickle_markers
Co-authored-by: Azure SRE Agent <noreply@microsoft.com>
* Apply suggestions from code review
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
---------
Co-authored-by: Azure SRE Agent <noreply@microsoft.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* fix: use getattr for non-OpenAI provider response compatibility
Fixes#6234Fixes#6235
Use getattr with None fallback for system_fingerprint and output
attributes to prevent AttributeError when non-OpenAI providers
return response objects without these fields.
* fix: use typed variable for response output to satisfy pyright
Fixes#6235
Use getattr with None fallback for the output attribute, and assign
to a typed list variable before the match statement to help pyright
narrow the response item types correctly.
* fix: rename response_outputs to avoid name collision with case-block variable
Fixes#6235
Rename outputs to response_outputs on line 1974 to avoid mypy error
about conflicting variable names in the match statement's case blocks.
Also use list[Any] for explicit generic type annotation.
* fix: use cast(list[Any]) for response output to satisfy pyright
Fixes#6235
The getattr() call returns Unknown type which pyright cannot narrow
in the match statement. Use an explicit cast to list[Any].
* fix: use hasattr guard instead of getattr for response.output
Fixes#6235
Using hasattr(response, 'output') and then accessing response.output
directly gives pyright enough type information to verify the match
statement exhaustiveness. This avoids the cast(list[Any]) approach
which pyright still flagged as partially unknown.
* fix: use ternary operator for response_outputs assignment
Replace if-else block with ternary expression to satisfy ruff SIM108 lint rule.
This fixes the Package Checks (3.11) CI failure.
* fix: use ternary with cast for ruff SIM108 and pyright type safety
Replace if-else block with ternary expression using cast(list[Any], ...)
to satisfy:
- ruff SIM108 (use ternary instead of if-else)
- ruff E501 (line length < 120)
- pyright type narrowing (cast preserves type info lost in ternary)
All local checks pass: ruff check, ruff format, pyright, 298 tests.
* fix: replace hasattr+cast with try/except to preserve pyright types
---------
Co-authored-by: Tao Chen <taochen@microsoft.com>
* Move token params from HarnessAgent constructor to options
Remove the required maxContextWindowTokens and maxOutputTokens
constructor parameters from HarnessAgent and AsHarnessAgent, replacing
them with optional MaxContextWindowTokens and MaxOutputTokens properties
on HarnessAgentOptions.
When both values are provided, compaction is enabled as before (in-loop
CompactionProvider and chat reducer on the default InMemoryChatHistory
Provider). When either is null, compaction is disabled entirely, making
it opt-in.
New constructor: HarnessAgent(IChatClient, HarnessAgentOptions?,
ILoggerFactory?, IServiceProvider?)
Closes#6333
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Improving comments.
* feat: Add custom CompactionStrategy and DisableCompaction to HarnessAgentOptions
Allow users to provide their own CompactionStrategy via options, with
a clear priority system:
1. DisableCompaction=true: no compaction regardless of other settings
2. Custom CompactionStrategy provided: use it (token params ignored)
3. Both MaxContextWindowTokens and MaxOutputTokens set: default strategy
4. Otherwise: no compaction
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: Address PR review comments on compaction opt-in
- Update chatClient param XML doc to reflect compaction is opt-in
- Strengthen compaction tests to assert ChatReducer is null/not-null
rather than just asserting construction succeeds
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add reasoning option to request chat options in ChatClientAgent
* Add tests for ChatOptions reasoning merging in ChatClientAgent
---------
Co-authored-by: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com>
* Filter MCP tool kwargs to declared params via allowlist
Previously MCPTool combined framework runtime kwargs (from
FunctionInvocationContext.kwargs) with the LLM-supplied arguments and
stripped only a hardcoded denylist of known framework keys before
forwarding to the MCP server. Any new framework-injected kwarg leaked to
the server unless the denylist was updated.
Switch to an allowlist built from each tool's declared parameters
(inputSchema.properties). Only declared params are forwarded; everything
else is stripped. Add an `additional_tool_argument_names` constructor
argument so users can opt extra names back in, globally (Sequence[str])
and/or per remote tool name (Mapping with reserved "*" global key). The
existing denylist is kept as a safety net for framework-named params a
server declares in its schema; explicitly opted-in extras always win. The
reserved _meta handling is unchanged.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address MCP allowlist review comments and fix reload arg loss
- Fix pyright reportUnknownArgumentType in _load_tools (cast schema properties).
- Register declared param names before the existing-tool skip guard so that
tool-list reloads preserve the allowlist for already-loaded tools (previously
unchanged tools silently dropped all declared args after a background reload).
- Handle bare-string values in an additional_tool_argument_names mapping instead
of iterating their characters.
- Clarify the framework denylist comment: explicit extras override the denylist.
- Make the extras-override-denylist test unambiguous (opt in a denylisted name).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(claude): bump claude-agent-sdk to 0.2.87
Upgrade claude-agent-sdk dependency from >=0.1.36,<0.1.49 to >=0.2.87,<0.3.
Changes:
- Bump version pin in pyproject.toml
- Add 'xhigh' effort level to ClaudeAgentOptions (Opus 4.7 specific)
- Expose new upstream SDK options: skills, session_id, task_budget,
include_hook_events, strict_mcp_config, continue_conversation,
fork_session
- Add TaskBudget type import
- Update uv.lock
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore: lower claude-agent-sdk floor to >=0.1.36
Keep the lower bound at 0.1.36 since the 0.1→0.2 transition was additive
and our code works on older versions as long as new options aren't used.
This avoids forcing unnecessary upgrades on existing users.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: replace TaskBudget import with inline type for SDK compat
TaskBudget was added in claude-agent-sdk 0.2.93 but does not exist in
0.2.87. Use dict[str, int] inline type instead so type checking passes
against 0.2.87. Lock file pinned to 0.2.87.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix per-service-call history persistence with server-storing clients
When an Agent set require_per_service_call_history_persistence=True together
with a HistoryProvider, and the chat client stored history server-side by
default (e.g. OpenAIChatClient, STORES_BY_DEFAULT=True), the external history
provider was silently never persisted.
Unify persistence on the per-service-call middleware: when the flag is set and
a HistoryProvider exists, the middleware is always installed and owns
persistence. service_stores_history now only selects middleware behavior:
- service does not store: load providers and drive the function loop with a
local sentinel conversation id, or
- service stores: skip loading (the service owns history) and persist each
service call while the real conversation id flows through.
Also rationalize chat-options handling in _prepare_run_context:
- _merge_options now skips None overrides and strips remaining None values, so
an unset `store` is never forwarded and the service decides its own default.
- Resolve `store` and `conversation_id` once from a single combined view
(effective_options) instead of probing both default and runtime dicts; the
auto-injection and per-service-call resolution now agree on conversation_id.
Fixes#5798
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Correct as_agent() docstring: persistence is per service call, not once per run
Address PR review: when the client stores history server-side, the
per-service-call middleware still persists after each model call; only
provider loading is skipped. The previous "persist once per run()" wording
contradicted the implementation.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address PR review: docs, missing-conversation-id warning, and tests
- Clarify that require_per_service_call_history_persistence is a no-op when no
HistoryProvider is present (docstrings in _agents.py and _clients.py).
- Warn on every service call when the client stores history server-side but
returns no conversation_id, so the (uncommon) loss of cross-turn resumability
cannot fail silently.
- Add tests: storing client + existing conversation_id does not raise and the id
propagates; two runs on the same session keep persisting with a stable
service_session_id and no provider loading; storing-without-conversation-id
warns per call.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Migrate .NET GitHub Copilot SDK from 1.0.0-beta.2 to 1.0.0
- Update namespace from GitHub.Copilot.SDK to GitHub.Copilot
- Replace PermissionRequestResult/PermissionRequestResultKind with PermissionDecision
- Remove ConnectionState check (StartAsync is now idempotent)
- Rename ConfigDir to ConfigDirectory
- Use SessionConfig.Clone() for CopySessionConfig
- Update Tools type from List<AIFunction> to List<AIFunctionDeclaration>
- Rename UserMessageAttachmentFile to AttachmentFile
- Update usage data types (CacheWriteTokens: long, Duration: TimeSpan)
- Add GHCP001 NoWarn for experimental SDK APIs (matches framework convention)
- Specify type argument on CopilotSession.On<SessionEvent>()
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix formatting: remove unused using directive
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Skip AzureFunctions SamplesValidation tests pending func tools fix
Azure Functions Core Tools v4 can no longer auto-detect the worker
runtime in CI (local.settings.json is gitignored). All 7 active
SamplesValidation tests fail with 'Worker runtime cannot be None'.
Tracked by: https://github.com/microsoft/agent-framework/issues/6402
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Skip additional failing integration tests in CI
WorkflowSamplesValidation (5 tests): same func tools issue as #6402.
WorkflowConsoleAppSamplesValidation (4 tests): KeyNotFoundException
during workflow execution, tracked by #6404.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(mem0): parallel memory retrieval logic and strict type compliance
* fix(mem0): align parallel retrieval types for pyright and mypy
* fix(mem0): handle asyncio.CancelledError in search response and update test description
* fix(mem0): improve error handling for asyncio.CancelledError and update test names for clarity
* fix(mem0): improve retrieval response handling
* fix(gemini): preserve schema response_format
* fix(gemini): satisfy pyright strict in response schema extraction
Cast Any-narrowed mappings to Mapping[str, Any] in the structured-output
schema helpers so pyright strict no longer reports partially-unknown
member, argument, and variable types. Pass response_format["format"]
straight into the recursive extractor, which already guards non-mapping
inputs. No behavior change.
* fix(gemini): use Sequence[object] cast to satisfy both mypy and pyright
The Sequence[Any] cast pyright strict needs to know the loop element type
is reported as a redundant-cast by mypy, which already narrows the
isinstance branch to Sequence[Any]. Cast to Sequence[object] instead:
pyright gets a fully known element type and mypy no longer sees an
identical-type cast. No behavior change.
---------
Co-authored-by: Evan Mattson <evan.mattson@microsoft.com>
* MCP long-running task support in Python
* Fix pyupgrade and AGENTS.md reconnect description
- pyupgrade: drop forward-reference string annotations in _mcp.py (Python 3.10+ resolves them natively now that MCPTaskOptions is defined before use).
- AGENTS.md: align reconnect description with current behavior. Phase 1 (initial tools/call) does NOT retry on connection loss; raises 'connection lost; task state unknown' instead, so a server that accepted the request but lost the response cannot start the operation twice. Phase 2 (tasks/get / tasks/result) still reconnects once against the same task_id.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix bandit nosec marker for CI pipeline
* Address PR feedbacks
* Clarifiied comments and addressed more PR feedbacks.
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add a dedicated integration test job for the github_copilot package to both
python-integration-tests.yml and python-merge-tests.yml.
The job:
- Runs 6 integration tests marked with @pytest.mark.integration
- Uses COPILOT_GITHUB_TOKEN secret from the integration environment
- Follows the same pattern as other provider integration jobs
- Includes path filtering in merge-tests (github_copilot package + core changes)
- Added to needs lists in report and check jobs
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Restore UTF-8 BOMs and fix BuildScriptSchemasBlock doc comment
- Restore UTF-8 BOM on all changed files to match repo convention
- Fix XML doc: <schema name=...> -> <schema script=...> to match emitted output
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address PR review comments: fix doc remarks and rename tests
- Update script doc remarks to clarify only parameter schemas are included
- Fix grammar: 'arguments format' -> 'argument format'
- Rename misleading test methods to match actual assertions
- Clarify comment about removed wrapper element
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: SergeyMenshykh <SergeMenshikh@outlook.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: fix ConnectTimeout on multi-turn FoundryAgent conversations (#6241)
Expose a `timeout` parameter on `RawFoundryAgentChatClient`,
`_FoundryAgentChatClient`, `RawFoundryAgent`, `FoundryAgent`, and
`RawOpenAIChatClient` so callers can override the HTTP timeout used by
the underlying AsyncOpenAI client.
Root cause: `RawFoundryAgentChatClient.__init__` called
`project_client.get_openai_client()` without configuring any timeout,
inheriting the OpenAI SDK default of `httpx.Timeout(connect=5.0)`.
When connections are recycled between turns under load, the 5 s connect
timeout fires and surfaces as `openai.APITimeoutError`.
Fix:
- `load_openai_service_settings` (`_shared.py`): accept `timeout` and
include it in `client_args` for all three `AsyncOpenAI`/
`AsyncAzureOpenAI` construction paths.
- `RawOpenAIChatClient.__init__` (`_chat_client.py`): accept `timeout`
and forward to `load_openai_service_settings`.
- `RawFoundryAgentChatClient.__init__` (`_agent.py`): accept `timeout`
and set `openai_client.timeout = timeout` on the client returned by
`get_openai_client()` before passing it to the base class.
- `_FoundryAgentChatClient`, `RawFoundryAgent`, `FoundryAgent`: accept
and propagate `timeout` through the construction chain.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add timeout parameter to FoundryAgent and RawOpenAIChatClient
Expose a timeout parameter on RawFoundryAgentChatClient,
_FoundryAgentChatClient, RawFoundryAgent, FoundryAgent, and
RawOpenAIChatClient. When provided, the value is applied to the
underlying AsyncOpenAI client so that connect timeouts under load
or after connection recycling can be tuned by callers.
Previously, get_openai_client() was called without any timeout
override, so the SDK default of httpx.Timeout(connect=5.0) was
inherited and could fire on multi-turn conversations where the
underlying connection is recycled between turns.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: Add `timeout` parameter to `FoundryAgent` to fix `ConnectTimeout` on multi-turn conversations
Fixes#6241
* fix(foundry): use with_options to avoid mutating shared OpenAI client timeout (#6241)
Replace direct assignment with
in
RawFoundryAgentChatClient.__init__.
The Azure AI Projects SDK caches and returns a shared AsyncOpenAI client
per AIProjectClient. Mutating its .timeout attribute leaked the override
to all other code paths sharing that client (other agents, user code).
with_options() returns a new client instance with the override applied,
leaving the original shared client untouched.
Update tests to assert with_options is called with the correct timeout
and that the original shared client's timeout attribute is not mutated.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* test(foundry): assert with_options return value flows to instance.client (#6241)
The four timeout propagation tests verified that with_options was called
but did not confirm that the returned (timeout-configured) client was
actually stored on the instance. A silent discard of the return value
would have left the tests green while the timeout had no effect.
Each test now captures the constructed instance and asserts:
assert <instance>.client is openai_client_mock.with_options.return_value
Affected tests:
- test_raw_foundry_agent_chat_client_init_applies_timeout_to_openai_client
- test_raw_foundry_agent_chat_client_init_applies_timeout_with_preview_enabled
- test_foundry_agent_chat_client_init_propagates_timeout
- test_foundry_agent_init_propagates_timeout_to_openai_client
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix magentic manager warning
* Use typing_extensions.Sentinel for _MISSING sentinel value
Replace the bare object() sentinel with typing_extensions.Sentinel per
PEP 661 (now final). Sentinel provides a proper name and repr
('<_MISSING>') and is the idiomatic approach going forward.
Refs #4306
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: correct Sentinel type annotation for max_stall_count param (#6261)
Use int | Sentinel for max_stall_count parameter type annotation instead
of int with cast(Any, _MISSING) to properly express that the parameter
can hold either an int or the _MISSING sentinel value. This fixes the
pyright reportUnnecessaryComparison errors caused by the types int and
Sentinel having no overlap.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Rename _MISSING sentinel to UNSET in orchestrations
The sentinel is user-visible as a default in public init signatures, so
use UNSET (no leading underscore) instead of the private _MISSING name.
Drop the now-unnecessary reportPrivateUsage ignores on the UNSET imports.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix compaction message-id collisions and tool-loop summary persistence
Fixes two bugs in the compaction strategies:
- #5237: incremental group annotation assigned message ids by position
within the re-annotated slice, so moving the re-annotation start back to
a previous group start restarted ids at 0 and produced collisions
(e.g. a user message reusing an assistant message's id), merging groups
and causing tool-result compaction to wrongly exclude messages.
group_messages/_ensure_message_ids now take an id_offset and guard
against existing-id collisions; annotate_message_groups threads the
slice start index through as the offset.
- #4991: the function-invocation loop copied the message list each
iteration, so summaries inserted by compaction landed in a throwaway
copy and were lost across tool-loop iterations (only the persistent
excluded flags survived). _prepare_messages_for_model_call now compacts
the list in place when messages is a list, so inserted summaries persist.
Adds regression tests (incremental id uniqueness, existing-id collision
avoidance, idempotency, and tool-loop summary persistence including
streaming and conversation-id modes).
Also adds a summarization.py sample demonstrating SummarizationStrategy
directly with a real client, and reworks advanced.py with tool-call
groups and a real summarizer.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Guard incremental message-id assignment against prefix-id collisions
Addresses PR review on #5237: _ensure_message_ids only guarded against
collisions within the re-annotated slice. A preexisting (e.g. user-supplied)
id in the preserved prefix could still be reassigned in the suffix when the
id was numerically out of position, merging groups across the re-annotation
boundary again.
group_messages/_ensure_message_ids now accept reserved_ids, and
annotate_message_groups passes the preserved prefix's ids so auto-assigned
suffix ids never collide across the full list. Adds a regression test
reproducing the out-of-position prefix-id collision.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add MCP-based skills discovery (McpSkill, McpSkillsSource, McpSkillResource)
Implement Agent Skills discovery over MCP following the SEP-2640 convention:
- McpSkillsSource: reads skill://index.json to discover skills served by an MCP server
- McpSkill: lazily fetches SKILL.md content via resources/read on demand
- McpSkillResource: wraps MCP resource results (text and binary)
- Path traversal protection in get_resource for defense in depth
- Samples for Foundry Toolbox and standalone MCP skills server
- Comprehensive unit tests (514 lines)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address PR review comments: rename to MCP* convention, fix error handling and samples
- Rename McpSkill/McpSkillResource/McpSkillsSource to MCPSkill/MCPSkillResource/MCPSkillsSource
- Add data-URI prefix stripping for blob resource decoding
- Let non-McpError exceptions propagate from get_resource()
- Fix contradictory test comment
- Use interactive input() in mcp_based_skill sample
- Remove misleading sample output block
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Restore debug logging for McpError in get_resource()
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Use AzureCliCredential in Foundry toolbox skills sample for consistency
Replace DefaultAzureCredential with AzureCliCredential to match the
credential convention used in all other samples.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Use MCPStreamableHTTPTool in MCP skills sample
Replace raw mcp library imports (ClientSession, streamable_http_client)
with the framework's MCPStreamableHTTPTool to keep MCP server connections
consistent regardless of whether skills are enabled.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Branch on McpError.error.code so only not-found errors return empty
Previously _try_read_index() and get_resource() swallowed every McpError
as 'no skills available', making auth failures, server crashes, and
connection drops indistinguishable from a server that simply has no
skills.
Now only two codes are treated as not-found:
- -32002 (MCP-spec Resource not found)
- -32601 (METHOD_NOT_FOUND — server lacks resources/read)
All other McpError codes and non-McpError exceptions propagate with a
warning log, surfacing real failures visibly.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add tests for non-McpError and non-not-found error propagation in MCP skills
Cover the re-raise branch in MCPSkill.get_resource for plain
ConnectionError/TimeoutError, the generic McpError (code 0) propagation
on get_resource, and TimeoutError propagation in _try_read_index.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Revert "Use MCPStreamableHTTPTool in MCP skills sample"
This reverts commit f31ed0ded9.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Introduce MCP_SKILLS experimental feature for MCP skill classes
Add a separate MCP_SKILLS feature ID to ExperimentalFeature enum and
use it for MCPSkillResource, MCPSkill, and MCPSkillsSource, since their
promotion timeline is partly outside of our control.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add mcp tool execution fix
* Apply IsolationKeyScopedAgentSessionStore to MapAGUI by default if not yet set and improve comments in samples
* Address PR comments
* Fix formatting
* Add ILoggerFactory and IServiceProvider to HarnessAgent constructor
Add optional ILoggerFactory and IServiceProvider parameters to the
HarnessAgent constructor and AsHarnessAgent extension method, passing
them to all downstream components that accept them:
- FunctionInvokingChatClient (via UseFunctionInvocation)
- CompactionProvider
- AgentSkillsProvider
- ChatClientAgent (via BuildAIAgent)
- AIAgentBuilder.Build()
Closes#6103
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Improve tests to verify ILoggerFactory and IServiceProvider propagation
- Add test verifying ILoggerFactory.CreateLogger() is called by
downstream components (CompactionProvider, AgentSkillsProvider)
- Add test verifying IServiceProvider is queried during pipeline build
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: progressive tool exposure via FunctionInvocationContext
Add first-class progressive tool exposure to the Python core function-calling
loop. Tools can now add or remove real FunctionTool schemas at runtime via the
injected FunctionInvocationContext, taking effect on the next iteration of the
loop.
- FunctionInvocationContext gains a live `tools` list plus experimental
`add_tools()` / `remove_tools()` helpers (feature: PROGRESSIVE_TOOLS).
- The function-calling loop establishes a run-local, normalized tools list and
threads it into the context at both invocation paths so mutations propagate.
- Add a sample (dynamic_tool_exposure.py) and a tools samples README, including
a note that CodeAct providers (Monty/Hyperlight) use their own provider-level
tool management instead.
Supersedes #3877.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Validate non-negative input in dynamic_tool_exposure sample tools
Address review feedback: factorial and fibonacci now return an error
message for negative n instead of producing incorrect results.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Make add_tools atomic and surface swallowed function errors
Address review feedback on progressive tool exposure:
- add_tools now validates the full batch against a throwaway copy before
committing, so a duplicate-name clash partway through a sequence leaves
the live tool list unchanged (all-or-nothing).
- _auto_invoke_function now logs a warning (with traceback) when a tool
raises, so contract errors such as a duplicate-name ValueError from
add_tools are debuggable without enabling include_detailed_errors.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Avoid retaining tracebacks when logging swallowed function errors
Logging with exc_info=exc fed the exception traceback to the logging
machinery, whose frame references created reference cycles collected
lazily by the cyclic GC. On Windows that could drop a hyperlight
WasmSandbox on a non-owning thread ("unsendable, dropped on another
thread"), crashing the xdist worker. Log a pre-formatted message with
the exception repr instead, so no traceback object is retained.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* added missing decorator
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix FoundryAgent stripping model from PromptAgent requests
Move run_options.pop('model', None) inside the _uses_foundry_agent_session()
conditional so that model is only stripped for hosted agent sessions (where
the server manages the model) and preserved for PromptAgent requests that
require it in the Responses API call.
Fixes#5525
* test: add coverage for resp_* continuation preserving model
Adds test_raw_foundry_agent_chat_client_prepare_options_preserves_model_for_resp_continuation
to explicitly verify that HostedAgent v1 / v2-no-session paths (where conversation_id
starts with resp_) preserve model and previous_response_id without triggering the
hosted-session gate.
---------
Co-authored-by: Benke Qu <bequ@microsoft.com>
Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
* Promote Workflows.Declarative packages to stable versions
* Address PR feedback: enable package validation on GA declarative packages
Both Workflows.Declarative and Workflows.Declarative.Mcp set IsReleased=true
but were disabling package validation, bypassing the repo's GA convention
(see dotnet/nuget/nuget-package.props which auto-enables validation when
IsReleased=true).
Re-enable validation by removing the local EnablePackageValidation=false
overrides and pointing PackageValidationBaselineVersion at 1.8.0-rc1 (the
latest published version of each package). This catches accidental breaking
changes between RC and the first GA. Future GAs should bump the baseline to
the previous GA version.
Verified locally: dotnet build -c Release on both projects runs
RunPackageValidation -> APICompat ran successfully without finding any
breaking changes.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Update statement for the baseline validation.
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: Fix OTLP HTTP base-endpoint losing /v1/{signal} auto-append
Per the OTel spec, OTEL_EXPORTER_OTLP_ENDPOINT is a *base* URL for HTTP —
the SDK auto-appends /v1/traces, /v1/metrics, /v1/logs when it reads the
env var directly. Signal-specific endpoint env vars are *full* URLs used
verbatim.
_get_exporters_from_env read the base endpoint and forwarded it as the
constructor ``endpoint=`` argument, which the SDK always treats as a full
signal URL. As a result, with OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
and HTTP protocol, the exporter sent to http://localhost:4318 instead of
http://localhost:4318/v1/traces (and likewise for metrics/logs).
Replicate the spec's auto-append here when falling back to the base
endpoint under HTTP. gRPC behavior is unchanged.
* Python: Fix mypy type errors in OTLP endpoint assignment
Pre-declare traces_endpoint, metrics_endpoint, logs_endpoint as
str | None before the if/else block. Mypy inferred str from the
if-branch f-string assignments and then rejected the str | None
expressions in the else-branch as incompatible.
* feat(bedrock): add structured output support via Converse API (Fixes#5966)
* fix(bedrock): improve unsupported model exception handling and schema parsing
* refactor(bedrock): use generic traversal for strict schema enforcement
* address Copilot review comments on structured output
* refine bedrock structured output: guard additionalProperties, TypeError check, docs + test
* fix(bedrock): widen response_format to Mapping and add missing test coverage
* Python: feat(evals): RubricScore type + EvalScoreResult.dimensions
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: feat(foundry-evals): RubricDimension + GeneratedEvaluatorRef + accept in evaluators=
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: feat(evals): parse rubric_scores from output items + assertion helpers
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: feat(evals): BaseAgent.as_eval_source / Workflow.as_eval_source
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: feat(foundry-evals): EvalGenerationSource + generate_rubric helper
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: feat(foundry-evals): YAML config loader + sample
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: fix(evals): address PR review feedback
Addresses 4 Copilot review comments on PR #6101:
1. assert_dimension_score_at_least: drop the (not evaluator or found_any) guard so require_applicable=True correctly raises when the named evaluator produces no entries for the dimension. Adds TestRubricAssertions covering the regression.
2. GeneratedEvaluatorRef docstring: reword to describe actual behaviour (pinning recommended, not required) so it matches the dataclass default and FoundryEvals warning path.
3. _poll_generation_job: switch from asyncio.get_event_loop() to get_running_loop() and bound the per-iteration sleep by remaining time, matching _poll_eval_run.
4. generate_rubric: type category as Literal['quality','safety'] and validate at the entry point with a ValueError; drop the silent 'invalid -> quality' rewrite in _generation_job_to_ref. Adds a regression test.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: feat(foundry-evals): hosted-agent-aware rubric generation
* Auto-detect hosted Foundry agents in agent_as_eval_source: when the
agent's chat_client exposes a string agent_name (the convention used
by RawFoundryAgentChatClient for PromptAgents/HostedAgents), emit a
type='agent' EvalGenerationSource so the service fetches instructions
and tools from the agent registry instead of relying on the local
wrapper (which holds neither for hosted agents).
* Add hosted_agent_version kwarg and a new agent_version field on
EvalGenerationSource so PromptAgent runs can pin to a specific hosted
version for reproducible rubric generation.
* Add force_prompt_source escape hatch to bypass auto-detection and
always emit a rendered prompt dossier - useful when the local wrapper
carries overrides the service-side agent doesnt see.
* Fix _to_sdk_source for dataset sources: SDK ctor takes name=/version=,
not dataset_name=/dataset_version=. The mismatch would raise TypeError
against the real azure-ai-projects 2.3.0a* SDK; only unmocked
integration paths were affected.
Tests cover: auto-detection happy path, versionless hosted agent,
explicit hosted_agent_version forwarding, force_prompt_source override,
non-string chat_client attrs (MagicMock test doubles) not mis-detected,
agent_version forwarded through _to_sdk_source, and the corrected
dataset SDK kwarg names.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(foundry-evals): accept canonical dimension_scores key per docs
The published Foundry rubric-evaluator output (Microsoft Learn 'Rubric evaluators' reference) places per-dimension breakdowns under properties.dimension_scores, not properties.rubric_scores. The parser now tries dimension_scores first and falls back to rubric_scores for preview-build compatibility, and tolerates non-list payloads (e.g. MagicMock auto-attrs) by trying the next candidate when parsing yields zero entries.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(foundry-evals): add manual create_rubric_evaluator
Adds FoundryEvals.create_rubric_evaluator as the agent-framework surface over project_client.beta.evaluators.create_version. This is the manual counterpart to generate_rubric: callers supply RubricDimension instances (authored locally, ported from another framework, or hand-tuned) and we POST a RubricBasedEvaluatorDefinition. The service auto-attaches the non-editable residual dimension (general_quality for quality, general_policy_compliance for safety).
Per the Microsoft Learn 'Rubric evaluators' reference, the auto-generation path (create_generation_job) is primarily a portal/UI feature; external SDK clients with rich local agent context are better served by manual create_version. This keeps generate_rubric for users who want to round-trip through a Foundry-registered agent.
Validation up front: weight must be in [1,10], ids unique, descriptions non-empty, pass_threshold in [0,1]. The returned GeneratedEvaluatorRef is identical in shape to one obtained from generate_rubric, so downstream evaluators= lists work unchanged.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* samples(foundry-evals): manual rubric sample + namespace re-exports
Adds evaluate_with_manual_rubric_sample.py demonstrating the end-to-end dev scenario for FoundryEvals.create_rubric_evaluator: hand-author a list of RubricDimension, register via create_rubric_evaluator, then use the pinned GeneratedEvaluatorRef alongside built-in evaluators in an agent regression run.
Also re-exports RubricDimension, GeneratedEvaluatorRef, build_sources, and load_evals_config from agent_framework.foundry (both the lazy runtime shim and the type stub) so the rubric samples can import everything from a single namespace; the auto-generate sample was previously broken because the shim was missing build_sources / load_evals_config.
Updates the foundry-evals README with a chooser entry for the two rubric paths.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(foundry-evals): remove rubric creation flows; keep consumption only
Reframes agent-framework as a pure consumer of Foundry rubric evaluators: scoring against rubrics that already exist (authored in the Foundry portal or via the dedicated SDK / REST surface) instead of creating them from the SDK.
Removed creation surface area:
- FoundryEvals.generate_rubric (auto-generate path) and create_rubric_evaluator (manual path), plus all _GenerationSdkTypes / _ManualRubricSdkTypes / _to_sdk_dimensions / _coalesce_generation_sources / _to_sdk_source / _poll_generation_job / _generation_job_to_ref / _evaluator_version_to_ref / _get_beta_evaluators / _import_*_sdk_types helpers.
- EvalGenerationSource (the input source discriminator), RubricDimension (the input dimension type), agent_as_eval_source / workflow_as_eval_source / _detect_hosted_foundry_agent helpers, and the YAML-config loader (_evals_config.py with RubricGenerationSpec / RubricSourceSpec / parse_evals_config / load_evals_config / build_sources).
- BaseAgent.as_eval_source / Workflow.as_eval_source plus the _render_agent_dossier / _render_workflow_dossier helpers in core. These existed only to feed the now-removed generation pipeline.
- Samples evaluate_with_generated_rubric_sample.py, evaluate_with_manual_rubric_sample.py, and evaluators.yaml. Replaced with a short README section showing how to reference an existing rubric evaluator via GeneratedEvaluatorRef.
Kept (consumption surface):
- GeneratedEvaluatorRef, slimmed to (name, version, display_name). Still accepted alongside built-in evaluator strings in FoundryEvals(evaluators=[...]). Versionless refs still warn.
- RubricScore on EvalScoreResult.dimensions plus EvalResults.assert_dimension_score_at_least for per-dimension CI gates.
- _parse_dimension_entries / _extract_rubric_scores output parsing (both canonical dimension_scores and the legacy rubric_scores key).
Tests: 160/160 foundry unit tests and 71/71 core local-eval tests pass; pyright is clean across changed files. The pre-existing tests/core/test_telemetry.py::test_detect_hosted_fallback_import_error failure is unrelated and reproduces on the prior commit.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* samples(foundry-evals): add evaluate_with_rubric_sample
Adds a runnable end-to-end sample showing how to consume a pre-existing rubric evaluator created in Foundry: reference it with GeneratedEvaluatorRef(name, version), mix it with built-in evaluators in FoundryEvals, and gate CI with assert_dimension_score_at_least on a specific dimension.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(foundry-evals): satisfy mypy on _fetch_output_items
mypy infers OutputItemListResponse.sample as dict[str, object] | None while pyright correctly infers the typed Sample model. Cast to Any so both type checkers accept the attribute access pattern, rename the local to avoid shadowing the inner-loop sample binding, and drop the now-stale pyright suppressions.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* docs(foundry-evals): drop unpublished rubric-evaluators learn.microsoft.com link
The Adaptive Evals authoring docs are not yet published on Microsoft Learn, so the link 404s. Keep the descriptive text without the broken hyperlink; we can re-add it once the docs ship.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* test(foundry-evals): hoist repeated local imports to module top
Per code review feedback (eavanvalkenburg): the test file repeated 'from agent_framework_foundry._foundry_evals import ...' inside 22 test bodies and 'from agent_framework_foundry import GeneratedEvaluatorRef' inside 8 more. Move all of them to the existing top-level imports; the symbols are the same across tests and the local imports were redundant.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Ben Thomas <25218250+alliscode@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: safely serialize function-call arguments in core observability
Apply make_json_safe() to content.arguments in _to_otel_part() before
building the otel message dict, so that dataclass/framework payloads
(e.g. workflow request_info events) do not cause a TypeError when
_capture_messages() calls json.dumps().
Lift make_json_safe() into agent_framework._serialization (no new
external deps — dataclasses/datetime only) so the core observability
path can use it without a dependency on the ag-ui adapter.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(core): safely serialize workflow request_info payloads in observability (#5733)
- Add make_json_safe() helper to recursively convert non-serializable objects
- Use make_json_safe() in _to_otel_part() for function_call arguments
- Fix CustomPayload test class to use @dataclass (resolves B903 lint error)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(serialization): guard callability and normalize dict keys in make_json_safe (#5733)
- Use callable(getattr(obj, method, None)) instead of hasattr() so that
non-callable attributes named model_dump/to_dict/dict do not raise
TypeError at runtime.
- Wrap each call in try/except TypeError to handle callables with
mandatory arguments gracefully.
- Convert dict keys to str() so that non-string keys (e.g. datetime,
int) cannot cause json.dumps to raise TypeError.
- Add regression tests for both scenarios.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address observability serialization review feedback
---------
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Updating to latest Foundry hosting packages.
* Re-applying .gitignore.
* Adding empty line at end of .gitignore
---------
Co-authored-by: Ben Thomas <25218250+alliscode@users.noreply.github.com>
* Fix missing id on function_call_output in Foundry Hosting
The Foundry storage layer was rejecting responses with
"ID cannot be null or empty (Parameter 'id')" because
function_call_output items emitted by OutputConverter had no id on
the wire.
OutputItemFunctionToolCallOutput's public ctor only sets CallId and
Output; Id is read-only and only the SDK's internal ctor populates
it. OutputItemBuilder<T>.ApplyAutoStamps fills ResponseId and
AgentReference but not Id, so the itemId passed to
AddOutputItem<T>(itemId) was used only for event sequencing and the
serialized item went out with id=null.
Switch to stream.OutputItemFunctionCallOutput(callId, output), the
SDK convenience method that uses the internal ctor and stamps the
id. Add a regression test asserting the added/done events carry a
non-empty matching Id.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* ci: free disk space and relocate NuGet cache on ubuntu runners
The ubuntu-latest dotnet-build/test jobs were hitting No space left on device because the runner image only ships ~14 GB free on /. The full multi-TFM build plus the dotnet pack + console-app install-check exhausts that easily.
Add a reusable composite action .github/actions/free-runner-disk-space that runs on Linux runners only and:
* removes pre-installed toolchains we never use here (Android SDK, GHC/Haskell, CodeQL, PyPy, Ruby, Go, boost, vcpkg, etc.), prunes docker images, and disables swap (reclaims ~25-30 GB on /)
* relocates the NuGet package cache to /mnt/nuget via NUGET_PACKAGES env, since /mnt has ~75 GB free on hosted runners
Wire the action into the four ubuntu-touching jobs in dotnet-build-and-test.yml (dotnet-build, dotnet-test, dotnet-foundry-hosted-it, dotnet-test-functions). The action self-guards with runner.os == 'Linux' so the matrix legs that run on windows are unaffected.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: alliscode <25218250+alliscode@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Initial plan
* Fix integration test worker crashes on Python 3.13
Three changes to prevent pytest-xdist workers from crashing during
Azure Functions integration tests:
1. Add `start_new_session=True` to subprocess on Linux so signals
(e.g. from test-timeout) cannot propagate between the func host
and the xdist worker process.
2. Add an overall 100-second budget to the fixture setup loop so
the retry logic never exceeds the 120-second test timeout. When
pytest-timeout's thread method fires during fixture setup and the
thread doesn't respond, it calls os._exit() which kills the
xdist worker – this is the root cause of the "Not properly
terminated" crashes.
3. Remove the `UV_PYTHON: "3.10"` workaround from both workflow
files so integration tests actually run on Python 3.13.
Co-authored-by: larohra <41490930+larohra@users.noreply.github.com>
* Validate integration tests on Python 3.13
Co-authored-by: larohra <41490930+larohra@users.noreply.github.com>
* Revert unintentional uv.lock dependency bumps
Co-authored-by: larohra <41490930+larohra@users.noreply.github.com>
* Use time.monotonic() instead of time.time() for fixture budget timing
Addresses review feedback: monotonic clock is immune to NTP/clock
adjustments that could skew the budget enforcement.
Co-authored-by: larohra <41490930+larohra@users.noreply.github.com>
* Fix func worker segfault on Python 3.13 by redirecting worker to Python 3.12
The Azure Functions Python worker crashes with SIGSEGV (exit code 139)
on Python 3.13 due to protobuf C extension (google._upb) compatibility
issues. When the test runner uses Python >=3.13, the conftest now
automatically finds a compatible Python 3.10-3.12 and sets
languageWorkers__python__defaultExecutablePath so the func host uses
it for the worker process.
The CI setup action also ensures Python 3.12 is available on the
runner, falling back to uv python install if the system doesn't have
it.
Co-authored-by: larohra <41490930+larohra@users.noreply.github.com>
* Address code review: add path validation, clarify version range and config key format
Co-authored-by: larohra <41490930+larohra@users.noreply.github.com>
* Run func worker natively on Python 3.13 by disabling dependency isolation
Replace the Python 3.12 redirect workaround with the proper fix:
set PYTHON_ISOLATE_WORKER_DEPENDENCIES=0 on Python >=3.13.
The segfault (exit code 139) is caused by the Azure Functions worker's
module isolation mechanism conflicting with protobuf's C extensions
(google._upb) on Python 3.13. Disabling isolation lets the worker
load dependencies from the app's own environment, which avoids the
crash while keeping everything running on Python 3.13.
See: https://github.com/Azure/azure-functions-python-worker/issues/1797
Co-authored-by: larohra <41490930+larohra@users.noreply.github.com>
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: larohra <41490930+larohra@users.noreply.github.com>
Co-authored-by: Laveesh Rohra <larohra@microsoft.com>
* Reorganize A2A samples: client demos in 02-agents, use package A2AExecutor
- Move client samples (agent_with_a2a, a2a_agent_as_function_tools) to samples/02-agents/a2a/
- Add new concept samples: polling, stream reconnection, protocol selection
- Replace sample agent_executor.py with package-level A2AExecutor (stream=True)
- Update 04-hosting/a2a to focus on server-side, point to 02-agents for clients
- Add README.md for the new 02-agents/a2a/ sample collection
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix streaming artifact coalescing and address PR review feedback
A2AExecutor fix:
- Generate a stable artifact_id per stream in _run_stream so all streaming
chunks share the same ID, enabling proper append=True coalescing per the
A2A spec (TaskArtifactUpdateEvent with same artifactId).
- Previously, item.message_id was None for OpenAI/Foundry streaming updates,
causing the SDK to generate a new random UUID per token (100+ separate
artifacts instead of 1 appended artifact).
Sample improvements:
- Replace join workaround with response.text now that coalescing works
- Add background=True to stream reconnection resume call (required for
continuation token emission on in-progress tasks)
- Fix type ignore specificity in polling sample
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Preserve per-message CreatedAt attribute if it's available
* Add unit test
---------
Co-authored-by: Sam Chang <changsam@microsoft.com>
Co-authored-by: samchang-msft <samchang.msft@gmail.com>
MagenticOrchestrator.TakeTurnAsync dropped the `messages` parameter
on subsequent turns, so participant replies never reached the manager's
ChatHistory. The manager kept re-dispatching the same speaker every
round until MaxRounds.
Append the incoming messages to taskContext.ChatHistory before running
the coordination round (matches Python's _handle_response).
Adds RecordingReplayAgent + regression test that asserts the worker's
reply reaches round-2's progress-ledger call.
Co-authored-by: Jacob Alber <jaalber@microsoft.com>
* Bump Azure.AI.AgentServer.* package versions
* Align Azure.Core/System.ClientModel to AgentServer transitive deps
Bump Azure.Core 1.55->1.56 and System.ClientModel 1.11->1.12 to match Azure.AI.AgentServer.* requirements, and add explicit references in transitive-pinning-off Foundry consumers to avoid CS1705/MSB3277 version conflicts.
Map A2A protocol message_id to AgentResponseUpdate.message_id in two paths
where it was previously omitted, aligning with .NET behavior:
1. Standalone A2AMessage: set message_id=msg.message_id (matches .NET
ConvertToAgentResponseUpdate(Message) which sets both ResponseId and
MessageId to message.MessageId)
2. TaskStatusUpdateEvent (terminal/input_required): set
message_id=message.message_id (matches .NET which sets
MessageId=statusUpdateEvent.Status.Message?.MessageId)
Fixes#5949
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* test: reshuffle .NET Workflow tests in preparation for Outputs overhaul
Phase 1 of the .NET Workflows outputs overhaul (see
working/implementation-plan.md). Pure moves/renames in
dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests; no production code
changes, no new test cases. The split keeps each orchestration mode in
its own source file so the upcoming tag-aware and orchestration-default
test additions land on clean diffs.
Renames:
* WorkflowBuilderSmokeTests.cs -> WorkflowBuilderTests.cs (with class
rename to match). The scope is no longer "smoke"-only once subsequent
phases add tag-aware builder tests.
* InputWaiterAndOutputFilterTests.cs -> InputWaiterTests.cs +
OutputFilterTests.cs. The file already declared the two test classes
separately; this split simply gives each its own file so the
output-filter cases have a dedicated home for tag-aware additions.
Split of AgentWorkflowBuilderTests.cs:
* AgentWorkflowBuilderTests.cs is now the outer
`public static partial class AgentWorkflowBuilderTests` holding the
shared test helpers (DoubleEchoAgent + session + WithBarrier variant,
WorkflowRunResult, RunWorkflow* methods) bumped from `private` to
`internal` so the new top-level GroupChatWorkflowBuilderTests in the
same assembly can reach them.
* AgentWorkflowBuilder.SequentialTests.cs (nested SequentialTests):
BuildSequential_InvalidArguments_Throws,
BuildSequential_AgentsRunInOrderAsync.
* AgentWorkflowBuilder.ConcurrentTests.cs (nested ConcurrentTests):
BuildConcurrent_InvalidArguments_Throws,
BuildConcurrent_AgentsRunInParallelAsync.
Sequential and Concurrent are kept as nested classes because they're
modes of the same `AgentWorkflowBuilder` static factory and do not
produce dedicated builder types.
New file:
* GroupChatWorkflowBuilderTests.cs (top-level): the existing
BuildGroupChat_* and GroupChatManager_* cases moved out of the old
AgentWorkflowBuilderTests file. They exercise the
`GroupChatWorkflowBuilder` type (returned by
`AgentWorkflowBuilder.CreateGroupChatBuilderWith`), so a dedicated
top-level test class - matching the convention reserved by the plan
for HandoffWorkflowBuilderTests / MagenticWorkflowBuilderTests - is
the right home. Cross-class helper references qualify with
`AgentWorkflowBuilderTests.DoubleEchoAgent` and
`AgentWorkflowBuilderTests.RunWorkflowAsync`.
The outer partial class is `static` (and nested classes carry the
instance test methods) because the outer holds only static helpers;
this satisfies CA1052 without suppressions and is invisible to xUnit
discovery, which finds tests on the nested classes as
`AgentWorkflowBuilderTests.SequentialTests.*` etc.
Validation: `dotnet build` clean on both target frameworks; all 547
tests in Microsoft.Agents.AI.Workflows.UnitTests pass on net10.0.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat: introduce OutputTag, Futures, and tag-aware WorkflowBuilder API
Phase 2 of the .NET Workflows outputs overhaul. Additive code change
only - no observable runtime behavior change. The runner still uses the
legacy bypass for AgentResponse / AgentResponseUpdate payloads, and the
new `Futures.EnableAgentResponseOutputTaggingAndFiltering` flag defaults
to false. Phase 3 will wire the flag into the runner; this commit only
introduces the types and the builder API.
New public surface:
* `OutputTag` (readonly struct): wraps a string Value with ordinal
equality (IEquatable, GetHashCode, == / !=) so it can participate as a
HashSet element. Internal ctor closes the set. One public singleton:
`OutputTag.Intermediate`. Terminal / regular outputs carry no tag
(empty Tags set). JSON-serialized as a bare string via
[JsonConverter(typeof(OutputTagJsonConverter))], with the converter
rehydrating to the well-known singleton on read.
* `Futures` (static class): hosts opt-in pre-GA behavior switches.
First flag is `EnableAgentResponseOutputTaggingAndFiltering`; XML doc
captures the v2.0.0 obsoletion / v3.0.0 removal lifecycle.
* `WorkflowOutputEvent.Tags`: `HashSet<OutputTag>` exposed directly
(concrete collection, matches the JSON-serialization convention used
for `WorkflowInfo.OutputExecutorIds`). Never null; empty for legacy /
terminal events. New ctors take a single `OutputTag` or
`IEnumerable<OutputTag>?`; the existing (data, executorId) ctor
remains and produces an untagged event. `HasTag(OutputTag)` helper.
`AgentResponseEvent` and `AgentResponseUpdateEvent` gain matching
tag-accepting ctors forwarding to the base.
* `WorkflowOutputEventExtensions.IsIntermediate(this WorkflowOutputEvent)`:
extension method returning `evt.HasTag(OutputTag.Intermediate)`. The
preferred way to ask "is this an intermediate output?" without
reaching into the Tags set.
* `WorkflowBuilder.WithOutputFrom(IEnumerable<ExecutorBinding>, OutputTag)`
and `WorkflowBuilder.WithOutputFrom(ExecutorBinding, OutputTag)`:
forward-looking tagged overloads. The IEnumerable form is the primary
tagged surface; the single-executor form is a convenience for the
common one-executor case. Currently usable for the
`OutputTag.Intermediate` singleton; will become the primary surface
once the `OutputTag` constructor is opened to user-defined tags in
a future release. Callers in this release should prefer the
intent-specific `WithIntermediateOutputFrom` extension for the
intermediate case. Tags accumulate across repeated calls; same tag
repeated dedupes via the HashSet.
* `WorkflowBuilderExtensions.WithIntermediateOutputFrom(this WorkflowBuilder, IEnumerable<ExecutorBinding>)`:
helper that forwards to `WithOutputFrom(executors, OutputTag.Intermediate)`.
Takes an IEnumerable (matching the tagged WithOutputFrom shape) -
callers pass collection literals: `builder.WithIntermediateOutputFrom([a, b])`.
XML doc remarks call out the Futures-flag interaction and the
AIAgent-payload forwarding contract.
Internal shape changes:
* `WorkflowBuilder._outputExecutors`: HashSet<string> -> Dictionary<
string, HashSet<OutputTag>>. The value set is empty for executors
designated only via the untagged WithOutputFrom; contains Intermediate
(and possibly future tags) otherwise.
* `Workflow.OutputExecutors`: HashSet<string> -> Dictionary<string,
HashSet<OutputTag>>.
* `OutputFilter.CanOutput`: `Contains(id)` -> `ContainsKey(id)`.
* `WorkflowInfo.OutputExecutorIds`: HashSet<string> -> Dictionary<
string, HashSet<OutputTag>>, with a custom JsonConverter that reads
both the new map shape (`{id: ["intermediate", ...]}`) and the legacy
array shape (`[id1, id2]`, where each id is treated as an untagged
output). Always writes the map shape. IsMatch updated to compare
per-id tag sets.
Tests landing in this commit (per the test-with-feature principle):
* `OutputTagTests.cs` (6 tests): KnownValues, EqualityIsOrdinalOnValue,
DefaultStructValueIsDistinct (default(OutputTag) does not collide
with the Intermediate singleton in a HashSet),
GetHashCodeMatchesEquals, JsonConverter_RoundtripsValueAsString,
ConstructorIsInternal (reflection-based assertion that the (string)
ctor is `internal`).
* `WorkflowBuilderTests.cs` adds 7 new tests pinning the builder
API contract: RegistersWithEmptyTagSet, AddsIntermediateTag,
MultipleExecutorsAllUntagged, ThenIntermediate_AccumulatesTags,
RepeatedDedupes, OnlyRegistersWithoutPriorWithOutputFrom,
TracksExecutorBinding.
* `BackwardsCompatibility/JsonCheckpointSerializationTests.cs`
(new folder + file, 5 tests): event-level ctor contract tests
(single-tag, no-tag, multi-tag — the last with a custom tag);
IsIntermediate() asserted; load-bearing JSON BC tests for
`WorkflowInfo.OutputExecutorIds` -
`WorkflowOutputExecutorsReadsLegacyArrayShape` (legacy ids map to
empty tag sets) and `WorkflowOutputExecutorsWritesMapShape`.
The plan's three JSON round-trip tests for `WorkflowOutputEvent.Tags`
were dropped: `WorkflowEvent` is not currently a serialized checkpoint
shape (see the comment in WorkflowsJsonUtilities.cs about events not
being persisted), so there is no real back-compat surface to pin
through JSON. They are substituted with in-process ctor/property
round-trip tests that exercise the `Tags` / `HasTag` / `IsIntermediate`
contract.
Validation: full `Microsoft.Agents.AI.Workflows.UnitTests` suite runs
green on net10.0 (565 passing, 0 failing). Core library builds clean
on net472, netstandard2.0, net8.0, net9.0, and net10.0. Test project
builds clean on net472 + net10.0.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat: route AgentResponse(Update) through the output filter under a Futures flag
`InProcessRunnerContext.YieldOutputAsync` historically special-cased AgentResponse and
AgentResponseUpdate payloads: it built the typed event subclass and emitted it directly,
bypassing the output filter. Rewrites the method so that:
- When `Futures.EnableAgentResponseOutputTaggingAndFiltering` is `false` (the current
default), AgentResponse(Update) keep the legacy bypass — emitted as
AgentResponseEvent / AgentResponseUpdateEvent with no tags. Existing callers see no
behavior change.
- When the flag is `true`, AIAgent payloads flow through the output filter just like
every other payload type: undesignated sources are dropped, and the emitted event
carries the source's tag set (empty for terminal `WithOutputFrom`, `{Intermediate}`
for `WithIntermediateOutputFrom`, the set union when both designations apply).
Non-AIAgent (POCO) outputs also now carry the source's tag set on the emitted
WorkflowOutputEvent unconditionally — additive, since no existing assertion inspected
Tags. Subclass events (`AgentResponseEvent` / `AgentResponseUpdateEvent`) continue to
be emitted under both modes so `switch (evt) { case AgentResponseEvent: ... }`
consumer code keeps matching.
Adds `OutputFilter.TryGetTags` as the tag-aware lookup used by the runner.
`OutputFilter.CanOutput` is kept (still used by the existing sync tests in
`OutputFilterTests.cs`).
Tests
-----
- `Futures/Futures.AgentResponseOutputFilteringAndTaggingTests.cs` (new): the F1–F13
matrix from the plan, covering every combination of `(flag on/off) Ă— (designation)
Ă— (payload shape)`. Uses a `FuturesScope` IDisposable + a `FuturesSerial` xUnit
collection (DisableParallelization = true) to keep the process-global flag from
leaking across parallel tests.
- `OutputFilterTests.cs`: four new `Test_OutputFilter_…` cases for the `TryGetTags`
surface (empty-tag-set for terminal designation, `{Intermediate}` for intermediate
designation, union for accumulated designation, `false` for unregistered).
582/582 unit tests pass on net10.0 (565 baseline + 17 new).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat: tag-aware defaults and designation API on orchestration builders
Aligns the .NET orchestration builders with Python's output / intermediate-output
distinction. Each builder either applies a Python-aligned default designation set or
replays the user's explicit `WithOutputFrom` / `WithIntermediateOutputFrom` calls,
never both.
Static `AgentWorkflowBuilder.BuildSequential` / `BuildConcurrent` apply defaults
unconditionally (no user-facing fluent surface to take control through):
- Sequential: terminal `end` + every agent designated intermediate.
- Concurrent: terminal `end` + every agent and per-agent accumulator designated
intermediate.
The three fluent instance builders memoize agent-typed designation calls in a
`Dictionary<AIAgent, HashSet<OutputTag>>` (empty set = terminal-only, non-empty =
intermediate tag(s)) so repeated calls dedupe naturally. They replay the entries
at `Build()` time, suppressing defaults when any call has been made:
- `HandoffWorkflowBuilder` / `HandoffWorkflowBuilderCore<TBuilder>` (also picked up
by the obsolete `HandoffsWorkflowBuilder` via inheritance).
Default: terminal `HandoffEnd` + every handoff agent intermediate.
(Bug fix: legacy code relied on `WithOutputFrom(end)` to bind `HandoffEnd`. The
new explicit-designation path bypasses that, so `Build()` now calls
`BindExecutor(end)` unconditionally to keep validation happy.)
- `GroupChatWorkflowBuilder` — default: terminal host + every participant intermediate.
- `MagenticWorkflowBuilder` — default: terminal orchestrator + every team member
intermediate.
Designating a non-participant agent throws `InvalidOperationException`.
The bare `WorkflowBuilder` default is unchanged — only the orchestration-style
builders gain implicit defaults, matching the plan's non-goal.
Tests
-----
- `AgentWorkflowBuilder.SequentialTests` / `.ConcurrentTests`: one default-spec
assertion each.
- `GroupChatWorkflowBuilderTests`: defaults-match-spec, explicit-replaces-defaults,
non-participant throws.
- `HandoffWorkflowBuilderTests` (new file): same three.
- `MagenticWorkflowBuilderTests` (new file): same three.
593/593 unit tests pass on net10.0 (582 baseline + 11 new).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat: WorkflowHostAgent forwards AgentResponseEvent unconditionally under Futures-on
Aligns the .NET Workflow-as-Agent surface with Python `as_agent`. Under
`Futures.EnableAgentResponseOutputTaggingAndFiltering = true`,
`WorkflowSession.InvokeStageAsync` now forwards `AgentResponseEvent`
unconditionally — joining `AgentResponseUpdateEvent` in ignoring the host's
`includeWorkflowOutputsInResponse` switch. That switch keeps governing the
generic `WorkflowOutputEvent` path for non-AIAgent payloads, where it is
further short-circuited by an `IsIntermediate()` check (tagged intermediate
outputs always surface).
Under Futures-off the legacy asymmetry is preserved: `AgentResponseUpdateEvent`
always forwarded, `AgentResponseEvent` gated by `includeWorkflowOutputsInResponse`.
Back-compat: with `Futures.EnableAgentResponseOutputTaggingAndFiltering` left at
its default `false`, observable behavior is identical to before.
`Futures` documentation gains a remark explaining the `Workflow.AsAIAgent()`
interaction in both flag states.
Runner fix
----------
`InProcessRunnerContext.YieldOutputAsync` now skips `Executor.CanOutput` for
AgentResponse-shaped payloads under both Futures branches. `AIAgentHostExecutor`
doesn't declare AgentResponse(Update) in its `Yields` set, so the historical
legacy bypass had silently skipped the check; Phase 3's Futures-on path was
running it and would reject AIAgent payloads. AIAgent-shaped payloads are now
always a valid output shape, matching the legacy bypass semantics.
Phase 4 follow-on
-----------------
Switched the three orchestration-builder designation-replay loops to iterate
`Dictionary.Keys` with a value lookup instead of constructing/destructuring
`KeyValuePair<,>`. Cleaner shape and avoids the netstandard2.0 / net472
`KeyValuePair<,>.Deconstruct` unavailability that surfaced when this branch
multi-TFM-built.
Tests
-----
`WorkflowHostSmokeTests.IntermediateForwarding` (new nested class, 6 tests):
- intermediate AgentResponse forwarded past the include-outputs gate (Futures on)
- terminal AgentResponse forwarded unconditionally (Futures on)
- terminal AgentResponse gated by include flag (Futures off, legacy)
- undesignated AIAgent executor emits no AgentResponseEvent under Futures-on
- legacy bypass still emits AgentResponseEvent under Futures-off
- intermediate tag is observable via `update.RawRepresentation`
The class joins the `FuturesSerial` xUnit collection so the process-global flag
is serialized against other Futures-toggling tests.
599/599 unit tests pass on net10.0 (593 baseline + 6 new).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat: SequentialWorkflowBuilder and ConcurrentWorkflowBuilder, OrchestrationBuilderBase
Promotes the Sequential and Concurrent orchestration shapes to first-class fluent
builder classes, matching Handoff / GroupChat / Magentic. Users can call
`WithOutputFrom(agents)` / `WithIntermediateOutputFrom(agents)` to control which
agents are designated output / intermediate sources; when no designation call is
made, the Python-aligned defaults apply (terminal aggregator output + every agent
intermediate; Concurrent also tags per-agent accumulators).
`AgentWorkflowBuilder.BuildSequential(...)` and `BuildConcurrent(...)` are kept
and now delegate to the new builders; observable behavior unchanged. Five static
factories now mirror each other:
- `AgentWorkflowBuilder.CreateSequentialBuilderWith(params IEnumerable<AIAgent>)`
- `AgentWorkflowBuilder.CreateConcurrentBuilderWith(params IEnumerable<AIAgent>)`
- `AgentWorkflowBuilder.CreateHandoffBuilderWith(AIAgent)` (already existed)
- `AgentWorkflowBuilder.CreateGroupChatBuilderWith(Func<...>)` (already existed)
- `AgentWorkflowBuilder.CreateMagenticBuilderWith(AIAgent)` (new)
OrchestrationBuilderBase
------------------------
New abstract `OrchestrationBuilderBase<TBuilder>` unifies the shared fluent
surface across all five orchestration builders: `WithName`, `WithDescription`,
`WithOutputFrom`, `WithIntermediateOutputFrom`, and the
`ApplyOutputDesignations(builder, agentMap, kind, applyDefaults)` helper that
either replays the user's designations or invokes the orchestration-specific
defaults.
Removes ~150 LOC of duplicated designation-management code from the four
non-Handoff builders, plus the equivalent from `HandoffWorkflowBuilderCore`.
Tests
-----
- New `SequentialWorkflowBuilderTests.cs` / `ConcurrentWorkflowBuilderTests.cs`
(replace the old `AgentWorkflowBuilder.{Sequential,Concurrent}Tests.cs`
nested-class files). Method names normalized to
`Test_<BuilderType>_<Scenario>[Async]`.
- Shared helpers (`DoubleEchoAgent`, `DoubleEchoAgentWithBarrier`,
`WorkflowRunResult`, `RunWorkflow*`) moved from the old
`AgentWorkflowBuilderTests` partial class into a new
`OrchestrationTestHelpers` static class in `OrchestrationTestHelpers.cs`.
Downstream test files (Group Chat, Handoff, Sequential, Concurrent) updated
to qualify with `OrchestrationTestHelpers.*`.
- A new `AgentWorkflowBuilderTests.cs` covers the static surface directly:
`BuildSequential` / `BuildConcurrent` invariants and aggregator wiring, plus
null-rejection + round-trip checks for every `Create*BuilderWith` factory.
- New AsAgent intermediate-suppression tests on a nested `AsAgentForwarding`
class for each of Sequential and Concurrent: build with only the terminal
agent designated via `WithOutputFrom`, run via `AsAIAgent(...)`, assert via
`AgentResponseUpdate.AuthorName` that intermediate agents do not surface.
Both join the `FuturesSerial` collection.
- New `Test_<Builder>_WithDescriptionPropagatesToWorkflow` smoke tests on
Sequential and Concurrent (newly available via the base class).
625/625 unit tests pass on net10.0.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore: dotnet format
* fixup: encoding
* fixup: charset
* fixup: Updates for PR feedback
* fixup: format
* fixup: merge issue
* Fix intermediate filtering on .AsAgent()
* fix filter logic
* fix: Revert logic change and add comments
---------
Co-authored-by: Jacob Alber <jalber@lokitoth.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Adding AgentFileStore and FileAccessProvider to support file ased operations for agents.
* Address PR review feedback on FileAccessProvider
- Probe symlinks on the unresolved candidate path so in-root symlinks
cannot silently pass and out-of-root symlinks surface the correct
error message.
- Validate matching_lines elements in FileSearchResult.from_dict and
raise a clean ValueError for non-mapping entries.
- Cap search regex pattern length (256 chars) via a new
_compile_search_regex helper to mitigate ReDoS, and surface the cap
in the file_access_search_files tool description.
- Skip non-UTF-8 files during filesystem search instead of aborting
the entire directory walk.
- Replace the module-scope trailing string in the data-processing
sample with comments to avoid Ruff B018.
- Remove the checked-in working/region_totals.md sample artifact so
the save flow works from a clean checkout.
- Expand the Windows stdout reconfiguration comment in task_runner.py
for clarity.
- Add tests for invalid/oversize regex, non-UTF-8 file search, and
in-root symlink rejection.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix mypy redundant-cast in FileSearchResult.from_dict
Use cast(list[object], ...) instead of cast(list[Any], ...) so the
cast represents a real type change (lists are invariant) and is no
longer flagged by mypy as redundant, while still satisfying pyright's
reportUnknownVariableType. Matches the existing pattern in _memory.py.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Tighten path normalization and directory resolution in FileAccess
- _normalize_relative_path now strips surrounding whitespace up front
so leading/trailing spaces never leak into file segments, and
rejects trailing path separators for file paths so 'foo/' is no
longer silently coerced to 'foo'.
- FileSystemAgentFileStore._resolve_safe_directory_path normalizes
with is_directory=True and maps an empty normalized result to the
root. This matches InMemoryAgentFileStore so whitespace-only
directory inputs resolve to the root instead of raising.
- Added tests for whitespace stripping, trailing-separator rejection,
and whitespace-only directory listing on the filesystem store.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Harden FileAccess search and atomic save in store API
- Add wall-clock timeout (10s) around regex scans so a pathological pattern (e.g. `(a+)+`) below the length cap cannot stall the event loop.
- Offload the InMemoryAgentFileStore regex scan to a worker thread, matching the filesystem store.
- Fail closed when `Path.is_symlink` raises during the safe-path probe so a permission error cannot silently bypass the symlink/reparse-point rejection.
- Add `overwrite: bool = True` to `AgentFileStore.write_file`; the in-memory store performs the check under the existing lock and the filesystem store uses `open(mode='x')` so concurrent callers cannot race past `overwrite=False`.
- `file_access_save_file` now relies on the atomic store call instead of a separate `file_exists` round-trip.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix Python 3.10 timeout handling and add directory arg to list/search tools
- Catch asyncio.TimeoutError in _run_search_with_timeout. In Python 3.10
asyncio.wait_for raises asyncio.exceptions.TimeoutError, which is
distinct from the builtin TimeoutError (the two were unified in 3.11).
Catching the asyncio alias works on every supported version.
- Add an optional directory parameter to file_access_list_files and
file_access_search_files so agents can enumerate / scope searches to
nested folders, not just the store root.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address FileAccess review feedback: case, errors, signal, TOCTOU
- InMemoryAgentFileStore now stores (display_name, content) so list_files
and search_files return the original-case names callers wrote, matching
the behaviour of FileSystemAgentFileStore on case-preserving filesystems
and removing the silent in-memory vs. on-disk contract divergence.
- FileSystemAgentFileStore.read_file raises ValueError instead of letting
UnicodeDecodeError bubble for binary / non-UTF-8 input, restoring
symmetry with search_files (which still skips) and giving the tool
layer a recoverable type to translate.
- Tool wrappers now catch ValueError and OSError around every operation
and surface them as readable strings, so 'you used ..' and 'the file
already exists' are both reported to the model the same way instead of
the former crashing out as an unhandled exception.
- _search_files_sync logs per skipped non-UTF-8 file at WARNING and an
aggregate INFO summary so operators can distinguish 'no matches' from
'half the corpus was unreadable'.
- FileSystemAgentFileStore softens its docstrings to acknowledge the
inherent probe-then-open TOCTOU window. On POSIX both read and write
now pass O_NOFOLLOW so the kernel refuses if the leaf segment becomes
a symlink between the probe and the open. Windows has no equivalent
flag; the limitation is documented.
- Tests cover: case preservation on list/search, ValueError on non-UTF-8
read at the store and tool layer, tool-layer string responses for
path-traversal and oversized-regex inputs, search-skip log output,
symlink rejection on delete/search/list, and symlinked intermediate
directory rejection.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address FileAccess nit comments: docstrings, enumerate, opt-in delete approval
- Expand FileSearchMatch/FileSearchResult.to_dict docstrings to explain why
the override is needed (__slots__ defeats the mixin's __dict__ iteration)
and why exclude/exclude_none are accepted-but-ignored (mixin signature
compatibility for callers like to_json).
- Use enumerate(lines, start=1) in _search_file_content so the +1 below is
no longer needed; rename loop variable to line_number for clarity.
- Add opt-in require_delete_approval: bool = False on FileAccessProvider.
When True, file_access_delete_file is registered with approval_mode
'always_require' so the host must approve every delete. Default False
preserves current behaviour and matches the .NET reference, but
deployments that want a safer-by-default posture can enable it.
- Add tests covering both delete approval modes.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* FileAccess: require delete approval by default
Flip the default for FileAccessProvider(require_delete_approval=...) from
False to True so destructive deletes are gated by host approval out of the
box. Callers that want the previous autonomous behaviour (which matches the
.NET reference) can pass require_delete_approval=False.
Tests updated accordingly.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fixing linkinspector by installing Chrome for puppeteer first.
---------
Co-authored-by: Ben Thomas <25218250+alliscode@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Expose supported_protocol_bindings as configurable parameter on A2AAgent
Add supported_protocol_bindings parameter to A2AAgent.__init__() allowing
users to configure which A2A protocol bindings (JSONRPC, GRPC, HTTP+JSON)
the client prefers when connecting to remote agents.
- Defaults to ["JSONRPC"] matching current behavior
- Passes through to ClientConfig for transport negotiation
- Replaces 4 hardcoded references with the configurable value
Closes#6057
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix empty list falsy trap and add fallback path test coverage
- Use 'is not None' check instead of 'or' to preserve explicit empty list
- Add test verifying empty list is not silently replaced with defaults
- Add test verifying fallback path uses custom bindings
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Document known protocol binding values in docstring
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Use Literal union for protocol binding type hint
Provides IDE autocomplete for known values while keeping the type
open for custom bindings (Literal is str at runtime).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Refactor group chat workflow to prevent message echoing and enhance checkpointing
- Updated GroupChatWorkflowBuilder to disable forwarding incoming messages to prevent duplicates.
- Enhanced RoundRobinGroupChatManager with checkpointing support to preserve state across executions.
- Modified GroupChatHost to maintain a history of messages and track the current speaker for message broadcasting.
- Implemented broadcasting logic to ensure participants receive messages from others while excluding their own responses.
- Added comprehensive unit tests for group chat orchestration, including scenarios for tool approval and function calls.
- Introduced a new ApprovalHarness for testing tool invocation and approval workflows.
* fixup: format
* Add JSON serialization support for GroupChatManagerState and RoundRobinGroupChatManagerState
---------
Co-authored-by: Jacob Alber <jalber@lokitoth.com>
* Refactor AgentFileSkillsSource to use filter predicates and add AgentFileSkillFilterContext
- Replace hardcoded script/resource directory lists with configurable ScriptFilter and ResourceFilter predicates
- Add AgentFileSkillFilterContext class to provide contextual file information to filter predicates
- Replace MaxSearchDepth constant with configurable SearchDepth option
- Update AgentFileSkillsSourceOptions with new filter and search depth properties
- Update tests to reflect the new filtering approach
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Log '(none)' instead of empty string for missing file extensions in debug output
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat: Add DelegatingAgentSessionStore
Add helper for decorator pattern for AgentSessionStore
* feat: Add UserIdentityScopedSessionStore
Add support for using the ASP.Net Core ambient `ClaimsIdentity` User, along with a user-specified claim type to scope the session store based on authenticated identity.
* fix: Harden scope mapping
* fix: Add UserIdentityScopeSessionStoreOptions to avoid future breaking changes
* Split UserIdentityScopedSessionStore into a separate IsolationKeyProvider and IsolationKeyScopedSessionStore
* Add GetService<>() capabilities to interrogate AgentSessionStore delegation chain
* Harden default for A2A hosting by using an IsolationKeyScopedAgentSessionStore when no store is available.
* Pipe isolation through Hosting helper extension methods
* Add comment to samples about adding SessionIsolationKeyProvider
* Fix isolation key provider nullability semantics
* fix A2A defaults
* fixup
* remove unneeded keyProvider requirement test
* Add trust-model XML docs to AgentSessionStore, InMemoryAgentSessionStore, MapAGUI, A2A entry points
Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/e466c53a-faad-40a8-8b5f-83cf0dce0b1d
Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>
* fix: Switch ClaimsBasedIsolationKeyProvider to be Singleton
* matches HttpContextAccessor and related MAF services
* release: Ensure new project is in the release filter
* fixup: Integraitaon tests
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>
Bumps the released 1.6.0 packages agent-framework, agent-framework-core, agent-framework-foundry, and agent-framework-openai to 1.7.0, with root continuing to exactly pin agent-framework-core[all]. Bumps the changed prerelease packages agent-framework-a2a, agent-framework-chatkit, agent-framework-declarative, agent-framework-devui, and agent-framework-foundry-hosting to the 260528 date stamp, raises core floors on the packages included in this release, raises Foundry's OpenAI floor alongside OpenAI, and raises ChatKit's openai-chatkit floor to the minimum version required by the current typed API usage. No beta cohort bump was applied; the absent mistal/mistral package was intentionally not bumped because no such package exists in this branch.
* Python: Allow hosted checkpoints to restore MessageRole
Allow Responses hosting checkpoint storage to deserialize the Azure Responses MessageRole enum that hosted workflows can persist inside Agent Framework Message objects.
Add regression coverage for both direct load() and the hosted get_latest() restore path, including the plain-storage failure mode where list_checkpoints logs the blocked type and get_latest() returns None.
Ruff also normalizes a duplicate contextlib import in the touched hosting module.
* Address MessageRole checkpoint review comments
* Cover hosted MessageRole checkpoint restore path
* Align c# and python TodoProvider tool names
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* Address PR review: remove __slots__ and add typed schemas for tool params
- Remove __slots__ from TodoItem, TodoInput, and TodoCompleteInput classes
(not needed for low-instance-count objects and hinders dev scenarios)
- Add _TodoAddItemSchema and _TodoCompleteItemSchema TypedDicts to provide
proper JSON schema for todos_add and todos_complete tool parameters
- Use typing_extensions for Python 3.10 compatibility
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
`OpenAIChatClient._inner_get_response()` reads `.headers` on the raw streaming
response returned by `client.responses.with_raw_response.create(stream=True)`
(and its three sibling call sites - retrieve-streaming, non-streaming create
and background retrieve) to surface the `x-ms-served-model` Azure header,
introduced in #5910.
When `azure-ai-projects>=2.1.0` experimental GenAI tracing is enabled
(`AZURE_EXPERIMENTAL_ENABLE_GENAI_TRACING=true`), the instrumentor wraps the
raw streaming response in an inline `AsyncStreamWrapper` that exposes
`.response` but not `.headers`. Reading `raw_create_response.headers` then
raises `AttributeError: 'AsyncStreamWrapper' object has no attribute 'headers'`,
which `FoundryChatClient` rethrows as a `ChatClientException` and breaks every
streaming call (workflows and free chat).
Fix: read the header dict via `getattr(raw_response, "headers", None)` at all
four call sites. `_extract_served_model()` already short-circuits on `None`,
so the served-model surfacing degrades gracefully (model stays the deployment
alias) instead of crashing when the response is wrapped by an instrumentor
that does not proxy `.headers`.
Regression test added:
`test_streaming_response_without_headers_attribute_does_not_crash`
simulates a stream wrapper that raises `AttributeError` on `.headers` and
asserts the stream still completes with the deployment alias as `update.model`.
Fixes#6028
Co-authored-by: Emilien Mottet <emilien.mottet@michelin.com>
* feat(a2a): link follow-up messages via reference_task_ids
Track the task_id from A2A responses (task, status_update, artifact_update,
and message payloads) on session.state and include it as reference_task_ids
on subsequent outgoing messages. This enables remote agents to correlate
follow-up messages as task refinements per the A2A spec.
Resolves#5938
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(a2a): add A2AAgentSession for typed protocol state tracking
Introduce A2AAgentSession (subclass of AgentSession) with context_id,
task_id, and task_state properties. This follows the DurableAgentSession
pattern and mirrors the .NET A2AAgentSession design.
- Track task_id, context_id, and task_state from all response payload types
- Validate context_id consistency (raise on mismatch)
- Auto-assign server-generated context_id when not set
- Only A2AAgentSession gets reference tracking (no state dict fallback)
- Plain AgentSession continues to work without reference tracking
- Add serialization support (to_dict/from_dict)
- Export via agent_framework.a2a and agent_framework_a2a
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* style: remove unnecessary string annotation (pyupgrade)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: use AgentSession.from_dict for state deserialization
Avoids importing private _deserialize_state, matching the
DurableAgentSession pattern.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: track context_id from message payloads in A2AAgentSession
Previously, context_id was only captured from task, status_update, and
artifact_update payloads. Message-only responses (which carry context_id
but may lack task_id) were silently lost. This fix:
- Captures msg.context_id in the message handler
- Persists session state when either last_task_id or last_context_id is
present (not only when task_id is truthy)
- Only updates task_id/task_state when a task_id was actually returned
- Adds a test for message-only context_id tracking
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* addressed comments
* Gate status content to INPUT_REQUIRED/terminal states (match .NET)
Match .NET's GetUserInputRequests pattern: only emit TaskStatusUpdateEvent
message content when state is INPUT_REQUIRED or terminal. Intermediate
status text (WORKING, SUBMITTED) is no longer surfaced to callers.
When state is INPUT_REQUIRED, set additional_properties['input_required']
= True so callers can distinguish input requests from final responses.
Closes#5937
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address review: remove message task_id tracking, defensive fallbacks, and input_required flag
- Do not track task_id from Message payloads (simple interactions
without task tracking)
- Remove 'or last_task_id' fallback from status_update and
artifact_update handlers (spec guarantees task_id is always set)
- Remove additional_properties['input_required'] flag (content gating
to INPUT_REQUIRED/terminal states is the signal itself)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Fixes#4522
Replace deprecated `asyncio.iscoroutinefunction()` with `inspect.iscoroutinefunction()`
to resolve Python 3.13+ deprecation warning.
Changes:
- Added `import inspect` to imports
- Replaced `asyncio.iscoroutinefunction(hook)` with `inspect.iscoroutinefunction(hook)` on line 126
- This makes the code consistent with other test methods in the same file (lines 201, 236)
The rest of the file already uses `inspect.iscoroutinefunction()` correctly, making
this change consistent with the existing codebase pattern.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Tao Chen <taochen@microsoft.com>
* Add MCP-based skills support
- Add AgentMcpSkill, AgentMcpSkillResource, AgentMcpSkillsSource, and McpSkillIndex to Microsoft.Agents.AI.Mcp
- Add AgentSkillsProviderBuilderMcpExtensions for DI integration
- Add Agent_Step06_McpBasedSkills sample project
- Add unit tests for AgentMcpSkillsSource
- Update solution file and project references
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Remove unnecessary [Experimental] attributes from MCP package
The package is already alpha, so the [Experimental] attribute is redundant.
Removed from both AgentSkillsProviderBuilderMcpExtensions and
AgentMcpSkillsSource classes.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Make Agent_Step06_McpBasedSkills self-contained and add to verify-samples
Embed an internal MCP server (launched via --server flag as a child process)
that serves skill://index.json and skill://unit-converter/SKILL.md resources,
replacing the external MCP_SKILLS_ENDPOINT dependency. The sample now uses
StdioClientTransport and a fixed prompt instead of an interactive loop.
Added SampleDefinition to AgentsSamples.cs for automated verification.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Sort usings
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add a HarnessAgent with available features and sample
* Fix formatting
* Address PR comments and fix mypy error
* Add web search support to HarnessAgent
* Fix build warning
* Apply suggestions from code review
Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com>
* Address PR comments
* Address PR comments
* Address further PR comments.
* Fix markdown broken link
---------
Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com>
* feat(foundry): add experimental to_prompt_agent converter
Adds `to_prompt_agent(agent)`, an experimental converter
(`ExperimentalFeature.TO_PROMPT_AGENT`) that turns an Agent Framework
`Agent` into a Foundry `PromptAgentDefinition` ready to publish via
`AIProjectClient.agents.create_version(...)`.
Behaviour:
* `agent.client` must be a `FoundryChatClient` (or subclass); otherwise
`TypeError` is raised. The model deployment name is lifted from the
bound client so the same Agent definition used for local runs can be
published as a hosted prompt agent without restating the model.
* Foundry SDK tool instances (from `FoundryChatClient.get_*_tool()`) are
passed through unchanged. AF `FunctionTool`s (and `@tool`-decorated
callables) are emitted as Foundry `FunctionTool` declarations.
* Local AF MCP tools cannot be expressed in a `PromptAgentDefinition`;
the converter raises `ValueError` and points at
`FoundryChatClient.get_mcp_tool()` for hosted MCP servers.
* The converter walks both `agent.default_options["tools"]` and
`agent.mcp_tools` because `normalize_tools()` splits local MCP off
into its own list.
Re-exported through the `agent_framework.foundry` lazy-loading namespace
(updates both `__init__.py` and the `__init__.pyi` type stub).
Adds a portable-agent sample showing the same `Agent` driven through
both `agent.run(...)` and `to_prompt_agent(agent)`, and a README section
covering the new converter.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore(samples): remove snippet tags from portable agent sample
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore(samples): inline FoundryChatClient and enable prompt-agent publish
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore(samples): drop async credential context manager
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* docs(foundry): trim README to_prompt_agent example to publish-only flow
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* docs(foundry): note FoundryAgent runs @tool callables for deployed prompt agents
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(foundry): address review comments on to_prompt_agent converter
* Construct `PromptAgentDefinition` `Tool` from a dict via `**tool_item`
unpacking rather than the positional Mapping constructor \u2014 cleaner and
matches the typical Pydantic / Azure SDK pattern.
* Drop the redundant `isinstance(mcp_tool, MCPTool)` guard in
`_convert_tools`; the parameter is already typed `Iterable[MCPTool]` so
the second `raise` was unreachable. The remaining single `raise`
fires for every entry as intended.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(foundry): match Agent.__init__ model resolution in to_prompt_agent
* Read the model from `agent.default_options.get("model")` first,
falling back to `agent.client.model`. This mirrors the order
`Agent.__init__` uses (`_agents.py:740`) when assembling
default_options, so the model the agent runs with is the same model
the converter publishes \u2014 e.g. when the caller passes
`default_options={"model": "..."}` to override the bound client.
* Updated the missing-model error message to point at both the client
and the default_options paths.
* Added tests:
* tool-only agent with no `instructions` produces a definition
where `instructions` is `None` and is omitted from the dict
payload (`Agent.__init__` strips None values from default_options
before storing them).
* `default_options['model']` wins over the bound client's model.
* Fallback to client.model when default_options has no model.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(foundry): add deploy_as_prompt_agent helper + samples
Adds `deploy_as_prompt_agent(agent)`, a convenience wrapper around
`to_prompt_agent` that reuses the bound FoundryChatClient's project
client to call `project_client.agents.create_version(...)`. Defaults
`agent_name` / `description` from `agent.name` / `agent.description`
so the Agent stays the single source of truth.
* Exposed from `agent_framework_foundry` and the lazy-loading
`agent_framework.foundry` namespace (including the .pyi stub).
* Marked experimental with the existing
`ExperimentalFeature.TO_PROMPT_AGENT` tag.
* Tests cover the happy path, name/description defaulting, explicit
override, no-name error, metadata + description forwarding, extra
kwargs passthrough, and the experimental metadata.
Samples:
* Renamed the existing sample to `creating_prompt_agents.py`, drops
'portable' wording, presents `deploy_as_prompt_agent` first as the
recommended path and `to_prompt_agent` + `AIProjectClient` as the
two-step alternative, and adds a cleanup step that deletes the
published agent so re-runs stay idempotent.
* New `using_prompt_agents.py` shows the end-to-end loop: deploy the
agent, connect to it with `FoundryAgent` passing the same local
`@tool` callable, run a query against the deployed prompt agent,
then clean up.
README updated to introduce `deploy_as_prompt_agent` as the
recommended path and link to both runnable samples.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(foundry): restore missing-model ValueError in to_prompt_agent
The check was accidentally dropped while reworking docstrings in the
previous commit. Test `test_to_prompt_agent_rejects_missing_model`
exercises this path and was failing on CI as a result.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(foundry): rename deploy_as_prompt_agent -> create_prompt_agent
Renames the helper across the foundry package, core lazy-loader stubs,
tests, README and samples. The new name better matches the action
performed (a prompt-agent definition is created in Foundry) and is
consistent with the surrounding ''create_*'' API surface.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(foundry): drop create_prompt_agent, enrich to_prompt_agent params
Remove the create_prompt_agent helper and consolidate on to_prompt_agent.
Expose every PromptAgentDefinition parameter that has either an Agent
Framework equivalent (sourced from default_options) or no equivalent
(accepted as a keyword argument).
* default_options-sourced (with kwarg overrides):
temperature, top_p, string tool_choice
* kwarg-only Foundry knobs:
reasoning, text, structured_inputs, rai_config, ToolChoiceParam tool_choice
Precedence is always: explicit keyword > default_options entry > unset.
Tests cover every path (defaults, default_options, kwargs, kwarg override).
Samples and README rewritten around the enriched to_prompt_agent.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(foundry): single source of truth for prompt-agent options
Stop duplicating the generation-parameter surface between FoundryChatOptions
and to_prompt_agent. Translate every field with an Agent Framework equivalent
(temperature, top_p, tool_choice, reasoning, response_format/text/verbosity)
from agent.default_options via a new RawFoundryChatClient helper
_prepare_prompt_agent_options. Only Foundry-specific fields with no AF
equivalent — structured_inputs and rai_config — remain as keyword arguments
on to_prompt_agent.
- tool_choice is dropped when there are no tools (mirrors _prepare_options
semantics and avoids polluting tool-less prompt agents with Agent.__init__'s
'auto' default).
- response_format Pydantic models route through
openai.lib._parsing._responses.type_to_text_format_param; dict shapes go
through the existing _prepare_response_and_text_format helper.
- default_options is not mutated; text dict is defensively copied.
Tests, README, and creating_prompt_agents.py sample updated to reflect the
new single-source model.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* docs(foundry): consolidate prompt-agent sample
Drop creating_prompt_agents.py (the publish-only variant) and rename
using_prompt_agents.py to foundry_prompt_agents.py so the single sample
covers the full convert -> publish -> connect -> run loop. Update the
README link list accordingly.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* docs(foundry): run local Agent + deployed agent in same sample
Add an agent.run() call against the local Agent before publishing, then run
the deployed prompt agent on the same query. Expand the docstring with a
compare-and-contrast covering runtime/latency, configurability, and
persistence/sharing differences between the two execution paths.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* test(foundry): cover conflicting response_format + text.format in to_prompt_agent
Exercises the ValueError path when a Pydantic response_format would overwrite
an explicit text.format mapping with a different shape. Lifts _chat_client.py
coverage from 89% to 90%.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(foundry): move _prepare_prompt_agent_options into _to_prompt_agent
Lift the translation helper off RawFoundryChatClient and into the
_to_prompt_agent module as a module-private function that takes the client
as its first argument. The chat client no longer needs to carry a method
whose only consumer is the prompt-agent converter, while still serving as
the source of the request-path helper (_prepare_response_and_text_format)
that the converter reuses for dict-shaped response_format values.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* docs(python): codify GA terminology + post-run docs review
Add two pieces of guidance to python/AGENTS.md:
* Terminology - reserve 'GA' for hosted services; use 'released' or 'stable'
for Agent Framework code/features to match the feature-lifecycle stages.
* Maintaining Documentation - review AGENTS.md and skills at the end of every
run and update any guidance the conversation made stale; before adding a
new principle, ask the user to confirm it should be captured.
Also pulls in a docstring fix in foundry_prompt_agents.py that swaps the
stray 'GA' for 'released', applying the new terminology rule.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* address PR review: strict=True default, Tool._deserialize dispatch, sample cleanup safety
- FunctionTool published as strict=True so the server-side schema validation
matches what the local FoundryAgent(tools=[same_callable]) dispatcher
enforces. AF FunctionTool has no 'strict' attribute, so the safer default
is used uniformly instead of silently downgrading to a permissive contract.
- _validate_mapping_tool now dispatches through ProjectsTool._deserialize so
dict-shaped tools rehydrate to the concrete subclass (FunctionTool,
WebSearchTool, ...) via the 'type' discriminator instead of returning a
generic Tool. Added a test that asserts isinstance(WebSearchTool) and a
new test for the function-typed dict path.
- foundry_prompt_agents.py sample now wraps credential + project client in
async with and the create_version / run flow in try/finally so a failure
on connect or run still deletes the published prompt agent rather than
leaving an orphaned, billable resource in the user's Foundry project.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(ci): correct linkspector ignorePattern typo (./pulls -> ./pull)
GitHub PR URLs use the singular segment /pull/N (compare to /issues/N
for issues). The existing './pulls' ignore pattern never matched
anything as a result, so legitimately stale PR links (e.g. PRs deleted
from forks) surface as linkspector failures on unrelated PRs.
This is the same convention the './issues' rule above already follows.
Fixes the markdown-link-check failure on a dangling link in
dotnet/src/Microsoft.Agents.AI.DurableTask/CHANGELOG.md.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add Python parity sample for invoking Foundry Toolbox tools from declarative workflows
* Python: address PR review on declarative toolbox sample
Two security fixes for PR #5933:
1. Add safe_mode flag to WorkflowFactory (default True) mirroring
AgentFactory. Gates =Env.* exposure inside DeclarativeWorkflowState
PowerFx symbols via _safe_mode_context, so workflow YAML loaded from
untrusted sources no longer leaks the host's full os.environ snapshot
into PowerFx evaluation. The flag is also forwarded to the
internally-constructed AgentFactory so inline agent definitions
follow the same policy.
2. Pin the invoke_foundry_toolbox_mcp sample's _client_provider to the
resolved toolbox endpoint. The bearer-authenticated httpx client is
now only returned when MCPToolInvocation.server_url matches the
toolbox URL case-insensitively; any other URL gets None (the default
unauthenticated path), preventing the Foundry AAD bearer token from
being attached to a mis-configured or injected server URL. Mirrors
the .NET sample's httpClientProvider guard.
The sample is updated to opt in to safe_mode=False because its YAML
intentionally uses =Env.FOUNDRY_TOOLBOX_* to keep configuration in env
vars under the developer's control.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix pyright issues.
* Addressed PR comments.
* Fix CI pipelines.
* Resolve PR comments
* Revamped sample to address PR comments.
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Refactor AgentSkill API to async resource and script lookup
Replace property-based AgentSkill.Content, Resources, and Scripts with
async-by-name lookup methods plus boolean availability flags:
- Content (string getter) -> GetContentAsync(CancellationToken)
- Resources (full list) -> HasResources + GetResourceAsync(name, ct)
- Scripts (full list) -> HasScripts + GetScriptAsync(name, ct)
This makes the API friendlier for sources like MCP where enumerating all
resources up front is expensive or impossible, and allows skill implementations
to fetch content lazily.
Subclass changes:
- AgentFileSkill and AgentInlineSkill implement the new async API while
preserving content caching.
- AgentClassSkill<TSelf> keeps virtual Resources/Scripts properties for
reflection-based discovery and seals the new HasResources/HasScripts/
GetResourceAsync/GetScriptAsync overrides. Its previously non-thread-safe
lazy initialization is replaced with Lazy<T> (default thread-safety) wired
up in a new protected constructor, so concurrent first-access from multiple
threads is safe.
- AgentSkillsProvider calls the new async API and exposes
ead_skill_resource
/ load_skill /
un_skill_script tools that await the per-name lookups.
Includes baseline CompatibilitySuppressions.xml entries for the removed
property getters.
Tests:
- Direct coverage for HasResources, HasScripts, GetResourceAsync, and
GetScriptAsync on all three skill implementations (positive, missing-name,
and no-resources/no-scripts cases).
- Thread-safety regression test for AgentClassSkill<TSelf> that exercises
concurrent first-access to Resources, Scripts, and GetContentAsync from
many tasks and asserts all observers see the same cached instance.
- Provider-level coverage for the
ead_skill_resource tool (invocation +
error paths) and for the previously untested error paths of load_skill
and
un_skill_script (empty names, skill/resource/script not found).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address PR review comments
- Move GetScriptAsync inside try/catch in RunSkillScriptAsync for error-handling parity
- Remove dead _reflectedResources branch from AgentSkillTestExtensions
- Fix XML docs to reference virtual Resources/Scripts properties (not sealed methods)
- Add Async suffix to async test methods per naming convention
- Make no-await tests synchronous to eliminate CS1998
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix formatting: add UTF-8 BOM and remove unused using
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix XML cref: Resources/Scripts are on AgentClassSkill<TSelf>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Remove HasResources and HasScripts properties from AgentSkill
Drop the virtual HasResources and HasScripts properties from AgentSkill
and all concrete subclasses (AgentFileSkill, AgentInlineSkill,
AgentClassSkill). AgentSkillsProvider now always includes all three
tools (load_skill, read_skill_resource, run_skill_script) and both
instruction blocks, since the tools already handle missing
resources/scripts gracefully.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add blank line for readability in file-based skills sample
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix HostedAgentSkillsPatternTests for always-included tools
Update assertions to expect read_skill_resource and run_skill_script
tools are always present, matching the new behavior.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Add Hosted-AgentSkills sample for Foundry Skills integration
Add a new hosted agent sample that demonstrates how to load behavioral
guidelines from Foundry Skills at startup using AgentSkillsProvider and
the progressive disclosure pattern (advertise -> load on demand).
The sample:
- Downloads SKILL.md files from Foundry via ProjectAgentSkills SDK
- Extracts ZIP archives with zip-slip protection
- Wires skills into AgentSkillsProvider as an AIContextProvider
- Hosts the agent via the Responses protocol
Ships two Contoso Outdoors skills matching the Python sample (PR #5822):
- support-style: tone, formatting, signature guidelines
- escalation-policy: when and how to escalate tickets
Includes convenience provisioning gated behind PROVISION_SAMPLE_SKILLS
env var, clearly documented as NOT a production pattern.
Closes#5776
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Add unit tests and integration test for Hosted-AgentSkills
Unit tests (14 tests, all passing):
- ZIP extraction with zip-slip guard (valid archive, traversal attack,
sibling-prefix attack, directory entries)
- Skill name validation (rejects dots, separators, traversal patterns)
- AgentSkillsProvider with downloaded skills (advertises both skills,
load_skill returns canary tokens, unknown skill returns error)
Container integration test:
- New 'agent-skills' scenario in the test container that creates
Contoso Outdoors skills on disk and wires AgentSkillsProvider
- AgentSkillsHostedAgentFixture + 4 integration tests verifying:
- Routine questions load support-style skill (STYLE-CANARY-3318)
- Escalation triggers load escalation-policy (ESC-CANARY-7742)
- Skills are advertised in system prompt
- load_skill tool is invoked via FunctionCallContent
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Add smoke test, bootstrap, and docs for agent-skills integration
- Add scripts/smoke.ps1 for local Docker smoke testing: builds the
contributor image, runs the container, verifies both skills are loaded
via canary tokens (STYLE-CANARY-3318, ESC-CANARY-7742)
- Add 'agent-skills' to the bootstrap script scenario list
- Add agent-skills row to the integration test README scenarios table
- Exclude HostedAgentSkillsPatternTests from net472 (uses net8.0+ APIs)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Update commented-out package versions to latest across all hosted samples
Update the end-user PackageReference versions (in the commented-out
sections) from 1.0.0 to the current latest NuGet versions:
- Microsoft.Agents.AI: 1.6.1
- Microsoft.Agents.AI.Foundry: 1.6.1-preview.260514.1
- Microsoft.Agents.AI.Foundry.Hosting: 1.6.1-preview.260514.1
- Microsoft.Agents.AI.Hosting: 1.6.1-preview.260514.1
- Microsoft.Agents.AI.OpenAI: 1.6.1
- Microsoft.Agents.AI.Workflows: 1.6.1
Also adds explicit versions to Hosted-Workflow-Handoff which had bare
PackageReference entries without Version attributes.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Fix broken markdown links in Hosted-AgentSkills README
Remove references to non-existent ../../README.md. Replace with
inline instructions matching other hosted samples that don't have
a parent README.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Use OS-appropriate string comparison in zip-slip guard
Use Ordinal on Unix (case-sensitive FS) and OrdinalIgnoreCase on
Windows to prevent case-based path bypass on Linux containers.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Fix three interlocked bugs that prevent parallel tool calls from rendering
correctly in AG-UI protocol clients:
Bug #1: Scope synthetic MessageId fallback to text events only. The shared
streamingMessageId was leaking into ToolCallStartEvent.ParentMessageId,
causing all parallel tool calls to collapse into one FE card.
Bug #2: Make ToolCallResultEvent.MessageId deterministically unique using
result-{CallId} format. MEAI's FunctionInvokingChatClient batches all
results with a shared MessageId, collapsing them in FE reconciliation.
Bug #3: Coalesce consecutive assistant-tool-call messages in AsChatMessages.
Once Bug #1 is fixed, the FE produces separate AGUIAssistantMessage per
tool call. On multi-turn replay these become consecutive assistant messages
without intervening tool results, triggering HTTP 400 from Azure OpenAI.
Remove the now-dead ContainsToolResult helper introduced by PR #5800.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When A2AAgent receives a TaskStatusUpdateEvent during streaming,
ConvertToAgentResponseUpdate now sets AgentResponseUpdate.MessageId
from Status.Message.MessageId when the message is present.
This fixes the missing message correlation metadata reported in
microsoft/agent-framework#4987.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(core): point @experimental warnings at user code, not stdlib internals
Previously the wrappers installed by @experimental called warnings.warn
with a fixed stacklevel=3. ABCMeta inserts an extra abc.__new__ frame
when an experimental ABC is subclassed, so the warning landed inside
abc.py (or <frozen abc>:106 on modern CPython) instead of the user's
class Sub(...) line.
Resolve the user frame by walking inspect.currentframe(), skipping
frames whose module name is abc/functools/typing/contextlib (or
submodules), then emit via warnings.warn_explicit so the recorded
filename/lineno point at user code. Falls back to warnings.warn with
stacklevel=2 if no user frame is found. Module-name matching is used
because frozen stdlib modules report '<frozen abc>' as their filename.
Also install a one-line warnings.formatwarning specifically for
FeatureStageWarning so 'file:line: ExperimentalWarning: [ID] Name ...'
prints without the secondary source-snippet line. Other categories
delegate to the stdlib default formatter unchanged.
Added a regression test that subclasses an @experimental ABC inside
warnings.catch_warnings and asserts the recorded filename equals the
test file.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(core): address review feedback on @experimental warning fix
- Make _install_feature_stage_formatter idempotent: tag the installed
formatter with a marker attribute and short-circuit re-installation,
so re-imports/reloads don't wrap the formatter on top of itself.
Also expose the previous formatter via __wrapped__ for restoration.
- Avoid leaking frame references in _resolve_user_frame: capture data
into plain locals inside try and del frame/candidate in finally,
per CPython's guidance on inspect.currentframe usage.
- Drop redundant _WARNED_FEATURES.clear() in the new ABC subclass test
(the autouse fixture already handles it).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* changed query for foundry web search test
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-22 12:07:10 +00:00
816 changed files with 48373 additions and 35139 deletions
This project uses GitHub Issues to track bugs and feature requests. Please search the existing
issues before filing new issues to avoid duplicates. For new issues, file your bug or
feature request as a new Issue.
For help and questions about using this project, please create a GitHub issue.
AI Support team will support Microsoft Agent Framework issues for customers under a **Unified support agreement when the issue arises from usage of Azure AI services** (Foundry Models, Foundry Agents etc.) in conjunction with the SDK. Conversely, if customer has any other / non unified support agreement and/or Agent Framework SDK is used in a way **not involving an Azure service**, it is treated as a purely open-source tool – Microsoft’s support organization will not handle it, and users should use GitHub or forums for assistance
For Copilot Studio SDK implementation issues, customers should use GitHub Issues for assistance, as outlined above. Conversely, for prerequisites managed within the Copilot Studio portal, customers can rely on the standard Microsoft Copilot Studio support channels.
## Microsoft Support Policy
Support for this **PROJECT or PRODUCT** is limited to the resources listed above.
# Support
## How to file issues and get help
This project uses GitHub Issues to track bugs and feature requests. Please search the existing
issues before filing new issues to avoid duplicates. For new issues, file your bug or
feature request as a new Issue.
For help and questions about using this project, please create a GitHub issue.
AI Support team will support Microsoft Agent Framework issues for customers under a **Unified support agreement when the issue arises from usage of Azure AI services** (Foundry Models, Foundry Agents etc.) in conjunction with the SDK. Conversely, if customer has any other / non unified support agreement and/or Agent Framework SDK is used in a way **not involving an Azure service**, it is treated as a purely open-source tool – Microsoft’s support organization will not handle it, and users should use GitHub or forums for assistance
For Copilot Studio SDK implementation issues, customers should use GitHub Issues for assistance, as outlined above. Conversely, for prerequisites managed within the Copilot Studio portal, customers can rely on the standard Microsoft Copilot Studio support channels.
## Microsoft Support Policy
Support for this **PROJECT or PRODUCT** is limited to the resources listed above.
# Python minimal hosting core and pluggable channels
## Context and Problem Statement
Agent Framework has several protocol-specific hosting surfaces. App authors who want one agent or workflow on multiple protocols must compose servers, routes, middleware, session handling, and lifecycle code by hand.
We will introduce a small Python hosting core that owns the common server shape and leaves protocol details inside channel packages. The first public contract must be intentionally narrow so Python can ship a base contract before adding identity linking, proactive delivery, or multicast behavior. Other language implementations may reuse the same conceptual boundary, but this ADR records the Python decision.
## Decision Drivers
- Keep the first host easy to explain: one app, one hostable target, one or more channels.
- one hostable target (`SupportsAgentRun` agent-compatible object or a `Workflow`), and
- one or more channels.
Channels own:
- contributed routes, middleware, commands, and lifecycle callbacks,
- protocol-native request parsing into `ChannelRequest`,
- protocol-native rendering of the originating response, and
- any channel-specific authentication or signature validation.
The host owns:
- route/lifecycle aggregation,
- invocation of the target,
-`ChannelSession(isolation_key=...)` to `AgentSession` resolution and caching,
-`reset_session(isolation_key=...)`,
- host-level middleware, including Foundry isolation middleware only when the Foundry hosting environment flag is present,
- invocation of per-channel hooks (`ChannelRunHook`, `ChannelResponseHook`, `ChannelStreamUpdateHook`), and
- workflow checkpoint wiring through an explicit `checkpoint_location`.
`ChannelIdentity`, when present, is request metadata only. In v1 it is not a linking, authorization, or delivery key.
### Trust boundary for `isolation_key`
The host treats `ChannelSession.isolation_key` as a session partition key, not as proof of identity. Channels or host middleware must authenticate and authorize any externally supplied value before passing it to the host. For example, a Responses caller must not be allowed to choose an arbitrary `previous_response_id` or header-derived key unless the platform or middleware has already established that the caller owns that conversation. The host deliberately does not infer that trust from the string itself.
### Hook ownership
Channels provide hook configuration and protocol-native context. The host invokes those hooks as part of the common invocation pipeline:
-`ChannelRunHook` runs after channel parsing and before target invocation.
-`ChannelResponseHook` runs after target invocation and before the originating channel serializes its response.
-`ChannelStreamUpdateHook` is applied by the host while the channel consumes streamed updates because streaming serialization is protocol-specific.
`ChannelStreamUpdateHook` is an update hook, not a final-response sanitizer. Channels that use it for redaction or filtering must also apply equivalent policy to any final response they render. Channels choose whether the response is streaming before run hooks execute.
This keeps hook call conventions centralized while leaving protocol payload parsing and response formatting in channel packages.
### State owned by v1
`state_dir` is limited to host-owned local files for reset-session aliases and workflow checkpoint path derivation. It does not store linked identities, active-channel state, response-routing state, continuation records, durable runner queues, or delivery attempts. Those storage concerns belong to ADR-0028.
## Non-goals for v1
The following are deliberately **not** part of the v1 contract:
- cross-channel identity linking (`IdentityLinker`, `local_identity_link`, or `agent-framework-hosting-entra`),
- identity allowlists or authorization policy (`IdentityAllowlist`, `AuthPolicy`),
- response routing beyond the originating channel (`ResponseTarget`, active channel, specific linked channel, `all_linked`),
- push or payload codecs (`ChannelPush`, `ChannelPushCodec`),
These areas are follow-up enhancements covered by [ADR-0028](0028-hosting-linking-multicast-enhancements.md). They are not prerequisites for shipping or using the v1 host.
## Consequences
Positive:
- The host/channel model can be implemented and tested without designing a security-sensitive identity graph.
- Existing and new channel packages can share one Starlette app, middleware stack, lifecycle, and target invocation path.
- Session continuity is explicit and debuggable: two channels share history only when they produce the same `isolation_key`.
- Hook invocation is centralized in the host, so channels do not each invent the call convention.
Negative:
- Apps that need OAuth linking, allowlists, proactive messages, or multicast must continue to implement those behaviors outside the v1 host.
- Some richer cross-channel scenarios from the original design move to a separate decision and validation cycle.
- The host must document `isolation_key` trust clearly because it now provides the shared session boundary.
## Validation Gates
Before this ADR is accepted:
- A sample can expose one target on multiple channels with one `AgentFrameworkHost` and no handwritten Starlette route composition.
- Built-in channel tests prove that routes, commands, startup, and shutdown callbacks are contributed by channels and aggregated by the host.
- Session tests prove that identical `ChannelSession.isolation_key` values resolve to the same cached `AgentSession`, and `reset_session` rotates that mapping.
- Channel tests prove that each channel renders only its own originating response; there is no host-level push, multicast, or active-channel delivery path.
- Workflow tests or samples use an explicit `checkpoint_location`.
- Foundry isolation middleware is documented and covered by integration or contract tests, including the non-Foundry case where raw isolation headers are ignored.
- The v1 API and packages do not expose the removed symbols or packages listed in [Non-goals for v1](#non-goals-for-v1).
- The Python spec is updated to match this simplified contract and uses "public", "stable", or "released" terminology for Agent Framework APIs.
[ADR-0027](0027-hosting-channels.md) defines the minimal v1 hosting core: originating-channel responses, explicit `ChannelSession.isolation_key`, and no host-level identity linking, push, multicast, background delivery, or durable runners.
This ADR tracks the richer cross-channel behaviors that were removed from v1. These enhancements are **follow-up work** and are **not prerequisites** for shipping, using, or stabilizing the v1 host/channel core.
## Decision Drivers
- Cross-channel continuity must not create accidental cross-user, cross-tenant, or cross-channel data leaks.
- Non-originating delivery must be observable, idempotent, retryable, and supportable.
- Protocol payloads must remain channel-native while still being safe to persist and replay.
- App authors need opt-in policy controls, not hidden defaults.
- The enhancement stack should layer on top of the v1 host without reshaping the minimal channel contract.
## Enhancement Areas
The follow-up design should cover these capabilities together because they share identity, storage, delivery, and replay concerns:
- **Cross-channel identity linking** — a user can connect multiple `ChannelIdentity` values to one channel-neutral `isolation_key`.
- **Authorization and allowlist policy** — channels or hosts can require verified identity, allow specific native identities or claims, and deny unknown callers.
- **Non-originating response delivery** — a run can respond somewhere other than the request's originating protocol when explicitly configured.
- **Active-channel routing** — delivery can target the most recently observed linked channel for an `isolation_key`.
- **Multicast / all-linked delivery** — delivery can fan out to every linked channel or a selected set.
- **Background runs and continuation tokens** — long-running requests can return immediately and complete later, with a polling/status fallback.
- **Durable delivery runners** — delivery work can survive process restarts and support dead-letter handling.
- **Retry and replay semantics** — delivery attempts are bounded, deduplicated, and safe to replay.
- **Payload serialization** — channel-specific payloads can be persisted, redacted, versioned, and reconstructed without losing protocol fidelity.
Candidate API names from the broader design (`IdentityLinker`, `IdentityAllowlist`, `AuthPolicy`, `ResponseTarget`, `ChannelPush`, `ChannelPushCodec`, `DurableTaskRunner`, `InProcessTaskRunner`, `RetryPolicy`, `LinkPolicy`) remain design vocabulary for this ADR. They are not approved v1 APIs.
## Considered Options
### Option A — Leave all behavior to applications
Applications implement linking, authorization, push, retry, and serialization independently.
- Good: the hosting core stays very small.
- Neutral: advanced apps can still build what they need.
- Bad: every app must solve the same security and delivery problems, likely inconsistently.
### Option B — Add the full enhancement stack to v1
The first host release includes linking, authorization, active channel, multicast, background runs, durable runners, and codecs.
- Good: the original cross-channel experience is available immediately.
- Neutral: samples can demonstrate rich end-to-end flows.
- Bad: v1 becomes security-sensitive, storage-heavy, and harder to stabilize.
### Option C — Layer opt-in enhancement packages after v1
Ship the minimal host first, then add linking, authorization, and delivery packages behind explicit configuration.
- Good: v1 remains simple while leaving room for a reviewed, supportable enhancement stack.
- Neutral: apps that need advanced delivery wait for follow-up packages.
- Bad: the first release does not satisfy proactive or all-linked scenarios.
### Option D — Build only platform-specific integrations
Implement linking and proactive delivery separately in Telegram, Activity Protocol, Discord, and future channels.
- Good: each package can match its protocol exactly.
- Neutral: some shared abstractions may emerge later.
- Bad: cross-channel behavior becomes fragmented and hard to reason about.
## Decision Outcome
Proposed direction: **Option C — layered opt-in enhancement packages after v1**.
The minimal host remains the foundation. Follow-up packages may add linking, authorization, delivery, and durable execution, but must be explicitly enabled and must pass the validation gates below before becoming part of the public contract.
## Safety Requirements
### Threat model
The design must account for:
- spoofed channel-native identities,
- stolen or replayed link challenges,
- cross-tenant or cross-confidentiality data leakage,
- unsolicited proactive messages,
- malicious payloads persisted for replay,
- denial-of-service through fan-out or retry storms, and
- privacy leakage through logs, metrics, or support tooling.
Required mitigations include verified identity claims where available, signed and expiring link challenges, explicit user consent, per-channel capability checks, default-deny policy options, tenant partitioning, and uninformative denial messages on shared channels.
### Idempotency and replay
Exactly-once delivery is not a realistic guarantee. The design must provide:
- stable run, continuation, and delivery-attempt identifiers,
- channel-level idempotency keys where protocols support them,
- bounded retry with jitter and explicit terminal states,
- replay windows and expiration,
- duplicate suppression for persisted attempts, and
- clear semantics for "delivered", "accepted by platform", and "observed by user".
### Storage
Enhancement storage must stay distinct from v1 `AgentSession` history and workflow checkpoints unless an implementation deliberately backs them with the same physical store.
Stored data should be schema-versioned, minimized, encrypted or otherwise protected as appropriate, and partitioned by tenant/project. Link records, continuation records, active-channel state, delivery attempts, dead letters, and serialized payloads need independent TTL and deletion policies.
### Observability and support
The design must include structured logs, traces, and metrics for link attempts, authorization decisions, delivery scheduling, retries, replay, and dead-letter outcomes. Logs must avoid message content and sensitive identity claims by default. Operators need a way to inspect, revoke, replay, or purge stuck records safely.
## Validation Gates
Before these enhancements are accepted:
- A reviewed threat model covers identity linking, authorization, non-originating delivery, multicast, and replay.
- Cross-channel linking tests prove a verified identity can link two channels and that unlink/deny paths do not leak information.
- Background/continuation tests cover polling fallback, cancellation or expiration, process restart, retry, and dead-letter behavior.
- Codec tests prove payloads are versioned, redacted where needed, backward compatible, and rejected safely when unknown.
- Multicast tests prove fan-out is bounded, independently retried, and idempotent per destination.
- Observability tests or manual validation prove support operators can correlate a request to delivery attempts without exposing sensitive content.
## Relationship to ADR-0027
ADR-0027 remains valid without any of these enhancements. This ADR extends the hosting model only after the safety, storage, and support requirements above are satisfied.
This specification is the Python implementation plan for [ADR-0027](../decisions/0027-hosting-channels.md). It documents the simplified v1 host/channel contract only.
The v1 contract is:
-`AgentFrameworkHost` owns one Starlette app, one hostable target, and one or more channels.
- A hostable target is either a `SupportsAgentRun`-compatible agent or a `Workflow`.
- Channels contribute routes, middleware, commands, and lifecycle callbacks.
- Channels parse protocol-native input into `ChannelRequest`.
- Channels render their own originating response.
- Session continuity is explicit: a channel supplies `ChannelSession(isolation_key=...)`, and the host resolves/caches an `AgentSession` for that key.
- The host invokes `ChannelRunHook` and `ChannelResponseHook`; channels provide hook configuration and protocol context.
The host does not link identities, route responses to other channels, run background continuations, or multicast in v1. Those enhancements are tracked in [ADR-0028](../decisions/0028-hosting-linking-multicast-enhancements.md).
## Goals
- Let an app expose one agent or workflow on multiple protocols without handwritten Starlette composition.
- Keep protocol parsing and response formatting inside channel packages.
- Provide one session-resolution path shared by all channels.
- Keep the channel authoring surface small enough for new channels to implement.
- Preserve full-fidelity agent and workflow results until a channel decides how to render them.
## Non-goals for v1
The following are removed from the v1 implementation pass:
-`IdentityLinker`, `IdentityAllowlist`, `AuthPolicy`, and `LinkPolicy`
-`ResponseTarget`, active-channel routing, `all_linked`, fan-out, and multicast
-`ChannelPush` and `ChannelPushCodec`
-`DurableTaskRunner`, `InProcessTaskRunner`, and `RetryPolicy`
- continuation tokens and background delivery
- confidentiality tiers
-`agent-framework-hosting-entra`
-`local_identity_link`
These are follow-up design topics, not hidden requirements of the v1 host.
| `agent-framework-foundry-hosting` | `agent_framework.foundry_hosting` | Foundry isolation middleware and Foundry-backed hosting helpers usable with the v1 host. |
Channel packages may depend on their native SDKs. The core hosting package should not depend on channel SDKs or on top-level legacy protocol hosts.
## Key Types
### `AgentFrameworkHost`
The host constructor accepts:
-`target`: one `SupportsAgentRun`-compatible object or one `Workflow`
-`channels`: one or more `Channel` instances
- optional Starlette middleware
- optional `state_dir`
- optional workflow `checkpoint_location`
The host exposes:
-`app`: the canonical Starlette ASGI application
-`serve(...)`: a convenience wrapper for local serving
-`reset_session(isolation_key: str)`: rotate the cached `AgentSession` for a host-tracked conversation
`state_dir` is narrowed to v1 host-owned local files only:
- session aliases (`isolation_key` to current `AgentSession` id), and
- workflow checkpoint paths when the app chooses the host-provided file layout.
It is not a store for identity links, continuations, active-channel state, delivery attempts, or multicast payloads.
Externally supplied isolation keys are trusted only after the channel or host middleware has authenticated and authorized the caller. The host uses `isolation_key` as a partition key; the string itself is not proof of identity or ownership.
### `Channel`
A channel implements a small protocol:
- declare a stable channel id/name,
- contribute routes, middleware, commands, and lifecycle callbacks,
- parse inbound protocol data into `ChannelRequest`,
- call the host through `ChannelContext.run(...)` or `ChannelContext.run_stream(...)`, and
- serialize the returned result to the originating protocol response.
Channels own protocol authentication, signature validation, native command registration, and protocol-specific error bodies.
### `ChannelContribution`
`ChannelContribution` is the channel's host-facing contribution:
- Starlette routes and optional middleware,
- native command descriptors,
- startup and shutdown callbacks, and
- any channel-local metadata needed by the package.
The host aggregates contributions but does not interpret protocol payloads.
### `ChannelRequest`
`ChannelRequest` is the host-neutral request envelope produced by a channel. It carries:
- target input,
- optional `ChannelSession`,
- optional `ChannelIdentity`,
- options and attributes produced by the channel, and
- request metadata useful to hooks and context providers.
The host may pass attributes through to context providers and middleware. Channels should treat attributes as a documented extension bag, not as a cross-channel delivery contract.
### `ChannelSession`
`ChannelSession(isolation_key=...)` is the only v1 session-continuity mechanism.
When a request contains an isolation key:
1. The host looks up or creates the cached `AgentSession` for that key.
2. The target runs with that `AgentSession` when the target is an agent.
3.`reset_session(isolation_key)` rotates the alias so the next request starts a new conversation.
If two channels produce the same isolation key on the same host, they share the same cached session. If they produce different keys, they do not share session state.
### `ChannelIdentity`
`ChannelIdentity` is optional request metadata such as channel id, native user id, tenant id, claims, or display attributes.
In v1, `ChannelIdentity` does not link channels, authorize callers, select delivery destinations, or imply that two identities should share an `AgentSession`. A channel that wants shared history must still produce the same `ChannelSession.isolation_key`.
### Hooks
Hooks are optional and channel-owned:
-`ChannelRunHook`: runs after channel parsing and before host invocation; returns the `ChannelRequest` to execute.
-`ChannelResponseHook`: runs after target completion and before the originating channel renders a one-shot response.
-`ChannelStreamUpdateHook`: the host applies it to streamed updates before the originating channel serializes the stream.
Common uses include adapting chat text into workflow inputs, enforcing deployment-specific options, flattening rich output for text-only protocols, or filtering streamed updates for a protocol. Stream update hooks are update-only; they do not automatically sanitize `get_final_response()` output. Channels choose their response transport from the parsed protocol request before invoking run hooks.
### `HostedRunResult`
`HostedRunResult[T]` wraps the target's full-fidelity result plus the resolved `AgentSession | None`.
- Agent targets produce `HostedRunResult[AgentResponse]`.
- Workflow targets produce `HostedRunResult[WorkflowRunResult]`.
The host does not flatten, filter, or translate the result. Each channel decides how much of the result its protocol can carry.
## Host Behavior
1.`AgentFrameworkHost` builds one Starlette app and asks each channel for its contribution.
2. A channel route receives a protocol-native request.
3. The channel validates/parses the native payload and creates `ChannelRequest`.
4. The channel passes the request, optional `ChannelRunHook`, and protocol-native context to the host.
5. The host invokes `ChannelRunHook`, if configured, and receives the prepared request.
6. The host resolves an `AgentSession` from `ChannelSession.isolation_key` when present.
7. The host invokes the agent or workflow target.
8. The host wraps the result in `HostedRunResult` or the streaming equivalent.
9. The host invokes `ChannelResponseHook`, if configured, for non-streaming/final response shaping.
10. The host applies stream update hooks while the channel consumes streams; the channel renders the originating protocol response.
There is no host-level route from one channel's request to another channel's response in v1.
## Workflow Checkpoints
Workflow checkpointing is explicit. Apps either configure checkpoint storage on the workflow itself or pass a `checkpoint_location` to the host so the workflow dispatch path can use the intended file location.
`state_dir` may provide a conventional location for workflow checkpoint files, but checkpointing is still opt-in and separate from agent session history. Checkpoints are workflow-runtime state, not channel state and not identity-link state.
## Foundry Isolation Middleware
V1 keeps Foundry isolation as middleware rather than as a channel-linking feature.
The middleware is installed only when the Foundry hosting environment flag is present. In that environment it reads Foundry-provided isolation values at the trusted hosting boundary, exposes them as read-only request context for Foundry-aware history or memory providers, and rejects unsafe session resumes when the live isolation context does not match persisted session context. Outside Foundry, raw isolation headers are ignored unless an app supplies its own trusted middleware.
This middleware does not create cross-channel identity links and does not authorize non-Foundry channels.
## Current Channels
### Responses
`ResponsesChannel` exposes the OpenAI-compatible Responses API shape. It maps request body fields such as input, options, and conversation identifiers into `ChannelRequest`, and it renders Responses-compatible one-shot or streaming responses.
Responses session continuity uses a channel-selected `isolation_key`, commonly derived from a response/conversation id, caller-provided session id, Foundry isolation context, or deployment-specific request metadata.
### Invocations
`InvocationsChannel` exposes an invocation endpoint for server-side callers and tools. It maps the request body into `ChannelRequest` and renders the invocation result on the same HTTP response.
Invocations is useful for typed workflow inputs because a `ChannelRunHook` can translate the request body into the workflow's expected input type.
### Telegram
`TelegramChannel` supports webhook or polling transport, native command registration, and message rendering back to the originating Telegram chat.
The channel chooses a default `isolation_key` from Telegram-native data such as chat id, user id, or a configured user/chat scope. A `/new` or equivalent command may call `reset_session` for that isolation key.
### Activity Protocol
`ActivityChannel` supports Activity Protocol requests, typically through Azure Bot Service for Teams, Web Chat, and other Bot Framework-fronted surfaces.
The channel maps incoming `Activity` objects to `ChannelRequest` and renders a reply activity to the originating conversation. Proactive Activity delivery, active-channel routing, and all-linked fan-out are not v1 host semantics.
### Discord
`DiscordChannel` supports Discord messages, slash commands, and interactions as channel-native input.
The channel maps Discord-native user, guild, channel, thread, and interaction data into `ChannelRequest` metadata and a configured `ChannelSession.isolation_key`. It renders the result to the originating Discord response path.
The hook adapts channel-native input to the workflow's typed input. Checkpoints use the explicit workflow checkpoint location, not identity-link or delivery storage.
"The output should show an agent analyzing a dataset named 'sales-2025-q1' and producing a summary mentioning rows, revenue, anomalies, or outliers.",
"The output should contain both a non-streaming response (after RunAsync) and a streaming response (after RunStreamingAsync) for the same analysis question.",
"The output should not contain error messages or stack traces.",
@@ -439,15 +439,6 @@ internal static class WorkflowSamples
ExpectedOutputDescription=["The output should show a workflow calling function tools (e.g. a menu plugin) to answer a question about restaurant specials."],
@@ -9,6 +9,7 @@ Samples demonstrating Agent Skills capabilities. Each sample shows a different w
| [Agent_Step03_ClassBasedSkills](Agent_Step03_ClassBasedSkills/) | Define skills as C# classes using `AgentClassSkill`. |
| [Agent_Step04_MixedSkills](Agent_Step04_MixedSkills/) | **(Advanced)** Combine file-based, code-defined, and class-based skills using `AgentSkillsProviderBuilder`. |
| [Agent_Step05_SkillsWithDI](Agent_Step05_SkillsWithDI/) | Use Dependency Injection with both code-defined (`AgentInlineSkill`) and class-based (`AgentClassSkill`) skills. |
| [Agent_Step06_McpBasedSkills](Agent_Step06_McpBasedSkills/) | Discover skills served over the [Model Context Protocol (MCP)](https://modelcontextprotocol.io) via `AgentMcpSkillsSource`. Spins up an in-process MCP server that exposes skills as resources (`skill://...`) and connects an `McpClient` to it. |
`AgentSkillsProviderBuilder` to discover MCP-based skills from a Foundry Toolbox endpoint
and inject them as `AIContextProviders` so the agent can discover and use them at runtime.
## What this sample demonstrates
- Connecting to a Foundry toolbox's MCP endpoint via Streamable HTTP transport
- Injecting a fresh Azure AI bearer token (`https://ai.azure.com/.default`) on every MCP request
- Using `AgentSkillsProviderBuilder.UseMcpSkills(client)` to discover skills from the toolbox
- Injecting the discovered skills into `AIProjectClient.AsAIAgent(...)` via `AIContextProviders`
## Prerequisites
- A Microsoft Foundry project with a toolbox already configured
- The toolbox MCP endpoint must expose `skill://index.json` with `skill-md` entries (SEP-2640). If the resource is absent, the sample runs but the skills provider will be empty.
- Azure CLI installed and authenticated (`az login`)
| [Foundry toolbox via MCP](./Agent_Step25_FoundryToolboxMcp/) | Use a Foundry Toolbox from a non-hosted agent via its MCP endpoint |
| [Foundry toolbox MCP skills](./Agent_Step26_FoundryToolboxMcpSkills/) | Use a Foundry Toolbox with MCP-based skills discovery (SEP-2640) via AIContextProviders |
# Agent with MCP long-running task (transparent polling)
This sample demonstrates Microsoft Agent Framework's MCP long-running task support: an agent invokes an MCP tool whose execution takes too long for a single request/response cycle, and the framework polls it to completion behind the function-calling loop. From the agent's perspective the tool simply returns its result.
## What this sample shows
- Using `McpClient.ListAgentToolsWithTaskSupportAsync(...)` (in `Microsoft.Agents.AI.Mcp`) to wrap MCP tools with task-aware behavior.
- Configuring `McpTaskOptions.DefaultTimeToLive` to bound the server-side task.
- Hosting a small MCP server (in this same executable, launched with `--server`) that advertises `execution.taskSupport=required` on a tool that sleeps for ~15 seconds.
- No application-level polling, continuation tokens, or `AllowBackgroundResponses` flag are required.
The decorator drives the lifecycle internally:
1.`tools/call` augmented with task metadata (`CallToolAsTaskAsync`)
2.`tasks/get` polled until terminal (`PollTaskUntilCompleteAsync`)
3.`tasks/result` retrieved (`GetTaskResultAsync`) and returned to the function-calling loop
The sample exercises both invocation styles against the same wrapper:
-`agent.RunAsync(...)` blocks until the tool completes (~15 seconds in this sample) and returns the final response.
-`agent.RunStreamingAsync(...)` returns immediately and yields `AgentResponseUpdate` chunks as the model emits them; in this scenario the model only begins streaming its answer once the wrapped tool's task reaches the `Completed` state, so the perceived "pause" before tokens arrive reflects tool execution time, not stream-channel latency.
# Prerequisites
- .NET 10 SDK or later
- Azure OpenAI service endpoint and a chat-completions deployment
- Azure CLI installed and authenticated (`az login`)
@@ -22,6 +22,7 @@ Before you begin, ensure you have the following prerequisites:
|[Agent with MCP server tools](./Agent_MCP_Server/)|This sample demonstrates how to use MCP server tools with a simple agent|
|[Agent with MCP server tools and authorization](./Agent_MCP_Server_Auth/)|This sample demonstrates how to use MCP Server tools from a protected MCP server with a simple agent|
|[Responses Agent with Hosted MCP tool](./ResponseAgent_Hosted_MCP/)|This sample demonstrates how to use the Hosted MCP tool with the Responses Service, where the service invokes any MCP tools directly|
|[Agent with long-running MCP task (transparent polling)](./Agent_MCP_LongRunningTask_Client/)|This sample demonstrates how an agent transparently drives a long-running MCP task (SEP-2663) to completion. The wrapper polls the task internally on both `RunAsync` and `RunStreamingAsync` invocations.|
- **CoderAgent** uses `HostedCodeInterpreterTool` for quantitative analysis.
- **MagenticManager** plans the work, tracks progress, and decides who should act next.
## What This Sample Demonstrates
- Building a Magentic workflow with `MagenticWorkflowBuilder`
- Combining standard responses-based agents with a code interpreter-enabled participant
- Streaming orchestration events such as the initial plan, replans, and progress-ledger updates
- Printing the final multi-agent conversation transcript
## Prerequisites
-`AZURE_AI_PROJECT_ENDPOINT` set to your Azure AI Foundry project endpoint
-`AZURE_AI_MODEL_DEPLOYMENT_NAME` set to your model deployment name (defaults to `gpt-5.4-mini`)
-`az login` completed before running the sample
## Running the Sample
```bash
dotnet run
```
## Expected Output
The sample prints:
1. The original task prompt
2. Streamed updates from the participating agents
3. Magentic plan and progress-ledger events as the workflow coordinates the team
4. The final conversation transcript returned by the workflow
## Related Samples
- [Handoff Orchestration](../Handoff) - another multi-agent orchestration pattern in .NET workflows
- [Python Magentic workflow sample](../../../../../python/samples/03-workflows/orchestrations/magentic.py) - the source scenario that this sample ports
@@ -62,3 +62,4 @@ Once completed, please proceed to the other samples listed below.
| Sample | Concepts |
|--------|----------|
| [Handoff Orchestration](./Orchestration/Handoff) | Introduces the Handoff Orchestration pattern |
| [Magentic Orchestration](./Orchestration/Magentic) | Coordinates multiple agents with a Magentic manager, streamed plan events, and a final transcript |
# Dockerfile for end-users consuming the Agent Framework via NuGet packages.
#
# This Dockerfile performs a full `dotnet restore` and `dotnet publish` inside the container,
# which only succeeds when the project references its dependencies via PackageReference (see the
# commented-out section in HostedAgentSkills.csproj). Contributors building from the
# agent-framework repository source must use Dockerfile.contributor instead because
# ProjectReference dependencies live outside this folder and cannot be restored from inside
# this build context.
#
# Use the official .NET 10.0 ASP.NET runtime as a parent image
FROMmcr.microsoft.com/dotnet/aspnet:10.0ASbase
WORKDIR/app
FROMmcr.microsoft.com/dotnet/sdk:10.0ASbuild
WORKDIR/src
COPY . .
RUN dotnet restore
RUN dotnet publish -c Release -o /app/publish
# Final stage
FROMbaseASfinal
WORKDIR/app
COPY --from=build /app/publish .
EXPOSE8088
ENVASPNETCORE_URLS=http://+:8088
ENTRYPOINT["dotnet","HostedAgentSkills.dll"]
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.