* .NET: Replace internal AG-UI implementation with external ag-ui packages
Remove the in-tree Microsoft.Agents.AI.AGUI sources and consume the external
AG-UI .NET SDK packages (AGUI.Abstractions, AGUI.Formatting, AGUI.Protobuf,
AGUI.Client, AGUI.Server) at 0.1.0-preview instead.
- Microsoft.Agents.AI.Hosting.AGUI.AspNetCore keeps its own ASP.NET glue
(MapAGUI / AddAGUI / SSE result) layered over the framework-agnostic
AGUI.Server primitives (ToChatRequestContext / AsAGUIEventStreamAsync).
- Migrate call sites to the options-based AGUIChatClient constructor and recover
the originating AG-UI input via ChatOptions.TryGetRunAgentInput.
- Multi-turn continuation flows through parentRunId + threadId on
RawRepresentationFactory; shared state flows through RunAgentInput.State and is
surfaced as StateSnapshotEvent raw representations.
- Update samples, hosting/unit/integration tests, and central package versions.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add migration README for removed Microsoft.Agents.AI.AGUI package
Keep the package folder in place with a README explaining that the in-tree AG-UI protocol abstractions moved to the external AGUI.* NuGet packages, with a mapping of old namespaces to the new packages and a migration guide.
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com>
* feat(durabletask): add workflow naming helpers (multi-workflow phase 0)
Foundation for hosting multiple workflows (and later sub-workflows) on one
durable task host. Adds a host-agnostic naming module that derives the stable
durable names a hosted workflow registers under.
- New `_workflows/naming.py`:
- `workflow_orchestrator_name(name)` -> `dafx-{name}` (orchestration name,
aligned byte-for-byte with .NET `WorkflowNamingHelper`).
- `workflow_name_from_orchestrator(name)` -> reverse, `None` when not prefixed.
- `validate_workflow_name(name)` -> rejects empty / malformed / auto-generated
`WorkflowBuilder-<uuid>` names (validate-and-reject rather than silently
sanitize, since the name becomes a durable identity and an HTTP route segment).
- `is_auto_generated_workflow_name(name)`, `DURABLE_NAME_PREFIX`.
- Export the helpers from the package public API.
- Mark `WORKFLOW_ORCHESTRATOR_NAME` deprecated in favor of per-workflow names
(kept functional; the single-workflow path still uses it until phase 1).
- 39 unit tests covering round-trips and validation.
Design: docs/design/durabletask-multiworkflow-and-subworkflows.md
* feat(durabletask): host multiple workflows per worker with scoped names (phase 1)
Enables hosting more than one MAF workflow on a single standalone Durable Task
worker, and aligns both hosts on workflow-scoped durable names so two co-hosted
workflows that reuse an executor id cannot collide.
Naming (shared, host-agnostic):
- orchestration: dafx-{workflowName} (matches .NET; the name DT tooling surfaces)
- non-agent activity / agent entity: dafx-{workflowName}-{executorId} (scoped)
- New naming helpers workflow_scoped_executor_id / workflow_executor_activity_name.
Standalone worker (agent-framework-durabletask):
- configure_workflow is now additive: stores workflows keyed by Workflow.name,
rejects duplicate / auto-generated (WorkflowBuilder-<uuid>) / invalid names,
registers one orchestrator per workflow plus its scoped activities/entities.
- The shared orchestrator dispatches scoped names derived from workflow.name.
- New registered_workflow_names property.
Client (DurableWorkflowClient):
- Optional default workflow_name on the client; start/run/stream accept a per-call
workflow_name and target dafx-{name}.
- Opt-in ownership validation on status/HITL methods: when a workflow name is
resolvable, an instance whose orchestration name does not match is treated as
not-found (status -> None, pending -> [], send_hitl_response / await -> raise),
mirroring the Azure Functions route-scoping check.
Azure Functions host (agent-framework-azurefunctions):
- Registration now uses the same scoped names so the shared orchestrator's
dispatch matches (single workflow per app for now; flat workflow/* routes kept).
- Workflow name is validated up front; workflow agents register under the scoped
entity id; _is_workflow_orchestration scopes to dafx-{workflow.name}.
Samples + tests:
- Durable Task and Azure Functions workflow samples now name their workflow.
- Unit tests cover multi-workflow registration, name validation, client targeting,
and ownership; integration tests target the named workflows.
WORKFLOW_ORCHESTRATOR_NAME remains exported (deprecated). This is a hard switch:
in-flight single-workflow instances created before upgrade (under the old
workflow_orchestrator name) will not resume.
Design: docs/design/durabletask-multiworkflow-and-subworkflows.md
* feat(azurefunctions): host multiple workflows per app with per-workflow routes (phase 2)
Completes multi-workflow hosting on the Azure Functions host, building on the
shared scoped-naming foundation from the worker phase.
AgentFunctionApp:
- New `workflows=` parameter accepting a list (keyed by each `Workflow.name`) or a
name->Workflow mapping; the existing `workflow=` is a single-workflow alias.
Both may be combined. Duplicate names and mapping-key/name mismatches are rejected.
- Each workflow registers its own `dafx-{name}` orchestration, workflow-scoped
activities/entities, and per-workflow HTTP routes:
`workflow/{name}/run`, `workflow/{name}/status/{instanceId}`,
`workflow/{name}/respond/{instanceId}/{requestId}`. Routes are always
per-workflow (even for a single workflow) so callers don't change URLs as an app
grows from one workflow to many.
- Route ownership check is per-workflow (`_is_owned_orchestration(status, name)`):
a leaked instance id for another orchestration -- or another workflow -- is
treated as not-found, extending the route-scoping defense.
- `get_agent(context, name, workflow_name=...)` resolves a workflow agent under its
scoped id; bare `agents=` registration keeps the standalone surface. New
`workflows` introspection property; `.workflow` now returns the sole workflow
(or None when several are hosted).
- Removed the now-unused flat-URL helper `_build_status_url` (handlers inline
per-workflow URLs).
Samples + tests:
- Azure Functions workflow samples (09-12) name their workflow; integration tests
target the per-workflow routes.
- Unit tests cover multi-workflow registration, duplicate/mapping/auto-name
rejection, and per-workflow ownership.
Note: sample README / demo.http route docs are updated in the docs phase.
Design: docs/design/durabletask-multiworkflow-and-subworkflows.md
* feat(durabletask): sub-workflows via durable child orchestrations (phase 3)
Run WorkflowExecutor nodes as durable child orchestrations on both hosts.
- Protocol: add call_sub_orchestrator to WorkflowOrchestrationContext, implemented by the durabletask and Azure Functions adapters.
- Registration: planner classifies WorkflowExecutor as subworkflow_executors; collect_hosted_workflows walks nested workflows (parent first, deduped by name). Both hosts recursively register every nested workflow's orchestration/agents/activities once; only top-level workflows get HTTP routes. Names validated up front before any registration side effects.
- Orchestrator: dispatch WorkflowExecutor nodes via call_sub_orchestrator(dafx-{innerName}) with deterministic child instance ids ({instanceId}::{executorId}::{counter}), a trusted-input marker carrying nesting depth (bounded at 25), and outputs routed as messages (default) or parent outputs (allow_direct_output).
- Tests: registration/collect, orchestrator prepare/process/unwrap, recursive registration on both hosts. Sample: 11_subworkflow.
* feat(durabletask): sub-workflow HITL via qualified request ids (phase 4)
Surface a nested sub-workflow's human-in-the-loop request behind the top-level instance (B2 single addressing surface).
- Orchestrator records dispatched sub-workflow child instance ids in its custom status (subworkflows map) before suspending in task_all, so the read side can reach a child's pending request while the parent is paused.
- Read side (durabletask client get_pending_hitl_requests; AF status route) recurses into nested child statuses, qualifying each nested request id as {executorId}::{requestId} (accumulated for deeper nesting).
- Write side (durabletask client send_hitl_response; AF respond route) splits a qualified id on '::', resolves the owning child orchestration via the parent's subworkflows map, and raises the event on the leaf child with the bare request id. Unknown/inactive sub-workflow -> error/404.
- Shared SUBWORKFLOW_REQUEST_SEPARATOR ('::') in naming so both hosts and the client agree. respondUrl/respond always targets the top-level instance.
- Tests: TestSubworkflowHitl (durabletask client, 7), TestAgentFunctionAppSubworkflowHitl (AF, 7). Sample: 12_subworkflow_hitl (HITL pause inside an embedded sub-workflow).
* docs(durabletask): ADR + sample route docs for multi-workflow and sub-workflows (phase 5)
- Add ADR-0030 capturing the multi-workflow and sub-workflow hosting decisions (naming, scoped inner names, per-workflow routes, child-orchestration sub-workflows, hard-switch migration, B2 sub-workflow HITL, scoped agent addressing) with considered alternatives; mark the design doc as implemented and link the ADR.
- Update Azure Functions workflow samples (09-12) README/demo.http to the per-workflow route shape (workflow/{name}/run|status|respond) introduced in phase 2.
- Extend the durabletask sample catalog with the workflow hosting patterns (08-12), including the new 11_subworkflow and 12_subworkflow_hitl samples.
* fix(durabletask): harden sub-workflow hosting + add sub-workflow integration tests
Post-review hardening of the multi-workflow / sub-workflow durable hosting:
- Trust boundary: strip the reserved sub-workflow envelope key from untrusted
client input at both host boundaries (DurableWorkflowClient.start_workflow and
the AF start route) so a forged envelope cannot reach the trusted pickle path.
- Nested HITL addressing: qualify nested pending requests by (executorId, ordinal)
using a '~' separator (was '::', which collided with core's auto::N functional
request ids); the parent status subworkflows map is now a per-executor list so
multiple children dispatched in one superstep stay independently addressable.
- Reject two different workflow instances that share a name (the same instance
reused by sibling nodes is still deduped); validate executor ids (separator-free,
length-bounded) when hosting durably.
- Remove the arbitrary sub-workflow nesting depth cap: a WorkflowExecutor wraps a
concrete Workflow so the nesting tree is finite at build time, and the durable
instance-id length limit is the natural ceiling (matches .NET, which has none).
Tests/samples:
- New durabletask integration tests for sub-workflow composition (11) and nested
sub-workflow HITL (12); new no-agent AF sub-workflow HITL sample (13) + test.
- Exempt no-agent samples from the model-credential gate in both integration
conftests so the nested-HITL plumbing is covered deterministically.
- Update durabletask sample 12 docs to the new qualified-id format.
Validated: 484 unit tests; durabletask integration 08/09/11/12 and AF 12/13 pass
against the live emulators; pyright 0 errors; ruff clean.
* fix(durabletask): address PR review feedback on naming, typing, and docs
- Unquote df.DurableOrchestrationClient annotations so pyupgrade passes.
- Narrow the split_subworkflow_request_id result before unpacking in a naming test so the strict type checkers pass.
- Correct the durabletask sample catalog to the {executor}~{ordinal}~{requestId} qualified id format.
- Reword the Azure Functions sub-workflow sample intro so it does not imply a difference from a same-numbered sample.
- Drop internal shorthand (B2, phase labels) from code comments.
* fix(durabletask): reject case-insensitive workflow name collisions
The route ownership guard compares the durable orchestration name with casefold(), but registration kept raw names as distinct keys. Hosting 'Orders' and 'orders' therefore succeeded while either workflow's status/respond route could operate on the other's instances. Reject case-insensitive name collisions at registration (within a composition via collect_hosted_workflows, and across registration calls via the case-folded _registered_orchestrations map and the top-level guard in both hosts) so the case-folded ownership boundary stays real. Single names of any case remain valid; only collisions are rejected.
* docs(durabletask): remove multiworkflow/subworkflow ADR and design docs
Drop the ADR and design exploration documents and the dangling docstring reference to them.
* refactor(durabletask): simplify workflow client status parsing and drop deprecated orchestrator-name symbols
Extract a shared _parse_custom_status helper in DurableWorkflowClient to remove duplicated custom-status JSON parsing across three call sites.
Drop the now-unused single-workflow compatibility shims WORKFLOW_ORCHESTRATOR_NAME and WorkflowRegistrationPlan.orchestrator_name, replaced by per-workflow workflow_orchestrator_name(name).
* fix(core): drop WORKFLOW_ORCHESTRATOR_NAME from agent_framework.azure re-exports
The constant was removed from agent-framework-durabletask, but the core azure lazy-loading namespace still re-exported it, breaking pyright in packages/core. Remove it from both the runtime _IMPORTS map and the .pyi stub.
* fix(durabletask): atomic multi-workflow registration and bubble sub-workflow events
Make configure_workflow / AgentFunctionApp registration atomic: check every cross-call name collision before mutating any state, so a colliding nested sub-workflow no longer leaves a host partially configured (with the top-level name stuck in the registry). Applied to both the standalone worker and the Functions app.
Bubble sub-workflow intermediate events: a workflow run as a child orchestration now returns a SUBWORKFLOW_RESULT_KEY envelope carrying its outputs plus event timeline, and the parent re-tags the child's intermediate events with the WorkflowExecutor node id and republishes them, matching the in-process WorkflowExecutor contract. Top-level runs still return a bare outputs list.
Adds cross-registration atomicity tests on both hosts and unit tests for the result envelope and event bubbling. Resolves review threads on _worker.py, orchestrator.py, and test coverage.
* fix(azurefunctions): widen workflow orchestrator wrapper return type
The shared run_workflow_orchestrator now returns list | dict (the sub-workflow result envelope), so the azurefunctions _workflow.py wrapper that delegates to it must widen its Generator return annotation to match. Caught by the package-level pyright in CI (Package Checks), which type-checks the whole package, not just the files changed in the previous commit.
* fix: bump GitHub.Copilot.SDK to 1.0.5 to resolve strong-naming mismatch
SDK 1.0.5 introduced strong-naming (PublicKeyToken=cc7b13ffcd2ddd51).
The adapter was compiled against the unsigned SDK (PublicKeyToken=null),
causing CS0012 for any consumer referencing both packages.
Fixes#6948
* fix: update tests and extension for SDK 1.0.5 namespace changes
- Add 'using GitHub.Copilot;' to CopilotClientExtensions.cs
- Change extension namespace to Microsoft.Agents.AI.GitHub.Copilot
- Update test files for new SDK types and removed APIs
- Add #pragma to suppress GHCP001 experimental warnings in tests
- All 45 tests pass across net8.0, net9.0, net10.0
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
* style: run dotnet format to fix linting issues
Remove unnecessary using directives (IDE0005) and fix file encoding (CHARSET).
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
* fix: revert CopilotClientExtensions namespace to GitHub.Copilot
Per reviewer feedback, extension methods should live in the namespace
of the type they extend (CopilotClient). This follows .NET team guidance.
The original namespace was GitHub.Copilot.SDK which was renamed to
GitHub.Copilot in SDK 1.0.5.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
* refactor: narrow tools parameter from AITool to AIFunctionDeclaration
Since SessionConfig.Tools only accepts AIFunctionDeclaration, change the
constructor and extension method parameters to accept IList<AIFunctionDeclaration>
instead of the more general IList<AITool>. This makes the API honest about what
it actually uses and avoids silently discarding non-AIFunctionDeclaration tools.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
* fix: sync Directory.Packages.props with upstream main
Take upstream's package versions (including MessagePack 3.1.7 pin
that fixes NU1902/NU1903 vulnerability warnings) while keeping
GitHub.Copilot.SDK at 1.0.5 which is the purpose of this PR.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Tamir Dresher <tamirdresher@users.noreply.github.com>
Co-authored-by: Copilot <Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
* Python: Add SkillsSourceContext to SkillsSource.get_skills
Thread an invocation context (agent + optional session) through the skill
source pipeline so sources and decorators can make context-aware decisions.
- Add frozen, experimental SkillsSourceContext(agent, session).
- Change SkillsSource.get_skills and all sources/decorators to accept and
forward the context.
- Make FilteringSkillsSource predicate context-aware: (skill, context) -> bool.
- Add optional cache_isolation_key_selector to CachingSkillsSource for
per-key cache isolation (None keeps the shared-bucket behavior).
- Build the context in SkillsProvider from before_run agent/session.
- Update foundry_hosting toolbox source, exports, tests, and docs.
Python port of .NET PR #6797.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: Clarify skills source docstring examples
Address PR review: docstring examples referenced `context` without
constructing it. Add a `SkillsSourceContext` construction line (with a
placeholder agent) to each source example and a note that the provider
normally supplies it. Use `source_context` in the FilteringSkillsSource
example to avoid clashing with the predicate's `context` parameter.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: Fix CI type errors and skill_filtering sample predicate
Address CI failures from the SkillsSourceContext change:
- Update the skill_filtering sample to the 2-arg predicate signature
(skill, context); the old 1-arg lambda would fail at runtime.
- Replace ad-hoc _StubAgent test stubs with the shared MockAgent /
MockAgentSession from conftest so all type checkers (incl. ty) accept
the SupportsAgentRun-typed agent. Add a small _NamedMockAgent subclass
for tests needing distinct agent names, and drop now-unnecessary
attr-defined ignores.
- Use cast(SupportsAgentRun, ...) in foundry_hosting tests, which have no
shared mock infrastructure.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: Make SkillsProvider caching safe-by-default; clarify context docstrings
Address PR review comments:
- Do not auto-wrap a caller-supplied SkillsSource in the provider's default
CachingSkillsSource. A shared, unkeyed cache around a context-aware source
replays the first invocation's skills for later SkillsSourceContexts,
leaking skills across agents/tenants. Default caching now applies only to
the built-in, context-independent file/in-memory leaf sources
(Deduplicating(Caching(leaf))), matching the .NET provider. Callers who
want caching on a custom pipeline compose CachingSkillsSource (optionally
with a cache_isolation_key_selector) themselves. disable_caching now only
affects the built-in leaves. Adds a leak-prevention test.
- Reword the misleading "Unused by this source" context docstrings on the
File/InMemory/MCP sources: the param is part of the get_skills contract;
these sources just return the same skills regardless of context.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: Allow disabling approval for SkillsProvider tools
Add disable_load_skill_approval, disable_read_skill_resource_approval, and disable_run_skill_script_approval keyword arguments to SkillsProvider.__init__ and SkillsProvider.from_paths. When set, the corresponding tool is registered with approval_mode=never_require so it runs without approval for trusted-skill scenarios. Approval remains required by default.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Preserve from_paths compatibility for SkillsProvider subclasses
Forward the disable_*_approval kwargs from SkillsProvider.from_paths only when explicitly enabled, so subclasses that override __init__ with the previous signature keep working when the flags are left at their defaults. Add a regression test covering a legacy-signature subclass.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Process messages to an executor serially within a superstep
Add a per-executor asyncio.Lock in Executor.execute so each executor processes its messages one at a time within a superstep, while preserving concurrency across distinct executors. Includes a regression test.
* Create per-executor lock lazily under the running loop
asyncio.Lock created in Executor.__init__ would bind to the first event loop it was awaited under, so reusing an executor/workflow across loops (e.g. successive asyncio.run calls) raised 'bound to a different event loop'. Create the lock lazily via _get_execution_lock(), re-creating it when the running loop changes. Adds a loop-scoped lock test.
* Re-create runner context event queue lazily under the running loop
Like the per-executor lock, the runner context's asyncio.Queue bound to the first event loop it was awaited under, so reusing a workflow across loops (e.g. successive asyncio.run calls) raised 'bound to a different event loop'. Re-create the queue lazily via _get_event_queue() when the running loop changes. Adds an integration test reusing a workflow across event loops.
* Use lazy-None init for the event queue, matching the executor lock
Initialize _event_queue to None and create it on first use in _get_event_queue, mirroring the per-executor lock. Avoids constructing a queue in __init__/reset_for_new_run that is immediately discarded once the running loop is known.
* Improve comments
* Fix formatting
* .NET: Fix flaky OpenTelemetryAgentTests via thread-safe activity collector
The Ctor_NullOrWhitespaceSourceName test subscribed a process-global TracerProvider to the shared default source Experimental.Microsoft.Agents.AI and exported into a plain List<Activity>. That source is also used by CompactionTelemetry, and xUnit runs the Compaction test classes in parallel, so a compaction span could be appended from another thread mid-assertion, throwing 'Collection was modified'.
Add a thread-safe ConcurrentActivityList collector (locked Add plus snapshot enumeration) for all InMemoryExporter collectors in the file, and scope the shared-source test to its own invoke_agent TraceId after ForceFlush so parallel compaction spans cannot affect the count or source-name checks.
* .NET: Assert ForceFlush result in OpenTelemetryAgentTests default-source test
Assert the boolean returned by TracerProvider.ForceFlush(timeout) so a flush timeout surfaces as a clear test failure instead of silently snapshotting incomplete activities.
* Validate Foundry toolbox name is a single path segment before building the proxy URL
Reject toolbox name/identifier inputs that carry path separators or relative-path segments (including their percent-encoded forms) before they are interpolated into the toolbox MCP proxy request URL, so a caller-influenced marker cannot alter the request target. Validation runs both at per-request marker resolution and at the shared open choke point, and is covered by red-to-green unit tests.
* Reject residual percent-encoding in toolbox name validation
After the bounded percent-decode loop, also reject a name that still contains a percent sign, so encoding nested deeper than the decode cap cannot survive validation. Dispose the service via await using in the rejection test. Adds a deeply-encoded coverage case.
* Validate toolbox name by request-target effect instead of a character list
Replace the character/decoding checks with an effect-based check: build the proxy URL and confirm the name resolves to a single, intact path segment between 'toolboxes' and 'mcp' with the scheme, authority, path shape, and fragment unchanged, and that the segment round-trips back to the name. This forgives characters that stay inside the segment (for example ':' , '@' , parentheses) while still rejecting names that would move the request target, including '?' and '#' and their percent-encoded forms. Adds coverage for the delimiter cases and for the newly-allowed names.
When a create-response request references a conversation id that does not
exist, validate its existence up front and return a clean not-found error
mapped to HTTP 404, consistent with the Conversations API, instead of failing
mid-execution and surfacing a generic server error.
Centralize the responses validation error codes and their HTTP status mapping
in a single ResponseErrorCodes type so handlers translate a code to a 404 or
400 without ad-hoc string comparisons. Add unit and HTTP integration tests.
* fix: require explicit TokenCredential in AddFoundryToolboxes
The AddFoundryToolboxes extension methods now require callers to
pass a TokenCredential explicitly rather than relying on an
internally-created default credential. This makes the credential
choice intentional and avoids non-deterministic credential probing
in production environments.
Breaking change (experimental API):
- AddFoundryToolboxes(IServiceCollection, params string[]) becomes
AddFoundryToolboxes(IServiceCollection, TokenCredential, params string[])
- AddFoundryToolboxes(IServiceCollection, Action?, params string[]) becomes
AddFoundryToolboxes(IServiceCollection, TokenCredential, Action?, params string[])
- Azure.Identity package dependency removed from Foundry.Hosting library.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: simplify redundant generic type argument (IDE0001)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: avoid duplicate FoundryToolboxService registration
Inject the AddFoundryToolboxes credential directly into the
FoundryToolboxService factory and fail early if the service was
already registered. This avoids registering TokenCredential in the
host DI container while preserving a single toolbox service instance
for both request handling and hosted startup.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: resolve CA1873 in GitHubCopilotAgent by using LoggerMessage source generator
Replace the direct logger.LogWarning() call (which eagerly evaluates
string.Join()) with a [LoggerMessage]-generated extension method in
GitHubCopilotAgentLogMessages.cs.
Fixes build error:
GitHubCopilotAgent.cs(580,13): error CA1873: Evaluation of this argument
may be expensive and unnecessary if logging is disabled
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fixing more dotnet samples
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Ben Thomas <25218250+alliscode@users.noreply.github.com>
* .NET: Bump Azure.AI.Projects to 2.1.0-alpha.20260629.1
Bumps Azure.AI.Projects beta.3 to alpha.20260629.1 and aligns transitive deps (System.ClientModel 1.14.0, Azure.Core 1.59.0, Msal 4.84.2). Adapts to renamed AgentSessionFiles APIs (Upload/GetAll/Delete, scoped GetAgentSessionFiles, SizeInBytes), AgentToolboxes (CreateVersion/Delete), and strongly typed toolbox tools (WebSearchToolboxTool, MCPToolboxTool). Adds azure-sdk public dev feed for prerelease restore.
* Use positional arg for AgentSessionFiles.DeleteAsync cleanup
* Move to Azure.AI.Projects 2.1.0-beta.4 (released beta)
Swaps the alpha daily build for the published 2.1.0-beta.4. Drops the azure-sdk public dev feed since beta.4 and its deps are on nuget.org. Beta.4 requires Azure.Core 1.60.0, which cascades the 10.0.8 servicing packages (Microsoft.Bcl.AsyncInterfaces, System.Diagnostics.DiagnosticSource, System.Text.Json, System.Threading.Channels, Microsoft.Extensions.DependencyInjection.Abstractions, Microsoft.Extensions.Logging.Abstractions) to 10.0.9.
* Reconcile Azure.Core 1.60.0 bump with merged main
Reverts the over-eager System.Threading.Channels 10.0.9 bump back to 10.0.8 (it was not part of the Azure.Core 1.60.0 cascade and caused a net472 MSB3277 conflict against the 10.0.8 that Microsoft.Extensions.AI pulls). Drops the now-obsolete Azure.Core VersionOverride=1.59.0 in HostedWorkflowHandoff (added on main to satisfy AgentServer while the central pin was lower); the central pin is now 1.60.0 which already satisfies the >=1.59.0 floor, and the override was downgrading this project below sibling projects (CS1705).
* Fix Hyperlight workspace link staging
Reject symlinks, Windows junctions, and reparse points during Hyperlight input staging, and harden output collection/cleanup against the same link types.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address Hyperlight staging review
Anchor workspace enumeration to the resolved root and avoid following links while classifying output cleanup entries.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Improve Hyperlight path resolve errors
Handle RuntimeError from path resolution alongside OSError when validating Hyperlight sandbox paths and report the source-root validation context in the error message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Mark Hyperlight real sandbox tests as integration
Ensure Windows unit CI excludes real Hyperlight sandbox tests by applying the integration marker consistently.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Clean up Hyperlight integration sandboxes
Close real sandbox fixtures and provider-owned registries in Hyperlight integration tests so they do not rely on process teardown.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Make Foundry Hosting resilient to missing user identity in local runs
AgentFrameworkResponseHandler threw InvalidOperationException (surfaced as a
500 on every request) when the isolation-key provider returned null, which
always happens locally because the platform x-agent-user-id header is absent.
Running a hosted image outside Foundry therefore failed out of the box.
The handler now branches on FoundryEnvironment.IsHosted: hosted stays strict
(null identity is still a hard error), but non-hosted (local docker run /
dotnet run) tolerates a null identity - per-user isolation is simply not
triggered, the request proceeds with userId null (no partition), and no
hosted context is stamped or validated.
Because local runs no longer need a fallback, the sample-side
DevTemporaryLocalUserIdProvider and AddDevTemporaryLocalContributorSetup are
removed from Hosted_Shared_Contributor_Setup and all sample Program.cs files.
To simulate distinct users locally, send an x-agent-user-id request header;
the default provider reads it exactly as it reads the platform-injected value.
The Memory sample smoke script now drives alice/bob against one container via
that header. AGENT_NAME defaults added to Hosted-ChatClientAgent and
Hosted-MemoryAgent so a hosted deploy (where AGENT_* is a reserved env var)
does not crash at startup.
Updates the two affected unit tests to assert the local-success path and
amends ADR 0031.
* Address review: correct isolation-guarantee and Memory-sample local docs
- AgentFrameworkResponseHandler: note the null/local case is unscoped/shared,
not fully partitioned per user.
- HostedSessionIsolationKeyProvider XML docs: phrase the non-null UserId rule as
a constraint on the returned-context case, since null is now allowed locally.
- Hosted-MemoryAgent: the PerUser() memory scope requires a resolved user, so a
local run needs an x-agent-user-id header; corrected the Program.cs comment
and README (removed the inaccurate "shared bucket locally" claim).
- Test: assert absence of any u-* per-user directory via a wildcard search
rather than checking for a literal "u-" directory.
* Python: [BREAKING] Extract caching from SkillsProvider into CachingSkillsSource decorator
Adds a composable CachingSkillsSource(DelegatingSkillsSource) decorator that caches the inner source's skills list, and rewires SkillsProvider to wrap its resolved source in it by default (skipped when disable_caching=True). Removes the provider's baked-in caching (_cached_context field and _get_or_create_context). Mirrors .NET #6768.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add ty ignore for dynamic _test_context attribute in skills test helper
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: Stop skill discovery at skill boundaries
File-based skill discovery kept descending after finding a SKILL.md, which treated content nested beneath a skill boundary as an independent skill root. Return immediately after recording a directory that contains SKILL.md so everything below it stays part of that skill, and add a regression test with a nested SKILL.md.
Fixes#6682
* Python: Attach nested skill content to the parent skill
Removing the SKILL.md subdirectory skip in resource and script scanning so that content beneath a skill boundary is attached to that skill, and update the discovery docstring and the nested-skill test to match. Complements the discovery early-return so a nested SKILL.md is never treated as an independent skill root.
* .NET: Consolidate skill-source caching and make skill sources disposable
Move all caching into the generic CachingAgentSkillsSource decorator and
remove the duplicate inline cache from AgentMcpSkillsSource, so a single
cache layer governs skill fetching. Add RefreshInterval-based expiry to
CachingAgentSkillsSourceOptions.
Make AgentSkillsSource (and its decorators) IDisposable so pipelines can
release owned resources, and give AgentSkillsProvider an ownsSource flag
controlling whether it disposes the source it wraps. Provider convenience
constructors and the builder set ownsSource: true.
Serialize ArchiveEntryLoader's reconcile/extract/read of the shared on-disk
directory with a per-instance lock to prevent concurrent corruption.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Fix IDE0032 by using an auto-property in test source
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Make cancellation cache test deterministic
Ensure the first caller owns the fetch before the second caller queues, so
the cancellation-restart assertion is no longer race-prone.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Throw ObjectDisposedException from CachingAgentSkillsSource after disposal
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Document AgentSkillsProviderBuilder source ownership and single-build contract
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Update API compatibility suppressions for AgentSkillsProvider ctor change
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Add test asserting archive skill updates are observed after reconcile
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: Allow custom argument marshaling for skill scripts
Add an optional argument_marshaler hook so callers can plug in their own argument conversion logic for inline skill scripts. Supplied at the InlineSkillScript, InlineSkill, and ClassSkill levels; when omitted, behavior is unchanged. This supports backends (e.g. vLLM) that send tool-call arguments in a non-conforming shape such as a JSON string.
Port of .NET PR #6498. Closes#6543.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address review feedback on skill argument marshaling
- Widen InlineSkillScript.run args to accept a raw str (the one place a marshaler-converted value is valid), and drop the now-unneeded type: ignore markers in tests.
- Constrain the SkillScriptArgumentMarshaler output type to dict | None so the type enforces the inline-script contract instead of a docstring note.
- Add a clear TypeError when a str reaches an inline script with no marshaler configured.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Rename SkillScriptArgumentMarshaler to SkillScriptArgumentParser
In Python 'marshalling' specifically connotes the stdlib marshal module, so the term is misleading here. Rename the type alias, the argument_parser parameter/attribute, docstrings, exports, and tests accordingly.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fold argument_parser docstring into Args section
The skill constructors are fully keyword-only, so name/description/function are already documented under Args. Singling out argument_parser into its own Keyword Args section was inconsistent; merge it into Args for InlineSkillScript, InlineSkill, and ClassSkill.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The auto-injection of InMemoryHistoryProvider was gated on there being no
context providers at all, so registering any non-history provider (e.g.
SkillsProvider, FileAccessProvider, or a RAG memory provider) suppressed local
history. On stateless clients this dropped prior messages across turns — most
visibly the tool-approval resume turn lost the prior assistant function_call,
causing a 400 "Expected toolResult blocks" error.
Gate the injection on the absence of a loading HistoryProvider instead, matching
the pattern already used in _workflows/_agent.py. Add regression tests covering
a non-history provider, an existing loading provider, and a persist-only
provider.
Fixes#5672
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: Fix GeminiChatClient dropping image/file content
GeminiChatClient._convert_message_contents only handled text and function_call content, so data/uri (image, PDF, audio) parts were silently dropped and never reached Gemini. Convert data URIs to inline_data Parts and external URIs to file_data Parts, warning on genuinely unconvertible content. Adds tests for the multimodal conversion paths.
Fixes#6688
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address review: strip data-URI mime params and handle non-inferable URIs
Strip parameters (e.g. charset) from a data URI media type before passing it to Gemini, and wrap types.Part.from_uri so a URI with no media_type and no guessable extension is passed through as file_data without a mime type instead of raising ValueError. Adds tests for both paths.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address review: reuse shared data-URI helpers
Reuse _get_data_bytes and detect_media_type_from_base64 from agent_framework instead of reimplementing base64 extraction/decoding and data-URI header parsing in the Gemini client. This also removes the manual header parsing that previously needed charset-parameter stripping. Updates tests accordingly.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Hosting packages (hosting, hosting-responses, hosting-telegram) were excluded
from the 1.10.0 release but their entries remained in the CHANGELOG.
Also removes the core hosting channel entry since it's unreachable without
the hosting packages.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add per-agent and per-user session storage isolation for Foundry Hosting
Partitions hosted session and checkpoint files as {root}/a-{agentName}/u-{userId}/c-{contextId}.json so a container that serves multiple agents and multiple users cannot leak state across tenants. The user layer collapses to a-{agent}/c-{conv}.json when no x-agent-user-id is present (raw local). Adds a reject-style path-traversal guard (CWE-22) for the untrusted user id plus a resolve-and-assert-under-root containment check, and keeps the strict-resume 403 identity check as a second defense layer.
AgentSessionStore.GetSessionAsync/SaveSessionAsync take a required (nullable) userId so a caller can never silently persist a session unscoped; the handler resolves the user id before loading the session and threads it to both. Tool approvals ride in the session checkpoint (ToolApprovalIdMap to AgentSessionStateBag), so the partitioned path covers them and no separate approval store is needed. Renames the sample HOSTED_USER_ISOLATION_KEY env var to HOSTED_USER_ID and DevTemporaryLocalSessionIsolationKeyProvider to DevTemporaryLocalUserIdProvider. Documents the design in ADR 0031. Adds handler-driven multi-agent/multi-user file-system tests and store-level traversal/isolation tests.
* Fail fast with a clear 501 when hosted container is served responses protocol 1.0.0
A 2.0.0-only hosted image served container protocol 1.0.0 (no x-agent-foundry-call-id
header) previously threw and surfaced an opaque 500 on every request. It now returns a
clear 501 "unsupported_container_protocol_version" naming the required protocol.
* HostedProtocolCompatibility gate keyed on FoundryEnvironment.IsHosted plus
PlatformContext.CallId (the 2.0.0 exclusive marker); invoked before isolation resolution
* HostedProtocolCompatibilityTests unit coverage; AgentFrameworkResponseHandlerTests note
clarifies the non-hosted path
* UnsupportedProtocolHostedAgentTests integration test deploys a dedicated
it-unsupported-protocol agent as 1.0.0 and asserts the 501 (validated live on cace)
* TestContainer recognizes the unsupported-protocol scenario
* it-bootstrap-agents.ps1 placeholder default raised to responses 2.0.0 and adds the
it-unsupported-protocol agent; HostedAgentFixture protocol version is overridable
* Address PR review: whitespace protocol gate and InMemory store agent keying
* HostedProtocolCompatibility treats a whitespace-only x-agent-foundry-call-id as
absent (IsNullOrWhiteSpace) so a proxy injecting whitespace cannot bypass the gate;
unit test covers empty, spaces and tab
* InMemoryAgentSessionStore keys sessions by agent.Name (omitting the agent segment
when Name is unset), mirroring FileSystemAgentSessionStore, so session continuity
survives a recreated or transient agent rather than keying on the per-instance agent.Id
* Add AgentSkillsSourceContext to AgentSkillsSource.GetSkillsAsync
Pass agent/session context through the skills retrieval pipeline so
sources, filters, and caching can make context-aware decisions.
- AgentSkillsSourceContext (Agent, Session) is built by AgentSkillsProvider
from the InvokingContext and flows through all sources and decorators.
- FilteringAgentSkillsSource predicate now receives an AgentSkillFilterContext
bundling the skill and the source context.
- CachingAgentSkillsSource supports per-key isolation via
CachingAgentSkillsSourceOptions.CacheIsolationKeySelector; a null selector
preserves the shared-cache behavior.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Make AgentSkillsSourceContext constructor public and harden cache key
- Make the AgentSkillsSourceContext constructor public so external callers
can invoke AgentSkillsSource.GetSkillsAsync directly; drop the
Mcp.UnitTests InternalsVisibleTo entry it required.
- Use a dedicated sentinel cache key for the shared bucket so an isolation
selector returning an empty string gets its own bucket.
- Document cache-key cardinality guidance and baseline the experimental
API breaking changes in CompatibilitySuppressions.xml.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Drop AgentSkillFilterContext in favor of a two-argument filter predicate
Replace the AgentSkillFilterContext bundle with a
Func<AgentSkill, AgentSkillsSourceContext, bool> predicate in
FilteringAgentSkillsSource and AgentSkillsProviderBuilder.UseFilter, and
update the tests accordingly.
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: align GitHub Copilot approval to SDK on_pre_tool_use hook
Replace the bespoke on_function_approval enforcement in the GitHub Copilot provider with the Copilot SDK's native on_pre_tool_use hook. When no caller hook is supplied, a default hook returns 'ask' for approval_mode='always_require' tools (routed to on_permission_request) and defers others; a caller-supplied on_pre_tool_use takes precedence and logs a warning for any unenforced approval tool.
Fixes#6746
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix type-checker errors and restore load_dotenv in sample
Use a complete PreToolUseHookInput in on_pre_tool_use hook tests so pyright/pyrefly/ty/zuban no longer report missing required TypedDict keys. Restore load_dotenv() in the function-approval sample for consistency with the other GitHub Copilot samples (PR review feedback).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Deprecate on_function_approval instead of removing it
Per PR review feedback, keep the on_function_approval callback working (still enforced in the tool handler for approval_mode='always_require' tools) but emit a DeprecationWarning at construction, so existing users get a signal rather than a silent behavior change. The default on_pre_tool_use ask-hook is not installed when on_function_approval is set, avoiding double-gating. Precedence: user on_pre_tool_use > on_function_approval > default ask-hook. Adds tests for the deprecated path and documents it in the package README.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Make on_function_approval and on_pre_tool_use mutually exclusive
Per automated review feedback, instead of a precedence ordering between the deprecated on_function_approval callback and the new on_pre_tool_use hook (which silently double-gated when both were set), raise ValueError if both are supplied - at construction (both in default_options) or per run (per-run on_pre_tool_use with a construction-time on_function_approval). This matches the repo convention for deprecated-vs-new params (see _workflows/_workflow.py) and removes the flag-threading. Updates tests and the package README.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace dotnet/nuget/icon.png with the new Microsoft Foundry Agent Framework color logo (resized to 128x128, the NuGet-recommended icon size). Source: docs/assets/PNG/Microsoft Foundry Agent Framework - Color.png.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace the direct logger.LogWarning() call (which eagerly evaluates
string.Join()) with a [LoggerMessage]-generated extension method in
GitHubCopilotAgentLogMessages.cs.
Fixes build error:
GitHubCopilotAgent.cs(580,13): error CA1873: Evaluation of this argument
may be expensive and unnecessary if logging is disabled
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace the direct logger.LogWarning() call (which eagerly evaluates
string.Join()) with a [LoggerMessage]-generated extension method in
GitHubCopilotAgentLogMessages.cs.
Fixes build error:
GitHubCopilotAgent.cs(580,13): error CA1873: Evaluation of this argument
may be expensive and unnecessary if logging is disabled
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Bump Azure.AI.AgentServer to 2.0.0 protocol and migrate Foundry.Hosting
Bumps Core .25->.26, Invocations .4->.5, Responses .5->.6 and adopts the 2.0.0 container protocol.
Breaking change: IsolationContext (UserIsolationKey + ChatIsolationKey) is replaced by PlatformContext (UserIdKey from x-agent-user-id, CallId from x-agent-foundry-call-id). The per-chat key is gone; HostedSessionContext is now user-only and the per-request CallId is forwarded outbound to Foundry first-party services (toolbox/MCP).
Also fixes a real call-id egress bug: AsyncLocal writes inside the streaming response iterator are reverted across yield boundaries, so the call id was dropped before the toolbox/MCP egress ran. The handler now re-applies HostedCallContext.CallId before each egress point.
Adds HostedConversationKey to map a request to a stable MAF AgentSession via conversation_id, else the partition key embedded in previous_response_id, else the minted response id. This keeps store=false previous_response_id chains and conversation_id forks on a single hosted MAF session without using the container session id.
Sample manifests bump the responses protocol to 2.0.0 (invocations stays 1.0.0). Integration tests split store/session semantics into HostedResponsesStoreConfigTests with its own scenario, read stored responses through the per-agent endpoint client, and inject the model deployment into the container.
* Pin Azure.Core 1.59.0 for Hosted-Workflow-Handoff sample
AgentServer 1.0.0-beta.26 (pulled transitively via Foundry.Hosting) requires Azure.Core 1.59.0. This sample disables transitive pinning and references Azure.Core directly, so override just this project to the SDK-required version without moving the solution-wide central pin.
* Add guard test for request-scoped call-id cleanup
Asserts HostedCallContext.CallId does not leak into the caller's execution context after CreateAsync's stream completes, while confirming the agent run still observed the call id. Documents the request-scoped contract and guards against stale-header leakage across requests handled on the same thread.
* Refresh hosting READMEs for AgentServer 2.0 migration
Updates stale docs to match the shipped code: the MemoryAgent README now describes the x-agent-user-id user-identity header (chat isolation key removed) feeding HostedSessionContext.UserId; the IntegrationTests README corrects the scenario count (six to eleven), adds the missing memory scenario row, and stops claiming all scenarios are skipped now that several are validated and active.
* Add ADR 0030 superseding 0026 for AgentServer 2.0 platform context
Documents the migration from ResponseContext.Isolation (UserIsolationKey/ChatIsolationKey) to ResponseContext.PlatformContext (UserIdKey/CallId): user-only HostedSessionContext, the request-scoped HostedCallContext call-id forwarded on egress, HostedConversationKey session keying, and removal of the PerChat/PerUserAndChat memory scopes. Marks ADR 0026 as superseded.
* Add breaking-change v2.0-only disclaimer to package metadata
Augments the package Description and adds PackageReleaseNotes stating this release targets the Foundry Responses container protocol v2.0 only, is not compatible with v1, and directs consumers to a previous release for the v1 protocol definition.
* Address review comments: dead chat-key surface and weak test assertions
Fixes the automated review findings: the MemoryAgent/AgentSkills .env.example now say one variable (only HOSTED_USER_ISOLATION_KEY remains); the MemoryAgent smoke script drops the unused ChatKey parameter and its call-site arguments; HostedConversationKey null test now exercises a real null (and whitespace); and the reuse-one-session test asserts an exact SessionCount of 1 instead of <= 1.
* Python: Add include_detailed_errors option for skill script execution
Port the .NET fix from #6680. SkillsProvider previously swallowed
exceptions from skill script execution and resource reading, returning a
generic error string so the model could not self-correct.
- Add an include_detailed_errors option to SkillsProvider.__init__ and
from_paths. When True, script-execution failures return an error string
with the exception message appended; when False (default), the exception
is logged and re-raised, delegating to the function-invocation pipeline's
own include_detailed_errors policy.
- _read_skill_resource now logs and re-raises instead of returning a
generic error string. Resources take no model arguments, so a swallowed
generic error is not actionable by the model.
- Update and add tests covering the new propagation and detailed-error
behavior.
Fixes#6681
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Re-raise skill script/resource errors instead of adding a provider option
Address PR review: returning a plain error string from the skill provider
bypassed the shared tool-error contract (no exception metadata, not counted
toward consecutive-error limits), risking infinite retries.
Instead of porting the .NET provider-level IncludeDetailedErrors option,
_run_skill_script and _read_skill_resource now always log and re-raise on
failure. This delegates error handling to the function-invocation pipeline,
whose existing include_detailed_errors policy is the Python equivalent of
.NET's FunctionInvokingChatClient.IncludeDetailedErrors and correctly
preserves exception metadata and consecutive-error counting.
Validation failures (empty/unknown skill, script, or resource names) still
return user-facing error strings. Tests updated accordingly.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: [BREAKING] Make all SkillsProvider tools require approval by default
All tools exposed by SkillsProvider (load_skill, read_skill_resource,
run_skill_script) now require approval by default. Previously only
run_skill_script could be gated, and only when require_script_approval=True.
- Register all three tools with approval_mode="always_require"
- Add read_only_tools_auto_approval_rule and all_tools_auto_approval_rule
static rules plus tool-name constants (mirrors FileAccessProvider)
- Remove the require_script_approval option from __init__ and from_paths
- Add skills_auto_approval sample; update script_approval sample/docs
Closes#6728
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address PR review: batch skill approval responses and tidy sample
- Collect a response for every approval request and send them in a single
agent.run so the approval loop always makes progress (no infinite loop when
a request lacks a function_call); reject non-function requests instead of
skipping them. Applied to both the skills_auto_approval and script_approval
samples.
- Extract ToolApprovalMiddleware into a local variable in skills_auto_approval
for readability.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address PR review: add approval handling to remaining skills samples
The secure-by-default change makes all SkillsProvider tools require approval,
which left the other skills samples emitting approval requests instead of the
documented answers. Add ToolApprovalMiddleware with the all-tools auto-approval
rule (and a session, which the middleware requires) so these samples run
unattended as before:
- code_defined_skill, file_based_skill, class_based_skill, mixed_skills,
skill_filtering, mcp_based_skill
- providers/foundry/foundry_chat_client_with_toolbox_skills
The dedicated script_approval (manual) and skills_auto_approval (selective)
samples continue to demonstrate interactive approval handling.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address PR review: simplify "host approval" wording to "approval"
Apply maintainer suggestions dropping "host" from the skill-approval
docstrings, and align the matching SkillsProvider docstring/AGENTS.md note for
consistency.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Update agent-framework-azure-ai-search to work across the stable/GA azure-search-documents SDK (12.0.0, api-version 2026-04-01) and the preview SDK (12.1.0b1, api-version 2026-05-01-preview) for both semantic and agentic modes.
- Bump the dependency to azure-search-documents>=12.0.0,<13 and the package to 1.0.0b260618.
- Add an api_version parameter (threaded into SearchClient, SearchIndexClient, and KnowledgeBaseRetrievalClient) plus STABLE_API_VERSION/PREVIEW_API_VERSION constants, re-exported from agent_framework.azure.
- Auto-detect preview-only agentic features (output mode, low/medium reasoning effort) via _preview_features_active(), which requires both the preview SDK and a preview api-version; defaults (extractive + minimal) work on both channels and preview-only options raise an actionable error otherwise.
- Make knowledge-base imports SDK-version resilient and fix the 12.x surface (k -> k_nearest_neighbors, defensive additional_properties).
- Update tests (pass on both SDKs), docs, samples, CHANGELOG, and uv.lock.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add samples for the harness blog part 2
* Address PR comments
* Fix blog links.
* Address PR comments
* Fix bug where mode was incorrectly defaulted when reading the mode before the first run.
* Add reference to new sample readme
Ollama's `format` param only accepts '', 'json', or a JSON-schema dict, so
passing a Pydantic model class (the form OpenAIChatClient/FoundryChatClient and
create_harness_agent plan mode use) raised a ValidationError while building the
request. Convert a model class to its JSON schema when mapping response_format
-> format, keeping the original class for typed response parsing.
* Python: add GitHub MCP security label sample
* modified samples to create devui auth token, support debugging with security, and change context label only using the labels of unhidden result from tools
* FIDES: secure MCP labeling, _meta IFC parsing, and docs updates
* FIDES: secure MCP labeling, _meta IFC parsing, and docs updates
* modified docs
* fixed PR comments, simplified github_mcp example
* commented github_mcp example
* remove the parse_github_mcp_labels and fix the user_identity label propogation
* fix: use standard GitHub MCP endpoint with X-MCP-Features: ifc_labels instead of /insiders
- Switch MCP_URL from /mcp/insiders to /mcp/ in github_mcp_example.py
- Add MCP_HEADERS constant with X-MCP-Features: ifc_labels to opt-in to
server-side IFC label emission in _meta payloads
- Fix SecureMCPToolProxy to pass headers via httpx.AsyncClient so they are
included on session.initialize(), not just on tool calls (was causing 401
to silently surface as anyio cancel-scope CancelledError)
- Update README, FIDES_DEVELOPER_GUIDE, FIDES_IMPLEMENTATION_SUMMARY, and
0024-prompt-injection-defense.md to remove all /insiders references
* address PR comments
* Simplify GitHub MCP security sample to DevUI-only; document SecureAgentConfig quarantine client global behavior
* minor PR comments
* fixing failed checks
* fixing failed checks
---------
Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com>
* Python: bump package versions for 1.10.0 release
- Released cohort (core, openai, foundry, root): 1.9.0/1.8.2 -> 1.10.0
- agent-framework-ag-ui: rc5 -> rc6 (tool history replay fix)
- Beta/alpha packages with changes: anthropic, azurefunctions, bedrock,
durabletask, hyperlight, purview, foundry-hosting, gemini, hosting,
hosting-responses, hosting-telegram, tools bumped to new date stamp (260625)
- Inter-package dependency bounds updated for changed packages
- CHANGELOG.md updated with [1.10.0] section and compare links
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: update stale hosting dependency pins in hosting-responses and hosting-telegram
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* CI: cap xdist workers at 4 for Azure OpenAI and Functions integration jobs
The Azure OpenAI and Functions+Durable Task integration jobs ran with
`-n logical` (~20 workers on the hosted runner), oversubscribing the box and
collapsing the whole pytest session (all workers reporting `node down: Not
properly terminated`) in the merge queue. Pin these two jobs to `-n 4` in
python-merge-tests.yml and python-integration-tests.yml to remove the
oversubscription while keeping full coverage.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* test: temporarily skip flaky Python integration tests crashing the merge queue
Revert the `-n 4` xdist experiment (it did not prevent the runner crash) and
instead skip the integration tests that collapse the pytest-xdist runner in the
merge queue (all workers report `node down: Not properly terminated`):
- Azure OpenAI: flip the per-file `skip_if_azure_openai_integration_tests_disabled`
guard to an unconditional skip (integration tests only; unit tests still run).
- Azure Functions / Durable Task: skip the four specific failing tests
(test_weather_agent, test_parallel_workflow_end_to_end, test_weather_agent_with_tool,
test_conditional_branching).
Tracked for re-enablement in #6777.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* test: skip flaky test_math_agent_with_tool (durabletask integration)
Same empty-AgentResponse flakiness as test_weather_agent_with_tool in the same
file (AssertionError: assert 0 > 0 / empty .text). Skip it in the merge queue.
Tracked in #6777.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Enforce ApprovalRequiredAIFunction in GitHub Copilot provider
The GitHub Copilot SDK owns the tool-calling loop and invokes registered
custom functions directly, so the standard FunctionInvokingChatClient
approval round-trip never runs for this provider. As a result a tool wrapped
in ApprovalRequiredAIFunction (only a marker) could execute without any
Agent Framework approval.
Add an agent-level onFunctionApproval callback and wrap approval-required
tools in an ApprovalGatedAIFunction that enforces approval before invoking
the underlying function. Secure-by-default: with no callback, or when the
callback denies or throws, execution is denied. The gate forwards tool
metadata (including the Copilot skip_permission flag) so it stays
transparent to the SDK. This mirrors the Python provider's behavior.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Propagate cancellation from GitHub Copilot approval callback
Let OperationCanceledException propagate from the approval callback instead
of swallowing it into a denial, so cooperative cancellation is honored.
Other callback failures still deny by default. Added a unit test.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Enforce ApprovalRequiredAIFunction via Copilot SDK OnPreToolUse hook
Replace the custom approval enforcement (ApprovalGatedAIFunction wrapper +
onFunctionApproval callback) with the GitHub Copilot SDK's native OnPreToolUse
hook, which the SDK already provides for pre-execution gating.
When a tool wrapped in ApprovalRequiredAIFunction is registered and the caller
hasn't supplied their own OnPreToolUse hook, the agent installs a default hook
that returns "ask" for those tools (routing the decision to OnPermissionRequest)
and defers (null) for all other tools, preserving today's behavior for
non-approval tools. If the caller supplies their own OnPreToolUse hook, it takes
precedence and they own approval handling; the agent logs a warning naming any
approval-required tool that will not be auto-gated, and the behavior is
documented. Adds an optional ILoggerFactory parameter for the warning.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Address PR review feedback on GitHub Copilot approval hook
- Build the approval-required tool-name HashSet directly instead of via an
intermediate List.
- Remove the redundant MEAI001 NoWarn suppression (tests already suppress it via
.editorconfig and the source project builds clean without it).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Include the optional description attribute on <resource> and <script>
elements within <available_resources> and <available_scripts> blocks,
aligning .NET with the Python implementation. The description is emitted
only when non-null/non-empty and is XML-escaped.
Co-authored-by: Marco Minerva <marco.minerva@gmail.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move the cache-once-then-replay logic out of AgentSkillsProvider into a
new CachingAgentSkillsSource decorator following the DelegatingAgentSkillsSource
pattern used by DeduplicatingAgentSkillsSource and FilteringAgentSkillsSource.
- Add internal CachingAgentSkillsSource (lock-free, thread-safe; clears on failure)
- AgentSkillsProviderBuilder applies caching after aggregation, before filter/dedup
- Add builder DisableCaching() opt-out method
- Convenience constructors wrap with CachingAgentSkillsSource before dedup
- Remove DisableCaching from AgentSkillsProviderOptions
- Add CachingAgentSkillsSourceTests
Co-authored-by: SergeyMenshykh <SergeMenshikh@outlook.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fixing some samples and sample verification.
* Workaround for continuation token moved to sample.
* Address PR review comments: reset _stdinEof on reuse, null-guard modelId, format
- WorkflowRunner: reset _stdinEof=false at start of ExecuteAsync so reused
instances don't exit immediately on the next external request
- 04_memory: throw clear InvalidOperationException when DefaultModelId is null
rather than silently sending null to the Foundry Responses API
- dotnet format: no code changes, formatting only
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Improving memory sample by not creating an agent just to get a chat client.
---------
Co-authored-by: Ben Thomas <25218250+alliscode@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Prefer allocated HTTPS endpoints when resolving Aspire DevUI backends and fall back to HTTP for existing services. Update the DevUI Aspire sample so WriterAgent exercises HTTPS redirection.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Switch the remaining MAF-specific [Experimental(OPENAI001)] usages in Microsoft.Agents.AI.Foundry.Hosting to MAAI001 (AgentsAIExperiments). None of these public types surface an OpenAI experimental type, so OPENAI001 was a copy-paste inconsistency; MAAI001 is the correct id for MAF hosting/agent abstractions.
Fixes#6742
* .NET: Foundry hosted-agent toolbox OAuth consent support
Add per-user OAuth (MCP CONSENT_REQUIRED) support for Foundry hosted agents.
* Defer hard toolbox startup failures so a per-user OAuth-gated toolbox no
longer bricks the container at startup (new Degraded status, retried per
request). The container stays routable and surfaces consent on the first
user request.
* Emit the platform-canonical oauth_consent_request output item (instead of
mcp_approval_request) for toolbox OAuth consent, matching the Python
implementation and how the Foundry platform heads render consent.
* Parse the toolbox CONSENT_REQUIRED (-32006) error and surface the consent
link; resume by re-sending the prompt with no reply item needed.
* Add the Hosted-Toolbox-AuthPaths OAuth consent REPL client sample that
detects oauth_consent_request, prints the consent link, and re-sends.
* Add tests for the consent parser, startup deferral, and oauth_consent_request
emission.
Fixes#6562
* .NET: Address review feedback on toolbox OAuth consent
* Make RecomputeStatus the single source that refreshes ConsentRequiredToolboxNames
from the pending-consent set, so a per-request marker that records consent via
GetToolboxToolsAsync no longer leaves ConsentRequiredToolboxNames stale (which
made ResolvePendingConsentsAsync skip surfacing it).
* Surface lazy / per-request marker consent in the same request: after resolving
markers the handler now emits oauth_consent_request + incomplete when a marker
hit CONSENT_REQUIRED, instead of silently running without that toolbox.
* Add FoundryToolboxService.GetPendingConsents() snapshot accessor.
* Fix stale ToolboxConsentParser doc comment (mcp_approval_request -> oauth_consent_request).
* .NET: Harden toolbox consent paths from code review
* Thread-safety: GetPendingConsents() now returns an immutable snapshot rebuilt
in RecomputeStatus under the lock, instead of enumerating the live
_pendingConsents dictionary off-lock (which could throw under concurrent requests).
* Resource leak: OpenToolboxAsync builds the endpoint Uri before allocating the
HttpClient and now disposes the HttpClient when McpClient.CreateAsync throws
(the unreachable/deferred case retried per request), not only when ListToolsAsync fails.
* StrictMode now gates on the pre-registered ToolboxNames set rather than the
opened-toolbox cache, so a registered-but-deferred toolbox is no longer rejected
as unknown.
* Sample REPL: the legacy approval-args consent fallback only reads the explicit
consent_url key, so a normal function-tool approval carrying a URL argument is
not misread as an OAuth consent request.
* .NET: Scope per-request toolbox marker consent to the request
Addresses review feedback that a marker-originated toolbox could leak into global
scope after consent. GetToolboxToolsAsync now returns a request-scoped
ToolboxResolution (tools or consent requirements) instead of recording marker
consent in the container-global _pendingConsents and appending resolved tools to
the service-wide Tools list.
* Marker consent is surfaced as oauth_consent_request for the requesting turn only
and collected in the handler's marker loop; it no longer injects tools into, or
raises a consent prompt on, a later request that did not reference the marker.
* Marker resolution no longer flips the container StartupStatus to ConsentRequired
(per-request markers must not affect readiness, per the StartupStatus contract).
* Remove the now-unused GetPendingConsents()/snapshot path; _pendingConsents is once
again exclusively the pre-registered/startup consent set.
* .NET: Add consent request-scoping UTs and an OAuth consent integration test
Unit tests (Microsoft.Agents.AI.Foundry.Hosting.UnitTests):
* New FoundryToolboxMarkerScopingTests proves per-request marker resolution is
request-scoped: a marker consent is returned to the caller without mutating
ConsentRequiredToolboxNames, StartupStatus, or the service-wide Tools cache, and
marker-resolved tools are returned to the caller rather than injected globally
(so a request with no marker sees neither the tools nor the consent).
* Adds a test-only ToolboxOpener seam on FoundryToolboxService so the consent/tools
resolution can be exercised without a live MCP proxy. Makes ToolboxOpenResult and
CachedToolbox internal (CachedToolbox.Client nullable, guarded at dispose).
Integration test (Foundry.Hosting.IntegrationTests):
* New toolbox-oauth-consent scenario wired into the TestContainer (pre-registers a
Foundry toolbox via AddFoundryToolboxes from IT_TOOLBOX_NAME), a
ToolboxOAuthConsentHostedAgentFixture, and a ToolboxOAuthConsentHostedAgentTests
that invokes the deployed agent and asserts the consumer captures an
oauth_consent_request consent link (container stays routable, no 424). Skipped by
default per the IT convention; documents the consent-gated toolbox prerequisite.
* Adds the scenario to it-bootstrap-agents.ps1 and the README scenario table.
* Refactor runner/workflow responsibilities, add concurrency guards, and fix checkpoint ancestry bug
Move runner-state ownership out of Workflow into Runner for clearer responsibilities. Add a weakref-based concurrent-run guard in Workflow and fix the stream-drop race in run_until_convergence. Fix the checkpoint ancestry bug by tracking the previous checkpoint id as runner instance state so parent pointers persist across resumed runs. Move Runner to a deprecated lazy __getattr__ export (backward-compatible with DeprecationWarning) and export CheckpointID.
* Scope runtime checkpoint storage to its owning run
Close the stream-drop race where a dropped run's deferred async-generator finalizer could leave a runtime checkpoint storage override set (inherited by a new run) or clear a successor run's storage. run() now defensively clears any stale override before starting, and _run_core only clears the override if this run still owns it (mirroring the _active_run ownership guard). Adds regression tests for both the inheritance and clobber cases.
* Collapse runtime-storage ownership into the active-run weakref
_runtime_storage_owner always held the same weakref as _active_run, so the two ownership conditions were equivalent. Derive ownership from a single owns_run = (_active_run is my_active_run) captured before the active-run clear, and remove the redundant field. No behavior change.
* Nest runtime-storage clear under the owns_run guard
Both the active-run release and the runtime-storage clear are gated on owns_run, so fold the storage clear inside the if owns_run block. No behavior change.
* Reset resume flag in a finally so it can't leak across runs
_resumed_from_checkpoint was only cleared on the success path of run_until_convergence, so a failure during a resumed run (e.g. executor failure) left it True. The next fresh run then skipped the superstep-0 checkpoint and parented later checkpoints to the stale resume point. Move the reset into a finally. Add a regression test that fails a resumed run via an executor error and asserts the next fresh run creates the superstep-0 checkpoint.
* Fix tests and formatting
* Fix formatting
* Address comments
* Update type ignore statements
* Make all AgentSkillsProvider tools require approval by default
- Wrap all tools (load_skill, read_skill_resource, run_skill_script) with
ApprovalRequiredAIFunction unconditionally
- Add ReadOnlyToolsAutoApprovalRule and AllToolsAutoApprovalRule static
properties following the FileAccessProvider pattern
- Remove ScriptApproval from AgentSkillsProviderOptions and
UseScriptApproval from AgentSkillsProviderBuilder
- Add Agent_Step07_SkillsAutoApproval sample
Closes#6727
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add UseToolApproval to hosted AgentSkills scenarios
Wire AllToolsAutoApprovalRule into the integration test container and
the Hosted-AgentSkills sample so skill tools execute without blocking
on approval when no interactive approval handler is configured.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add API compatibility suppressions for removed ScriptApproval members
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>
Embed resource and script instruction text directly in the default
prompt template instead of using placeholder substitution. Custom
templates now only need the {skills} placeholder.
Co-authored-by: SergeyMenshykh <SergeMenshikh@outlook.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: Add Telegram channel for agent-framework-hosting
- Add agent-framework-hosting-telegram package with TelegramChannel
supporting polling and webhook transports, streaming edits with
Telegram Bot API rate limiting, per-chat serial workers, and
multi-modal inbound/outbound (text, photo, document, voice)
- Add local_telegram sample demonstrating multi-channel hosting with
a TelegramChannel alongside ResponsesChannel, using per-chat
FileHistoryProvider and a run_hook for Telegram persona temperature
- Fix test layout: move tests to tests/hosting_telegram/ (no __init__.py)
- Remove old [tool.mypy] section and mypy poe task; source type-checking
is handled by pyright via shared_tasks
- Update uv.lock, pyproject.toml workspace sources, and PACKAGE_STATUS.md
Fixes#6588
Refs #6265
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: Address Telegram channel CI failures and review feedback
- Fix webhook secret validation to use constant-time compare_digest
- Harden webhook update parsing: require integer chat IDs and guard slash-only commands
- Fix streaming edge cases in TelegramChannel:
- prevent edit worker deadlocks when text exceeds 4096 chars
- prevent deadlock when placeholder send fails (message_id stays None)
- enforce edit throttling with minimum interval sleep
- honor send_typing_action=False in streaming mode
- always forward final multimodal output (e.g. images), while avoiding duplicate text sends
- Expand Telegram tests for slash-only command handling, non-int chat IDs, and streaming behavior (long text, final images, typing toggle)
- Fix sample/docs feedback:
- rename sample package to agent-framework-hosting-sample-local-telegram
- switch sample uv.sources from feature branch to main
- align docs/tool names with lookup_weather
- fix broken links and server run instructions in README/call_server.py
- align local_telegram app docstrings with reasoning hook behavior and strip model in responses_hook
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: Fix TelegramChannel streaming to iterate contents for multimodal support
- Remove stale PR reference from module docstring
- Add Google-style docstring to TelegramChannel.__init__ documenting all keyword args
- Fix _stream_to_chat to iterate update.contents instead of using
getattr(update, 'text', None); text chunks are extracted from Content
items with type='text', non-text content in updates is correctly
ignored (images etc. are forwarded via the final response)
- Update _FakeStreamUpdate test helper to use contents list matching the
real AgentResponseUpdate API; add from_text/from_image class methods
- Update _FakeResponseStream to accept _FakeStreamUpdate objects directly
- Add test verifying multimodal stream updates don't corrupt text accumulator
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: Split local_telegram into simple Telegram-only and new multi-channel sample
local_telegram is now a focused Telegram-only sample:
- Removes ResponsesChannel and all responses_hook code
- Removes call_server.py (no HTTP endpoint to call)
- Uses a deterministic lookup_weather tool (hash-based, not random)
- Single run_hook that strips model and raises reasoning effort
- Drops agent-framework-hosting-responses dependency
New local_multi_channel sample shows running both channels at once:
- ResponsesChannel + TelegramChannel sharing a FileHistoryProvider
- Cross-channel session resumption via previous_response_id
- call_server.py moved here (the Responses endpoint lives here now)
- Demonstrates the multi-channel coordination story
Update README table to list both samples with clear descriptions.
Also delete personal_assistant/.venv which was not tracked but caused
pyright to crawl the entire installed venv (thousands of files),
making sample pyright checks hang indefinitely.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: Fallback when Telegram final edit fails
- only mark final edit as sent after a confirmed 2xx edit response
- fall back to sendMessage when final edit returns a non-success status
- add regression test covering failed final edit fallback behavior
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: Fix optional await_args typing in telegram test
- assert await_args is not None before reading kwargs in streaming fallback test
- resolves test-typing failures across mypy/pyright/ty/zuban for hosting-telegram
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: [Breaking] Refactor FileSkillsSource for depth-based discovery and predicate filters
Refactors FileSkillsSource to make script and resource discovery more flexible.
## Changes
- **Drops** resource_directories / script_directories options (preconfigured
directory whitelists).
- **Adds** search_depth option (>= 1, default 2): controls how deep the
recursive scan goes within each skill directory.
- **Adds** script_filter / resource_filter predicate options that receive a
FileSkillFilterContext (skill_name + relative_file_path), allowing
whitelist/blacklist filtering by file path.
- **Adds** FileSkillFilterContext class exported from agent_framework.
## Notes
- The Skills API is marked @experimental -- the option removals are intentional
breaking changes within the experimental surface.
- Security checks (path containment, symlink detection) are preserved and
continue to use the skill root directory as the trusted boundary.
- Ports the same refactoring from .NET PR #6109 while following Python
conventions (instance methods, Callable type hints, __slots__).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address PR feedback: clarify depth constants and skip nested skill directories
- Add clarifying comments distinguishing MAX_SEARCH_DEPTH (SKILL.md
discovery) from DEFAULT_SEARCH_DEPTH (per-skill resource/script scanning).
- Stop recursing into subdirectories that contain their own SKILL.md,
preventing child skill files from being attached to the parent skill.
- Add test verifying nested skill boundary is respected.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Remove __slots__ from FileSkillFilterContext and add type-ignore comments
- Remove __slots__ from FileSkillFilterContext per reviewer feedback —
the optimization is negligible and inconsistent with sibling classes.
- Add type: ignore[attr-defined] / ty: ignore[unresolved-attribute]
comments to test lines accessing private _resources/_scripts attributes,
matching the convention established on main.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Simplify filter predicates: remove FileSkillFilterContext, use Callable[[str, str], bool]
Address reviewer feedback:
- Remove FileSkillFilterContext class — a dedicated class for two strings
is overkill in Python. Filters now receive (skill_name, relative_file_path)
directly as positional args.
- Update docstrings to describe behavior instead of referencing private
instance attributes.
- Remove FileSkillFilterContext from exports and __all__.
- Update all test lambdas and remove TestFileSkillFilterContext class.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Use DEFAULT_SEARCH_DEPTH as default argument directly
Instead of accepting int | None and resolving None to the default
internally, use DEFAULT_SEARCH_DEPTH as the parameter default value
on both FileSkillsSource.__init__() and SkillsProvider.from_paths().
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Migrate 02-agents/Agents samples to AIProjectClient (Foundry)
Replace AzureOpenAIClient with AIProjectClient as the AI provider in all
02-agents/Agents samples, aligning with the Foundry-first approach.
Changes:
- 19 Program.cs files migrated to use AIProjectClient.AsAIAgent()
- 19 .csproj files updated (Azure.AI.OpenAI -> Microsoft.Agents.AI.Foundry)
- Environment variables: AZURE_OPENAI_* -> FOUNDRY_PROJECT_ENDPOINT/FOUNDRY_MODEL
- Updated description comments to reflect Foundry backend
- Provider-specific samples in AgentsWithFoundry/ intentionally unchanged
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Migrate 02-agents/AgentSkills, AgentWithMemory, AgentWithRAG, AgentOpenTelemetry to AIProjectClient
Replace AzureOpenAIClient with AIProjectClient as the AI provider.
Environment variables: AZURE_OPENAI_* -> FOUNDRY_PROJECT_ENDPOINT/FOUNDRY_MODEL.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Migrate 03-workflows samples to AIProjectClient (Foundry)
Replace AzureOpenAIClient with AIProjectClient as the AI provider in
all 03-workflows samples that use an AI model.
Environment variables: AZURE_OPENAI_* -> FOUNDRY_PROJECT_ENDPOINT/FOUNDRY_MODEL.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix PR 6557 build breaks and align Foundry client usage
- Add explicit Azure.Identity package references to migrated sample projects
that use DefaultAzureCredential
- Fix AgentWithRAG_Step05_Neo4jGraphRAG to use AIProjectClient.AsAIAgent()
with ChatOptions.ModelId instead of AIProjectClient.AsIChatClient()
- Keep migrated samples on AIProjectClient pattern (no FoundryAgent/AzureOpenAIClient)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address PR 6557 Foundry review follow-ups
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix post-rebase sample build and format regressions
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Updates to fix issues from switching to Responses.
* Fixing more tests and deleting checkpoint directories created for samples.
* Fixing formatting
* Restore DefaultAzureCredential warnings in agents samples
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Fix hosted agent crash after tool call by rooting session store under $HOME
FileSystemAgentSessionStore.CreateDefault rooted the hosted session store at the
filesystem root "/.checkpoints", which is read-only inside a Foundry hosted
container. After a local tool call the response handler persists the session, so
the write to "/.checkpoints" threw IOException and tore down the container, which
the platform surfaced as "mount: /app: mount failed: No such file or directory.".
Root the hosted store at $HOME (default /home/session), the only writable and
durable location per the container image spec. Persistence failures stay fatal but
are now wrapped in a clear, actionable IOException instead of the opaque raw error.
Add unit tests covering hosted and local path resolution plus the clear error, and
enable the ToolCalling Foundry Hosted Agents integration tests (verified live).
Fixes#6231
* .NET: Harden hosted session store against a filesystem-root HOME
Address review feedback on #6714: a misconfigured HOME pointing at a filesystem
root (e.g. "/") resolved back to "/.checkpoints" and would reintroduce the original
read-only-root crash. CreateDefault now falls back to the default session-data
directory (/home/session) when HOME is missing, blank, a filesystem root, or an
unnormalizable path. Adds a unit test locking in the "never the filesystem root"
behavior for a hosted HOME of "/".
Related #6231
* Python: surface Gemini cached and thinking token counts in usage details
* Python: surface Bedrock cache token counts in usage details
* Python: surface Gemini cached and thinking token counts in usage details
* Python: surface Bedrock cache token counts in usage details
* Return None from Bedrock _parse_usage when no token counts are present
Matches the UsageDetails | None return annotation and the Gemini
connector's behavior, so a usage payload with no recognized keys no
longer propagates an empty mapping. Adds a regression test.
Skill content now always emits <available_resources> and <available_scripts>
blocks, using self-closing elements when empty, so models receive an
authoritative list per category and do not hallucinate resource/script names.
FileSkill now also emits its resources block.
Closes#6348
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET Foundry: add CreateMcpTool projectConnectionId overload
Adds FoundryAITool.CreateMcpTool(serverLabel, serverUri, projectConnectionId, ...)
so hosted MCP tools can authenticate through a Foundry project connection, matching
the Python FoundryChatClient.get_mcp_tool(..., project_connection_id=...) factory.
The connection id is applied via the McpTool.ProjectConnectionId extension that ships
in Azure.AI.Projects.Agents (patches project_connection_id), already referenced by the
Foundry package. Includes unit tests and sample/README guidance plus the existing
FromResponseTool workaround.
* Fold projectConnectionId into existing CreateMcpTool overload
Replaces the separate project-connection overload with an optional
projectConnectionId parameter on the existing serverUri CreateMcpTool, so all
settings (authorizationToken, headers, allowedTools, ...) stay available and there
is no positional overload ambiguity. Adds tests for the default (no connection)
path and for preserving other settings. Sample/README now show only the supported
overload.
* NET: Support archive-type skills in AgentMcpSkillsSource
Add archive-type skill discovery to the MCP skills source. Index entries
are dispatched to per-type loaders (skill-md and archive) via a new
IMcpSkillEntryLoader strategy. The archive loader downloads, safely
unpacks, and serves packaged skills through an internal file skills
source, while ensuring MCP-delivered scripts are never executed.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix CS0121 ambiguity in UseSource null test
Cast null! to AgentSkillsSource to disambiguate from the new
Func<ILoggerFactory?, AgentSkillsSource> overload.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address PR review: fix misleading comment and catch UnauthorizedAccessException in Dispose
- Remove hardcoded '50' from test comment; it now says 'default cap'
without citing a specific number that can drift from the constant.
- Catch UnauthorizedAccessException alongside IOException in test
Dispose for robust cleanup.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Decouple shared refresh from per-caller cancellation
Use CancellationToken.None for the shared refresh so one caller's
cancellation does not abort work for all concurrent waiters. Waiters
use WaitAsync(cancellationToken) to cancel independently. The refresh
owner checks its own token after publishing the result.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix file encoding: add UTF-8 BOM to archive tests
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix file encoding: add UTF-8 BOM to ArchiveFormat.cs
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Clarify pruning doc: covers non-actionable entries too
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add branch-coverage tests and drop [Experimental] attribute
- Add 5 unit tests covering FilterValidEntries/download condition branches
(missing name, invalid name chars, missing url, unsupported format, text-only blob)
- Remove [Experimental] attribute from AgentMcpSkillsSourceOptions (alpha package suffices)
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>
* fix: propagate skill script/resource exceptions instead of swallowing them
Stop catching and returning generic error strings in RunSkillScriptAsync and
ReadSkillResourceAsync. Exceptions are now logged and rethrown so that
FunctionInvokingChatClient can decide whether to surface details to the model
via its existing IncludeDetailedErrors option (default: safe generic message).
Fixesmicrosoft/agent-framework#6304
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add IncludeDetailedErrors option for skill script execution
Add an IncludeDetailedErrors option to AgentSkillsProviderOptions. When enabled,
RunSkillScriptAsync appends the exception message to the error returned to the
model so it can self-correct (e.g. retry with different arguments). When
disabled (default), the exception is logged and rethrown, letting
FunctionInvokingChatClient apply its own IncludeDetailedErrors policy.
ReadSkillResourceAsync now logs and rethrows as well, since resources take no
arguments and a generic swallowed error is not actionable by the model.
Fixesmicrosoft/agent-framework#6304
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add prompt-injection caution to IncludeDetailedErrors doc
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>
* Fix SearchDirectoriesForSkills to stop recursing after finding SKILL.md
When a directory contains SKILL.md, subdirectories are part of that skill
and should not be treated as independent skill roots. Add a return after
adding the directory to results to prevent incorrect recursion.
Also adds a regression test verifying nested SKILL.md files are not
discovered as separate skills.
Fixesmicrosoft/agent-framework#6683
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix test: use matching directory name so nested SKILL.md would pass validation
The child skill's frontmatter name must match its directory name,
otherwise it gets rejected by validation regardless of the recursion fix.
This ensures the test actually validates the stop-recursing behavior.
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>
* Purview: prefer token principal for user identity
Align Purview middleware identity resolution so user-token principals are preferred before supplied message identities, while app-token flows continue to use validated fallback user IDs. Also fix the content activities user route and add regression coverage for identity precedence and route construction.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Fix user ID resolution logic in ScopedContentProcessor and add unit test for empty token user ID
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add sample for per-run refreshable MCP authentication headers
Adds a Foundry RAPI sample that attaches per-run, refreshable authentication headers to MCP requests using existing primitives: a DelegatingHandler on the MCP transport's HttpClient plus an AsyncLocal run scope. The same agent runs under two contexts, each minting a fresh token, proving the header is per run rather than bound at agent or connection creation time.
The handler attaches the bearer only over HTTPS to the MCP server's own origin, logs the non-secret label only, disables cookies, and checks certificate revocation. The README covers security considerations and production notes.
Fixes#1631
* Address PR review: harden redirect handling, nest-safe scope, README env vars
Disable AllowAutoRedirect on the shared handler so a redirect cannot carry the bearer past the origin check. Save and restore the prior run scope instead of clearing to null so the helper is safe under nesting. Note the Foundry env vars in the samples folder README row and update the sample README security notes.
* Require approvals for file-access and expose auto approval funcs for it
* Scope file-access auto-approval rules to local tools; fix base-Agent sample
Address PR #6599 review feedback:
- read_only/all_tools auto-approval rules now reject any call carrying a
server_label so they stay scoped to FileAccessProvider's local tools and
never auto-approve a same-named hosted tool.
- Expand the FileAccessProvider docstring to explain the runtime effect of
approval_mode="always_require" and point to ToolApprovalMiddleware /
create_harness_agent.
- Fix the base-Agent file_access_data_processing sample, which would otherwise
stop executing file tools under the new always_require defaults, by adding
ToolApprovalMiddleware with all_tools_auto_approval_rule.
- Add tests covering hosted (server_label) calls and update docs.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Clean up comments
* Update sample after merge
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Explicitly emit available_resources and available_scripts in skill content
AgentInlineSkillContentBuilder now always emits <available_resources> and
<available_scripts> elements, using self-closing tags when a skill has no
resources or scripts. This signals to the model exactly what is callable so it
does not hallucinate non-existent resource or script names. Script parameter
schemas are wrapped in a nested <parameters_schema> element.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Emit available_resources block for file-backed skills
Align AgentFileSkill with inline/class skills by surfacing discovered
resources in the loaded skill content. AgentFileSkill.GetContentAsync now
appends an <available_resources> block (before <available_scripts>) listing
resource names so the model has an authoritative list and does not
hallucinate resource names. Extracted a reusable BuildAvailableResourcesBlock
helper in AgentInlineSkillContentBuilder.
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>
* Change A2A default session store to NoopAgentSessionStore
Align the A2A hosting layer default session store with the AG-UI
sibling by using NoopAgentSessionStore, making persistence an explicit
opt-in choice.
Update samples to document how to register a persistent session store
for multi-turn conversations.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Clarify test name to specify session store default
Rename test to FallsBackToNoopSessionStoreDefaultAsync to avoid
implying all stores default to noop (task store still uses InMemory).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
OpenTelemetryAgent auto-wired OpenTelemetryChatClient above FICC, producing
OTel(FICC(leaf)). FICC resolved its ActivitySource at construction time as null,
so execute_tool spans were never emitted for tool-calling agents.
This repositions OTel below FICC, producing FICC(OTel(leaf)), via a deferred
NoOp slot pre-placed as the innermost decorator in WithDefaultAgentMiddleware
and activated once at the agent level.
- Add internal DeferredOpenTelemetryChatClient: inert DelegatingChatClient whose
Activate(sourceName) swaps its target to inner.AsBuilder().UseOpenTelemetry().Build().
- WithDefaultAgentMiddleware always registers the slot innermost so it lands below FICC.
- OpenTelemetryAgent activates the slot once in its constructor and forwards run
options straight through, removing the per-run ChatClientFactory outer wrap.
- Add and update unit tests, including a proof that execute_tool spans are emitted
on the agent source and parented under invoke_agent.
* Add samples for harness blog post part 1
* Add readme for python samples
* Update python instructions to match dotnet instructions
* Address PR comments
* Add link to blog posts
* Fix blog post naming.
* Add more blog post links
* Project ToolExecution events as FunctionCallContent/FunctionResultContent
GitHubCopilotAgent's event-dispatch switch previously had no case for
ToolExecutionStartEvent or ToolExecutionCompleteEvent. Both fell through
to the default case and were wrapped as opaque AIContent with
RawRepresentation, preventing downstream consumers and models from
recognizing tool call results.
Add explicit cases that project:
- ToolExecutionStartEvent → FunctionCallContent (role: Assistant)
- ToolExecutionCompleteEvent → FunctionResultContent (role: Tool)
This mirrors the Python fix already shipped in #4734/#4814/#4828.
Fixes#5897
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(#5897): Address review feedback for ParseArguments robustness
- Handle non-generic IDictionary variants (Hashtable, etc.) that don't
match IDictionary<string, object?> due to generic invariance
- Return null for empty/whitespace string arguments instead of wrapping
them in a spurious { value = "" } dictionary, aligning with
ParseFunctionArgumentsObject convention elsewhere in the repo
- Add test coverage for Dictionary, Hashtable, and JsonElement argument
types
- Add edge-case test for Success=true with null Result
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix non-generic IDictionary key handling in ParseArguments (#5897)
Use direct (string) cast for dictionary keys instead of ToString()
coercion, matching the established pattern in ObjectExtensions and
PortableValueExtensions. This validates keys are actually strings
rather than silently accepting and coercing non-string keys.
Add test verifying non-string dictionary keys throw InvalidCastException.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix missing 'using System' in ToolExecutionEventProjectionTests
Add the missing 'using System' directive needed for InvalidCastException
reference at line 375 of ToolExecutionEventProjectionTests.cs.
Fixes#5897
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Use source-generated JsonTypeInfo for AOT-safe argument deserialization
Replace reflection-based JsonSerializer.Deserialize<T>() calls with the
JsonTypeInfo overload that uses source-generated metadata, eliminating
IL2026/IL3050 trimming and AOT warnings without suppressions.
Changes:
- Register Dictionary<string, object?> in GitHubCopilotJsonUtilities JsonContext
- Add JsonSerializerOptions constructor parameter (defaults to
GitHubCopilotJsonUtilities.DefaultOptions)
- Use GetTypeInfo()-based Deserialize overload in ParseArguments
- Remove [UnconditionalSuppressMessage] attributes
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix dotnet format: add 'this.' qualification to instance method call
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Adapt to GitHub.Copilot.SDK 1.0.0 API after merge with main
- Update ToolExecutionEventProjectionTests: Arguments is now JsonElement?
(not object?), remove tests for string/Dictionary/Hashtable arguments
- Remove AutoStart option (removed in 1.0.0)
- Simplify ParseArguments to handle JsonElement primarily
- Add tests for empty object and nested JSON arguments
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(hosting): emit url_citation annotation events from streamed AI Search responses
OutputConverter.ConvertUpdatesToEventsAsync accumulated text content deltas but
silently dropped CitationAnnotation metadata from TextContent.Annotations. As a
result, hosted agents that use CreateAzureAISearchTool emitted citation markers in
text (e.g. 【5:0†source】) but produced empty annotations arrays and no
response.output_text.annotation.added SSE events.
The fix accumulates UrlCitationBody SDK annotations across all TextContent updates
for a message and emits them via TextContentBuilder.EmitAnnotationAdded after
EmitTextDone (as required by the SDK lifecycle) and before EmitDone. Non-citation
and region-less annotations are silently skipped, matching the existing OpenAI
ChatCompletions path in AgentResponseExtensions.
Adds 7 unit tests (N-01–N-07) covering: basic emission, ordering constraints,
multiple annotations, multi-update accumulation, and skip conditions.
Fixes#6641
* test: convert annotation test comments to XmlDoc and group in region
* fix: remove redundant long casts on annotation region indices
* test: assert done events carry url_citation annotation metadata
---------
Co-authored-by: Roger Barreto <19890735+RogerBarreto@users.noreply.github.com>
* Bring Hosted-Toolbox sample to parity with sibling hosting samples
Adds the standard scaffolding files (.env.example, agent.yaml, agent.manifest.yaml,
Dockerfile, Dockerfile.contributor) that every other 04-hosting Foundry sample ships
but Hosted-Toolbox lacked.
Fixes the toolbox name environment variable: reads TOOLBOX_NAME instead of the
platform reserved FOUNDRY_TOOLBOX_NAME so it survives agent create, and aligns the
default to my-toolset.
Rewrites the README to the standard section layout with PowerShell fenced commands,
and adds Using-Samples READMEs documenting why the client REPLs exist.
Renames Azure AI Foundry to Foundry across the 04-hosting sample READMEs and comments
for consistent product naming.
* Address PR review: accurate docs and TOOLBOX_NAME in ToolboxMcpSkills
- SimpleAgent README: correct the demo banner to the real per-agent URL the
client prints (https scheme and the /api/projects/<project> segment).
- Hosted-Toolbox Program.cs: move FOUNDRY_MODEL out of the Required block into
Optional since it has a gpt-4o default and an AZURE_AI_MODEL_DEPLOYMENT_NAME
fallback.
- Hosted-ToolboxMcpSkills: switch the toolbox name from the reserved
FOUNDRY_TOOLBOX_NAME to TOOLBOX_NAME across Program.cs, .env.example,
agent.yaml, agent.manifest.yaml and README so it is deployable via the
manifest, matching the other toolbox samples.
* Python: harden Hyperlight output capture against symlinks
Mirror the input-staging symlink hardening on the output-capture path of
HyperlightExecuteCodeTool. Output discovery now walks via the symlink-safe
_iter_real_entries instead of rglob, per-file collection validates that no
path component is a symlink and the final entry is a regular file, and file
reads use os.O_NOFOLLOW. Adds regression tests for the output path.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address review: reject traversal, fix listing test, harden read
- _is_safe_output_file now rejects '.'/'..' components (lexical relative_to
could otherwise escape root without a symlink)
- _read_output_file_bytes adds a cross-platform TOCTOU guard (lstat/fstat
st_dev+st_ino identity check) since O_NOFOLLOW is absent on Windows
- fix intermediate-dir-symlink test to use a relative listing path so it
exercises normalization + validation; add a parent-traversal unit test
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(durabletask): host MAF workflows on a standalone Durable Task worker
Add a host-agnostic workflow execution engine to agent-framework-durabletask so a MAF Workflow can run as a durable orchestration outside Azure Functions:
- WorkflowOrchestrationContext protocol + DurableTaskWorkflowContext adapter, the superstep orchestrator, serialization helpers, capturing runner context, and the shared non-agent activity body (including the yield-output classifier so intermediate executors are not surfaced as final outputs).
- DurableAIAgentWorker.configure_workflow auto-registers agent executors as entities, non-agent executors as activities, and the workflow orchestrator.
- plan_workflow_registration centralizes the 'what to register' decision so it can be shared across hosts.
- run_agent_coroutine runs all agent coroutines on one persistent event loop, fixing a cross-loop hang when shared chat clients/credentials bind their asyncio primitives to a dead loop.
- DurableWorkflowClient (start/await workflow + HITL discover/respond); DurableAIAgentClient stays agent-only.
* refactor(azurefunctions): delegate workflow execution to agent-framework-durabletask
AgentFunctionApp now reuses the shared orchestrator, activity body, and registration planner from agent_framework_durabletask instead of maintaining its own copies; _workflow.py becomes a thin host-specific adapter (AzureFunctionsWorkflowContext).
- Run agent entity coroutines on the shared persistent event loop, fixing the cross-loop hang.
- Relocate state-diff unit tests to the durabletask package; update entity loop tests.
* feat(core): expose durabletask workflow symbols via agent_framework.azure
Lazily re-export WORKFLOW_ORCHESTRATOR_NAME and DurableWorkflowClient from the agent_framework.azure namespace so standalone hosts can import them without depending on internal module paths.
* docs(samples): add standalone durabletask workflow and HITL samples
Add two samples under samples/04-hosting/durabletask demonstrating MAF workflows on a standalone Durable Task worker (no Azure Functions):
- 08_workflow: conditional spam-detection workflow started via DurableWorkflowClient.start_workflow / await_workflow_output.
- 09_workflow_hitl: content-moderation workflow that pauses with ctx.request_info and is resumed via DurableWorkflowClient.get_pending_hitl_requests / send_hitl_response.
Also add the durabletask workflow integration test (test_08_dt_workflow).
* fix: address PR review feedback
- Sanitize HITL external-event responses with strip_pickle_markers in the orchestrator (defense-in-depth for callers that bypass DurableWorkflowClient).
- Raise WorkflowConvergenceException when max_iterations is reached with pending messages, matching the core WorkflowRunner instead of silently returning partial output.
- Route falsy 'sent' messages (use 'is not None' instead of truthiness).
- Normalize None shared_state_snapshot/source_executor_ids in execute_workflow_activity.
- Cast Any returns in AzureFunctionsWorkflowContext to satisfy mypy/pyright.
- Fix sample docstrings to reference DurableWorkflowClient.
* fix: resolve pyright Package Checks errors
- Use typed locals instead of cast in AzureFunctionsWorkflowContext (mypy sees Any, pyright sees concrete types -> avoid reportUnnecessaryCast).
- Annotate shared_state_snapshot and cast partially-typed durabletask SDK returns / HITL custom-status parsing to satisfy reportUnknownVariableType/reportUnknownMemberType.
- Drop the dead deserialize/serialize re-export in _workflow.py and mark the intentional private _extract_message_content re-export.
* fix(durabletask): agent-executor identity and typed workflow input
Register each workflow agent entity under the executor id that the orchestrator dispatches to (instead of the agent name), so AgentExecutor(agent, id=...) works when the id differs from agent.name. The azure-functions host mirrors this.
Reconstruct the start executor declared input type from the workflow initial JSON payload in the shared engine (mirroring in-process delivery) instead of string-coercing it per host. Untrusted input is stripped of pickle markers before reconstruction to prevent deserialization RCE.
* fix(samples): type durable workflow start executors for reconstructed input
The HITL and parallel workflow samples no longer hand-parse a JSON string. Their start executors now declare their real input type (ContentSubmission / DocumentInput), which the durable engine reconstructs from the client payload before delivery.
* test(durabletask): unit coverage for registration, client, worker, and input coercion
Add unit tests for plan_workflow_registration, DurableWorkflowClient, the agent-executor identity registration (entity keyed by executor id), and the typed initial-input coercion including pickle-marker neutralization.
* test(durabletask): HITL and parallel durable workflow integration tests
Add an integration test for the standalone durabletask HITL workflow sample via a new workflow_client fixture. Re-enable the Azure Functions parallel workflow test, consolidated into one end-to-end case so the work-stealing xdist scheduler cannot spawn multiple func hosts for this sample.
* refactor(durabletask): group workflow modules into a _workflows subpackage
Move the eight workflow modules into a private _workflows/ subpackage and drop the redundant _workflow_ prefix (orchestrator.py, registration.py, activity.py, client.py, context.py, dt_context.py, runner_context.py, serialization.py). The public API and __all__ are unchanged; only direct internal-module imports were repointed (package __init__, the worker, the azure-functions shared shim, and the affected unit tests).
* fix(durabletask): harden workflow type resolution and HITL response handling
- resolve_type returns only real classes (avoids issubclass TypeError in reconstruct_to_type)
- re-wait on HITL responses rejected by pickle-marker sanitization instead of dropping the request and losing the run
- American spelling in strip_pickle_markers docstring
- unit tests for resolve_type
* fix(durabletask): treat async edge conditions as not-matched on the synchronous host
The durabletask orchestrator evaluates edge conditions synchronously and does not support async edge conditions. Such an edge is now treated as not matched (the edge is not traversed) rather than assuming a result. Adds unit coverage; full async-condition support will be handled separately.
* fix(durabletask): reconstruct typed workflow outputs at the host boundary
await_workflow_output and the Azure Functions status endpoint now decode the checkpoint-encoded outputs the shared activity produces, via a shared deserialize_workflow_output helper. The client returns the original objects; the AF endpoint emits clean domain JSON instead of checkpoint-marker dicts, keeping the two hosts consistent.
* fix(durabletask): address review findings on workflow hosting
- AF: register workflow agents through add_agent(entity_id=...) so they remain tracked in app.agents / get_agent() (restores documented behavior) while keying by the executor id the orchestrator dispatches to; mirrors DurableAIAgentWorker.add_agent.
- async bridge: treat the shared loop as reusable only while its backing thread is alive, so a dead loop thread is replaced instead of hanging future.result() forever.
- client: add get_runtime_status; the standalone HITL sample now stops polling and reports the real terminal state instead of a generic timeout.
- tests: guard send_hitl_response pickle-marker stripping and add get_runtime_status coverage.
* fix(durabletask): wait indefinitely for HITL responses, matching core
The durable workflow host previously raced HITL responses against a 72h timer and failed the orchestration on elapse. MAF core's request_info has no timeout concept (it waits for the response), and the .NET durable host waits too, so the durable Python host now does the same: it stays paused until a response arrives. Removes the hitl_timeout_hours parameter and DEFAULT_HITL_TIMEOUT_HOURS constant from both hosts. A configurable timeout can be added later once core defines the contract (what happens on elapse).
* feat(durabletask): typed workflow event streaming and async client API
Add a brokerless workflow event stream to the durable host. Each non-agent executor runs inside a durable activity that captures its real WorkflowEvents (with data payloads); the orchestrator replays them into the orchestration custom status after each superstep, and the client streams them back as typed WorkflowEvent objects with reconstructed data. Agent executors contribute synthesized invoked/completed lifecycle events.
Add async client methods run_workflow (start with optional wait) and stream_workflow (typed event iterator), plus is_replaying plumbing through the orchestration context protocol and both host adapters so live status is published only on non-replay execution.
* docs(samples): standalone durabletask workflow streaming sample
Add sample 10_workflow_streaming demonstrating the async DurableWorkflowClient API on a standalone Durable Task worker: run_workflow(wait=False) to start without blocking, then stream_workflow to consume typed WorkflowEvent objects as a WriterAgent -> ReviewerAgent -> publish pipeline runs.
* refactor(durabletask): internal-only checkpoint codec and host-scoped workflow event streaming
Two related hardening changes to the durable workflow hosting layer, plus a
rebase-restored improvement.
Internal-only serialization codec (MSRC follow-up):
- Rename serialize_value/deserialize_value -> _serialize_value/_deserialize_value
in the shared durabletask serialization module and update all call sites, so the
pickle-backed checkpoint codec is unambiguously framework-internal. Untrusted
input is still neutralized with strip_pickle_markers at the HTTP boundary.
- Remove the duplicate agent_framework_azurefunctions._serialization module and
import strip_pickle_markers from the shared durabletask module instead. Move its
unique serialization/strip-marker tests into the durabletask test suite.
Scope workflow event streaming to hosts that can carry it:
- Add WorkflowOrchestrationContext.supports_event_streaming. The standalone
DurableTask host returns True (no custom-status size cap, has a stream_workflow
consumer); the Azure Functions host returns False.
- The orchestrator now accumulates and publishes the WorkflowEvent timeline to the
orchestration custom status only when the host supports streaming. On Azure
Functions the custom status returns to its pre-streaming shape
({state[, pending_requests]}), which fixes orchestrator failures with
"The size of the JSON-serialized payload must not exceed 16 KB" and stops leaking
pickle markers into the HTTP status response. The Azure Functions status endpoint
never consumed the event stream.
Workflow start endpoint:
- Accept text/plain raw request bodies (fall back from get_json to the raw body),
restoring an improvement from main that the rebase conflict resolution dropped.
* fix(azurefunctions): scope workflow status/respond endpoints to the workflow orchestrator
The workflow/status/{instanceId} and workflow/respond/{instanceId}/{requestId}
HTTP endpoints resolved durable instances by ID only. The durable client looks up
IDs across every orchestration in the task hub (agent entities, any
user-registered orchestrations, and other apps sharing the hub), so a caller
holding one instance ID could read another orchestration's status -- including
pending HITL request payloads -- or inject external events into it.
Add AgentFunctionApp._is_workflow_orchestration() and gate both endpoints on it:
an instance whose orchestration name is not WORKFLOW_ORCHESTRATOR_NAME now returns
404 instead of leaking state or accepting events. send_hitl_response now fetches
the orchestration status and validates ownership before raising the external
event. Legitimate workflow instances are unaffected.
Mirrors the .NET fix in PR #6608.
* fix(durabletask): resolve CI typing failures
- serialization: rename _serialize_value/_deserialize_value back to
serialize_value/deserialize_value to follow the package convention for
cross-module internal helpers (matches strip_pickle_markers, resolve_type).
The leading underscore tripped pyright reportPrivateUsage on cross-module
imports under the strict source gate; internal-only status is preserved by
not exporting them from the public API.
- Remove type-ignore comments pyright flags as unnecessary
(reportUnnecessaryTypeIgnoreComment) in _worker.py, orchestrator.py,
serialization.py.
- test_08_dt_workflow: add AgentClientFactoryProtocol and annotate the
agent_client_factory fixture as type[AgentClientFactoryProtocol] (matching
test_01-07) so mypy/ty stop reporting "type has no attribute create".
- samples (08_workflow, 09_workflow_hitl): pass structured output via
FoundryChatOptions[Any](response_format=...) instead of a plain dict so the
samples pyright (basic) config accepts default_options.
---------
Co-authored-by: Gavin Aguiar <80794152+gavin-aguiar@users.noreply.github.com>
* Migrate 01-get-started samples to Foundry as canonical default
Change canonical provider from Azure OpenAI to Microsoft Foundry Responses API:
Code changes:
- Updated all 01-get-started samples (01_hello_agent, 02_add_tools, 03_multi_turn,
04_memory, 06_host_your_agent) to use FoundryAgent or AIProjectClient.AsAIAgent()
- Updated environment variables: AZURE_OPENAI_* → FOUNDRY_PROJECT_ENDPOINT/FOUNDRY_MODEL
- Updated .csproj files to reference Microsoft.Agents.AI.Foundry instead of Azure.AI.OpenAI
- Added warning comments about DefaultAzureCredential production usage
- 05_first_workflow unchanged (workflow pattern only, no AI model)
Documentation changes:
- Updated AGENTS.md Default provider section to reflect Foundry as canonical
- Updated code example to use FoundryAgent constructor pattern
- Updated env var documentation
Note: 04_memory (AIContextProvider sample) extracts IChatClient from FoundryAgent
to maintain the memory pattern while using Foundry backend.
All samples verified to build successfully.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address PR 6555 review feedback and format failures
- Add Microsoft.Agents.AI.Foundry using to AGENTS.md Foundry snippet
- Update verify-samples GetStarted env vars to FOUNDRY_PROJECT_ENDPOINT/FOUNDRY_MODEL
- Remove unnecessary usings flagged by dotnet format in 01_get_started samples
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Switch 01-get-started samples from FoundryAgent to AIProjectClient.AsAIAgent()
Use AIProjectClient.AsAIAgent() as the canonical pattern for all 01-get-started
samples. Reserve FoundryAgent only for samples that specifically demonstrate the
Foundry-managed (prompt) agent — i.e. 02-agents/AgentsWithFoundry/.
Changes:
- 01_hello_agent, 02_add_tools, 03_multi_turn, 06_host_your_agent: swap
FoundryAgent constructor for AIProjectClient.AsAIAgent(model, instructions)
- 04_memory: get IChatClient via AIProjectClient.AsAIAgent(options).GetService()
instead of extracting from a throwaway FoundryAgent
- AGENTS.md: update default-provider snippet and note on when to use FoundryAgent
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix auto function calling stripping explicit null arguments (fixes#5934)
* fix: re-role trailing assistant message to user for Anthropic (fixes#5008)
* fix: address Copilot review feedback (exclude_unset, test coverage, synthetic user turn)
* fix: update docstring and extend exclude_unset to auto_invoke_function
* revert: remove unrelated core _tools.py changes from Anthropic PR
The exclude_none/exclude_unset changes in the core package are out of scope
for this Anthropic-specific fix. This PR now only contains the Anthropic
chat client docstring fix and the synthetic user turn append.
* fix: avoid appending user turn after Anthropic tool use
* Fix Anthropic tool-use type narrowing
Use object-typed content narrowing before checking Anthropic tool-use block types so strict Pyright no longer treats dynamic message content as Unknown.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Evan Mattson <evan.mattson@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Scope workflow status/respond endpoints to route workflow.
Validate that the orchestration instance belongs to the workflow
named in the route. Prevents cross-workflow access via runId.
* Add changelog.
* Address Copilot review feedback: fix duplicate XML doc, make IsOrchestrationOwnedByWorkflow non-throwing, drop misleading Async suffix in test name
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: feat(evals): RubricScore type + EvalScoreResult.Dimensions
Adds the core rubric-evaluator surface that mirrors the Python work in
PR #6101 (commit e45b934cc). Provider-agnostic types only — no Foundry
coupling. Subsequent commits will wire these into FoundryEvals.
- RubricScore: per-dimension score record (Id, Score?, Applicable, Weight, Reason).
- EvalScoreResult.Dimensions: optional init-only list of RubricScore.
Null for non-rubric (built-in) evaluators.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: feat(evals): GeneratedEvaluatorRef + assertion helpers
Adds the provider-agnostic surface for referencing a pre-existing rubric
evaluator and gating CI on per-item / per-dimension thresholds. Mirrors
Python PR #6101 commits e5830dd7f (ref type) and 4bc60462d (asserts).
- GeneratedEvaluatorRef: name + optional version/display-name, plus a
Latest(name) factory for versionless refs (discouraged for CI; consumers
should warn at run time).
- AgentEvaluationResults.AssertScoreAtLeast: walks DetailedItems[].Scores,
optionally filtered by evaluator name, recurses into SubResults.
- AgentEvaluationResults.AssertDimensionScoreAtLeast: walks each score's
Dimensions list, skips non-applicable dimensions by default, supports
requireApplicable to flip that, recurses into SubResults.
- AgentEvaluationResults.AssertNoFailedItems: walks DetailedItems for
fail/error statuses, recurses into SubResults.
All helpers throw InvalidOperationException (matches existing AssertAllPassed).
Truncates offender lists to the first 5 with a '+N more' suffix to keep
CI output readable, mirroring the Python helpers.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: feat(foundry-evals): accept GeneratedEvaluatorRef in evaluators=
Adds FoundryEvaluatorSpec, a readonly-struct union with implicit conversions
from both string and GeneratedEvaluatorRef so call sites can mix built-in
evaluator names with rubric evaluator references:
var evals = new FoundryEvals(
projectClient, model,
new GeneratedEvaluatorRef("policy-rubric", "3"),
FoundryEvals.Relevance,
FoundryEvals.Coherence);
FoundryEvals constructors (3 overloads), EvaluateTracesAsync, and
EvaluateFoundryTargetAsync now take FoundryEvaluatorSpec[]/params instead of
string[]/params. Existing call sites using string literals or string[] keep
working unchanged via implicit conversion.
FoundryEvalConverter.BuildTestingCriteria emits the documented Foundry wire
format for rubric refs:
{
"type": "azure_ai_evaluator",
"name": <DisplayName ?? Name>,
"evaluator_name": <Name>,
"evaluator_version": <Version>, // omitted when null
"initialization_parameters": { "deployment_name": <model> },
"data_mapping": { conversation arrays, optional tool_definitions }
}
WireTestingCriterion gains an optional EvaluatorVersion field. Rubric refs
are preserved through FilterToolEvaluators (tool-aware but not tool-required)
and ignored by FindMissingGroundTruthEvaluators. A versionless ref emits a
Trace.TraceWarning at criterion-build time so CI authors notice the floating
version (mirrors the Python warning).
Adds 6 new Foundry unit tests (3 BuildTestingCriteria rubric paths, 1
FindMissingGroundTruthEvaluators, 1 FilterToolEvaluators preservation, 1
mixed-order). 369/369 Foundry tests pass.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: feat(foundry-evals): parse rubric dimension_scores into RubricScore
Adds FoundryEvals.ParseRubricScores, called per result inside ParseDetailedItem.
Each EvalScoreResult now populates Dimensions when the evaluator's sample carries
a rubric breakdown.
Accepts three shapes for forward compatibility with provider SDK iterations:
1. sample.properties.dimension_scores (canonical Foundry runtime shape)
2. sample.properties.rubric_scores (preview/legacy key)
3. top-level sample.dimension_scores / sample.rubric_scores (defensive fallback)
Entries missing 'id', 'weight', or 'applicable' are skipped without invalidating
well-formed siblings. Non-applicable dimensions may omit 'score' (parsed as null).
Adds 6 unit tests covering canonical and legacy keys, top-level fallback, no-match
returns null, malformed-entry skipping, and the non-applicable null-score path.
375/375 Foundry tests pass.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: feat(samples): Evaluation_FoundryRubric end-to-end sample
Adds dotnet/samples/05-end-to-end/Evaluation/Evaluation_FoundryRubric mirroring
the Python evaluate_with_rubric_sample.py:
- Fetches a pre-existing Foundry agent via AgentAdministrationClient
(GetAgentAsync for latest, GetAgentVersionAsync when FOUNDRY_AGENT_VERSION
is pinned).
- References a rubric evaluator by GeneratedEvaluatorRef(name, version);
falls back to GeneratedEvaluatorRef.Latest(name) with the documented
floating-version warning.
- Mixes the rubric with FoundryEvals.Relevance and FoundryEvals.Coherence
in a single FoundryEvals run (implicit string-and-ref conversion).
- Prints per-dimension breakdowns from EvalScoreResult.Dimensions for each
item.
- Demonstrates a CI quality gate with AssertDimensionScoreAtLeast("general_quality", 3.0).
Documents the FOUNDRY_PROJECT_ENDPOINT footgun (must be project-scoped URL
.../api/projects/<project>, not the bare Azure OpenAI endpoint) and the
Eval-Definition-vs-Rubric-Evaluator distinction in the README. Ships a
.env.example with the FOUNDRY_* variables.
Registers the project in agent-framework-dotnet.slnx and cross-links from
the sibling Evaluation_Multimodal / Evaluation_ExpectedOutputs READMEs.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(foundry-evals): harden FoundryEvals public surface for review
Address PR #6267 review comments on the .NET FoundryEvals integration:
- Add source-compat overloads accepting `string[] evaluators` for `FoundryEvals` ctor, `EvaluateTracesAsync`, and `EvaluateFoundryTargetAsync` so existing callers passing string arrays keep compiling unchanged. New overloads forward via a private `ToSpecs` helper that wraps each name through the implicit `string -> FoundryEvaluatorSpec` conversion.
- Guard against `default(FoundryEvaluatorSpec)` entries (both `BuiltinName` and `GeneratedRef` null) that would NRE the downstream converter. Adds `FoundryEvaluatorSpec.IsValid` / `EnsureValid` plus an internal `EnsureAllSpecsValid` helper, wired into the main ctor and both static evaluation entry points.
- Add 6 unit tests covering the new validation surface.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(sample): set ExitCode=1 when rubric dimension gate trips
PR #6267 review comment: the FoundryRubric sample swallowed the AssertDimensionScoreAtLeast failure, so a CI run that included it as a quality gate would still exit 0 even when the rubric regressed. Set `System.Environment.ExitCode = 1` in the catch so CI fails while still letting the rest of the sample's logging complete cleanly.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(foundry-evals): search typed Sample directly for rubric scores
PR #6267 review comment: `_extract_rubric_scores` only searched the `properties` dict when the sample exposed one. When the Azure AI Projects typed SDK returns a Sample object that puts `dimension_scores` / `rubric_scores` directly on the instance (no `properties` wrapper), we missed them and surfaced no per-dimension scores.
Add an `else: containers.append(sample)` branch so non-dict typed samples are also inspected for the score keys. Covered by two new tests: one with `dimension_scores` directly on a typed Sample without a `properties` wrapper, and one with the legacy `rubric_scores` key in the same shape.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* test(evals): cover assert_score_at_least and assert_no_failed_items
PR #6267 review comments: both assertion helpers shipped without unit tests. Add `TestAssertScoreAtLeast` (above threshold, below w/ offenders, evaluator filter, sub_results recursion) and `TestAssertNoFailedItems` (all passing, failed/errored statuses, sub_results recursion) with a shared `_score_results` fixture builder.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* docs(samples): remove dead rubric-evaluator doc link from FoundryRubric sample
The Azure AI Foundry rubric evaluator concept doc page has not yet been published, so the link in the sample README and Program.cs comment 404s. Drop the references until the upstream doc is live.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* Address PR 6267 review nits
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>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* Port FileMemoryProvider to python and integrate it and FileAccessProvider into the harness
* Address PR comments
* Address PR comments
* Create FileSystemAgentFileStore root lazily on first write
Construction no longer calls mkdir, so building a store (and therefore a
default create_harness_agent, which wires default file-memory and file-access
stores under the CWD) performs no filesystem writes and does not fail in
read-only working directories. The root directory is created on the first
write_file / create_directory call; all read/list/search operations already
tolerate a missing root. Updates docstrings and adds a regression test.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix typing
* Fixing typing errors
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: Split type checkers by target (pyright source, 5 checkers on tests/samples)
Rework the typing setup along the lines of the 'too many type checkers'
approach:
- Pyright (strict) is now the sole source-code type checker; mypy is
removed from source and its [tool.mypy] block becomes a relaxed profile
used only for tests/samples.
- Tests are checked by all five checkers (pyright relaxed, mypy, pyrefly,
ty, zuban); samples by pyright, pyrefly, and ty. All run in a relaxed/
basic profile so authors aren't forced into over-annotation.
- Add pyrightconfig.tests.json and bump sample pyright configs to basic.
- Unify test/sample typing onto the same parallel fan-out used by source
pyright via run_command_items in task_runner.py.
- Make version-conditional imports symmetric: keep or drop the
'# type: ignore' on both branches so results match across interpreter
versions (local vs CI).
- Update SKILL.md, DEV_SETUP.md, and CODING_STANDARD.md for the five
gating checkers and pyright on source+tests+samples.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: Fix merge regressions from main (typing + runtime)
Merging main into the type-checker split branch surfaced regressions that
the new five-checker test suite and unit tests caught:
Runtime fixes:
- anthropic: restore the dropped `cache_read_input_token_count` mapping in
_parse_usage_from_anthropic (lost during merge conflict resolution).
- gemini: _get_function_calling_mode test helper returned str(enum)
('FunctionCallingConfigMode.AUTO') instead of the enum value ('AUTO').
- openai: _response_id_from_token test helper was an infinite self-recursion;
return token['response_id'].
- orchestrations: reset output_events per approval iteration so the terminal
output assertion counts only the final run.
- core: drop a stale duplicate harness test whose message ('non-negative')
contradicted the source ('positive').
- purview: import PolicyLocation/PolicyScope/ProtectionScopeActivities/
ExecutionMode used by the processor tests.
Type-checker fixes (tests, relaxed profile):
- core: pyright/mypy/pyrefly/ty/zuban green-ups across the harness, MCP,
observability and types tests.
- anthropic/openai: route provider-namespaced UsageDetails keys through a
dict cast (extra_items TypedDict unsupported by mypy/ty).
- purview: typed model constructors and cache-mock casts.
- ag-ui: annotate WorkflowContext[Any, Any] so yield_output accepts test
payloads, guard Optional forwarded_props, and ty-ignore intentional bad args.
Source pyright (sole source checker) flagged unnecessary ignores newly
introduced by merged code in core _tools.py and declarative _declarative_base.py.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: Isolate per-package mypy cache in test-typing fan-out
The parallel test-typing fan-out runs many mypy processes concurrently,
all defaulting to a single shared ./.mypy_cache. Concurrent writes corrupt
the cache and mypy aborts with INTERNAL ERROR (intermittently, depending on
worker timing) -- which is why CI's Test Typing job failed on a shifting set
of packages while a single-package run was fine.
Give each mypy invocation an isolated cache dir keyed by its target paths so
incremental caching still works per package without races. Other checkers
(zuban/pyrefly/ty/pyright) maintain their own caches and are unaffected.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: Make lab pyright-only on source (drop source mypy)
Lab was the last package still running mypy on its source code, requiring
mypy-only `# type: ignore` comments that pyright (the sole source checker
everywhere else) flags as unnecessary. Align lab with the rest of the
monorepo:
- Remove the lab source mypy poe tasks (mypy-gaia/lightning/tau2) and the
now-dead strict [tool.mypy] config block.
- Drop the 'Run lab mypy' CI step; lab source is type-checked by pyright only.
Lab tests remain covered by the workspace test-typing fan-out (mypy, pyrefly,
ty, zuban, pyright over tests using the relaxed root config).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: Fix test-typing regressions from latest main merge
A fresh merge from main brought in new test code never run under the
five-checker test-typing suite. Green up across the affected packages:
- core: narrow Optional span.attributes with 'and' guards in span filters
and assert+cast the json.loads(...attributes[...]) reads (test_observability);
match the existing as_agent ignore on the protocol-typed fixture (test_clients).
- openai: align new streaming tests with the established chat_options dict
pattern (ChatOptions TypedDict isn't assignable to dict), route Optional
.annotations[0] access through a small _first_annotation helper (mirrors the
file's assert-not-None convention), and annotate a mapped ResponseStream.
- foundry_hosting: annotate error: dict[str, Any] = body.get(...) or {}
(zuban needs the annotation).
- foundry: narrow ignores for the live AIProjectClient credential arg (pyrefly)
and connections.get_default (zuban) SDK type gaps.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* updated pyright version
* pyright fix
* Python: Fix source typing for pyright 1.1.410
Pyright 1.1.410 tightened several checks. Apply the same source fixes as
upstream PR #6275:
- anthropic: import AsyncAnthropicBedrock from anthropic.lib.bedrock and
AsyncAnthropicVertex from anthropic.lib.vertex (no longer re-exported from
the anthropic top-level package -> reportPrivateImportUsage).
- core _types.py: cast the transform-hook result to UpdateT (reportAssignmentType).
- core _workflows/_events.py: annotate the @contextmanager helper as
Generator[None] instead of Iterator[None] (reportDeprecated).
- redis: build the combined filter expression with an explicit loop instead of
reduce(and_, ...), which pyright could no longer fully type (drops the now
unused functools.reduce / operator.and_ imports).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: Accept plain-text body in Azure Functions workflow/run endpoint
The workflow_orchestrator already accepts plain strings as well as JSON
objects via context.get_input(), but the start_workflow_orchestration HTTP
handler only accepted JSON and returned 400 for any non-JSON body. This made
the functions integration tests that POST text/plain to /api/workflow/run
(e.g. test_09_workflow_shared_state) fail consistently with 400 != 202.
Fall back to the raw request body (decoded as UTF-8) when the body is not
JSON, rejecting only a truly empty body. The JSON path is unchanged.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Bump Azure.AI.Projects to 2.1.0-beta.3
Updates Azure.AI.Projects from 2.1.0-beta.2 to 2.1.0-beta.3, together with the transitive Azure.Core (1.56.0 to 1.57.0) and System.ClientModel (1.12.0 to 1.13.0) pins that beta.3 requires (beta.3 forces System.ClientModel 1.13.0.0 via Azure.Core 1.57.0).
Migrates the affected samples and integration test to the beta.3 surface:
* MemorySearch sample: MemorySearchToolCallResponseItem renamed to MemorySearchToolCall, Results renamed to Memories, MemoryItem indirection removed.
* AgentSkills sample: skill provisioning/download API redesigned to a version based model (CreateSkillVersionFromFiles, GetSkillContent which now downloads and unzips), removing manual ZIP handling.
* Session files integration test: GetSessionFilesAsync now returns an async collection of SessionDirectoryEntry and renames the sessionId parameter to agentSessionId.
* Stream session file listing and short-circuit in integration test
Avoids materializing the entire session directory listing into a List. The test now streams GetSessionFilesAsync and breaks as soon as the expected entry is found, then asserts it was located.
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Adding an observer to the python harness for web search tools
* Escape dynamic strings with rich.markup.escape() in WebSearchDisplayObserver
Apply rich.markup.escape() to all user/tool-provided strings (queries, URLs,
titles, patterns) before interpolation into Rich-markup-enabled output. This
prevents characters like '['/']' from being interpreted as Rich markup tags.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Ensure an argument-scoped standing approval (the "always approve with exact
arguments" path) records an empty argument set rather than null when the
approved call has no arguments, so it matches only future no-argument calls.
null remains reserved exclusively for tool-level approvals, keeping the two
scopes distinct. This aligns the .NET behavior with the existing Python harness.
Adds regression tests covering the no-argument standing-approval flow, the
MatchesRule argument-scoping semantics, and empty-arguments rule serialization.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The Hosted-AgentSkills sample and its mirrored unit-test helper gated ZIP
extraction on `StartsWith(destinationRoot)` OR `Equals(destinationRoot)`. The
second branch left an acceptance path not covered by the containment check, so
static analysis could not prove the extraction sink stays within the
destination. Make the single resolved-path StartsWith check the only gate to
extraction in both files and add a nested-entry regression test.
Closes#6564
Selective, CHANGELOG-driven version bumps for the 2026-06-18 release.
Released tier: agent-framework-core and the root agent-framework go to 1.9.0
(minor). Core ships new public APIs (agent-loop middleware, tool-approval
middleware and harness integration, shell-tool harness integration, AG-UI
thread snapshot persistence, context-provider telemetry) plus two behavioral
breaking changes on evolving surfaces: MCP sampling now denies server-initiated
requests by default, and the FileAccess tools were aligned with the .NET
implementation. These are treated as within-1.x changes because every package
caps core at <2; a major bump would require rewriting those caps. The foundry
and openai packages go to 1.8.2 (patch, bug fixes only). The root
agent-framework-core[all] pin was moved to 1.9.0 in lockstep with core.
Release-candidate tier: ag-ui to 1.0.0rc5 and declarative to 1.0.0rc2 for their
respective changes. orchestrations is promoted to stable 1.0.0; PACKAGE_STATUS
and the README install hint were updated accordingly.
Prerelease tier (new Pacific date stamp 260618): anthropic (beta),
azure-contentunderstanding (alpha) and foundry-hosting (alpha). No beta cohort
bump was applied; only packages with changes this cycle were stamped.
Dependency floors: following the established convention, the core floor was
raised to >=1.9.0 on every non-core package bumped this cycle, preserving the
existing <2 upper bound.
Also resolves two pre-existing failures in the dependency-bounds validator that
are unrelated to the version bumps. Hosted-environment detection now catches a
bare ImportError so optional Foundry hosting probing cannot crash user-agent
setup. The harness shell-tool integration, which lazily imports the separate
agent-framework-tools package to avoid a circular runtime dependency, is now
type-checked and tested in isolated environments via a core dev
dependency-group, with the shell-tool tests guarded to skip when that package
is absent.
* Refactor DocumentEntry model and update result handling
- Changed the type of `result` in DocumentEntry from dict to str to store LLM-ready text.
- Introduced `search_payload` in DocumentEntry for optional alternate rendering.
- Updated FileSearchConfig to include `include_fields` option for vector store uploads.
- Modified tests to reflect changes in DocumentEntry and FileSearchConfig.
- Adjusted integration tests to validate new result structure and rendering.
- Removed legacy format_result tests as rendering is now handled by the SDK.
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* Add test to ensure page markers are preserved in LLM input
Co-authored-by: Copilot <copilot@github.com>
* fix(cu-context-provider): scope LLMStats telemetry filter to rai_warnings block
Address PR #5796 review comment: the previous defensive scrubber ran a global regex substitution over the full rendered string, so any markdown body bullet shaped like '- LLMStats: ...' would also be silently deleted.
Add a _strip_rai_telemetry helper that confines the substitution to the front-matter rai_warnings: YAML sub-block, leaving the body verbatim. Cover the new behavior with three tests (scoped strip, body preservation, and no-op branches).
* Sync uv.lock with azure-ai-contentunderstanding>=1.2.0b1 dependency bump
* Python: Drop search_payload/include_fields, single to_llm_input rendering (CU context provider)
Address PR #5796 review: remove the redundant search_payload field and _render_search_payload helper, drop the include_fields opt-in (already covered by output_sections), rename _resolve_pending_tokens -> _resolve_pending_analysis, and have _upload_to_vector_store read entry['result'] directly.
* Python: Adopt SDK 1.2.0b2 LLMStats filtering, drop local workaround (CU context provider)
azure-ai-contentunderstanding 1.2.0b2 filters LLMStats telemetry from rai_warnings and emits InputPageNumber page markers in to_llm_input, so the provider's local defense is redundant.
- Bump dependency to azure-ai-contentunderstanding>=1.2.0b2 (re-lock uv.lock)
- Remove _strip_rai_telemetry and its two regexes; _render_for_llm now returns to_llm_input(...) directly
- Delete 4 workaround unit tests for the removed helper
---------
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: changjian-wang <v-changjwang@microsoft.com>
Co-authored-by: aluneth <wangchangjian1130@163.com>
* scope MCP threadId to the current agent
* Fix Async suffix on test methods and add CHANGELOG entries
- Rename three test methods to include Async suffix (IDE1006 fix)
- Add CHANGELOG entries for DurableTask and Hosting.AzureFunctions
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(dotnet): Add LocalCodeAct package scaffold
Create Microsoft.Agents.AI.LocalCodeAct package with:
- Project file with embedded Python resources
- ExecutionMode enum (Subprocess only)
- ProcessExecutionLimits record
- FileMount record and FileMountMode enum
- README.md documentation
- Embedded Python runner and validator scripts
This is the .NET equivalent of the Python agent-framework-local-codeact
package. Next: Implement process bridge and tool integration.
* feat(dotnet): Add embedded Python runner and validator
Copy Python runner and validator scripts from the Python implementation
as embedded resources for the .NET package.
* feat(dotnet): Add CodeValidator wrapper
Implement CodeValidator.cs that:
- Extracts embedded Python validator script to temp file
- Invokes Python validator with JSON request
- Passes custom allow/block lists
- Throws CodeValidationException on failures
- Cleans up temp files
Uses the embedded Resources/validator.py for AST validation.
* feat(dotnet): Add LocalExecuteCodeFunction
Implement LocalExecuteCodeFunction as AIFunction:
- Accepts Python executable path (required)
- Registers host tools for code to call
- Validates code via CodeValidator if custom lists provided
- Executes via ProcessBridge
- Converts result dict to ChatMessage list
- Builds dynamic description including available tools
Matches Python LocalExecuteCodeTool functionality.
* feat(dotnet): Add LocalCodeActProvider
Implement AIContextProvider that:
- Injects execute_code tool into context
- Adds CodeAct instructions
- Enforces single-provider-per-agent via StateKeys
- Wraps LocalExecuteCodeFunction lifecycle
Minimal provider implementation matching Python LocalCodeActProvider.
* feat(dotnet): Add tests and sample for LocalCodeAct
Add unit tests:
- LocalExecuteCodeFunctionTests (4 tests)
- ProcessExecutionLimitsTests (2 tests)
- FileMountTests (2 tests)
Add sample:
- LocalCodeAct/Program.cs - Demonstrates provider and function usage
- LocalCodeAct/README.md - Documentation and safety warnings
Tests verify basic construction, metadata, and disposal.
Sample shows provider creation, function setup, and configuration.
Note: Build requires .NET 10 SDK per global.json.
* feat(dotnet): Add LocalCodeAct sample project
Add sample demonstrating:
- LocalCodeActProvider creation and configuration
- LocalExecuteCodeFunction direct usage
- Execution modes and file mount configuration
- Safety warnings and prerequisites
Includes project file and README with security guidance.
* feat(dotnet): Add file mount support and integration tests
- Added FileMountHelper.cs for file mount normalization, snapshot, and capture
- Updated LocalExecuteCodeFunction to support file mounts parameter
- Added file snapshot before/after execution with capture logic
- Updated LocalCodeActProvider to pass file mounts through
- Created comprehensive IntegrationTests.cs with 10 test cases:
- Simple code execution
- Timeout handling
- Syntax error handling
- Blocked import validation
- Blocked builtin validation
- Custom allowed imports
- File mount read/write with capture
- Stdout capture
- Provider tool injection
All features from Python implementation now ported to .NET.
* Rewrite .NET LocalCodeAct to address all PR review comments
Complete rewrite that follows the Hyperlight package conventions
(see Microsoft.Agents.AI.Hyperlight) and addresses all 24 review
comments on PR #6105:
Architectural fixes:
* LocalCodeActProvider now uses options-class constructor pattern
matching HyperlightCodeActProvider.
* Override of ProvideAIContextAsync uses the correct
(InvokingContext, CancellationToken) signature returning
ValueTask<AIContext>.
* ExecuteCodeFunction follows the AIFunction Name/Description/JsonSchema
property pattern with InvokeCoreAsync override.
* Provider exposes AddTools/GetTools/RemoveTools/ClearTools and
AddFileMounts/GetFileMounts/RemoveFileMounts/ClearFileMounts CRUD
methods, with snapshot-at-invocation semantics under a lock.
Runtime/security fixes:
* Subprocess IPC uses JsonObject/JsonNode end-to-end (no
Dictionary<string, object?> casts that broke under JsonElement
deserialization).
* Validator runs in its own subprocess with a dedicated timeout
(ProcessExecutionLimits.ValidationTimeoutSeconds), never reuses
the runner script.
* Validation enabled by default; can be opt-ed out via
ValidationEnabled = false.
* validator.py has a __main__ entrypoint that reads JSON from
stdin and exits with structured errors.
* validator.py is now compatible with Python 3.9+ (Match nodes
added conditionally).
* call_id parsed as long to match Python id(kwargs) range.
Other:
* README rewritten with valid C# syntax (options-class, FileMount
constructor) and accurate descriptions of validator and file
capture behavior.
* Added integration tests that exercise the real subprocess and
validator (skipped gracefully when python3 is not on PATH).
* All 18 tests pass (15 unit + 3 integration) across net8/net9/net10.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Sync embedded validator.py with Python package allow-list enforcement
The embedded Python validator script used by the .NET LocalCodeAct
package now enforces the builtin allow-list, matching the latest
behavior of agent_framework_local_codeact._validator. Names that are
real Python builtins must appear in the allow-list, while unknown names
(user-defined functions, registered tools) remain allowed.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add Hosted-LocalCodeAct foundry hosted-agent sample
Mirrors the Python foundry_hosted_agent.py sample for the local-codeact
package: registers compute and fetch_data as sandbox-only host tools on
LocalCodeActProvider so the model only sees execute_code and reaches them
via await call_tool(...). Includes the standard hosted-agent supporting
files (agent.yaml, agent.manifest.yaml, Dockerfile, Dockerfile.contributor,
.env.example, README.md) and installs python3 in the container images so
the embedded runner and validator can execute.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(local-codeact-dotnet): sync validator os.* allow-list with Python
Mirror the Python package change: the embedded validator.py invoked by the
.NET ProcessBridge replaces the os.* deny-list with an allow-list of
{environ, path}. Add allowed_os_attrs parameter to validate_code and
_CodeValidator, and surface it via the stdin JSON request schema so the
.NET host can opt in to a broader allow-list when needed.
Default behavior tightens to match the documented contract: any os.*
attribute outside {environ, path} (for example os.listdir, os.open,
os.getcwd) is rejected.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(local-codeact-dotnet): address review + tighten validator
- validator.py: enforce os.* allow-list on `from os import X` so names like
`system`, `getcwd` cannot bypass the visit_Attribute restriction.
- ProcessBridge.ConfigureEnvironment: document that null Environment inherits
the parent env (matching real behavior) and update the public
LocalCodeActProviderOptions.Environment doc to describe the explicit
empty-dictionary opt-in for a scrubbed environment.
- Tests:
* FileMountHelperTests covers per-file, per-mount, and total
capture-limit branches that return TextContent omissions.
* Integration tests cover unknown-tool dispatch error, tool throwing
exception, and CodeValidator timeout that kills the process and
raises CodeValidationException.
- Sample: drop unused `Microsoft.Agents.AI.Foundry` using in
Hosted-LocalCodeAct/Program.cs to satisfy IDE0005 check-format.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore(local-codeact-dotnet): remove stale orphan sample
The dotnet/samples/LocalCodeAct/ scaffolding sample referenced APIs
that don't exist in the current package (`ExecutionMode`, FileMount
object-initializer syntax, the old LocalExecuteCodeFunction
constructor signature, function.Metadata.*), produced a long list of
check-format violations (CHARSET, IMPORTS, IDE0073 header, IDE0005
unused using, IDE1006 Async suffix, RCS1037 trailing whitespace), and
did not match any of the documented sample layouts.
The hosted-agent example at
dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-LocalCodeAct
is the supported entry-point sample for this package.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* style(local-codeact-dotnet): satisfy check-format rules
- Add UTF-8 BOM to source files (CHARSET)
- Remove unused using directives (IDE0005)
- Simplify type names (IDE0001/IDE0002/IDE0090)
- Rename static field JsonOptions -> s_jsonOptions (IDE1006)
- Rename static field SyncRoot -> s_syncRoot (IDE1006)
- Add missing this. qualifications in ProcessBridge (IDE0009)
- Remove unused _options field from LocalCodeActProvider (IDE0052)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(local-codeact-dotnet): wire hosted sample into solution
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(local-codeact-dotnet): sync embedded Python scripts
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(local-codeact-dotnet): exercise Python integration on Windows
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Address LocalCodeAct API review feedback
Move the required Python executable path to LocalCodeAct constructors, invert the validation flag default, and apply small project/file mount cleanup suggestions.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Address LocalCodeAct concurrency review
Surface unauthorized mount traversal errors and use concurrent provider registries for LocalCodeAct tool and file mount CRUD operations.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Simplify LocalCodeAct function wrappers
Use AIFunctionFactory-created inner functions for LocalCodeAct execute_code wrappers and remove redundant script cache and JsonNode cloning logic.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Update LocalCodeAct factory result tests
Handle JsonElement result values produced by AIFunctionFactory delegation in LocalCodeAct execute_code integration tests.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix render issue for tools that are streamed in parts.
* Address PR review: missing call_id fallback, empty-mapping args, _is_complete perf
- Print call_id-less function calls as-is instead of merging under a name-derived
key (which could drop distinct unnamed calls).
- Preserve an empty {} mapping rather than coercing it to None.
- Add a structural bracket-balance gate before json.loads in _is_complete to
avoid O(n^2) re-parsing of growing streamed arguments.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Remove unsupported as_agent config parameter
Fixes#6313
Remove the unsupported function_invocation_configuration parameter from BaseChatClient.as_agent(), which currently forwards an invalid kwarg into Agent.__init__(). This keeps the existing TypeError behavior for callers but changes the error source to the public API boundary, which we do not consider a breaking change.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix sample
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Rebuild Hyperlight sandbox after tool registry updates
Track provider tool registry updates in Hyperlight run snapshots so subsequent executions rebuild the sandbox after AddTools replaces registered tools.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Strengthen Hyperlight registry replacement test
Add provider-level coverage that same-name AddTools replacement changes the captured execute_code snapshot fingerprint.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Use Guid for Hyperlight registry version
Use a Guid token for Hyperlight tool registry generations to avoid overflow concerns in long-lived providers.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Fix Hyperlight Guid test import
Add the missing System import required by the Guid-based Hyperlight fingerprint test.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add an optional Func<JsonElement?, AIFunctionArguments> argument marshaler to inline and class-based skills so callers can customize how raw JSON tool-call arguments are converted into AIFunctionArguments before delegate invocation. This enables handling backends (e.g. vLLM) that send tool-call arguments as a JSON string instead of a JSON object. The marshaler can be supplied at the script, inline-skill, or class-skill level; when omitted, the existing strict JSON-object behavior is preserved unchanged.
Co-authored-by: SergeyMenshykh <SergeMenshikh@outlook.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Improve PR template and breaking-change label automation
- Add a structured "Related Issue" section using GitHub closing keywords
- Add a Review Guide prompt (major changes, impact, reviewer focus) with a
note that the focus item is for human reviewers only
- Add checklist items for issue linkage / no duplicate PRs and invert the
breaking-change item (checked = not breaking)
- Extend label-title-prefix to prepend [BREAKING] when the "breaking change"
label is added
- Add label-breaking-change workflow to apply the "breaking change" label
when a PR title contains [BREAKING]
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add pull-requests agent skill with dotnet/python links
- Add root .github/skills/pull-requests/SKILL.md covering PR description
authoring (following the PR template) and the review-comment workflow
(review -> plan -> user review -> implement -> reply to all -> resolve)
- Symlink the skill from python/.github/skills and dotnet/.github/skills
- Reference the skill from python/AGENTS.md and dotnet/AGENTS.md
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fold breaking-change labeling into label-pr workflow
Move the title -> 'breaking change' label logic into the existing label-pr
workflow (which already applies the python/.NET labels) and drop the separate
label-breaking-change workflow.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address PR title prefix review feedback
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Pin patched MessagePack for .NET restore
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Revert MessagePack central pin
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Move title prefix tests out of tracked GitHub tests
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Exclude skill docs from CI path filters
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Match skill symlinks in CI path exclusions
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Exclude AGENTS docs from CI path filters
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Scope title-prefix normalization to a real prefix
The normalization branch in addTitlePrefix matched ^Python (no colon), so
titles like "Python samples improvements" or "Pythonic refactor" were treated
as already-prefixed and only re-cased, never receiving the "Python: " prefix.
Scope the match to ^<prefix>:\s* so only an actual existing prefix is
normalized; otherwise the prefix is prepended. Same fix applies to the .NET
prefix (e.g. ".NETStandard bump").
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix ollama_chat_client.py sample: pass tools via options dict
The sample was passing tools as a direct keyword argument to
get_response(), which caused a TypeError. The tools parameter
must be passed inside the options dict per the SupportsChatGetResponse
protocol.
Fixes#6411
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Wrap tools in a list as expected by OllamaChatClient
_prepare_tools_for_ollama iterates the tools value, so it must be a
list rather than a bare FunctionTool instance.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Added CosmosOptionsHelper (in Microsoft.Agents.AI.CosmosNoSql namespace)
that sets CosmosClientOptions.ApplicationName per component, producing
wire-visible UserAgent suffixes:
- CosmosChatHistoryProvider: Microsoft.Agents.CosmosNoSql.ChatHistory/{version}
- CosmosCheckpointStore: Microsoft.Agents.CosmosNoSql.Checkpoint/{version}
This ensures Cosmos DB requests from the Agent Framework are identifiable
in telemetry, enabling usage tracking and diagnostics queries that can
distinguish between chat history and checkpoint workloads.
Addressed review feedback:
- Truncates ApplicationName to 64 chars (Cosmos SDK max length)
- Moved helper to Microsoft.Agents.AI.CosmosNoSql namespace (scoped ownership)
- Uses StringComparison.Ordinal for IndexOf call
When users provide their own CosmosClient instance, the ApplicationName
is not overridden - users retain full control.
Co-authored-by: TheovanKraay <TheovanKraay@users.noreply.github.com>
* Python: Add AgentLoopMiddleware for re-running agents in a loop
Add `AgentLoopMiddleware`, an `AgentMiddleware` that re-runs the wrapped
agent in a loop. A single configurable class covers three common patterns,
each with a convenience classmethod factory:
- Ralph loop (`.ralph(...)`): no exit criteria, with feedback tracking
(`record_feedback`/`progress`), progress injection (`inject_progress`),
optional fresh context per iteration (`fresh_context`), and an early-stop
completion signal (`is_complete`).
- Predicate (`.with_predicate(...)`): loop while a `should_continue` callable
returns True (e.g. paired with `todos_remaining`/`background_tasks_running`).
- Judge (`.with_judge(...)`): a second chat client decides whether the original
request was answered, using a `JudgeVerdict` structured-output response.
The loop also auto-resolves pending function-approval / user-input requests via
an `on_approval_request` callable (bounded by `max_approval_rounds`), and the
next iteration's input is controlled by `next_message`. Supports both streaming
and non-streaming runs.
Exports `AgentLoopMiddleware`, `JudgeVerdict`, `todos_remaining`, and
`background_tasks_running`. Adds tests, a sample, and docs.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: Refine AgentLoopMiddleware API and sample
- with_judge: add criteria list with {{criteria}} templating into judge
instructions plus an agent-side instruction; add fresh_context, additional
judge feedback relay; default judge max_iterations.
- should_continue is now required and positional; supports (bool, str|None)
feedback tuples surfaced to next_message/record_feedback via feedback kwarg.
- Judge forwards full multi-modal request and response messages.
- Default max_iterations=10 (explicit None = unbounded); removed is_complete and
Ralph terminology; ShouldContinueResult is a real TypeAlias.
- Sample: stream all loops, print iteration counts via injected user-block
boundaries (robust to function calling), <role>: content formatting, per-method
expected output, and a looping todo sample.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: Fix CI checks for AgentLoopMiddleware
- Resolve pyright errors in _loop.py: drop the always-true final_result None
check (the while loop always assigns it) and cast finish_reason to the
AgentResponse constructor's expected type.
- Apply pyupgrade --py310-plus: import TypeAlias from typing.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: Resolve mypy/pyright disagreement on finish_reason
pyright infers AgentResponse.finish_reason as including str and rejects the
direct assignment, while mypy considers a cast redundant. Drop the cast and
suppress only pyright with a targeted reportArgumentType ignore, satisfying
both type checkers.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: Add todo+judge AgentLoopMiddleware sample
Add a second AgentLoopMiddleware sample that composes two criteria in one
should_continue predicate: a TodoProvider check (evaluated first) and a
report-style judge chat client (evaluated once todos are complete) that grades
the assembled report against shared requirements. Register it in the middleware
samples README.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: Compose todo+judge loops as two middleware
Rework the todo+judge sample to compose two AgentLoopMiddleware on the agent
itself (middleware=[judge_loop, todo_loop]) instead of a single hand-written
predicate. The inner todos_remaining loop drafts the report todo-by-todo and the
outer with_judge loop re-runs it until an editor chat client judges the report
publication-ready, reusing the built-in helpers.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Reset session for fresh_context loops via snapshot/restore
AgentLoopMiddleware.fresh_context previously only reset context.messages,
so with an attached session each iteration still reloaded the local
transcript or re-threaded the service-side conversation id and the model
saw the accumulated history. Snapshot the session once before the loop
(via to_dict) and restore it (from_dict + field copy) between iterations,
so every pass starts from the pre-loop baseline. The final iteration's
pass is persisted (no restore after the terminating iteration), so a
subsequent agent.run continues from there.
Removed the obsolete warning, updated docstrings and core AGENTS.md, and
added tests: a snapshot/restore round-trip, a session-reset
streaming x fresh_context x inject_progress x store matrix across multiple
runs and loop iterations, and response_format parsing across the loop.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Updated samples and docstrings
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(ag-ui): add thread snapshot store primitives
Key decisions:\n- Introduce an AGUIThreadSnapshot model limited to replayable messages, optional Shared State, and optional interrupt state.\n- Define AGUIThreadSnapshotStore as an async protocol keyed by explicit Snapshot Scope and AG-UI Thread id.\n- Add InMemoryAGUIThreadSnapshotStore as memory-only, latest-only, bounded local/demo/test storage; no file-backed store is introduced.\n- Require snapshot_scope_resolver whenever an endpoint is configured with a snapshot store, including pre-wrapped runners, so thread ids are not authorization boundaries.\n\nFiles changed:\n- packages/ag-ui/agent_framework_ag_ui/_snapshots.py\n- packages/ag-ui/agent_framework_ag_ui/__init__.py\n- packages/ag-ui/agent_framework_ag_ui/_agent.py\n- packages/ag-ui/agent_framework_ag_ui/_workflow.py\n- packages/ag-ui/agent_framework_ag_ui/_endpoint.py\n- packages/core/agent_framework/ag_ui/__init__.py\n- packages/core/agent_framework/ag_ui/__init__.pyi\n- packages/ag-ui/tests/ag_ui/test_snapshots.py\n- packages/ag-ui/tests/ag_ui/test_endpoint.py\n- packages/ag-ui/tests/ag_ui/test_public_exports.py\n- packages/ag-ui/AGENTS.md\n\nVerification:\n- uv run pytest packages/ag-ui/tests/ag_ui/test_snapshots.py packages/ag-ui/tests/ag_ui/test_public_exports.py packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_requires_snapshot_scope_resolver_when_store_configured packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_accepts_snapshot_store_with_scope_resolver -q\n- uv run pytest packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_requires_snapshot_scope_resolver_when_store_configured packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_requires_snapshot_scope_resolver_when_wrapped_runner_has_store packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_accepts_snapshot_store_with_scope_resolver -q\n- uv run poe syntax -P ag-ui -C\n- uv run poe pyright -P ag-ui\n- uv run poe syntax -P core -C\n- uv run poe pyright -P core\n- uv run poe typing -P ag-ui\n- uv run poe typing -P core\n- uv run poe test -P ag-ui\n- uv run poe check -P ag-ui\n- git diff --check\n- git diff --cached --check\n\nBlockers / next iteration:\n- No blockers. Next slice can use the store contract to capture and hydrate agent snapshots.\n- uv repeatedly refreshed azure-ai-projects in uv.lock during local runs; reverted the generated lockfile churn because this change does not alter dependencies.\n- The poe-check commit hook was skipped after manual verification because it reformatted unrelated core MCP files outside this task.
* feat(ag-ui): hydrate agent threads from snapshots
Key decisions:
- Resolve Snapshot Scope per endpoint request and pass it to the AG-UI runner only when snapshot storage is active.
- Treat empty messages with no resume payload as an agent Hydrate Request when a scoped snapshot store is configured, replaying stored Shared State and message snapshots without invoking the wrapped agent.
- Save the latest replayable agent message snapshot and Shared State at normal completion under Snapshot Scope plus AG-UI Thread id; no durable or file-backed store is introduced.
Files changed:
- packages/ag-ui/agent_framework_ag_ui/_agent_run.py
- packages/ag-ui/agent_framework_ag_ui/_endpoint.py
- packages/ag-ui/agent_framework_ag_ui/_snapshots.py
- packages/ag-ui/tests/ag_ui/test_endpoint.py
Verification:
- uv run pytest packages/ag-ui/tests/ag_ui/test_endpoint.py::test_agent_endpoint_hydrates_stored_thread_snapshot_without_invoking_agent -q
- uv run pytest packages/ag-ui/tests/ag_ui/test_endpoint.py::test_agent_endpoint_hydrates_stored_thread_snapshot_without_invoking_agent packages/ag-ui/tests/ag_ui/test_endpoint.py::test_agent_endpoint_hydrates_snapshots_by_scope_and_thread -q
- uv run pytest packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_empty_messages packages/ag-ui/tests/ag_ui/test_endpoint.py::test_agent_endpoint_hydrates_stored_thread_snapshot_without_invoking_agent packages/ag-ui/tests/ag_ui/test_endpoint.py::test_agent_endpoint_hydrates_snapshots_by_scope_and_thread -q
- uv run poe syntax -P ag-ui -C
- uv run poe pyright -P ag-ui
- uv run poe typing -P ag-ui
- uv run poe test -P ag-ui
- uv run poe check -P ag-ui
- git diff --check
- git diff --cached --check
Blockers / next iteration:
- No blockers. Next slice can reconstruct normal new-user agent turns from stored snapshots.
- uv repeatedly refreshed azure-ai-projects in uv.lock during local runs; reverted the generated lockfile churn because this change does not alter dependencies.
- The poe-check commit hook was skipped after manual verification because it refreshed unrelated uv.lock dependency resolution.
* feat(ag-ui): reconstruct agent turns from snapshots
Key decisions:
- Load scoped thread snapshots for non-hydrate agent requests only when snapshot storage is active and no resume payload is present.
- Rebuild prior AG-UI history from stored snapshot messages, preserving the incoming new user suffix and treating stored snapshot content as authoritative over conflicting prior client history.
- Merge stored Shared State with request state overrides before schema defaults and existing state-context injection.
Files changed:
- packages/ag-ui/agent_framework_ag_ui/_agent_run.py
- packages/ag-ui/tests/ag_ui/test_endpoint.py
Verification:
- uv run pytest packages/ag-ui/tests/ag_ui/test_endpoint.py::test_agent_endpoint_prepends_stored_snapshot_for_new_user_turn -q
- uv run pytest packages/ag-ui/tests/ag_ui/test_endpoint.py::test_agent_endpoint_deduplicates_full_history_and_merges_fresh_state -q
- uv run pytest packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_empty_messages packages/ag-ui/tests/ag_ui/test_endpoint.py::test_agent_endpoint_hydrates_stored_thread_snapshot_without_invoking_agent packages/ag-ui/tests/ag_ui/test_endpoint.py::test_agent_endpoint_hydrates_snapshots_by_scope_and_thread packages/ag-ui/tests/ag_ui/test_endpoint.py::test_agent_endpoint_prepends_stored_snapshot_for_new_user_turn packages/ag-ui/tests/ag_ui/test_endpoint.py::test_agent_endpoint_deduplicates_full_history_and_merges_fresh_state -q
- uv run pytest packages/ag-ui/tests/ag_ui/test_endpoint.py -q
- uv run poe syntax -P ag-ui -C
- uv run poe pyright -P ag-ui
- uv run poe test -P ag-ui
- uv run poe check -P ag-ui
- uv run poe typing -P ag-ui
- git diff --check
- git diff --cached --check
Blockers / next iteration:
- No blockers. Next slice can enable workflow AG-UI Thread Snapshot persistence and hydration.
- uv repeatedly refreshed azure-ai-projects in uv.lock during local runs; reverted the generated lockfile churn because this change does not alter dependencies.
- The poe-check commit hook was skipped after manual verification because it refreshes unrelated uv.lock dependency resolution.
* feat(ag-ui): hydrate workflow threads from snapshots
Key decisions:
- Handle workflow Hydrate Requests before resolving or invoking the wrapped workflow when snapshot storage and Snapshot Scope are active.
- Capture only replayable workflow protocol data: workflow-emitted state snapshots, workflow-emitted message snapshots, and synthesized messages from text/tool output.
- Keep workflow snapshot capture inactive without configured persistence, and skip saving snapshots when the workflow stream emits RUN_ERROR.
Files changed:
- packages/ag-ui/agent_framework_ag_ui/_workflow.py
- packages/ag-ui/tests/ag_ui/test_endpoint.py
Verification:
- uv run pytest packages/ag-ui/tests/ag_ui/test_endpoint.py::test_workflow_endpoint_hydrates_emitted_snapshots_without_invoking_workflow packages/ag-ui/tests/ag_ui/test_endpoint.py::test_workflow_endpoint_hydrates_synthesized_text_and_tool_snapshot -q
- uv run pytest packages/ag-ui/tests/ag_ui/test_endpoint.py -q
- uv run pytest packages/ag-ui/tests/ag_ui/golden/test_scenario_workflow.py -q
- uv run poe syntax -P ag-ui -C
- uv run poe pyright -P ag-ui
- uv run poe test -P ag-ui
- uv run poe typing -P ag-ui
- uv run poe check -P ag-ui
- git diff --check
- git diff --cached --check
Blockers / next iteration:
- No blockers. Next slice can preserve interruption state and protect snapshots on errors across agent and workflow endpoints.
- uv repeatedly refreshed azure-ai-projects in uv.lock during local runs; reverted the generated lockfile churn because this change does not alter dependencies.
- The poe-check commit hook was skipped after manual verification because it refreshes unrelated uv.lock dependency resolution.
* feat(ag-ui): preserve interrupted thread snapshots
Key decisions:
- Capture workflow RUN_FINISHED interrupt metadata in replayable AG-UI Thread Snapshots so Hydrate Requests can restore pending workflow actions without invoking or resuming the workflow.
- Keep failed agent and workflow runs from replacing the last good snapshot; RUN_ERROR streams leave the previous snapshot available for hydration.
- Verify interruption hydration through endpoint-level AG-UI streams for both agent and workflow wrappers, including Shared State replay and no wrapped runner invocation.
Files changed:
- packages/ag-ui/agent_framework_ag_ui/_workflow.py
- packages/ag-ui/tests/ag_ui/test_endpoint.py
Verification:
- uv run pytest packages/ag-ui/tests/ag_ui/test_endpoint.py::test_workflow_endpoint_hydrates_interrupted_thread_without_invoking_workflow -q
- uv run pytest packages/ag-ui/tests/ag_ui/test_endpoint.py::test_agent_endpoint_hydrates_interrupted_thread_without_invoking_agent packages/ag-ui/tests/ag_ui/test_endpoint.py::test_agent_endpoint_run_error_does_not_overwrite_previous_snapshot packages/ag-ui/tests/ag_ui/test_endpoint.py::test_workflow_endpoint_hydrates_interrupted_thread_without_invoking_workflow packages/ag-ui/tests/ag_ui/test_endpoint.py::test_workflow_endpoint_run_error_does_not_overwrite_previous_snapshot -q
- uv run pytest packages/ag-ui/tests/ag_ui/test_endpoint.py -q
- uv run pytest packages/ag-ui/tests/ag_ui/golden/test_scenario_workflow.py -q
- uv run poe syntax -P ag-ui -C
- uv run poe pyright -P ag-ui
- uv run poe test -P ag-ui
- uv run poe typing -P ag-ui
- uv run poe check -P ag-ui
- git diff --check
- git diff --cached --check
Blockers / next iteration:
- No blockers. Next slice can document AG-UI Thread Snapshot security and usage.
- uv repeatedly refreshed azure-ai-projects in uv.lock during local runs; reverted the generated lockfile churn because this change does not alter dependencies.
- The poe-check commit hook was skipped after manual verification because it refreshes unrelated uv.lock dependency resolution.
* docs(ag-ui): document thread snapshot security
Key decisions:
- Document AG-UI Thread Snapshot persistence as opt-in and disabled unless a snapshot_store is configured.
- Place Snapshot Scope guidance next to endpoint authentication guidance, making clear that AG-UI Thread ids identify threads but do not authorize snapshot access.
- Describe built-in storage as in-memory only, process-local, latest-only, and not durable production storage; durable stores remain app-owned implementations of AGUIThreadSnapshotStore.
- Call out snapshot confidentiality impact and that no file-backed AG-UI snapshot store is provided.
Files changed:
- packages/ag-ui/README.md
Verification:
- uv run python scripts/check_md_code_blocks.py packages/ag-ui/README.md --no-glob
- git diff --check
- git diff --cached --check
- commit hook without SKIP ran changed-package lint/format and AG-UI README markdown-code-lint successfully before stopping because uv.lock was modified
- uv run poe markdown-code-lint (failed due existing unrelated packages/mistral/README.md missing agent_framework_mistral import resolution; changed AG-UI README blocks passed)
Blockers / next iteration:
- No blockers. Local issue/PRD planning artifacts remain uncommitted.
- uv refreshed azure-ai-projects in uv.lock during markdown lint and the commit hook; reverted the generated lockfile churn because this documentation change does not alter dependencies.
- The poe-check commit hook was skipped after manual verification because it refreshes unrelated uv.lock dependency resolution.
* fix(ag-ui): harden thread snapshot persistence edge cases
- Persist the completed confirm_changes turn with interrupt=None so hydration
no longer replays a stale pending interrupt after the user responds; resume
requests prepend stored history so the persisted thread is not truncated.
- Defer endpoint default_state application to the runners when snapshot
persistence is active, filling only keys missing from both the stored
snapshot state and the request state so defaults never reset persisted
Shared State.
- Always fold the turn's output into the persisted messages snapshot even when
the outbound MESSAGES_SNAPSHOT event is suppressed for predictive tools
without confirmation.
- Load the stored snapshot on workflow follow-up turns, reconstruct full
thread history into the run input, and seed the snapshot builder with merged
state so saving a new turn no longer replaces prior history.
- Move snapshot message reconstruction helpers to _run_common for reuse by the
workflow runner; load stored agent snapshots on resume turns for state merge.
- Add endpoint regression tests for all four scenarios.
* fix(ag-ui): protect snapshot history on resume and harden suffix trust
- Prepend stored thread history when persisting snapshots for resume runs on
both the agent and workflow paths, so a resumed interrupt no longer
overwrites the stored thread with just the resume turn's output.
- Filter the incoming message suffix during thread reconstruction: only user
turns and tool results answering backend-issued tool calls (stored tool
calls or pending interrupts) may extend authoritative history. Client-forged
assistant and tool messages are dropped and logged instead of being
persisted and replayed.
- Close the workflow snapshot builder's tool-call group when a tool result or
text message lands, so synthesized transcripts keep tool results adjacent to
their tool_calls message and stay valid as provider replay history.
- Export DEFAULT_MAX_THREAD_SNAPSHOTS from agent_framework_ag_ui and expose
SnapshotScopeResolver through the core ag_ui facade and stub.
- Add regression tests for agent and workflow resume history preservation,
forged suffix rejection, builder tool-call grouping, and the export surface.
* fix(ag-ui): tolerate snapshot save failures and scope workflow cache
- Wrap snapshot_store.save() on both the agent and workflow paths so a
transient store failure (timeout, connection refused) is logged instead of
propagating. Previously a failing save converted an already-streamed
successful run into RUN_ERROR, and on the workflow path emitted RUN_ERROR
after RUN_FINISHED, violating the single-terminal-event invariant. The
previous snapshot stays available for hydration.
- Key the workflow_factory instance cache by (snapshot_scope, thread_id). The
Snapshot Scope is the authorization boundary, so the same thread id under
different scopes no longer shares an in-memory workflow instance.
clear_thread_workflow accepts an optional snapshot_scope and clears all
scopes for the thread when omitted.
- Add tests: save-failure tolerance for agent and workflow endpoints,
scope-isolated workflow cache, async snapshot_scope_resolver support, and
in-memory store key validation errors.
* fix(ci): ignore all dotnet.microsoft.com links in linkspector
The existing ignore pattern only matched https://dotnet.microsoft.com/download,
but Microsoft sites insert a locale segment between host and path
(e.g. /en-us/download/dotnet/10.0), so localized links slip past the pattern
and get checked. dotnet.microsoft.com bot-blocks CI link checkers with
intermittent 403s across the whole site, which fails markdown-link-check on
unrelated pull requests since linkspector scans the entire repository.
Ignore the domain wholesale, matching how platform.openai.com is already
handled for the same reason. A 403 from bot blocking is indistinguishable
from a removed page, so the checker cannot produce a meaningful signal for
this domain either way.
* ag-ui: simplify raw_messages assignment and drop OrderedDict
- Replace list(cast(...)) with a typed annotation for raw_messages
(_agent_run.py:866) per review suggestion
- Replace OrderedDict with a plain dict in InMemoryAGUIThreadSnapshotStore
(_snapshots.py:136); regular dicts are insertion-order-safe since
Python 3.7, so OrderedDict is unnecessary. Update _evict_oldest to use
next(iter(...)) for FIFO removal instead of popitem(last=False).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address review feedback for #2458: review comment fixes
---------
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Integrate shell tool into AgentHarness
* Validate shell_executor exposes as_function() with a clear TypeError
Addresses PR review feedback: a public factory should fail fast with an
actionable error rather than a cryptic AttributeError when an incompatible
shell_executor is supplied. Validation happens upfront, regardless of whether
the client supports shell tools.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Type shell harness params via TYPE_CHECKING import
Addresses PR review feedback: type shell_executor and
shell_environment_provider_options instead of Any, using a TYPE_CHECKING
import from agent_framework_tools.shell. The import never executes at
runtime, so there is no circular dependency, and the lazy runtime import of
ShellEnvironmentProvider is retained. Since ShellExecutor is a protocol
without as_function(), the validated getattr result is invoked directly.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix CopySessionConfig and CopyResumeSessionConfig ignoring Streaming value (#4732)
CopySessionConfig() and CopyResumeSessionConfig() hardcoded Streaming = true,
ignoring the caller's explicitly set SessionConfig.Streaming value. This made it
impossible to disable streaming when using AsAIAgent() with the GitHub Copilot SDK.
Changed both methods to use source.Streaming ?? true (and source?.Streaming ?? true
for the nullable overload), preserving the caller's value when set while maintaining
backward compatibility by defaulting to true when unset.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix non-streaming response path for SessionConfig.Streaming=false (#4732)
The config-copy fix (preserving Streaming=false via null-coalescing) was
already in place, but ConvertToAgentResponseUpdate(AssistantMessageEvent)
always emitted raw AIContent without text—assuming delta events had already
delivered it. When streaming is disabled there are no delta events, so the
assistant's final text was silently dropped.
Changes:
- Add isStreaming parameter to ConvertToAgentResponseUpdate for
AssistantMessageEvent so it emits TextContent in non-streaming mode.
- Capture the resolved streaming flag in RunCoreStreamingAsync and pass
it through the event subscription closure.
- Add/update unit tests for both streaming and non-streaming paths.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add test for null Data path in ConvertToAgentResponseUpdate (#4732)
Add a regression test covering the null-propagation path where
AssistantMessageEvent.Data is null. The production code already handles
this via ?. operators, but no test previously verified the behavior.
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>
* Add LoopAgent capability for Harnesses
* Address PR comments.
* Add support for returning user messages and response aggregation
* Support fresh context per iteration with input sessions via cloning
* Add ability to receive newly created sessions via callback
* Address PR comments
* Add judge criteria
* Address PR comments
* Adds Valkey to chat message history
* Address review: switch to Valkey.Glide, add options class, remove context provider
- Switch from StackExchange.Redis to Valkey.Glide 1.1.0 (official Valkey .NET client)
- Extract optional params into ValkeyChatHistoryProviderOptions
- Add JsonSerializerOptions support, remove [RequiresUnreferencedCode]
- Make MaxMessages/MaxMessagesToRetrieve readonly via options
- Remove ValkeyContextProvider (overlaps with ChatHistoryMemoryProvider + MEVD)
- Remove ValkeyProviderScope (only used by context provider)
- Remove connection string constructors (caller manages IConnectionMultiplexer)
- Update samples to use new API and gpt-5.4-mini
* Use type-safe JsonSerializer overloads, remove suppress attributes
Use JsonSerializerOptions.GetTypeInfo() for Serialize/Deserialize calls
to enable NativeAOT/trimming compatibility without suppress attributes.
Default to AgentAbstractionsJsonUtilities.DefaultOptions when no options provided.
Signed-off-by: Matthias Howell <matthias.howell@improving.com>
* Update READMEs: remove context provider references
Remove ValkeyContextProvider and long-term memory references from sample
READMEs since the context provider was removed from this PR. Simplify
Valkey server requirements (no search module needed for chat history).
Signed-off-by: Matthias Howell <matthias.howell@improving.com>
* Apply suggestion from @westey-m
* Fix formatting (dotnet format)
Signed-off-by: Matthias Howell <matthias.howell@improving.com>
* Update dotnet/src/Microsoft.Agents.AI.Valkey/Microsoft.Agents.AI.Valkey.csproj
Co-authored-by: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com>
---------
Signed-off-by: Matthias Howell <matthias.howell@improving.com>
Co-authored-by: Matthias Howell <matthias.howell@yoppworks.com>
Co-authored-by: westey <164392973+westey-m@users.noreply.github.com>
Co-authored-by: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com>
* Fix MCP allowed_tools empty list handling
When allowed_tools is set to an empty list [], the falsy check
'if not self.allowed_tools' incorrectly treats it as unconfigured
(same as None), causing all tools to be exposed. Change to an
explicit 'is None' check so that an empty list correctly results
in no tools being allowed.
Co-authored-by: Azure SRE Agent <noreply@microsoft.com>
* Clarify allowed_tools docstring: None vs [] semantics
Per Eduard's review on PR #6296: explicitly document that None exposes all tools and [] exposes none, across all four MCPTool / MCPStdioTool / MCPStreamableHTTPTool / MCPWebsocketTool docstrings.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* allowed_tools docstring: recommend load_tools=False for full disable
Per Eduard's follow-up on PR #6296: `load_tools=False` is the cleaner idiom when you don't want to expose any tools. Reframe `allowed_tools=[]` in the docstring as a runtime guard / inspection-only path and cross-reference `load_tools`.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Azure SRE Agent <noreply@microsoft.com>
Co-authored-by: Giles Odigwe <79032838+giles17@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Make GitHub.Copilot.SDK build targets reach transitive consumers (#6455)
Microsoft.Agents.AI.GitHub.Copilot now ships a buildTransitive/ bridge so
consumers who only reference this package (the normal use case) get the
GitHub.Copilot.SDK's CLI binary-download MSBuild targets executed at build
time. Without this, the SDK shipped its targets under build/ which NuGet
only auto-imports for projects with a direct PackageReference to the SDK,
so consumers of the adapter package got only the managed .dll, no
copilot.exe in their output, and a runtime InvalidOperationException on
the first RunAsync.
The bridge consists of two files under buildTransitive/:
* Microsoft.Agents.AI.GitHub.Copilot.props is generated at this package's
pack time and pins the SDK version (from PackageVersion items in
Directory.Packages.props) into _MicrosoftAgentsAICopilotSdkVersion.
* Microsoft.Agents.AI.GitHub.Copilot.targets is static and imports the
SDK's own build/GitHub.Copilot.SDK.targets from the NuGet cache using
the pinned version. The version-pin condition no-ops gracefully if the
resolved SDK differs from what was baked in (e.g. consumer overrides
the SDK version directly), so this is purely additive.
Verified by packing locally, restoring from a flat local feed, and
building a transitive-only consumer (PackageReference to MAF only, no
direct SDK ref). copilot.exe lands at bin/{cfg}/{tfm}/runtimes/{rid}/
native/copilot.exe as expected, matching the path the SDK's runtime
CopilotClient looks at.
Fixes#6455
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address Copilot review feedback (#6457)
- buildTransitive/.targets: compute the full SDK targets path with a single
Path.Combine call into one property (_MicrosoftAgentsAICopilotSdkTargetsPath),
used in both Project= and Exists() — no more split between Path.Combine for
the directory and inline / separator for the file name.
- Split the version-defaulting Condition between the two files: the generated
.props now just bakes the packaged SDK version into a dedicated property
(_MicrosoftAgentsAICopilotSdkPackagedVersion), and the static .targets file
is the single place that defaults _MicrosoftAgentsAICopilotSdkVersion to it.
Removes the need for any MSBuild escape gymnastics in the pack-time string
construction, and keeps the consumer override path the same.
- _GenerateBuildTransitiveProps now hangs off public BeforeTargets (Build, Pack)
in addition to _GetPackageFiles, so the file is generated even without a
full pack, and we're not solely dependent on an underscore-prefixed internal
target. The <None Pack=true /> items live in a top-level ItemGroup so they
are collected at evaluation time instead of being added from inside the
Target.
End-to-end retested with a transitive-only consumer (PackageReference to MAF
only, no direct GitHub.Copilot.SDK ref): copilot.exe lands at
bin/Debug/net10.0/runtimes/win-x64/native/copilot.exe (141.8 MB) as before.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Tamir Dresher <tamirdresher@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Add Hosted-Toolbox-AuthPaths sample and auto-map /readiness with toolbox health gating (#5777)
Add a new hosted agent sample demonstrating five MCP tool authentication paths
(API key, agent MI, project MI, custom OAuth, literal token) via a Foundry Toolbox.
Package changes (Microsoft.Agents.AI.Foundry.Hosting):
- MapFoundryResponses now auto-maps GET /readiness via MapHealthChecks, idempotent
across Tier 1/2 (AgentHost, already mapped) and Tier 3 (WebApplication, gap filled).
- AddFoundryResponses registers AddHealthChecks() so the pipeline is available.
- AddFoundryToolboxes registers FoundryToolboxHealthCheck on the /readiness aggregate,
gating readiness on pre-registered toolbox startup outcome (per spec section 3.1).
- FoundryToolboxService now exposes StartupStatus and FailedToolboxNames properties.
New types:
- FoundryToolboxStartupStatus (public enum): Pending, Healthy, Failed, NoEndpoint.
- FoundryToolboxHealthCheck (internal IHealthCheck): adapts startup status to the
AspNetCore HealthChecks pipeline with failed toolbox names in result data.
Tests:
- 3 new tests for /readiness auto-mapping (Tier 3 default, pre-mapped skip, idempotent).
- 4 new tests for FoundryToolboxHealthCheck (Pending, NoEndpoint, Failed, Healthy).
- 3 enhanced FoundryToolboxServiceTests with StartupStatus assertions.
* .NET: Align FoundryToolboxService with tools-integration-spec (#5777 Part A)
Bring Microsoft.Agents.AI.Foundry.Hosting's toolbox path into compliance with
tools-integration-spec.md sections 2-4, 6.3, and 9. Empirically validated
against tao-foundry-prj: the previous code (reading FOUNDRY_AGENT_TOOLSET_ENDPOINT,
which the platform never injects) silently registered zero tools in production.
Package changes (Microsoft.Agents.AI.Foundry.Hosting):
- FoundryToolboxService.StartAsync now derives the toolbox proxy base URL from
the platform-injected FOUNDRY_PROJECT_ENDPOINT and constructs the per-toolbox
URL as {FOUNDRY_PROJECT_ENDPOINT}/toolboxes/{name}/mcp?api-version={ApiVersion}
per spec sections 2-3. The legacy FOUNDRY_AGENT_TOOLSET_ENDPOINT env var is
removed outright (preview package, no production consumers).
- FoundryToolboxOptions.ApiVersion default flipped to 'v1' to match spec example.
- FoundryToolboxBearerTokenHandler always sends the mandatory
Foundry-Features: Toolboxes=V1Preview header per spec section 2, merging any
additional flags supplied via the FOUNDRY_AGENT_TOOLSET_FEATURES env var.
- FoundryToolboxBearerTokenHandler token scope changed from
https://cognitiveservices.azure.com/.default to https://ai.azure.com/.default
per spec section 4.
- FoundryToolboxBearerTokenHandler propagates W3C trace context (traceparent,
tracestate, baggage) from Activity.Current per spec section 6.3.
Sample changes:
- Hosted-Toolbox-AuthPaths and Hosted-Toolbox Program.cs, README.md, and
.env.example corrected to describe the actual env-var contract
(FOUNDRY_PROJECT_ENDPOINT auto-injected; AZURE_AI_PROJECT_ENDPOINT as the
local-dev fallback). Removes the misleading 'auto-injected by Foundry runtime'
claims for FOUNDRY_AGENT_TOOLSET_ENDPOINT.
- Hosted-Toolbox-AuthPaths/agent.manifest.yaml declares the toolbox and model
dependencies under resources[] per the AgentManifest schema so azd ai agent
init users get them provisioned automatically.
Tests:
- 4 new FoundryToolboxServiceTests covering env-var derivation, EndpointOverride
precedence, trailing-slash normalization, and the existing NoEndpoint behavior
under the new env var name.
- 4 new FoundryToolboxBearerTokenHandlerTests covering token scope, mandatory
feature header always present, header merging with override, no duplicate
mandatory flag, trace context propagation from Activity.Current, and no
override of caller-set traceparent.
- New FoundryProjectEndpointEnvFixture xUnit collection definition serializes
env-var-mutating tests across FoundryToolboxServiceTests and
FoundryToolboxHealthCheckTests, preventing parallel-execution races.
- FoundryToolboxHealthCheckTests adjusted for the new env var name.
* .NET: Drop ACA prereq from Hosted-Toolbox-AuthPaths README (#5777 Part B)
Empirically verified that any Azure Cognitive Services MCP endpoint already in
the Foundry project (e.g., a Language service MCP) accepts Entra tokens and can
serve Paths 2 and 3 without deploying a separate Azure MCP Server to ACA.
README updates:
- Step 0 rewritten: 'Identify an Entra-authenticated MCP target in your project'
instead of 'Deploy Azure MCP Server to Azure Container Apps' (the original
azmcp-foundry-aca-mi setup is now optional, not required).
- Auth-paths matrix updated to describe AAD-based connections targeting a
Cognitive Services MCP URL (e.g., Language service) instead of an ACA URL.
- Step 2 connections table updated: the Entra ID category is now a single 'AAD'
authType. The original 'Agent Identity' vs 'Project Managed Identity' as
selectable connection sub-types is NOT exposed via the ARM control plane
today; the platform selects the calling principal contextually. Both
connections in the walkthrough share the same shape and target.
- Added an explicit RBAC note: the agent identity AND project MI must hold the
required role (typically Cognitive Services User) on the target resource;
without it the MCP server returns HTTP 401 even though the connection wiring
is correct.
- Toolbox tool entries renamed lang_entra_agent / lang_entra_project to
match the new connection names.
Empirical validation supporting these changes is captured in the session
plan.md (Part B addendum).
* .NET: Document correct connection shape for Hosted-Toolbox-AuthPaths Paths 2/3 (#5777)
Updates the sample README with the verified connection shape and RBAC procedure
for Microsoft Entra agent-identity and project-managed-identity MCP authentication:
- Connection authType values: AgenticIdentityToken (agent identity) and
ProjectManagedIdentity (project MI), both with category=RemoteTool.
- Top-level audience property required; for Cognitive Services targets the value
is https://cognitiveservices.azure.com.
- Connections created via ARM REST (the Foundry portal wizard does not yet
expose these authTypes).
- RBAC grants target the project's shared agent identity blueprint principal
(project.properties.agentIdentity.agentIdentityId) for Path 2 and the
project's system-assigned MI (project.identity.principalId) for Path 3.
- Troubleshooting table updated with the audience-mismatch symptom and the
startup-cache behavior of FoundryToolboxService.
* .NET: Drop Path 3 (project MI) and align with new agent model in Hosted-Toolbox-AuthPaths (#5777)
Updates the sample to use only the new Foundry agent object model and removes
the project managed identity path:
- Auth-path matrix reduced to four paths: key, Entra agent identity, custom
OAuth, inline authorization. Project managed identity is moved into a note
describing when it applies (multiple agents sharing access) rather than as
a documented sample path.
- RBAC instructions reference the agent's own instance_identity.principal_id
from the agent ARM resource (new agent object model) instead of the
project's shared agent identity blueprint (legacy model).
- Step 2 (connections) creates only the AgenticIdentityToken connection.
- Step 3 (toolbox tools) lists four tool entries instead of five.
- Sample prompts and troubleshooting table updated to match.
* .NET: Restore Path 3 (project MI) to Hosted-Toolbox-AuthPaths matrix (#5777)
The sample's purpose is to enumerate every authentication path a Foundry toolbox
can drive, not to pick one. Path 3 belongs alongside the other four with
explicit guidance for when each path is the right choice.
- Path 3 (project managed identity, authType=ProjectManagedIdentity) restored
to the matrix with a 'When to pick this' column.
- Step 2 (connections) provisions both lang-mcp-agent-id and lang-mcp-project-mi
via ARM REST.
- Step 3 (toolbox) lists five tool entries (one per path).
- RBAC instructions cover both the agent's instance identity (Path 2) and the
project's system-assigned MI (Path 3).
- Sample prompts include all five paths.
- Troubleshooting table updated accordingly.
* .NET: Fix duplicate line in Hosted-Toolbox-AuthPaths README (#5777)
* .NET: Fix broken markdown link to ToolCallingApprovalHostedAgentFixture (#5777)
* .NET: Fix relative path depth in markdown link (#5777)
* .NET: Address Copilot review feedback for #5777
- FoundryToolboxHealthCheck description: rename FOUNDRY_AGENT_TOOLSET_ENDPOINT
→ FOUNDRY_PROJECT_ENDPOINT (stale reference; operator-facing in /readiness body).
- FoundryToolboxStartupStatus.NoEndpoint XML doc: same rename.
- ServiceCollectionExtensions XML docs: same rename + URL shape update.
- Foundry.Hosting.IntegrationTests.TestContainer: remove explicit
app.MapGet('/readiness') — now redundant + would conflict with the
auto-mapped readiness route from MapFoundryResponses.
- Hosted-Toolbox-AuthPaths agent.manifest.yaml: parameterize TOOLBOX_NAME via
{{TOOLBOX_NAME}} template substitution and declare it under parameters with a
default of 'auth-paths-toolbox' so the README's 'use any name' guidance
actually works for hosted deployments.
* .NET: Address Copilot review round 2 — fallback env + dedup + naming (#5777)
- FoundryToolboxService.StartAsync: fall back to AZURE_AI_PROJECT_ENDPOINT when
FOUNDRY_PROJECT_ENDPOINT is absent. Matches the local-dev convention used by
the samples and resolves the doc/code mismatch flagged in review.
- FoundryToolboxHealthCheck description updated for the fallback.
- AddFoundryToolboxes: guard against duplicate health-check registration via an
explicit name-uniqueness check on HealthCheckServiceOptions.Registrations.
AddCheck<T>(name, ...) does not dedupe by name, so repeated AddFoundryToolboxes
calls would have registered multiple instances.
- FoundryToolboxOptions.EndpointOverride doc: clarify URL becomes
{EndpointOverride}/toolboxes/{name}/mcp (was missing /toolboxes/ segment).
- Hosted-Toolbox sample (Program.cs + README): switch FOUNDRY_TOOLBOX_NAME to
TOOLBOX_NAME (the FOUNDRY_* prefix is reserved by the platform), default
changed from 'my-toolset' to 'my-toolbox', terminology updated from 'Toolset'
to 'Toolbox'.
- FoundryToolboxServiceTests: 2 test renames to reflect what they actually
assert (StartupStatus + FailedToolboxNames, not URL shape directly).
- Tests adjusted to clear both env vars in NoEndpoint scenarios.
* .NET: Fix stale NoEndpoint XML doc and misleading test comment (#5777)
Update FoundryToolboxStartupStatus.NoEndpoint XML doc to mention both
FOUNDRY_PROJECT_ENDPOINT and AZURE_AI_PROJECT_ENDPOINT (the service
checks both since the fallback was added).
Fix test comment that claimed URL derivation validation when the test
only asserts on StartupStatus and FailedToolboxNames.
* Remove OAuth consent path from AuthPaths sample, keep four working auth paths
The interactive OAuth identity passthrough path needs a protocol gap closed in the
hosting package (the proprietary oauth_consent_request item is not representable
through the OpenAI/MEAI abstractions), so it is deferred to a separate spike branch.
This strips the OAuth path from the AuthPaths sample, the companion REPL client, the
agent manifest, and the docs, then renumbers the inline Authorization path so the
sample teaches four contiguous paths: API key via connection, Entra agent identity,
Entra project managed identity, and inline Authorization (anti-pattern).
Package code is unchanged; the consent infrastructure already present in main stays
as baseline. Both samples build with --warnaserror and all 246 hosting unit tests pass.
* .NET: Drop project MI auth path and dedicated client from Hosted-Toolbox-AuthPaths (#5777)
Live validation against tao-foundry-prj showed the ProjectManagedIdentity
path failing with an unresolved token audience 401, so the sample now ships
three working auth paths instead of four: connection key, agent managed
identity, and inline Authorization.
Changes:
- Remove the project managed identity path from the AuthPaths sample matrix,
prerequisites, connections, toolbox table, prompts, Program.cs instructions
and agent.manifest.yaml.
- Delete the near duplicate Hosted-Toolbox-AuthPaths-Client project and remove
it from the solution. The README now drives the agent with the shared
SimpleAgent REPL via AsAIAgent(agentEndpoint).
- Correct the troubleshooting note: the Foundry toolbox tools/list is all or
nothing, so one bad source returns -32007, fails startup, and returns 424
for every path. Add the allowed_tools caveat that names must match the
upstream server.
- Mark the toolbox startup status and health check experimental under
AgentsAIExperiments (MAAI001) instead of AIOpenAIResponses, and update the
package NoWarn set accordingly.
* .NET: Address PR review nits for Hosted-Toolbox-AuthPaths (#5777)
- Remove duplicated NU1903 comment in Foundry.Hosting csproj.
- Fix stale 'four-tool' cross-links in Hosted-Toolbox and Hosted-McpTools READMEs to describe the three-path toolbox driven by the shared SimpleAgent REPL.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Address toolbox startup-status review feedback (#5777)
- Rename FoundryToolboxStartupStatus.Failed to Unhealthy so it is the proper opposite of Healthy, and clarify the doc comment covers the partial-failure case.
- Raise the missing-endpoint toolbox log from Information to Warning, since enabling toolboxes is an explicit opt-in and a silently disabled toolbox warrants a higher-severity signal.
- Update unit tests and the AuthPaths README troubleshooting row accordingly.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Reword toolbox-wiring comment to avoid hosting-layer internals (#5777)
Address PR review feedback: explain how a Foundry Toolbox is attached using the public API (AddFoundryToolboxes vs the CreateHostedMcpToolbox marker) and observable behavior, instead of naming the internal AgentFrameworkResponseHandler type and FoundryToolboxService.Tools property.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix .NET Copilot integration tests for SDK v1.0.0
- Remove hard-skip in favor of runtime Assert.Skip when COPILOT_GITHUB_TOKEN is not set
- Add [Trait("Category", "Integration")] for CI filtering
- Fix FunctionTool test: use explicit SessionConfig with Tools, OnPermissionRequest, and SystemMessage
- Mark RemoteMcp test as IntegrationDisabled (requires OAuth flow)
- Create explicit sessions in all tests and delete after each (cleanup)
- Remove unused System.Diagnostics import
- Simplify SkipIfCopilotNotConfigured to only check env var
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address review: use try/finally for session cleanup, IsNullOrWhiteSpace
- Wrap act/assert in try/finally so sessions are always deleted even on failure
- Use IsNullOrWhiteSpace instead of IsNullOrEmpty for token check
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add COPILOT_GITHUB_TOKEN to .NET integration test workflow
The Copilot SDK runtime reads this env var directly for authentication.
No Node.js/npm install needed - the SDK downloads the CLI binary at build time.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Parse structuredContent from MCP CallToolResult (#3313)
The _parse_tool_result_from_mcp method only iterated over the content
field from CallToolResult, ignoring the structuredContent field entirely.
MCP servers that return JSON data via structuredContent (e.g., Power BI
MCP) appeared to return None.
Add handling for structuredContent: when present, serialize it as JSON
text and append it to the result list. This preserves the data for the
LLM while maintaining backward compatibility with existing behavior.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: Parse MCP CallToolResult.structuredContent field to prevent tool results returning None
Fixes#3313
* Address review feedback: add default=str to json.dumps and remove .checkpoints/
- Add default=str to json.dumps for structuredContent serialization so
non-JSON-serializable values (e.g. bytes) degrade gracefully instead
of raising TypeError
- Remove all .checkpoints/ runtime artifacts from the repository
- Add **/.checkpoints/ to .gitignore to prevent future accidental commits
- Add test for non-serializable structuredContent values
Fixes#3313
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address review feedback for #3313: Python: MCP CallToolResult.structuredContent field is not parsed, causing tool results to return None
---------
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add sampling guardrails to MCP tools
Add approval, token, and request-count controls to the MCP sampling
callback used when an MCPTool is configured with a chat client.
- Add `sampling_approval_callback`, `sampling_max_tokens`, and
`sampling_max_requests` parameters to `MCPTool` and its
`MCPStdioTool`, `MCPStreamableHTTPTool`, and `MCPWebsocketTool`
subclasses, positioned directly after `client`.
- Gate each server-initiated `sampling/createMessage` request behind the
approval callback, which denies by default when no callback is provided.
- Clamp the requested `maxTokens` to `sampling_max_tokens` and enforce a
per-session request count via `sampling_max_requests`.
- Log incoming sampling requests at WARNING level (counts only).
- Export `SamplingApprovalCallback` from the public API.
- Add tests, a sample, and documentation updates.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Make sampling denial message context-aware
Distinguish the deny-by-default case (no approval callback configured)
from an explicit denial by a configured `sampling_approval_callback`, so
the returned ErrorData message is accurate for callback-driven denials
and exceptions.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add 'Deploying to Foundry (azd spec)' sections to all Foundry hosted agent samples
This commit adds comprehensive deployment documentation to all 13 .NET Foundry hosted agent samples that were missing it. Each sample now includes:
- Instructions to initialize an azd project from the sample's agent.manifest.yaml
- Steps to deploy using 'azd deploy'
- Example environment variable overrides for customization
- Link to the official Foundry deployment guide
Samples updated:
- Hosted-LocalTools
- Hosted-Files
- Hosted-FoundryAgent
- Hosted-McpTools
- Hosted-Observability
- Hosted-MemoryAgent
- Hosted-TextRag
- Hosted-ToolboxMcpSkills
- Hosted-AzureSearchRag
- Hosted-AgentSkills
- Hosted-Workflow-Handoff
- Hosted-Workflow-Simple
- Hosted-Invocations-EchoAgent
Each section includes the correct agent name from the sample's manifest and points to the correct GitHub URL for initializing the azd project.
Fixes: https://github.com/microsoft/agent-framework/issues/6308
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* docs(samples): fix Foundry hosted README consistency
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* docs(samples): address PR 6365 README review comments
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>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* Parallelize Purview PSPC cold cache path
* Cache Purview payment-required state for scope refresh
* Cache Purview payment-required state for scope refresh
* Align Purview policy action dedupe and 402 caching
Deduplicate combined policy actions by action and restriction action so restriction-only actions are preserved
without duplicating identical entries. Cache tenant-level payment-required state from background scope refresh so
subsequent calls short-circuit consistently.
* .NET: Implement best-effort caching for background job scope retrieval and add unit tests for cache write failures
* Purview - feat: Enhance ScopedContentProcessor to queue ContentActivityJob when no applicable scopes are found and update related tests
* docs: Update purview package README and AGENTS documentation to reflect caching optimizations and policy enforcement scenarios
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* 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 f31ed0ded914e094f3ac5d811997b2cefc55836b.
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>
* Fix declarative workflow regressions for hosted agents
Three regressions surfaced when running a declarative workflow as a
Foundry hosted agent. Together they caused every condition group to fall
through to elseActions and the raw agent JSON to leak to the caller.
1. AgentProviderExtensions.InvokeAgentAsync forced autoSend to true
whenever the agent ran on the workflow conversation, which overrode
the explicit autoSend: false declared in workflow.yaml and streamed
the raw structured-output JSON straight to the user. Honor the
caller-supplied autoSend instead.
2. IWorkflowContextExtensions.ReadState / QueueStateUpdateAsync /
QueueStateResetAsync took the variable name and namespace alias
directly from PropertyPath.VariableName / NamespaceAlias. Against
Microsoft.Agents.ObjectModel 2026.2.4.1 those properties return null
for a dotted reference such as `Local.Triage` even when
SegmentCount == 2 and IsValid == true, so every assignment threw
ArgumentNullException via Throw.IfNull. Fall back to Segments() to
reconstruct the name and alias when the parser returns null.
3. The same ObjectModel version no longer recognizes the user-facing
`Local` scope alias: VariableScopeNames.IsValidName(`Local`)
returns false and GetNamespaceFromName(`Local`) returns Unknown, so
the declarative interpreter's IsManagedScope check fails and the
State.Set call is silently skipped. Translate the `Local` alias to
its canonical `Topic` form before forwarding to
QueueStateUpdateAsync; WorkflowFormulaState.Bind continues to expose
it as `Local` to PowerFx.
Verified end-to-end against a deployed Foundry hosted agent: the
declarative triage workflow now routes Technical / Billing / General
inputs correctly and only the autoSend-eligible messages reach the
caller.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Hosted-agent HITL: persist session across previous_response_id chains; run approved local AIFunctions
Two regressions hit declarative workflows that use require_approval=true when
the client chains turns via previous_response_id (no conversation_id):
1. AgentFrameworkResponseHandler keyed the AgentSession store solely on
conversation_id, so when only previous_response_id was present the
StateBag (which holds ToolApprovalIdMap) was discarded after each turn.
The next turn then threw 'No approval mapping recorded for wire id ...'
in InputConverter.ConvertMcpApprovalResponse.
Fix: fall back to previous_response_id on load and to context.ResponseId
on save so the response-id chain becomes a valid session key. Conversation
id remains preferred when present.
2. InvokeFunctionToolExecutor.CaptureResponseAsync only acted on
FunctionResultContent. In the hosted Foundry path the approval response
arrives as a ToolApprovalResponseContent with no FunctionResultContent,
so the local AIFunction never ran and downstream PropertyPath/SendActivity
consumers (e.g. {Local.RefundResult}) saw empty values.
Fix: when no FunctionResultContent matches but an approved
ToolApprovalResponseContent does, look up the registered AIFunction by
name on agentProvider.Functions and invoke it with the evaluated
arguments, surfacing the result through the existing assignment path.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Apply PropertyPath workaround to initialization path; share + tidy helpers
Address PR #5905 review feedback:
* Move the PropertyPath VariableName/NamespaceAlias fallback and 'Local'
-> 'Topic' scope remap into a shared internal PropertyPathExtensions
helper. Materializes Segments() once, names the magic 'Local' alias
as a const, and carries a TODO referencing the tracking issue.
* Apply the same helper in WorkflowDiagnostics.InitializeDefaults so a
declared default for a dotted variable like 'Local.Triage' is no
longer silently skipped at workflow startup (closes the gap flagged
by the reviewer: runtime ReadState/QueueStateUpdateAsync worked but
state.Initialize did not).
* Restore the previous strict failure mode on namespace alias by
wrapping GetNamespaceAlias() in Throw.IfNull at call sites so a
malformed single-segment path keeps failing fast rather than
silently passing null to State.Get/Set.
All 821 unit tests pass.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add tests for AgentProviderExtensions.InvokeAgentAsync autoSend behavior
Covers the autoSend regression fix: when the agent runs on the workflow conversation with autoSend=false, no AgentResponseUpdateEvent or AgentResponseEvent is added to the context. Also covers autoSend=true (events emitted) and autoSend=false on a non-workflow conversation.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Surface SendActivity output via AgentResponseUpdateEvent
SendActivityExecutor previously only emitted the activity text via YieldOutputAsync, which the runtime converts to an AgentResponseEvent. WorkflowSession gates AgentResponseEvent behind includeWorkflowOutputsInResponse, so when a host opts out of summary outputs (the default for AsAIAgent) the SendActivity reply is silently dropped.
Mirror the pattern used by AgentProviderExtensions for autoSend agent invocations: also emit an AgentResponseUpdateEvent, which WorkflowSession yields unconditionally. This makes SendActivity reliably reach chat-protocol clients without requiring includeWorkflowOutputsInResponse = true (which would also duplicate autoSend agent output).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Revert previous_response_id session-key fallback
The fallback let a session be keyed by an unbroken previous_response_id chain,
but conversation_id is the right way to thread state across turns: it survives
shared/branched chains (e.g. when another agent generates a response in between)
and is the documented model for stateful clients. Restore conversation_id as the
sole session key and rely on the client to thread it. The InvokeFunctionTool
approval/local-function half of 1baf4af4d remains.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Set Foundry ProductContext per-executor instead of via PropertyPath workaround
ObjectModel 2026.2.4.1 resolves PropertyPath.VariableName / NamespaceAlias and VariableScopeNames.IsValidName against AsyncLocal<ProductContext> at access time. In hosted-agent scenarios each HTTP request runs on a fresh async context where that AsyncLocal is default, so dotted refs like Local.Triage returned null and the Local scope alias was rejected.
Replace the PropertyPathExtensions helper (which papered over both symptoms) with a single WorkflowDiagnostics.SetFoundryProduct() call at the entry of DeclarativeActionExecutor.HandleAsync. The set writes to the request's logical async context before any code reads PropertyPath, letting the existing parser and scope resolver work as designed.
Validated: 824/824 declarative unit tests pass; technical/billing/general routes all dispatch correctly against a deployed Foundry hosted agent.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address review feedback on InvokeFunctionToolExecutor
- Surface registered-function lookup failures and invocation exceptions via FunctionResultContent.Exception instead of returning the error text as a successful Result, so downstream {Local.X} assignments can distinguish failures from successes.
- Use AIJsonUtilities.DefaultOptions to JSON-serialize non-string function results (matching FunctionInvokingChatClient / ToolBridge), so complex types stay consumable by PropertyPath consumers instead of degrading to Object.ToString().
- Drop the explicit System. prefix on StringComparison / Exception now that the file imports System.
- Add AutoSendTrueOnExternalConversationEmitsResponseEventsAndCopiesMessagesAsync to cover the (autoSend: true, external conversation) quadrant, asserting that response events are emitted and that messages are mirrored to the workflow conversation.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Honor AutoSendIsDefaultValue when computing autoSend
AzureAgentOutput.AutoSend and InvokeToolOutput.AutoSend in
Microsoft.Agents.ObjectModel 2026.2.4.1 are never null — they
return a literal-false default when the YAML omits the field.
The previous null check in Get/AutoSendValue therefore always
fell through to evaluating the literal false, so every action
whose YAML had any output block but no explicit autoSend was
treated as autoSend = false. This was previously masked by
`autoSend |= isWorkflowConversation` in AgentProviderExtensions
(removed earlier in this PR to honor explicit autoSend: false),
which silently re-enabled autoSend on the workflow conversation.
Use AutoSendIsDefaultValue to distinguish an explicit autoSend
value from the implicit default and treat the implicit default
as true, restoring the historical behavior for ValidateCaseAsync
InvokeAgent.yaml (3 InvokeAzureAgent actions, last one captures
to Local.RatingResponse via output.messages with no autoSend
specified) while keeping the hosted-agent fix that honors an
explicit autoSend: false.
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>
* feat(tools): add cross-OS LocalShellTool in new agent-framework-tools package
Introduces a safe, cross-OS local shell tool as the first citizen of a new
agent-framework-tools workspace package. Supports persistent (default) and
stateless modes across pwsh/powershell.exe/bash/sh, with policy denylist,
allowlist, approval gating, process-tree kill on timeout, output truncation,
and audit hooks. Integrates with existing provider get_shell_tool(func=...)
factories via FunctionTool kind='shell'.
See docs/decisions/0026-builtin-tools-local-shell.md for the full design.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(tools): security hardening for LocalShellTool
Codifies what LocalShellTool does and does not defend against, and
delegates the security-relevant lifecycle primitive to a battle-tested
library instead of hand-rolled per-OS code.
Changes:
- Adopt psutil for cross-OS process-tree termination (executor + session).
Replaces hand-rolled taskkill/killpg with one canonical implementation.
- Resolve taskkill.exe to absolute %SystemRoot%\System32 path so PATH
poisoning cannot redirect us to an attacker-supplied binary.
- Reframe ShellPolicy docstring + ADR + README: denylist is a guardrail,
not a security boundary.
- Require acknowledge_unsafe=True to set approval_mode='never_require',
making the unsafe path explicitly opt-in with a self-documenting name.
- Add tests/test_security.py codifying named CVE-style cases. Defenses
we DO claim are asserted; non-defenses (denylist bypasses via
backslash insertion, variable expansion, interpreter escape, base64,
alternative tools, PowerShell-native verbs) are documented as
expected-to-pass tests so residual risk stays visible.
- Add Threat Model + Confidence Strategy sections to ADR 0026.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(tools): add DockerShellTool sandboxed shell tier
Adds a container-backed shell executor as the recommended pattern for untrusted-input shell workflows. The container provides the security boundary (--network none, non-root user, --read-only, --cap-drop ALL, no-new-privileges, memory/pids limits, tmpfs /tmp), so approval gating is optional unlike LocalShellTool.
Also introduces a ShellExecutor Protocol so callers can plug in custom backends (Firecracker, SSH, WASI) without forking the framework.
Removes the planned HyperlightShellExecutor follow-up from ADR 0026: Hyperlight is a WASM code sandbox with no kernel/userland/shell binary, so a Hyperlight-backed shell is not viable. Docker is the realistic sandbox tier for shell.
Tests: 11 unit tests for argv builders + lifecycle (no Docker daemon required); 3 integration tests gated on is_docker_available().
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(tools): backport shell-tool fixes from .NET parity review
Applies the applicable subset of bug fixes accumulated during the
.NET shell-tool PR review (microsoft/agent-framework#5604) to the
Python shell tool.
A1 - Quote workdir safely in _maybe_reanchor
Previously _tool.py used double-quote interpolation when emitting
the cd/Set-Location prefix, which expanded $VAR, $(), and backticks
in the workdir path. A workdir containing shell metacharacters could
trigger arbitrary command execution before the user command ran.
Replaced with single-quote escaping helpers _quote_posix and
_quote_powershell that emit literal-string forms safe for both
hosts.
A5/A6 - Consolidate truncation to a single byte-aware helper
Extracted a shared truncate_head_tail / truncate_text_head_tail
helper in _truncate.py. The new implementation distributes odd
caps so head receives floor(cap/2) and tail receives ceil(cap/2)
bytes, matching the .NET round-9 fix and ensuring no input bytes
are silently dropped on the boundary.
_session.py previously truncated by Python str length while the
caller passed _max_output_bytes - the unit mismatch is now gone:
raw byte buffers go through truncate_head_tail and decoded text
goes through truncate_text_head_tail.
Unit tests added for the truncate and quote helpers.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* docs(tools): tone down narrative and overconfident comments in shell tool
The shell tool's docstrings and comments contained two patterns that
the .NET review pushed back on:
- Narrative framing about implementation history ("hard-won",
"we sidestep", "design inspiration: ...", competitor framework
name-drops in module docstrings).
- Overstated security guarantees ("battle-tested",
"reasonable for untrusted input", "recommended executor for any
agent that runs commands from untrusted input",
"destructive commands are blocked", "safe local shell tool",
"blocks shell injection").
Rewrites the affected docstrings and comments to describe what the
code does in neutral terms. Behaviour is unchanged.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(tools): add ShellEnvironmentProvider for the Python shell tool
Ports the .NET ShellEnvironmentProvider as a Python ContextProvider
so agents using LocalShellTool or DockerShellTool can be primed with
an accurate description of the shell they're talking to (family,
version, OS, working directory, and which CLIs are available).
The provider runs probes through any ShellExecutor, caches the
resulting snapshot, and on every before_run extends the session
instructions with a markdown block describing the shell idiom to
use. A failed first probe leaves the cache empty so the next call
retries (no permanent poisoning).
Probe failures from a narrow set of expected error types
(ShellCommandError, ShellExecutionError, ShellTimeoutError, and
asyncio.TimeoutError from the per-probe timeout) are recorded as
None fields in the snapshot. Other exceptions propagate. Tool
names are validated against ^[A-Za-z0-9._-]+$ before being
interpolated into a probe command.
Includes 12 unit tests covering happy path, stderr fallback,
timeout handling, expected/unexpected exception paths, malicious
tool name rejection, case-insensitive deduplication, retry after
failure, concurrent first-callers sharing one probe, and the
default and custom formatter paths.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* docs(tools): document ShellEnvironmentProvider and finish comment cleanup
Add a README section introducing ShellEnvironmentProvider, soften two remaining overconfident security-boundary comments in _executor_base.py and the DockerShellTool class docstring, and add a sample (shell_with_environment_provider.py) that demonstrates the provider in stateless and persistent modes.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(tools): move shell samples to python/samples/02-agents/tools
The repository convention is to host samples under python/samples/ rather than inside the package directory. Move the two net-new shell samples (allow-list and environment-provider) to python/samples/02-agents/tools/ and drop the in-package samples/ directory; the existing top-level providers/openai/client_with_local_shell.py already covers the basic LocalShellTool walkthrough.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* test(tools): cover confine_workdir default and ShellResult.format_for_model
Two new tests in test_local_shell_tool.py exercise the default confine_workdir=True behaviour on POSIX and PowerShell, asserting that 'cd' inside one persistent-mode call does not leak into the next. A new test_shell_result.py module provides direct unit coverage for every conditional branch of ShellResult.format_for_model (stdout, truncated, stderr, timed_out, exit_code) so regressions in the LLM-facing format are caught immediately.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(tools): address PR #5664 review feedback
- _tool.py: detect PowerShell via is_powershell() helper instead of basename string match
- _environment.py: use public ContextProvider import (no private _ prefix)
- _session.py: trim _stdout_buf/_stderr_buf after copying to avoid unbounded retention across calls
- _docker.py: short-circuit start()/close() in stateless mode; add configurable shell kwarg (default bash, e.g. 'sh' for alpine)
- tests: parenthesized multi-line assert; alpine integration tests now pass shell='sh'
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(tools): satisfy CI quality gates
- pyupgrade: drop quoted self-class refs in __aenter__/method annotations
- ruff format: reflow long lines per workspace style
- pyright: assert psutil non-None in optional-import branch; lowercase mutable module globals; annotate _approval_mode as Literal so tool() Literal-typed kwarg is accepted; add ... body to ShellExecutor.run protocol; remove unused deprecated _kill_tree wrapper
- tests: skip docker integration tests on win32 (Windows containers don't support --read-only / alpine images)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Remove DEFAULT_DENYLIST; document single-session ownership; fix bandit findings
Mirrors the .NET PR #5604 cleanup:
- Remove DEFAULT_DENYLIST from ShellPolicy. ShellPolicy() now ships with an empty deny-list; operators opt into site-specific patterns explicitly. No major agent framework uses regex matching as a primary security control; AutoGen v2 removed theirs. Approval gating + sandbox tier remain the real boundaries.
- Rewrite module / class docstrings to frame ShellPolicy as a UX pre-filter, not a security control.
- Add Single-session ownership paragraphs to ShellExecutor, ShellSession, LocalShellTool, and DockerShellTool: a persistent-mode tool is owned by exactly one conversation / agent session; do not share across users or concurrent conversations.
- Tests now supply explicit deny patterns instead of relying on a default.
- Address Pre-commit Hooks (bandit) CI failures: convert internal-invariant asserts to explicit RuntimeError, annotate intentional subprocess/shell usage with # nosec, document container-internal /tmp paths.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address PR #5664 round-2 review feedback
Deny-list documentation drift:
- README and the OpenAI/local-shell sample no longer claim a built-in deny-list of destructive commands. ShellPolicy is described as an optional, operator-supplied UX pre-filter; the real boundaries remain approval gating and the sandbox tier.
Behavioural fixes called out in review:
- ShellPolicy.evaluate() now denies empty / whitespace-only commands explicitly instead of returning allow with no rationale.
- truncate_head_tail() raises ValueError for cap <= 0 instead of silently returning the full input with truncated=False, which previously could defeat output-capping in callers that mis-configured the budget.
- LocalShellTool.as_function() / DockerShellTool.as_function() return the ShellCommandError text directly so the model sees a single, non-redundant 'Command rejected by policy: …' message instead of the prior duplicated 'Command blocked by policy: Command rejected …' wrapping.
- ShellSession POSIX sentinel trailer now snapshots and restores the prior errexit (set -e) state around the trailer, so a user 'set -e' in the persistent shell is no longer permanently disabled by the next run().
Tests:
- New test_shell_parse_rc.py covers the full _parse_rc() edge-case surface (zero, positive, negative, CRLF, no newline, missing prefix, empty input, non-digits, trailing garbage, partial digits).
- test_policy.py asserts the new empty-command deny.
- test_shell_truncate_and_quote.py asserts ValueError for cap=0 and cap<0.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address PR review feedback for shell tool
- _resolve.py: reject empty/whitespace shell override string
- _tool.py / _docker.py: mode-aware default tool description (persistent vs stateless)
- _tool.py: fix misleading workdir docstring (re-anchor, not blocking)
- _types.py: emit stream-agnostic [output truncated] marker
- _policy.py: declare _denies/_allows as dataclass fields
- _environment.py: use $(pwd) instead of $PWD in POSIX probe
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address PR review feedback: shell override flag + probe timeout safety
- _resolve.py: in stateless mode, ensure shell overrides end with -c/-Command so commands aren't misinterpreted as script-file paths.
- ShellExecutor.run / LocalShellTool.run / DockerShellTool.run now accept an optional imeout kwarg; ShellEnvironmentProvider drops the outer asyncio.wait_for and lets the executor enforce the probe timeout internally, so cancellation no longer risks leaving a hung subprocess or corrupted session.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address review feedback: docker isolation + lifecycle robustness
- pyproject.toml: bump agent-framework-core minimum from 1.2.0 to 1.2.2 to align with the rest of the workspace.
- _docker.py: validate extra_run_args at construction time and reject flags that would dismantle the isolation defaults (--privileged, --cap-add, --security-opt, --network/--net, -v/--volume/--mount, --device, --pid, --ipc, --userns, --user, --read-only, --tmpfs, --add-host, --gpus, --cgroupns, --device-cgroup-rule); also documented the warning on the docstring.
- _docker._stop_container: retry docker rm -f once and log a warning/error when it does not succeed, so operators can audit leaked containers instead of getting a silent success.
- _docker._run_stateless timeout path: fall back to docker rm -f when docker kill fails or times out (--rm only reaps on clean exit), and log instead of silently swallowing communicate() errors.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: alliscode <bentho@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: alliscode <25218250+alliscode@users.noreply.github.com>
* .NET: Surface x-ms-served-model header as ChatResponse.ModelId for Foundry agents
Mirrors Python PR #5910. Adds an internal SCM PipelinePolicy that reads the x-ms-served-model HTTP response header on Azure OpenAI Responses calls and writes it into an AsyncLocal box. A DelegatingChatClient sits between OpenTelemetry and the MEAI OpenAIResponsesChatClient and overwrites ChatResponse.ModelId with the served snapshot so OTel spans report the actual model rather than the deployment alias. Wired through all AsAIAgent paths in Microsoft.Agents.AI.Foundry.
* .NET: Fix line endings and BOM on ResponsesAgentServedModelTests
* .NET: Address Copilot review on Foundry served-model PR
- Restore previous ServedModelScope in finally to avoid AsyncLocal leak into caller execution context.
- Make served-model integration test assertion robust to deployment names that already match the snapshot pattern.
- Broaden UnitTests csproj comment to cover all conditional removals (net8.0+ requirement).
* .NET: Split ServedModelTests into per-SUT files with regions
Split the combined ServedModelTests.cs into one test class per SUT:
- ServedModelScopeTests.cs (AsyncLocal carrier)
- ServedModelPolicyTests.cs (SCM pipeline policy)
- ServedModelChatClientTests.cs (delegating client, with regions for Non-streaming / Streaming / End-to-end)
Shared helpers and fake clients moved into ServedModelTestHelpers.cs.
Csproj net8.0+ exclusion list updated accordingly.
* .NET: Consolidate served-model logic into FoundryChatClient
Move x-ms-served-model header capture from the standalone ServedModelChatClient
decorator directly into FoundryChatClient, eliminating a separate wrapper that
had to be applied at every Foundry entry point via WireServedModel().
- Register ServedModelPolicy in FoundryChatClient constructors (alongside the
existing AgentFrameworkUserAgentPolicy registration)
- Add StrongBox push/read logic to FoundryChatClient.GetResponseAsync and
GetStreamingResponseAsync
- Delete ServedModelChatClient.cs and its unit tests
- Remove WireServedModel() from FoundryAgent and AIProjectClientExtensions
- Update ServedModelPolicy/Scope XML docs to reference FoundryChatClient
- Simplify ServedModelTestHelpers to use FoundryChatClient directly
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(a2a): use non-streaming transport and return_immediately for background ops
When stream=False, use a client configured with streaming=False so the
SDK sends a single HTTP POST to message/send instead of opening an SSE
connection via message/stream. This matches the A2A protocol's design:
non-streaming calls use direct request/response, streaming calls use
Server-Sent Events.
Also sets return_immediately=background on SendMessageConfiguration so
the server respects the caller's intent for background operations.
Changes:
- Create separate streaming and non-streaming internal clients (sharing
the same httpx connection pool) to match protocol transport semantics
- Select non-streaming client for run(stream=False) calls
- Add SendMessageConfiguration with return_immediately=background
- Fallback to streaming client when non-streaming unavailable (e.g. user
provides their own client via constructor)
- Add tests for client selection and return_immediately behavior
Resolvesmicrosoft/agent-framework#5936
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: address PR review feedback
- Initialize last_request in MockA2AClient.__init__ for explicit state
- Use 'is not None' instead of truthiness for _non_streaming_client check
- Assert return_immediately propagates through non-streaming client path
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: only set configuration when background=True
Only attach SendMessageConfiguration to the request when background=True,
keeping requests minimal and preserving server-side defaults for normal
(foreground) operations. This follows the framework pattern of only
setting optional fields when they have meaningful values.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: only set return_immediately for non-streaming background ops
Per the A2A spec, return_immediately only applies to message/send
(non-streaming). It has no effect on streaming operations. Only set
the configuration field when both background=True and stream=False.
Adds test verifying streaming+background does not set return_immediately.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Consolidate Foundry chat client decorators into FoundryChatClient
- Replace AzureAIProjectChatClient and AzureAIProjectResponsesChatClient with a single internal sealed FoundryChatClient that covers three modes (pure responses, server-side agent reference, hosted agent endpoint).
- Rename AzureAIProjectChatClientExtensions to AIProjectClientExtensions to reflect that it extends AIProjectClient.
- All four AsAIAgent extension overloads and both FoundryAgent constructors now construct FoundryChatClient internally so the microsoft.foundry telemetry tag is uniform across paths.
- Introduce AgentFrameworkUserAgentPolicy that stamps agent-framework-dotnet/{version} on outbound requests, mirroring the Python agent-framework-python/{version} contract.
- Delete the Foundry-local MeaiUserAgentPolicy duplicate; rely on MEAI 10.5.1 to stamp MEAI/{version} automatically.
- HostedAgentUserAgentPolicy keeps the combined foundry-hosting/agent-framework-dotnet/{version} segment (Python parity) and upgrades the bare segment in place to avoid duplication.
- Tests reorganized: FoundryChatClientTests, AIProjectClientExtensionsTests, AgentFrameworkUserAgentPolicyTests, MeaiAutoUserAgentVerificationTests, plus in-place upgrade unit tests in HostedOutboundUserAgentTests.
* Promote FoundryChatClient to public; add file/vector-store helpers and ToPromptAgentAsync converter
- Promote FoundryChatClient from internal sealed to public sealed for Python parity, so .NET developers can hold and pass a FoundryChatClient directly the way Python developers do.
- Mode 3 (hosted agent endpoint) now materializes an AIProjectClient from the parsed project root, making GetService<AIProjectClient>() non-null across all three construction modes. This eliminates the per-mode asymmetry that previously hid project-level helpers from agents constructed via an agent endpoint URL.
- Add four new instance methods on FoundryChatClient mirroring Python's spec: UploadFileAsync, DeleteFileAsync, CreateVectorStoreAsync (bundles upload + create + wait), DeleteVectorStoreAsync. Single overload each, path-only inputs to start; additional overloads can be added later without breaking callers. All are Experimental, consistent with the rest of the Foundry package.
- Add ToPromptAgentAsync extension methods on ChatClientAgent and FoundryAgent for the agent-to-prompt-agent converter described in the Foundry spec. Mode 1 (responses API) synthesizes a DeclarativeAgentDefinition from the agent's ChatOptions; mode 2 (server-side agent reference, version, or record) returns the cached or freshly fetched Definition; mode 3 throws InvalidOperationException because no local definition exists to convert.
- Strict AITool to ResponseTool mapping for mode 1: AIFunction becomes CreateFunctionTool with the function's JSON schema; AITool instances that wrap a ResponseTool unwrap via GetService(typeof(ResponseTool)); anything else throws InvalidOperationException naming the offending tool type. Matches the Python spec's unsupported-tools-raise-ValueError contract.
- New unit tests: FoundryChatClientVectorStoreTests (22 tests covering all four helpers across the three FoundryChatClient construction modes plus validation and cancellation), FoundryPromptAgentConverterTests (16 tests covering both extension entry points across mode 1 synthesis, mode 2 cached and fetched paths, all failure modes, and a Python-parity guard asserting both extensions produce equivalent definitions for equivalent inputs), plus four new tests in FoundryChatClientTests for the mode 3 AIProjectClient materialization.
* Stop building duplicate ProjectOpenAIClient in FoundryAgent agent-endpoint ctor
After Plan #2's mode-3 AIProjectClient materialization, the inner FoundryChatClient already exposes a project-level AIProjectClient (via GetService) that internally provides the project-level ProjectOpenAIClient via GetProjectOpenAIClient(). FoundryAgent's agent-endpoint constructor was still independently constructing a second project-level ProjectOpenAIClient via the now-redundant CreateProjectLevelOpenAIClientFromAgentEndpoint helper — two handles to the same logical resource.
Refactor: the agent-endpoint constructor now reads the inner FoundryChatClient's materialized AIProjectClient via base.GetService(typeof(AIProjectClient)) and derives the project-level ProjectOpenAIClient from it. The dead helper on both FoundryAgent (private static wrapper) and FoundryChatClient (the actual implementation) is removed. The user-supplied per-agent ClientPipelineOptions primitives (Transport, RetryPolicy, NetworkTimeout, UserAgentApplicationId) are propagated into the materialized AIProjectClientOptions so test-injected transports and explicit retry / timeout / user-agent settings reach the project-level pipeline — preserving the behavior the dead helper used to provide.
Updated AgentEndpointConstructor_GetServiceAIProjectClient_ReturnsNull to its now-correct counterpart AgentEndpointConstructor_GetServiceAIProjectClient_ReturnsNonNull, since after Plan #2 the agent-endpoint ctor surfaces a non-null AIProjectClient (per user direction in Plan #2 Q2).
* Strip duplicated AIProjectClient/ProjectOpenAIClient state from FoundryAgent
Both _aiProjectClient and _projectOpenAIClient fields on FoundryAgent were redundant:
- _aiProjectClient: FoundryAgent's GetService<AIProjectClient> override returned this field, but DelegatingAIAgent.GetService → ChatClientAgent.GetService → FoundryChatClient.GetService<AIProjectClient> already returns the same instance through the delegating chain. Field + override are pure duplication.
- _projectOpenAIClient: only used by FoundryAgent's own GetService<ProjectOpenAIClient> override and by CreateConversationSessionAsync. Per user direction, ProjectOpenAIClient is no longer exposed via GetService on either FoundryChatClient or FoundryAgent — callers retrieve it from the AIProjectClient themselves (aiProjectClient.GetProjectOpenAIClient()) the same way the framework does internally. This eliminates the mode-3 asymmetry where the chat client's stored ProjectOpenAIClient was per-agent (URL /agents/{name}/endpoint/protocols/openai) while the agent's was project-level.
Refactor:
- Delete both fields on FoundryAgent and the GetService override.
- Delete the ProjectOpenAIClient branch from FoundryChatClient.GetService.
- CreateConversationSessionAsync now resolves AIProjectClient at call time via this.GetService<AIProjectClient>() and derives the conversations client from it.
- Update FoundryChatClient tests that asserted on GetService<ProjectOpenAIClient> to assert Null (deliberate removal).
- Update FoundryAgent tests AgentEndpointConstructor_GetServiceProjectOpenAIClient_ReturnsNonNull and ProjectEndpointConstructor_GetServiceProjectOpenAIClient_ReturnsNonNull to ...ReturnsNull, and rewrite AgentEndpointConstructor_PropagatesUserAgentApplicationId_ToProjectLevelClient to look up AIProjectClient instead.
No production code (only tests) referenced GetService<ProjectOpenAIClient>, so this is a safe surface reduction. Net: 30 insertions, 61 deletions; FoundryAgent shrinks to a pure delegator with only the two convenience methods (CreateSessionAsync, CreateConversationSessionAsync) on top of the delegating chain.
* Rename FoundryChatClient.HostedAgentName to AgentName and populate it for mode 2
The previous name implied a mode 3 only property tied to the hosted-agent endpoint URL. Today only hosted endpoints surface this name, but conceptually an agent name exists for every server-side agent the client talks to. Renaming to AgentName makes the property general-purpose and ready for future modes where the same chat client may target other server-side agent shapes that are not necessarily 'hosted'.
Mode 2 (server-side agent reference) now mirrors AgentReference.Name into AgentName so callers have a uniform handle regardless of construction mode:
* Mode 1 (pure responses): AgentName is null. There is no agent.
* Mode 2 (AgentReference): AgentName == AgentReference.Name.
* Mode 3 (agent endpoint URL): AgentName is parsed from the URL segment as before.
Converter discriminator update: FoundryPromptAgentConverter previously used 'HostedAgentName is not null' to detect mode 3 and reject it. Now that mode 2 also populates AgentName, the mode 3 guard moves to the end of the resolution chain and uses the unambiguous 'AgentName is set AND no AgentReference exists' test. The user-visible error message and behavior are preserved.
Dead-state cleanup spotted during format verify:
* IDE0052 surfaced that FoundryChatClient._projectOpenAIClient is never read since the prior refactor stopped exposing ProjectOpenAIClient via GetService and rewired CreateConversationSessionAsync to resolve the AIProjectClient through the delegating chain. The field is deleted and its three ctor assignments removed.
* HostedAgentEndpointInner.PerAgentClient only existed to plumb the per-agent ProjectOpenAIClient into that now-deleted field, so the property and its ctor parameter are removed. The local 'perAgentClient' variable inside BuildHostedAgentEndpointInner is still needed to derive the inner IChatClient, but no longer escapes the helper.
Tests:
* Mode1_PureResponses_ReturnsNullForAgentSpecificServices now also asserts AgentName is null.
* New Mode2_AgentReference_PopulatesAgentNameFromAgentReference asserts the mode 2 mirror.
* Mode3_HostedAgentEndpoint_ParsesAgentNameFromUrl renamed assertion target HostedAgentName to AgentName.
Verification: 335/335 net10.0, 273/273 net472 Foundry unit; 229/229 Foundry.Hosting unit; format-verify (WSL2 + Docker mcr.microsoft.com/dotnet/sdk:10.0) clean on Microsoft.Agents.AI.Foundry.
* Adopt canonical mode names: Responses Agent, Prompt Agent, Agent Endpoint
Three FoundryChatClient construction modes now have one canonical noun used everywhere.
* Responses Agent (Mode 1): inline ChatClientAgent, project-level Responses API, no server-side def.
* Prompt Agent (Mode 2): server-side ProjectsAgentDefinition invoked by AgentReference.
* Agent Endpoint (Mode 3): per-agent URL /agents/{name}/endpoint/protocols/openai. Hosted-or-not.
'Hosted' stays the kind of agent (Microsoft.Agents.AI.Foundry.Hosting). Not synonym of Mode 3.
Rings:
1. XML docs + error messages use canonical names. en-GB to en-US: centralises, synthesise.
2. HostedAgentEndpointInner -> AgentEndpointInner, BuildHostedAgentEndpointInner -> BuildAgentEndpointInner.
3. Tests: Mode1_PureResponses_* -> Mode1_ResponsesAgent_*, Mode2_AgentReference_* -> Mode2_PromptAgent_*, Mode3_HostedAgentEndpoint_* -> Mode3_AgentEndpoint_*.
Pure rename. No behavior change. 335/335 net10 + 273/273 net472 unit, format clean.
* Address PR #5940 design feedback (Q-A through Q-F)
Q-A: poll vector store til status leaves InProgress before return. Exp backoff 250ms-2s. Honor cancel.
Q-B: try/catch upload loop. Mid-fail = best-effort DeleteFileAsync on already-uploaded ids. Swallow cleanup errors.
Q-C: pinned AgentReference.Version uses GetAgentVersionAsync. Empty/whitespace/'latest' = GetLatest path.
Q-D: HostedAgentUserAgentPolicy detects existing combined 'foundry-hosting/...' segment. No double prefix.
Q-E: mode-3 vector-store test uses fake transport. No DNS to example.com.
Q-F: no shim. Class always [Experimental] (since 8015e00f5, before dotnet-1.0.0). No compat contract. Callers rename to AIProjectClientExtensions.
Rebase onto origin/main reconciliation: aad20c2b3 added public AsAIAgent(this AIProjectClient, Uri agentEndpoint, ...) extension that calls an internal FoundryAgent(AIProjectClient, Uri, ...) ctor. Reintroduced that ctor + a new FoundryChatClient(AIProjectClient, Uri, ProjectOpenAIClientOptions?) overload that reuses the supplied AIProjectClient's pipeline (via GetProjectResponsesClientForAgentEndpoint) instead of stamping a fresh credential.
Verified: 346/346 net10 + 284/284 net472 Foundry unit, 230/230 Foundry.Hosting unit, format clean.
* Add FoundryAgent helper extensions: UploadFile/DeleteFile/CreateVectorStore/DeleteVectorStore
4 thin forwarders on FoundryAgent that route to the inner FoundryChatClient's helpers via agent.GetService<FoundryChatClient>().X(). Live in existing FoundryAgentExtensions.cs alongside ToPromptAgentAsync.
Throws InvalidOperationException when agent does not expose a FoundryChatClient via GetService (same pattern as ToPromptAgentAsync).
Unit tests: FoundryAgentExtensionsTests covers all 4 forwarders + null-agent ArgumentNullException for each. 8 new tests, 354/354 net10 + 292/292 net472.
Integration tests: parallel FoundryAgentExtensionsTests under Foundry.IntegrationTests mirrors the existing CreateAgent_CreatesAgentWithVectorStoresAsync shape (upload -> create vector store -> FileSearch tool answers question -> cleanup), but routes every helper call through the new FoundryAgent extensions. 4 new IT tests, all verified pass live against the real Foundry project (12-30s each). Skipped by default like the existing vector-store IT.
* Address Sergey's PR review comments
#1 (FoundryAgent.cs:139): drop unused aiProjectClient param from internal FoundryAgent(AIProjectClient, ChatClientAgent) ctor. Was discarded after null-check. Inner FoundryChatClient already surfaces AIProjectClient via GetService. 3 call sites in AIProjectClientExtensions updated.
#2 (FoundryChatClient.cs:376): add pollingTimeout param to CreateVectorStoreAsync. Defaults to 5 min, configurable, Timeout.InfiniteTimeSpan disables. Throws TimeoutException with vector store id and elapsed seconds when bound exceeded. CancellationToken still wins. New unit test PollingTimeout_ThrowsTimeoutExceptionAsync. FoundryAgentExtensions forwarder updated to plumb the new param.
Verified: 355/355 net10 + 293/293 net472 Foundry unit, 230/230 Foundry.Hosting unit, format clean.
* feat(foundry): add experimental hosted tool factories on FoundryChatClient
Adds eight new `@experimental` static factory methods on `FoundryChatClient`
covering Foundry-hosted tools that previously had no helper:
- get_azure_ai_search_tool
- get_sharepoint_tool
- get_fabric_tool
- get_memory_search_tool
- get_computer_use_tool
- get_browser_automation_tool
- get_bing_custom_search_tool
- get_a2a_tool
All factories are marked with the new `ExperimentalFeature.FOUNDRY_TOOLS` tag
and resolve the underlying `azure-ai-projects` preview classes lazily through
a `_require_sdk_class` helper so older SDK versions still import cleanly and
fail with a clear `ImportError` only on use.
Tests cover each factory's return type and field wiring, the experimental
metadata, and the missing-SDK-class fallback.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* test(foundry): address review comments on tool-factory tests
* Skip preview-tool tests gracefully (`_skip_if_sdk_class_missing`) when
the installed `azure-ai-projects` does not expose the required preview
class, matching the lazy-import guard in production code so the test
suite stays green on older SDK installs.
* Add `filterwarnings("ignore::FutureWarning")` to each new tool-factory
test (and the parametrized metadata test) so they remain stable under
strict warning configurations \u2014 the global dedup in
`_feature_stage._WARNED_FEATURES` makes `pytest.warns` brittle across
ordered runs.
* Use `monkeypatch.setattr(..., None, raising=False)` instead of
`delattr` in the missing-SDK-class test so it works for modules that
implement PEP 562 `__getattr__`.
* Split the long `get_bing_custom_search_tool` return into two lines for
readability.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(foundry): harden tool-factory kwargs against silent override
* Reorder the dict-literal kwargs assembly in get_azure_ai_search_tool,
get_memory_search_tool, and get_bing_custom_search_tool so explicit
parameters always take precedence over **kwargs (matching the safe
pattern already used in get_a2a_tool). This prevents a caller
passing `project_connection_id`, `index_name`, `memory_store_name`,
`scope`, or `instance_name` through `**kwargs` from silently
overriding the explicit security-sensitive arguments.
* Update the README experimental note to reflect once-per-feature-id
dedup semantics of `_feature_stage._WARNED_FEATURES` rather than
claiming a per-factory "first use" warning.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(foundry): split FOUNDRY_TOOLS / FOUNDRY_PREVIEW_TOOLS, add bing-grounding
- Add ExperimentalFeature.FOUNDRY_PREVIEW_TOOLS to distinguish wrappers around
preview Foundry SDK tool classes (Sharepoint/Fabric/Memory/ComputerUse/
BrowserAutomation/BingCustomSearch/A2A) from FOUNDRY_TOOLS, which is for
GA-SDK wrappers that are simply new in agent-framework-foundry
(AzureAISearch, BingGrounding).
- Add get_bing_grounding_tool factory and a 'Choosing a web grounding tool'
comparison block on get_web_search_tool / get_bing_grounding_tool /
get_bing_custom_search_tool docstrings.
- Drop the _require_sdk_class lazy resolver: every guarded class is available
at azure-ai-projects>=2.1.0 (the package floor), so import them eagerly.
Concrete return types replace 'Any'.
- README: split the experimental factories into two tables, one per feature
flag, with a note explaining the distinction.
- Tests: split into FOUNDRY_TOOLS / FOUNDRY_PREVIEW_TOOLS factory cases;
drop the obsolete missing-SDK-class ImportError test.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replaces every floating tag in our workflow and composite action files
with an immutable 40-character commit SHA, keeping the original `# vX`
comment so Dependabot can still propose version bumps. 186 occurrences
across 25 workflows and 2 composite actions.
Also widens the github-actions Dependabot entry to use the plural
`directories` key with `/.github/actions/*` so composite actions under
`.github/actions/<name>/action.yml` are kept up to date. Previously
Dependabot only scanned `.github/workflows` and the repo-root
`action.yml`, leaving our `python-setup` and `sample-validation-setup`
composite actions unmaintained.
* Show more authentication methods in Foundry Toolbox MCP
* Remove hardcoded toolbox version num
* Add Foundry MCP OAuth consent handling
* Use message instead of the dedicated item type
* Go back to using OAuthConsentRequestOutputItem
* WIP: sample testing
* Update error code
* Address review on Foundry Toolbox MCP samples
Reviewed feedback addressed:
- Drop the branch-pinned `git+https://...@feature/...` entries from
`04_foundry_toolbox/requirements.txt`; restore the simple comment + `mcp`
runtime dep. The git pins were only useful while iterating on the PR and
shouldn't ship. (eavanvalkenburg)
- Fix the `/toolsets/` typo in both `04_foundry_toolbox/README.md` and
`06_files/README.md`. Verified empirically against the
research_toolbox in the test workspace: the toolbox MCP gateway lives at
`/toolboxes/{name}/mcp?api-version=v1` and requires the
`Foundry-Features: Toolboxes=V1Preview` header. `/toolsets/{name}/mcp`
returns 403 with `preview_feature_required: Toolsets=V1Preview` (a
different opt-in feature).
- Wrap `httpx.AsyncClient(...)` in `async with ... as http_client:` in both
samples so the connection pool is cleaned up. (Copilot reviewer)
- Make the `TOOLBOX_NAME` env var consistent in both samples. Previously the
tool name silently fell back to `"toolbox"` when `TOOLBOX_NAME` was unset,
but `resolve_toolbox_endpoint()` still required `TOOLBOX_NAME` and would
raise `KeyError`. The samples now resolve the endpoint once and derive the
tool name from the resolved URL when `TOOLBOX_NAME` isn't set, so the
local tool name always matches the upstream toolbox identity regardless
of which env var the user set. (Copilot reviewer)
- Rename `_responses.is_consent_error` to `consent_url_from_error`: the
helper returns `str | None` (the consent URL), not a bool, so the new
name matches behavior. Update the test class accordingly. (eavanvalkenburg)
- Tighten `_handle_inner_agent`'s lazy-entry catch from `Exception` to
`AgentFrameworkException`, the type the MCP layer actually wraps consent
errors in via `MCPStreamableHTTPTool.__aenter__` →
`ToolExecutionException(inner_exception=mcp_error)`. Network failures,
cancellations, and other non-framework exceptions now propagate normally
instead of being briefly caught and re-raised. The test helper
`_make_consent_error` is updated to use `ToolExecutionException` so it
matches the real-world wrapping. (eavanvalkenburg)
- Clarify the `github_pat` description in `agent.manifest.yaml` to note
it's only needed when the PAT-based connection (`github-mcp-pat-conn`)
is chosen; users selecting the OAuth2 connection (`github-mcp-oauth-conn`)
can leave it empty. (Copilot reviewer)
Validation: ran both samples end-to-end against a real Foundry toolbox
(`research_toolbox`) -- the samples connect successfully and the agent
lists the toolbox's MCP tools (`api_specs___fetch_azure_rest_api_docs`,
etc.). `uv run poe test -P foundry_hosting` passes (119 tests), pyright +
mypy clean.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* docs: fix broken Foundry samples link in 04_foundry_toolbox README
The previous URL pointed to an old location of the toolbox supported-scenarios
doc; the doc moved to /samples/python/hosted-agents/SUPPORTED_TOOLBOX_SCENARIOS.md
and the old /samples/python/toolbox/azd path now 404s.
Caught by the markdown-link-check CI step.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Enable instrumentation by default
* Update samples
* Optimization when span is not recording
* Address Copilot comments
* Revert uv.lock
* Add warning
* Formatting
* Fix mypy
* Add disable_instrumentation() with sticky user-intent semantics
Add a public disable_instrumentation() entry point so users can explicitly opt
out of Agent Framework telemetry, with a sticky-disable flag that makes the
user's intent "leading" — no framework code path (foundry's
configure_azure_monitor, configure_otel_providers, enable_instrumentation,
enable_sensitive_telemetry, or direct OBSERVABILITY_SETTINGS.enable_*
writes) can re-enable instrumentation until the user explicitly clears the
disable with enable_instrumentation(force=True) /
enable_sensitive_telemetry(force=True).
Also addresses the two remaining unresolved review threads on the PR:
1. test_observability_settings_defaults_instrumentation_true pins the new
"ENABLE_INSTRUMENTATION defaults to True when env unset" behavior.
2. test_enable_instrumentation_reads_env_sensitive_data restores coverage
for the post-import load_dotenv() fallback path.
Implementation:
- ObservabilitySettings.enable_instrumentation / enable_sensitive_data become
properties backed by _enable_*. While _user_disabled is True, the getters
return False and the setters drop True writes (defense in depth so third-
party writes can't subvert the disable).
- Public is_user_disabled read-only property lets integrations (e.g. foundry's
configure_azure_monitor) cheaply check the disable state without poking at
privates.
- enable_instrumentation() and enable_sensitive_telemetry() short-circuit with
an info log when disabled; gain a force=True kwarg that clears the disable.
- configure_otel_providers() still creates providers / exporters / views so a
later force-enable can use them, but logs an info message when called while
disabled.
- Foundry's FoundryChatClient.configure_azure_monitor and
FoundryAgent.configure_azure_monitor early-return when the user has
disabled, so Azure Monitor's global providers aren't installed unnecessarily.
Tests: 11 new tests covering default-on, env re-read at call time, sticky
behavior against each re-enable surface (enable_instrumentation,
enable_sensitive_telemetry, configure_otel_providers, direct attribute
writes), force=True override, re-arming the disable, and the __all__ export.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* docs: document disable_instrumentation() and force=True paths
Add a "Disabling instrumentation" section to the observability sample README
that walks through:
- The distinction between the ENABLE_INSTRUMENTATION env var (initial,
non-sticky) and disable_instrumentation() (process-wide, sticky).
- Why the sticky semantics matter: framework integrations like
FoundryChatClient.configure_azure_monitor() can call
enable_instrumentation() as part of their setup, and the user's opt-out
needs to win.
- All five surfaces guarded by the sticky disable (property reads, public
enable functions, configure_otel_providers, direct attribute writes,
is_user_disabled-aware integrations).
- The force=True escape hatch on both enable_instrumentation() and
enable_sensitive_telemetry().
- How third-party integrations should consult OBSERVABILITY_SETTINGS.is_user_disabled.
- The limits of the disable (does not tear down existing providers /
in-flight spans / third-party instrumentation, does not persist across
processes).
Cross-links the new section from the ENABLE_INSTRUMENTATION row in the env
vars table.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* docs: soften disable_instrumentation() overclaim about telemetry guarantees
Replace 'no telemetry will be emitted no matter what' (which is too strong,
since callers can still pass force=True or mutate private attributes) with
language framing the disable as a user-intent contract that library and
framework code is expected to honor: the framework actively short-circuits
the public enable paths, force=True and private-attribute writes are
acknowledged as out-of-contract escape hatches that integrations should
not use on the user's behalf.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* docs: correct observability Dependencies section
- opentelemetry-sdk is no longer a hard dependency; it is lazily imported by
create_resource(), create_metric_views(), and configure_otel_providers()
with a clear ImportError when missing. Day-to-day instrumentation works
with opentelemetry-api alone provided some other component configures the
global OpenTelemetry providers (Azure Monitor, an APM agent, application
bootstrap, etc.).
- opentelemetry-semantic-conventions-ai is no longer used anywhere in the
source; remove it from the listed dependencies.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* docs: replace stale observability migration guide with current PR's only relevant migration
The old guide documented the move away from setup_observability(otlp_endpoint=...)
which was an earlier-release API change unrelated to this PR and stale enough that
it's more confusing than helpful at this point. Replace it with a short note on the
single migration this PR introduces: callers of
enable_instrumentation(enable_sensitive_data=True) should switch to
enable_sensitive_telemetry(). Cross-link to the Disabling instrumentation section
for the rare 'force on without enabling sensitive data' use case where
enable_instrumentation() still applies.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Add A2AAgentOptions and align A2AAgent constructors with ChatClientAgent pattern
Adds a new A2AAgentOptions class (Id, Name, Description, Clone) and an options-based constructor on A2AAgent, mirroring ChatClientAgent/ChatClientAgentOptions. The existing parameter-based constructor is preserved for backward compatibility and now delegates to the options-based one.
Extension methods are extended with options-based overloads:
- A2AClientExtensions.AsAIAgent(IA2AClient, A2AAgentOptions, ...)
- A2AAgentCardExtensions.AsAIAgent(AgentCard, A2AAgentOptions, ...)
- A2ACardResolverExtensions.GetAIAgentAsync(A2ACardResolver, A2AAgentOptions, ...)
For card-based creation, user-supplied options override values from the agent card; Name and Description fall back to card values when not set.
Options are cloned when stored on the agent to prevent post-construction mutation, matching the ChatClientAgent pattern.
Resolves#5870.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address PR review comments
- Add Throw.IfNull(client) in A2AClientExtensions.AsAIAgent
- Add Throw.IfNull(card) in A2AAgentCardExtensions.AsAIAgent
- Clarify httpClient docs in A2ACardResolverExtensions.GetAIAgentAsync: it applies to the created A2A client, not to card discovery
- Rename test methods from GetAIAgent_* to AsAIAgent_* to match the API under test
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: feat: add agent-framework-monty (Monty-backed CodeAct)
New alpha package that wraps pydantic-monty (a Rust-based Python
interpreter) behind the same CodeAct API surface as
agent-framework-hyperlight, so users can swap providers with minimal
code change.
Public API (agent_framework_monty):
- MontyCodeActProvider — ContextProvider that injects a run-scoped
execute_code tool plus dynamic CodeAct instructions.
- MontyExecuteCodeTool — standalone FunctionTool for mixed-tool agents
or manual static wiring.
- FileMount / FileMountInput / MountMode — public types mirroring the
Hyperlight names, with Monty's mode (read-only/read-write/overlay)
and write_bytes_limit on FileMount.
Constructor kwargs (both classes) mirror Hyperlight where possible:
tools, approval_mode, workspace_root, file_mounts; plus a Monty-only
resource_limits forwarding ResourceLimits to Monty.start().
Filesystem flow:
- workspace_root auto-mounts at /input (read-write), matching Hyperlight.
- file_mounts accepts string shorthand, (host, mount) tuple, or
FileMount with mode + write cap.
- Files written under read-write mounts are scanned post-execution and
returned as Content.from_data items (mirrors Hyperlight /output).
- overlay mounts buffer writes in-memory; read-only mounts reject writes.
Internals:
- _monty_bridge.InlineCodeBridge ports the inline (non-durable) bridge
from anthonychu/maf-codeact-monty-python; handles FunctionSnapshot /
FutureSnapshot pause/resume, dispatches direct typed calls + the
call_tool fallback, forwards mount/limits to Monty.start(...).
- generate_type_stubs emits per-tool stubs so Monty's `ty` type-checker
rejects bad calls before any host tool runs.
Alpha-policy compliance (per python-package-management skill):
- Added agent-framework-monty = { workspace = true } to root
pyproject.toml.
- Added row to python/PACKAGE_STATUS.md.
- Added monty entry under Experimental in python/AGENTS.md.
- NOT added to core[all]; NO agent_framework.monty lazy shim (deferred
to beta promotion).
Samples (three sets, import from agent_framework_monty directly):
- samples/02-agents/context_providers/code_act/monty_code_act.py
(provider pattern) + updated local README.
- samples/02-agents/tools/monty_code_interpreter/ (standalone +
manual-wiring + README).
- samples/04-hosting/foundry-hosted-agents/responses/11_monty_codeact/
(full hosted-agent layout with uv-based pyproject.toml + Dockerfile,
Azure Monitor wiring via APPLICATIONINSIGHTS_CONNECTION_STRING +
enable_instrumentation, ENABLE_INSTRUMENTATION and
ENABLE_SENSITIVE_DATA env vars). The alpha wheel is vendored into
./wheels/ (gitignored) via vendor-wheel.sh; new row added to the
parent Responses-API README.
Tests:
- 28 hermetic unit tests (stubbed pydantic_monty).
- 18 integration tests marked @pytest.mark.integration, auto-skipped
when pydantic_monty is unimportable; exercise the real Monty
runtime: print round-trip, last-expression value, direct typed
tool dispatch, call_tool fallback, async tool, asyncio.gather
parallelism, ty type-check rejection, OS blocked by default,
workspace_root read+write capture, read-only / overlay mount
semantics, resource_limits.max_duration_secs abort, approval
gating end-to-end, full Agent run with a scripted chat client.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: fix: monty FileMount test compares against the normalized POSIX path
The shorthand string mount goes through _normalize_mount_path, which
rewrites Windows drive letters like 'C:\\Users\\...' into
'/C:/Users/...' (POSIX-style). The Windows CI runners surfaced this
because tmp_path resolves to a backslashed Windows path; the test was
comparing against the raw str(host_a) instead of the normalized form.
Compare against _normalize_mount_path(str(host_a)) so the assertion is
platform-independent.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: fix: address PR #5915 review feedback
- _execute_code_tool docstring: clarify that the Monty backend supports
scoped filesystem access via workspace_root / file_mounts (blocked by
default).
- _to_monty_mount: import pydantic_monty lazily through load_monty so
missing-dependency errors surface as the same actionable RuntimeError
the rest of the package raises (not a bare ImportError at module load).
Renamed _load_monty -> load_monty for the same reason.
- _python_type_repr: emit None for type(None) instead of Any, and
normalize both typing.Union[...] and PEP-604 X | Y to PEP-604 syntax
so Optional[X] / Union[..., None] / -> None signatures round-trip
correctly through ty validation. Added a regression test.
- _PrintCollector: track a running character count instead of
recomputing sum(len(c) for c in self.chunks) per callback. Eliminates
the O(n^2) cost on print-heavy code.
- Instructions: mention that the value of the final expression is also
returned alongside captured stdout (matches actual behavior).
- 11_monty_codeact Dockerfile: pin ghcr.io/astral-sh/uv to 0.11.6
instead of :latest for reproducible builds.
- 11_monty_codeact README: replace the bare "see parent README" pointer
with sample-specific steps (./vendor-wheel.sh + uv sync + uv run),
since the sample uses pyproject.toml + a vendored wheel rather than
requirements.txt.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: sample: 11_monty_codeact installs agent-framework-monty from PyPI
Drop the vendored-wheel scaffolding now that agent-framework-monty is on
PyPI as an alpha (1.0.0a*) release:
- pyproject.toml: remove [tool.uv.sources] override; keep [tool.uv]
prerelease = "allow" so uv pulls the alpha automatically.
- Dockerfile: drop the COPY wheels/ step.
- README: drop the ./vendor-wheel.sh setup step and the
not-yet-on-PyPI warning.
- Delete vendor-wheel.sh and the gitignored wheels/ directory.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: fix(monty): harden post-execution file capture against symlink escape
Same class of issue as the MSRC-reported Hyperlight finding: the
post-execution capture walked workspace_root with Path.rglob() +
is_file() + read_bytes() - all of which follow symlinks. An attacker
who controls the workspace (cloned repo, extracted archive, shared
workspace) could pre-place `workspace/leak.txt -> /etc/passwd` or
`workspace/outside_dir -> /etc/` and have host files surface as
captured Content items.
Monty's mount layer already rejects symlink reads from inside the
sandbox across all three modes (verified empirically), so the runtime
path was safe. This commit closes the post-execution scan path.
Changes:
- New `_iter_real_files(root)` walker that uses iterdir() +
is_symlink() to skip symlinks at every directory level and yields
only real files. Replaces the previous `host_root.rglob("*")` calls
in both `_snapshot_writable_mounts` and `_capture_written_files`.
- Use `Path.lstat()` instead of `Path.stat()` so size/mtime can never
be taken from a symlink target.
- Three new integration tests reproducing the MSRC attack shape
against the workspace_root flow: symlink-to-file outside workspace,
symlink-to-directory outside workspace, and a guard ensuring
legitimate sandbox writes are still captured when symlinks are
present.
Per user request, hyperlight is untouched in this commit (separate fix).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: fix(monty): skip symlink regression tests when unsupported
Apply the same Windows-CI safety guard as the hyperlight fix in PR #5919:
the three symlink integration tests create symlinks via Path.symlink_to(),
which fails with OSError / NotImplementedError on unprivileged Windows
runners. Add a local _symlinks_supported helper (mirroring the one in
packages/core/tests/core/test_skills.py) and pytest.skip when symlinks
aren't available, so the tests no longer fail for environment reasons.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: fix(monty): address PR #5915 follow-up review feedback
- _invoke_tool: drop the inspect.iscoroutinefunction(...) branch and
always `await self.tool_map[name](**kwargs)`. Every entry in
tool_map is `partial(FunctionTool.invoke, skip_parsing=True)` and
FunctionTool.invoke is `async def`, so the branching was dead code -
and on Python versions affected by cpython#98590,
iscoroutinefunction(partial(bound_async_method, ...)) returns False,
causing the bridge to take the asyncio.to_thread path, return an
unawaited coroutine, and surface it as a JSON-serialization failure
for every tool call. Added a regression test
test_invoke_tool_awaits_partial_wrapped_async_method.
- generate_type_stubs: skip tools whose name is not a valid Python
identifier or is a Python keyword. FunctionTool.name has no upstream
validation, so a name like "weird-name" produced a syntax error in
the stubs and a name like "broken\n pass\nasync def injected"
would inject arbitrary stub source. Non-identifier names stay
reachable via `call_tool("weird-name", ...)` at runtime; they just
don't get type-checked stubs. Added regression test
test_generate_type_stubs_skips_non_identifier_tool_names.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Bump Python package versions to 1.5.0 for a release
* Promote orchestrations to 1.0.0rc1
* ci(python-setup): merge dynamic exclude into existing workspace exclude
The python-setup action injected exclude = [...] verbatim into
[tool.uv.workspace], producing a duplicate 'exclude' key when the
section already had a static exclude. Scope the rewrite to the
[tool.uv.workspace] section and append the package to the existing
array when present; idempotent if the package is already excluded.
* Address Copilot review feedback: raise inter-package floors to 1.5.0
- foundry, foundry-local: agent-framework-openai >=1.4.0 -> >=1.5.0
- azure-contentunderstanding: agent-framework-foundry >=1.4.0 -> >=1.5.0
- azurefunctions: pin agent-framework-durabletask to >=1.0.0b260519,<2
Keeps lockstep cohort consistent and avoids mixed 1.4.x / 1.5.0 installs.
* Re-include azurefunctions and durabletask in the uv workspace
The pinned durabletask>=1.4.0 floor is enough to make resolution succeed;
the workspace exclude was over-correction and broke CI samples and pyright
type-checking (re-exports in agent_framework/azure/__init__.pyi plus
samples/04-hosting/{azure_functions,durabletask}/ could not resolve their
imports). Dropping them from agent-framework-core[all] still stands so the
metapackage does not pull them.
* Restore azurefunctions and durabletask in agent-framework-core[all]
The durabletask floor pin keeps users on the safe 1.4.0, so they are once
again included in the metapackage. Update CHANGELOG to reflect the pin
rather than an [all] removal.
* Raise uvicorn ceiling in ag-ui and devui to allow 0.42+
The root override-dependencies pins uvicorn[standard]>=0.34.0 (no upper)
and the workspace lock resolves to 0.47.0. The package ceiling <0.42.0
meant the workspace was no longer testing the declared supported range.
Bump to <1 so the lock fits within the declared bounds.
Also picked up by validate-dependency-bounds: refresh stale orchestrations
RC pin in devui dev deps.
The shared composite action ran `uv sync --all-packages --all-extras
--dev -U` on every job, which upgrades every dependency to the latest
compatible version instead of using the pinned versions in `uv.lock`.
That is currently producing a hard resolver failure on every CI job:
No solution found when resolving dependencies for split
(markers: python_full_version >= '3.11' and sys_platform == 'darwin')
Because there are no versions of durabletask and
agent-framework-durabletask depends on durabletask>=1.3.0,<2,
we can conclude that agent-framework-durabletask's requirements
are unsatisfiable.
Dropping `-U` makes the install use the workspace lockfile, which is
what is reproducible locally and what we publish releases against.
Upgrades should be opt-in (via a scheduled job or a separate workflow)
rather than implicit on every CI run.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add sample that shows code execution and skills together
* Use nuget for python module path
* Update readme.
* Fix formatting.
* Reduce flashing in rendering.
* Improve screen clearing for Powershell
* Add a couple of small UX fixes
The second self._cache.pop(key, None) call is a guaranteed no-op: the first pop has already removed the key (or returned None), and there is no await between the two statements that could allow another coroutine to re-add it. Removing the dead line clarifies intent without changing behavior.
* Python: fix(hyperlight): skip symlinks when staging files into the sandbox
The helpers that populate the sandbox input tree (``_copy_path`` and the
``_path_tree_signature`` walker used for cache invalidation) relied on
``Path.is_file()``, ``Path.is_dir()`` and ``shutil.copy2`` - all of which
follow symlinks by default. When the source tree contains symlinks, that
let entries from outside the configured input source surface inside the
sandbox.
Harden both code paths to never follow symlinks:
- ``_copy_path`` now bails out via ``Path.is_symlink()`` before any
``is_dir()`` / ``is_file()`` check, skips non-regular files, and uses
``shutil.copy2(..., follow_symlinks=False)`` as defense in depth.
- New ``_iter_real_entries`` walker replaces the previous ``Path.rglob``
call inside ``_path_tree_signature`` (rglob follows directory symlinks).
- ``_path_tree_signature`` switches to ``Path.lstat()`` so size/mtime are
never read through a symlink target.
Added regression tests covering:
- A pre-placed file symlink in ``workspace_root`` (top level).
- A pre-placed directory symlink in ``workspace_root``.
- A nested file symlink inside a real subdirectory.
- ``_path_tree_signature`` ignoring symlinks so the cache key reflects only
what is actually staged.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: fix(hyperlight): address PR #5919 review feedback
- _iter_real_entries now yields directories and regular files only,
skipping non-regular entries (sockets/FIFOs/devices). Keeps the
cache-key signature consistent with what _copy_path actually stages.
- The four new symlink regression tests skip when the platform does not
support symlink creation (e.g. unprivileged Windows runners), via a
local _symlinks_supported helper modelled on the one in
packages/core/tests/core/test_skills.py. Prevents OSError /
NotImplementedError from failing CI jobs that have nothing to do with
the change under test.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: fix(hyperlight): address PR #5919 follow-up review feedback
- _copy_path docstring: narrow the scope to "symlink entries present in
the source tree at rest" and explicitly call out that the copy is NOT
atomic with respect to concurrent mutation of the source tree.
Callers who need that stronger guarantee should snapshot their
workspace before passing it in. Avoids overpromising on a TOCTOU
window that pathlib cannot express; closing it properly would need
fd-based traversal (O_NOFOLLOW | O_DIRECTORY + os.scandir(fd)) with
a separate Windows story, which is out of scope for this targeted
fix.
- _path_tree_signature: drop the `if path.is_symlink(): return ()`
short-circuit. Resolve a symlink root to its real target before
walking instead. The public construction flow already resolves
workspace_root / file_mounts[].host_path up front so this never
affected user-facing code, but the short-circuit was misleading and
would have produced an empty, stable signature for any direct
caller that builds a _RunConfig without going through the public
constructor. Defense in depth: even if a future call site forgets
to resolve the root, the cache key still reflects real contents.
- Added regression test
test_path_tree_signature_walks_through_symlinked_root: a symlinked
workspace root must produce a non-empty signature, AND the signature
must change when the real target's contents change so the cache key
actually invalidates.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Record actual served model as response model for Azure OpenAI
* Formatting
* Fix tests
* Fix pipeline error
* Comments
* Address review: surface served model via ChatResponse.model
Apply blocking review feedback from PR #5910:
- Use ChatResponse.model / ChatResponseUpdate.model as the source of truth
for the Azure x-ms-served-model header value, instead of stashing it in
additional_properties and overriding it again in observability.
Observability already reads response.model; the chat client now overwrites
it post-parse when the served-model header is present. Empirically the
Azure Responses API returns the deployment alias in body.model and the
actual snapshot (e.g. gpt-5-nano-2025-08-07) in this header.
- Move the AZURE_OPENAI_SERVED_MODEL_HEADER constant out of observability.py
and into RawOpenAIChatClient (as the SERVED_MODEL_HEADER ClassVar). The
header is Azure-OpenAI-Responses-API-specific so observability does not
need to know about it.
- Revert the streaming text_format path to client.responses.stream(...) and
drop the _pydantic_model_to_text_format_param helper. That helper imported
from openai.lib._parsing._responses (a private SDK path) and the swap to
responses.create(stream=True) dropped client-side output_parsed for
structured-output streaming. The streaming-with-text_format path is the
only one that does not surface the served-model header - documented inline.
- Wrap the raw streaming responses in async with so the underlying socket
closes deterministically (continuation_token retrieve + create paths).
- Fix the empty-string / whitespace-only header at the source by stripping
in _extract_served_model and returning None when nothing remains.
- Revert unrelated formatting-only churn in _skills.py and test_mcp.py.
- Update unit tests to assert against chat_response.model / update.model
and add an aggregated streaming assertion plus a pin that the
streaming-with-text_format path does not get the header.
Verified end-to-end against Azure OpenAI Responses API: deployment alias
gpt-5-nano now reports gpt-5-nano-2025-08-07 as ChatResponse.model in both
the non-streaming and streaming paths.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: preserve streaming structured output finalization
Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/f62076ef-558d-49e8-8fe2-f38d527c9639
Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>
* refactor: name streaming response finalizer
Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/f62076ef-558d-49e8-8fe2-f38d527c9639
Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>
* fix: capture streaming response format after prepare
Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/f62076ef-558d-49e8-8fe2-f38d527c9639
Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>
* refactor: clarify streaming response format capture
Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/f62076ef-558d-49e8-8fe2-f38d527c9639
Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>
* test: use public API for streaming structured output
Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/f62076ef-558d-49e8-8fe2-f38d527c9639
Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>
* Inline the served-model header override at its two call sites
The `_apply_served_model_header` helper was a 1-line wrapper around
`_extract_served_model`. Inlining the `if served_model is not None: ...`
matches the pattern already used in the streaming paths and folds the
explanatory docstring onto `_extract_served_model` (which is now the
single place that knows about the header).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>
* Improve the handling of intermediate outputs for workflows and orchestrations
* Address PR review feedback on intermediate output forwarding
- Switch workflow.as_agent() forwarding to an explicit allowlist of {output,
intermediate, data, request_info} so orchestration-internal events
(group_chat, handoff_sent, magentic_orchestrator) stay inside the workflow
instead of leaking into agent responses via str(data) coercion.
- Stop raising on intermediate AgentResponseUpdate in non-streaming run();
surface the partial as a Message with text_reasoning content. The defensive
raise still applies to terminal output events, where Update payloads would
corrupt message ordering.
- Extend the DevUI workflow-event mapper so intermediate yields wrapping
plain strings, Messages, and list[Message] render as visible output items
instead of generic completed-trace events.
- Add orchestration coverage for GroupChat, Handoff, and Magentic builders
(default vs intermediate_outputs=True; structural where end-to-end is heavy).
* Lift output-designation policy into a value type
Replace the ``Workflow._output_executors`` list and the
``RunnerContext.should_label_as_intermediate`` Protocol method with a single
immutable ``OutputDesignation`` value type owned by ``Workflow``. Thread the
designation as a parameter through the existing call chain (Runner ->
EdgeRunner -> Executor -> WorkflowContext) so ``yield_output`` consults the
threaded snapshot directly rather than calling back into the runner context.
Removes the ``InProcRunnerContext._workflow`` back-reference and the
``WorkflowBuilder.build()`` assignment that wired it up. Adds the public
predicate ``Workflow.is_terminal_executor(executor_id)`` for external
observers; ``OutputDesignation`` itself stays package-internal.
Key decisions
- ``OutputDesignation.designated`` is ``frozenset[str] | None`` -- ``None``
preserves legacy "every yield is type='output'" behavior, any frozenset
(including empty) opts into strict mode. The ``DeprecationWarning`` for
legacy mode at build time is unchanged.
- ``output_designation`` is an optional parameter on ``Runner``,
``EdgeRunner.send_message``, ``EdgeRunner._execute_on_target``,
``Executor.execute``, ``Executor._create_context_for_handler``, and
``WorkflowContext.__init__``. Each defaults to legacy ``OutputDesignation()``
so direct callers (Azure Functions ``CapturingRunnerContext``,
``test_runner`` recording fixtures) keep working without ceremony.
- The workflow-level filter in ``_run_core`` reads ``self._output_designation``
live, preserving today's semantics where mutating the designation after
build still affects subsequent runs (used by two existing tests).
- ``Workflow.to_dict()`` continues to emit ``"output_executors":
list[str] | None`` (sorted from the frozenset). Checkpoint format unchanged.
Files changed
- _workflow.py: add ``OutputDesignation`` dataclass; replace
``_output_executors`` with ``_output_designation``; add
``is_terminal_executor``; delete ``_should_yield_output_event``.
- _runner_context.py: drop ``should_label_as_intermediate`` Protocol method
and ``InProcRunnerContext`` impl; drop ``_workflow`` back-reference.
- _workflow_builder.py: remove ``context._workflow = workflow`` assignment.
- _runner.py, _edge_runner.py, _executor.py, _workflow_context.py: thread
``output_designation`` parameter through the call chain.
- tests/workflow/test_output_designation.py (new): three-state coverage of
the value type plus the public predicate delegation.
- tests/workflow/test_workflow_builder.py, test_validation.py,
test_workflow.py, test_runner.py and
orchestrations/tests/test_orchestration_intermediate_vs_terminal.py:
switch probes from ``_output_executors`` set checks to
``get_output_executors`` / ``is_terminal_executor``; update two
post-build mutation tests to set ``_output_designation`` instead.
Verification
- core/tests/workflow/, orchestrations/tests/, azurefunctions/tests/:
1119 passed, 42 skipped, 2 xfailed.
- ``uv run poe lint``: clean.
- ``uv run poe typing``: only the pre-existing
``_AGENT_FORWARDED_EVENT_TYPES`` pyright warning from 394bcd607 remains.
Notes for next iteration
- The builder's own ``_output_executors`` attribute (``list[Executor |
SupportsAgentRun]``) is intentionally untouched; the issue scoped the
rename to the workflow attribute.
- Adjacent review candidates (twin ``WorkflowAgent`` translators,
``_AGENT_FORWARDED_EVENT_TYPES`` kind classifier,
``_event_origin_context`` ContextVar removal, ``WorkflowEvent`` ADT
split, legacy-mode removal) remain out of scope.
* Add explicit workflow output designation
Key decisions
- Extend the internal OutputDesignation value type from terminal-only membership to output/intermediate/hidden classification. Legacy mode remains outputs=None, so workflows built without output_executors or intermediate_executors still label every yield_output as type='output'.
- WorkflowBuilder now accepts intermediate_executors. Providing either designation enters explicit mode; output executors emit output, intermediate executors emit intermediate, and unlisted yield_output payloads are hidden from caller-facing events while remaining in executor_completed data.
- Empty explicit designation, duplicate entries, overlaps, unknown executors, and designated executors without workflow output annotations fail build validation. Existing orchestration builders pass intermediate-capable participants through intermediate_executors to preserve current intermediate_outputs behavior until participant-oriented designation lands.
Files changed
- packages/core/agent_framework/_workflows/_workflow.py, _workflow_builder.py, _workflow_context.py, _validation.py, _events.py
- packages/core/tests/workflow/test_output_designation.py, test_output_executors_contract.py, test_strict_mode_event_labeling.py, test_validation.py, test_workflow.py, test_workflow_agent_intermediate.py
- packages/orchestrations/agent_framework_orchestrations/_sequential.py, _concurrent.py, _group_chat.py, _magentic.py
- packages/core/AGENTS.md
Verification
- uv run pytest packages/core/tests/workflow packages/orchestrations/tests packages/devui/tests/devui/test_mapper.py -q
- uv run pytest packages/azurefunctions/tests -q
- uv run poe lint
- uv run poe typing fails only on pre-existing packages/core/agent_framework/_workflows/_agent.py _AGENT_FORWARDED_EVENT_TYPES private-use pyright error.
Notes for next iteration
- issues/03-core-workflow-explicit-designation.md was moved to issues/done but issues/ remains untracked and intentionally excluded from this commit.
- Slice 4 should tighten workflow.as_agent() mapping for hidden emissions and streaming-only update payloads; Slice 5 should replace orchestration intermediate_outputs with participant-oriented designation.
* Tighten workflow-as-agent output mapping
Key decisions
- Treat AgentResponseUpdate as a streaming-only payload across the workflow.as_agent() adapter, so non-streaming agent runs now reject both terminal output and intermediate workflow events carrying updates.
- Keep streaming classification behavior explicit: terminal update payloads remain normal text content, while intermediate update payloads are rewritten to text_reasoning content.
- Add explicit-mode coverage proving hidden yield_output emissions do not appear in non-streaming AgentResponse messages or streaming AgentResponseUpdate chunks.
Files changed
- packages/core/agent_framework/_workflows/_agent.py
- packages/core/tests/workflow/test_workflow_agent_intermediate.py
Verification
- uv run pytest packages/core/tests/workflow/test_workflow_agent_intermediate.py -q
- uv run pytest packages/core/tests/workflow/test_workflow_agent.py packages/core/tests/workflow/test_workflow_agent_intermediate.py -q
- uv run pytest packages/core/tests/workflow packages/orchestrations/tests packages/devui/tests/devui/test_mapper.py -q
- uv run poe lint
- uv run poe typing fails only on the pre-existing packages/core/agent_framework/_workflows/_agent.py _AGENT_FORWARDED_EVENT_TYPES private-use pyright error.
Blockers or notes for next iteration
- issues/04-workflow-as-agent-output-mapping.md was moved to issues/done/ but issues/ remains untracked and intentionally excluded from this commit.
- Slice 5 should replace orchestration intermediate_outputs with participant-oriented designation.
* Add orchestration participant output designation
Key decisions
- Replace orchestration intermediate_outputs with participant-oriented output_participants and intermediate_participants across Sequential, Concurrent, GroupChat, Magentic, and Handoff builders.
- Keep synthetic final executors terminal by default for Concurrent, GroupChat, and Magentic; keep Sequential's final participant terminal by default; keep Handoff participants terminal by default.
- Centralize participant designation validation for empty explicit designation, duplicates, overlaps, and unknown participants, then map validated participants to workflow output/intermediate executors.
Files changed
- packages/orchestrations/agent_framework_orchestrations/_participant_designation.py
- packages/orchestrations/agent_framework_orchestrations/_sequential.py
- packages/orchestrations/agent_framework_orchestrations/_concurrent.py
- packages/orchestrations/agent_framework_orchestrations/_group_chat.py
- packages/orchestrations/agent_framework_orchestrations/_magentic.py
- packages/orchestrations/agent_framework_orchestrations/_handoff.py
- packages/orchestrations/tests/test_orchestration_intermediate_vs_terminal.py
- packages/orchestrations/tests/test_magentic.py
Blockers or notes for next iteration
- issues/05-orchestration-participant-designation.md was moved to issues/done/ but issues/ remains untracked and intentionally excluded from this commit.
- Slice 7 should migrate samples and docs away from intermediate_outputs to the new participant designation API.
- uv run poe typing still fails only on the pre-existing packages/core/agent_framework/_workflows/_agent.py _AGENT_FORWARDED_EVENT_TYPES private-use pyright error.
* Migrate samples to explicit output designation
Key decisions
- Replace sample usage of the removed orchestration intermediate_outputs boolean with participant-oriented intermediate_participants designation.
- Update raw workflow guidance to show output_executors together with intermediate_executors, and document that unlisted yields are hidden in explicit designation mode.
- Keep orchestration final outputs terminal while streaming designated participant responses as intermediate progress, including workflow.as_agent() samples where intermediates map to text_reasoning content.
- Refresh workflow and orchestration README guidance plus the changelog reference so public docs no longer point users at intermediate_outputs.
Files changed
- CHANGELOG.md
- packages/orchestrations/README.md
- samples/README.md
- samples/03-workflows/README.md
- samples/03-workflows/control-flow/intermediate_vs_terminal_outputs.py
- samples/03-workflows/orchestrations/README.md
- samples/03-workflows/orchestrations/group_chat_agent_manager.py
- samples/03-workflows/orchestrations/group_chat_philosophical_debate.py
- samples/03-workflows/orchestrations/group_chat_simple_selector.py
- samples/03-workflows/orchestrations/magentic.py
- samples/03-workflows/orchestrations/magentic_human_plan_review.py
- samples/03-workflows/orchestrations/sequential_chain_only_agent_responses.py
- samples/03-workflows/agents/group_chat_workflow_as_agent.py
- samples/03-workflows/agents/magentic_workflow_as_agent.py
- samples/03-workflows/agents/sequential_workflow_as_agent.py
- samples/semantic-kernel-migration/orchestrations/group_chat.py
- samples/semantic-kernel-migration/orchestrations/magentic.py
Blockers or notes for next iteration
- issues/07-samples-and-docs-explicit-output-designation.md was moved to issues/done/ but issues/ remains untracked and intentionally excluded from this commit.
- issues/06-devui-intermediate-event-rendering.md remains present and appears already satisfied by existing DevUI mapper/tests from the prior implementation slice.
- PRD-explicit-workflow-output-designation.md remains untracked and intentionally excluded from this commit.
* Render DevUI intermediate workflow outputs
Key decisions
- Preserve workflow output designation metadata on visible DevUI output messages and text deltas so intermediate/data emissions remain distinguishable from terminal output.
- Render intermediate workflow message items in the execution timeline using executor metadata, while excluding them from the final workflow result aggregation.
- Keep terminal output message rendering unchanged and retain legacy data events on the intermediate compatibility path.
Files changed
- packages/devui/agent_framework_devui/_mapper.py
- packages/devui/frontend/src/components/features/workflow/execution-timeline.tsx
- packages/devui/frontend/src/components/features/workflow/workflow-view.tsx
- packages/devui/frontend/src/types/openai.ts
- packages/devui/tests/devui/test_mapper.py
Blockers or notes for next iteration
- issues/06-devui-intermediate-event-rendering.md was moved to issues/done/ but issues/ remains untracked and intentionally excluded from this commit.
- PRD-explicit-workflow-output-designation.md remains untracked and intentionally excluded from this commit.
- uv run poe typing still fails only on the pre-existing packages/core/agent_framework/_workflows/_agent.py _AGENT_FORWARDED_EVENT_TYPES private-use pyright error.
* Fix mypy
* Clarify orchestration participant output config
* Rename participant output kwargs for clarity
output_participants -> final_output_from, intermediate_participants ->
intermediate_output_from. The old names read like categories of
participant; the new names make it clear the kwarg designates which
participants' outputs surface as final vs. intermediate events.
* Rename core workflow output kwargs with deprecation shim
Adds final_output_from / intermediate_output_from as canonical kwargs on
Workflow and WorkflowBuilder. Old output_executors / intermediate_executors
kwargs continue to work but emit DeprecationWarning via a shared coalesce
helper that also rejects supplying both. Wire-format keys in to_dict()
stay as output_executors / intermediate_executors so checkpoint
compatibility is preserved.
Internal call sites in orchestrations and samples updated to the new
names so users following sample code learn the canonical vocabulary;
legacy callers still work with a one-shot warning.
* Suppress pyright reportPrivateUsage on cross-module sentinel import
* Update docstrings
* Propagate sub-workflow intermediate outputs, fix handoff/sequential intermediate-only designation, and shore up tests, sample, and docstrings around the intermediate output contract.
* Add canonical workflow output_from selection
Key decisions:\n- Make output_from the canonical workflow-output allow-list and keep output_executors/final_output_from as deprecated compatibility aliases.\n- Treat empty output_from/intermediate_output_from lists as explicit selections and keep validation responsible for empty, duplicate, overlap, and unknown selections.\n- Remove the branch-only public intermediate_executors WorkflowBuilder kwarg while preserving legacy wire keys in to_dict().\n\nFiles changed:\n- packages/core/agent_framework/_workflows/_workflow.py\n- packages/core/agent_framework/_workflows/_workflow_builder.py\n- packages/core/agent_framework/_workflows/_workflow_context.py\n- packages/core/agent_framework/_workflows/_agent.py\n- packages/core/agent_framework/_workflows/_agent_executor.py\n- packages/core/tests/workflow/* output-selection coverage updates\n- packages/core/AGENTS.md\n- issues/done/001-canonical-list-based-output-selection.md\n\nBlockers/notes:\n- Orchestration builders still pass final_output_from internally; follow-up issue 004 should migrate them to output_from.\n- Legacy omitted-selection behavior and explicit all/all_other literals are left for issues 002 and 003.
* Add explicit all workflow output selection
Key decisions:
- Treat output_from='all' as an explicit workflow-output selection sentinel and expand it at build time to executors with declared workflow output types.
- Keep omitted output selections in legacy all-output mode with a deprecation warning that names output_from and intermediate_output_from and points to output_from='all'.
- Reject intermediate_output_from='all' at construction because the all-output literal is output-only for this issue.
Files changed:
- packages/core/agent_framework/_workflows/_workflow_builder.py
- packages/core/tests/workflow/test_output_executors_contract.py
- issues/done/002-explicit-all-output-and-legacy-migration.md
Blockers/notes:
- all_other intermediate-output selection remains for issue 003.
- Workflow-as-agent/orchestration parity remains for issue 004.
* Add all-other intermediate output selection
Key decisions:
- Treat intermediate_output_from='all_other' as an explicit intermediate-output selection sentinel and expand it at build time after the workflow graph is complete.
- Expand all_other to output-capable executors not selected by output_from; omitted or empty output_from selects no workflow outputs, while output_from='all' leaves an empty intermediate selection.
- Keep output_from='all_other' invalid so all_other remains intermediate-output-only and runtime classification still receives concrete executor-id sets.
Files changed:
- packages/core/agent_framework/_workflows/_workflow_builder.py
- packages/core/tests/workflow/test_output_executors_contract.py
- issues/done/003-all-other-intermediate-output-selection.md
Blockers/notes:
- Workflow-as-agent and orchestration parity remains for issue 004.
- Full documentation updates remain for issue 005.
* Add orchestration output selection parity
Key decisions:
- Expose output_from on sequential, concurrent, group chat, handoff, and magentic builders while keeping final_output_from as a deprecated compatibility alias.
- Resolve orchestration participant selections through the same explicit rules as workflows: output_from='all', intermediate_output_from='all_other', hidden unselected participant payloads, and overlap/duplicate/unknown/invalid-literal validation.
- Continue preserving documented orchestration defaults by always designating each pattern's terminal internal executor where applicable.
Files changed:
- packages/orchestrations/agent_framework_orchestrations/_participant_output_config.py
- packages/orchestrations/agent_framework_orchestrations/_sequential.py
- packages/orchestrations/agent_framework_orchestrations/_concurrent.py
- packages/orchestrations/agent_framework_orchestrations/_group_chat.py
- packages/orchestrations/agent_framework_orchestrations/_handoff.py
- packages/orchestrations/agent_framework_orchestrations/_magentic.py
- packages/orchestrations/agent_framework_orchestrations/_orchestration_request_info.py
- packages/orchestrations/tests/test_orchestration_intermediate_vs_terminal.py
- issues/done/004-workflow-as-agent-and-orchestration-parity.md
Blockers/notes:
- Full documentation and sample migration wording remains for issue 005.
- Existing tests that intentionally use final_output_from now emit the new deprecation warning.
* Document workflow output selection contract
Key decisions:
- Use Workflow Output and Intermediate Output as the developer-facing terms for selected caller-facing emissions.
- Document output_from and intermediate_output_from as the canonical API, with output_from as an allow-list and unselected payloads hidden unless explicitly selected as intermediate.
- Add scenario and invalid-selection tables for workflow and orchestration docs, including legacy omission warnings, output_from='all', intermediate_output_from='all_other', list selections, invalid literals, overlap, duplicates, unknown selections, and empty explicit selections.
- Migrate samples away from final_output_from and output_executors except where compatibility aliases are explicitly documented.
Files changed:
- packages/core/AGENTS.md
- packages/orchestrations/README.md
- packages/orchestrations/agent_framework_orchestrations/_handoff.py
- packages/orchestrations/agent_framework_orchestrations/_sequential.py
- samples/03-workflows/README.md
- samples/03-workflows/control-flow/intermediate_vs_terminal_outputs.py
- samples/03-workflows/human-in-the-loop/agents_with_approval_requests.py
- samples/03-workflows/orchestrations/README.md
- samples/04-hosting/foundry-hosted-agents/responses/05_workflows/main.py
- scripts/sample_validation/create_dynamic_workflow_executor.py
- issues/done/005-document-output-selection-contract.md
Blockers/notes:
- Direct full Ruff on scripts/sample_validation/create_dynamic_workflow_executor.py still reports pre-existing docstring/print/line-length issues outside this docs migration; syntax-focused checks for changed files pass.
- No remaining AFK issue files are present under issues/.
* Latest updates
* Typing fixes
* Cleanup
* .NET: Bump Azure.AI.Projects to 2.1.0-beta.2 and add agent-endpoint AsAIAgent path
Bumps Azure.AI.Projects to 2.1.0-beta.2 with the matching transitive pins (Azure.Core 1.55.0, System.ClientModel 1.11.0).
Foundry agent endpoint plumbing:
* FoundryAgent now routes the agent-endpoint constructor through the new GetProjectResponsesClientForAgentEndpoint helper.
* Adds an internal FoundryAgent ctor that takes an existing AIProjectClient plus a parsed agent endpoint so the public extension does not need to construct a second project client.
* Adds public AIProjectClient.AsAIAgent(Uri agentEndpoint, ...) extension. This is the path consumer samples are expected to use for hosted agents because version selection happens server-side.
* Trims the dangling "If you want to construct a FoundryAgent against a project endpoint..." sentence from ParseAgentEndpoint.
Unit tests:
* Four new tests in AzureAIProjectChatClientExtensionsTests cover the AIProjectClient.AsAIAgent(Uri agentEndpoint, ...) overload. 263/263 pass.
Consumer samples (Using-Samples):
* SimpleAgent and SessionFilesClient now read AZURE_AI_PROJECT_ENDPOINT and AZURE_AI_AGENT_NAME (both required, throw on missing), derive the agent endpoint with new Uri($"{projectEndpoint}/agents/{agentName}/endpoint/protocols/openai"), then call aiProjectClient.AsAIAgent(agentEndpoint, ...).
* SessionFilesClient README updated.
Contributor samples (responses/*):
* New HostedContributorRouteExtensions.MapDevTemporaryLocalAgentEndpoint() wildcard route extension so localhost contributor servers accept the per-agent OpenAI endpoint shape the production Hosted runtime exposes.
* All 11 contributor Program.cs files call MapDevTemporaryLocalAgentEndpoint() with a contributor-only warning comment.
* Hosted-Files and Hosted-AzureSearchRag were importing Hosted_Shared_Contributor_Setup but never calling AddDevTemporaryLocalContributorSetup(). Both now call it so HostedSessionIsolationKeyProvider resolves correctly in dev.
* Hosted-AzureSearchRag, Hosted-Files, Hosted-MemoryAgent csprojs drop stale VersionOverride="2.1.0-beta.1" pins.
* Hosted-AzureSearchRag and Hosted-Files csprojs add ProjectReference to Hosted_Shared_Contributor_Setup.
* Hosted-Observability/.dockerignore removed the out/ exclusion that was blocking COPY out/ . in Dockerfile.contributor.
Verified:
* Full solution-scoped build of changed projects: green.
* Scoped CI-parity dotnet format via WSL2 + Docker (mcr.microsoft.com/dotnet/sdk:10.0) over every changed csproj: clean.
* Foundry unit tests: 263/263.
* Contributor docker smoke for 8 hosted samples (publish + docker build + docker run + curl POST to the wildcard route): HTTP 200 / 500 with route matched.
* End-to-end smoke against the real Azure Foundry project with a fresh bearer token: Hosted-Files contributor container served HTTP 200, the agent invoked ListBundledFiles, and returned the expected file name.
* Address PR review: forward pipeline settings; add UTs
- CreateProjectClientOptions also carries RetryPolicy, NetworkTimeout, ClientLoggingOptions, MessageLoggingPolicy (was Transport+UserAgentApplicationId only).
- Make CreateProjectClientOptions internal so tests can verify the copy directly.
- Add AsAIAgent(Uri) UTs covering tools forwarding to inner ChatOptions and null tools handling.
- Add CreateProjectClientOptions UTs covering null caller and full pipeline-settings copy.
* Fix GitHubCopilotAgent ignoring tools from context providers (#5736)
_create_session and _resume_session only forwarded self._tools (constructor
tools) to CopilotClient.create_session, dropping any tools contributed by
context providers via session_context.extend_tools() during before_run.
Merge provider-contributed tools into runtime_options in both _run_impl and
_stream_updates before session creation, mirroring how RawAgent handles the
merge at lines 1435-1440 in _agents.py. Update _create_session and
_resume_session to combine self._tools with the merged runtime tools.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: Fix GitHubCopilotAgent to include tools added by ContextProvider.before_run in session creation
Fixes#5736
* Fix provider tool merge to avoid mutating caller's list
- Replace in-place .extend() with fresh list creation in both
_run_impl and _stream_updates paths to prevent mutating the
caller-provided options['tools'] list (shallow copy issue)
- Also handles immutable Sequence types (e.g. tuple) correctly
- Add test for provider tools forwarded via _resume_session path
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address review feedback for #5736: review comment fixes
---------
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The frontmatter parser previously matched only single-line `key: value` pairs, so block scalar indicators (`|` literal, `>` folded, with chomping `-`/`+`) were silently truncated to the indicator character. Multi-line descriptions like `description: >\n ...` lost their content.
Add `_parse_yaml_scalar_value()` which detects block scalar indicators, collects indented continuation lines, strips the common leading indentation, joins per scalar style (newlines for `|`, spaces for `>`), and applies chomping per the YAML 1.2 spec. Update `_extract_frontmatter()` to use the helper for unquoted values.
Adds 15 unit tests covering literal/folded styles, all chomping variants, indentation handling, content containing colons, non-description fields, tab indentation, blank-line preservation, and a regression test for plain values.
Fixes#5713.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Add Hosted-MemoryAgent sample with isolation key plumbing (#5692)
Adds HostedSessionContext + HostedSessionIsolationKeyProvider in Microsoft.Agents.AI.Foundry.Hosting so AIContextProviders (notably FoundryMemoryProvider) can scope per user via the platform's x-agent-user-isolation-key / x-agent-chat-isolation-key headers.
- New types: HostedSessionContext (sealed), HostedSessionContextExtensions (public Get, internal Set), abstract HostedSessionIsolationKeyProvider (async), internal PlatformHostedSessionIsolationKeyProvider mapping ResponseContext.Isolation.
- AgentFrameworkResponseHandler now resolves the provider, tags fresh sessions, and validates resumed sessions against the live request (strict 403 'Hosted session identity context mismatch' on any mismatch; 500 on null keys).
- New shared sample project Hosted_Shared_Contributor_Setup hosts DevTemporaryTokenCredential and DevTemporaryLocalSessionIsolationKeyProvider plus AddDevTemporaryLocalContributorSetup. All 9 existing responses samples migrated to consume it so local runs keep working under the strict isolation contract.
- New Hosted-MemoryAgent sample: travel assistant wired through FoundryMemoryProvider with stateInitializer reading session.GetHostedContext().UserId. Includes Dockerfile, smoke.ps1, agent.yaml/manifest.
- New IT scenario 'memory' in Foundry.Hosting.IntegrationTests + MemoryHostedAgentFixture + MemoryHostedAgentTests. Verified end to end against the tao Foundry project.
- ADR 0026 captures the design tree.
* Address PR review feedback
- Dockerfile: add header noting it targets NuGet builds; contributors must use Dockerfile.contributor for ProjectReference source builds.
- PlatformHostedSessionIsolationKeyProvider: doc said 'returns context with empty values'; corrected to 'returns null' which the handler treats as 500.
- FakeHostedSessionIsolationKeyProvider: doc clarifies that null configurations are allowed for testing the handler error path.
- HostedSessionContextExtensions.SetHostedContext: enforce write-once with InvalidOperationException; doc + xml exception updated.
- AgentFrameworkResponseHandler: cache PlatformHostedSessionIsolationKeyProvider as static readonly to avoid per-request allocation.
- MemoryHostedAgentTests: tighten waits from 20s to 5s (FoundryMemoryProvider defaults UpdateDelay=0; ingestion ~3s).
- Sample Program.cs imports reordered to satisfy IDE0005.
* Add HostedFoundryMemoryProviderScopes built-in helpers (#5692)
Addresses review feedback from @lokitoth on Hosted-MemoryAgent/Program.cs:54.
- New HostedFoundryMemoryProviderScopes static class with PerUser, PerChat, PerUserAndChat factories returning Func<AgentSession?, FoundryMemoryProvider.State>.
- All helpers throw InvalidOperationException when GetHostedContext() is null, with a message pointing at writing a custom stateInitializer for non-hosted scenarios.
- New HostedFoundryMemoryScope enum and AddHostedFoundryMemoryProvider DI extension (two overloads: explicit AIProjectClient and DI-resolved). Singleton lifetime. Default scope = PerUser.
- Hosted-MemoryAgent sample and the memory IT scenario container both swap their inline lambdas for HostedFoundryMemoryProviderScopes.PerUser().
- 14 new unit tests (241/241 hosting unit tests pass).
* Replace HostedFoundryMemoryScope enum with Func<...> parameter (#5692)
Address PR review feedback from @westey-m: enums are a breaking-change hazard when extended, and the enum was redundant with the existing HostedFoundryMemoryProviderScopes static class.
- Delete HostedFoundryMemoryScope.cs.
- AddHostedFoundryMemoryProvider DI extensions now take Func<AgentSession?, FoundryMemoryProvider.State>? stateInitializer = null. When null, default to HostedFoundryMemoryProviderScopes.PerUser().
- Callers pick a built-in helper (PerUser/PerChat/PerUserAndChat) or pass a custom delegate. New built-ins are a single static method addition with zero impact on existing callers.
- Tests updated; 244/244 hosting unit tests pass.
* Fix isolation context resume for externally-created conversations (#5692)
Branch on the session's existing hosted-context (not on conversation_id presence) so a conversation provisioned externally (e.g. via conversations.CreateProjectConversationAsync) is treated as fresh on first hosted-agent request and stamped, rather than rejected with 403 hosted_session_identity_mismatch. Strict equality is preserved on real resume of an already-stamped session.
Also tighten dotnet/global.json to version 10.0.204 + rollForward latestPatch so local builds match the CI Docker image SDK and avoid 10.0.300 dotnet format stripping required usings.
* Revert global.json SDK pin to upstream (#5692)
The 10.0.204 + latestPatch pin from the previous commit broke the dotnet-format CI job (hostfxr_resolve_sdk2 could not find a compatible SDK in the mcr.microsoft.com/dotnet/sdk:10.0 image). Restore upstream 10.0.200 + minor; local Release builds with SDK 10.0.300 should set GITHUB_ACTIONS=true to bypass the auto-format-on-build target.
- Fix non-streaming empty response by accumulating intermediate WORKING
status updates and flushing them when an empty terminal event arrives
- Fix sample agent_executor.py to enqueue Task before status events
(required by v1.0 ActiveTask validation)
- Fix create_jsonrpc_routes() calls to include required rpc_url param
- Fix TYPE_CHECKING imports in sample agent_definitions.py
- Add tests for non-streaming content accumulation behavior
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Restructure harness console so that reactive app is the entry point
* Further refactoring to split tool formatters, improve UX, make console configurable and fix bugs
* Address PR comments.
* UX tweak
* Fix streaming text bug
* Address PR comments.
TestServerWithDevUI_ResolvesMixedAgentsAndWorkflows_AllRegistrationsAsync fails intermittently in the merge_group with NRE on the discovery response, blocking PRs unrelated to DevUI from merging. Skip via Fact(Skip=...) referencing #5845 while the underlying race is investigated.
* Python: DevUI: tighten default access controls and CORS posture
Adjusts the default configuration of the DevUI server so the out-of-the-box
posture matches what most callers expect when running locally. Adds explicit
opt-outs for callers who need the previous behavior.
- DevServer gains auth_enabled and auth_token constructor params; auth is on by
default. Auto-generates and logs a token when none provided.
- CORS default is an empty allowlist on every host. Callers wanting cross-origin
pass cors_origins explicitly.
- Streaming /v1/responses no longer sets Access-Control-Allow-Origin directly;
CORSMiddleware owns all CORS decisions.
- Loopback binds enforce a Host-header allowlist.
- /meta moved out of the auth bypass list (was alongside /health and /).
- serve() default flipped to auth_enabled=True; passes auth args through to
DevServer instead of using env-var indirection.
- CLI: --auth opt-in replaced with --no-auth opt-out; --auth-token preserved.
- Tests cover the eight behaviors above in test_server.py.
* Python: DevUI: address PR review comments
- /meta now derives auth_required from self.auth_enabled instead of
reading DEVUI_AUTH_TOKEN, so the auto-generated and explicit
auth_token paths report correctly.
- Reorder middleware so the loopback Host-header allowlist is registered
last; Starlette wraps later-added middleware around earlier-added ones,
so the host check now runs outermost (before CORS/auth) as intended.
- Rework comments to describe the behavior rather than threat scenarios.
- Streaming-headers and CORS tests now construct the server with an
explicit auth_token and send a Bearer header, so the assertions
actually exercise the streaming/CORS path instead of short-circuiting
in the auth middleware.
Replaces two ILogger.LogWarning(string, params object?[]) calls in DevUIAuthFilter and DevUIExtensions with allocation-free [LoggerMessage] partial methods on a new internal DevUILog class. Preserves original message templates and structured property names ({RemoteIp}, {EnvVar}).
Co-authored-by: alliscode <25218250+alliscode@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Fixes microsoft/agent-framework#3295. When the OpenAI Responses chat
client sends a request that carries previous_response_id / conversation_id
/ conversation, the server already has the prior turn's response items
and rejects duplicates with "Duplicate item found with id fc_xxx". The
chat client was re-sending them inline whenever the input messages still
carried the items in additional_properties (workflow replay, history
providers, etc.), which broke any tool-using agent with persistent
history.
Decisions:
- Single chokepoint: _prepare_message_for_openai. When the resulting
request uses service-side storage, drop function_call, reasoning,
approval-request/response, and local-shell-call items from the wire
input. Keep function_result with its call_id; the server pairs it to
the prior function_call via that key.
- function_result is preserved unconditionally except for the local-shell
variant, which carries its own server-issued item id.
- No public API change. Wire format change is subtractive and only on
requests that would otherwise 400.
- Re-pointed the strict-xfail in test_full_conversation.py from #4047 to
#3295. Kept xfail because the test asserts executor-level session-id
clearing, which is the defense-in-depth half tracked by 3295-03; this
slice closes the wire-level half.
Files:
- python/packages/openai/agent_framework_openai/_chat_client.py: strip
rule applied alongside the existing reasoning-item branch.
- python/packages/openai/tests/openai/test_openai_chat_client.py: four
new tests pin the contract (function_call, approval, local-shell-call
stripped under storage; everything kept without storage). Updated
pre-existing tests that exercised the storage-on path to either pass
request_uses_service_side_storage=False explicitly or assert the new
strip behavior.
- python/packages/foundry/tests/foundry/test_foundry_chat_client.py:
same explicit storage-off opt-in for the inherited test.
- python/packages/core/tests/workflow/test_full_conversation.py:
re-pointed xfail reason to #3295 and the executor-level follow-up.
Notes for next iteration:
- 3295-01 (HITL wire-format validation against live OpenAI/Foundry) was
not run; it requires the user's API credentials. The PRD design is
locked but the empirical confirmation is still pending. If script 3
fails on either provider, this slice may need to be revisited.
- 3295-03 (clear service_session_id in AgentExecutor on full-history
replay) remains open. After it lands the xfail in
test_full_conversation.py can be removed.
- pytest was not run in this iteration because uv-based pytest commands
required interactive approval. Validation rests on careful reading;
next iteration should run the openai + core test suites.
* Fix Skill docstring consistency and spelling
- Add ClassSkill to Skill class docstring concrete implementations list
- Normalize 'defence' to 'defense' for American English consistency
- Remove extra blank line in InlineSkill docstring example
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix E501 line-too-long lint error in test_skills.py
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix stale test section header to reflect SkillFrontmatter API
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix metadata children overriding top-level frontmatter fields
Scope YAML_KV_RE to column-0 keys only so indented children
under metadata: are not mistakenly parsed as top-level fields.
Add regression test and spec fields to sample SKILL.md files.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(python): prevent MCP message_handler deadlock on notification reload
When an MCP server sends a notifications/tools/list_changed or
notifications/prompts/list_changed notification, the message_handler
previously awaited load_tools()/load_prompts() directly. Since the
handler runs on the MCP SDK's single-threaded receive loop, this
caused a deadlock: load_tools() sends a list_tools request and waits
for its response, but the receive loop cannot deliver that response
while blocked in the handler.
This manifested as a timeout in call_tool(), which then surfaced as
"Error: Function failed." to the model instead of the real tool
output. The MATLAB MCP server reliably triggers this because it sends
a tools/list_changed notification during tool execution.
Fix: schedule reloads as background asyncio.Tasks via a new
_schedule_reload() helper, freeing the receive loop immediately.
Fixes#4828
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address PR review feedback: fix exc_info, coalesce reloads, shutdown cleanup, tests
- Fix exc_info=exc -> exc_info=True in _schedule_reload and message_handler
- Tighten _schedule_reload param type from Any to Coroutine[Any, Any, None]
- Coalesce reloads: cancel-and-replace per reload kind to prevent unbounded growth
- Cancel pending reload tasks in _close_on_owner before tearing down session
- Re-raise CancelledError in _safe_reload to respect task cancellation
- Replace flaky asyncio.sleep(0) with asyncio.wait_for/gather in tests
- Add caplog assertions to verify reload failure is actually logged
- Assert _pending_reload_tasks cleanup on error path
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: address review comments on MCP reload handling
- Fix exc_info=True -> exc_info=message in message_handler error logging,
since the handler is not called from an except block
- Await cancelled reload tasks in _close_on_owner before tearing down
the session to avoid 'Task was destroyed but pending' warnings
- Add cancel-and-replace test verifying duplicate notifications cancel
the first reload task and only keep one in flight
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: remove Task.cancelling() call for Python 3.10 compat
Task.cancelling() was added in Python 3.11. Replace with awaiting
the task and checking cancelled() instead.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add debug log when cancelling superseded reload task
Log at DEBUG level when a new notification cancels an in-flight reload
task, improving observability of the cancel-and-replace behavior.
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>
* .NET: feat(evals): add ground_truth/expected_output support for workflow eval
Brings .NET to parity with Python PR #5234 for issue #5135:
- Add expectedOutput parameter to Run.EvaluateAsync (workflow) and stamp on the overall EvalItem.ExpectedOutput.
- Map EvalItem.ExpectedOutput -> ground_truth in the Foundry JSONL payload, item_schema, and data_mapping for similarity.
- Add GroundTruthEvaluators set (currently builtin.similarity) and a FindMissingGroundTruthEvaluators helper.
- Fail fast with InvalidOperationException when a ground-truth evaluator is selected but no item provides an ExpectedOutput, instead of surfacing a remote provider error.
- Add tests in FoundryEvalConverterTests and WorkflowEvaluationTests.
- Add Evaluation_WorkflowExpectedOutputs sample (workflow + Foundry similarity).
Fixesmicrosoft/agent-framework#5135 (.NET side).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address review: relax BuildOverallItem events to IReadOnlyList<WorkflowEvent>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Sample: disable per-agent breakdown when using reference-based evaluator
Per-agent EvalItems are intentionally left without ExpectedOutput, so the new fail-fast validation in FoundryEvals would throw when Similarity is invoked for per-agent items. Pass includePerAgent: false in the workflow + similarity sample, and document this gotcha in the EvaluateAsync XML doc.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix BuildOverallItem: fall back to last ExecutorCompletedEvent
AgentResponseEvent is only emitted when AIAgentHostOptions.EmitAgentResponseEvents is enabled, which is not the default for WorkflowBuilder(agent).AddEdge(...). When it is absent, fall back to the last non-internal ExecutorCompletedEvent whose Data is an AgentResponse / ChatMessage / string so the overall EvalItem (and any expectedOutput) is produced. Without this, samples wired up the standard way returned 0 evaluation items.
Update test to cover the fallback path.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Sample: enable EmitAgentResponseEvents; eval throws clear error when no overall response found
Root cause of '0 results': AIAgentHostExecutor only emits AgentResponseEvent when AIAgentHostOptions.EmitAgentResponseEvents is true (default false). For ordinary AIAgent executors the runtime's ExecutorCompletedEvent.Data is null, so the prior fallback couldn't find a final response either.
Sample now builds executors with EmitAgentResponseEvents=true via BindAsExecutor(hostOptions). EvaluateAsync now throws InvalidOperationException with a remediation hint when the user supplies expectedOutput but no overall final response can be located, instead of silently returning 0/0.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Guard against null sample/error/usage/datasource_item in ParseDetailedItem
Foundry eval responses can have these properties present with JSON null
or non-object values, which caused JsonElement.TryGetProperty to throw
'requires Object, has Null'. Check ValueKind == Object before drilling in.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address PR review: reorder expectedOutput, tighten ground-truth check, add fail-fast test
* WorkflowEvaluationExtensions.EvaluateAsync: move 'expectedOutput' to
after 'splitter' so the original positional contract of (splitter,
cancellationToken) is preserved for existing callers.
* FoundryEvals: require ALL items to carry ExpectedOutput when a
ground-truth evaluator is selected (e.g. similarity), not just any.
Reference-based evaluators score per-item, so a single missing GT
would still surface as a provider-side validation error. Updated
fail-fast message accordingly.
* WorkflowEvaluationTests: add EvaluateAsync_WithExpectedOutputButNoFinalResponse_ThrowsAsync
to verify the InvalidOperationException is thrown (and that the
message mentions EmitAgentResponseEvents).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fail-fast on missing overall item regardless of expectedOutput; harden BuildOverallItem default
* EvaluateAsync now throws InvalidOperationException whenever 'includeOverall'
is requested but BuildOverallItem cannot produce an item, instead of only
when 'expectedOutput' is supplied. Same misconfiguration (agents not bound
with EmitAgentResponseEvents) used to silently return empty results — now
it surfaces a clear, actionable error in both cases.
* BuildOverallItem switch default now throws instead of returning null. The
preceding for-loop already constrains Data to AgentResponse/ChatMessage/
string, so reaching default would indicate a contract drift; throw to make
the bug visible.
* Test renamed and broadened to verify the throw fires without expectedOutput.
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
* .NET: Auto-wire ChatClient with OpenTelemetryChatClient in OpenTelemetryAgent
Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/96dd033a-0c48-4d3f-9148-324bfd436b75
Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com>
* Address review: remove extension overload; honor UseProvidedChatClientAsIs; drop redundant check
Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/6ac3f75d-eeb7-4811-8043-9a27511b0a8b
Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com>
* Resolve ChatClientAgent via GetService before checking options/chat client
Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/008d914d-8cbb-4e9f-81b6-f8c3c8bd8d04
Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com>
* Split OpenTelemetryAgent ctor to preserve original (innerAgent, sourceName) signature
Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/a890c9a7-0b77-40ab-802c-dfbf09f8c260
Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com>
* Preserve base AgentRunOptions properties and avoid double-wrap on user factory
Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/3afbf18c-de22-4236-a2f2-02ca1e98ae21
Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com>
* .NET: OpenTelemetryAgent normalize sourceName once and add OTEL wiring path coverage
Normalize the configured source name once in the constructor so the outer OpenTelemetryChatClient and the auto-wired inner OpenTelemetryChatClient always emit spans on the same ActivitySource. A caller passing an empty string previously produced agent-level spans on DefaultSourceName but auto-wired chat spans on the empty source, causing the chat spans to be silently dropped by exporters subscribed to the default source.
Tests added to cover the previously unexercised OTEL wiring branches:
- Ctor_NullOrEmptySourceName_AutoWiredChatClientUsesDefaultSource_Async (Theory: null and empty)
- AutoWireChatClient_PlainAgentRunOptions_PreservesContinuationToken_Async
- AutoWireChatClient_ChatClientAgentRunOptions_NoUserFactory_PreservesChatOptions_Async
- AutoWireChatClient_StreamingDisabled_DoesNotEmitChatSpan_Async
* .NET: Mark OpenTelemetryAgent autoWireChatClient ctor as [Experimental]
Annotate the new 3-arg OpenTelemetryAgent(AIAgent, string?, bool) constructor with [Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)] (MAAI001) so callers must explicitly opt in to the auto-wire toggle. The original 2-arg constructor stays non-experimental and delegates with autoWireChatClient: true; the delegating call is locally suppressed so the existing source compatibility surface is preserved.
* .NET: OpenTelemetryAgent address westey-m PR review
- Use string.IsNullOrWhiteSpace (not IsNullOrEmpty) when normalizing the constructor sourceName, so callers passing whitespace-only strings still land on OpenTelemetryConsts.DefaultSourceName instead of an unsubscribed ActivitySource.
- Fix the misleading pragma comment on the 2-arg ctor delegating call: auto-wiring is the new default, it does not preserve the original (pre-PR) behavior.
- Expand the GetRunOptionsWithChatClientWiring XML doc to spell out that a base AgentRunOptions (not ChatClientAgentRunOptions) is also accepted: it is converted to ChatClientAgentRunOptions with the auto-wire factory installed and base properties copied.
- Tests: extend the source-name normalization Theory with whitespace cases (' ' and '\t'); add end-to-end coverage for plain AgentRunOptions over a real ChatClientAgent (sync + streaming) asserting the inner chat client is invoked and both invoke_agent + chat spans are emitted.
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com>
The upsidr/merge-gatekeeper@v1 action is a Dockerfile-based action that
builds a golang image on every run. On merge_group events the run step
is conditioned out via `if: github.event_name == 'pull_request'`, so the
build happens but produces nothing.
Replace with an actions/github-script@v8 polling loop that mirrors the
action's behavior exactly: merges combined-statuses and check-runs for
the PR head SHA, with combined-status winning on name collisions, and
the same conclusion mapping (skipped → dropped, success/neutral →
success, anything else terminal → error). Same job name, triggers,
permissions, timeout (3600s), interval (30s), and ignored list, so
existing required-check rules stay valid.
PR runs now poll the API in seconds instead of waiting on a per-run
docker image build, and merge_group runs become near-instant no-ops.
* Python: add ag-ui tool result display channel
Key decisions:
- Add TOOL_RESULT_DISPLAY_KEY and make state_update accept optional state plus a tool_result display payload.
- Keep text as the LLM-bound tool result while using the display marker only for ToolCallResultEvent.content.
- Reuse one outer/inner Content additional_properties extraction helper for state and display markers, preserving fallback behavior when display is absent.
Files changed:
- python/packages/ag-ui/agent_framework_ag_ui/_state.py
- python/packages/ag-ui/agent_framework_ag_ui/_run_common.py
- python/packages/ag-ui/tests/ag_ui/test_run_common.py
- python/packages/ag-ui/tests/ag_ui/golden/test_scenario_deterministic_state.py
- python/issues/done/01-tool-result-display-channel.md
Blockers/notes:
- Slice 1 is complete and moved to issues/done.
- Slice 2 remains for docstring and README documentation.
* Python: document ag-ui tool result display channel
Key decisions:
- Document state_update as the single helper for LLM text, UI-only tool_result display content, and durable shared state.
- Keep the display guidance explicit that text remains LLM-bound while tool_result feeds ToolCallResultEvent.content.
- List both reserved additional_properties markers in the docstring return contract.
Files changed:
- python/packages/ag-ui/agent_framework_ag_ui/_state.py
- python/packages/ag-ui/README.md
- python/issues/done/02-docs-tool-result-display.md
Blockers/notes:
- Slice 2 is complete and moved to issues/done.
- Verification passed: uv run poe syntax -P ag-ui --check; uv run poe test -P ag-ui; uv run poe markdown-code-lint; uv run ruff check packages/ag-ui/agent_framework_ag_ui/_state.py.
- Commit hooks were skipped after poe-check repeatedly rewrote uv.lock ordering; the same checks were run manually and passed.
* Python: update gitignore
* Split DurableTask/AzureFunctions integration tests into dedicated CI job
- Add -TestProjectNameExclude parameter to New-FilteredSolution.ps1
- Add 'functions' and 'core' path filters to paths-filter job
- Exclude DurableTask/AzureFunctions from main dotnet-test job
- Remove emulator setup from dotnet-test (no longer needed)
- Add new dotnet-test-functions job (ubuntu/net10.0 only, path-conditional)
- Update merge gate and report job to include dotnet-test-functions
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address PR feedback: add Workflows.Generators to core filter, drop dotnetChanges gate from functions job
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Re-enable Anthropic integration tests
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Upgrade Anthropic SDK 12.13.0 -> 12.20.0 to fix M.E.AI incompatibility
Fixes MissingMethodException on WebSearchToolResultContent.get_Results()
caused by Anthropic 12.13.0 being compiled against an older
Microsoft.Extensions.AI.Abstractions version.
Suppress RT0003 in AI.Abstractions.csproj as the transitive reference
from the upgraded Anthropic SDK conflicts with the explicit one.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix Anthropic unit test mocks for SDK 12.20.0 interface changes
Add missing interface members: IAnthropicClient.WebhookKey,
IBetaService.MemoryStores, IBetaService.Webhooks, IBetaService.UserProfiles
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Re-enable CheckSystem declarative integration tests
The CheckSystem.yaml tests were temporarily skipped in PR #4270 during
the Azure.AI.Projects 2.0.0-beta.1 SDK update. Since then, the system
variable plumbing (SystemScope, SetLastMessageAsync, conversation
initialization) has been significantly updated and stabilized. The
other tests in these same files pass reliably using the same
infrastructure.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix CheckSystem test case to expect 1 response
The CheckSystem workflow sends a 'PASSED!' SendActivity when all system
variables are populated, producing 1 AgentResponseEvent. The test case
had min_response_count: 0 with no max, so the assertion defaulted max
to 0 and failed with 'Response count greater than expected: 0 (Actual: 1)'.
Updated to expect exactly 1 response, matching the SendActivity pattern.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Re-enable Foundry OpenAPI server-side tool integration test
Remove Skip="For manual testing only" from
AsAIAgent_WithOpenAPITool_NativeSDKCreation_InvokesServerSideToolAsync.
The test already uses RetryFact(3 retries, 5s delay) to handle
transient failures from the external restcountries.com API.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Include workflow file in functions/core path filters
A PR editing only dotnet-build-and-test.yml would skip
dotnet-test-functions because the workflow path was missing
from both the functions and core path filter lists.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Rename filter parameters for consistency
TestProjectNameFilter -> TestProjectNameIncludeFilter
TestProjectNameExclude -> TestProjectNameExcludeFilter
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Remove unnecessary RT0003 warning suppression
The RT0003 suppression was added during the Anthropic SDK 12.20.0
upgrade but the warning no longer fires. Removing it to keep the
NoWarn list minimal.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Remove duplicate WebhookKey properties from merge
Both our branch and main added WebhookKey to the Anthropic test
mock classes, resulting in CS0102 duplicate definition errors.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix OpenAIResponsesAgentClient endpoint to include agentName in path (#5324)
The sample OpenAIResponsesAgentClient used '/v1/' as the endpoint, which
routes to the multi-agent endpoint requiring agent.name in the request body.
However, AsIChatClient(agentName) maps agentName to the model field, not
agent.name, causing HTTP 400 errors on OpenAI-compatible endpoints.
Changed the endpoint to '/{agentName}/v1/' to match the pattern used by
OpenAIChatCompletionsAgentClient, routing to the single-agent endpoint
where no agent.name body field is needed.
Added regression test verifying that the model field alone is insufficient
for agent resolution on the multi-agent endpoint.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address review feedback for #5324
- URL-escape agentName in OpenAIResponsesAgentClient endpoint path to
handle reserved characters safely
- Add per-agent MapOpenAIResponses() calls in AgentHost so the sample
host serves the /{agentName}/v1/responses routes the client now targets
- Replace brittle Assert.Contains("agent.name") assertions with stable
machine-readable error code assertion ("missing_required_parameter")
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address additional review feedback for #5324
- Apply Uri.EscapeDataString to OpenAIChatCompletionsAgentClient endpoint
for consistency with OpenAIResponsesAgentClient
- Map OpenAI Responses and ChatCompletions endpoints for all builder-based
agents (chemist, mathematician, literator, science workflows) so every
discoverable agent is reachable via the single-agent endpoint path
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>
* feat(dotnet): add Microsoft.Agents.AI.Tools.Shell with LocalShellTool
Ports Python LocalShellTool to .NET as a new package (net8/9/10).
- Microsoft.Agents.AI.Tools.Shell: LocalShellTool, ShellPolicy (deny-list
guardrail), ShellResolver (cross-OS pwsh/powershell/cmd vs bash/sh),
ShellResult with head+tail truncation, timeout + process-tree kill,
AsAIFunction with required-by-default human approval gate.
- Persistent mode via ShellSession (sentinel protocol over pwsh/bash).
- acknowledgeUnsafe parity gate matches the Python implementation.
- Auto-injected platform context in the AIFunction description so the
LLM sees the active OS and shell at tool-discovery time.
- 17 xunit.v3 tests cover policy allow/deny, echo roundtrip, exit
codes, timeout/kill, AsAIFunction shape + approval wrapping,
persistent cwd/env carry-over, head+tail truncation, sentinel race.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(shell): close Python parity gaps for LocalShellTool
Closes the .NET vs Python parity gaps identified in the competitive eval:
- Default mode flipped to ShellMode.Persistent (matches Python). Every
call now reuses a long-lived shell so cd/exports/functions persist;
pass mode: ShellMode.Stateless to opt out.
- New IShellExecutor interface — pluggable backend so future
DockerShellTool / Hyperlight / SSH executors don't fork the framework.
LocalShellTool implements it.
- Workdir confinement: confineWorkingDirectory (default true) re-anchors
every persistent-mode command back to workingDirectory so a wandering
cd in one call doesn't leak to the next. Mirrors Python _maybe_reanchor.
- Graceful interrupt on timeout: ShellSession sends SIGINT (POSIX) or
Ctrl+C-on-stdin (Windows) before falling back to a hard close+respawn.
Successfully-interrupted commands return exit 124 + TimedOut=true while
preserving session state for the next call.
- cleanEnvironment opt-in: when true, only PATH/HOME/USER/USERNAME/
USERPROFILE/SystemRoot/TEMP/TMP plus user-supplied vars are visible.
- shellArgv: IReadOnlyList<string> override accepted alongside the
string shell binary param (mutually exclusive). Lets advanced callers
inject flags like --rcfile or --login.
- Typed exceptions ShellTimeoutException and ShellExecutionException
replace InvalidOperationException for launch / liveness failures.
Tests: 17 -> 23. New cases cover persistent-default ctor, mutually-
exclusive shell/shellArgv, confined re-anchor, confine-disabled leak,
clean-env strip, and IShellExecutor implementation. All green on net10.0.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(shell): add DockerShellTool sandboxed shell tier
Ports the Python DockerShellTool to .NET. Mirrors the public surface of
LocalShellTool but executes commands inside an isolated container, where
the container is the security boundary. Stateless and persistent modes
both supported; persistent mode reuses ShellSession by launching
'docker exec -i <ctr> bash --noprofile --norc' as the long-lived REPL,
so the sentinel protocol works unchanged.
Defaults chosen for safety:
- --network none, --user 65534:65534 (nobody), --read-only root
- --cap-drop=ALL, --security-opt=no-new-privileges
- 512m memory cap, pids-limit 256, --tmpfs /tmp
- Optional host workdir mount, ro by default
Public surface:
- DockerShellTool ctor with image/container_name/mode/host_workdir/
workdir/network/memory/pids_limit/user/read_only_root/extra_run_args/
environment/policy/timeout/max_output_bytes/on_command/docker_binary
- StartAsync, CloseAsync, RunAsync, AsAIFunction, IShellExecutor impl
- IsAvailableAsync(binary) probe
- Static argv builders (BuildRunArgv, BuildExecArgv) — pure, side-
effect free, so unit tests don't need a Docker daemon
AsAIFunction defaults to requireApproval: false (the container IS the
boundary). LocalShellTool keeps the opposite default.
Tests: 23 -> 35. 12 new tests cover argv builders, env/extra-args/host-
workdir flags, exec interactive vs stateless, container name uniqueness,
IShellExecutor implementation, AsAIFunction approval defaults, and
IsAvailableAsync false-path. None require Docker. Multi-TFM build
(net8/9/10) green.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* test(shell): add DockerShellTool integration tests
Adds 9 end-to-end tests that exercise DockerShellTool against a live
Docker (or Podman) daemon. Tests are tagged [Trait("Category",
"Integration")] and auto-skip via Assert.Skip when no daemon is
available, so they are CI-safe.
Coverage:
- IsAvailableAsync probe
- Persistent mode basic command + state preservation across calls
- --network none blocks outbound DNS
- --read-only root prevents writes outside /tmp; /tmp tmpfs is writable
- --user 65534:65534 (nobody) is in effect
- Stateless mode: env vars do not leak across calls
- HostWorkdir bind-mount + read-only enforcement
- Environment variables passed via -e
Tests use debian:stable-slim (alpine ships only busybox sh, which
ShellSession persistent bash REPL cannot drive).
Run locally:
dotnet test --filter "Category=Integration"
or filter by class on the test exe directly.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* style(shell): apply dotnet format pass
- Whitespace and code-style fixes from `dotnet format` across both
projects
- Convert all new files to UTF-8 with BOM and LF line endings
(repo convention)
- Rename ShellSession statics to s_ prefix (IDE1006)
- Add Async suffix to async test methods (IDE1006)
No behavioral changes. All 44 tests still pass on net10.0; multi-TFM
build (net8/net9/net10) green. `dotnet format --verify-no-changes`
now reports clean for both projects.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* docs(shell): add DockerShellTool walkthrough with sequence diagrams
Explains the mental model (we shell out to the docker CLI; we never speak the engine API), the hardened docker run argv, persistent vs stateless lifecycles with mermaid sequence diagrams, the full agent-to-bash call ladder, and the failure modes.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* PR 5604 review fixes (group a): libc DllImport, namespace cleanup, policy-msg dedup
Three quick-win review comments on PR #5604:
1. ShellSession: the libc `killpg` P/Invoke was annotated with
`DllImportSearchPath.System32`, a Windows-only loader hint that does
nothing for libc.so on POSIX. Switched to `SafeDirectories` (CA5392
/CA5393 clean) and added a comment noting the call site is gated to
non-Windows.
2. DockerShellToolTests: replaced the fully-qualified
`Extensions.AI.ApprovalRequiredAIFunction` with a `using
Microsoft.Extensions.AI;` import and the bare type name, matching
`LocalShellToolTests`.
3. LocalShellTool / DockerShellTool: `AsAIFunction`'s catch block was
producing a doubled "Command blocked by policy: Command rejected by
policy: ..." prefix because the `ShellPolicyException` message
already starts with "Command rejected by policy". Now we return
`ex.Message` directly.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* PR 5604 review fix (group b): add ShellKind.Sh for /bin/sh fallback
Review comment (#3): when /bin/bash is missing the resolver fell back to
/bin/sh but tagged it as ShellKind.Bash, so the launcher passed bash-only
flags --noprofile --norc to dash/ash/busybox, which interpret them as
positional script names.
Fix:
* Added ShellKind.Sh for minimal POSIX shells (sh, dash, ash, busybox).
* /bin/sh fallback is now tagged Sh.
* ClassifyKind maps "SH" / "DASH" / "ASH" / "BUSYBOX" binary names to Sh.
* StatelessArgvForCommand emits just `-c <command>` for Sh (no
bash-only flags); PersistentArgv emits no flags at all.
* LocalShellTool's system-prompt builder describes Sh distinctly and
warns the model away from bash-only constructs.
Tests: ShellResolverTests covers Sh/Bash classification through the
observable argv output (14 new theory cases). Total: 58/58.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* PR 5604 review fix (group d): honor timeout=null, add DefaultTimeout
Review comment (#5): both LocalShellTool and DockerShellTool documented
`timeout: null` as "disables timeouts" but the constructor coerced null
to 30 seconds, making the documented disable mechanism unreachable
through the public API.
Fix:
* Drop the `?? TimeSpan.FromSeconds(30)` coercion in both ctors.
`_timeout` now faithfully reflects what the caller passed (null =
disabled). The downstream CTS-construction sites already short-circuit
on null, so no other code changes are required.
* Add `public static readonly TimeSpan DefaultTimeout` (30 s) on both
tools so callers who want a bounded timeout can opt in explicitly.
Tests:
* New `RunAsync_NullTimeout_DoesNotTimeOutAsync` confirms a quick
command runs to completion when the caller passes `timeout: null`.
* New `DefaultTimeout_IsThirtySeconds` documents the constant.
Behavioral note: this is a deliberate change-of-default. Callers that
previously omitted `timeout` and relied on the implicit 30 s now get
"no timeout". They should pass `LocalShellTool.DefaultTimeout` or
`DockerShellTool.DefaultTimeout` explicitly to preserve the prior
behavior.
Tests: 60/60 (44 baseline + 14 resolver + 2 new timeout tests).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* PR 5604 review fix (group e): smart requireApproval default for DockerShellTool
Review comment (#6, design): requireApproval: false baked in a
safety decision the type cannot prove on its own. Callers can
weaken any isolation knob (network, user, readOnlyRoot, mount,
extraRunArgs) and still get an unapproved tool by default.
Fix:
* New public IsHardenedConfiguration property returns true iff the
effective config matches the safe defaults: network=="none",
non-root user, read-only root, host mount (if any) read-only,
no extra run args.
* AsAIFunction's requireApproval parameter is now bool? defaulting
to null. When null, approval is enabled iff
IsHardenedConfiguration is false. Pass false explicitly to opt
out, or true to force.
* docker-shell-tool.md updated with the new approval matrix.
Tests: 4 new theory cases + 2 facts cover hardened-default,
relaxed-network, root-user, writable-root, extraRunArgs, and
explicit-opt-out branches. Total: 66/66.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* PR 5604 review fix (group c): wrap POSIX shell in setsid for correct killpg
Review comment (#1): killpg(proc.Id, SIGINT) only behaves like a
process-group signal when proc.Id IS a process group id. Since the
.NET launcher does not call setsid() / setpgid() itself, the spawned
shell inherits the agent host's process group — so killpg targeted
the wrong group and the cancel signal could leak to the agent.
Fix:
* On non-Windows, EnsureStartedAsync probes for setsid (well-known
paths first, then PATH). When found it wraps the shell launch as
`setsid <shell> <args...>` so the spawned shell becomes a session
leader (PID == PGID).
* A new _isSessionLeader flag tracks whether the wrap succeeded.
* InterruptCurrentCommandAsync only calls killpg when
_isSessionLeader is true. Without setsid, killpg on an unsuited
PID could signal the agent itself, so we skip the fast path and
let the caller's hard close-and-respawn handle the timeout.
* Windows behaviour is unchanged (Ctrl+C-via-stdin to pwsh).
No public-API changes; existing tests cover the interrupt path and
all 66/66 still pass.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .Net: DockerShellTool design + caller-cancel container leak fixes (PR #5604)
Addresses three Copilot review findings on PR #5604.
Design (group f):
* StartAsync: change inner ResolvedShell from ShellKind.Bash to ShellKind.Sh.
BuildExecArgv() already includes `--noprofile --norc` in ExtraArgv;
Bash's PersistentArgv() was appending those flags a second time,
yielding `bash --noprofile --norc --noprofile --norc`. Sh's
PersistentArgv() returns Array.Empty so ExtraArgv is forwarded
unchanged.
* BuildExecArgv: remove the dead `interactive: false` branch and the
`interactive` parameter. The `false` path produced an unusable argv
ending in `-c` with no command and was never invoked internally
(stateless mode uses BuildRunArgvStateless). Updated tests and
docs/docker-shell-tool.md sequence diagram.
Reliability (group g):
* RunStatelessAsync: add a second `catch (OperationCanceledException)`
guarded on `cancellationToken.IsCancellationRequested` that issues
`docker kill --signal KILL <perCallName>` before rethrowing.
Previously, caller-driven cancellation bypassed the timeout-only
catch and propagated without killing the container; because `--rm`
only fires when PID 1 exits, the container ran indefinitely.
Extracted the kill-by-name logic into a `BestEffortKillContainerAsync`
helper shared by both the timeout and caller-cancel paths.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .Net: Fill PR #5604 test coverage gaps for Shell tools
Addresses the test-coverage findings in the latest Copilot review.
* ShellResultTests (new): direct branch coverage for
ShellResult.FormatForModel() — empty stdout, non-empty stderr,
truncated, timed-out, success, and the truncated-with-empty-stdout
edge where the marker is intentionally suppressed. This method's
string is what the language model sees, so it benefits from
explicit unit-level coverage independent of integration tests.
* ShellSessionTests (new): direct unit tests for the internal
TruncateHeadTail head-tail truncation utility — under-cap (no
truncation), exactly at cap (no truncation), over-cap (truncated
with marker, both head and tail preserved), and empty-string.
Reachable via InternalsVisibleTo.
* LocalShellToolTests: Theory test exercising 8 representative
patterns from ShellPolicy.DefaultDenyList (rm -rf /, mkfs.ext4,
curl|sh, wget|sh, Remove-Item /, shutdown, reboot, Format-Volume)
to catch deny-list regex regressions; previously only 1/16 was
tested.
* LocalShellToolTests: explicit stderr-capture assertion (echo to
stderr → result.Stderr contains the message). Stderr capture was
not directly asserted anywhere in the suite.
* DockerShellToolTests: RunAsync_RejectedCommand throws
ShellCommandRejectedException. The Docker-side policy check is a
pure-logic path that runs before any docker invocation, so this
test covers the rejection branch without needing a Docker daemon.
Total: 66 -> 85 tests, all passing on net10.0.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(dotnet/shell): add ShellEnvironmentProvider for OS-aware shell instructions
Pairs LocalShellTool/DockerShellTool with an AIContextProvider that
probes the live shell once per session (OS, family, version, CWD,
configurable CLI versions) and injects authoritative instructions so
the agent uses platform-native idioms (PowerShell vs POSIX). Fixes the
class of bugs where the model emits 'VAR=value' / '/tmp' / '$VAR' on
a Windows PowerShell session.
- ShellEnvironmentProvider/Snapshot/Options public surface in the
existing Microsoft.Agents.AI.Tools.Shell package (one new project
reference to Microsoft.Agents.AI.Abstractions).
- Probes go through the same IShellExecutor that runs agent commands,
so they respect the configured policy and (for DockerShellTool) the
container boundary.
- 8 unit tests covering snapshot capture, default formatter idioms,
missing-tool handling, custom formatter override, and refresh.
- Agent_Step21_ShellWithEnvironment sample replays the DEMO_TOKEN
cross-call scenario using a persistent local shell.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(dotnet/shell): address PR review feedback round 3
- ShellEnvironmentProvider.cs split into one-type-per-file (ShellFamily,
ShellEnvironmentSnapshot, ShellEnvironmentProviderOptions, plus the
provider class) to match FoundryMemoryProvider/AgentSkillsProvider
layout.
- csproj: drop IsPackable=false (package will publish on merge), add
IsReleased=true and disable package validation baseline (first release),
use TargetFrameworksCore, add InjectSharedDiagnosticIds and
InjectExperimentalAttributeOnLegacy to align with shipping packages.
- Sample: refactor to demonstrate stateless mode first (independent
read-only commands), then persistent mode (state carried across calls,
e.g. DEMO_TOKEN). Strip narrative/historical comments.
- Move docker-shell-tool.md out of the package — that doc lives in
the docs repo (semantic-kernel-pr/agent-framework, branch
feat/dotnet-shell-tool).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address PR #5604 round 4 review feedback
- Sample (Agent_Step21_ShellWithEnvironment): add prominent WARNING block
noting LocalShellTool runs real commands on the host. Restructure sample
to demonstrate stateless mode first (cd does not carry across calls) then
persistent mode (cd and env vars persist), motivating when to pick each.
- DockerShellTool class XML doc: reframe as a best-effort baseline rather
than a security guarantee; list mitigations users should still apply.
- DockerShellTool ShellKind.Sh comment: rephrase as forward-looking design
rationale (avoid duplicate --noprofile/--norc if Bash is reintroduced)
instead of bug-history narrative.
- DockerShellTool.IsHardenedConfiguration / AsAIFunction XML docs: clarify
these are configuration-shape checks and convenience defaults, not
security guarantees.
- Drop IDisposable from LocalShellTool and DockerShellTool. The previous
sync Dispose() blocked on DisposeAsync().GetAwaiter().GetResult() with a
VSTHRD002 suppression, which is fragile under sync contexts. Both tools
now expose IAsyncDisposable only; tests updated to await using.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add Async suffix to async test methods to satisfy IDE1006
Fixes check-format CI failure on PR #5604.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix CPU busy-spin in WaitForSentinelAsync
When new bytes arrived in the stdout read loop, the producer called
TrySetResult on _stdoutSignal but did not replace it with a fresh TCS.
A consumer looping inside WaitForSentinelAsync would then re-read the
same already-completed TCS, causing WaitAsync(100ms) to return
synchronously every iteration — a tight busy-spin that pinned a core
until the sentinel arrived or the timeout fired.
Swap the signal before completing the old one so the next consumer
iteration observes a fresh (uncompleted) TCS, matching the pattern
already used in ReadExitCodeAsync.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Remove unused onCommand audit hook from shell tools
The Action<string> onCommand callback was a redundant audit-logging seam:
no production callers, no Python parity, and the framework already
provides function-invocation middleware for cross-cutting concerns at
the AIFunction layer. Removing the parameter from LocalShellTool and
DockerShellTool keeps the public surface lean.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Align Shell csproj with Foundry.Hosting preview-package conventions
- Add RootNamespace
- Move Title/Description into the primary PropertyGroup with
TargetFrameworks/VersionSuffix to match the Foundry.Hosting layout
- Drop IsReleased (preview packages do not set it)
- Drop UTF-8 BOM
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Document why ShellEnvironmentProvider uses Instructions, not Messages
Expand the class XML doc to record the design rationale: the shell
environment is stable runtime metadata, not per-turn retrieval, so it
belongs in AIContext.Instructions (matching AgentSkillsProvider).
Messages is reserved for retrieval payloads (TextSearchProvider,
ChatHistoryMemoryProvider). System-role placement also has higher
steering weight and benefits from prompt caching in major providers.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Clarify which probe failures ShellEnvironmentProvider swallows
Name the four exception types explicitly (timeout, policy rejection,
spawn failure, cancellation) and note that all other exceptions
propagate normally. Avoids the misleading impression that the provider
is a blanket try/catch.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Strip cross-language and bug-history narrative from shell tool comments
Remove "hard-won" framing and explicit "Mirrors the Python ..." cross
references from class XML docs and inline comments in ShellSession,
DockerShellTool, and ShellResolver. Comments now describe current
behavior without commentary on prior implementations or development
history.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address PR #5604 round 5 review feedback
- ShellResolver: classify only `bash` as ShellKind.Bash; sh/zsh/dash/ash/ksh/busybox now route through ShellKind.Sh so bash-only --noprofile/--norc flags are not emitted to shells that reject them. Update enum doc and tests.
- ShellEnvironmentProvider.ProbeToolVersionAsync: validate the tool name against ^[A-Za-z0-9._-]+$ before interpolating into a shell command (prevents injection if ProbeTools is sourced from untrusted config). Fall back to stderr when stdout is empty so CLIs like java/older gcc still report a version. Drop misleading 'quoted' comment.
- ShellSession.TruncateHeadTail: truncate by UTF-8 byte count on rune boundaries, honouring the documented maxOutputBytes contract for non-ASCII output.
- ShellEnvironmentProviderTests: drop reflection on private _options; assert against the options instance the test already owns. Rename misnamed RefreshAsync test to reflect re-probing semantics. Add coverage for invalid tool names and stderr-only version output.
- ShellSessionTests: add multi-byte UTF-8 truncation tests (byte-budget honoured, no rune split, no U+FFFD).
- Move DockerShellToolIntegrationTests.cs from the unit test project into a new Microsoft.Agents.AI.Tools.Shell.IntegrationTests project so 'dotnet test' on the unit suite no longer requires a Docker daemon. Wire the new project into agent-framework-dotnet.slnx.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address PR #5604 round 6 review feedback
- ShellSession.MaybeReanchor: switch from double-quoted to single-quoted literal-quoting per shell. Double quotes still expand $VAR, ``, and backticks in both PowerShell and POSIX, so a working directory containing shell metacharacters could trigger command substitution. Add QuotePowerShell (escape ' as '') and QuotePosix (close-and-reopen around ') helpers and route MaybeReanchor through them. Add tests covering ``, $VAR, backticks, and embedded single quotes.
- ShellEnvironmentProvider.RunProbeAsync: narrow the OperationCanceledException filter to `when (!cancellationToken.IsCancellationRequested)` so caller-driven cancellation propagates instead of being silently converted to a null snapshot. Update the class XML doc to call out the distinction. Add tests for both paths (caller cancellation throws, probe-timeout returns null fields).
- DockerShellTool.RunStatelessAsync / RunDockerCommandAsync: replace unbounded StringBuilder accumulators with a shared HeadTailBuffer (extracted from LocalShellTool into its own internal type). Caps memory at roughly maxOutputBytes regardless of how much output a command emits; drops the now-redundant trailing TruncateHeadTail call. RunDockerCommandAsync caps helper-command output at 1 MiB (defends against chatty docker pull progress streams). Add HeadTailBufferTests covering bounded behaviour over 10 MiB of streamed input.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address PR #5604 round 7 review feedback
- HeadTailBuffer: switch to UTF-8 byte-aware truncation. The class previously
capped on UTF-16 char count while callers pass _maxOutputBytes, so multi-byte
output could exceed the budget and head/tail boundaries could split surrogate
pairs into orphaned halves. Now tracks UTF-8 byte counts and treats each rune
as an indivisible unit (encode -> bytes -> head/tail), guaranteeing the final
string round-trips through UTF-8 and never contains an unpaired surrogate.
The truncation marker now reads `bytes` instead of `chars` to match.
- ShellEnvironmentProvider: clear cached _snapshotTask on failure. Previously a
faulted/cancelled first probe permanently poisoned the provider — every later
ProvideAIContextAsync await replayed the same exception. Now the failed task
is cleared via a CompareExchange so the next caller starts a fresh probe.
Tests: added rune-boundary coverage for HeadTailBuffer, plus two regression
tests for poison-recovery (executor-throw and caller-cancellation paths).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address PR #5604 round 8 review feedback
- HeadTailBuffer odd-cap data loss: previously _halfCap = cap / 2 was used as
both the head fill bound and the tail eviction threshold, so an odd cap (e.g.
cap=5 -> halfCap=2) would silently drop a byte while ToFinalString still
reported truncated == false. Split into _headCap = cap / 2 and _tailCap =
cap - _headCap so head + tail budgets always sum to exactly cap; any input
whose UTF-8 size is <= cap now round-trips losslessly.
- ShellSession.TakePrefixByBytes unpaired-high-surrogate: the prefix walker
advanced 2 chars whenever it saw a high surrogate, without verifying that the
next char was actually a low surrogate. Mirrored the pair check from
TakeSuffixByBytes so unpaired surrogates are treated as a single (invalid)
BMP char and the encoder substitutes U+FFFD as it would anywhere else.
- Centralize clean-environment preserved-vars list. The {PATH, HOME, USER,
USERNAME, USERPROFILE, SystemRoot, TEMP, TMP} allowlist was duplicated in
LocalShellTool (stateless launch) and ShellSession (persistent startup), so
adding a new variable required touching both. Extracted into
CleanEnvironmentHelper.PreservedVariables / ApplyPreserved; both call sites
collapse to a single line.
Tests: HeadTailBuffer round-trip-at-odd-cap regression, ShellSession unpaired-
surrogate test.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address PR #5604 round 9 review feedback
- ShellSession.TruncateHeadTail odd-cap budget: same fix applied to
HeadTailBuffer last round but missed here. Use headCap = cap/2 +
tailCap = cap - headCap so the head/tail budgets sum to exactly cap.
- Replace TakePrefixByBytes / TakeSuffixByBytes Encoder.Convert loops with
rune iteration. The old code ignored Encoder.charsUsed and trusted the
caller's hand-rolled surrogate-pair detection, which made the byte count
fragile around unpaired surrogates. EnumerateRunes + Utf8SequenceLength
is stateless and self-evidently correct.
- ShellEnvironmentProvider.ProbeAsync now skips case-insensitive duplicates
in the user-supplied ProbeTools list. Previously {\"git\",\"GIT\"} would
probe twice and rely on insertion order to determine the kept value.
- DockerShellToolTests.AsAIFunction_RelaxedConfig_DefaultsToApprovalGated:
removed unused trailing ool _ parameter and matching InlineData column.
Tests: added duplicate-ProbeTools regression test.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address PR #5604 round 10 review feedback
* ShellSession.ReadLoopAsync: replace per-byte buf.Add(chunk[i]) loop with a single buf.AddRange(new ArraySegment<byte>(chunk, 0, n)) bulk copy on the read hot path.
* ShellPolicy: compile allow-list patterns with RegexOptions.IgnoreCase, matching the deny-list and avoiding case-mismatch surprises.
* LocalShellToolTests.RunAsync_NonZeroExit: drop the redundant ternary that selected between two identical 'exit 7' literals.
* DockerShellToolIntegrationTests.NetworkNone: fix the comment to reference 'getent' (matching the actual command) instead of the stale 'wget' phrasing.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(dotnet): address PR #5604 round-3 review feedback
- Rename LocalShellTool/DockerShellTool -> LocalShellExecutor/DockerShellExecutor
- Rename IShellExecutor.StartAsync/CloseAsync -> InitializeAsync/ShutdownAsync
- Rename ShellDecision -> ShellPolicyOutcome
- Rename CleanEnvironmentHelper.ApplyPreserved -> EnvironmentSanitizer.RemoveNonPreserved
- Convert ShellRequest/ShellPolicyOutcome from record struct to plain readonly struct (with IEquatable<T>)
- Split ShellMode, ShellTimeoutException, ShellExecutionException into their own files
- Add DockerNetworkMode static class with None/Bridge/Host constants
- Convert DockerShellExecutor memory parameter from string to long? memoryBytes
- Use Throw.IfNull(image) in DockerShellExecutor ctor
- Make ShellResolver.EnvVarName public const
- Inline-comment each DefaultDenyList regex; document allow-precedence-over-deny on ShellPolicy.Evaluate
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(dotnet): address PR #5604 round-3 follow-up nits
- DockerShellExecutor / LocalShellExecutor: drop redundant IAsyncDisposable from class declarations (IShellExecutor : IAsyncDisposable already covers it)
- DockerShellExecutor: scope DefaultImage / DefaultContainerUser / DefaultNetwork / DefaultMemoryBytes / DefaultPidsLimit / DefaultContainerWorkdir to internal (only used as parameter defaults; tests have InternalsVisibleTo)
- DockerShellExecutor.RunAsync: blank line after the null-guard block (style consistency)
- csproj: move <Title>/<Description> below the nuget-package.props import so they are not overwritten by the shared defaults; refresh wording to match new executor names
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Refactor shell tool: abstract ShellExecutor, options classes, ContainerUser record
Round-3 review responses for PR #5604:
* Replace IShellExecutor interface with abstract ShellExecutor base class so the surface can be extended without breaking implementers (review feedback from @westey-m).
* Drop ShutdownAsync from the executor surface; DisposeAsync is the canonical teardown (review feedback from @SergeyMenshykh).
* Replace the long parameter lists on Local/DockerShellExecutor constructors with LocalShellExecutorOptions and DockerShellExecutorOptions classes so adding new knobs is no longer a breaking change (review feedback from @SergeyMenshykh).
* Introduce ContainerUser(Uid, Gid) record in place of a 'uid:gid' string for the Docker user, with Default and Root statics (review feedback from @lokitoth).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Remove IsHardenedConfiguration; AsAIFunction defaults to approval-gated
Addresses PR #5604 review thread AZpMj. The IsHardenedConfiguration
property was a configuration-shape check, not a security guarantee,
and using it to auto-disable approval gating gave false confidence.
- Delete IsHardenedConfiguration property.
- AsAIFunction(requireApproval: null) now always wraps in
ApprovalRequiredAIFunction; callers must explicitly pass false to
opt out.
- Update class- and method-level XML docs to drop hardened-attestation
language and call out approval gating as the primary safety control.
- Drop two hardening-assertion tests and the relaxed-config theory;
add one test asserting null requireApproval is approval-gated.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Replace ShellExecutionException/ShellTimeoutException with standard exceptions
Addresses PR #5604 review threads AaqVP and Aasod. The custom
exception types added no behavior beyond the base type — only a
different name — so callers gain nothing from them.
- Delete ShellExecutionException.cs and ShellTimeoutException.cs.
- Process spawn failures (LocalShellExecutor, DockerShellExecutor)
and broken-pipe to a long-lived shell (ShellSession) now throw
IOException, which is the natural .NET shape for these failures.
- ShellTimeoutException was declared but never thrown; the only
in-process timeout path uses the OperationCanceledException raised
by the linked CancellationTokenSource. The catch-and-swallow in
ShellEnvironmentProvider now matches IOException + TimeoutException.
- Update XML doc comments accordingly.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Remove ShellPolicy.DefaultDenyList; default policy is empty
Addresses PR #5604 review thread AY7Ba. A regex deny-list is
bypassed in seconds by hex escapes ($(echo -e "\x72\x6D")),
command substitution ($(base64 -d <<<...)), and envvar splicing
($(A=r B=m; echo $A$B)). No major agent framework uses regex
matching as a primary control; AutoGen explicitly removed theirs
in v2. The real defenses are approval gating (default) and the
Docker sandbox tier.
- Delete DefaultDenyList property from ShellPolicy.
- ShellPolicy(denyList: null) now means an empty deny-list.
- Rewrite ShellPolicy class XML docs to frame as a UX pre-filter
for operator-supplied patterns, not as a security control.
- Update LocalShellExecutorOptions/DockerShellExecutorOptions
Policy docs to match.
- Tests that exercise the deny-list mechanism now supply patterns
explicitly, mirroring real operator usage.
- Add Policy_DefaultConstruction_AllowsAnyNonEmptyCommand test.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Document single-session ownership for persistent shell mode
Several PR #5604 review threads (notably AaQh2) raised that the persistent
shell experience has no concurrency story. The framework's actual design
is "one executor per conversation" — there is no per-caller isolation —
but that contract was only stated briefly on ShellExecutor and not at all
on the types and properties developers reach for first.
Strengthen the docs in the places a user is most likely to land:
- ShellMode.Persistent: explicit single-session-ownership paragraph
(state visible across calls, single pipe, no isolation, one per session).
- ShellExecutor: rewrite the Concurrency paragraph to enumerate what
leaks (cwd, env, history, background jobs) and call out DI scoping.
- LocalShellExecutor: new Single-session-ownership paragraph mirroring
the executor-level contract and pointing at Stateless mode as the
escape hatch.
- DockerShellExecutor: same, framed around the container + bash REPL
the persistent-mode executor owns end-to-end.
- ShellSession: add a Single-owner paragraph on the type docs and a
comment on _runLock clarifying that it serializes the owner's calls,
not multiple tenants.
- LocalShellExecutorOptions.Mode / DockerShellExecutorOptions.Mode:
per-property note pointing at the executor remarks.
Docs-only; src builds clean with zero warnings, zero errors.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: alliscode <bentho@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: align Anthropic Extensions AI version
* test: update Anthropic test stubs for new interfaces
---------
Co-authored-by: Jacob Alber <jaalber@microsoft.com>
* test: Split out Handoff Orchestration tests
* fix: Synthesized Handoff FunctionResult is never sent to agent
When we receive a handoff request from the agent, we need to service it outside of the Agent Loop to terminate the loop. What this means is that we take ownership of terminating the call by feeding the result back into the agent on a subsequent invocation.
When we refactored Handoff to support HITL and make use of AgentSession, we inadvertantly removed this step, causing subsequent invocations to the Handoff agent to fail (first works, but breaks the state).
The fix is to be more precise about the agent's bookmark when concatenating the result of agent invocation to the shared conversation history.
* test: Add unit tests for Handoff FunctionCall/Result matching fix
* .NET: Add A2A input-request content for human-in-the-loop scenarios
Adds first-class support for handling user input requests from A2A agents
when they return an `input-required` task state.
- Add `A2AInputRequestContent` (wraps the requested `AIContent`) and
`A2AInputResponseContent` (wraps the user's `AIContent` reply), with
`CreateResponse` helper overloads on the request type.
- Surface input requests on `AgentResponse` / `AgentResponseUpdate` via
`AgentTask` and `TaskStatusUpdateEvent` mappings.
- Link follow-up messages containing `A2AInputResponseContent` to the
existing task via `TaskId` instead of `ReferenceTaskIds`.
- Add `A2AAgent_HumanInTheLoop` sample and register it in the solution
and parent README.
- Add unit tests for the new types, extensions, and `A2AAgent` paths.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Remove unnecessary using directive flagged by CI format check
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* address feedback
* Guard against null TaskId when sending A2AInputResponseContent
Throw InvalidOperationException if TaskId is missing when the message
contains A2AInputResponseContent, preventing silent no-op responses.
Also adds tests for both RunAsync and RunStreamingAsync paths.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Leave Contents null for non-InputRequired status updates
Remove unnecessary '?? []' fallback so Contents stays null when there
are no input requests, matching the other update mapping patterns.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Use consistent GUID format for request IDs
Use ToString("N") to match message ID format used elsewhere in
the A2A component.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Remove Debug build exclusion for the HumanInTheLoop sample so it participates in normal solution validation.
* Add missing using Microsoft.Extensions.AI to A2AAgent_HumanInTheLoop
The sample uses ChatMessage, TextContent, and ChatRole types from
Microsoft.Extensions.AI but was missing the using directive, causing
CS0246 build errors on all CI jobs.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* change the way user input requests are handled based on pr review comments
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: Migrate agent-framework-a2a to a2a-sdk v1.0
Upgrade the a2a-sdk dependency from v0.3.x to v1.0.0 and migrate all
source, tests, samples, and documentation to the v1.0 API.
Key changes:
- Dependency: a2a-sdk>=1.0.0,<2 (was >=0.3.5,<0.3.24)
- Types are now protobuf-based: Part replaces TextPart/FilePart/DataPart
- Enums use SCREAMING_SNAKE_CASE (e.g. TaskState.TASK_STATE_COMPLETED)
- Roles: Role.ROLE_AGENT, Role.ROLE_USER
- Client: SendMessageRequest wrapper, subscribe() replaces resubscribe()
- Server: A2AStarletteApplication replaced by Starlette + route factories
- DefaultRequestHandler now requires agent_card parameter
- TaskUpdater: final parameter removed, add_artifact gains last_chunk
- AgentCard.url removed; use supported_interfaces with AgentInterface
- Stream yields StreamResponse with WhichOneof('payload')
Closes#5661
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address PR review: validate fallback URL, remove unused task_id vars
- Raise ValueError with clear message when transport negotiation fails
and no fallback URL is available (neither url arg nor supported_interfaces)
- Remove unused task_id local in status_update branch
- Inline artifact_event.task_id directly in artifact_update branch
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: DevUI: add configurable access controls for the DevUI HTTP surface
* .NET: DevUI: address review and fix dotnet format
- Restore parameterless AddDevUI overloads for binary compatibility on
IServiceCollection and IHostApplicationBuilder.
- Keep /meta outside the auth-filtered group so the frontend can discover
whether a bearer token is required before prompting for one. Surface the
actual requirement via MetaResponse.auth_required.
- Invoke DevUIOptions.ConfigureEndpoints before mapping protected endpoints
so RouteGroupBuilder conventions (RequireAuthorization, rate limiting)
reliably apply.
- Treat a null RemoteIpAddress as non-loopback in DevUIAuthFilter; tests
now set IPAddress.Loopback explicitly when exercising the loopback path.
- Add a DEVUI_AUTH_TOKEN env-var fallback test and a /meta-public test.
- Fix dotnet format: add UTF-8 BOM to new files, simplify a cref in
DevUIOptions, and drop an unused using in the new test.
* .NET: DevUI: add missing authRequired param XML tag
* .NET: DevUI tests: set loopback/AllowRemoteAccess for null-RemoteIp default
DevUIIntegrationTests use the default TestServer which leaves RemoteIpAddress
null. With the new conservative loopback default those tests now hit 403; set
AllowRemoteAccess on the option since those tests are not exercising access
control. Also add the missing SimulateRemoteIp call in the wrong-bearer test.
* .NET: DevUI tests: capture DEVUI_AUTH_TOKEN before parallel tests can see it
The env-var test was leaking DEVUI_AUTH_TOKEN into parallel DevUIIntegrationTests,
intermittently causing their requests to be rejected as 401. Eagerly resolve the
singleton DevUIAuthFilter so its constructor captures the token, then restore the
env var before any HTTP requests run.
* .NET: Remove Foundry Toolbox server-side tools support
Mirrors the Python cleanup in microsoft/agent-framework#5671. Passing
toolbox tools as server-side Responses tools is not the experience we
want to support; the hosted-agent MCP toolbox path (HostedMcpToolboxAITool
+ FoundryToolboxService) remains the supported way to consume Foundry
Toolboxes.
Removed:
- FoundryToolbox static class (GetToolboxVersionAsync / GetToolsAsync /
ToAITools / SanitizeAndConvert)
- AIProjectClient.GetToolboxToolsAsync extension
- Agent_Step25_ToolboxServerSideTools sample (+ slnx entry)
- FoundryToolboxTests, TestDataUtil, HttpHandlerAssert, and the toolbox
JSON fixtures only those tests referenced
- ToolboxHostedAgentTests and ToolboxHostedAgentFixture; the "toolbox"
switch arm + CreateToolboxAgent helper in TestContainer; matching
README scenario row and bootstrap script entry
Kept (MCP path, unchanged):
- HostedMcpToolboxAITool, FoundryAITool.CreateHostedMcpToolbox,
FoundryAIToolExtensions.CreateHostedMcpToolbox(ToolboxRecord/Version)
- FoundryToolboxService, AddFoundryToolboxes, marker injection in
AgentFrameworkResponseHandler, InputConverter.ReadMcpToolboxMarkers
- Hosted-Toolbox sample, McpToolbox* tests, FoundryToolboxServiceTests
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Add Foundry Toolbox MCP sample (Agent_Step25_FoundryToolboxMcp)
Adds a non-hosted-agent equivalent of the Python foundry_chat_client_with_toolbox.py sample. The agent connects to a Foundry Toolbox's MCP endpoint via Streamable HTTP, injects a fresh Azure AI bearer token on every request, and discovers the toolbox's tools at runtime via McpClient.ListToolsAsync.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Tighten Agent_Step25_FoundryToolboxMcp README/Program comments
Drop 'non-hosted agent' framing from README (this sample isn't related to hosted agents) and remove narrative comparison to server-side tools from the Program.cs header comment.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Drop python sample reference from Agent_Step25 README
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Drop incorrect .NET 10 prereq from Agent_Step25 README
Toolboxes don't require .NET 10 (Microsoft.Agents.AI.Foundry targets net8.0+); the parent AgentsWithFoundry README already lists the sample SDK prereq.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix Toolsets api-version in Agent_Step25 example endpoint
Use 2025-05-01-preview to match FoundryToolboxOptions.ApiVersion. The placeholder 'v1' is not accepted by the Toolsets endpoint.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: alliscode <bentho@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Persist input messages on streaming errors in PerServiceCallChatHistoryPersistingChatClient
When the underlying chat service emits an in-stream error (for example a
`response.error` SSE event from the OpenAI Responses API on rate limit),
the OpenAI client surfaces it as an `ErrorContent` update and ends the
stream without throwing. Previously, `PerServiceCallChatHistoryPersistingChatClient`
only persisted history when the streaming loop completed successfully and
`NotifyProvidersOfNewMessagesAsync` was called at the end. On the
in-stream-error path, the input messages handed to that iteration -
typically `FunctionResultContent` produced by `FunctionInvokingChatClient`
in the previous iteration - were never persisted. The next run would
replay session history with a dangling `FunctionCallContent` and the
service would reject the request with `No tool output found for function
call <id>`.
This change:
- Adds a `PersistInputOnErrorAsync` helper that persists the input
messages (with no response messages) so function-call/function-result
pairings are not split across failures.
- Calls the helper from every error path: pre-loop enumerator creation,
the first `MoveNextAsync`, the in-loop `MoveNextAsync`, and a new
`finally` that handles abnormal iterator disposal.
- After the streaming loop, scans the assembled response for any
`ErrorContent` and, if present, persists the input, notifies
providers of failure, and throws `InvalidOperationException` so the
error is surfaced to the caller instead of silently corrupting history.
- Hardens `InMemoryChatHistoryProvider.StoreChatHistoryAsync` to treat
a null `RequestMessages` as empty, since the new error path can
invoke it with no response messages.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix dropped FunctionResultContent on streaming pipeline early-disposal
When a consumer of ChatClientAgent.RunStreamingAsync stops iterating early
(e.g. ToolApprovalAgent yields the approval request and then `yield break`),
the framework cascades DisposeAsync down the stream. C# async iterators do
not auto-dispose IAsyncDisposable locals, so the inner enumerator returned
by IChatClient.GetStreamingResponseAsync(...).GetAsyncEnumerator(ct) was
left suspended. That suspended FunctionInvokingChatClient downstream, which
suspended PerServiceCallChatHistoryPersistingChatClient at its `yield
return`, so its finally block never ran and the in-flight
FunctionResultContent for the just-completed tool call was not persisted
to chat history. The next turn then loaded a session that contained a
FunctionCallContent with no matching FunctionResultContent and the model
returned HTTP 400 `No tool output found for function call`.
Fixes:
* ChatClientAgent.RunStreamingAsync: wrap the iteration in
try/finally that disposes the inner enumerator. Disposal now cascades
through the pipeline and PerService's finally runs on early exit.
* PerServiceCallChatHistoryPersistingChatClient: in the streaming path,
snapshot input messages with `messages.ToList()` (the caller, FICC,
reuses a single mutable buffer across iterations and may mutate it
before our finally / error path persists), wrap GetAsyncEnumerator,
the first MoveNextAsync, and in-loop MoveNextAsync in try/catch each
calling PersistInputOnErrorAsync + NotifyProvidersOfFailureAsync, and
add a finally that calls PersistInputOnErrorAsync when the loop did
not exit normally so per-iteration FRCs are persisted on early
disposal as well as on errors.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Add tests for PerService streaming error/dispose persistence paths
Adds five regression tests covering the new error-path persistence in
PerServiceCallChatHistoryPersistingChatClient.GetStreamingResponseInnerAsync:
- Persists input messages when GetStreamingResponseAsync throws synchronously.
- Persists input messages when the first MoveNextAsync throws.
- Persists input messages when a mid-stream MoveNextAsync throws.
- Persists input messages when the consumer abandons enumeration early
(the ToolApprovalAgent yield-break / disposal-cascade case).
- Throws and persists input when the stream emits an in-band ErrorContent.
All 66 tests in the class pass on net10.0 and net472.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Address PR feedback on PerService streaming error persistence
Two follow-ups from PR #5744 review:
1. Prevent duplicate persistence on the in-loop MoveNextAsync catch path.
The inner catch persists input messages, then rethrows, which propagates
through the surrounding try/finally where loopExitedNormally is still false,
causing the finally to persist again. Introduced an inputPersisted flag
that the inner catch sets after persisting; the finally now skips when
inputPersisted is true.
2. Use the caller's CancellationToken in the abnormal-exit finally instead
of CancellationToken.None, so cleanup remains responsive to cancellation.
Fall back to CancellationToken.None only when the caller's token is
already canceled (otherwise the persist call would observe the
cancellation, throw, and mask the original early-exit reason).
Tightened all five new streaming-error tests from Times.AtLeastOnce to
Times.Once on the input-persistence matcher to regression-guard against
duplicate persistence. All 66 tests in the class still pass (net10.0 + net472).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Scope PerService streaming changes to cooperative early-exit only
Per discussion on PR #5744, scope this PR back to fix only the original
ToolApprovalAgent dropped-FunctionResultContent bug and address the
enumerator-disposal review comment. Specifically:
- Remove input-message persistence from the GetAsyncEnumerator and
MoveNextAsync error paths. Routing failed service calls through the
success notification channel was breaking the provider contract; we
will instead rely on inner-agent retries for transient errors. Failure
paths still call NotifyProvidersOfFailureAsync as before.
- Remove the in-stream ErrorContent detection block (same rationale).
- Keep the try/finally that calls the (now narrower) early-exit input
notification on cooperative disposal (e.g. ToolApprovalAgent yield
break). A new serviceErrorOccurred flag ensures we do NOT renotify
on exception paths.
- Always DisposeAsync the underlying enumerator on every exit path,
addressing the copilot-reviewer comment about leaked HTTP/streams.
- Rename PersistInputOnErrorAsync -> NotifyProvidersOfEarlyExitInputAsync
to better reflect what it does and when it runs (rogerbarreto nit).
- Apply rogerbarreto nit on InMemoryChatHistoryProvider null-coalescing.
- Drop the four tests that covered the removed error-path behavior;
keep RunStreamingAsync_PersistsInputMessages_WhenConsumerAbandons
EnumerationAsync (regression guard for the cooperative-pause path).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: alliscode <bentho@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Hosted Agents - RAG Sample with Azure AI Search (#5693)
Adds a Hosted-AzureSearchRag sample plus a live Foundry.Hosting integration
test scenario backed by a real Azure AI Search index.
Sample (Hosted-AzureSearchRag): keyword-only Azure AI Search via
SearchClient adapter into TextSearchProvider, scope-aware
DevTemporaryTokenCredential consuming AZURE_BEARER_TOKEN_FOUNDRY +
AZURE_BEARER_TOKEN_SEARCH for local Docker, Dockerfile + contributor
Dockerfile mirroring Hosted-TextRag.
Integration test: AzureSearchRagHostedAgentFixture extends the PR #5598
HostedAgentFixture with the new azure-search-rag scenario branch in the
shared test container; AzureSearchRagHostedAgentTests asserts the model
returns canary tokens (TR-CANARY-7821, SHIP-CANARY-4493) that exist only
in the seeded documents - real proof the agent grounded its answer in
retrieved content rather than training data.
* Address PR 5701 Copilot review feedback
- Sample README: drop stale 'bootstraps the index on first run' line; index is pre-provisioned out of band
- Sample + TestContainer search adapters: propagate CancellationToken to await foreach via .WithCancellation()
Wesley pointed out (with a clean demo) that AsyncLocal<T> mutations made
inside an awaited async method do not leak back to the caller after the
method returns - the runtime restores the caller's view automatically.
ClientHeadersAgent.RunCoreAsync and RunCoreStreamingAsync are the only
callers of the scope, both are async methods awaited by their callers,
so the explicit using/Dispose pattern was doing work the runtime already
does for us.
* ClientHeadersScope collapsed to a single Current { get; set; } property
over an AsyncLocal<IReadOnlyDictionary<string,string>?>. Drops Push,
the Scope struct, and Dispose. XML doc explains the AsyncLocal natural-
restoration semantics so the design intent is self-documenting.
* ClientHeadersAgent uses a direct ClientHeadersScope.Current = snapshot
before delegating. Drops the local RunAsyncCoreAsync helper and the
snapshot-passed-as-parameter dance.
* Test 10 renamed to ClientHeadersScope_IsAsyncLocalIsolatedAndAutoRestoresAsync;
drops the LIFO claim, keeps the parallel-isolation assertion, and adds
a Wesley-style 'set inside async, caller sees null on return' assertion.
* Test 12 switches from using ClientHeadersScope.Push to direct
Current = ... with try/finally for test isolation.
Snapshot deep-copy in TrySnapshot stays - it defends against caller
mutating the source Dictionary mid-run, which is independent of the
AsyncLocal restoration mechanism.
* .NET: Add Hosted-Files sample + alpha AgentSessionFiles SDK companion + integration test
Closes#5691
- Hosted-Files server sample (mirrors python 06_files): 3 local tools reading
the per-session \C:\Users\rbarreto sandbox volume.
- SessionFilesClient REPL companion: code-first equivalent of
zd ai agent files upload using the alpha
Azure.AI.Projects.AgentSessionFiles SDK (upload/ls/download/rm + session
lifecycle with isolation key).
- session-files scenario added to the Foundry.Hosting.IntegrationTests
multi-scenario harness (PR #5598): SessionFilesHostedAgentFixture +
SessionFilesHostedAgentTests.UploadAndAgentReadsFileAsync, end-to-end
validating upload then agent-reads-file (agent_session_id pinned via
CreateResponseOptions.Patch). Bundled testdata is linked from the sample
so there is a single source of truth.
* .NET: Hosted-Files: REPL companion now demonstrates file-as-knowledge end-to-end
Adds an 'ask <prompt>' command to SessionFilesClient that pins
agent_session_id (via CreateResponseOptions.Patch) so the agent invoked from
the REPL reads files this REPL just uploaded. Surfaces the file content as
agent knowledge in the same in-process loop instead of telling the user to
shell out to azd ai agent invoke.
* .NET: Reshape Hosted-Files sample - bake files into image, SessionFilesClient becomes thin chat REPL
The previous SessionFilesClient leaned on the alpha AgentSessionFiles SDK
to upload files at runtime, which made it diverge from the canonical
Using-Samples shape (SimpleAgent / SimpleInvocationsAgent: tiny chat REPLs).
This change:
- Bakes the sample resources/ directory into the published output via a
Content Include in HostedFiles.csproj. Inside the container the files live
at /app/resources/. Two local function tools (ListFiles, ReadFile) surface
them to the model.
- Reshapes SessionFilesClient as a thin FoundryAgent chat REPL, identical
shape to SimpleAgent. AGENT_ENDPOINT + AGENT_NAME, that is it.
- Demo flow: user asks 'Give me the total revenue in the contoso file' and
the agent answers with the figure read from its bundled file. Validated
end-to-end locally against Hosted-Files on http://localhost:60419.
- Bypasses SampleEnvironment alias on optional env vars to avoid stdin
prompts when running unattended.
The Foundry.Hosting.IntegrationTests session-files scenario continues to
validate the alpha AgentSessionFiles SDK end-to-end (upload + agent reads
from session HOME) and is unchanged.
* .NET: Foundry.Hosting.IntegrationTests TestContainer - constrain session-files tools to $HOME
Addresses the path-traversal review comment on the session-files scenario:
ResolveSessionPath in TestContainer used to allow absolute paths and ..
traversals, which (when chained with indirect prompt injection in an
uploaded file) would let the model read or list arbitrary container files
via the ReadFile / ListFiles tools.
Mirrors the canonicalize + StartsWith(home) pattern from the framework's
own FileSystemAgentFileStore.ResolveSafePath: rejects rooted paths, calls
Path.GetFullPath, and verifies the result stays under $HOME, throwing
ArgumentException otherwise.
The Hosted-Files sample is already safe (uses Path.GetFileName which strips
any directory component) so no change there. The integration test continues
to upload and read 'contoso_q1_2026_report.txt', a single relative filename
which passes the new validation unchanged.
* .NET: SessionFilesHostedAgentTests - shrink to alpha SDK round-trip
The previous test attempted to pin agent_session_id into the /responses
payload via JsonPatch so the agent would read the file uploaded through
AgentSessionFiles. The Foundry alpha service now consistently rejects the
explicit-session-id pin with HTTP 400 conflict on /responses, regardless
of whether the session was pre-created via AgentAdministrationClient or
left to be auto-provisioned, so the agent leg of the test is no longer
reachable from the SDK surface.
Reshape the test to exercise what the alpha SDK actually guarantees:
create session, upload, list (assert presence + size), download (assert
deterministic token), delete (assert removed), cleanup. Everything stays
inside Azure.AI.Projects.Agents.AgentSessionFiles.
Verified live against tao-foundry-prj:
UploadListDownloadAndDeleteAsync passed in 30s.
Full Foundry.Hosting.IntegrationTests run: 25 total, 6 passed, 19
skipped (existing placeholders), 0 failed.
* .NET: SessionFilesHostedAgentTests - rewrite as upload-then-FoundryAgent.RunAsync e2e
Per review feedback the integration test must validate the hosted agent
itself: client uploads a file via the alpha AgentSessionFiles SDK, then
FoundryAgent.RunAsync invokes the deployed agent and the agent's
container-side ReadFile tool surfaces the uploaded file content into the
response.
Test flow:
1. agent.RunAsync(warmup) - platform provisions a per-session container.
2. AgentAdministrationClient.GetSessionsAsync(latest) - resolve the
just-provisioned agent_session_id.
3. AgentSessionFiles.UploadSessionFileAsync - upload contoso file to
that session, asserts BytesWritten + GetSessionFiles listing.
4. agent.RunAsync(real prompt, options=PreviousResponseId chain) -
chained to warmup so the platform routes back to the same container.
5. Assert response contains '1,482.6' (deterministic token from file).
6. Best-effort cleanup.
The test is annotated with [Fact(Skip=...)] right now: the Foundry alpha
service consistently returns HTTP 400 conflict on /responses requests
that link to a prior session via previous_response_id, conversation_id,
or agent_session_id pinning - verified across multiple retries with
multiple chaining strategies. Without that link we cannot route the
second invocation to the same container the file was uploaded to. When
the platform regression is resolved, removing the Skip will exercise
the full flow.
Full Foundry.Hosting.IntegrationTests run with this change: 25 total,
5 passed, 20 skipped (existing placeholders + this one), 0 failed.
* .NET: SessionFilesHostedAgentTests - end-to-end upload-then-FoundryAgent.RunAsync now passes
The blocker was a routing problem combined with a platform race:
1. Routing two /responses calls to the same per-session container.
- agent_session_id pin in body -> 400 (platform treats it as create)
- conversation_id created at project root -> 404 at agent endpoint
- previous_response_id chain -> different session
The working answer is to create the conversation on a per-agent
ProjectOpenAIClient (AgentName option, URL becomes
/agents/{name}/endpoint/protocols/openai/conversations) and pass that
conversation_id on both calls. Both then resolve to the SAME
x-agent-session-id (verified by capturing the response header).
2. Race after AgentSessionFiles upload. The upload mutates session/
conversation revision; a /responses call issued immediately after
400-conflicts with 'modified concurrently. Please retry.' Bounded
exponential retry handles it (5 attempts, 2*attempt seconds).
Test flow:
1. Create per-agent OpenAI client + ProjectConversationsClient + ProjectResponsesClient.
2. CreateProjectConversationAsync on the per-agent client.
3. Warm-up agent.RunAsync(prompt, ChatOptions { ConversationId = ... })
- captures x-agent-session-id from the response header via a custom pipeline policy.
4. AgentSessionFiles.UploadSessionFileAsync to that session id.
5. ProjectResponsesClient.CreateResponseAsync (raw, retry-on-conflict)
with the same conversation_id -> routes back to the same container.
6. Assert response contains '1,482.6' (deterministic token from file).
7. Cleanup: delete file, leave session for TTL.
Verified live against tao-foundry-prj:
UploadedFile_IsReadByHostedAgentAsync passed in 24.9s.
Full Foundry.Hosting.IntegrationTests run: 25 total, 6 passed, 19
skipped (existing placeholders), 0 failed.
* .NET: address Copilot PR review findings
- agent.manifest.yaml: description + tags now reflect bundled-files agent (image-baked /app/resources), not the obsolete session-sandbox tools the prior shape claimed.
- SessionFilesHostedAgentTests: wrap test body in try/finally to call DeleteConversationAsync on the conversation we created (matches HappyPathHostedAgentTests pattern; prevents conversation leakage across runs).
- ResponseHeaderCapturePolicy: drop unused LastRequestBody capture left over from diagnosis.
Test still passes live (40s).
* .NET: Hosted-Files: split into bundled vs session-file tool pairs
The previous Hosted-Files agent only exposed bundled (image-baked) file
knowledge. The platform also surfaces session-uploaded files at \C:\Users\rbarreto
inside the per-session container per container-image-spec.md line 172
(verified live by SessionFilesHostedAgentTests). The sample now teaches
both patterns.
Two distinct tool pairs, each scoped to its own root:
Bundled (image-baked): ListBundledFiles, ReadBundledFile
-> /app/resources/ (BUNDLED_FILES_DIR override)
Session-uploaded (\C:\Users\rbarreto): ListSessionFiles, ReadSessionFile
-> \C:\Users\rbarreto (default /home/session per container spec)
Security model -- distinct tools, distinct sandboxes:
- Tool input is a fileName, not a path. Schema-level: model cannot
request directories or traversals.
- Path.GetFileName(input) strips any directory components.
- Path.GetFullPath + StartsWith(root) check rejects anything outside
the tool's root, mirroring FileSystemAgentFileStore.ResolveSafePath.
- Read-only, non-recursive listing. No glob, no '..'.
- Failures non-revealing: 'File <name> not found in <scope>.'
The two roots are physically isolated (image-baked vs platform-mounted
per-session volume). A bundled-root tool can never reach a session file
and vice-versa, even if the implementation has a bug.
README updated to document both flows, the security pattern, and cite
the container-image-spec.md line 172 contract for \C:\Users\rbarreto. Live IT
SessionFilesHostedAgentTests.UploadedFile_IsReadByHostedAgentAsync
re-passed in 42s after the change (TestContainer is unchanged; the
sample-agent split does not affect the IT).
* .NET: Hosted-Files README - fix broken relative link to IT (4..5 dots)
* .NET: Foundry.Hosted IT - fix MSBuild parallel-output races
Two surgical changes inside the dotnet-foundry-hosted-it job:
1. Replace dotnet build <slnx> -f net10.0 with dotnet build <test.csproj>. The test csproj pins TargetFrameworks=net10.0 and its ProjectReference closure gives MSBuild a single-rooted graph, eliminating the duplicate inner-builds that race on bin/obj. Drops the two New-FilteredSolution.ps1 steps.
2. In it-build-image.ps1, drop the -UsePrebuiltProjectReferences switch and always pass --no-dependencies to dotnet publish. Publish now resolves TestContainer's framework refs by reading prebuilt DLLs and never re-touches them. Replaces the partial-mitigation in PR #5689 with a structural fix.
Local validation confirmed published Foundry.dll has identical mtime and bytes as the prebuild output.
* .NET: dotnet test - use --project flag for Microsoft Testing Platform
* Adding the ability to inject messages during the function call loop
* Split message injection functionality
* Remove interface, since it is not required not that we split the chat client.
* Address conversation id propogation
* Fix formatting issue
* .NET: Foundry agent-endpoint constructor uses ProjectOpenAIClient directly to fix hosted-agent URL routing
Fixes the experimental FoundryAgent(Uri agentEndpoint, AuthenticationTokenProvider, ...)
constructor so it actually works against Foundry hosted agents.
The previous implementation routed through AzureAIProjectChatClient, which
internally called aiProjectClient.GetProjectOpenAIClient().GetProjectResponsesClientForAgent(...).
For an agent-endpoint URL of the canonical shape
https://<host>/api/projects/<project>/agents/<agentName>/endpoint/protocols/openai
the chain produced
POST https://<host>/api/projects/<project>/openai/v1/responses
(project-level path, no /agents/ segment). The Foundry service rejects this with
HTTP 400 "Hosted agents can only be called through the agent endpoint:
.../agents/<agentName>/endpoint/protocols/openai/responses".
The constructor also extracted the agent name via
agentEndpoint.Segments[^1].TrimEnd('/'), which returns "openai" (the last segment),
not the agent name.
What changed
- Public ctor signature: clientOptions parameter type changed from
AIProjectClientOptions? to ProjectOpenAIClientOptions?. The constructor is
fundamentally building a ProjectOpenAIClient; accepting AIProjectClientOptions
was a leaky abstraction whose translation silently dropped any pipeline
policies the caller added via AddPolicy(...). With the direct type, caller
policies pass through to the per-agent traffic verbatim.
- Per-agent client construction: `new ProjectOpenAIClient(BearerTokenPolicy, ProjectOpenAIClientOptions)`
with Endpoint and AgentName set, then `GetProjectResponsesClient().AsIChatClient()`.
The SDK auto-appends ?api-version=v1 when AgentName is set.
- New private static ParseAgentEndpoint helper: single source of truth for both
agent-name extraction and project-root derivation. Tolerates trailing slash,
case variants on /agents/ and the suffix segment, strips query/fragment, and
throws ArgumentException with paramName=nameof(agentEndpoint) for malformed input.
- Project-level client (used by CreateConversationSessionAsync) is built fresh
from the derived project root with primitive properties copied
(RetryPolicy/NetworkTimeout/Transport/UserAgentApplicationId) plus MEAI UA.
- New GetService<ProjectOpenAIClient>() entry alongside the existing
GetService<AIProjectClient>() (the latter returns null in agent-endpoint mode
since no AIProjectClient is constructed on that path).
- Endpoint and AgentName on caller-supplied ProjectOpenAIClientOptions are
overridden by values derived from agentEndpoint.
Compatibility
- FoundryAgent is [Experimental(OPENAI001)]. No GA surface touched. The Foundry
project does not maintain PublicAPI.*.txt baselines so there is no shipped
baseline to update.
- The Microsoft.Agents.AI.Foundry csproj pins
Azure.AI.Projects to VersionOverride 2.1.0-beta.1 (matching what the IT and
hosting projects already use); the central pin in Directory.Packages.props
stays at 2.0.0.
- WireClientHeaders from PR #5652 is invoked on the agent-endpoint path so
per-call x-client-* headers behave identically across both ctors.
Tests
- 23 new unit tests in FoundryAgentTests.cs:
- 12 for the agent-endpoint constructor (URL routing for non-streaming and
streaming, conversations URL shape, MEAI UA stamping, caller-policy
passthrough on the per-agent pipeline, Endpoint/AgentName override
semantics, GetService matrix, ProjectOpenAIClient propagation,
UserAgentApplicationId propagation, null-arg validation, ID/Name slug)
- 9 for ParseAgentEndpoint (standard shape, trailing slash, casing,
sovereign-cloud host without /api/projects/ literal prefix, special chars
in agent name, query/fragment stripping, three negative cases)
- 2 null-arg tests for the public ctor
- All 250 Microsoft.Agents.AI.Foundry.UnitTests pass (was 221 baseline plus
29 from PR #5652 plus 23 new in this PR equals 273; pre-existing tests
collapsed by the rebase merge keep the total at 250).
- All 225 Microsoft.Agents.AI.Foundry.Hosting.UnitTests pass; no behavioral
change to the hosting layer.
- dotnet build clean across net8/9/10/netstandard2.0/net472 with
TreatWarningsAsErrors=true.
- dotnet format --verify-no-changes clean for the touched src and test projects.
* .NET: Bump central Azure.AI.Projects pin to 2.1.0-beta.1 and flip Microsoft.Agents.AI.Foundry to preview
Required to fix the NU1109 downgrade chain that broke CI on the agent-endpoint
constructor rewire (#5677). Microsoft.Agents.AI.Foundry now depends on
ProjectOpenAIClientOptions.AgentName and the (AuthenticationPolicy, options)
constructor that only exist in Azure.AI.Projects 2.1.0-beta.1.
Changes:
* Directory.Packages.props: Azure.AI.Projects 2.0.0 -> 2.1.0-beta.1.
* Microsoft.Agents.AI.Foundry.csproj: drop IsReleased=true so the package ships
as preview (matches the beta SDK we now depend on). Add a comment noting the
flip is temporary and should revert once Azure.AI.Projects ships a stable
2.1.0.
* Drop redundant VersionOverride="2.1.0-beta.1" from the 10 csprojs that had it
as a workaround; the central pin now suffices.
Verified:
* dotnet build agent-framework-dotnet.slnx --warnaserror clean across all TFMs.
* Microsoft.Agents.AI.Foundry.UnitTests 250/250 pass.
* Microsoft.Agents.AI.Foundry.Hosting.UnitTests 211/211 pass.
* dotnet format --verify-no-changes clean for the touched src and test projects.
<!-- Thank you for your contribution to the Agent Framework repo!
Please help reviewers and future users, providing the following information:
1. Why is this change required?
2. What problem does it solve?
3. What scenario does it contribute to?
4. If it fixes an open issue, please link to the issue here.
4. If it fixes an open issue, please link to the issue below.
-->
### Description
### Description & Review Guide
<!-- Describe your changes, the overall approach, the underlying design.
Highlight what you want the reviewers to focus on.
These notes will help understanding how your code works. Thanks! -->
- **What are the major changes?**
- **What is the impact of these changes?**
- **What do you want reviewers to focus on?**
<!-- NOTE FOR AUTOMATED/AI REVIEWERS: the "What do you want reviewers to focus on?"
item above is intended for human reviewers only. Automated/AI reviewers should
ignore it and review the entire change rather than narrowing scope to it. -->
### Related Issue
<!-- Which issue does this PR fix? Link it using a GitHub closing keyword so it is
closed automatically when this PR is merged, e.g. "Fixes #123" or "Closes #123".
PRs that are not linked to an issue may be closed, no matter how valid the change is.
Also check whether an open PR already exists for this issue; if so,
explain how this PR is different. -->
Fixes #
### Contribution Checklist
<!-- Before submitting this PR, please make sure: -->
- [ ] The code builds clean without any errors or warnings
- [ ] The PR follows the [Contribution Guidelines](https://github.com/microsoft/agent-framework/blob/main/CONTRIBUTING.md)
- [ ] All unit tests pass, and I have added new tests where possible
- [ ]**Is this a breaking change?** If yes, add "[BREAKING]" prefix to the title of the PR.
- [ ] The PR follows the [Contribution Guidelines](https://github.com/microsoft/agent-framework/blob/main/CONTRIBUTING.md)
- [ ] This PR is linked to an issue and there is no other open PR for this issue (see Related Issue above).
- [x]**This is not a breaking change.** If it _is_ a breaking change, add the `breaking change` label (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.
"Automated dependency bounds test mode failed before dependency upper-bound validation could run.",
"",
"The weekly dependency maintenance workflow kept only dev dependency updates for the generated PR, if any, and skipped dependency range updates for this run.",
"This automated update keeps Python dependency metadata coherent across the uv workspace. Python dependencies can be declared in multiple `pyproject.toml` files, but the workspace has one shared `python/uv.lock`, so dependency maintenance should update and validate them together instead of through per-manifest Dependabot PRs.",
"",
"### Description & Review Guide",
"",
"- **What are the major changes?** Refresh Python dev dependency pins, update package dependency ranges when the bounds tooling succeeds, and refresh `python/uv.lock`.",
"- **What is the impact of these changes?** Keeps the Python workspace dependency set current while producing at most one dependency PR for the week. If dependency range validation fails, this PR contains only the dev dependency updates that still pass final validation, and separate issues track failed range candidates.",
"- **What do you want reviewers to focus on?** Review the generated dependency metadata changes and any dependency-range updates for package-specific compatibility concerns.",
'<!-- NOTE FOR AUTOMATED/AI REVIEWERS: the "What do you want reviewers to focus on?"',
" item above is intended for human reviewers only. Automated/AI reviewers should",
" ignore it and review the entire change rather than narrowing scope to it. -->",
"",
"",
"### Related Issue",
"",
"No linked issue; this PR is generated by scheduled Python dependency maintenance.",
"",
"### Contribution Checklist",
"",
"- [x] The code builds clean without any errors or warnings",
"- [x] All unit tests pass, and I have added new tests where possible",
"- [x] The PR follows the [Contribution Guidelines](https://github.com/microsoft/agent-framework/blob/main/CONTRIBUTING.md)",
"- [ ] This PR is linked to an issue and there is no other open PR for this issue (see Related Issue above).",
'- [x] **This is not a breaking change.** If it _is_ a breaking change, add the `breaking change` label (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.',
].join("\n")
const prBodyFence = "```"
const command = [
"PR_BODY_FILE=\"$(mktemp)\"",
`cat > "$PR_BODY_FILE" <<'EOF'`,
prBody,
"EOF",
"gh pr create --repo microsoft/agent-framework --base main \\",
` --head ${owner}:${branch} \\`,
` --title "${prTitle}" \\`,
" --body-file \"$PR_BODY_FILE\"",
].join("\n")
const issueBody = [
"The Python dependency maintenance workflow generated and validated dependency updates, then pushed them to the automation branch.",
"",
`- Branch: \`${branch}\``,
`- Commit: \`${branchSha}\``,
`- Compare: ${compareUrl}`,
`- Workflow run: ${runUrl}`,
"",
"GitHub Actions is not permitted to create pull requests in this repository, so a maintainer needs to create the PR manually.",
# Probe the highest allowed dependency versions, then open issues/PRs from the passing updates.
name:Python - Dependency Range Validation
on:
workflow_dispatch:
permissions:
contents:write
issues:write
pull-requests:write
env:
UV_CACHE_DIR:/tmp/.uv-cache
jobs:
dependency-range-validation:
name:Dependency Range Validation
runs-on:ubuntu-latest
env:
# For now only run 3.13, if we do encounter situations where there are mismatches between packages and python versions (other then 3.10 and 3.14 which are known to not be able to install everything)
# then we will have to reevaluate.
UV_PYTHON:"3.13"
GH_TOKEN:${{ secrets.GITHUB_TOKEN }}
steps:
- uses:actions/checkout@v6
with:
fetch-depth:0
- name:Set up python and install the project
uses:./.github/actions/python-setup
with:
python-version:${{ env.UV_PYTHON }}
os:${{ runner.os }}
env:
UV_CACHE_DIR:/tmp/.uv-cache
- name:Run dependency range validation
id:validate_ranges
# Keep workflow running so we can still publish diagnostics from this run.
continue-on-error:true
run:uv run poe validate-dependency-bounds-project --mode upper --package "*"
working-directory:./python
- name:Upload dependency range report
# Always publish the report so failures are inspectable even when validation fails.
Microsoft Agent Framework (MAF) is an open, multi-language framework for building **production-grade AI agents and multi-agent workflows** in **.NET and Python**.
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.
Observation: No explicit middleware/filters; modularity allows composable units but no dedicated interception hooks or callbacks for custom reading/modification mid-execution.
For more details, see the official documentation: [Atomic Agents Docs](https://brainblend-ai.github.io/atomic-agents/). No specific code examples available for interception.
No specific code examples available for interception.
@@ -43,6 +43,11 @@ FIDES (Flow Integrity Deterministic Enforcement System) is a label-based securit
3.**Variable Indirection** — `ContentVariableStore` and `VariableReferenceContent` for physical isolation of untrusted content from the LLM context.
4.**Quarantined Execution** — `quarantined_llm` and `inspect_variable` tools for isolated processing of untrusted data with audit logging.
In addition, remote MCP integrations are secured through two mechanisms:
- **Hint-based tool auto-labeling**: MCP `ToolAnnotations` (`readOnlyHint`, `openWorldHint`, etc.) are mapped to FIDES tool properties (`source_integrity`, `accepts_untrusted`, `max_allowed_confidentiality`).
- **Server `_meta.ifc` result labels**: MCP result metadata is parsed into per-item `security_label` values, so provider-supplied IFC labels are enforced by middleware.
### Consequences
- Good, because it provides deterministic security guarantees about what untrusted content can influence.
- Attaches labels via `additional_properties` (no schema changes).
- Leverages `SerializationMixin` for label persistence.
- Integrates MCP hint/result metadata through `additional_properties` keys (`max_allowed_confidentiality`, `source_integrity`, `__mcp_result_meta__`) without transport-specific policy code in core middleware.
### MCP-Specific Security Notes
-`SecureMCPToolProxy` applies `apply_mcp_security_labels(...)` automatically when connecting an MCP tool or URL.
- For servers like the GitHub MCP server (with `X-MCP-Features: ifc_labels`), `_meta.ifc` labels are considered authoritative for per-result label assignment.
- Tools that are not explicitly `readOnlyHint=True` are treated as potential sinks and default to `max_allowed_confidentiality=PUBLIC` to prevent exfiltration.
status: superseded by [ADR-0030](0030-hosted-platform-context-agentserver-2.0.md)
contact: rogerbarreto
date: 2026-06-29
deciders: rogerbarreto
consulted: []
informed: []
---
# Hosted session identity context for Foundry Hosting
> **Superseded by [ADR-0030](0030-hosted-platform-context-agentserver-2.0.md).** `Azure.AI.AgentServer.*` 2.0.0 (responses protocol `2.0.0`) replaced `ResponseContext.Isolation` (`UserIsolationKey` / `ChatIsolationKey`, headers `x-agent-user-isolation-key` / `x-agent-chat-isolation-key`) with `ResponseContext.PlatformContext` (`UserIdKey` / `CallId`, headers `x-agent-user-id` / `x-agent-foundry-call-id`). The chat isolation key was removed and `HostedSessionContext` is now user-only. This ADR is retained as the historical record of the original design.
## Context and Problem Statement
Server-hosted Foundry agents need a way to scope per-user state (most notably `FoundryMemoryProvider` memories) by the end user that initiated the request. The Foundry platform already injects `x-agent-user-isolation-key` and `x-agent-chat-isolation-key` headers on every Responses request, but the agent-framework hosting layer did not surface those values to `AIContextProvider` instances. The provider's `stateInitializer` only received an `AgentSession?` with no identity attached, so per-user scoping was impossible without out-of-band plumbing.
## Decision Drivers
- Memory and any future user-private context must be partitioned per end user without per-sample boilerplate.
- The identity must be **read-only** from the perspective of `AIContextProvider`s, so a buggy or hostile provider cannot escalate or leak across users.
- The persisted session must validate against the live request on every resume to defend against session-id leak and in-process tampering.
- The change must work for every existing hosted-agent type (`ChatClientAgent`, `FoundryAgent`, future ones) without per-type refactoring of cast-heavy code paths in `Microsoft.Agents.AI`.
- Local Docker debugging must remain possible when the platform headers are absent.
## Considered Options
1.**`HostedSessionContext` stored in `AgentSessionStateBag`, exposed via a public read accessor and an `internal` setter.** Hosting writes once on session creation and validates on every resume.
2.**Specialised `HostedAgentSession : AgentSession` wrapper** that carries `UserId`/`ChatId` properties, with `GetService<ChatClientAgentSession>()` as the unwrap escape hatch.
3.**New property on `AgentSession` base class** (`HostedSessionContext? HostedContext { get; internal set; }`).
4.**AsyncLocal middleware** that reads the headers and stuffs them into a per-request `AsyncLocal<HostedSessionContext>` consumed by the provider.
For the source of identity:
- A. The platform-injected `IsolationContext` exposed by `ResponseContext.Isolation` (typed `UserIsolationKey`/`ChatIsolationKey`).
- B. The OpenAI Responses spec's top-level `request.User` field.
- C. A custom HTTP header `x-client-user`.
## Decision Outcome
**Option 1** was chosen for the storage shape, sourced from **Option A** (`ResponseContext.Isolation`).
Rationale:
- **Wrapper rejected (Option 2).** `ChatClientAgentSession` is `sealed` and `ChatClientAgent` rejects any other session type via direct `is not ChatClientAgentSession` checks at multiple call sites. Wrapping would force non-trivial refactors across `Microsoft.Agents.AI` and a corresponding repeat for every other agent type.
- **Base-class property rejected (Option 3).** Leaks "hosted" semantics into the universal `AgentSession` abstraction used by Durable, A2A, and CopilotStudio agents that have no notion of a hosted user.
- **AsyncLocal rejected (Option 4).** Surfaces the concept only locally, requires every consumer to re-implement the bridge, and cannot be enforced as read-only.
- **`request.User` rejected (Option B).** Set by the caller, not the platform. Forging it client-side trivially defeats per-user partitioning.
- **`x-client-user` rejected (Option C).** Non-standard, requires custom HTTP plumbing, and duplicates the platform-provided isolation contract.
Implementation summary in `Microsoft.Agents.AI.Foundry.Hosting`:
| Type | Visibility | Purpose |
|---|---|---|
| `HostedSessionContext` | public sealed | Captures `UserId` and `ChatId` (both required, non-whitespace). |
| `HostedSessionContextExtensions.GetHostedContext` | public | Read accessor for `AIContextProvider`s. |
| `HostedSessionContextExtensions.SetHostedContext` | internal | Writer reserved for the hosting assembly. Backed by `AgentSessionStateBag` under a well-known key for serialisation. |
| `PlatformHostedSessionIsolationKeyProvider` | internal sealed | Default implementation. Maps `context.Isolation.UserIsolationKey` and `context.Isolation.ChatIsolationKey`. Returns `null` when either is absent. |
Behaviour added to `AgentFrameworkResponseHandler.CreateAsync`:
1. Resolve `HostedSessionIsolationKeyProvider` from DI; fall back to `PlatformHostedSessionIsolationKeyProvider`.
2. Call `GetKeysAsync(context, request, cancellationToken)`. A `null` result throws `InvalidOperationException` (becomes 500). A null/whitespace `UserId` or `ChatId` is rejected by `HostedSessionContext`'s constructor.
3. Branch on the **session's existing context**, not on whether a `conversation_id` was supplied:
- **No session (`session is null`):** nothing to stamp; skip.
- **Session present but un-stamped (`GetHostedContext() is null`):** treat as fresh. This covers both newly-created sessions and pre-existing sessions whose `conversation_id` was provisioned externally (e.g. via `conversations.CreateProjectConversationAsync()`) before the first hosted-agent request. Stamp the resolved identity now.
- **Session present with stamped context:** strict resume. The persisted `UserId` and `ChatId` must equal the resolved values exactly. Mismatch throws `ResponsesApiException` with status 403 and body `Hosted session identity context mismatch`.
## Consequences
Positive:
- Per-user memory partitioning works out of the box for any agent that consumes a `Microsoft.Agents.AI.Foundry.FoundryMemoryProvider` configured to read `session.GetHostedContext().UserId`.
- Cross-user session-id leak and in-process tampering of the persisted identity both surface as a 403 with a deliberately uninformative body.
- The identity is opaque to the framework, matching the platform's semantics. The framework never inspects user identity; the `IsolationContext` keys are pre-partitioned per agent.
Negative:
- Every existing hosted sample fails locally without a `HostedSessionIsolationKeyProvider` registered, because the platform headers are absent outside the platform. Mitigated by shipping `Hosted_Shared_Contributor_Setup` with `DevTemporaryLocalSessionIsolationKeyProvider` and `AddDevTemporaryLocalContributorSetup`, and migrating all 9 existing responses samples.
- An attacker who can plant an un-stamped session under a victim's `conversation_id`*before* the victim's first hosted-agent request would be stamped with the attacker's identity on that first request. This is not a regression vs. behaviour without this contract, and is mitigated in practice because the `conversation_id` namespace is allocated by the platform per project. Once a session is stamped, the strict equality check fully defends the resume path.
## Out of scope
- Per-request `User` field on `CreateResponse` is intentionally not consumed; only the platform `IsolationContext` headers carry trustworthy identity.
- Generic (non-Foundry) hosting layers can re-define an equivalent type if needed; nothing in this ADR is moved into `Microsoft.Agents.AI.Hosting` because `Microsoft.Agents.AI.Foundry.Hosting` does not depend on it.
- HMAC tamper signatures over the persisted context are not implemented; comparison against `ResponseContext.Isolation` on every request is sufficient because the platform sets those headers at the trust boundary.
# 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.
## More Information
- Follow-up linking and multicast ADR: [ADR-0028](0028-hosting-linking-multicast-enhancements.md)
[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 document explores design options for two SEP-2640 features. The decisions are not yet finalized.
- **Part 1: MCP Resource Template Skills** - skills described by a URI template with variables that must be resolved before loading.
- **Part 2: Direct Skill References** - reading `skill://` URIs referenced directly (e.g., in server instructions) without being listed in the index.
## Part 1: MCP Resource Template Skills
### Context and Problem Statement
The `AgentMcpSkillsSource` currently only supports `skill-md` type entries from `skill://index.json` (support for `archive` type is planned). The SEP-2640 specification also defines `mcp-resource-template` entries: **parameterized skill namespaces** described by a URI template with variables (e.g., `{product}`) that resolve to concrete `SKILL.md` URIs. Rather than materializing every skill in the index, the template's variables must be resolved before a skill can be loaded.
| `url` | Concrete URI to `SKILL.md` | URI template with variables |
| `description` | Describes the skill | Describes the addressable skill space |
### Use Cases
Template skills address two scenarios where listing concrete skills is impractical:
- **Large skill catalogs** - too many skills to enumerate every entry in the index.
- **Dynamically generated skills** - skill content generated on the fly from parameters, so the set of valid skills is not known at index-creation time.
### How Template Skills Are Consumed
Per SEP-2640, the consumption flow relies on the MCP `completion/complete` method:
1.**Server registers a resource template** - The MCP server registers the same `url` value (e.g., `skill://docs/{product}/SKILL.md`) as an MCP [resource template](https://modelcontextprotocol.io/specification/2025-11-25/server/resources#resource-templates), wiring template variables to the [completion API](https://modelcontextprotocol.io/specification/2025-11-25/server/utilities/completion).
2.**Host reads `skill://index.json`** - Discovers the template entry with `type: "mcp-resource-template"`.
3.**Host surfaces template in UI** - Presents the template as an interactive discovery point where the user fills in variables.
4.**Host calls `completion/complete`** - For each template variable (e.g., `{product}`), the host calls the MCP completion API to get possible values from the server:
5. **User selects a value** - The user picks a value (e.g., `"billing"`) from the list.
6. **Host resolves the URI** - The template `skill://docs/{product}/SKILL.md` becomes the concrete URI `skill://docs/billing/SKILL.md`.
7. **Host reads the resolved skill** - Calls `resources/read` with the concrete URI and proceeds as with any `skill-md` skill.
### Potential Implementation Options
### Option 1: Callback on `AgentMcpSkillsSource` for Variable Value Selection
Add a callback to `AgentMcpSkillsSource` (or its options) that is invoked for each `mcp-resource-template` entry to let the caller select variable values.
return (selected, IncludeSkill: selected is not null);
};
})
.Build();
```
**Pros:**
- Simple implementation
- Easy to understand and use
**Cons:**
- Cannot be used in server-side scenarios where there is no interactive user at skill-discovery time
- Does not integrate with the agent's conversational flow
---
### Option 2: Integrate into Agent Conversation via `ChatClientAgent` Decorator
Model the template variable resolution as a request/response interaction within the agent's conversational loop.
**Flow:**
1. A `DelegatingAIAgent` decorator (e.g., `McpTemplateSkillResolutionAgent`) intercepts `RunAsync`/`RunStreamingAsync` calls and checks whether the inner agent has an `AgentSkillsProvider` with an `AgentMcpSkillsSource` containing unresolved template entries. The check is performed via `GetService<AgentMcpSkillsSource>()` on the `AgentSkillsProvider`, which delegates to a `GetService` method on the `AgentSkillsSource` base class.
2. The decorator calls an internal member on `AgentMcpSkillsSource` to get the list of `mcp-resource-template` entries from the index. The `AgentMcpSkillsSource` needs to be extended with an internal member that exposes unresolved template entries separately from concrete skills.
3. For each template entry, the decorator calls an internal member on `AgentMcpSkillsSource` to retrieve possible values for the template's variables via the MCP `completion/complete` API.
4. For each variable needing resolution, the decorator returns an `McpResourceTemplateValueRequestContent` (inherits from MEAI's `InputRequestContent`) in the agent response - bypassing the call to the inner agent. The content carries the template description, variable name, and possible values.
5. The user app receives the response, identifies the `McpResourceTemplateValueRequestContent` content type, and displays UI to the user showing the variable name and possible values, or forwards it further downstream if the user app is a service.
6. The user selects a value, and the user app calls the agent again with a corresponding `McpResourceTemplateValueResponseContent` (inherits from MEAI's `InputResponseContent`) containing the selected value. The `RequestId` property (inherited from the base classes) correlates the response with the original request.
7. The decorator identifies the response content and provides the resolved values to `AgentMcpSkillsSource` so it can use them when constructing concrete skills.
8. Having resolved all template variables, the decorator calls `RunAsync`/`RunStreamingAsync` on the inner agent.
9. The inner agent invokes the `AgentSkillsProvider`, which calls `AgentMcpSkillsSource.GetSkillsAsync()`. The source now has all resolved variable values and constructs concrete `AgentMcpSkill` instances from the resolved URIs, so it can provide the skill content if requested by the model.
**API sketch:**
```csharp
// New content types inheriting from MEAI's InputRequestContent/InputResponseContent:
public sealed class McpResourceTemplateValueRequestContent : InputRequestContent
{
public string TemplateDescription { get; }
public string VariableName { get; }
public IReadOnlyList<string> PossibleValues { get; }
public string TemplateUrl { get; }
}
public sealed class McpResourceTemplateValueResponseContent : InputResponseContent
{
public string SelectedValue { get; }
public string TemplateUrl { get; }
}
// Decorator usage:
var provider = new AgentSkillsProviderBuilder()
.UseMcpSkills(mcpClient)
.Build();
AIAgent agent = new ChatClientAgent(chatClient, new ChatClientAgentOptions
{
AIContextProviders = [provider],
});
agent = new McpTemplateSkillResolutionAgent(agent);
```
**Pros:**
- Works in server-side scenarios
- Fits the existing `DelegatingAIAgent` decorator pattern
- Can be composed with other decorators (tool approval, etc.)
**Cons:**
- Complex implementation
- Requires user app awareness of the new content types
- Users need to know that an additional decorator is required for handling MCP template skills, in addition to registering the MCP skills source
- Resolved template variable values must be persisted across conversation turns so the decorator does not re-prompt on subsequent agent runs within the same session
**Note:** This writeup is high-level and may miss details that could change the design. A POC would be needed to validate the approach.
### Open Questions
1. **Completion API limit** - The MCP completion API returns at most 100 values per request and provides no offset/cursor mechanism for enumeration. If a variable has more than 100 possible values, it's unclear how to retrieve the rest - the API only supports prefix-based filtering (typeahead), not bulk pagination.
2. **Multi-variable templates** - A template like `skill://{org}/{product}/SKILL.md` has multiple variables. Should they be resolved sequentially (org first, then product - since product values may depend on org) or presented together?
3. **Caching** - Should resolved template values be saved in the `AgentSession` so the user isn't re-prompted on every agent run? How should they be persisted between sessions?
---
## Part 2: Direct Skill References
This part covers how to let the model read `skill://` URIs referenced directly (e.g., in an MCP server's `instructions`, in a resource, or in another skill's content) without being listed in `skill://index.json`.
### How MCP Skills and Relative Links Work Today
The `AgentMcpSkillsSource` discovers skills by reading the well-known `skill://index.json` resource from the MCP server:
"description": "Convert between world currencies using live rates.",
"url": "skill://currency-converter/SKILL.md"
}
]
}
```
For each `skill-md` entry it creates an `AgentMcpSkill` instance - frontmatter (name/description) comes straight from the entry. The `AgentSkillsProvider` lists the discovered skills in the model's context (name + description):
```xml
<available_skills>
<skill>
<name>unit-converter</name>
<description>Convert between common units.</description>
</skill>
<skill>
<name>currency-converter</name>
<description>Convert between world currencies using live rates.</description>
</skill>
</available_skills>
```
It also provides functions to the model so it can load a skill and access its resources:
```csharp
// Loads the full content of a specific skill.
load_skill(string skillName)
// Reads a resource associated with a skill (references, assets, dynamic data).
The model calls `load_skill("unit-converter")` and receives the skill content:
```markdown
---
name: unit-converter
description: Convert between common units.
---
## Usage
For the full conversion table, see references/units-table.md.
```
The skill body references `references/units-table.md` by relative path. The model calls `read_skill_resource("unit-converter", "references/units-table.md")` and receives the resource content:
```markdown
# Unit Conversion Table
| From | To | Factor |
| miles | km | 1.60934 |
| kg | lbs | 2.20462 |
```
### Direct Reference Examples
A `skill://` URI can appear in any of these locations:
**Server instructions** - the MCP server advertises a skill the model should load:
```text
Follow our coding standards. Load skill://code-standards/SKILL.md for details.
```
**A skill body** - a skill's `SKILL.md` links to a sibling resource:
```markdown
---
name: code-standards
description: Coding standards and conventions.
---
## Naming
Follow the naming rules in skill://code-standards/references/naming.md.
```
**A resource** - the linked resource holds the actual content:
```markdown
# Naming Rules
- Use PascalCase for public members and type names.
- Use camelCase for locals and parameters.
- Prefix interfaces with `I` (e.g. `ISkillReader`).
- Suffix async methods with `Async`.
For examples, see skill://code-standards/references/naming-examples.md.
```
How can the model access content by direct reference?
### Function for Reading Direct Skill References
### Option 1: Extend existing `load_skill` and `read_skill_resource` functions
```csharp
// Added optional 'origin' and a direct skill:// URI is passed in 'skillName'.
| `skill://` link (skill) | `read_skill_uri(uri: "skill://commit-guidelines/SKILL.md", origin:"DirectRefServer")` |
| `skill://` link (resource) | `read_skill_uri(uri: "skill://commit-guidelines/examples/COMMIT_EXAMPLES.md", origin: "DirectRefServer")` |
**Pros:**
- Purely additive - no changes to existing functions needed; `read_skill_uri` can be deferred and added later when direct `skill://` reference support is needed.
- Granular approval: each function can have its own approval gate (like the existing `ScriptApproval` for `run_skill_script`), making per-operation approval for skill loading, resource reading, and direct URI access straightforward to add.
- Both `uri` and `origin` are required - no silent misuse through optional parameters.
- Clean split: `load_skill`/`read_skill_resource` for named skills, `read_skill_uri` for `skill://` links - no parameter ambiguity.
**Cons:**
- Three read functions (`load_skill`, `read_skill_resource`, `read_skill_uri`), not counting `run_skill_script`: larger tool surface than a single-function design.
### Option 3: Collapse `load_skill` and `read_skill_resource` into a single `read_resource` function
```csharp
// Single entrypoint for all skill content. 'uri' is required; 'origin' is optional.
read_resource(string uri, string? origin = null)
```
- `uri` - what to read: a skill name, a relative resource path, or a `skill://` link.
- `origin` - determines how `uri` is interpreted:
- **omitted** → load skill by name (`uri` is the skill name).
- **skill name** → read a relative resource (`uri` is the path within that skill).
- **server name** → read content by the `skill://` link (`uri` is handled by the source identified by the `[Origin: X]` marker).
Dispatch is ordered: null `origin` routes to Case 1; if `origin` names a known skill, routes to Case 2; otherwise tries to find an `ISkillUriReader` whose `CanRead` returns true for `origin` (Case 3).
| `skill://` link (skill) | `read_resource(uri: "skill://commit-guidelines/SKILL.md", origin: "DirectRefServer")` |
| `skill://` link (resource) | `read_resource(uri: "skill://commit-guidelines/examples/COMMIT_EXAMPLES.md", origin: "DirectRefServer")` |
**Pros:**
- Minimal tool surface: one read function instead of two or three (not counting `run_skill_script`) reduces token usage and gives the model fewer choices.
**Cons:**
- No per-operation approval: all cases (skill loading, resource reading, direct URI access) share one function, so approval cannot be scoped to individual operations.
- Unreliable on gpt-4.1-mini: omits `origin` when reading `skill://` links, passes skill name as `origin` when loading a plain skill (should be omitted), and hallucinates resource names (e.g. `API_SPECIFICATION.md`) that do not exist.
---
### Origin Marker
A `skill://` URI does not carry an origin, but the model needs to provide one when reading it. The `origin` is what routes the read call to the source that can handle the URI - the provider uses it to pick the matching source. Since the URI itself carries no such hint, the MCP source injects an `[Origin: ...]` marker wherever a `skill://` URI appears, so the model can read it back and pass it as the `origin` argument.
The marker is only added when the content actually contains `skill://` references. If a piece of content (server instructions, a skill body, or a resource) has no `skill://` URIs, there is nothing for the model to read back, so no marker is injected.
Into **server instructions**, which may mention `skill://` URIs directly:
```
[Origin: code-standards-server]
Follow our coding standards. Load skill://code-standards/SKILL.md for details.
```
Into **skill bodies**, since a `SKILL.md` may reference other `skill://` URIs (a resource file or a related skill):
```
[Origin: code-standards-server]
# Code Standards
For naming conventions, load skill://code-standards/references/naming.md.
```
Into **skill resources**, since a resource may itself reference further `skill://` URIs:
```
[Origin: code-standards-server]
# Naming Rules
- Use PascalCase for public members and type names.
- Use camelCase for locals and parameters.
For examples, see skill://code-standards/references/naming-examples.md.
```
---
### Read-by-URI Capability: Interface vs Base Class Virtual Methods
Now let's look at how an `AgentSkillsSource` can opt in to reading `skill://` URIs and signal that capability to the provider.
### Option 1: New `ISkillUriReader` interface
```csharp
public interface ISkillUriReader
{
// Returns true if this reader can handle the given skill:// URI from the given origin.
bool CanRead(string uri, string origin);
// Reads and returns the content for the given skill:// URI.
The provider may treat a source implementing `ISkillUriReader` as the signal to advertise `read_skill_uri`: if at least one registered source implements the interface, the function is exposed to the model; otherwise it is not.
### Option 2 (Proposed): Virtual methods on `AgentSkillsSource` base class
```csharp
public abstract class AgentSkillsSource
{
// New members for reading by URI.
// Whether this source can read by URI; drives whether read_skill_uri is advertised. Off by default.
public virtual bool SupportsReadByUri => false;
// Returns true if this source can handle the given skill:// URI from the given origin.
public virtual bool CanReadByUri(string uri, string origin) => false;
// Reads and returns the content for the given skill:// URI.
// Reads content by skill:// URI from the MCP server.
public override Task<string?> ReadByUriAsync(string uri, string origin, CancellationToken cancellationToken)
=> /* resolve uri via the MCP server identified by origin */;
```
All sources inherit the methods, so there is no type signal - `SupportsReadByUri` fills that role. The function is advertised when any registered source returns `true`.
### Comparison
| Aspect | Option 1: Interface | Option 2: Base class virtual methods |
| Discovery | Service locator | Direct call on source |
| Advertising signal | Interface implementation | `SupportsReadByUri` flag |
| Adding new members | Breaking change | Non-breaking |
| Complexity | Higher | Lower |
---
### Include MCP Server Instructions Into Agent Instructions
MCP server instructions may contain the `skill://` references the model needs, so we want to surface them in the agent's instructions. But they can also carry system prompts or behavioral directives irrelevant to the agent, polluting context - so inclusion is **opt-in** via the `IncludeServerInstructions` option:
```csharp
public sealed class AgentMcpSkillsSourceOptions
{
// When true, the MCP server's instructions are injected into the agent instructions. Off by default.
public bool IncludeServerInstructions { get; set; }
Following direct `skill://` references is **disabled by default** and activated via an option. When enabled, the provider advertises the read function to the model, and the source injects the `[Origin: ...]` marker into all content provided by the MCP server that contains `skill://` references. When disabled, no function is advertised and no marker is injected.
### Template Variable Resolution: Callback vs Decorator (Part 1)
**Postponed.** Deferring this decision until:
- We have a concrete list of scenarios that require template variable resolution.
- The skills-over-MCP spec is released (it is still a draft, so the design may change).
- There is a strong signal of demand from users or the ecosystem.
### Function for Reading Direct Skill References (Part 2)
**Postponed.** Leaning toward **Option 2 - dedicated `read_skill_uri` function alongside existing ones** (purely additive, and each function can have its own approval gate for granular per-operation approval), but deferring the decision until:
- The skills-over-MCP spec is released (it is still a draft, so the design may change).
- There is a strong signal of demand from users or the ecosystem.
### Read-by-URI Capability: Interface vs Base Class (Part 2)
**Postponed.** Leaning toward **Option 2 - virtual methods on `AgentSkillsSource`** (non-breaking, lower complexity, and a natural fit with the existing base class hierarchy), but deferring the decision until:
- The skills-over-MCP spec is released (it is still a draft, so the design may change).
- There is a strong signal of demand from users or the ecosystem.
The method naming (`SupportsReadByUri`, `CanReadByUri`, `ReadByUriAsync`) should also be abstracted a little more before adoption, so the same members can be reused when a similar direct-reference concept is needed for other skill types (e.g. file skills).
[ADR-0026](0026-hosted-session-identity-context.md) sourced the hosted-agent end-user identity from `ResponseContext.Isolation` (an `IsolationContext` typed `UserIsolationKey` / `ChatIsolationKey`), injected by the platform as the `x-agent-user-isolation-key` and `x-agent-chat-isolation-key` headers.
`Azure.AI.AgentServer.*` 2.0.0 (responses protocol `2.0.0`) removes that surface. `ResponseContext.Isolation` is gone; the platform now exposes `ResponseContext.PlatformContext` (a `PlatformContext` typed `UserIdKey` and `CallId`), populated from the `x-agent-user-id` and `x-agent-foundry-call-id` headers. The chat isolation key no longer exists, and a new per-request **call id** is introduced that first-party Foundry services (the toolbox proxy in particular) require on outbound calls to resolve the server-side-stored caller context. The hosting layer in `Microsoft.Agents.AI.Foundry.Hosting` had to migrate to this contract without changing the public shape that samples and providers depend on.
## Decision Drivers
- Track the breaking `Azure.AI.AgentServer.*` 2.0.0 surface (`PlatformContext` replacing `Isolation`) while keeping the same per-user partitioning guarantees from ADR-0026.
- Keep the change **internal**: existing hosted samples and `AIContextProvider`s must not need code changes. `session.GetHostedContext().UserId`, `HostedSessionIsolationKeyProvider`, and `AddFoundryResponses` stay source-compatible.
- Forward the new per-request call id verbatim on outbound calls to Foundry first-party services so per-user toolbox OAuth consent and other server-side caller-context lookups keep working.
- Remain resilient on protocol `1.0.0`: when only the legacy headers are present, `UserIdKey` still resolves and `CallId` is simply absent.
- Preserve the strict-resume tamper defense from ADR-0026 with identity now reduced to user only.
## Considered Options
For the identity source:
1.**Map `ResponseContext.PlatformContext.UserIdKey`** into the existing `HostedSessionContext` (user only), keeping ADR-0026's storage shape and read accessor.
2. Keep a `ChatId` slot on `HostedSessionContext` for backward source-compatibility, populated from `CallId` or left null.
For the call id propagation:
A. **A request-scoped ambient (`HostedCallContext`, an `AsyncLocal<string?>`)** set by the handler and re-applied before each egress point, read by the outbound delegating handler.
B. Thread the call id through every method signature down to the toolbox bearer handler.
For session keying (previously implied by the conversation/chat pairing):
I. **`HostedConversationKey`** resolving a stable partition from `conversation_id ?? partition(previous_response_id) ?? partition(responseId)`.
II. Continue keying on the container session id (`FOUNDRY_AGENT_SESSION_ID`).
## Decision Outcome
Chosen: **Option 1** for identity, **Option A** for call id, **Option I** for session keying.
Rationale:
- **`ChatId` dropped (Option 2 rejected).** The platform no longer supplies a chat key; carrying a synthetic one would invent identity the trust boundary does not provide. `HostedSessionContext` becomes user-only (`HostedSessionContext(string userId)` / `UserId`), and the strict-resume check validates `UserId` alone. The corresponding `HostedFoundryMemoryProviderScopes` values `PerChat` and `PerUserAndChat` are removed; `PerUser` is retained.
- **Ambient call id (Option B rejected).** Writing `HostedCallContext.CallId` inside the streaming `async IAsyncEnumerable` iterator is reverted across each `yield`, so a single up-front assignment is lost before the toolbox/MCP egress runs. The handler therefore captures `context.PlatformContext?.CallId` once and **re-applies it immediately before each egress point**; `FoundryToolboxBearerTokenHandler` forwards it as `x-agent-foundry-call-id`. The ambient is request-scoped and never leaks into the caller's execution context (guarded by a unit test).
- **`HostedConversationKey` (Option II rejected).** One container serves many conversations for its lifetime, so the container session id cannot key per-conversation state. The partition key is derived from the conversation/`previous_response_id`/minted response id instead.
Implementation summary in `Microsoft.Agents.AI.Foundry.Hosting`:
| Type | Visibility | Change vs ADR-0026 |
|---|---|---|
| `HostedSessionContext` | public sealed | Now user-only (`UserId`); `ChatId` removed. |
Package manifests bump the responses container protocol to `2.0.0` (invocations stays `1.0.0`).
## Consequences
Positive:
- Per-user memory partitioning and the strict-resume tamper defense from ADR-0026 are preserved with no public API churn for samples or providers.
- Per-user toolbox OAuth consent and other server-side caller-context lookups keep working because the per-request call id is forwarded on egress.
- Works unchanged on protocol `1.0.0` (no call id) and `2.0.0`.
Negative:
-`HostedSessionContext.ChatId` and the `PerChat` / `PerUserAndChat` memory scopes are removed; any out-of-tree consumer that referenced them must move to user-scoped partitioning.
- The call id must be re-applied before every egress point because of the async-iterator `AsyncLocal` revert; a missed re-apply silently drops the header. This is covered by unit tests.
## Out of scope
- HMAC tamper signatures over the persisted context remain unimplemented; equality comparison against `ResponseContext.PlatformContext` on every request is sufficient because the platform sets the header at the trust boundary.
- The per-request `User` field on `CreateResponse` is still intentionally not consumed.
FIDES now secures remote MCP integration end-to-end:
- **Tool labels from hints**: `apply_mcp_security_labels(...)` maps MCP hints (`readOnlyHint`, `openWorldHint`) to FIDES tool properties.
- **Safe sink defaults**: tools not explicitly marked `readOnlyHint=True` are treated as potential sinks and receive `max_allowed_confidentiality=public`.
- **Result labels from metadata**: MCP result `_meta` is propagated via `__mcp_result_meta__`; `_meta.ifc` is parsed into `security_label` per result item.
- **`SecureMCPToolProxy` convenience**: wraps MCP tools/URLs and applies this labeling automatically on connect.
This behavior is used with the GitHub MCP server when `X-MCP-Features: ifc_labels` is passed, which causes the server to return IFC labels in `_meta` (for example `{"ifc": {"integrity": "untrusted", "confidentiality": "public"}}`).
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.
description: How to use the verify-samples tool to run, verify, and manage sample definitions in the Agent Framework repository. Use this when adding, updating, or running sample verification.
<PackageVersion Include="MessagePack" Version="3.1.7" /> <!-- Transitive dependency of Aspire pinned to newer version due to vulnerability in 2.5.192 -->
ExpectedOutputDescription=["The output should show a computer automation session processing simulated browser screenshots with iteration steps and a final response describing search results."],
ExpectedOutputDescription=["The output should show an agent using the Microsoft Learn MCP server to search for documentation and provide a response."],
@@ -1151,6 +1157,25 @@ internal static class AgentsSamples
SkipReason="Runs as an MCP stdio server that does not exit on its own.",
"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.",
Inputs=["My laptop won't start","The laptop is now working, thank you!"],
InputDelayMs=5000,
ExpectedOutputDescription=["The output should show a customer support workflow processing a laptop issue, with agent responses providing troubleshooting or support."],
},
@@ -405,26 +405,16 @@ internal static class WorkflowSamples
ExpectedOutputDescription=["The output should show a declarative workflow executing generated code, processing a math question and producing a result."],
ExpectedOutputDescription=["The output should show a workflow calling function tools (e.g. a menu plugin) to answer a question about restaurant specials."],
ExpectedOutputDescription=["The output should show a workflow invoking a function tool (e.g. a menu plugin) to answer a question about the soup of the day."],
Inputs=["How do I use Azure OpenAI with my data?"],
InputDelayMs=3000,
ExpectedOutputDescription=["The output should show a workflow using Foundry Toolbox MCP tools to search Microsoft Learn documentation and web search to provide a summary of results."],
Inputs=["Search for .NET tutorials on Microsoft Learn"],
InputDelayMs=3000,
ExpectedOutputDescription=["The output should show a workflow using MCP tools to search Microsoft Learn documentation and provide a summary of results."],
@@ -493,8 +485,8 @@ internal static class WorkflowSamples
ExpectedOutputDescription=["The output should show a student-teacher workflow where a student asks a math question and a teacher provides the answer."],
@@ -515,8 +507,8 @@ internal static class WorkflowSamples
ExpectedOutputDescription=["The output should show a workflow using an MCP tool with approval to search Microsoft Learn, followed by an exit from the input loop."],
@@ -35,7 +35,7 @@ A basic AG-UI server and client that demonstrate the foundational concepts.
A basic AG-UI server that hosts an AI agent accessible via HTTP. Demonstrates:
- Creating an ASP.NET Core web application
- Setting up an AG-UI server endpoint with `MapAGUI`
- Setting up an AG-UI server endpoint with `MapAGUIServer`
- Creating an AI agent from an Azure OpenAI chat client
- Streaming responses via Server-Sent Events (SSE)
@@ -204,7 +204,7 @@ dotnet run
### Server-Side
1. Client sends HTTP POST request with messages
2. ASP.NET Core endpoint receives the request via `MapAGUI`
2. ASP.NET Core endpoint receives the request via `MapAGUIServer`
3. Agent processes messages using Agent Framework
4. Responses are streamed back as Server-Sent Events (SSE)
@@ -214,16 +214,22 @@ dotnet run
2. Server responds with SSE stream
3. Client parses events into `AgentResponseUpdate` objects
4. Updates are displayed based on content type
5.`ConversationId` maintains conversation context
5.The client sends the full message history each turn (the stateless AG-UI client does not rely on a server-assigned `ConversationId`)
### Protocol Features
- **HTTP POST** for requests
- **Server-Sent Events (SSE)** for streaming responses
- **JSON** for event serialization
- **Thread IDs** (as `ConversationId`) for conversation context
- **Thread IDs** (read from the `RUN_STARTED` event's raw representation) for conversation context. `AGUIChatClient` is stateless and intentionally does not surface a `ConversationId`.
- **Run IDs** (as `ResponseId`) for tracking individual executions
## Security considerations
`ConversationId` keeps request/response continuity. It is not proof that the caller owns that conversation. In multi-user deployments, authenticate each AG-UI request and authorize conversation access using your application's real boundary, such as the authenticated user, tenant, or workspace.
If your ASP.NET Core host shares session storage across users, pair `MapAGUI` with an isolation strategy such as `UseClaimsBasedSessionIsolation(...)` so the storage key includes a principal-specific dimension instead of relying on the conversation identifier alone.
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.