Compare commits

...

531 Commits

Author SHA1 Message Date
Javier Calvarro Nelson e556c81924 Fix AGUI overload tests after rebase
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-08-18 18:49:00 +02:00
Javier Calvarro Nelson 18d05e8824 Add MapAGUI hosting overloads with IHostedAgentBuilder and agent name support
This adds the same hosting patterns from A2A and OpenAI to AGUI:
- MapAGUI(IHostedAgentBuilder) and MapAGUI(IHostedAgentBuilder, string? path)
- MapAGUI(string agentName) and MapAGUI(string agentName, string? path)
- MapAGUI(AIAgent) and MapAGUI(AIAgent, string? path)
- ValidateAgentName for URL-safe validation
- Updated namespace to Microsoft.AspNetCore.Builder
- Renamed class to MicrosoftAgentAIHostingAGUIEndpointRouteBuilderExtensions
- Added comprehensive unit tests
2026-08-18 18:35:45 +02:00
SergeyMenshykh 4ce2804db0 .NET: Fix release build analyzer failures (#7721)
Guard the hosted storage error log before evaluating the agent name and update the SDK to the servicing release containing the net9 ILLink analyzer fix.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: e3a6cce8-e1e8-4cf2-9cf4-3c0c8f3ed6d8
2026-08-18 13:17:22 +00:00
SergeyMenshykh 1b45c15749 .NET: Update version for 1.18.0 release (#7713)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 0acc90aa-5690-41e6-b546-16083fc1c8b5
2026-08-18 10:01:07 +00:00
badhope 00d7102c54 Python: fix(workflows): preserve all trace contexts in FanInEdgeRunner aggregation (#7557)
* fix(workflows): preserve all trace contexts in FanInEdgeRunner aggregation

FanInEdgeRunner collected trace contexts and source span IDs using the
singular backward-compat properties (msg.trace_context / msg.source_span_id),
which return only the first element of the plural lists. When a message
arriving at a fan-in already carries multiple trace contexts (e.g. from
a prior fan-in aggregation), all but the first were silently dropped.

Iterate over the plural fields (trace_contexts / source_span_ids) and
extend the aggregated lists so every trace context and source span ID
from every source message is preserved. This keeps distributed tracing
links intact for nested fan-in topologies.

Added test_fan_in_preserves_multiple_trace_contexts_per_message that
sends a message with two trace contexts through a fan-in and asserts
all three contexts (2 + 1) reach the target executor.

* fix: address Copilot review comments on trace context aggregation

1. Pair trace_contexts and source_span_ids per-message (via zip) instead
   of flattening independently. This prevents misalignment when a message
   has mismatched counts — orphans are dropped per-message rather than
   shifting all subsequent pairs out of alignment.

2. Remove TraceCapturingAggregator's override of Executor.execute()
   (documented as "do not override"). Capture trace data from the
   WorkflowContext passed to the handler instead.

---------

Co-authored-by: weed33834 <weed33834@users.noreply.github.com>
Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
2026-08-18 04:26:04 +00:00
Evan Mattson d80c340a06 Clarify function-loop spec update guidance (#7706) 2026-08-18 02:11:10 +00:00
Evan Mattson af4347a61d Python: Restrict workflow type deserialization (#7500)
Resolve request-info type names only from exact caller-provided mappings or already-loaded module namespaces. Remove payload-selected imports and add focused regression coverage for both request and response type fields.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: a53fe20b-c3f0-4583-badc-d5deac7c1049
2026-08-18 02:08:21 +00:00
dependabot[bot] a445e4815d Bump ty from 0.0.64 to 0.0.70 in /python (#7644)
Bumps [ty](https://github.com/astral-sh/ty) from 0.0.64 to 0.0.70.
- [Release notes](https://github.com/astral-sh/ty/releases)
- [Changelog](https://github.com/astral-sh/ty/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ty/compare/0.0.64...0.0.70)

---
updated-dependencies:
- dependency-name: ty
  dependency-version: 0.0.69
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-17 23:30:52 +00:00
dependabot[bot] 8b8fbbba03 Bump flit from 3.12.0 to 4.0.2 in /python (#7645)
Bumps [flit](https://github.com/pypa/flit) from 3.12.0 to 4.0.2.
- [Changelog](https://github.com/pypa/flit/blob/main/doc/history.rst)
- [Commits](https://github.com/pypa/flit/compare/3.12.0...4.0.2)

---
updated-dependencies:
- dependency-name: flit
  dependency-version: 4.0.2
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-17 23:30:33 +00:00
Ruiming Zhao 925d722acf Python: clarify skill script argument guidance (#7695)
* Python: clarify skill script argument guidance

* test: harden skill argument guidance coverage
2026-08-17 21:38:58 +00:00
Peter Ibekwe 6001c12cd3 .NET: Fix declarative workflows deep research sample (#7674)
* Fix declarative workflows deep research sample

* Address PR comments
2026-08-17 19:50:16 +00:00
Tao Chen 6a3633e54a Python: Add a global workflow checkpoint type registry (#7636)
* Add a glocal checkpoint type registry

* Update samples

* Revert uv.lock

* Address comments

* Revert uv.lock

* Revert uv.lock
2026-08-17 18:33:01 +00:00
LeoZhaoo 648a31ade6 Python: Surface A2A preview consent URLs (#7606)
* fix(foundry-hosting): surface A2A consent URLs

* Use non-hashing membership

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Tao Chen <williamchan444307762@hotmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Tao Chen <taochen@microsoft.com>
2026-08-17 18:31:02 +00:00
Ilia Sokolov c6584ffaad .NET: Add Cosmos chat history retrieval API (#7412)
* .NET: Add Cosmos chat history retrieval API

* Clarify Cosmos message ordering semantics
2026-08-17 17:45:02 +00:00
Roger Barreto 74808cb6c7 .NET: Add Foundry hosted session and user identity pass-through (#7648)
* .NET: Add Foundry session and user identity pass-through

Let user agents pin hosted agent_session_id on AgentSession and
send x-ms-user-identity per call for Foundry hosted agents.

* .NET: Add live ITs for Foundry session and user identity

Cover service-managed and admin-pinned hosted sandboxes, sticky
hosted session id, and per-call x-ms-user-identity isolation with
separate AgentSessions sharing one sandbox. Echo container avoids
model quota for identity assertions.

* .NET: Reject Foundry hosted session switch when sticky

Persist sticky id in finally, clone run options before factory wrap,
validate whitespace pin on CreateHostedSessionAsync, and throw on
unexpected hosted session id change in the response. Docs: distinct
AgentSessions per user identity may share one sandbox.

* .NET: Clear nested user identity and preserve run options

Always assign UserIdentityScope including null so nested runs do not
inherit a parent identity. When upgrading plain AgentRunOptions, keep
background, format, and additional properties on the specialized clone.

* .NET: Clarify previous_response_id user binding in docs

Align WithUserIdentity guidance with Foundry Learn multiplex docs:
response chains are bound to the creating user even inside a shared
hosted sandbox.

* refactor(foundry): clarify hosted agent APIs
2026-08-17 17:28:31 +00:00
Roger Barreto 11592495db docs: fix Agent Lightning installation link (#7693) 2026-08-17 16:49:12 +00:00
ump45nose 047ec7eaff .NET: Allow agents to opt into concurrent tool invocation (#7650)
* .NET: allow agents to opt into concurrent tool invocation

* .NET: address concurrent invocation review feedback
2026-08-17 11:15:27 +00:00
Chinedum Echeta 9c3a1a4af7 Python: Enhance _OutputItemTracker to prevent duplicate function call streaming (#7486)
* Python: Enhance _OutputItemTracker to prevent duplicate function call streaming

* Handle empty function call metadata arguments

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 9b232bff-6a8c-4b02-addd-89de57a82f6a

* Python: Refactor _OutputItemTracker to manage outstanding function calls and update tests for call ID reuse

---------

Copilot-Session: 9b232bff-6a8c-4b02-addd-89de57a82f6a
2026-08-16 16:22:30 +00:00
Atharva Vichare 228754d7fa Python: Fix AG-UI url source dropping attachments when the URL is in source.value (#7655)
The ag-ui-protocol `InputContentUrlSource` carries the URL in `source.value`,
but `_extract_multimodal_source_fields` only read `source.url`/`source.uri`
for url-typed sources, so attachments sent in the spec shape were dropped
during the AG-UI to MAF conversion. The base64 branch already read
`source.value` correctly.

Read `source.value` first, keeping `url`/`uri` as fallbacks for the non-spec
shape. Adds tests for both.

Fixes #7653
2026-08-16 16:12:17 +00:00
Chinedum Echeta 8461667fe4 fix: deduplicate streamed DevUI tool calls (#7652)
Refs #7651

🐛 - Generated by Copilot
2026-08-16 16:11:28 +00:00
Roger Barreto 12621e0a74 .NET: Fix IDE0039 by using local functions in samples (#7666)
Replace Func lambda assignments with local functions so
dotnet format --verify-no-changes passes on the agent and RAG samples.
2026-08-14 15:32:54 +00:00
Tao Chen e1e005f226 Enforce code owner (#7660)
* Draft: Enforce code owner

* Apply new assignments after feedback

* Address comments
2026-08-14 15:16:53 +00:00
westey e289320027 Python: Add approval storage and improve truth checks (#7631)
* Add approval storage and improve truth checks

* Address PR comments

* Update spec

* Revert changes to agui since it is already handled in another pr

* Add missed change
2026-08-14 09:52:47 +00:00
Evan Mattson ae7fa3389c Python: Bump Python package versions for 1.14.0 release (#7661)
* Bump Python package versions for 1.14.0 release

Bump the CHANGELOG-selected packages for the 1.14.0 release: minor versions for root/core, AG-UI, Foundry, OpenAI, and orchestrations due to additive public APIs; patch versions for declarative and GitHub Copilot fixes; and Pacific-date prerelease stamps only for changed alpha/beta packages. No beta cohort bump was applied. Core dependency floors follow the strict policy and remain unchanged because no dependent package requires a new 1.14 API. Release validation also identified and corrected missing AG-UI and Copilot Studio runtime dependencies and aligned GitHub Copilot metadata with its Python 3.11 SDK requirement. Lab is intentionally skipped because its changes are development-only, and the moved Azure Functions and Durable Task packages are documented but no longer versioned here.

* Raise AG-UI core dependency floor
2026-08-14 11:06:35 +09:00
Evan Mattson 4aa737eee5 Python: [BREAKING] Require building functional workflow instances (#7521)
* Harden functional workflow continuation authority

Use a versioned opaque single-use token on WorkflowRunResult, validate it before request correlation, consume it immediately before replayed user code, and rotate it on each pause. Carry the same explicit authority through streaming and non-streaming FunctionalWorkflowAgent responses.

Files changed: functional workflow/runtime result APIs, functional HITL regression tests, core agent guidance, and the functional HITL sample.

Next iteration: enforce pending-state overlap and token-authorized abandonment, then document and test checkpoint authorization boundaries.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Enforce one pending functional continuation

Reject fresh messages and checkpoint restores while an in-memory continuation is pending. Add token-authorized abandonment on FunctionalWorkflow and FunctionalWorkflowAgent, and clear retained replay state atomically when authority is consumed while preserving the active message for token rotation and checkpoints.

Files changed: functional workflow runtime and agent adapter, functional lifecycle regression tests, and core workflow guidance.

Next iteration: preserve and document authorized checkpoint continuation boundaries.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Preserve authorized functional checkpoint continuation

Treat checkpoint restore as a host- and storage-authorized path independent of process-local continuation tokens, and issue fresh authority whenever restored execution pauses again. Cover default and per-run storage, deterministic and custom request IDs, token rotation, and checkpoint-plus-response restore.

Files changed: functional workflow and checkpoint interface guidance, functional checkpoint lifecycle tests, the functional HITL sample, and core workflow guidance.

Next iteration: run the final repository-wide Python validation gates.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Validate Python continuation hardening

Run the complete Python workspace checks, aggregate coverage suite, repository hooks, and core package build from the final combined worktree. Keep the validation iteration code-neutral because all gates pass without corrective changes.

Files changed: none; this commit records the final validation gate.

Blockers: none. Next iteration: no remaining AFK tasks.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Handle functional checkpoint continuation failures

Publish retained continuation state only after checkpoint persistence succeeds, and cover reuse after a transient save failure.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: a8f47743-1cdc-4924-8e1b-667d0d790b78

* Address functional continuation review findings

Add owner recovery for lost tokens, harden malformed token validation, preserve consistent failure surfaces, and keep agent pending state aligned with resumable workflow state.

Document process-local single-use continuation semantics and extend regression coverage across direct, streaming, checkpoint, and agent paths.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: a8f47743-1cdc-4924-8e1b-667d0d790b78

* Handle functional continuation cancellation

Release the workflow run guard when cancellation interrupts resumed user code while keeping the single-use continuation token consumed.

Replace sample assertions with explicit runtime checks and add cancellation regression coverage.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: a8f47743-1cdc-4924-8e1b-667d0d790b78

* Simplify functional workflow instance isolation

Remove continuation-token handling and align functional workflows with the graph workflow ownership model: one stateful instance per logical caller or session.

Add create_instance() for independent callers, document the ownership contract, and cover pending-state isolation between instances.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: a8f47743-1cdc-4924-8e1b-667d0d790b78

* Scope functional workflow checkpoint storage

Do not inherit checkpoint storage when creating an independent workflow instance. Allow hosts to provide an explicitly caller-scoped storage adapter and document that shared checkpoint access requires host authorization and tenant isolation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: a8f47743-1cdc-4924-8e1b-667d0d790b78

* Require building functional workflow instances

Make @workflow return a stateless FunctionalWorkflowDefinition and require build() before run() or as_agent(). This aligns functional workflows with the graph definition/build lifecycle and prevents module-level decorated definitions from retaining caller state.

Move checkpoint configuration to build(), export the definition type, migrate samples, and cover isolated built instances.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: a8f47743-1cdc-4924-8e1b-667d0d790b78

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a8f47743-1cdc-4924-8e1b-667d0d790b78
2026-08-14 00:31:52 +00:00
Evan Mattson 8c4da3c3b9 Python: Harden AG-UI approval lifecycle and resume semantics (#7594)
* Route local approvals through lifecycle owner

Key decisions:
- Add an internal typed approval lifecycle with pending, claimed, executing, and settled states.
- Keep authorization separate from execution; only LocalPendingToolTransitionOwner invokes approved local calls.
- Register server-owned occurrences before canonical ResumeDecision claims and retain one replayable result under the original call identity.

Files changed:
- packages/ag-ui/agent_framework_ag_ui/_approval_lifecycle.py
- packages/ag-ui/agent_framework_ag_ui/_approval_state.py
- packages/ag-ui/agent_framework_ag_ui/_agent_run.py
- packages/ag-ui/tests/ag_ui/test_approval_lifecycle.py
- packages/ag-ui/tests/ag_ui/test_approval_result_event.py
- packages/ag-ui/tests/ag_ui/test_agent_wrapper_comprehensive.py

Verification:
- 952 AG-UI tests passed.
- Focused lifecycle/public tracer passed with warnings treated as errors.
- Ruff format/check and AG-UI Pyright passed.
- git diff --check passed.

Notes for next iteration:
- The function-calling-loop scenario mapping is inaccessible under the organization content-exclusion policy and could not be updated.
- The workspace Poe package fan-out is blocked by the pre-existing missing packages/durabletask/pyproject.toml; equivalent package-local checks were run.

* Make approval batches occurrence-safe

Key decisions:
- Give each local approval a scoped logical occurrence identity and share one occurrence across trusted thread aliases.
- Validate complete Resume Decision batches before applying claims, then account for accepted, rejected, and cancelled occurrences independently.
- Preserve sibling authority and original result identity across failures, mixed decisions, and reused raw call IDs.

Files changed:
- packages/ag-ui/agent_framework_ag_ui/_approval_lifecycle.py
- packages/ag-ui/agent_framework_ag_ui/_approval_state.py
- packages/ag-ui/agent_framework_ag_ui/_agent_run.py
- packages/ag-ui/tests/ag_ui/test_approval_lifecycle.py
- packages/ag-ui/tests/ag_ui/test_agent_wrapper_comprehensive.py

Verification:
- 959 AG-UI tests passed with 90% lifecycle branch coverage.
- 30 focused lifecycle/public tracer tests passed with warnings treated as errors.
- Ruff format/check and AG-UI Pyright passed.
- git diff --check passed.

Notes for next iteration:
- The function-calling-loop scenario mapping remains inaccessible under the organization content-exclusion policy.
- Workspace typing fan-out remains blocked by the pre-existing missing packages/durabletask/pyproject.toml; package-local Pyright passed, while package-local MyPy retains three unrelated baseline errors.

* Make approval resume retries idempotent

Key decisions:
- Retain terminal decisions and outcomes by scoped occurrence so identical accepted and rejected retries reproject results without granting execution authority again.
- Reject conflicting names, arguments, decisions, wrong-scope lookups, and expired authority before an execution intent can reach the local transition owner.
- Keep protocol normalization in the runner while using server-owned lifecycle context to canonicalize retries and preserve existing AG-UI wire aliases.

Files changed:
- packages/ag-ui/agent_framework_ag_ui/_approval_lifecycle.py
- packages/ag-ui/agent_framework_ag_ui/_agent_run.py
- packages/ag-ui/tests/ag_ui/test_approval_lifecycle.py
- packages/ag-ui/tests/ag_ui/test_agent_wrapper_comprehensive.py
- packages/ag-ui/tests/ag_ui/test_endpoint.py

Verification:
- 965 AG-UI tests passed with 90% approval lifecycle coverage.
- 18 focused lifecycle, hostile-resume, wrong-thread, and endpoint retry tests passed with runtime and deprecation warnings treated as errors.
- Ruff format/check and AG-UI package-local Pyright passed.
- git diff --check passed.

Notes for next iteration:
- Terminal retention is process-local and unbounded until the later bounded-retention issue adds its explicit policy.
- The function-calling-loop scenario mapping remains inaccessible under the organization content-exclusion policy.

* Separate approval execution ownership

Key decisions:
- Carry explicit local, hosted, deferred in-run, or unavailable ownership on every approval occurrence and authorized intent.
- Keep lifecycle authorization separate from execution; local calls execute only through the local adapter while hosted and setup-injected decisions forward through owner-specific adapters.
- Leave declaration-only calls pending when no transition owner can act, and settle forwarded outcomes against the original occurrence without local fallback.

Files changed:
- packages/ag-ui/agent_framework_ag_ui/_approval_lifecycle.py
- packages/ag-ui/agent_framework_ag_ui/_approval_state.py
- packages/ag-ui/agent_framework_ag_ui/_agent_run.py
- packages/ag-ui/tests/ag_ui/test_approval_lifecycle.py
- packages/ag-ui/tests/ag_ui/test_endpoint.py

Verification:
- 967 AG-UI tests passed with 92% package coverage and 89% approval lifecycle coverage.
- 94 focused lifecycle, hosted, deferred-owner, hostile-resume, and approval tests passed.
- Ruff format/check and package-local Pyright passed.
- git diff --check passed.

Notes for next iteration:
- Executing-without-outcome recovery remains for the indeterminate execution-window issue.
- The function-calling-loop scenario mapping remains inaccessible under the organization content-exclusion policy.
- Workspace Poe fan-out remains blocked by the pre-existing missing packages/durabletask/pyproject.toml; equivalent package-local checks passed.

* Represent approval execution uncertainty

Key decisions:
- Distinguish reserved claims from execution windows that may have started an external side effect.
- Recover non-idempotent execution failures as indeterminate and reject identical retries without another invocation.
- Permit claim release only under an explicit safe policy and execution retry only with a predeclared idempotency key shared by local and forwarded owners.

Files changed:
- packages/ag-ui/agent_framework_ag_ui/_approval_lifecycle.py
- packages/ag-ui/tests/ag_ui/test_approval_lifecycle.py
- packages/ag-ui/tests/ag_ui/test_endpoint.py

Verification:
- 972 AG-UI tests passed with 92% line coverage and 89% package branch coverage.
- 23 focused lifecycle, duplicate-resume, hosted-owner, and public settlement-window tests passed.
- Package-local Ruff and Pyright passed; git diff --check passed.

Notes for next iteration:
- The function-calling-loop scenario mapping remains inaccessible under the organization content-exclusion policy.
- Workspace Poe fan-out remains blocked by the pre-existing missing packages/durabletask/pyproject.toml; equivalent package-local checks passed.

* Reconcile approval snapshots with lifecycle state

Key decisions:
- Keep Approval State authoritative and emit typed snapshot reconciliation keyed by logical occurrence identity.
- Retire settled, rejected, cancelled, expired, indeterminate, and missing controls while preserving nonterminal authority.
- Reconcile stale snapshots before hydration or resume, and retain lifecycle deduplication when snapshot saves fail.

Files changed:
- packages/ag-ui/agent_framework_ag_ui/_approval_lifecycle.py
- packages/ag-ui/agent_framework_ag_ui/_agent_run.py
- packages/ag-ui/agent_framework_ag_ui/_snapshot_session.py
- packages/ag-ui/tests/ag_ui/test_approval_lifecycle.py
- packages/ag-ui/tests/ag_ui/test_endpoint.py

Verification:
- 975 AG-UI tests passed with 92% package coverage and 89% approval lifecycle coverage.
- Package-local Ruff and Pyright passed; git diff --check passed.

Notes for next iteration:
- The function-calling-loop scenario mapping remains inaccessible under the organization content-exclusion policy.
- Workspace Poe fan-out remains blocked by the pre-existing missing packages/durabletask/pyproject.toml; equivalent package-local checks passed.

* Bound process-local approval lifecycle state

Key decisions:
- Protect pending, claimed, executing, and indeterminate occurrences from eviction while retaining terminal outcomes for a configurable 15-minute process-local deduplication window.
- Serialize complete approval batches by logical occurrence locks so aliases share atomic decisions and independent batches can progress concurrently.
- Fail capacity, claim, and settlement conflicts explicitly, and emit redacted structured lifecycle telemetry without tool names, arguments, or approval payloads.
- Remove legacy LRU eviction paths so active Approval State and middleware state are never silently discarded.

Files changed:
- packages/ag-ui/agent_framework_ag_ui/_approval_lifecycle.py
- packages/ag-ui/agent_framework_ag_ui/_approval_state.py
- packages/ag-ui/agent_framework_ag_ui/_agent_run.py
- packages/ag-ui/tests/ag_ui/test_approval_lifecycle.py
- packages/ag-ui/tests/ag_ui/test_approval_state.py

Verification:
- 982 AG-UI tests passed with 92% package coverage and 91% approval lifecycle coverage.
- 34 focused lifecycle and storage tests passed with RuntimeWarning and DeprecationWarning treated as errors.
- Package-local Ruff and Pyright passed; git diff --check passed.

Notes for next iteration:
- The function-calling-loop scenario mapping remains inaccessible under the organization content-exclusion policy.
- Workspace Poe fan-out remains blocked by the pre-existing missing packages/durabletask/pyproject.toml; equivalent package-local checks passed.

* Complete approval lifecycle cutover

Key decisions:
- Make ApprovalLifecycle the sole owner of trusted aliases, occurrence metadata, authority transitions, and retained outcomes.
- Remove the parallel mutable pending-approval registry and route local, hosted, deferred, cancellation, replay, and snapshot reconciliation through lifecycle occurrences.
- Encapsulate middleware Approval State behind copy-isolated store methods while keeping AG-UI protocol normalization and event projection in the runner.

Files changed:
- packages/ag-ui/AGENTS.md
- packages/ag-ui/agent_framework_ag_ui/_agent.py
- packages/ag-ui/agent_framework_ag_ui/_agent_run.py
- packages/ag-ui/agent_framework_ag_ui/_approval_lifecycle.py
- packages/ag-ui/agent_framework_ag_ui/_approval_state.py
- packages/ag-ui/tests/ag_ui/test_agent_wrapper_comprehensive.py
- packages/ag-ui/tests/ag_ui/test_approval_lifecycle.py
- packages/ag-ui/tests/ag_ui/test_approval_result_event.py
- packages/ag-ui/tests/ag_ui/test_approval_state.py
- packages/ag-ui/tests/ag_ui/test_endpoint.py
- packages/ag-ui/tests/ag_ui/test_run.py

Verification:
- 964 package-local AG-UI tests passed with 92% coverage and 90% approval lifecycle coverage.
- 85 warning-strict focused approval tests passed.
- Package-local Ruff and Pyright passed; git diff --check passed.

Notes for next iteration:
- The function-calling-loop scenario mapping remains inaccessible under the organization content-exclusion policy.
- Workspace Poe fan-out remains blocked by the pre-existing missing packages/durabletask/pyproject.toml; equivalent package-local checks passed.

* Align AG-UI approval resumes with protocol

* Align workflow approvals with AG-UI resumes

* Address AG-UI approval review findings

* fix AG-UI test typing checks

* fix AG-UI approval retention and cancellation retries
2026-08-14 00:28:14 +00:00
Evan Mattson 5fafa18569 Python: track agent-hooks feature usage (#7558) 2026-08-14 00:06:33 +00:00
Tao Chen ee27065359 Python: Update agentserver to x.1.0b1 (#7621)
* Update agentserver to 2.1.0

* Update agentserver responses and invocations to x.1.0b1

* Pass platform context to state store provider

* Pass user id

* Correct requirements.txt

* Fix unit tests

* Fix unit tests
2026-08-14 00:02:17 +00:00
Atharva Vichare 9645d33cde Python: Fix Cosmos memory provider calling renamed add_cosmos toolkit API (#7635)
* Python: Fix Cosmos memory provider calling renamed add_cosmos toolkit API

The Agent Memory Toolkit renamed AsyncCosmosMemoryClient.add_cosmos to
upsert_memory with an identical signature. The provider declares
azure-cosmos-agent-memory>=0.2.0b3 with no upper bound, so a resolved
install can expose either name. after_run swallows write errors and only
logs a warning, so on a post-rename toolkit the agent turn still looks
successful while long-term memory silently stops receiving turns.

Resolve the write method once per after_run, preferring upsert_memory and
falling back to add_cosmos, so both ends of the declared range keep working.
Same treatment for the emulator test's direct seed call.

Fixes #7633

* Ponytail comment erased

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Clarify TODO comment regarding memory method rename

Updated TODO comment to include author and clarify context , to resolve linting error

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-08-13 21:25:38 +00:00
pratik wayase 9a06fa3f42 Python: fix(python): add release_session API to prevent BackgroundAgentsProvider memory leaks (#7450)
* fix: add release_session API to prevent BackgroundAgentsProvider memory leaks

* fix: address Copilot review comments on release_session

* fix(harness): make background agent session release race-safe and bounded

* fix (harness): address release_session and review feedback
2026-08-13 19:35:36 +00:00
westey 6d25fb1e9c Remove clear and package source mapping from nuget.config to allow user level config inheritance (#7646) 2026-08-13 16:09:41 +00:00
westey e926ad2859 Python: fix streaming transcript duplication with message injection and per-service-call persistence (#7605)
* Fix ordering issue when streaming with content injection and per-service-call persistence

* Update spec

* Address PR comment

* revert uv.lock changes
2026-08-13 15:04:58 +00:00
Giles Odigwe 7cfa905486 Python: scope under-specified approve-for-session permission decisions (#7607)
* Python: scope under-specified approve-for-session permission decisions

PermissionDecisionApproveForSession carries an optional `approval` (tool
prompts) and an optional `domain` (URL prompts), so it can be constructed
with neither. A bare PermissionDecisionApproveForSession() serializes to
{"kind": "approve-for-session"}, which the Copilot CLI cannot interpret: it
dereferences the absent approval and crashes the CLI process with "Cannot
read properties of undefined (reading 'commandIdentifiers')", taking the
whole run down rather than failing a single tool call.

Wrap the resolved permission handler so such decisions are scoped using the
request that triggered them: shell prompts become an approval for that
prompt's command identifiers, MCP prompts an approval for that server and
tool, URL prompts an approval for that URL's domain, and so on.

The decision is only ever narrowed, never widened. When the prompt reports
can_offer_session_approval=False, or the request kind has no session-scoped
approval (such as a hook prompt), the decision is downgraded to a single-use
approval and a warning is logged. Decisions that already specify a scope are
forwarded unchanged, and handler exceptions still propagate so the SDK's
deny-on-error behavior is preserved.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1b45752e-b602-4117-8304-3c8a8b877e3e

* Fix test-suite type-checker errors for permission-decision normalizer

The permission-handler wrapper returned PermissionHandlerType (the sync-or-async
union), so awaiting its result in tests was rejected by the stricter CI type
checkers (pyrefly, ty, zuban). Give the wrapper a dedicated
AsyncPermissionHandlerType return type, and narrow the awaited result with an
isinstance assert before accessing its scope in the async-handler test.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1b45752e-b602-4117-8304-3c8a8b877e3e

* Add regression tests for extension permission approval normalization

Cover the two previously-untested branches of _derive_session_approval:
extension-management preserves the request operation, and
extension-permission-access preserves the extension name. Both assert the
serialized approval payload as well.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1b45752e-b602-4117-8304-3c8a8b877e3e

* Scope URL session approvals only for parser-unambiguous URLs

The URL branch derived the persisted domain with Python's urlparse, but the
Copilot CLI parses URLs with WHATWG semantics. The two disagree on crafted
authorities -- e.g. a backslash before the '@' in
'https://example.com<backslash>@evil.com' resolves to example.com under the CLI
but evil.com under urlparse -- so trusting urlparse could persist a session-wide
approval for an unrelated, attacker-chosen domain, widening authorization.

Add _derive_url_session_domain, which returns a domain only when the URL
contains none of the characters WHATWG and urlparse handle differently
(backslash, tab, newline, carriage return); any ambiguity (or a URL with no
host) narrows the decision to a single-use PermissionDecisionApproveOnce.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1b45752e-b602-4117-8304-3c8a8b877e3e

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1b45752e-b602-4117-8304-3c8a8b877e3e
2026-08-13 01:13:19 +00:00
Ruiming Zhao 3221011427 fix(core): warn when advertised MCP archives are rejected (#7622) 2026-08-13 00:44:55 +00:00
Chinmay V 3aadac53c8 Python: fix(redis): honour a max_messages retention limit of zero (#7470)
* Python: fix(redis): honour a max_messages retention limit of zero

RedisHistoryProvider documents None as the sentinel for unlimited storage,
so max_messages=0 must retain nothing. It retained everything: trimming to
-max_messages emits LTRIM key 0 -1, which is Redis's "keep the whole list",
and the count > max_messages guard is true for any non-empty list, so the
trim ran on every save and did nothing.

Negative values were worse than a no-op. max_messages=-5 emitted
LTRIM key 5 -1, deleting the five oldest messages on every save while the
list still grew without bound.

Handle a limit of zero by deleting the key, which is what clear() in this
class already does, and reject negative values in __init__ alongside the
three ValueErrors it already raises for invalid configuration. None and
positive limits are unchanged.

* Python: never write the payload when Redis retention is disabled

Addresses the automated review on #7470. With max_messages=0 the previous
change still RPUSHed every message and deleted the key afterwards, so the
payload reached Redis - and any AOF or replica stream - before being removed,
and was briefly visible to other readers. Short-circuit instead: drop any
existing history and return before serializing, so nothing is written at all.

Also documents the new ValueError in the Raises: section, and asserts in the
test that the pipeline is never used.

* Python: leave stored history alone when Redis retention is disabled

max_messages=0 deleted the session key. _redis_key omits source_id, so
two providers with the default prefix share {key_prefix}:{session_id},
and the after-run pass persists in reverse provider order - a
zero-retention provider listed first would drop a co-located provider's
just-written history on every turn.

Return before serializing instead: no payload reaches Redis, an AOF or a
replica, and stored history is left as it is. Removing stored history is
what clear() is for.

---------

Co-authored-by: Chinmay V <203952148+chinmayv095@users.noreply.github.com>
2026-08-13 00:44:20 +00:00
Ruiming Zhao 35c6b880f7 Python: Preserve Mistral prompt-cache usage details (#7597)
* Fix Mistral cached token usage

Map prompt cache hits from Mistral chat usage into the standard usage details. Add regression coverage for regular and streaming responses.

* Validate Mistral cached token usage

* fix(mistral): satisfy strict cached token typing

Narrow prompt token details before reading cached_tokens so the Mistral package passes strict Pyright without changing runtime validation.\n\nAddresses https://github.com/microsoft/agent-framework/pull/7597#discussion_r3750712320
2026-08-13 00:43:50 +00:00
Vaibhav Patel 3a5d00be54 Python: add checkpointing support to AgentFrameworkWorkflow.run() in agent-framework-ag-ui (#6646)
* Python: add checkpointing support to AgentFrameworkWorkflow.run() in ag-ui

The ag-ui AgentFrameworkWorkflow.run() previously accepted only a
RunAgentInput payload and exposed no way to use the core workflow's
checkpointing/state-persistence, unlike the core agent-framework workflow
implementations. This left ag-ui workflows without resumable execution.

Add optional checkpoint_storage and checkpoint_id keyword arguments to
run(), threaded through run_workflow_stream() into the core Workflow.run().
This delegates to the existing core capability instead of reinventing it and
keeps the public surface consistent with Workflow.run():

- checkpoint_storage enables checkpoint creation at each superstep boundary.
- checkpoint_id resumes a run from a persisted checkpoint; incoming messages
  are forwarded only as request-info responses (never as a new start-executor
  message) to honor the core's message/checkpoint_id mutual exclusivity, and
  responses + checkpoint_id performs a restore-then-send in one call.

Both can also be supplied via the input_data keys __ag_ui_checkpoint_storage
and __ag_ui_checkpoint_id so the FastAPI endpoint (which calls run(input_data)
positionally) can opt in without changing its call site; explicit keyword
arguments take precedence. Checkpoint resume bypasses the AG-UI thread snapshot
hydration early-returns so it always reaches the core restore path.

Backward compatible: run(input_data) keeps working unchanged, and the
non-checkpoint path still calls run_workflow_stream(input_data, workflow) with
its original two-argument convention. Adds focused tests covering checkpoint
creation, resume-from-checkpoint, input-data-keyed params, and the unchanged
default path.

Fixes #6632.

* Import Executor from the public agent_framework API in ag-ui workflow test

* Fix ag-ui checkpoint resume: preserve thread snapshot, coerce resume responses; fix CI lint/typing

A checkpoint-only resume no longer clobbers the stored AG-UI thread snapshot:
the snapshot builder is seeded with the prior stored history so the saved
snapshot keeps the earlier replayable transcript plus the newly produced output.

Resume responses are now coerced against the post-restore pending requests on a
checkpoint restore, so a JSON function_approval_response resumes through AG-UI
after a cold restore instead of failing with a response-type mismatch.

Also update the test-double workflow run() overrides to match the new keyword-only
parent signature and re-sort the workflow test imports so ruff and the typing
checkers pass.

* Coerce ag-ui resume responses without a second checkpoint restore

Reading pending request_info events for resume-response coercion previously
restored the checkpoint into the live workflow, which invoked every executor's
on_checkpoint_restore hook. workflow.run(checkpoint_id=...) then restored again,
running those hooks a second time. Custom restore hooks are not required to be
idempotent, so this could duplicate restoration work or break workflows that
expect exactly one restore per resume.

Load the persisted WorkflowCheckpoint directly from storage (runtime override
or the workflow's build-time context storage) and read its
pending_request_info_events instead. This exposes the same post-restore pending
set for the resume contract and response coercion without mutating workflow
state or running any restore hook, leaving workflow.run(checkpoint_id=...) as
the single restore per resume.

Add a regression test asserting on_checkpoint_restore runs exactly once on a
checkpointed ag-ui resume.

* Python: rework AG-UI workflow checkpointing onto public configuration surfaces

Checkpoint storage is now configured on AgentFrameworkWorkflow (or the
FastAPI endpoint) instead of being smuggled through input_data keys, and
a run resumes by supplying its checkpoint id in the AG-UI forwarded
props. With storage always in hand, resume-response coercion reads the
pending request set straight from the persisted checkpoint via the
public CheckpointStorage.load(), replacing the private runner-context
fallback, and the core run call forwards checkpoint arguments directly,
relying on core validation for conflicting parameters. Requesting a
resume without configured storage now fails with a clear error.

* Assign endpoint checkpoint storage in a single place

The raw-workflow branch assigned checkpoint_storage at construction and
the wiring block assigned it again. Construct the wrapper bare and let
the wiring block own the assignment; the existing-storage guard keeps
allowing a pre-wrapped runner without storage to adopt the endpoint's.

---------

Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
Co-authored-by: Evan Mattson <evan.mattson@microsoft.com>
2026-08-12 22:07:52 +00:00
westey 56d13bce4e .NET: Add BackgroundAgentsProvider.ReleaseSessionAsync to cancel and release per-session background tasks (#7602)
* Add the abilty for the caller to release and cancel background tasks

* Improve param validation

* Address PR comments

* Address PR comments.

* Address PR comments: cancel tasks before publishing the release

Set IsReleased and publish the ReleaseCompletion only after the in-flight
tasks have actually been cancelled, so a failure to cancel leaves the
session un-released instead of flagging it as released while its tasks are
still running.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-08-12 14:25:59 +00:00
Luis Rodriguez 27d82b1567 Python: Ignore non-project workspace glob matches (#7509)
* Python: Ignore non-project workspace glob matches

* test: collect workspace script tests

* test: remove standalone script test

---------

Co-authored-by: Luis Rodriguez <25299418+luisangelrod@users.noreply.github.com>
Co-authored-by: Luis Rodriguez <luis.rodriguez@bcpos.com>
2026-08-11 19:14:18 +00:00
Giles Odigwe 5e52c6a718 Python: Fix ClaudeAgent reusing one SDK client across distinct fresh sessions (#7404)
* Python: Fix ClaudeAgent reusing one SDK client across distinct fresh sessions

RawClaudeAgent kept a single mutable ClaudeSDKClient on the agent instance
and reused it across distinct fresh AgentSession objects, because a fresh
session passes session_id=None and the old reuse check treated that as
"keep the current client". Two independent fresh sessions on one shared
agent instance therefore shared a single provider conversation, so the
second session continued the first session's conversation.

Treat a fresh (None) continuation id as always requiring a new client, so
an unbound session never inherits an existing provider conversation.
Legitimate continuity is preserved: once a session runs, its
service_session_id is written back, so later runs pass a real id and resume
correctly. Guard client selection/creation with an asyncio.Lock so
concurrent runs cannot race between the check and the client assignment.

Add regression tests asserting two fresh sessions produce two clients and
that an explicit continuation id still resumes the existing client.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 598a9fe1-28c5-4db1-88fd-e14acd9340af

* Python: Bind Claude SDK client ownership to each run

Replace the single mutable ClaudeSDKClient stored on the agent with a
per-run client. Because a ClaudeSDKClient represents exactly one provider
conversation, sharing one across distinct sessions collapsed them onto the
same conversation and, for concurrent runs, let a fresh session disconnect
a client another run was still streaming from.

_acquire_client now returns a per-run client (owned) that resumes the
framework session's provider conversation when one exists, and _get_stream
releases it in a finally once the run completes. An injected client is
reused verbatim and left to the caller. The streaming loop moves into
_stream_run so the client is a local per-run value rather than shared agent
state, which keeps distinct sessions isolated even under concurrency.

Continuity is preserved: a session's service_session_id is written back
after each run and forwarded as the resume id on subsequent runs. Replace
the client-lifecycle tests with per-run ownership and end-to-end isolation
tests (two fresh sessions get two separate clients, each disconnected).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 598a9fe1-28c5-4db1-88fd-e14acd9340af

* Python: Close remaining Claude session-isolation gaps

Address three shared-state gaps in the Claude adapter surfaced in review:

- Run-scope structured output: carry the run's structured_output through a
  per-run state holder and a per-run finalizer instead of storing it on the
  agent, so a concurrent run cannot overwrite another run's value before its
  finalizer reads it.
- Bind an injected client to one session: an injected ClaudeSDKClient is a
  single Claude conversation, so bind it to the first session that uses it and
  raise AgentInvalidRequestException if a different session tries to reuse it.
  A no-session run reuses the bound session so multi-turn continuity still
  works; multi-session callers must omit client= or use one agent per session.
- Serialize the injected-client path with an asyncio.Lock so concurrent runs
  cannot race its connect or interleave queries on the one shared client.
  Owned per-run clients stay lock-free.

Update and extend the tests accordingly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 598a9fe1-28c5-4db1-88fd-e14acd9340af

* Python: Bind injected Claude client on provider conversation identity

Compare an injected client's binding on the session's service_session_id
(the Claude conversation identity) rather than the framework-local
session_id, falling back to session_id only when the incoming session has
no provider id yet. A reconstructed session from
get_session(service_session_id=...) carries a fresh session_id but the same
provider conversation, so it now continues the bound conversation instead of
raising. Sessions targeting a different conversation are still rejected.

Add regression tests for reconstructed-same-conversation continuation and
different-conversation rejection.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 598a9fe1-28c5-4db1-88fd-e14acd9340af

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 598a9fe1-28c5-4db1-88fd-e14acd9340af
2026-08-11 19:04:08 +00:00
Giles Odigwe 30996433ac Python: Restore Gemini thought_signature on approval replays (#7546)
Gemini 3.x rejects a request whose functionCall parts lack a
thought_signature. The signature was carried as base64 protected_data on a
text_reasoning content and re-attached by adjacency, which requires the
carrier to immediately precede its call. An approval round trip replays the
call with no carrier at all, so the next turn failed with a 400.

Track signatures in a bounded per-client call_id map populated at parse time
from the resolved call_id, and backfill only when the emitted part has no
signature. Also stop clearing the held signature on contents that emit no
Part, so an approval response or an unsigned thought summary between the
carrier and its call no longer drops it.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: dd0909cd-c7c3-42cb-aef1-1e9a3e64d917
2026-08-11 19:03:57 +00:00
Tao Chen e85b3c8ba8 Python: Fix FHA session ID translation (#7608)
* Fix FHA session ID traslation

* Fix tests

* Address comments and fix tests

* Fix typing

* Show how to use user created sessions

* Update README
2026-08-11 17:30:56 +00:00
Roger Barreto 4ca093371e .NET: Add Options for Hosted Agent to Allow Backend Storage (#7572)
* Let the container choose who stores a hosted turn, and say so when it is stored twice

Turning storage off downstream was unconditional and silent. It is now a container choice, and a
deployment that ends up storing anyway is reported instead of quietly recording the conversation in
two places nothing reconciles.

FoundryResponsesOptions, passed through AddFoundryResponses, carries two settings.
AllowStoredOutputEnabled defaults to false, which is when hosting turns storage off for every run and
checks the result. Setting it to true leaves the agent's own configuration exactly as the container
built it, and nothing is checked, overridden, or refused. IncludeReasoningEncryptedContent applies
while storage is off, asking for the encrypted form of the reasoning tokens so reasoning survives
between turns, mirroring AsIChatClientWithStoredOutputDisabled.

Two checks replace the 400 that used to refuse a session carrying a conversation id. The readiness
probe runs each registered agent with its chat client swapped for one that calls nothing, so the
request the agent builds on its own is visible without leaving the container, and an agent asking for
its responses to be stored keeps the container out of rotation. Per request, a conversation id on the
session after the run means the agent's own service kept the turn, which fails with 501 and leaves
the session unsaved so later turns do not resume onto it. A misconfigured container is a server
problem, not a bad request, hence 5xx.

Only a confirmed "this asks to be stored" fails either check. An agent that is not a ChatClientAgent,
a request shape carrying no such setting, and a run that could not be completed all pass: this
package cannot tell what those would do.

* Rename the stored-session flag to say what it means

* Say plainly what server-side storage does to a hosted turn

* Read the store gate as an allow, and align the messages

The flag that decides whether the session may be saved reads as an allow at every use, while the
test it comes from keeps saying what is not allowed, so neither side has to be read inside out.

The wording now matches what the readiness probe says: server side storage must be off, because with
it on the agent's own service records a conversation and response nothing tracks while the hosted
agent records its own for the same request. The message the readiness probe raises no longer travels
through a shared constant, since each check says its own thing.

* Address the review comments left open on the merged PR

Five points raised on #7525 were marked resolved without a code change, and the code they pointed at
was still there.

A hosted workflow session is now recognised by its full type name, so a session of the same short
name from another namespace is not mistaken for one. The test double moves into the namespace it
stands in for, otherwise it would no longer exercise the check.

The per-run chat history provider is handed over on AgentRunOptions.AdditionalProperties, which
ChatClientAgent copies onto the chat options with precedence, rather than being written onto the
chat options here.

The test that pins down who supplies the history said the agent's own provider is used, while it
asserts the opposite, so it is renamed after what it checks.

Reading a response back in the hosted integration tests no longer swallows every failure: only
"not stored" and "not readable through this endpoint" are, so an expired token or a server fault
cannot be mistaken for an absent response and pass the test.

Also fills in the readiness message for the case where storing is explicitly allowed.

* Address the review on #7572

Four findings, all real, all in code this branch introduced.

A container that allows its own service to keep the conversation was still being handed the platform
history on every turn. That service replays the earlier turns itself, so the model was getting each
of them twice, which is the very thing this work exists to prevent. The history now goes in only
while nothing else holds it: the first turn of such a conversation still gets it, and the service
takes over from there.

A turn that fails for storing downstream was announcing itself as completed first and only then
failing, leaving the caller with two different answers for the same turn. The completed event is now
held back until the run is wound up and the session can be read, because the id of any conversation
the agent's service kept only lands there at the very end.

The readiness probe replaced the chat client but left the agent's chat history provider running, so
a provider backed by a database was reading and writing on every probe, and adding the probe's empty
turn to a real conversation. It is stood down for that run now.

The probe also treated any cancellation as the health check's own, so a timeout inside an agent could
fail readiness. Only a cancellation of the health check's token is left to propagate.

Fixing the completed event turned up a latent problem: the terminal event types are named the same in
two namespaces this file pulls in, and the short name binds to the ones the response stream never
produces, so vt is ResponseCompletedEvent was quietly always false. The three terminal types are
now named explicitly.

* Let the chat history provider carry the conversation

The handler used to read the hosting service's record of the conversation and prepend it to the
input of every run, then work out who should not get it: a resumed workflow by the name of its
session type, and a container whose own service already holds the conversation. Two exceptions, a
type name matched as a string, and a shape where the same turns could arrive from two directions.

An agent that reads its history through a provider is now given one, seeded with that record, for
the length of the run. The turns arrive the way the agent expects them rather than as fresh input,
so nothing is stored back as if it had just been said, and the provider is dropped when the run
ends. Only the new input is passed to the run now.

Everything else supplies its own history and is left alone: an agent built with a provider keeps
using it, an agent whose service keeps the conversation reads it from there, and an agent that is
not a ChatClientAgent, a hosted workflow for instance, carries the conversation in its own session
state and wants only the new input. The workflow session type name check is gone with it.

The session is saved on every turn again. It was being withheld when the agent's own service had
kept the turn, which is a decision about that service, not about the session; nothing this handler
adds for a turn reaches the session anyway.

* Fail a turn to skip its session, and name the store check after what it detects

The session was being withheld from the store on a condition about the agent's own service rather
than about the turn, and guarded by an emptiness check on a key that is never empty. A turn that is
being failed now says so, and only that skips the save. A turn that ends incomplete, waiting on
OAuth consent or interrupted by a shutdown, is not a failure: the caller comes back for it and needs
the state built up so far, the tool approval ids among it.

The session key is resolved once as a value that always exists, so both the load and the save use it
without asking again whether it is there.

CheckNotAllowedStoreUsage and notAllowedStoreUsageDetected now read as what they are: a check for an
agent storing when it should not, and the flag saying it was seen.

* Read a hosted response through the agent client, and only forgive a 404

Reading a response back tried the project-level client first and then the per-agent one, swallowing
403 as well as 404 to get past the first. The project-level client cannot see a hosted agent's
responses at all, so that attempt only ever produced the 403 the catch then had to forgive, and any
other 403, an authorization failure for instance, was read as "nothing is stored" and passed the
test.

Only the per-agent client is used now, and only a 404 counts as not stored. Verified against the
service: a well-formed id it has no response for answers 404 invalid_request_error "Response '...'
not found", the same id through the project-level client answers 403 session_not_accessible, and a
malformed id answers 400. Everything but the 404 now surfaces.

* Move the store setting next to the code that reads and writes it

The two halves of the stored output concern lived in a shared helper: one that installs the factory
turning storage off, and one that reads back what a request would have asked for. Each had exactly
one caller, so the helper only added a hop. They now sit in the converter that builds the request and
in the probe client that inspects it, and the helper keeps just the error the handler throws.

The test double standing in for a hosted workflow session is also gone. It was declared inside the
Workflows namespace because the handler used to recognise a resumed workflow by the full name of its
session type; that comparison no longer exists, so the double only needs to not be a ChatClientAgent.

* Say that the stored output setting could not be determined, which is the case being logged
2026-08-11 16:22:26 +00:00
Peter Ibekwe 8a0731ad92 .NET: Prevent telemetry serialization failures from failing workflows (#7612)
* Prevent telemetry serialization failures from failing workflows

* Address PR comments
2026-08-11 15:16:40 +00:00
Peter Ibekwe 6fff2c9b1f Fix misleading workflow protocol attribute diagnostics (#7609) 2026-08-11 15:16:21 +00:00
Saurish 0d75365331 .NET: Add Cosmos NoSQL vector memory sample (#7552)
* .NET: Add Cosmos NoSQL vector memory sample

* Address Cosmos memory sample review feedback

* Fix Cosmos NoSQL memory sample build

---------

Co-authored-by: nos-redacted <nosxredacted@gmail.com>
2026-08-11 10:33:06 +00:00
Peter Ibekwe db979b616a Python: Improve Json parsing for declarative workflow (#7550)
* Json parsing improvement

* Fix PR comments

* Address PR comments.
2026-08-11 05:09:28 +00:00
Tao Chen d0a4165f17 [BREAKING] Python: Migrate FHA to responses==2.0.0b1 and add Foundry state store (#7533)
* Migrate FHA to responses==2.0.0b1 and add Foundry state store

* Fix session id error

* Fix tests

* Improve tests

* Fix copilot comments

* Address comments

* Revert sample changes

* Address comments

* Add ContextScopedStoreProvider

* Fix type check

* Fix type check

* Export ContextScopedStoreProvider
2026-08-10 05:51:59 +00:00
dependabot[bot] 4357ff5742 Bump postcss (#7529)
Bumps [postcss](https://github.com/postcss/postcss) from 8.5.22 to 8.5.25.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.5.22...8.5.25)

---
updated-dependencies:
- dependency-name: postcss
  dependency-version: 8.5.25
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-10 02:47:05 +00:00
dependabot[bot] 221f4b6df1 Bump postcss from 8.5.15 to 8.5.25 in /python/packages/devui/frontend (#7493)
Bumps [postcss](https://github.com/postcss/postcss) from 8.5.15 to 8.5.25.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.5.15...8.5.25)

---
updated-dependencies:
- dependency-name: postcss
  dependency-version: 8.5.25
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-10 02:46:59 +00:00
dependabot[bot] a9f7b2b788 Bump pyrefly from 1.1.1 to 1.2.0 in /python (#7541)
Bumps [pyrefly](https://github.com/facebook/pyrefly) from 1.1.1 to 1.2.0.
- [Release notes](https://github.com/facebook/pyrefly/releases)
- [Commits](https://github.com/facebook/pyrefly/compare/1.1.1...1.2.0)

---
updated-dependencies:
- dependency-name: pyrefly
  dependency-version: 1.2.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-10 02:44:59 +00:00
dependabot[bot] adcc3de654 Bump js-yaml from 4.3.0 to 4.3.1 in /python/packages/devui/frontend (#7554)
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 4.3.0 to 4.3.1.
- [Changelog](https://github.com/nodeca/js-yaml/blob/4.3.1/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/4.3.0...4.3.1)

---
updated-dependencies:
- dependency-name: js-yaml
  dependency-version: 4.3.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-10 02:42:11 +00:00
dependabot[bot] 034f5fa119 Bump zuban from 0.9.0 to 0.9.1 in /python (#7545)
Bumps [zuban](https://github.com/zubanls/zubanls-python) from 0.9.0 to 0.9.1.
- [Release notes](https://github.com/zubanls/zubanls-python/releases)
- [Commits](https://github.com/zubanls/zubanls-python/compare/v0.9.0...v0.9.1)

---
updated-dependencies:
- dependency-name: zuban
  dependency-version: 0.9.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-10 02:41:55 +00:00
Evan Mattson 48e547506b Python: Make encrypted reasoning opt-in for Foundry chat (#7536)
* Python: Make Foundry encrypted reasoning opt-in

* Python: Opt hosted replay test into encrypted reasoning
2026-08-10 02:36:48 +00:00
Peter Ibekwe 5eb3eb745e Improve string parsing in declarative workflows (#7535) 2026-08-07 18:07:56 +00:00
SergeyMenshykh c987529df3 .NET: [BREAKING] Rename to AgentIsolationKeyProvider (#7567)
* Update store isolation documentation

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 824a2e29-e79c-42aa-b28e-aa6c12ec3292

* Rename store isolation key provider

Rename the shared session isolation abstraction to reflect its use for both session and task stores.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 824a2e29-e79c-42aa-b28e-aa6c12ec3292

* Rename to AgentIsolationKeyProvider per review feedback

Drops the `Store` qualifier and keeps an `Agent` prefix so the type is not
confused with generic isolation-key abstractions from other libraries, while
leaving room for future non-store isolation (memory, retrieval).

- StoreIsolationKeyProvider -> AgentIsolationKeyProvider
- ClaimsIdentityStoreIsolationKeyProvider(+Options) -> ClaimsIdentityAgentIsolationKeyProvider(+Options)
- GetStoreIsolationKeyAsync -> GetIsolationKeyAsync
- UseClaimsBasedStoreIsolation -> UseClaimsBasedAgentIsolation

XML docs now state that the `Agent` prefix identifies the hosting API domain and
does not mean agent instances are isolated.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 824a2e29-e79c-42aa-b28e-aa6c12ec3292

* Update hosting spec for AgentIsolationKeyProvider rename

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 824a2e29-e79c-42aa-b28e-aa6c12ec3292

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 824a2e29-e79c-42aa-b28e-aa6c12ec3292
2026-08-07 14:59:07 +00:00
westey aaaa56bc60 .NET: Store executable function calls bypassed by declaration-only tool calls (#7388)
* Allow storing executable functions when mixed with non-executable

* Address PR review feedback on executable function bypassing

- Guard enumerator acquisition so pending bypassed calls are restored when
  the inner client throws synchronously, before the first MoveNextAsync.
- Always surface buffered streaming updates, even when stripping empties
  them, so metadata such as ConversationId and ResponseId is not discarded.
- Document that the decorator must sit below ApprovalResponseBindingChatClient,
  which drops approval responses that have no recorded request.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Address PR comments

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-08-07 13:52:31 +00:00
Roger Barreto 18ceb182b1 .NET: Give a hosted agent a single source of conversation history (#7525)
* Read hosted chat history through a provider instead of the request input

The handler used to fetch the platform conversation history and prepend it to the
input of every turn. For a ChatClientAgent that runs in parallel with its own chat
history provider, so the conversation had two sources at once. It also had a hidden
cost: platform items carry no chat-history source marker, so the agent's provider
stored them again as if this turn had written them, leaving a second copy of the
conversation inside the persisted session that then diverges from the platform.

Make the chat history provider the single source for a ChatClientAgent:

- Add FoundryChatHistoryProvider, which reads the conversation through
  ResponseContext.GetHistoryAsync (it already resolves previous_response_id and the
  conversation the request belongs to) and stores nothing, because the platform
  persists the response items itself. An instance is created per request because it
  holds that request's context, and it is passed as a run-scoped override so the host
  does not have to mutate the agent.
- Register it only when the agent was created without a chat history provider. When
  one was supplied at construction, that provider owns the conversation and the
  platform history is not used at all.
- Stop adding the platform history to the input for a ChatClientAgent, since the
  provider now delivers it.

A workflow hosted as an agent is not a ChatClientAgent and has no provider pipeline,
so it keeps receiving the platform history from the handler exactly as before.

* Add regression tests for the duplicated hosted chat history

Cover the three symptoms the previous handler produced, each verified to fail when
the handler is reverted to fetching the platform history into the turn input:

- the conversation the service already keeps was copied into the persisted agent
  session by the default in-memory history provider;
- a custom history provider was asked to write that same conversation into its own
  database, because platform items carry no chat-history source marker and so look
  like content this turn produced;
- an agent with its own provider received both that provider's history and the
  platform's in a single request.

Also state precisely, in the provider's remarks, why nothing is written back: for a
stored request the response orchestrator hands the finished response to its responses
provider, which persists the input and output items that a later turn then reads back
through GetHistoryAsync; for a non-stored request nothing is persisted and nothing is
readable, so the request is self-contained either way.

* Keep unstored turns in the session so mixed conversations stay whole

A conversation can mix turns the service stores with turns it does not. History is
resolved from previous_response_id or the conversation regardless of the current
request's store flag, so an unstored turn still reads the stored ones back, but the
service records nothing for it and a later turn would never see it again.

Reading the platform history through FoundryChatHistoryProvider alone lost those
turns: from the second turn onwards the handler treats the session as a resume and
stops feeding history in, and the provider kept nothing of its own, so an unstored
turn simply vanished from the conversation. A regression test drives three turns of
one conversation, the first stored and the rest not, and without this change the
model receives only [second question, ok, third question]: the stored opening turn
is gone.

Give the provider both halves instead of choosing one:

- reading returns what the service serves, followed by the turns kept in the session,
  which are by definition later than anything the service recorded;
- writing keeps a turn only when the service was not asked to store it, so a stored
  turn is never duplicated and an unstored one is never lost.

The turns are held in the agent session under the provider's own state key, so they
travel with the session the host already persists.

* Refuse a stored turn once a conversation holds unstored ones

A conversation can move between stored and unstored turns, and the unstored ones live
only in the agent session. Going back to a stored turn after that would have the
service record it on top of turns the service never saw, so anyone reading the
conversation back from the service would find an answer with no question. Refuse it
before the model is called instead of writing that gap.

Cover the whole shape with a walkthrough of nine turns over one conversation and three
provider instances, each with its own session:

- an instance that never took an unstored turn starts from the turn the service last
  saved, and does not see another instance's unstored turns;
- an instance that did keeps reading the saved turns and adds its own on top;
- asking such an instance for a stored turn is refused, twice, while unstored turns
  keep working;
- a turn stored from one instance does not appear for another, because it sits on a
  different branch of the conversation and so is not among the turns leading to what
  that other instance last saved.

* Say plainly that kept turns belong to the session

The turns the service was not asked to store are written into the agent session's state
bag under this provider's own state key, and a new provider is built for every request,
so nothing is held on the provider object itself. The walkthrough named its three
threads after provider instances, which read as if the object carried the memory.

Name them after the sessions they are, and add a test that pins the behaviour down: a
turn kept through one provider object is read back by a different one given the same
session, and is absent for one given another session.

* Show which half of the conversation each provider decides

The session decides what is kept, but the provider still decides two things: which
service-side conversation is read, because it holds the request's response context, and
whether the turn is kept at all, because it holds the request's store flag.

Add two tests that separate those from the session:

- two providers reading one session, each built for a request of a different
  conversation, return the same kept turn behind different served turns;
- two providers writing to one session, one for a stored request and one for an
  unstored one, leave only the unstored turn behind.

* Say why a hosted workflow keeps taking history from the handler

The comment stated that a workflow hosted as an agent has no provider pipeline
without saying what that means. It derives from AIAgent directly, so it never calls
a ChatHistoryProvider and does not read the run options' additional properties: the
provider could not reach it even if it were registered.

* Ask the session store whether a turn is a resume

The handler decided that a turn was resuming an existing conversation by looking for
state on the session. That reading broke once the handler itself started writing to the
session before the check: it records the caller's identity there, so a session created
moments earlier already carried state and the very first turn of a conversation looked
like a resume. Its history was then never fetched, and the agent answered knowing
nothing of a conversation the service was already holding. It only showed up when
hosted, because running locally there is no identity to record.

Let the store answer the question instead. GetSessionAsync now returns null when nothing
is stored rather than quietly handing back a new session, so a non-null result means a
prior turn established this session and nothing else has to be inferred. Callers that
just want a usable session can use the new GetOrCreateSessionAsync, which is written in
terms of GetSessionAsync so a store overriding one gets the other for free.

Both store implementations and their tests follow the plain-lookup contract: a miss
creates nothing, deserializes nothing, and touches no directory.

* Drop the experimental marker from an internal type

FoundryChatHistoryProvider is internal, so the attribute reached no caller: the marker
exists to warn people consuming the public surface. It also does not follow from the base
type, which does not carry one, and most internal types in this package have none either.
Removing it leaves two usings behind, so they go as well.

* Stand down the agent's second-manager guard for the host's own provider

An agent refuses a second history manager once the model reports a conversation id of its
own, which happens as soon as the container lets the model keep the conversation. The
guard is meant for an application that configured a provider by hand and would otherwise
end up with two of them. Here the host is the one supplying the provider, deliberately and
for every turn, so the guard was rejecting the arrangement it is hosting: the first turn
failed while streaming, and every later one failed before reaching the model at all.

Turn the three conflict settings off on the agent the host is serving, and let the
provider decide what reaches the model. A test drives two turns of one conversation
against a model that reports a conversation id and asserts both complete.

* Pass a caller's request not to store on to the chat client

A request asking the hosting service not to store the response was honoured there and nowhere else, so the service behind the agent's own chat client kept recording the conversation and reporting an id for it. A caller opting out of storage still ended up with a stored conversation, and the container went on continuing it.

Only that direction travels. Carrying store=true across would either force storage on a container whose author turned it off on purpose or change nothing, since storing is already the default.

* Hand the conversation to the agent's own provider instead of a host one

The host no longer supplies a chat history provider of its own. It writes the turns the service holds into the provider the agent already created for itself, and only when that is the stock in-memory one, so an agent given a provider keeps sole control of its storage and the model receives the conversation once.

A conversation the caller stops asking the service to store moves into the session state and stays there. The session's conversation id no longer names anything the service records and cannot be cleared, so the session is cloned without it on that single turn. Asking for a stored turn afterwards is refused: the service would record a turn whose predecessors it does not hold.

An agent that does not read history through a provider, a hosted workflow for example, is still given its prior turns as input, now marked as chat history so no provider along the way stores them as new.

* Run the agent's own request factory instead of replacing it

ChatClientAgent chains a request's raw representation factory with the agent's by taking the agent's only when the request's returns null. The factory added for an unstored turn always answers, so anything the container configured on the agent's ChatOptions was silently dropped for that turn.

The agent's factory is now invoked first and its result is what carries the setting. A result that is not a CreateResponseOptions belongs to some other chat client, which has no notion of storing a response, so it is handed back untouched.

* Cover a stored conversation that stops being stored and asks again

The refusal was only tested on a conversation the service never stored. Reaching it from a stored one goes through the turn that rebuilds the session without its conversation id, so the mark saying the conversation left the service has to survive that rebuild to be found on the next turn.

* Leave the conversation to the AgentServer storage provider alone

The AgentServer SDK records a hosted turn through its own storage provider, around the handler, and serves the conversation back through ResponseContext.GetHistoryAsync. Anything the container stores of its own is a second conversation that storage provider never sees and no one reconciles.

The handler now takes that history as the single source and hands it to the agent as input alongside this turn's messages. The agent's own provider is replaced for the run by one holding its messages in a field, so a run that calls tools still has what its earlier calls produced while nothing survives the request. The service behind the agent's chat client is asked not to store on every turn, whatever the caller asked of the hosting service.

A session that still carries a conversation id means that service is recording a second conversation regardless, so the turn is refused with a 400 rather than run against something nobody can reconcile.

* Narrow the history skip to a resumed workflow

Withholding the conversation from every agent that is not a ChatClientAgent assumed they all carry it in their own session. A hand-written one that keeps nothing would answer with no history from its second turn on, so the check is now on the session type a workflow runs with, which is what actually accumulates the turns.

The conversation and previous response id tests went with it: the session key falls back to the partition of a freshly minted response id, which never has a session saved for it, so a loaded session already implies one of the two was sent.

Also asks a Chat Completions client not to store, since the setting carries the same name on both OpenAI request shapes.

* Add a live test that a hosted turn is not stored twice

The AgentServer SDK's storage provider records every hosted turn around the handler, and
that record is the conversation the caller reads. The agent's own run inside the container
talks to its own service, and when that service is asked to keep the turn it writes a
second copy of the same exchange, on a trail of its own that nobody reads and nobody
reconciles. The caller's conversation looks clean, so the second copy goes unnoticed.

The new downstream-store scenario runs an ordinary Foundry ChatClientAgent, like the first
hosted agent sample, wrapped so that after the run it appends DOWNSTREAM_ID=<id> to the
reply, carrying whatever its own run left behind. The tests then go looking for that id on
the service: finding it means a second copy exists.

Verified live against a Foundry project. On main both tests fail, reporting a readable id
such as resp_0940e276..., and here the container reports DOWNSTREAM_ID=none and both pass.

* Let the session carry the conversation in the downstream store test

The run options were setting the conversation on every call, which the session already does.
The single turn test now binds the session to the conversation up front, and the multi turn
test starts from the agent's own default session and reads back what the hosted agent kept
for the caller off ChatClientAgentSession once the first turn returns.

Re-verified live: still fails on main, reporting a readable id such as resp_0c07a5e4..., and
still passes here.
2026-08-07 10:02:23 +00:00
westey ec32e86646 .NET: Aggregate usage across looping agents and chat clients (#7539)
* Ensure usage is merged for all looping components

* Add max tool approval loop fixes

* Fix net472 build break in usage aggregation tests

DateTimeOffset.UnixEpoch is not available on .NET Framework 4.7.2, so the
WithAggregatedUsage copy tests failed to compile for that target framework.
Use an explicit DateTimeOffset instead; the specific instant is irrelevant,
the value only needs to be non-default so the copy assertion is meaningful.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Address PR comments

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-08-07 10:00:27 +00:00
SergeyMenshykh 94bbfb2ac8 .NET: Harden file skill discovery (#7540)
* .NET: Harden file skill discovery

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 8ff072fa-42d6-44b0-b226-2182cfc7639c

* .NET: Handle inaccessible skill directories

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 8ff072fa-42d6-44b0-b226-2182cfc7639c

---------

Copilot-Session: 8ff072fa-42d6-44b0-b226-2182cfc7639c
2026-08-07 09:53:56 +00:00
Giles Odigwe 4b1afd9052 Python: surface Gemini thought summaries as reasoning content (#7488)
Gemini thought-summary parts (part.thought=True) were dropped in _parse_parts, so reasoning never reached ChatResponse.contents. Emit them as text_reasoning content instead, matching OpenAIResponsesClient. Round-trip is safe: _convert_message_contents never re-emits reasoning text as a Part.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: b8aa906e-1408-40c1-9a45-6deb40dc36f8
2026-08-07 03:08:25 +00:00
Giles Odigwe 45c515b8a7 Python: fix CopilotStudioAgent LineTooLong on large activities (#7417)
* Python: fix CopilotStudioAgent LineTooLong on large activities

Bump microsoft-agents-copilotstudio-client to >=1.2.0,<2 and forward a configurable read_bufsize (default 1 MiB) to the underlying aiohttp ClientSession via ConnectionSettings.client_session_settings. Copilot Studio streams each activity as a single SSE data line, so activities larger than aiohttp's 512 KB per-line limit previously raised aiohttp.http_exceptions.LineTooLong. Adds a client_session_settings parameter to CopilotStudioAgent and unit tests covering the default, override, and partial-settings cases.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2766dc09-ab5f-4adc-8627-98361d7ccef0

* Python: apply read_bufsize default to supplied CopilotStudio settings

Address review feedback on the LineTooLong fix: when a user supplies their own ConnectionSettings but no client, inject the read_bufsize default so activities larger than aiohttp's 512 KB per-line limit still stream. Document configuring read_bufsize on the explicit pre-built-client path in the package and sample READMEs and the explicit-settings sample. Add unit tests covering the supplied-settings path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2766dc09-ab5f-4adc-8627-98361d7ccef0

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2766dc09-ab5f-4adc-8627-98361d7ccef0
2026-08-07 03:08:06 +00:00
Giles Odigwe b2a2fcbd87 Python: Add response/request customization hooks to OpenAIChatCompletionClient (#7028)
* Python: Fix reasoning content parsing in OpenAIChatCompletionClient

Fix two issues with reasoning content handling in the Chat Completions
client:

1. (#6979) reasoning_details plaintext buried as encrypted data:
   The client dumped the entire reasoning_details array into
   Content.protected_data without setting Content.text, causing AG-UI
   to emit ReasoningEncryptedValueEvent instead of visible
   ReasoningMessageContentEvent for plaintext reasoning providers
   (e.g. OpenRouter). Now extracts readable text from reasoning_details
   entries into Content.text while preserving protected_data for
   round-trip fidelity.

2. (#6978) Mistral list content causes crash:
   Mistral reasoning models return content as a list of typed chunks
   ([{"type": "thinking", ...}, {"type": "text", ...}]) instead of a
   plain string. _parse_text_from_openai assumed content was always a
   string, causing a Pydantic ValidationError downstream. Now detects
   list content and parses thinking chunks as Content.from_text_reasoning
   and text chunks as Content.from_text.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix pyright strict-mode type errors and handle content-as-string shape

- Use cast() for proper type narrowing in _extract_reasoning_text and
  _parse_chunked_content to satisfy pyright strict mode
- Handle {"content": "..."} string shape in _extract_reasoning_text
  (addresses review comment about missing format coverage)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix mypy errors: cast list content to Any in tests

model_construct bypasses Pydantic runtime validation but mypy still
checks declared types. Use cast(Any, ...) for the list content args.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Address review comments: summary field, reasoning field, and round-trip

- Add 'summary' field extraction in _extract_reasoning_text for
  reasoning.summary entries from OpenRouter
- Handle message.reasoning and message.reasoning_content top-level
  fields (plaintext reasoning without reasoning_details) in both
  streaming and non-streaming paths
- reasoning_details takes priority when both fields are present
- Preserve original Mistral chunk list in additional_properties
  ('_source_content_list') so _prepare_message_for_openai can
  reconstruct the structured list content for multi-turn reasoning
- Add 5 new tests covering all new behaviors

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix ruff used-dummy-variable: rename _skip_structured_siblings

Remove leading underscore from _skip_structured_siblings variable since
it is accessed (not a dummy variable). Ruff's used-dummy-variable rule
flags variables with leading underscores that are read.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix missing newline at end of test file

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Address review: type-agnostic chunk round-trip and reasoning field echo-back

- Honor the _source_content_list marker regardless of the first emitted
  content's type by handling it before the type match, so a chunk list
  beginning with a text chunk still round-trips as one structured message
  (addresses github-actions review comment on results[0]).
- Tag every chunked-content item with a shared _structured_content_group
  id and skip only exact group siblings during serialization, instead of
  suppressing all later text/reasoning content.
- Record provenance of top-level reasoning/reasoning_content fields in
  _reasoning_source_field and echo the value back under the same key on
  the next request, which providers such as vLLM require (addresses
  Kimahriman review comment). Replaces the prior behavior that replayed
  surfaced reasoning as visible answer text.
- Factor the duplicated reasoning parsing into _parse_reasoning_content.
- Add tests for provenance capture, reasoning/reasoning_content round-trip,
  reasoning-only messages, text-first chunk round-trip, and unrelated
  sibling preservation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2f3c0308-51bf-4b66-8b53-87a8546743f5

* Replace provider-specific reasoning logic with configurable parse/prepare hooks

Following review feedback (#7028), keep OpenAIChatCompletionClient free of
provider-specific quirks for 'almost OpenAI-compatible' endpoints. Instead of
branching in core for OpenRouter/vLLM/Mistral, expose two optional callables so
callers adapt the client themselves:

- response_parser (OpenAIChatResponseContentsParser): post-processes the Content
  list parsed from each response choice/streaming delta, to surface non-standard
  fields (e.g. reasoning/reasoning_content/reasoning_details) for display.
- message_preparer (OpenAIChatMessagePreparer): post-processes the outgoing request
  message dicts built from each framework Message, to echo provider-specific fields
  back on later turns (e.g. vLLM reasoning) for multi-turn continuity.

Both default to None (no-op; byte-identical stock OpenAI behavior). This reverts the
provider-specific reasoning/chunked-content parsing and round-trip markers previously
added to core; Mistral chunked content is now handled by agent-framework-mistral.

- Add the two callables to RawOpenAIChatCompletionClient / OpenAIChatCompletionClient
  constructors and invoke them at the parse and prepare seams.
- Export the type aliases from the package and the core lazy openai namespace (+ .pyi).
- Replace the removed-behavior tests with tests for the two hooks.
- Document the hooks in packages/openai/AGENTS.md.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2f3c0308-51bf-4b66-8b53-87a8546743f5

* Skip non-string content in default text parsing

Structured list content (e.g. Mistral reasoning models returning content as a
list of chunks) was wrapped verbatim into a text Content, producing a malformed
Content whose text is a list that crashes downstream (issue #6978). Default text
parsing now skips non-string content so a configured response_parser receives a
clean slate to expand it. Applies to both streaming and non-streaming paths.

Add tests for the skip and for a response_parser expanding chunked content.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2f3c0308-51bf-4b66-8b53-87a8546743f5

* Address review: hook signature, per-role preparer, robust round-trip

- response_parser now receives the already-selected ChatCompletionMessage /
  ChoiceDelta instead of Choice | ChunkChoice, so callers no longer duplicate the
  streaming dispatch (removes the Any/hasattr pattern from tests). The client owns
  the dispatch; parsers read provider fields directly.
- message_preparer now runs once per Message for every role: the build logic moved
  to _build_openai_messages and the hook is applied at a single exit point in
  _prepare_message_for_openai, so system/developer messages no longer bypass it.
- Round-trip example/test now correlates surfaced reasoning via an
  additional_properties marker on message.contents with bounded, order-aware,
  one-to-one dict removal, instead of fragile request-string matching. Adds a test
  proving an answer whose text equals the reasoning text is no longer dropped.
- Update packages/openai/AGENTS.md for the new parser signature and guidance.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2f3c0308-51bf-4b66-8b53-87a8546743f5

---------

Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2f3c0308-51bf-4b66-8b53-87a8546743f5
2026-08-07 02:51:06 +00:00
MohammadHaroonAbuomar 7302d0bf23 Python: agent-hooks interception contract as a first-class experimental core feature (#7515)
* feat(python): add agent-hooks middleware as experimental core feature

Implement the AGENT-HOOKS-0.1 interception contract as a first-class
experimental feature in agent_framework core.

- Single public factory agent_hooks_middleware() returning a private
  agent/chat/function middleware trio (one object per middleware
  category); partial or stacked installs fail closed with loud errors.
- All eight interception points: input/output at the agent seam,
  pre/post_model_call at the chat seam, pre/post_tool_call at the
  function seam, agent_startup/agent_shutdown bracketing each run.
- Fail-closed enforcement throughout: transforms write back into the
  native contexts (messages, arguments, results) or raise; content is
  preserved as Content objects; MiddlewareTermination short-circuits
  are guarded at every seam; enforcement-layer failures halt the run;
  interceptor crashes surface as host_error denies.
- Streaming is fully buffered per spec buffered_output semantics: no
  update egresses before the post_model_call/output verdicts; a deny
  at pull time releases zero updates; run state stays active across
  lazy pulls with cleanup on every exit path.
- Session scoping: per-run by default (startup/shutdown bracket each
  run) or host-owned via emitter/builder parameters for one session
  spanning multiple runs.
- agent-hooks-sdk is an opt-in agent-hooks extra (not in all),
  lazy-imported per the _mcp.py pattern; core imports cleanly without
  it and the factory raises a clear ModuleNotFoundError.
- ExperimentalFeature.AGENT_HOOKS + @experimental decorator, lazy root
  export, typing surface, PACKAGE_STATUS.md entry.
- 55 tests built on real Agent/mock-client flows covering deny-before-
  execution, transform write-back, rich-content preservation, complete
  streaming ordering, error cleanup, concurrency isolation, nested
  agents, and importability without the optional SDK.

Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>

* style(python): unquote ResponseStream annotation per pyupgrade

The pre-commit pyupgrade hook rewrites the quoted forward reference;
ResponseStream is imported at runtime in this module, so the quotes
were unnecessary.

Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>

* refactor(python): address agent-hooks review feedback

Reworks the agent-hooks feature per PR review:

- Verdicts now precede durability: a run-scoped persistence gate
  (_sessions.py) defers per-service-call history persistence and
  after-run provider work until the covering post_model_call/output
  verdict permits; denied content never persists, transforms persist
  post-write-back. Unhooked runs are unchanged (verified against an
  instrumented baseline).
- ResponseStream.buffered_and_gated: a buffered-gate combinator that
  applies the run's pending stream hooks before the gate, then seals
  the stream, so no middleware can rewrite egress after the output
  verdict. Replaces the hand-rolled replay iterator.
- MiddlewareBundle (public, _middleware.py): the factory returns an
  indivisible bundle categorize_middleware splits, making partial
  installs impossible by construction; members are validated at
  construction. Bare (non-sequence) middleware at agent construction
  is now normalized instead of silently dropped, and unrecognized
  middleware logs a warning instead of vanishing.
- Factory split and rename: create_agent_hooks_middleware (per-run
  sessions) and create_agent_hooks_middleware_from_emitter
  (host-owned); the sentinel parameter-diffing is gone.
- Wire conversions live in per-point codec classes owning to_wire and
  write_back. Fixes in that code: tool-call name transforms apply or
  raise; non-object args transforms raise; argument write-back merges
  only changed keys (original values, including bytes, preserved by
  identity); message-list write-back matches by identity, not index.
- function_approval_request objects on the normal return path pass
  through un-emitted, preserving the human approval pause.
- Hosted (service-executed) tool calls surface in the post_model_call
  content projection; the tool-seam limitation is documented.
- Import probe covers the full SDK surface and re-raises as
  missing-extra only for the agent_hooks module; module logger added;
  _json_safe replaced by make_json_safe (which gained bytes support);
  tools_registered uses normalize_tools; dependency-pyright analyzes
  the module again via the test dependency-group.
- Tests: 75 in the feature suite (persistence gating, stream-hook
  sealing, approval passthrough, codec units, bundle validation,
  bare-bundle installs), full core suite green.

Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>

* refactor(python): second review round for agent-hooks

Addresses the second review round on the agent-hooks feature:

- Nested-run persistence ownership: RawAgent.run stamps a run identity
  over the run's dynamic extent (including streaming pulls and result
  hooks); the persistence gate binds to its owning run via an
  offer/adopt handshake keyed to the agent instance and accepts only
  its owner's persists — nested runs persist inline regardless of how
  they were started (tool calls, middleware, custom run loops). The
  tool-seam suspension remains for custom-loop sub-agents invoked as
  tools; the one residual case (custom loop nested in a custom loop
  off the tool path) is fail-closed and documented. Fixes a latent
  pre-existing re-deferral: flush() now drains with the gate context
  suspended, so a nested hooked run's permitted after-run persistence
  no longer re-defers into an enclosing gate.
- as_tool stream_callback consumes the released (verdicted) stream;
  observers cannot see denied or pre-transform content. Both
  directions are regression-tested.
- categorize_middleware gained supported_categories: a bundle member
  landing in a category a call site cannot install raises; bare
  middleware warns like _add_middleware. Wired at the chat-client
  sites and the provider seam.
- ResponseStream.buffered_and_gated owns the re-derivation rule via a
  rederive callable (gates cannot choose released updates) and is
  marked experimental.
- Wire codecs compare with bool-aware equality (Python == equates
  1 == True, which made bool/number transforms look untouched and get
  dropped) and _ToolResultCodec.write_back owns the untouched-wire
  rule via the before value.
- middleware parameters accept a bare middleware or bundle everywhere
  the runtime does (constructors, run overloads, as_agent, telemetry
  and harness layers, foundry); the bare-source rule has a single
  owner in categorize_middleware; bare middleware assigned to the
  attribute now executes (documented behavior change).
- MiddlewareBundle is experimental and validates members; approval
  passthrough, typing-check fixes (ty ignores mypy-coded ignore
  comments), logging, and documentation updates per review.

Test count: 85 feature tests plus 12 new this round across sessions,
middleware, agents; full core suite green; typing checked under
mypy, pyrefly, ty, zuban, and pyright.

Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>

* docs(python): drop previous-behavior notes from middleware docstrings

Per review: docstrings describe current behavior only. The
bare-middleware behavior change stays recorded in the PR description
and commit history.

Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>

* fix(python): gate ownership survives retrying middleware

A retry or fallback middleware issuing a second call_next() gave the
new attempt a fresh run identity that the persistence gate's
first-bind-wins ownership rejected, so the retried attempt's history
persisted inline before the output verdict — a denied response became
durable again. The gate now accumulates every identity adopted
through its own offer ticket: all attempts' persistence stays behind
the one final verdict (deny drops all of it, allow flushes all of
it). Accumulation over rebind-replace is deliberate: rebinding would
flip an earlier attempt's still-running background work from deferred
to inline, which is the fail-open direction. A foreign agent still
cannot bind: tickets are minted only by the covered pipeline's final
handler and adoption is instance-keyed.

Also consolidates the bare-middleware-source rule into a single
_as_middleware_list owner used by every interpretation site (the
harness merge, BaseAgent.__init__, categorize_middleware, both
client-kwargs merges, get_response, SessionContext.extend_middleware),
including the str/bytes exclusion the stray copies missed. The
constructor now stores a copy of the caller's sequence; assign to the
middleware attribute for post-construction changes.

Retry regression tests cover denied and allowed retried runs in both
stream modes and fail with first-bind-wins restored.

Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>

* fix(python): streaming seam runs pipeline descent inside the gate

The streaming agent seam ran call_next() outside the persistence
gate (only _consume entered it later), so a retry middleware that
drained a successful attempt with get_final_response() and discarded
it persisted that attempt's exchange before any verdict existed; a
later deny dropped only the retry attempt's deferred work. The
descent is now wrapped in the gate exactly like the non-streaming
seam: attempt identities adopted during descent are accepted owners,
so in-pipeline draining defers, deny drops every attempt, and a
middleware that raises after draining strands the pending persists
unexecuted. The bind_owner docstring now states the actual soundness
invariant covering both bind sites: every bind comes from a run
inside the covered pipeline.

New tests cover drained-and-discarded attempts (deny and allow, both
stream modes) and a sub-agent tool inside a drained attempt; the
streaming deny variant fails with the gate wrap reverted.

Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>

* fix(python): flush deferred persistence on streaming no-result termination

With the pipeline descent now running inside the persistence gate, a
middleware that drains a successful attempt and then terminates
without a result left that attempt's deferred persistence stranded:
the streaming no-result termination path raised before any flush, so
history of exchanges that really happened and passed their own
verdicts quietly vanished (streaming only; non-streaming already
flushes before its re-raise). The path now flushes before re-raising
the termination, with a state.halted guard first so an enforcement
failure during the drained attempt still strands pending fail-closed
and surfaces the halt, mirroring the non-streaming ordering exactly.

The regression test covers both seams; the streaming variant fails
without the fix.

Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>

---------

Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>
2026-08-07 00:25:09 +00:00
westey 422160eabe Python: Add windows junction detection for skills (#7507)
* Add windows junction detection for skills

* Address PR comment
2026-08-06 09:08:37 +00:00
westey 5a1d96df67 Python: Separate mem0 storage and search scopes (#7531)
* Separate mem0 storage and search scopes

* Apply suggestions from code review

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-08-06 08:32:36 +00:00
Atharva Vichare 74a144085a .NET: Bound the tool-approval auto-approval loop (#7472) (#7474)
* .NET: Bound the tool-approval auto-approval loop (#7472)

`ToolApprovalAgent` re-invoked the inner agent from two unbounded `while (true)`
loops whenever every surfaced approval request was auto-approved. Each pass is a
fresh `InnerAgent.RunAsync` / `RunStreamingAsync` call, so a per-request cap such
as `FunctionInvokingChatClient.MaximumIterationsPerRequest` restarts every time
and cannot bound the chain. Under `AllToolsAutoApprovalRule` a model that keeps
requesting an auto-approved tool therefore drives billable model calls
indefinitely; the reporter measured 100M+ tokens over three days.

Adds `ToolApprovalAgentOptions.MaxAutoApprovalIterations` (default
`ToolApprovalAgent.DefaultMaxAutoApprovalIterations`, 10) and bounds both loops.
Naming, default and `Throw.IfLessThan` validation follow the existing
`LoopAgent.DefaultMaxIterations` / `LoopAgentOptions.MaxIterations` convention in
this assembly.

On reaching the cap the agent takes one final inner turn without auto-approving
again, so a remaining approval request is surfaced to the caller to decide.
Returning early instead would hand back an empty response, because
`ProcessAndQueueOutboundApprovalRequestsAsync` strips every approval request once
they are all auto-approved -- the case the loop exists to avoid. This mirrors the
Python behaviour, which logs and issues one final request with tools disabled
once its iteration budget is spent (`_tools.py`).

Python is not affected: it caps at `DEFAULT_MAX_ITERATIONS` (40) and persists
`attempt_count` in the budget state across approval resumes, so a resumed run
continues the count rather than restarting it.

Tests: the runaway is reproduced on both the streaming and non-streaming paths
with an inner agent that never stops requesting an auto-approved tool. Inner
invocations equal the cap plus the final turn, and scale with the configured cap,
so the assertions fail if the bound is removed.

No sample changes: with the loop bounded, Agent_Step01, Agent_Step06,
Agent_Step07 and Hosted-AgentSkills are safe as written.

* .NET: Add Arrange/Act/Assert comments to the cap constructor test

Matches the test convention documented in dotnet/AGENTS.md and used by the
surrounding tests in this file.

* Increase default max auto approval iterations to 40

* Apply suggestion from @westey-m

Co-authored-by: westey <164392973+westey-m@users.noreply.github.com>

* Update comments in ToolApprovalAgent.cs

---------

Co-authored-by: westey <164392973+westey-m@users.noreply.github.com>
2026-08-05 19:33:51 +00:00
Evan Mattson 594954700a Python: Fix AG-UI conversation correlation across runs (#7430)
* Add single agent AGUI sample

* Fix AG-UI conversation correlation across runs

* Address PR review and code quality feedback

* Correlate AG-UI chat spans across runs

---------

Co-authored-by: Tao Chen <taochen@microsoft.com>
2026-08-05 17:17:53 +00:00
SergeyMenshykh a4d4eafa5e Add CodeQL suppression comment for DevUI proxy validation (#7505)
The proxy target validation in ValidateProxyTarget already ensures
requests stay on the configured backend. Add an inline suppression
comment following the repo's established pattern.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 4363ab44-4d9e-41a0-97d3-4ab0b973f0b2
2026-08-05 11:33:41 +00:00
SergeyMenshykh da056275e6 .NET: [Experimental] Extend A2A task store with isolation key scoping (#7504)
* .NET: Add tenant-scoped task store isolation for A2A hosting

Wrap ITaskStore with IsolationKeyScopedTaskStore when a
SessionIsolationKeyProvider is registered, mirroring the existing
session store isolation pattern. This ensures task operations are
scoped per tenant in multi-user deployments.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: adc30d6c-ce66-40bb-933e-9801c2156cda

* fix formatting issue

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: adc30d6c-ce66-40bb-933e-9801c2156cda
2026-08-05 11:33:11 +00:00
Peter Ibekwe 1da571860a Updating version for dotnet release 1.17.0 (#7514) 2026-08-04 21:04:06 +00:00
Peter Ibekwe d56e81357e Fail declarative workflows when an agent returns an error (#7497) 2026-08-04 17:23:49 +00:00
Evan Mattson 5f3ca8f93c Python: Fix AG-UI approval resume at the protocol boundary (#7480)
* Python: Fix Ollama approval resume message handling

* Python: Reject empty Ollama approval resume payload

* Python: Keep AG-UI approval controls out of provider input

* Python: Do not trust pending AG-UI tool results
2026-08-04 15:12:33 +00:00
Scarab Systems 4d3c7844d6 Python: Bound tool result compaction summaries (#7396)
* Python: bound tool result compaction summaries

Keep ToolResultCompactionStrategy from re-inserting oversized tool result payloads through the synthetic summary message by bounding the generated digest text.

Add regression coverage proving a large tool result is not embedded verbatim, keeps a bounded prefix, and marks truncation.

* Python: keep excluded tool results out of compaction digests

Build ToolResultCompactionStrategy digest content from messages still included in the group so a summary cannot restore payloads that an earlier compaction already excluded.

Use the strategy cap constant in the large-payload regression and add coverage for already-excluded tool results.

Validation: uv run pytest packages/core/tests/core/test_compaction.py -q -k 'tool_result_compaction'; uv run ruff check packages/core/agent_framework/_compaction.py packages/core/tests/core/test_compaction.py; uv run ruff format --check packages/core/agent_framework/_compaction.py packages/core/tests/core/test_compaction.py; uv run poe test -P core; uv run poe build -P core; env HOME=/tmp/sds-home XDG_CACHE_HOME=/tmp/sds-cache uv run poe test -A.

* Python: align compaction digest review cleanup

Align ToolResultCompactionStrategy's included-message filter with the module's existing EXCLUDED_KEY boolean semantics.

Make the large-payload regression size scale from _SUMMARY_MAX_CHARS so it continues to exercise truncation if the digest cap changes.

Validation: uv run pytest packages/core/tests/core/test_compaction.py -q -k 'tool_result_compaction'; uv run ruff check packages/core/agent_framework/_compaction.py packages/core/tests/core/test_compaction.py; uv run ruff format --check packages/core/agent_framework/_compaction.py packages/core/tests/core/test_compaction.py; uv run poe test -P core; uv run poe build -P core.

* Python: collapse tool result digest scan

---------

Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com>
2026-08-04 15:05:02 +00:00
Eduard van Valkenburg 07511b80c9 Python: Prevent orphaned local approval responses (#7462)
* Python: Prevent orphaned local approval responses

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 0efaca91-a0a7-46f4-9b81-022385607fe4

* Python: Clarify approval serialization boundaries

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 0efaca91-a0a7-46f4-9b81-022385607fe4

---------

Copilot-Session: 0efaca91-a0a7-46f4-9b81-022385607fe4
2026-08-04 01:05:08 +00:00
CTW_CTWalk e84b5a07c1 Python: fix LocalEvaluator reporting zero-check items as passed (#7399)
LocalEvaluator.evaluate initialized item_passed to True and only ever
cleared it inside the loop over check results. With no checks configured
the loop never runs, so an item with zero scores was recorded as passed:
result_counts reported one pass, all_passed was True, and
raise_for_status() did not raise.

Initialize item_passed from bool(check_results) so an item with no
evaluated checks fails closed. This matches the .NET contract in this
repository, where AgentEvaluationResults.ItemPassed ends with
'return result.Metrics.Count > 0' and is pinned by
LocalEvaluator_WithZeroChecks_ItemsHaveZeroMetricsAndFailAsync.

Add a focused regression covering the counts, all_passed, the empty
score list, and raise_for_status(). Update the LocalEvaluator class and
evaluate() docstrings, which previously described the pass rule without
the zero-check case.

Fixes #7397
2026-08-04 00:23:08 +00:00
Evan Mattson 84d5a5eec1 Consolidate Dependabot dependency updates (#7445)
* Bump AgentMemory from 1.2.0 to 1.3.0

---
updated-dependencies:
- dependency-name: AgentMemory
  dependency-version: 1.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* .NET: consolidate #7280 AgentMemory.AgentFramework 1.3.0

* Bump github/codeql-action/init from 4.37.0 to 4.37.3

Bumps [github/codeql-action/init](https://github.com/github/codeql-action) from 4.37.0 to 4.37.3.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/99df26d4f13ea111d4ec1a7dddef6063f76b97e9...e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81)

---
updated-dependencies:
- dependency-name: github/codeql-action/init
  dependency-version: 4.37.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* Bump astral-sh/setup-uv from 8.3.2 to 9.0.0

Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 8.3.2 to 9.0.0.
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](https://github.com/astral-sh/setup-uv/compare/11f9893b081a58869d3b5fccaea48c9e9e46f990...c771a70e6277c0a99b617c7a806ffedaca235ff9)

---
updated-dependencies:
- dependency-name: astral-sh/setup-uv
  dependency-version: 9.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* Bump github/codeql-action/analyze from 4.37.0 to 4.37.3

Bumps [github/codeql-action/analyze](https://github.com/github/codeql-action) from 4.37.0 to 4.37.3.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/99df26d4f13ea111d4ec1a7dddef6063f76b97e9...e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81)

---
updated-dependencies:
- dependency-name: github/codeql-action/analyze
  dependency-version: 4.37.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* Bump actions/cache from 5.0.5 to 6.1.0

Bumps [actions/cache](https://github.com/actions/cache) from 5.0.5 to 6.1.0.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/27d5ce7f107fe9357f9df03efb73ab90386fccae...55cc8345863c7cc4c66a329aec7e433d2d1c52a9)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: 6.1.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* Bump actions/checkout from 6.0.2 to 7.0.1

Bumps [actions/checkout](https://github.com/actions/checkout) from 6.0.2 to 7.0.1.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/de0fac2e4500dabe0009e67214ff5f5447ce83dd...3d3c42e5aac5ba805825da76410c181273ba90b1)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 7.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* Bump astral-sh/setup-uv in /.github/actions/python-setup

Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 8.3.2 to 9.0.0.
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](https://github.com/astral-sh/setup-uv/compare/11f9893b081a58869d3b5fccaea48c9e9e46f990...c771a70e6277c0a99b617c7a806ffedaca235ff9)

---
updated-dependencies:
- dependency-name: astral-sh/setup-uv
  dependency-version: 9.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* Bump ty from 0.0.60 to 0.0.64 in /python

Bumps [ty](https://github.com/astral-sh/ty) from 0.0.60 to 0.0.64.
- [Release notes](https://github.com/astral-sh/ty/releases)
- [Changelog](https://github.com/astral-sh/ty/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ty/compare/0.0.60...0.0.64)

---
updated-dependencies:
- dependency-name: ty
  dependency-version: 0.0.65
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* Bump prek from 0.4.10 to 0.4.11 in /python

Bumps [prek](https://github.com/j178/prek) from 0.4.10 to 0.4.11.
- [Release notes](https://github.com/j178/prek/releases)
- [Changelog](https://github.com/j178/prek/blob/master/CHANGELOG.md)
- [Commits](https://github.com/j178/prek/compare/v0.4.10...v0.4.11)

---
updated-dependencies:
- dependency-name: prek
  dependency-version: 0.4.11
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* Bump uv from 0.11.29 to 0.11.32 in /python

Bumps [uv](https://github.com/astral-sh/uv) from 0.11.29 to 0.11.32.
- [Release notes](https://github.com/astral-sh/uv/releases)
- [Changelog](https://github.com/astral-sh/uv/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/uv/compare/0.11.29...0.11.32)

---
updated-dependencies:
- dependency-name: uv
  dependency-version: 0.12.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* Bump ruff from 0.15.22 to 0.16.0 in /python

Bumps [ruff](https://github.com/astral-sh/ruff) from 0.15.22 to 0.16.0.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.15.22...0.16.0)

---
updated-dependencies:
- dependency-name: ruff
  dependency-version: 0.16.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* update uv-build requirement in /python

---
updated-dependencies:
- dependency-name: uv-build
  dependency-version: 0.12.0
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>

* Python: align workspace pins for #7436-#7439

* Python: support ty 0.0.64 diagnostics for #7436

* Python: apply Ruff 0.16 formatting for #7439

* Update workflow action version annotations

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-04 00:01:14 +00:00
Tao Chen 8d379168b2 Python: Improve python sample validation workflow (#7350)
* Add skill to replace hardcoded foundry project endpoint and model

* Include more samples and fix migration samples part 1

* Fix migration samples

* Replace Foundry hosted agent validation skill

* Fix hosted agent file sample

* Fix agent result format

* Reorganize jobs

* Update discovery heuristic for apps

* Split agents into even more jobs

* Add toolbox endpoint

* Add more pre configured resources

* Fix using deployed agent sample

* Add sample status

* Add playbook

* Exclude hidden folder in sample discovery

* Install autogen dependencies

* Grant azure search RBAC role

* Increase timeout for magentic

* Build search resouce id deterministically

* Remove grant in the workflow

* Move azure cli login closer to when the sample actually runs

* Refactor playbook

* Fix using deployed agent sample

* Actually save the playbooks

* Fix action syntax error

* Fix magentic sample

* Address copilot comments

* Fix link inspection

* Address comments

* Correct README

* Fix playbook path

* Remove trailing space
2026-08-03 22:28:53 +00:00
Evan Mattson 18997c2fde Python: Give the AG-UI Thread Snapshot lifecycle a single owner module (#7479)
* Python: Give the AG-UI Thread Snapshot lifecycle a single owner module

Both the agent and workflow runners independently implemented the thread
snapshot lifecycle: hydration replay, the load-once stored read, resume
message seeding, the stored/request/deferred-default state overlay, and
the save whose storage failures must never surface on an already-streamed
run. The two copies had already drifted in small ways (one hydrate helper
re-checked a store the caller had verified; the two cancelled-resume-id
helpers differed on missing-id handling).

Introduce ThreadSnapshotSession in _snapshot_session.py as the one owner
of that lifecycle, opened once per run and inert when no store or scope
is configured so callers stop branching on configuration. Rewire both
runners onto it, consolidate _cancelled_resume_interrupt_ids in
_run_common (defensive variant) and _event_messages_to_snapshot_dicts in
the new module, and delete the superseded per-runner copies. The session
interface is covered by dedicated tests; existing suites pin runner
behavior. Public exports are unchanged.

* Python: Narrow AG-UI event types in snapshot session tests

The hydration test accessed run_id, snapshot, and messages on values
typed as BaseEvent, which fails the tests/samples type checkers. Narrow
each event with isinstance assertions before reading its fields.
2026-08-03 21:15:39 +00:00
Vaibhav Patel 5cc1b8e3c3 Python: Add hosted agent sample for the agent harness (#7010)
* Python: Add hosted agent sample for the agent harness

* Disable file providers and fix call_server usage in hosted harness sample

Addresses PR review: disable the harness file-memory and file-access
providers so the headless sample doesn't expose file tools or write
outside storage/, and correct the app.py docstring to match
call_server.py (which takes no prompt argument).

* Python: update hosted harness sample for current APIs

---------

Co-authored-by: Evan Mattson <evan.mattson@microsoft.com>
2026-08-03 20:55:47 +00:00
Evan Mattson 9ce55cae00 Python: Remove dead AG-UI orchestration helpers and flatten subpackage (#7426)
The _orchestration/_helpers module had no production callers; its only
importer was its own test file. It also carried a stale fork of the live
metadata sanitization in _agent_run.py: the dead copy truncated oversized
values, behavior the live copy deliberately replaced with drop-plus-warning
because truncation can produce invalid JSON.

Move _tooling.py and _predictive_state.py to the package root and remove
the now-empty _orchestration subpackage. Public exports are unchanged.
2026-08-03 20:46:05 +00:00
Evan Mattson 06c0fc2b10 Python: forward Azure AI Search query-source identity (#7278)
* Forward Azure AI Search query-source identity

* Address query source credential review feedback
2026-08-03 20:45:40 +00:00
Henry Su a74811edec fix(python): preserve falsey EditTableV2 items (#7380) 2026-08-03 16:54:29 +00:00
Peter Ibekwe 9c8151699a Fix Handoff orchestration sample not responding to user input (#7442) 2026-08-03 16:40:16 +00:00
NekoPunch f5dfb1413e Python: Add Mistral chat client (#7392)
* feat(python): add Mistral chat client

Implements native Mistral support (#7366) with streaming, tool calling,
and structured output. Talks to the REST API directly over httpx: the
mistralai SDK's pinned OpenTelemetry deps conflict with the workspace.

* refactor(python): simplify Mistral client per review

Drop the streamed tool-call accumulator and multi-choice parsing in
favor of the framework's built-in fragment merging, mark n unsupported,
omit unset strict from json_schema, and leave CI secret wiring to
maintainers.

* test(python): drop n forwarding assertion

n is typed as unsupported on MistralChatOptions; the option-mapping test
still passed n, failing pyrefly/ty/zuban/mypy in CI.

* refactor(python): drop n from MistralChatOptions

n is not part of the base ChatOptions, so removing the key rejects it
without an explicit None override.

* feat(python): mark Mistral feature usage

Both clients flip the shared FeatureIndex.MISTRAL bit before each
request, matching the feature-usage telemetry other providers emit.

* fix(python): key streamed tool calls by index

Mistral omits the tool call id on continuation fragments, and the
framework only coalesces empty-id fragments into the immediately
preceding call, so interleaved parallel calls merged into the wrong
call with corrupted arguments. Accumulate fragments per (choice,
index) and emit each call only once complete.

* fix(python): restore Mistral SDK client injection

Dropping the mistralai dependency turned the embedding client's
client= parameter into a breaking change for injected SDK clients.
Add http_client= for httpx.AsyncClient and keep client= working:
httpx goes to the REST path, a duck-typed mistralai.Mistral goes
through the legacy SDK path with a DeprecationWarning until the
next major release.

* chore(python): tidy Mistral sample header
2026-08-03 06:29:09 +00:00
Chris Gillum 43309018be .NET and Python: Extract Durable Task and Azure Functions integrations (#7465)
* Extract Durable Task and Azure Functions integrations

Remove the migrated implementations, samples, tests, documentation, and repository wiring now owned by microsoft/agent-framework-durable-extension. Preserve Python compatibility through the agent_framework.azure shim and agent-framework-core[all], and leave customer-facing redirects to the new repository.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 6181dcf9-857b-43ea-9fd2-fcd6b175ffdd

* Fix feature registry validation after extraction

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 6181dcf9-857b-43ea-9fd2-fcd6b175ffdd

* Narrow external feature package paths

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 6181dcf9-857b-43ea-9fd2-fcd6b175ffdd

---------

Copilot-Session: 6181dcf9-857b-43ea-9fd2-fcd6b175ffdd
2026-08-03 03:02:07 +00:00
Joshua Nwachinemere 10fe3c4c72 Python: Ignore excluded tool results during compaction (#7391)
* Python: Ignore excluded tool results during compaction

* fix: avoid extra compaction message pass
2026-08-03 01:38:21 +00:00
Chris Gillum c073ed9f74 docs: ADR-0032 — propose durable/Azure Functions repo extraction (#7247)
* docs: add ADR-0032 proposing durable/Azure Functions repo extraction

Proposes extracting the Durable Task and Azure Functions hosting integrations into a dedicated repository (microsoft/agent-framework-durable-extension), keeping a backward-compatible shim and the [all] extra so the move is invisible to consumers. Status: proposed, for stakeholder signoff ahead of the code-removal PR.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 6181dcf9-857b-43ea-9fd2-fcd6b175ffdd

* Fix GitHub user handles

* docs: generalize publish-lag example in ADR-0032

Replace the WorkflowHitlContext-specific illustration with a generic description of the publish-lag mechanism. The named symbol is currently exported by the extension and present in core's shim, so using it as an 'unpublished' example read as internally inconsistent.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 6181dcf9-857b-43ea-9fd2-fcd6b175ffdd

* Add note about issue transfers

* Updates to ADR based on offline discussion

---------

Copilot-Session: 6181dcf9-857b-43ea-9fd2-fcd6b175ffdd
2026-07-31 19:43:26 +00:00
Giles Odigwe e39a8a2e79 Python: Bump Python package versions for 1.13.0 release (#7443)
CodeQL / Analyze (csharp) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
dotnet-build-and-test / paths-filter (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test-check (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Debug, windows-latest, net9.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net8.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-foundry-hosted-it (push) Has been cancelled
dotnet-build-and-test / dotnet-test-functions (push) Has been cancelled
dotnet-build-and-test / Integration Test Report (push) Has been cancelled
* Bump Python package versions for 1.13.0 release

Bump all 37 Python package projects because the CHANGELOG-driven release includes cross-package feature-usage telemetry, with core and root advancing to 1.13.0, OpenAI to 1.12.0, patch bumps for other stable packages, and 260730 stamps for alpha and beta packages. No optional beta cohort bump was applied; every prerelease package changed. Raise core floors conservatively across co-released packages.

Copilot-Session: e234a28b-c2fd-4ff4-a51d-3d8917936541

* Align co-released Python package dependencies

Update the four hosting adapter pins to the co-released agent-framework-hosting alpha and raise the Azure Functions Durable Task floor to the co-released beta.

Copilot-Session: e234a28b-c2fd-4ff4-a51d-3d8917936541

* Minimize Python release lockfile updates

Regenerate uv.lock with the pre-commit hook pinned uv version so the release changes only workspace package versions while preserving platform markers and agentlightning 0.3.0.

Copilot-Session: e234a28b-c2fd-4ff4-a51d-3d8917936541

---------

Copilot-Session: e234a28b-c2fd-4ff4-a51d-3d8917936541
2026-07-30 22:47:07 +00:00
Giles Odigwe 25ec4c3b5c Python: Support archive-type MCP skills (source, toolbox, sample) (#7121)
* Python: Support archive-type MCP skills in MCPSkillsSource

Add `archive`-type skill support to `MCPSkillsSource` so an MCP server can
advertise packaged skills (ZIP / TAR / gzip-compressed TAR) that are
downloaded, safely unpacked to a local directory, and served like file-based
skills, while keeping the guarantee that MCP-delivered scripts are never
executed.

- Dispatch `skill://index.json` entries by `type`: `skill-md` (existing,
  fetched on demand) and `archive` (new). Unknown types are skipped.
- `_ArchiveEntryLoader` downloads, extracts, and prunes archive skills and
  delegates discovery to an internal `FileSkillsSource` created with no
  script extensions and no runner, so bundled scripts surface as read-only
  resources only.
- Hardened stdlib extraction: path-traversal (zip-slip) guard, non-regular
  TAR member skipping, and file-count / uncompressed-size / download-size
  limits.
- Configure via `archive_*` constructor kwargs (no options object, per Python
  conventions); use `CachingSkillsSource` for refresh rather than a source
  level refresh interval.
- Fix `FileSkillsSource` to treat `None` extensions as "use defaults" and an
  empty tuple as "discover none" (an empty tuple previously fell back to
  defaults).

Port of .NET PR #6631.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9e358a4e-538f-46be-8c58-128b6182352d

* Propagate non-not-found archive download errors in MCPSkillsSource

Only swallow "resource not found" MCP errors when downloading an archive
resource; re-raise every other error (auth failure, INTERNAL_ERROR,
connection drop, timeout) so a transient transport failure is not silently
turned into a missing skill. This matches the existing failure model used by
`_try_read_index` and `MCPSkill.get_resource`, and avoids a failed
`CachingSkillsSource` refresh overwriting a previously cached list with a
partial result.

Add tests asserting archive-download INTERNAL_ERROR and ConnectionError
propagate out of `get_skills`.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9e358a4e-538f-46be-8c58-128b6182352d

* Python: Expose archive skill options on FoundryToolbox and demo in sample

- FoundryToolbox.as_skills_provider() now forwards the MCPSkillsSource archive
  options (archive_skills_directory, archive_resource_extensions,
  archive_resource_search_depth, archive_max_file_count, archive_max_size_bytes,
  archive_max_uncompressed_size_bytes). Only explicitly-set options are
  forwarded so unset ones keep the MCPSkillsSource defaults. This lets a hosted
  toolbox agent redirect archive extraction to a writable directory (the default
  is under the cwd, which may be read-only in a container).
- Add unit tests covering default (no options forwarded) and override forwarding.
- Update the 12_foundry_toolbox_mcp_skills sample to demonstrate all three
  progressive-disclosure stages with an archive skill: escalation-policy now
  ships a references/refund-matrix.md resource and is uploaded as a ZIP archive;
  main.py disables load_skill and read_skill_resource approval and points
  archive extraction at a temp directory. README, toolbox.yaml, and ignore files
  updated accordingly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 4f14f83d-1868-45c1-be1a-12f49a58ac36

* Python: Fix ty type error in toolbox archive-option test

Cast provider._source to _FoundryToolboxSkillsSource before accessing the
private _archive_options, so the ty checker (which runs over tests) resolves
the concrete type instead of the SkillsSource base. Replaces the mypy-style
type: ignore that ty did not honor.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 4f14f83d-1868-45c1-be1a-12f49a58ac36

* Rework archive-type skill support in MCPSkillsSource to unpack archives
entirely in memory instead of extracting them to a local directory, and
apply reviewer feedback.

* Python: Raise on archive member path-traversal (zip-slip)

Treat a `..` path-traversal member in an archive skill as a hostile archive
and reject the whole skill, matching how the file-count and uncompressed-size
limits reject a malformed archive (previously the member was silently skipped
while the rest of the skill still loaded).

- `_normalize_archive_member_name` now raises `ValueError` on a `..` escape;
  benign degenerate entries (empty, `.`, `/`) still return None (skipped) and
  absolute paths are still neutralized to relative. The raise propagates to
  `_ArchiveEntryLoader._build_skill`, which already skips the skill on error.
- Update tests: traversal cases now assert a raise, and add an end-to-end test
  that a zip-slip archive drops the whole skill.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9e358a4e-538f-46be-8c58-128b6182352d

* Python: Revert archive skill demo in toolbox MCP skills sample

Restore the 12_foundry_toolbox_mcp_skills sample to its pre-PR, skill-md-only
form (matching the .NET Agent_Step26_FoundryToolboxMcpSkills sample, which uses
skill-md and no ZIP archive):

- Revert main.py, toolbox.yaml, README.md, .azdignore, .dockerignore, and
  escalation-policy/SKILL.md to the single-file SKILL.md version.
- Remove the archive demo files added by this PR (.gitignore and
  escalation-policy/references/refund-matrix.md).
- Soften two README notes so they no longer claim archive skills are
  unsupported/silently dropped (this PR adds archive support); instead frame
  single-file SKILL.md as a focus choice and point to the archive_* options.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9e358a4e-538f-46be-8c58-128b6182352d

* Python: Clarify archive framing in mcp_based_skill sample README

The mcp_based_skill sample is a generic MCP consumer that discovers whatever
the server advertises; it does not itself demonstrate archive skills. Reword
the archive note so it reads as an MCPSkillsSource capability rather than a
sample feature, and fix the stale "unpacked to a local directory" claim to
"unpacked in memory" (matching the in-memory extraction implementation).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9e358a4e-538f-46be-8c58-128b6182352d

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9e358a4e-538f-46be-8c58-128b6182352d
Copilot-Session: 4f14f83d-1868-45c1-be1a-12f49a58ac36
2026-07-30 20:57:51 +00:00
SergeyMenshykh 3ad861f0b2 reference code of conduct in readme (#4998)
Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com>
2026-07-30 18:39:05 +00:00
SergeyMenshykh 2aa267e028 .NET: Updating version for dotnet release 1.16.0 (#7441)
Co-authored-by: SergeyMenshykh <SergeMenshikh@outlook.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 28ce674d-8c40-4d49-864c-5d02894fe762
2026-07-30 16:36:36 +00:00
Peter Ibekwe 6a3d535204 .NET: Add regression tests and sample guidance for stable agent IDs in checkpointed workflows (#7415)
* Add regression tests and sample guidance for stable agent IDs in checkpointed workflows

* Updated tests to address PR comments

* Improve test for checkpoint state.
2026-07-30 16:21:18 +00:00
westey 73f48d255e .NET: Add FileMemoryProvider sample to 02-agents/AgentWithMemory (#7401)
* Add FileMemoryProvider sample

* Address PR comments
2026-07-30 15:26:51 +00:00
Eduard van Valkenburg 28389df805 Python: Move SessionStore to core and persist Foundry Responses sessions (#7306)
* Python: Move session persistence into core

Move SessionStore and durable msgspec-backed storage into core, restore sessions in Foundry Responses hosting with per-user isolation, and document the serialization design.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3e5c81ad-75e8-4e92-a883-8bbd676c6c8c

* Python: Address session persistence review feedback

Harden scoped file paths and corruption recovery, preserve session serialization compatibility, clarify dependency placement, and add reproducible benchmark evidence.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3e5c81ad-75e8-4e92-a883-8bbd676c6c8c

* Python: Preserve session snapshot compatibility

Deep-copy in-memory session writes and retain existing Telegram session keys so stored conversations continue resolving.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3e5c81ad-75e8-4e92-a883-8bbd676c6c8c

* Python: Simplify Foundry session isolation

Add experimental FoundrySessionStore backed by Agent Server request context, remove resolver plumbing, and centralize v2 user isolation for sessions, checkpoints, and approvals.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3e5c81ad-75e8-4e92-a883-8bbd676c6c8c

* Python: Reduce Foundry session helper layering

Inline the single-use request user accessor while keeping separate context validation, fingerprint, and directory helpers for their distinct callers.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3e5c81ad-75e8-4e92-a883-8bbd676c6c8c

* Python: Clarify Foundry request context validation

Separate fail-fast request validation from context retrieval so Responses no longer appears to discard a returned context.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3e5c81ad-75e8-4e92-a883-8bbd676c6c8c

* Python: Share Foundry request context helpers

Move protocol validation and user-scope derivation into a dedicated request-context module, leaving the session-store module focused on storage.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3e5c81ad-75e8-4e92-a883-8bbd676c6c8c

* Restore Foundry checkpoint storage paths

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3e5c81ad-75e8-4e92-a883-8bbd676c6c8c

* Simplify Foundry session storage paths

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3e5c81ad-75e8-4e92-a883-8bbd676c6c8c

* Persist Foundry sessions under hosted home

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3e5c81ad-75e8-4e92-a883-8bbd676c6c8c

* Make hosted path test platform independent

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3e5c81ad-75e8-4e92-a883-8bbd676c6c8c

* Address session persistence review feedback

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3e5c81ad-75e8-4e92-a883-8bbd676c6c8c

* Isolate Foundry session path handling

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3e5c81ad-75e8-4e92-a883-8bbd676c6c8c

* Clarify Foundry session path terminology

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3e5c81ad-75e8-4e92-a883-8bbd676c6c8c

* Align Foundry sessions with Responses continuity

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3e5c81ad-75e8-4e92-a883-8bbd676c6c8c

* Finalize Foundry Responses session persistence

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3e5c81ad-75e8-4e92-a883-8bbd676c6c8c

* Add session store feature usage telemetry

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3e5c81ad-75e8-4e92-a883-8bbd676c6c8c

* Fix hosted per-call history persistence

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3e5c81ad-75e8-4e92-a883-8bbd676c6c8c

---------

Copilot-Session: 3e5c81ad-75e8-4e92-a883-8bbd676c6c8c
2026-07-30 13:04:08 +00:00
HaoJun 143386fecc Python: fix(core): restrict unpickler module-prefix allowlist to types only (#5923)
* fix(core): harden restricted pickle attribute resolution

* fix(core): validate nested pickle types against allowlist

---------

Co-authored-by: White-Mouse <15983334+White-Mouse@users.noreply.github.com>
Co-authored-by: Evan Mattson <evan.mattson@microsoft.com>
Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
2026-07-30 10:46:15 +00:00
westey 47c8e29b64 Python: Add FileMemoryProvider context provider sample (#7428)
* Add FileMemoryProvider sample

* Address PR comments
2026-07-30 10:46:11 +00:00
Evan Mattson 51615d5468 Fix DevFlow review comment trigger (#7434)
Use the exact /review command without mentioning an unrelated GitHub user account.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: d9fa4e9c-c32d-42fb-8ee4-4772473e6479
2026-07-30 19:41:21 +09:00
Giles Odigwe 12b2893bac Python: Apply header_provider headers to the MCP initialize handshake and other ambient requests (#7305)
* Python: Apply header_provider headers to ambient MCP requests

MCPStreamableHTTPTool.header_provider was only invoked from call_tool(),
so the initialize handshake, load_tools/load_prompts discovery, and
background pings all went out with no headers. MCP servers that require
auth on initialize (e.g. Azure AI Search knowledge-base MCP endpoints)
therefore returned 401 before any tool call could run.

Add an ambient fallback in the _inject_headers httpx request hook: when
neither the per-call ContextVar nor the active-call snapshot is set, the
hook invokes header_provider({}) so every ambient request is
authenticated. Providers that require per-call kwargs raise on the empty
dict; that is caught, logged, and the request proceeds unauthenticated,
preserving prior behavior. Calling the provider on demand also keeps
dynamic token refresh working for post-connect requests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cf0c1dbf-99bc-4f3f-bcf4-7791ce7dbe6a

* Python: address review - distinguish unset vs empty headers, warn once

Review feedback on the ambient header_provider fallback:

- Distinguish 'unset' (no active call) from 'set but empty' (call_tool
  produced no headers). Use _mcp_call_headers.get(None) and the None-ness
  of the snapshot instead of a truthiness check, so a provider that
  legitimately returns {} during a real call is no longer re-invoked by
  the ambient fallback mid-call.
- A kwargs-dependent provider raises on every ambient request (initialize,
  discovery, recurring pings). Warn once per tool instance with a
  traceback via _ambient_header_warning_emitted and drop subsequent
  occurrences to DEBUG to avoid log spam.

Add regression tests for both behaviors.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cf0c1dbf-99bc-4f3f-bcf4-7791ce7dbe6a

* Python: narrow ambient header_provider catch to KeyError

Only the missing-per-call-kwargs case (KeyError, e.g. the
mcp_api_key_auth.py sample indexing kwargs['mcp_api_key']) is tolerated
during ambient requests. Any other exception - a token-refresh failure
or a provider bug - now propagates instead of being silently converted
into unauthenticated traffic, matching the call_tool path which does not
catch header_provider exceptions.

Add a regression test asserting a non-KeyError provider failure
surfaces from the request hook.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cf0c1dbf-99bc-4f3f-bcf4-7791ce7dbe6a

* Python: address review - raise instead of assert, simplify ambient logging

- Reword the ambient-fallback comment to describe the kwargs-dependent
  provider pattern generically instead of naming a sample file, which
  would go stale if the sample is renamed (also in a test docstring).
- Replace the type-narrowing assert with a RuntimeError carrying a
  concise message for the unreachable no-provider state.
- Drop the warn-once/_ambient_header_warning_emitted machinery; the
  KeyError ambient case is expected and benign, so log a single DEBUG
  line and proceed without headers.

Update the corresponding test to assert behavior (request proceeds
without an Authorization header and no WARNING is emitted) instead of
log-count.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cf0c1dbf-99bc-4f3f-bcf4-7791ce7dbe6a

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
Copilot-Session: cf0c1dbf-99bc-4f3f-bcf4-7791ce7dbe6a
2026-07-30 10:31:11 +00:00
Yufeng He 93e8cb2de3 Python: make SerializationMixin.from_dict enforce the documented type check (#7256)
from_dict resolved the expected type identifier from the payload itself
(_get_type_identifier(value) prefers value["type"]), so the mismatch
guard could never fire: any supplied 'type' matched itself, and a payload
like {"type": "function_tool", ...} silently deserialized into a Message,
getting its type rewritten on the next to_dict. The docstring has always
promised a ValueError on mismatch.

Resolve the identifier from the class instead, matching what to_dict
emits, so a mismatched or foreign 'type' now raises as documented.
Payloads without a 'type' field and dependency-injection lookups are
unchanged: in every previously valid case the class-resolved identifier
is the same string the payload carried.

Fixes #7255

Signed-off-by: Yufeng He <40085740+he-yufeng@users.noreply.github.com>
Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com>
2026-07-30 10:28:27 +00:00
Eduard van Valkenburg b64a2e2f82 Python: add feature-usage User-Agent telemetry (#7420)
* Python: add first-pass feature usage telemetry

Add the 128-bit feature accumulator, package-local indexes, activation markers, and destination-scoped User-Agent emission for the initial Python implementation slice.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 346bf168-b668-4c4a-a8db-a67282ee5e5f

* Python: track declarative feature usage

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 346bf168-b668-4c4a-a8db-a67282ee5e5f

* Python: complete feature usage telemetry

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 346bf168-b668-4c4a-a8db-a67282ee5e5f

* Python: report core version in User-Agent

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 346bf168-b668-4c4a-a8db-a67282ee5e5f

* Python: configure Lab telemetry import path

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 346bf168-b668-4c4a-a8db-a67282ee5e5f

* Python: preserve telemetry transport behavior

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 346bf168-b668-4c4a-a8db-a67282ee5e5f

* Python: preserve caller-owned Foundry transports

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 346bf168-b668-4c4a-a8db-a67282ee5e5f

* Python: remove stale Anthropic test import

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 346bf168-b668-4c4a-a8db-a67282ee5e5f

---------

Copilot-Session: 346bf168-b668-4c4a-a8db-a67282ee5e5f
2026-07-30 10:24:34 +00:00
Yufeng He 962b86ddbb Python: preserve model emission order in AG-UI MESSAGES_SNAPSHOT (#7239)
* Preserve model emission order in AG-UI messages snapshot

* Address moonbox3's review: cover the remaining snapshot gaps

- Preopened message ids (tool-only path) now open a text segment when
  the first text arrives, so their content can't drop out of the snapshot.
- A tool result closes the current tool-call segment, so
  call A -> result A -> call B snapshots as two pairs in stream order.
- emitted_call_ids only marks calls actually emitted, keeping stale
  segment ids eligible for the leftover fallback.
- The leftover path carries its tool results too instead of dropping them.

* Python: narrow leftover tool-call ids so pyright accepts the update

---------

Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
2026-07-30 10:19:40 +00:00
Tao Chen 80eb2570c7 Remove indices in FHA sample names (#7405) 2026-07-30 10:17:26 +00:00
Dineshsuriya D d42c78c8cf Python: Add GitHub Copilot BYOK sample (#7336)
* Python: Add GitHub Copilot BYOK sample

Demonstrates routing GitHubCopilotAgent requests through a custom OpenAI-compatible
endpoint via ProviderConfig instead of the default GitHub Copilot backend.

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Python: Address BYOK sample review feedback

- Make the provider type configurable via BYOK_PROVIDER_TYPE (default "openai") instead
  of hardcoding "openai" — a partial autofix commit had already updated the docstring to
  document this env var but left the code hardcoded, which this finishes.
- Stop calling the endpoint "OpenAI-compatible" everywhere; Anthropic isn't OpenAI-wire-
  compatible, so reword to "your own endpoint" and list the actual supported providers
  (mirrors the equivalent .NET sample fix).

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-30 10:15:17 +00:00
dependabot[bot] d9e1990484 Bump postcss (#7315)
Bumps [postcss](https://github.com/postcss/postcss) from 8.5.15 to 8.5.23.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.5.15...8.5.23)

---
updated-dependencies:
- dependency-name: postcss
  dependency-version: 8.5.23
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-30 10:06:27 +00:00
dependabot[bot] fa7cc021c6 Bump postcss (#7314)
Bumps [postcss](https://github.com/postcss/postcss) from 8.5.15 to 8.5.23.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.5.15...8.5.23)

---
updated-dependencies:
- dependency-name: postcss
  dependency-version: 8.5.23
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-30 10:06:05 +00:00
Thota Sai Karthik 4d67eefa5f Python: Fix FoundryAgent inheriting OPENAI_CHAT_MODEL for agent-reference requests (#7283)
* Python: Fix FoundryAgent inheriting OPENAI_CHAT_MODEL for agent-reference requests (#7272)

* Python: Fix FoundryAgent inheriting OPENAI_CHAT_MODEL for agent-reference requests

* fix(foundry): update test typing annotations to pass mypy, pyrefly, and ty

---------

Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com>
2026-07-30 10:05:33 +00:00
Scarab Systems 32928e645b Python: Bound summarization input before provider call (#7375)
* Bound summarization input before provider call

SummarizationStrategy now selects complete message groups that fit a configurable summary input token budget before calling the summary client. Only messages actually sent to the summarizer are annotated and excluded, leaving oversized later groups for a later compaction pass instead of shipping the whole transcript unbounded.

Validation: uv run pytest packages/core/tests/core/test_compaction.py -k bounds_summary_input -m "not integration" failed before the implementation and passed after it; uv run pytest packages/core/tests/core/test_compaction.py -m "not integration" passed; uv run poe test -P core passed; uv run poe install completed; uv run poe check -P core passed.

* Handle oversized leading summary groups

Skip individually over-budget leading groups when selecting summarization input so a large early transcript item does not prevent later compactable groups from being summarized.

Validation: uv run pytest packages/core/tests/core/test_compaction.py -k skips_oversized_first_group -q; uv run pytest packages/core/tests/core/test_compaction.py -q; uv run poe check -P core.

* Escalate repeated summary failures

Track consecutive SummarizationStrategy failures and emit a single error once the strategy has failed three times without a successful summary. Reset the escalation state after a successful summary so only persistent failures become loud.

Validation: uv run pytest packages/core/tests/core/test_compaction.py -k 'repeated_summary_failures or resets_failure_escalation' -q; uv run pytest packages/core/tests/core/test_compaction.py -q; uv run poe check -P core.

* Refine summary input selection

Avoid rebuilding and re-tokenizing the full selected summary transcript on every candidate group while preserving complete-group selection and oversized leading group skipping.

Tighten the scripted summarizer test helper to expected Exception failures instead of BaseException.

Verification: uv run pytest packages/core/tests/core/test_compaction.py -q; uv run poe syntax -P core.

---------

Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com>
2026-07-30 10:01:57 +00:00
Evan Mattson d07edffaed Python: Fix Actions token environment (#7427)
* Fix Copilot Actions token environment

Expose workflow tokens through GITHUB_TOKEN so Copilot CLI uses native Actions authentication, while preserving user-token integration test support.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: d9fa4e9c-c32d-42fb-8ee4-4772473e6479

* Gate Copilot integration tests explicitly

Use GitHub Actions authentication only when both GITHUB_ACTIONS and GITHUB_TOKEN are present, and require an explicit local opt-in that relies on stored Copilot login.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: d9fa4e9c-c32d-42fb-8ee4-4772473e6479

---------

Copilot-Session: d9fa4e9c-c32d-42fb-8ee4-4772473e6479
2026-07-30 18:57:23 +09:00
Nadjib Attig 59fe8bedeb Python: Sanitize author_name for the Chat Completions message name field (#7127)
OpenAI validates the Chat Completions message 'name' against
^[^\s<|\/>]+$, so an agent display name containing a space (or
< | \ / >) failed every request with a 400. Sanitize at the three
assignment sites, mirroring SanitizeAuthorName in the .NET client
(dotnet/extensions): remove characters outside [a-zA-Z0-9_], omit the
name when nothing remains, truncate to 64 characters.

Fixes #7126

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 09:47:57 +00:00
Yufeng He 31d6af1447 Python: fix Anthropic streaming double-counting token usage (#7162)
* Python: fix Anthropic streaming double-counting token usage

* Python: address review on the Anthropic usage increment helper

- accumulate the emitted totals in a plain dict instead of string-cast
  TypedDict views, so static checkers see real types throughout
- compute the increment through _types.add_usage_details with negated
  emitted totals instead of a hand-rolled subtraction loop; keys absent
  from a snapshot stay untouched, matching the partial-delta semantics
2026-07-30 09:45:21 +00:00
Chinedum Echeta ae6923c8b1 Python: feat(observability): add support for OpenAI cache write tokens in usage details (#7369)
* feat(observability): add support for OpenAI cache write tokens in usage details

* feat(openai): add cache write tokens handling in usage details

* Fix test
2026-07-30 09:12:30 +00:00
Eduard van Valkenburg 99dcf3c133 Python: Preserve declaration-only streaming metadata (#7409)
* Python: Preserve declaration-only streaming metadata

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 1ee1d250-d1e2-4c6f-8c36-aae0d94fe7a1

* Chore: retrigger PR checks

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1ee1d250-d1e2-4c6f-8c36-aae0d94fe7a1

* Python: Reconcile remaining function-loop spec gaps

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1ee1d250-d1e2-4c6f-8c36-aae0d94fe7a1

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1ee1d250-d1e2-4c6f-8c36-aae0d94fe7a1
2026-07-30 09:11:51 +00:00
Eduard van Valkenburg 95ec5b7d36 Python: Preserve approval decisions under OpenAI continuation (#7407)
* Python: Preserve approval decisions under OpenAI continuation

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 1ee1d250-d1e2-4c6f-8c36-aae0d94fe7a1

* Chore: retrigger PR checks

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1ee1d250-d1e2-4c6f-8c36-aae0d94fe7a1

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1ee1d250-d1e2-4c6f-8c36-aae0d94fe7a1
2026-07-30 08:29:05 +00:00
Eduard van Valkenburg 0937233d86 Python: Remove tool content returned after invocation limits (#7408)
* Python: Remove tool content returned after invocation limits

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 1ee1d250-d1e2-4c6f-8c36-aae0d94fe7a1

* Chore: retrigger PR checks

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1ee1d250-d1e2-4c6f-8c36-aae0d94fe7a1

* Python: Preserve provider-owned content after limits

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1ee1d250-d1e2-4c6f-8c36-aae0d94fe7a1

* Python: Isolate post-limit spec update

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1ee1d250-d1e2-4c6f-8c36-aae0d94fe7a1

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1ee1d250-d1e2-4c6f-8c36-aae0d94fe7a1
2026-07-30 08:27:57 +00:00
Eduard van Valkenburg 572a9621bd Python: Keep call and result occurrences atomic in compaction (#7406)
* Python: Keep call and result occurrences atomic in compaction

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 1ee1d250-d1e2-4c6f-8c36-aae0d94fe7a1

* Python: Clarify ambiguous compaction reannotation

Document why incremental reannotation retains all prior duplicate candidates and strengthen the regression that keeps ambiguous results unpaired without changing existing groups.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1ee1d250-d1e2-4c6f-8c36-aae0d94fe7a1

* Python: Handle assistant-embedded compaction results

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1ee1d250-d1e2-4c6f-8c36-aae0d94fe7a1

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1ee1d250-d1e2-4c6f-8c36-aae0d94fe7a1
2026-07-30 07:58:07 +00:00
Evan Mattson df723d768f Update GH Actions workflows (#7424)
* Use Actions token for DevFlow Copilot auth

Grant the review job Copilot request permission and remove the user token fallback so organization-billed GitHub Actions authentication is exercised directly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: d9fa4e9c-c32d-42fb-8ee4-4772473e6479

* Enable DevFlow PR review comparisons

Pass the dedicated DevFlow repository token for A/B artifact branches while keeping the built-in Actions token as the only Copilot credential.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: d9fa4e9c-c32d-42fb-8ee4-4772473e6479

* Allow team-triggered DevFlow reviews

Accept an exact @devflow /review PR comment only from organization members, verify the commenter against the developer team with the GitHub App, and react after authorization.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: d9fa4e9c-c32d-42fb-8ee4-4772473e6479

* Use Actions token for issue triage Copilot auth

Grant the triage job Copilot request permission and remove the user PAT so issue reproduction exercises organization-billed GitHub Actions authentication.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: d9fa4e9c-c32d-42fb-8ee4-4772473e6479

* Use tracked DevFlow CI model configuration

Point PR review and issue triage runs at the dashboard's tracked GPT-5.6 Sol and Claude Opus 5 model configuration.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: d9fa4e9c-c32d-42fb-8ee4-4772473e6479

* Use Actions tokens for Copilot test workflows

Remove Copilot PAT secrets from integration and sample validation workflows, grant Copilot request permission at the required caller and job boundaries, and preserve the environment variable expected by the tests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: d9fa4e9c-c32d-42fb-8ee4-4772473e6479

---------

Copilot-Session: d9fa4e9c-c32d-42fb-8ee4-4772473e6479
2026-07-30 16:59:28 +09:00
Eduard van Valkenburg e344f456ae Python: Correlate AG-UI confirm_changes snapshots by call id (#7411)
* Python: Correlate AG-UI confirm_changes snapshots by call id

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 1ee1d250-d1e2-4c6f-8c36-aae0d94fe7a1

* Chore: retrigger PR checks

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1ee1d250-d1e2-4c6f-8c36-aae0d94fe7a1

* Python: Require real results for accepted confirmations

Keep accepted confirm_changes snapshot payloads inert unless approval resolution produced a matching function result, while retaining explicit rejection cleanup.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1ee1d250-d1e2-4c6f-8c36-aae0d94fe7a1

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1ee1d250-d1e2-4c6f-8c36-aae0d94fe7a1
2026-07-30 07:08:03 +00:00
Eduard van Valkenburg e18a64569c Python: Defer provider-injected approvals to in-run execution (#7410)
* Python: Defer provider-injected approvals to in-run execution

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 1ee1d250-d1e2-4c6f-8c36-aae0d94fe7a1

* Python: Remove vacuous AG-UI approval test

Drop the forged-approval test that was stripped by pending-approval validation; the real pause-approve-resume regression remains the authoritative provider-injected coverage.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1ee1d250-d1e2-4c6f-8c36-aae0d94fe7a1

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1ee1d250-d1e2-4c6f-8c36-aae0d94fe7a1
2026-07-30 06:48:36 +00:00
pratik wayase ce5ee8a9c7 Python: fix(foundry-hosting): root hosted checkpoints under durable home dire… (#7220)
* fix(foundry-hosting): root hosted checkpoints under durable home directory

* fix: add None guard for _checkpoint_storage_path in test

* Disable Foundry image test

---------

Co-authored-by: Tao Chen <taochen@microsoft.com>
2026-07-30 00:35:19 +00:00
Giles Odigwe 4a7af303af Bump .NET SDK from 10.0.301 to 10.0.302 (#7376)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 63ac3381-c9ca-47af-b0e7-9a09c0a5b2be
2026-07-29 20:44:42 +00:00
Eduard van Valkenburg 5987a6791b Python: Improve function approval resume and replay (#7345)
* Python: Harden function approval resume and replay

Make approval resume immutable and occurrence-aware, return grouped approved and rejected results consistently, preserve pending approval history without model-orphaned calls, and align streaming, non-streaming, and AG-UI result boundaries.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1ee1d250-d1e2-4c6f-8c36-aae0d94fe7a1

* Python: Clarify function invocation orchestration

Simplify approval-resolution setup and add phase-level comments around the key function invocation orchestration paths.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1ee1d250-d1e2-4c6f-8c36-aae0d94fe7a1
EOF && git push origin python-approval-resume-contract

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1ee1d250-d1e2-4c6f-8c36-aae0d94fe7a1
2026-07-29 20:01:42 +00:00
Tao Chen 0e6a104192 [BREAKING] Python: Allow workflow checkpoint full replayability (#7374)
* Allow workflow checkpoint full replayability

Seed the initial run input through the start executor's internal self-edge and record an entry checkpoint (iteration 0) before any executor runs, plus a response-entry checkpoint when responses are delivered, so a run is fully replayable from its checkpoints. Simplify the runner to only checkpoint after each superstep. Drop stale events in apply_checkpoint on restore, and deprecate the unused RunnerContext.reset_for_new_run.

* Fix type

* Add max iteration detailed doc string

* Refine comments
2026-07-29 19:25:35 +00:00
Binit Mohanty ce2208c62c Python: Fix OpenAIChatCompletionClient passing raw JSON-Schema dict response_format through unwrapped (#7199)
* Python: Fix OpenAIChatCompletionClient passing raw JSON-Schema dict response_format through unwrapped

Raw schema dicts (e.g. {"type": "object", ...}) were forwarded to the
Chat Completions API verbatim, which OpenAI rejects with a 400. The
Responses client already auto-wraps the same input. Mirror its raw-schema
detection (primitive types / schema keywords), wrap into the
{"type": "json_schema", "json_schema": {...}} envelope with
additionalProperties: false injection and title -> name promotion, and
leave already-valid response_format dicts untouched.

Fixes #7197

(cherry picked from commit dce5c3b06328fbde45eb2a9a25638af5b1ec85e3)

* Python: Add live integration coverage for raw JSON-Schema response_format dicts

Adds a response_format_raw_json_schema param to test_integration_options in
both the Chat Completions and Responses client test suites, proving the same
bare schema dict (title set, additionalProperties omitted) round-trips through
both live APIs and yields parsed structured output.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Python: Fix response format dict typing

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
2026-07-29 16:35:02 +00:00
Roger Barreto 0ca6a3e652 .NET: Add source (ZIP) deploy oriented hosted agent samples (#7372)
* Add zip/code-deploy POC for Hosted-ChatClientAgent (.NET)

Migrate the sample to Foundry source (ZIP) deployment as the default: add azure.yaml with codeConfiguration (remote_build, dotnet_10) and the tool-generated .agentignore, make the csproj self-contained (single target, CPM off, published PackageReferences), and simplify Program.cs to the pristine end-user hosting path. Container files are kept for now; contributor and remaining samples handled in follow-ups.

* .NET: Auto-bind Foundry hosted port for zip/code deploy; migrate Hosted-ChatClientAgent to source (ZIP)

Foundry.Hosting: AddFoundryResponses now binds Kestrel to FoundryEnvironment.Port (the PORT env var, default 8088) for a plain WebApplication.CreateBuilder (Tier 3) host, mirroring AgentHostBuilder. This lets a source/ZIP-deployed .NET agent pass the readiness probe with no Dockerfile. It respects an explicit ASPNETCORE_URLS override and is idempotent. Adds FoundryListenPortTests plus a serialized env-var collection.

Hosted-ChatClientAgent: migrate to source (ZIP) deploy as the default. Add azure.yaml with codeConfiguration (remote_build, dotnet_10) and the tool-generated .agentignore, make the csproj self-contained (single target, CPM off) with a local Directory.Packages.props, embed the local-dev per-agent route so the Using-Samples REPL can reach the local server, and rewrite the README around the azd flow. Documents AZURE_TOKEN_CREDENTIALS=dev for local runs.

Using-Samples/SimpleAgent: fix the per-agent endpoint scheme rewrite so the local HTTP dev port is preserved (the policy now lives on the per-agent ProjectOpenAIClientOptions that actually serves the request).

* .NET: Bind Foundry hosted port unconditionally; drop container files and the local-only agent route

Zip/code deploy runs the sample as a plain ASP.NET app, so the Foundry readiness
port was never bound and every invoke returned HTTP 424 session_not_ready. The
first attempt skipped the binding when ASPNETCORE_URLS was already set, but the
.NET base image always sets it to port 80, so the skip always tripped. Kestrel
ListenAnyIP overrides ASPNETCORE_URLS, so the binding is now unconditional and
PORT stays the only knob.

Sample cleanup for zip deploy:
* Remove Dockerfile, Dockerfile.contributor, agent.manifest.yaml and agent.yaml.
  Source deploy needs none of them.
* Remove LocalDevEndpoint.cs and the invented per-agent local route. The local
  server already serves the standard POST /responses route, so the client can
  reach it directly.
* Trim .env.example: the port and environment variables are no longer needed.
* Exclude .checkpoints/ from the upload so local session state does not ship.

SimpleAgent now asks at startup whether to chat with the local server or the
deployed agent, the same choice azd ai agent invoke exposes through --local.
Local uses an OpenAI responses client pointed at http://localhost:8088; Foundry
uses the per-agent endpoint.

Add scripts/New-ContributorStage.ps1, which stages a sample to a temp folder with
the local Agent Framework source packed into a feed inside the upload, so
contributors can deploy framework changes through the same azd flow end users run.

* Pin hosted agent listen port in azure.yaml

* Use the documented env map in azure.yaml

* Make the contributor flow an extra step inside the end-user flow

* Keep contributor scaffolding out of the sample project file

* Document the full deploy walkthrough and add a bash contributor script

* Trim troubleshooting detail from the sample README

* Pass the model deployment name to the hosted container

* Add --local and --remote flags to the SimpleAgent REPL

* Use central package management in the hosted sample

* Clarify where the contributor step fits in the deploy walkthrough

* Restore the HTTP scheme rewrite for local AIProjectClient runs

* Keep the sample package versions in the project file

* Drop the sample Directory.Packages.props

* Add a container deploy variant of the hosted chat client agent sample

* Treat a blank model deployment variable as unset

* Let azd prompt for the Foundry project and expand the contributor section

* Document the stale conversation 404 in the hosted agent samples

* Remove using directives already covered by global usings

* Bind the Foundry listen port only inside a hosted container

* Resolve the Foundry listen port from IConfiguration
2026-07-29 15:34:54 +00:00
Peter Ibekwe 5543bc94fc Fix and re-enable flaky InputWaiter timeout test (#7377) 2026-07-29 15:24:54 +00:00
Eduard van Valkenburg 5f84917f15 docs: ADR-0033 feature-usage bitmask in the User-Agent (#6500)
* docs: ADR-0027 feature-usage bitmask in the User-Agent

Add an ADR, design spec, and per-language bit registry for a lightweight
feature-usage signal: a 64-bit mask, emitted as a `(feat=vN.<hex>)` User-Agent
comment, stamped per request on first-party (Azure/Foundry) clients only.

- docs/decisions/0027-feature-usage-bitmask-user-agent.md — ADR (options-first,
  with Limitations, Open Questions, and v1->v2 migration)
- docs/specs/002-feature-usage-telemetry.md — design spec + implementation plan
- docs/specs/feature-usage-bit-registry.md — per-language bit tables + governance

Granularity is per package with core broken out per feature (each orchestration
pattern and built-in context/history provider). Registries are per language
(decoder selects by the language already in the UA). OpenTelemetry emission is
deferred (privacy). Docs only; no code changes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs: fix dead links to removed registry JSON in ADR-0027

The registry JSON was consolidated into feature-usage-bit-registry.md; point
the ADR's two remaining links at the markdown instead of the deleted file
(fixes markdown-link-check 404s).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs: address review — drop JSON-parity wording, clarify per-language decode

- ADR option J: the parity test compares the enum against the per-language table
  in the registry doc, not a (now-removed) JSON file.
- Spec .NET mapping: the wire format is shared, but the mask is decoded
  per-language (select the table via the UA product token) — fixes the
  "decoded numbers mean the same thing in both SDKs" wording that conflicted
  with the per-language, non-synchronized bit indexes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs: add dedicated mask-only opt-out env var (AGENT_FRAMEWORK_FEATURE_MASK_DISABLED)

Re-introduce a dedicated opt-out that disables only the feature mask while keeping
the base agent-framework-<lang>/{version} User-Agent, alongside the existing
AGENT_FRAMEWORK_USER_AGENT_DISABLED (whole UA). Updates the spec accumulator gate,
API surface, opt-out table and examples; the registry opt-out section; and the
ADR (decision outcome, consequences, open questions -> decided).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs: add prior-art comparison (AWS botocore m/, Stainless, Azure, etc.)

Add a Prior art section to ADR-0027 surveying how comparable SDKs encode
identity/usage in the User-Agent or sidecar headers, with citations:

- AWS botocore `m/` feature-code list — the direct analog (per-request,
  usage-based feature flags in the UA); contrasts short-code set vs our hex
  bitmask.
- OpenAI/Anthropic Stainless `X-Stainless-*` headers (static identity).
- Azure azure-core UserAgentPolicy + AZURE_TELEMETRY_DISABLED.
- Google x-goog-api-client; LangSmith version token + tracing opt-in.

Also add an Open Question on honoring the cross-tool DO_NOT_TRACK convention.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs: fold in botocore lessons; record accumulation-scope decision

botocore's m/ feature list scopes features to a per-request contextvars set that
resets between calls — clean per-call attribution, but it assumes every feature
lives inside a service request. That holds for an SDK natively bound to its own
services; it does not for us, where many features (agent/workflow/provider
construction, session setup) are not bound to any request.

- ADR: add Accumulation scope options — P (process-global monotonic, chosen) vs
  Q (botocore per-request set, rejected) with the request-binding rationale;
  reference P in the decision; reframe the "no per-call attribution" limitation
  as a deliberate scope choice.
- ADR Prior art: bitmask gives bounded token size for free (vs botocore's
  1024-byte cap + truncation); mechanism is private, wire format is the contract;
  fix a duplicated phrase.
- Spec: note the mask is process-global, monotonic, never reset (intentional,
  lock/Interlocked.Or-safe), the token is safe-by-construction (no sanitization),
  and the helpers are private API.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs: update feature mask ADR

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs: refresh feature usage telemetry design

Rebase the proposal on current main, renumber it to ADR-0033/SPEC-004, and reconcile the registry and implementation notes with current Python and .NET surfaces.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs: expand feature usage mask to 128 bits

Repartition the v1 registries with additional skill categories, define the bit-allocation tenet, and document the two-lane .NET accumulator and 128-bit decoder contract.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 346bf168-b668-4c4a-a8db-a67282ee5e5f

* docs: tighten feature telemetry activation and scoping

Require approved pipeline and actual-origin classification, preserve OpenAI transport defaults, use activation-based marking, and move index ownership into packages with parity and no-overlap validation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 346bf168-b668-4c4a-a8db-a67282ee5e5f

* docs: preserve SDK transport defaults for telemetry

Record the transport-preservation requirement at the ADR decision level.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 346bf168-b668-4c4a-a8db-a67282ee5e5f

* docs: split declarative agent and workflow usage

Allocate separate adjacent v1 indexes for declarative agents and declarative workflows in Python and .NET, shifting later unreleased rows.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 346bf168-b668-4c4a-a8db-a67282ee5e5f

* docs: accept feature usage telemetry ADR

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 346bf168-b668-4c4a-a8db-a67282ee5e5f

* docs: record feature telemetry ADR participants

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 346bf168-b668-4c4a-a8db-a67282ee5e5f

* docs: expand feature telemetry ADR consultation

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 346bf168-b668-4c4a-a8db-a67282ee5e5f

* docs: clarify feature telemetry semantics

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 346bf168-b668-4c4a-a8db-a67282ee5e5f

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 346bf168-b668-4c4a-a8db-a67282ee5e5f
2026-07-29 14:37:13 +00:00
Yufeng He 33bc9c063c Python: extract keywords from non-English text for topic selection (#7130)
_WORD_PATTERN matched only ASCII (`[a-z0-9]...`), so a message written in
CJK, Cyrillic or any other non-Latin script produced an empty keyword set.
_select_topics returns early on an empty keyword set, so non-English users
never had memory topic files loaded automatically.

Make the pattern Unicode-aware (`[^\W_][\w-]+`, a letter/digit start plus
word chars/hyphen), which is the exact Unicode generalization of the old
pattern: English tokenization is unchanged and CJK/Cyrillic text now yields
keywords.
2026-07-29 02:34:58 +00:00
Alexander Nachtmann 8d22bb9177 .NET: Add Anthropic-backed live tests for OpenAI Responses hosting helpers (#7362)
Mirrors OpenAIResponsesHostingLiveTests with the hosted agent backed by an
Anthropic chat client, confirming the app-owned hosting helper surface
(OpenAIResponses + AgentSessionStore) is provider-agnostic end to end.
Skipped unless ANTHROPIC_API_KEY is configured.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 21:12:42 +00:00
Dineshsuriya D ad20ab009b .NET: Add GitHub Copilot BYOK sample (#7337)
* .NET: Add GitHub Copilot BYOK sample

Demonstrates routing GitHubCopilotAgent requests through a custom OpenAI-compatible
endpoint via SessionConfig.Provider instead of the default GitHub Copilot backend.

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Update dotnet/samples/02-agents/AgentProviders/github-copilot/Agent_With_GitHubCopilot_BYOK/README.md

Co-authored-by: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com>

* Update dotnet/samples/02-agents/AgentProviders/github-copilot/Agent_With_GitHubCopilot_BYOK/README.md

Co-authored-by: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com>

* .NET: Address remaining BYOK sample review feedback

- Make the provider type configurable via BYOK_PROVIDER_TYPE (default "openai") instead
  of hardcoding "openai", since the sample already documents Azure/Anthropic support.
- Stop calling the endpoint "OpenAI-compatible" everywhere; Anthropic isn't OpenAI-wire-
  compatible, so reword to "your own endpoint" and list the actual supported providers.
- Move the "About BYOK" explainer to the top of the README so the term is introduced
  before it's used, and finish applying the WireApi/ModelId comment suggestions.
- Reword the AgentProviders/README.md entry to match (not OpenAI-specific).

* .NET: Fix UTF-8 BOM on BYOK sample Program.cs

The repo's .editorconfig requires utf-8-bom for .cs files; check-format was
failing because the new file was written without one.

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com>
2026-07-28 20:48:57 +00:00
Peter Ibekwe aa53182dc0 .NET: Preserve table state across declarative EditTable operations  (#7353)
* Preserve table state across declarative EditTable operations 

* Address PR comments.
2026-07-28 18:44:38 +00:00
Scarab Systems 3daa3b2c2d Python: Fix Gemini harness tool declarations (#7322)
* Fix Gemini harness tool declarations

Forward Agent Framework FunctionTool JSON Schemas to the Gemini SDK parameters_json_schema field and enable Developer API server-side tool invocation reporting when native Gemini tools are mixed with function declarations.

Preserves Vertex AI behavior and existing function-calling tool_choice config.

Validation:

- uv run --directory python poe check -P gemini

- uv run --directory python poe build -P gemini

- uv run --directory python poe test -A -m 'not integration'

- uv run --directory python pytest packages/gemini/tests/test_gemini_client.py -q -m integration (8 skipped: credential-gated)

* Python: Use typing_extensions TypedDict in Gemini tests

Use typing_extensions.TypedDict for the Gemini JSON Schema test helper so Pydantic can build the model on Python 3.11.

This keeps the CI fix scoped to the failing test compatibility issue without changing Gemini client behavior.

---------

Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com>
2026-07-28 17:49:14 +00:00
Eduard van Valkenburg 7514122d59 Python: isolate dependency-bound validation (#7342)
* Python: isolate dependency-bound validation

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: e446e833-ea7c-44e8-8b73-e730e30160af

* Python: remove unused validator import

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: e446e833-ea7c-44e8-8b73-e730e30160af

* Python: keep core dependency validation isolated

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: e446e833-ea7c-44e8-8b73-e730e30160af

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: e446e833-ea7c-44e8-8b73-e730e30160af
2026-07-28 17:15:42 +00:00
Roger Barreto 859f56fb49 .NET: Skip flaky InputWaiterTests timeout test blocking the merge queue (#7361)
InputWaiter_WaitForInputAsync_CompletesWhenTimeoutExpiresAsync races a
300ms SemaphoreSlim timeout against a 5s Task.Delay guard and asserts
which one won by object identity. On the loaded net472/windows-latest
leg, thread pool starvation can delay the 300ms continuation past the
5s guard, so Task.Delay wins and the assertion fails.

It failed in 7 of the last 18 failed dotnet-build-and-test runs, always
on net472/windows-latest and always in merge_group, blocking PRs that
do not touch the workflows code.

Quarantine it following the existing convention used for #5845, and
track the real fix in #7360.

Copilot-Session: 0be6f810-51de-4f49-b9c7-8d1c7efa2c43
2026-07-28 15:50:47 +00:00
SergeyMenshykh 2694120383 Forward A2A MessageSendParams.Configuration in the A2A adapter (#7365)
The A2A hosting layer now forwards the caller-supplied
SendMessageConfiguration from RequestContext.Configuration into
AgentRunOptions.AdditionalProperties under the key
'a2a.configuration'. This covers all three handler paths:
non-streaming, streaming, and task continuation.

The server-configured AgentRunMode remains authoritative for
AllowBackgroundResponses — the caller's ReturnImmediately is
forwarded but does not override the server decision.

Closes microsoft/agent-framework#5869

Co-authored-by: SergeyMenshykh <SergeMenshikh@outlook.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 5ee820de-3e34-493b-a19c-1db6bc04871d
2026-07-28 15:25:38 +00:00
westey 7b6d257988 Python: Add TodoProvider and AgentModeProvider samples (#7309)
* Python: Add TodoProvider and AgentModeProvider context provider samples

Add two Python samples under samples/02-agents/context_providers/ mirroring the
.NET samples from #7262:
- todo_provider.py: scripted walkthrough of TodoProvider that plans multi-step
  work and prints the evolving todo list after each turn.
- agent_mode_provider.py: interactive loop using AgentModeProvider with a /mode
  slash command, demonstrating built-in plan/execute and custom modes.

Also index both samples in the context_providers README.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8725831c-086b-475f-90e6-cdba41d59c33

* Python: Address review comments on AgentModeProvider sample

- Replace the AGENT_MODE_USE_CUSTOM env var with an in-file USE_CUSTOM_MODES
  constant for choosing between built-in and custom modes.
- Use plain input() in the interactive loop instead of asyncio.to_thread.
- Update the README prerequisites to reference the in-file toggle.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8725831c-086b-475f-90e6-cdba41d59c33

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8725831c-086b-475f-90e6-cdba41d59c33
2026-07-27 20:28:58 +00:00
Henry Su acbcdaa086 Python: fix(python): handle callable class middleware safely in _determine_middleware_type (#6697) (#7333)
* fix(python): handle callable class middleware safely in _determine_middleware_type (#6697)

* test(python): type-annotate test middleware lists to pass test-typing checks
2026-07-27 19:39:47 +00:00
KXH 0b0dcaa5af fix(dotnet): preserve table after EditTable add (#7324)
Signed-off-by: KXH <shepherdlaurie238@gmail.com>
2026-07-27 18:29:32 +00:00
Copilot 35a8891d67 .NET: Add Microsoft.Agents.AI.LocalCodeAct to release solution filter (#7343)
* Initial plan

* Add Microsoft.Agents.AI.LocalCodeAct to release solution filter

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-27 14:18:31 +00:00
westey 28e02d4669 Create session for tool approval agent when non-present (#7310) 2026-07-27 11:07:43 +00:00
Sriraj 5ccd7784a6 Python: Reject Windows junctions in FileSystemAgentFileStore (#7291)
* Python: reject file-store junctions during enumeration

* Python: clarify file-store probe diagnostics
2026-07-27 10:47:57 +00:00
pratik wayase 754cbe5976 Python: [Feature]: Support OpenAI instructions in Responses API (#7292)
* Python: Support OpenAI instructions in Responses API

* fix: address PR comments and fix typing for OpenAIChatOptions
2026-07-27 10:30:34 +00:00
Giles Odigwe c6442de528 .NET: Graduate GitHub Copilot agent to stable (#7313)
Promote Microsoft.Agents.AI.GitHub.Copilot from release candidate to
released by replacing IsReleaseCandidate=true with IsReleased=true, so the
package builds with the stable central version (no -rc suffix). Also clears
the package-validation baseline and disables package validation for this
first stable release, since the package has never shipped a stable NuGet to
validate against (mirrors the Microsoft.Agents.AI.Harness graduation in
#7119). Non-breaking: the package exposes no [Experimental] APIs to un-mark.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f523064c-60b4-4d18-bf95-c16c5fda9126
2026-07-24 22:46:19 +00:00
Tao Chen 0df184e7dd Python: Fix sub-workflow checkpoint restore to preserve sub-workflow state (#7097)
* Fix sub-workflow checkpoint restore to preserve sub-workflow state

Add Runner.capture_checkpoint_object/restore_from_checkpoint_object (quiescent-only nested checkpoint) and embed a sub_workflow_checkpoint in WorkflowExecutor.on_checkpoint_save/on_checkpoint_restore so a resumed parent restores each sub-workflow's mid-progress state instead of only replaying pending request-info events. Keeps a backward-compat fallback when sub_workflow_checkpoint is absent.

* Move checkpoint-object construction into the runner context

Add RunnerContext.create_checkpoint_object alongside create_checkpoint (create_checkpoint now delegates to it and persists), so Runner.capture_checkpoint_object builds the snapshot via the context instead of a one-off get_messages peek primitive. In-flight messages are captured non-destructively (per-source lists copied). The checkpoint-less capturing contexts (azurefunctions, durabletask) raise NotImplementedError to match create_checkpoint.

* Remove per-execution bookkeeping from WorkflowExecutor

The sub-workflow is a single shared instance, so per-execution ExecutionContext/request routing never provided real isolation. Delegate request/response tracking to the sub-workflow itself: can_handle accepts targeted propagated responses, _handle_response validates against the sub-workflow's pending requests and forwards responses immediately, and on_checkpoint_save embeds only the sub-workflow checkpoint (on_checkpoint_restore keeps a legacy reader for older checkpoints). Also emit the fresh-message/checkpoint-while-pending warning from FunctionalWorkflow.run to match Workflow.run.

* Drop redundant decode in WorkflowExecutor.on_checkpoint_restore

The storage backend already materializes the full checkpoint on load (FileCheckpointStorage decodes recursively; InMemoryCheckpointStorage deep-copies), so the embedded sub_workflow_checkpoint (and legacy execution_contexts) arrive already decoded - like every other executor's on_checkpoint_restore state. Remove the no-op decode_checkpoint_value calls and the now-unused import.

* Clean up

* Do not allow checkpoint storage in sub workflow

* Address comments

* Fix syntax check

* Add warning

---------

Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
2026-07-24 17:21:41 +00:00
westey d0a0d5a3df .NET: Add TodoProvider and AgentModeProvider samples (#7262)
* Add samples for todo and mode providers

* Address PR review: add Step21 to samples index and trim slash-command input

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Print agent mode after each turn in AgentMode sample

Reflects mode changes the agent makes itself via the mode_set tool.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-24 12:57:10 +00:00
westey c59a65da5e .NET: fix InMemoryChatHistoryProvider persisting when service stores history (#7284)
* Fix chat history storage bug

* Improve error messaging

* Address PR comment
2026-07-24 10:44:52 +00:00
Evan Mattson e90b6de5a7 Python: Improve python package management operations (#7274)
* improve package mgmt timings

* Address Python release validation review feedback
2026-07-23 22:14:10 +00:00
Atharva Vichare d98ac29115 Python: Fix duplicate function call on approval round-trip (#7267) (#7271)
* Python: Fix duplicate function call on approval round-trip (#7267)

`_replace_approval_contents_with_results` deduped restored function calls
against only the message currently being scanned. On an approval round-trip
the hosting layer replays the stored `function_call` item and its
`mcp_approval_request` item as two separate assistant messages, so the
per-message check never fired and the approval request restored a second
copy of the call.

Only one copy received the function result; the orphaned copy was left
unanswered, which the Responses API rejects with
"No tool output found for function call call_<id>".

Collect existing call ids across all messages instead, and add a restored
call to that set so two approval requests for the same call cannot both
expand.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Refactor approval placeholder result handling

Refactor approval handling logic to improve clarity and maintainability.

* Refactor test to support reused call IDs after completion

Updated the test to allow reused call IDs after completion, ensuring that a completed call does not suppress later approval requests with the same ID. Adjusted assertions to reflect the new behavior.

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 21:27:26 +00:00
Giles Odigwe 040e2705aa Promote agent-framework-github-copilot to 1.0.0 (released) (#7302)
Promote the GitHub Copilot package from release candidate (1.0.0rc4) to released (1.0.0): bump the version, switch the classifier to Production/Stable, update PACKAGE_STATUS.md, and drop the --pre install flag from the package and sample READMEs. Add a github-copilot-1.0.0 CHANGELOG section covering the promotion and the input-attachment forwarding shipped in this release. No core/root bump: this is a standalone package promotion and the core[all] extra references the package without a version pin.

Copilot-Session: f523064c-60b4-4d18-bf95-c16c5fda9126
2026-07-23 21:06:31 +00:00
Giles Odigwe 8c057507f4 Python: Forward GitHub Copilot input attachments as inline blobs (#7300)
* Python: Forward GitHub Copilot input attachments as inline blobs

The Python GitHubCopilotAgent built the prompt from message text only, so
DataContent (images/documents) passed on input was silently dropped. The .NET
provider already forwards these as attachments.

Map input data content to the Copilot SDK's inline BlobAttachment (base64,
no temp files) in both the streaming and non-streaming send paths. Data content
without a media type is dropped with a warning instead of silently.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ac85c429-4115-42ef-a18a-f576e3cf03f7

* Python: Handle non-base64 data URIs and fix attachment docstring

Address PR review feedback:
- Guard _get_data_bytes_as_str against ContentError so a non-base64 data:
  URI (which _validate_uri still classifies as type="data") is skipped with a
  warning instead of failing the entire Copilot request.
- Correct the docstring: remote URIs and non-base64 data URIs are neither
  attached nor added to the prompt (the prompt is built from text content only).
- Add tests for the non-base64 data URI path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ac85c429-4115-42ef-a18a-f576e3cf03f7

* Python: Fix flaky attachment test under telemetry

The end-to-end non-base64 data URI test failed in CI because GitHubCopilotAgent's
telemetry layer serializes message content (observability._to_otel_part ->
_get_data_bytes_as_str), which raises ContentError on a non-base64 data: URI
before the attachment code runs. That is an unrelated core-observability
limitation, not attachment behavior.

Use RawGitHubCopilotAgent (no telemetry layer) for that test so it isolates the
provider's send path. The direct helper test still covers the ContentError guard.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ac85c429-4115-42ef-a18a-f576e3cf03f7

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ac85c429-4115-42ef-a18a-f576e3cf03f7
2026-07-23 21:05:52 +00:00
Peter Ibekwe 0d5c0f8fa0 .NET: Add language and prompt customization to Magentic orchestration (#7263)
* Add language and prompt customization to Magentic orchestration

* Update default prompts formatting
2026-07-23 18:19:12 +00:00
Chinedum Echeta 217912a2c0 Python: Support async credentials in FoundryToolbox (#7208)
* Python: Support async credentials in `FoundryToolbox`

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Refactor: Use AzureCredentialTypes for credential type annotations in Toolbox classes

* Remove auth_flow method from _ToolboxAuth class

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-23 17:57:31 +00:00
pratik wayase 0841116330 Python: fix(foundry_hosting): preserve auth credentials across FoundryToolbox reconnections (#7202)
* fix(foundry_hosting): preserve auth credentials across FoundryToolbox reconnections

* Address copilot comments

* fix syntax check

* Fix tests

* Fix formatting

* Fix formatting

---------

Co-authored-by: Tao Chen <taochen@microsoft.com>
2026-07-23 17:15:19 +00:00
Evan Mattson cd6345e91e Python: Align AG-UI workflow cache scoping (#7277)
* snapshot scope resolver isolation

* Fix AG-UI scope resolver test typing
2026-07-23 17:13:27 +00:00
Amit Dhawan 59b979213a Python: Fix stale agent.json references in A2A sample (#7281)
Co-authored-by: Amit Dhawan <amit.dhawan@barco.com>
2026-07-23 15:59:01 +00:00
westey ad26cfe8c7 .NET: Switch to using new community toolkit VectorData packages (#5694)
* Switch to using new community toolkit VectorData packages

* Fix formatting.

* Update dotnet/Directory.Packages.props

Co-authored-by: Adam Sitnik <adam.sitnik@gmail.com>

* Fix build error.

* Upgrade MEAI

* Upgrade additional dependencies

* Address rename after package upgrade.

* Revert some packages versions due to version mismatches

---------

Co-authored-by: Adam Sitnik <adam.sitnik@gmail.com>
2026-07-23 15:02:24 +00:00
dependabot[bot] 0c8bf5b6c0 Bump brace-expansion in /python/packages/devui/frontend (#7232)
Bumps [brace-expansion](https://github.com/juliangruber/brace-expansion) from 1.1.12 to 1.1.16.
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/v1.1.12...v1.1.16)

---
updated-dependencies:
- dependency-name: brace-expansion
  dependency-version: 1.1.16
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-23 11:57:16 +00:00
Eduard van Valkenburg cb2914fa7e Bump agent-framework-hosting-a2a to 1.0.0a260723 (#7282)
Prepare the focused alpha release for the progressive A2A adapters from #7258. No other package versions or dependency bounds change.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 003e02dd-dba0-40a5-9ebf-083901aefb57
2026-07-23 10:56:15 +00:00
Eduard van Valkenburg 0796af0c26 Python: add progressive A2A hosting adapters (#7258)
* Python: add progressive A2A hosting adapters

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 5d6987cd-1b67-4ba1-8b54-3c50da6e7607

* Python: address A2A adapter review feedback

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 5d6987cd-1b67-4ba1-8b54-3c50da6e7607

---------

Copilot-Session: 5d6987cd-1b67-4ba1-8b54-3c50da6e7607
2026-07-23 07:43:30 +00:00
Evan Mattson 711d6f24ae Bump Python package versions for 1.12.1 release (#7273)
Bump root and core to 1.12.1, OpenAI to 1.11.0 for new public prompt-cache options, Foundry to 1.10.3, and Gemini and Foundry Hosting to beta 260722 based on CHANGELOG entries. Promote AG-UI from 1.0.0rc9 to stable 1.0.0. No beta cohort bump was applied, and core floors remain unchanged under the strict affected-dependency policy because the connectors do not require a new core API.
2026-07-23 13:29:20 +09:00
Evan Mattson bd17a64697 Restore dedicated DevFlow Copilot authentication (#7276)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d9fa4e9c-c32d-42fb-8ee4-4772473e6479
2026-07-23 13:23:39 +09:00
Eduard van Valkenburg 5147579992 Python: Enforce package coverage by lifecycle (#7261)
* Enforce Python coverage by package lifecycle

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9ae5ad8e-6b66-41b3-a862-4e2a3fae1cd0

* Fix Python CI and deprecation usage

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 9ae5ad8e-6b66-41b3-a862-4e2a3fae1cd0

* Make POSIX kill-tree test portable

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 9ae5ad8e-6b66-41b3-a862-4e2a3fae1cd0

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9ae5ad8e-6b66-41b3-a862-4e2a3fae1cd0
2026-07-23 02:38:28 +00:00
Evan Mattson 2d34deeb82 Reduce workflow credential exposure (#7270)
* Mask workflow authentication configuration

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d9fa4e9c-c32d-42fb-8ee4-4772473e6479

* Use run-scoped Copilot authentication

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d9fa4e9c-c32d-42fb-8ee4-4772473e6479

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d9fa4e9c-c32d-42fb-8ee4-4772473e6479
2026-07-23 08:57:40 +09:00
Evan Mattson a2927c1c09 Python: Fix stateless replay of reasoning-paired tool calls (#7233)
* Python: Fix reasoning-paired client tool replay

* Python: Handle middleware-terminated reasoning tool loops

* Python: Replay encrypted reasoning function groups

Key decisions:
- Request encrypted reasoning on client-managed Responses calls while preserving caller include values.
- Store encrypted payloads in Content.protected_data and reconstruct one provider reasoning item per reasoning id.
- Replay active and completed function call/result groups; retain continuation-owned history behavior and the existing orphan-safe MCP path.

Files changed:
- python/packages/openai/agent_framework_openai/_chat_client.py
- python/packages/openai/tests/openai/test_openai_chat_client.py

Next iteration:
- Extend encrypted reasoning preservation to streaming and framework serialization boundaries.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Python: Preserve encrypted reasoning through streaming

Key decisions:
- Capture encrypted reasoning from terminal streamed output items in Content.protected_data.
- Preserve summary and private reasoning as distinct framework contents while reconstructing one provider reasoning item per id.
- Prove replay after Message JSON and workflow checkpoint round trips, including encrypted-only and completed function groups.

Files changed:
- python/packages/core/agent_framework/_types.py
- python/packages/openai/agent_framework_openai/_chat_client.py
- python/packages/openai/tests/openai/test_openai_chat_client.py

Next iteration:
- Extend lossless stateless reasoning replay to hosted MCP call/output groups.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Python: Replay hosted MCP reasoning groups

Key decisions:
- Preserve hosted MCP call/output groups in client-managed history instead of deleting them when reasoning cannot be reconstructed.
- Keep call/result coalescing and orphan-result exclusion intact, while retaining continuation-owned duplicate avoidance.
- Cover completed, active, and multi-call reasoning groups plus the public outgoing request boundary.

Files changed:
- python/packages/openai/agent_framework_openai/_chat_client.py
- python/packages/openai/tests/openai/test_openai_chat_client.py

Next iteration:
- Preserve middleware-terminated and parallel function groups atomically.
- Add preflight rejection for non-replayable reasoning groups in the dedicated validation slice.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Python: Preserve terminated parallel reasoning groups

Key decisions:
- Return ordinary function results when middleware terminates a loop, removing the provider-specific durable marker.
- Preserve every parallel call and available sibling result as one encrypted reasoning group in stateless replay.
- Prove successful and policy-blocked batches through the public two-agent Foundry workflow and outgoing HTTP boundary.

Files changed:
- python/packages/core/agent_framework/_tools.py
- python/packages/core/tests/core/test_function_invocation_logic.py
- python/packages/openai/tests/openai/test_openai_chat_client.py
- python/packages/foundry/tests/foundry/test_foundry_agent.py

Next iteration:
- Add preflight rejection for non-replayable and partially compacted reasoning groups.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Python: Reject unsafe stateless reasoning replay

Key decisions:
- Validate client-managed reasoning groups after compaction and report every affected reasoning and call identifier before transport.
- Permit service-owned continuation and fully excluded atomic groups while rejecting partial compaction projections.
- Surface encrypted-reasoning capability failures without lossy retries.

Files changed:
- python/packages/openai/agent_framework_openai/_chat_client.py
- python/packages/openai/tests/openai/test_openai_chat_client.py

Next iteration:
- Run the resource-specific Foundry proof and finish PR #7233; that live proof remains intentionally local and requires the configured developer resource.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Python: Preserve reasoning metadata in Foundry hosting

* Python: Avoid duplicating reasoning text metadata

* Python: Gate encrypted reasoning for Foundry agents

* Python: Type stateless reasoning integration test

* Python: Narrow Foundry mock call arguments

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-22 23:51:22 +00:00
Ben Thomas c68c099347 .NET: Fix expensive logging (#7268)
* .NET: Guard workflow warning logging

Avoid unnecessary structured logging argument evaluation when warning logging is disabled, resolving CA1873 in release builds.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 0bc01e26-22ba-42ce-ac1e-6fe166500f4f

* .NET: Use generated workflow logging

Align the no-progress warning with the repository-standard LoggerMessage source generator pattern.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 0bc01e26-22ba-42ce-ac1e-6fe166500f4f

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Ben Thomas <25218250+alliscode@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot-Session: 0bc01e26-22ba-42ce-ac1e-6fe166500f4f
2026-07-22 23:03:32 +00:00
Yunus Emre Gültepe 61802723ff Python: Support prompt cache breakpoints for GPT-5.6 models in OpenAI clients (#7163)
* Python: Support prompt cache breakpoints for GPT-5.6 models in OpenAI clients

Add request-level prompt_cache_options to OpenAIChatOptions and
OpenAIChatCompletionOptions, and forward a per-part prompt_cache_breakpoint
from Content.additional_properties onto the content blocks each API supports.
Text parts that carry a breakpoint keep typed list content, since the
plain-string form cannot hold one; without a breakpoint the existing string
forms are unchanged.

* Clarify system-message content-shape comment

* Address review: SDK prompt cache types, private helper, add sample

Replace the custom PromptCacheOptions TypedDict with the openai SDK's own
types for each API, which raises the openai floor to 2.45.0 where those
types were introduced. Make the breakpoint helper private to the two chat
clients. Add a prompt caching sample with a README entry, and unquote the
helper's Content annotation so the pyupgrade hook passes.

* Guard the prompt cache options import for older openai versions

The SDK's PromptCacheOptions types only exist in openai 2.45.0 and
later, so each client falls back to a local mirror when the import
fails and the dependency floor stays at 2.25.0. A TYPE_CHECKING-only
import is not enough because the options classes are introspected with
get_type_hints() at runtime. Verified against openai 2.25.0: the
package imports, the fallback resolves, and part-level breakpoints
still work; sending the option itself requires 2.45.0, which the field
docstrings now note.

* Make the old-openai fallback for PromptCacheOptions deliberately empty

Assigning None instead, as suggested in review, trips pyright's
reportInvalidTypeForm on the field annotation (the symbol becomes
type | None after the try/except). An empty TypedDict gives the same
effect for users on older openai versions: any content they put in
prompt_cache_options is flagged by their type checker, since the
option cannot be sent on those versions anyway, while
get_type_hints() on the options classes keeps working at runtime.

* Guard prompt_cache_options at runtime instead of via an empty fallback type

The empty-TypedDict fallback flagged valid `prompt_cache_options` usage under
pyright on every openai version — including this PR's own
`client_prompt_caching.py` sample (`poe check -S`) — because pyright resolves the
try/except symbol to the fallback shape regardless of the installed openai, while
mypy/ty resolve the failed import to `Any` and never warn. So a type-only "warn on
old openai" signal is not achievable cleanly across type checkers.

Restore the faithful fallback (mirrors the SDK's `mode`/`ttl` shape) so the option
type-checks identically on every supported openai version, and add a runtime guard:
setting `prompt_cache_options` on openai < 2.45 now raises a clear
ChatClientInvalidRequestException instead of forwarding an unusable option to the
SDK. This keeps the option non-silent for all users regardless of type checker,
without forcing an openai upgrade. Adds tests covering the guard for both clients.

* Gate system/developer breakpoint shape on a real mapping value

The system/developer branch switched to list-form content whenever
prompt_cache_breakpoint was set to any non-None value, but the option is
only attached when the value is a mapping. A malformed value (e.g. a
string) therefore changed the message shape without adding a breakpoint.
Decide the shape from the built part instead, matching the user-role path.
2026-07-22 21:43:19 +00:00
Whit Waldo ddb0622f9c .NET: Added GettingStarted example demonstrating Dapr as an agent provider (#1615)
* Added example demonstrating creating an AIAgent using the Microsoft.AI.Extensions implementation of IChatClient using Dapr as the inference backend provider - in this example, using Ollama

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Update dotnet/samples/GettingStarted/AgentProviders/Agent_With_Dapr/README.md

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Added copyright statement at top of file

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Update dotnet/agent-framework-dotnet.slnx

That's odd the IDE added it a second time.

Co-authored-by: westey <164392973+westey-m@users.noreply.github.com>

* Address review nits: configurable Dapr gRPC endpoint and document VersionOverride

Make the Dapr sidecar gRPC endpoint configurable via the DAPR_GRPC_ENDPOINT environment variable
(defaulting to http://localhost:3501) and document it in the README. Add a comment explaining why the
Microsoft.Extensions.* VersionOverride entries are needed and when they can be removed.

---------

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: westey <164392973+westey-m@users.noreply.github.com>
Co-authored-by: Roger Barreto <19890735+RogerBarreto@users.noreply.github.com>
2026-07-22 19:28:00 +00:00
Ben Thomas 12b23250f4 Updating dotnet version for release. (#7265)
Co-authored-by: Ben Thomas <25218250+alliscode@users.noreply.github.com>
2026-07-22 10:05:59 -07:00
Ben Thomas bfc73a5b14 .NET: Fix declarative autosend output (#7217)
* Fix declarative workflow auto-send output

Restore completed responses for workflow-conversation agents while preventing hosted workflow adapters from materializing streamed responses twice.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: c2d86826-ead0-40bc-b84b-a513ac4d325f

* Correlate streamed workflow responses by message

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: c2d86826-ead0-40bc-b84b-a513ac4d325f

* Handle empty streaming message IDs

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: c2d86826-ead0-40bc-b84b-a513ac4d325f

* Restore workflow conversation auto-send

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: c2d86826-ead0-40bc-b84b-a513ac4d325f

* Address workflow response review feedback

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: c2d86826-ead0-40bc-b84b-a513ac4d325f

* Ignore whitespace workflow message IDs

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: c2d86826-ead0-40bc-b84b-a513ac4d325f

* Correlate all content-bearing agent updates

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: c2d86826-ead0-40bc-b84b-a513ac4d325f

---------

Co-authored-by: Ben Thomas <25218250+alliscode@users.noreply.github.com>
2026-07-22 15:11:59 +00:00
Roger Barreto 1f1da1bddb .NET: [BREAKING] Hosting OpenAI Responses protocol helpers and optional execution state (#7000)
* .NET: Add OpenAI Responses protocol helpers and optional execution state (ADR-0032)

* Fix netstandard2.0/net472 build; harden helpers and workflow checkpoint key per review

* .NET: Migrate hosting Responses samples to Azure.AI.Projects and fix workflow resume

Migrate HostingResponsesAgent and HostingResponsesWorkflow samples from
Azure.AI.OpenAI to Azure.AI.Projects (AIProjectClient.AsAIAgent), using the
FOUNDRY_PROJECT_ENDPOINT/FOUNDRY_MODEL convention.

Fix HostedWorkflowState.RunOrResumeAsync: on subsequent turns, restore the
session's latest checkpoint and run the workflow forward with the new turn's
input (mirroring the Python hosting host's restore-then-run semantics) instead
of resuming a halted run with no input, which waited on input indefinitely.
Add round-trip resume tests and update ADR-0032/spec-003 wording.

* .NET: Fix HostedWorkflowState resume hang on unserviced external requests

On resume, HostedWorkflowState.RunOrResumeAsync drained the workflow with the
blocking WatchStreamAsync overload, so a workflow that halts at an unserviced
RequestInfoEvent (human-in-the-loop / approval) blocked forever — asymmetric
with the first-turn RunAsync path, which returns at the same halt. Break the
drain when a superstep completes with HasPendingRequests, restoring symmetry
with turn 1. Add a HITL approval-gate workflow and a resume-does-not-block test.

* .NET: Warn when a HostedWorkflowState resume makes no progress

Add an optional ILoggerFactory to HostedWorkflowState and log a warning when a
resumed turn produces no events, mirroring the Python host's zero-event restore
warning (a stale checkpoint or an input that does not match the workflow's
expected type leaves session state unprogressed). Add a non-chat string workflow
helper, a capturing logger, and a red/green test.

* .NET: Resume HostedWorkflowState from durable checkpoint on cursor miss

Add CheckpointManager.GetLatestCheckpointAsync(sessionId) and have
HostedWorkflowState fall back to it when its in-memory head cursor misses, so a
durable CheckpointManager resumes a session across a process restart or a new
holder instead of restarting from the workflow's start executor. Mirrors the
Python host's per-turn get_latest read-through. Add a counting workflow that
proves resume-vs-fresh via accumulated state, plus a red/green test, and update
ADR-0032/spec-003 and the XML remarks.

* .NET: Serialize HostedWorkflowState turns through a workflow lock

A single workflow instance backs the holder and workflow instances do not
support concurrent runs (the runner throws "already owned by another runner"),
so concurrent turns could fault or race the head cursor. Serialize all turns
through one SemaphoreSlim (mirroring the Python host's workflow lock) and make
HostedWorkflowState IDisposable to own it. Add a gated workflow and a
deterministic concurrency red/green test.

* .NET: Cover non-chat resume and multi-turn checkpoint advance

Add tests for HostedWorkflowState resuming a non-chat-protocol workflow (no
TurnToken) and for a third turn continuing to advance the head checkpoint,
closing the coverage gaps the parity review flagged.

* .NET: Add streaming workflow resume path and stream the workflow sample

Add HostedWorkflowState.RunOrResumeStreamingAsync, which yields the turn's
WorkflowEvents as they occur (fresh run or checkpoint resume) under the same
serialization lock and records the head checkpoint after the stream drains,
keeping the blocking and streaming workflow paths in lockstep with the Python
host. Honor stream:true in the HostingResponsesWorkflow sample by projecting
AgentResponseUpdateEvent updates over the Responses SSE wire. Add a streaming
resume test and update the README/spec.

* .NET: Cover Responses input adaptation to a typed workflow start executor

Demonstrate that HostedWorkflowState's generic RunOrResumeAsync<TInput> is the
input-adaptation seam (parity with Python's ResponsesChannel run hook): the app
adapts the Responses input into the workflow start executor's own type at the
call site. Add a typed-brief workflow and a test, and note the seam in spec-003.

* .NET: Drain workflow resume non-blocking to prevent hang and truncation

The resume drain used a SuperStepCompletedEvent{HasPendingRequests} proxy over
the blocking public WatchStreamAsync. That proxy (a) truncated a resumed turn
when a superstep both emitted a request and queued downstream work, and (b)
could fail to fire at all — re-introducing the indefinite hang — when a resume
input drove no superstep (e.g. a rejected non-chat input).

Make StreamingRun.WatchStreamAsync(bool blockOnPendingRequest, CancellationToken)
public and drain both the blocking and streaming resume paths with
blockOnPendingRequest:false, exactly matching the first-turn RunAsync semantics
(Run.RunToNextHaltAsync). Add guard tests: resume with a rejected input does not
hang, and a resume superstep with a request plus downstream work is not
truncated (verified red against the old proxy).

* .NET: Return file-store checkpoint index in commit order

CheckpointManager.GetLatestCheckpointAsync takes the last entry of a store's
index as the head checkpoint. FileSystemJsonCheckpointStore backed its index
with a HashSet, whose enumeration order is not contractual: after a rollback
frees and reuses a slot, enumeration can diverge from commit order, so the
durable read-through could resume a stale checkpoint. Mirror the HashSet with an
insertion-ordered list and enumerate it from RetrieveIndexAsync so 'latest' is
reliable. Add a CheckpointManager.GetLatestCheckpointAsync contract test over the
file store.

Note: the HashSet disorder is only reachable via the internal rollback path, so
the test locks the ordering contract rather than reproducing the rare disorder.

* .NET: Advance cursor when a streaming resume is abandoned

RunOrResumeStreamingAsync recorded the head checkpoint only after the stream was
fully enumerated. If an SSE consumer disconnected mid-turn after supersteps had
committed, the in-memory cursor kept the previous turn's head; because the next
turn is then a cursor hit, durable read-through could not self-heal, so it
resumed pre-disconnect state. Record the run's last committed checkpoint in a
finally so an abandoned stream still advances the cursor. Add a red/green test.

* .NET: Stream only the final agent's updates in the workflow sample

ExtractUpdates streamed every agent's updates, so the sequential Writer->Reviewer
sample streamed the intermediate draft and the final answer over SSE, differing
from the non-streaming response (final message only). Filter the streamed updates
to the final agent so streaming and non-streaming produce the same response.
Live-verified against Foundry: one output item streamed instead of two.

* .NET: Isolate the holder lock in the concurrency test

The concurrency test asserted the second same-session turn did not enter the
workflow, which also passes via the engine's concurrent-run ownership guard
(which faults) rather than the holder lock (which waits). Assert instead that the
second turn is not completed while the first holds the lock: a fault would
complete the task, so a pending task isolates the holder lock from the engine
guard. Verified red with the lock removed.

* Fix IDE1006 naming in tests; address review feedback and add hosting/live tests

* Document commit-order contract for ICheckpointStore.RetrieveIndexAsync

* Restructure hosting samples under af-hosting with client/server split matching Python parity

* Clarify hosting sample README wording and drop Python comparisons

* Make AgentSessionStore.DeleteSessionAsync abstract and rename session id parameter to sessionStoreId

* Rename OpenAIResponses id helpers and parse the request once for id extraction

* Reclaim per-session locks in HostedAgentState and demonstrate session locking in the agent sample

* Internalize per-session locking in HostedAgentState (automatic, on by default) and remove mirroring-Python wording from code and spec

* Remove HostedAgentState; app-owned routes use AgentSessionStore directly

HostedAgentState only bundled an AIAgent with an AgentSessionStore and, after
the per-session lock was removed, its GetOrCreateSessionAsync/SaveSessionAsync/
DeleteSessionAsync were pass-throughs that just bound the agent argument.
Create-on-miss already lives in the store (unlike Python, whose get/set-only
SessionStore justifies its AgentState holder), so the type earned its place
only via the lock.

Each AgentSessionStore.GetSessionAsync now returns an independent session
instance per call, so concurrent gets fork the same stored state (e.g.
branching from previous_response_id or managing several conversation ids)
without sharing an instance. The store does no cross-call locking; serializing
concurrent runs against the same id is the application's concern.

- Delete HostedAgentState and its unit tests.
- Rewire the local_responses sample and the OpenAI hosting unit/integration
  tests to call AgentSessionStore (GetSessionAsync/SaveSessionAsync) directly.
- Update ADR-0032, spec-003, and the af-hosting sample READMEs.

* Isolate hosted session snapshots and distinguish conversation vs response continuation

Mirrors the Python hosted-session isolation work: a hosted session read must be
an independent copy, and the app-owned route must persist under the right
continuation key depending on how the caller continued the thread.

- AgentSessionStore.GetSessionAsync: document the isolation invariant (each
  call returns an independent AgentSession so concurrent branches from one
  previous_response_id do not observe each other's mutations or alter stored
  state); fix the stale "or null if not found" wording (in-box stores return a
  fresh created session on miss). The in-box stores already satisfy this via a
  serialize/deserialize snapshot round-trip.
- local_responses sample + hosting unit-test route: choose the save key by
  channel. A stable conversation id is a mutable head (write back under the
  same id; app owns single-writer coordination). A previous_response_id
  continuation or first turn is an immutable snapshot (save under the new
  response id so branches from the same prior response stay independent).
- Add regression tests: independent get returns a distinct instance
  (InMemoryAgentSessionStore); previous_response_id supports independent
  branches ([1,2,2,3,3]); conversation id advances the mutable head ([1,2]).
- Update the sample README and ADR-0032 wording.

* Add workflow-factory support to HostedWorkflowState for concurrent sessions

HostedWorkflowState backed every session with one shared Workflow instance and
serialized all turns through a lock, so independent sessions could not run
concurrently. Add a workflow-factory constructor and remove the run lock.

- New constructor HostedWorkflowState(Func<CancellationToken, ValueTask<Workflow>>
  workflowFactory, ..., bool cacheWorkflow = false):
  - cacheWorkflow: false (default) builds a fresh instance per run, so independent
    sessions run in parallel. A resume rehydrates a fresh instance from the
    session's checkpoint in the shared store.
  - cacheWorkflow: true builds the workflow once, lazily on first use, and reuses
    it (a deferred, cached target that, like a shared instance, cannot run
    concurrent turns).
- Remove the internal SemaphoreSlim run lock and IDisposable; the instance
  constructor is unchanged in behaviour (one shared instance still cannot run
  concurrent turns). Turns are no longer serialized by the holder; a single
  writer per session is the application's responsibility.
- Switch the local_responses_workflow sample to the factory constructor with an
  explicit cacheWorkflow: false, and document the option.
- Add tests: parallel independent sessions (factory), fresh-instance resume,
  cached factory builds once and reuses, uncached factory builds per run.
- Update ADR-0032, spec-003, and the sample README.

* Clarify in ADR-0032 how .NET covers AgentState factory and async-setup via DI

* Rebuild cached workflow after a faulted build and add checkpoint index dedup tests
2026-07-22 10:32:44 +00:00
Giles Odigwe 83ba938d1e Python: preserve Gemini 3 thought_signature across function-call replays (#7095)
* Python: preserve Gemini 3 thought_signature across function-call replays

Gemini 3 requires the opaque thought_signature attached to each functionCall
part to be echoed back on every replay of that call, or the request is rejected
with 400 INVALID_ARGUMENT. The signature previously survived only via
raw_representation, so any layer that reconstructs a FunctionCallContent (e.g.
harness tool approval) dropped it and broke the next step of the tool loop.

Capture the signature into additional_properties on parse and replay it when
building the Gemini Part, independent of raw_representation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 33233834-bc6e-4ad2-a6f3-6f1d6e57b1d2

* Store Gemini thought_signature as base64 for JSON-safe persistence

Content.additional_properties is serialized via json.dumps(message.to_dict())
by history providers (e.g. RedisHistoryProvider), which fails on raw bytes.
Store the thought_signature as a base64 string on parse and decode it back to
bytes when building the Gemini Part. Also narrow call_id/name in the round-trip
test to satisfy the type checkers.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 33233834-bc6e-4ad2-a6f3-6f1d6e57b1d2

* Harden Gemini thought_signature decode against corrupted history

Guard the untyped additional_properties value with an isinstance(str) check and
decode with validate=True, degrading gracefully (warn + drop the signature) on
malformed data instead of raising binascii.Error mid tool loop. Matches the
defensive base64 handling already used for data URIs in this file.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 33233834-bc6e-4ad2-a6f3-6f1d6e57b1d2

* Carry Gemini thought_signature on reasoning content via protected_data

Represent the signature as a text_reasoning content's protected_data (base64)
immediately preceding the function call, instead of a bespoke additional_properties
key. This uses the framework's first-class opaque-signature field (as Anthropic
does), survives streaming accumulation, and stays intact when the harness
reconstructs the function call. Replay correlates the signature by adjacency.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 33233834-bc6e-4ad2-a6f3-6f1d6e57b1d2

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-22 07:10:45 +00:00
Evan Mattson d97c901301 Harden workflow credential selection (#7249)
* Harden workflow credential selection

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d9fa4e9c-c32d-42fb-8ee4-4772473e6479

* Address workflow authentication review feedback

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d9fa4e9c-c32d-42fb-8ee4-4772473e6479

* Fail safely on membership lookup errors

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d9fa4e9c-c32d-42fb-8ee4-4772473e6479

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-22 15:51:08 +09:00
Tao Chen d1d2610b28 Add MCPStreamableHTTPTool security guidance for custom http client (#7245) 2026-07-22 04:57:09 +00:00
Tao Chen 848443ac68 [BREAKING] Python: Ensure session isolation for FHA invocation impl (#7158)
* Ensure session isolation for FHA invocation impl

* Fix type check errors

* Add user isolation to sample
2026-07-21 19:33:01 +00:00
Giles Odigwe 1466d68cf1 Python: make FoundryToolbox.as_skills_provider() disable_caching effective (#7135)
* Python: make FoundryToolbox.as_skills_provider() disable_caching effective

as_skills_provider() forwarded disable_caching to SkillsProvider, which
ignores it for a caller-supplied SkillsSource, so it was a no-op and the
toolbox re-read skill://index.json on every agent run.

Compose caching in as_skills_provider() instead: wrap the context-independent
_FoundryToolboxSkillsSource in DeduplicatingSkillsSource(CachingSkillsSource(...)).
Add a cache_refresh_interval param, fix the docstring, and add tests covering
cached, disabled, and refresh-interval behavior.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 84150ec4-6f7c-4ef8-b9fb-12fa11652773

* Clarify caller-invariant skill-set wording in as_skills_provider docs

Emphasize that the toolbox advertises the same skill set to every caller (the
per-request call-id governs execution/authorization, not which skills are
listed) rather than leaning on 'ignores SkillsSourceContext'.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 84150ec4-6f7c-4ef8-b9fb-12fa11652773

* Make MCP skills reconnect-safe via session_provider

Cached MCPSkill objects captured the MCP ClientSession at construction, so
after a FoundryToolbox reconnect (which replaces its session) load_skill and
read_skill_resource would fail against the closed session. This regressed once
as_skills_provider() started caching discovery by default.

Add an optional session_provider callable to MCPSkillsSource and MCPSkill
(exactly one of client or session_provider). When supplied, the session is
resolved on every fetch, mirroring how MCPTool resolves self.session live at
call time. _FoundryToolboxSkillsSource now passes a provider that returns the
toolbox's current session, so cached skills always use the live session.

The fixed client= path is unchanged and backward-compatible. Update core tests,
foundry_hosting tests, and core AGENTS.md.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 84150ec4-6f7c-4ef8-b9fb-12fa11652773

* Fix ty error: type captured session_provider as Callable in test

ty could not call the provider narrowed from \object\ (Top callable). Type the
captured value as Callable[[], object] and drop the redundant callable() assert.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 84150ec4-6f7c-4ef8-b9fb-12fa11652773

* Simplify _resolve_mcp_session_provider per review

Address review feedback: replace the dense (client is None) == (session_provider
is None) guard with explicit branches, and drop the cast by binding the narrowed
client to a typed local. Keeps strict 'exactly one' semantics (raises on both and
on neither), matching the codebase convention (e.g. security.py mcp_tool/url).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 84150ec4-6f7c-4ef8-b9fb-12fa11652773

* Add PR #7135 entries to the 1.12.0 changelog

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 84150ec4-6f7c-4ef8-b9fb-12fa11652773

* Drop redundant @pytest.mark.asyncio from MCP skills tests

asyncio_mode is 'auto', so the marker is unnecessary. Remove it from the whole
file for consistency with the async-by-default convention. Per review feedback.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 84150ec4-6f7c-4ef8-b9fb-12fa11652773

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-21 18:02:38 +00:00
Eduard van Valkenburg d08200d00e Python: Bump package versions for 1.12.0 release (#7238)
* Bump Python package versions for 1.12.0 release

Bump packages represented in the 1.12.0 changelog, promote Foundry Hosting, Azure Content Understanding, Gemini, Mistral, Monty, and Tools to beta, and apply the requested beta cohort date stamp. Root and core move to 1.12.0, released and RC packages use their selected increments, alpha packages including Hosting MCP use the 260721 stamp, and core floors are raised only for proven consumers.

Copilot-Session: 2dd9980a-b869-4c16-8642-75b7a6d6ebdf

* fix version in readme

* Add Responses conversation ID changes to release notes

Include the breaking Hosting Responses conversation ID helper changes from #7234 in the Python 1.12.0 changelog.

Copilot-Session: 2dd9980a-b869-4c16-8642-75b7a6d6ebdf
2026-07-21 15:45:00 +00:00
Binit Mohanty fb38b1d10a Python: Fix PropertySchema.to_json_schema() not recursing into nested array items / object properties (#7200)
* Python: Fix PropertySchema.to_json_schema() not recursing into nested schemas

Nested array 'items' and object 'properties' kept the declarative 'kind'
key and empty 'enum' placeholders, producing JSON Schema OpenAI rejects
('schema must have a type key'). Recursively apply the same conversion the
top-level properties loop performs, including the serialized named-list
properties shape and nested required arrays.

Fixes #7198

(cherry picked from commit c156ffd05924fb5a1884625f2fc3d9bdc3e152b1)

* Python: Validate nested properties list before mutating to avoid partial conversion

Review feedback: the list-shaped properties branch popped name/required from
each element and returned on the first unexpected one, leaving earlier
elements half-converted. Validate the whole list first so an unexpected
shape leaves the node fully untouched.

* Python: Type nested-properties normalization for strict Pyright and drop unreachable dict branch

ObjectProperty always stores nested properties as a named list, so the
elif-dict branch in _normalize_nested_schemas was unreachable; remove it
and flatten the list conversion behind an early return. Cast the narrowed
items/props values so strict Pyright no longer reports unknown types.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Python: Emit additionalProperties: false on nested object nodes in PropertySchema.to_json_schema()

OpenAI strict structured outputs require additionalProperties: false on
every object node, but the chat clients only inject it at the schema
root, so declarative schemas with nested objects (e.g. array items)
failed with a schema-validation 400. Route the top-level properties loop
through _normalize_schema_node so all object nodes get the key, and add
a live OpenAI integration test covering the nested array-of-objects
response_format shape.

Verified live against the Responses API: the previous emission fails
with "In context=('properties', 'issues', 'items'),
'additionalProperties' is required to be supplied and to be false";
the new emission returns valid structured output.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 15:38:38 +00:00
Eduard van Valkenburg a70fe21298 [BREAKING] Python: add Responses conversation ID helper (#7234)
* Python: add Responses conversation ID helper

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 2dd9980a-b869-4c16-8642-75b7a6d6ebdf

* Python: make Responses session flag optional

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 2dd9980a-b869-4c16-8642-75b7a6d6ebdf

* Python: correlate Responses session return types

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 2dd9980a-b869-4c16-8642-75b7a6d6ebdf

* Python: clarify Responses conversation parameter

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 2dd9980a-b869-4c16-8642-75b7a6d6ebdf

* Python: clarify streaming conversation parameter

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 2dd9980a-b869-4c16-8642-75b7a6d6ebdf

* Python: include conversation in created event

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 2dd9980a-b869-4c16-8642-75b7a6d6ebdf

* Python: warn on nonstandard Responses IDs

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 2dd9980a-b869-4c16-8642-75b7a6d6ebdf
2026-07-21 15:17:14 +00:00
Roger Barreto f6a3c43e9a .NET: Add source-type-agnostic consent regression test for a2a_preview (#7229) 2026-07-21 15:08:06 +00:00
westey e6f7b3e9be Version bump for .net release (#7237) 2026-07-21 14:59:07 +00:00
Eduard van Valkenburg a1f3e536bc Python: Add MCP hosting helpers (#7209)
* Python: Add MCP hosting helpers

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 5d6987cd-1b67-4ba1-8b54-3c50da6e7607

* Python: Address MCP hosting review comments

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 5d6987cd-1b67-4ba1-8b54-3c50da6e7607

* renamed to AgentMCPTool
2026-07-21 14:46:46 +00:00
westey c033adb1f4 .NET: [BREAKING] Graduate HarnessAgent (#7119)
* Graduate HarnessAgent

* Switch harness project to released and remove unreleased shell dependency

* Address PR comments.
2026-07-21 14:24:19 +00:00
Roger Barreto 09473fa7ed .NET: [BREAKING] Bind tool-approval responses to surfaced approval requests (#7111)
* .NET: Bind tool-approval responses to surfaced approval requests

Harden the tool-approval flow so an approved tool call always matches the
request the framework surfaced for approval.

Add ApprovalResponseBindingChatClient as the outermost decorator above
FunctionInvokingChatClient. It records each model-originated
ToolApprovalRequestContent in the session state and, on the next request,
binds every ToolApprovalResponseContent to its recorded request: the
response tool call is rebound to the recorded call, matched entries are
consumed for one-time use, and only approvals tied to a framework-issued
request take effect.

Apply the same binding in the ToolApprovalAgent harness by tracking the
requests it surfaces and binding collected responses to them during a
queue cycle.

Add ChatClientAgentOptions.DisableApprovalResponseBinding (default off) and
a UseApprovalResponseBinding builder extension for custom chat client stacks.
Includes unit tests for the decorator and the harness.

* .NET: Bind approval responses once per turn and avoid re-enumeration

Address review feedback on the approval-response binding decorator:
consume a matched request from the per-turn lookup so a duplicate response
with the same request id in one turn is honored only once, and return the
materialized message list instead of the original enumerable so a single-use
sequence is not enumerated twice. Rename the local pending list to
pendingRequests for clarity. Adds a duplicate-response regression test.

* .NET: Snapshot recorded approval requests and consume duplicates in the harness

Address review feedback on ToolApprovalAgent:
store a snapshot of each surfaced/pending approval request (cloned tool call
with a copied arguments dictionary) so a later mutation of the caller-visible
instance cannot change the recorded call used to bind the response, and consume
a surfaced request on match so a duplicate response with the same request id in
one pass is honored only once. Apply both symmetrically in the harness and the
ApprovalResponseBindingChatClient decorator. Adds regression tests for the
snapshot and duplicate-response cases.

* .NET: Address review feedback on approval-response binding

- Harness: store surfaced approval requests in a dictionary and consume matches directly, drop the extra hashset and the redundant record-time dedup; replace clear-on-resolution with a debug assert.
- Harness pipeline: add UseApprovalResponseBinding() as the outermost decorator in HarnessAgent (it uses UseProvidedChatClientAsIs) behind a new DisableApprovalResponseBinding option, with tests.
- Decorator: avoid message/content allocations when nothing changes, keep the original content when a response already matches the recorded call, clear pending each inbound turn, and shorten helpers.

* .NET: Compare tool calls by fields instead of serializing

Replace the JSON-serialization comparison in the approval-response binding
decorator with a direct field comparison. Fast-path FunctionCallContent by
comparing CallId, Name, and arguments field by field; any other tool call
shape rebinds. The comparison only skips an allocation (the call is always
rebound to the recorded request otherwise), so a miss just triggers a safe
rebuild. Adds a test that a matching response is forwarded unchanged.

* .NET: Bind approval responses against requests present in history

Fix a merge-queue regression where AG-UI mixed server/client tool invocation
stopped executing the server tool. The binding decorator validated approval
responses only against its own recorded pending state, so a matched approval
request/response pair replayed from conversation history was treated as
unbound and dropped, and the auto-approved server tool never ran.

Treat known requests as the recorded pending state plus any approval requests
already present in the current messages, and stop dropping approval requests
(a request in history is the pairing authority). A response with no known
request anywhere is still dropped, so a forged approval cannot execute.

Also address review feedback: return the mutable contents buffer from a
helper instead of a null-forgiving operator, and use clearer naming
(PrepareMutableContentsBuffer / mutableContentsBuffer). Adds regression tests
for a request in history and a response bound to a history request with empty
pending state.
2026-07-21 14:23:48 +00:00
Robbie Walmsley a4f02aabf0 Python: Fix header_provider headers not reaching streamable HTTP transport requests (#7218)
* Python: fix header_provider headers not reaching streamable HTTP requests

MCPStreamableHTTPTool.call_tool stores header_provider output in a
ContextVar, but the streamable HTTP transport sends requests from tasks
spawned at connect time, whose contexts never observe values set later.
The request hook therefore always read an empty dict on real connections
and the per-call headers (e.g. Authorization) were silently dropped.

Keep the ContextVar for in-context reads and add an instance-level
snapshot of the active call's headers that the request hook falls back
to across tasks.

* Python: serialize header_provider tool calls to prevent cross-call header mixing

Parallel tool invocations run concurrently per function-invocation batch,
so two call_tool invocations on the same MCPStreamableHTTPTool could
overwrite each other's active-header snapshot while requests were still
in flight, attaching the wrong per-call credentials. Hold a per-instance
lock for the duration of a header-bearing call, add a regression test
that fails without the lock, and normalize captured header casing in the
transport-task test.
2026-07-21 13:20:27 +00:00
HaoFeng Zhao afdf8af400 Python: Prevent compaction from emitting empty projections (#7219)
* Python: preserve a non-empty compaction projection

* Python: annotate compaction regression input

* Python: document compaction retention floor
2026-07-21 13:16:43 +00:00
Scarab Systems 9e836f7b42 Python: Return MCP tool-use sampling results (#7189)
* Python: support MCP sampling tool-use results

* Python: align MCP sampling test tool schemas

* Python: centralize MCP sampling content type
2026-07-21 12:27:34 +00:00
安妮的心动录 9cf5143321 .NET: Populate AgentResponse metadata in CopilotStudioAgent (#6791)
* .NET: Populate AgentResponse metadata in CopilotStudioAgent

Map CreatedAt, FinishReason, RawRepresentation and AdditionalProperties onto
AgentResponse and AgentResponseUpdate, and map the activity timestamp and
properties onto ChatMessage, so Copilot Studio agents expose the same metadata
surface as other AIAgent implementations. Streaming sets the finish reason on
the terminal update while still emitting already-received content if the source
faults. Add unit tests covering the metadata mapping.

* fix: add Async suffix to async test methods (IDE1006)
2026-07-20 21:38:24 +00:00
Giles Odigwe b6b16ddb75 Python: forward GitHubCopilotOptions verbatim to create_session (#7155)
* Python: forward GitHubCopilotOptions verbatim to create_session

Refactor the GitHub Copilot agent to forward the full options dict to the
Copilot SDK's create_session/resume_session instead of hand-mapping a fixed
subset. GitHubCopilotOptions stays as the curated, typed surface, but any
other create_session parameter (reasoning_effort, context_tier,
enable_citations, ...) is now passed through verbatim. Unknown keys surface
as TypeError from the SDK instead of being silently dropped.

De-duplicates the near-identical _create_session/_resume_session bodies into
a shared _build_session_kwargs helper.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f9d016d5-4d8c-43f0-a8fb-f3cf3d1ad7eb

* Python: address review feedback on GHCP options passthrough

- Strip agent-internal/client-level keys (on_pre_tool_use, on_function_approval,
  timeout, cli_path, log_level, base_directory) from the forwarded kwargs so they
  cannot leak into create_session/resume_session and raise TypeError.
- Source caller tools from the merged options layer so tools supplied via
  default_options are honored instead of silently dropped.
- Honor a caller-supplied native 'hooks' dict in _build_session_hooks (composing
  with the on_pre_tool_use shortcut) instead of unconditionally overwriting it.
- Validate mock create_session/resume_session calls against the real SDK
  signatures in tests so invalid kwargs surface as TypeError, and add regression
  tests for the passthrough contract.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f9d016d5-4d8c-43f0-a8fb-f3cf3d1ad7eb

* Python: avoid redundant re-read of model in _build_session_kwargs

model is popped from default_options into settings at init, so a per-run
model already lands in the merged kwargs. Keep that value when present and
only fall back to the resolved setting otherwise, instead of re-reading opts.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f9d016d5-4d8c-43f0-a8fb-f3cf3d1ad7eb

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-20 18:08:55 +00:00
Evan Mattson c218067646 Python: Consolidate dependency updates (#7204)
* Bump uv from 0.11.28 to 0.11.29 in /python

Bumps [uv](https://github.com/astral-sh/uv) from 0.11.28 to 0.11.29.
- [Release notes](https://github.com/astral-sh/uv/releases)
- [Changelog](https://github.com/astral-sh/uv/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/uv/compare/0.11.28...0.11.29)

---
updated-dependencies:
- dependency-name: uv
  dependency-version: 0.11.29
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* Bump zuban from 0.8.2 to 0.9.0 in /python

Bumps [zuban](https://github.com/zubanls/zubanls-python) from 0.8.2 to 0.9.0.
- [Release notes](https://github.com/zubanls/zubanls-python/releases)
- [Commits](https://github.com/zubanls/zubanls-python/compare/v0.8.2...v0.9.0)

---
updated-dependencies:
- dependency-name: zuban
  dependency-version: 0.9.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* PR #7146: Bump ty from 0.0.55 to 0.0.60 in /python

* Bump ruff from 0.15.20 to 0.15.22 in /python

Bumps [ruff](https://github.com/astral-sh/ruff) from 0.15.20 to 0.15.22.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.15.20...0.15.22)

---
updated-dependencies:
- dependency-name: ruff
  dependency-version: 0.15.21
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* Bump mypy from 2.2.0 to 2.3.0 in /python

Bumps [mypy](https://github.com/python/mypy) from 2.2.0 to 2.3.0.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v2.2.0...v2.3.0)

---
updated-dependencies:
- dependency-name: mypy
  dependency-version: 2.3.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* Bump prek from 0.4.8 to 0.4.10 in /python

Bumps [prek](https://github.com/j178/prek) from 0.4.8 to 0.4.10.
- [Release notes](https://github.com/j178/prek/releases)
- [Changelog](https://github.com/j178/prek/blob/master/CHANGELOG.md)
- [Commits](https://github.com/j178/prek/compare/v0.4.8...v0.4.10)

---
updated-dependencies:
- dependency-name: prek
  dependency-version: 0.4.10
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* Bump azure-ai-projects from 2.2.0 to 2.3.0 in /python

Bumps azure-ai-projects from 2.2.0 to 2.3.0.

---
updated-dependencies:
- dependency-name: azure-ai-projects
  dependency-version: 2.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* Bump types-python-dateutil in /python

Bumps [types-python-dateutil](https://github.com/python/typeshed) from 2.9.0.20260518 to 2.9.0.20260716.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-python-dateutil
  dependency-version: 2.9.0.20260716
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* Bump mypy from 2.2.0 to 2.3.0 in /python

Bumps [mypy](https://github.com/python/mypy) from 2.2.0 to 2.3.0.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v2.2.0...v2.3.0)

---
updated-dependencies:
- dependency-name: mypy
  dependency-version: 2.3.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* Bump botocore from 1.43.45 to 1.43.49 in /python

Bumps [botocore](https://github.com/boto/botocore) from 1.43.45 to 1.43.49.
- [Commits](https://github.com/boto/botocore/compare/1.43.45...1.43.49)

---
updated-dependencies:
- dependency-name: botocore
  dependency-version: 1.43.49
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* PRs #7144/#7147: Align lab uv and ruff pins

* Regenerate lockfile for Python Dependabot PRs #7144-#7153

* Python: Support azure-ai-projects 2.3 session operations (#7150)

* Python: Apply Ruff 0.15.22 suppression updates (#7147)

* Python: Update tests for ty 0.0.60 (#7146)

* Python: Update Foundry samples for azure-ai-projects 2.3 (#7150)

* Python: Address dependency rollup review comments

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-20 18:01:24 +00:00
westey ac474100ce Python: [BREAKING] Graduate create_harness_agent out of experimental (#7120)
* Graduate harness agent

* Add agents.md update

* Fix build errors

* Address PR comments

* Fix build error
2026-07-20 18:00:28 +00:00
Theo van Kraay a057cd505c Python: Add agent-framework-azure-cosmos-memory context provider (#6719)
* Add agent-framework-azure-cosmos-memory context provider (draft)

Introduces CosmosMemoryContextProvider, a ContextProvider that wraps the azure-cosmos-agent-memory toolkit to give agents long-term, Cosmos DB-backed memory (fact/procedural recall + user summaries). Includes package scaffolding, unit tests (mocked client), live Azure integration tests (marked), samples, README, and AGENTS.md.

Draft: uv.lock is intentionally left unchanged. This package depends on azure-cosmos-agent-memory (requires Python >=3.11), which is unsatisfiable against the workspace's current >=3.10 floor, so adding it to the shared lock requires a workspace decision (raise floor to 3.11 or exclude from workspace). Test coverage to be expanded.

* ci: exclude azure-cosmos-memory from uv workspace resolution

The package depends on azure-cosmos-agent-memory which requires Python
>=3.11 and a prompty pre-release (>=2.0.0a9). Both are unsatisfiable
against the workspace's >=3.10 floor and pre-release policy, causing
uv sync to fail in every Python CI job. Exclude the package from the
shared workspace so it is resolved and tested as a standalone package.

* ci: fix code-quality failures for azure-cosmos-memory

- Strip trailing whitespace from package files (pre-commit trailing-whitespace hook)
- Exclude the package README from markdown-code-lint: the package is excluded
  from the uv workspace, so its README snippets import a module that is not
  installed in the workspace env and Pyright cannot resolve it

* Exclude azure-cosmos-memory README from markdown-code-lint task

* Address PR review comments on cosmos-memory context provider

- Wire credential into Cosmos and AI Foundry clients; let toolkit own
  DefaultAzureCredential when none supplied (remove dead import).
- Honor auto_extract=False by zeroing extraction/summary cadence thresholds.
- Skip whitespace-only conversation turns and store stripped content.
- Show confidence 0.0 and coerce confidence to float in _format_memories.
- Register both 'integration' and 'azure' pytest markers accurately.
- Fix duplicated install block in README.
- Update and extend unit tests for new credential wiring and fixes.

* Include azure-cosmos-memory in the uv workspace

Follow the github_copilot pattern for a package with a Python 3.11-only
dependency: lower requires-python to >=3.10 and gate azure-cosmos-agent-memory
behind a python_version >= '3.11' marker. Add a direct, gated prompty
pre-release dependency so the workspace's if-necessary-or-explicit prerelease
policy permits the toolkit's transitive prompty requirement. Guard the test
modules with pytest.importorskip so the 3.10 CI leg skips cleanly. Remove the
workspace exclude and the markdown-code-lint exclude, and regenerate uv.lock.

* Address review feedback on cosmos-memory provider

Rename provider parameters to match Agent Framework conventions:
foundry_endpoint (was ai_foundry_endpoint) and embedding_model/chat_model
(were *_deployment_name). Move DEFAULT_* to module-level constants, type
memory_types as a Literal, use DEFAULT_CONTEXT_PROMPT as the default value,
and add ProcessorConfig/CosmosMemorySettings TypedDicts. Resolve connection
settings via agent_framework load_settings with required-field validation,
replacing the manual getenv/raise blocks. Scope user_id/thread_id to the
provider state and drop the unpreventable first-turn warning.

Rewrite the samples around Agent (not raw SessionContext), provider-scoped
state, and session-id threading; use PEP 723 inline dependencies instead of a
samples dependency group; use a plain input() loop; remove the dead custom
processor stub. Update README/AGENTS for the renamed parameters and env vars.
Add a samples ruff per-file-ignores entry now that the package is linted in CI.

* Add emulator-backed vector search integration test

Bump azure-cosmos-agent-memory to >=0.2.0b2 (adds the embeddings/chat client
injection seam) and add tests/test_emulator.py: an integration (not azure)
suite that exercises real Cosmos vector search with a quantizedFlat index
against a local Cosmos DB emulator, using deterministic in-memory fakes for
embeddings and chat so no Azure AI Foundry account or LLM is required.

To run on a stock emulator the fixture strips the toolkit's full-text index
(the provider only does pure vector search) and requests provisioned autoscale
throughput instead of serverless. The suite skips cleanly when no emulator is
reachable.

* Fix CI typing and package checks for azure-cosmos-memory

The package recently joined the uv workspace, so its source and tests are now covered by the Test Typing Checks and Package Checks gates for the first time.

tests: rename stale constructor kwargs to the current provider API (foundry_endpoint/embedding_model/chat_model); use a typed _STUB_AGENT for the unused agent param so pyright/pyrefly/ty/zuban all accept it; make processor_config values ints; assert non-None memory_client in the emulator tests.

source: relax reportUnknown*/reportOptional* for this package only (the toolkit ships no py.typed; mirrors the hosting-telegram precedent); decouple the conditional toolkit import from the annotation type; use settings.get(); fix memory_types list invariance; drop a redundant None guard; read role via getattr.

* Apply pyupgrade: single-arg AsyncGenerator in test_integration

* Make Cosmos memory extraction drain transparently on provider exit

The provider now drains in-flight background memory extraction in __aexit__, so applications no longer need to call flush() in their own control flow; the client's close() would otherwise cancel pending extraction tasks. flush() is hardened against clients that expose no usable background-task registry.

sample: interactive_chat reads input via asyncio.to_thread so the event loop stays free and background extraction runs during the session; removes the manual flush now that the provider drains on exit.

tests: add explicit transparent-extraction integration tests (emulator: after_run schedules extraction and __aexit__ drains it; live Azure: a fact is extracted and recalled in a later session with no manual flush). Emulator tests reuse a single fixed database to avoid exhausting the emulator's partition budget across runs.

* Add custom extraction-prompt seam and sample to cosmos-memory provider

Adds a prompts_dir option to CosmosMemoryContextProvider that points the Agent Memory Toolkit pipeline at a caller-supplied directory of Prompty templates, so callers can override extract_memories.prompty to control what the extraction LLM produces. The toolkit exposes no public prompts-directory seam, so the provider contains the one internal touch (swapping the pipeline's template loader after the store connects); applies to both provider-built and supplied clients.

sample: interactive_chat_custom_extraction.py - the interactive chat wired with a custom coding-assistant extraction rubric. It derives a complete prompts directory at runtime (copies the bundled templates and augments extract_memories.prompty) so it stays schema-compatible with the installed toolkit.

tests: unit tests assert the provider redirects the pipeline loader only when prompts_dir is set; an emulator integration test proves end to end that a unique marker in a custom extract_memories.prompty reaches the extraction LLM call.

* docs: document prompts_dir custom-extraction seam in cosmos-memory README

Replaces the stale, non-functional CustomMemoryProcessor snippet with the working prompts_dir approach, lists the new interactive_chat_custom_extraction.py sample, and corrects the interactive-sample feature list.

* Address review: rename _new_session, drop defensive toolkit import guard

Sample (comment): rename _new_thread to _new_session in both interactive samples (a new session is the new thread).

Provider (comment): replace the _memory_toolkit_available flag + __init__ ImportError guard with a plain guarded import that re-raises a clear ImportError, matching the github_copilot package's pattern for its 3.11-only SDK. Kept requires-python >=3.10 (bumping this one workspace member to 3.11 would force the entire uv workspace lock floor to 3.11). Tests now run importorskip before importing the package, mirroring github_copilot.

* Pass cadence via cadence_thresholds instead of mutating os.environ

* Mark package alpha and drop private naming in samples

* Require Python 3.11 and inject user summary as untrusted context

* CI: exclude azure-cosmos-memory from uv sync on Python 3.10

* Re-trigger CI (flaky external link check)

* Require chat/embedding models instead of silent defaults

* Fix pyright: narrow resolved chat/embedding models to str

---------

Co-authored-by: Theo van Kraay <thvankra@microsoft.com>
2026-07-20 09:44:11 +00:00
Evan Mattson c66bb39ea2 Normalize durable workflow inputs (#7205) 2026-07-20 08:33:20 +00:00
Yufeng He 7c6b1e975f Python: fix compaction token count inflating non-ASCII text (#7124)
TokenBudgetComposedStrategy estimates tokens by feeding a JSON-serialized
message to the tokenizer, but _serialize_message() used ensure_ascii=True.
That escapes non-ASCII text into \uXXXX sequences, so CJK and other
non-Latin content is token-counted as the escape sequences rather than the
characters the model actually sees, inflating the estimate (~1.6x for mixed
Japanese, more for pure CJK) and skewing compaction/token-budget decisions.

Serialize with ensure_ascii=False, matching the ensure_ascii=False already
used elsewhere in this module. Only affects token estimation; the serialized
string is never stored or transmitted.

Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com>
2026-07-19 12:44:40 +00:00
Yufeng He 0d2925037d Python: preserve explicit null arguments in auto function calling (#7108)
* Python: preserve explicit null arguments in auto function calling

FunctionTool.invoke dumped validated arguments with model_dump(exclude_none=True),
which strips any argument the model set to null. A required nullable parameter
(e.g. unit: Literal["C","F"] | None) that the model deliberately sets to null was
therefore dropped, and the function failed to invoke on the missing argument.

Use exclude_unset instead: keep the arguments the model actually provided (null
included) and omit only the ones it left out, so the function's own defaults still
apply. Because the input model is generated from the function signature, its field
defaults match the signature defaults, so omitted optionals are unchanged.

Fixes #5934

Signed-off-by: Yufeng He <40085740+he-yufeng@users.noreply.github.com>

* Python: extend null-arg fix to the auto function-calling path

The earlier change fixed FunctionTool.invoke, but _auto_invoke_function
(the path a model-emitted function_call actually takes) still ran
model_dump(exclude_none=True), so an explicit null for a required
nullable argument was still dropped and the call failed with a missing
argument. Switch it to exclude_unset to match invoke, and add a
regression test that drives _auto_invoke_function with an explicit null.

---------

Signed-off-by: Yufeng He <40085740+he-yufeng@users.noreply.github.com>
Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com>
2026-07-19 12:43:41 +00:00
Eduard van Valkenburg b5e635ed4d Python: isolate hosted session snapshots (#7141)
* Python: isolate hosted session snapshots

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 75d26ffd-7dc7-46b3-9966-9aaebb7b6bc3

* Python: avoid duplicate conversation snapshots

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 75d26ffd-7dc7-46b3-9966-9aaebb7b6bc3

* added some notes in the docstring
2026-07-18 11:59:24 +00:00
Eduard van Valkenburg 1036fa7438 Python: docs: add self-hosting sample snippets (#7104)
* docs: add self-hosting sample snippets

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 636b7fb2-381a-4d62-b5cf-d029efa3ad20

* docs: use stable hosting sample ranges

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 636b7fb2-381a-4d62-b5cf-d029efa3ad20

* docs: address hosting sample review feedback

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 636b7fb2-381a-4d62-b5cf-d029efa3ad20

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-17 22:10:28 +00:00
Eduard van Valkenburg 62da382082 Python: Optimize shared serialization paths (#7165)
* Optimize core serialization paths

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: cda42f21-2500-4f78-a527-d6eeaeef922b

* Streamline AG-UI serialization

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: cda42f21-2500-4f78-a527-d6eeaeef922b

* Document shared serialization guidance

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: cda42f21-2500-4f78-a527-d6eeaeef922b

* Bound serialization protocol cache

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: cda42f21-2500-4f78-a527-d6eeaeef922b
2026-07-17 22:09:53 +00:00
Eduard van Valkenburg 3604ba70f6 Python: Normalize chat finish reasons (#7105)
* Python: Normalize chat finish reasons

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 45b65bfa-8e36-47b0-99d9-ec58ec60ace1

* Preserve Copilot finish reasons

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 45b65bfa-8e36-47b0-99d9-ec58ec60ace1

* fix claude finish reasons
2026-07-17 22:05:36 +00:00
Marco Minerva 3ab2630243 .NET: Refactor Workflows MessageMerger to preserve message order and structure (#6826)
* Refactor MessageMerger to preserve message order

Refactored MessageMerger to delegate update grouping and merging to M.E.AI, preserving the correct order and structure of assistant messages, especially for reasoning content without message IDs. Removed per-message bucketing and CreatedAt-based sorting. Added tests to verify message order and correct merging of reasoning and text updates.

* Set CreatedAt from merged responses preservation of original message timestamps during merging.

* Set merged message CreatedAt to current UTC time

Removed logic for tracking unique creation times and now always assign DateTimeOffset.UtcNow to the merged response's CreatedAt property. This simplifies timestamp handling during message merging.

* Refactor MessageMerger id-less folding logic

Refactored MessageMerger to fold identifierless reasoning segments into the following id'd message at the flattened-message level, ensuring correct merging across response buckets (fixes #6329). Updated ComputeMerged to merge id-less messages with the next message of the same role. Removed redundant per-bucket folding logic. Added unit tests to verify correct folding behavior and role matching.

* Remove unused property

 Removed the unused Role property from MessageMergeState for code cleanliness.

* Refactor MessageMerger to iterate backward for merging

Changed MessageMerger to iterate messages in reverse order, ensuring all consecutive messages without IDs preceding a message with an ID are merged correctly. Updated merging logic, index handling, and comments to reflect this new approach.

* Update code comment to better reflect its behavior.

---------

Co-authored-by: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com>
2026-07-17 17:10:14 +00:00
Eduard van Valkenburg bc59c72170 Python: Add A2A hosting helpers (#7050)
* Python: Add A2A hosting helpers

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 5d6987cd-1b67-4ba1-8b54-3c50da6e7607

* Python: Preserve final A2A streaming output

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 5d6987cd-1b67-4ba1-8b54-3c50da6e7607

* Python: Clarify A2A conversion boundary

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 5d6987cd-1b67-4ba1-8b54-3c50da6e7607

* Python: Document A2A sample auth boundary

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 5d6987cd-1b67-4ba1-8b54-3c50da6e7607

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-17 16:58:39 +00:00
ssccinng d5f2c77b35 .NET: Honor terminal workflow outputs in Workflow.AsAIAgent responses (#6212)
* Fix workflow agent terminal output responses

* Address workflow agent response review feedback

---------

Co-authored-by: Peter Ibekwe <109177538+peibekwe@users.noreply.github.com>
2026-07-17 15:36:26 +00:00
VectorPeak 6afae2f9b4 Python: raise ValueError for malformed data URIs (#6916)
* Python: raise ValueError for malformed data URIs

* Address malformed data URI review feedback

---------

Co-authored-by: VectorPeak <VectorPeak@users.noreply.github.com>
Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com>
2026-07-17 11:40:20 +00:00
Benke Qu cad81923e3 Python: fix: concurrent_agents sample incorrectly treats output as list[Message] (#6548)
* Python: fix: concurrent_agents sample treats output as list[Message] but it is AgentResponse

The default ConcurrentBuilder aggregator yields AgentResponse, not
list[Message]. The sample incorrectly cast the output to list[Message]
and iterated it directly. Fix by checking isinstance(output, AgentResponse)
and iterating output.messages instead.

* fix: remove warning print per review feedback

* fix: address review - remove unused Message import, update docs and sample output

- Remove unused Message import
- Update docstring: default aggregator yields AgentResponse objects, not list[Message]
- Fix sample output: remove user prompt entry (aggregator returns only assistant messages)
- Renumber sample output entries (researcher=01, marketer=02, legal=03)

---------

Co-authored-by: Benke Qu <bequ@microsoft.com>
Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com>
2026-07-17 11:38:04 +00:00
Ahmed Muhsin 5ab8877ba5 Python: HITL respond-URL addressing from inside workflows (#7001)
* feat(durabletask): surface HITL respond-URL addressing to workflow executors

Let a workflow notify a human reviewer (e.g. email an approval link) from inside the
graph, without the caller threading the instanceId/requestId by hand.

- durabletask: the orchestrator injects host_context {instance_id, workflow_name,
  request_path_prefix} into each activity input; CapturingRunnerContext surfaces it as
  host_metadata. No new core API.
- azurefunctions: WorkflowHitlContext.from_context(ctx) builds the canonical
  respond/status URLs (returns None in-process so callers degrade gracefully).
  Re-exported through the agent_framework.azure lazy namespace.
- Nested sub-workflows: the address context (root instance + workflow name +
  accumulated {executor}~{ordinal}~ prefix) propagates down call_sub_orchestrator via a
  new SUBWORKFLOW_ADDRESS_KEY marker, so an executor at any depth builds a URL that
  targets the addressable top-level instance with a qualified request id. The per-child
  ordinal matches the read-side enumerate() index used by the status/respond endpoints.
  The marker is stripped from untrusted input alongside SUBWORKFLOW_INPUT_KEY
  (confused-deputy / info-leak guard).
- Samples 12 and 13 reworked into the retry-safe two-step notify pattern: the emitter
  generates an explicit request id and a downstream NotifyExecutor builds the URL and
  notifies, so failed upstream retries never produce a dead link.

Tests: unit coverage for the metadata round-trip, address/ordinal agreement (fan-out at
depth and nested prefix accumulation), marker stripping, and URL building; integration
tests assert the helper-built URL equals the server respondUrl and resumes the run, for
both the flat (12) and nested (13) samples.

* refactor(durabletask): read back request_info id instead of generating one in samples

Add WorkflowHitlContext.pending_request_id(ctx), an async helper that returns the
id request_info just generated (read from the runner context's pending request-info
events). This works on any host via the core RunnerContext protocol method, so it
needs no core change.

Samples 12 and 13 now call request_info() and read the id back to forward to the
NotifyExecutor, instead of minting a uuid by hand and passing request_id=. The
read-back happens in the same activity execution that generated the id, so the
pending request event and the notify message still commit together with the same id
(retry-safe; failed upstream retries notify no one).

* docs(azurefunctions): document request_info id read-back and notify safety

Tighten pending_request_id docstring to require calling it immediately after request_info, and explain why that is safe on the durable host (each executor runs in its own activity with its own runner context, so the pending set only holds this executor's requests and the newest is the one just emitted). Document the two-step notify pattern in the 12 and 13 sample READMEs, including the downstream-notifier retry safety and the nested address-prefix propagation.

* fix(python): resolve ty typing error and address PR review comments

- test_subworkflow_orchestration: replace the mypy-only type:ignore[arg-type] with a cast so the ty checker passes too (the other four checkers already honored the ignore).

- samples 12/13 README: guard the notify snippet against None before build_respond_url to match the documented graceful-degradation behavior.

- integration tests 12/13: reword comments that implied request_info now generates an explicit uuid4; it generates the id internally by default.

* fix(python): honor configurable Functions route prefix and address HITL PR review

- Resolve the route prefix from host.json (extensions.http.routePrefix, default api) in a new azurefunctions _routes module, used by both the server endpoints and WorkflowHitlContext, so a custom or empty routePrefix no longer 404s respond/status URLs (was hardcoded /api/ in four places).

- Extract respond/status URL construction into one shared builder called from _app.py and _hitl_context.py, removing the sync-by-test duplication.

- Broaden loopback detection (localhost, 127.0.0.0/8, 0.0.0.0, ::1, [::1]) via a _is_loopback helper so local links use http.

- Pin the host_context key names as shared constants in durabletask so producer and azurefunctions consumer cannot drift.

- Reword a stale base_url comment to reference WEBSITE_HOSTNAME.

- Add unit tests for the route module and loopback handling.

* refactor(azurefunctions): derive server-side HITL URLs from the request URL

The run and status endpoints now derive the base URL and route prefix from the incoming request URL (the value the host actually routed) via split_request_url, so the caller-visible respond/status URLs no longer depend on reading host.json on the server. The in-workflow helper keeps reading host.json since it has no request context. Replaces strip_route_prefix and updates its tests.

* test(durabletask): enforce sub-workflow ordinal and read-index agreement

Extract the read-side subworkflows grouping into a shared _index_subworkflows helper (used by the orchestrator) and add test_readside_index_matches_dispatch_ordinal, which round-trips a fan-out through that helper and asserts subworkflows[executor][ordinal] resolves to the child the dispatch stamped that ordinal onto. Turns the previously comment-only write-ordinal / read-index invariant into a shared, CI-enforced one.

* fix(azurefunctions): suppress bandit B104 on loopback host set
2026-07-16 22:06:25 +00:00
Jose Luis Latorre Millas f4e49958f3 samples: add AgentMemory (Neo4j-agent memory reimplemented in NET ) shopping assistant sample (#7096)
* samples: add Neo4j Shopping Assistant (standalone, published AgentMemory 1.0.1)

The .NET port of the official Neo4j Agent Memory "retail assistant" example
(neo4j-labs/agent-memory examples/microsoft_agent_retail_assistant, referenced
from the Learn integration page), which is currently Python-only.

Wires Neo4jMemoryContextProvider (AIContextProvider), MemoryToolFactory
memory tools, and a ProductCatalog of retail tools over a Neo4j :Product
graph, via the published AgentMemory + AgentMemory.AgentFramework 1.0.1
NuGet packages.

Lives at the repo root rather than under dotnet/samples/: that tree is
.NET 10 + Central Package Management + Microsoft.Agents.AI ~1.13 with
source ProjectReferences, while AgentMemory currently targets net9.0 +
Microsoft.Agents.AI 1.9.0. A repo-native version needs AgentMemory bumped
to track the newer Agents.AI/Extensions.AI line first. Cross-linked from
dotnet/samples/02-agents/AgentWithMemory/README.md as a "See also" entry,
same pattern already used for the cross-folder Custom Memory Implementation
link.

Verified: dotnet build succeeds (0 warnings, 0 errors) against the published
packages, proving the AgentMemory public surface is package-consumable.
Matches sibling AgentWithMemory samples' conventions (BOM + copyright file
header on .cs files, README sections: Features Demonstrated / Prerequisites
/ Environment Variables / Run the Sample / Expected Output).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* samples: move Neo4j shopping assistant into AgentWithMemory as Step06

Relocates the standalone shopping-assistant sample from the repo root into
dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step06_MemoryUsingAgentMemory,
following that folder's naming/README/solution conventions. Renames its identity
from "Neo4j" to "AgentMemory" (the library it actually demonstrates) since this
is a community .NET port, not an officially recognized Neo4j integration - Neo4j
is still referenced where it's a genuine technical detail (the graph backing
store, env vars, Cypher). Adds empty Directory.Build.props/targets markers so it
stays isolated from the repo's net10.0/CPM build, and registers it (skipped, like
the Mem0 sample) in the CI sample-verification list since it needs a live Neo4j
instance.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Update dotnet/samples/02-agents/AgentWithMemory/README.md

Co-authored-by: westey <164392973+westey-m@users.noreply.github.com>

* Update dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step06_MemoryUsingAgentMemory/AgentWithMemory_Step06_MemoryUsingAgentMemory.csproj

Co-authored-by: westey <164392973+westey-m@users.noreply.github.com>

* cleanup :)

* DefaultAzureCredential warning

* fixes - simplification userId

* minor doc fix

* NU1015 fix

* PR review fixes-improvements

* Bump AgentMemory to 1.2.0, let the context provider surface memory tools

WithMemoryOwnerScoping(sp) (1.1.0) already removed the need to manually
wrap agent.RunAsync in ownerContext.BeginOwnerScope(userId). This picks
up 1.2.0's ExposeMemoryToolsFromContextProvider option, so
Neo4jMemoryContextProvider now appends the memory tools to AIContext.Tools
itself on every model call — no more separate MemoryToolFactory wiring,
AIContextProviders = [memoryProvider] is enough.

Addresses westey-m's PR review suggestion.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* improvements according to pr review comments

* Fix CI: use plural TargetFrameworks to actually restrict this sample to net10.0

Directory.Build.props sets a repo-wide TargetFrameworks (plural) list
before this project's own properties are evaluated, and the SDK decides
multi-targeting from that plural property at Sdk.props time. The prior
singular TargetFramework=net10.0 override didn't take effect early
enough, so restore still ran against net9.0/net8.0/netstandard2.0/net472
too - frameworks the published AgentMemory 1.2.0 packages don't support
(NU1202), plus surfaced an OpenTelemetry.Api advisory as an error
(NU1902) since TreatWarningsAsErrors is on repo-wide.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Fix CI: pin OpenTelemetry.Api to unblock NU1902 audit failure

The sample opts out of central package management, so it was pulling in
OpenTelemetry.Api 1.12.0 transitively (via Microsoft.Agents.AI), which has
a known moderate-severity vulnerability (GHSA-g94r-2vxg-569j). The repo
treats NuGet audit warnings as errors, so restore failed outright and took
down every dotnet-build matrix leg plus check-format.

Pinned OpenTelemetry.Api to 1.15.3, matching Directory.Packages.props.
With restore succeeding, previously-masked analyzer/format issues surfaced
and are fixed too: RCS1118 (const local for immutable Cypher queries),
CA1859 (List<IRecord> param instead of IReadOnlyList<IRecord>), and IDE1006
naming violations (s_seed field prefix, PascalCase Cypher/Shopper consts).

Verified locally with the same mcr.microsoft.com/dotnet/sdk:10.0 image CI
uses: dotnet build --warnaserror and dotnet format --verify-no-changes both
pass clean, and a full solution build completed ~24 min with zero errors.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: westey <164392973+westey-m@users.noreply.github.com>
2026-07-16 15:39:23 +00:00
Eduard van Valkenburg 5282c158aa .NET: Fix LocalCodeAct validation and package checks (#7138)
* Fix LocalCodeAct validation and package checks

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: aecf332f-b940-41a7-ac3a-6fbbe9892141

* Address LocalCodeAct alias review feedback

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: aecf332f-b940-41a7-ac3a-6fbbe9892141
2026-07-16 15:17:52 +00:00
feiyun0112 dde7635760 .NET: [Feature]: .NET Improve ChatClientAgentSession constructor (#7142)
* .NET: [Feature]: .NET Improve ChatClientAgentSession constructor

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* test: make deserialize test actually reproduce issue #7109

VerifyDeserializeWithWhenWritingNullOptions passed against both the old
and the fixed constructor, so it did not guard against the regression.

The bug only reproduces when required constructor parameters are respected
(the issue uses RespectRequiredConstructorParametersDefault=true). With
WhenWritingNull a null conversationId is omitted from the JSON, and STJ then
throws 'missing required properties including: conversationId' because the
constructor parameter had no default value.

Adding RespectRequiredConstructorParameters = true to the test options makes
the test red against the parameter-without-default constructor and green with
the default-valued constructor parameters, so it now protects the fix.

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Roger Barreto <19890735+RogerBarreto@users.noreply.github.com>
2026-07-16 15:06:37 +00:00
King Star 85c00fc55b .NET: preserve HeadTailBuffer UTF-8 order (#7128)
Co-authored-by: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com>
2026-07-16 10:03:43 +00:00
westey f19a129b55 Graduate FileMemoryProvider (#7114) 2026-07-16 09:38:59 +00:00
westey a376577263 Gradudate FileMemoryProvider (#7113) 2026-07-15 22:57:21 +00:00
Tao Chen b2549337ff Python: Best effort to serialize tool def to Json for observability (#7029)
* Best effort to serialize tool def to Json

* Fix formatting

* Fix tests

* Optimize json serialization

* Best effort: Add secret filtering

* Remove frozen set and only convert required fields

* Fix tests

* Address comments

* Fix typing
2026-07-15 20:43:36 +00:00
Peter Ibekwe 05834b56e3 Fix message ordering in workflow-hosted agents (#7123) 2026-07-15 19:40:47 +00:00
Roger Barreto 42ae534a07 CI: resolve PR author in community team check (#7129)
* CI: resolve PR author in community team check

pull_request_target events expose the author on payload.pull_request, not payload.issue. Read that field first and fall back to pulls.get so limit-community-prs no longer calls issues.get and fails with 401.

* Docs: clarify issueNumber accepts PR numbers
2026-07-15 18:14:02 +00:00
Evan Mattson a17102f9f5 Harden manual integration test trust boundary (#7081)
* Harden manual integration test workflow

Require two write-capable approvals for the exact PR head SHA, pin all targets to immutable commits, and narrow secret and OIDC access.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b8c7d85f-7576-4fc7-a7b5-c77833344088

* Require integration workflow credentials

Declare credentials consumed by the reusable integration workflows as required while retaining the explicitly optional Foundry models key.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b8c7d85f-7576-4fc7-a7b5-c77833344088

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-15 17:54:52 +00:00
westey b3f2e53923 .NET: [BREAKING] Graduate ToolApprovalAgent and add ToolAutoApprovalRuleContext (#7107)
* Graduate ToolApprovalAgent and introduce tool auto approval context

* Address PR comments
2026-07-15 09:21:12 +00:00
Chris Brown b5300fe0c0 Python: fix per-run additional_beta_flags leaking into Anthropic request kwargs (#7060)
* Python: fix per-run additional_beta_flags leaking into Anthropic request kwargs

_prepare_options copied every key from the caller-supplied options dict
into run_options except "instructions" and "response_format". A
per-run additional_beta_flags value is correctly folded into the betas
set by _prepare_betas, but the raw key was never excluded, so it
survived into run_options and was forwarded straight through to
AsyncMessages.create(), which rejects it with TypeError: got an
unexpected keyword argument 'additional_beta_flags'. Add it to the
exclusion set alongside the other framework-level keys.

Fixes #5764

* Exclude additional_beta_flags from filtered_kwargs too

Copilot's review on the original fix pointed out the exclusion only
covered the options-dict copy, not kwargs passed directly to
_prepare_options — so additional_beta_flags supplied as a raw kwarg
would still leak through and reproduce the same TypeError. Add the
same exclusion to filtered_kwargs for consistency, with a regression
test covering the kwarg path.

---------

Co-authored-by: Chris Brown <albatrossflyon1@gmail.com>
2026-07-14 23:02:55 +00:00
Evan Mattson c35a63ed8d Python: feat: cross-session origin attribution on context messages (#7041)
* Python: feat: cross-session origin attribution on context messages

Add an optional origin_session_id parameter to SessionContext.extend_messages
that propagates into the existing _attribution payload on
Message.additional_properties. Downstream context observers can use it to
detect when a provider injects content stored under a different session than
the requesting one.

Populate the field from the harness memory consolidation pipeline
(_harness/_memory.py) when injected topics include contributions from
sessions other than the current one. Add a self-contained sample observer
under samples/02-agents/context_providers/cross_session_observer.py
demonstrating how to subscribe to the signal.

Backward-compatible: omitting the parameter preserves the existing
attribution shape exactly. Tests added in test_sessions.py and
test_harness_memory.py cover the new parameter, the harness cross-session
case, and the same-session case.

Motivated by Dai et al., Stateful Agent Backdoor (arXiv:2605.06158, May
2026), which specifically surveys MAF in section 6.1 / Table 10.
See #5914 for design discussion.

Surfaced during independent audit conducted by @finnoybu (Ken Tannenbaum, AEGIS Initiative); [MEDIUM, python/packages/core].

* Address cross-session attribution review feedback

* Address follow-up review feedback

* Address follow-up review comments

* Address origin attribution review feedback

---------

Co-authored-by: finnoybu <21694570+finnoybu@users.noreply.github.com>
2026-07-14 17:34:52 +00:00
Rince Yuan 47cd0a508d docs/.NET: fix typos in XML doc comments, ADR docs, and test comments (#7085)
- Fix double period in AnthropicClientExtensions.cs XML param docs (lines 23, 77)
- Fix double period in IScopedContentProcessor.cs XML param doc (line 20)
- Fix 'similar the the' -> 'similar to the' in ADR 0009 (line 1092)
- Fix 'reponse' -> 'response' in ADR 0001 (line 142)
- Fix 'retreive' -> 'retrieve' in ChatClientAgentTests.cs (line 467)
- Remove leftover template placeholder from ADR 0001 and 0006 frontmatter

Co-authored-by: j-zhangyiyuan <j-zhangyiyuan@microsoft.com>
Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
Co-authored-by: Giles Odigwe <79032838+giles17@users.noreply.github.com>
2026-07-14 17:06:46 +00:00
Giles Odigwe 56e9a8f74c Python: Make foundry toolbox MCP skills sample self-contained (#7099)
* Python: Make foundry toolbox MCP skills sample self-contained

Rework sample 12 (foundry_toolbox_mcp_skills) so users can build it from
zero with azd, mirroring samples 04 and 09:

- Bundle two single-file SKILL.md skills (support-style, escalation-policy)
  and a skills-only toolbox.yaml (with one connectionless code_interpreter
  tool, required by `azd ai toolbox create`).
- Rewrite the README as an azd-native, from-zero guide (create skills ->
  create toolbox -> set TOOLBOX_ENDPOINT -> run) and fix the stale
  MCPSkillsSource API description to match main.py.
- Switch config from TOOLBOX_NAME to the versioned TOOLBOX_ENDPOINT
  (.env.example, agent.yaml, agent.manifest.yaml); add .azdignore.

Also enable the sample to run unattended behind ResponsesHostServer:

- Forward disable_load_skill_approval / disable_read_skill_resource_approval
  / disable_run_skill_script_approval from FoundryToolbox.as_skills_provider()
  to the underlying SkillsProvider, so load_skill needs no approval round-trip
  (the Responses host runs without an AgentSession, which the default approval
  flow requires). main.py now uses as_skills_provider(disable_load_skill_approval=True).
- Add unit tests covering the default and overridden approval behaviour.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 4f14f83d-1868-45c1-be1a-12f49a58ac36

* Python: Address PR review on toolbox MCP skills sample

- Remove the unused parameters section from agent.manifest.yaml (TOOLBOX_ENDPOINT
  is supplied via environment_variables, matching sample 04).
- README: state the sample is self-contained directly instead of contrasting
  with the C# sample.
- README: describe skill discovery behaviour without naming the internal
  MCPSkillsSource class.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 4f14f83d-1868-45c1-be1a-12f49a58ac36

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-14 17:03:26 +00:00
westey ba0ad2d1d2 Gradudate ToolApprovalMiddleware (#7106) 2026-07-14 11:16:10 +00:00
Evan Mattson b123480b65 Python: Fix AG-UI workflow handoff replay results (#7102)
* Fix AG-UI workflow handoff replay results

Decisions:
- Reconcile finalized function results only for call IDs exposed in the current run, and skip results already emitted or never exposed.
- Treat message-derived function results as workflow responses only when their IDs match pending interrupts.

Files:
- Updated _workflow_run.py reconciliation and resume filtering.
- Added runner and public two-turn handoff acceptance coverage.
- Expanded finalized-response call-ID, privacy, and deduplication tests.

Verification:
- uv run poe test -P ag-ui
- uv run poe syntax -P ag-ui -C
- uv run poe pyright -P ag-ui
- uv run poe test-typing -P ag-ui

Notes:
- No blockers. The handoff sample remains unchanged; local PRD and issue files are not included.

* Prevent duplicate AG-UI workflow tool results

* Preserve finalized AG-UI tool results

* Use AG-UI text emission controls
2026-07-14 09:29:46 +00:00
Evan Mattson 4c0d9ed43c Python: .NET: Consolidate Dependabot dependency updates (#7103)
* Bump Anthropic from 12.31.0 to 12.35.1

---
updated-dependencies:
- dependency-name: Anthropic
  dependency-version: 12.35.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* #7070 Bump Anthropic.Foundry from 0.6.0 to 0.7.1

* Bump astral-sh/setup-uv from 7.6.0 to 8.3.2

Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 7.6.0 to 8.3.2.
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](https://github.com/astral-sh/setup-uv/compare/37802adc94f370d6bfd71619e3f0bf239e1f3b78...11f9893b081a58869d3b5fccaea48c9e9e46f990)

---
updated-dependencies:
- dependency-name: astral-sh/setup-uv
  dependency-version: 8.3.2
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* Bump actions/cache/save from 5.0.5 to 6.1.0

Bumps [actions/cache/save](https://github.com/actions/cache) from 5.0.5 to 6.1.0.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/27d5ce7f107fe9357f9df03efb73ab90386fccae...55cc8345863c7cc4c66a329aec7e433d2d1c52a9)

---
updated-dependencies:
- dependency-name: actions/cache/save
  dependency-version: 6.1.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* Bump github/codeql-action/analyze from 4.35.5 to 4.37.0

Bumps [github/codeql-action/analyze](https://github.com/github/codeql-action) from 4.35.5 to 4.37.0.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/9e0d7b8d25671d64c341c19c0152d693099fb5ba...99df26d4f13ea111d4ec1a7dddef6063f76b97e9)

---
updated-dependencies:
- dependency-name: github/codeql-action/analyze
  dependency-version: 4.37.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* Bump softprops/action-gh-release from 2.6.2 to 3.0.1

Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.6.2 to 3.0.1.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](https://github.com/softprops/action-gh-release/compare/3bb12739c298aeb8a4eeaf626c5b8d85266b0e65...718ea10b132b3b2eba29c1007bb80653f286566b)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-version: 3.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* Bump dorny/paths-filter from 4.0.1 to 4.0.2

Bumps [dorny/paths-filter](https://github.com/dorny/paths-filter) from 4.0.1 to 4.0.2.
- [Release notes](https://github.com/dorny/paths-filter/releases)
- [Changelog](https://github.com/dorny/paths-filter/blob/master/CHANGELOG.md)
- [Commits](https://github.com/dorny/paths-filter/compare/fbd0ab8f3e69293af611ebaee6363fc25e6d187d...7b450fff21473bca461d4b92ce414b9d0420d706)

---
updated-dependencies:
- dependency-name: dorny/paths-filter
  dependency-version: 4.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* Bump astral-sh/setup-uv in /.github/actions/python-setup

Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 6.8.0 to 8.3.2.
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](https://github.com/astral-sh/setup-uv/compare/d0cc045d04ccac9d8b7881df0226f9e82c39688e...11f9893b081a58869d3b5fccaea48c9e9e46f990)

---
updated-dependencies:
- dependency-name: astral-sh/setup-uv
  dependency-version: 8.3.2
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* Align workflow action versions

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-14 09:18:27 +00:00
t-anjan 1c0082721c Python: Fix structured value parsing for split text chunks (#6990)
* Fix structured value parsing for split text chunks

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Fix structured value parsing for split text chunks

---------

Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-14 08:39:11 +00:00
S3rj 6c0950adeb fix: clarify require_confirmation docstring to reflect confirm_changes HITL gating (#6884)
Co-authored-by: Sergey Borisov <sergey.borisov@dataimpact.io>
Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
2026-07-14 08:01:58 +00:00
Hasan Ghomi f1ba16e3fd Python: Fix Magentic manager duplicating conversation history (#6297)
* Python: Fix Magentic manager duplicating conversation history

_complete() reused one persistent AgentSession, so the default history provider
re-injected prior turns on top of the full prompt the manager already rebuilds
each call — duplicating task/facts/plan and compounding every round. Use a fresh
session per call; keep self._session only for
checkpointing. GroupChatOrchestrator is unaffected. Add a regression test and
update the session-propagation test.

* Python: Clean up Magentic manager per Copilot review (drop dead _session)

---------

Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
2026-07-14 08:01:18 +00:00
Giles Odigwe cba77e3cd0 Python: quiet A2AExecutor logging for unmapped content types (#7034)
* Python: quiet A2AExecutor logging for unmapped content types

Tool-use responses include function_call/function_result content that the A2A executor does not surface, causing a WARNING per tool call. Log these at DEBUG and skip instead, matching the outbound content-conversion convention used across the Python chat clients.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: ee74cc55-44df-4fcf-b38f-1f79f2600dfc

* Address PR review: assert debug log args and drop redundant cast

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: ee74cc55-44df-4fcf-b38f-1f79f2600dfc
2026-07-14 07:44:31 +00:00
Eduard van Valkenburg 7ca8bb55b6 Python: Add Telegram hosting helpers and samples (#7047)
* Python: Add Telegram hosting helpers

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 5d6987cd-1b67-4ba1-8b54-3c50da6e7607

* Python: Exclude Telegram samples from aggregate typing

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 5d6987cd-1b67-4ba1-8b54-3c50da6e7607

* Python: Address Telegram helper review feedback

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 5d6987cd-1b67-4ba1-8b54-3c50da6e7607

* Python: Serialize Telegram webhook sessions

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: d76a9c32-d170-426d-a64f-b70958b08b12
2026-07-14 07:40:12 +00:00
westey d93fc2dd74 .NET: [BREAKING] Harness: Switch FileAccess to opt-in (#7093)
* Switch FileAcessProvider on Harness to opt-in

* Address PR comment

---------

Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
2026-07-14 07:08:33 +00:00
Nick Brady 54617557e6 Update Foundry branding (#6999)
Replace user-facing Azure AI Foundry branding with Microsoft Foundry across docs, samples, comments, and display text while preserving technical identifiers.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
2026-07-14 06:44:26 +00:00
Syed Osama Ali Shah df198005fd Python: fix: preserve function-call name when merging streaming deltas (#6809)
* fix: preserve function-call name when merging streaming deltas

`Content._add_function_call_content` built the merged name with
`getattr(self, "name", getattr(other, "name", None))`. Because
`Content.__init__` always sets `self.name` (defaulting to `None`), the
attribute is never missing, so the `getattr` default is never consulted
and `other.name` is ignored. When two function_call contents are merged
and only the second carries the name -- e.g. a streaming delta where the
function name arrives after the first chunk -- the name was silently
dropped.

Use the same "either side" pattern already used for the sibling
`exception` field on the next line: `getattr(self, "name", None) or
getattr(other, "name", None)`. Extend the existing merge test to cover
the late-name and both-None cases.

* test: construct nameless function-call deltas via Content(...) directly

Per review (pyright `reportArgumentType`): `Content.from_function_call`
annotates `name: str`, so passing `name=None` to model a streaming delta
with no name yet tripped the typing gate. Build those nameless deltas
with the `Content("function_call", ...)` constructor instead (its `name`
param is `str | None`) — the factory just wraps that same constructor, so
the runtime objects and the merge assertions are unchanged.

---------

Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
2026-07-14 06:36:57 +00:00
westey 0ceca9a76a Add name collision warnings for auto-approvals (#7090)
Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
2026-07-14 06:35:38 +00:00
westey 23977a6045 .NET: Add name collision warnings for auto-approvals (#7089)
* Add name collision warnings for auto-approvals

* Address PR comments
2026-07-14 06:32:38 +00:00
Evan Mattson 18b03ea487 Python: adjust checkpoint encoding handling (#6579)
* Adjust checkpoint encoding handling

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Refine checkpoint encoding handling

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Adjust checkpoint dict encoding

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Clarify checkpoint unpickler blocked globals

* Preserve typed HITL requests in durable activities

* test: skip flaky durabletask multi-turn integration test

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-14 15:21:12 +09:00
Evan Mattson 56c4425db2 Python: bridge AG-UI request state and session continuity (#7084)
* Python: bridge AG-UI request state into sessions

Decisions:
- Project resolved AG-UI Shared State into the per-run AgentSession without typed restoration.
- Preserve existing local/service session identifiers and keep AG-UI state out of provider metadata.

Files changed:
- packages/ag-ui/agent_framework_ag_ui/_agent_run.py
- packages/ag-ui/tests/ag_ui/test_endpoint.py

Verification:
- uv run poe check -P ag-ui
- uv run poe test -P ag-ui (912 passed)

Notes:
- Scoped cross-run Session Continuation State remains for the next dependent issue.

* Python: persist scoped AG-UI session continuity

Decisions:
- Store private Session Continuation State atomically in scoped thread snapshots and restore it through the core AgentSession contract.
- Exclude Shared State keys, all HistoryProvider buckets, and tool approval state; request overlays evict colliding private values.
- Finalize interrupted response streams before snapshotting so provider after_run mutations are included.

Files changed:
- packages/ag-ui/agent_framework_ag_ui/_agent_run.py
- packages/ag-ui/agent_framework_ag_ui/_snapshots.py
- packages/ag-ui/tests/ag_ui/test_endpoint.py
- packages/ag-ui/tests/ag_ui/test_snapshots.py
- packages/ag-ui/AGENTS.md

Verification:
- uv run poe test -P ag-ui (921 passed)
- uv run poe check -P ag-ui
- uv run poe typing -P ag-ui

Notes:
- Lifecycle, isolation, and broader storage guidance remain for the next dependent issue.

* Python: document AG-UI session continuity lifecycle

Decisions:
- Keep scoped thread snapshots as the single reset and continuity boundary, with missing request Shared State preserving private continuation.
- Document trusted typed-restoration storage, State Authorities, custom-store round trips, and one-active-run last-writer-wins consistency.
- Verify failure, hydration privacy, scope/thread isolation, and reset mechanics through public endpoint and store seams.

Files changed:
- packages/ag-ui/README.md
- packages/ag-ui/tests/ag_ui/test_endpoint.py
- packages/ag-ui/tests/ag_ui/test_snapshots.py

Verification:
- uv run pytest -q <focused lifecycle tests> (6 passed)
- uv run poe test -P ag-ui
- uv run poe syntax -P ag-ui -C
- uv run poe typing -P ag-ui
- uv run poe check -P ag-ui
- uv run poe markdown-code-lint

Notes:
- No runtime capability probe, secondary state store, locking, or configuration flag was added.
- No blockers remain for this lifecycle and guidance slice.

* Python: harden AG-UI session continuity

* Python: isolate AG-UI request state
2026-07-14 04:55:46 +00:00
Evan Mattson 13066cdf96 Python: Refine DevUI request logging (#7083)
* Refine DevUI request logging

* Address DevUI logging review feedback
2026-07-14 03:40:18 +00:00
westey f11cfd9d76 Switch FileAcessProvider on Harness to opt-in (#7094) 2026-07-14 02:26:59 +00:00
Evan Mattson 774fc94bd2 Enable manual issue triage (#7098) 2026-07-14 11:04:38 +09:00
Peter Ibekwe 4bac2c2c05 Python: Promote python declarative workflows to stable version (#7065)
* Promote python declarative workflows to stable version

* Updated changelog with PR detail.

* Updated to address pr comments.

* Remove changelog update
2026-07-13 22:30:21 +00:00
pratik wayase 43568f1ef2 Fix: coalesce reasoning deltas into single block when content.id is None (#6804)
python/packages/ag-ui/tests/ag_ui/test_run_commoclear
:wq
2026-07-13 21:11:22 +00:00
VectorPeak 52005ff17d Python: accept AG-UI state data URI parameters (#6905)
* Python: Accept AG-UI state data URI parameters

* Python: Handle invalid AG-UI state base64

---------

Co-authored-by: VectorPeak <VectorPeak@users.noreply.github.com>
Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
2026-07-13 21:05:27 +00:00
pratik wayase a4e4a5a51c Python: Fix: Ollama parallel tool calls collide on same call_id (#6822)
* Fix: Ollama parallel tool calls collide on same call_id

* fix(ollama): use uuid4 for tool call IDs and support colons in tool names

---------

Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com>
2026-07-13 21:02:24 +00:00
Alireza Afzali c8fb491644 Python: docs: add env example files for durabletask samples (#5948)
* docs: add env example files for durabletask samples

* docs: clarify env example values and comments

* docs: set default Redis URL in streaming sample env example
2026-07-13 21:01:19 +00:00
westey e57f046d8a Graduate mode and todo providers (#7052) 2026-07-13 13:47:58 +00:00
westey c9b19e831f Gradudate mode and todo providers (#7053) 2026-07-13 13:47:53 +00:00
westey beb65b21a8 .NET: [BREAKING] Graduate message injection out of experimental (#7044)
* Remove experiemental flags for MessageInjection component

* Improve locking on message injection.
2026-07-13 11:26:01 +00:00
westey b3d523ee50 Python: [BREAKING] Fix harness before-strategy compaction under per-service-call persistence (#7055)
* Fix middleware ordering to ensure compaction runs

* Address PR comments

* Fix build issue
2026-07-13 09:51:00 +00:00
Copilot 6f38cb724d .NET: Enable Valkey NuGet package publishing (#7059)
* Initial plan

* Enable Valkey NuGet package publishing

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-13 09:31:49 +00:00
Tao Chen 8e74360d52 Python: Add Microsoft OpenTelemetry Distro sample (#5632)
* Add Microsoft OpenTelemetry Distro sample

* Verify and add README

* Add dependency header

* Add maf dependency in PEP 723 block
2026-07-13 01:51:15 +00:00
ByteWise 7f4cc296fd Python: preserve tool span context for parallel calls (#6512)
* Python: preserve tool span context for parallel calls

* Python: address parallel tool span review feedback

* Python: fix parallel tool span test checks

---------

Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com>
2026-07-13 00:07:53 +00:00
Benke Qu f3057ef20c Python: fix: clear service_session_id in _agent_wrapper when propagate_session=True (#5875)
* fix: clear service_session_id in _agent_wrapper when propagate_session=True

When propagate_session=True, the child agent inherits the parent's
service_session_id. After the parent's first LLM call, MAF auto-populates
this from the Responses API conversation_id. The child sends it as
previous_response_id which the server rejects because the parent's
tool_call is still pending (400 error).

This fix saves and clears service_session_id before calling the child
agent and restores it in a finally block, preserving session.state
sharing while isolating the server-side conversation pointer.

Fixes #5874

* refactor: use child session copy instead of in-place mutation

Address Copilot review comments:
- Create a child AgentSession with shared state dict but isolated
  service_session_id, avoiding race conditions under concurrent
  asyncio.gather tool invocations.
- Update tests to verify child gets a separate session object and
  that child-set service_session_id does not leak to parent.

* fix: update test_chat_agent_as_tool_propagate_session_true for child session isolation

The existing test asserted captured_session is parent_session, but since
we now create a separate child AgentSession (to avoid racing under concurrent
asyncio.gather), the child is a different object. Updated assertions to verify:
- child is NOT the parent object (isolation)
- child shares the same session_id and state dict (by reference)
- child's service_session_id is None (isolated)

* fix: add type narrowing asserts for captured_session

Add 'assert captured_session is not None' before attribute access to
satisfy mypy/pyright type checking on Optional values.

* Python: Fix test typing checks

---------

Co-authored-by: Benke Qu <bequ@microsoft.com>
Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
Co-authored-by: Evan Mattson <evan.mattson@microsoft.com>
2026-07-12 23:37:53 +00:00
Eduard van Valkenburg 68136ee081 Python: Clean up dependency groups and compatibility (#7046)
* Python: Clean up dependency management

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 2f7b1c89-f3ff-418d-ab4e-4f014fda308f

* Python: Harden Mistral SDK import fallback

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 2f7b1c89-f3ff-418d-ab4e-4f014fda308f
2026-07-10 22:40:42 +00:00
Peter Ibekwe 875031ff56 Fix broken sample 2026-07-10 11:26:05 -07:00
Evan Mattson 87af313119 Python: [BREAKING]: Emit TOOL_CALL events for workflow participant tool calls in AG-UI (#7039)
* Python: emit participant tool calls in AG-UI workflows

Decisions:
- Pass function call, function result, and approval request content from streaming agent updates regardless of role.
- Preserve the assistant-role gate for text and reuse the shared AG-UI content emitters without dual custom-event emission.

Files changed:
- packages/ag-ui/agent_framework_ag_ui/_workflow_run.py
- packages/ag-ui/tests/ag_ui/test_workflow_run.py

Verification:
- uv run poe test -P ag-ui
- uv run poe pyright -P ag-ui
- uv run poe test-typing -P ag-ui
- uv run poe syntax -P ag-ui -C

Notes:
- Existing workflow golden scenarios do not exercise participant tool calls, so no snapshot changed.
- No blockers.

* Python: guard participant tool call duplication

Decisions:
- Assert the workflow stream emits one TOOL_CALL_START when a streamed call is also present in final conversation history.
- Keep production flow unchanged because latest-assistant final-response conversion prevents duplication.

Files changed:
- packages/ag-ui/tests/ag_ui/test_workflow_run.py

Verification:
- uv run pytest packages/ag-ui/tests/ag_ui/test_workflow_run.py -k 'participant_tool_call or repeat_tool_call' -q
- uv run poe test -P ag-ui
- uv run poe pyright -P ag-ui
- uv run poe test-typing -P ag-ui
- uv run poe syntax -P ag-ui -C
- git diff --check

Notes:
- No blockers; no call-id guard was required.

* Python: scope workflow tool content bypass to resumable tool calls

- Exclude approval request content from the role bypass. Workflow approvals
  resume through request_info pending state, so an approval interrupt emitted
  from streamed content would have no pending request to resume against.
- Admit mcp_server_tool_call and mcp_server_tool_result so provider-hosted MCP
  tool calls from workflow participants emit standard tool call events.
- Add unit tests for MCP passthrough, approval exclusion, and mixed
  text-plus-tool content in non-assistant updates.
2026-07-10 16:58:07 +00:00
Evan Mattson 9ac548ad15 Python: keep attachments close (#7038)
* Python: keep attachments close

* Python: close attachment edge cases
2026-07-10 16:57:29 +00:00
Peter Ibekwe 737042fc93 Fix workflow session bug (#7032) 2026-07-10 16:56:57 +00:00
Patrick Woo-Sam e677ccc3b1 .NET: Fix CompactionMessageIndex.IsSummaryMessage (#7042)
* Fix CompactionMessageIndex.IsSummaryMessage

* Add more robust JsonElement value checking and tests cases
2026-07-10 15:03:26 +00:00
Theo van Kraay d9c0c36379 Fix CosmosChatHistoryProvider: omit ttl when MessageTtlSeconds is null (#6992) (#7030) 2026-07-10 13:01:59 +00:00
HaoJun 32a547a1a7 Python: bind AG-UI tool arguments to call IDs (#6342)
Co-authored-by: White-Mouse <15983334+White-Mouse@users.noreply.github.com>
2026-07-10 06:54:53 +00:00
Evan Mattson 7464a59228 Python: Bump Python package versions for 1.11.0 release (#7035)
* Bump Python package versions for 1.11.0 release

Bump the CHANGELOG-selected packages for the 1.11.0 release: core and the root package move to 1.11.0 for the new stable APIs, Foundry and OpenAI receive patch bumps, changed prerelease packages receive the 260709 stamp or next RC counter, and Monty joins the bump set for corrected published dependency metadata. No beta cohort bump was applied. Raise core floors conservatively on every package publishing this cycle and correct dependency floors exposed by lower-bound validation.

Copilot-Session: ee33d338-c1fc-4182-9106-0345ccf26b8e

* Fix Gemini streaming type suppression

Move the targeted Pyright suppression to the SDK contents argument, where the google-genai invariant content-list alias produces the compatibility diagnostic, and remove the now-unnecessary member suppression.

Copilot-Session: ee33d338-c1fc-4182-9106-0345ccf26b8e

* Raise Monty core dependency floor

Align Monty with the conservative release policy by requiring agent-framework-core 1.11.0 or later for the package version published in this cycle.

Copilot-Session: ee33d338-c1fc-4182-9106-0345ccf26b8e
2026-07-10 12:15:26 +09:00
Ethan qu 01ec3b7bcf Fix AG-UI approval thread aliases (#6908)
Co-authored-by: godququ5-code <256881196+godququ5-code@users.noreply.github.com>
2026-07-10 00:51:49 +00:00
westey ce96fd4b72 Python: Integrate message injection into harness agent (#7027)
* Integrate message injection into harness agent and sample console

* Add agents.md update.

* Address PR comments
2026-07-10 00:33:31 +00:00
Evan Mattson 52237b8eff Python: consolidate dependency updates (#7033) 2026-07-10 00:25:30 +00:00
Giles Odigwe e6cc2c09af Python: Fix read_skill_resource instruction dropping .md extension (#7031)
The RESOURCE_INSTRUCTIONS example told the model to use

eferences/FAQ instead of 
eferences/FAQ.md, contradicting the
actual exact-match resource lookup (which lists and matches names
including the extension). This caused read_skill_resource to fail with
'Resource not found'. Align the example with the .NET original.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-10 00:06:09 +00:00
Chinedum Echeta 7440b1c376 feat: enhance tool choice handling for required mode in _prepare_options (#7024) 2026-07-09 22:57:16 +00:00
Peter Ibekwe 3f4ffc6c2c .NET: Fix declarative InvokeAzureAgent failing on non-object JSON agent output (#7002)
* Fix declarative InvokeAzureAgent failing on non-object JSON agent output

* Fix PR comments.
2026-07-09 17:10:51 +00:00
Eduard van Valkenburg d43e52df69 Python: support mem0ai 2.x (#7004)
* Python: support mem0ai 2.x

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* updated lock

* Address mem0 OSS application scope

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Use filters for mem0 platform add

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-09 16:55:51 +00:00
westey fbaa346eec .NET: Python/.Net: Agent Harness blog post accompanying samples part 3 (#6741)
* Python/.Net: Agent Harness blog post accompanying samples part 3

* Delete inadvertently added files

* Address PR feedback.

* Rename files that are causing dotnet format failures

* Address PR comment

* Fix blog links
2026-07-09 13:52:53 +00:00
dependabot[bot] f5c078b70f .NET: Bump AGUI.Abstractions from 0.0.1 to 0.0.3 (#7007)
* Bump AGUI.Abstractions from 0.0.1 to 0.0.3

---
updated-dependencies:
- dependency-name: AGUI.Abstractions
  dependency-version: 0.0.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* Bump AGUI.Formatting, AGUI.Protobuf, AGUI.Client, AGUI.Server from 0.0.1 to 0.0.3

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com>
2026-07-09 13:43:08 +00:00
Sheldon 9f4526a41e fix: parse structured response value from final message (#6383)
Signed-off-by: liuzemei <35027683+liuzemei@users.noreply.github.com>
2026-07-09 11:58:39 +00:00
Sumesh Ramasamy 13fc425bf5 Python: docs: fix removed ChatAgent references in _clients.py docstrings (#6924)
* docs: fix removed ChatAgent references in _clients.py docstrings

* docs: make _clients.py tool-support examples copy/paste-safe

Import Agent in each tool-support protocol docstring example so
copy/pasting no longer raises NameError, and define the shell
executor (LocalShellTool) in the SupportsShellTool example.

Addresses Copilot review feedback on #6924.

Co-authored-by: Cursor <cursoragent@cursor.com>

* docs: wrap SupportsShellTool example in async function

`async with LocalShellTool()` is a SyntaxError at module level, so the
copy/pasted snippet must live inside an async function to be valid.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Sumesh Bharathi Ramasamy <sumesh@iconicair.io>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-09 10:16:23 +00:00
Roger Barreto 5f9ac6b394 Python: bind policy-enforcement approvals to a single tool invocation (#6966)
* Bind policy-enforcement approvals to a single tool invocation

PolicyEnforcementFunctionMiddleware retained approved call_ids in a set
that was never cleared, so a reused call_id could re-authorize a later
or different tool call without a fresh approval. It also accepted an
approved response as long as the invocation metadata carried a pending
call_id, without checking the response id or embedded function_call.

Bind each approval to the exact invocation shown for review: call_id,
function name, arguments, the security label (integrity/confidentiality),
and the session. Validate that the approval response itself names the
pending request (its id and embedded function_call), and consume the
approval on first use. A reused call_id, a different function, changed
arguments, an escalated label, a different session, or a mismatched
approved response now all require a fresh approval. Adds regression tests
covering each of those cases plus legitimate re-approval.

* Require approval response identifiers to be present and match

Make the policy-enforcement approval-response check reject a response
that omits its id or embedded function_call.call_id: both must now be
present and equal to the pending call_id, closing a None-identifier
bypass. Adds a regression test.

* Disclose all policy violations in a single approval request

PolicyEnforcementFunctionMiddleware computed the approval decision once
and reused it across the integrity and confidentiality checks, so a call
that violated both policies produced an approval request describing only
the untrusted-context violation and then silently waved the undisclosed
confidentiality violation on replay.

Detect every applicable violation up front and surface them together in a
single approval request, so a granted approval waves only what it
disclosed. The binding (call_id, function, arguments, security label,
session) and consume-once behavior are unchanged. Adds a regression test
covering a combined untrusted-context and confidentiality violation.

* Bind policy approval to the disclosed violation set and fingerprint

A pending policy approval was bound to the call body, security label, and
session but not to the violations it disclosed. Because the violation set
depends on the tool's policy metadata (max_allowed_confidentiality,
accepts_untrusted), a replay could compute a different or larger set after
that metadata changed and execute it under the old approval even though the
user never reviewed that risk.

Record the canonical disclosed violation fingerprint (type plus reason) in
the pending record and require the replay to trip the same set, otherwise
re-request approval disclosing the new set. Also require the approval
response's approved flag to be a strict boolean True so a truthy non-boolean
value is not treated as approval. Adds regression tests for a new violation
appearing on replay, a same-type violation whose disclosed risk worsened,
and a non-boolean approved flag.
2026-07-09 10:01:53 +00:00
Eduard van Valkenburg 1aca7601b8 Python: Add hosting protocol helper surface (#6891)
* Add Python hosting protocol helper surface

Introduce AgentFrameworkState and SessionStore for app-owned hosting routes, add Responses run conversion/rendering helpers, and update the local Responses sample to use native FastAPI routing with streaming support.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix CI failures, session continuity, and streaming model reporting

- Fix constrained TargetT TypeVar in AgentFrameworkState: split __init__
  into per-shape overloads (instance/sync factory/async factory/awaitable)
  since a bound TypeVar combined with one big Callable/Awaitable union
  parameter was unsolvable across pyright/pyrefly/ty/zuban.
- Fix _FakeAgent test fixtures to structurally satisfy SupportsAgentRun
  (matching attribute types and overloaded run()), which the above surfaced.
- Add SessionStore.put() to alias an additional session id to an
  already-resolved session, and use it in the local_responses sample to fix
  a real session-continuity bug: previous_response_id rotates every turn,
  so without aliasing the newly minted response id, turn 3+ of a
  conversation silently lost all prior history. Verified against a live
  Foundry model across a 3-turn conversation.
- Fix responses_stream_events_from_run to report the real model instead of
  the "agent" fallback: AgentResponse.from_updates never carries a raw
  representation forward, so capture model from the individual streamed
  updates' raw representations instead. Verified live.
- Add response_model=None to the sample's FastAPI route (it could not boot
  at all: FastAPI tried to build a Pydantic response model from the
  JSONResponse | StreamingResponse return annotation).
- Map responses_to_run's ValueError to HTTP 400 instead of a 500.
- Add HTTP round-trip integration tests (packages/hosting-responses) that
  exercise the same FastAPI + AgentFrameworkState + Responses helper wiring
  as the sample via httpx.ASGITransport, including a regression test for
  the session-continuity fix.
- Add Workflow-target test coverage, SessionStore.put/reset_session tests,
  and TypeError-path coverage to packages/hosting/tests/hosting/test_state.py.
- Extend call_server.py / call_server_af.py to a third conversation turn so
  they actually exercise the continuity chain (previous scripts stopped at
  turn 2, which would never have revealed the bug above).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Simplify session-continuity aliasing: fold put() into get()

Per feedback: the growth of SessionStore was not the problem -- it's
intentional, since OpenAI's previous_response_id is designed to let a
caller continue (fork) from any earlier response, not just the latest
one, so every response id has to stay independently resolvable. That
part stays as-is.

What was too complex was the call site: routes had to manually fetch a
session and then conditionally alias it with a separate put() call.
Folded that into a single get(session_id, alias=...) call instead:

- SessionStore.get() gains an optional `alias` keyword that registers an
  additional id for the same session in the same call (no-op if alias is
  None or equal to session_id). Removed the separate put() method.
- AgentFrameworkState.get_session() passes `alias` through.
- local_responses sample and the HTTP round-trip integration tests now
  do `await state.get_session(lookup_id, alias=response_id)` instead of
  pulling the store out and orchestrating get()/put() by hand.
- Documented that this in-memory SessionStore intentionally never evicts
  (by design, to support forking), and that a storage-backed replacement
  (Redis, a database, ...) is responsible for its own TTL/eviction
  policy.

Verified against a live Foundry model across a 3-turn previous_response_id
chain after the simplification.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Refine hosting state helpers

Split the shared state surface into AgentState and WorkflowState, keep SessionStore and CheckpointStore as plain storage, and make state helpers responsible for get-or-create behavior. Update the Responses sample and HTTP round-trip tests to store the post-run session explicitly under the minted response id, and support WorkflowBuilder/orchestration-style builders via structural build() support.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix hosting state test protocol fakes

Widen fake agents' get_session service_session_id parameter to match the SupportsAgentRun protocol under the Python 3.11 test typing checkers.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Simplify Responses stream helper naming

Rename responses_stream_events_from_run to responses_stream_from_run across exports, tests, docs, and the local Responses sample to align with the generic <protocol>_stream_from_run helper convention.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add state-level storage setters

Add AgentState.set_session and WorkflowState.set_checkpoint_storage so app code can pair get-or-create helpers with explicit post-run storage without reaching into the underlying stores. Update Responses docs, tests, and sample to use state.set_session.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Simplify WorkflowState checkpoint handling

Remove CheckpointStore from WorkflowState so workflow checkpointing uses the existing CheckpointStorage abstraction directly. Keep WorkflowState focused on resolving workflow targets, including builders, and update hosting docs/tests accordingly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Rename Responses streaming run helper

Rename responses_stream_from_run to responses_from_streaming_run across the hosting-responses exports, tests, docs, and local Responses sample.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Align Python hosting spec with protocol helpers

Rewrite SPEC-002 to match the accepted helper-first hosting ADR and the implementation PR: AgentState, WorkflowState, SessionStore, Responses helpers, app-owned security/state responsibilities, and the minimal FastAPI Responses shape.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Remove old Python hosting channel implementation

Remove the unreleased AgentFrameworkHost/channel implementation, the old hosting-telegram package, and old host/channel samples. Keep agent-framework-hosting focused on AgentState, WorkflowState, and SessionStore, and keep hosting-responses focused on helper-first Responses conversion. Update SPEC-002 to match the accepted helper-first ADR and the implementation surface.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Restore helper-first workflow sample

Rebuild the local Responses workflow sample on the protocol-helper surface, add production-readiness cautions to the local hosting samples, and align file-backed workflow checkpoint/cursor storage under one sample storage root.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Address hosting helper review feedback

Handle streaming failures as terminal Responses SSE events, guard concurrent target/session initialization, and scope workflow sample checkpoint storage per continuation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Clarify Responses sample continuation behavior

Document unknown conversation_id behavior in the agent sample and make the workflow sample explicitly reject conversation_id while continuing to use responses_session_id for previous_response_id.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Clarify Responses sample option policy

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-09 07:51:47 +00:00
Eduard van Valkenburg 9a5312b278 Python: Add message injection middleware (#6998)
* Python: Add message injection middleware

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Python: Ignore informational tool calls for message injection

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Python: Preserve per-service history with message injection

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-09 07:47:39 +00:00
Ethan qu 978cfcd9e4 Python: Fix Foundry reasoning MCP compaction (#6907)
* Fix Foundry reasoning MCP compaction

* Address reasoning MCP review feedback

---------

Co-authored-by: godququ5-code <256881196+godququ5-code@users.noreply.github.com>
Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com>
2026-07-09 07:10:39 +00:00
VectorPeak 7a73455e56 Python: normalize single Anthropic tools (#6903)
* Python: Normalize Anthropic single tools

* Potential fix for pull request finding

Thanks

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Python: Address Anthropic review feedback

---------

Co-authored-by: VectorPeak <VectorPeak@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-09 07:05:24 +00:00
Evan Mattson 1b4484829c Python: Add Python package release skill (#6356)
* Add Python package release skill

* Address release skill review feedback

* Address Python release skill review comments

* Resolve Python release skill docs conflict
2026-07-09 06:59:59 +00:00
Eduard van Valkenburg 346d3f0820 Python: Mark hosted tool calls informational-only (#6997)
* Python: Mark hosted tool calls informational-only

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Python: Address informational-only review feedback

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Python: Preserve approval responses in tool invocation

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-09 06:07:58 +00:00
Evan Mattson 9cc020e486 Python: Add AG-UI FastAPI SSE keepalive support (#6980)
* Python: Add AG-UI SSE keepalive endpoint option

Key decisions: add keepalive_seconds as endpoint-owned FastAPI registration configuration with default 15, accept None as the explicit off switch, validate that non-None values are greater than zero during route registration, and keep agent/workflow runner constructors unchanged. Declare sse-starlette>=3.4.5,<4 as a direct AG-UI dependency without changing the existing StreamingResponse path in this slice.

Files changed: packages/ag-ui/agent_framework_ag_ui/_endpoint.py adds validation and the public endpoint parameter; packages/ag-ui/tests/ag_ui/test_endpoint.py covers default, supported runner shapes, endpoint ownership, and invalid intervals; packages/ag-ui/pyproject.toml and uv.lock add the direct sse-starlette dependency metadata.

Verification: uv run pytest focused keepalive endpoint tests -q; uv run poe test -P ag-ui; uv run poe syntax -P ag-ui -C; uv run poe pyright -P ag-ui; uv run poe validate-dependency-bounds-test -P ag-ui; git diff --check; git diff --cached --check. Also ran validate-dependency-bounds-project --mode both --package ag-ui --dependency sse-starlette; it completed but broadened the lower bound, so the issue-required >=3.4.5,<4 contract was restored and re-locked.

Notes: uv run poe typing -P ag-ui and uv run poe check -P ag-ui currently fail in mypy before checking project files because .venv/lib/python3.13/site-packages/numpy/__init__.pyi uses type-statement syntax while the test mypy profile targets Python 3.11. Local issue file was moved to issues/done/ but not staged.

* Python: Emit AG-UI SSE keepalive comments

Key decisions: switch only enabled AG-UI FastAPI endpoint keepalive responses to EventSourceResponse, keep encoded AG-UI SSE frames as bytes on that path to avoid double encoding, and emit the fixed static SSE comment ': keepalive' while preserving existing SSE headers.

Files changed: packages/ag-ui/agent_framework_ag_ui/_endpoint.py adds the EventSourceResponse enabled path and static comment factory; packages/ag-ui/tests/ag_ui/test_endpoint.py adds an endpoint test for a long output-silent gap, keepalive comments, headers, valid data frames, and no data: data: double encoding.

Verification: uv run pytest packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_keepalive_enabled_emits_static_comment_during_silent_gap -q; focused endpoint pytest selection; uv run poe test -P ag-ui; uv run poe syntax -P ag-ui -C; uv run poe pyright -P ag-ui; git diff --check; git diff --cached --check.

Notes: uv run poe check -P ag-ui still fails in the test-typing mypy phase before project files are checked because .venv/lib/python3.13/site-packages/numpy/__init__.pyi uses type-statement syntax while the mypy test profile targets Python 3.11. Local PRD/Ralph/context artifacts were not staged.

* Python: Preserve disabled AG-UI SSE keepalive behavior

Key decisions: cover keepalive_seconds=None at the FastAPI endpoint seam and assert it preserves the legacy StreamingResponse SSE shape without emitting transport keepalive comments.

Files changed: packages/ag-ui/tests/ag_ui/test_endpoint.py adds disabled keepalive endpoint coverage for headers, valid AG-UI data frames, no keepalive comments, and no data: data: double encoding.

Verification: uv run pytest packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_keepalive_disabled_preserves_streaming_response_shape packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_keepalive_enabled_emits_static_comment_during_silent_gap -q; uv run poe test -P ag-ui; uv run poe syntax -P ag-ui -C; uv run poe pyright -P ag-ui; uv run poe test-typing -P ag-ui --checker pyright; git diff --check.

Notes: no production code changes were needed because the endpoint already branches to the existing StreamingResponse path when keepalive_seconds=None. Local PRD/Ralph/context artifacts were not staged.

* Python: Document AG-UI SSE keepalive behavior

Key decisions: document keepalive_seconds at the FastAPI endpoint seam as a default-enabled transport keepalive with None as the off switch, and record that SSE keepalive emits comments without changing AG-UI events or adding protocol heartbeat events.

Files changed: packages/ag-ui/agent_framework_ag_ui/_endpoint.py expands the public endpoint docstring; packages/ag-ui/AGENTS.md records endpoint-owned keepalive guidance; packages/ag-ui/tests/ag_ui/test_endpoint.py adds a public docstring regression.

Verification: uv run pytest packages/ag-ui/tests/ag_ui/test_endpoint.py::test_add_endpoint_docstring_describes_keepalive_transport_behavior -q failed before the doc update; focused keepalive endpoint tests passed; uv run poe test -P ag-ui; uv run poe syntax -P ag-ui -C; uv run poe pyright -P ag-ui; uv run poe test-typing -P ag-ui --checker pyright; uv run python scripts/check_md_code_blocks.py packages/ag-ui/AGENTS.md; git diff --check.

Notes: no standalone docs page was added. Local issue bookkeeping was moved to issues/done but not staged; local PRD and Ralph/context artifacts remain unstaged.

* Python: Tighten AG-UI FastAPI dependency bound

* Python: Defer AG-UI keepalive transport imports
2026-07-09 05:54:45 +00:00
Lucas Kim 9ef8fadeac Python: Fix Bedrock non-ASCII escaping in JSON content blocks (#6628)
* Python: Fix Bedrock non-ASCII escaping in JSON content blocks

The Bedrock Converse `json` content block was serialized with
`json.dumps(json_value)`, whose default `ensure_ascii=True` escapes
CJK/emoji/accented characters to `\uXXXX` and surfaces garbled text.
Add `ensure_ascii=False` to match the sibling OpenAI client and the
16+ other call sites across the repo. Includes a regression test.

Closes #6627

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Fix Bedrock test trailing whitespace

---------

Co-authored-by: kimnamu <kimnamu@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com>
Co-authored-by: Evan Mattson <evan.mattson@microsoft.com>
2026-07-09 05:54:26 +00:00
Vaibhav Patel 23bfa49575 Python: Strip tools from Foundry agent request on the preview path (#6644)
The Foundry service rejects requests that include tool declarations when an
agent is specified (HTTP 400 invalid_payload, "Not allowed when agent is
specified."). RawFoundryAgentChatClient._prepare_options stripped tools,
tool_choice, and parallel_tool_calls only on the non-preview path, so when
allow_preview=True (where the agent identity is bound on the OpenAI client via
get_openai_client(agent_name=...)) the tool fields were still sent and the call
failed.

This client always targets a pre-provisioned agent, so it must never send tool
declarations. Drop the tool fields unconditionally and log a single warning
when the caller supplied tools, noting they are used only for client-side
function dispatch. The non-agent FoundryChatClient (model-based) is unaffected.

Fixes #5130.

Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com>
2026-07-09 01:53:39 +00:00
安妮的心动录 c47f20d9a2 Python: fix: DevUI list[Message] input for declarative ToolAgent entry (#6533) (#6534)
* fix: DevUI list[Message] entry for declarative ToolAgent (#6533)

When a declarative ToolAgent is created with default settings the
entry JoinExecutor declares `input_types = [dict | str | list[Message]
| ActionTrigger | ...]`.  DevUI called `select_primary_input_type`
which returned bare `Message` instead of `list[Message]`, then passed
a single Message to the executor that expects a list — causing a
"cannot handle message of type Message" runtime error.

Changes:
- Add `_is_list_message_type` helper (GenericAlias cannot be used with
  isinstance; get_origin/get_args required).
- Add `_find_chat_message_type` that recursively searches union members
  and returns `list[Message]` in preference to bare `Message`.
- `select_primary_input_type`: first-pass uses `_find_chat_message_type`
  so the declarative entry type is correctly returned as `list[Message]`.
- `generate_input_schema`: returns `{"type":"string"}` for `list[Message]`
  so DevUI renders a plain text box.
- Add `_looks_like_message_dict` heuristic (role present, type=="message",
  or exactly {"input":...}) to distinguish serialised Message payloads
  from structured workflow inputs without false positives.
- `parse_input_for_type`: handle `list[Message]` target — wrap plain
  strings/Message objects, convert lists of dicts item-by-item, pass
  structured workflow inputs through unchanged.
- Add 12 regression tests (57 total pass).

* fix: resolve pyright unknown-type errors in parse_input_for_type

---------

Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com>
2026-07-08 23:08:13 +00:00
gezw ab90300a71 Python: Prefer explicit AG-UI resume payloads (#6360)
* Prefer explicit AG-UI resume payloads

* test: tighten AG-UI resume assertions

---------

Co-authored-by: gezw <26155255+gezw@users.noreply.github.com>
Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
Co-authored-by: Evan Mattson <evan.mattson@microsoft.com>
2026-07-08 23:06:13 +00:00
Eduard van Valkenburg c64f8d9e86 Clarify service session ID scoping (#6993)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-08 20:04:08 +00:00
Eduard van Valkenburg 0b49609176 Python: Add progressive MCP disclosure (#6850)
* Add progressive MCP disclosure

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Address progressive MCP review feedback

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Document progressive MCP loader name collisions

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Address progressive MCP review feedback

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix progressive MCP test typing

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Track progressive MCP warning feature id

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Document internal typing helper guidance

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix README stars badge link

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Support batch progressive MCP load unload

Allow progressive MCP load_tool and unload_tool to accept either a single tool name or a list of tool names, applying successful changes in batches with per-tool model-visible results.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
2026-07-08 18:39:02 +00:00
malsabbagh05 2aae66d063 Python: use writable runtime directory for Foundry Skills sample (#6606)
* fix: use writable skills download directory

* fix: handle empty skills download directory override

---------

Co-authored-by: malsabbagh05 <malsabbagh05@users.noreply.github.com>
Co-authored-by: Tao Chen <taochen@microsoft.com>
2026-07-08 18:26:43 +00:00
Giles Odigwe 5adc038b16 Python: Add refresh_interval (TTL) to CachingSkillsSource (#6977)
* Python: Add refresh_interval (TTL) to CachingSkillsSource

Port .NET's CachingAgentSkillsSourceOptions.RefreshInterval to the Python
skills cache. Previously CachingSkillsSource cached a source's skill list
indefinitely (only clearing on a failed fetch), so callers had no built-in
way to periodically re-discover skills whose backing source changes at
runtime (notably MCPSkillsSource over the network).

CachingSkillsSource now accepts an optional refresh_interval (timedelta):
a cached list older than the interval is treated as stale and re-fetched on
the next call. When None (default) the cache never expires, so existing
behavior is unchanged. Freshness is measured with a monotonic clock via a
monkeypatchable _monotonic() helper. SkillsProvider.__init__ and from_paths
expose a cache_refresh_interval kwarg threaded into the built-in cache.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Python: Address review feedback on CachingSkillsSource refresh_interval

- from_paths: do not forward cache_refresh_interval when disable_caching=True,
  matching the docstring and avoiding a TypeError for legacy subclass __init__
  signatures.
- Correct docstring/AGENTS.md wording: a failed fetch does not update the cache
  (initial failure leaves it empty; a refresh failure keeps the prior list),
  rather than "resetting"/"leaving empty" in all cases.
- Fix test typing: narrow provider._source via isinstance before accessing
  inner_source/_refresh_interval so ty/zuban/mypy/pyright all resolve them.
- Add regression tests for disable_caching + interval and legacy-subclass paths.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Python: Do not forward cache_refresh_interval from from_paths into __init__

The refresh interval is already baked into the composed CachingSkillsSource
that from_paths builds, and __init__ leaves a caller-supplied source
un-wrapped, so forwarding cache_refresh_interval into cls(...) was a no-op
for caching behavior while breaking legacy subclasses whose __init__ predates
the kwarg (with caching enabled or disabled). Remove the forwarding entirely.

Strengthen the regression test to cover the real break: a legacy subclass
calling from_paths(paths, cache_refresh_interval=...) with caching enabled
must not raise and the composed source still carries the interval.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Python: Drop _monotonic wrapper; call time.monotonic() directly

Address review feedback: remove the _monotonic() helper that existed only to
aid testing. CachingSkillsSource now calls time.monotonic() inline, and the
refresh-interval tests monkeypatch time.monotonic directly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Python: Restore main's AGENTS.md sections lost in merge resolution

The merge used 'checkout --ours' for AGENTS.md, which took the whole file
from this branch and inadvertently reverted main's non-conflicting additions
(the __init__.pyi tree entry and the 'Root Public API' section). Restore
main's version and re-apply only the intended SkillsSource decorators change
(refresh_interval docs + reworded cache-failure semantics).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-08 18:17:16 +00:00
Evan Mattson 7f31bcc294 Python: Clear AG-UI queued approvals on cancel (#6947)
* Python: Add AG-UI approval state store

Key decisions: introduce a bounded process-local server-side Approval State store for AG-UI agent approvals; scope pending approval validation by AG-UI thread id plus the endpoint's configured server-side scope when present; fail closed when approval-like resume decisions arrive without matching server-owned pending Approval State, covering replayed and wrong-scope attempts without requiring Thread Snapshot persistence.

Files changed: packages/ag-ui/agent_framework_ag_ui/_approval_state.py adds the approval-only in-memory store and scoped thread-key helper; _agent.py owns the default store; _endpoint.py forwards the configured scope to approval handling independently of snapshot persistence; _agent_run.py keys pending approvals by scoped thread id and rejects approval resumes with missing state; tests/ag_ui/test_endpoint.py covers successful default resumes, replay failure, and wrong-scope failure without a snapshot store.

Verification: uv run pytest focused approval resume tests -q; uv run poe test -P ag-ui; uv run poe syntax -P ag-ui -C; uv run poe pyright -P ag-ui; git diff --check; git diff --cached --check. uv run poe typing -P ag-ui and uv run poe check -P ag-ui currently stop in mypy before package checking on .venv/lib/python3.13/site-packages/numpy/__init__.pyi using Python 3.12 type-statement syntax under the configured Python 3.11 target.

Notes: local issue/PRD planning artifacts were not staged. Follow-up slices still own already-approved sibling release, queued/auto-approved approval state preservation, replayable tool result persistence, synthetic-skip tightening, and final security/invariant coverage.

* Python: Release AG-UI approved siblings on resume

Key decisions: preserve core already-approved approval request groups inside AG-UI server-side Approval State for the visible approval interrupt; restore those siblings as server-generated approval responses only after the visible canonical resume passes server-owned validation; keep cancelled visible approvals fail-closed without executing or fabricating sibling results.

Files changed: packages/ag-ui/agent_framework_ag_ui/_agent_run.py stores hidden already-approved sibling approval requests with pending approval entries and rehydrates them during resume; packages/ag-ui/tests/ag_ui/test_endpoint.py adds mixed approval-batch endpoint coverage for approved, rejected, and cancelled visible approvals.

Verification: uv run pytest focused mixed approval sibling tests -q; uv run pytest neighboring approval endpoint tests -q; uv run poe syntax -P ag-ui -C; uv run poe pyright -P ag-ui; uv run poe test -P ag-ui; git diff --check; git diff --cached --check. uv run poe typing -P ag-ui and uv run poe check -P ag-ui pass pyright/pyrefly/ty/zuban for this change but still stop in mypy before package checking on .venv/lib/python3.13/site-packages/numpy/__init__.pyi using Python 3.12 type-statement syntax under the configured Python 3.11 target.

Notes: local .ralph, PRD, and CONTEXT.md planning artifacts were not staged. Follow-up slices still own queued/auto-approved approval state preservation, replayable tool result persistence, synthetic-skip tightening, and final security/invariant coverage.

* Python: Preserve AG-UI queued approval state

Key decisions: persist only the core tool-approval state bag inside the AG-UI server-side Approval State Store, keyed by the scoped AG-UI approval thread id; restore that approval-only state into each per-run AgentSession before approval resolution; pop server-collected auto-approved responses into validated server-generated approval messages so they execute exactly like resumed approvals without trusting client state.

Files changed: packages/ag-ui/agent_framework_ag_ui/_approval_state.py stores bounded tool approval state alongside pending approval entries; _agent.py passes the shared store into agent runs; _agent_run.py restores/saves tool approval state and drains collected auto-approved responses through existing pending-approval validation; packages/ag-ui/tests/ag_ui/test_endpoint.py covers queued approval surfacing and auto-approved response execution through SSE behavior.

Verification: uv run pytest focused queued/auto approval endpoint tests -q; uv run pytest neighboring approval endpoint tests -q; uv run poe syntax -P ag-ui -C; uv run poe pyright -P ag-ui; uv run poe test -P ag-ui; git diff --check; git diff --cached --check. uv run poe check -P ag-ui still stops in mypy before package checking on .venv/lib/python3.13/site-packages/numpy/__init__.pyi using Python 3.12 type-statement syntax under the configured Python 3.11 target; syntax, pyright, pyrefly, ty, and zuban portions completed successfully before that failure.

Notes: local .ralph, PRD, and CONTEXT.md planning artifacts were not staged. Follow-up slices still own replayable tool result persistence, synthetic-skip tightening, and final security/invariant coverage.

* Python: Persist AG-UI approved tool results

Key decisions: fold approval-resolved function_result messages into AG-UI Thread Snapshot history under their original tool call ids; strip server-generated canonical function_approvals resume controls from replayable snapshots; keep live TOOL_CALL_RESULT emission unchanged while preserving next-turn provider history validity.

Files changed: packages/ag-ui/agent_framework_ag_ui/_agent_run.py adds snapshot merge helpers for approval-resolved tool results; packages/ag-ui/tests/ag_ui/test_endpoint.py covers mixed approval batch resume, hydration, and next-turn replay through observable endpoint behavior.

Verification: uv run pytest focused replayable approval endpoint test -q; uv run pytest neighboring approval replay tests and test_approval_result_event.py -q; uv run poe syntax -P ag-ui -F; uv run poe syntax -P ag-ui -C; uv run poe pyright -P ag-ui; uv run poe test -P ag-ui; git diff --check; git diff --cached --check. uv run poe check -P ag-ui still stops in mypy before package checking on .venv/lib/python3.13/site-packages/numpy/__init__.pyi using Python 3.12 type-statement syntax under the configured Python 3.11 target; syntax, pyright, pyrefly, ty, and zuban portions completed successfully before that failure.

Notes: local .ralph, PRD, and CONTEXT.md planning artifacts were not staged. Follow-up slices still own synthetic-skip tightening and final security/invariant coverage.

* Python: Limit AG-UI synthetic skipped results

Key decisions: treat server-owned Approval State, current approval resume decisions, and existing replayable tool results as non-abandoned tool calls for AG-UI sanitizer repair; keep the defensive skipped-result fallback for genuinely abandoned tool calls; reject client-injected tool results as insufficient to satisfy pending server-owned Approval State.

Files changed: packages/ag-ui/agent_framework_ag_ui/_message_adapters.py adds protected tool-call context to synthetic skip injection; packages/ag-ui/agent_framework_ag_ui/_agent_run.py derives protected ids from pending approvals and stored approval-only state; packages/ag-ui/tests/ag_ui/test_message_adapters.py and test_endpoint.py cover protected pending calls, resume decisions, abandoned-call repair, and forged tool-result behavior.

Verification: uv run pytest focused sanitizer red/green tests -q; uv run pytest focused pending-approval endpoint tests -q; uv run pytest package sanitizer plus neighboring approval endpoint tests -q; uv run poe syntax -P ag-ui -C; uv run poe pyright -P ag-ui; uv run poe test -P ag-ui; git diff --check; git diff --cached --check. uv run poe typing -P ag-ui passes pyright/pyrefly/ty/zuban but still stops in mypy before package checking on .venv/lib/python3.13/site-packages/numpy/__init__.pyi using Python 3.12 type-statement syntax under the configured Python 3.11 target.

Notes: local .ralph, PRD, and CONTEXT.md planning artifacts were not staged. Follow-up slice still owns final AG-UI approval repair security and exact-once invariant coverage.

* Python: Verify AG-UI approval invariants

Key decisions: cover final AG-UI approval repair invariants at the FastAPI endpoint seam; treat wrong-thread resumes, client-supplied approval message spoofing, and client-injected approval state as non-executing fail-closed paths; assert exact-once replayable tool results for completed approval batches; document that Approval State is process-local and production authentication, authorization, and deployment/storage durability remain application responsibilities.

Files changed: packages/ag-ui/tests/ag_ui/test_endpoint.py adds endpoint-observable security and exact-once coverage; packages/ag-ui/README.md documents Approval State production responsibilities.

Verification: uv run pytest packages/ag-ui/tests/ag_ui/test_endpoint.py -q -k 'approval_resume_wrong_thread or approval_function_name_mismatch_message or approval_argument_mismatch_message or approval_client_fields_do_not_mutate or approval_resume_persists_replayable_tool_results'; uv run poe syntax -P ag-ui -F; 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 test-typing -P ag-ui --checker pyright; git diff --check; git diff --cached --check.

Notes: local .ralph, PRD, and CONTEXT.md planning artifacts were not staged. This completes the final AG-UI approval repair security and invariant coverage slice.

* Python: Clear AG-UI queued approvals on cancel

* Python: Address AG-UI approval review feedback
2026-07-08 18:04:30 +00:00
Willow Lopez 62456f044d Python: Fix web_search_options sent to Azure OpenAI Chat Completions API (issue #3629) (#6225)
* Fix: Skip web_search_options for Azure OpenAI Chat Completions API

Azure OpenAI Chat Completions API does not support the web_search_options
parameter. Sending it results in a 400 error: 'Unknown parameter:
web_search_options'.

This fix:
- Stores the use_azure_client flag during initialization
- In _prepare_tools_for_openai, skips web search tools when the client
  is Azure-based, logging a warning that guides users to the Responses
  API (OpenAIChatClient) for web search support on Azure

Closes #3629

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: raise ValueError instead of silently ignoring web search on Azure

Address review feedback: silent logger.warning was too easy to miss.
Raising ValueError ensures callers know immediately that web search is
incompatible with Azure Chat Completions and directs them to the
Responses API alternative.

- Changed logger.warning to ValueError in _prepare_tools_for_openai
- Added test_prepare_tools_with_web_search_on_azure_raises
- Added test_prepare_tools_with_web_search_on_openai_allowed

* Fix Azure web search test regex

---------

Co-authored-by: Autumn <Autumn@Autumns-MacBook-Air.local>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com>
2026-07-08 17:44:08 +00:00
Giles Odigwe 1249275997 Python: Remove experimental marker from Skills API (#6974)
* Python: Remove experimental marker from Skills API

Promote the Skills feature from experimental to stable, mirroring
.NET PR #6861. Removes the @experimental(SKILLS) decorators from the
skills APIs and the SKILLS ExperimentalFeature enum member, updates
tests and samples accordingly. MCP skills (MCP_SKILLS) remain
experimental, matching the .NET change.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add experimental-stage assertions for MCP skills types

Guard MCPSkill, MCPSkillResource, and MCPSkillsSource against accidental
promotion by asserting their docstring warning block and
__feature_stage__/__feature_id__ metadata remain experimental (MCP_SKILLS).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Remove redundant stable-stage test for Skills API

Drop TestSkillsStableStage: asserting the absence of experimental
markers on a released API is not meaningful, and the feature-stage
decorator machinery is already covered by test_feature_stage.py.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-08 17:34:34 +00:00
Adam Lin f280742c01 Python: Samples: deterministic action-boundary validation middleware (#5366) (#6528)
* Python: add ATR validation FunctionMiddleware sample (execution-boundary validation, #5366)

Adds python/samples/02-agents/middleware/atr_validation_middleware.py: a
FunctionMiddleware that validates tool arguments at the execution boundary and
raises MiddlewareTermination before call_next() when they match an attack
pattern, so the tool never runs. This is the deterministic, single-enforcement-
point pattern named in #5366 and answers its open follow-up about a recommended
validation-at-execution-boundary sample.

The check is a small self-contained deny-list mirroring Agent Threat Rules (ATR)
intent (prompt injection, exfiltration, credential access in tool args); a
docstring notes how to swap in the full open ruleset via pyatr. No external
dependency, so the sample stays import-clean.

Updates the middleware README Files table.

Signed-off-by: Adam Lin <adam@agentthreatrule.org>

* Python: Samples: run the real ATR engine in atr_validation_middleware

Address review on #6528:
- Load and run the real ATR ruleset via pyatr (ATREngine + AgentEvent
  tool_call event) instead of re-implementing a regex deny-list; the
  built-in deny-list is now only a fallback when pyatr is not installed.
- Add re.DOTALL (and a whole-text scan) to the fallback patterns so
  multiline injection payloads are not missed.
- Move load_dotenv() into main() so importing the module has no side
  effects.
- Route the middleware block/allow messages through a module logger
  instead of print().
- Include the matched ATR rule id in the log and in the
  MiddlewareTermination message for auditability.
- Update the middleware README entry to match.

* fix(samples): make ATR validation middleware pass ty/pyrefly typing CI

Resolve the three type-checker errors flagged on the samples typing jobs
(ty + pyrefly, reportMissingImports/reportAttributeAccessIssue via pyright):

- pyatr is an optional, unstubbed runtime dependency that is not installed
  in the typing CI env; mark its imports with `# type: ignore` so the
  unresolved-import error is suppressed while keeping the graceful
  ImportError -> deny-list fallback intact.
- Replace the function-attribute engine cache
  (`_detect_with_atr._engine`), which ty/pyrefly reject, with a clean
  `functools.lru_cache`-backed `_load_atr_engine()` loader.
- Type the argument-scanning helpers to accept the real
  `FunctionInvocationContext.arguments` type (`BaseModel | Mapping[str, Any]`)
  and normalise a pydantic model via `model_dump()` before scanning, fixing
  the invalid-argument-type error.

ty / pyrefly / pyright (samples config) / ruff check + format all clean on
the file; runtime block/allow behaviour verified for both dict and BaseModel
arguments.

* Python: Samples: simplify ATR middleware to plain pyatr import

Address review feedback (@eavanvalkenburg): now that the sample runs the
real pyatr engine, drop the optional-import scaffolding.

- Add a dependency header declaring pyatr (pip install pyatr).
- Switch to a plain top-level `import pyatr` and remove the
  try/except ImportError fallback path.
- Remove the regex deny-list (_FALLBACK_PATTERNS, _detect_with_fallback);
  keep 2-3 representative pattern shapes inline as a reference comment so
  readers still see the kind of rules ATR encodes. Detection is now a
  single straight-line engine call.
- Keep the prior typing fixes: `# type: ignore` on the pyatr import
  (unstubbed, absent in the typing CI env), the functools.lru_cache
  engine loader, and the BaseModel | Mapping[str, Any] signatures.

* fix: use PEP 723 inline script metadata for sample dependencies

---------

Signed-off-by: Adam Lin <adam@agentthreatrule.org>
Co-authored-by: eeee2345 <eeee2345@users.noreply.github.com>
Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com>
2026-07-08 11:19:45 +00:00
Eduard van Valkenburg 0438ee61c6 Python: Refocus hosting channels ADR on protocol helpers (#6837)
* Revise Python hosting channels ADR

Refocus the accepted-but-unreleased Python hosting channels ADR on protocol-specific Agent Framework conversion helpers and an optional execution-state host instead of a channel route-contribution framework.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Align hosting ADR with split state helpers

Update the protocol-helper ADR to reflect AgentState and WorkflowState, plain SessionStore and CheckpointStore behavior, explicit post-run session storage, workflow checkpoint storage, and direct WorkflowBuilder/orchestration-builder support.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Generalize protocol helper taxonomy

Add protocol-neutral helper families for run conversion, result rendering, streaming, session-id extraction, and command/action parsing. Classify protocol-specific helpers based on quick scans across Activity/Bot Framework, Discord, A2A, and MCP.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Simplify stream helper naming

Use the single <protocol>_stream_from_run(...) helper naming convention in the hosting protocol-helper ADR.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Use state-level storage helpers in hosting ADR

Update ADR examples so app code calls AgentState.set_session and WorkflowState.set_checkpoint_storage instead of reaching into underlying stores directly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Address hosting ADR review comments

Clarify fail-closed Foundry isolation helpers, fix workflow checkpoint resume examples, describe durable checkpoint cursor storage, add caller-owned session authorization comments, and switch the Django sketch to an async view.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Simplify workflow checkpoint state in hosting ADR

Keep WorkflowState focused on resolving workflow targets, use existing CheckpointStorage directly, describe app-owned checkpoint cursor storage, and mark appendix code as minimum-shape sketches rather than runtime-ready samples.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Rename stream helper convention

Use <protocol>_from_streaming_run(...) as the protocol-helper naming convention for rendering streaming run output.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* added notes on state and continuity

* updates based on review

* added consulted

* updates based on review

* remove pyright for illustrative code

* Add streaming to Responses ADR sketch

Extend the FastAPI appendix sketch with the streaming branch and note that the Django sketch omits streaming to avoid duplicating the same state/finalization pattern.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* added note on extending the server

* added note on responsible for

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-08 09:59:50 +00:00
westey 76f2c1a0c9 .NET: [BREAKING] Graduate per-service-call persistence and approval-not-required function bypassing (#6970)
* Remove experimental flags for RequirePerServiceCallChatHistoryPersistence and DisableApprovalNotRequiredFunctionBypassing

* Address PR comments

* Fix failing test
2026-07-08 09:31:33 +00:00
Eduard van Valkenburg 094d8d209a Python: Lazy load root agent_framework exports (#6962)
* Lazy load root agent_framework exports

Move the root public API to lazy runtime exports backed by a typed stub, keep Runner deprecation handling in the owning workflow runner module, and document the maintenance pattern.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Tighten harness factory typing

Add a private harness stub so create_harness_agent has a fully known public signature without depending on agent-framework-tools at runtime.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Address lazy root export review comments

Harden the circular import guard and add root export smoke tests covering representative lazy imports, star imports, and root stub export synchronization.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-08 09:20:27 +00:00
Eduard van Valkenburg 783e8c4568 Python: skip NumPy stubs in mypy typing (#6969)
Mypy intentionally targets Python 3.10 for test typing, but NumPy 2.5 stubs include Python 3.12 type statement syntax. Skip following NumPy stubs so dependency maintenance can validate the repository tests without parsing NumPy internals.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-08 09:13:59 +00:00
Evan Mattson 12b029858e Build(deps): consolidate Dependabot dependency updates (#6984)
* Consolidate Dependabot dependency updates

* Restore method assignment suppression
2026-07-08 09:09:03 +00:00
Eduard van Valkenburg e26c8591ae Python: Fix response metadata construction (#6955)
* Python: Fix response metadata construction

Propagate complete AgentResponse metadata through core response construction and provider finalization paths.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Python: Refine Ollama response metadata parsing

Filter Ollama usage details to real token counts and only propagate streaming finish metadata from final chunks.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Python: Address response metadata review comments

Accumulate Copilot non-streaming usage events and keep structured response value parsing lazy for provider hooks.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-08 09:05:50 +00:00
Benke Qu 7f551f057a Python: fix invalid options kwarg in workflow shared session sample (#6294)
* fix: use client_kwargs instead of invalid options kwarg in workflow sample

Workflow.run() does not accept an options parameter. The store=False
kwarg was silently ignored. Use client_kwargs to correctly forward it
to the underlying chat client.

Fixes #6293

* fix: use backend-neutral wording in client_kwargs comment

---------

Co-authored-by: Benke Qu <bequ@microsoft.com>
Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com>
2026-07-08 08:13:57 +00:00
Rayan Salhab 0661c69b78 fix(ag-ui): preserve streamed text message id (#6269)
Co-authored-by: cyphercodes <cyphercodes@users.noreply.github.com>
Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
2026-07-08 08:12:27 +00:00
Alireza Afzali 2889475bce docs: add prerequisite commands for Python hosting samples (#5935)
Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
2026-07-08 08:08:53 +00:00
Minh Vu 339bbeb881 Fix DevUI deployment Dockerfile auth args (#6150)
Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com>
2026-07-08 08:06:30 +00:00
t-anjan 5e0793542d Python (anthropic): Migrate structured outputs to GA output_config.format (#5884)
Bug
---
`RawAnthropicClient._prepare_options` forwards `response_format` as the
**deprecated** beta parameter `output_format={"type": "json_schema", "schema":
{...}}` plus the beta flag `structured-outputs-2025-11-13`. When the same
request also includes `tools`, Claude emits concatenated / malformed JSON —
e.g. three copies of the schema's empty default like
`{"matches":[]}{"matches":[]}{"matches":[]}` — instead of populating the
schema. Anthropic's GA shape — `output_config={"format": {"type":
"json_schema", "schema": {...}}}` — works correctly with tools.

Verified empirically on `agent-framework-anthropic` against
`claude-sonnet-4-6` for a structured-output workload that combined
`response_format` with a tool (`run_shell`); the deprecated path produced
the malformed concatenated output, the GA path did not.

Changes
-------
- Move `response_format` into `run_options["output_config"]["format"]` and
  stop adding the `structured-outputs-2025-11-13` beta flag (the GA path
  doesn't need it).
- Merge the format into any caller-supplied `output_config` so e.g.
  `output_config["effort"]` (adaptive-thinking effort level) survives the
  transformation.
- Drop the now-unused `STRUCTURED_OUTPUTS_BETA_FLAG` constant (private to
  this module — no external callers).
- `_prepare_response_format` keeps the same `{"type": "json_schema",
  "schema": ...}` return shape; the docstring is updated to point at the
  GA target.

Test plan
---------
- `uv run pytest packages/anthropic/tests` → 130 passed.
- New tests:
  - `test_prepare_options_uses_output_config_for_response_format` — the
    GA `output_config.format` shape is emitted, the deprecated
    `output_format` key is not, and the `structured-outputs-2025-11-13`
    beta flag is not added.
  - `test_prepare_options_preserves_caller_supplied_output_config_effort`
    — a caller-supplied `output_config["effort"]` survives the merge.
  - `test_prepare_options_no_response_format_omits_output_config` — no
    `output_config` is added implicitly when `response_format` is absent.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-08 07:47:48 +00:00
Kranthi Manchikanti de39be9e58 Python: Improve error message when TypeVar is used in handler registration (#4553)
* Python: Improve error message when TypeVar is used in handler registration

Fixes #4547. Adds early detection of unresolved TypeVar instances in:
- @handler decorator (both explicit and introspected type paths)
- @executor decorator (both explicit and introspected type paths)
- WorkflowContext type argument validation (direct and union members)

When a TypeVar is detected, a clear ValueError is raised with actionable
guidance to use concrete types via @handler(input=ConcreteType, output=ConcreteType).

* Address PR review: runtime-safe TypeVar detection and unit tests

- Add shared is_typevar() helper in _typing_utils.py that safely detects
  TypeVar from both typing and typing_extensions modules
- Replace all isinstance(x, TypeVar) calls with is_typevar() in
  _executor.py, _function_executor.py, and _workflow_context.py
- Add 18 unit tests covering TypeVar validation for @handler, @executor,
  and WorkflowContext[T] (explicit params, introspection, union members)

* Fix pyright error: add type annotation to _TYPEVAR_TYPES

Pyright's reportUnknownVariableType flagged the inferred type as
partially unknown. Adding an explicit `tuple[type, ...]` annotation
resolves the strict-mode check.

* Suppress pyright reportUnknownVariableType for _TYPEVAR_TYPES

Pyright cannot infer the runtime type of TypeVar constructors, so the
tuple elements resolve to type[Unknown]. A type annotation alone does
not satisfy strict mode — add an inline suppression for this specific
diagnostic since the unknown types are intentional (runtime TypeVar
class detection).

* Reject nested TypeVars in workflow annotations

---------

Co-authored-by: Kranthi Kumar Manchikanti <kmanchikanti@microsoft.com>
Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
Co-authored-by: Evan Mattson <evan.mattson@microsoft.com>
2026-07-08 07:32:54 +00:00
Matt Van Horn fb4be3bb1f Python: request reference source data in agentic search (#5095) (#5100)
Pass knowledge_source_params with include_reference_source_data=True for
each resolved knowledge source on the KnowledgeBaseRetrievalRequest, so
ref.source_data is populated when the source has source_data_fields
configured. Uses SearchIndexKnowledgeSourceParams (azure-search-documents
12.0.0) and resolves real source names for both created and existing
knowledge bases (avoids the prior 'None-source' name).

Fixes #5095

Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
2026-07-08 05:05:54 +00:00
Tao Chen b31c8981a4 Python: Add multi-tenant hosting hosting security consideration to a2a sample (#6983)
* Add multi-tenant hosting hosting security consideration to a2a sample

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-08 03:27:09 +00:00
Giles Odigwe 96e009f87a Python: Forward skill_directories and disabled_skills to Copilot session (#6937)
GitHubCopilotAgent never forwarded the Copilot SDK's skill_directories
(and disabled_skills) parameters to create_session/resume_session, so
native Copilot CLI skills could not be configured through the agent.

Add both as fields on GitHubCopilotOptions and forward them (with
runtime-override and empty-list-clears-defaults semantics matching
instruction_directories) in _create_session and _resume_session.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-08 01:59:11 +00:00
Evan Mattson 93189e437d Remove unnecessary design doc in root (#6945) 2026-07-08 10:33:19 +09:00
Javier Calvarro Nelson 3d17615b6e .NET: Replace MAF AG-UI abstractions with the AG-UI C# SDK abstractions (#6653)
* .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>
2026-07-07 20:09:53 +00:00
westey 03a96faf43 .NET: Add security information to harness features xml docs (#6933)
* Add security information to harness features xml docs

* Address PR comments
2026-07-07 17:50:55 +00:00
Eduard van Valkenburg 07981847ed Add chat client Agent typing tests (#6950)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-07 17:15:22 +00:00
Ahmed Muhsin 81e425b44f Python: [BREAKING] Durable Task multi-workflow hosting and sub-workflows (#6696)
* 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.
2026-07-07 14:43:28 +00:00
westey 757a832dbf .NET: Add OpenTelemetry Chat Client to harness stack (#6961)
* Add OpenTelemetry Chat Client to harness stack

* Address PR comments
2026-07-07 14:14:26 +00:00
Tamir Dresher cc20093da1 .NET: fix: bump GitHub.Copilot.SDK to 1.0.5 to resolve strong-naming mismatch (#6949)
* 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>
2026-07-07 14:09:28 +00:00
Giles Odigwe 9c4cd07899 Python: Add SkillsSourceContext to SkillsSource.get_skills (#6895)
* 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>
2026-07-07 09:41:45 +00:00
westey cba9a1c050 Python: Add security information to harness features inline docs (#6936)
* Add security information to harness features inline docs

* Address PR comments

---------

Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
2026-07-07 09:33:44 +00:00
Evan Mattson c67372ff32 Python: [BREAKING]: Canonicalize AG-UI interrupt and resume handling (#6925)
* Python: Emit AG-UI interrupt outcomes

Key decisions: raise ag-ui-protocol to 0.1.19, type AGUIRequest/AGUIChatOptions with protocol Interrupt and ResumeEntry, and emit interrupted runs through RUN_FINISHED.outcome.interrupts instead of the legacy top-level interrupt field. Preserve existing internal resume/snapshot compatibility by translating legacy interruption metadata into canonical Interrupt metadata.

Files changed: packages/ag-ui pyproject, AG-UI run/type/workflow/snapshot helpers, AG-UI protocol-shape tests, and uv.lock.

Verification: uv run poe test -P ag-ui; uv run poe syntax -P ag-ui -C; uv run poe typing -P ag-ui; uv run poe validate-dependency-bounds-test -P ag-ui; uv run poe check -P ag-ui; git diff --cached --check.

Notes: README and local PRD/Ralph planning files were left unstaged. Follow-up slices still own richer approval/workflow response schemas and full client-side resume forwarding.

* Python: Emit canonical AG-UI approval interrupts

Key decisions: build Agent Framework approval pauses as canonical AG-UI Interrupt entries under RUN_FINISHED.outcome.interrupts; use reason=tool_call with toolCallId routing; advertise generic approval response schemas using the existing accepted/edited-argument payload contract; keep legacy Agent Framework approval metadata nested under metadata.agent_framework.value for internal snapshot/resume compatibility while avoiding any top-level interrupt value in emitted protocol JSON.

Files changed: packages/ag-ui/agent_framework_ag_ui/_run_common.py adds canonical approval interrupt/schema helpers and uses them for function approval requests; packages/ag-ui/agent_framework_ag_ui/_agent_run.py emits canonical interrupts for predictive confirm_changes pauses; packages/ag-ui/tests/ag_ui/test_endpoint.py covers endpoint/SSE approval pause shape; packages/ag-ui/tests/ag_ui/test_run.py covers helper and run-level confirmation interrupt behavior.

Verification: uv run pytest packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_agent_approval_pause_emits_canonical_interrupt_outcome packages/ag-ui/tests/ag_ui/test_run.py::test_emit_approval_request_populates_interrupt_metadata packages/ag-ui/tests/ag_ui/test_run.py::test_predictive_confirmation_run_finished_interrupt_links_tool_call -q; uv run pytest packages/ag-ui/tests/ag_ui/test_run.py::test_run_agent_stream_accumulates_multiple_confirm_interrupts 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_endpoint_agent_approval_pause_emits_canonical_interrupt_outcome packages/ag-ui/tests/ag_ui/test_run.py::test_predictive_confirmation_run_finished_interrupt_links_tool_call -q; uv run poe test -P ag-ui; 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 check -P ag-ui; git diff --check; git diff --cached --check.

Notes: local README, PRD, .ralph, and issue planning artifacts remain unstaged. Follow-up slices still own canonical ResumeEntry approval continuation, workflow request_info canonical resume, client-side forwarding, pending interrupt contract enforcement, snapshot stale-prompt clearing, and documentation/examples.

* Python: Resume AG-UI approvals canonically

Key decisions: translate canonical ResumeEntry approval payloads into the existing Agent Framework function approval response path at the AG-UI agent-run boundary; route by canonical interruptId while preserving pending approval registry validation; allow edited arguments only through canonical resume translation by updating the stored pending argument fingerprint before execution; emit RUN_ERROR for cancelled, unknown, or malformed approval resumes instead of proceeding.

Files changed: packages/ag-ui/agent_framework_ag_ui/_agent_run.py adds canonical approval resume translation, interrupt-id registry aliasing, explicit approval resume RUN_ERROR handling, and alias cleanup on consumption; packages/ag-ui/tests/ag_ui/test_endpoint.py adds endpoint/SSE coverage for approved, denied, edited, cancelled, and unknown canonical approval resumes.

Verification: uv run pytest packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_agent_approval_resume_entry_executes_approved_tool packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_agent_approval_resume_entry_denial_does_not_execute_tool packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_agent_approval_resume_entry_applies_edited_arguments packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_agent_approval_cancelled_resume_entry_emits_run_error packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_agent_approval_unknown_resume_entry_emits_run_error -q; uv run pytest packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_agent_approval_pause_emits_canonical_interrupt_outcome packages/ag-ui/tests/ag_ui/test_endpoint.py::test_agent_endpoint_confirm_changes_clears_persisted_interrupt packages/ag-ui/tests/ag_ui/test_approval_result_event.py -q; uv run pytest packages/ag-ui/tests/ag_ui/test_agent_wrapper_comprehensive.py::test_approval_argument_mismatch_is_blocked packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_agent_approval_resume_entry_applies_edited_arguments packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_agent_approval_resume_entry_executes_approved_tool -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.

Notes: issues/agui-int-03-agent-approval-resume-entry.md was moved to issues/done locally but remains unstaged. Existing local README, PRD, and .ralph artifacts remain unstaged. Follow-up slices still own workflow request_info canonical resume, client-side forwarding, stricter pending interrupt contract enforcement, snapshot stale-prompt clearing, and documentation/examples.

* Python: Resume workflow interrupts canonically

Key decisions: emit workflow request_info pauses as canonical input_required interrupt outcomes with response schemas and Agent Framework metadata; normalize typed ResumeEntry model dumps through the shared resume parser while preserving status; translate resolved workflow resume payloads through the existing workflow response coercion path; emit RUN_ERROR for cancelled workflow resumes before invoking the workflow.

Files changed: packages/ag-ui/agent_framework_ag_ui/_run_common.py preserves canonical resume status/model dumps and merges interrupt metadata values; packages/ag-ui/agent_framework_ag_ui/_workflow_run.py builds canonical workflow request_info interrupts and cancellation errors; packages/ag-ui/tests/ag_ui/test_endpoint.py adds endpoint/SSE workflow pause, resolved resume, and cancelled resume coverage; packages/ag-ui/tests/ag_ui/test_run_common.py and packages/ag-ui/tests/ag_ui/test_workflow_run.py update canonical helper expectations.

Verification: uv run pytest packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_workflow_request_info_emits_canonical_interrupt_and_resumes packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_workflow_request_info_cancelled_resume_emits_run_error -q; uv run pytest packages/ag-ui/tests/ag_ui/test_workflow_run.py -q; uv run pytest packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_agent_approval_resume_entry_executes_approved_tool packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_agent_approval_resume_entry_denial_does_not_execute_tool packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_agent_approval_resume_entry_applies_edited_arguments packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_agent_approval_cancelled_resume_entry_emits_run_error packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_agent_approval_unknown_resume_entry_emits_run_error -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.

Notes: issue bookkeeping and local PRD files were not staged; existing unstaged packages/ag-ui/README.md remains untouched. Follow-up slices still own client-side forwarding, stricter pending interrupt contract enforcement, snapshot stale-prompt clearing, and documentation/examples.

* Python: Forward AG-UI interrupts through client

Key decisions: normalize typed Interrupt and ResumeEntry values at the AGUIChatClient and AGUIHttpService boundaries using protocol aliases; map legacy request_info available-interrupt hints to canonical input_required reason while preserving legacy resume wrapper shapes; preserve remote RUN_FINISHED.outcome metadata and expose outcome.interrupts for Agent Framework callers without changing normal success completion handling.

Files changed: packages/ag-ui/agent_framework_ag_ui/_client.py forwards normalized available_interrupts/resume values; packages/ag-ui/agent_framework_ag_ui/_http_service.py serializes typed protocol models and compatible values to camelCase wire JSON; packages/ag-ui/agent_framework_ag_ui/_event_converters.py preserves canonical outcomes/interruption metadata; packages/ag-ui/tests/ag_ui/test_ag_ui_client.py, test_http_service.py, and test_event_converters.py cover outgoing typed JSON, canonical interrupted conversion, and success outcome behavior.

Verification: uv run pytest packages/ag-ui/tests/ag_ui/test_http_service.py::test_post_run_serializes_typed_interrupts_and_resume_with_protocol_aliases packages/ag-ui/tests/ag_ui/test_ag_ui_client.py::TestAGUIChatClient::test_typed_interrupt_options_forward_canonical_protocol_shape packages/ag-ui/tests/ag_ui/test_event_converters.py::TestAGUIEventConverter::test_run_finished_event_with_canonical_interrupt_outcome packages/ag-ui/tests/ag_ui/test_event_converters.py::TestAGUIEventConverter::test_run_finished_event_with_success_outcome_preserves_normal_completion -q; uv run pytest packages/ag-ui/tests/ag_ui/test_http_service.py packages/ag-ui/tests/ag_ui/test_ag_ui_client.py packages/ag-ui/tests/ag_ui/test_event_converters.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.

Notes: issues/agui-int-05-chat-client-http-forwarding.md was moved to issues/done locally but not staged. Existing unstaged packages/ag-ui/README.md, .ralph, PRD, and snapshot planning artifacts remain untouched. Follow-up slices still own stricter pending interrupt contract enforcement, snapshot stale-prompt clearing, and documentation/examples.

* Python: Enforce AG-UI resume contract

Key decisions: validate pending AG-UI interrupts before agent or workflow execution; require resume entries to address every open interrupt exactly once; emit RUN_ERROR for missing, unknown, duplicate, malformed, cancelled, or schema-invalid resume payloads; keep successful canonical approval and workflow resume flows working while removing heuristic non-resume workflow continuation for interrupted threads.

Files changed: packages/ag-ui/agent_framework_ag_ui/_run_common.py adds strict resume parsing and exact pending-interrupt contract validation; _agent_run.py applies the contract to approval resumes, validates edited approval argument types, and considers stored canonical interrupt ids; _workflow_run.py applies the contract to request_info resumes and fails invalid response coercion explicitly; AG-UI endpoint, workflow, golden, wrapper, and subgraph tests now cover RUN_ERROR failures and canonical resume entries.

Verification: uv run pytest focused new resume-contract endpoint tests -q; uv run pytest existing approval/workflow resume endpoint tests -q; uv run pytest packages/ag-ui/tests/ag_ui/test_workflow_run.py packages/ag-ui/tests/ag_ui/test_run.py packages/ag-ui/tests/ag_ui/golden/test_scenario_workflow.py -q; uv run poe test -P ag-ui; 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 check -P ag-ui; git diff --check; git diff --cached --check.

Notes: issues/agui-int-06-resume-contract-validation.md was moved to issues/done locally but not staged. Existing unstaged packages/ag-ui/README.md and local .ralph/PRD artifacts remain untouched. Follow-up slices still own canonical snapshot stale-prompt clearing and documentation/examples.

* Python: Clear AG-UI snapshot interrupts on cancel

Key decisions: treat cancelled canonical approval and workflow resumes as completion of the stored interruption for AG-UI Thread Snapshot hydration; clear only the persisted interrupt field while preserving replayable messages and Shared State; consume cancelled approval registry entries so server-side approval state does not remain open; preserve existing RUN_ERROR responses for cancelled resumes.

Files changed: packages/ag-ui/agent_framework_ag_ui/_snapshots.py adds shared persisted-interrupt clearing; _agent_run.py clears snapshots and consumes pending approvals on cancelled approval resumes; _workflow.py clears snapshots on cancelled workflow resumes; test_endpoint.py covers agent/workflow cancelled-resume stale prompt clearing; test_run_common.py covers canonical interrupt toolCallId trusted suffix filtering.

Verification: uv run pytest focused interrupted snapshot/resume tests -q; uv run poe test -P ag-ui; 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 check -P ag-ui; git diff --check; git diff --cached --check.

Notes: issues/agui-int-07-thread-snapshot-interrupt-hydration.md was moved to issues/done locally but not staged. Existing unstaged packages/ag-ui/README.md and local .ralph/PRD artifacts remain untouched. Follow-up docs/examples slice still owns public guidance updates.

* Python: Document canonical AG-UI interrupts

Key decisions: document the clean release-candidate interrupt cutover around canonical AG-UI protocol models; direct users to RUN_FINISHED.outcome.interrupts and canonical resume arrays; make clear that Interrupt and ResumeEntry come from ag_ui.core rather than an Agent Framework-specific model; retain normal RUN_FINISHED completion guidance for non-interrupted runs.

Files changed: packages/ag-ui/AGENTS.md updates package guidance; packages/ag-ui/README.md adds interrupt/resume protocol and migration notes; packages/ag-ui/agent_framework_ag_ui_examples/README.md documents canonical resume shape for examples; packages/ag-ui/getting_started/README.md teaches outcome.interrupts and ResumeEntry usage.

Verification: uv run poe markdown-code-lint failed on pre-existing packages/mistral/README.md; uv run python scripts/check_md_code_blocks.py packages/ag-ui/README.md packages/ag-ui/agent_framework_ag_ui_examples/README.md packages/ag-ui/getting_started/README.md packages/ag-ui/AGENTS.md; git diff --check; git diff --cached --check.

Notes: no issue or PRD artifacts were staged. Root AGENTS.md could not be read because access was denied; package and Python workspace guidance were applied. Follow-up docs/examples issue appears complete; no remaining AG-UI interrupt cutover tasks were found locally.

* Canonicalize AG-UI interrupt and resume handling

* Fix AG-UI interrupt resume feedback

* Address AG-UI review feedback
2026-07-07 06:34:51 +00:00
Giles Odigwe bcd2800d4c Python: Allow disabling approval for SkillsProvider tools (#6867)
* 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>
2026-07-07 04:25:49 +00:00
Evan Mattson 38b5f70d1f Fix README stars badge link (#6944) 2026-07-07 11:08:55 +09:00
Tao Chen 868744aeea Python: Process messages to an executor serially within a superstep (#6776)
* 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
2026-07-06 22:40:48 +00:00
Tao Chen 5ac5038545 .NET: Add defense-in-depth for MCP cross origin request (#6871)
* Add defense-in-depth for MCP cross origin request

* Address comments
2026-07-06 17:37:36 +00:00
Tao Chen fc10ef31bd Python: Add FHA declarative workflow sample (#6897)
* Add FHA declarative workflow sample

* Address comments

* Address comments
2026-07-06 16:38:52 +00:00
Roger Barreto c09408cbd6 .NET: Fix flaky OpenTelemetryAgentTests via thread-safe activity collector (#6935)
* .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.
2026-07-06 16:11:11 +00:00
dependabot[bot] 329d59eff4 Bump vite and @vitejs/plugin-react-swc (#6613)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) and [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react-swc). These dependencies needed to be updated together.

Updates `vite` from 7.3.2 to 8.0.16
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v8.0.16/packages/vite)

Updates `@vitejs/plugin-react-swc` from 3.11.0 to 4.3.1
- [Release notes](https://github.com/vitejs/vite-plugin-react/releases)
- [Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite-plugin-react/commits/v4.3.1/packages/plugin-react-swc)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 8.0.16
  dependency-type: direct:development
- dependency-name: "@vitejs/plugin-react-swc"
  dependency-version: 4.3.1
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
2026-07-06 14:26:42 +09:00
dependabot[bot] a5fcd33967 Build(deps-dev): Bump js-yaml in /python/packages/devui/frontend (#6813)
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 4.1.1 to 4.3.0.
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/4.1.1...4.3.0)

---
updated-dependencies:
- dependency-name: js-yaml
  dependency-version: 4.3.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-06 13:34:57 +09:00
westey ccba1fbbef .NET: [BREAKING] Align ShellPolicy allow/deny semantics with Python (#6906)
* Align dotnet shell policy with python implementation to support deny if not allowed semantics.

* Address PR comments.

* Address PR comments
2026-07-03 18:09:35 +00:00
Roger Barreto 2c7aadce4e .NET: Validate Foundry toolbox name is a single path segment before building the proxy URL (#6890)
* 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.
2026-07-03 17:00:26 +00:00
Roger Barreto 8be157e2a1 Return 404 for a response created against a nonexistent conversation (#6892)
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.
2026-07-03 17:00:00 +00:00
SergeyMenshykh 7ca73c0645 Update .NET version to 1.13.0 (#6900)
Co-authored-by: SergeyMenshykh <SergeMenshikh@outlook.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-07-03 16:30:21 +00:00
Eduard van Valkenburg 0260ea0e61 Python: implement ADR-0029 service_session_id lifecycle mapping (#6724)
* python: implement ADR-0029 service_session_id lifecycle mapping

- Extend AgentSession service_session_id to support structured values
- Add agent-owned conversation id extraction for chat forwarding and telemetry
- Migrate A2A durable continuation state to A2AServiceSessionId
- Keep A2AAgentSession as compatibility shim and mark it deprecated
- Update core/a2a tests and package guidance

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix service_session_id type fallout across packages

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix remaining test typing signatures for service_session_id

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix hosting test stubs for widened get_session type

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix remaining test stubs for get_session union type

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Simplify A2A session state handling

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix import

* Fix hosting-telegram test get_session typing

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-02 18:36:48 +00:00
westey 24581d6865 Python: Allow devs to opt-out of file-access approvals (#6879) 2026-07-02 18:36:44 +00:00
SergeyMenshykh 331d17c5a1 .NET: fix: Require explicit TokenCredential in AddFoundryToolboxes (#6877)
* 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>
2026-07-02 15:44:56 +00:00
Ben Thomas db80926f31 .NET: Improving DotNet samples (#6869)
* 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>
2026-07-02 15:16:26 +00:00
westey 48436f8ab6 .NET: Make default-approval harness features configurable + customizable shell tool (#6880)
* Dotnet: Allow devs to opt-out of file-access approvals

* Address PR comments
2026-07-02 14:57:59 +00:00
Roger Barreto 551b44f04f .NET: Bump Azure.AI.Projects to 2.1.0-beta.4 (#6795)
* .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).
2026-07-02 14:40:03 +00:00
Eduard van Valkenburg 09ea690062 Python: Fix Hyperlight workspace staging (#6856)
* 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>
2026-07-02 14:02:38 +00:00
Roger Barreto 62f0024707 .NET: Foundry Hosting gracefully tolerates lacking user identity when run locally (#6870)
* .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.
2026-07-02 09:32:22 +00:00
Eduard van Valkenburg e38592a23c Python: Fix Anthropic messages and function-loop fallback (#6794)
* Fix function loop fallback and Anthropic messages

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Address PR feedback for fallback and instructions

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-02 05:37:23 +00:00
Tao Chen 08a09e7ebb Python: Update FHA samples after v2 changes (#6841)
* Update FHA samples after v2 changes

* Add missing pakcage pin

* Address comments
2026-07-01 21:45:42 +00:00
SergeyMenshykh c1e20632f7 .NET: Remove Experimental attribute from Skills API in Microsoft.Agents.AI (#6861)
* .NET: Remove Experimental attribute from Skills API in Microsoft.Agents.AI

Closes microsoft/agent-framework#6835

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Restore MAAI001 suppression for Step07 sample (still uses ToolApproval experimental APIs)

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Keep bare NoWarn placeholder in Step01 and Step02 samples

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: SergeyMenshykh <SergeMenshikh@outlook.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-07-01 17:33:10 +00:00
Giles Odigwe c41676682e Python: [BREAKING] Extract caching from SkillsProvider into a CachingSkillsSource decorator (#6847)
* 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>
2026-07-01 16:21:08 +00:00
Giles Odigwe effbd17325 Python: [BREAKING] Treat nested SKILL.md content as part of the parent skill (#6849)
* 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.
2026-07-01 16:06:56 +00:00
westey bc8dd4b63c Python: [BREAKING] FileAccess/FileMemory replace_lines literal replacement with line deletion (#6859)
* FileAccess/FileMemory: Allowing removing lines by using full line replace

* Add agents.md changes

* Address PR comments
2026-07-01 15:44:11 +00:00
VectorPeak 0f1fa21070 Python: Accept A2A data URI media parameters (#6818)
Co-authored-by: VectorPeak <VectorPeak@users.noreply.github.com>
2026-07-01 15:27:53 +00:00
westey 300dfa7e36 .NET: [BREAKING] Refactor OpenAI Hosting OptionsMapping to disallow passing options by default (#6855)
* Refactor OptionsMapping to disallow passing options by default

* Address PR comments

* Address PR comment
2026-07-01 15:05:53 +00:00
westey e56f34c521 .NET: [BREAKING] Add file editing tools and align FileAccess/FileMemory store API (#6807)
* Add support for editing to file access and memory plus renames

* Address PR comments

* Address PR comments
2026-07-01 13:35:48 +00:00
SergeyMenshykh 51c05fc862 .NET: Add skill approval options (#6843)
* .NET: Add skill approval options

Add per-tool options for disabling approval on skills provider tools and cover the behavior with unit tests.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* .NET: Document mixed approval behavior

Document the non-approval bypass requirement and update tests to avoid file-discovery dependency.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: SergeyMenshykh <SergeMenshikh@outlook.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-07-01 12:46:08 +00:00
SergeyMenshykh b7fc23c61f .NET: Consolidate skill-source caching and make skill sources disposable (#6827)
* .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>
2026-07-01 12:46:07 +00:00
SergeyMenshykh 00e4d4ffde Make skills source classes public and sealed with Experimental attribute (#6838)
- AgentInMemorySkillsSource: internal sealed → public sealed
- AggregatingAgentSkillsSource: internal sealed → public sealed
- CachingAgentSkillsSource: internal sealed → public sealed + [Experimental]
- DeduplicatingAgentSkillsSource: internal sealed partial → public sealed partial + [Experimental]
- FilteringAgentSkillsSource: internal sealed partial → public sealed partial + [Experimental]
- DelegatingAgentSkillsSource: internal abstract → public abstract + [Experimental]

Co-authored-by: SergeyMenshykh <SergeMenshikh@outlook.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-01 11:41:21 +00:00
Copilot 2cb97545bf .NET: Pin patched OpenAPI dependencies to unblock NU1903 in sample restores (#6853)
* Initial plan

* Bump OpenAPI packages to patched versions

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-01 11:25:58 +00:00
Giles Odigwe d50698bb79 Python: Allow custom argument parsing for skill scripts (#6817)
* 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>
2026-07-01 05:25:12 +00:00
Giles Odigwe 059e1e055f Python: Fix local history not injected when non-history context providers are present (#6810)
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>
2026-06-30 21:03:23 +00:00
Giles Odigwe 43f2095244 Python: Fix GeminiChatClient dropping image/file content (#6751)
* 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>
2026-06-30 21:00:54 +00:00
Giles Odigwe f01bea77bb Python: remove hosting package entries from 1.10.0 CHANGELOG (#6846)
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>
2026-06-30 20:45:43 +00:00
Giles Odigwe a25756b9ec Python: selective version bump for 1.10.0 release (date 260630) (#6840)
- Re-date all beta/alpha packages to 260630 (actual release date)
- agent-framework-ag-ui: rc6 -> rc7 (fastapi bound update)
- agent-framework-github-copilot: rc1 -> rc2 (approval hook feature)
- Newly bumped: azure-ai-search, devui, gemini, hyperlight, ollama
- CHANGELOG [1.10.0] date updated to 2026-06-30 with new entries
- Hosting/hosting-responses/hosting-telegram excluded per team decision
- Inter-package dependency bounds updated
- uv.lock refreshed

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-30 20:10:24 +00:00
Roger Barreto f9b2fbb676 .NET: Foundry Hosting per-user session isolation and Responses v2 protocol fast-fail (#6832)
* 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
2026-06-30 19:30:46 +00:00
Tao Chen 7f3a2aec38 [BREAKING] Python: Foundry Hosted Agent V2 protocol upgrade (#6811)
* Upgrade to FHA protocol v2 + toolbox integration

* Scope checkpoints and approval storage by user id

* Add toolbox skills integration

* Fix formatting

* Add httpx lower and upper bound

* Update foundry-hosting package version

* Remove custom http client

* Revert "Remove custom http client"

This reverts commit 60f1d5aa52.

* Remove custom http client

* correct fail fast exceptino wording
2026-06-30 17:13:08 +00:00
SergeyMenshykh 09fbccfb20 .NET: Add AgentSkillsSourceContext to AgentSkillsSource.GetSkillsAsync (#6797)
* 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>
2026-06-30 13:01:51 +01:00
Giles Odigwe 4cb1a6651d Python: Align GitHub Copilot provider function approval to use SDK on_pre_tool_use hook (#6750)
* 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>
2026-06-30 11:40:59 +00:00
SergeyMenshykh 3cc511f0ca .NET: Harden dotnet-format workflow shell handling (#6796)
* Harden dotnet-format workflow shell handling

Minor robustness improvements to how the dotnet-format workflow passes values to the shell.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Refine workflow path handling

Adjust shell settings around workflow path iteration for more predictable behavior.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-30 11:08:29 +00:00
westey a2f56f7688 Python: [BREAKING] Improve FileAccess/FileMemory harness providers (surgical edits, read-only tier, consistent naming) (#6801)
* Python: Add additional file access and memory provider functionality and renames for simplification and consistency

* Address PR comments and build failures.

* Address PR comments

* Address PR comments
2026-06-30 07:46:27 +00:00
chetantoshniwal e7c7f7477a Update NuGet package icon to new Microsoft Foundry Agent Framework logo (#6812)
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>
2026-06-30 03:57:23 +00:00
Ben Thomas a94db111a8 Bumping version for dotnet release. (#6816) 2026-06-29 19:13:30 -07:00
Ben Thomas fdc71075b7 fix: resolve CA1873 in GitHubCopilotAgent by using LoggerMessage source generator (#6815)
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>
2026-06-29 19:07:15 -07:00
Ben Thomas e59a31c96c fix: resolve CA1873 in GitHubCopilotAgent by using LoggerMessage source generator (#6814)
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>
2026-06-29 18:24:02 -07:00
Roger Barreto 0d53d11bc6 .NET: [BREAKING] Bump Azure.AI.AgentServer to 2.0.0 protocol and migrate Foundry.Hosting (#6800)
* .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.
2026-06-29 16:39:54 -07:00
chetantoshniwal 87210686b3 Revert commit ae09be1eed 2026-06-29 15:26:03 -07:00
chetantoshniwal ae09be1eed Replace NuGet icon with new PNG from docs/assets/PNG 2026-06-29 15:22:50 -07:00
SergeyMenshykh 07ddabbef7 Update hash algorithm in workspace_poe_tasks.py (#6802)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-29 19:39:47 +00:00
Giles Odigwe 7e5ba70884 Python: Stop swallowing skill script and resource errors so the model can self-correct (#6755)
* 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>
2026-06-29 19:10:42 +00:00
westey 6dd30950c1 Python: Fix background agent telemetry context error (#6764)
* Fix issue when using background agents with telemetry

* Address PR comments

* Fix issue when using background agents with telemetry

* Address PR comments

* Fix uv.lock

* Remove unecessary comments and commit hook reformatted code
2026-06-29 16:13:17 +00:00
Giles Odigwe c89a539c02 Python: [BREAKING] Make all SkillsProvider tools require approval by default (#6754)
* 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>
2026-06-29 14:14:45 +00:00
Farzad Sunavala 6dfcbc5c62 Python: support stable + preview Azure AI Search (Foundry IQ) API versions (#6603)
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>
2026-06-29 13:12:52 +00:00
westey 4272d90051 Python/.Net: Agent Harness blog post accompanying samples part 2 (#6692)
* 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
2026-06-29 11:38:52 +01:00
安妮的心动录 9a565f2bf8 Python: convert Pydantic model class response_format to JSON schema in OllamaChatClient (#6782)
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.
2026-06-29 10:15:03 +00:00
westey 9fd3d29e09 Python: Fix FunctionShellTool throw and empty streaming shell command (#6763)
* Fix shell tool bug

* Address PR feedback

* Fix uv.lock changes

* Update uv.lock
2026-06-29 09:48:37 +00:00
westey 6968a7fc59 Updating background agent loop to resolve provider automatically and add feedback message builder. (#6735) 2026-06-29 09:36:13 +00:00
Copilot 4d4db7f501 Python: create_harness_agent skills_paths accepts str | Path | Sequence[str | Path] | None (#6717)
* fix: skills_paths accepts str | Path | Sequence[str | Path] | None

* fix: update _assemble_context_providers skills_paths annotation to match public signature

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-06-29 08:40:12 +00:00
shrutitople 730bcee9ea Python: Autolabelling MCP servers based on hints and Github MCP server ifc labels (#6171)
* 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>
2026-06-29 08:34:00 +00:00
chetantoshniwal a1c37b69e0 [Generated by SRE Agent] Clarify identifier security guidance (#6510)
Co-authored-by: Azure SRE Agent <noreply@microsoft.com>
Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
2026-06-29 07:13:57 +00:00
SergeyMenshykh cb8cef3ef6 .NET: Improve proxy target validation in DevUI aggregator (#6771)
* Improve proxy target validation in DevUI aggregator

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fail closed on malformed proxy target URIs

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add direct coverage for proxy target validation

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Apply arrange-act-assert structure to aggregator tests

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix formatting in aggregator tests

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-28 18:32:01 +00:00
Giles Odigwe f1d838fc5e Python: bump package versions for 1.10.0 release (#6753)
* 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>
2026-06-27 16:10:30 +00:00
Giles Odigwe d5c5fb9d3d .NET: Enforce ApprovalRequiredAIFunction in GitHub Copilot provider via OnPreToolUse hook (#6674)
* .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>
2026-06-27 01:22:47 +00:00
SergeyMenshykh 846c963e85 .NET: Add description attribute to resource and script elements in skill body (#6759)
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>
2026-06-26 22:46:56 +00:00
SergeyMenshykh e0274b764e .NET: Extract caching from AgentSkillsProvider into CachingAgentSkillsSource (#6768)
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>
2026-06-26 22:29:35 +00:00
Ben Thomas 7749823393 .NET: Sample fix (#6773)
* 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>
2026-06-26 22:18:39 +00:00
SergeyMenshykh d09451408f Disable failing DurableTask and AzureFunctions integration tests (#6774)
Skip the following tests that are persistently failing in CI:

DurableTask - AgentEntityTests:
- EntityNamePrefixAsync
- RunAgentMethodNamesAllWorkAsync
- OrchestrationIdSetDuringOrchestrationAsync

DurableTask - ExternalClientTests:
- SimplePromptAsync
- CallFunctionToolsAsync
- CallLongRunningFunctionToolsAsync

DurableTask - WorkflowConsoleAppSamplesValidation:
- ConcurrentWorkflowSampleValidationAsync
- WorkflowAndAgentsSampleValidationAsync

DurableTask - ConsoleAppSamplesValidation:
- SingleAgentSampleValidationAsync
- SingleAgentOrchestrationChainingSampleValidationAsync
- MultiAgentConcurrencySampleValidationAsync
- MultiAgentConditionalSampleValidationAsync

AzureFunctions - SamplesValidation:
- SingleAgentSampleValidationAsync
- MultiAgentOrchestrationConcurrentSampleValidationAsync
- MultiAgentOrchestrationConditionalsSampleValidationAsync
- LongRunningToolsSampleValidationAsync
- AgentAsMcpToolAsync

AzureFunctions - WorkflowSamplesValidation:
- WorkflowAndAgentsSampleValidationAsync
- ConcurrentWorkflowSampleValidationAsync

Related to: microsoft/agent-framework#6732

Co-authored-by: SergeyMenshykh <SergeMenshikh@outlook.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-26 21:31:08 +00:00
Tommaso Stocchi daac8c15f3 .NET: Prefer HTTPS backends in Aspire DevUI (#6772)
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>
2026-06-26 18:15:51 +00:00
Roger Barreto 62ff5ac79e .NET: Align Foundry.Hosting experimental flags to MAAI001 for MAF-specific APIs (#6743)
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
2026-06-26 14:18:11 +00:00
Roger Barreto 231b35da55 .NET: Foundry hosted-agent toolbox OAuth consent support (#6718)
* .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.
2026-06-26 13:07:23 +00:00
westey 772c6fd921 .NET: Add BackgroundTaskCompletionLoopEvaluator for harness background agents (#6736)
* Add background agent loop evaluator

* Address PR comments
2026-06-26 14:24:20 +01:00
Marco Minerva 82e8653f95 Fix typo in XML doc comment for workflow outputs param (#6326)
Corrected a duplicated word ("into into") in the XML documentation
for the includeWorkflowOutputsInResponse parameter.
2026-06-25 23:27:46 +00:00
Tao Chen 3c3feb8705 Python: Refactor runner/workflow responsibilities and fix checkpoint ancestry bug (#6695)
* 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
2026-06-25 23:15:01 +00:00
Tao Chen b8b43798b9 Python: Update FHA with toolbox sample with more auth methods (#6713)
* Update FHA with toolbox sample with more auth methods

* Clean up

* Clean up other samples
2026-06-25 22:59:15 +00:00
Peter Ibekwe d9ec5eaab4 update package version (#6752) 2026-06-25 22:06:53 +00:00
SergeyMenshykh e3b64fdc47 .NET: [BREAKING] Make all AgentSkillsProvider tools require approval by default (#6729)
* 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>
2026-06-25 20:44:12 +00:00
Peter Ibekwe 3a5bbb5f8e .NET: Add DeclarativeWorkflowJsonOptions for AOT-safe declarative workflow checkpointing (#6745)
* Add experimental DeclarativeWorkflowJsonOptions for AOT-safe declarative workflow checkpointing

* Address PR comments
2026-06-25 19:57:22 +00:00
Peter Ibekwe a7332d69a8 .NET: Fix issue with resuming checkpoint after package version upgrade (#6670)
* Fix issue with resuming checkpoint after package version upgrade

* Address PR comments

* Fix changelog encoding
2026-06-25 18:32:22 +00:00
dependabot[bot] e57e9455b3 Build(deps): Bump hyperlight-sandbox-python-guest in /python (#6737)
Bumps hyperlight-sandbox-python-guest from 0.4.0 to 0.5.0.

---
updated-dependencies:
- dependency-name: hyperlight-sandbox-python-guest
  dependency-version: 0.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-25 17:41:39 +00:00
dependabot[bot] d97bc4fe39 Build(deps): Bump huggingface-hub from 1.20.1 to 1.21.0 in /python (#6738)
Bumps [huggingface-hub](https://github.com/huggingface/huggingface_hub) from 1.20.1 to 1.21.0.
- [Release notes](https://github.com/huggingface/huggingface_hub/releases)
- [Commits](https://github.com/huggingface/huggingface_hub/compare/v1.20.1...v1.21.0)

---
updated-dependencies:
- dependency-name: huggingface-hub
  dependency-version: 1.21.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-25 17:41:32 +00:00
dependabot[bot] 5d57b10b9f Build(deps): Bump google-genai from 1.75.0 to 2.10.0 in /python (#6739)
Bumps [google-genai](https://github.com/googleapis/python-genai) from 1.75.0 to 2.10.0.
- [Release notes](https://github.com/googleapis/python-genai/releases)
- [Changelog](https://github.com/googleapis/python-genai/blob/main/CHANGELOG.md)
- [Commits](https://github.com/googleapis/python-genai/compare/v1.75.0...v2.10.0)

---
updated-dependencies:
- dependency-name: google-genai
  dependency-version: 2.10.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-25 17:41:22 +00:00
dependabot[bot] 8cd71dd4f6 Build(deps): Bump fastapi from 0.124.4 to 0.138.0 in /python (#6740)
Bumps [fastapi](https://github.com/fastapi/fastapi) from 0.124.4 to 0.138.0.
- [Release notes](https://github.com/fastapi/fastapi/releases)
- [Commits](https://github.com/fastapi/fastapi/compare/0.124.4...0.138.0)

---
updated-dependencies:
- dependency-name: fastapi
  dependency-version: 0.138.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-25 17:41:13 +00:00
SergeyMenshykh 5e5dd87c91 Update .NET SDK to 10.0.301 (#6730)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-25 15:27:54 +00:00
SergeyMenshykh d0be98d649 Remove {resource_instructions} and {script_instructions} placeholder mechanism (#6706)
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>
2026-06-25 15:19:35 +00:00
SergeyMenshykh 802fe13053 Disable failing durable function integration tests (#6731)
Skip LongRunningToolsSampleValidationAsync and ReliableStreamingSampleValidationAsync
tests that are persistently failing in CI.

Co-authored-by: SergeyMenshykh <SergeMenshikh@outlook.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-25 14:42:34 +00:00
Eduard van Valkenburg d75f2286f4 Python: Add Telegram channel for agent-framework-hosting (#6698)
* 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>
2026-06-25 11:40:48 +00:00
Eduard van Valkenburg ce74c84bdb Python: Preserve OTel parent context for deferred streams (#6709)
* Python: Preserve OTel parent context for deferred streams

- capture current OTel context when opening host-managed streaming runs
- re-activate captured context during deferred stream pulls and finalization
- add host-level regression coverage for deferred stream parent-span linkage
- add Responses channel integration coverage for request parent span propagation

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Python: Capture OTel stream context before target.run

- capture OTel context snapshot before invoking target.run in _invoke_stream
- add regression test guarding capture-before-run evaluation order

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-25 05:51:18 +00:00
Eduard van Valkenburg 4fb1fb615a Python: Fix Hyperlight CodeAct span parenting (#6712)
* Fix Hyperlight CodeAct span parenting

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix Hyperlight test OTEL fixture

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix Hyperlight test typing annotations

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-25 05:50:58 +00:00
Ben Thomas 41a9c54bbe Add Foundry project environment variables (#6721) 2026-06-24 15:18:59 -07:00
Giles Odigwe 9f1ee23a4b Python: [BREAKING] Refactor FileSkillsSource for depth-based discovery and predicate filters (#6488)
* 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>
2026-06-24 21:48:25 +00:00
Ben Thomas 336a19fd32 .NET: .NET samples: migrate coding samples to Foundry-first AIProjectClient (#6557)
* 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>
2026-06-24 13:45:14 -07:00
Roger Barreto 0283fd00a1 .NET: Fix hosted agent crash after tool call by rooting session store under $HOME (#6231) (#6714)
* .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
2026-06-24 18:55:08 +00:00
Eduard van Valkenburg 5627dc0493 docs: Add Python session identity ADR (#6630)
* docs: Add Python session identity ADR

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs: Clarify session identity ADR example

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs: Reorder session identity options

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs: Select richer service session identity option

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs: Accept Python session identity ADR

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs: clarify ADR session identity lifecycle

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs: fix ADR concrete gap framing

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs: refine ADR identity decision guide

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-24 17:42:45 +00:00
Yufeng He 1df47667ea Python: surface cache and reasoning token counts for the Bedrock and Gemini connectors (#6640)
* 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.
2026-06-24 17:09:01 +00:00
Giles Odigwe 91f639a694 Python: Explicitly emit available_resources and available_scripts in skill content (#6694)
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>
2026-06-24 15:42:58 +00:00
Roger Barreto a9e5f6d798 .NET: .NET Foundry: add CreateMcpTool projectConnectionId overload (#6703)
* .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.
2026-06-24 12:40:47 +00:00
SergeyMenshykh ea7ae1cc00 .NET: [BREAKING] Support archive-type skills in AgentMcpSkillsSource (#6631)
* 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>
2026-06-24 13:12:08 +01:00
SergeyMenshykh e049bb5691 .NET: Add IncludeDetailedErrors option for skill script execution (#6680)
* 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).

Fixes microsoft/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.

Fixes microsoft/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>
2026-06-24 11:59:55 +01:00
SergeyMenshykh dd4b7ff475 .NET: Fix SearchDirectoriesForSkills to stop recursing after finding SKILL.md (#6686)
* 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.

Fixes microsoft/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>
2026-06-24 08:35:11 +00:00
Taisir Hassan d5c15f2fe1 .NET/Python: Purview: prefer token principal for user identity (#6693)
* 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>
2026-06-24 00:16:44 +00:00
Eduard van Valkenburg 4cf7ace446 Python: track dependency maintenance PR creation (#6665)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-24 00:09:56 +00:00
Amit Dhawan 5fff0df2af Python: Add load_dotenv to get-started samples and fix chat_response_… (#6691)
* Python: Add load_dotenv to get-started samples and fix chat_response_cancellation docs

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Amit Dhawan <amit.dhawan@barco.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-23 23:11:08 +00:00
Eduard van Valkenburg acb28a63b5 Python: Fix MCP metadata and tool name handling (#6656)
* Fix MCP metadata and tool name handling

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Address MCP review feedback

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-23 21:00:12 +00:00
Eduard van Valkenburg f2d02e58b3 Python: Add hosting core and Responses channel (#6580)
* Add Python hosting core and Responses channel

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Address hosting core review feedback

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Adopt source pyright typing setup for hosting packages

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Cover ResponsesChannel custom path routing

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Align hosting tests with package layout

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix hosting workflow fixture imports in aggregate tests

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Apply useful Responses channel hardening

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix hosting package typing checks

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix hosting pyright under Python 3.11

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Avoid static diskcache dependency in hosting

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix aggregate typing and Docker test resilience

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Simplify local Responses workflow sample

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Clarify generic hosting is not Foundry hosting

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Revert "Clarify generic hosting is not Foundry hosting"

This reverts commit 73b584d919053bed43a258d75dc2b76406e9c181.

* Clarify isolation key source flexibility

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Clarify isolation header reuse boundary

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Support multimodal Responses channel outputs

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Preserve multimodal streaming Responses output

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Stream Responses output items from updates

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Improve Responses streaming output handling

* Tighten Responses channel default option handling

- Restore full option parsing in parse_responses_request: known fields
  are remapped (max_output_tokens→max_tokens, parallel_tool_calls→
  allow_multiple_tool_calls), transport/session keys excluded, None
  values dropped, everything else forwarded as-is so run_hook can
  inspect the full set.
- Add a default _strip_options_hook on ResponsesChannel that removes
  all parsed options before reaching the agent. Callers cannot inject
  generation params (temperature, instructions, tools, …) unless the
  host explicitly allows it.
- A custom run_hook replaces the default entirely and receives the
  full ChannelRequest.options plus the raw protocol_request.
- Update tests to cover remap, default-strip, and custom-hook paths.
- Clarify host debug-log docstring to match new option flow.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-23 20:56:46 +00:00
Tao Chen 36420c515e Python: Align serialized tool format to OTel GenAI tool def format (#6556)
* Align serialized tool format to OTel GenAI tool def format

* Cache serialized tools
2026-06-23 20:47:18 +00:00
Peter Ibekwe 1109d0bf64 Get date suffix up to date with release date (#6690) 2026-06-23 18:58:19 +00:00
Peter Ibekwe e030fb53de .NET: Replace the symlink index entries with regular file entries (#6687)
* replace the symlink index entries  with regular file entries

* Fixed broken links.
2026-06-23 16:16:23 +00:00
SergeyMenshykh e6ebba1884 Add ADR 0029: Skills over MCP implementation design options (#6679)
* Add ADR 0029: Skills over MCP implementation design options

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>

---------

Co-authored-by: SergeyMenshykh <SergeMenshikh@outlook.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>
2026-06-23 15:51:05 +00:00
Tao Chen 7051a4920d Python: Add MCP as a hard dep in Foundry Hosting (#6634)
* Add MCP as a hard dep in Foundry Hosting

* Pin GitHub SDK

* Fix formatting

* Fix formatting
2026-06-23 15:07:05 +00:00
Roger Barreto 15df1152fc .NET: Add sample for per-run refreshable MCP authentication headers (#6624)
* 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.
2026-06-23 15:03:44 +00:00
westey a2018b40f9 Python: [BREAKING] Require approval for file-access tools with read-only auto-approval (#6599)
* 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>
2026-06-23 09:51:12 +00:00
SergeyMenshykh e4b89373f1 .NET: Explicitly emit available_resources and available_scripts in skill content (#6672)
* .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>
2026-06-23 09:51:10 +00:00
SergeyMenshykh 88f0b23fb0 .NET: Change A2A default session store to NoopAgentSessionStore (#6635)
* 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>
2026-06-23 08:55:59 +00:00
Peter Ibekwe 9ba6b3a94e Remove unnecessary declarative logging (#6677) 2026-06-23 00:35:12 +00:00
Peter Ibekwe 2999f7416f Update package release version (#6673) 2026-06-22 22:13:50 +00:00
Roger Barreto 09791533cf .NET: Emit execute_tool spans by placing OpenTelemetry below FunctionInvokingChatClient (#6667)
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.
2026-06-22 15:09:29 -07:00
Tao Chen 7f2e19ca2f Python: Ensure spans created inside sync preparations in streaming call are correctly nested (#6552)
* Make sure spans created inside sync ops in streaming path are correctly nested

* Add tests

* Fix comments

* Fix typing
2026-06-22 19:46:11 +00:00
westey 7b6f582b13 Python: Agent Harness blog post accompanying samples part 1 (#6605)
* 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
2026-06-22 18:33:36 +01:00
Giles Odigwe dc60722cee .NET: Project ToolExecution events as FunctionCallContent/FunctionResultContent in GitHubCopilotAgent streaming (#6228)
* 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>
2026-06-22 16:45:03 +00:00
Peter Ibekwe 2f5a76ab1d Fix issue with resuming checkpoint after package version upgrade (#6636) 2026-06-22 15:18:22 +00:00
Eduard van Valkenburg a7381d8bef Python: stabilize dependency maintenance final checks (#6662)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-22 14:23:29 +00:00
westey d108d4b549 Python: [BREAKING] Integrate looping into HarnessAgent (#6607)
* Integrate looping into harness

* Address PR comments

* Address PR comments.

* Fix typing error
2026-06-22 13:14:30 +00:00
Eduard van Valkenburg fd160a7782 Python: fix dependency maintenance cutoff (#6658)
* Python: fix dependency maintenance cutoff

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Python: fix Hyperlight output dir typing

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-22 11:46:26 +00:00
Copilot ad3c1535c4 fix: propagate EnableSensitiveData to auto-wired inner OpenTelemetryChatClient (#6096)
When _autoWireChatClient=true and the caller sets EnableSensitiveData=true on
the outer OpenTelemetryAgent, the auto-wired inner OpenTelemetryChatClient now
also has EnableSensitiveData propagated, so the inner chat span correctly
captures message content (gen_ai.input.messages / gen_ai.output.messages).

Red test added first to reproduce the bug, then the fix applied (green).

Fixes #5873

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/fda69dd4-9576-4f3f-b954-514321652ea9

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com>
2026-06-22 10:27:13 +00:00
安妮的心动录 10b7d08bff .NET: fix(hosting): emit url_citation annotation events from streamed AI Search responses (#6649)
* 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>
2026-06-22 10:10:02 +00:00
Eduard van Valkenburg fc3111c391 Python: Add FoundryAgent conversation session helper (#6623)
* Add FoundryAgent conversation session helper

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Simplify Foundry conversation session helper

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Rename Foundry conversation helper

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* use named kw

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-22 08:41:15 +00:00
Roger Barreto 098e521586 .NET: Bring Hosted-Toolbox sample to parity with sibling hosting samples (#6633)
* 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.
2026-06-20 09:31:19 +00:00
Eduard van Valkenburg 7435dd48d0 Python: harden Hyperlight output capture against symlinks (#6601)
* 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>
2026-06-19 22:43:38 +00:00
Ahmed Muhsin 148f57020a Python: host MAF workflows on a standalone Durable Task worker (#6418)
* 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>
2026-06-19 22:21:08 +00:00
Ben Thomas 89d19a2370 .NET: Migrate 01-get-started samples to Foundry as canonical default (#6555)
* 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>
2026-06-19 15:17:12 +00:00
Peter Ibekwe c815902344 .NET: InProcessRunnerContext bugfix for workflows (#6551)
* workfllow bugfix

* Update exception message

* Fix unit test.
2026-06-19 14:09:36 +00:00
Peter Ibekwe 074ac68a6c .NET: Harden fan-in barrier checkpoint state and extend resume coverage (#6574)
* Harden fan-in barrier checkpoint state and extend resume coverage

* Address PR comment
2026-06-19 13:33:26 +00:00
Eduard van Valkenburg d049d94b49 Python: consolidate dependency maintenance workflow (#6570)
* Python: consolidate dependency maintenance workflow

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Python: delay dependency maintenance updates

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Python: track dependency bounds test failures

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Python: scope dependency maintenance token

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-19 09:41:35 +00:00
dependabot[bot] 41995e265d Build(deps): Bump anthropic from 0.80.0 to 0.107.1 in /python (#6396)
Bumps [anthropic](https://github.com/anthropics/anthropic-sdk-python) from 0.80.0 to 0.107.1.
- [Release notes](https://github.com/anthropics/anthropic-sdk-python/releases)
- [Changelog](https://github.com/anthropics/anthropic-sdk-python/blob/main/CHANGELOG.md)
- [Commits](https://github.com/anthropics/anthropic-sdk-python/compare/v0.80.0...v0.107.1)

---
updated-dependencies:
- dependency-name: anthropic
  dependency-version: 0.107.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-19 08:53:17 +00:00
dependabot[bot] 2adacb3034 Bump aiohttp from 3.13.4 to 3.14.1 in /python (#6395)
---
updated-dependencies:
- dependency-name: aiohttp
  dependency-version: 3.14.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-19 08:53:08 +00:00
dependabot[bot] 6e3836698b Bump Anthropic.Foundry from 0.5.0 to 0.6.0 (#6057)
---
updated-dependencies:
- dependency-name: Anthropic.Foundry
  dependency-version: 0.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-19 08:52:48 +00:00
dependabot[bot] 0d3e3504f0 Build(deps): Bump mistralai from 2.4.2 to 2.4.9 in /python (#6393)
Bumps [mistralai](https://github.com/mistralai/client-python) from 2.4.2 to 2.4.9.
- [Release notes](https://github.com/mistralai/client-python/releases)
- [Changelog](https://github.com/mistralai/client-python/blob/main/RELEASES.md)
- [Commits](https://github.com/mistralai/client-python/compare/v2.4.2...v2.4.9)

---
updated-dependencies:
- dependency-name: mistralai
  dependency-version: 2.4.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-19 08:45:29 +00:00
dependabot[bot] 2ba97ebcca Bump openai from 2.24.0 to 2.43.0 in /python (#6394)
Bumps [openai](https://github.com/openai/openai-python) from 2.24.0 to 2.43.0.
- [Release notes](https://github.com/openai/openai-python/releases)
- [Changelog](https://github.com/openai/openai-python/blob/main/CHANGELOG.md)
- [Commits](https://github.com/openai/openai-python/compare/v2.24.0...v2.43.0)

---
updated-dependencies:
- dependency-name: openai
  dependency-version: 2.41.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-19 08:45:17 +00:00
hanhan761 2d0555c537 Python: re-role trailing assistant message to user for Anthropic compatibility (fixes #5008) (#6207)
* 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>
2026-06-19 06:25:34 +00:00
Evan Mattson 5145d50be8 Python: Fix AG-UI tool history replay sanitization  (#6581)
* Python: Fix AG-UI tool history replay sanitization 

* Python: Address AG-UI replay review comments

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-19 15:01:01 +09:00
dependabot[bot] 7f7c88bfa5 Build(deps): Bump python-multipart from 0.0.26 to 0.0.32 in /python (#6406)
* Build(deps): Bump python-multipart from 0.0.26 to 0.0.27 in /python

Bumps [python-multipart](https://github.com/Kludex/python-multipart) from 0.0.26 to 0.0.27.
- [Release notes](https://github.com/Kludex/python-multipart/releases)
- [Changelog](https://github.com/Kludex/python-multipart/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Kludex/python-multipart/compare/0.0.26...0.0.27)

---
updated-dependencies:
- dependency-name: python-multipart
  dependency-version: 0.0.27
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

* Build(deps): Bump python-multipart to 0.0.32 via override-dependencies floor >=0.0.31

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-06-19 01:00:26 +00:00
Shyju Krishnankutty bcef77af6a .NET: (Durable): Scope workflow status/respond endpoints to route workflow name (#6608)
* 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>
2026-06-19 00:01:15 +00:00
Ben Thomas 54a30571aa Dotnet - Add support for Foundry Adaptive evals (#6267)
* .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>
2026-06-18 20:50:28 +00:00
westey dc445592ed Python: [BREAKING] Port FileMemoryProvider and integrate FileMemoryProvider & FileAccess into the harness agent (#6547)
* 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>
2026-06-18 20:15:46 +00:00
westey 92823e9e61 .NET: [BREAKING] Require approval for FileAccessProvider tools with auto-approval rules (#6521)
* Require approval for file-access

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Rename DisableToolApproval to DisableToolAutoApproval for clarity

* Fix broken suggestion.

* Address PR comments and fix build issue.

* Update dotnet/src/Microsoft.Agents.AI.Harness/HarnessAgentOptions.cs

Co-authored-by: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com>

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com>
2026-06-18 18:55:22 +00:00
Eduard van Valkenburg 7a491f8e76 Python: Add hosting channel ADRs and spec (#6578)
* Add Python hosting channel ADRs

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add Python hosting implementation spec

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-18 18:29:24 +00:00
Ben Thomas 015e3bcd3b .NET: Enabling sequential orchestration to pass entire conversation or only previous output. (#6554)
* Fix sequential workflow input forwarding

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Make sequential workflow context configurable

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Clarify sequential chain-only behavior

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Clarify sequential output messaging

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>
2026-06-18 17:53:47 +00:00
Eduard van Valkenburg 6e95517659 Python: Split type checkers by target (pyright source, 5 checkers on tests/samples) (#6443)
* 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>
2026-06-18 15:06:20 +00:00
Evan Mattson 97bb1d588a Migrate to using issue type bug instead of label bug. (#6595) 2026-06-18 14:47:00 +00:00
Roger Barreto 1fc57c45ee .NET: Bump Azure.AI.Projects to 2.1.0-beta.3 (#6542)
* 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>
2026-06-18 13:58:22 +00:00
Evan Mattson b3f8aaa9d7 Python: adjust coverage report handoff (#6576)
* Adjust coverage report handoff

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Simplify coverage report handoff check

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-18 12:35:45 +00:00
dependabot[bot] c22fc8d653 Build(deps): Bump esbuild, @tailwindcss/vite, @vitejs/plugin-react and vite (#6503)
Removes [esbuild](https://github.com/evanw/esbuild). It's no longer used after updating ancestor dependencies [esbuild](https://github.com/evanw/esbuild), [@tailwindcss/vite](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-vite), [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react) and [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite). These dependencies need to be updated together.


Removes `esbuild`

Updates `@tailwindcss/vite` from 4.1.12 to 4.3.1
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.1/packages/@tailwindcss-vite)

Updates `@vitejs/plugin-react` from 5.0.1 to 5.2.0
- [Release notes](https://github.com/vitejs/vite-plugin-react/releases)
- [Changelog](https://github.com/vitejs/vite-plugin-react/blob/plugin-react@5.2.0/packages/plugin-react/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite-plugin-react/commits/plugin-react@5.2.0/packages/plugin-react)

Updates `vite` from 7.3.2 to 8.0.16
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v8.0.16/packages/vite)

---
updated-dependencies:
- dependency-name: esbuild
  dependency-version:
  dependency-type: indirect
- dependency-name: "@tailwindcss/vite"
  dependency-version: 4.3.1
  dependency-type: direct:production
- dependency-name: "@vitejs/plugin-react"
  dependency-version: 5.2.0
  dependency-type: direct:development
- dependency-name: vite
  dependency-version: 8.0.16
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-18 11:35:48 +00:00
dependabot[bot] a3131b8130 Build(deps): Bump esbuild, @vitejs/plugin-react and vite (#6501)
Removes [esbuild](https://github.com/evanw/esbuild). It's no longer used after updating ancestor dependencies [esbuild](https://github.com/evanw/esbuild), [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react) and [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite). These dependencies need to be updated together.


Removes `esbuild`

Updates `@vitejs/plugin-react` from 4.7.0 to 6.0.2
- [Release notes](https://github.com/vitejs/vite-plugin-react/releases)
- [Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite-plugin-react/commits/plugin-react@6.0.2/packages/plugin-react)

Updates `vite` from 7.3.2 to 8.0.16
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v8.0.16/packages/vite)

---
updated-dependencies:
- dependency-name: esbuild
  dependency-version:
  dependency-type: indirect
- dependency-name: "@vitejs/plugin-react"
  dependency-version: 6.0.2
  dependency-type: direct:development
- dependency-name: vite
  dependency-version: 8.0.16
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-18 11:35:40 +00:00
dependabot[bot] 3d46595111 Python: Bump prek from 0.4.3 to 0.4.5 in /python (#6527)
* Bump prek from 0.4.3 to 0.4.5 in /python

Bumps [prek](https://github.com/j178/prek) from 0.4.3 to 0.4.5.
- [Release notes](https://github.com/j178/prek/releases)
- [Changelog](https://github.com/j178/prek/blob/master/CHANGELOG.md)
- [Commits](https://github.com/j178/prek/compare/v0.4.3...v0.4.5)

---
updated-dependencies:
- dependency-name: prek
  dependency-version: 0.4.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix python workspace prek pin mismatch

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-06-18 10:09:11 +00:00
dependabot[bot] 205f7bcca8 Python: Bump pytest from 9.0.3 to 9.1.0 across /python workspace (#6524)
* Bump pytest from 9.0.3 to 9.1.0 in /python

Bumps [pytest](https://github.com/pytest-dev/pytest) from 9.0.3 to 9.1.0.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/9.0.3...9.1.0)

---
updated-dependencies:
- dependency-name: pytest
  dependency-version: 9.1.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* Fix Python workspace pytest pin mismatch

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-06-18 10:08:10 +00:00
dependabot[bot] 2048289fb0 Build(deps): Bump pydantic-monty from 0.0.17 to 0.0.18 in /python (#6392)
Bumps [pydantic-monty](https://github.com/pydantic/monty) from 0.0.17 to 0.0.18.
- [Release notes](https://github.com/pydantic/monty/releases)
- [Commits](https://github.com/pydantic/monty/compare/v0.0.17...v0.0.18)

---
updated-dependencies:
- dependency-name: pydantic-monty
  dependency-version: 0.0.18
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-18 10:05:13 +00:00
westey 699916d639 Python: Add WebSearchDisplayObserver to harness console (#6572)
* 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>
2026-06-18 09:42:30 +00:00
Roger Barreto 1ba5cd3f44 .NET: Scope argument-based standing approvals correctly in ToolApprovalAgent (#6486) (#6487)
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>
2026-06-18 09:13:24 +00:00
Roger Barreto 1519e50f2f Harden archive extraction guard so path containment is statically recognized (#6564) (#6565)
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
2026-06-18 09:12:00 +00:00
Evan Mattson b55992bb67 Bump Python package versions for 1.9.0 release (#6583)
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.
2026-06-18 18:01:17 +09:00
Evan Mattson e8cec71ed8 Use issue type for triage workflow (#6577)
* Use issue type for triage workflow

* Disable blank issue reports

* Revert "Disable blank issue reports"

This reverts commit 222c8444a7b3b5768e01b9d562195a27d1a29f1a.
2026-06-18 14:48:00 +09:00
Eduard van Valkenburg d7e63d7d0e Fix Foundry aiohttp dependency (#6567)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-18 02:03:16 +00:00
Changjian Wang f59d5c67d8 Python: Adopt azure-ai-contentunderstanding to_llm_input in CU context provider (#5796)
* 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>
2026-06-18 01:57:41 +00:00
Shyju Krishnankutty 26a0a7e8be .NET: (Durable): bind MCP threadId to the current agent and guard cross-agent session dispatch (#6531)
* 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>
2026-06-17 21:56:53 +00:00
Yufeng He 616315339e .NET: fix fan-in checkpoint edge state (#6491) 2026-06-17 20:17:53 +00:00
Eduard van Valkenburg fcc5576b04 .NET: feat(dotnet): Add LocalCodeAct package for local Python execution (#6105)
* 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>
2026-06-17 15:30:52 +00:00
westey 6cc7ddb73e .NET: Integrate LoopAgent into HarnessAgent with TodoCompletionLoopEvaluator (#6544)
* Add LoopAgent to Harness with TodoEvaluator sample

* Address PR comments

* Fix build error
2026-06-17 19:03:16 +01:00
westey 39f4b5ec72 Align function tool names for BackgroundAgent and FileMemory between python and .net (#6550) 2026-06-17 17:14:12 +01:00
dependabot[bot] 02eb9435bf Bump litellm from 1.83.14 to 1.84.0 in /python (#6559)
Bumps [litellm](https://github.com/BerriAI/litellm) from 1.83.14 to 1.84.0.
- [Release notes](https://github.com/BerriAI/litellm/releases)
- [Commits](https://github.com/BerriAI/litellm/commits/v1.84.0)

---
updated-dependencies:
- dependency-name: litellm
  dependency-version: 1.84.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-17 12:56:50 +00:00
Eduard van Valkenburg 4ff952e100 Python: Capture context provider instructions in agent telemetry (#6515)
* Fix agent instructions telemetry

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Simplify agent instructions telemetry guard

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix observability mypy cast

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-17 08:28:16 +00:00
westey 7bf2d2a6d0 Python: Fix harness console rendering one streamed tool call many times (#6549)
* 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>
2026-06-17 05:15:51 +00:00
Peter Ibekwe 1a280ae7c5 Bugfix for Declarative Workflow (#6530)
* Declarative workflow bugfix

* Fix PR comments
2026-06-16 17:55:51 +00:00
Ben Thomas 4d492614a9 .NET samples: structural alignment changes (#6485)
* Rescope dotnet provider samples cleanup

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix provider samples README link

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-16 17:55:38 +00:00
Eduard van Valkenburg 8e10c0399a Python: Remove unsupported as_agent function_invocation_configuration (#6520)
* 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>
2026-06-16 17:12:04 +00:00
Tao Chen bce2757477 Foundry hosted agent responses emit failed events (#6502) 2026-06-16 16:25:06 +00:00
Eduard van Valkenburg 106e065774 .NET: Rebuild Hyperlight sandbox after tool registry updates (#6523)
* .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>
2026-06-16 15:31:20 +00:00
westey 0db9305625 Python: Integrate tool approval into the harness (#6522)
* Integrate auto tool-approval feature into harness

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Rename disable_tool_approval to disable_tool_auto_approval

Addresses PR review feedback that the parameter name was unclear. The flag
toggles the auto/standing tool-approval middleware.

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>
2026-06-16 15:21:31 +00:00
Eduard van Valkenburg 571cae426c Python: Fix Azure AI Search citation URLs (#6453)
* Fix Azure AI Search citation URLs

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Enrich MCP search citation metadata

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix Azure AI Search citation enrichment follow-ups

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Address PR #6453 review comments

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* updated filter for paths

* also updated python paths

* reverted dotnet-format change

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: westey <164392973+westey-m@users.noreply.github.com>
2026-06-16 16:13:30 +01:00
SergeyMenshykh 9fb16e034f .NET: Allow custom argument marshaling for skill scripts (#6498)
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>
2026-06-16 14:27:13 +01:00
westey e07cfba0f3 Disable Anthropic tests by not providing environment vars until 404 failure is resolved (#6539) 2026-06-16 14:23:50 +01:00
Roger Barreto 40a2dd5cd0 .NET: Restore ambient client-header scope between non-streaming ClientHeadersAgent runs (#6517)
* Restore ambient client-header scope between non-streaming runs (#6516)

Make ClientHeadersAgent.RunCoreAsync async + await so the per-run
ClientHeadersScope is unwound on return, matching the streaming path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Assert per-run on wire instead of brittle exact request count

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-15 13:21:53 +00:00
Eduard van Valkenburg 7e9c043c4c Python: Improve PR template and breaking-change label automation (#6473)
* 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>
2026-06-15 10:55:23 +00:00
Eduard van Valkenburg d7e8d2206d Python: Fix Python OTel usage detail attributes (#6493)
* fix python otel usage detail attributes

Map cached/read/reasoning usage detail fields to standard OTel GenAI attributes while preserving provider-specific legacy keys.

Add focused coverage for direct response spans, aggregated agent spans, and provider usage parsing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* address usage detail review feedback

Omit missing OpenAI Responses usage detail counts while preserving zero-valued counts.

Record zero-valued token usage in OTel histograms and add regression coverage.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-15 07:10:14 +00:00
westey d7027fc1f9 Python: [BREAKING] Align FileAccess tools with .NET — directory discovery and recursive search (#6476)
* Align FileAccess tools with .Net; add directory discovery and recursive search

* Fix choices field description: spacing, line length, grammar

Addresses PR review: separate concatenated string literals with proper
spacing/newlines, wrap lines under the 120-char Ruff limit, and fix
"doesn't" -> "don't".

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Address PR comments

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-15 06:55:21 +00:00
Giles Odigwe df0bd4da82 Python: Fix ollama_chat_client.py sample: pass tools via options dict (#6480)
* 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>
2026-06-15 06:52:14 +00:00
Peter Ibekwe ed4ff188fc Python: [Breaking] Additional bug fix for declarative workflows (#6489)
* Fix declarative object parsing bug

* Remove unnecessary comment

* Address PR comments

* Address PR comments.

* Fix CI failures.

* declarative action approval bugfix

* Address PR comments

* Inlined single use variables.
2026-06-12 16:58:35 +00:00
Theo van Kraay 0f483fa968 Set ApplicationName on CosmosClientOptions for UserAgent telemetry (#6481)
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>
2026-06-12 16:32:07 +00:00
westey 5e830f4dc9 .NET: Only use the output from the last message for structured output (#6499)
* Only use the output from the last message for structured output

* Address PR comments

* Address PR comment

* Address PR comments
2026-06-12 15:47:32 +00:00
Eduard van Valkenburg 1acd242550 Python: Add AgentLoopMiddleware for re-running agents in a loop (#6174)
* 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>
2026-06-12 14:35:54 +00:00
westey 3f77c555cf .NET: [BREAKING] Align FileAccess tools with Python; add directory discovery and recursive search (#6474)
* Align FileAccess with python and improve functionality

* Addressing PR comments
2026-06-12 14:28:26 +00:00
westey cd512da731 .NET: Updating MessagePack to latest version (#6497)
* Updating MessagePack to latest version

* Remove MessagePack from package directly, since CentralPackageTransitivePinningEnabled is true
2026-06-12 13:45:14 +00:00
Evan Mattson 76b2b1bf39 Python: Add opt-in AG-UI thread snapshot persistence and hydration (#6471)
* 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>
2026-06-12 08:29:38 +00:00
Yufeng He 4c1b9efa8c .NET: fix: filter filesystem checkpoint index by session (#6132)
* fix: filter filesystem checkpoint index by session

* fix: filter checkpoint index by parent

* .NET: preserve legacy checkpoint index discovery
2026-06-11 22:35:57 +00:00
Peter Ibekwe e7937947d9 Python: Bug fix for declarative workflows (#6468)
* Fix declarative object parsing bug

* Remove unnecessary comment

* Address PR comments

* Address PR comments.

* Fix CI failures.
2026-06-11 22:34:15 +00:00
westey 3d5421edc1 Python: Integrate shell tool into harness agent (#6451)
* 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>
2026-06-11 20:51:59 +00:00
Giles Odigwe 8b0405de1b .NET: Fix CopySessionConfig() and CopyResumeSessionConfig() to preserve SessionConfig.Streaming value (#6463)
* 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>
2026-06-11 18:18:05 +00:00
Eduard van Valkenburg df29af611c Python: Add tool approval middleware (#6414)
* Add Python tool approval middleware

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix tool approval restored state handling

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Gate hidden approvals on explicit approval responses

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Handle string inputs in approval replay scan

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Cover argument-scoped approval rules

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Refine tool approval state and budgets

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix tool approval PR CI failures

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Revert DevUI Aspire README link change

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-11 17:35:44 +00:00
Ben Thomas c79f886dc3 .NET: Align Foundry sample environment variables and credentials. (#6422)
* dotnet: refresh Foundry sample guidance

Carry forward the still-relevant sample guidance and Foundry-specific documentation fixes from the old stacked sample migration work, adapted to the current repo layout and policy.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* dotnet: rename Foundry sample env vars

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* dotnet: remove persistent provider sample

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* dotnet: drop SAMPLE_GUIDELINES.md from this PR

Defer the guidelines doc and its cross-link to a follow-on PR to avoid broken-link failures in CI.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* dotnet: add DefaultAzureCredential warning to remaining samples

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* dotnet: address PR review feedback

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-11 17:26:00 +00:00
Giles Odigwe c9e2a490be Fix AzureFunctions integration tests — set FUNCTIONS_WORKER_RUNTIME (#6425)
Azure Functions Core Tools v4 can no longer auto-detect the worker
runtime when local.settings.json is absent. Add the required
FUNCTIONS_WORKER_RUNTIME=dotnet-isolated environment variable to
both StartFunctionApp helpers and re-enable the skipped tests.

Fixes: https://github.com/microsoft/agent-framework/issues/6402

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-11 15:09:55 +00:00
westey 12ce099165 .NET: Add LoopAgent capability for Harnesses (#6384)
* 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
2026-06-11 15:00:01 +00:00
Matthias Howell 8e1998ddcb .NET: Adds Valkey to chat message history - issue 5445 (#5542)
* 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>
2026-06-11 13:18:00 +00:00
chetantoshniwal 4149f24791 Python: [Generated by SRE Agent] Fix MCP allowed_tools empty list handling (#6296)
* 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>
2026-06-11 06:46:46 +00:00
Peter Ibekwe 3753d938f5 .NET: Bug fixes for declarative workflows (#6427)
* declarative workflow approval flow fix

* Update mcp handler cache construction

* fix method argument.

* Update dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/ObjectModel/InvokeFunctionToolExecutor.cs

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Fix identation

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-06-10 18:08:32 +00:00
Tamir Dresher 60cc5ee4e4 .NET: Make GitHub.Copilot.SDK build targets reach transitive consumers (#6455) (#6457)
* .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>
2026-06-10 18:07:18 +00:00
Roger Barreto dd29f9aa65 .NET: Hosted Agent Sample - Toolbox with various Auth (#5777) (#6018)
* .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>
2026-06-10 16:49:48 +00:00
Giles Odigwe a5f4e0078e .NET: Fix .NET Copilot integration tests for SDK v1.0.0 (#6424)
* 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>
2026-06-10 15:41:48 +00:00
westey 3c0c12cd46 .NET: Update release version for 2026-06-10 release and switch GH.CP Agent to RC (#6454)
* Update release version for 2026-06-10 release

* Switch GitHub.Copilot to RC
2026-06-10 15:26:23 +00:00
westey 8dde9ef627 Python: HarnessAgent: Disable compaction when max tokens not provided (#6410)
* HarnessAgent: Disable compaction when max tokens not provided

* Fix regression.

* Address PR comments

* Require max_output_tokens to be positive

Reject max_output_tokens=0 (must be positive), mirroring
max_context_window_tokens. Addresses PR review feedback.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-10 13:57:23 +00:00
Giles Odigwe 93cbf6b3f0 Python: Parse MCP CallToolResult.structuredContent field to prevent tool results returning None (#6421)
* 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>
2026-06-10 12:51:09 +00:00
Eduard van Valkenburg 9a56bc9f16 Python: [BREAKING] Add sampling guardrails to MCP tools (#6413)
* 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>
2026-06-10 10:17:36 +00:00
Copilot cea83bd8d5 .NET: Bump Microsoft.Extensions.AI packages to 10.6.0, align transitive dependency floor, and update Merge Gatekeeper ignores (#6148)
* Bump Microsoft.Extensions.AI packages to 10.6.0

* Align transitive package versions for Microsoft.Extensions.AI 10.6.0

* Ignore external review check in Merge Gatekeeper

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com>
2026-06-10 10:02:22 +00:00
Eduard van Valkenburg 7ae73a68d6 Remove broken Atomic Agents docs link (#6442)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-10 09:07:51 +00:00
Copilot 3daed114ee Python: bump package versions for 1.8.1 release (#6420)
* Python: bump package versions for 1.8.1 release

* Python: bump agent-framework-foundry-hosting for 1.8.1 release

* Python: bump ag-ui and azurefunctions for 1.8.1 release

* Remove incorrect agent-framework-foundry changelog entry for #6259

* Add [1.8.1] changelog compare link and update [Unreleased] base

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-06-09 21:27:42 +00:00
2891 changed files with 192546 additions and 102254 deletions
+6 -2
View File
@@ -2,6 +2,7 @@ dirs:
- .
excludedFiles:
- ./python/CHANGELOG.md
- "**/SKILL.md"
ignorePatterns:
- pattern: "/github/"
- pattern: "./actions"
@@ -20,10 +21,13 @@ ignorePatterns:
- pattern: "https://your-resource.openai.azure.com/"
- pattern: "http://host.docker.internal"
- pattern: "https://openai.github.io/openai-agents-js/openai/agents/classes/"
- pattern: "https:\/\/dotnet.microsoft.com\/download"
# dotnet.microsoft.com bot-blocks CI link checkers with intermittent 403s on any
# path (including localized variants like /en-us/download/...), so ignore the
# whole domain rather than just /download.
- pattern: "https:\/\/dotnet.microsoft.com"
- pattern: "https://github.com/Rel1cx/eslint-react"
# excludedDirs:
# Folders which include links to localhost, since it's not ignored with regular expressions
# Folders which include links to localhost, since it's not ignored with regular expressions
baseUrl: https://github.com/microsoft/agent-framework/
aliveStatusCodes:
- 200
+127 -4
View File
@@ -1,7 +1,130 @@
# Code ownership assignments
# https://docs.github.com/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
#
# Policy: a PR needs one approval, and it must come from a code owner of the changed
# files ("Require review from Code Owners" + "Required approvals: 1" on `main`).
# A PR touching several CODEOWNERS patterns will request review from the applicable
# code owners, but an approval from any applicable code owner is sufficient to satisfy
# GitHub's required-code-owner review.
#
# Order matters: the LAST matching pattern wins, so a module rule fully replaces the
# catch-all rather than adding to it. @chetantoshniwal is included on every line as a
# repository-wide fallback owner. All owners on a line have equal approval authority.
#
# CONVENTION: owners are written in the order
# @chetantoshniwal <owner A> <owner B> [...]
# @chetantoshniwal is at the beginning for aesthetics. The owners share equal approval
# power and responsibility.
#
# RULE: every path must list at least two owners besides @chetantoshniwal. An author
# cannot approve their own PR, so a path with a single module owner leaves only Chetan
# to review whenever that owner is the author, which defeats the point of naming a
# module owner.
#
# Samples: owned by all core developers of that language, not by the module a sample
# demonstrates. Any core Python developer can approve any Python sample, and any core
# .NET developer can approve any .NET sample. No dedicated rule is needed -- samples
# fall through to the /python and /dotnet rules, which already list those developers.
#
# Tests: same as samples. Tests that live inside a package (python/packages/<pkg>/tests)
# are covered by that package's rule instead, since they sit under its path.
# Default owners for everything not matched by a module rule below.
* @chetantoshniwal @westey-m
# Repository-level paths: every core Agent Framework developer is a code owner, so any
# one of them can approve. Be explicit now and we can use the AgentFramework team in the future.
/docs/ @chetantoshniwal @westey-m @eavanvalkenburg @giles17 @moonbox3 @TaoChenOSU @peibekwe @rogerbarreto @SergeyMenshykh
/*.md @chetantoshniwal @westey-m @eavanvalkenburg @giles17 @moonbox3 @TaoChenOSU @peibekwe @rogerbarreto @SergeyMenshykh
/LICENSE @chetantoshniwal @westey-m @eavanvalkenburg @giles17 @moonbox3 @TaoChenOSU @peibekwe @rogerbarreto @SergeyMenshykh
/.gitattributes @chetantoshniwal @westey-m @eavanvalkenburg @giles17 @moonbox3 @TaoChenOSU @peibekwe @rogerbarreto @SergeyMenshykh
/.gitignore @chetantoshniwal @westey-m @eavanvalkenburg @giles17 @moonbox3 @TaoChenOSU @peibekwe @rogerbarreto @SergeyMenshykh
/.github @chetantoshniwal @westey-m @eavanvalkenburg @giles17 @moonbox3 @TaoChenOSU @peibekwe @rogerbarreto @SergeyMenshykh
# Repository-level paths that require specific owners
/.devcontainer @chetantoshniwal @westey-m @rogerbarreto @SergeyMenshykh
/declarative-agents @chetantoshniwal @moonbox3 @peibekwe
# Core Python developers: @eavanvalkenburg @giles17 @moonbox3 @TaoChenOSU
/python @chetantoshniwal @eavanvalkenburg @giles17 @moonbox3 @TaoChenOSU
# Python packages
/python/packages/a2a/ @chetantoshniwal @giles17 @eavanvalkenburg
/python/packages/ag-ui/ @chetantoshniwal @moonbox3 @giles17
/python/packages/anthropic/ @chetantoshniwal @eavanvalkenburg @giles17
/python/packages/azure-ai-search/ @chetantoshniwal @eavanvalkenburg @giles17
/python/packages/azure-contentunderstanding/ @chetantoshniwal @giles17 @eavanvalkenburg
/python/packages/azure-cosmos/ @chetantoshniwal @eavanvalkenburg @giles17
/python/packages/azure-cosmos-memory/ @chetantoshniwal @eavanvalkenburg @giles17
/python/packages/bedrock/ @chetantoshniwal @eavanvalkenburg @giles17
/python/packages/chatkit/ @chetantoshniwal @moonbox3 @giles17
/python/packages/claude/ @chetantoshniwal @eavanvalkenburg @giles17
/python/packages/copilotstudio/ @chetantoshniwal @eavanvalkenburg @giles17
/python/packages/core/ @chetantoshniwal @eavanvalkenburg @moonbox3 @TaoChenOSU @giles17
/python/packages/core/agent_framework/_workflows/ @chetantoshniwal @moonbox3 @TaoChenOSU
/python/packages/core/agent_framework/_harness/ @chetantoshniwal @westey-m @eavanvalkenburg
/python/packages/declarative/ @chetantoshniwal @moonbox3 @peibekwe
/python/packages/devui/ @chetantoshniwal @eavanvalkenburg @moonbox3
/python/packages/foundry/ @chetantoshniwal @eavanvalkenburg @TaoChenOSU @moonbox3 @giles17
/python/packages/foundry_hosting/ @chetantoshniwal @TaoChenOSU @eavanvalkenburg
/python/packages/foundry_local/ @chetantoshniwal @eavanvalkenburg @giles17
/python/packages/gemini/ @chetantoshniwal @giles17 @eavanvalkenburg
/python/packages/github_copilot/ @chetantoshniwal @giles17 @eavanvalkenburg
/python/packages/hosting/ @chetantoshniwal @eavanvalkenburg @TaoChenOSU
/python/packages/hosting-a2a/ @chetantoshniwal @eavanvalkenburg @TaoChenOSU
/python/packages/hosting-mcp/ @chetantoshniwal @eavanvalkenburg @TaoChenOSU
/python/packages/hosting-responses/ @chetantoshniwal @eavanvalkenburg @TaoChenOSU
/python/packages/hosting-telegram/ @chetantoshniwal @eavanvalkenburg @TaoChenOSU
/python/packages/hyperlight/ @chetantoshniwal @eavanvalkenburg @giles17
/python/packages/lab/ @chetantoshniwal @eavanvalkenburg @giles17 @moonbox3 @TaoChenOSU
/python/packages/mem0/ @chetantoshniwal @eavanvalkenburg @giles17
/python/packages/mistral/ @chetantoshniwal @eavanvalkenburg @giles17
/python/packages/monty/ @chetantoshniwal @eavanvalkenburg @giles17
/python/packages/ollama/ @chetantoshniwal @eavanvalkenburg @giles17
/python/packages/openai/ @chetantoshniwal @eavanvalkenburg @moonbox3 @TaoChenOSU @giles17
/python/packages/orchestrations/ @chetantoshniwal @moonbox3 @TaoChenOSU
/python/packages/purview/ @chetantoshniwal @eavanvalkenburg @giles17
/python/packages/redis/ @chetantoshniwal @eavanvalkenburg @giles17
/python/packages/tools/ @chetantoshniwal @eavanvalkenburg @giles17
# Core .NET developers: @peibekwe @rogerbarreto @SergeyMenshykh @westey-m
/dotnet @chetantoshniwal @peibekwe @rogerbarreto @SergeyMenshykh @westey-m
# .NET projects
/dotnet/src/Aspire.Hosting.AgentFramework.DevUI/ @chetantoshniwal @peibekwe @rogerbarreto @SergeyMenshykh @westey-m
/dotnet/src/LegacySupport/ @chetantoshniwal @peibekwe @rogerbarreto @SergeyMenshykh @westey-m
/dotnet/src/Shared/ @chetantoshniwal @peibekwe @rogerbarreto @SergeyMenshykh @westey-m
/dotnet/src/Microsoft.Agents.AI/ @chetantoshniwal @rogerbarreto @SergeyMenshykh @westey-m
/dotnet/src/Microsoft.Agents.AI.A2A/ @chetantoshniwal @rogerbarreto @SergeyMenshykh @westey-m
/dotnet/src/Microsoft.Agents.AI.Abstractions/ @chetantoshniwal @rogerbarreto @SergeyMenshykh @westey-m
/dotnet/src/Microsoft.Agents.AI.AGUI/ @chetantoshniwal @peibekwe @rogerbarreto @SergeyMenshykh @westey-m
/dotnet/src/Microsoft.Agents.AI.Anthropic/ @chetantoshniwal @rogerbarreto @westey-m
/dotnet/src/Microsoft.Agents.AI.AzureAI.Persistent/ @chetantoshniwal @rogerbarreto @westey-m
/dotnet/src/Microsoft.Agents.AI.CopilotStudio/ @chetantoshniwal @rogerbarreto @westey-m
/dotnet/src/Microsoft.Agents.AI.CosmosNoSql/ @chetantoshniwal @rogerbarreto @westey-m
/dotnet/src/Microsoft.Agents.AI.Declarative/ @chetantoshniwal @peibekwe @westey-m
/dotnet/src/Microsoft.Agents.AI.DevUI/ @chetantoshniwal @peibekwe @rogerbarreto @SergeyMenshykh @westey-m
/dotnet/src/Microsoft.Agents.AI.Foundry/ @chetantoshniwal @rogerbarreto @westey-m
/dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/ @chetantoshniwal @rogerbarreto @westey-m
/dotnet/src/Microsoft.Agents.AI.GitHub.Copilot/ @chetantoshniwal @rogerbarreto @westey-m
/dotnet/src/Microsoft.Agents.AI.Harness/ @chetantoshniwal @westey-m @SergeyMenshykh
/dotnet/src/Microsoft.Agents.AI.Hosting/ @chetantoshniwal @rogerbarreto @SergeyMenshykh @westey-m
/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/ @chetantoshniwal @rogerbarreto @SergeyMenshykh @westey-m
/dotnet/src/Microsoft.Agents.AI.Hosting.A2A.AspNetCore/ @chetantoshniwal @rogerbarreto @SergeyMenshykh @westey-m
/dotnet/src/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore/ @chetantoshniwal @rogerbarreto @SergeyMenshykh @westey-m
/dotnet/src/Microsoft.Agents.AI.Hosting.AspNetCore/ @chetantoshniwal @rogerbarreto @SergeyMenshykh @westey-m
/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/ @chetantoshniwal @rogerbarreto @SergeyMenshykh @westey-m
/dotnet/src/Microsoft.Agents.AI.Hyperlight/ @chetantoshniwal @westey-m @SergeyMenshykh
/dotnet/src/Microsoft.Agents.AI.LocalCodeAct/ @chetantoshniwal @westey-m @SergeyMenshykh
/dotnet/src/Microsoft.Agents.AI.Mcp/ @chetantoshniwal @westey-m @peibekwe
/dotnet/src/Microsoft.Agents.AI.Mem0/ @chetantoshniwal @westey-m @SergeyMenshykh
/dotnet/src/Microsoft.Agents.AI.OpenAI/ @chetantoshniwal @westey-m @rogerbarreto
/dotnet/src/Microsoft.Agents.AI.Purview/ @chetantoshniwal @westey-m @SergeyMenshykh
/dotnet/src/Microsoft.Agents.AI.Tools.Shell/ @chetantoshniwal @westey-m @SergeyMenshykh
/dotnet/src/Microsoft.Agents.AI.Valkey/ @chetantoshniwal @westey-m @SergeyMenshykh
/dotnet/src/Microsoft.Agents.AI.Workflows/ @chetantoshniwal @peibekwe @rogerbarreto
/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/ @chetantoshniwal @peibekwe @rogerbarreto
/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative.Foundry/ @chetantoshniwal @peibekwe @rogerbarreto
/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative.Mcp/ @chetantoshniwal @peibekwe @rogerbarreto
/dotnet/src/Microsoft.Agents.AI.Workflows.Generators/ @chetantoshniwal @peibekwe @rogerbarreto
python/packages/azurefunctions/ @microsoft/agentframework-durabletask-developers
python/packages/durabletask/ @microsoft/agentframework-durabletask-developers
python/samples/getting_started/azure_functions/ @microsoft/agentframework-durabletask-developers
python/samples/getting_started/durabletask/ @microsoft/agentframework-durabletask-developers
+1 -1
View File
@@ -1,7 +1,7 @@
name: .NET Bug Report
description: Report a bug in the Agent Framework .NET SDK
title: ".NET: [Bug]: "
labels: ["bug", ".NET"]
labels: [".NET"]
type: bug
body:
- type: textarea
+1 -1
View File
@@ -1,7 +1,7 @@
name: Python Bug Report
description: Report a bug in the Agent Framework Python SDK
title: "Python: [Bug]: "
labels: ["bug", "Python"]
labels: ["Python"]
type: bug
body:
- type: textarea
@@ -1,48 +0,0 @@
name: Azure Functions Integration Test Setup
description: Prepare local emulators and tools for Azure Functions integration tests
runs:
using: "composite"
steps:
- name: Start Durable Task Scheduler Emulator
shell: bash
run: |
if [ "$(docker ps -aq -f name=dts-emulator)" ]; then
echo "Stopping and removing existing Durable Task Scheduler Emulator"
docker rm -f dts-emulator
fi
echo "Starting Durable Task Scheduler Emulator"
docker run -d --name dts-emulator -p 8080:8080 -p 8082:8082 -e DTS_USE_DYNAMIC_TASK_HUBS=true mcr.microsoft.com/dts/dts-emulator:latest
echo "Waiting for Durable Task Scheduler Emulator to be ready"
timeout 30 bash -c 'until curl --silent http://localhost:8080/healthz; do sleep 1; done'
echo "Durable Task Scheduler Emulator is ready"
- name: Start Azurite (Azure Storage emulator)
shell: bash
run: |
if [ "$(docker ps -aq -f name=azurite)" ]; then
echo "Stopping and removing existing Azurite (Azure Storage emulator)"
docker rm -f azurite
fi
echo "Starting Azurite (Azure Storage emulator)"
docker run -d --name azurite -p 10000:10000 -p 10001:10001 -p 10002:10002 mcr.microsoft.com/azure-storage/azurite
echo "Waiting for Azurite (Azure Storage emulator) to be ready"
timeout 30 bash -c 'until curl --silent http://localhost:10000/devstoreaccount1; do sleep 1; done'
echo "Azurite (Azure Storage emulator) is ready"
- name: Start Redis
shell: bash
run: |
if [ "$(docker ps -aq -f name=redis)" ]; then
echo "Stopping and removing existing Redis"
docker rm -f redis
fi
echo "Starting Redis"
docker run -d --name redis -p 6379:6379 redis:latest
echo "Waiting for Redis to be ready"
timeout 30 bash -c 'until docker exec redis redis-cli ping | grep -q PONG; do sleep 1; done'
echo "Redis is ready"
- name: Install Azure Functions Core Tools
shell: bash
run: |
echo "Installing Azure Functions Core Tools"
npm install -g azure-functions-core-tools@4 --unsafe-perm true
func --version
+112
View File
@@ -0,0 +1,112 @@
name: Get GitHub automation token
description: Creates a GitHub App installation token with a temporary PAT fallback
inputs:
mode:
description: Authentication mode (app, app-with-fallback, or pat)
required: false
default: app-with-fallback
azure-client-id:
description: Client ID of the Azure workload identity
required: false
azure-tenant-id:
description: Azure tenant ID
required: false
azure-subscription-id:
description: Azure subscription containing the Key Vault
required: false
key-vault-name:
description: Azure Key Vault name
required: false
key-name:
description: Key Vault key used to sign the GitHub App JWT
required: false
github-app-client-id:
description: GitHub App client ID
required: false
github-app-installation-id:
description: GitHub App installation ID
required: false
repository:
description: Repository to include in the installation token
required: false
fallback-token:
description: PAT used temporarily when app authentication is unavailable
required: false
outputs:
token:
description: GitHub App installation token or fallback PAT
value: ${{ steps.select-token.outputs.token }}
source:
description: Selected authentication source
value: ${{ steps.select-token.outputs.source }}
runs:
using: composite
steps:
- name: Validate authentication mode
shell: bash
env:
AUTH_MODE: ${{ inputs.mode || 'app-with-fallback' }}
run: |
if [[ "$AUTH_MODE" != "app" && "$AUTH_MODE" != "app-with-fallback" && "$AUTH_MODE" != "pat" ]]; then
echo "::error::Unsupported GitHub authentication mode."
exit 1
fi
- name: Sign in to Azure
id: azure-login
if: ${{ (inputs.mode || 'app-with-fallback') != 'pat' }}
continue-on-error: true
uses: azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2
with:
client-id: ${{ inputs.azure-client-id }}
tenant-id: ${{ inputs.azure-tenant-id }}
subscription-id: ${{ inputs.azure-subscription-id }}
- name: Create GitHub App installation token
id: app-token
if: ${{ (inputs.mode || 'app-with-fallback') != 'pat' && steps.azure-login.outcome == 'success' }}
continue-on-error: true
shell: bash
env:
AZURE_SUBSCRIPTION_ID: ${{ inputs.azure-subscription-id }}
KEY_VAULT_NAME: ${{ inputs.key-vault-name }}
KEY_NAME: ${{ inputs.key-name }}
GITHUB_APP_CLIENT_ID: ${{ inputs.github-app-client-id }}
GITHUB_APP_INSTALLATION_ID: ${{ inputs.github-app-installation-id }}
TARGET_REPOSITORY: ${{ inputs.repository }}
run: |
token="$(node "$GITHUB_ACTION_PATH/create-token.js")"
echo "::add-mask::$token"
echo "token=$token" >> "$GITHUB_OUTPUT"
- name: Select authentication token
id: select-token
shell: bash
env:
AUTH_MODE: ${{ inputs.mode || 'app-with-fallback' }}
APP_TOKEN: ${{ steps.app-token.outputs.token }}
FALLBACK_TOKEN: ${{ inputs.fallback-token }}
run: |
if [[ "$AUTH_MODE" != "pat" && -n "$APP_TOKEN" ]]; then
token="$APP_TOKEN"
source="app"
echo "::notice::GitHub authentication source: app"
elif [[ "$AUTH_MODE" == "app-with-fallback" && -n "$FALLBACK_TOKEN" ]]; then
token="$FALLBACK_TOKEN"
source="pat-fallback"
echo "::warning::GitHub authentication source: PAT fallback"
elif [[ "$AUTH_MODE" == "pat" && -n "$FALLBACK_TOKEN" ]]; then
token="$FALLBACK_TOKEN"
source="pat-forced"
echo "::warning::GitHub authentication source: PAT (forced rollout mode)"
else
echo "::error::GitHub App authentication is unavailable and no fallback PAT was provided."
exit 1
fi
echo "::add-mask::$token"
echo "token=$token" >> "$GITHUB_OUTPUT"
echo "source=$source" >> "$GITHUB_OUTPUT"
@@ -0,0 +1,133 @@
// Copyright (c) Microsoft. All rights reserved.
const crypto = require('node:crypto');
const { execFileSync } = require('node:child_process');
function base64Url(value) {
return Buffer.from(value).toString('base64url');
}
function base64ToBase64Url(value) {
return Buffer.from(value, 'base64').toString('base64url');
}
function createJwtSigningInput(clientId, nowSeconds) {
const header = base64Url(JSON.stringify({ alg: 'RS256', typ: 'JWT' }));
const payload = base64Url(JSON.stringify({
iat: nowSeconds - 60,
exp: nowSeconds + 540,
iss: clientId,
}));
return `${header}.${payload}`;
}
function signJwt(signingInput, config, execute = execFileSync) {
const digest = crypto.createHash('sha256').update(signingInput).digest('base64');
const signature = execute(
'az',
[
'keyvault', 'key', 'sign',
'--subscription', config.azureSubscriptionId,
'--vault-name', config.keyVaultName,
'--name', config.keyName,
'--algorithm', 'RS256',
'--digest', digest,
'--query', 'signature',
'--output', 'tsv',
'--only-show-errors',
],
{ encoding: 'utf8' },
).trim();
if (!signature) {
throw new Error('Key Vault returned an empty signature.');
}
return `${signingInput}.${base64ToBase64Url(signature)}`;
}
async function createInstallationToken(config, dependencies = {}) {
const execute = dependencies.execute ?? execFileSync;
const request = dependencies.fetch ?? fetch;
const nowSeconds = dependencies.nowSeconds ?? Math.floor(Date.now() / 1000);
const repositoryParts = config.targetRepository.split('/');
if (repositoryParts.length !== 2 || repositoryParts.some((part) => part.length === 0)) {
throw new Error('TARGET_REPOSITORY must use the owner/repository format.');
}
const [, repository] = repositoryParts;
const signingInput = createJwtSigningInput(config.githubAppClientId, nowSeconds);
const jwt = signJwt(signingInput, config, execute);
const response = await request(
`https://api.github.com/app/installations/${config.githubAppInstallationId}/access_tokens`,
{
method: 'POST',
headers: {
Accept: 'application/vnd.github+json',
Authorization: `Bearer ${jwt}`,
'X-GitHub-Api-Version': '2022-11-28',
},
body: JSON.stringify({
repositories: [repository],
permissions: {
contents: 'read',
issues: 'write',
members: 'read',
pull_requests: 'write',
},
}),
},
);
if (!response.ok) {
throw new Error(`GitHub installation token request failed with HTTP ${response.status}.`);
}
const result = await response.json();
if (typeof result.token !== 'string' || result.token.length === 0) {
throw new Error('GitHub returned an empty installation token.');
}
return result.token;
}
function readConfig(environment) {
const config = {
azureSubscriptionId: environment.AZURE_SUBSCRIPTION_ID,
keyVaultName: environment.KEY_VAULT_NAME,
keyName: environment.KEY_NAME,
githubAppClientId: environment.GITHUB_APP_CLIENT_ID,
githubAppInstallationId: environment.GITHUB_APP_INSTALLATION_ID,
targetRepository: environment.TARGET_REPOSITORY,
};
if (Object.values(config).some((value) => !value)) {
throw new Error('Required GitHub App authentication configuration is missing.');
}
return config;
}
async function main() {
try {
const token = await createInstallationToken(readConfig(process.env));
process.stdout.write(token);
} catch {
console.error('GitHub App token generation failed.');
process.exitCode = 1;
}
}
if (require.main === module) {
void main();
}
module.exports = {
base64ToBase64Url,
createInstallationToken,
createJwtSigningInput,
readConfig,
signJwt,
};
+2 -2
View File
@@ -17,7 +17,7 @@ runs:
using: "composite"
steps:
- name: Set up uv
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
version-file: "python/pyproject.toml"
enable-cache: true
@@ -46,4 +46,4 @@ runs:
- name: Install the project
shell: bash
run: |
cd python && uv sync --all-packages --all-extras --dev --prerelease=if-necessary-or-explicit
cd python && uv sync --all-packages --all-extras --all-groups --prerelease=if-necessary-or-explicit
@@ -0,0 +1,19 @@
name: Save Sample Playbooks
description: >
Save the cached sample-validation playbooks. Split out from
sample-validation-setup (which only restores) so the save runs even when the
validation step fails. Combining restore+save via actions/cache would skip the
save on a failing job (post-if: success()), so freshly authored playbooks for
samples that failed validation would never persist. Invoke this with
'if: not-cancelled' after the validation step in each job.
runs:
using: "composite"
steps:
- name: Save sample playbooks cache
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
# Must match the restore path/key in sample-validation-setup/action.yml and the
# sample_validation --playbooks-dir default (samples/sample_validation/playbooks).
path: python/samples/sample_validation/playbooks/
key: sample-playbooks-${{ github.job }}-${{ github.run_id }}
@@ -36,15 +36,31 @@ runs:
shell: bash
run: copilot --version && copilot -p "What can you do in one sentence?"
- name: Set up python and install the project
uses: ./.github/actions/python-setup
with:
python-version: ${{ inputs.python-version }}
os: ${{ inputs.os }}
- name: Restore sample playbooks
# Restore-only. The matching save is a separate step in each job that runs with
# `if: ${{ !cancelled() }}` (see .github/actions/sample-validation-save-playbooks).
# A combined actions/cache would skip its post-job save on a failing job
# (post-if: success()), so playbooks authored for samples that failed validation
# would never persist. Keyed per job so each validate-* job keeps its own playbooks.
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
# Must match the sample_validation --playbooks-dir default, which resolves to
# samples/sample_validation/playbooks (see python/scripts/sample_validation/__main__.py).
# If a job overrides --playbooks-dir, update this path to match.
path: python/samples/sample_validation/playbooks/
key: sample-playbooks-${{ github.job }}-${{ github.run_id }}
restore-keys: |
sample-playbooks-${{ github.job }}-
- name: Azure CLI Login
uses: azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2
with:
client-id: ${{ inputs.azure-client-id }}
tenant-id: ${{ inputs.azure-tenant-id }}
subscription-id: ${{ inputs.azure-subscription-id }}
- name: Set up python and install the project
uses: ./.github/actions/python-setup
with:
python-version: ${{ inputs.python-version }}
os: ${{ inputs.os }}
+5 -3
View File
@@ -24,12 +24,14 @@ updates:
- ".NET"
- "dependencies"
# Maintain dependencies for python
# Maintain dependencies for python.
# TODO: Remove these Python Dependabot entries after we have confidence in the
# Python dependency-maintenance workflow.
- package-ecosystem: "pip"
directory: "python/"
schedule:
interval: "weekly"
day: "monday"
day: "thursday"
labels:
- "python"
- "dependencies"
@@ -37,7 +39,7 @@ updates:
directory: "python/"
schedule:
interval: "weekly"
day: "monday"
day: "thursday"
labels:
- "python"
- "dependencies"
@@ -1,17 +0,0 @@
---
applyTo: "dotnet/src/Microsoft.Agents.AI.DurableTask/**,dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/**"
---
# Durable Task area code instructions
The following guidelines apply to pull requests that modify files under
`dotnet/src/Microsoft.Agents.AI.DurableTask/**` or
`dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/**`:
## CHANGELOG.md
- Each pull request that modifies code should add just one bulleted entry to the `CHANGELOG.md` file containing a change title (usually the PR title) and a link to the PR itself.
- New PRs should be added to the top of the `CHANGELOG.md` file under a "## [Unreleased]" heading.
- If the PR is the first since the last release, the existing "## [Unreleased]" heading should be replaced with a "## v[X.Y.Z]" heading and the PRs since the last release should be added to the new "## [Unreleased]" heading.
- The style of new `CHANGELOG.md` entries should match the style of the other entries in the file.
- If the PR introduces a breaking change, the changelog entry should be prefixed with "[BREAKING]".
+25 -5
View File
@@ -1,23 +1,43 @@
### Motivation and Context
### Motivation & Context
<!-- 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.
+25 -10
View File
@@ -1,25 +1,40 @@
// Copyright (c) Microsoft. All rights reserved.
/**
* Resolve the issue author and check their team membership.
* Resolve the issue or pull request author and check their team membership.
*
* @param {object} opts
* @param {object} opts.github - Octokit REST client from actions/github-script
* @param {object} opts.context - GitHub Actions context
* @param {object} opts.core - GitHub Actions core toolkit
* @param {string} opts.teamSlug - Team slug to check membership against
* @param {string|number} opts.issueNumber - Issue number to resolve author for
* @param {string|number} opts.issueNumber - Issue or pull request number to resolve author for
* @param {string} [opts.username] - Explicit user to check instead of the issue or pull request author
* @returns {Promise<{author: string|null, isTeamMember: boolean}>}
*/
async function checkTeamMembership({ github, context, core, teamSlug, issueNumber }) {
let author = context.payload.issue?.user?.login;
async function checkTeamMembership({ github, context, core, teamSlug, issueNumber, username = '' }) {
let author = username.trim() || (
context.payload.issue?.user?.login ??
context.payload.pull_request?.user?.login
);
if (!author) {
const { data: issue } = await github.rest.issues.get({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: Number(issueNumber),
});
author = issue.user?.login;
const number = Number(issueNumber);
if (context.payload.pull_request) {
const { data: pr } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: number,
});
author = pr.user?.login;
} else {
const { data: issue } = await github.rest.issues.get({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: number,
});
author = issue.user?.login;
}
}
if (!author) {
+212
View File
@@ -0,0 +1,212 @@
# Copyright (c) Microsoft. All rights reserved.
"""Enforce Python package coverage according to package lifecycle."""
# ruff:file-ignore[print]
# ruff:file-ignore[implicit-namespace-package]
from __future__ import annotations
import re
import sys
import xml.etree.ElementTree as ET # ruff:ignore[suspicious-xml-etree-import]
from dataclasses import dataclass
from pathlib import Path
import tomllib
DEVELOPMENT_STATUS_PREFIX = "Development Status :: "
ENFORCED_DEVELOPMENT_STATUS = 4
EXEMPT_PACKAGES = {"devui", "lab"}
@dataclass(frozen=True)
class PackagePolicy:
"""Coverage policy derived from a package's project metadata."""
directory: str
distribution_name: str
development_status: int
development_status_label: str
enforced: bool
exempt: bool
@dataclass
class CoverageStats:
"""Line and branch coverage counters."""
lines_valid: int = 0
lines_covered: int = 0
branches_valid: int = 0
branches_covered: int = 0
@property
def line_coverage_percent(self) -> float:
"""Return line coverage as a percentage."""
if not self.lines_valid:
return 0
return self.lines_covered / self.lines_valid * 100
def normalize_coverage_path(path: str) -> str:
"""Normalize a coverage path for matching."""
return path.replace("\\", "/").lstrip("./")
def load_package_policies(packages_dir: Path) -> list[PackagePolicy]:
"""Load lifecycle-based coverage policies from package pyproject files."""
policies: list[PackagePolicy] = []
for pyproject_path in sorted(packages_dir.glob("*/pyproject.toml")):
with pyproject_path.open("rb") as pyproject_file:
pyproject = tomllib.load(pyproject_file)
project = pyproject.get("project", {})
distribution_name = str(project.get("name", "")).strip()
if not distribution_name:
raise ValueError(f"{pyproject_path}: project.name is required")
status_classifiers = [
classifier
for classifier in project.get("classifiers", [])
if classifier.startswith(DEVELOPMENT_STATUS_PREFIX)
]
if len(status_classifiers) != 1:
raise ValueError(
f"{pyproject_path}: expected exactly one Development Status classifier, found {len(status_classifiers)}"
)
match = re.fullmatch(r"Development Status :: (\d+) - (.+)", status_classifiers[0])
if match is None:
raise ValueError(f"{pyproject_path}: malformed Development Status classifier")
directory = pyproject_path.parent.name
development_status = int(match.group(1))
exempt = directory in EXEMPT_PACKAGES
policies.append(
PackagePolicy(
directory=directory,
distribution_name=distribution_name,
development_status=development_status,
development_status_label=match.group(2),
enforced=development_status >= ENFORCED_DEVELOPMENT_STATUS and not exempt,
exempt=exempt,
)
)
if not policies:
raise ValueError(f"No package pyproject.toml files found below {packages_dir}")
return policies
def parse_coverage_xml(xml_path: Path) -> tuple[dict[str, CoverageStats], float, float]:
"""Parse Cobertura XML and aggregate coverage by package directory."""
root = ET.parse(xml_path).getroot() # ruff:ignore[suspicious-xml-element-tree-usage] # Trusted CI-generated coverage report.
package_stats: dict[str, CoverageStats] = {}
for class_elem in root.findall(".//class"):
file_path = normalize_coverage_path(class_elem.get("filename", ""))
path_parts = file_path.split("/")
try:
packages_index = path_parts.index("packages")
package_directory = path_parts[packages_index + 1]
except (ValueError, IndexError):
continue
stats = package_stats.setdefault(package_directory, CoverageStats())
for line in class_elem.findall(".//line"):
stats.lines_valid += 1
if int(line.get("hits", 0)) > 0:
stats.lines_covered += 1
if line.get("branch") != "true":
continue
condition_coverage = line.get("condition-coverage", "")
match = re.search(r"\((\d+)/(\d+)\)", condition_coverage)
if match is not None:
stats.branches_covered += int(match.group(1))
stats.branches_valid += int(match.group(2))
return (
package_stats,
float(root.get("line-rate", 0)) * 100,
float(root.get("branch-rate", 0)) * 100,
)
def check_coverage(xml_path: Path, threshold: float, packages_dir: Path) -> bool:
"""Check all lifecycle-enforced packages against the coverage threshold."""
policies = load_package_policies(packages_dir)
package_stats, overall_line_coverage, overall_branch_coverage = parse_coverage_xml(xml_path)
print("\n" + "=" * 110)
print("PYTHON PACKAGE TEST COVERAGE")
print("=" * 110)
print(f"Overall Line Coverage: {overall_line_coverage:.1f}%")
print(f"Overall Branch Coverage: {overall_branch_coverage:.1f}%")
print(f"Enforced Threshold: {threshold:.1f}%")
print("-" * 110)
print(f"{'Package':<48} {'Stage':<20} {'Policy':<14} {'Lines':<12} {'Line Cov':<10}")
print("-" * 110)
failed_packages: list[str] = []
for policy in sorted(policies, key=lambda item: (not item.enforced, item.distribution_name)):
stats = package_stats.get(policy.directory)
if policy.exempt:
policy_label = "EXEMPT"
elif policy.enforced:
policy_label = "ENFORCED"
else:
policy_label = "REPORT ONLY"
if stats is None:
lines = "-"
coverage = "missing"
if policy.enforced:
failed_packages.append(f"{policy.distribution_name} (missing from coverage report)")
else:
lines = f"{stats.lines_covered}/{stats.lines_valid}"
coverage = f"{stats.line_coverage_percent:.1f}%"
if policy.enforced and stats.line_coverage_percent < threshold:
failed_packages.append(f"{policy.distribution_name} ({coverage})")
stage = f"{policy.development_status} - {policy.development_status_label}"
print(f"{policy.distribution_name:<48} {stage:<20} {policy_label:<14} {lines:<12} {coverage:<10}")
print("-" * 110)
if failed_packages:
print(f"\nFAILED: Enforced packages below {threshold:.1f}% or missing:")
for package in failed_packages:
print(f" - {package}")
return False
print(f"\nPASSED: All non-exempt Beta-or-higher packages meet {threshold:.1f}% line coverage.")
return True
def main() -> int:
"""Run the coverage policy check."""
if len(sys.argv) != 3:
print(f"Usage: {sys.argv[0]} <coverage-xml-path> <threshold>")
return 1
try:
threshold = float(sys.argv[2])
except ValueError:
print(f"Error: Invalid threshold value: {sys.argv[2]}")
return 1
repository_root = Path(__file__).resolve().parents[2]
try:
passed = check_coverage(
Path(sys.argv[1]),
threshold,
repository_root / "python" / "packages",
)
except (FileNotFoundError, ET.ParseError, ValueError) as error:
print(f"Error: {error}")
return 1
return 0 if passed else 1
if __name__ == "__main__":
sys.exit(main())
@@ -0,0 +1,170 @@
// Copyright (c) Microsoft. All rights reserved.
const DECISIVE_REVIEW_STATES = new Set(['APPROVED', 'CHANGES_REQUESTED', 'DISMISSED']);
const SHA_PATTERN = /^[0-9a-f]{40}$/;
const BRANCH_PATTERN = /^[a-zA-Z0-9_./-]+$/;
function assertValidSha(sha, description) {
if (!SHA_PATTERN.test(sha)) {
throw new Error(`GitHub returned an invalid ${description} SHA.`);
}
}
function hasWritePermission(permissionData) {
return permissionData.user?.permissions?.push === true
|| ['admin', 'maintain', 'write'].includes(permissionData.permission);
}
function latestDecisiveReviews(reviews) {
const latestByReviewer = new Map();
const sortedReviews = [...reviews].sort((left, right) => {
const submittedComparison = (left.submitted_at || '').localeCompare(right.submitted_at || '');
return submittedComparison || Number(left.id) - Number(right.id);
});
for (const review of sortedReviews) {
const state = review.state?.toUpperCase();
const reviewer = review.user?.login?.toLowerCase();
if (reviewer && DECISIVE_REVIEW_STATES.has(state)) {
latestByReviewer.set(reviewer, review);
}
}
return latestByReviewer;
}
async function resolvePullRequest({ github, context, core, prNumber, requiredApprovals }) {
if (!/^[0-9]+$/.test(prNumber)) {
throw new Error('Invalid PR number. Only numeric values are allowed.');
}
const pullNumber = Number(prNumber);
const { data: pullRequest } = await github.rest.pulls.get({
...context.repo,
pull_number: pullNumber,
});
if (pullRequest.state !== 'open') {
throw new Error(`PR #${pullNumber} is not open (state: ${pullRequest.state}).`);
}
const headSha = pullRequest.head.sha;
const baseSha = pullRequest.base.sha;
assertValidSha(headSha, 'PR head');
assertValidSha(baseSha, 'PR base');
const reviews = await github.paginate(github.rest.pulls.listReviews, {
...context.repo,
pull_number: pullNumber,
per_page: 100,
});
const latestReviews = latestDecisiveReviews(reviews);
const author = pullRequest.user?.login?.toLowerCase();
const approvalCandidates = [...latestReviews.entries()]
.filter(([, review]) => review.state.toUpperCase() === 'APPROVED')
.filter(([, review]) => review.commit_id === headSha)
.filter(([reviewer]) => reviewer !== author);
const approvedMaintainers = [];
for (const [reviewer] of approvalCandidates) {
const { data: permissionData } = await github.rest.repos.getCollaboratorPermissionLevel({
...context.repo,
username: reviewer,
});
if (hasWritePermission(permissionData)) {
approvedMaintainers.push(reviewer);
} else {
core.info(`Ignoring approval from ${reviewer}: reviewer does not have write permission.`);
}
}
if (approvedMaintainers.length < requiredApprovals) {
throw new Error(
`PR #${pullNumber} head ${headSha} requires ${requiredApprovals} approvals from unique `
+ `write-capable maintainers; found ${approvedMaintainers.length}.`,
);
}
core.info(
`PR #${pullNumber} head ${headSha} approved by: ${approvedMaintainers.join(', ')}.`,
);
return {
baseRef: baseSha,
checkoutRef: headSha,
description: `PR #${pullNumber}`,
};
}
async function resolveBranch({ github, context, core, branch }) {
if (!BRANCH_PATTERN.test(branch)) {
throw new Error(
'Invalid branch name. Only alphanumeric characters, hyphens, underscores, dots, and slashes '
+ 'are allowed.',
);
}
const [{ data: repository }, { data: targetBranch }] = await Promise.all([
github.rest.repos.get(context.repo),
github.rest.repos.getBranch({ ...context.repo, branch }),
]);
const { data: baseBranch } = await github.rest.repos.getBranch({
...context.repo,
branch: repository.default_branch,
});
const checkoutRef = targetBranch.commit.sha;
const baseRef = baseBranch.commit.sha;
assertValidSha(checkoutRef, 'branch head');
assertValidSha(baseRef, 'default branch');
core.info(`Branch ${branch} resolved to immutable commit ${checkoutRef}.`);
return {
baseRef,
checkoutRef,
description: `branch ${branch}`,
};
}
/**
* Resolve a manually requested integration-test target to an immutable commit.
*
* Pull requests must have fresh approvals from two unique write-capable
* maintainers for the exact head commit. Branches are limited to branches in
* the base repository and are pinned to their current commit.
*/
async function resolveIntegrationTestTarget({
github,
context,
core,
prNumber = '',
branch = '',
requiredApprovals = 2,
}) {
const normalizedPrNumber = prNumber.trim();
const normalizedBranch = branch.trim();
if (normalizedPrNumber && normalizedBranch) {
throw new Error('Please provide either a PR number or a branch name, not both.');
}
if (!normalizedPrNumber && !normalizedBranch) {
throw new Error('Please provide either a PR number or a branch name.');
}
if (normalizedPrNumber) {
return resolvePullRequest({
github,
context,
core,
prNumber: normalizedPrNumber,
requiredApprovals,
});
}
return resolveBranch({
github,
context,
core,
branch: normalizedBranch,
});
}
module.exports = resolveIntegrationTestTarget;
+253
View File
@@ -0,0 +1,253 @@
// Copyright (c) Microsoft. All rights reserved.
const BREAKING_CHANGE_LABEL = 'breaking change';
const BREAKING_PREFIX = '[BREAKING]';
const DEFAULT_PREFIX_LABELS = Object.freeze({
python: 'Python',
'.NET': '.NET',
});
const DEFAULT_BRACKET_PREFIX_LABELS = Object.freeze({
[BREAKING_CHANGE_LABEL]: BREAKING_PREFIX,
});
function escapeRegExp(value) {
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function getMatchingValueByKey(valuesByKey, keyToFind) {
const matchingKey = Object.keys(valuesByKey).find((key) => key.toLowerCase() === keyToFind.toLowerCase());
return matchingKey === undefined ? null : valuesByKey[matchingKey];
}
function getPrefixPattern(prefixes) {
return prefixes.map(escapeRegExp).join('|');
}
function canonicalizePrefix(prefix, prefixes) {
return prefixes.find((knownPrefix) => knownPrefix.toLowerCase() === prefix.toLowerCase()) ?? prefix;
}
function normalizeLeadingBracketPrefix(title, bracketPrefixes) {
const bracketPattern = getPrefixPattern(bracketPrefixes);
if (!bracketPattern) {
return title;
}
const leadingBracketPrefix = new RegExp(`^(${bracketPattern})(?=\\s|$)`, 'i');
return title.replace(
leadingBracketPrefix,
(bracketPrefix) => canonicalizePrefix(bracketPrefix, bracketPrefixes),
);
}
function parseLeadingTitlePrefix(title, titlePrefixes) {
const titlePrefixPattern = getPrefixPattern(titlePrefixes);
if (!titlePrefixPattern) {
return null;
}
const match = title.match(new RegExp(`^(${titlePrefixPattern}):\\s*`, 'i'));
if (!match) {
return null;
}
return {
prefix: canonicalizePrefix(match[1], titlePrefixes),
rest: title.slice(match[0].length).trimStart(),
};
}
function removeBracketPrefixToken(title, bracketPrefix) {
const bracketPrefixPattern = escapeRegExp(bracketPrefix);
return title
.replace(new RegExp(`(^|\\s+)${bracketPrefixPattern}(?=\\s|$)`, 'ig'), '$1')
.replace(/\s{2,}/g, ' ')
.trim();
}
function addTitlePrefix(title, prefix, bracketPrefixes = Object.values(DEFAULT_BRACKET_PREFIX_LABELS)) {
const bracketPattern = getPrefixPattern(bracketPrefixes);
const prefixPattern = escapeRegExp(prefix);
if (bracketPattern) {
const bracketThenTitlePrefix = new RegExp(`^(${bracketPattern})(\\s+)(${prefixPattern})(?=:)`, 'i');
if (bracketThenTitlePrefix.test(title)) {
return title.replace(
bracketThenTitlePrefix,
(match, bracketPrefix, spacing) => `${canonicalizePrefix(bracketPrefix, bracketPrefixes)}${spacing}${prefix}`,
);
}
title = normalizeLeadingBracketPrefix(title, bracketPrefixes);
}
if (!title.startsWith(`${prefix}: `)) {
const existingTitlePrefix = new RegExp(`^${prefixPattern}:\\s*`, 'i');
if (existingTitlePrefix.test(title)) {
return title.replace(existingTitlePrefix, `${prefix}: `);
}
return `${prefix}: ${title}`;
}
return title;
}
function hasBracketPrefix(title, bracketPrefix, titlePrefixes = Object.values(DEFAULT_PREFIX_LABELS)) {
const bracketPrefixPattern = escapeRegExp(bracketPrefix);
const leadingBracketPrefix = new RegExp(`^${bracketPrefixPattern}(?=\\s|$)`, 'i');
if (leadingBracketPrefix.test(title)) {
return true;
}
const leadingTitlePrefix = parseLeadingTitlePrefix(title, titlePrefixes);
if (!leadingTitlePrefix) {
return false;
}
return leadingBracketPrefix.test(leadingTitlePrefix.rest);
}
function addBracketPrefix(title, bracketPrefix, titlePrefixes = Object.values(DEFAULT_PREFIX_LABELS)) {
const bracketPrefixPattern = escapeRegExp(bracketPrefix);
const leadingBracketPrefix = new RegExp(`^${bracketPrefixPattern}(?=\\s|$)`, 'i');
if (leadingBracketPrefix.test(title)) {
return title.replace(leadingBracketPrefix, bracketPrefix);
}
const leadingTitlePrefix = parseLeadingTitlePrefix(title, titlePrefixes);
if (leadingTitlePrefix) {
if (leadingBracketPrefix.test(leadingTitlePrefix.rest)) {
const normalizedRest = leadingTitlePrefix.rest.replace(leadingBracketPrefix, bracketPrefix);
return `${leadingTitlePrefix.prefix}: ${normalizedRest}`;
}
const titleWithoutBracketPrefix = removeBracketPrefixToken(leadingTitlePrefix.rest, bracketPrefix);
return `${leadingTitlePrefix.prefix}: ${bracketPrefix}`
+ (titleWithoutBracketPrefix ? ` ${titleWithoutBracketPrefix}` : '');
}
const titleWithoutBracketPrefix = removeBracketPrefixToken(title, bracketPrefix);
return `${bracketPrefix}${titleWithoutBracketPrefix ? ` ${titleWithoutBracketPrefix}` : ''}`;
}
function hasLabel(labels, labelName) {
return labels.some((label) => label.toLowerCase() === labelName.toLowerCase());
}
function getCurrentTitle(context) {
switch (context.eventName) {
case 'issues':
return context.payload.issue.title;
case 'pull_request_target':
return context.payload.pull_request.title;
default:
throw new Error(`Unrecognized eventName: ${context.eventName}`);
}
}
async function updateTitleForAddedLabel({
github,
context,
core,
prefixLabels = DEFAULT_PREFIX_LABELS,
bracketPrefixLabels = DEFAULT_BRACKET_PREFIX_LABELS,
}) {
const labelAdded = context.payload.label?.name;
if (!labelAdded) {
throw new Error('This script must be run from a labeled event.');
}
const currentTitle = getCurrentTitle(context);
let newTitle = null;
const titlePrefix = getMatchingValueByKey(prefixLabels, labelAdded);
if (titlePrefix !== null) {
newTitle = addTitlePrefix(currentTitle, titlePrefix, Object.values(bracketPrefixLabels));
}
const bracketPrefix = getMatchingValueByKey(bracketPrefixLabels, labelAdded);
if (bracketPrefix !== null) {
newTitle = addBracketPrefix(currentTitle, bracketPrefix, Object.values(prefixLabels));
}
if (newTitle === null) {
core.info(`No title prefix configured for label "${labelAdded}".`);
return { updated: false, newTitle: currentTitle };
}
if (newTitle === currentTitle) {
core.info(`Title already includes the prefix for label "${labelAdded}".`);
return { updated: false, newTitle };
}
switch (context.eventName) {
case 'issues':
await github.rest.issues.update({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
title: newTitle,
});
break;
case 'pull_request_target':
await github.rest.pulls.update({
pull_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
title: newTitle,
});
break;
default:
throw new Error(`Unrecognized eventName: ${context.eventName}`);
}
return { updated: true, newTitle };
}
async function syncBreakingChangeLabelFromTitle({
github,
context,
core,
labelName = BREAKING_CHANGE_LABEL,
bracketPrefix = BREAKING_PREFIX,
titlePrefixes = Object.values(DEFAULT_PREFIX_LABELS),
}) {
const pullRequest = context.payload.pull_request;
if (!pullRequest) {
throw new Error('This script must be run from a pull_request_target event.');
}
const title = pullRequest.title || '';
if (!hasBracketPrefix(title, bracketPrefix, titlePrefixes)) {
core.info(`Title does not include ${bracketPrefix} in the title prefix.`);
return { added: false };
}
const labels = pullRequest.labels?.map((label) => label.name).filter(Boolean) ?? [];
if (hasLabel(labels, labelName)) {
core.info(`PR already has the "${labelName}" label.`);
return { added: false };
}
await github.rest.issues.addLabels({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: [labelName],
});
return { added: true };
}
module.exports = {
addBracketPrefix,
addTitlePrefix,
hasBracketPrefix,
syncBreakingChangeLabelFromTitle,
updateTitleForAddedLabel,
};
+116
View File
@@ -0,0 +1,116 @@
---
name: pull-requests
description: >
Guidance for creating pull requests and handling PR review comments in the
Agent Framework repository. Use this when writing a PR description (filling out
the PR template) or when responding to and resolving review comments on an
existing PR.
---
# Pull Request Workflow
This skill covers two tasks: (1) writing a high-quality PR description, and
(2) handling review comments on an existing PR.
## 1. Writing the PR description
Always follow the repository PR template at
[`.github/pull_request_template.md`](../../pull_request_template.md). Keep its
exact structure and headings. Fill every section:
### `### Motivation & Context`
Explain *why* the change is needed: the problem it solves and the scenario it
contributes to. Describe the net change relative to `main` — this is implied, so
do **not** spell out "vs main" explicitly.
### `### Description & Review Guide`
Describe the changes, the overall approach, and the design. Answer the three
prompts:
- **What are the major changes?**
- **What is the impact of these changes?**
- **What do you want reviewers to focus on?** — This item is for **human
reviewers only**. Automated/AI reviewers must ignore it and review the entire
change rather than narrowing scope to it.
### `### Related Issue`
Link the issue the PR fixes using a GitHub closing keyword (`Fixes #123` /
`Closes #123`) so it closes automatically on merge. A PR with no linked issue may
be closed regardless of how valid the change is. Before opening, confirm there is
no other open PR for the same issue; if there is, explain how this PR differs.
### `### Contribution Checklist`
Check every item that applies. For the breaking-change item:
- Leave **"This is not a breaking change."** checked for the common case.
- If the change **is** breaking, add the `breaking change` label **or** put
`[BREAKING]` in the title prefix, before or after a language prefix such as
`Python:` or `.NET:` — workflows keep the label and the title prefix in sync
automatically (see `.github/workflows/label-title-prefix.yml` and
`.github/workflows/label-pr.yml`).
### Do not
- Do **not** add ad-hoc sections such as "Validation" or "Tests run"; CI/CD and
the checklist already cover validation status.
- Do **not** remove or reorder the template's headings.
### Creating the PR
Open new PRs as **drafts** until they are ready for review. Example:
```bash
gh pr create --repo microsoft/agent-framework --base main \
--head <your-fork-owner>:<branch> --draft \
--title "<concise title>" --body "<body following the template>"
```
## 2. Handling review comments
When a PR receives review comments, follow this sequence — **do not start editing
code before the user has reviewed the plan**:
1. **Review the comments.** Read every review comment and thread on the PR,
including inline code comments and general review summaries.
2. **Make a plan.** Produce a concrete plan describing how each comment will be
addressed (or why it should not be, with reasoning).
3. **Let the user review the plan.** Present the plan and wait for the user's
approval or adjustments before implementing anything.
4. **Implement.** Make the agreed changes.
5. **Reply to every comment.** Add a reply to **all** comments explaining how it
was addressed (or the agreed outcome) — leave none unanswered.
6. **Resolve resolved threads.** Mark a review thread as resolved only when the
comment has actually been addressed.
### Useful commands
List review comments and threads:
```bash
# Inline review comments
gh api repos/{owner}/{repo}/pulls/{pr}/comments
# Review threads with resolution state (GraphQL)
gh api graphql -f query='
query($owner:String!,$repo:String!,$pr:Int!){
repository(owner:$owner,name:$repo){
pullRequest(number:$pr){
reviewThreads(first:100){
nodes{ id isResolved comments(first:50){ nodes{ id body author{login} } } }
}
}
}
}' -F owner={owner} -F repo={repo} -F pr={pr}
```
Reply to an inline review comment:
```bash
gh api repos/{owner}/{repo}/pulls/{pr}/comments/{comment_id}/replies \
-f body="Addressed in <commit>: <explanation>"
```
Resolve a review thread (needs the thread node id from the GraphQL query above):
```bash
gh api graphql -f query='
mutation($threadId:ID!){
resolveReviewThread(input:{threadId:$threadId}){ thread{ isResolved } }
}' -F threadId={thread_id}
```
+73 -2
View File
@@ -16,7 +16,12 @@ const checkTeamMembership = require('../scripts/check_team_membership.js');
// Helpers
// ---------------------------------------------------------------------------
function createMocks({ payloadIssue = undefined, apiUser = 'api-user', teamState = 'active' } = {}) {
function createMocks({
payloadIssue = undefined,
payloadPullRequest = undefined,
apiUser = 'api-user',
teamState = 'active',
} = {}) {
const core = {
_infoMessages: [],
_failedMessages: [],
@@ -24,8 +29,16 @@ function createMocks({ payloadIssue = undefined, apiUser = 'api-user', teamState
setFailed(msg) { this._failedMessages.push(msg); },
};
const payload = {};
if (payloadIssue !== undefined) {
payload.issue = payloadIssue;
}
if (payloadPullRequest !== undefined) {
payload.pull_request = payloadPullRequest;
}
const context = {
payload: { issue: payloadIssue },
payload,
repo: { owner: 'test-org', repo: 'test-repo' },
};
@@ -36,6 +49,11 @@ function createMocks({ payloadIssue = undefined, apiUser = 'api-user', teamState
data: { user: apiUser ? { login: apiUser } : null },
}),
},
pulls: {
get: async () => ({
data: { user: apiUser ? { login: apiUser } : null },
}),
},
teams: {
getByName: async () => ({}),
getMembershipForUserInOrg: async () => ({
@@ -56,6 +74,28 @@ const BASE_OPTS = { teamSlug: 'my-team', issueNumber: '123' };
// ---------------------------------------------------------------------------
describe('author resolution', () => {
it('uses an explicit username instead of the issue author', async () => {
const { github, context, core } = createMocks({
payloadIssue: { user: { login: 'issue-author' } },
});
let issuesGetCalled = false;
github.rest.issues.get = async () => {
issuesGetCalled = true;
return { data: { user: { login: 'api-user' } } };
};
const result = await checkTeamMembership({
github,
context,
core,
...BASE_OPTS,
username: 'comment-author',
});
assert.equal(result.author, 'comment-author');
assert.equal(issuesGetCalled, false);
});
it('resolves author from event payload', async () => {
const { github, context, core } = createMocks({
payloadIssue: { user: { login: 'payload-user' } },
@@ -64,6 +104,37 @@ describe('author resolution', () => {
assert.equal(result.author, 'payload-user');
});
it('resolves author from pull_request event payload', async () => {
const { github, context, core } = createMocks({
payloadPullRequest: { user: { login: 'pr-author' } },
});
let issuesGetCalled = false;
github.rest.issues.get = async () => {
issuesGetCalled = true;
return { data: { user: { login: 'api-user' } } };
};
const result = await checkTeamMembership({ github, context, core, ...BASE_OPTS });
assert.equal(result.author, 'pr-author');
assert.equal(issuesGetCalled, false);
});
it('resolves author via pulls API when pull_request payload user is null', async () => {
const { github, context, core } = createMocks({
payloadPullRequest: { user: null },
apiUser: 'fetched-pr-author',
});
let pullsGetCalled = false;
github.rest.pulls.get = async () => {
pullsGetCalled = true;
return { data: { user: { login: 'fetched-pr-author' } } };
};
const result = await checkTeamMembership({ github, context, core, ...BASE_OPTS });
assert.equal(result.author, 'fetched-pr-author');
assert.equal(pullsGetCalled, true);
});
it('resolves author via API when payload issue is absent', async () => {
const { github, context, core } = createMocks({ apiUser: 'api-user' });
const result = await checkTeamMembership({ github, context, core, ...BASE_OPTS });
@@ -0,0 +1,125 @@
// Copyright (c) Microsoft. All rights reserved.
const { describe, it } = require('node:test');
const assert = require('node:assert/strict');
const {
base64ToBase64Url,
createInstallationToken,
createJwtSigningInput,
readConfig,
} = require('../actions/github-app-token/create-token.js');
const CONFIG = {
azureSubscriptionId: 'subscription-id',
keyVaultName: 'vault-name',
keyName: 'key-name',
githubAppClientId: 'client-id',
githubAppInstallationId: '12345',
targetRepository: 'microsoft/agent-framework',
};
describe('GitHub App token creation', () => {
it('creates a short-lived GitHub App JWT', () => {
const signingInput = createJwtSigningInput('client-id', 1_000);
const [encodedHeader, encodedPayload] = signingInput.split('.');
const header = JSON.parse(Buffer.from(encodedHeader, 'base64url').toString());
const payload = JSON.parse(Buffer.from(encodedPayload, 'base64url').toString());
assert.deepEqual(header, { alg: 'RS256', typ: 'JWT' });
assert.deepEqual(payload, { iat: 940, exp: 1_540, iss: 'client-id' });
});
it('converts Key Vault signatures to unpadded base64url', () => {
assert.equal(base64ToBase64Url('+/8='), '-_8');
});
it('requests a repository-scoped installation token', async () => {
let request;
const token = await createInstallationToken(CONFIG, {
nowSeconds: 1_000,
execute: (command, args) => {
assert.equal(command, 'az');
assert.ok(args.includes('RS256'));
return '+/8=\n';
},
fetch: async (url, options) => {
request = { url, options };
return {
ok: true,
json: async () => ({ token: 'installation-token' }),
};
},
});
assert.equal(token, 'installation-token');
assert.equal(request.url, 'https://api.github.com/app/installations/12345/access_tokens');
assert.match(request.options.headers.Authorization, /^Bearer [^.]+\.[^.]+\.-_8$/);
assert.deepEqual(JSON.parse(request.options.body), {
repositories: ['agent-framework'],
permissions: {
contents: 'read',
issues: 'write',
members: 'read',
pull_requests: 'write',
},
});
});
it('rejects incomplete configuration', () => {
assert.throws(
() => readConfig({}),
/Required GitHub App authentication configuration is missing/,
);
});
it('rejects repository values with extra path segments before signing', async () => {
let signed = false;
await assert.rejects(
createInstallationToken(
{ ...CONFIG, targetRepository: 'microsoft/agent-framework/extra' },
{
execute: () => {
signed = true;
return '+/8=\n';
},
},
),
/TARGET_REPOSITORY must use the owner\/repository format/,
);
assert.equal(signed, false);
});
it('rejects an empty Key Vault signature', async () => {
await assert.rejects(
createInstallationToken(CONFIG, {
execute: () => '\n',
}),
/Key Vault returned an empty signature/,
);
});
it('rejects a failed GitHub token request', async () => {
await assert.rejects(
createInstallationToken(CONFIG, {
execute: () => '+/8=\n',
fetch: async () => ({ ok: false, status: 403 }),
}),
/GitHub installation token request failed with HTTP 403/,
);
});
it('rejects an empty GitHub installation token', async () => {
await assert.rejects(
createInstallationToken(CONFIG, {
execute: () => '+/8=\n',
fetch: async () => ({
ok: true,
json: async () => ({ token: '' }),
}),
}),
/GitHub returned an empty installation token/,
);
});
});
+134
View File
@@ -0,0 +1,134 @@
# Copyright (c) Microsoft. All rights reserved.
# ruff:file-ignore[implicit-namespace-package, undocumented-public-class, undocumented-public-method]
from __future__ import annotations
import importlib.util
import sys
import tempfile
import unittest
from pathlib import Path
SCRIPT_PATH = Path(__file__).parents[1] / "scripts" / "python_check_coverage.py"
SPEC = importlib.util.spec_from_file_location("python_check_coverage", SCRIPT_PATH)
if SPEC is None or SPEC.loader is None:
raise RuntimeError(f"Unable to load {SCRIPT_PATH}")
coverage_checker = importlib.util.module_from_spec(SPEC)
sys.modules[SPEC.name] = coverage_checker
SPEC.loader.exec_module(coverage_checker)
class CoveragePolicyTests(unittest.TestCase):
def setUp(self) -> None:
self.temp_dir = tempfile.TemporaryDirectory()
self.root = Path(self.temp_dir.name)
self.packages_dir = self.root / "packages"
self.packages_dir.mkdir()
def tearDown(self) -> None:
self.temp_dir.cleanup()
def write_package(self, directory: str, name: str, status: str) -> None:
package_dir = self.packages_dir / directory
package_dir.mkdir()
(package_dir / "pyproject.toml").write_text(
f"""
[project]
name = "{name}"
classifiers = ["Development Status :: {status}"]
""".strip()
)
def write_coverage(self, files: dict[str, list[int]]) -> Path:
classes = []
total_lines = 0
covered_lines = 0
for file_path, hits in files.items():
lines = []
for line_number, hit_count in enumerate(hits, start=1):
total_lines += 1
covered_lines += hit_count > 0
lines.append(f'<line number="{line_number}" hits="{hit_count}"/>')
classes.append(f'<class filename="{file_path}"><lines>{"".join(lines)}</lines></class>')
line_rate = covered_lines / total_lines if total_lines else 0
xml_path = self.root / "coverage.xml"
xml_path.write_text(
f"""
<coverage line-rate="{line_rate}" branch-rate="0">
<packages>
<package name="test">
<classes>{"".join(classes)}</classes>
</package>
</packages>
</coverage>
""".strip()
)
return xml_path
def test_load_package_policies_uses_lifecycle_exemptions(self) -> None:
self.write_package("alpha", "agent-framework-alpha", "3 - Alpha")
self.write_package("beta", "agent-framework-beta", "4 - Beta")
self.write_package("stable", "agent-framework-stable", "5 - Production/Stable")
self.write_package("devui", "agent-framework-devui", "4 - Beta")
self.write_package("lab", "agent-framework-lab", "4 - Beta")
policies = {policy.directory: policy for policy in coverage_checker.load_package_policies(self.packages_dir)}
self.assertFalse(policies["alpha"].enforced)
self.assertTrue(policies["beta"].enforced)
self.assertTrue(policies["stable"].enforced)
self.assertTrue(policies["devui"].exempt)
self.assertFalse(policies["devui"].enforced)
self.assertTrue(policies["lab"].exempt)
self.assertFalse(policies["lab"].enforced)
def test_load_package_policies_rejects_missing_lifecycle(self) -> None:
package_dir = self.packages_dir / "missing"
package_dir.mkdir()
(package_dir / "pyproject.toml").write_text('[project]\nname = "agent-framework-missing"\n')
with self.assertRaisesRegex(ValueError, "exactly one Development Status"):
coverage_checker.load_package_policies(self.packages_dir)
def test_parse_coverage_aggregates_nested_modules_by_distribution(self) -> None:
xml_path = self.write_coverage({
"packages/core/agent_framework/_agents.py": [1, 0],
"packages/core/agent_framework/_workflows/_workflow.py": [1, 1],
})
package_stats, _, _ = coverage_checker.parse_coverage_xml(xml_path)
self.assertEqual(package_stats["core"].lines_valid, 4)
self.assertEqual(package_stats["core"].lines_covered, 3)
def test_beta_package_below_threshold_fails(self) -> None:
self.write_package("beta", "agent-framework-beta", "4 - Beta")
xml_path = self.write_coverage({"packages/beta/agent_framework_beta/client.py": [1, 0]})
self.assertFalse(coverage_checker.check_coverage(xml_path, 85, self.packages_dir))
def test_missing_beta_package_fails(self) -> None:
self.write_package("beta", "agent-framework-beta", "4 - Beta")
xml_path = self.write_coverage({})
self.assertFalse(coverage_checker.check_coverage(xml_path, 85, self.packages_dir))
def test_alpha_and_exempt_packages_do_not_fail(self) -> None:
self.write_package("alpha", "agent-framework-alpha", "3 - Alpha")
self.write_package("devui", "agent-framework-devui", "4 - Beta")
self.write_package("lab", "agent-framework-lab", "4 - Beta")
xml_path = self.write_coverage({})
self.assertTrue(coverage_checker.check_coverage(xml_path, 85, self.packages_dir))
def test_beta_package_at_threshold_passes(self) -> None:
self.write_package("beta", "agent-framework-beta", "4 - Beta")
xml_path = self.write_coverage({"packages/beta/agent_framework_beta/client.py": [1] * 17 + [0] * 3})
self.assertTrue(coverage_checker.check_coverage(xml_path, 85, self.packages_dir))
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,212 @@
// Copyright (c) Microsoft. All rights reserved.
/**
* Tests for resolve_integration_test_target.js.
*
* Run with: node --test .github/tests/test_resolve_integration_test_target.js
*/
const { describe, it } = require('node:test');
const assert = require('node:assert/strict');
const resolveIntegrationTestTarget = require('../scripts/resolve_integration_test_target.js');
const HEAD_SHA = 'a'.repeat(40);
const BASE_SHA = 'b'.repeat(40);
function review({
id,
login,
state = 'APPROVED',
commitId = HEAD_SHA,
submittedAt = `2026-07-13T00:00:${String(id).padStart(2, '0')}Z`,
}) {
return {
id,
state,
commit_id: commitId,
submitted_at: submittedAt,
user: { login },
};
}
function createMocks({
pullState = 'open',
pullAuthor = 'contributor',
reviews = [],
permissions = {},
} = {}) {
const core = {
infoMessages: [],
info(message) {
this.infoMessages.push(message);
},
};
const context = {
repo: { owner: 'microsoft', repo: 'agent-framework' },
};
const github = {
paginate: async () => reviews,
rest: {
pulls: {
get: async () => ({
data: {
state: pullState,
user: { login: pullAuthor },
head: { sha: HEAD_SHA },
base: { sha: BASE_SHA },
},
}),
listReviews: async () => {},
},
repos: {
get: async () => ({ data: { default_branch: 'main' } }),
getBranch: async ({ branch }) => ({
data: { commit: { sha: branch === 'main' ? BASE_SHA : HEAD_SHA } },
}),
getCollaboratorPermissionLevel: async ({ username }) => ({
data: permissions[username] || {
permission: 'read',
user: { permissions: { push: false } },
},
}),
},
},
};
return { core, context, github };
}
const WRITE_PERMISSION = {
permission: 'write',
user: { permissions: { push: true } },
};
describe('input validation', () => {
it('rejects missing and conflicting targets', async () => {
const mocks = createMocks();
await assert.rejects(
() => resolveIntegrationTestTarget(mocks),
/provide either a PR number or a branch name/,
);
await assert.rejects(
() => resolveIntegrationTestTarget({ ...mocks, prNumber: '1', branch: 'feature' }),
/not both/,
);
});
it('rejects invalid PR numbers and branch names', async () => {
const mocks = createMocks();
await assert.rejects(
() => resolveIntegrationTestTarget({ ...mocks, prNumber: '1;echo' }),
/Invalid PR number/,
);
await assert.rejects(
() => resolveIntegrationTestTarget({ ...mocks, branch: 'feature branch' }),
/Invalid branch name/,
);
});
});
describe('pull request resolution', () => {
it('pins an open PR with two fresh write-capable approvals', async () => {
const mocks = createMocks({
reviews: [
review({ id: 1, login: 'maintainer-one' }),
review({ id: 2, login: 'maintainer-two' }),
],
permissions: {
'maintainer-one': WRITE_PERMISSION,
'maintainer-two': WRITE_PERMISSION,
},
});
const result = await resolveIntegrationTestTarget({ ...mocks, prNumber: '123' });
assert.deepEqual(result, {
baseRef: BASE_SHA,
checkoutRef: HEAD_SHA,
description: 'PR #123',
});
});
it('rejects closed PRs', async () => {
const mocks = createMocks({ pullState: 'closed' });
await assert.rejects(
() => resolveIntegrationTestTarget({ ...mocks, prNumber: '123' }),
/is not open/,
);
});
it('ignores stale, self, and read-only approvals', async () => {
const mocks = createMocks({
reviews: [
review({ id: 1, login: 'stale', commitId: 'c'.repeat(40) }),
review({ id: 2, login: 'contributor' }),
review({ id: 3, login: 'reader' }),
review({ id: 4, login: 'maintainer' }),
],
permissions: {
contributor: WRITE_PERMISSION,
reader: { permission: 'read', user: { permissions: { push: false } } },
maintainer: WRITE_PERMISSION,
},
});
await assert.rejects(
() => resolveIntegrationTestTarget({ ...mocks, prNumber: '123' }),
/found 1/,
);
});
it('uses each reviewer latest decisive review and ignores later comments', async () => {
const mocks = createMocks({
reviews: [
review({ id: 1, login: 'changes-requested' }),
review({ id: 2, login: 'changes-requested', state: 'CHANGES_REQUESTED' }),
review({ id: 3, login: 'maintainer-one' }),
review({ id: 4, login: 'maintainer-one', state: 'COMMENTED' }),
review({ id: 5, login: 'maintainer-two' }),
],
permissions: {
'changes-requested': WRITE_PERMISSION,
'maintainer-one': WRITE_PERMISSION,
'maintainer-two': WRITE_PERMISSION,
},
});
const result = await resolveIntegrationTestTarget({ ...mocks, prNumber: '123' });
assert.equal(result.checkoutRef, HEAD_SHA);
});
it('does not count a dismissed approval', async () => {
const mocks = createMocks({
reviews: [
review({ id: 1, login: 'dismissed', state: 'DISMISSED' }),
review({ id: 2, login: 'maintainer' }),
],
permissions: {
dismissed: WRITE_PERMISSION,
maintainer: WRITE_PERMISSION,
},
});
await assert.rejects(
() => resolveIntegrationTestTarget({ ...mocks, prNumber: '123' }),
/found 1/,
);
});
});
describe('branch resolution', () => {
it('pins base-repository branches and their comparison base to SHAs', async () => {
const mocks = createMocks();
const result = await resolveIntegrationTestTarget({ ...mocks, branch: 'feature/test' });
assert.deepEqual(result, {
baseRef: BASE_SHA,
checkoutRef: HEAD_SHA,
description: 'branch feature/test',
});
});
});
@@ -23,16 +23,16 @@ For each project that needs to be migrated, you need to do the following:
- Identify the specific Semantic Kernel agent types being used:
- `ChatCompletionAgent``ChatClientAgent`
- `OpenAIAssistantAgent``assistantsClient.CreateAIAgent()` (via OpenAI Assistants client extension)
- `AzureAIAgent``persistentAgentsClient.CreateAIAgent()` (via Azure AI Foundry client extension)
- `AzureAIAgent``persistentAgentsClient.CreateAIAgent()` (via Microsoft Foundry client extension)
- `OpenAIResponseAgent``responsesClient.CreateAIAgent()` (via OpenAI Responses client extension)
- `A2AAgent``AIAgent` (via A2A card resolver)
- `BedrockAgent` → Custom implementation required (not supported)
- Determine if agents are being created new or retrieved from hosted services:
- **New agents**: Use `CreateAIAgent()` methods
- **Existing hosted agents**: Use `GetAIAgent(agentId)` methods for OpenAI Assistants and Azure AI Foundry
- **Existing hosted agents**: Use `GetAIAgent(agentId)` methods for OpenAI Assistants and Microsoft Foundry
</agent_type_identification>
- Determine the AI provider being used (OpenAI, Azure OpenAI, Azure AI Foundry, etc.)
- Determine the AI provider being used (OpenAI, Azure OpenAI, Microsoft Foundry, etc.)
- Analyze tool/function registration patterns
- Review thread management and invocation patterns
@@ -90,7 +90,7 @@ below in wrong order or skip any of them):
you generate report when migration complete. Report should contain:
- all project dependencies changes (mention what was changed, added or removed, including provider-specific packages)
- all code files that were changed (mention what was changed in the file, if it was not changed, just mention that the file was not changed)
- provider-specific migration patterns used (OpenAI, Azure OpenAI, Azure AI Foundry, A2A, ONNX, etc.)
- provider-specific migration patterns used (OpenAI, Azure OpenAI, Microsoft Foundry, A2A, ONNX, etc.)
- all cases where you could not convert the code because of unsupported features and you were unable to find a workaround
- unsupported providers that require custom implementation (Bedrock, CopilotStudio)
- breaking glass pattern migrations (InnerContent → RawRepresentation) and any CodeInterpreter or advanced tool usage
@@ -223,7 +223,7 @@ using Microsoft.Agents.AI;
// Provider-specific namespaces (add only if needed):
using OpenAI; // For OpenAI provider
using Azure.AI.OpenAI; // For Azure OpenAI provider
using Azure.AI.Agents.Persistent; // For Azure AI Foundry provider
using Azure.AI.Agents.Persistent; // For Microsoft Foundry provider
using Azure.Identity; // For Azure authentication
```
</configuration_changes>
@@ -499,7 +499,7 @@ For every thread created if there's intent to cleanup, the caller should track a
var assistantClient = new OpenAIClient(apiKey).GetAssistantClient();
await assistantClient.DeleteThreadAsync(thread.ConversationId);
// For Azure AI Foundry (when cleanup is needed):
// For Microsoft Foundry (when cleanup is needed):
var persistentClient = new PersistentAgentsClient(endpoint, credential);
await persistentClient.Threads.DeleteThreadAsync(thread.ConversationId);
@@ -514,7 +514,7 @@ await persistentClient.Threads.DeleteThreadAsync(thread.ConversationId);
1. Remove `thread.DeleteAsync()` calls
2. Use provider-specific client for cleanup when required
3. Access thread ID via `thread.ConversationId` property
4. Only implement cleanup for providers that require it (Assistants, Azure AI Foundry)
4. Only implement cleanup for providers that require it (Assistants, Microsoft Foundry)
</api_changes>
### Provider-Specific Creation Patterns
@@ -550,13 +550,13 @@ AIAgent agent = new AzureOpenAIClient(endpoint, credential)
.CreateAIAgent(instructions: instructions);
```
**Azure AI Foundry (New):**
**Microsoft Foundry (New):**
```csharp
AIAgent agent = new PersistentAgentsClient(endpoint, credential)
.CreateAIAgent(model: deploymentName, instructions: instructions);
```
**Azure AI Foundry (Existing):**
**Microsoft Foundry (Existing):**
```csharp
AIAgent agent = await new PersistentAgentsClient(endpoint, credential)
.GetAIAgentAsync(agentId);
@@ -1079,7 +1079,7 @@ AgentThread thread = agent.GetNewThread();
```
</api_changes>
### 4. Azure AI Foundry (AzureAIAgent) Migration
### 4. Microsoft Foundry (AzureAIAgent) Migration
<configuration_changes>
**Remove Semantic Kernel Packages:**
+4 -4
View File
@@ -32,13 +32,13 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4
uses: github/codeql-action/init@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -51,7 +51,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4
uses: github/codeql-action/autobuild@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4
# ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
@@ -64,6 +64,6 @@ jobs:
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4
uses: github/codeql-action/analyze@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4
with:
category: "/language:${{matrix.language}}"
+83 -33
View File
@@ -6,6 +6,9 @@ on:
- opened
- reopened
- ready_for_review
issue_comment:
types:
- created
workflow_dispatch:
inputs:
pr_number:
@@ -15,11 +18,12 @@ on:
permissions:
contents: read
id-token: write
issues: write
pull-requests: write
concurrency:
group: devflow-pr-review-${{ github.repository }}-${{ github.event.pull_request.number || inputs.pr_number || github.run_id }}
group: devflow-pr-review-${{ github.repository }}-${{ github.event.pull_request.number || github.event.issue.number || inputs.pr_number || github.run_id }}
cancel-in-progress: true
env:
@@ -27,10 +31,22 @@ env:
DEVFLOW_REF: main
TARGET_REPO_PATH: ${{ github.workspace }}/target-repo
DEVFLOW_PATH: ${{ github.workspace }}/devflow
MODEL_CONFIG_PATH: ${{ github.workspace }}/devflow/config.ci.yaml
jobs:
team_check:
if: >-
github.event_name != 'issue_comment' ||
(
github.event.issue.pull_request &&
github.event.comment.body == '/review' &&
(
github.event.comment.author_association == 'MEMBER' ||
github.event.comment.author_association == 'OWNER'
)
)
runs-on: ubuntu-latest
environment: github-app-auth
outputs:
is_team_member: ${{ steps.check.outputs.is_team_member }}
pr_number: ${{ steps.pr.outputs.pr_number }}
@@ -42,6 +58,7 @@ jobs:
shell: bash
env:
PR_HTML_URL: ${{ github.event.pull_request.html_url }}
PR_NUMBER_COMMENT: ${{ github.event.issue.number }}
PR_NUMBER_EVENT: ${{ github.event.pull_request.number }}
PR_NUMBER_INPUT: ${{ inputs.pr_number }}
run: |
@@ -50,6 +67,9 @@ jobs:
if [[ "${GITHUB_EVENT_NAME}" == "pull_request_target" ]]; then
pr_number="${PR_NUMBER_EVENT}"
pr_url="${PR_HTML_URL}"
elif [[ "${GITHUB_EVENT_NAME}" == "issue_comment" ]]; then
pr_number="${PR_NUMBER_COMMENT}"
pr_url="https://github.com/${GITHUB_REPOSITORY}/pull/${pr_number}"
else
pr_number="${PR_NUMBER_INPUT}"
pr_url="https://github.com/${GITHUB_REPOSITORY}/pull/${pr_number}"
@@ -64,49 +84,78 @@ jobs:
echo "pr_number=${pr_number}" >> "$GITHUB_OUTPUT"
echo "repo=${GITHUB_REPOSITORY}" >> "$GITHUB_OUTPUT"
- name: Check PR author team membership
- name: Checkout GitHub automation
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.base.sha || github.sha }}
sparse-checkout: |
.github/actions/github-app-token
.github/scripts/check_team_membership.js
fetch-depth: 1
persist-credentials: false
- name: Get GitHub automation token
id: github-auth
uses: ./.github/actions/github-app-token
with:
mode: ${{ vars.GH_APP_AUTH_MODE }}
azure-client-id: ${{ secrets.GH_APP_AZURE_CLIENT_ID }}
azure-tenant-id: ${{ secrets.GH_APP_AZURE_TENANT_ID }}
azure-subscription-id: ${{ secrets.GH_APP_AZURE_SUBSCRIPTION_ID }}
key-vault-name: ${{ secrets.GH_APP_KEY_VAULT_NAME }}
key-name: ${{ secrets.GH_APP_KEY_NAME }}
github-app-client-id: ${{ secrets.GH_APP_CLIENT_ID }}
github-app-installation-id: ${{ secrets.GH_APP_INSTALLATION_ID }}
repository: ${{ github.repository }}
fallback-token: ${{ secrets.GH_ACTIONS_PR_WRITE }}
- name: Check review requester team membership
id: check
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
env:
MEMBERSHIP_USER: ${{ github.event_name == 'issue_comment' && github.event.comment.user.login || '' }}
TEAM_NAME: ${{ secrets.DEVELOPER_TEAM }}
PR_NUMBER: ${{ steps.pr.outputs.pr_number }}
with:
github-token: ${{ secrets.GH_ACTIONS_PR_WRITE }}
github-token: ${{ steps.github-auth.outputs.token }}
script: |
let author = context.payload.pull_request?.user?.login;
if (!author) {
const { data: pr } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: Number(process.env.PR_NUMBER),
});
author = pr.user.login;
}
let isTeamMember = false;
try {
const teamMembership = await github.rest.teams.getMembershipForUserInOrg({
org: context.repo.owner,
team_slug: process.env.TEAM_NAME,
username: author,
});
isTeamMember = teamMembership.data.state === 'active';
} catch (error) {
console.log(`Team membership lookup failed for ${author}: ${error.message}`);
isTeamMember = false;
}
const checkTeamMembership = require('./.github/scripts/check_team_membership.js');
const { author, isTeamMember } = await checkTeamMembership({
github,
context,
core,
teamSlug: process.env.TEAM_NAME,
issueNumber: process.env.PR_NUMBER,
username: process.env.MEMBERSHIP_USER,
});
core.setOutput('is_team_member', isTeamMember ? 'true' : 'false');
if (isTeamMember) {
core.info(`Author ${author} is a team member; proceeding with review.`);
core.info(`User ${author} is a team member; proceeding with review.`);
} else {
core.info(`Author ${author} is not a member of ${process.env.TEAM_NAME}; skipping review.`);
core.info(`User ${author} is not a member of ${process.env.TEAM_NAME}; skipping review.`);
}
- name: React to authorized review command
if: ${{ github.event_name == 'issue_comment' && steps.check.outputs.is_team_member == 'true' }}
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
github-token: ${{ steps.github-auth.outputs.token }}
script: |
await github.rest.reactions.createForIssueComment({
...context.repo,
comment_id: context.payload.comment.id,
content: 'eyes',
});
review:
runs-on: ubuntu-latest
needs: team_check
if: ${{ needs.team_check.outputs.is_team_member == 'true' }}
permissions:
copilot-requests: write
contents: read
issues: write
pull-requests: write
timeout-minutes: 60
# Advisory check: failures here should not block the PR. The reviewer
# posts comments as a best-effort signal; if the pipeline breaks, the
@@ -116,7 +165,7 @@ jobs:
steps:
# Safe checkout: base repo only, not the untrusted PR head.
- name: Checkout target repo base
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.base.sha || github.sha }}
fetch-depth: 0
@@ -125,7 +174,7 @@ jobs:
# Private DevFlow checkout: the PAT/token grants access to this repo's code.
- name: Checkout DevFlow
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ env.DEVFLOW_REPOSITORY }}
ref: ${{ env.DEVFLOW_REF }}
@@ -140,7 +189,7 @@ jobs:
python-version: "3.13"
- name: Set up uv
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
version: "0.11.x"
enable-cache: true
@@ -153,8 +202,8 @@ jobs:
id: review
working-directory: ${{ env.DEVFLOW_PATH }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_COPILOT_TOKEN: ${{ secrets.GH_COPILOT_TOKEN }}
DEVFLOW_TOKEN: ${{ secrets.DEVFLOW_TOKEN }}
GITHUB_TOKEN: ${{ github.token }}
SK_REPO_PATH: ${{ env.TARGET_REPO_PATH }}
AGENT_REPO_PATH: ${{ env.TARGET_REPO_PATH }}
PR_URL: ${{ needs.team_check.outputs.pr_url }}
@@ -162,4 +211,5 @@ jobs:
uv run python scripts/trigger_pr_review.py \
--pr-url "$PR_URL" \
--github-username "$GITHUB_ACTOR" \
--review-compare \
--no-require-comment-selection
+17 -126
View File
@@ -38,11 +38,10 @@ jobs:
dotnetChanges: ${{ steps.filter.outputs.dotnet }}
cosmosDbChanges: ${{ steps.filter.outputs.cosmosdb }}
foundryHostingChanges: ${{ steps.filter.outputs.foundryHosting }}
functionsChanged: ${{ steps.filter.outputs.functions }}
coreChanged: ${{ steps.filter.outputs.core }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2
id: filter
with:
filters: |
@@ -66,13 +65,6 @@ jobs:
- 'dotnet/Directory.Packages.props'
- 'dotnet/tests/Foundry.Hosting.IntegrationTests/scripts/it-build-image.ps1'
- '.github/workflows/dotnet-build-and-test.yml'
functions:
- 'dotnet/src/Microsoft.Agents.AI.DurableTask/**'
- 'dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/**'
- 'dotnet/tests/Microsoft.Agents.AI.DurableTask.IntegrationTests/**'
- 'dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.IntegrationTests/**'
- '.github/actions/azure-functions-integration-setup/**'
- '.github/workflows/dotnet-build-and-test.yml'
core:
- 'dotnet/src/Microsoft.Agents.AI/**'
- 'dotnet/src/Microsoft.Agents.AI.Abstractions/**'
@@ -111,7 +103,7 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
sparse-checkout: |
@@ -163,6 +155,7 @@ jobs:
# Change to project directory to ensure local nuget.config is used
pushd consoleapp
dotnet add packcheck.csproj package Microsoft.Agents.AI --prerelease
dotnet add packcheck.csproj package Microsoft.Agents.AI.LocalCodeAct --prerelease
dotnet build -f ${{ matrix.targetFramework }} -c ${{ matrix.configuration }} packcheck.csproj
# Clean up
@@ -184,7 +177,7 @@ jobs:
runs-on: ${{ matrix.os }}
environment: ${{ matrix.environment }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
sparse-checkout: |
@@ -241,7 +234,6 @@ jobs:
-OutputPath dotnet/filtered-unit.slnx
./dotnet/eng/scripts/New-FilteredSolution.ps1 @commonArgs `
-TestProjectNameIncludeFilter "*IntegrationTests*" `
-TestProjectNameExcludeFilter "*DurableTask.IntegrationTests*","*AzureFunctions.IntegrationTests*" `
-OutputPath dotnet/filtered-integration.slnx
- name: Run Unit Tests
@@ -312,14 +304,15 @@ jobs:
AZURE_OPENAI_DEPLOYMENT_NAME: ${{ vars.AZURE_OPENAI_DEPLOYMENT_NAME }}
AZURE_OPENAI_CHAT_DEPLOYMENT_NAME: ${{ vars.AZURE_OPENAI_DEPLOYMENT_NAME }}
AZURE_OPENAI_ENDPOINT: ${{ vars.AZURE_OPENAI_ENDPOINT }}
# Azure AI Foundry
# Microsoft Foundry
AZURE_AI_PROJECT_ENDPOINT: ${{ vars.AZURE_AI_PROJECT_ENDPOINT }}
AZURE_AI_MODEL_DEPLOYMENT_NAME: ${{ vars.AZURE_AI_MODEL_DEPLOYMENT_NAME }}
AZURE_AI_BING_CONNECTION_ID: ${{ vars.AZURE_AI_BING_CONNECTION_ID }}
# Anthropic Models
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
ANTHROPIC_CHAT_MODEL_NAME: ${{ vars.ANTHROPIC_CHAT_MODEL_NAME }}
ANTHROPIC_REASONING_MODEL_NAME: ${{ vars.ANTHROPIC_REASONING_MODEL_NAME }}
# Disable Anthropic tests by not providing environment vars until 404 failure is resolved
# ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
# ANTHROPIC_CHAT_MODEL_NAME: ${{ vars.ANTHROPIC_CHAT_MODEL_NAME }}
# ANTHROPIC_REASONING_MODEL_NAME: ${{ vars.ANTHROPIC_REASONING_MODEL_NAME }}
# Generate test reports and check coverage
- name: Generate test reports
@@ -362,7 +355,7 @@ jobs:
env:
configuration: Release
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
sparse-checkout: |
@@ -438,113 +431,11 @@ jobs:
AZURE_SEARCH_INDEX_NAME: ${{ secrets.AZURE_SEARCH_INDEX_NAME }}
# IT_HOSTED_AGENT_IMAGE was exported into $GITHUB_ENV by the previous step.
# DurableTask and AzureFunctions integration tests (ubuntu/net10.0 only).
# Split from main dotnet-test job for path-based filtering and parallelism.
dotnet-test-functions:
needs: [paths-filter]
if: >
github.event_name != 'pull_request' &&
(needs.paths-filter.outputs.functionsChanged == 'true' ||
needs.paths-filter.outputs.coreChanged == 'true' ||
github.event_name == 'schedule' ||
github.event_name == 'workflow_dispatch')
runs-on: ubuntu-latest
environment: integration
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
sparse-checkout: |
.
.github
dotnet
python
declarative-agents
- name: Free runner disk space
uses: ./.github/actions/free-runner-disk-space
- name: Setup dotnet
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
with:
global-json-file: ${{ github.workspace }}/dotnet/global.json
- name: Build functions integration test projects
shell: bash
working-directory: dotnet
run: |
dotnet build ./tests/Microsoft.Agents.AI.DurableTask.IntegrationTests -c Release -f net10.0 --warnaserror
dotnet build ./tests/Microsoft.Agents.AI.Hosting.AzureFunctions.IntegrationTests -c Release -f net10.0 --warnaserror
- name: Azure CLI Login
uses: azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Set up Durable Task and Azure Functions Integration Test Emulators
uses: ./.github/actions/azure-functions-integration-setup
id: azure-functions-setup
- name: Run Functions Integration Tests
shell: pwsh
working-directory: dotnet
run: |
# Run DurableTask integration tests
dotnet test `
--project ./tests/Microsoft.Agents.AI.DurableTask.IntegrationTests `
-f net10.0 `
-c Release `
--no-build -v Normal `
--report-xunit-trx `
--report-junit `
--results-directory ../IntegrationTestResults/ `
--ignore-exit-code 8 `
--filter-not-trait "Category=IntegrationDisabled" `
--parallel-algorithm aggressive `
--max-threads 2.0x
# Run AzureFunctions integration tests
dotnet test `
--project ./tests/Microsoft.Agents.AI.Hosting.AzureFunctions.IntegrationTests `
-f net10.0 `
-c Release `
--no-build -v Normal `
--report-xunit-trx `
--report-junit `
--results-directory ../IntegrationTestResults/ `
--ignore-exit-code 8 `
--filter-not-trait "Category=IntegrationDisabled" `
--parallel-algorithm aggressive `
--max-threads 2.0x
env:
# OpenAI Models
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_CHAT_MODEL_NAME: ${{ vars.OPENAI_CHAT_MODEL_NAME }}
OPENAI_REASONING_MODEL_NAME: ${{ vars.OPENAI_REASONING_MODEL_NAME }}
# Azure OpenAI Models
AZURE_OPENAI_DEPLOYMENT_NAME: ${{ vars.AZURE_OPENAI_DEPLOYMENT_NAME }}
AZURE_OPENAI_CHAT_DEPLOYMENT_NAME: ${{ vars.AZURE_OPENAI_DEPLOYMENT_NAME }}
AZURE_OPENAI_ENDPOINT: ${{ vars.AZURE_OPENAI_ENDPOINT }}
# Azure AI Foundry
AZURE_AI_PROJECT_ENDPOINT: ${{ vars.AZURE_AI_PROJECT_ENDPOINT }}
AZURE_AI_MODEL_DEPLOYMENT_NAME: ${{ vars.AZURE_AI_MODEL_DEPLOYMENT_NAME }}
AZURE_AI_BING_CONNECTION_ID: ${{ vars.AZURE_AI_BING_CONNECTION_ID }}
- name: Upload functions test results
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: dotnet-test-results-functions-net10.0-ubuntu-latest
path: IntegrationTestResults/**/*.junit
if-no-files-found: ignore
# This final job is required to satisfy the merge queue. It must only run (or succeed) if no tests failed
dotnet-build-and-test-check:
if: always()
runs-on: ubuntu-latest
needs: [dotnet-build, dotnet-test, dotnet-foundry-hosted-it, dotnet-test-functions]
needs: [dotnet-build, dotnet-test, dotnet-foundry-hosted-it]
steps:
- name: Get Date
shell: bash
@@ -591,13 +482,13 @@ jobs:
github.event_name != 'pull_request' &&
(contains(join(needs.*.result, ','), 'success') ||
contains(join(needs.*.result, ','), 'failure'))
needs: [dotnet-test, dotnet-test-functions]
needs: [dotnet-test]
runs-on: ubuntu-latest
defaults:
run:
working-directory: python
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
sparse-checkout: |
@@ -609,12 +500,12 @@ jobs:
python-version: "3.13"
os: ${{ runner.os }}
- name: Download all test results from current run
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: dotnet-test-results-*
path: dotnet-test-results/
- name: Restore report history cache
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: python/dotnet-integration-report-history.json
key: dotnet-integration-report-history-${{ github.run_id }}
@@ -631,7 +522,7 @@ jobs:
run: cat dotnet-integration-test-report.md >> $GITHUB_STEP_SUMMARY
- name: Save report history cache
if: always()
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: python/dotnet-integration-report-history.json
key: dotnet-integration-report-history-${{ github.run_id }}
+11 -4
View File
@@ -30,7 +30,7 @@ jobs:
steps:
- name: Check out code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
@@ -54,11 +54,14 @@ jobs:
- name: Find csproj files
id: find-csproj
if: github.event_name != 'pull_request' || steps.changed-files.outputs.added_modified != '' || steps.changed-files.outcome == 'failure'
env:
ADDED_MODIFIED: ${{ steps.changed-files.outputs.added_modified }}
run: |
csproj_files=()
exclude_files=("Experimental.Orchestration.Flow.csproj" "Experimental.Orchestration.Flow.UnitTests.csproj" "Experimental.Orchestration.Flow.IntegrationTests.csproj")
set -f
if [[ ${{ steps.changed-files.outcome }} == 'success' ]]; then
for file in ${{ steps.changed-files.outputs.added_modified }}; do
for file in $ADDED_MODIFIED; do
echo "$file was changed"
dir="./$file"
while [[ $dir != "." && $dir != "/" && $dir != $GITHUB_WORKSPACE ]]; do
@@ -80,6 +83,7 @@ jobs:
csproj_files=($(printf "%s\n" "${csproj_files[@]}" | sort -u))
echo "Found ${#csproj_files[@]} unique csproj/slnx files: ${csproj_files[*]}"
echo "csproj_files=${csproj_files[*]}" >> $GITHUB_OUTPUT
set +f
- name: Pull container dotnet/sdk:${{ matrix.dotnet }}
if: steps.find-csproj.outputs.csproj_files != ''
@@ -88,8 +92,11 @@ jobs:
# This step will run dotnet format on each of the unique csproj files and fail if any changes are made
- name: Run dotnet format
if: steps.find-csproj.outputs.csproj_files != ''
env:
CSPROJ_FILES: ${{ steps.find-csproj.outputs.csproj_files }}
run: |
for csproj in ${{ steps.find-csproj.outputs.csproj_files }}; do
set -f
for csproj in $CSPROJ_FILES; do
echo "Running dotnet format on $csproj"
docker run --rm -v $(pwd):/app -w /app mcr.microsoft.com/dotnet/sdk:${{ matrix.dotnet }} /bin/sh -c "dotnet format $csproj --verify-no-changes --verbosity diagnostic"
docker run --rm -v "$(pwd):/app" -w /app mcr.microsoft.com/dotnet/sdk:${{ matrix.dotnet }} dotnet format "$csproj" --verify-no-changes --verbosity diagnostic
done
+18 -7
View File
@@ -9,16 +9,30 @@ on:
workflow_call:
inputs:
checkout-ref:
description: "Git ref to checkout (e.g., refs/pull/123/head)"
description: "Immutable commit SHA to check out"
required: true
type: string
secrets:
AZURE_CLIENT_ID:
required: true
AZURE_TENANT_ID:
required: true
AZURE_SUBSCRIPTION_ID:
required: true
AZUREAI__ENDPOINT:
required: true
OPENAI__APIKEY:
required: true
permissions:
contents: read
id-token: write
jobs:
dotnet-integration-tests:
permissions:
copilot-requests: write
contents: read
id-token: write
strategy:
fail-fast: false
matrix:
@@ -29,7 +43,7 @@ jobs:
environment: integration
timeout-minutes: 60
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ inputs.checkout-ref }}
persist-credentials: false
@@ -69,10 +83,6 @@ jobs:
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Set up Durable Task and Azure Functions Integration Test Emulators
if: matrix.os == 'ubuntu-latest'
uses: ./.github/actions/azure-functions-integration-setup
- name: Run Integration Tests
shell: bash
run: |
@@ -88,6 +98,7 @@ jobs:
env:
COSMOSDB_ENDPOINT: https://localhost:8081
COSMOSDB_KEY: C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==
GITHUB_TOKEN: ${{ github.token }}
OpenAI__ApiKey: ${{ secrets.OPENAI__APIKEY }}
OpenAI__ChatModelId: ${{ vars.OPENAI__CHATMODELID }}
OpenAI__ChatReasoningModelId: ${{ vars.OPENAI__CHATREASONINGMODELID }}
+5 -2
View File
@@ -41,7 +41,7 @@ jobs:
environment: 'integration'
timeout-minutes: 90
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
sparse-checkout: |
@@ -105,10 +105,13 @@ jobs:
AZURE_OPENAI_DEPLOYMENT_NAME: ${{ vars.AZURE_OPENAI_DEPLOYMENT_NAME }}
AZURE_OPENAI_CHAT_DEPLOYMENT_NAME: ${{ vars.AZURE_OPENAI_DEPLOYMENT_NAME }}
AZURE_OPENAI_ENDPOINT: ${{ vars.AZURE_OPENAI_ENDPOINT }}
# Azure AI Foundry
# Microsoft Foundry
AZURE_AI_PROJECT_ENDPOINT: ${{ vars.AZURE_AI_PROJECT_ENDPOINT }}
AZURE_AI_MODEL_DEPLOYMENT_NAME: ${{ vars.AZURE_AI_MODEL_DEPLOYMENT_NAME }}
AZURE_AI_BING_CONNECTION_ID: ${{ vars.AZURE_AI_BING_CONNECTION_ID }}
# Foundry
FOUNDRY_PROJECT_ENDPOINT: ${{ vars.FOUNDRY_PROJECT_ENDPOINT }}
FOUNDRY_MODEL: ${{ vars.FOUNDRY_MODEL }}
- name: Write Job Summary
if: always()
@@ -0,0 +1,42 @@
name: GitHub automation tests
on:
pull_request:
paths:
- ".github/actions/**"
- ".github/scripts/**"
- ".github/tests/**"
- ".github/workflows/python-test-coverage.yml"
- ".github/workflows/github-automation-tests.yml"
push:
branches:
- main
paths:
- ".github/actions/**"
- ".github/scripts/**"
- ".github/tests/**"
- ".github/workflows/python-test-coverage.yml"
- ".github/workflows/github-automation-tests.yml"
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: "22"
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.11"
- name: Run JavaScript tests
run: node --test .github/tests/*.js
- name: Run Python tests
run: python .github/tests/test_python_check_coverage.py
+53 -52
View File
@@ -3,7 +3,7 @@
# Go to Actions → "Integration Tests (Manual)" → Run workflow → enter a PR number or branch name.
#
# It calls dedicated integration-only workflows (dotnet-integration-tests and python-integration-tests),
# passing a ref so they check out and test the correct code.
# passing an immutable commit SHA so they check out and test the approved code.
# Changed paths are detected here so only the relevant test suites run.
#
@@ -26,7 +26,6 @@ on:
permissions:
contents: read
pull-requests: read
id-token: write
concurrency:
group: integration-tests-manual-${{ github.event.inputs.pr-number || github.event.inputs.branch }}
@@ -38,67 +37,50 @@ jobs:
runs-on: ubuntu-latest
outputs:
checkout-ref: ${{ steps.resolve.outputs.checkout-ref }}
base-ref: ${{ steps.resolve.outputs.base-ref }}
dotnet-changes: ${{ steps.detect-changes.outputs.dotnet }}
python-changes: ${{ steps.detect-changes.outputs.python }}
steps:
- name: Resolve checkout ref
- name: Check out trusted workflow helpers
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.sha }}
persist-credentials: false
sparse-checkout: .github/scripts
- name: Resolve and authorize checkout ref
id: resolve
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const resolveIntegrationTestTarget = require(
'./.github/scripts/resolve_integration_test_target.js'
);
const target = await resolveIntegrationTestTarget({
github,
context,
core,
prNumber: process.env.PR_NUMBER,
branch: process.env.BRANCH,
});
core.setOutput('checkout-ref', target.checkoutRef);
core.setOutput('base-ref', target.baseRef);
core.info(`Running integration tests for ${target.description} at ${target.checkoutRef}.`);
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.inputs.pr-number }}
BRANCH: ${{ github.event.inputs.branch }}
REPO: ${{ github.repository }}
run: |
if [ -n "$PR_NUMBER" ] && [ -n "$BRANCH" ]; then
echo "::error::Please provide either a PR number or a branch name, not both."
exit 1
fi
if [ -z "$PR_NUMBER" ] && [ -z "$BRANCH" ]; then
echo "::error::Please provide either a PR number or a branch name."
exit 1
fi
if [ -n "$PR_NUMBER" ]; then
if ! echo "$PR_NUMBER" | grep -Eq '^[0-9]+$'; then
echo "::error::Invalid PR number. Only numeric values are allowed."
exit 1
fi
PR_DATA=$(gh pr view "$PR_NUMBER" --repo "$REPO" --json state)
PR_STATE=$(echo "$PR_DATA" | jq -r '.state')
if [ "$PR_STATE" != "OPEN" ]; then
echo "::error::PR #$PR_NUMBER is not open (state: $PR_STATE)"
exit 1
fi
echo "checkout-ref=refs/pull/$PR_NUMBER/head" >> "$GITHUB_OUTPUT"
echo "Running integration tests for PR #$PR_NUMBER"
else
if ! echo "$BRANCH" | grep -Eq '^[a-zA-Z0-9_./-]+$'; then
echo "::error::Invalid branch name. Only alphanumeric characters, hyphens, underscores, dots, and slashes are allowed."
exit 1
fi
echo "checkout-ref=$BRANCH" >> "$GITHUB_OUTPUT"
echo "Running integration tests for branch $BRANCH"
fi
- name: Detect changed paths
id: detect-changes
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.inputs.pr-number }}
BRANCH: ${{ github.event.inputs.branch }}
BASE_REF: ${{ steps.resolve.outputs.base-ref }}
CHECKOUT_REF: ${{ steps.resolve.outputs.checkout-ref }}
REPO: ${{ github.repository }}
run: |
if [ -n "$PR_NUMBER" ]; then
CHANGED_FILES=$(gh pr diff "$PR_NUMBER" --repo "$REPO" --name-only)
else
# For branches, compare against main using the GitHub API
CHANGED_FILES=$(gh api "repos/$REPO/compare/main...$BRANCH" --jq '.files[].filename')
fi
CHANGED_FILES=$(gh api "repos/$REPO/compare/$BASE_REF...$CHECKOUT_REF" \
--jq '.files[].filename')
DOTNET_CHANGES=false
PYTHON_CHANGES=false
@@ -113,22 +95,41 @@ jobs:
echo "dotnet=$DOTNET_CHANGES" >> "$GITHUB_OUTPUT"
echo "python=$PYTHON_CHANGES" >> "$GITHUB_OUTPUT"
echo "Detected changes dotnet: $DOTNET_CHANGES, python: $PYTHON_CHANGES"
echo "Detected changes; dotnet: $DOTNET_CHANGES, python: $PYTHON_CHANGES"
dotnet-integration-tests:
name: .NET Integration Tests
needs: resolve-ref
if: needs.resolve-ref.outputs.dotnet-changes == 'true'
permissions:
copilot-requests: write
contents: read
id-token: write
uses: ./.github/workflows/dotnet-integration-tests.yml
with:
checkout-ref: ${{ needs.resolve-ref.outputs.checkout-ref }}
secrets: inherit
secrets:
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
AZUREAI__ENDPOINT: ${{ secrets.AZUREAI__ENDPOINT }}
OPENAI__APIKEY: ${{ secrets.OPENAI__APIKEY }}
python-integration-tests:
name: Python Integration Tests
needs: resolve-ref
if: needs.resolve-ref.outputs.python-changes == 'true'
permissions:
copilot-requests: write
contents: read
id-token: write
uses: ./.github/workflows/python-integration-tests.yml
with:
checkout-ref: ${{ needs.resolve-ref.outputs.checkout-ref }}
secrets: inherit
secrets:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
FOUNDRY_MODELS_API_KEY: ${{ secrets.FOUNDRY_MODELS_API_KEY }}
OPENAI__APIKEY: ${{ secrets.OPENAI__APIKEY }}
+59 -18
View File
@@ -2,7 +2,13 @@ name: Issue Triage
on:
issues:
types: [opened, labeled]
types: [opened, typed]
workflow_dispatch:
inputs:
issue_number:
description: Issue number to triage
required: true
type: string
permissions:
contents: read
@@ -12,9 +18,8 @@ permissions:
concurrency:
group: >-
issue-triage-${{ github.repository }}-${{
((github.event.action == 'opened' && contains(github.event.issue.labels.*.name, 'bug'))
|| (github.event.action == 'labeled' && github.event.label.name == 'bug'))
&& github.event.issue.number
github.event_name == 'workflow_dispatch' && inputs.issue_number
|| github.event.issue.type.name == 'Bug' && github.event.issue.number
|| github.run_id
}}
cancel-in-progress: true
@@ -24,11 +29,17 @@ env:
DEVFLOW_REF: main
TARGET_REPO_PATH: ${{ github.workspace }}/target-repo
DEVFLOW_PATH: ${{ github.workspace }}/devflow
MODEL_CONFIG_PATH: ${{ github.workspace }}/devflow/config.ci.yaml
jobs:
team_check:
runs-on: ubuntu-latest
if: ${{ (github.event.action == 'opened' && contains(github.event.issue.labels.*.name, 'bug')) || (github.event.action == 'labeled' && github.event.label.name == 'bug') }}
environment: github-app-auth
if: >-
${{
github.event_name == 'workflow_dispatch'
|| github.event.issue.type.name == 'Bug'
}}
outputs:
is_team_member: ${{ steps.check.outputs.is_team_member }}
issue_number: ${{ steps.issue.outputs.issue_number }}
@@ -38,14 +49,18 @@ jobs:
id: issue
shell: bash
env:
ISSUE_NUMBER_EVENT: ${{ github.event.issue.number }}
ISSUE_NUMBER: >-
${{
github.event_name == 'workflow_dispatch' && inputs.issue_number
|| github.event.issue.number
}}
run: |
set -euo pipefail
issue_number="${ISSUE_NUMBER_EVENT}"
issue_number="${ISSUE_NUMBER}"
if [[ ! "$issue_number" =~ ^[1-9][0-9]*$ ]]; then
echo "Could not determine issue number from event payload." >&2
echo "Could not determine issue number from event payload or manual input." >&2
exit 1
fi
@@ -53,20 +68,38 @@ jobs:
echo "repo=${GITHUB_REPOSITORY}" >> "$GITHUB_OUTPUT"
- name: Checkout scripts
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
sparse-checkout: .github/scripts
sparse-checkout: |
.github/actions/github-app-token
.github/scripts
fetch-depth: 1
persist-credentials: false
- name: Get GitHub automation token
id: github-auth
uses: ./.github/actions/github-app-token
with:
mode: ${{ vars.GH_APP_AUTH_MODE }}
azure-client-id: ${{ secrets.GH_APP_AZURE_CLIENT_ID }}
azure-tenant-id: ${{ secrets.GH_APP_AZURE_TENANT_ID }}
azure-subscription-id: ${{ secrets.GH_APP_AZURE_SUBSCRIPTION_ID }}
key-vault-name: ${{ secrets.GH_APP_KEY_VAULT_NAME }}
key-name: ${{ secrets.GH_APP_KEY_NAME }}
github-app-client-id: ${{ secrets.GH_APP_CLIENT_ID }}
github-app-installation-id: ${{ secrets.GH_APP_INSTALLATION_ID }}
repository: ${{ github.repository }}
fallback-token: ${{ secrets.GH_ACTIONS_PR_WRITE }}
- name: Check issue author team membership
if: ${{ github.event_name != 'workflow_dispatch' }}
id: check
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
env:
TEAM_NAME: ${{ secrets.DEVELOPER_TEAM }}
ISSUE_NUMBER: ${{ steps.issue.outputs.issue_number }}
with:
github-token: ${{ secrets.GH_ACTIONS_PR_WRITE }}
github-token: ${{ steps.github-auth.outputs.token }}
script: |
const checkTeamMembership = require('./.github/scripts/check_team_membership.js');
const { author, isTeamMember } = await checkTeamMembership({
@@ -86,14 +119,23 @@ jobs:
triage:
runs-on: ubuntu-latest
needs: team_check
if: ${{ needs.team_check.outputs.is_team_member == 'false' }}
if: >-
${{
github.event_name == 'workflow_dispatch'
|| needs.team_check.outputs.is_team_member == 'false'
}}
environment: integration
permissions:
copilot-requests: write
contents: read
id-token: write
issues: write
timeout-minutes: 60
steps:
# Safe checkout: base repo only.
- name: Checkout target repo base
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
@@ -101,7 +143,7 @@ jobs:
# Private DevFlow (maf-dashboard) checkout.
- name: Checkout DevFlow
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ env.DEVFLOW_REPOSITORY }}
ref: ${{ env.DEVFLOW_REF }}
@@ -116,7 +158,7 @@ jobs:
python-version: "3.13"
- name: Set up uv
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
version: "0.11.x"
enable-cache: true
@@ -136,7 +178,7 @@ jobs:
id: spam
working-directory: ${{ env.DEVFLOW_PATH }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ github.token }}
DEVFLOW_TOKEN: ${{ secrets.DEVFLOW_TOKEN }}
SK_REPO_PATH: ${{ env.TARGET_REPO_PATH }}
AGENT_REPO_PATH: ${{ env.TARGET_REPO_PATH }}
@@ -161,8 +203,7 @@ jobs:
id: repro
working-directory: ${{ env.DEVFLOW_PATH }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_COPILOT_TOKEN: ${{ secrets.GH_COPILOT_TOKEN }}
GITHUB_TOKEN: ${{ github.token }}
# Not seen by the agent prompt; used only to push a paper-trail
# branch back to maf-dashboard at run end.
DEVFLOW_TOKEN: ${{ secrets.DEVFLOW_TOKEN }}
+37 -19
View File
@@ -10,12 +10,39 @@ jobs:
name: "Issue: add labels"
if: ${{ github.event.action == 'opened' || github.event.action == 'reopened' }}
runs-on: ubuntu-latest
environment: github-app-auth
permissions:
contents: read
id-token: write
issues: write
steps:
- name: Checkout GitHub automation
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
sparse-checkout: |
.github/actions/github-app-token
.github/scripts/check_team_membership.js
fetch-depth: 1
persist-credentials: false
- name: Get GitHub automation token
id: github-auth
uses: ./.github/actions/github-app-token
with:
mode: ${{ vars.GH_APP_AUTH_MODE }}
azure-client-id: ${{ secrets.GH_APP_AZURE_CLIENT_ID }}
azure-tenant-id: ${{ secrets.GH_APP_AZURE_TENANT_ID }}
azure-subscription-id: ${{ secrets.GH_APP_AZURE_SUBSCRIPTION_ID }}
key-vault-name: ${{ secrets.GH_APP_KEY_VAULT_NAME }}
key-name: ${{ secrets.GH_APP_KEY_NAME }}
github-app-client-id: ${{ secrets.GH_APP_CLIENT_ID }}
github-app-installation-id: ${{ secrets.GH_APP_INSTALLATION_ID }}
repository: ${{ github.repository }}
fallback-token: ${{ secrets.GH_ACTIONS_PR_WRITE }}
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
github-token: ${{ secrets.GH_ACTIONS_PR_WRITE }}
github-token: ${{ steps.github-auth.outputs.token }}
script: |
// Get the issue body and title
const body = context.payload.issue.body
@@ -24,21 +51,14 @@ jobs:
// Define the labels array
let labels = []
// Check if the issue author is in the agentframework-developers team
let isTeamMember = false
try {
const teamMembership = await github.rest.teams.getMembershipForUserInOrg({
org: context.repo.owner,
team_slug: process.env.TEAM_NAME,
username: context.payload.issue.user.login
})
console.log("Team Membership Data:", teamMembership);
isTeamMember = teamMembership.data.state === 'active'
} catch (error) {
// User is not in the team or team doesn't exist
console.error("Error fetching team membership:", error);
isTeamMember = false
}
const checkTeamMembership = require('./.github/scripts/check_team_membership.js')
const { isTeamMember } = await checkTeamMembership({
github,
context,
core,
teamSlug: process.env.TEAM_NAME,
issueNumber: context.issue.number,
})
// Only add triage label if the author is not in the team
if (!isTeamMember) {
@@ -90,9 +110,7 @@ jobs:
// Check for issue type from issue form dropdown
const issueTypeField = getFormFieldValue(body, 'Type of Issue')
if (issueTypeField) {
if (issueTypeField === 'Bug') {
labels.push("bug")
} else if (issueTypeField === 'Feature Request') {
if (issueTypeField === 'Feature Request') {
labels.push("enhancement")
} else if (issueTypeField === 'Question') {
labels.push("question")
+40 -2
View File
@@ -6,16 +6,54 @@
# https://github.com/actions/labeler
name: Label pull request
on: [pull_request_target]
on:
pull_request_target:
types: [opened, synchronize, reopened, edited]
jobs:
add_label:
runs-on: ubuntu-latest
environment: github-app-auth
permissions:
contents: read
id-token: write
issues: write
pull-requests: write
steps:
- name: Checkout scripts
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.event.pull_request.base.sha }}
sparse-checkout: |
.github/actions/github-app-token
.github/scripts
fetch-depth: 1
persist-credentials: false
- name: Get GitHub automation token
id: github-auth
uses: ./.github/actions/github-app-token
with:
mode: ${{ vars.GH_APP_AUTH_MODE }}
azure-client-id: ${{ secrets.GH_APP_AZURE_CLIENT_ID }}
azure-tenant-id: ${{ secrets.GH_APP_AZURE_TENANT_ID }}
azure-subscription-id: ${{ secrets.GH_APP_AZURE_SUBSCRIPTION_ID }}
key-vault-name: ${{ secrets.GH_APP_KEY_VAULT_NAME }}
key-name: ${{ secrets.GH_APP_KEY_NAME }}
github-app-client-id: ${{ secrets.GH_APP_CLIENT_ID }}
github-app-installation-id: ${{ secrets.GH_APP_INSTALLATION_ID }}
repository: ${{ github.repository }}
fallback-token: ${{ secrets.GH_ACTIONS_PR_WRITE }}
- uses: actions/labeler@f27b608878404679385c85cfa523b85ccb86e213 # v6
with:
repo-token: "${{ secrets.GH_ACTIONS_PR_WRITE }}"
repo-token: ${{ steps.github-auth.outputs.token }}
- name: "PR: add breaking change label from title"
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
github-token: ${{ steps.github-auth.outputs.token }}
script: |
const { syncBreakingChangeLabelFromTitle } = require('./.github/scripts/title_prefix.js');
await syncBreakingChangeLabelFromTitle({ github, context, core });
+9 -50
View File
@@ -15,58 +15,17 @@ jobs:
pull-requests: write
steps:
- name: Checkout scripts
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
sparse-checkout: .github/scripts
fetch-depth: 1
persist-credentials: false
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
name: "Issue/PR: update title"
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
let prefixLabels = {
"python": "Python",
".NET": ".NET"
};
function addTitlePrefix(title, prefix)
{
// Update the title based on the label and prefix
// Check if the title starts with the prefix (case-sensitive)
if (!title.startsWith(prefix + ": ")) {
// If not, check if the first word is the label (case-insensitive)
if (title.match(new RegExp(`^${prefix}`, 'i'))) {
// If yes, replace it with the prefix (case-sensitive)
title = title.replace(new RegExp(`^${prefix}`, 'i'), prefix);
} else {
// If not, prepend the prefix to the title
title = prefix + ": " + title;
}
}
return title;
}
labelAdded = context.payload.label.name
// Check if the issue or PR has the label
if (labelAdded in prefixLabels) {
let prefix = prefixLabels[labelAdded];
switch(context.eventName) {
case 'issues':
github.rest.issues.update({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
title: addTitlePrefix(context.payload.issue.title, prefix)
});
break
case 'pull_request_target':
github.rest.pulls.update({
pull_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
title: addTitlePrefix(context.payload.pull_request.title, prefix)
});
break
default:
core.setFailed('Unrecognited eventName: ' + context.eventName);
}
}
const { updateTitleForAddedLabel } = require('./.github/scripts/title_prefix.js');
await updateTitleForAddedLabel({ github, context, core });
+45 -6
View File
@@ -6,6 +6,7 @@ on:
permissions:
contents: read
id-token: write
issues: write
pull-requests: write
@@ -21,16 +22,35 @@ env:
jobs:
team_check:
runs-on: ubuntu-latest
environment: github-app-auth
outputs:
is_team_member: ${{ steps.check.outputs.is_team_member }}
steps:
- name: Checkout scripts
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
sparse-checkout: .github/scripts
ref: ${{ github.event.pull_request.base.sha }}
sparse-checkout: |
.github/actions/github-app-token
.github/scripts
fetch-depth: 1
persist-credentials: false
- name: Get GitHub automation token
id: github-auth
uses: ./.github/actions/github-app-token
with:
mode: ${{ vars.GH_APP_AUTH_MODE }}
azure-client-id: ${{ secrets.GH_APP_AZURE_CLIENT_ID }}
azure-tenant-id: ${{ secrets.GH_APP_AZURE_TENANT_ID }}
azure-subscription-id: ${{ secrets.GH_APP_AZURE_SUBSCRIPTION_ID }}
key-vault-name: ${{ secrets.GH_APP_KEY_VAULT_NAME }}
key-name: ${{ secrets.GH_APP_KEY_NAME }}
github-app-client-id: ${{ secrets.GH_APP_CLIENT_ID }}
github-app-installation-id: ${{ secrets.GH_APP_INSTALLATION_ID }}
repository: ${{ github.repository }}
fallback-token: ${{ secrets.GH_ACTIONS_PR_WRITE }}
- name: Check PR author team membership
id: check
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
@@ -38,7 +58,7 @@ jobs:
TEAM_NAME: ${{ secrets.DEVELOPER_TEAM }}
PR_NUMBER: ${{ github.event.pull_request.number }}
with:
github-token: ${{ secrets.GH_ACTIONS_PR_WRITE }}
github-token: ${{ steps.github-auth.outputs.token }}
script: |
const checkTeamMembership = require('./.github/scripts/check_team_membership.js');
const { author, isTeamMember } = await checkTeamMembership({
@@ -57,20 +77,39 @@ jobs:
limit_open_prs:
runs-on: ubuntu-latest
environment: github-app-auth
needs: team_check
if: ${{ needs.team_check.outputs.is_team_member == 'false' }}
steps:
- name: Checkout scripts
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
sparse-checkout: .github/scripts
ref: ${{ github.event.pull_request.base.sha }}
sparse-checkout: |
.github/actions/github-app-token
.github/scripts
fetch-depth: 1
persist-credentials: false
- name: Get GitHub automation token
id: github-auth
uses: ./.github/actions/github-app-token
with:
mode: ${{ vars.GH_APP_AUTH_MODE }}
azure-client-id: ${{ secrets.GH_APP_AZURE_CLIENT_ID }}
azure-tenant-id: ${{ secrets.GH_APP_AZURE_TENANT_ID }}
azure-subscription-id: ${{ secrets.GH_APP_AZURE_SUBSCRIPTION_ID }}
key-vault-name: ${{ secrets.GH_APP_KEY_VAULT_NAME }}
key-name: ${{ secrets.GH_APP_KEY_NAME }}
github-app-client-id: ${{ secrets.GH_APP_CLIENT_ID }}
github-app-installation-id: ${{ secrets.GH_APP_INSTALLATION_ID }}
repository: ${{ github.repository }}
fallback-token: ${{ secrets.GH_ACTIONS_PR_WRITE }}
- name: Enforce open PR limit
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
github-token: ${{ secrets.GH_ACTIONS_PR_WRITE }}
github-token: ${{ steps.github-auth.outputs.token }}
script: |
const { enforcePrLimit } = require('./.github/scripts/pr_limit_moderation.js');
await enforcePrLimit({
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-22.04
# check out the latest version of the code
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
+1 -1
View File
@@ -27,7 +27,7 @@ jobs:
# "Cleanup artifacts", "Agent", "Prepare", and "Upload results" are check runs
# created by an org-level GitHub App (MSDO), not by any workflow in this repo.
# They are outside our control and their transient failures should not block merges.
IGNORED_NAMES: "CodeQL,CodeQL analysis (csharp),Cleanup artifacts,Agent,Prepare,Upload results"
IGNORED_NAMES: "CodeQL,CodeQL analysis (csharp),Cleanup artifacts,Agent,Prepare,Upload results,review"
with:
script: |
const timeoutSeconds = Number(process.env.TIMEOUT_SECONDS);
-382
View File
@@ -1,382 +0,0 @@
#!/usr/bin/env python3
# Copyright (c) Microsoft. All rights reserved.
"""Check Python test coverage against threshold for enforced targets.
This script parses a Cobertura XML coverage report and enforces a minimum
coverage threshold on specific targets. Targets can be package names
(e.g., "packages.core.agent_framework") or individual Python file paths
(e.g., "packages/core/agent_framework/observability.py").
Non-enforced targets are reported for visibility but don't block the build.
Usage:
python python-check-coverage.py <coverage-xml-path> <threshold>
Example:
python python-check-coverage.py python-coverage.xml 85
"""
import sys
import xml.etree.ElementTree as ET
from dataclasses import dataclass
# =============================================================================
# ENFORCED TARGETS CONFIGURATION
# =============================================================================
# Add or remove entries from this set to control which targets must meet
# the coverage threshold. Only these targets will fail the build if below
# threshold. Other targets are reported for visibility only.
#
# Target values can be:
# - Package paths as they appear in the coverage report
# (e.g., "packages.azure-ai.agent_framework_azure_ai")
# - Python source file paths as they appear in the coverage report
# (e.g., "packages/core/agent_framework/observability.py")
# =============================================================================
ENFORCED_TARGETS: set[str] = {
# Packages (sorted alphabetically)
"packages.anthropic.agent_framework_anthropic",
"packages.azure-ai-search.agent_framework_azure_ai_search",
"packages.core.agent_framework",
"packages.core.agent_framework._workflows",
"packages.foundry.agent_framework_foundry",
"packages.openai.agent_framework_openai",
"packages.purview.agent_framework_purview",
# Individual files (if you want to enforce specific files instead of whole packages)
"packages/core/agent_framework/observability.py",
# Add more targets here as coverage improves
}
@dataclass
class PackageCoverage:
"""Coverage data for a single package."""
name: str
line_rate: float
branch_rate: float
lines_valid: int
lines_covered: int
branches_valid: int
branches_covered: int
@property
def line_coverage_percent(self) -> float:
"""Return line coverage as a percentage."""
return self.line_rate * 100
@property
def branch_coverage_percent(self) -> float:
"""Return branch coverage as a percentage."""
return self.branch_rate * 100
def normalize_coverage_path(path: str) -> str:
"""Normalize coverage paths for reliable matching."""
return path.replace("\\", "/").lstrip("./")
def parse_coverage_xml(
xml_path: str,
) -> tuple[dict[str, PackageCoverage], dict[str, PackageCoverage], float, float]:
"""Parse Cobertura XML and extract per-package coverage data.
Args:
xml_path: Path to the Cobertura XML coverage report.
Returns:
A tuple of (packages_dict, files_dict, overall_line_rate, overall_branch_rate).
"""
tree = ET.parse(xml_path)
root = tree.getroot()
# Get overall coverage from root element
overall_line_rate = float(root.get("line-rate", 0))
overall_branch_rate = float(root.get("branch-rate", 0))
packages: dict[str, PackageCoverage] = {}
file_stats: dict[str, dict[str, int]] = {}
for package in root.findall(".//package"):
package_path = package.get("name", "unknown")
line_rate = float(package.get("line-rate", 0))
branch_rate = float(package.get("branch-rate", 0))
# Count lines and branches from classes within this package
lines_valid = 0
lines_covered = 0
branches_valid = 0
branches_covered = 0
for class_elem in package.findall(".//class"):
file_path = normalize_coverage_path(class_elem.get("filename", ""))
if file_path and file_path not in file_stats:
file_stats[file_path] = {
"lines_valid": 0,
"lines_covered": 0,
"branches_valid": 0,
"branches_covered": 0,
}
for line in class_elem.findall(".//line"):
lines_valid += 1
if int(line.get("hits", 0)) > 0:
lines_covered += 1
if file_path:
file_stats[file_path]["lines_valid"] += 1
if int(line.get("hits", 0)) > 0:
file_stats[file_path]["lines_covered"] += 1
# Branch coverage from line elements
if line.get("branch") == "true":
condition_coverage = line.get("condition-coverage", "")
if condition_coverage:
# Parse "X% (covered/total)" format
try:
coverage_parts = (
condition_coverage.split("(")[1].rstrip(")").split("/")
)
branches_covered += int(coverage_parts[0])
branches_valid += int(coverage_parts[1])
if file_path:
file_stats[file_path]["branches_covered"] += int(
coverage_parts[0]
)
file_stats[file_path]["branches_valid"] += int(
coverage_parts[1]
)
except (IndexError, ValueError):
# Ignore malformed condition-coverage strings; treat this line as having no branch data.
pass
# Use full package path as the key (no aggregation)
packages[package_path] = PackageCoverage(
name=package_path,
line_rate=line_rate if lines_valid == 0 else lines_covered / lines_valid,
branch_rate=branch_rate
if branches_valid == 0
else branches_covered / branches_valid,
lines_valid=lines_valid,
lines_covered=lines_covered,
branches_valid=branches_valid,
branches_covered=branches_covered,
)
files: dict[str, PackageCoverage] = {}
for file_path, stats in file_stats.items():
lines_valid = stats["lines_valid"]
lines_covered = stats["lines_covered"]
branches_valid = stats["branches_valid"]
branches_covered = stats["branches_covered"]
files[file_path] = PackageCoverage(
name=file_path,
line_rate=0 if lines_valid == 0 else lines_covered / lines_valid,
branch_rate=0 if branches_valid == 0 else branches_covered / branches_valid,
lines_valid=lines_valid,
lines_covered=lines_covered,
branches_valid=branches_valid,
branches_covered=branches_covered,
)
return packages, files, overall_line_rate, overall_branch_rate
def format_coverage_value(coverage: float, threshold: float, is_enforced: bool) -> str:
"""Format a coverage value with optional pass/fail indicator.
Args:
coverage: Coverage percentage (0-100).
threshold: Minimum required coverage percentage.
is_enforced: Whether this target is enforced.
Returns:
Formatted string like "85.5%" or "85.5%" or "75.0%".
"""
formatted = f"{coverage:.1f}%"
if is_enforced:
icon = "" if coverage >= threshold else ""
formatted = f"{formatted} {icon}"
return formatted
def print_coverage_table(
packages: dict[str, PackageCoverage],
files: dict[str, PackageCoverage],
threshold: float,
overall_line_rate: float,
overall_branch_rate: float,
) -> None:
"""Print a formatted coverage summary table.
Args:
packages: Dictionary of package name to coverage data.
files: Dictionary of file path to coverage data, used for per-file enforcement.
threshold: Minimum required coverage percentage.
overall_line_rate: Overall line coverage rate (0-1).
overall_branch_rate: Overall branch coverage rate (0-1).
"""
print("\n" + "=" * 80)
print("PYTHON TEST COVERAGE REPORT")
print("=" * 80)
# Overall coverage
print(f"\nOverall Line Coverage: {overall_line_rate * 100:.1f}%")
print(f"Overall Branch Coverage: {overall_branch_rate * 100:.1f}%")
print(f"Threshold: {threshold}%")
enforced_targets = {normalize_coverage_path(t) for t in ENFORCED_TARGETS}
# Package table
print("\n" + "-" * 110)
print(f"{'Package':<80} {'Lines':<15} {'Line Cov':<15}")
print("-" * 110)
# Sort: enforced package targets first, then alphabetically
sorted_packages = sorted(
packages.values(),
key=lambda p: (p.name not in ENFORCED_TARGETS, p.name),
)
for pkg in sorted_packages:
is_enforced = normalize_coverage_path(pkg.name) in enforced_targets
enforced_marker = "[ENFORCED] " if is_enforced else ""
line_cov = format_coverage_value(
pkg.line_coverage_percent, threshold, is_enforced
)
lines_info = f"{pkg.lines_covered}/{pkg.lines_valid}"
package_label = f"{enforced_marker}{pkg.name}"
print(f"{package_label:<80} {lines_info:<15} {line_cov:<15}")
print("-" * 110)
# Enforced file/model entries (if configured)
enforced_files = [
files[target]
for target in sorted(enforced_targets)
if target in files and target.endswith(".py")
]
if enforced_files:
print("\nEnforced Files/Models")
print("-" * 110)
print(f"{'File':<80} {'Lines':<15} {'Line Cov':<15}")
print("-" * 110)
for file_cov in enforced_files:
line_cov = format_coverage_value(
file_cov.line_coverage_percent, threshold, True
)
lines_info = f"{file_cov.lines_covered}/{file_cov.lines_valid}"
print(f"[ENFORCED] {file_cov.name:<69} {lines_info:<15} {line_cov:<15}")
print("-" * 110)
def check_coverage(xml_path: str, threshold: float) -> bool:
"""Check if all enforced targets meet the coverage threshold.
Args:
xml_path: Path to the Cobertura XML coverage report.
threshold: Minimum required coverage percentage.
Returns:
True if all enforced targets pass, False otherwise.
"""
packages, files, overall_line_rate, overall_branch_rate = parse_coverage_xml(
xml_path
)
print_coverage_table(
packages, files, threshold, overall_line_rate, overall_branch_rate
)
# Check enforced targets
failed_targets: list[str] = []
missing_targets: list[str] = []
for target_name in ENFORCED_TARGETS:
normalized_target = normalize_coverage_path(target_name)
package_alias = normalized_target.replace("/", ".")
target_coverage = None
if target_name in packages:
target_coverage = packages[target_name]
elif normalized_target in files:
target_coverage = files[normalized_target]
elif package_alias in packages:
target_coverage = packages[package_alias]
if target_coverage is None:
missing_targets.append(target_name)
continue
if target_coverage.line_coverage_percent < threshold:
failed_targets.append(
f"{target_name} ({target_coverage.line_coverage_percent:.1f}%)"
)
# Report results
if missing_targets:
print(
f"\n❌ FAILED: Enforced targets not found in coverage report: {', '.join(missing_targets)}"
)
return False
if failed_targets:
print(
f"\n❌ FAILED: The following enforced targets are below {threshold}% coverage threshold:"
)
for target in failed_targets:
print(f" - {target}")
print("\nTo fix: Add more tests to improve coverage for the failing targets.")
return False
if ENFORCED_TARGETS:
found_enforced = [
target
for target in ENFORCED_TARGETS
if target in packages or normalize_coverage_path(target) in files
]
if found_enforced:
print(
f"\n✅ PASSED: All enforced targets meet the {threshold}% coverage threshold."
)
return True
def main() -> int:
"""Main entry point.
Returns:
Exit code: 0 for success, 1 for failure.
"""
if len(sys.argv) != 3:
print(f"Usage: {sys.argv[0]} <coverage-xml-path> <threshold>")
print(f"Example: {sys.argv[0]} python-coverage.xml 85")
return 1
xml_path = sys.argv[1]
try:
threshold = float(sys.argv[2])
except ValueError:
print(f"Error: Invalid threshold value: {sys.argv[2]}")
return 1
try:
success = check_coverage(xml_path, threshold)
return 0 if success else 1
except FileNotFoundError:
print(f"Error: Coverage file not found: {xml_path}")
return 1
except ET.ParseError as e:
print(f"Error: Failed to parse coverage XML: {e}")
return 1
if __name__ == "__main__":
sys.exit(main())
+14 -12
View File
@@ -6,6 +6,10 @@ on:
branches: ["main"]
paths:
- "python/**"
- "!python/AGENTS.md"
- "!python/**/AGENTS.md"
- "!python/.github/skills/*"
- "!python/.github/skills/**"
env:
# Configure a constant location for the uv cache
@@ -27,7 +31,7 @@ jobs:
env:
UV_PYTHON: ${{ matrix.python-version }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- name: Set up python and install the project
@@ -38,11 +42,11 @@ jobs:
os: ${{ runner.os }}
env:
UV_CACHE_DIR: /tmp/.uv-cache
- uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/prek
key: prek|${{ matrix.python-version }}|${{ hashFiles('python/.pre-commit-config.yaml') }}
- uses: j178/prek-action@0bb87d7f00b0c99306c8bcb8b8beba1eb581c037 # v1
- uses: j178/prek-action@bdca6f102f98e2b4c7029491a53dfd366469e33d # v2.0.4
name: Run Pre-commit Hooks (excluding poe-check)
env:
SKIP: poe-check
@@ -64,7 +68,7 @@ jobs:
env:
UV_PYTHON: ${{ matrix.python-version }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- name: Set up python and install the project
@@ -93,7 +97,7 @@ jobs:
env:
UV_PYTHON: ${{ matrix.python-version }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- name: Set up python and install the project
@@ -109,8 +113,8 @@ jobs:
- name: Run markdown code lint
run: uv run poe markdown-code-lint
mypy:
name: Mypy Checks
test-typing:
name: Test Typing Checks
if: "!cancelled()"
strategy:
fail-fast: false
@@ -124,7 +128,7 @@ jobs:
env:
UV_PYTHON: ${{ matrix.python-version }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- name: Set up python and install the project
@@ -135,7 +139,5 @@ jobs:
os: ${{ runner.os }}
env:
UV_CACHE_DIR: /tmp/.uv-cache
- name: Run Mypy
env:
GITHUB_BASE_REF: ${{ github.event.pull_request.base.ref || github.base_ref || 'main' }}
run: uv run python scripts/workspace_poe_tasks.py ci-mypy
- name: Run tests/samples type checkers (mypy, pyrefly, ty)
run: uv run python scripts/workspace_poe_tasks.py ci-test-typing
@@ -0,0 +1,431 @@
name: Python - Dependency Maintenance
on:
workflow_dispatch:
schedule:
- cron: "0 4 * * 1"
permissions:
contents: write
issues: write
concurrency:
group: python-dependency-maintenance
cancel-in-progress: false
env:
UV_CACHE_DIR: /tmp/.uv-cache
jobs:
dependency-maintenance:
name: Dependency Maintenance
runs-on: ubuntu-latest
env:
# Match the existing Python dependency maintenance workflows. Reevaluate if package
# installability starts differing across supported Python versions.
UV_PYTHON: "3.13"
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
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: Set dependency release cutoff
run: |
cutoff="$(date -u -d '7 days ago' '+%Y-%m-%dT%H:%M:%SZ')"
echo "DEPENDENCY_RELEASE_CUTOFF=${cutoff}" >> "$GITHUB_ENV"
echo "Using dependency release cutoff: ${cutoff}"
- name: Repin dev dependency declarations
run: uv run poe upgrade-dev-dependency-pins
working-directory: ./python
- name: Refresh lockfile after dev pin updates
run: uv lock
working-directory: ./python
- name: Save dev dependency changes
run: |
DEV_PATCH="${RUNNER_TEMP}/python-dev-dependency-updates.patch"
git diff -- python/pyproject.toml "python/packages/*/pyproject.toml" python/uv.lock > "${DEV_PATCH}"
if [ -s "${DEV_PATCH}" ]; then
echo "has_dev_changes=true" >> "$GITHUB_OUTPUT"
else
echo "has_dev_changes=false" >> "$GITHUB_OUTPUT"
fi
echo "patch=${DEV_PATCH}" >> "$GITHUB_OUTPUT"
id: dev_changes
- name: Run dependency bounds test scenarios
id: validate_bounds_test
continue-on-error: true
run: uv run poe validate-dependency-bounds-test --package "*"
working-directory: ./python
- name: Run dependency upper-bound validation
id: validate_ranges
if: steps.validate_bounds_test.outcome == 'success'
continue-on-error: true
run: uv run poe validate-dependency-bounds-project --mode upper --package "*"
working-directory: ./python
- name: Upload dependency validation reports
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: dependency-maintenance-results
path: |
python/scripts/dependencies/dependency-bounds-test-results.json
python/scripts/dependencies/dependency-range-results.json
if-no-files-found: warn
- name: Create issue for failed dependency bounds test
if: steps.validate_bounds_test.outcome != 'success'
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const fs = require("fs")
const reportPath = "python/scripts/dependencies/dependency-bounds-test-results.json"
const owner = context.repo.owner
const repo = context.repo.repo
const openIssues = await github.paginate(github.rest.issues.listForRepo, {
owner,
repo,
state: "open",
per_page: 100,
})
const openIssueTitles = new Set(
openIssues.filter((issue) => !issue.pull_request).map((issue) => issue.title)
)
const formatError = (message) => String(message || "No error output captured.").replace(/```/g, "'''")
const title = "Dependency bounds test failed"
if (openIssueTitles.has(title)) {
core.info(`Issue already exists: ${title}`)
return
}
const bodyLines = [
"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.",
"",
]
if (fs.existsSync(reportPath)) {
const report = JSON.parse(fs.readFileSync(reportPath, "utf8"))
const failedScenarios = (report.scenarios ?? []).filter((scenario) => scenario.status === "failed")
for (const scenario of failedScenarios) {
bodyLines.push(`### ${scenario.name} scenario (${scenario.resolution})`)
const failedPackages = (scenario.packages ?? []).filter((pkg) => pkg.status === "failed")
for (const pkg of failedPackages.slice(0, 10)) {
bodyLines.push(
"",
`- Package: \`${pkg.package_name}\``,
`- Project path: \`${pkg.project_path}\``,
"",
"```",
formatError(pkg.error).slice(0, 3500),
"```"
)
}
if (failedPackages.length > 10) {
bodyLines.push("", `_Additional failed packages omitted: ${failedPackages.length - 10}_`)
}
}
} else {
bodyLines.push(`No dependency bounds test report was found at \`${reportPath}\`.`)
}
bodyLines.push("", `Workflow run: ${context.serverUrl}/${owner}/${repo}/actions/runs/${context.runId}`)
await github.rest.issues.create({
owner,
repo,
title,
body: bodyLines.join("\n"),
})
core.info(`Created issue: ${title}`)
- name: Create issues for failed dependency candidates
if: always()
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const fs = require("fs")
const reportPath = "python/scripts/dependencies/dependency-range-results.json"
if (!fs.existsSync(reportPath)) {
core.info(`No dependency range report found at ${reportPath}`)
return
}
const report = JSON.parse(fs.readFileSync(reportPath, "utf8"))
const dependencyFailures = []
for (const packageResult of report.packages ?? []) {
for (const dependency of packageResult.dependencies ?? []) {
const candidateVersions = new Set(dependency.candidate_versions ?? [])
const failedAttempts = (dependency.attempts ?? []).filter(
(attempt) => attempt.status === "failed" && candidateVersions.has(attempt.trial_upper)
)
if (!failedAttempts.length) {
continue
}
const failuresByVersion = new Map()
for (const attempt of failedAttempts) {
const version = attempt.trial_upper || "unknown"
if (!failuresByVersion.has(version)) {
failuresByVersion.set(version, attempt.error || "No error output captured.")
}
}
dependencyFailures.push({
packageName: packageResult.package_name,
projectPath: packageResult.project_path,
dependencyName: dependency.name,
originalRequirements: dependency.original_requirements ?? [],
finalRequirements: dependency.final_requirements ?? [],
failedVersions: [...failuresByVersion.entries()].map(([version, error]) => ({ version, error })),
})
}
}
if (!dependencyFailures.length) {
core.info("No failing dependency candidates found.")
return
}
const owner = context.repo.owner
const repo = context.repo.repo
const openIssues = await github.paginate(github.rest.issues.listForRepo, {
owner,
repo,
state: "open",
per_page: 100,
})
const openIssueTitles = new Set(
openIssues.filter((issue) => !issue.pull_request).map((issue) => issue.title)
)
const formatError = (message) => String(message || "No error output captured.").replace(/```/g, "'''")
for (const failure of dependencyFailures) {
const title = `Dependency validation failed: ${failure.dependencyName} (${failure.packageName})`
if (openIssueTitles.has(title)) {
core.info(`Issue already exists: ${title}`)
continue
}
const visibleFailures = failure.failedVersions.slice(0, 5)
const omittedCount = failure.failedVersions.length - visibleFailures.length
const failureDetails = visibleFailures
.map(
(entry) =>
`- \`${entry.version}\`\n\n\`\`\`\n${formatError(entry.error).slice(0, 3500)}\n\`\`\``
)
.join("\n\n")
const body = [
"Automated dependency range validation found candidate versions that failed checks.",
"",
`- Package: \`${failure.packageName}\``,
`- Project path: \`${failure.projectPath}\``,
`- Dependency: \`${failure.dependencyName}\``,
`- Original requirements: ${
failure.originalRequirements.length
? failure.originalRequirements.map((value) => `\`${value}\``).join(", ")
: "_none_"
}`,
`- Final requirements after run: ${
failure.finalRequirements.length
? failure.finalRequirements.map((value) => `\`${value}\``).join(", ")
: "_none_"
}`,
"",
"### Failed versions and errors",
failureDetails,
omittedCount > 0 ? `\n_Additional failed versions omitted: ${omittedCount}_` : "",
"",
`Workflow run: ${context.serverUrl}/${owner}/${repo}/actions/runs/${context.runId}`,
].join("\n")
await github.rest.issues.create({
owner,
repo,
title,
body,
})
openIssueTitles.add(title)
core.info(`Created issue: ${title}`)
}
- name: Keep only dev updates when range validation fails
if: steps.validate_bounds_test.outcome != 'success' || steps.validate_ranges.outcome != 'success'
env:
DEV_PATCH: ${{ steps.dev_changes.outputs.patch }}
HAS_DEV_CHANGES: ${{ steps.dev_changes.outputs.has_dev_changes }}
run: |
git restore python/pyproject.toml python/packages/*/pyproject.toml python/uv.lock
if [ "${HAS_DEV_CHANGES}" = "true" ]; then
git apply "${DEV_PATCH}"
fi
- name: Refresh lockfile after dependency range updates
if: steps.validate_bounds_test.outcome == 'success' && steps.validate_ranges.outcome == 'success'
run: uv lock
working-directory: ./python
- name: Install final dependency set
run: uv run poe install
working-directory: ./python
- name: Run final checks
run: uv run poe check
working-directory: ./python
- name: Run final typing
run: uv run poe typing
working-directory: ./python
- name: Commit and push dependency updates
id: commit_updates
run: |
BRANCH="automation/python-dependency-maintenance"
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git checkout -B "${BRANCH}"
git add python/pyproject.toml python/packages/*/pyproject.toml python/uv.lock
if git diff --cached --quiet; then
echo "has_changes=false" >> "$GITHUB_OUTPUT"
echo "No dependency updates to commit."
exit 0
fi
git commit -m "Python: chore: update dependencies"
git push --force-with-lease --set-upstream origin "${BRANCH}"
echo "has_changes=true" >> "$GITHUB_OUTPUT"
- name: Create or update dependency maintenance tracking issue
if: steps.commit_updates.outputs.has_changes == 'true'
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const branch = "automation/python-dependency-maintenance"
const prTitle = "Python: chore: update dependencies"
const issueTitle = "Python dependency maintenance PR needed"
const owner = context.repo.owner
const repo = context.repo.repo
const branchRef = await github.rest.git.getRef({
owner,
repo,
ref: `heads/${branch}`,
})
const branchSha = branchRef.data.object.sha
const runUrl = `${context.serverUrl}/${owner}/${repo}/actions/runs/${context.runId}`
const compareUrl = `${context.serverUrl}/${owner}/${repo}/compare/main...${branch}`
const prBody = [
"### Motivation & Context",
"",
"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.",
"",
"### Create the PR",
"",
"```bash",
command,
"```",
"",
"### Generated PR body",
"",
prBodyFence,
prBody,
prBodyFence,
].join("\n")
const openIssues = await github.paginate(github.rest.issues.listForRepo, {
owner,
repo,
state: "open",
per_page: 100,
})
const existingIssue = openIssues.find((issue) => !issue.pull_request && issue.title === issueTitle)
if (existingIssue) {
await github.rest.issues.update({
owner,
repo,
issue_number: existingIssue.number,
title: issueTitle,
body: issueBody,
})
core.info(`Updated issue #${existingIssue.number}: ${issueTitle}`)
} else {
const createdIssue = await github.rest.issues.create({
owner,
repo,
title: issueTitle,
body: issueBody,
})
core.info(`Created issue #${createdIssue.data.number}: ${issueTitle}`)
}
@@ -1,216 +0,0 @@
# 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 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.
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: dependency-range-results
path: python/scripts/dependencies/dependency-range-results.json
if-no-files-found: warn
- name: Create issues for failed dependency candidates
# Always process the report so failed candidates create actionable tracking issues.
if: always()
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
script: |
const fs = require("fs")
const reportPath = "python/scripts/dependencies/dependency-range-results.json"
if (!fs.existsSync(reportPath)) {
core.warning(`No dependency range report found at ${reportPath}`)
return
}
const report = JSON.parse(fs.readFileSync(reportPath, "utf8"))
const dependencyFailures = []
for (const packageResult of report.packages ?? []) {
for (const dependency of packageResult.dependencies ?? []) {
const candidateVersions = new Set(dependency.candidate_versions ?? [])
const failedAttempts = (dependency.attempts ?? []).filter(
(attempt) => attempt.status === "failed" && candidateVersions.has(attempt.trial_upper)
)
if (!failedAttempts.length) {
continue
}
const failuresByVersion = new Map()
for (const attempt of failedAttempts) {
const version = attempt.trial_upper || "unknown"
if (!failuresByVersion.has(version)) {
failuresByVersion.set(version, attempt.error || "No error output captured.")
}
}
dependencyFailures.push({
packageName: packageResult.package_name,
projectPath: packageResult.project_path,
dependencyName: dependency.name,
originalRequirements: dependency.original_requirements ?? [],
finalRequirements: dependency.final_requirements ?? [],
failedVersions: [...failuresByVersion.entries()].map(([version, error]) => ({ version, error })),
})
}
}
if (!dependencyFailures.length) {
core.info("No failing dependency candidates found.")
return
}
const owner = context.repo.owner
const repo = context.repo.repo
const openIssues = await github.paginate(github.rest.issues.listForRepo, {
owner,
repo,
state: "open",
per_page: 100,
})
const openIssueTitles = new Set(
openIssues.filter((issue) => !issue.pull_request).map((issue) => issue.title)
)
const formatError = (message) => String(message || "No error output captured.").replace(/```/g, "'''")
for (const failure of dependencyFailures) {
const title = `Dependency validation failed: ${failure.dependencyName} (${failure.packageName})`
if (openIssueTitles.has(title)) {
core.info(`Issue already exists: ${title}`)
continue
}
const visibleFailures = failure.failedVersions.slice(0, 5)
const omittedCount = failure.failedVersions.length - visibleFailures.length
const failureDetails = visibleFailures
.map(
(entry) =>
`- \`${entry.version}\`\n\n\`\`\`\n${formatError(entry.error).slice(0, 3500)}\n\`\`\``
)
.join("\n\n")
const body = [
"Automated dependency range validation found candidate versions that failed checks.",
"",
`- Package: \`${failure.packageName}\``,
`- Project path: \`${failure.projectPath}\``,
`- Dependency: \`${failure.dependencyName}\``,
`- Original requirements: ${
failure.originalRequirements.length
? failure.originalRequirements.map((value) => `\`${value}\``).join(", ")
: "_none_"
}`,
`- Final requirements after run: ${
failure.finalRequirements.length
? failure.finalRequirements.map((value) => `\`${value}\``).join(", ")
: "_none_"
}`,
"",
"### Failed versions and errors",
failureDetails,
omittedCount > 0 ? `\n_Additional failed versions omitted: ${omittedCount}_` : "",
"",
`Workflow run: ${context.serverUrl}/${owner}/${repo}/actions/runs/${context.runId}`,
].join("\n")
await github.rest.issues.create({
owner,
repo,
title,
body,
})
openIssueTitles.add(title)
core.info(`Created issue: ${title}`)
}
- name: Refresh lockfile
# Only refresh lockfile after a clean validation to avoid committing known-bad ranges.
if: steps.validate_ranges.outcome == 'success'
run: uv lock --upgrade
working-directory: ./python
- name: Commit and push dependency updates
id: commit_updates
if: steps.validate_ranges.outcome == 'success'
run: |
BRANCH="automation/python-dependency-range-updates"
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git checkout -B "${BRANCH}"
git add python/packages/*/pyproject.toml python/uv.lock
if git diff --cached --quiet; then
echo "has_changes=false" >> "$GITHUB_OUTPUT"
echo "No dependency updates to commit."
exit 0
fi
git commit -m "chore: update dependency ranges"
git push --force-with-lease --set-upstream origin "${BRANCH}"
echo "has_changes=true" >> "$GITHUB_OUTPUT"
- name: Create or update pull request with GitHub CLI
# Only open/update PRs for validated updates to keep automation branches trustworthy.
if: steps.validate_ranges.outcome == 'success' && steps.commit_updates.outputs.has_changes == 'true'
run: |
BRANCH="automation/python-dependency-range-updates"
PR_TITLE="Python: chore: update dependency ranges"
PR_BODY_FILE="$(mktemp)"
cat > "${PR_BODY_FILE}" <<'EOF'
This PR was generated by the dependency range validation workflow.
- Ran `uv run poe validate-dependency-bounds-project --mode upper --package "*"`
- Updated package dependency bounds
- Refreshed `python/uv.lock` with `uv lock --upgrade`
EOF
PR_NUMBER="$(gh pr list --head "${BRANCH}" --base main --state open --json number --jq '.[0].number')"
if [ -n "${PR_NUMBER}" ]; then
gh pr edit "${PR_NUMBER}" --title "${PR_TITLE}" --body-file "${PR_BODY_FILE}"
else
gh pr create --base main --head "${BRANCH}" --title "${PR_TITLE}" --body-file "${PR_BODY_FILE}"
fi
@@ -1,91 +0,0 @@
name: Python - Dev Dependency Upgrade
on:
workflow_dispatch:
permissions:
contents: write
pull-requests: write
env:
UV_CACHE_DIR: /tmp/.uv-cache
jobs:
upgrade-dev-dependencies:
name: Upgrade Dev Dependencies
runs-on: ubuntu-latest
env:
UV_PYTHON: "3.13"
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 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: Upgrade dev dependencies and validate workspace
run: uv run poe upgrade-dev-dependencies
working-directory: ./python
- name: Commit and push dev dependency updates
id: commit_updates
run: |
BRANCH="automation/python-dev-dependency-updates"
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git checkout -B "${BRANCH}"
git add python/pyproject.toml python/packages/*/pyproject.toml python/uv.lock
if git diff --cached --quiet; then
echo "has_changes=false" >> "$GITHUB_OUTPUT"
echo "No dev dependency updates to commit."
exit 0
fi
git commit -F- <<'EOF'
Python: chore: upgrade dev dependencies
EOF
git push --force-with-lease --set-upstream origin "${BRANCH}"
echo "has_changes=true" >> "$GITHUB_OUTPUT"
- name: Create or update pull request with GitHub CLI
if: steps.commit_updates.outputs.has_changes == 'true'
run: |
BRANCH="automation/python-dev-dependency-updates"
PR_TITLE="Python: chore: upgrade dev dependencies"
PR_BODY_FILE="$(mktemp)"
cat > "${PR_BODY_FILE}" <<'EOF'
### Motivation and Context
This automated update refreshes Python dev dependency pins across the workspace and reruns the repo validation gates before opening a pull request.
### Description
- Ran `uv run poe upgrade-dev-dependencies`
- Refreshed dev dependency pins in workspace `pyproject.toml` files
- Refreshed `python/uv.lock` with `uv lock --upgrade`
- Reinstalled from the frozen lockfile and reran `check`, `typing`, and `test`
### Contribution Checklist
- [x] The code builds clean without any errors or warnings
- [x] The PR follows the [Contribution Guidelines](https://github.com/microsoft/agent-framework/blob/main/CONTRIBUTING.md)
- [x] 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.
EOF
PR_NUMBER="$(gh pr list --head "${BRANCH}" --base main --state open --json number --jq '.[0].number')"
if [ -n "${PR_NUMBER}" ]; then
gh pr edit "${PR_NUMBER}" --title "${PR_TITLE}" --body-file "${PR_BODY_FILE}"
else
gh pr create --base main --head "${BRANCH}" --title "${PR_TITLE}" --body-file "${PR_BODY_FILE}"
fi
+2 -2
View File
@@ -24,9 +24,9 @@ jobs:
run:
working-directory: python
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up uv
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
version-file: "python/pyproject.toml"
enable-cache: true
+43 -83
View File
@@ -13,13 +13,25 @@ on:
workflow_call:
inputs:
checkout-ref:
description: "Git ref to checkout (e.g., refs/pull/123/head)"
description: "Immutable commit SHA to check out"
required: true
type: string
secrets:
ANTHROPIC_API_KEY:
required: true
AZURE_CLIENT_ID:
required: true
AZURE_TENANT_ID:
required: true
AZURE_SUBSCRIPTION_ID:
required: true
FOUNDRY_MODELS_API_KEY:
required: false
OPENAI__APIKEY:
required: true
permissions:
contents: read
id-token: write
env:
UV_CACHE_DIR: /tmp/.uv-cache
@@ -36,7 +48,7 @@ jobs:
run:
working-directory: python
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ inputs.checkout-ref }}
persist-credentials: false
@@ -69,7 +81,7 @@ jobs:
run:
working-directory: python
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ inputs.checkout-ref }}
persist-credentials: false
@@ -99,6 +111,9 @@ jobs:
# Azure OpenAI integration tests
python-tests-azure-openai:
name: Python Integration Tests - Azure OpenAI
permissions:
contents: read
id-token: write
runs-on: ubuntu-latest
environment: integration
timeout-minutes: 60
@@ -112,7 +127,7 @@ jobs:
run:
working-directory: python
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ inputs.checkout-ref }}
persist-credentials: false
@@ -163,7 +178,7 @@ jobs:
run:
working-directory: python
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ inputs.checkout-ref }}
persist-credentials: false
@@ -177,7 +192,7 @@ jobs:
run: curl -fsSL https://ollama.com/install.sh | sh
working-directory: .
- name: Cache Ollama models
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.ollama/models
key: ollama-models-qwen2.5-1.5b-nomic-embed-text-v1
@@ -217,13 +232,15 @@ jobs:
fallback_url: ${{ env.LOCAL_MCP_URL }}
- name: Prefer local MCP URL when available
run: echo "LOCAL_MCP_URL=${{ steps.local-mcp.outputs.effective_url }}" >> "$GITHUB_ENV"
- name: Test with pytest (Anthropic, Hyperlight, Ollama, MCP integration)
- name: Test with pytest (Anthropic, Hyperlight, Mistral, Ollama, MCP integration)
run: >
uv run pytest --import-mode=importlib
packages/anthropic/tests
packages/hyperlight/tests
packages/mistral/tests
packages/ollama/tests
packages/core/tests/core/test_mcp.py
packages/hosting-mcp/tests
-m integration
-n logical --dist worksteal
--timeout=120 --session-timeout=900 --timeout_method thread
@@ -257,73 +274,12 @@ jobs:
done
kill -KILL -- "-$server_pid" 2>/dev/null || kill -KILL "$server_pid" 2>/dev/null || true
# Azure Functions + Durable Task integration tests
python-tests-functions:
name: Python Integration Tests - Functions
runs-on: ubuntu-latest
environment: integration
timeout-minutes: 60
env:
UV_PYTHON: "3.11"
OPENAI_CHAT_COMPLETION_MODEL: ${{ vars.OPENAI__CHATMODELID }}
OPENAI_CHAT_MODEL: ${{ vars.OPENAI__RESPONSESMODELID }}
OPENAI_MODEL: ${{ vars.OPENAI__RESPONSESMODELID }}
OPENAI_EMBEDDING_MODEL: ${{ vars.OPENAI_EMBEDDING_MODEL_ID }}
OPENAI_API_KEY: ${{ secrets.OPENAI__APIKEY }}
AZURE_OPENAI_ENDPOINT: ${{ vars.AZUREOPENAI__ENDPOINT }}
AZURE_OPENAI_MODEL: ${{ vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
AZURE_OPENAI_CHAT_MODEL: ${{ vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
AZURE_OPENAI_CHAT_COMPLETION_MODEL: ${{ vars.AZUREOPENAI__CHATDEPLOYMENTNAME }}
FOUNDRY_PROJECT_ENDPOINT: ${{ vars.FOUNDRY_PROJECT_ENDPOINT }}
FOUNDRY_MODEL: ${{ vars.FOUNDRY_MODEL }}
FUNCTIONS_WORKER_RUNTIME: "python"
DURABLE_TASK_SCHEDULER_CONNECTION_STRING: "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None"
AzureWebJobsStorage: "UseDevelopmentStorage=true"
defaults:
run:
working-directory: python
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
ref: ${{ inputs.checkout-ref }}
persist-credentials: false
- name: Set up python and install the project
id: python-setup
uses: ./.github/actions/python-setup
with:
python-version: ${{ env.UV_PYTHON }}
os: ${{ runner.os }}
- name: Azure CLI Login
uses: azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Set up Azure Functions Integration Test Emulators
uses: ./.github/actions/azure-functions-integration-setup
id: azure-functions-setup
- name: Test with pytest (Functions + Durable Task integration)
run: >
uv run pytest --import-mode=importlib
packages/azurefunctions/tests/integration_tests
packages/durabletask/tests/integration_tests
-m integration
-n logical --dist worksteal
-x
--timeout=480 --session-timeout=900 --timeout_method thread
--retries 2 --retry-delay 5
--junitxml=pytest.xml
- name: Upload test results
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: test-results-functions
path: ./python/pytest.xml
if-no-files-found: ignore
# Foundry integration tests
python-tests-foundry:
name: Python Integration Tests - Foundry
permissions:
contents: read
id-token: write
runs-on: ubuntu-latest
environment: integration
timeout-minutes: 60
@@ -341,7 +297,7 @@ jobs:
run:
working-directory: python
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ inputs.checkout-ref }}
persist-credentials: false
@@ -378,6 +334,9 @@ jobs:
# Foundry Hosting integration tests
python-tests-foundry-hosting:
name: Python Integration Tests - Foundry Hosting
permissions:
contents: read
id-token: write
runs-on: ubuntu-latest
environment: integration
timeout-minutes: 60
@@ -388,7 +347,7 @@ jobs:
run:
working-directory: python
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ inputs.checkout-ref }}
persist-credentials: false
@@ -443,7 +402,7 @@ jobs:
run:
working-directory: python
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ inputs.checkout-ref }}
persist-credentials: false
@@ -479,15 +438,18 @@ jobs:
name: Python Integration Tests - GitHub Copilot
runs-on: ubuntu-latest
environment: integration
permissions:
copilot-requests: write
contents: read
timeout-minutes: 60
env:
COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ github.token }}
GITHUB_COPILOT_TIMEOUT: "120"
defaults:
run:
working-directory: python
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ inputs.checkout-ref }}
persist-credentials: false
@@ -525,7 +487,6 @@ jobs:
python-tests-openai,
python-tests-azure-openai,
python-tests-misc-integration,
python-tests-functions,
python-tests-foundry,
python-tests-foundry-hosting,
python-tests-cosmos,
@@ -536,7 +497,7 @@ jobs:
run:
working-directory: python
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ inputs.checkout-ref }}
persist-credentials: false
@@ -546,12 +507,12 @@ jobs:
python-version: ${{ env.UV_PYTHON }}
os: ${{ runner.os }}
- name: Download all test results from current run
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: test-results-*
path: test-results/
- name: Restore report history cache
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: python/integration-report-history.json
key: integration-report-history-integration-${{ github.run_id }}
@@ -568,7 +529,7 @@ jobs:
run: cat integration-test-report.md >> $GITHUB_STEP_SUMMARY
- name: Save report history cache
if: always()
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: python/integration-report-history.json
key: integration-report-history-integration-${{ github.run_id }}
@@ -590,7 +551,6 @@ jobs:
python-tests-openai,
python-tests-azure-openai,
python-tests-misc-integration,
python-tests-functions,
python-tests-foundry,
python-tests-foundry-hosting,
python-tests-cosmos,
+8 -7
View File
@@ -24,13 +24,17 @@ jobs:
outputs:
pythonChanges: ${{ steps.filter.outputs.python}}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2
id: filter
with:
filters: |
python:
- 'python/**'
- '!python/AGENTS.md'
- '!python/**/AGENTS.md'
- '!python/.github/skills/*'
- '!python/.github/skills/**'
# run only if 'python' files were changed
- name: python tests
if: steps.filter.outputs.python == 'true'
@@ -59,7 +63,7 @@ jobs:
run:
working-directory: python
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up python and install the project
id: python-setup
@@ -67,7 +71,7 @@ jobs:
with:
python-version: ${{ matrix.python-version }}
os: ${{ runner.os }}
exclude-packages: ${{ matrix.python-version == '3.10' && 'agent-framework-github-copilot' || '' }}
exclude-packages: ${{ matrix.python-version == '3.10' && 'agent-framework-github-copilot agent-framework-azure-cosmos-memory' || '' }}
env:
# Configure a constant location for the uv cache
UV_CACHE_DIR: /tmp/.uv-cache
@@ -88,9 +92,6 @@ jobs:
- name: Run lab type checking
run: cd packages/lab && uv run poe pyright
- name: Run lab mypy
run: cd packages/lab && uv run poe mypy
# Surface failing tests
- name: Surface failing tests
if: always()
+24 -101
View File
@@ -36,14 +36,13 @@ jobs:
openaiChanged: ${{ steps.filter.outputs.openai }}
azureChanged: ${{ steps.filter.outputs.azure }}
miscChanged: ${{ steps.filter.outputs.misc }}
functionsChanged: ${{ steps.filter.outputs.functions }}
foundryChanged: ${{ steps.filter.outputs.foundry }}
foundryHostingChanged: ${{ steps.filter.outputs.foundry_hosting }}
cosmosChanged: ${{ steps.filter.outputs.cosmos }}
githubCopilotChanged: ${{ steps.filter.outputs.github_copilot }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2
id: filter
with:
filters: |
@@ -68,16 +67,15 @@ jobs:
misc:
- 'python/packages/anthropic/**'
- 'python/packages/hyperlight/**'
- 'python/packages/mistral/**'
- 'python/packages/ollama/**'
- 'python/packages/core/agent_framework/_mcp.py'
- 'python/packages/core/tests/core/test_mcp.py'
- 'python/packages/hosting-mcp/**'
- 'python/scripts/local_mcp_streamable_http_server.py'
- '.github/actions/setup-local-mcp-server/**'
- '.github/workflows/python-merge-tests.yml'
- '.github/workflows/python-integration-tests.yml'
functions:
- 'python/packages/azurefunctions/**'
- 'python/packages/durabletask/**'
foundry:
- 'python/packages/foundry/**'
- 'python/samples/**/providers/foundry/**'
@@ -109,7 +107,7 @@ jobs:
run:
working-directory: python
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up python and install the project
id: python-setup
uses: ./.github/actions/python-setup
@@ -156,7 +154,7 @@ jobs:
run:
working-directory: python
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up python and install the project
id: python-setup
uses: ./.github/actions/python-setup
@@ -217,7 +215,7 @@ jobs:
run:
working-directory: python
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up python and install the project
id: python-setup
uses: ./.github/actions/python-setup
@@ -287,7 +285,7 @@ jobs:
run:
working-directory: python
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up python and install the project
id: python-setup
uses: ./.github/actions/python-setup
@@ -298,7 +296,7 @@ jobs:
run: curl -fsSL https://ollama.com/install.sh | sh
working-directory: .
- name: Cache Ollama models
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.ollama/models
key: ollama-models-qwen2.5-1.5b-nomic-embed-text-v1
@@ -338,13 +336,15 @@ jobs:
fallback_url: ${{ env.LOCAL_MCP_URL }}
- name: Prefer local MCP URL when available
run: echo "LOCAL_MCP_URL=${{ steps.local-mcp.outputs.effective_url }}" >> "$GITHUB_ENV"
- name: Test with pytest (Anthropic, Hyperlight, Ollama, MCP integration)
- name: Test with pytest (Anthropic, Hyperlight, Mistral, Ollama, MCP integration)
run: >
uv run pytest --import-mode=importlib
packages/anthropic/tests
packages/hyperlight/tests
packages/mistral/tests
packages/ollama/tests
packages/core/tests/core/test_mcp.py
packages/hosting-mcp/tests
-m integration
-n logical --dist worksteal
--timeout=120 --session-timeout=900 --timeout_method thread
@@ -388,84 +388,6 @@ jobs:
path: ./python/pytest.xml
if-no-files-found: ignore
# Azure Functions + Durable Task integration tests
python-tests-functions:
name: Python Tests - Functions Integration
needs: paths-filter
if: >
github.event_name != 'pull_request' &&
needs.paths-filter.outputs.pythonChanges == 'true' &&
(github.event_name != 'merge_group' ||
needs.paths-filter.outputs.functionsChanged == 'true' ||
needs.paths-filter.outputs.coreChanged == 'true')
runs-on: ubuntu-latest
environment: integration
env:
UV_PYTHON: "3.11"
OPENAI_CHAT_COMPLETION_MODEL: ${{ vars.OPENAI__CHATMODELID }}
OPENAI_CHAT_MODEL: ${{ vars.OPENAI__RESPONSESMODELID }}
OPENAI_MODEL: ${{ vars.OPENAI__RESPONSESMODELID }}
OPENAI_EMBEDDING_MODEL: ${{ vars.OPENAI_EMBEDDING_MODEL_ID }}
OPENAI_API_KEY: ${{ secrets.OPENAI__APIKEY }}
AZURE_OPENAI_ENDPOINT: ${{ vars.AZUREOPENAI__ENDPOINT }}
AZURE_OPENAI_MODEL: ${{ vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
AZURE_OPENAI_CHAT_MODEL: ${{ vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
AZURE_OPENAI_CHAT_COMPLETION_MODEL: ${{ vars.AZUREOPENAI__CHATDEPLOYMENTNAME }}
FOUNDRY_PROJECT_ENDPOINT: ${{ vars.FOUNDRY_PROJECT_ENDPOINT }}
FOUNDRY_MODEL: ${{ vars.FOUNDRY_MODEL }}
FUNCTIONS_WORKER_RUNTIME: "python"
DURABLE_TASK_SCHEDULER_CONNECTION_STRING: "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None"
AzureWebJobsStorage: "UseDevelopmentStorage=true"
defaults:
run:
working-directory: python
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Set up python and install the project
id: python-setup
uses: ./.github/actions/python-setup
with:
python-version: ${{ env.UV_PYTHON }}
os: ${{ runner.os }}
- name: Azure CLI Login
if: github.event_name != 'pull_request'
uses: azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Set up Azure Functions Integration Test Emulators
uses: ./.github/actions/azure-functions-integration-setup
id: azure-functions-setup
- name: Test with pytest (Functions + Durable Task integration)
run: >
uv run pytest --import-mode=importlib
packages/azurefunctions/tests/integration_tests
packages/durabletask/tests/integration_tests
-m integration
-n logical --dist worksteal
-x
--timeout=480 --session-timeout=900 --timeout_method thread
--retries 2 --retry-delay 5
--junitxml=pytest.xml
working-directory: ./python
- name: Surface failing tests
if: always()
uses: pmeier/pytest-results-action@20b595761ba9bf89e115e875f8bc863f913bc8ad # v0.7.2
with:
path: ./python/pytest.xml
summary: true
display-options: fEX
fail-on-empty: false
title: Functions integration test results
- name: Upload test results
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: test-results-functions
path: ./python/pytest.xml
if-no-files-found: ignore
python-tests-foundry:
name: Python Integration Tests - Foundry
needs: paths-filter
@@ -491,7 +413,7 @@ jobs:
run:
working-directory: python
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up python and install the project
id: python-setup
uses: ./.github/actions/python-setup
@@ -552,7 +474,7 @@ jobs:
run:
working-directory: python
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up python and install the project
id: python-setup
uses: ./.github/actions/python-setup
@@ -623,7 +545,7 @@ jobs:
run:
working-directory: python
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up python and install the project
id: python-setup
uses: ./.github/actions/python-setup
@@ -673,15 +595,18 @@ jobs:
needs.paths-filter.outputs.coreChanged == 'true')
runs-on: ubuntu-latest
environment: integration
permissions:
copilot-requests: write
contents: read
timeout-minutes: 60
env:
COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ github.token }}
GITHUB_COPILOT_TIMEOUT: "120"
defaults:
run:
working-directory: python
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up python and install the project
id: python-setup
uses: ./.github/actions/python-setup
@@ -725,7 +650,6 @@ jobs:
python-tests-openai,
python-tests-azure-openai,
python-tests-misc-integration,
python-tests-functions,
python-tests-foundry,
python-tests-foundry-hosting,
python-tests-cosmos,
@@ -736,19 +660,19 @@ jobs:
run:
working-directory: python
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up python and install the project
uses: ./.github/actions/python-setup
with:
python-version: ${{ env.UV_PYTHON }}
os: ${{ runner.os }}
- name: Download all test results from current run
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: test-results-*
path: test-results/
- name: Restore report history cache
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: python/integration-report-history.json
key: integration-report-history-merge-${{ github.run_id }}
@@ -765,7 +689,7 @@ jobs:
run: cat integration-test-report.md >> $GITHUB_STEP_SUMMARY
- name: Save report history cache
if: always()
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: python/integration-report-history.json
key: integration-report-history-merge-${{ github.run_id }}
@@ -787,7 +711,6 @@ jobs:
python-tests-openai,
python-tests-azure-openai,
python-tests-misc-integration,
python-tests-functions,
python-tests-foundry,
python-tests-foundry-hosting,
python-tests-cosmos,
+2 -2
View File
@@ -23,7 +23,7 @@ jobs:
run:
working-directory: python
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up python and install the project
id: python-setup
uses: ./.github/actions/python-setup
@@ -56,7 +56,7 @@ jobs:
- name: Build the package
run: uv run poe --directory packages/${{ env.PACKAGE }} build
- name: Release
uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2
uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3.0.1
with:
files: |
python/dist/*
+281 -67
View File
@@ -8,11 +8,11 @@ on:
env:
# Configure a constant location for the uv cache
UV_CACHE_DIR: /tmp/.uv-cache
# GitHub Copilot configuration
GITHUB_COPILOT_MODEL: claude-opus-4.6
COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ github.token }}
GITHUB_COPILOT_MODEL: auto
permissions:
copilot-requests: write
contents: read
id-token: write
@@ -23,13 +23,13 @@ jobs:
environment: integration
env:
# Required configuration for get-started samples
FOUNDRY_PROJECT_ENDPOINT: ${{ vars.FOUNDRY_PROJECT_ENDPOINT || vars.AZURE_AI_PROJECT_ENDPOINT }}
FOUNDRY_MODEL: ${{ vars.FOUNDRY_MODEL || vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
FOUNDRY_PROJECT_ENDPOINT: ${{ vars.FOUNDRY_PROJECT_ENDPOINT }}
FOUNDRY_MODEL: ${{ vars.FOUNDRY_MODEL }}
defaults:
run:
working-directory: python
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup environment
uses: ./.github/actions/sample-validation-setup
@@ -48,6 +48,10 @@ jobs:
run: |
cd scripts && uv run python -m sample_validation --subdir 01-get-started --save-report --report-name 01-get-started
- name: Save sample playbooks
if: ${{ !cancelled() }}
uses: ./.github/actions/sample-validation-save-playbooks
- name: Upload validation report
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
if: always()
@@ -57,12 +61,13 @@ jobs:
validate-02-agents:
name: Validate 02-agents
if: false # Temporarily disabled - to free up Copilot quota for other jobs
runs-on: ubuntu-latest
environment: integration
env:
# Foundry configuration
FOUNDRY_PROJECT_ENDPOINT: ${{ vars.FOUNDRY_PROJECT_ENDPOINT || vars.AZURE_AI_PROJECT_ENDPOINT }}
FOUNDRY_MODEL: ${{ vars.FOUNDRY_MODEL || vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
FOUNDRY_PROJECT_ENDPOINT: ${{ vars.FOUNDRY_PROJECT_ENDPOINT }}
FOUNDRY_MODEL: ${{ vars.FOUNDRY_MODEL }}
# Azure OpenAI configuration
AZURE_OPENAI_ENDPOINT: ${{ vars.AZUREOPENAI__ENDPOINT }}
AZURE_OPENAI_MODEL: ${{ vars.AZURE_OPENAI_DEPLOYMENT_NAME || vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
@@ -70,19 +75,19 @@ jobs:
AZURE_OPENAI_CHAT_MODEL: ${{ vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
AZURE_OPENAI_EMBEDDING_MODEL: ${{ vars.AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME || vars.AZUREOPENAI__EMBEDDINGDEPLOYMENTNAME }}
# OpenAI configuration
OPENAI_API_KEY: ${{ secrets.OPENAI__APIKEY }}
OPENAI_CHAT_COMPLETION_MODEL: ${{ vars.OPENAI__CHATMODELID }}
OPENAI_CHAT_MODEL: ${{ vars.OPENAI__RESPONSESMODELID }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_CHAT_COMPLETION_MODEL: ${{ vars.OPENAI_CHAT_MODEL_NAME }}
OPENAI_CHAT_MODEL: ${{ vars.OPENAI_REASONING_MODEL_NAME }}
# GitHub MCP
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
OPENAI_MODEL: ${{ vars.OPENAI__RESPONSESMODELID }}
OPENAI_MODEL: ${{ vars.OPENAI_REASONING_MODEL_NAME }}
# Observability
ENABLE_INSTRUMENTATION: "true"
defaults:
run:
working-directory: python
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup environment
uses: ./.github/actions/sample-validation-setup
@@ -108,7 +113,11 @@ jobs:
- name: Run sample validation
run: |
cd scripts && uv run python -m sample_validation --subdir 02-agents --exclude providers --save-report --report-name 02-agents
cd scripts && uv run python -m sample_validation --subdir 02-agents --exclude providers harness tools --save-report --report-name 02-agents
- name: Save sample playbooks
if: ${{ !cancelled() }}
uses: ./.github/actions/sample-validation-save-playbooks
- name: Upload validation report
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
@@ -117,20 +126,110 @@ jobs:
name: validation-report-02-agents
path: python/samples/sample_validation/reports/
validate-02-agents-openai:
name: Validate 02-agents/providers/openai
validate-02-agents-harness:
name: Validate 02-agents/harness
if: false # Temporarily disabled - to free up Copilot quota for other jobs
runs-on: ubuntu-latest
environment: integration
env:
OPENAI_API_KEY: ${{ secrets.OPENAI__APIKEY }}
OPENAI_MODEL: ${{ vars.OPENAI__CHATMODELID }}
OPENAI_CHAT_COMPLETION_MODEL: ${{ vars.OPENAI__CHATMODELID }}
OPENAI_CHAT_MODEL: ${{ vars.OPENAI__RESPONSESMODELID }}
FOUNDRY_PROJECT_ENDPOINT: ${{ vars.FOUNDRY_PROJECT_ENDPOINT }}
FOUNDRY_MODEL: ${{ vars.FOUNDRY_MODEL }}
# Optional: enables the Foundry memory path in harness samples
FOUNDRY_EMBEDDING_MODEL: ${{ vars.FOUNDRY_EMBEDDING_MODEL || '' }}
FOUNDRY_MEMORY_STORE: ${{ vars.FOUNDRY_MEMORY_STORE || '' }}
defaults:
run:
working-directory: python
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup environment
uses: ./.github/actions/sample-validation-setup
with:
azure-client-id: ${{ secrets.AZURE_CLIENT_ID }}
azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }}
azure-subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
os: ${{ runner.os }}
- name: Create .env for samples
run: |
echo "FOUNDRY_PROJECT_ENDPOINT=$FOUNDRY_PROJECT_ENDPOINT" >> .env
echo "FOUNDRY_MODEL=$FOUNDRY_MODEL" >> .env
echo "FOUNDRY_EMBEDDING_MODEL=$FOUNDRY_EMBEDDING_MODEL" >> .env
echo "FOUNDRY_MEMORY_STORE=$FOUNDRY_MEMORY_STORE" >> .env
- name: Run sample validation
run: |
cd scripts && uv run python -m sample_validation --subdir 02-agents/harness --save-report --report-name 02-agents-harness
- name: Save sample playbooks
if: ${{ !cancelled() }}
uses: ./.github/actions/sample-validation-save-playbooks
- name: Upload validation report
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
if: always()
with:
name: validation-report-02-agents-harness
path: python/samples/sample_validation/reports/
validate-02-agents-tools:
name: Validate 02-agents/tools
if: false # Temporarily disabled - to free up Copilot quota for other jobs
runs-on: ubuntu-latest
environment: integration
env:
FOUNDRY_PROJECT_ENDPOINT: ${{ vars.FOUNDRY_PROJECT_ENDPOINT }}
FOUNDRY_MODEL: ${{ vars.FOUNDRY_MODEL }}
defaults:
run:
working-directory: python
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup environment
uses: ./.github/actions/sample-validation-setup
with:
azure-client-id: ${{ secrets.AZURE_CLIENT_ID }}
azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }}
azure-subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
os: ${{ runner.os }}
- name: Create .env for samples
run: |
echo "FOUNDRY_PROJECT_ENDPOINT=$FOUNDRY_PROJECT_ENDPOINT" >> .env
echo "FOUNDRY_MODEL=$FOUNDRY_MODEL" >> .env
- name: Run sample validation
run: |
cd scripts && uv run python -m sample_validation --subdir 02-agents/tools --save-report --report-name 02-agents-tools
- name: Save sample playbooks
if: ${{ !cancelled() }}
uses: ./.github/actions/sample-validation-save-playbooks
- name: Upload validation report
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
if: always()
with:
name: validation-report-02-agents-tools
path: python/samples/sample_validation/reports/
validate-02-agents-openai:
name: Validate 02-agents/providers/openai
if: false # Temporarily disabled - to free up Copilot quota for other jobs
runs-on: ubuntu-latest
environment: integration
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_MODEL: ${{ vars.OPENAI_CHAT_MODEL_NAME }}
OPENAI_CHAT_COMPLETION_MODEL: ${{ vars.OPENAI_CHAT_MODEL_NAME }}
OPENAI_CHAT_MODEL: ${{ vars.OPENAI_REASONING_MODEL_NAME }}
defaults:
run:
working-directory: python
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup environment
uses: ./.github/actions/sample-validation-setup
@@ -151,6 +250,10 @@ jobs:
run: |
cd scripts && uv run python -m sample_validation --subdir 02-agents/providers/openai --save-report --report-name 02-agents-openai
- name: Save sample playbooks
if: ${{ !cancelled() }}
uses: ./.github/actions/sample-validation-save-playbooks
- name: Upload validation report
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
if: always()
@@ -160,6 +263,7 @@ jobs:
validate-02-agents-azure:
name: Validate 02-agents/providers/azure
if: false # Temporarily disabled - to free up Copilot quota for other jobs
runs-on: ubuntu-latest
environment: integration
env:
@@ -170,7 +274,7 @@ jobs:
run:
working-directory: python
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup environment
uses: ./.github/actions/sample-validation-setup
@@ -190,6 +294,10 @@ jobs:
run: |
cd scripts && uv run python -m sample_validation --subdir 02-agents/providers/azure --save-report --report-name 02-agents-azure
- name: Save sample playbooks
if: ${{ !cancelled() }}
uses: ./.github/actions/sample-validation-save-playbooks
- name: Upload validation report
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
if: always()
@@ -199,6 +307,7 @@ jobs:
validate-02-agents-anthropic:
name: Validate 02-agents/providers/anthropic
if: false # Temporarily disabled - to free up Copilot quota for other jobs
runs-on: ubuntu-latest
environment: integration
env:
@@ -208,7 +317,7 @@ jobs:
run:
working-directory: python
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup environment
uses: ./.github/actions/sample-validation-setup
@@ -227,6 +336,10 @@ jobs:
run: |
cd scripts && uv run python -m sample_validation --subdir 02-agents/providers/anthropic --save-report --report-name 02-agents-anthropic
- name: Save sample playbooks
if: ${{ !cancelled() }}
uses: ./.github/actions/sample-validation-save-playbooks
- name: Upload validation report
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
if: always()
@@ -236,13 +349,14 @@ jobs:
validate-02-agents-github-copilot:
name: Validate 02-agents/providers/github_copilot
if: false # Temporarily disabled - to free up Copilot quota for other jobs
runs-on: ubuntu-latest
environment: integration
defaults:
run:
working-directory: python
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup environment
uses: ./.github/actions/sample-validation-setup
@@ -256,6 +370,10 @@ jobs:
run: |
cd scripts && uv run python -m sample_validation --subdir 02-agents/providers/github_copilot --save-report --report-name 02-agents-github-copilot
- name: Save sample playbooks
if: ${{ !cancelled() }}
uses: ./.github/actions/sample-validation-save-playbooks
- name: Upload validation report
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
if: always()
@@ -274,7 +392,7 @@ jobs:
run:
working-directory: python
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup environment
uses: ./.github/actions/sample-validation-setup
@@ -288,6 +406,10 @@ jobs:
run: |
cd scripts && uv run python -m sample_validation --subdir 02-agents/providers/amazon --save-report --report-name 02-agents-amazon
- name: Save sample playbooks
if: ${{ !cancelled() }}
uses: ./.github/actions/sample-validation-save-playbooks
- name: Upload validation report
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
if: always()
@@ -306,7 +428,7 @@ jobs:
run:
working-directory: python
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup environment
uses: ./.github/actions/sample-validation-setup
@@ -320,6 +442,10 @@ jobs:
run: |
cd scripts && uv run python -m sample_validation --subdir 02-agents/providers/ollama --save-report --report-name 02-agents-ollama
- name: Save sample playbooks
if: ${{ !cancelled() }}
uses: ./.github/actions/sample-validation-save-playbooks
- name: Upload validation report
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
if: always()
@@ -329,19 +455,19 @@ jobs:
validate-02-agents-foundry:
name: Validate 02-agents/providers/foundry
if: false # Temporarily disabled - provider folder also contains the local Foundry sample
if: false # Temporarily disabled - to free up Copilot quota for other jobs
runs-on: ubuntu-latest
environment: integration
env:
FOUNDRY_PROJECT_ENDPOINT: ${{ vars.FOUNDRY_PROJECT_ENDPOINT || vars.AZURE_AI_PROJECT_ENDPOINT }}
FOUNDRY_MODEL: ${{ vars.FOUNDRY_MODEL || vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
FOUNDRY_PROJECT_ENDPOINT: ${{ vars.FOUNDRY_PROJECT_ENDPOINT }}
FOUNDRY_MODEL: ${{ vars.FOUNDRY_MODEL }}
FOUNDRY_AGENT_NAME: ${{ vars.FOUNDRY_AGENT_NAME || '' }}
FOUNDRY_AGENT_VERSION: ${{ vars.FOUNDRY_AGENT_VERSION || '' }}
defaults:
run:
working-directory: python
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup environment
uses: ./.github/actions/sample-validation-setup
@@ -362,6 +488,10 @@ jobs:
run: |
cd scripts && uv run python -m sample_validation --subdir 02-agents/providers/foundry --save-report --report-name 02-agents-foundry
- name: Save sample playbooks
if: ${{ !cancelled() }}
uses: ./.github/actions/sample-validation-save-playbooks
- name: Upload validation report
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
if: always()
@@ -383,7 +513,7 @@ jobs:
run:
working-directory: python
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup environment
uses: ./.github/actions/sample-validation-setup
@@ -404,6 +534,10 @@ jobs:
run: |
cd scripts && uv run python -m sample_validation --subdir 02-agents/providers/copilotstudio --save-report --report-name 02-agents-copilotstudio
- name: Save sample playbooks
if: ${{ !cancelled() }}
uses: ./.github/actions/sample-validation-save-playbooks
- name: Upload validation report
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
if: always()
@@ -419,7 +553,7 @@ jobs:
run:
working-directory: python
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup environment
uses: ./.github/actions/sample-validation-setup
@@ -433,6 +567,10 @@ jobs:
run: |
cd scripts && uv run python -m sample_validation --subdir 02-agents/providers/custom --save-report --report-name 02-agents-custom
- name: Save sample playbooks
if: ${{ !cancelled() }}
uses: ./.github/actions/sample-validation-save-playbooks
- name: Upload validation report
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
if: always()
@@ -442,16 +580,17 @@ jobs:
validate-03-workflows:
name: Validate 03-workflows
if: false # Temporarily disabled - to free up Copilot quota for other jobs
runs-on: ubuntu-latest
environment: integration
env:
FOUNDRY_PROJECT_ENDPOINT: ${{ vars.FOUNDRY_PROJECT_ENDPOINT || vars.AZURE_AI_PROJECT_ENDPOINT }}
FOUNDRY_MODEL: ${{ vars.FOUNDRY_MODEL || vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
FOUNDRY_PROJECT_ENDPOINT: ${{ vars.FOUNDRY_PROJECT_ENDPOINT }}
FOUNDRY_MODEL: ${{ vars.FOUNDRY_MODEL }}
defaults:
run:
working-directory: python
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup environment
uses: ./.github/actions/sample-validation-setup
@@ -470,6 +609,10 @@ jobs:
run: |
cd scripts && uv run python -m sample_validation --subdir 03-workflows --save-report --report-name 03-workflows
- name: Save sample playbooks
if: ${{ !cancelled() }}
uses: ./.github/actions/sample-validation-save-playbooks
- name: Upload validation report
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
if: always()
@@ -477,21 +620,68 @@ jobs:
name: validation-report-03-workflows
path: python/samples/sample_validation/reports/
validate-04-hosting:
name: Validate 04-hosting
if: false # Temporarily disabled because of sample complexity
validate-04-hosting-foundry-hosted-agents:
name: Validate 04-hosting (foundry-hosted-agents)
runs-on: ubuntu-latest
environment: integration
env:
FOUNDRY_PROJECT_ENDPOINT: ${{ vars.FOUNDRY_PROJECT_ENDPOINT || vars.AZURE_AI_PROJECT_ENDPOINT }}
FOUNDRY_MODEL: ${{ vars.FOUNDRY_MODEL || vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
FOUNDRY_PROJECT_ENDPOINT: ${{ vars.FOUNDRY_PROJECT_ENDPOINT }}
FOUNDRY_MODEL: ${{ vars.FOUNDRY_MODEL }}
# Foundry hosted agent configuration
AZURE_AI_MODEL_DEPLOYMENT_NAME: ${{ vars.FOUNDRY_MODEL }}
FOUNDRY_PROJECT_ID: ${{ vars.FOUNDRY_PROJECT_ID }}
AZURE_CONTAINER_REGISTRY_ENDPOINT: ${{ vars.AZURE_CONTAINER_REGISTRY_ENDPOINT }}
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
TOOLBOX_ENDPOINT: ${{ vars.TOOLBOX_ENDPOINT }}
FOUNDRY_AGENT_NAME: ${{ vars.FOUNDRY_HOSTED_AGENT_NAME }}
MEMORY_STORE_NAME: ${{ vars.FOUNDRY_HOSTED_AGENT_MEMORY_STORE }}
AZURE_SEARCH_ENDPOINT: ${{ vars.AZURE_SEARCH_ENDPOINT }}
AZURE_SEARCH_INDEX_NAME: ${{ vars.FOUNDRY_HOSTED_AGENT_SEARCH_INDEX_NAME }}
defaults:
run:
working-directory: python
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup environment
uses: ./.github/actions/sample-validation-setup
with:
azure-client-id: ${{ secrets.AZURE_CLIENT_ID }}
azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }}
azure-subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
os: ${{ runner.os }}
- name: Run sample validation
# Maximum parallel workers is set to 1 because all samples use the same port
run: |
cd scripts && uv run python -m sample_validation --subdir 04-hosting/foundry-hosted-agents --save-report --report-name 04-hosting-foundry-hosted-agents --max-parallel-workers 1
- name: Save sample playbooks
if: ${{ !cancelled() }}
uses: ./.github/actions/sample-validation-save-playbooks
- name: Upload validation report
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
if: always()
with:
name: validation-report-04-hosting-foundry-hosted-agents
path: python/samples/sample_validation/reports/
validate-04-hosting-other:
name: Validate 04-hosting (other)
if: false # Temporarily disabled - to free up Copilot quota for other jobs
runs-on: ubuntu-latest
environment: integration
env:
FOUNDRY_PROJECT_ENDPOINT: ${{ vars.FOUNDRY_PROJECT_ENDPOINT }}
FOUNDRY_MODEL: ${{ vars.FOUNDRY_MODEL }}
# A2A configuration
A2A_AGENT_HOST: http://localhost:5001/
defaults:
run:
working-directory: python
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup environment
uses: ./.github/actions/sample-validation-setup
@@ -503,13 +693,17 @@ jobs:
- name: Run sample validation
run: |
cd scripts && uv run python -m sample_validation --subdir 04-hosting --save-report --report-name 04-hosting
cd scripts && uv run python -m sample_validation --subdir 04-hosting --exclude foundry-hosted-agents --save-report --report-name 04-hosting-other
- name: Save sample playbooks
if: ${{ !cancelled() }}
uses: ./.github/actions/sample-validation-save-playbooks
- name: Upload validation report
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
if: always()
with:
name: validation-report-04-hosting
name: validation-report-04-hosting-other
path: python/samples/sample_validation/reports/
validate-05-end-to-end:
@@ -518,8 +712,8 @@ jobs:
runs-on: ubuntu-latest
environment: integration
env:
FOUNDRY_PROJECT_ENDPOINT: ${{ vars.FOUNDRY_PROJECT_ENDPOINT || vars.AZURE_AI_PROJECT_ENDPOINT }}
FOUNDRY_MODEL: ${{ vars.FOUNDRY_MODEL || vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
FOUNDRY_PROJECT_ENDPOINT: ${{ vars.FOUNDRY_PROJECT_ENDPOINT }}
FOUNDRY_MODEL: ${{ vars.FOUNDRY_MODEL }}
# Azure OpenAI configuration
AZURE_OPENAI_ENDPOINT: ${{ vars.AZUREOPENAI__ENDPOINT }}
AZURE_OPENAI_MODEL: ${{ vars.AZURE_OPENAI_DEPLOYMENT_NAME || vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
@@ -534,7 +728,7 @@ jobs:
run:
working-directory: python
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup environment
uses: ./.github/actions/sample-validation-setup
@@ -548,6 +742,10 @@ jobs:
run: |
cd scripts && uv run python -m sample_validation --subdir 05-end-to-end --save-report --report-name 05-end-to-end
- name: Save sample playbooks
if: ${{ !cancelled() }}
uses: ./.github/actions/sample-validation-save-playbooks
- name: Upload validation report
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
if: always()
@@ -560,21 +758,21 @@ jobs:
runs-on: ubuntu-latest
environment: integration
env:
FOUNDRY_PROJECT_ENDPOINT: ${{ vars.FOUNDRY_PROJECT_ENDPOINT || vars.AZURE_AI_PROJECT_ENDPOINT }}
FOUNDRY_MODEL: ${{ vars.FOUNDRY_MODEL || vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
FOUNDRY_PROJECT_ENDPOINT: ${{ vars.FOUNDRY_PROJECT_ENDPOINT }}
FOUNDRY_MODEL: ${{ vars.FOUNDRY_MODEL }}
# Azure OpenAI configuration
AZURE_OPENAI_ENDPOINT: ${{ vars.AZUREOPENAI__ENDPOINT }}
AZURE_OPENAI_MODEL: ${{ vars.AZURE_OPENAI_DEPLOYMENT_NAME || vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
# OpenAI configuration
OPENAI_API_KEY: ${{ secrets.OPENAI__APIKEY }}
OPENAI_CHAT_COMPLETION_MODEL: ${{ vars.OPENAI__CHATMODELID }}
OPENAI_CHAT_MODEL: ${{ vars.OPENAI__RESPONSESMODELID }}
OPENAI_MODEL: ${{ vars.OPENAI__RESPONSESMODELID }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_CHAT_COMPLETION_MODEL: ${{ vars.OPENAI_CHAT_MODEL_NAME }}
OPENAI_CHAT_MODEL: ${{ vars.OPENAI_REASONING_MODEL_NAME }}
OPENAI_MODEL: ${{ vars.OPENAI_REASONING_MODEL_NAME }}
defaults:
run:
working-directory: python
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup environment
uses: ./.github/actions/sample-validation-setup
@@ -594,9 +792,16 @@ jobs:
echo "OPENAI_CHAT_COMPLETION_MODEL=$OPENAI_CHAT_COMPLETION_MODEL" >> .env
echo "OPENAI_CHAT_MODEL=$OPENAI_CHAT_MODEL" >> .env
- name: Pre-install AutoGen dependencies for migration samples
run: uv pip install "autogen-agentchat" "autogen-ext[openai]"
- name: Run sample validation
run: |
cd scripts && uv run python -m sample_validation --subdir autogen-migration --save-report --report-name autogen-migration
cd scripts && uv run python -m sample_validation --subdir autogen-migration --save-report --report-name autogen-migration --agent-timeout 600
- name: Save sample playbooks
if: ${{ !cancelled() }}
uses: ./.github/actions/sample-validation-save-playbooks
- name: Upload validation report
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
@@ -607,23 +812,25 @@ jobs:
validate-semantic-kernel-migration:
name: Validate semantic-kernel-migration
if: false # Temporarily disabled - to free up Copilot quota for other jobs
runs-on: ubuntu-latest
environment: integration
env:
FOUNDRY_PROJECT_ENDPOINT: ${{ vars.FOUNDRY_PROJECT_ENDPOINT || vars.AZURE_AI_PROJECT_ENDPOINT }}
FOUNDRY_MODEL: ${{ vars.FOUNDRY_MODEL || vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
FOUNDRY_PROJECT_ENDPOINT: ${{ vars.FOUNDRY_PROJECT_ENDPOINT }}
FOUNDRY_MODEL: ${{ vars.FOUNDRY_MODEL }}
# Azure OpenAI configuration for AF
AZURE_OPENAI_ENDPOINT: ${{ vars.AZUREOPENAI__ENDPOINT }}
AZURE_OPENAI_MODEL: ${{ vars.AZURE_OPENAI_DEPLOYMENT_NAME || vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
# Azure OpenAI configuration for SK
AZURE_OPENAI_CHAT_DEPLOYMENT_NAME: ${{ vars.AZURE_OPENAI_DEPLOYMENT_NAME }}
# OpenAI key
OPENAI_API_KEY: ${{ secrets.OPENAI__APIKEY }}
OPENAI_CHAT_COMPLETION_MODEL: ${{ vars.OPENAI__CHATMODELID }}
OPENAI_CHAT_MODEL: ${{ vars.OPENAI__RESPONSESMODELID }}
OPENAI_MODEL: ${{ vars.OPENAI__RESPONSESMODELID }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_CHAT_COMPLETION_MODEL: ${{ vars.OPENAI_CHAT_MODEL_NAME }}
OPENAI_CHAT_MODEL: ${{ vars.OPENAI_REASONING_MODEL_NAME }}
OPENAI_MODEL: ${{ vars.OPENAI_REASONING_MODEL_NAME }}
# OpenAI configuration for SK
OPENAI_CHAT_MODEL_ID: ${{ vars.OPENAI__CHATMODELID }}
OPENAI_CHAT_MODEL_ID: ${{ vars.OPENAI_CHAT_MODEL_NAME }}
OPENAI_RESPONSES_MODEL_ID: ${{ vars.OPENAI__RESPONSESMODELID }}
# Copilot Studio
COPILOTSTUDIOAGENT__ENVIRONMENTID: ${{ secrets.COPILOTSTUDIOAGENT__ENVIRONMENTID }}
COPILOTSTUDIOAGENT__SCHEMANAME: ${{ secrets.COPILOTSTUDIOAGENT__SCHEMANAME }}
@@ -633,7 +840,7 @@ jobs:
run:
working-directory: python
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup environment
uses: ./.github/actions/sample-validation-setup
@@ -661,6 +868,10 @@ jobs:
run: |
cd scripts && uv run python -m sample_validation --subdir semantic-kernel-migration --save-report --report-name semantic-kernel-migration
- name: Save sample playbooks
if: ${{ !cancelled() }}
uses: ./.github/actions/sample-validation-save-playbooks
- name: Upload validation report
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
if: always()
@@ -675,6 +886,8 @@ jobs:
needs:
- validate-01-get-started
- validate-02-agents
- validate-02-agents-harness
- validate-02-agents-tools
- validate-02-agents-openai
- validate-02-agents-azure
- validate-02-agents-anthropic
@@ -685,15 +898,16 @@ jobs:
- validate-02-agents-copilotstudio
- validate-02-agents-custom
- validate-03-workflows
- validate-04-hosting
- validate-04-hosting-foundry-hosted-agents
- validate-04-hosting-other
- validate-05-end-to-end
- validate-autogen-migration
- validate-semantic-kernel-migration
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Download all validation reports
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: validation-report-*
path: reports/
@@ -701,7 +915,7 @@ jobs:
- name: Restore validation history
id: cache-restore
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: validation-history/
key: validation-history-${{ github.run_id }}
@@ -719,7 +933,7 @@ jobs:
run: cat trend-report.md >> "$GITHUB_STEP_SUMMARY"
- name: Save validation history
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: validation-history/
key: validation-history-${{ github.run_id }}
@@ -20,7 +20,7 @@ jobs:
run:
working-directory: python
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Download coverage report
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
@@ -30,24 +30,34 @@ jobs:
merge-multiple: true
- name: Display structure of downloaded files
run: ls
- name: Read and set PR number
# Need to read the PR number from the file saved in the previous workflow
# because the workflow_run event does not have access to the PR number
# The PR number is needed to post the comment on the PR
- name: Read and validate PR number
# Keep the artifact handoff aligned with the workflow run that produced it.
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
RUN_HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
run: |
if [ ! -s pr_number ]; then
echo "PR number file 'pr_number' is missing or empty"
exit 1
fi
PR_NUMBER=$(cat pr_number)
if ! [[ "$PR_NUMBER" =~ ^[0-9]+$ ]]; then
ARTIFACT_PR_NUMBER=$(cat pr_number)
if ! [[ "$ARTIFACT_PR_NUMBER" =~ ^[0-9]+$ ]]; then
echo "::error::PR number file contains invalid content"
exit 1
fi
echo "PR_NUMBER=$PR_NUMBER" >> "$GITHUB_ENV"
PR_HEAD_SHA=$(gh pr view "$ARTIFACT_PR_NUMBER" --repo "$REPO" --json headRefOid --jq '.headRefOid')
if [ "$PR_HEAD_SHA" != "$RUN_HEAD_SHA" ]; then
echo "::error::PR head SHA does not match the triggering workflow run"
exit 1
fi
echo "PR_NUMBER=$ARTIFACT_PR_NUMBER" >> "$GITHUB_ENV"
- name: Pytest coverage comment
id: coverageComment
uses: MishaKav/pytest-coverage-comment@26f986d2599c288bb62f623d29c2da98609e9cd4 # v1.6.0
uses: MishaKav/pytest-coverage-comment@dd5b80bde6d16941f336518e92929e89069d8451 # v1.7.2
with:
github-token: ${{ github.token }}
issue-number: ${{ env.PR_NUMBER }}
+6 -3
View File
@@ -6,6 +6,9 @@ on:
paths:
- "python/packages/**"
- "python/tests/unit/**"
- "python/scripts/workspace_poe_tasks.py"
- ".github/scripts/python_check_coverage.py"
- ".github/workflows/python-test-coverage.yml"
env:
# Configure a constant location for the uv cache
UV_CACHE_DIR: /tmp/.uv-cache
@@ -22,7 +25,7 @@ jobs:
env:
UV_PYTHON: "3.11"
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# Save the PR number to a file since the workflow_run event
# in the coverage report workflow does not have access to it
- name: Save PR number
@@ -37,10 +40,10 @@ jobs:
env:
# Configure a constant location for the uv cache
UV_CACHE_DIR: /tmp/.uv-cache
- name: Run all tests with coverage report
- name: Run aggregate tests with coverage report
run: uv run poe test -A -C --cov-report=xml:python-coverage.xml -q --junitxml=pytest.xml
- name: Check coverage threshold
run: python ${{ github.workspace }}/.github/workflows/python-check-coverage.py python-coverage.xml ${{ env.COVERAGE_THRESHOLD }}
run: python ${{ github.workspace }}/.github/scripts/python_check_coverage.py python-coverage.xml ${{ env.COVERAGE_THRESHOLD }}
- name: Upload coverage report
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
+6 -2
View File
@@ -5,6 +5,10 @@ on:
branches: ["main", "feature*"]
paths:
- "python/**"
- "!python/AGENTS.md"
- "!python/**/AGENTS.md"
- "!python/.github/skills/*"
- "!python/.github/skills/**"
env:
# Configure a constant location for the uv cache
UV_CACHE_DIR: /tmp/.uv-cache
@@ -27,14 +31,14 @@ jobs:
run:
working-directory: python
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up python and install the project
id: python-setup
uses: ./.github/actions/python-setup
with:
python-version: ${{ matrix.python-version }}
os: ${{ runner.os }}
exclude-packages: ${{ matrix.python-version == '3.10' && 'agent-framework-github-copilot' || '' }}
exclude-packages: ${{ matrix.python-version == '3.10' && 'agent-framework-github-copilot agent-framework-azure-cosmos-memory' || '' }}
env:
# Configure a constant location for the uv cache
UV_CACHE_DIR: /tmp/.uv-cache
+19 -2
View File
@@ -26,12 +26,29 @@ jobs:
ping_stale:
name: "Ping stale issues and PRs"
runs-on: ubuntu-latest
environment: github-app-auth
permissions:
contents: read
id-token: write
issues: write
pull-requests: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Get GitHub automation token
id: github-auth
uses: ./.github/actions/github-app-token
with:
mode: ${{ vars.GH_APP_AUTH_MODE }}
azure-client-id: ${{ secrets.GH_APP_AZURE_CLIENT_ID }}
azure-tenant-id: ${{ secrets.GH_APP_AZURE_TENANT_ID }}
azure-subscription-id: ${{ secrets.GH_APP_AZURE_SUBSCRIPTION_ID }}
key-vault-name: ${{ secrets.GH_APP_KEY_VAULT_NAME }}
key-name: ${{ secrets.GH_APP_KEY_NAME }}
github-app-client-id: ${{ secrets.GH_APP_CLIENT_ID }}
github-app-installation-id: ${{ secrets.GH_APP_INSTALLATION_ID }}
repository: ${{ github.repository }}
fallback-token: ${{ secrets.GH_ACTIONS_PR_WRITE }}
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
@@ -43,7 +60,7 @@ jobs:
- name: Run stale issue/PR ping
run: python .github/scripts/stale_issue_pr_ping.py
env:
GITHUB_TOKEN: ${{ secrets.GH_ACTIONS_PR_WRITE }}
GITHUB_TOKEN: ${{ steps.github-auth.outputs.token }}
TEAM_SLUG: ${{ secrets.DEVELOPER_TEAM }}
DAYS_THRESHOLD: ${{ github.event.inputs.days_threshold || '4' }}
DRY_RUN: ${{ github.event.inputs.dry_run || 'false' }}
+2
View File
@@ -206,6 +206,7 @@ temp*/
.temp/
# AI
**/.checkpoints/
.claude/
.omc/
.omx/
@@ -213,6 +214,7 @@ WARP.md
**/memory-bank/
**/projectBrief.md
**/tmpclaude*
.kiro/
# Dependency-bound validation reports
python/scripts/dependency-*-results.json
python/scripts/dependencies/dependency-*-results.json
+6 -5
View File
@@ -6,7 +6,7 @@
[![MS Learn Documentation](https://img.shields.io/badge/MS%20Learn-Documentation-blue)](https://learn.microsoft.com/en-us/agent-framework/)
[![PyPI](https://img.shields.io/pypi/v/agent-framework)](https://pypi.org/project/agent-framework/)
[![NuGet](https://img.shields.io/nuget/v/Microsoft.Agents.AI)](https://www.nuget.org/profiles/MicrosoftAgentFramework/)
[![GitHub stars](https://img.shields.io/github/stars/microsoft/agent-framework?style=social)](https://github.com/microsoft/agent-framework/stargazers)
[![GitHub stars](https://img.shields.io/github/stars/microsoft/agent-framework?style=social)](https://github.com/microsoft/agent-framework)
Microsoft Agent Framework (MAF) is an open, multi-language framework for building **production-grade AI agents and multi-agent workflows** in **.NET and Python**.
@@ -161,19 +161,19 @@ Console.WriteLine(await agent.RunAsync("Write a haiku about Microsoft Agent Fram
### Python
- [Getting Started](./python/samples/01-get-started): progressive tutorial from hello-world to hosting
- [Getting Started](./python/samples/01-get-started): progressive tutorial from hello-world to workflows
- [Agent Concepts](./python/samples/02-agents): deep-dive samples by topic (tools, middleware, providers, etc.)
- [Workflows](./python/samples/03-workflows): workflow creation and integration with agents
- [Hosting](./python/samples/04-hosting): A2A, Azure Functions, Durable Task hosting
- [Hosting](./python/samples/04-hosting): A2A, self-hosted protocol helpers, and Foundry hosted agents. Durable Task and Azure Functions samples are in the [Durable Agent Framework extension](https://github.com/microsoft/agent-framework-durable-extension/tree/main/python/samples).
- [End-to-End](./python/samples/05-end-to-end): full applications, evaluation, and demos
### .NET
- [Getting Started](./dotnet/samples/01-get-started): progressive tutorial from hello agent to hosting
- [Getting Started](./dotnet/samples/01-get-started): progressive tutorial from hello agent to workflows
- [Agent Concepts](./dotnet/samples/02-agents/Agents): basic agent creation and tool usage
- [Agent Providers](./dotnet/samples/02-agents/AgentProviders): samples showing different agent providers
- [Workflows](./dotnet/samples/03-workflows): advanced multi-agent patterns and workflow orchestration
- [Hosting](./dotnet/samples/04-hosting): A2A, Durable Agents, Durable Workflows
- [Hosting](./dotnet/samples/04-hosting): A2A and Foundry hosted agents. Durable agent and workflow samples are in the [Durable Agent Framework extension](https://github.com/microsoft/agent-framework-durable-extension/tree/main/dotnet/samples).
- [End-to-End](./dotnet/samples/05-end-to-end): full applications and demos
## Community & Feedback
@@ -199,6 +199,7 @@ For environment variable configuration specific to each sample, refer to the REA
## Contributor Resources
- [Contributing Guide](./CONTRIBUTING.md)
- [Code of Conduct](./CODE_OF_CONDUCT.md)
- [Python Development Guide](./python/DEV_SETUP.md)
- [Design Documents](./docs/design)
- [Architectural Decision Records](./docs/decisions)
+2 -2
View File
@@ -2,7 +2,7 @@
**What is Microsoft Agent Framework?**
Microsoft Agent Framework is a comprehensive multi-language (C#/.NET and Python) framework for building, orchestrating, and deploying AI agents and multi-agent workflows. The system takes user instructions and conversation inputs and produces intelligent responses through AI agents that can integrate with various LLM providers (OpenAI, Azure OpenAI, Azure AI Foundry). It provides both simple chat agents and complex multi-agent workflows with graph-based orchestration.
Microsoft Agent Framework is a comprehensive multi-language (C#/.NET and Python) framework for building, orchestrating, and deploying AI agents and multi-agent workflows. The system takes user instructions and conversation inputs and produces intelligent responses through AI agents that can integrate with various LLM providers (OpenAI, Azure OpenAI, Microsoft Foundry). It provides both simple chat agents and complex multi-agent workflows with graph-based orchestration.
**What can Microsoft Agent Framework do?**
@@ -12,7 +12,7 @@ The framework offers:
- **Multi-Agent Orchestration**: Group chat, sequential, concurrent, and handoff patterns
- **Graph-based Workflows**: Connect agents and deterministic functions using data flows with streaming, checkpointing, time-travel, and Human-in-the-loop
- **Extensibility Framework**: Extend with native functions, A2A, Model Context Protocol (MCP)
- **LLM Integration**: Support for OpenAI, Azure OpenAI, Azure AI Foundry, and other providers
- **LLM Integration**: Support for OpenAI, Azure OpenAI, Microsoft Foundry, and other providers
- **Runtime Support**: Both in-process and distributed agent execution
**What is/are Microsoft Agent Framework's intended use(s)?**
@@ -14,6 +14,9 @@
# - Knowledge Agent: Performs generic web searches.
# - Coder Agent: Able to write and execute code.
# - Weather Agent: Provides weather information.
#
# Example input:
# Find the current temperatures in Seattle and San Francisco, calculate the difference in Celsius and Fahrenheit, and recommend what clothing to pack for each city.
#
kind: Workflow
maxTurns: 500
@@ -264,14 +267,14 @@ trigger:
output:
messages: Local.Plan
input:
arguments:
team: =Local.TeamDescription
messages: |-
=UserMessage(
"Please briefly explain what went wrong on this last run (the root cause of the failure),
and then come up with a new plan that takes steps and/or includes hints to overcome prior challenges and especially avoids repeating the same mistakes.
As before, the new plan should be concise, be expressed in bullet-point form, and consider the following team composition
(do not involve any other outside people since we cannot contact anyone else):
{Local.TeamDescription}")
As before, the new plan should be concise, be expressed in bullet-point form, and only involve the team members already described
(do not involve any other outside people since we cannot contact anyone else).")
- kind: SetTextVariable
id: setVariable_jW7tmM
@@ -11,7 +11,7 @@ trigger:
kind: OnConversationStart
id: workflow_demo
actions:
- kind: InvokeAzureAgent
id: question_student
conversationId: =System.ConversationId
+2 -2
View File
@@ -2,7 +2,7 @@
# These are optional elements. Feel free to remove any of them.
status: accepted
contact: westey-m
date: 2025-07-10 {YYYY-MM-DD when the decision was last updated}
date: 2025-07-10
deciders: sergeymenshykh, markwallace, rbarreto, dmytrostruk, westey-m, eavanvalkenburg, stephentoub
consulted:
informed:
@@ -139,7 +139,7 @@ Therefore something like `AgentResponse.Text` which also aggregates all `TextCon
#### Option 1.2 Presence of Secondary Content is determined by a runtime parameter
We can allow callers to choose whether to include secondary content in the list of reponse messages.
We can allow callers to choose whether to include secondary content in the list of response messages.
Open Question: Do we allow secondary content to use `TextContent` types?
```csharp
+8 -8
View File
@@ -113,7 +113,7 @@ Implement a hybrid strategy where common tools use generic `AITool`-derived abst
### AI Agent Tool Types Availability
Tool Type | Azure AI Foundry Agent Service | OpenAI Assistant API | OpenAI ChatCompletion API | OpenAI Responses API | Amazon Bedrock Agents | Google | Anthropic | Description
Tool Type | Microsoft Foundry Agent Service | OpenAI Assistant API | OpenAI ChatCompletion API | OpenAI Responses API | Amazon Bedrock Agents | Google | Anthropic | Description
-- | -- | -- | -- | -- | -- | -- | -- | --
Function Calling | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | Enables custom, stateless functions to define specific agent behaviors.
Code Interpreter | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | Allows agents to execute code for tasks like data analysis or problem-solving.
@@ -132,7 +132,7 @@ Image Generation | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | Generates or edits
#### Function Calling
<details>
<summary>Azure AI Foundry Agent Service</summary>
<summary>Microsoft Foundry Agent Service</summary>
Source: <a href="https://learn.microsoft.com/en-us/azure/ai-foundry/agents/how-to/tools/function-calling?pivots=rest">https://learn.microsoft.com/en-us/azure/ai-foundry/agents/how-to/tools/function-calling?pivots=rest</a>
Message Request:
@@ -401,7 +401,7 @@ Image Generation | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | Generates or edits
#### Code Interpreter
<details>
<summary>Azure AI Foundry Agent Service</summary>
<summary>Microsoft Foundry Agent Service</summary>
<p>Source: <a href="https://learn.microsoft.com/en-us/azure/ai-foundry/agents/how-to/tools/code-interpreter-samples?pivots=rest-api">https://learn.microsoft.com/en-us/azure/ai-foundry/agents/how-to/tools/code-interpreter-samples?pivots=rest-api</a></p>
<p>.NET Support: ✅</p>
@@ -709,7 +709,7 @@ Image Generation | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | Generates or edits
#### Search and Retrieval
<details>
<summary>Azure AI Foundry Agent Service</summary>
<summary>Microsoft Foundry Agent Service</summary>
Source: <a href="https://learn.microsoft.com/en-us/azure/ai-foundry/agents/how-to/tools/file-search-upload-files?pivots=rest">https://learn.microsoft.com/en-us/azure/ai-foundry/agents/how-to/tools/file-search-upload-files?pivots=rest</a>
File Search Request:
@@ -1083,7 +1083,7 @@ Image Generation | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | Generates or edits
#### Web Search
<details>
<summary>Azure AI Foundry Agent Service</summary>
<summary>Microsoft Foundry Agent Service</summary>
Source: <a href="https://learn.microsoft.com/en-us/azure/ai-foundry/agents/how-to/tools/bing-code-samples?pivots=rest">https://learn.microsoft.com/en-us/azure/ai-foundry/agents/how-to/tools/bing-code-samples?pivots=rest</a>
Bing Search Message Request:
@@ -1630,7 +1630,7 @@ Image Generation | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | Generates or edits
#### OpenAPI Spec Tool
<details>
<summary>Azure AI Foundry Agent Service</summary>
<summary>Microsoft Foundry Agent Service</summary>
Source: <a href="https://learn.microsoft.com/en-us/azure/ai-foundry/agents/how-to/tools/openapi-spec-samples?pivots=rest-api">https://learn.microsoft.com/en-us/azure/ai-foundry/agents/how-to/tools/openapi-spec-samples?pivots=rest-api</a><br>
Source: <a href="https://learn.microsoft.com/en-us/rest/api/aifoundry/aiagents/run-steps/get-run-step?view=rest-aifoundry-aiagents-v1&tabs=HTTP#runstepopenapitoolcall">https://learn.microsoft.com/en-us/rest/api/aifoundry/aiagents/run-steps/get-run-step?view=rest-aifoundry-aiagents-v1&tabs=HTTP#runstepopenapitoolcall</a>
@@ -1712,7 +1712,7 @@ Image Generation | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | Generates or edits
#### Stateful Functions
<details>
<summary>Azure AI Foundry Agent Service</summary>
<summary>Microsoft Foundry Agent Service</summary>
Source: <a href="https://learn.microsoft.com/en-us/azure/ai-foundry/agents/how-to/tools/azure-functions-samples?pivots=rest">https://learn.microsoft.com/en-us/azure/ai-foundry/agents/how-to/tools/azure-functions-samples?pivots=rest</a>
Message Request:
@@ -1832,7 +1832,7 @@ Image Generation | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | Generates or edits
#### Microsoft Fabric
<details>
<summary>Azure AI Foundry Agent Service</summary>
<summary>Microsoft Foundry Agent Service</summary>
Source: <a href="https://learn.microsoft.com/en-us/azure/ai-foundry/agents/how-to/tools/fabric?pivots=rest">https://learn.microsoft.com/en-us/azure/ai-foundry/agents/how-to/tools/fabric?pivots=rest</a>
Message Request:
+2 -2
View File
@@ -2,7 +2,7 @@
# These are optional elements. Feel free to remove any of them.
status: accepted
contact: westey-m
date: 2025-09-12 {YYYY-MM-DD when the decision was last updated}
date: 2025-09-12
deciders: sergeymenshykh, markwallace-microsoft, rogerbarreto, dmytrostruk, westey-m, eavanvalkenburg, stephentoub, peterychang
consulted:
informed:
@@ -25,7 +25,7 @@ See various features that would need to be supported via this type of mechanism,
- Also see [the openai human-in-the-loop guide](https://openai.github.io/openai-agents-js/guides/human-in-the-loop/#approval-requests).
- Also see [the openai MCP guide](https://openai.github.io/openai-agents-js/guides/mcp/#optional-approval-flow).
- Also see [MCP Approval Requests from OpenAI](https://platform.openai.com/docs/guides/tools-remote-mcp#approvals).
- Also see [Azure AI Foundry MCP Approvals](https://learn.microsoft.com/en-us/azure/ai-foundry/agents/how-to/tools/model-context-protocol-samples?pivots=rest#submit-your-approval).
- Also see [Microsoft Foundry MCP Approvals](https://learn.microsoft.com/en-us/azure/ai-foundry/agents/how-to/tools/model-context-protocol-samples?pivots=rest#submit-your-approval).
- Also see [MCP Elicitation requests](https://modelcontextprotocol.io/specification/draft/client/elicitation)
## Decision Drivers
@@ -1125,7 +1125,7 @@ Naming (Python): N/A (Composable Components)
Supports: N
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.
#### Smolagents (Hugging Face)
@@ -57,7 +57,7 @@ This section describes different options for various aspects required to add lon
### 1. Methods for Working with Long-Running Operations
Based on the analysis of existing APIs that support long-running operations (such as OpenAI Responses, Azure AI Foundry Agents, and A2A),
Based on the analysis of existing APIs that support long-running operations (such as OpenAI Responses, Microsoft Foundry Agents, and A2A),
the following operations are used for working with long-running operations:
- Common operations:
- **Start Long-Running Execution**: Initiates a long-running operation and returns its Id.
@@ -757,7 +757,7 @@ Some of them natively support resuming streaming from a specific point in the st
| API | Can Resume Streaming | Model |
|-------------------------|--------------------------------------|------------------------------------------------------------------------------------------------------------|
| OpenAI Responses | Yes | StreamingResponseUpdate.**SequenceNumber** + GetResponseStreamingAsync(responseId, **startingAfter**, ct) |
| Azure AI Foundry Agents | Emulated<sup>2</sup> | RunStep.**Id** + custom pseudo code: client.Runs.GetRunStepsAsync(...).AllStepsAfter(**stepId**) |
| Microsoft Foundry Agents | Emulated<sup>2</sup> | RunStep.**Id** + custom pseudo code: client.Runs.GetRunStepsAsync(...).AllStepsAfter(**stepId**) |
| A2A | Implementation dependent<sup>1</sup> | |
<sup>1</sup> The [A2A specification](https://github.com/a2aproject/A2A/blob/main/docs/topics/streaming-and-async.md#1-streaming-with-server-sent-events-sse)
@@ -765,7 +765,7 @@ allows an A2A agent implementation to decide how to handle streaming resumption:
a task is still active (and the server hasn't sent a final: true event for that phase), the client can attempt to reconnect to the stream using the tasks/resubscribe RPC method.
The server's behavior regarding missed events during the disconnection period (e.g., whether it backfills or only sends new updates) is implementation-dependent._
<sup>2</sup> The Azure AI Foundry Agents API has an API to start a streaming run but does not have an API to resume streaming from a specific point in the stream.
<sup>2</sup> The Microsoft Foundry Agents API has an API to start a streaming run but does not have an API to resume streaming from a specific point in the stream.
However, it has non-streaming APIs to access already started runs, which can be used to emulate streaming resumption by accessing a run and its steps and streaming all the steps after a specific step.
#### Required Changes
@@ -828,7 +828,7 @@ Sequence of updates from OpenAI Responses API to answer the question "What time
| resp_2 | 10 | resp.output_item.done | - | InProgress | |
| resp_2 | 11 | resp.completed | Completed | Completed | |
Sequence of updates from Azure AI Foundry Agents API to answer the question "What time is it?" using a function call:
Sequence of updates from Microsoft Foundry Agents API to answer the question "What time is it?" using a function call:
| Id | SN | UpdateKind | Run.Status | Step.Status | Message.Status | ChatResponseUpdate.Status | Description |
|--------|---------|-------------------|----------------|-------------|-----------------|---------------------------|---------------------------------------------------|
| run_1 | - | RunCreated | Queued | - | - | Queued | |
@@ -852,7 +852,7 @@ Sequence of updates from Azure AI Foundry Agents API to answer the question "Wha
To support long-running operations, the following values need to be returned by the GetResponseAsync and GetStreamingResponseAsync methods:
- `ResponseId` - identifier of the long-running operation or an entity representing it, such as a task.
- `ConversationId` - identifier of the conversation or thread the long-running operation is part of. Some APIs, like Azure AI Foundry Agents, use
- `ConversationId` - identifier of the conversation or thread the long-running operation is part of. Some APIs, like Microsoft Foundry Agents, use
this identifier together with the ResponseId to identify a run.
- `SequenceNumber` - identifier of an update within a stream of updates. This is required to support streaming resumption by the GetStreamingResponseAsync method only.
- `Status` - status of the long-running operation: whether it is queued, running, failed, cancelled, completed, etc.
@@ -1089,7 +1089,7 @@ public class ChatOptions
##### 6.1.5 Continuation Token of a Custom Type
The option is similar the the "6.1.3 Continuation Token of System.ClientModel.ContinuationToken Type" option but suggests using a
The option is similar to the "6.1.3 Continuation Token of System.ClientModel.ContinuationToken Type" option but suggests using a
custom type for the continuation token instead of the `System.ClientModel.ContinuationToken` type.
**Pros**
@@ -1203,7 +1203,7 @@ response = await agent.CancelRunAsync(response.ResponseId, new AgentCancelRunOpt
In case an agent supports either or both cancellation and deletion of long-running operations, it will override the corresponding methods.
Otherwise, it won't override them, and the base implementations will return null by default.
Some agents, for example Azure AI Foundry Agents, require the thread identifier to cancel a run. To accommodate this requirement, the `CancelRunAsync` method
Some agents, for example Microsoft Foundry Agents, require the thread identifier to cancel a run. To accommodate this requirement, the `CancelRunAsync` method
accepts an optional `AgentCancelRunOptions` parameter that allows callers to specify the thread associated with the run they want to cancel.
```csharp
@@ -1574,7 +1574,7 @@ the thread is provided with background operations consistently for all runs.
</details>
<details>
<summary>Azure AI Foundry Agents</summary>
<summary>Microsoft Foundry Agents</summary>
- Create a thread and run the agent against it and wait for it to complete using polling:
```csharp
@@ -34,11 +34,11 @@ Key changes:
1. **New `agent-framework-openai` package** with dependencies on `agent-framework-core`, `openai`, and `packaging` only.
2. **Class renames**: `OpenAIResponsesClient``OpenAIChatClient` (Responses API), `OpenAIChatClient``OpenAIChatCompletionClient` (Chat Completions API). Old names remain as deprecated aliases.
3. **Deprecated classes**: `OpenAIAssistantsClient`, all `AzureOpenAI*Client` classes, `AzureAIClient`, `AzureAIAgentClient`, and `AzureAIProjectAgentProvider` are marked deprecated.
4. **New `FoundryChatClient`** in azure-ai for Azure AI Foundry Responses API access, built on `RawFoundryChatClient(RawOpenAIChatClient)`.
4. **New `FoundryChatClient`** in azure-ai for Microsoft Foundry Responses API access, built on `RawFoundryChatClient(RawOpenAIChatClient)`.
5. **All deprecated `AzureOpenAI*` classes** consolidated into a single file (`_deprecated_azure_openai.py`) in the azure-ai package for clean future deletion.
6. **Core's `agent_framework.openai` and `agent_framework.azure` namespaces** become lazy-loading gateways, preserving backward-compatible import paths while removing hard dependencies.
7. **Unified `model` parameter** replaces `model_id` (OpenAI), `deployment_name` (Azure OpenAI), and `model_deployment_name` (Azure AI) across all client constructors. The term `model` is intentionally generic: it naturally maps to an OpenAI model name *and* to an Azure OpenAI deployment name, making it straightforward to use `OpenAIChatClient` with either OpenAI or Azure OpenAI backends (via `AsyncAzureOpenAI`). Environment variables are similarly unified (e.g., `OPENAI_MODEL` instead of separate `OPENAI_CHAT_MODEL_ID` / `OPENAI_CHAT_COMPLETION_MODEL_ID`).
8. **`FoundryAgent`** replaces the pattern of `Agent(client=AzureAIClient(...))` for connecting to pre-configured agents in Azure AI Foundry (PromptAgents and HostedAgents). The underlying `RawFoundryAgentChatClient` is an implementation detail — most users interact only with `FoundryAgent`. `AzureAIAgentClient` is separately deprecated as it refers to the V1 Agents Service API. See below for design rationale.
8. **`FoundryAgent`** replaces the pattern of `Agent(client=AzureAIClient(...))` for connecting to pre-configured agents in Microsoft Foundry (PromptAgents and HostedAgents). The underlying `RawFoundryAgentChatClient` is an implementation detail — most users interact only with `FoundryAgent`. `AzureAIAgentClient` is separately deprecated as it refers to the V1 Agents Service API. See below for design rationale.
### Foundry Agent Design: `FoundryAgentClient` vs `FoundryAgent`
@@ -7,11 +7,11 @@ consulted: Pratyush Mishra, Shivam Shrivastava, Manni Arora (Centrica eval scena
informed: Agent Framework team, Foundry Evals team
---
# Agent Evaluation Architecture with Azure AI Foundry Integration
# Agent Evaluation Architecture with Microsoft Foundry Integration
## Context and Problem Statement
Azure AI Foundry provides a rich evaluation service for AI agents — built-in evaluators for agent behavior (task adherence, intent resolution), tool usage (tool call accuracy, tool selection), quality (coherence, fluency, relevance), and safety (violence, self-harm, prohibited actions). Results are viewable in the Foundry portal with dashboards and comparison views.
Microsoft Foundry provides a rich evaluation service for AI agents — built-in evaluators for agent behavior (task adherence, intent resolution), tool usage (tool call accuracy, tool selection), quality (coherence, fluency, relevance), and safety (violence, self-harm, prohibited actions). Results are viewable in the Foundry portal with dashboards and comparison views.
However, using Foundry Evals with an agent-framework agent today requires significant manual effort. Developers must:
@@ -445,7 +445,7 @@ These factorings produce different scores for the same conversation. The framewo
### Azure AI: FoundryEvals
`Evaluator` implementation backed by Azure AI Foundry:
`Evaluator` implementation backed by Microsoft Foundry:
```python
class FoundryEvals:
@@ -812,4 +812,4 @@ public sealed class EvalItem
## More Information
- [Foundry Evals documentation](https://learn.microsoft.com/azure/ai-foundry/concepts/evaluation-approach-gen-ai) — Azure AI Foundry evaluation overview
- [Foundry Evals documentation](https://learn.microsoft.com/azure/ai-foundry/concepts/evaluation-approach-gen-ai) — Microsoft Foundry evaluation overview
@@ -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.
@@ -117,6 +122,13 @@ Monitor agent behavior and block suspicious actions post-facto.
- Uses existing `FunctionMiddleware` base class.
- 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.
### Backwards Compatibility
@@ -9,7 +9,7 @@ deciders: evmattso
## What is the goal of this feature?
Enable Agent Framework users to consume Foundry **toolboxes** — named, versioned bundles of tool definitions stored server-side in an Azure AI Foundry project — directly from `FoundryChatClient`, without dropping to the raw `azure-ai-projects` SDK.
Enable Agent Framework users to consume Foundry **toolboxes** — named, versioned bundles of tool definitions stored server-side in a Microsoft Foundry project — directly from `FoundryChatClient`, without dropping to the raw `azure-ai-projects` SDK.
A user who has configured a toolbox in the Foundry portal (or via the raw SDK) should be able to load it into an agent with a single call:
@@ -1,7 +1,7 @@
---
status: accepted
status: superseded by [ADR-0030](0030-hosted-platform-context-agentserver-2.0.md)
contact: rogerbarreto
date: 2026-05-07
date: 2026-06-29
deciders: rogerbarreto
consulted: []
informed: []
@@ -9,6 +9,8 @@ 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.
+524
View File
@@ -0,0 +1,524 @@
---
status: accepted
contact: eavanvalkenburg
date: 2026-06-30
deciders: eavanvalkenburg
consulted: rogerbarreto, moonbox3
---
# Python protocol helpers and optional execution state
## Context and Problem Statement
Agent Framework needs to help applications expose agents and workflows over external protocols such as OpenAI
Responses, Telegram, Activity Protocol, and future transports.
FastAPI, Starlette, Azure Functions, Django, Telegram SDKs, Bot Framework SDKs, and other app frameworks already own
route registration, dependency injection, middleware, authentication, background tasks, lifecycle, and native client
calls. Agent Framework should not duplicate those surfaces unless a specific hosting environment requires it.
## Decision Drivers
- Keep the released surface small enough to explain without first teaching a channel framework.
- Provide reusable Agent Framework run translation that works with FastAPI, Django, and other web frameworks.
- Let app/framework code own route declaration, auth, middleware, native SDK clients, command handling, and background
work.
- Keep stateful execution support explicit: session lookup/storage and workflow checkpoint lookup/storage may still need
a small AF-owned home.
## Considered Options
1. Create protocol-specific hosts.
2. Ship a full host/channel framework with route contribution and channel hooks.
3. Ship protocol conversion helpers plus optional execution state.
### 1. Create protocol-specific hosts
- Good: no new shared abstraction.
- Neutral: each protocol host can evolve independently.
- Bad: every package reinvents AF input/result mapping, session-key conventions, and stateful execution helpers.
### 2. Ship a full host/channel framework
- Good: one object can assemble routes, channels, session handling, hooks, and lifecycle callbacks.
- Good: app code using the supported host shape can be short.
- Bad: the framework owns concerns already handled by web frameworks, protocol SDKs and/or other services.
- Bad: users must understand `Channel`, contribution, hook, and host-dispatch concepts before they can see how a request
becomes `agent.run(...)`.
- Bad: the abstraction is hard to reuse outside the chosen web framework.
### 3. Ship protocol helpers plus optional execution state
- Good: protocol packages provide the Agent Framework run value directly: `<protocol>_to_run(...)` and
`<protocol>_from_run(...)` style helpers.
- Good: apps keep native FastAPI, Starlette, Azure Functions, Django, Bot Framework, or Telegram SDK code.
- Good: helper functions can be tested without a web framework app or host pipeline.
- Good: small state objects can still own target-coupled state: `AgentState` pairs an agent target with a `SessionStore`,
and `WorkflowState` resolves a workflow target while reusing the existing `CheckpointStorage` abstraction.
- Good: provides maximum configurability in handling input and outputs (outside of the conversions)
- Bad: building a first iteration of a new Host is more verbose.
- Bad: samples show more explicit route/client code than a fully assembled channel host.
## Decision Outcome
Chosen option: **3. Ship protocol helpers plus optional execution state**.
Protocol packages own:
- parsing protocol-native input into Agent Framework run input and options;
- rendering `AgentResponse`, `AgentResponseUpdate`, workflow results, or workflow updates back into protocol-native
response/event payloads;
- protocol-specific isolation/session id helper functions when useful, such as `telegram_session_id(update)`;
- protocol-specific typing/update event helpers where the protocol has a native concept.
Application or web-framework code owns:
- HTTP route declaration and route grouping;
- dependency injection;
- authentication and authorization;
- middleware;
- background tasks and webhook acknowledgement policy;
- native protocol SDK clients and outbound calls;
- command registration and command dispatch;
- request/response status codes and framework-specific error handling;
- choosing the isolation/session id source for the current deployment and route.
The application builder can make the server exactly as they see fit, but this is outside the responsibilities of this proposed scheme.
This might include implementing other known API surfaces from vendors like OpenAI, such as creating conversations, vector stores, deleting things, etc.
If they want they can build the full OpenAI API, but it will include code that does not rely on agent-framework-hosting, which is fine.
They are responsible for what they expose.
The optional execution-state helpers, if provided, are limited to shared execution state:
- `AgentState`: one `SupportsAgentRun`-compatible target plus a `SessionStore`;
- `WorkflowState`: one `Workflow`, `WorkflowBuilder`-shaped builder, orchestration builder, or workflow factory;
- `SessionStore`: plain async storage (`get` / `set` / `delete`) by an app-selected id.
The store does not create sessions. `AgentState` provides the target-aware `get_or_create_session(...)` helper because
only the state object has both the store and the resolved agent target. Workflow checkpointing should use the existing
`CheckpointStorage` abstraction directly; app/state code may keep a small cursor (`session_id -> checkpoint_id`) when it
needs to resume a workflow for a session.
These objects are **not** app objects, channel registries, or route owners. They do not own FastAPI/Starlette setup,
route contribution, protocol dispatch, command projection, or native SDK calls.
### Helper naming and families
Helpers should be protocol-specific, not generic. Avoid a generic `protocol_to_run(...)` name in public samples because it
hides the protocol-specific contract behind a second abstraction.
Protocol packages should consider these helper families. This table is a set of examples, not a required protocol or
checklist. Not every protocol needs every helper, but when a protocol has the concept the naming should stay consistent:
| Helper family | Shape | Purpose |
| --- | --- | --- |
| Run conversion | `<protocol>_to_run(...)` | Convert one protocol-native call/update/request into `Agent.run` or `Workflow.run` values. |
| Final rendering | `<protocol>_from_run(...)` | Convert a final `AgentResponse` / workflow result into protocol-native response payloads or operations. |
| Stream rendering | `<protocol>_from_streaming_run(...)` | Convert `ResponseStream` / workflow updates into protocol-native events or operations. |
| Session id extraction | `<protocol>_session_id(...)` | Extract the protocol's natural continuation/partition key from the call, if present. |
| Command/action parsing | `<protocol>_command(...)` | Parse a protocol-native command/action/operation name without deciding app policy. |
Examples:
- `responses_to_run(...)`, `responses_from_run(...)`, `responses_from_streaming_run(...)`,
`responses_session_id(...)`;
- `telegram_to_run(...)`, `telegram_from_run(...)`, `telegram_from_streaming_run(...)`,
`telegram_session_id(...)`, `telegram_command(...)`;
- `activity_to_run(...)`, `activity_from_run(...)`, `activity_session_id(...)`, `activity_command(...)`;
- `discord_to_run(...)`, `discord_from_run(...)`, `discord_session_id(...)`, `discord_command(...)`.
The app still owns what a parsed command means. For example, a Telegram `/new`, Discord slash command, Bot Framework
command activity, or A2A cancellation/request action may parse through a command/action helper, but the route or SDK
handler decides whether that command clears a session, cancels a task, calls an agent, or is ignored.
Additional helper functions can be protocol-specific when the concept is not broadly shared. Examples include
`telegram_chat_id(...)`, `telegram_callback_query_id(...)`, `telegram_media_file_id(...)`,
`discord_interaction_id(...)`, `a2a_task_id(...)`, `a2a_context_id(...)`, and MCP tool/prompt/resource helpers. These
helpers should still stay side-effect-free: they extract, normalize, or describe protocol data, while app/native SDK code
performs acknowledgements, sends/edits messages, resolves protected file URLs, applies rate limits, and registers
handlers.
### Security responsibilities for application builders
The application builder owns the trust boundary. Protocol helper packages can parse native payloads and expose candidate
ids or operations, but they do not authenticate callers, authorize access to state, or decide which side effects are
allowed.
Application code that uses these helpers are responsible for (this means that we advice you to think through these topics,
but ultimately, the choice of which controls are needed for the intended use case is up to the application builder):
- authenticate the caller through the app's normal mechanism before using protocol-provided ids;
- authorize any caller-supplied session, checkpoint, task, context, conversation, thread, or response id before loading
state for it;
- bind externally supplied ids to the authenticated user, tenant, workspace, installation, or chat context before using
them as `SessionStore` keys or checkpoint cursor keys;
- treat `<protocol>_session_id(...)` results as untrusted candidate keys until that ownership check has passed;
- keep platform-provided isolation helpers fail-closed outside their trusted hosting environment;
- authorize command/action effects such as reset, cancel, approve, submit, or tool invocation after parsing them;
- opt in explicitly before resolving protected media/resource/file URLs and passing them to a remote model provider;
- persist post-run session or checkpoint state only after `agent.run(...)`, `workflow.run(...)`, or stream finalization has
updated that state.
For Foundry specifically, helpers may read values established by Foundry hosting middleware, but must not treat raw
request headers as trusted Foundry isolation when the app is running outside Foundry. Implementations must test that
non-Foundry requests do not accept spoofable isolation headers as platform-provided keys.
For workflow checkpointing, the checkpoint boundary must be at least as specific as the authorized session/tenant
boundary. A shared storage lookup such as "latest checkpoint for workflow name" is safe only when the storage is already
scoped to the authorized session. In a shared durable store, map the authorized `session_id` to a checkpoint id or other
cursor and load that specific checkpoint.
### Session continuity
Session continuity remains explicit. Run parsing and isolation/session id selection are separate operations because
isolation can come from more than one source:
- protocol input, such as OpenAI Responses `previous_response_id`, a Telegram chat id, or an Activity conversation id;
- running environment, such as Foundry Hosted Agents user/chat isolation context;
- app-specific trusted middleware or route state.
The app chooses which helper to call for that route and deployment. For example:
- `responses_session_id(body)` from `agent-framework-hosting-responses`, which can return either a `resp_*` previous
response id or a `conv_*` conversation id when present;
- `telegram_session_id(update, bot_id=...)` from `agent-framework-hosting-telegram`, which uses the bot and sender for
private chats and the bot and chat for shared group sessions;
- `activity_session_id(activity)`, `discord_session_id(interaction_or_message)`, or
`a2a_session_id(request_context)` from their respective protocol packages;
- `foundry_user_isolation_key()` or `foundry_chat_isolation_key()` from `agent-framework-foundry-hosting`.
Keep these helpers outside `responses_to_run(...)`, `telegram_to_run(...)`, and other run-input parsers. That makes the
trust boundary visible: using a request-derived key is a different decision than using a platform-provided isolation key.
The application builder is also responsible for deciding whether the hosting environment is **persistent** (for example,
a long-running container or web app) or **transient** (for example, Azure Functions, Foundry Hosted Agents, or any
environment where process memory is not a reliable continuity boundary). That decision controls which state mechanisms are
safe to use:
- persistent single-process apps may use in-memory state for local development or simple deployments, while still needing
durable state for multi-replica continuity;
- transient apps must not rely on in-memory `SessionStore` state between calls and need a durable session store or a
service-owned continuation id;
- workflow hosts must choose an explicit `CheckpointStorage` and, when they need per-session resume, a durable
`session_id -> checkpoint_id` cursor because in-process workflow state and in-memory checkpoint cursors do not survive
transient execution.
A `SessionStore` stores `session_id -> AgentSession`, but it does not create sessions. `AgentState` resolves the agent
target and creates the session on first use. Reads return independent working copies so running from one continuation
point does not mutate the stored snapshot or another simultaneous branch:
For agent targets:
```python
session = await state.get_or_create_session(session_id)
target = await state.get_target()
result = await target.run(messages, session=session, options=options)
```
If the protocol mints a new continuation id as part of the response being created (for example, OpenAI Responses
`resp_*` ids), store the **post-run** session explicitly under that new id:
```python
session = await state.get_or_create_session(previous_response_id)
target = await state.get_target()
result = await target.run(messages, session=session, options=options)
await state.set_session(response_id, session)
```
`agent.run(...)` may update the session object (for example, with service continuation state), so the explicit store call
belongs after the run, not before it.
Response ids are immutable continuation points, so simultaneous callers can branch from one `previous_response_id` and
store their completed sessions under different new response ids. A stable `conversation_id` is a mutable head: the app
must explicitly update it after the run and provide single-writer coordination. The hosting state helper does not lock
an entire run or resolve concurrent updates to that stable key.
The session id is a partition key, not proof of identity. App or platform code must authenticate and authorize any
externally supplied key before using it.
### Workflow checkpoints
Workflow checkpointing is execution state, not protocol state. `WorkflowState` pairs a workflow target with checkpoint
state, but it should not wrap or replace the existing `CheckpointStorage` abstraction. Apps should pass the actual
`CheckpointStorage` they want the workflow to use. If an app needs per-session resume, it can keep a small cursor from
authorized `session_id` to `checkpoint_id` (or an equivalent store-specific resume token).
Workflow runs do not currently emit a checkpoint id on `WorkflowRunResult` or normal workflow events by default. The
runner receives checkpoint ids internally from `CheckpointStorage.save(...)`. App/state code that owns the storage can
observe the latest id by querying the storage after a run, for example
`await storage.get_latest(workflow_name=target.name)`.
For workflow targets, app code adapts the protocol helper output into the workflow's expected input and invokes the
workflow through the state object's target:
```python
# session_id must already be authenticated and authorized for this caller
target = await state.get_target()
result = await target.run(message=workflow_input, checkpoint_storage=checkpoint_storage)
latest = await checkpoint_storage.get_latest(workflow_name=target.name)
if latest is not None:
await checkpoint_cursor_store.set(session_id, latest.checkpoint_id)
```
If a route wants to resume from a prior checkpoint, it explicitly chooses the checkpoint and passes it to
`workflow.run(...)`:
```python
# session_id must already be authenticated and authorized for this caller
target = await state.get_target()
checkpoint_id = await checkpoint_cursor_store.get(session_id)
if checkpoint_id is None:
result = await target.run(message=workflow_input, checkpoint_storage=checkpoint_storage)
else:
result = await target.run(checkpoint_id=checkpoint_id, checkpoint_storage=checkpoint_storage)
latest = await checkpoint_storage.get_latest(workflow_name=target.name)
if latest is not None:
await checkpoint_cursor_store.set(session_id, latest.checkpoint_id)
```
`workflow.run(...)` writes checkpoints to the provided storage, so storage selection must be explicit at the route layer.
Protocol helper packages should not own checkpoint layout, route lifecycle, or durable execution.
## Non-goals for v1
The following remain outside the v1 protocol-helper contract. Some are deliberately app-owned in v1; others are possible
future framework work only after a separate design.
### App-owned in v1
The app builder owns these concerns with normal web-framework, SDK, platform, or application code:
- authentication, authorization policy, and allowlists;
- deciding whether identities across protocols map to the same `session_id`;
- non-originating sends using native SDK clients;
- background work, durable execution, retry, and replay when app code owns the work;
- routing between multiple agents.
This is easier in the protocol-helper model than it was in the host/channel model: app code already owns the native SDK
clients, route handlers, authenticated caller context, session id selection, and outbound send calls. An app can link
channels by choosing the same authorized `session_id` for multiple protocols, and can do non-originating delivery by
calling the destination protocol's native client directly. That does not make a reusable framework feature safe by
default; it just means the app-specific version no longer has to fight a host abstraction.
### Future framework work
The following require a reviewed identity, storage, delivery, replay, and observability model before becoming reusable
framework features:
- reusable cross-channel identity linking;
- framework-owned proactive or non-originating delivery;
- fan-out, multicast, selected-channel, active-channel, or all-linked delivery;
- framework-owned delivery observability, dead-letter handling, and replay semantics;
- cross-channel confidentiality and link policy.
These possible framework enhancements are tracked by [ADR-0028](0028-hosting-linking-multicast-enhancements.md). They are
not prerequisites for shipping or using the v1 protocol-helper surface. ADR-0028 was written against the earlier
host/channel framing and must be revised to align with this protocol-helper and execution-state boundary before those
enhancements are implemented.
## Consequences
Positive:
- The released surface is smaller and easier to inspect: helpers plus state, not a channel framework.
- Protocol helpers can be used from FastAPI, Starlette, Azure Functions, Django, CLI tools, tests, or native SDK webhook
handlers.
- App authors can use the authentication, dependency injection, lifecycle, and background-task tools they already know.
- Session continuity stays explicit and debuggable.
- Workflow checkpointing can still be centralized if needed without making protocol packages own routing.
Negative:
- Multi-protocol samples include explicit route/client code.
- Apps that want a batteries-included ASGI app must write or depend on an app-specific wrapper.
- Existing unreleased code and docs that mention channels, contribution, or hooks must be revised before release.
## More Information
- Follow-up linking and multicast ADR: [ADR-0028](0028-hosting-linking-multicast-enhancements.md). That ADR still uses
some earlier host/channel terminology and must be aligned before implementation work starts.
## Appendix: Developer experience sketch
The examples below are sketches, not runtime-ready sample code. They show the minimum shape a developer would need to
build: where protocol helpers are called, where app-owned auth/authorization belongs, where state is loaded/stored, and
where native framework code remains in charge.
### Optional execution state
`AgentState` and `WorkflowState` stay small: they are target-specific state holders, not app hosts.
```python
from typing import Protocol
from agent_framework import AgentSession, SupportsAgentRun, Workflow
class SupportsBuild(Protocol):
def build(self) -> Workflow: ...
class SessionStore:
async def get(self, session_id: str) -> AgentSession | None: ...
async def set(self, session_id: str, session: AgentSession) -> None: ...
async def delete(self, session_id: str) -> None: ...
class CheckpointCursorStore:
async def get(self, session_id: str) -> str | None: ...
async def set(self, session_id: str, checkpoint_id: str) -> None: ...
async def delete(self, session_id: str) -> None: ...
class AgentState:
def __init__(self, target: SupportsAgentRun, *, session_store: SessionStore | None = None) -> None: ...
async def get_target(self) -> SupportsAgentRun: ...
async def get_or_create_session(self, session_id: str) -> AgentSession: ...
async def set_session(self, session_id: str, session: AgentSession) -> None: ...
class WorkflowState:
def __init__(self, target: Workflow | SupportsBuild) -> None: ...
async def get_target(self) -> Workflow: ...
```
`WorkflowState` accepts direct `Workflow` instances, workflow factories, and builder-shaped objects with
`build() -> Workflow`. That structurally covers `WorkflowBuilder` and the builders in `agent_framework_orchestrations`
without making `agent-framework-hosting` depend on the orchestration package.
### Responses-only route
This sketch shows the intended Responses-only shape. The protocol package owns the Agent Framework run conversion helpers and
response-id minting details; the application owns FastAPI routing, auth, policy adjustment, and response construction.
```python
import os
from collections.abc import AsyncIterator
from agent_framework import Agent, ResponseStream
from agent_framework.openai import OpenAIChatClient
from agent_framework_hosting import AgentState # pyright: ignore[reportAttributeAccessIssue]
from agent_framework_hosting_responses import create_response_id, responses_from_run, responses_from_streaming_run, responses_session_id, responses_to_run # pyright: ignore[reportAttributeAccessIssue]
from fastapi import Body, FastAPI, Header, HTTPException
from fastapi.responses import JSONResponse, StreamingResponse
app = FastAPI()
agent = Agent(
client=OpenAIChatClient(),
name="Assistant",
instructions="Be concise and helpful.",
)
state = AgentState(agent)
@app.post("/responses")
async def responses(body: dict = Body(...), x_api_key: str | None = Header(default=None)) -> JSONResponse | StreamingResponse:
if x_api_key != os.environ["RESPONSES_API_KEY"]:
raise HTTPException(status_code=401, detail="bad api key")
# parse the request body into a set of AF objects
run = responses_to_run(body)
# get the candidate session id from the body
# can be a resp_* for previous_response_id or a conv_* for a conversation
candidate_session_id = responses_session_id(body)
# create a new response_id for this run
response_id = create_response_id()
# the developer can make any adjustments to the request, i.e.:
run["options"]["store"] = False
run["options"].pop("model", None)
# the options here are of the shape defined by the ChatClient/Agent
# load the session (or create a new one) - this is optional
# verify this caller owns candidate_session_id before loading it; API-key auth
# alone does not prove ownership of a caller-supplied resp_* or conv_* id
session_id = candidate_session_id or response_id
session = await state.get_or_create_session(session_id)
target = await state.get_target()
if run["stream"]:
stream = target.run(
run["messages"],
stream=True,
session=session,
options=run["options"],
)
async def stream_events() -> AsyncIterator[str]:
async for event in responses_from_streaming_run(
stream,
response_id=response_id,
session_id=candidate_session_id,
):
yield event
# agent.run may update the session during stream finalization, so store the post-run session explicitly
await state.set_session(response_id, session)
return StreamingResponse(stream_events(), media_type="text/event-stream")
result = await target.run(
run["messages"],
session=session,
options=run["options"],
)
# agent.run may update the session, so store the post-run session explicitly under the response id
# this might also be skipped, if the app chooses to respect `store=False` policy
await state.set_session(response_id, session)
return JSONResponse(responses_from_run(result, response_id=response_id, session_id=candidate_session_id))
```
### Responses-only Django class-based view
The same helper surface can be used without FastAPI. A Django app owns URL routing, CSRF/auth policy, request parsing,
and `JsonResponse` construction. In a real Django project this would live in the app's normal view module (for example
`assistant/views.py`) and be routed from that app's `urls.py`; Django discovers it through its standard project/app
layout, not through Agent Framework. This sketch shows the non-streaming path only; the streaming branch is the same
state/finalization pattern shown in the FastAPI sketch and is omitted here to avoid duplicating it.
```python
import json
import os
from agent_framework import Agent
from agent_framework.openai import OpenAIChatClient
from agent_framework_hosting import AgentState # pyright: ignore[reportAttributeAccessIssue]
from agent_framework_hosting_responses import create_response_id, responses_from_run, responses_session_id, responses_to_run # pyright: ignore[reportAttributeAccessIssue]
from django.http import HttpRequest, HttpResponseBadRequest, HttpResponseForbidden, JsonResponse
from django.views import View
agent = Agent(
client=OpenAIChatClient(),
name="Assistant",
instructions="Be concise and helpful.",
)
state = AgentState(agent)
class ResponsesView(View):
async def post(self, request: HttpRequest) -> JsonResponse:
if request.headers.get("x-api-key") != os.environ["RESPONSES_API_KEY"]:
return HttpResponseForbidden("bad api key")
try:
body = json.loads(request.body)
except json.JSONDecodeError:
return HttpResponseBadRequest("invalid json")
run = responses_to_run(body)
candidate_session_id = responses_session_id(body)
response_id = create_response_id()
options = run["options"]
# verify this caller owns candidate_session_id before loading it; API-key auth
# alone does not prove ownership of a caller-supplied resp_* or conv_* id
session_id = candidate_session_id or response_id
session = await state.get_or_create_session(session_id)
target = await state.get_target()
result = await target.run(
run["messages"],
session=session,
options=options,
)
await state.set_session(response_id, session)
return JsonResponse(responses_from_run(result, response_id=response_id, session_id=candidate_session_id))
```
@@ -0,0 +1,132 @@
---
status: proposed
contact: eavanvalkenburg
date: 2026-06-11
deciders: eavanvalkenburg
---
# Hosting linking and multicast enhancements
## Context and Problem Statement
[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.
- Authorization tests cover native-id allowlists, verified-claim allowlists, default-deny behavior, and misconfiguration failures.
- Delivery tests cover originating-only, specific-channel, active-channel, selected-channel, and all-linked routing.
- 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.
@@ -0,0 +1,641 @@
---
status: proposed
contact: sergeymenshykh
date: 2026-06-23
deciders: sergeymenshykh
---
# Skills Over MCP: Implementation Design Options
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.
### Index Entry Format
```json
{
"$schema": "https://schemas.agentskills.io/discovery/0.2.0/schema.json",
"skills": [
{
"name": "git-workflow",
"type": "skill-md",
"description": "Follow this team's Git conventions for branching and commits",
"url": "skill://git-workflow/SKILL.md"
},
{
"type": "mcp-resource-template",
"description": "Per-product documentation skill",
"url": "skill://docs/{product}/SKILL.md"
}
]
}
```
Key differences from `skill-md`:
| Field | `skill-md` | `mcp-resource-template` |
|-------|------------|-------------------------|
| `name` | Required (the skill name) | **Omitted** (represents many skills) |
| `type` | `"skill-md"` | `"mcp-resource-template"` |
| `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:
```json
{
"method": "completion/complete",
"params": {
"ref": {
"type": "ref/resource",
"uri": "skill://docs/{product}/SKILL.md"
},
"argument": {
"name": "product",
"value": ""
}
}
}
```
The server responds with possible completions:
```json
{
"completion": {
"values": ["widgets", "billing", "auth", "payments"],
"hasMore": false,
"total": 4
}
}
```
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.
**Flow:**
1. `AgentMcpSkillsSource.GetSkillsAsync()` reads `skill://index.json`
2. For each entry with `type: "mcp-resource-template"`:
- Parse the URI template to extract variable names (e.g., `{product}`)
- Call the MCP `completion/complete` API to get possible values for each variable
- Invoke the caller-provided callback with the variable name, description, and possible values
- The callback returns a selected value and a `bool` indicating whether to include the skill
3. Resolve the URI template with the selected values
4. Create an `AgentMcpSkill` from the resolved URI and add it to the skills list
**API sketch:**
```csharp
public delegate Task<(string? SelectedValue, bool IncludeSkill)> McpTemplateVariableSelector(
string templateDescription,
string variableName,
IReadOnlyList<string> possibleValues,
CancellationToken cancellationToken);
// Usage via builder:
var provider = new AgentSkillsProviderBuilder()
.UseMcpSkills(mcpClient, options => {
options.TemplateVariableSelector = async (description, variable, values, ct) =>
{
// Present to user, return selection
var selected = PromptUser(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:
```json
{
"$schema": "https://schemas.agentskills.io/discovery/0.2.0/schema.json",
"skills": [
{
"name": "unit-converter",
"type": "skill-md",
"description": "Convert between common units.",
"url": "skill://unit-converter/SKILL.md"
},
{
"name": "currency-converter",
"type": "skill-md",
"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).
read_skill_resource(string skillName, string resourceName)
```
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'.
load_skill(string skillName, string? origin = null)
// Added optional 'origin', made 'skillName' optional, and a direct skill:// URI is passed in 'resourceName'.
read_skill_resource(string resourceName, string? skillName = null, string? origin = null)
```
The optional `origin` identifies the source/MCP server that should handle the direct URI.
| Case | Call |
|------|------|
| Load skill | `load_skill("commit-guidelines")` |
| Relative resource | `read_skill_resource("commit-guidelines", "examples/COMMIT_EXAMPLES.md")` |
| `skill://` link (skill) | `load_skill(skillName: "skill://commit-guidelines/SKILL.md", origin: "DirectRefServer")` |
| `skill://` link (resource) | `read_skill_resource(resourceName: "skill://commit-guidelines/examples/COMMIT_EXAMPLES.md", origin: "DirectRefServer")` |
**Pros:**
- No new functions added: existing tool surface stays at two functions.
**Cons:**
- Unreliable on some models (gpt-4o, gpt-4.1-mini): it often omits `origin` when it should not or calls the wrong function.
- Optional parameters create silent ambiguity - the model can pass `origin` for non-MCP skills or omit it for `skill://` URIs.
### Option 2 (Proposed): Add a dedicated `read_skill_uri` function alongside existing ones
```csharp
// Existing functions stay unchanged.
load_skill(string skillName)
read_skill_resource(string skillName, string resourceName)
// New function added alongside: reads content by direct skill:// URI.
read_skill_uri(string uri, string origin)
```
| Case | Call |
|------|------|
| Load skill | `load_skill("commit-guidelines")` |
| Relative resource | `read_skill_resource("commit-guidelines", "examples/COMMIT_EXAMPLES.md")` |
| `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).
| Case | Call |
|------|------|
| Load skill | `read_resource(uri: "commit-guidelines")` |
| Relative resource | `read_resource(uri: "examples/COMMIT_EXAMPLES.md", origin: "commit-guidelines")` |
| `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.
Task<object?> ReadByUriAsync(string uri, string origin, CancellationToken cancellationToken = default);
}
```
Sources that support direct `skill://` URI reads - such as `AgentMcpSkillsSource` - implement this interface to opt in.
The provider discovers readers via a service locator and dispatches to the first that can handle the URI:
```csharp
// Discover all registered readers.
var readers = source.GetService<IEnumerable<ISkillUriReader>>();
// Pick the first reader that can handle the URI.
var reader = readers.FirstOrDefault(r => r.CanRead(uri, origin))
?? throw new InvalidOperationException($"No reader can handle URI '{uri}' from origin '{origin}'.");
// Delegate the read to it.
return await reader.ReadByUriAsync(uri, origin, cancellationToken);
```
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.
public virtual Task<object?> ReadByUriAsync(string uri, string origin, CancellationToken cancellationToken = default)
=> Task.FromResult<object?>(null);
// Existing member.
public abstract Task<IList<AgentSkills>> GetSkillsAsync(CancellationToken cancellationToken = default);
}
```
Sources opt in by overriding, and the provider calls them directly:
```csharp
// AgentMcpSkillsSource opts in by overriding the virtuals.
public override bool SupportsReadByUri => true;
// Handles the URI when its origin matches this source's MCP server.
public override bool CanReadByUri(string uri, string origin)
=> string.Equals(origin, this.Origin, StringComparison.OrdinalIgnoreCase);
// 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; }
}
builder.UseMcpSkills(mcpClient, options => options.IncludeServerInstructions = true);
```
When enabled, the instructions travel alongside the discovered skills on `AgentSkillsResult`:
```csharp
public class AgentSkillsResult
{
// The skills discovered from the source.
public IList<AgentSkill> Skills { get; }
// The MCP server instructions, when IncludeServerInstructions is enabled; otherwise null.
public string? Instructions { get; }
}
```
The `AgentSkillsProvider` then appends them to its own skill-usage guidance when building the agent's instructions:
```csharp
var result = await source.GetSkillsAsync(cancellationToken);
var instructions = DefaultSkillsInstructionPrompt;
if (!string.IsNullOrWhiteSpace(result.Instructions))
{
// Combine the provider's skill-usage guidance with the server instructions.
instructions += Environment.NewLine + result.Instructions;
}
```
### Enabling Direct Skill References
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.
```csharp
public sealed class AgentMcpSkillsSourceOptions
{
public bool EnableDirectReferences { get; set; }
}
builder.UseMcpSkills(mcpClient, options => options.EnableDirectReferences = true);
```
## Decision Outcome
### 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).
## References
- [SEP-2640: Skills Extension](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2640) - Draft proposal
- [SEP-2640 Implementation Guidelines: Model-Driven Resource Loading](https://github.com/modelcontextprotocol/experimental-ext-skills/blob/main/docs/sep-draft-skills-extension.md#hosts-model-driven-resource-loading)
- [MCP Completion API](https://modelcontextprotocol.io/specification/2025-11-25/server/utilities/completion) - Used for template variable resolution
- [MCP Resource Templates](https://modelcontextprotocol.io/specification/2025-11-25/server/resources#resource-templates)
- [Skills Over MCP Working Group](https://github.com/modelcontextprotocol/experimental-ext-skills)
- [Open Question #4: Multi-server skill dependencies](https://github.com/modelcontextprotocol/experimental-ext-skills/issues/39)
- [Anthropic Agent Skills - Overview](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/overview) - Prior art: single skill entrypoint + generic file reads
- [Anthropic Agent Skills in the SDK](https://code.claude.com/docs/en/agent-sdk/skills) - The `Skill` tool exposed to the model
@@ -0,0 +1,356 @@
---
status: accepted
contact: eavanvalkenburg
date: 2026-06-19
deciders: eavanvalkenburg, moonbox3, TaoChenOSU, chetantoshnival
consulted: westey-m
informed:
---
# Python identity lifetimes for sessions, tasks, and continuation
## Context and Problem Statement
Python `AgentSession` currently carries a local `session_id`, an optional opaque service continuation
`service_session_id`, and provider state. `service_session_id` is any service-owned value that lets that service continue
a conversation, session, or thread; chat clients happen to map it through the abstract `conversation_id` ChatOption, but
other agent types can use it differently. It is not a generic correlation field, and generic correlation should not
require parsing or understanding that opaque service-owned value.
The related issues mix values with different lifetimes:
- **Session / conversation identity**: values that group a multi-turn interaction. Examples: A2A `context_id`, OpenAI
Responses `conversation` (`conv_*`) or response-chain continuation (`previous_response_id`).
- **Task identity**: values that identify a protocol task and may affect future protocol calls. Example: A2A `task_id`.
- **Message / response identity**: values that identify an output message or response. Examples: A2A `message_id` /
`artifact_id`, OpenAI Responses response id (`resp_*`).
- **Continuation token**: a framework resume payload for in-progress work. It may contain the same underlying value as a
protocol id, such as A2A `task_id`, but it only exists when there is an unfinished operation to resume.
These values should not automatically live in the same object just because they all help "continue" something. A value
belongs in `AgentSession` only when it is needed to continue future calls across turns. A value that identifies one
result belongs on the response or message. A value that resumes in-progress work belongs in a `ContinuationToken`.
An `AgentSession` created for one agent is not expected to be guaranteed to work against another agent. When a session is
used with an incompatible agent, protocol, or service, the framework should still help users understand what is wrong as
early as possible, preferably before calling out to the remote service.
For #4673, native conversation identity propagation should be based on `AgentSession` where the value is durable session
state. For #4893, A2A `context_id` and `task_id` need a coherent Agent Framework mapping.
AG-UI is out of scope for the decision. Its `thread_id` already maps to `AgentSession.session_id` in the normal wrapper
path, and `run_id` is wrapper-owned event correlation. If AG-UI run correlation needs framework telemetry integration
later, that should be handled as a run-context/telemetry design, not as session identity.
### Concrete gap example
At the protocol level, the durable continuation payload shapes are different:
```json
// A2A: future calls may need multiple durable protocol fields
{
"context_id": "ctx_123",
"task_id": "task_789",
"task_state": "input_required"
}
```
```json
// OpenAI Responses: future calls usually need one continuation value
{
"previous_response_id": "resp_abc123"
}
```
The gap is that A2A continuation state is multi-field while OpenAI continuation is
typically single-field.
## Current implementation notes
- A2A currently has `A2AAgentSession`, but `A2AAgent.create_session(...)` does not automatically return it.
- A2A currently mirrors `context_id` into `service_session_id`; that is current behavior, not necessarily the target
abstraction.
- A2A `task_id` is not just cosmetic correlation. It is used for `task_id` when a task is `INPUT_REQUIRED`, for
`reference_task_ids` when refining a previous task, and inside `A2AContinuationToken` for in-progress tasks.
- `RawAgent._prepare_run_context(...)` currently forwards `active_session.service_session_id` as chat `conversation_id`,
so any non-string or formatted value affects existing chat-client paths.
- `OpenAIChatClient` maps chat options `conversation_id` to the Responses API as `previous_response_id` for `resp_*`,
`conversation` for `conv_*`, and defaults unrecognized strings to `previous_response_id`. When `store` is not `False`,
it returns `response.conversation.id` when available, otherwise `response.id`, as the next service continuation value.
- For Responses API, the response id (`resp_*`) is also the response/message identity surfaced as
`ChatResponse.response_id`; when used for continuation on the next request, it becomes the `previous_response_id`
value.
- Python A2A has not been released as stable yet, so its session factory or session shape can still be adjusted before
release.
## Decision Drivers
- Preserve `AgentSession.session_id` as the local/client conversation identity.
- Preserve `AgentSession.service_session_id` as an opaque service-owned continuation handle.
- Keep `AgentSession` for durable state needed across turns, not per-run bookkeeping.
- Store values needed by future calls in durable session state; keep values that only resume in-progress work in
`ContinuationToken`.
- Fix the current confusion where session, task, response, and continuation values can be treated as interchangeable
because they all participate in "continuing" something.
- Make the implementation following this ADR preserve the lifetime split clearly: future-call state, in-progress resume
tokens, response/message ids, and protocol event correlation must not be silently mixed.
- Expose durable continuation state in a typed way when future calls depend on it.
- Let telemetry correlate runs without parsing opaque service continuation handles.
- Reuse existing run/context surfaces before introducing a new identity abstraction.
- Keep MCP and other remote tool boundaries safe: framework identity must not be forwarded to remote tools unless an
existing explicit opt-in mechanism says so.
- Keep existing `AgentSession.to_dict()` / `from_dict()` migration and compatibility straightforward.
- Stay close to .NET where there is already behavior to match, especially A2A's `ContextId`, `TaskId`, and `TaskState`.
- Detect incompatible session identity shapes as early as practical, preferably before a remote service call.
## Non-goals
- Do not design a provider-agnostic conversation creation API here. That is tracked separately in #6622.
- Do not make `service_session_id` a generic telemetry or run-correlation field.
- Do not introduce a new identity object if existing run/context objects can carry the selected per-run correlation value.
- Do not make a session from one agent guaranteed to work against another agent.
- Do not optimize the public `agent.run(...)` API for protocol-wrapper internals.
## Remaining question: durable shape for additional continuation state
- Option A: Use protocol-specific `AgentSession` subclasses.
- Option B: Extend `service_session_id` with richer service-owned values.
- Option C: Add a dedicated dict for additional session details.
- Option D: Store additional durable state inside `AgentSession.state`.
### Option A: Use protocol-specific `AgentSession` subclasses
Each protocol or agent type that needs additional durable state keeps a specialized `AgentSession` subclass. For A2A,
that means keeping `A2AAgentSession` for A2A-specific durable state and changing `A2AAgent.create_session(...)` to return
that type.
Example:
```python
# First call returns a task that future A2A messages may need to reference.
session = await a2a_agent.create_session()
response = await a2a_agent.run(
message,
session=session,
)
# A2AAgent updates durable A2A protocol state from the returned task/status payload.
# The user does not set these manually.
assert isinstance(session, A2AAgentSession)
assert session.task_id is not None
assert session.task_state is not None
# Later call reuses the durable A2A session state. A2AAgent decides whether to send task_id
# for INPUT_REQUIRED or reference_task_ids for task refinement.
next_response = await a2a_agent.run(
next_message,
session=session,
)
```
- Good, because protocol-specific state stays in a protocol-specific type.
- Good, because it aligns with .NET A2A's `A2AAgentSession` shape.
- Good, because Python A2A can still make this pre-release session factory adjustment.
- Good, because `task_state` does not get promoted to a base `AgentSession` concept.
- Bad, because generic consumers cannot read protocol-specific state without knowing about the subclass or a helper API.
- Bad, because it depends on each subclass consistently setting shared session fields such as `service_session_id` where
those are part of the shared abstraction.
### Option B: Extend `service_session_id` with richer service-owned values
Keep the common `service_session_id` case as a plain string. When an agent/service needs more than one service-owned
continuation value, allow `service_session_id` to be a typed structured value, such as a `TypedDict`. The main session ID
used for `gen_ai.conversation.id` should still be extracted by the owning agent, not inferred by generic telemetry code.
Examples:
```python
simple_session = AgentSession(
service_session_id="resp_123",
)
structured_session = AgentSession(
service_session_id=A2AServiceSessionId(
context_id="ctx_123",
task_id="task_789",
task_state=TaskState.TASK_STATE_WORKING,
),
)
```
- Good, because the common case remains a plain string and stays simple.
- Good, because richer service-owned continuation state stays under the existing continuation property.
- Good, because a structured value can make framework-side validation possible before a value is sent back to a service.
- Good, because A2A can keep `context_id`, `task_id`, and `task_state` together as the service/protocol-owned continuation
value without adding A2A fields to base `AgentSession`.
- Neutral, because telemetry needs an agent-owned extractor to pick the `gen_ai.conversation.id` value from either a
string or structured `service_session_id`.
- Neutral, because Python A2A would need a pre-release adjustment to stop relying on `A2AAgentSession` for these fields.
- Bad, because changing the `service_session_id` type is a compatibility risk for users, providers, serialization, and
tests.
- Bad, because every path that sends `service_session_id` back to a service must consistently extract/adapt the
service-owned continuation component.
### Option C: Add a dedicated dict for additional session details
Keep `service_session_id` as the primary opaque service-owned continuation handle, and add a separate dictionary for
additional durable protocol/service values that need to travel with the session.
Example:
```python
session = AgentSession(
service_session_id="ctx_123",
session_details={
"task_id": "task_456",
"task_state": TaskState.TASK_STATE_WORKING,
},
)
```
- Good, because the main service continuation handle stays a plain `service_session_id` string.
- Good, because extra state has an explicit home and does not overload `service_session_id`.
- Good, because generic consumers can look in one documented place for additional session-scoped values.
- Neutral, because helper APIs can hide the raw dictionary access.
- Bad, because this still introduces string-keyed state unless the dict values are wrapped by typed helpers.
- Bad, because it adds another public session field that needs serialization, naming, and compatibility rules.
- Bad, because generic consumers still need to understand the shape or use helpers for the selected agent/session type.
### Option D: Store additional durable state inside `AgentSession.state`
Keep base `AgentSession` unchanged and store additional durable continuation/protocol state under namespaced keys in
`session.state`.
Example:
```python
session = AgentSession(session_id="ctx_123")
session.state["a2a"] = {
"task_id": "task_456",
"task_state": TaskState.TASK_STATE_WORKING,
}
```
- Good, because it avoids new public fields and avoids a subclass requirement.
- Good, because `AgentSession.state` already exists for provider/session state.
- Neutral, because helper APIs can hide the raw dictionary access.
- Bad, because stringly typed state is easier to corrupt and harder to validate.
- Bad, because generic consumers need helper APIs anyway; directly reading nested dictionaries is not a good abstraction.
- Bad, because users may accidentally overwrite or persist invalid protocol state.
## Decision
Chosen decision criteria for the future: **split identity by lifecycle**.
When a protocol emits an id/token, place it by answering "what lifecycle does this value serve?":
- **Future-call continuation state** -> durable session state. Examples: A2A `context_id` + `task_id` + `task_state`;
OpenAI Responses `previous_response_id`/`conversation`.
- **Single-result identity** -> response/message object only. Examples: OpenAI `resp_*`, A2A `message_id`,
A2A `artifact_id`.
- **Resume unfinished work** -> `ContinuationToken` only. Example: a token carrying in-progress task resume data.
- **Run-start-only request fields** -> run method arguments/options, not durable session state. Example: A2A
`reference_task_ids` for a specific follow-up/refinement request.
- **Per-run correlation/telemetry** -> protocol wrapper or run context, not `AgentSession`. Example: wrapper-managed
`run_id` used only for tracing/events.
Durable-state option decision: **Option B: Extend `service_session_id` with richer service-owned values**.
This does **not** add a new top-level identity abstraction; it keeps continuation identity under
`service_session_id` and keeps run correlation in existing run/telemetry context.
The immediate implementation gap is mainly in A2A mapping clarity, but the lifecycle split applies
consistently across providers.
To support telemetry, `BaseAgent` should expose a method that accepts an `AgentSession | None` and returns the value to
use for `gen_ai.conversation.id`. The default implementation should return `session.service_session_id` when it is a
string. Agents that use a structured `service_session_id`, such as `A2AAgent`, should override that method and return the
appropriate primary session/context value.
## Appendix: A2A `task_id` and `reference_task_ids` implementation check
The A2A protocol distinguishes a message's `task_id` from `reference_task_ids`:
- `task_id` associates the message with a specific task.
- `reference_task_ids` provides additional task context, for example when a new task refines or follows up on the result
of a previous task.
The protocol does not appear to prescribe that `task_id` and `reference_task_ids` are mutually exclusive. If both are
present, the natural reading is that the message is associated with one task while also referencing other tasks for
context. The serving agent decides how to interpret that context.
The Python implementation should check and likely adjust the current behavior:
- `task_id` should be updated by the current run when the remote A2A service returns a task/status payload.
- `task_id` should remain durable A2A session state when needed for future calls, for example when a task is
`INPUT_REQUIRED`.
- `reference_task_ids` should be a run parameter / caller intent for the current request, not implicit durable session
continuation state.
- A follow-up/refinement request should pass explicit `reference_task_ids` when it wants to reference previous tasks.
- If both session `task_id` and run `reference_task_ids` are present, the wrapper should preserve the protocol
distinction rather than treating one as a replacement for the other.
- If no `reference_task_ids` are supplied, the wrapper should not automatically infer them from the last session task
unless we deliberately keep that convenience for compatibility.
## Appendix: implementation notes for Option B
The exact names are implementation details, but the shape should be:
```python
class A2AServiceSessionId(TypedDict):
context_id: str
task_id: str | None
task_state: TaskState | None
class AgentSession:
def __init__(
self,
*,
session_id: str | None = None,
service_session_id: str | ServiceSessionId | None = None,
) -> None:
...
class BaseAgent:
def _get_otel_conversation_id(self, session: AgentSession | None) -> str | None:
service_session_id = session.service_session_id if session else None
return service_session_id if isinstance(service_session_id, str) else None
class A2AAgent(BaseAgent):
def _get_otel_conversation_id(self, session: AgentSession | None) -> str | None:
service_session_id = session.service_session_id if session else None
if isinstance(service_session_id, Mapping):
return service_session_id.get("context_id")
return service_session_id if isinstance(service_session_id, str) else None
class AgentTelemetryLayer:
def _trace_agent_invocation(...):
attributes = _get_span_attributes(
...,
thread_id=self._get_otel_conversation_id(session),
...,
)
```
This keeps the OpenTelemetry extraction decision with the agent that owns the service continuation shape. Generic OTel
code should not parse structured `service_session_id` values directly.
`AgentSession` must also be updated so `service_session_id` can store either the current string value or a structured
service-owned value. Serialization must preserve both shapes, and existing serialized sessions with string
`service_session_id` must continue to round-trip unchanged.
## More Information
Related work and issues:
- #4673: native conversation ID propagation.
- #4893: align A2A protocol concepts with Agent Framework session/continuation concepts.
- #2931: Foundry-specific conversation creation helper, split into a separate Python PR.
- #6622: broader provider-agnostic conversation creation API discussion requiring .NET sync.
- [ADR-0015](0015-agent-run-context.md): AgentRunContext for Agent Run.
- [ADR-0018](0018-agentthread-serialization.md): AgentSession serialization.
- [ADR-0026](0026-hosted-session-identity-context.md): hosted session identity context.
@@ -0,0 +1,84 @@
---
status: accepted
contact: rogerbarreto
date: 2026-06-29
deciders: rogerbarreto
consulted: []
informed: []
---
# Hosted platform context (user id + call id) for Foundry Hosting on AgentServer 2.0
Supersedes [ADR-0026](0026-hosted-session-identity-context.md).
## Context and Problem Statement
[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. |
| `PlatformHostedSessionIsolationKeyProvider` | internal sealed | Maps `context.PlatformContext.UserIdKey` (was `context.Isolation.UserIsolationKey` / `ChatIsolationKey`). |
| `HostedCallContext` | internal static | New. Request-scoped `AsyncLocal<string?>` holding the `x-agent-foundry-call-id` value. |
| `HostedConversationKey` | internal | New. Resolves the per-conversation partition key. |
| `FoundryToolboxBearerTokenHandler` | internal | Now also forwards `x-agent-foundry-call-id` outbound. |
| `HostedFoundryMemoryProviderScopes` | public | `PerChat` / `PerUserAndChat` removed; `PerUser` kept. |
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.
@@ -0,0 +1,119 @@
---
status: accepted
contact: rogerbarreto
date: 2026-06-30
deciders: rogerbarreto
consulted: []
informed: []
---
# Per-agent and per-user session-storage isolation for Foundry Hosting
Builds on [ADR-0030](0030-hosted-platform-context-agentserver-2.0.md).
## Context and Problem Statement
A Foundry hosted container can serve many end users (and, in .NET, many agents) over its lifetime. The
`AgentSessionStore` persists each turn's `AgentSession` (which for a workflow agent carries the workflow
checkpoint, and which also carries the tool-approval mapping via `ToolApprovalIdMap` in the session state
bag). [ADR-0030](0030-hosted-platform-context-agentserver-2.0.md) protected cross-user access only through
the strict-resume identity check (a 403 when the persisted `HostedSessionContext.UserId` does not match the
live request). The persisted artifacts themselves were keyed by `conversationId` (+ agent name), not
physically partitioned per user, so a forged `conversation_id` would still resolve to another user's file
path before the identity check rejected it.
The Python hosting package added physical per-user partitioning (`<root>/<user_id>/<context_id>`) plus a
reject-style path-traversal guard. We want .NET to provide the same defense-in-depth, adapted to the .NET
hosting model.
## Decision Drivers
- Defense in depth: a forged/guessed id must not even resolve to another tenant's storage path, independent
of the identity check.
- Multi-agent hosting: a single .NET container hosts multiple agents resolved from keyed DI, so the layout
must isolate per agent as well as per user (Python hosts a single agent and needs no agent layer).
- Path-traversal safety (CWE-22) for the untrusted, platform-injected user id.
- Back-compat for local development (no `x-agent-user-id` header) and for direct/non-hosted store use.
- Keep the change contained and avoid the async-iterator `AsyncLocal` revert hazard from ADR-0030.
## Considered Options
- **Path partition inside `FileSystemAgentSessionStore`**, threading the user id explicitly through the
`AgentSessionStore` API, with self-describing prefixed segments.
- A delegating store that prefixes the conversation id with the user id (the
`IsolationKeyScopedAgentSessionStore` pattern from `Microsoft.Agents.AI.Hosting`). Rejected: still needs the
user id on the read path and yields a flat key rather than nested per-tenant directories.
- An `AsyncLocal<string?>` user-context set by the handler. Rejected: the session is saved in the handler's
`finally` after the streaming `yield`s, where an `AsyncLocal` set up front is reverted (the same hazard
that forced explicit call-id re-application in ADR-0030). Explicit threading is safer and clearer.
- A separate per-user approval store (as in Python). Rejected as unnecessary: see below.
## Decision Outcome
Path layout with self-describing, prefixed segments; user id threaded explicitly:
{root}/ a-{agentName} / u-{userId} / c-{contextId}.json
- `a-` (agent), `u-` (user), `c-` (context) are constant literals applied to the sanitized/validated value,
so a collapsed layout is never ambiguous and a user id can never masquerade as an agent name.
- `contextId` is `HostedConversationKey.Resolve` (conversation_id, else the partition of
previous_response_id, else of the minted response id).
- The agent and context layers are always present (Foundry always deploys a named agent). The only
collapse is the `u-` layer: present when a user id is resolved (Foundry header, or local dev fallback),
absent for raw local runs with no header (`{root}/a-{agent}/c-{conv}.json`). There is no user-only or
no-agent layout.
Other elements:
- `string? userId` was added as a **required** parameter (no default) on `AgentSessionStore.GetSessionAsync` /
`SaveSessionAsync` (a contained, breaking change to the experimental Foundry abstraction; both in-tree
implementations and the two handler call sites were updated). It is required rather than optional so a
caller can never silently persist a session unscoped; a genuine no-user caller (local without the header,
or a non-hosted direct caller) passes `null` explicitly. `AgentFrameworkResponseHandler` resolves the user
id before loading the session.
- Path-traversal guard: the user id is rejected (not sanitized) when it is not a single safe path segment
(path separators, NUL, drive letters, rooted paths, all-dot segments). After building the path, the
fully-resolved path is asserted to remain under the storage root.
- The strict-resume 403 identity check from ADR-0030 is **kept** as the second defense layer (it still
catches a session that reaches the wrong partition, e.g. via a non-partitioning custom store or in-process
tampering).
- **No separate approval store.** The tool-approval mapping lives in `ToolApprovalIdMap` ->
`AgentSessionStateBag`, which is serialized into the session checkpoint, so partitioning the session path
isolates pending approvals per tenant automatically. (Python needs a separate per-user approval store only
because it models approvals as a standalone store.)
## Consequences
Positive:
- Cross-tenant isolation is now defense-in-depth: physical per-agent/per-user partitioning plus the identity
check. Approvals and workflow checkpoints inherit the partitioning because they ride in the session.
- Self-describing prefixes make the on-disk layout auditable and collision-free across collapse cases.
Negative:
- Breaking change to the experimental Foundry `AgentSessionStore` API (added `userId`).
- The on-disk layout and leaf filename change (`<conv>.json` -> `c-<conv>.json`), orphaning sessions written
by the ADR-0030 release. Acceptable for an experimental package; a fresh session is created on next use.
## Out of scope
- Encryption at rest and quota enforcement remain platform concerns.
- Non-Foundry hosting layers can adopt an equivalent scheme independently.
## Update (2026-07-01): local runs no longer fail closed; sample dev provider removed
Superseding the ADR-0026/0030 behavior where a `null` result from `HostedSessionIsolationKeyProvider`
always became a 500, `AgentFrameworkResponseHandler` now branches on `FoundryEnvironment.IsHosted`:
- **Hosted** (`IsHosted == true`, production): a `null` identity is still a hard error (500). Isolation
stays strict; the platform always injects `x-agent-user-id`.
- **Not hosted** (local `docker run` / `dotnet run`): a `null` identity is tolerated. Per-user isolation
is simply not triggered — the handler passes `userId == null` to the store (the documented "no user
partition", `{root}/a-{agent}/c-{conv}.json`), stamps no `HostedSessionContext`, and runs no
strict-resume check. Contributors can run a hosted image locally with zero extra setup.
Consequently the sample-side `DevTemporaryLocalUserIdProvider` and `AddDevTemporaryLocalContributorSetup`
were removed. To simulate distinct users locally, send an `x-agent-user-id` request header; the default
`PlatformHostedSessionIsolationKeyProvider` reads it via `ResponseContext.PlatformContext.UserIdKey`
(the SDK's `PlatformContext.FromRequest` populates it from the header unconditionally, hosted or not).
@@ -0,0 +1,177 @@
---
status: accepted
contact: rogerbarreto
date: 2026-07-08
deciders: rogerbarreto
consulted: eavanvalkenburg
informed: []
---
# .NET hosting: OpenAI Responses protocol helpers for app-owned routing
Realizes the helper-first direction of [ADR-0027](0027-hosting-channels.md) for .NET.
## Context and Problem Statement
[ADR-0027](0027-hosting-channels.md) refocused the (Python) hosting design away from a channel
framework toward **protocol conversion helpers plus optional execution state**: Agent Framework owns
protocol-native <-> run conversion, while the application owns HTTP routing, authentication,
middleware, storage, and native SDK calls.
.NET already ships `Microsoft.Agents.AI.Hosting.OpenAI`, a route-owning server that **exposes an
`AIAgent` (or workflow) as the OpenAI Responses API** (`MapOpenAIResponses` + `IResponsesService`). It
owns the routes, an in-memory response/conversation store, streaming, and lifecycle. The question is
what, if anything, .NET must add to satisfy the ADR-0027 boundary.
## Decision Drivers
- Do not reinvent conversion logic that already exists and is battle-tested in `Hosting.OpenAI`.
- Give applications a way to own their own route/auth/middleware/storage while reusing Agent Framework
conversion (the ADR-0027 boundary).
- Keep the released public surface small.
- Stay consistent with the existing .NET hosting stack, which deliberately does **not** use the OpenAI
SDK Responses types server-side (it hand-rolled its own wire model).
## Considered Options
1. Self-contained new package that reimplements conversion using the OpenAI SDK Responses types
(mirrors the Python `agent-framework-hosting-responses` lineage).
2. New package that reuses `Hosting.OpenAI`'s internal converters (via `InternalsVisibleTo` or by
moving the conversion core out).
3. Thin public helper facade **inside** `Hosting.OpenAI` over the existing internal converters, plus
protocol-neutral execution-state holders in `Microsoft.Agents.AI.Hosting`.
### First-principles gap analysis
A capability comparison of the ADR-0027 / PR #6891 helper surface against the existing .NET stack:
| Python helper capability | .NET today | Status |
| --- | --- | --- |
| `responses_to_run` | `ResponseInput.GetInputMessages` + `InputMessage.ToChatMessage` + `OpenAIResponsesMapOptions.RunOptionsFactory` | exists, internal |
| `responses_from_run` | `AgentResponseExtensions.ToResponse` | exists, internal |
| `responses_from_streaming_run` | `AgentResponseUpdateExtensions.ToStreamingResponseAsync` + `SseJsonResult` (also renders workflow events) | exists, internal, richer |
| `responses_session_id` | continuity resolved inside `InMemoryResponsesService` | exists, internal, not standalone |
| `create_response_id` | `IdGenerator` | exists, internal |
| `AgentState` (target + store, get-or-create, callable/awaitable target) | `AgentSessionStore` (get-or-create + save + serialize + isolation) + DI container (target lifetime + async setup) | create-on-miss lives in the store; per-run instance and deferred/async target come from DI, so no separate holder is needed |
| `SessionStore` (get/set/delete) | `AgentSessionStore` + `InMemoryAgentSessionStore` | richer; `Delete` added |
| `WorkflowState` + checkpoint resume | `WorkflowCatalog`/`HostedWorkflowBuilder`; workflow events already render over Responses; `CheckpointManager` is session-keyed | partial; no per-session checkpoint cursor |
| App owns routing/auth/middleware/storage | `MapOpenAIResponses`/`IResponsesService` own routing + storage | **the one real gap** |
.NET already covers ~90% of the capability, and more richly (its streaming renderer even emits workflow
events; its session store serializes and supports per-principal isolation, neither of which Python's
in-memory `SessionStore` does). The single genuine gap is the **ownership model**: every conversion
primitive is bundled behind the route-owning server, so an application cannot own its own route and
call just the conversion.
Note on lineage: Python's Responses offering was introduced *as a channel* (PR #6580) and always used
the `openai` SDK Responses types. .NET's `Hosting.OpenAI` predates and is independent of channels and
hand-rolled its own server-side wire DTOs (the SDK's Responses types are client-shaped and awkward
server-side). So Option 1 would both reinvent a working asset and contradict the .NET codebase's own
precedent.
## Decision Outcome
Chosen option: **3. Thin public helper facade inside `Hosting.OpenAI` plus neutral state holders**,
because the only real gap is the ownership model, so the work is to *un-bundle* the existing
converters, not to rebuild them or add a package.
### Public surface
`Microsoft.Agents.AI.Hosting.OpenAI` gains a single public static facade, `OpenAIResponses`, whose
boundary is `System.Text.Json` (`JsonElement`/streamed events), matching Python's dict boundary and
keeping the hand-rolled wire DTOs internal:
- `OpenAIResponses.ToAgentRunRequest(JsonElement body)` -> messages + `AgentRunOptions?`.
- `OpenAIResponses.WriteResponse(AgentRunResponse response, string responseId, string? sessionId = null)`
-> a Responses-shaped `JsonElement`.
- `OpenAIResponses.WriteResponseStreamAsync(IAsyncEnumerable<AgentRunResponseUpdate> updates, string responseId, ...)`
-> Responses SSE `data:` frames.
- `OpenAIResponses.GetSessionId(JsonElement body)` -> `previous_response_id` or `conversation` id, or
`null`. Kept **separate** from `ToAgentRunRequest` so the trust boundary is visible: choosing to use
a request-derived key is an explicit application decision.
- `OpenAIResponses.CreateResponseId()` -> a `resp_*` id.
All helpers are side-effect-free and delegate to the existing internal converters. `MapOpenAIResponses`
public behavior is unchanged; it and the facade share one internal conversion core (an internal
`ToResponse` overload with an optional originating request is added so the facade can render without a
request object).
### Optional execution state (neutral package)
`Microsoft.Agents.AI.Hosting` gains:
- `AgentSessionStore.DeleteSessionAsync(...)` (+ `InMemoryAgentSessionStore` implementation and
isolation-decorator passthrough): the one missing store operation.
- No agent-side holder. Applications use `AgentSessionStore` directly: `GetSessionAsync(agent, id)`
already creates on miss and returns an independent session instance per call (so concurrent calls fork
the same stored state rather than sharing an instance), `SaveSessionAsync(agent, id, session)` persists
post-run (including under a newly minted id), and `DeleteSessionAsync(agent, id)` removes it. An earlier
draft added a `HostedAgentState` holder, but once create-on-miss lives in the store and the store does no
cross-call locking, the holder would only bind the `agent` argument, which is not enough to justify a
public type. Any coordination for concurrent runs against the same id is the application's concern.
(Unlike Python, whose `SessionStore` is get/set-only and whose `AgentState` therefore owns
create-on-miss, .NET's store already owns it.)
Python's `AgentState` carries two further responsibilities beyond create-on-miss: it accepts a callable
or awaitable target so the host can (1) obtain a fresh agent instance per run and (2) defer expensive or
asynchronous agent setup while keeping server construction synchronous. In .NET these two concerns are
owned by the dependency-injection container, not by a hosting type. Per-run lifetime is expressed by the
registration lifetime (`AddScoped`/`AddTransient` yields a fresh `AIAgent` per request or scope, resolved
by the framework), and deferred or asynchronous construction is expressed by an async factory registration
(for example an `async` factory delegate, `ActivatorUtilities`, or resolving the agent inside the request
after any async warm-up), so the route handler resolves an already-built agent from the container. An
`AIAgent` is also safe to invoke concurrently (per-turn state lives in `AgentSession`, not the agent), so
the "fresh instance per run" motivation does not apply to it the way it does to a workflow. This is the
deliberate asymmetry with `HostedWorkflowState` below: a `Workflow` instance is a stateful run engine that
cannot be driven by two runners at once, so the factory/`cacheWorkflow` affordance is load-bearing there
for correctness, whereas for agents the container already provides both per-run instances and async setup.
- `HostedWorkflowState`: a thin holder bundling a workflow target with a `CheckpointManager` and an
internal `sessionId -> CheckpointInfo` head cursor, exposing `RunOrResumeAsync`. .NET's checkpoint
store is already `sessionId`-keyed (unlike Python's workflow-name keying), but `CheckpointInfo` has
no ordering, so the holder remembers the head checkpoint per session to resume. On subsequent turns it
restores that checkpoint and runs the workflow forward with the new turn's input (mirroring the Python
host's restore-then-run semantics), rather than continuing a halted run with no input. When the
in-memory cursor misses (new holder / process restart) it reads the session's latest checkpoint from the
`CheckpointManager`, so a durable manager resumes across restarts. It accepts either a single workflow
instance (which cannot be run by two runners at once, so its turns are processed one at a time) or a
workflow factory (`Func<CancellationToken, ValueTask<Workflow>>`). By default the factory builds a fresh
instance per run so independent sessions run in parallel; with `cacheWorkflow: true` the factory is invoked
once lazily and its result is cached and reused (a deferred, cached target that, like the instance, cannot
run concurrent turns). A resume rehydrates an instance from the session's checkpoint in the shared store, so
per-run instances still continue the same run; concurrent turns against the same session id remain the
application's coordination responsibility.
### Scope
Responses only for v1; the facade is named so a parallel `OpenAIChatCompletions` facade can follow.
No new package, no OpenAI-SDK-typed reimplementation, no change to `MapOpenAIResponses` public
behavior.
### Security responsibilities
Consistent with ADR-0027, the application owns the trust boundary. `GetSessionId(...)` returns an
untrusted candidate key; the application must authenticate the caller and authorize/bind the id before
using it as an `AgentSessionStore` key or workflow checkpoint session id. Multi-user hosts must scope
the session store per principal (`IsolationKeyScopedAgentSessionStore`). Helpers stay side-effect-free;
persistence happens only after the run completes.
## Consequences
Positive:
- Smallest possible surface: the released addition is one facade type plus one thin workflow state
holder and one new store method (agents use `AgentSessionStore` directly, no holder).
- No duplicated conversion; the app-owned-routing path and the route-owning server share one core.
- `MapOpenAIResponses` users are unaffected.
Negative:
- The facade's `JsonElement` boundary is less strongly typed than the internal DTOs (accepted to keep
the wire model internal and mirror Python's dict boundary).
- Workflow resume relies on an in-memory head cursor by default; durable multi-replica hosts must
supply their own cursor persistence.
## More Information
- Parent ADR: [ADR-0027](0027-hosting-channels.md).
- Spec: `docs/specs/003-dotnet-hosting-protocol-helpers.md`.
@@ -0,0 +1,104 @@
---
status: Accepted
contact: cgillum
date: 2026-07-21
deciders: cgillum, vrdmr, chetantoshniwal
consulted: westey-m, eavanvalkenburg, kshyju, larohra, ahmedmuhsin
informed:
---
# Extract Durable Task and Azure Functions hosting into a separate repository
## Context and Problem Statement
The Durable Task and Azure Functions hosting integrations (`agent-framework-durabletask`,
`agent-framework-azurefunctions`, plus their samples, docs, and CI) currently live in the
`microsoft/agent-framework` (MAF) monorepo. They carry heavyweight specialized dependencies
(Azure Functions runtime, Durable Task) and need integration-test infrastructure (Functions Core
Tools, Azurite, a DTS emulator) that the core repo otherwise does not.
This ADR proposes moving them into a dedicated repository
([`microsoft/agent-framework-durable-extension`](https://github.com/microsoft/agent-framework-durable-extension))
and considers how to do so without breaking existing users who import them today.
## Decision Drivers
- **Independent lifecycle** — the hosting integrations should be able to version and release on their
own cadence, decoupled from core (extends [ADR-0008](0008-python-subpackages.md)'s goal of keeping
heavyweight/optional dependencies out of the main package).
- **Dependency & CI isolation** — keep core lean and its PR pipeline free of heavyweight hosting
dependencies and integration-test prerequisites.
- **Ownership** — a dedicated repo would give the integrations their own issues, CODEOWNERS, and
contribution flow.
- **No breaking change** — existing `from agent_framework.azure import …` code and
`pip install agent-framework[all]` should keep working (stable-import-path guarantee, ADR-0008).
## Considered Options
1. **Keep in the MAF repo** (status quo).
2. **Move out, drop the core shim** — the extension becomes standalone; core stops re-exporting the
types and removes them from `[all]`.
3. **Move out, keep core's backward-compat shim + `[all]`** (proposed) — the code would live in the
new repo; core would still lazily re-export the entry-point types from `agent_framework.azure` and
keep both packages in the `[all]` extra (resolved from PyPI).
## Decision Outcome
Proposed choice: **Option 3.** Extract the integrations for lifecycle, dependency, and ownership
isolation, while preserving the existing import surface so the move is invisible to consumers.
Option 1 forgoes the isolation benefits; Option 2 achieves them but would be a breaking change for
existing imports and the `[all]` extra.
### Consequences
- Good — would give independent release cadence, a leaner/faster core repo and CI, and clear
ownership for the hosting integrations.
- Good — no user-visible break: existing imports and `agent-framework[all]` would continue to work
unchanged.
- Neutral — type *definitions* would live once in the extension; the core shim would re-export only a
curated subset of entry-point types (no metadata duplication). The extension's own samples/docs
would import directly from `agent_framework_durabletask` / `agent_framework_azurefunctions`; the
shim would be compatibility-only.
- Neutral — users may still open GitHub issues against the core repo for problems in the extension,
but the extension's own repo would be the primary place for issues and PRs. These issues would
need to be triaged and transferred to the extension repo.
- Neutral — **.NET public API boundary.** The extension should prefer the smallest stable public core
API over friend-assembly access where the capability is useful to external hosts or tooling. For
workflow routing metadata, the agreed first step is to expose a read-only `Workflow.Edges` view plus
public `EdgeData.Connection` and `FanOutEdgeData`, while keeping graph construction internal
([#7448](https://github.com/microsoft/agent-framework/issues/7448),
[#7459](https://github.com/microsoft/agent-framework/pull/7459)). This reduces internal coupling but
adds a public API compatibility commitment. Any remaining internal dependencies would still need to
be evaluated individually before retaining `InternalsVisibleTo`.
- Bad — **Python version coordination.** Core's shim correctness would track the extension's publish
cadence. In the other direction, when an extension package adopts a new core API, maintainers would
need to choose per feature between raising its minimum core version (simpler, but forces every
extension user to upgrade) and conditional imports with fallback behavior (preserves support for
older core versions, but adds implementation and testing complexity).
## Validation
Compliance would be validated by:
- Python: `uv lock --check` passing with both packages resolving from PyPI; the shim entry-point
symbols importing at runtime after `uv sync --all-extras`; `pyright` staying clean on
`agent_framework/azure/__init__.pyi`; and extension tests running against both the minimum supported
and current core versions when conditional compatibility behavior is used.
- .NET: tests from an external assembly confirming that workflow routing metadata is inspectable
through the agreed public surface while graph construction remains internal.
A known risk is **publish-lag**: if a symbol is added to core's shim before the extension has
published a release that exports it, that symbol would not resolve at runtime. The mitigation would
be to omit any such symbol from the shim until the extension publishes it, then add the entry and
re-lock.
## More Information
- Related: [ADR-0008](0008-python-subpackages.md) (vendor namespaces + stable import paths),
[ADR-0021](0021-provider-leading-clients.md) (lazy-loading gateways),
[issue #7448](https://github.com/microsoft/agent-framework/issues/7448) and
[PR #7459](https://github.com/microsoft/agent-framework/pull/7459) (.NET workflow routing API).
- Follow-ups: during extraction, keep the shim's re-exported symbols in sync with each newly
published extension release (adding any symbol only once the extension publishes it); document the
direct-import convention in the extension's samples READMEs so samples are not switched back to the
shim.
@@ -0,0 +1,642 @@
---
status: accepted
contact: eavanvalkenburg
date: 2026-07-22
deciders: eavanvalkenburg, chetantoshniwal
consulted: TaoChenOSU, moonbox3, peibekwe, rogerbarreto, westey-m
informed:
---
# Feature-usage bitmask in the User-Agent
## Context and Problem Statement
We can see which Agent Framework packages are installed and that *some* framework
call happened (via the existing `agent-framework-python/{version}` User-Agent),
but we have no usage-based signal about **which features are actually exercised**
at runtime, nor which are used *together* (e.g. workflows + MCP + Foundry). How
can we collect a lightweight, privacy-respecting signal of feature usage for the
traffic we can actually read, without standing up new event pipelines?
The detailed mechanism is in [SPEC-004](../specs/004-feature-usage-telemetry.md);
the per-language bit tables are in
[feature-usage-bit-registry.md](../specs/feature-usage-bit-registry.md).
## Decision Drivers
- **Transparency** — openly documented, human-decodable, user-controllable. No
hidden or obfuscated telemetry.
- **First-party scope / no third-party leakage** — emission requires both an
explicitly approved client/pipeline family and an approved actual HTTPS origin
on every request (including redirects). Credentials or an Azure setting alone
never approve a custom gateway/origin.
- **Live signal** — read the process's observed-feature set *so far* at request
send time, rather than freezing it at client construction.
- **Low cost / few moving parts** — reuse telemetry already in the request path;
bounded fixed-width processing; as little machinery as the job needs.
- **Privacy** — encode only coarse "observed at least once" Boolean feature
state, never counts; no identifiers, arguments, prompts, payloads,
model/deployment names, endpoints, or customer-defined names.
- **Use, not presence** — package-level indexes mean a capability reached its
first meaningful activation, not that a package was installed/imported or a
DI container constructed an unused service.
- **Versioning discipline** — v1 is a point-in-time decision. Adding bits later is
easier than removing or redefining them, so the initial table should lean toward
fewer bits and avoid forcing v2 shortly after launch.
- **Allocation discipline** — each bit represents a stable framework-owned
capability with a concrete product/support question and an actual-use mark
point; implementation detail and speculative distinctions stay out.
## Considered Options
The options below are grouped by the decisions that matter: the **transport**,
the **granularity**, and the **registry sharing model**.
### Transport
#### A. User-Agent token, first-party only, per request (chosen)
Stamp a `(feat=...)` comment onto the UA, but only on approved Azure/Foundry
client pipelines, and re-evaluate it per request.
- Good, reuses telemetry already sent to approved backends we can read.
- Good, request-time stamping reflects the live mask (not frozen at construction).
- Good, first-party scoping means no fingerprint leaks to third-party providers.
- Good, two-factor destination approval (pipeline + actual origin) denies custom
`base_url` gateways and strips the token on unapproved redirect hops.
- Good, maps onto .NET's existing per-request UA pipeline policies unchanged.
- Neutral, v1 stamps only pipelines the framework creates or can configure
through supported public hooks. It does not mutate caller-owned clients or
reach into private SDK pipelines.
- Bad, no signal for traffic that never hits a first-party endpoint (accepted —
we couldn't read it anyway).
#### B. User-Agent token on all clients
- Good, simplest to wire (one static header).
- Bad, sends a deployment fingerprint to OpenAI/Anthropic/AWS/Google logs we
cannot read — privacy leak for zero benefit.
- Bad, baked into static `default_headers`, so it freezes at client construction
and reports a near-empty mask.
#### C. OpenTelemetry span/resource attribute
- Good, precise per-call usage; no UA change.
- Bad (**privacy — the main reason to hold it**), a span attribute broadcasts the
feature-combination fingerprint into the user's **general** telemetry pipeline,
which is typically exported to third-party APM vendors (Datadog, Honeycomb, …).
That re-introduces exactly the fingerprint leakage the first-party-only UA
scoping (A) was chosen to avoid — just into a different set of third parties.
- Bad (secondary), also a cardinality footgun (a growing, combinatorial value
must never become a metric dimension).
- Neutral, for the team's own goal it reaches us only if the user exports to
Azure Monitor and we query it.
- **Deferred, not rejected.** The version prefix lets us add it later **if** the
User-Agent path cannot answer a concrete query and there is an acceptable
scoped/redacted variant.
#### D. Bespoke usage events
- Good, richest detail and flexibility.
- Bad, new data flow and cost; larger privacy surface; heavy to build and review;
overkill for a coarse "which features" signal.
#### E. Install/import-time signal only (status quo-ish)
- Good, zero new runtime work.
- Bad, measures installation, not usage; cannot capture feature combinations —
does not solve the problem.
### Accumulation scope
#### S1. Process-global, monotonic mask (chosen)
A single mask per process; bits are OR-ed in as features are first used and never
cleared. The token reflects "what this process has used so far."
- **Binary interpretation:** a set bit means the feature was observed at least
once in this process before the request was sent. A bit repeated on later
requests is the same Boolean observation, not another feature use. It cannot be
summed into invocation, request, agent, user, or tenant counts.
- Good, fits our **mixed feature lifecycle**: many features are *not* bound to an
outbound service request — an agent/workflow may first run or build, a
context/history provider may first participate in a session, and a host may
start serving before the request that later emits the token. A process-wide
mask can carry those activations forward.
- Good, trivial and cheap: one OR under a lock (Python) / one atomic OR into one
of two 64-bit lanes (.NET); no per-request state plumbing.
- Good, deliberately coarse for privacy: it avoids emitting a sequence of exact
per-call feature combinations that could reconstruct a workload's behavioral
trace.
- Neutral, coarser than per-call — early requests carry fewer bits than later
ones, and the token says "this process used X", not "this call used X" or "X
was used this many times."
For example, at time 1 Agent A can use MCP and a Foundry chat client. At time 2,
Agent B in the same worker can make a normal Foundry chat call without MCP. The
time-2 request still carries the MCP bit because MCP was previously observed in
that process. It does **not** say Agent B used MCP, nor count a second MCP use.
#### S2. Per-request set, reset between calls (botocore's model — rejected)
AWS botocore scopes its `m/` feature codes to a `contextvars` set that is reset
between requests, giving exact per-call attribution (and it deliberately no-ops
when called outside a request context to avoid features bleeding across requests).
See [Prior art](#prior-art).
- Good, exact per-call attribution directly in the User-Agent.
- Bad, **assumes every feature is exercised inside a single service request**
true for botocore (an SDK natively bound to AWS service calls), but *not* for
us. Our features split into request-scoped ones (a chat call, an MCP tool
invocation) and decidedly non-request ones (workflow build/start, provider
participation, hosting startup). The latter have no service request to attach to, so a
per-request set would simply miss them.
- Bad, needs `contextvars` propagation through every async/threaded path and a
reset discipline, plus enable/disable calls around every scoped operation; the
bleed-guard botocore documents is the warning sign.
- Bad, creates a more detailed per-call behavioral trace, increasing the privacy
sensitivity and review burden compared with a coarse process-lifetime Boolean.
- Note, per-call attribution for the request-scoped subset is better served by
the deferred OTel span path (option C) than by reshaping the UA token.
### Granularity
The mechanism can support several granularities. The remaining decision before
implementation is how detailed v1 should be. The estimates below are
intentionally rough; v1 uses a fixed 128-bit bound to leave useful headroom
without making the registry unbounded.
#### F0. Package-level bits
One bit per package, set on first use of a package-owned public API, client,
provider, or tool. It is **not** set on install, import, or assembly load.
Examples that get bits:
- `agent-framework-core` when `Agent`, `AgentSession`, `Workflow`, etc. is used.
- `agent-framework-tools` when a `LocalShellTool` or `DockerShellTool` first
executes/probes its shell capability.
- `agent-framework-foundry` when a `FoundryChatClient`, `FoundryAgent`, etc.
performs its first Foundry operation.
- `agent-framework-openai` when `OpenAIChatClient`,
`OpenAIEmbeddingClient`, etc. performs its first provider operation.
- `agent-framework-azure-ai-search` when `AzureAISearchContextProvider` is used.
- `agent-framework-azure-cosmos` when `CosmosHistoryProvider` is used.
- `agent-framework-redis` when `RedisContextProvider` or `RedisHistoryProvider`
is used.
Examples that do **not** get separate bits: merely installed dependencies;
imports or DI construction with no activation; `Agent` vs `AgentSession` vs
`InMemoryHistoryProvider`; `FunctionTool` vs `MCPStdioTool` vs `LocalShellTool`
vs `DockerShellTool`; `FoundryChatClient` vs `FoundryAgent`; `OpenAIChatClient`
vs `OpenAIEmbeddingClient`.
Rough estimate: Python ~25-35 bits; .NET ~15-25 bits.
- Good, lowest specificity and simplest registry.
- Good, clearly measures usage rather than dependency inventory if bits are set
only at package-owned public API/client/provider/tool use sites.
- Bad, does not answer which major capability within a package is used.
#### F1. Package + major capability bits
Package bits plus selected major capabilities that are product-distinct and stable
across implementations.
Examples that get bits:
- `agent-framework-core` plus `Agent`.
- `AgentSession` plus `InMemoryHistoryProvider` / `FileHistoryProvider` as one
history capability.
- `Workflow` / `FunctionalWorkflow` as one workflow capability.
- `FunctionTool`; MCP transports as one MCP capability; shell tools as one shell
capability.
- Skills provider plus stable source types: file, in-memory/programmatic, and
MCP-backed skills (with .NET inline/class skill distinctions).
- Foundry chat/agent/embedding capabilities; OpenAI chat/embedding capabilities.
Examples that do **not** get separate bits: `InMemoryHistoryProvider` vs
`FileHistoryProvider`; `WorkflowBuilder`, `AgentExecutor`, `FunctionExecutor`, or
`FanOutEdgeGroup`; `MCPStdioTool` vs `MCPStreamableHTTPTool` vs
`MCPWebsocketTool`; `LocalShellTool` vs `DockerShellTool` vs
`ShellEnvironmentProvider` vs `ShellPolicy`; `OpenAIChatClient` vs
`OpenAIChatCompletionClient`; skill-source decorators such as caching, filtering,
deduplication, and aggregation.
Rough estimate: Python ~60-70 indexes; .NET ~45-55 indexes. The current candidate
registry is at 63 Python / 52 .NET assigned indexes.
- Good, likely answers the first product adoption questions while staying compact.
- Good, fits comfortably within 128 bits while leaving room for additive package
and feature growth.
- Neutral, some provider internals remain collapsed until a later additive bit is
justified.
#### F2. Public construct / concrete type bits
One bit per public construct that users intentionally instantiate or configure.
Examples that get bits:
- `Agent`, `AgentSession`, `InMemoryHistoryProvider`, `FileHistoryProvider`.
- `Workflow`, `WorkflowBuilder`, `FunctionalWorkflow`.
- `FunctionTool`, `MCPStdioTool`, `MCPStreamableHTTPTool`, `MCPWebsocketTool`.
- `LocalShellTool`, `DockerShellTool`, `ShellEnvironmentProvider`, `ShellPolicy`.
- `FoundryChatClient`, `FoundryAgent`, `OpenAIChatClient`,
`OpenAIChatCompletionClient`, `OpenAIEmbeddingClient`.
Examples that do **not** get separate bits: `Agent.run` vs
`Agent.run_streamed`; workflow edge/executor internals such as `AgentExecutor`,
`FunctionExecutor`, or `FanOutEdgeGroup`; `LocalShellTool` persistent vs
stateless mode; `ShellPolicy` allowlist vs denylist configuration; `FunctionTool`
approval mode or result parser choices.
Rough estimate: Python ~70-100 bits; .NET ~55-80 bits.
- Good, concrete and directly tied to public API use.
- Neutral, fits within 128 bits at the current estimate, but consumes much of the
deliberate growth reserve.
- Bad, adds many call sites and more fingerprint specificity for v1.
#### F3. Construct subtype / configuration bits
Split important constructs by mode, transport, storage, or workflow primitive
when that distinction matters.
Examples that get bits:
- `InMemoryHistoryProvider` and `FileHistoryProvider` separately.
- `FunctionalWorkflow`, `WorkflowBuilder`, `AgentExecutor`, `FunctionExecutor`.
- `FanOutEdgeGroup`, `FanInEdgeGroup`, `SwitchCaseEdgeGroup`.
- `LocalShellTool` persistent, `LocalShellTool` stateless, `DockerShellTool`.
- `MCPStdioTool`, `MCPStreamableHTTPTool`, `MCPWebsocketTool`;
`OpenAIChatClient` vs `OpenAIChatCompletionClient`.
Examples that do **not** get separate bits: exact session id or persisted history
file path; exact shell command, workdir, timeout, or output cap; exact MCP server
command, URL, or tool names from the server; exact workflow graph shape or edge
count; model/deployment names, prompts, tool arguments, payloads.
Rough estimate: Python ~110-150 bits; .NET ~85-125 bits.
- Good, useful where mode-level distinctions are decision-relevant.
- Bad, trades simplicity for precision, increases fingerprint specificity, and
may exhaust or exceed 128 bits in Python.
#### F4. Option / behavior flag bits
The most detailed framework-owned option: bits for specific modes and behavior
switches, still excluding customer/runtime values.
Examples that get bits:
- Agent streaming used vs non-streaming used.
- `FunctionTool` `approval_mode="always_require"` vs `"never_require"`.
- `FunctionTool` `SKIP_PARSING` / result-parser path used.
- MCP sampling configured; MCP long-running task support used.
- `LocalShellTool` `clean_env` / `confine_workdir`; `DockerShellTool` container
mode.
Examples that do **not** get separate bits: function names wrapped by
`FunctionTool`; approval rule arguments or approval decisions; MCP remote tool
names or schemas; shell command text or policy regex patterns; prompt/message
content, model names, URLs, tenant/user/session identifiers.
Rough estimate: Python 150+ bits; .NET 120+ bits.
- Good, maximum framework-owned detail.
- Bad, exceeds or nearly exhausts 128 bits and is too detailed for v1 without a
concrete decision that requires it.
### Registry sharing model
#### H. Per-language bit lists (chosen)
Each SDK owns an independent list; the decoder picks the list using the language
already present in the UA product token.
- Good, **no cross-language coordination**: each SDK numbers and evolves its
features independently; adding a Python feature never touches .NET numbering.
- Good, no null placeholders for one-SDK features, no "same bit, same meaning"
rule, no SDK-aware decode caveats.
- Good, decoding is trivial: language (from UA) + version -> list -> AND.
- Neutral, two small lists to maintain instead of one (but they were going to
diverge anyway — the packages differ).
#### I. Single shared cross-language registry
- Good, one list, one number space.
- Bad, forces synchronized numbering and null placeholders for features that
exist in only one SDK, plus SDK-aware decode rules.
- Bad, the synchronization is pure accidental complexity — **the language is
already in the User-Agent**, so sharing the number space buys nothing.
### Registry maintenance
#### J. Package-local indexes + parity/no-overlap test (chosen)
- Good, each package owns private `FeatureIndex` declarations only for its own
rows; adding an optional-provider index does not require a core release after
the marker API exists.
- Good, one repository test compares the package-local declarations with the
per-language table and rejects missing rows, wrong ids, out-of-range indexes,
and any duplicate/overlapping index.
- Good, no build step, no generator to own.
#### K. Code-generate the enums from the registry
- Bad, a generator + drift test + schema test to maintain a short list of
integer constants; likely justified only if v1 deliberately chooses the most
detailed L3/L4 granularities.
### Representation (how the mask is rendered as text)
All examples below encode the same mask — bits 0, 2, 32, 48, 56 set
(agent + workflow + sequential-orchestration + foundry.chat_client + openai, in
the Python v1 list) = decimal `72339073309605893`.
#### L. Decimal — `feat=v1.72339073309605893`
- Good, human-familiar; trivial to parse.
- Neutral, no visual alignment to four-bit groups; slightly longer than hex for
large masks. No advantage over hex.
#### M. Hex (chosen) — `feat=v1.101000100000005`
- Good, compact (≤32 chars for a 128-bit mask).
- Good, decodes with one stdlib call in every language (`int(x, 16)` /
two 64-bit lane parses in .NET); each hex character corresponds to four
consecutive bit positions.
- Good, lowercase, no `0x` prefix, no leading zeros — unambiguous and stable.
A grouped variant such as `feat=v1.101.0001.0000.0005` was also considered.
Separators make the value longer and must be removed before `int(x, 16)` can
parse it, while the ordinary hex digits already preserve fixed four-bit groups.
#### N. Binary — `feat=v1.100000001000000000000000100000000000000000000000000000101`
- Good, directly shows every zero/one position.
- Bad, grows to 128 payload characters and is difficult to scan reliably.
#### O. Bit-list — `feat=v1.0,2,32,48,56`
- Good, most directly human-readable ("which bits").
- Bad, needs delimiter handling and grows with the number of set bits; a full
128-bit list is substantially larger than every fixed-width representation.
#### P. Alphabet / base-N (e.g. Crockford base32 `feat=v1.208004000005`, base62 `feat=v1.5LJRx1i6xJ`)
- Good, shortest representation.
- Bad, needs a custom alphabet + decode table on both ends; base62 is
case-sensitive (fragile through case-normalizing intermediaries); not
directly readable. Premature optimization for a value that is already ≤32
chars in hex.
All forms are ASCII. The table shows total bytes added to the existing
User-Agent, including the leading space and `(feat=v1.)` wrapper:
| Representation | Example (5 bits) | All current Python rows (63) | All current .NET rows (52) | Full 128-bit v1 |
| --- | ---: | ---: | ---: | ---: |
| Hex | 26 | 34 | 30 | 43 |
| Grouped hex | 29 | 39 | 34 | 50 |
| Decimal | 28 | 38 | 34 | 50 |
| Binary | 68 | 100 | 86 | 139 |
| Bit-list | 23 | 189 | 156 | 412 |
| Crockford base32 | 23 | 29 | 26 | 37 |
| Base62 | 21 | 26 | 24 | 33 |
There is no defensible average before rollout, and the design does not depend on
one: a process-global mask may eventually contain every assigned row. There is
no smaller per-request bit budget because the bits are not request-scoped; the
registry allocation tenet controls how many distinctions v1 assigns. Client
processing is bounded by the fixed 128-bit width: marking performs one
lock/atomic OR, and request-time stamping reads the mask, formats at most 32 hex
characters, and replaces one User-Agent comment. It performs no registry scan,
network call, or per-feature enable/disable bookkeeping.
## Decision Outcome
Chosen: **a request-time-stamped, first-party-only User-Agent `(feat=...)` token (A),
with a 128-bit process-global monotonic accumulator (S1), per-language bit lists
(H), package-local index enums kept honest by parity and no-overlap tests (J),
rendered as lowercase hex (M).**
This is a bounded design with enough v1 headroom. A 128-bit
**process-global, monotonic** mask accumulates from universal
`mark_feature_used()` calls (so it spans build/start/participation activations
that aren't bound to any service request — the per-request set model (S2) can't);
the token is **stamped per request** only when both the client/pipeline and the
actual HTTPS origin are approved, so custom origins and cross-origin redirects
cannot inherit the fingerprint; each
SDK owns an independent bit list selected by the language already in the UA; the
mask is rendered as hex (`feat=v1.101000100000005`). The dedicated
`AGENT_FRAMEWORK_FEATURE_MASK_DISABLED` opt-out drops only the mask while
keeping the base SDK identity/version User-Agent. Python's existing
`AGENT_FRAMEWORK_USER_AGENT_DISABLED` continues to suppress its entire
contribution, including the mask; this decision does not introduce a matching
whole-User-Agent switch in .NET. OTel (C) is deferred — mainly because a
broadly-emitted span attribute would leak the fingerprint into the user's
general telemetry, against the first-party-only stance and would require
user-side OTel setup that may still not make the data available to us — but left
open behind the version prefix. Per-request scoping (S2), a shared registry (I),
codegen for the initial registry (K), and the decimal/grouped-hex/binary/bit-list/
base-N representations (L, M variant, N, O, P) are rejected as complexity or
length the problem does not require.
The remaining choice before implementation is the **v1 granularity level** among
F0-F4. This is a point-in-time decision: adding new bits later is easier than
removing or redefining them, because removals/redefinitions require a new
registry version and historical decode tables. For v1, prefer the least detailed
level that answers the known product/support questions so we do not force a v2
shortly after launch. The refreshed candidate registry uses **63 Python indexes and
52 .NET indexes**, leaving 65 and 76 positions respectively. That headroom supports
normal growth; it does not waive the registry's
[allocation tenet](../specs/feature-usage-bit-registry.md#allocation-tenet).
### Consequences
- Good, adds a bounded-cost usage signal with no new data flow and few moving
parts.
- Good, transparent (public registry, human-decodable token) and disabled by a
dedicated `AGENT_FRAMEWORK_FEATURE_MASK_DISABLED` mask-only opt-out. Python's
existing whole-User-Agent opt-out also suppresses the mask.
- Good, first-party-only + request-time stamping gives a live mask and no
third-party fingerprint leak.
- Good, 128 bits leaves useful v1 headroom; .NET remains lock-free by storing two
independently atomic 64-bit lanes; per-language lists remove all cross-language
sync; package-local enums avoid both codegen and provider→core release coupling.
- Neutral, the token's reach equals eligible framework-configured first-party
traffic; broader per-call signal (OTel) can be added later if needed.
- Neutral, every set bit is a repeated Boolean observation after first use;
request rows carrying it are not feature invocation counts.
- Neutral, v1 granularity is intentionally a separate choice; the registry should
start with fewer bits unless a more detailed bit answers a concrete question.
- Bad, each feature must add an activation mark, first-party clients need a
per-request destination-aware hook, and the registry validator must scan all
package-local index declarations.
## Prior art
SDK telemetry-in-the-User-Agent is well-established; this design is closest to
AWS's, and conventional in the rest. Summary of what comparable SDKs do:
| SDK | What's in the UA / headers | Usage-based? | Opt-out | Closest to ours? |
| --- | --- | --- | --- | --- |
| **AWS botocore** | structured UA with an `m/` token: a per-request set of **short feature codes** for features actually exercised (`WAITER``B`, `PAGINATOR``C`, retry mode, checksums, credential source, …) | **Yes** — registered at call time via `register_feature_id`, contextvar-scoped per request | `AWS_SDK_UA_APP_ID` sets app id (no opt-out for `m/`) | **Yes — direct analog** |
| **OpenAI / Anthropic** (Stainless) | sidecar `X-Stainless-*` headers: lang, package version, OS, arch, runtime, runtime version; plus per-request `x-stainless-retry-count`, `x-stainless-read-timeout` | Mostly static identity (retry/timeout are per-request) | none | No (static identity) |
| **Azure SDK** (`azure-core`) | `User-Agent: azsdk-python-{pkg}/{ver} Python/{pyver} ({platform})` | No | `AZURE_TELEMETRY_DISABLED` (tracing spans only, **not** the UA) | No |
| **Google API core** | `x-goog-api-client: gl-python/… grpc/… gax/… gapic/…` | No | none | No |
| **LangSmith** | `User-Agent: langsmith-py/{ver}`; usage lives in trace payloads | No (header) | opt-in via `LANGSMITH_TRACING_V2`/`LANGCHAIN_TRACING_V2`; `…HIDE_INPUTS/OUTPUTS` | No |
Takeaways that shaped (or validate) our choices:
- **AWS `m/` is the precedent for usage-based feature flags in a first-party
User-Agent.** It validates the core idea. Its key *difference* is the encoding:
AWS uses a **comma-separated set of 12 char short codes** (open-ended, no bit
coordination, but variable length), whereas we use a fixed-width **hex
bitmask** (compact, bounded, decode-by-AND, but needs per-language bit
allocation). We keep the bitmask for boundedness and trivial AND-decoding;
AWS's short-code set is recorded as a viable alternative if bit-position
coordination ever becomes painful (it would also drop the fixed 128-bit bound).
- **A fixed-width bitmask gives bounded token size for free.** botocore must cap
the `m/` component at 1024 bytes and truncate at delimiter boundaries (with a
fallback log) precisely *because* its short-code set is unbounded. Our 128-bit
hex is ≤32 chars by construction — no size cap, no truncation logic.
- **Scope is where we diverge most — and deliberately.** botocore collects
features into a per-request `contextvars` set that is **reset between
requests**, and no-ops outside a request context to prevent cross-request
bleed. That works because every botocore feature is exercised *inside* an AWS
service request. We are more general: some features are request-scoped (a chat
call, an MCP tool invocation) but many are **not bound to any request**
(workflow build/start, provider participation, hosting startup). So we use a
**process-global, monotonic** mask (option S1), which is the only scope that can
represent the non-request features. Our mask therefore intentionally "bleeds"
(accumulates) for the life of the process — the opposite of botocore's reset —
and that is the intended semantic, not the bug botocore guards against.
- **The mechanism is private; the wire format is the contract.** botocore marks
its whole user-agent module private and "subject to abrupt breaking changes."
Same for us: the Python/.NET helpers are internal, and only the emitted token +
the per-language registry tables are the stable, decodable contract.
- **First-party-only emission** is stricter than any of the above; the closest in
spirit is Stainless headers, which only reach the owning API. We make the
client/pipeline allowlist explicit (initially Foundry/Azure OpenAI) rather than
attempting to infer safety from arbitrary request URLs. Other Azure clients
join only after telemetry access is confirmed.
- **Opt-out naming.** `AZURE_TELEMETRY_DISABLED` is the family precedent for our
`AGENT_FRAMEWORK_*_DISABLED` names. Separately, the cross-tool `DO_NOT_TRACK`
convention (honored by e.g. HuggingFace Hub) is worth considering — see Open
Questions.
Sources: botocore [`useragent.py`](https://github.com/boto/botocore/blob/develop/botocore/useragent.py)
(`_USERAGENT_FEATURE_MAPPINGS`, `register_feature_id`, `_build_feature_metadata`);
openai-python [`_base_client.py` `platform_headers()`](https://github.com/openai/openai-python/blob/main/src/openai/_base_client.py);
anthropic-sdk-python [`_base_client.py`](https://github.com/anthropics/anthropic-sdk-python/blob/main/src/anthropic/_base_client.py);
azure-core [`_universal.py` `UserAgentPolicy`](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/core/azure-core/azure/core/pipeline/policies/_universal.py);
google-api-core [`client_info.py`](https://github.com/googleapis/python-api-core/blob/main/google/api_core/client_info.py);
langsmith-sdk [`client.py`](https://github.com/langchain-ai/langsmith-sdk/blob/main/python/langsmith/client.py) /
[`utils.py`](https://github.com/langchain-ai/langsmith-sdk/blob/main/python/langsmith/utils.py);
huggingface_hub [`constants.py`](https://github.com/huggingface/huggingface_hub/blob/main/src/huggingface_hub/constants.py).
## Registry versioning and migration (v1 → v2)
The token carries a **per-language** version (`feat=v1.<hex>`); a version bump is
independent for Python and .NET.
- **Additive growth stays on v1 — no bump.** Allocating a new feature to a
reserved/unused bit is backward-compatible: an older decoder simply sees an
unknown bit and ignores it. Normal package growth never needs a new
version.
- **A bump (v2) is required only for breaking changes:** renumbering or
re-partitioning existing bits, changing the *meaning* of an already-assigned
index, or widening beyond 128-bit. Within a version an index is **never** reused or
reassigned — that invariant is what lets old decoders stay correct.
- **The draft 64→128 change is still v1.** No v1 token or enum has shipped, so
this pre-implementation repartition establishes the initial contract rather
than migrating an existing one.
- **Mixed-version coexistence is the norm.** A fleet runs many SDK releases at
once, so `v1` and `v2` tokens appear simultaneously for a long time (old SDKs
keep emitting `v1`). The decoder keeps **every** published `(language,
version)` table and selects by the token's version; the `v1` table is retained
indefinitely for historical decode.
- **Unknown version → do not guess.** A decoder without the `vN` table must
record "unknown registry version" rather than decode against an older table —
bit meanings may differ across versions, so mis-attribution is worse than
no data.
- **Producing v2:** publish the v2 table alongside v1, update the affected
package-local `FeatureIndex` declarations and SDK version constant, and emit
`v2` from the release that ships them. Prefer staying on v1 (additive) and
reserving a clean v2 for an eventual deliberate re-partition.
## Limitations
| Limitation | Caused by (choice) | Why we accepted it |
| --- | --- | --- |
| **No signal for self-hosted or third-party-only traffic.** If a process never calls Azure/Foundry, we see nothing. | First-party-only emission (A) | We can't read third-party logs anyway, and must not leak a fingerprint into them. Reach traded for privacy. |
| **Not every first-party client is stampable.** Caller-supplied `AIProjectClient` / OpenAI clients and toolkit-owned clients may not expose a supported per-request policy hook. | Supported-hook-only emission (A) | V1 does not mutate caller-owned clients or private SDK pipelines. Those features may still appear on another eligible request from the same process-global mask. |
| **Custom origins intentionally receive no feature token.** A customer gateway may use Azure credentials or Azure-named settings but route to a non-approved origin. | Two-factor destination classification (A) | Credentials and configuration names are not proof of telemetry ownership. Unknown/custom origins and cross-origin redirects are denied by default. |
| **No OTel / per-call signal in v1.** | OTel deferred (C) — primarily on **privacy** and availability grounds | A broadly-emitted span attribute would push the fingerprint into the user's general telemetry / third-party APM vendors, undoing the first-party-only scoping. It also requires customer/user OTel setup, and even Foundry users may not export data where we can query it. Left open only if there is a compelling reason to add. |
| **Mask reflects "usage so far," not the whole session.** Early requests carry fewer bits than later ones. | Process-global accumulator + request-time stamping | Honest and still useful as a Boolean process-lifetime observation. Repeated request rows must not be summed as additional uses. Reading the mask at request time makes it *grow* rather than freeze. |
| **No per-agent / per-call attribution.** The mask is one process-wide value — "this process used X", not "this agent/call used X". | Process-global monotonic scope (S1) | A deliberate choice, not a transport limit: botocore *does* per-call attribution in the UA via a per-request `contextvars` set, but many AF activations (workflow build/start, provider participation, hosting startup) occur outside the service request that later emits the token. Per-call detail remains deferred to OTel. |
| **Shared processes intentionally carry usage across agents and tenants.** A request can include bits first set by another workload in the same worker. | Process-global monotonic scope (S1) | The token must be interpreted only as process-level "used so far," never as request/user/tenant attribution. Privacy review must explicitly accept this. |
| **Bits are binary, sticky observations — not countable events.** Once set, a bit appears on every later eligible request from that process, so raw request counts repeat the same observation and long-lived/high-traffic processes dominate. | Monotonic mask stamped at request time | The signal supports coarse observed-feature and co-occurrence questions only. It cannot provide first-use counts, unique-process counts, request attribution, or feature invocation frequency. |
| **Granularity may be too coarse or too detailed.** The chosen level may miss useful distinctions or create more specificity than needed. | v1 granularity choice (F0-F4) | This is the main remaining decision. Adding bits later is easier than removing/redefining them, so v1 should lean toward fewer bits that answer known questions. |
| **.NET snapshots span two atomic lanes.** A bit can be marked between the low/high reads, so one request may omit that just-added bit. | 128-bit width without a global lock | The mask is monotonic: the snapshot cannot invent or clear a bit, and the next request includes the addition. This matches the existing "usage so far" timing semantics. |
| **Fingerprinting risk is reduced, not eliminated.** A feature-combination mask is still a deployment signature, and it transits intermediaries (proxies/CDNs) even when first-party-scoped. | Emitting any feature-combination value | Scope + opt-out + coarse granularity mitigate it; v1 should avoid unnecessary detailed bits. |
## Open Questions (for decider discussion)
These are unresolved and should be decided before implementation:
1. **Which v1 granularity level (F0-F4)?** This is the primary remaining choice.
Adding bits later is easier than removing or redefining bits, so v1 should
choose the least detailed level that answers known questions and avoids a quick
v2.
2. **Privacy approval for the v1 User-Agent signal.** Before implementation,
confirm that a transparent, opt-out, first-party-only feature-combination
fingerprint is acceptable, including the exact client allowlist, retention,
access, and permitted product queries. This is a rollout precondition.
3. **When (if ever) to add the OTel path?** Held back mainly for **privacy** and
data availability: a span attribute broadcasts the fingerprint into the user's
general telemetry and onward to third-party APM vendors, contradicting the
first-party-only stance, and it requires user-side OTel setup that may not make
the data available to us even for Foundry users. It also carries a
metric-cardinality hazard. Revisit only if the User-Agent path cannot answer a
concrete question.
4. **Honor the cross-tool `DO_NOT_TRACK` convention?** Several ecosystems treat
`DO_NOT_TRACK=1` as a universal telemetry opt-out (HuggingFace Hub honors it;
see [Prior art](#prior-art)). Should our mask opt-out also respect
`DO_NOT_TRACK` (in addition to `AGENT_FRAMEWORK_FEATURE_MASK_DISABLED` and
Python's pre-existing whole-UA flag)? Cheap to add and
community-friendly, but it widens the opt-out surface and needs a clear
precedence rule. Recommend yes; confirm with the deciders.
### Decided
- **Dedicated opt-out flag — included.** In addition to the existing
Python `AGENT_FRAMEWORK_USER_AGENT_DISABLED` (drops the whole UA), v1 ships
`AGENT_FRAMEWORK_FEATURE_MASK_DISABLED`, which drops **only** the feature mask
while keeping the base SDK identity/version User-Agent. This lets a
privacy-conscious user withhold the usage signal without losing the
support/compat value of the SDK-version header. .NET adopts the dedicated
mask-only flag; adding a .NET whole-User-Agent switch is outside this decision.
- **Caller-owned clients are not modified.** V1 stamps only framework-created
clients or clients with a supported public policy/hook registration point. It
does not patch private pipelines; injected clients are an explicit coverage
limitation.
- **Destination approval is explicit and redirect-aware.** An eligible pipeline
still emits only to a reviewed HTTPS origin. Custom origins are default-deny,
and the token is removed on an unapproved redirect hop.
- **Telemetry does not replace transport defaults.** Framework-created OpenAI
clients use the SDK's default async HTTP client with the request hook added,
preserving redirect, timeout, connection-limit, and pooling behavior.
- **Marking uses activation, not DI construction.** Operational surfaces mark on
first real use; a constructor marks only when construction itself exercises or
registers the capability.
## More Information
- Mechanism & API: [SPEC-004](../specs/004-feature-usage-telemetry.md)
- Per-language bit tables, encoding, opt-out, governance: [feature-usage-bit-registry.md](../specs/feature-usage-bit-registry.md)
- Existing accumulator pattern: `python/packages/core/agent_framework/_telemetry.py`
- .NET emission policies: `dotnet/src/Microsoft.Agents.AI.Foundry/AgentFrameworkUserAgentPolicy.cs`,
`dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/HostedAgentUserAgentPolicy.cs`
@@ -0,0 +1,308 @@
---
status: proposed
contact: eavanvalkenburg
date: 2026-07-24
deciders: eavanvalkenburg, chetantoshnival, taochenosu, moonbox3, giles17
---
# Python session storage and serialization
## Context and Problem Statement
Python does not have a broadly shared session-store API in
`agent-framework-core`. The alpha `agent-framework-hosting` package has a small process-local `SessionStore`, but that
type is hosting-specific, in-memory only, and unavailable to packages such as Foundry Hosting without taking a
dependency on the hosting helper package.
The alpha implementation is a prototype, not a compatibility constraint. This decision may replace its location,
names, method shape, and behavior if another design is preferable.
The existing file-backed persistence surfaces solve narrower problems:
- `FileHistoryProvider` stores conversation `Message` records, not complete `AgentSession` snapshots;
- `FileCheckpointStorage` stores workflow checkpoints; and
- the Responses provider stores protocol history, but not Agent Framework runtime state carried in
`AgentSession.state`.
`AgentSession.to_dict()` / `from_dict()` already provide a dictionary snapshot shape. Session state may contain
framework or application-defined objects, and `register_state_type` provides dynamic type restoration, but the
registration and collision behavior is not yet strong enough to serve as a durable, cold-start persistence contract.
The framework therefore needs to decide:
- where a reusable in-memory and file-backed session store belongs;
- how a complete `AgentSession` should be serialized atomically and validated;
- how custom nested state types are registered and restored after process restart; and
- how to provide the required readable JSON format while leaving room for an optional optimized binary format.
## Decision Drivers
### Session-store ownership and API
- Make session storage reusable by core, hosting, and provider packages without creating dependency cycles.
- Keep the smallest public API that supports in-memory use, durable implementations, and application-defined stores.
- Define the minimum async operations required for lookup, replacement, and deletion.
- Decide explicitly whether reads return shared instances or independent snapshots suitable for branching.
- Simpler is better
### Serialization and type restoration
- Provide readable JSON serialization as a required capability.
- Treat an optimized binary format as a nice-to-have only when the chosen JSON implementation supports it without a
separate state model or substantial additional complexity.
- Perform one typed encode and decode operation per file write/read.
- Preserve dynamic registration of nested state types by the provider modules that own them.
- Fail before persistence when an object cannot be restored after a cold start.
- Keep the existing serialized `{"type": "<id>", ...}` representation compatible.
## Decision 1: Session-store ownership and API shape
### Keep `SessionStore` in `agent-framework-hosting`
- Good: keeps the abstraction local to app-owned hosting scenarios.
- Bad: Foundry Hosting and other packages cannot reuse it without depending on the hosting helper package.
- Bad: a generic session snapshot store is not inherently or only a web-hosting concern.
- Bad: durable implementations would either be duplicated or placed in an unrelated package.
### Add an abstract store plus separate in-memory and file implementations
For example, define a `SessionStore` protocol/ABC with `InMemorySessionStore` and `FileSessionStore`.
- Good: clearly separates the contract from implementations.
- Good: implementation names state their storage behavior explicitly.
- Neutral: follows a familiar repository/adapter pattern.
- Bad: introduces an additional public type and rename for a three-method experimental API.
- Bad: callers must choose an implementation even for the default in-memory case.
- Bad: the abstraction adds little value while every implementation still needs the same method overrides.
### Move the concrete store to core and use it as the overridable base
Move `SessionStore` to `agent-framework-core`, retain its in-memory behavior, and implement `FileSessionStore` by
overriding the same async methods.
- Good: one public type is both the useful default and the extension point.
- Good: existing custom stores can continue subclassing and overriding `get` / `set` / `delete`.
- Good: core and provider packages can share the API without depending on hosting helpers.
- Good: `FileSessionStore` remains a focused subclass while the base stays free of file-system concerns.
- Bad: the class name does not explicitly say "in memory" when used without overrides.
## Decision 2: Serialization and type restoration
Once a file-backed store exists, it needs an on-disk format and a reliable way to reconstruct the complete
`AgentSession`, including nested framework and application-defined state. Serialization belongs to each durable store
implementation rather than the `SessionStore` API: the default in-memory store does not serialize, and custom stores
remain free to choose another protocol.
The alternatives below compare top-level snapshot validation, JSON encoding/decoding cost, and how each option
interacts with the dynamic custom-state registry. Binary storage is not a primary selection criterion.
### Considered options
The standard-library and optimized-JSON options are not mutually exclusive. A store can default to `json` while
accepting caller-supplied `dumps` / `loads` callables for `orjson` or another compatible implementation. This is the
pre-msgspec `FileHistoryProvider` design; those hooks remain only as a deprecated compatibility path.
### Standard library `json`
- Good: no additional dependency and familiar readable output.
- Good: accepts the existing dictionary snapshots without a schema.
- Good: can remain the fallback/default behind pluggable `dumps` / `loads`.
- Neutral: custom state restoration still requires the framework registry.
- Bad: slower encoding and decoding than optimized native implementations.
- Bad: provides no typed snapshot validation during file reads.
### Optimized drop-in JSON libraries such as `orjson`
- Good: substantially faster JSON encoding and decoding than the standard library.
- Good: can preserve the existing dictionary-oriented snapshot and custom `dumps` / `loads` shape.
- Good: can be an opt-in codec without making the optimized package a framework dependency.
- Neutral: returns bytes when encoding, which the file stores can already handle.
- Neutral: custom state restoration still requires the framework registry.
- Bad: remains an untyped top-level decode; the framework must separately validate the session snapshot shape.
- Bad: choosing one drop-in implementation as a core dependency adds a dependency without providing typed construction.
### Pydantic `model_dump` / `model_validate`
- Good: Pydantic is already a core dependency.
- Good: a typed session snapshot model can validate top-level fields and provide `model_dump_json` /
`model_validate_json` for file serialization.
- Good: validation errors include useful field paths.
- Neutral: the dynamic `state` field remains `dict[str, Any]`, so custom nested state restoration still requires the
framework registry.
- Neutral: the public `AgentSession` does not need to become a Pydantic model; an internal snapshot model can bridge it.
- Bad: benchmarked encode/decode includes model construction and dumping overhead on every operation.
- Bad: core dependency on Pydantic run the risk of us not being able to use different versions or users of the framework being unable to upgrade or having additional extra code dealing with major version bumps in Pydantic.
### msgspec typed/tagged unions only
- Good: msgspec owns validation and reconstruction end to end.
- Neutral: works well for a closed set of framework-owned `msgspec.Struct` types.
- Bad: every external type must be known when the decoder schema is constructed; dynamic registration is lost.
### msgspec codecs plus an explicit dynamic registry
- Good: one typed file encode/decode and dynamic nested custom types.
- Good: it satisfies the required readable JSON format.
- Neutral: the same typed snapshot can also support optional MessagePack as a low-cost implementation detail.
- Good: the registry can enforce stable IDs, codec completeness, and collision handling.
- Neutral: a single state-payload hook still recursively applies registry codecs.
- Bad: msgspec cannot infer dynamic types from JSON without the framework's type tags.
## Benchmark Evidence
A benchmark using a large `AgentSession` with 2,000 `Message` objects stored through
`InMemoryHistoryProvider`, nested standard dictionaries, registered custom classes, and registered Pydantic models
measured the complete `AgentSession.to_dict()` / codec / `AgentSession.from_dict()` path.
The reproducible harness is
[`python/scripts/session_serialization_benchmark.py`](../../python/scripts/session_serialization_benchmark.py):
```bash
cd python
uv run --with orjson python scripts/session_serialization_benchmark.py
```
| Codec | File size | Encode median (ms) | Decode median (ms) | Round-trip median (ms) | Disk round-trip median (ms) |
| --- | ---: | ---: | ---: | ---: | ---: |
| Standard library JSON | 1.57 MiB | 33.503 | 14.316 | 55.261 | 75.226 |
| orjson | 1.57 MiB | 25.808 | 11.754 | 39.398 | 63.319 |
| Pydantic JSON | 1.57 MiB | 28.330 | 18.344 | 53.522 | 77.096 |
| msgspec JSON | 1.57 MiB | 26.019 | 11.379 | **38.060** | 62.230 |
| msgspec MessagePack | **1.45 MiB** | **25.134** | **11.201** | 38.512 | **58.112** |
The JSON encodings produced the same 1.57 MiB file size. msgspec JSON had the best median JSON round-trip latency,
slightly ahead of orjson, while also supporting typed top-level decoding. Pydantic validation added measurable decode
and disk-round-trip overhead without eliminating the dynamic state registry.
MessagePack reduced file size to 92.2% of JSON (about 7.8% smaller) and produced the best encode, decode, and disk
round-trip medians. Its in-memory round-trip median was effectively tied with msgspec JSON. This supports offering it
as a nice-to-have, but it is not required to justify choosing msgspec for JSON.
These results are workload- and machine-dependent. The small differences between optimized JSON implementations are
not the basis for the architectural choice. The benchmark instead confirms that the typed design does not impose a
material regression for this representative payload:
- use msgspec JSON as the readable default;
- optionally offer msgspec MessagePack when storage size or disk latency matters;
- retain the explicit registry for dynamic custom state in both formats;
- do not add orjson solely for a small JSON performance difference without typed decoding; and
- do not use Pydantic as the file codec when its validation overhead does not replace the registry.
## Decision Outcome
### Decision 1: Move the concrete overridable store to core
`SessionStore` moves to `agent-framework-core` as an experimental public API. It remains a concrete in-memory store and
the default used by `AgentState` in the `hosting` package. Its async `get`, `set`, and `delete` methods remain overridable for custom storage
implementations.
`FileSessionStore` subclasses `SessionStore` and provides durable atomic file persistence. No separate
`InMemorySessionStore`, protocol, or ABC is introduced. `agent-framework-hosting` consumes the core type and no longer
owns or re-exports `SessionStore` (this will be a breaking change in the `hosting` package).
Actual `SessionStore` and `FileSessionStore` operations mark Python feature-usage index 17,
`core.session_store`, following ADR-0033's use-not-presence policy. Construction and import alone do not mark the bit.
`SessionStore` accepts opaque non-empty keys so custom backends can use their native key contracts. `FileSessionStore`
accepts opaque keys up to 128 characters and encodes values that are not portable filename stems; this supports
provider IDs such as `telegram:<bot-id>:<chat-id>` without permitting path traversal. `AgentState` remains
storage-agnostic and passes keys through unchanged; each store implementation owns backend-specific validation or
normalization. Protocol-specific hosts such as Foundry may still derive their own stable storage key before calling the
store.
Foundry Hosting exposes an experimental `FoundrySessionStore`, which is the
default `ResponsesHostServer` store when hosted; local hosting defaults to the
in-memory `SessionStore`. `FoundrySessionStore` currently subclasses
`FileSessionStore`, stores snapshots under
`/.sessions/<user-id>/<conversation-id-or-response-id>.json`, and derives the
validated user partition from
`azure.ai.agentserver.core.get_request_context()`. A Foundry session controls
hosted compute and filesystem lifetime and may host multiple users and
Responses conversations, so its ID is not used as the MAF session identifier.
Stored-conversation requests read and write one snapshot under
`conversation_id`. Response-chain requests read under `previous_response_id`
and write the updated, loaded MAF session under the current `response_id`, which
allows branching without overwriting the parent snapshot. Because Foundry does
not infer `agent_session_id` from `previous_response_id`, response-chain callers
must also reuse the prior response's hosted session ID so the request reaches
the same persistent `$HOME`; conversation objects bind a stable hosted session
automatically.
The Foundry-specific type is the host configuration seam; its implementation
may later move from files to a Foundry storage API without changing the generic
core store contract. The session file API maps `/` to the hosted `$HOME`
directory, so this API path is persisted on disk under `$HOME/.sessions`.
### Decision 2: Use msgspec codecs plus an explicit dynamic registry
Chosen option: **msgspec codecs plus an explicit dynamic registry**.
`FileSessionStore` uses a typed internal `msgspec.Struct` snapshot with reusable JSON and MessagePack encoders/decoders.
JSON is the required and default format. Because msgspec can reuse the same typed snapshot and registry hooks,
`serialization_format="msgpack"` is also exposed as an optional compact binary convenience. The complete state
dictionary is wrapped in one custom field; its encode/decode hooks recursively translate explicitly registered types
to and from the existing tagged mappings in either format.
The dependency range is `msgspec>=0.20.0,<0.22`: version 0.20.0 added Python 3.14 support, and the upper bound limits
core to the tested 0.20/0.21 minor lines.
Three dependency placements were considered:
1. Make msgspec a standard core dependency.
2. Make msgspec optional in core but standard in Foundry hosting.
3. Make msgspec optional in both packages.
Option 3 moves installation failures to application developers even though durable session persistence is required for
the primary `ResponsesHostServer` API to preserve Agent Framework state. Option 2 removes that burden from Foundry
hosting but makes core's shared `_sessions` module and public types conditionally defined or lazily imported without
removing msgspec from the default Foundry installation. Option 1 is therefore selected: msgspec is a standard core
dependency, giving both core file providers and Foundry hosting one predictable implementation path.
Core already depends on the native `pydantic-core` extension, so native-wheel availability is not a new packaging
constraint. The msgspec project is also actively tracking upcoming Python support; its merged
[`Add 3.15-dev to CI` PR](https://github.com/msgspec/msgspec/pull/1037) exercises Python 3.15 development builds. This gives confidence that they will add support for new python version quickly.
The public `AgentSession` remains a normal framework class. The msgspec Struct is an internal persistence DTO rather
than the inheritance base for runtime sessions. The Struct gives persistence one typed encode/decode operation, validates
the snapshot envelope, and carries an explicit payload version. The benchmark's small timing spread was not used to
choose the Struct.
`register_state_type` supports stable type IDs and optional codecs, rejects collisions, and provides defaults for
`to_dict` / `from_dict` classes and Pydantic models. Type IDs share one process-wide registry, so provider packages
should use stable package-qualified identifiers and register their own state types at module import time; consumers do
not need to know those implementation details. One recursive serializer is shared by `AgentSession.to_dict()` and the
durable codecs. The established implicit Pydantic registration behavior remains temporarily for compatibility, but now
emits `DeprecationWarning`. Same-process round-trips continue to work; cold-start deserialization is not guaranteed
without explicit provider registration. Unknown persisted type IDs remain raw dictionaries.
File snapshots are quarantined only when their bytes cannot be parsed as the selected JSON or MessagePack format.
Schema errors, unsupported snapshot versions, and registered state-decoder failures leave the original file in place so
an application fix, rollback, or compatible reader can recover it.
`FileHistoryProvider` also adds msgspec JSON as its default JSON Lines codec. It supports the same explicit
`serialization_format="msgpack"` choice using length-prefixed append-only MessagePack records. Its existing `dumps` /
`loads` extension points remain temporarily for JSON compatibility, emit `DeprecationWarning` when supplied, and do
not apply to MessagePack. New code uses the built-in codecs. The default JSON reader falls back to the standard library
for legacy JSON Lines containing `NaN` or infinity, and writes those non-finite values with the standard library so
existing history semantics are preserved.
## Follow-up Work
Audit the remaining file-backed stores to determine whether they benefit from the same typed msgspec treatment and
optional JSON / MessagePack formats. `FileCheckpointStorage` is the first candidate because it persists large,
structured workflow state and currently uses JSON plus custom checkpoint value encoding. Its existing
`WorkflowCheckpoint.version` field already provides a payload-shape discriminator.
Checkpoint migration should be reader-first. A compatibility release can detect the codec from the first byte, widen
the two `glob("*.json")` readers to discover future formats, and continue writing only JSON. A later release can add
opt-in MessagePack writes while retaining JSON as the default. The payload `version` should describe the checkpoint
shape rather than the codec, which is discoverable from the bytes. MessagePack should not become the default while
mixed-version fleets may share one checkpoint directory: older readers silently ignore non-JSON files and could resume
from no checkpoint instead of surfacing an incompatibility.
`MemoryContextProvider` is another candidate because its file-backed path combines `MemoryFileStore` state with
transcript files and still exposes `history_dumps` / `history_loads` passthroughs to the deprecated
`FileHistoryProvider` codec hooks.
The follow-up should measure real framework payloads before changing formats, preserve compatibility or define a clear
migration path for existing files, and consider whether each store needs readable JSON, compact binary storage, append
semantics, or atomic whole-file replacement. Other candidates include file-backed todo state, but each should be
evaluated independently rather than adopting msgspec by default solely for consistency.
@@ -8,6 +8,8 @@
- **Context Provider Pattern** - `SecureAgentConfig` extends `ContextProvider`, injecting tools, instructions, and middleware automatically
- **Automatic Variable Hiding** - UNTRUSTED content is automatically hidden without requiring manual intervention
- **Per-Item Embedded Labels** - Tools return `list[Content]` with `Content.from_text()` for proper label propagation
- **SecureMCPToolProxy Auto-Labeling** - MCP tools are labeled automatically from MCP `ToolAnnotations` hints
- **MCP `_meta.ifc` Support** - Per-result IFC labels from servers (for example GitHub MCP with `X-MCP-Features: ifc_labels`) are parsed and enforced
- **SecureAgentConfig** - One-line secure agent configuration via `context_providers=[config]`
- **Data Exfiltration Prevention** - `max_allowed_confidentiality` prevents sensitive data leakage
- **Message-Level Label Tracking** (Phase 1) - Track labels on every message in the conversation
@@ -23,6 +25,7 @@ The FIDES defense system consists of seven main components:
5. **Security Tools** - Specialized tools for safe handling of untrusted content (`quarantined_llm`, `inspect_variable`)
6. **SecureAgentConfig** - Context provider for easy secure agent configuration
7. **Message-Level Label Tracking** - Track labels on every message in the conversation (Phase 1)
8. **MCP Tool/Result Label Integration** - MCP hint-based tool labeling and `_meta.ifc` result label parsing
## Implementation Details
@@ -184,6 +187,17 @@ agent = Agent(
)
```
### 7. MCP Labeling Pipeline (Hints + `_meta.ifc`)
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"}}`).
## Security Properties
### Deterministic Defense
-48
View File
@@ -1,48 +0,0 @@
# AGENTS.md
Instructions for AI coding agents working on durable agents documentation.
## Scope
This directory contains feature documentation for the durable agents integration. The source code and samples live elsewhere:
- .NET implementation: `dotnet/src/Microsoft.Agents.AI.DurableTask/` and `dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/`
- Python implementation: `python/packages/durabletask/` and `python/packages/azurefunctions/` (package `agent-framework-azurefunctions`)
- .NET samples: `dotnet/samples/04-hosting/DurableAgents/`
- Python samples: `python/samples/04-hosting/durabletask/`
- Official docs (Microsoft Learn): <https://learn.microsoft.com/agent-framework/integrations/azure-functions>
## Document structure
| File | Purpose |
| --- | --- |
| `README.md` | Main technical overview: architecture, hosting models, orchestration patterns, and links to samples. |
| `durable-agents-ttl.md` | Deep-dive on session Time-To-Live (TTL) configuration and behavior. |
Add new sibling documents when a topic is too detailed for the README (e.g., a new feature like reliable streaming or MCP tool exposure). Keep the README focused on orientation and link out to siblings for depth.
## Writing guidelines
- **Audience**: Developers already familiar with the Microsoft Agent Framework who want to understand what durability adds and how to use it.
- **Host-agnostic first**: Durable agents work in console apps, Azure Functions, and any Durable Taskcompatible host. Show host-agnostic patterns (plain orchestration functions, `IServiceCollection` registration) before Azure Functionsspecific patterns. Avoid giving the impression that Azure Functions is the only hosting option.
- **Both languages**: Always include C# and Python examples side by side. Keep them equivalent in functionality.
- **Callout syntax**: Use GitHub-flavored callouts (`> [!NOTE]`, `> [!IMPORTANT]`, `> [!WARNING]`) rather than bold-text callouts (`> **Note:** ...`).
- **Line length**: Do not wrap long lines. Rely on text viewers / renderers for line wrapping.
- **Tables**: Use spaces around pipes in separator rows (`| --- |` not `|---|`).
- **Code snippets**: Keep them minimal and self-contained. Omit boilerplate (using statements, environment variable reads) unless the snippet is specifically about setup.
- **Cross-references**: Link to Microsoft Learn for conceptual background (Durable Entities, Durable Task Scheduler, Azure Functions). Link to sibling docs within this directory for feature deep-dives.
## Linting
Run markdownlint on all documents before committing, with line-length checks disabled:
```bash
markdownlint docs/features/durable-agents/ --disable MD013
```
## When to update these docs
- A new durable agent feature is added (e.g., a new orchestration pattern, hosting model, or configuration option).
- The public API surface changes in a way that affects how developers use durable agents.
- New sample directories are added — update the sample links in README.md.
- The official Microsoft Learn documentation is restructured — update external links.
+7 -237
View File
@@ -1,239 +1,9 @@
# Durable agents
# Durable Agents Have Moved
## Overview
Durable Task and Azure Functions integrations for Microsoft Agent Framework are now maintained in the [Durable Agent Framework extension repository](https://github.com/microsoft/agent-framework-durable-extension).
Durable agents extend the standard Microsoft Agent Framework with **durable state management** powered by the Durable Task framework. An ordinary Agent Framework agent runs in-process: its conversation history lives in memory and is lost when the process ends. A durable agent persists conversation history and execution state in external storage so that sessions survive process restarts, failures, and scale-out events.
| Capability | Ordinary agent | Durable agent |
| --- | --- | --- |
| Conversation history | In-memory only | Durably persisted |
| Failure recovery | State lost on crash | Automatically resumed |
| Multi-instance scale-out | Not supported | Any worker can resume a session |
| Multi-agent orchestrations | Manual coordination | Deterministic, checkpointed workflows |
| Human-in-the-loop | Must keep process alive | Can wait days/weeks with zero compute |
| Hosting | Any process | Console app, Azure Functions, or any Durable Taskcompatible host |
> [!NOTE]
> For a step-by-step tutorial and deployment guidance, see [Azure Functions (Durable)](https://learn.microsoft.com/agent-framework/integrations/azure-functions) on Microsoft Learn.
## How durable agents work
Durable agents are implemented on top of [Durable Entities](https://learn.microsoft.com/azure/azure-functions/durable/durable-functions-entities) (also called "virtual actors"). Each **agent session** maps to one entity instance whose state contains the full conversation history. When you send a message to a durable agent, the following happens:
1. The message is dispatched to the entity identified by an `AgentSessionId` (a composite of the agent name and a unique session key).
2. The entity loads its persisted `DurableAgentState`, which includes the complete conversation history.
3. The entity invokes the underlying `AIAgent` with the full conversation history, collects the response, and appends both the request and the response to the state.
4. The updated state is persisted back to durable storage automatically.
Because the entity framework serializes access to each entity instance, concurrent messages to the same session are processed one at a time, eliminating race conditions.
### Agent session identity
Every durable agent session is identified by an `AgentSessionId`, which has two components:
- **Name** the registered name of the agent (case-insensitive).
- **Key** a unique session key (case-sensitive), typically a GUID.
The session ID is mapped to an underlying Durable Task entity ID with a `dafx-` prefix (e.g., `dafx-joker`). This naming convention is consistent across both .NET and Python implementations.
## Architecture
### .NET
The .NET implementation consists of two NuGet packages:
| Package | Purpose |
| --- | --- |
| `Microsoft.Agents.AI.DurableTask` | Core durable agent types: `DurableAIAgent`, `AgentEntity`, `DurableAgentSession`, `AgentSessionId`, `DurableAgentsOptions`, and the state model. |
| `Microsoft.Agents.AI.Hosting.AzureFunctions` | Azure Functions hosting integration: auto-generated HTTP endpoints, MCP tool triggers, entity function triggers, and the `ConfigureDurableAgents` extension method on `FunctionsApplicationBuilder`. |
Key types:
- **`DurableAIAgent`** A subclass of `AIAgent` used *inside orchestrations*. Obtained via `context.GetAgent("agentName")`, it routes `RunAsync` calls through the orchestration's entity APIs so that each call is checkpointed.
- **`DurableAIAgentProxy`** A subclass of `AIAgent` used *outside orchestrations* (e.g., from HTTP triggers or console apps). It signals the entity via `DurableTaskClient` and polls for the response.
- **`AgentEntity`** The `TaskEntity<DurableAgentState>` that hosts the real agent. It loads the registered `AIAgent` by name, wraps it in an `EntityAgentWrapper`, feeds it the full conversation history, and persists the result.
- **`DurableAgentSession`** An `AgentSession` subclass that carries the `AgentSessionId`.
- **`DurableAgentsOptions`** Builder for registering agents and configuring TTL.
### Python
The core Python implementation is in the `agent-framework-durabletask` package (`python/packages/durabletask`). Azure Functions hosting (including `AgentFunctionApp`) is in the separate `agent-framework-azurefunctions` package (`python/packages/azurefunctions`).
Key types:
- **`DurableAIAgent`** A generic proxy (`DurableAIAgent[TaskT]`) implementing `SupportsAgentRun`. Returns a `TaskT` from `run()` — either an `AgentResponse` (client context) or a `DurableAgentTask` (orchestration context, must be `yield`ed).
- **`DurableAIAgentWorker`** Wraps a `TaskHubGrpcWorker` and registers agents as durable entities via `add_agent()`.
- **`DurableAIAgentClient`** Wraps a `TaskHubGrpcClient` for external callers. `get_agent()` returns a `DurableAIAgent[AgentResponse]`.
- **`DurableAIAgentOrchestrationContext`** Wraps an `OrchestrationContext` for use inside orchestrations. `get_agent()` returns a `DurableAIAgent[DurableAgentTask]`.
- **`AgentEntity`** Platform-agnostic agent execution logic that manages state, invokes the agent, handles streaming, and calls response callbacks.
## Hosting models
### Azure Functions
The recommended production hosting model. A single call to `ConfigureDurableAgents` (C#) or `AgentFunctionApp` (Python) automatically:
- Registers agent entities with the Durable Task worker.
- Generates HTTP endpoints at `/api/agents/{agentName}/run` for each registered agent.
- Supports `thread_id` query parameter / JSON field and the `x-ms-thread-id` response header for session continuity.
- Supports fire-and-forget via the `x-ms-wait-for-response: false` header (returns HTTP 202).
- Optionally exposes agents as MCP tools.
**C# example:**
```csharp
using IHost app = FunctionsApplication
.CreateBuilder(args)
.ConfigureFunctionsWebApplication()
.ConfigureDurableAgents(options => options.AddAIAgent(agent))
.Build();
app.Run();
```
**Python example:**
```python
app = AgentFunctionApp(agents=[agent])
```
### Console apps / generic hosts
For self-hosted or non-serverless scenarios, register durable agents via `IServiceCollection.ConfigureDurableAgents` (.NET) or `DurableAIAgentWorker` (Python) with explicit Durable Task worker and client configuration.
**C# example:**
```csharp
IHost host = Host.CreateDefaultBuilder(args)
.ConfigureServices(services =>
{
services.ConfigureDurableAgents(
options => options.AddAIAgent(agent),
workerBuilder: b => b.UseDurableTaskScheduler(connectionString),
clientBuilder: b => b.UseDurableTaskScheduler(connectionString));
})
.Build();
```
**Python example:**
```python
worker = DurableAIAgentWorker(TaskHubGrpcWorker(host_address="localhost:4001"))
worker.add_agent(agent)
worker.start()
```
## Deterministic multi-agent orchestrations
Durable agents can be composed into deterministic, checkpointed workflows using Durable Task orchestrations. The orchestration framework replays orchestrator code on failure, so completed agent calls are not re-executed.
### Patterns
| Pattern | Description |
| --- | --- |
| **Sequential (chaining)** | Call agents one after another, passing outputs forward. |
| **Parallel (fan-out/fan-in)** | Run multiple agents concurrently and aggregate results. |
| **Conditional** | Branch orchestration logic based on structured agent output. |
| **Human-in-the-loop** | Pause for external events (approvals, feedback) with optional timeouts. |
### Using agents in orchestrations
Inside an orchestration function, obtain a `DurableAIAgent` via the orchestration context. Each agent gets its own session (created with `CreateSessionAsync` / `create_session`), and you can call the same agent multiple times on the same session to maintain conversation context across sequential invocations.
**C#:**
```csharp
static async Task<string> WritingOrchestration(TaskOrchestrationContext context)
{
// Get a durable agent reference — works in any host (console app, Azure Functions, etc.)
DurableAIAgent writer = context.GetAgent("WriterAgent");
// Create a session to maintain conversation context across multiple calls
AgentSession session = await writer.CreateSessionAsync();
// First call: generate an initial draft
AgentResponse<TextResponse> draft = await writer.RunAsync<TextResponse>(
message: "Write a concise inspirational sentence about learning.",
session: session);
// Second call: refine the draft — the agent sees the full conversation history
AgentResponse<TextResponse> refined = await writer.RunAsync<TextResponse>(
message: $"Improve this further while keeping it under 25 words: {draft.Result.Text}",
session: session);
return refined.Result.Text;
}
```
**Python:**
```python
def writing_orchestration(context, _):
agent_ctx = DurableAIAgentOrchestrationContext(context)
# Get a durable agent reference — works in any host (standalone worker, Azure Functions, etc.)
writer = agent_ctx.get_agent("WriterAgent")
# Create a session to maintain conversation context across multiple calls
session = writer.create_session()
# First call: generate an initial draft
draft = yield writer.run(
messages="Write a concise inspirational sentence about learning.",
session=session,
)
# Second call: refine the draft — the agent sees the full conversation history
refined = yield writer.run(
messages=f"Improve this further while keeping it under 25 words: {draft.text}",
session=session,
)
return refined.text
```
> [!IMPORTANT]
> In .NET, `DurableAIAgent.RunAsync<T>` deliberately avoids `ConfigureAwait(false)` because the Durable Task Framework uses a custom synchronization context — all continuations must run on the orchestration thread.
## Streaming and response callbacks
Durable agents do not support true end-to-end streaming because entity operations are request/response. However, **reliable streaming** is supported via response callbacks:
- **`IAgentResponseHandler`** (.NET) or **`AgentResponseCallbackProtocol`** (Python) Implement this interface to receive streaming updates as the underlying agent generates them (e.g., push tokens to a Redis Stream for client consumption).
- The entity still returns the complete `AgentResponse` after the stream is fully consumed.
- Clients can reconnect and resume reading from a cursor-based stream (e.g., Redis Streams) without losing messages.
See the **Reliable Streaming** samples for a complete implementation using Redis Streams.
## Session TTL (Time-To-Live)
Durable agent sessions support automatic cleanup via configurable TTL. See [Session TTL](durable-agents-ttl.md) for details on configuration, behavior, and best practices.
## Observability
When using the [Durable Task Scheduler](https://learn.microsoft.com/azure/azure-functions/durable/durable-task-scheduler/durable-task-scheduler) as the durable backend, you get built-in observability through its dashboard:
- **Conversation history** View complete chat history for each agent session.
- **Orchestration visualization** See multi-agent execution flows, including parallel branches and conditional logic.
- **Performance metrics** Monitor agent response times, token usage, and orchestration duration.
- **Debugging** Trace tool invocations and external event handling.
## Samples
- **.NET** [Console app samples](../../../dotnet/samples/04-hosting/DurableAgents/ConsoleApps/) and [Azure Functions samples](../../../dotnet/samples/04-hosting/DurableAgents/AzureFunctions/) covering single-agent, chaining, concurrency, conditionals, human-in-the-loop, long-running tools, MCP tool exposure, and reliable streaming.
- **Python** [Durable Task samples](../../../python/samples/04-hosting/durabletask/) covering single-agent, multi-agent, streaming, chaining, concurrency, conditionals, and human-in-the-loop.
## Packages
| Language | Package | Source |
| --- | --- | --- |
| .NET | `Microsoft.Agents.AI.DurableTask` | [`dotnet/src/Microsoft.Agents.AI.DurableTask`](../../../dotnet/src/Microsoft.Agents.AI.DurableTask) |
| .NET | `Microsoft.Agents.AI.Hosting.AzureFunctions` | [`dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions`](../../../dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions) |
| Python | `agent-framework-durabletask` | [`python/packages/durabletask`](../../../python/packages/durabletask) |
| Python | `agent-framework-azurefunctions` | [`python/packages/azurefunctions`](../../../python/packages/azurefunctions) |
## Further reading
- [Azure Functions (Durable) — Microsoft Learn](https://learn.microsoft.com/agent-framework/integrations/azure-functions)
- [Durable Task Scheduler](https://learn.microsoft.com/azure/azure-functions/durable/durable-task-scheduler/durable-task-scheduler)
- [Durable Entities](https://learn.microsoft.com/azure/azure-functions/durable/durable-functions-entities)
- [Session TTL](durable-agents-ttl.md)
- [.NET source](https://github.com/microsoft/agent-framework-durable-extension/tree/main/dotnet/src)
- [.NET samples](https://github.com/microsoft/agent-framework-durable-extension/tree/main/dotnet/samples)
- [Python source](https://github.com/microsoft/agent-framework-durable-extension/tree/main/python/packages)
- [Python samples](https://github.com/microsoft/agent-framework-durable-extension/tree/main/python/samples)
- [Durable agent documentation](https://github.com/microsoft/agent-framework-durable-extension/tree/main/docs/features/durable-agents)
@@ -1,147 +0,0 @@
# Time-To-Live (TTL) for durable agent sessions
## Overview
The durable agents automatically maintain conversation history and state for each session. Without automatic cleanup, this state can accumulate indefinitely, consuming storage resources and increasing costs. The Time-To-Live (TTL) feature provides automatic cleanup of idle agent sessions, ensuring that sessions are automatically deleted after a period of inactivity.
## What is TTL?
Time-To-Live (TTL) is a configurable duration that determines how long an agent session state will be retained after its last interaction. When an agent session is idle (no messages sent to it) for longer than the TTL period, the session state is automatically deleted. Each new interaction with an agent resets the TTL timer, extending the session's lifetime.
## Benefits
- **Automatic cleanup**: No manual intervention required to clean up idle agent sessions
- **Cost optimization**: Reduces storage costs by automatically removing unused session state
- **Resource management**: Prevents unbounded growth of agent session state in storage
- **Configurable**: Set TTL globally or per-agent type to match your application's needs
## Configuration
TTL can be configured at two levels:
1. **Global default TTL**: Applies to all agent sessions unless overridden
2. **Per-agent type TTL**: Overrides the global default for specific agent types
Additionally, you can configure a **minimum deletion delay** that controls how frequently deletion operations are scheduled. The default value is 5 minutes, and the maximum allowed value is also 5 minutes.
> [!NOTE]
> Reducing the minimum deletion delay below 5 minutes can be useful for testing or for ensuring rapid cleanup of short-lived agent sessions. However, this can also increase the load on the system and should be used with caution.
### Default values
- **Default TTL**: 14 days
- **Minimum TTL deletion delay**: 5 minutes (maximum allowed value, subject to change in future releases)
### Configuration examples
#### .NET
```csharp
// Configure global default TTL and minimum signal delay
services.ConfigureDurableAgents(
options =>
{
// Set global default TTL to 7 days
options.DefaultTimeToLive = TimeSpan.FromDays(7);
// Add agents (will use global default TTL)
options.AddAIAgent(myAgent);
});
// Configure per-agent TTL
services.ConfigureDurableAgents(
options =>
{
options.DefaultTimeToLive = TimeSpan.FromDays(14); // Global default
// Agent with custom TTL of 1 day
options.AddAIAgent(shortLivedAgent, timeToLive: TimeSpan.FromDays(1));
// Agent with custom TTL of 90 days
options.AddAIAgent(longLivedAgent, timeToLive: TimeSpan.FromDays(90));
// Agent using global default (14 days)
options.AddAIAgent(defaultAgent);
});
// Disable TTL for specific agents by setting TTL to null
services.ConfigureDurableAgents(
options =>
{
options.DefaultTimeToLive = TimeSpan.FromDays(14);
// Agent with no TTL (never expires)
options.AddAIAgent(permanentAgent, timeToLive: null);
});
```
## How TTL works
The following sections describe how TTL works in detail.
### Expiration tracking
Each agent session maintains an expiration timestamp in its internally managed state that is updated whenever the session processes a message:
1. When a message is sent to an agent session, the expiration time is set to `current time + TTL`
2. The runtime schedules a delete operation for the expiration time (subject to minimum delay constraints)
3. When the delete operation runs, if the current time is past the expiration time, the session state is deleted. Otherwise, the delete operation is rescheduled for the next expiration time.
### State deletion
When an agent session expires, its entire state is deleted, including:
- Conversation history
- Any custom state data
- Expiration timestamps
After deletion, if a message is sent to the same agent session, a new session is created with a fresh conversation history.
## Behavior examples
The following examples illustrate how TTL works in different scenarios.
### Example 1: Agent session expires after TTL
1. Agent configured with 30-day TTL
2. User sends message at Day 0 → agent session created, expiration set to Day 30
3. No further messages sent
4. At Day 30 → Agent session is deleted
5. User sends message at Day 31 → New agent session created with fresh conversation history
### Example 2: TTL reset on interaction
1. Agent configured with 30-day TTL
2. User sends message at Day 0 → agent session created, expiration set to Day 30
3. User sends message at Day 15 → Expiration reset to Day 45
4. User sends message at Day 40 → Expiration reset to Day 70
5. Agent session remains active as long as there are regular interactions
## Logging
The TTL feature includes comprehensive logging to track state changes:
- **Expiration time updated**: Logged when TTL expiration time is set or updated
- **Deletion scheduled**: Logged when a deletion check signal is scheduled
- **Deletion check**: Logged when a deletion check operation runs
- **Session expired**: Logged when an agent session is deleted due to expiration
- **TTL rescheduled**: Logged when a deletion signal is rescheduled
These logs help monitor TTL behavior and troubleshoot any issues.
## Best practices
1. **Choose appropriate TTL values**: Balance between storage costs and user experience. Too short TTLs may delete active sessions, while too long TTLs may accumulate unnecessary state.
2. **Use per-agent TTLs**: Different agents may have different usage patterns. Configure TTLs per-agent based on expected session lifetimes.
3. **Monitor expiration logs**: Review logs to understand TTL behavior and adjust configuration as needed.
4. **Test with short TTLs**: During development, use short TTLs (e.g., minutes) to verify TTL behavior without waiting for long periods.
## Limitations
- TTL is based on wall-clock time, not activity time. The expiration timer starts from the last message timestamp.
- Deletion checks are durably scheduled operations and may have slight delays depending on system load.
- Once an agent session is deleted, its conversation history cannot be recovered.
- TTL deletion requires at least one worker to be available to process the deletion operation message.
+498
View File
@@ -0,0 +1,498 @@
---
status: proposed
contact: eavanvalkenburg
date: 2026-07-08
deciders: eavanvalkenburg
---
# Python protocol helpers and optional execution state
## Scope
This specification is the Python implementation plan for
[ADR-0027](../decisions/0027-hosting-channels.md). It documents the helper-first v1 contract for Python hosting.
The v1 contract is:
- protocol packages expose helper functions that convert protocol-native input to Agent Framework run values;
- protocol packages expose helper functions that convert Agent Framework run results or streams back to protocol-native
payloads or operations;
- application/framework code owns routes, native SDK clients, authentication, command policy, webhooks, response status
codes, and outbound sends;
- `agent-framework-hosting` provides small optional state holders for Agent Framework targets;
- state helpers do not own web apps, route contribution, protocol dispatch, command projection, or native SDK calls.
## Goals
- Let apps expose agents and workflows from FastAPI, Starlette, Django, Azure Functions, native SDK webhooks, CLIs, and
tests without adopting a host/channel framework.
- Keep protocol parsing and response formatting inside protocol packages.
- Keep session continuity explicit and app-owned at the trust boundary.
- Reuse Agent Framework primitives: `AgentSession`, `CheckpointStorage`, `Agent.run(...)`, `Workflow.run(...)`, and
`ResponseStream`.
- Preserve full-fidelity Agent Framework results until a protocol helper renders them.
## Non-goals for v1
### App-owned in v1
The app builder owns these concerns with normal web-framework, SDK, platform, or application code:
- authentication, authorization policy, and allowlists;
- deciding whether identities across protocols map to the same `session_id`;
- non-originating sends using native SDK clients;
- background work, durable execution, retry, and replay when app code owns the work;
- routing between multiple agents.
The helper-first model makes app-owned linking and non-originating delivery easier than the old host/channel model because
app code already owns the native SDK clients, authenticated caller context, session id selection, and outbound sends.
### Future framework work
The following require a separate reviewed design before becoming reusable framework features:
- reusable cross-channel identity linking;
- framework-owned proactive or non-originating delivery;
- fan-out, multicast, selected-channel, active-channel, or all-linked delivery;
- framework-owned delivery observability, dead-letter handling, and replay semantics;
- cross-channel confidentiality and link policy.
[ADR-0028](../decisions/0028-hosting-linking-multicast-enhancements.md) tracks possible follow-up work in this area and
must be aligned with the helper-first model before implementation. Old vocabulary such as `IdentityLinker`,
`ResponseTarget`, `ChannelPush`, `ChannelPushCodec`, `DurableTaskRunner`, `RetryPolicy`, and `LinkPolicy` is not v1 API.
## Packages
| Package | Import surface | v1 helper-first contents |
|---|---|---|
| `agent-framework-hosting` | `agent_framework_hosting` | `AgentState`, `WorkflowState`, `SessionStore`, and run-argument `TypedDict`s. |
| `agent-framework-hosting-a2a` | `agent_framework_hosting_a2a` | A2A `Message` to run conversion and Agent Framework output to A2A `Part` conversion. |
| `agent-framework-hosting-mcp` | `agent_framework_hosting_mcp` | Agent and workflow MCP tool adapters, MCP tool arguments to run conversion, and Agent Framework output to MCP `ContentBlock` conversion. |
| `agent-framework-hosting-responses` | `agent_framework_hosting_responses` | Responses helpers: request parsing, session id extraction, response id creation, response rendering, streaming rendering. |
| `agent-framework-hosting-telegram` | `agent_framework_hosting_telegram` | Telegram Bot API helpers: update parsing, chat/session/command/media extraction, final rendering, and streaming edit rendering. |
| Future protocol packages | e.g. `agent_framework_hosting_activity_protocol` | Protocol-specific helpers such as `activity_to_run(...)`, `activity_from_run(...)`, `activity_session_id(...)`, and command/media helpers when useful. |
The core hosting package must not depend on protocol SDKs. Protocol packages may depend on their native protocol SDKs if
needed, but helper functions should stay usable from plain app code and tests.
## Helper naming and families
Helper names are protocol-specific. Avoid a generic `protocol_to_run(...)` public surface.
Protocol packages may provide the following helper families when the protocol has the concept:
| Helper family | Shape | Purpose |
| --- | --- | --- |
| Run conversion | `<protocol>_to_run(...)` | Convert one protocol-native call/update/request into `Agent.run` or `Workflow.run` values. |
| Final rendering | `<protocol>_from_run(...)` | Convert a final `AgentResponse` or workflow result into protocol-native response payloads or operations. |
| Stream rendering | `<protocol>_from_streaming_run(...)` | Convert `ResponseStream` or workflow updates into protocol-native events or operations. |
| Session id extraction | `<protocol>_session_id(...)` | Extract the protocol's natural continuation/partition key from the call, if present. |
| Command/action parsing | `<protocol>_command(...)` | Parse a protocol-native command/action/operation name without deciding app policy. |
Examples:
- `responses_to_run(...)`, `responses_from_run(...)`, `responses_from_streaming_run(...)`,
`responses_session_id(...)`;
- `a2a_to_run(...)`, `a2a_from_run(...)`;
- `telegram_to_run(...)`, `telegram_from_run(...)`, `telegram_from_streaming_run(...)`,
`telegram_session_id(...)`, `telegram_command(...)`;
- `activity_to_run(...)`, `activity_from_run(...)`, `activity_session_id(...)`, `activity_command(...)`;
- `discord_to_run(...)`, `discord_from_run(...)`, `discord_session_id(...)`, `discord_command(...)`.
This table is a naming guide, not a required checklist. A protocol package should add only the helpers that match native
protocol concepts and current samples.
Protocol-specific helpers may also exist for native details such as `telegram_chat_id(...)`,
`telegram_callback_query_id(...)`, `telegram_media_file_id(...)`, `discord_interaction_id(...)`, `a2a_task_id(...)`,
`a2a_context_id(...)`, or MCP tool/prompt/resource helpers. These helpers should stay side-effect-free. App/native SDK
code performs acknowledgements, sends/edits messages, resolves protected file URLs, applies rate limits, and registers
handlers.
## `agent-framework-hosting` state helpers
### `SessionStore`
`SessionStore` is an in-memory async lookup:
```python
class SessionStore:
async def get(self, session_id: str) -> AgentSession | None: ...
async def set(self, session_id: str, session: AgentSession) -> None: ...
async def delete(self, session_id: str) -> None: ...
```
The store does not create sessions. It stores `session_id -> AgentSession` values supplied by callers.
The built-in store has no TTL or eviction. This is intentional for local/dev and simple process-local scenarios: protocols
such as OpenAI Responses can continue from any prior response id. Durable or multi-replica deployments should provide a
durable store and their own TTL/eviction policy.
### `AgentState`
`AgentState` holds an agent target and an optional `SessionStore`:
```python
state = AgentState(agent)
state = AgentState(create_agent)
state = AgentState(create_agent, cache_target=False)
```
The target may be:
- a `SupportsAgentRun` instance;
- a synchronous factory;
- an asynchronous factory;
- an awaitable target.
`AgentState` provides:
- `await get_target()`;
- synchronous `target` only after a target is already available/resolved;
- `session_store`;
- `await get_or_create_session(session_id)`;
- `await set_session(session_id, session)`.
`get_or_create_session(...)` resolves the target and calls `target.create_session(session_id=...)` only when the store has
no session for that id.
Apps must store the post-run session explicitly after `agent.run(...)` or stream finalization:
```python
session = await state.get_or_create_session(session_id)
target = await state.get_target()
result = await target.run(messages, session=session, options=options)
await state.set_session(response_id, session)
```
### `WorkflowState`
`WorkflowState` resolves a workflow target. It does not own checkpoint storage.
The target may be:
- a `Workflow` instance;
- a `WorkflowBuilder` or other object with `build() -> Workflow`;
- a synchronous factory;
- an asynchronous factory;
- an awaitable target.
`WorkflowState` provides:
- `await get_target()`;
- synchronous `target` only after a target is already available/resolved.
A workflow instance permits one active run. Concurrent hosts use a factory or
builder with `cache_target=False` to resolve a fresh instance per run.
Workflow checkpointing uses Agent Framework's existing `CheckpointStorage` abstraction directly. Apps that need
per-session workflow resume should keep an app-owned cursor such as `session_id -> checkpoint_id`. When the app uses
file-backed cursor storage, the file-based checkpoint storage should share the same app storage root and should be
scoped to the current authenticated user/tenant/session bucket, for example
`storage/checkpoints/<session-bucket>/` beside `storage/checkpoint_cursors.json`:
```python
# session_id must already be authenticated and authorized for this caller
target = await workflow_state.get_target()
checkpoint_id = await checkpoint_cursor_store.get(session_id)
if checkpoint_id is None:
result = await target.run(message=workflow_input, checkpoint_storage=checkpoint_storage)
else:
result = await target.run(checkpoint_id=checkpoint_id, checkpoint_storage=checkpoint_storage)
latest = await checkpoint_storage.get_latest(workflow_name=target.name)
if latest is not None:
await checkpoint_cursor_store.set(session_id, latest.checkpoint_id)
```
`Workflow.run(...)` does not currently emit a checkpoint id on `WorkflowRunResult` or normal workflow events by default.
The runner receives checkpoint ids internally from `CheckpointStorage.save(...)`. Apps that own the storage can query
`get_latest(workflow_name=...)` after the run if they need to update a cursor.
## `agent-framework-hosting-responses`
The Responses package provides the helper-first surface for OpenAI Responses-shaped requests.
### Request helpers
- `messages_from_responses_input(input) -> list[Message]`
- `responses_to_run(body) -> AgentRunArgs`
- `responses_session_id(body) -> str | None`
- `create_response_id() -> str`
`responses_to_run(...)` returns values corresponding to `Agent.run(...)`:
```python
run = responses_to_run(body)
messages = run["messages"]
options = run["options"]
stream = run["stream"]
```
It excludes protocol transport/session fields from `options` and remaps known Responses option names such as
`max_output_tokens -> max_tokens`.
`responses_session_id(...)` returns:
- `previous_response_id` when present (`resp_*`);
- otherwise `conversation_id` when present (`conv_*`);
- otherwise `None`.
The helper only extracts the candidate key. App code decides whether to trust and use that key.
### Response helpers
- `responses_from_run(result, *, response_id, session_id=None) -> dict[str, Any]`
- `responses_from_streaming_run(stream, *, response_id, session_id=None) -> AsyncIterator[str]`
`responses_from_run(...)` renders a full Responses JSON payload from an `AgentResponse`. It renders the full set of
OpenAI Responses output item types supported by Agent Framework content.
`responses_from_streaming_run(...)` renders Server-Sent Event strings for a `ResponseStream`. It emits a created event,
text deltas, and a completed event. The final completed payload is produced through `responses_from_run(...)`; the helper
also preserves the model id observed on streaming updates when the finalized `AgentResponse` no longer carries raw model
metadata.
## `agent-framework-hosting-a2a`
The A2A package provides only the conversion seam between the native A2A SDK
and Agent Framework:
- `a2a_to_run(message, *, stream=False) -> AgentRunArgs`
- `a2a_from_run(result) -> list[a2a.types.Part]`
`a2a_to_run(...)` accepts a native A2A `Message` and converts its text, URL,
raw-byte, and structured-data parts into one Agent Framework user message.
`a2a_from_run(...)` accepts an `AgentResponse`, `Message`, or
`AgentResponseUpdate` and converts supported text, URI, and data content into
native A2A `Part` values. This one helper is usable for both completed and
streaming runs.
The package does not provide an A2A `AgentExecutor`, application, route,
request handler, task store, event queue, `TaskUpdater`, task-state policy,
artifact-id policy, or session-key policy. Application code composes the two
helpers with those native A2A SDK constructs and may use any server framework
supported by the SDK.
## `agent-framework-hosting-mcp`
The MCP package provides only the conversion seam between native MCP SDK values
and Agent Framework:
- `MCPAgentTool(target, ...)`
- `MCPWorkflowTool(target, ...)`
- `mcp_to_run(arguments, *, argument_name="task", chat_option_arguments=()) -> AgentRunArgs`
- `mcp_from_run(result) -> list[mcp.types.ContentBlock]`
`MCPAgentTool` represents one Agent Framework agent as one native MCP tool. It
derives the default tool name and description from the agent, accepts
overrides for those values and the main text parameter, includes app-owned
additional parameter schemas, and explicitly maps selected parameter schemas
to ChatOptions. Its asynchronous `list_tools()` returns the native `Tool` list,
and `call_tool(...)` performs conversion, agent execution, and final result
conversion.
The adapter accepts either an agent or an existing `AgentState`. With a
configured `session_id_parameter`, it loads and stores the corresponding
`AgentSession`. The application remains responsible for deriving and
authorizing the session id and preventing concurrent updates to the same
session.
`MCPWorkflowTool` represents one Agent Framework workflow as one native MCP
tool. It derives the tool name and description from the workflow and derives
the input schema from the start executor's single declared input type.
Object-shaped dataclass and Pydantic inputs become top-level MCP arguments;
primitive inputs are wrapped in one configurable argument. The adapter
validates the arguments against that type, runs the workflow, and converts
terminal outputs to MCP content blocks.
Workflow instances preserve state and reject concurrent runs. Applications
that need independent calls should provide a `WorkflowState` factory with
`cache_target=False`. Checkpoint restoration, human-in-the-loop responses, and
continuation identifiers remain application-owned contracts. If a workflow
stops to request external input, the adapter raises rather than returning an
empty successful tool result.
`mcp_to_run(...)` accepts the argument mapping from a native MCP `call_tool`
handler. The application owns the tool schema and may select which required
string argument contains the user request. The application should define that
argument name once and use the same value in the native tool schema and the
`argument_name` parameter so those two sides of the contract remain aligned.
Applications may also expose selected ChatOptions fields in their native tool
schema and pass those names through `chat_option_arguments`. Only explicitly
selected names are copied to run options; the helper does not forward all MCP
arguments or own their JSON Schema validation.
MCP `tools/call` arguments are JSON-only and do not have a native multimodal
content-block union. The package does not impose a non-standard JSON
representation for multimodal tool arguments.
`mcp_from_run(...)` accepts an `AgentResponse` or `Message`. It converts text,
URI, image data, audio data, and other binary data into native MCP content
blocks.
Its output is specifically the content union accepted by `CallToolResult`.
Sampling-only values such as `ToolUseContent` belong to the separate MCP
sampling response path and are not emitted by this hosting helper.
MCP `tools/call` returns one final `CallToolResult`. Streamable HTTP can carry
multiple MCP messages and progress notifications can report operation status,
but the protocol does not define partial tool-result content chunks.
Experimental MCP tasks defer retrieval of the same final result. Therefore the
conversion helpers do not expose Agent Framework streaming updates.
The package does not provide an MCP `Server`, handler registration, transport, route,
session policy, authentication, authorization, or deployment wrapper.
Application code composes the adapters and conversion helpers with native MCP SDK constructs and
may use stdio, streamable HTTP, or another transport supported by the SDK.
## `agent-framework-hosting-telegram`
The Telegram package provides side-effect-free helpers around Telegram Bot API
update and method payloads. It does not provide a Bot API client, polling loop,
webhook route, command registry, retry policy, or rate limiter.
### Update helpers
- `telegram_to_run(update, *, resolve_file_url=None, stream=False) -> AgentRunArgs`
- `telegram_chat_id(update) -> int | None`
- `telegram_session_id(update, *, bot_id) -> str | None`
- `telegram_command(update) -> str | None`
- `telegram_callback_query_id(update) -> str | None`
- `telegram_media_file_id(update_or_message) -> tuple[str, str] | None`
`telegram_to_run(...)` handles `message`, `edited_message`, and
`callback_query` updates. Text and captions become AF text content. When the
app supplies an async `resolve_file_url` callback, supported Telegram media
file ids can become AF URI content. The package does not call Telegram's
`getFile` method itself.
`telegram_session_id(..., bot_id=...)` includes the bot identity in every key.
Private chats return `telegram:<bot_id>:<user_id>`; other chats return
`telegram:<bot_id>:<chat_id>`, giving groups a shared session by default. This
matches Telegram's native isolation boundaries while preventing two bots from
sharing state accidentally. Apps that want per-user sessions inside a group
can construct a key that includes both chat and sender ids. The app must
authorize those Telegram identities before loading session state.
`telegram_command(...)` parses Telegram's `/name` and `/name@bot` syntax. It
does not register commands or invoke handlers.
### Response helpers
- `telegram_from_run(result, *, chat_id, parse_mode=None)`
- `telegram_from_streaming_run(stream, *, chat_id, message_id, initial_text=None, parse_mode=None)`
The helpers produce Telegram method/payload values for app-owned Bot API
calls. Final rendering supports text and image URI output and applies
Telegram's text-length boundary. Streaming rendering produces cumulative
`editMessageText` payloads for a placeholder message id supplied by the app,
omitting edits that match an optional `initial_text`, then renders the final
rich output. Image-only responses remove the placeholder with `deleteMessage`
before sending the image. The app owns the initial placeholder send, Bot API
calls, edit throttling, retries, and failure policy.
## Security responsibilities
Protocol helper packages parse and render. They do not authenticate callers, authorize access to state, or decide which
side effects are allowed.
Application code that uses these helpers is responsible for:
- authenticating the caller through the app's normal mechanism before using protocol-provided ids;
- authorizing any caller-supplied session, checkpoint, task, context, conversation, thread, or response id before loading
state for it;
- binding externally supplied ids to the authenticated user, tenant, workspace, installation, or chat context before
using them as `SessionStore` keys or checkpoint cursor keys;
- treating `<protocol>_session_id(...)` results as untrusted candidate keys until that ownership check has passed;
- keeping platform-provided isolation helpers fail-closed outside their trusted hosting environment;
- authorizing command/action effects such as reset, cancel, approve, submit, or tool invocation after parsing them;
- opting in explicitly before resolving protected media/resource/file URLs and passing them to a remote model provider;
- persisting post-run session or checkpoint state only after `agent.run(...)`, `workflow.run(...)`, or stream finalization
has updated that state.
## Persistent versus transient hosting
The application builder decides whether the server is persistent or transient.
- Persistent single-process apps, such as a long-running container or web app, may use in-memory state for local
development or simple deployments. Multi-replica persistent apps still need durable state for continuity.
- Transient apps, such as Azure Functions, Foundry Hosted Agents, or any environment where process memory is not a
reliable boundary, must not rely on in-memory `SessionStore` state between calls. They need a durable session store or
a service-owned continuation id.
- Workflow hosts must choose an explicit `CheckpointStorage` and, when they need per-session resume, a durable
`session_id -> checkpoint_id` cursor. File-backed checkpoint storage and file-backed cursor storage should live under
the same app storage root, with checkpoints scoped to the current authenticated user/tenant/session bucket so a
"latest checkpoint" lookup cannot cross conversations. In-process workflow state and in-memory checkpoint cursors do
not survive transient execution.
## Minimal FastAPI Responses shape
This is the shape the local Responses sample should demonstrate. It is not an app framework.
```python
from collections.abc import AsyncIterator
from agent_framework import ResponseStream
from agent_framework_hosting import AgentState
from agent_framework_hosting_responses import (
create_response_id,
responses_from_run,
responses_from_streaming_run,
responses_session_id,
responses_to_run,
)
from fastapi import Body, FastAPI, HTTPException
from fastapi.responses import JSONResponse, StreamingResponse
app = FastAPI()
state = AgentState(create_agent)
@app.post("/responses", response_model=None)
async def responses(body: dict = Body(...)) -> JSONResponse | StreamingResponse:
run = responses_to_run(body)
candidate_session_id = responses_session_id(body)
response_id = create_response_id()
# Verify this caller owns candidate_session_id before loading it.
session_id = candidate_session_id or response_id
session = await state.get_or_create_session(session_id)
target = await state.get_target()
if run["stream"]:
stream = target.run(run["messages"], stream=True, session=session, options=run["options"])
if not isinstance(stream, ResponseStream):
raise HTTPException(status_code=500, detail="agent did not return a response stream")
async def events() -> AsyncIterator[str]:
async for event in responses_from_streaming_run(
stream,
response_id=response_id,
session_id=candidate_session_id,
):
yield event
await state.set_session(response_id, session)
return StreamingResponse(events(), media_type="text/event-stream")
result = await target.run(run["messages"], session=session, options=run["options"])
await state.set_session(response_id, session)
return JSONResponse(responses_from_run(result, response_id=response_id, session_id=candidate_session_id))
```
## Validation
Implementation validation must cover:
- `SessionStore` plain get/set/delete behavior;
- `AgentState` target resolution, target caching, and get-or-create session behavior;
- `WorkflowState` target resolution for direct workflows, factories, `WorkflowBuilder`, and orchestration-style builders;
- Responses request parsing and option remapping;
- Responses session id extraction;
- Responses response rendering, including rich output item mapping;
- Responses streaming SSE rendering;
- HTTP round-trip tests showing a native FastAPI route using `AgentState` and Responses helpers;
- sample type checking for the local Responses sample.
- Telegram update parsing, chat/session/command/media extraction, final
rendering, and streaming edit rendering;
- sample type checking for the local Telegram polling and webhook entry points.
@@ -0,0 +1,246 @@
---
status: accepted
contact: rogerbarreto
date: 2026-07-08
deciders: rogerbarreto
consulted: eavanvalkenburg
informed: []
---
# .NET hosting: OpenAI Responses protocol helpers and optional execution state
Implements [ADR-0032](../decisions/0032-dotnet-hosting-protocol-helpers.md), which realizes the
helper-first direction of [ADR-0027](../decisions/0027-hosting-channels.md) for .NET.
## What is the goal of this feature?
Let application developers expose an `AIAgent` or workflow over the OpenAI Responses protocol **while
owning their own ASP.NET Core route, authentication, middleware, and storage**, by calling small,
side-effect-free Agent Framework conversion helpers instead of adopting the batteries-included,
route-owning `MapOpenAIResponses` server.
Success: an application can implement a working `POST /responses` endpoint (sync + streaming) in its
own minimal-API handler using only the public helpers plus its own auth/storage, with no dependency on
`MapOpenAIResponses` or `IResponsesService`.
## What is the problem being solved?
.NET already exposes agents as the OpenAI Responses API, but only through the route-owning
`MapOpenAIResponses`/`IResponsesService`, which also owns routing, response/conversation storage,
streaming, and lifecycle. An application that wants its own routing (custom auth, middleware, status
codes, durable storage, or a different framework surface) currently has no supported way to reuse the
framework's Responses<->agent conversion. Every conversion primitive that would make this possible
already exists in `Microsoft.Agents.AI.Hosting.OpenAI` but is `internal`.
This feature un-bundles that conversion into a public, app-callable surface, and adds the minimal
execution-state helpers an app needs for session continuity and workflow checkpoint resume.
## API Changes
### `Microsoft.Agents.AI.Hosting.OpenAI` (new public static facade `OpenAIResponses`)
Boundary is `System.Text.Json`; the wire DTOs stay internal. All members are side-effect-free.
```csharp
namespace Microsoft.Agents.AI.Hosting.OpenAI;
public static class OpenAIResponses
{
// Wire -> Agent Framework run input.
public static OpenAIResponsesRunRequest ToAgentRunRequest(
JsonElement body,
OpenAIResponsesMapOptions? mapOptions = null);
// Agent Framework result -> Responses payload (no originating request required).
public static JsonElement WriteResponse(
AgentResponse response,
string responseId,
string? sessionId = null);
// Agent Framework stream -> Responses SSE `data:` frames.
public static IAsyncEnumerable<string> WriteResponseStreamAsync(
IAsyncEnumerable<AgentResponseUpdate> updates,
string responseId,
string? sessionId = null,
CancellationToken cancellationToken = default);
// Untrusted candidate continuation key: previous_response_id or conversation id (or null).
// Kept SEPARATE from ToAgentRunRequest so using a request-derived key is an explicit decision.
public static string? GetSessionId(JsonElement body);
// Mint a `resp_*` id.
public static string CreateResponseId();
}
// Result of ToAgentRunRequest.
public sealed class OpenAIResponsesRunRequest
{
public IList<ChatMessage> Messages { get; }
public AgentRunOptions? Options { get; }
}
```
`ToAgentRunRequest` honors `OpenAIResponsesMapOptions.RunOptionsFactory` exactly as the route model
does (by default no request setting is mapped onto the run; unsupported settings surface as a
`NotSupportedException`). `WriteResponse`/`WriteResponseStreamAsync` reuse the existing internal
`AgentResponseExtensions.ToResponse` / `AgentResponseUpdateExtensions.ToStreamingResponseAsync`
converters (an internal `ToResponse` overload with an optional originating request is added so the
facade can render without one). The streaming renderer's existing workflow-event support is preserved.
### `Microsoft.Agents.AI.Hosting` (execution state, protocol-neutral)
```csharp
namespace Microsoft.Agents.AI.Hosting;
public abstract class AgentSessionStore
{
// ... existing members ...
// New: the one missing store operation. Virtual (not abstract) with a default that throws
// NotSupportedException, so existing external stores (e.g. the Foundry hosting stores) keep
// compiling; the in-box Hosting stores override it. In-box overrides treat deleting a missing
// session as a no-op.
public virtual ValueTask DeleteSessionAsync(
AIAgent agent, string conversationId, CancellationToken cancellationToken = default);
}
// Thin holder: pairs a workflow target with checkpointing + a per-session head cursor.
public sealed class HostedWorkflowState
{
// Shared-instance mode: one instance cannot be run by two runners at once, so turns run one at a time.
public HostedWorkflowState(Workflow workflow, CheckpointManager? checkpointManager = null);
// Factory mode: by default a fresh instance is built per run, so independent sessions run in parallel.
// With cacheWorkflow: true the factory is invoked once lazily and the built instance is cached and reused.
public HostedWorkflowState(Func<CancellationToken, ValueTask<Workflow>> workflowFactory, CheckpointManager? checkpointManager = null, bool cacheWorkflow = false);
// First turn runs forward from the start; subsequent turns restore the session's latest
// checkpoint and run forward with the new turn's input, then record the new head checkpoint.
public ValueTask<HostedWorkflowRunResult> RunOrResumeAsync(
string sessionId, object input, CancellationToken ct = default);
}
```
For agents, the application uses `AgentSessionStore` directly: `GetSessionAsync(agent, id)` creates a
session on miss and returns an independent instance per call (so concurrent calls can fork the same
stored state — for example branching from a `previous_response_id` or managing several `conversation`
ids side by side — without one branch observing another's in-flight mutations). The store performs no
cross-call locking; an application that needs concurrent runs against the same id to be serialized owns
that coordination. `SaveSessionAsync(agent, id, session)` persists post-run, including under a newly
minted `resp_*` id when the protocol mints a new continuation id. `DeleteSessionAsync` uses the new
store method. No agent-side holder is needed: create-on-miss already lives in the store, so a
pass-through wrapper would only bind the `agent` argument.
`HostedWorkflowState` defaults to `CheckpointManager.CreateInMemory()` and an in-memory
`sessionId -> CheckpointInfo` cursor. Because the checkpoint store is already `sessionId`-keyed but
`CheckpointInfo` carries no ordering, the holder remembers the head checkpoint per session so
`RunOrResumeAsync` can resume the correct one. On subsequent turns it restores that checkpoint to
rehydrate accumulated workflow state and then runs the workflow forward with the new turn's input,
rather than continuing a halted run with no input (which would wait for input
indefinitely). For agent (chat-protocol) workflows the new input is accompanied by a `TurnToken` so the
turn is driven. When the in-memory cursor misses (a new holder or a process restart), the holder falls
back to `CheckpointManager.GetLatestCheckpointAsync(sessionId)`, so a durable `CheckpointManager` resumes
correctly across restarts (the default in-memory manager does not persist, so a restart starts fresh). A
resume that produces no events is logged as a warning (possible stale checkpoint or mismatched input).
Concurrency depends on how the holder is constructed. With a single shared workflow instance, concurrent runs
are not supported, because a workflow instance cannot be run by two runners at once; process turns one at a
time. With a workflow factory
(`Func<CancellationToken, ValueTask<Workflow>>`) it builds a fresh instance per run by default, so independent
sessions run in parallel; a resume rehydrates a fresh instance
from the session's checkpoint in the shared store, and concurrent turns against the same session id remain the
application's coordination responsibility. Passing `cacheWorkflow: true` instead builds the workflow once,
lazily on first use, and reuses it (a deferred, cached target that — like the instance — cannot run concurrent
turns). A
streaming counterpart, `RunOrResumeStreamingAsync`, yields the turn's `WorkflowEvent`s as they occur (for
example to render agent updates over the Responses SSE wire) and records the head checkpoint once the
stream is fully enumerated, keeping the blocking and streaming workflow paths in lockstep.
Because `RunOrResumeAsync`/`RunOrResumeStreamingAsync` are generic over the input type, the application
adapts the Responses input into the workflow's start-executor input type at the call site (for example
parsing a structured payload into a typed record), without coupling the holder to a specific wire type.
## Non-goals for v1
- ChatCompletions / Conversations helper surfaces (the facade is named so `OpenAIChatCompletions` can
follow).
- Changing `MapOpenAIResponses` public behavior.
- A new package or an OpenAI-SDK-typed reimplementation.
- Durable/pluggable workflow checkpoint-cursor storage (in-memory default only for v1).
## Security responsibilities (application-owned)
- Authenticate the caller before using any `GetSessionId(...)` result.
- Authorize and bind the candidate id to the authenticated principal/tenant before using it as an
`AgentSessionStore` key or a workflow checkpoint session id.
- For multi-user hosts, wrap the store with `IsolationKeyScopedAgentSessionStore` (for example via
`UseClaimsBasedAgentIsolation(...)`), so the session namespace is scoped per principal.
- Persist session/checkpoint state only after the run or stream has completed.
## E2E Code Samples
### Agent over Responses, app-owned route (non-streaming + SSE)
```csharp
var agent = /* an AIAgent */;
AgentSessionStore sessionStore = new InMemoryAgentSessionStore(); // in-memory session store
app.MapPost("/responses", async (HttpContext http, CancellationToken ct) =>
{
using var doc = await JsonDocument.ParseAsync(http.Request.Body, cancellationToken: ct);
JsonElement body = doc.RootElement;
// App owns auth + id trust decisions.
string? candidate = OpenAIResponses.GetSessionId(body);
string sessionId = Authorize(http.User, candidate) ?? OpenAIResponses.CreateResponseId();
var run = OpenAIResponses.ToAgentRunRequest(body);
var session = await sessionStore.GetSessionAsync(agent, sessionId, ct);
string responseId = OpenAIResponses.CreateResponseId();
if (body.TryGetProperty("stream", out var s) && s.GetBoolean())
{
http.Response.ContentType = "text/event-stream";
var updates = agent.RunStreamingAsync(run.Messages, session, run.Options, ct);
await foreach (var frame in OpenAIResponses.WriteResponseStreamAsync(updates, responseId, sessionId, ct))
{
await http.Response.WriteAsync(frame, ct);
await http.Response.Body.FlushAsync(ct);
}
await sessionStore.SaveSessionAsync(agent, responseId, session, ct);
return Results.Empty;
}
var result = await agent.RunAsync(run.Messages, session, run.Options, ct);
await sessionStore.SaveSessionAsync(agent, responseId, session, ct);
return Results.Json(OpenAIResponses.WriteResponse(result, responseId, sessionId));
});
```
### Workflow over Responses with checkpoint resume
Workflow checkpoint resume requires a **stable** session key across turns. `previous_response_id` changes
every turn, so it is not a valid checkpoint key; use the `conversation` id (constant for the conversation).
Because `GetSessionId(...)` prefers `previous_response_id`, a workflow route reads the conversation id
directly rather than calling `GetSessionId(...)`.
```csharp
var state = new HostedWorkflowState(workflow); // in-memory checkpoints + cursor
app.MapPost("/responses", async (HttpContext http, CancellationToken ct) =>
{
using var doc = await JsonDocument.ParseAsync(http.Request.Body, cancellationToken: ct);
JsonElement body = doc.RootElement;
// Stable, authorized checkpoint key. GetConversationId(...) reads the conversation id (string or object).
string sessionId = Authorize(http.User, GetConversationId(body))
?? OpenAIResponses.CreateResponseId();
var run = OpenAIResponses.ToAgentRunRequest(body);
// Runs forward on first call, resumes from the session's head checkpoint thereafter.
var result = await state.RunOrResumeAsync(sessionId, run.Messages, ct);
return Results.Json(OpenAIResponses.WriteResponse(result.AsAgentResponse(),
OpenAIResponses.CreateResponseId(), sessionId));
});
```
+500
View File
@@ -0,0 +1,500 @@
---
status: proposed
contact: eavanvalkenburg
date: 2026-07-22
deciders: eavanvalkenburg
consulted:
informed:
---
# Feature-usage telemetry via an accumulating bitmask
> Companion design for [ADR-0033](../decisions/0033-feature-usage-bitmask-user-agent.md).
> The per-language bit tables, encoding, opt-out, and governance live in
> [feature-usage-bit-registry.md](feature-usage-bit-registry.md). The registry
> allocates indexes; package-local `FeatureIndex` declarations implement them.
## What is the goal of this feature?
Give the Agent Framework team a lightweight signal about **which framework
features are actually exercised** at runtime (not merely installed), so we can
prioritise investment based on real usage. We emit a single small number — a
*feature mask* — on the User-Agent that already goes out with each request.
**Reach is deliberately bounded.** The mask accumulates from *all* feature usage,
but the `feat=` token is only stamped through an explicit allowlist of
**first-party Azure/Foundry client pipelines** whose User-Agent telemetry the
team can ingest (initially Foundry/Azure OpenAI). We do **not** send the token to
third-party providers (OpenAI direct, Anthropic, Bedrock, Gemini, Ollama,
Mistral), or to an Azure service merely because its hostname is first-party;
doing so would leak a deployment fingerprint into logs we cannot read (see
[Emission](#emission)).
The current candidate uses package-level bits plus selected major capabilities:
one bit per orchestration pattern (sequential / concurrent / group-chat /
magentic / handoff), **one bit per built-in context/history provider**, selected
skill source types, and separate Foundry chat/agent/memory/evals/toolbox bits
(plus embedding in Python).
See the
[registry](feature-usage-bit-registry.md). ADR-0033 still leaves final v1
granularity open. The refreshed candidate assigns 63 Python indexes and 52 .NET
indexes. V1 uses 128 bits, leaving 65 Python and 76 .NET positions for additive
growth.
Success metric: within one release after rollout, ≥80% of **eligible,
framework-created** first-party (Foundry) requests carry a **non-empty** feature
token whose mask reflects features activated **after** client construction (i.e.
the token is live, not frozen — see the request-time stamping requirement
below). This measures transport coverage, not feature invocation volume.
Secondary: ability to describe which process-lifetime feature bits are observed
together in eligible traffic (e.g. "requests observed from processes that have
used workflows"). Repeated requests carrying a bit are not additional uses.
This is done **transparently**: the bit registry is public, the emitted value is
human-decodable, and a dedicated `AGENT_FRAMEWORK_FEATURE_MASK_DISABLED`
disables the mask while preserving the base User-Agent. Python's existing
`AGENT_FRAMEWORK_USER_AGENT_DISABLED` continues to suppress its entire
User-Agent contribution, mask included.
## What is the problem being solved?
Today we only know which packages are *installed* (from package telemetry) or
that *some* Agent Framework call happened (the existing
`agent-framework-python/{version}` User-Agent). We have no usage-based signal
about feature combinations, and no way to tell that, say, a process uses
workflows + MCP + Foundry together. Collecting this through bespoke events would
add cost and new data flows; folding a tiny accumulating integer into telemetry
we already send is far cheaper and easier to reason about for privacy.
## Mechanism
### Process-global accumulator in `core`
The accumulator and its helpers live in the existing
`agent_framework/_telemetry.py` (alongside `get_user_agent()` /
`prepend_agent_framework_to_user_agent()`), so the User-Agent machinery stays in
one module. It owns a process-global 128-bit accumulator. Python's arbitrary-size
`int` stores it directly. A **dedicated**
`AGENT_FRAMEWORK_FEATURE_MASK_DISABLED` that drops **only** the feature mask
while keeping the base `agent-framework-python/{version}` User-Agent is
introduced by this design. The existing Python
`AGENT_FRAMEWORK_USER_AGENT_DISABLED` continues to drop the whole User-Agent
contribution, mask included:
```python
# agent_framework/_telemetry.py (same module as get_user_agent)
# IS_TELEMETRY_ENABLED already defined here (AGENT_FRAMEWORK_USER_AGENT_DISABLED)
FEATURE_MASK_DISABLED_ENV_VAR = "AGENT_FRAMEWORK_FEATURE_MASK_DISABLED"
REGISTRY_VERSION = 1
_feature_mask = 0
_feature_mask_lock = threading.Lock()
def _feature_mask_enabled() -> bool:
"""Mask is on unless the UA is disabled or the dedicated flag is set."""
if not IS_TELEMETRY_ENABLED:
return False
return os.environ.get(FEATURE_MASK_DISABLED_ENV_VAR, "false").lower() not in ("true", "1")
def mark_feature_used(index: int) -> None:
"""OR a feature bit into the process-global mask.
Called the first time a feature is exercised. Cheap and idempotent;
a no-op when the feature mask is disabled.
"""
global _feature_mask
if not _feature_mask_enabled():
return
if not 0 <= index < 128:
raise ValueError(f"Feature index must be in range 0..127, got {index}")
with _feature_mask_lock:
_feature_mask |= 1 << index
def get_feature_token() -> str | None:
"""Return ``v<version>.<hex_mask>`` for the accumulated mask, or None."""
if not _feature_mask_enabled() or _feature_mask == 0:
return None
return f"v{REGISTRY_VERSION}.{_feature_mask:x}"
```
- **Per package/feature, usage-based:** `mark_feature_used()` is called at the
feature's first meaningful activation, never at import/install time. For
operational clients, tools, providers, and hosts, activation is the first
public operation that exercises the capability. Construction is a valid mark
point only when construction itself performs the capability (for example,
registering/starting runtime resources), not merely because a DI container
instantiated an otherwise-unused object.
- **Process-global and monotonic — intentionally never reset.** Unlike a
per-request scheme (e.g. botocore's `contextvars` feature set that resets
between calls), our mask spans the whole process because many features are not
bound to any service request — an agent or workflow may first run, a provider
may first participate in a session, and a host may start serving independently
of the later request that emits the token. The single global
mask is the only scope that can represent them, and its monotonic "usage so
far" growth is the intended semantic, not a bleed bug. Concurrency-safe via the
module lock (Python) / two atomic 64-bit lanes in .NET.
- **Binary and non-countable.** A set bit means "this feature was observed at
least once in this process before this request." Repeating that bit on every
later eligible request does not represent additional uses and must not be
interpreted as request, invocation, agent, user, or tenant counts.
- **No scoped enable/disable bookkeeping.** Making the mask exact per operation
would add hot-path state changes, context propagation, and reset/error-path
handling. It would also produce a more detailed behavioral trace and therefore
increase privacy sensitivity. V1 deliberately keeps the coarser process-level
Boolean.
- **Token is safe by construction.** The emitted value is `v{int}.{hex}`
characters limited to `[0-9a-fv.]` — so no header-injection sanitization is
required. A 128-bit mask is at most 32 hex characters (contrast botocore,
which must sanitize and cap arbitrary component strings).
- **Private API.** `mark_feature_used`, `get_feature_token`, `apply_feature_token`
and the mask itself are internal helpers; only the emitted token and the
per-language registry tables are the stable, decodable contract.
- **No import cycles:** the accumulator lives in core, while each package owns
private index constants for its own features and calls the core marker. Core
never imports optional packages.
### Interpretation contract
At time 1, Agent A in a worker can use MCP and a Foundry chat client. At time 2,
Agent B in the same worker can make a normal Foundry chat call without MCP. The
time-2 request still carries the MCP bit because MCP was observed earlier in the
process.
That request means only "this process has used MCP." It does not mean Agent B
used MCP, that MCP was used on the time-2 request, or that two requests carrying
the bit equal two MCP uses. Without a separate stable process identifier, the
signal also cannot produce unique-process counts. Supported analysis is limited
to coarse observed-feature prevalence and feature co-occurrence, with the
request-weighting limitation called out explicitly.
### Bit constants
The registry is the allocation authority. Each package defines a private,
hand-written `FeatureIndex` IntEnum (or equivalent constants) containing only
the rows it owns. Core owns core indexes plus the accumulator; optional packages
can allocate and ship new indexes without requiring a core release after the
marker API exists.
```python
# agent_framework_foundry/_feature_usage.py
from enum import IntEnum
from agent_framework._telemetry import mark_feature_used # pyright: ignore[reportAttributeAccessIssue]
class FeatureIndex(IntEnum):
FOUNDRY_CHAT_CLIENT = 48
class RawFoundryChatClient:
async def _send_request(self) -> None:
mark_feature_used(FeatureIndex.FOUNDRY_CHAT_CLIENT)
...
```
A repository validation test reads every package-local declaration and the
matching language/version table. It fails when an index is out of range, missing
from the registry, duplicated/overlapping across packages, or mapped to the wrong
id. For reference, in v1 `FoundryChatClient` → index 48,
`FoundryAgent` → index 49, Foundry memory → index 50.
### Usage activation points
- **Clients/embeddings/evals:** first outbound operation.
- **Tools/MCP:** first connection, discovery, or invocation that exercises the
tool surface.
- **Context/history providers:** first provider hook or load/save operation, not
constructor-only registration.
- **Agents/workflows/orchestrations:** first run/build/start operation that
activates the defined runtime.
- **Hosting:** first serve/start/route activation.
- **Constructor marking:** allowed only when construction itself performs one of
those activations or acquires/registers the runtime resource.
## Emission
**One path in v1: the User-Agent `feat=` token, stamped at request time on an
explicit allowlist of first-party Azure/Foundry client pipelines only.**
Marking (`mark_feature_used`) is **universal** — every feature sets its index
regardless of provider. Only **emission** is scoped. A user who never calls a
first-party endpoint emits no token; this is the honest, intended behaviour (no
third-party leakage, no signal we couldn't read anyway).
The existing base User-Agent behavior (`agent-framework-python/{version}` plus
any dynamically detected hosting prefix) is unchanged; packages continue using
their current `default_headers`, `user_agent`, suffix, or policy mechanisms.
`get_user_agent()` stays base-only (no `feat=`). The `feat=` token is
**separate**, added **only** by eligible Azure/Foundry clients, and
**re-evaluated on each request** so it reflects the mask accumulated so far. A
helper stamps it:
This request-time read does not make the signal request-scoped. The payload
remains the process-global Boolean history described above.
```python
# agent_framework/_telemetry.py
def apply_feature_token(user_agent: str) -> str:
"""Append/refresh the live ``(feat=v<ver>.<hex>)`` comment on a UA string.
Re-reads the current mask on every call, so newly accumulated bits are
reflected immediately. Idempotent: replaces an existing ``(feat=...)``
comment rather than appending a second.
"""
token = get_feature_token() # None when disabled or mask == 0
base = _strip_feature_comment(user_agent)
return f"{base} (feat={token})" if token else base
```
Emission requires **both**:
1. an explicitly approved framework client/pipeline family; and
2. the actual request's normalized HTTPS origin matching that family's reviewed
first-party origin allowlist.
Credentials, `use_azure`, or an Azure-named setting alone do not approve a
destination. Approval depends on the **resolved origin**: customer-specific
subdomains on reviewed Azure/Foundry suffixes remain eligible even when supplied
through `base_url` / `AZURE_OPENAI_BASE_URL`, while customer gateways and unknown
OpenAI-compatible origins are denied by default. The check runs on every actual
request, including redirect hops; a cross-origin or otherwise unapproved redirect
removes `(feat=...)` before sending.
Eligible first-party clients install a **request hook** that performs this
classification and calls `apply_feature_token()`:
- **OpenAI-SDK clients created by Agent Framework**: construct the underlying
client with
`http_client=DefaultAsyncHttpxClient(event_hooks={"request": [_stamp_feat_hook]})`.
Using OpenAI's `DefaultAsyncHttpxClient` preserves the SDK's redirect,
connection-limit, and timeout defaults; a plain `httpx.AsyncClient` must not
replace them. The hook adds or removes the token based on the approved pipeline
plus actual-origin classification. Caller-supplied clients/transports are not
replaced or patched.
- **azure-core pipeline clients**: start with `AIProjectClient` paths whose
telemetry is confirmed ingestible. When Agent Framework constructs/configures
an approved pipeline, add a separate per-call `SansIOHTTPPolicy` whose
`on_request` performs the same actual-origin check and calls
`apply_feature_token()` on
`request.http_request.headers["User-Agent"]`. Do not stamp `SearchClient`,
`CosmosClient`, or another Azure client merely because it is first-party; add
it to the allowlist only after confirming the data path. This mirrors .NET's
request-time `PipelinePolicy` exactly.
This fixes the frozen-at-construction problem: the token is materialised at
**send time**, not client-init time, so it carries features activated after the
client was created. It also confines the token to first-party endpoints. Caller-owned
clients are not patched, and toolkit-owned clients without a supported public
hook are outside v1 coverage.
Encoding uses the RFC 7231 **comment** form `(feat=v1.<hex>)` (metadata, not a
product token), placed after the agent-framework product token, e.g.:
```text
foundry-hosting/agent-framework-python/1.2.3 (feat=v1.2a)
```
### OpenTelemetry — not in v1
An OTel span attribute carrying the same value was considered but **deferred —
primarily for privacy, not complexity**. Unlike the first-party-only UA token, a
span attribute broadcasts the feature-combination fingerprint into the user's
**general** telemetry pipeline, which is commonly exported to third-party APM
vendors (Datadog, Honeycomb, …) — re-introducing exactly the leakage the
first-party scoping was chosen to avoid. (It also carries a cardinality footgun:
a monotonically-growing, combinatorial value must never become a metric
dimension.) The version prefix leaves the door open to add it later **if** the
User-Agent path cannot answer a concrete query and there is an acceptable
scoped/redacted variant; v1 ships the UA path only. See
[ADR-0033 → option C](../decisions/0033-feature-usage-bitmask-user-agent.md#considered-options).
## API Changes
New **internal cross-package** surface in
`agent_framework._telemetry` (not exported from `agent_framework`):
- `mark_feature_used(index: int) -> None`
- `get_feature_token() -> str | None` — returns `v<ver>.<hex>` or `None`.
- `apply_feature_token(user_agent: str) -> str` — live, idempotent UA stamper
used by first-party request hooks.
- `FEATURE_MASK_DISABLED_ENV_VAR` constant — the dedicated mask-only opt-out env
var name (`AGENT_FRAMEWORK_FEATURE_MASK_DISABLED`).
Each package also adds a private package-local `FeatureIndex` declaration for
the rows it owns. The dedicated mask-only opt-out and Python's existing
whole-User-Agent opt-out gate the Python mask; see [Opt-out](#opt-out).
Behavioural change to existing API:
- `get_user_agent()` / `prepend_agent_framework_to_user_agent()` are
**unchanged** — they keep returning the base UA with no `feat=` token. The
token is added only by first-party request hooks via
`apply_feature_token()`.
No breaking changes: when the mask is empty or disabled, for any non-first-party
client, or for an injected client outside the supported-hook set, output is
byte-for-byte identical to today.
## Opt-out
The dedicated mask-only opt-out is shared by both SDKs. Python also retains its
pre-existing whole-User-Agent opt-out:
| Env var | SDKs | Effect |
| --- | --- | --- |
| `AGENT_FRAMEWORK_FEATURE_MASK_DISABLED` | Python and .NET | disables **only** the feature mask; the base `agent-framework-<lang>/{version}` User-Agent is still sent |
| `AGENT_FRAMEWORK_USER_AGENT_DISABLED` | Python (existing behavior) | disables the **entire** Python AF User-Agent contribution, mask included |
The flags accept `true`/`1` (case-insensitive). The dedicated flag lets a
privacy-conscious user keep contributing the SDK identity/version (useful for
support and compat triage) while withholding the feature-usage signal. The mask
is also disabled implicitly whenever Python's whole User-Agent is disabled. A
new whole-User-Agent opt-out for .NET is outside this design.
## E2E example
```python
from agent_framework import Agent
from agent_framework_foundry import FoundryChatClient
from agent_framework_openai import OpenAIChatClient
# First-party (Foundry) client: request hook stamps the live feat token.
agent = Agent(client=FoundryChatClient(...), instructions="...")
# Agent use marks bit 0; FoundryChatClient marks bit 48
await agent.run("Hello")
# Outgoing request to Foundry carries:
# User-Agent: agent-framework-python/1.2.3 (feat=v1.<mask-at-send-time>)
# Third-party client: NO feat token is added (no first-party hook).
other = Agent(client=OpenAIChatClient(...), instructions="...")
await other.run("Hi")
# Outgoing request to OpenAI carries only:
# User-Agent: agent-framework-python/1.2.3
```
Drop only the feature mask (keep the base User-Agent):
```bash
AGENT_FRAMEWORK_FEATURE_MASK_DISABLED=true python app.py
# Foundry request User-Agent: agent-framework-python/1.2.3 (no (feat=...) comment)
```
Python only: use the existing flag to drop its entire User-Agent contribution
(mask included):
```bash
AGENT_FRAMEWORK_USER_AGENT_DISABLED=true python app.py
```
## .NET mapping
- Core owns `FeatureUsage.MarkUsed(int index)` plus the core package's private
index declaration. Each optional assembly owns a private `FeatureIndex` enum
containing only its allocated rows. These are index positions `0..127`, not
`[Flags]` values; `MarkUsed` performs the shift.
- Store the 128-bit mask as **two `long` lanes** (`low` for bits 063, `high`
for 64127). Marking touches one lane with `Interlocked.Or` where available
and a small `Interlocked.CompareExchange` loop on `netstandard2.0` / `net472`.
Read each lane atomically. Since bits only move from zero to one, a concurrent
two-lane snapshot may miss a just-added bit but can never invent or clear one;
the next request includes it.
- Format without depending on `UInt128`: if `high == 0`, emit `low` as lowercase
hex; otherwise emit `high` without leading zeros followed by `low:x16`. Cast
each signed lane to `ulong` before formatting so bits 63 and 127 are preserved.
Reject indexes outside `0..127`.
- **Emission is stamped at request time and first-party-scoped**, matching
Python. The
existing `AgentFrameworkUserAgentPolicy` / `HostedAgentUserAgentPolicy`
pipeline policies already run per request — extend them to apply the same
approved-pipeline + actual-origin classifier, append/refresh the `(feat=...)`
comment only for approved destinations, and remove it on unapproved redirect
hops. Do not register it on third-party `IChatClient`s.
- Same **wire format** (`v<version>.<hex>` comment, hex encoding) and the same
dedicated mask-only opt-out (`AGENT_FRAMEWORK_FEATURE_MASK_DISABLED`). The
**mask is decoded per language**: indexes are not shared, so a decoder must
read the language from the UA product token and select that language's table
before decoding. (.NET's policy was already request-time, so there is no
Python/.NET timing asymmetry.) Adding a .NET whole-User-Agent opt-out is
outside this design.
## Keeping the bitmap in sync
[feature-usage-bit-registry.md](feature-usage-bit-registry.md) is the published
allocation contract. Package-local `FeatureIndex` declarations are the runtime
implementation. There is deliberately **no shared numbering across languages**
and **no machine-readable registry file**.
One repository validation test gathers every package-local declaration for one
language/version and parses the matching Markdown table. It asserts:
1. every declared index is within `0..127`;
2. every `(index, id)` exactly matches one registry row;
3. the union of declarations has no duplicate/overlapping indexes;
4. every non-reserved registry row is declared exactly once.
Adding an optional-package feature therefore changes that package and the
registry, not core. If a programmatic decoder is built later, export the table
to JSON then.
### Decoding
```
UA: agent-framework-python/1.2.3 (feat=v1.2a)
│ │ └ hex mask
│ └ version
└ language → pick the Python table (version 1)
```
Read language → pick the table; read `vN` → pick that version; `AND` the hex mask
against each bit. Unknown bits (from a newer SDK than the decoder's copy of the
table) are ignored.
## Implementation plan (post-approval)
1. **Privacy approval** — confirm the first-party-only feature-combination
signal, retention, access, allowed queries, and opt-out behavior before code
ships.
2. **Core accumulator** — in `agent_framework/_telemetry.py` add the 128-bit
mask, lock, `mark_feature_used(index)`, `get_feature_token`, and
`apply_feature_token`; `get_user_agent()` stays base-only.
3. **Package-local indexes + validation** — add private `FeatureIndex`
declarations to packages and a repository test for exact registry parity,
complete coverage, range, and zero overlap.
4. **First-party request-time hooks** — use OpenAI's
`DefaultAsyncHttpxClient` for framework-created clients and the separate
azure-core `SansIOHTTPPolicy`. Require approved pipeline **and** approved
actual origin on every request/redirect hop. Verify custom origins and
cross-origin redirects never carry the token.
5. **Mark feature usage** — call `mark_feature_used(FeatureIndex.X)` at the
first meaningful activation. Operational clients/providers/tools mark on
their first real operation; build/start points mark compositional features.
Constructor-only marking requires construction itself to exercise the
capability.
6. **.NET parity** — package-local index enums plus the two atomic 64-bit lanes
with `Interlocked.Or` / compare-exchange fallback; extend existing request-time
Foundry UA policies through the shared destination classifier and formatter.
7. **Docs & tests** — update package `AGENTS.md`/skills; tests for **both**
Python opt-out paths (dedicated mask-only and existing whole-UA), the
dedicated .NET mask-only opt-out, first-party scoping, and the live
(non-frozen) UA.
## Limitations & open questions
The decision-level limitations and unresolved trade-offs — reach, per-process
(not per-call) attribution, v1 granularity, fingerprinting residue, and the OTel
question — are owned by the ADR (the dedicated mask-only opt-out is now decided
and included). See
**[ADR-0033 → Limitations](../decisions/0033-feature-usage-bitmask-user-agent.md#limitations)**
and **[Open Questions](../decisions/0033-feature-usage-bitmask-user-agent.md#open-questions-for-decider-discussion)**.
This spec is the implementation reference; it does not re-litigate those choices.
Implementation-only note:
- **Per-request hook overhead is negligible** (a flag check, one Python integer
snapshot or two atomic .NET lane reads, and a string concat per first-party
request), but benchmark the hot path once if a high-QPS Foundry scenario is in
scope.
@@ -0,0 +1,607 @@
---
status: proposed
contact: eavanvalkenburg
date: 2026-07-27
deciders: eavanvalkenburg
---
# Python function-calling loop contract and validation matrix
## Scope
This specification defines the required behavior and validation coverage for the Python function-calling loop.
It covers:
- normal local function execution;
- streaming and non-streaming response aggregation;
- tool approval request and resume;
- approved, rejected, mixed, and replayed approval rounds;
- reasoning content and opaque reasoning signatures bound to function calls;
- history persistence and service-side continuation;
- error, user-input, middleware-termination, and loop-limit paths;
- provider and transport serialization of function calls and results.
The primary implementation is in `python/packages/core/agent_framework/_tools.py`. History replay behavior in
`python/packages/core/agent_framework/_sessions.py`, provider serializers, hosting packages, and UI transports are
part of the same contract when they carry function-call loop content.
## Change sensitivity
This code is high risk. Small changes can produce duplicate side effects, orphaned calls or results, invalid
provider histories, invisible streaming results, stale approval authority, or loops that never terminate.
Dropping reasoning content that a service binds to a tool call can also make an otherwise balanced call/result
transcript invalid.
Any change to the function-calling loop or its approval/history/serialization paths must:
1. identify every affected row in the scenario matrix below;
2. add or update the corresponding regression tests;
3. validate streaming updates, streaming finalization, and non-streaming output where applicable;
4. validate both model-bound history and caller-visible responses;
5. run the full core package tests plus every affected provider or transport package;
6. run source typing, test typing, and syntax checks for every affected package;
7. receive extra review focused on call/result pairing, exactly-once execution, and history replay.
A passing narrow regression test is not sufficient evidence for changes in this area.
### Contribution ownership
Issues involving this code must not be picked up by external contributors without first checking with the Agent
Framework core team. The core team must confirm the intended behavior, affected scenario-matrix rows, ownership
across core/providers/transports, and the required validation scope before implementation starts.
## Flow diagrams and code map
### Main function-calling flow
The main control flow deliberately has separate streaming and non-streaming methods. They share policy helpers, but
their output mechanics differ: one returns an aggregated `ChatResponse`; the other yields `ChatResponseUpdate`
items and is finalized by `ResponseStream`.
The diagrams use only the generic distinction between **local tools**, which Agent Framework executes, and
**hosted-service tools**, whose calls and approval decisions are owned by a remote service. Provider-specific wire
formats and regression tests appear later in the scenario matrix.
```mermaid
flowchart TD
Entry["FunctionInvocationLayer.get_response(...)"]
Setup["Prepare middleware, options, session, budget state,<br/>and execute_function_calls partial"]
Enabled{"Function invocation enabled?"}
Direct["Delegate directly to super().get_response(...)"]
Mode{"stream?"}
NonStream["_get_response_with_function_invocation(...)"]
Stream["_stream_response_with_function_invocation(...)"]
Resolve["_resolve_approval_responses(...)<br/>runs once before the model-iteration loop"]
ApprovalAction{"approval action"}
Immediate["Return/yield terminal result or user-input request<br/>without another model call"]
ApprovalPolicy["Record approval executions;<br/>apply stop/function-call-limit policy"]
Model["Call super_get_response(...)<br/>response may contain reasoning + function_call"]
Process["_process_model_function_calls(...)"]
FunctionAction{"function-processing action"}
Execute["_execute_function_calls(...)"]
Try["_try_execute_function_calls(...)"]
Single["_execute_single_function_call(...)"]
Handle["_handle_function_call_results(...)"]
PostCallPolicy["Record executions; apply error/function-call-limit policy;<br/>reset required tool choice"]
Advance["_prepare_messages_for_next_iteration(...)"]
More{"iteration budget remains?"}
Final["Final model call with tool_choice = none<br/>and deterministic fallback if needed"]
Output["Return ChatResponse or complete ResponseStream"]
Entry --> Setup --> Enabled
Enabled -- no --> Direct
Enabled -- yes --> Mode
Mode -- no --> NonStream
Mode -- yes --> Stream
NonStream --> Resolve
Stream --> Resolve
Resolve --> ApprovalAction
ApprovalAction -- return --> Immediate --> Output
ApprovalAction -- stop --> ApprovalPolicy
ApprovalAction -- continue --> ApprovalPolicy
ApprovalPolicy --> More
Model --> Process
Process --> Execute --> Try --> Single --> Handle --> FunctionAction
FunctionAction -- return --> Output
FunctionAction -- stop --> PostCallPolicy
FunctionAction -- continue --> PostCallPolicy
PostCallPolicy --> Advance
Advance --> More
More -- yes --> Model
More -- no --> Final --> Output
```
Code-reading landmarks:
- `get_response(...)` owns setup and selects the response mode.
- `_get_response_with_function_invocation(...)` owns non-streaming aggregation.
- `_stream_response_with_function_invocation(...)` owns streamed emission/finalization.
- `_resolve_approval_responses(...)` handles only inbound approval decisions.
- `_process_model_function_calls(...)` handles only calls from a completed model response.
- `_try_execute_function_calls(...)` decides approval/declaration/execution behavior for a batch.
- `_replace_approval_contents_with_results(...)` is the occurrence-aware approval transcript normalizer.
- `FunctionInvocationLayer._update_function_invocation_continuation_state(...)` updates continuation state after
every service response. Provider layers may override it to carry provider-specific continuation metadata into
the next service call, but must delegate to the base implementation so generic conversation continuation remains
synchronized with the active `AgentSession`.
### Approval pause and resume
```mermaid
sequenceDiagram
participant Caller
participant History as HistoryProvider
participant Layer as FunctionInvocationLayer
participant Tool
participant Model
Caller->>Layer: Initial user request
Layer->>Model: Messages + tools
Model-->>Layer: reasoning content + function_call
Layer->>Layer: Tool requires approval
Layer-->>Caller: function_call + function_approval_request
Caller->>Layer: function_approval_response
Layer->>Layer: Copy caller-owned messages
Layer->>Layer: _resolve_approval_responses(...)
alt approved
Layer->>Tool: Execute exactly once
Tool-->>Layer: result or exception
Layer->>Layer: Create terminal function_result
else rejected
Layer->>Layer: Create synthetic rejection function_result
end
Layer-->>Caller: Terminal result message/update
alt tool requests more user input
Layer-->>Caller: User-input request with assistant role
else middleware terminates
Layer-->>Caller: Termination result
else error limit reached
Layer->>Model: Normalized reasoning/call/result history, tools disabled
Model-->>Layer: Final assistant response
Layer-->>Caller: Final assistant response
else continue normally
Layer->>Model: Normalized reasoning/call/result history
Model-->>Layer: Final assistant response or another function_call
Layer-->>Caller: Final assistant response / continued loop
end
Layer-->>History: Persist caller input + returned response
Note over History: Later model replay filters approval request/response wrappers
```
The terminal result is caller-visible in both modes. The private normalized message copy is model-visible. The
original caller input and earlier response remain unchanged.
### Reasoning-bound function-call groups
Some hosted services bind reasoning content or an opaque reasoning signature to the function call that follows it.
For those services, reasoning is not optional decoration; it is part of the provider-valid function-call group.
```mermaid
flowchart TD
Response["Assistant response:<br/>reasoning content + function_call"]
Group["One logical reasoning/function-call group"]
Owner{"local or hosted-service tool?"}
Local["Local execution"]
Hosted["Hosted service owns tool execution/state"]
Result["Terminal function_result or hosted result"]
Continuation{"continuation mode"}
Stateless["Stateless or framework-history replay"]
Replayable{"reasoning payload/signature<br/>is replayable?"}
Replay["Replay reasoning + call + result atomically"]
Reject["Fail before the service call;<br/>do not send a lossy transcript"]
Service["Hosted-service continuation"]
Reference["Reference service-stored reasoning/call;<br/>send only the new result or approval decision"]
Compact{"compaction needed?"}
Atomic["Keep or exclude the complete<br/>reasoning/call/result group"]
Caller["Caller-visible response retains reasoning<br/>with the function-call turn"]
Response --> Group --> Owner
Group --> Caller
Owner -- local --> Local --> Result
Owner -- hosted service --> Hosted --> Result
Result --> Compact
Compact -- yes --> Atomic --> Continuation
Compact -- no --> Continuation
Continuation -- stateless / local history --> Stateless --> Replayable
Replayable -- yes --> Replay
Replayable -- no --> Reject
Continuation -- service-managed --> Service --> Reference
```
The generic contract is:
- reasoning content remains ordered immediately before or alongside the function call it explains;
- a terminal result does not replace or discard the reasoning/call portion of the active group;
- stateless replay includes the service-required reasoning payload or opaque signature;
- if required reasoning cannot be reconstructed, the adapter fails before sending invalid or lossy history;
- service-managed continuation may rely on the hosted service's stored reasoning/call items and send only new
outputs or approval decisions;
- compaction keeps or removes the entire reasoning/call/result group atomically.
In the code, core response aggregation preserves reasoning `Content` items, compaction annotations bind reasoning to
the tool-call group, and provider adapters serialize or reconstruct the provider-specific reasoning representation.
### Approval correlation, replay, and reused ids
`call_id` is not globally unique forever. The normalizer therefore tracks open logical occurrences in transcript
order instead of keeping one global result per id.
```mermaid
flowchart TD
Scan["Scan normalized messages in order"]
Kind{"content type"}
Call["function_call:<br/>open a call occurrence"]
Request["function_approval_request"]
Bind{"unbound call occurrence<br/>with same call_id?"}
BindExisting["Bind request id to existing occurrence<br/>and remove wrapper"]
Duplicate{"same request identity<br/>already restored?"}
DropDuplicate["Remove replayed duplicate wrapper"]
Restore["Restore embedded function_call<br/>as a new occurrence"]
Placeholder["function_result with APPROVAL_PENDING:<br/>attach placeholder to open occurrence"]
Completed["terminal function_result:<br/>close earliest open occurrence"]
Response["function_approval_response"]
Pending{"response still pending?"}
RemoveOld["Remove already-resolved historical response"]
Decision{"approved?"}
Approved["Pop next execution result for this call_id"]
Rejected["Create synthetic rejection result"]
HasPlaceholder{"occurrence has placeholder?"}
Replace["Replace placeholder and remove response wrapper"]
ReplaceResponse["Replace response wrapper with terminal content"]
Close["Close occurrence; append terminal content<br/>to resumed response"]
Next["Continue scan"]
Scan --> Kind
Kind -- function_call --> Call --> Next
Kind -- approval request --> Request --> Bind
Bind -- yes --> BindExisting --> Next
Bind -- no --> Duplicate
Duplicate -- yes --> DropDuplicate --> Next
Duplicate -- no --> Restore --> Next
Kind -- pending placeholder --> Placeholder --> Next
Kind -- terminal result --> Completed --> Next
Kind -- approval response --> Response --> Pending
Pending -- no --> RemoveOld --> Next
Pending -- yes --> Decision
Decision -- yes --> Approved --> HasPlaceholder
Decision -- no --> Rejected --> HasPlaceholder
HasPlaceholder -- yes --> Replace --> Close --> Next
HasPlaceholder -- no --> ReplaceResponse --> Close --> Next
Next --> Kind
```
This flow corresponds to `_ApprovalCallOccurrence`, `_collect_approval_responses(...)`, and
`_replace_approval_contents_with_results(...)`.
### History and service-side continuation
```mermaid
flowchart LR
Store["History backing store<br/>(may retain approval wrappers for audit)"]
Load{"HistoryProvider.load_messages?"}
Filter["_filter_approval_control_messages(...)"]
Context["SessionContext model history:<br/>function_call + terminal function_result"]
Current["Current caller input:<br/>new function_approval_response"]
Layer["FunctionInvocationLayer private copy"]
Local{"local or hosted-service approval?"}
LocalResult["Execute locally and normalize to function_result"]
Hosted["Hosted-service adapter"]
StoredRequest["Prior service-issued approval request"]
NewResponse["Current hosted approval decision"]
Skip["Do not replay the stored request inline"]
Send["Send the approval decision exactly once"]
Later["Later turn"]
Manual["Manual-history caller"]
Store --> Load
Load -- yes --> Filter --> Context --> Layer
Load -- no --> Layer
Current --> Layer
Layer --> Local
Local -- local --> LocalResult --> Later
Local -- hosted service --> Hosted
StoredRequest --> Hosted --> Skip
NewResponse --> Hosted --> Send --> Later
Later --> Store
Manual -. owns equivalent filtering .-> Layer
```
When `load_messages=False`, no history is replayed and the history filter is intentionally not invoked. Callers
that manually replay messages own the equivalent rule: do not resend an approval response after its terminal result.
## Normative contract
### Function calls and results
- Every actionable local `function_call` produces exactly one terminal `function_result`, unless execution pauses
for a new user-input request.
- Parallel calls retain model order in the returned transcript.
- Reused `call_id` values are correlated by logical occurrence, not one global value per id.
- A completed function call/result pair is inert on later turns.
- Informational-only and declaration-only calls are not executed as local tools.
### Reasoning-bound calls
- Reasoning content or opaque reasoning metadata that a service binds to a function call is part of the same logical
group as that call and its terminal result.
- Active function loops preserve the reasoning content, function call, function result, and final assistant output
in caller-visible responses.
- Framework-managed/stateless replay includes the service-required reasoning representation before the paired call.
- Service-managed continuation may omit inline reasoning/call items only when the hosted service already owns them.
- Missing non-reconstructable reasoning fails explicitly before a provider request instead of silently dropping the
content.
- Foundry clients do not request `reasoning.encrypted_content` implicitly; callers may opt in explicitly when the
selected deployment supports encrypted reasoning.
- Compaction preserves or excludes the complete reasoning/call/result group atomically.
### Approval request and resume
- A tool that requires approval does not execute before an approved response.
- With an `AgentSession`, every surfaced local or hosted approval request is stored as an immutable snapshot in one
active model batch. A new surfaced batch replaces an abandoned batch instead of accumulating session state.
- Approval request IDs use the provider function `call_id`, whose conversation-level uniqueness is required for
function-call/result correlation. Duplicate request IDs within one batch are rejected as malformed.
- An inbound response is honored only when its request id matches the pending server-held snapshot.
- Approval requests replayed in inbound message history do not create, replace, or resurrect approval authority.
- The executable call id, tool name, arguments, and local or hosted tool metadata are sourced from the recorded
request, never from the response payload.
- A matched approval response consumes its pending entry once. Unmatched, duplicate, and replayed responses do not
reach local execution.
- Tool lookup uses the recorded name against the current registry. A same-name implementation upgrade is allowed;
removing the name prevents local execution.
- Only the strict boolean `True` grants approval. Missing decisions and non-boolean values are rejection, not consent.
- Direct chat-client invocation without an `AgentSession` preserves pass-through compatibility, matching .NET;
authorization sinks still require strict `True`.
- An approved tool executes exactly once.
- A rejected tool executes zero times and produces one synthetic rejection `function_result` using the original
function `call_id`.
- The resumed response contains the newly resolved approved and rejected terminal results before any final assistant
message.
- Streaming yields the same logical result content and ordering as non-streaming output and
`ResponseStream.get_final_response()`.
- The function invocation layer normalizes a private copy of caller messages. It must not mutate the caller's
approval `Message`, approval `Content`, or an earlier returned response.
- Approval-time `UserInputRequiredException` and `MiddlewareTermination` return immediately without another model
call.
### Approval control content
- `function_approval_request` and `function_approval_response` are control-plane contents, not durable model
transcript items.
- A current hosted approval response must be sent once on the immediate resume request.
- AG-UI removes a local approval response from its request and snapshot replay when a terminal result belongs to an
already-consumed occurrence, including result-before-response replay. A client-authored result in the occurrence
that is still registered as pending does not prove completion: AG-UI removes that result, keeps the validated
response for local execution, and leaves hosted approval responses as provider protocol data.
- Hosted AG-UI approval interrupts expose an accept/reject decision only; argument edits are rejected because the
hosted provider executes the server-owned request rather than client-edited arguments.
- AG-UI tool approval resumes accept the standard `approved` decision and full-replacement `editedArgs` payload.
Existing MAF clients remain compatible through the `accepted` decision alias and direct partial argument edits.
- An AG-UI `cancelled` resume is a valid terminal decision, not a run error. In a resume covering parallel open
interrupts, resolved siblings still execute and cancelled calls do not. An identical cancellation retry during
the retained terminal window also completes normally without restoring authority.
- AG-UI Approval State capacity is enforced independently for each trusted application scope. Abandoned pending
authority expires after its configured window, and indeterminate execution records remain non-retryable until
their separate safety window permits reclamation. Reclamation never recreates approval authority.
- A server-issued approval request must not be replayed inline during service-side continuation.
- History providers may retain approval control contents in their backing store for audit, but base history replay
filters them before later model calls.
- Callers that manually own and replay message history without a loading `HistoryProvider` must likewise omit a
previously submitted approval response from later continuation requests.
### History and continuation
- Model-bound history contains one function call/result pair per completed logical occurrence.
- Append-only history must not replay stale approval request/response wrappers to the model.
- Framework-managed and service-managed continuation must preserve the same logical call/result transcript.
- A streaming response rebuilt from updates by an intermediate middleware must carry over the inner response's
conversation id and its internal-conversation-id marker, so framework-managed continuation appends only the latest
message instead of replaying a transcript the provider already holds. The rebuilt response mirrors the inner
conversation id exactly, including clearing it, and never retains an id emitted by an earlier service call in the
same turn.
- A trusted terminal result consumes the corresponding approval authority in explicit stateless replay; a result in a
server-registered pending occurrence cannot consume that authority before local execution.
## Scenario-to-test matrix
### Normal function invocation
| Scenario | Required invariant | Primary regression test |
|---|---|---|
| Single non-streaming call | Call, result, and final assistant message are returned in order. | `packages/core/tests/core/test_function_invocation_logic.py::test_base_client_with_function_calling` |
| String input | Flexible string input follows the same loop behavior. | `test_base_client_with_function_calling_string_input` |
| Multiple sequential rounds | Each round retains one call/result pair. | `test_base_client_with_function_calling_resets` |
| Streaming call | Call chunks, one result update, and final text are emitted in order. | `test_base_client_with_streaming_function_calling` |
| Reasoning-bound call | Finalized output retains reasoning, function call, function result, and final text. | `test_streaming_function_calling_response_includes_reasoning_and_tool_results` |
| Calls across response messages | Every actionable call is executed once. | `test_base_client_executes_function_calls_across_multiple_response_messages` |
| Parallel calls | Results retain the corresponding call ids and execution count. | `test_max_function_calls_limits_parallel_invocations`, `test_streaming_multiple_function_calls_parallel_execution` |
| Informational-only call | The call is returned but not executed or approved. | `test_informational_only_function_call_is_not_invoked`, `test_informational_only_function_call_does_not_request_approval`, `test_streaming_informational_only_function_call_is_not_invoked` |
| Declaration-only call | The call is surfaced as user input and is not executed; streaming arguments appear once while finalized request metadata remains available. | `test_declaration_only_tool`, `test_streaming_declaration_only_tool_preserves_metadata_without_duplicate_arguments` |
| Function invocation disabled | The client bypasses the invocation loop without losing invocation kwargs. | `test_function_invocation_config_enabled_false`, `test_function_invocation_config_enabled_false_preserves_invocation_kwargs`, `test_streaming_function_invocation_config_enabled_false` |
| Runtime tool changes | Added tools become available on the next iteration and retain approval behavior. | `test_add_tools_available_next_iteration`, `test_add_tools_with_approval_required_tool` |
### Approval pause and resume
| Scenario | Required invariant | Primary regression test |
|---|---|---|
| Initial approval request | Assistant response contains the original call and approval request; tool does not execute. | `test_approval_requests_in_assistant_message`, `test_streaming_approval_request_generated`, `test_streaming_approval_requests_in_assistant_message` |
| Approved non-streaming resume | Result precedes final text; tool executes once; inputs remain unchanged. | `packages/core/tests/core/test_harness_tool_approval.py::test_approval_resume_returns_result_without_mutating_inputs[non-streaming-approved]` |
| Rejected non-streaming resume | Rejection result precedes final text; tool executes zero times; inputs remain unchanged. | `test_approval_resume_returns_result_without_mutating_inputs[non-streaming-rejected]` |
| Approved streaming resume | Result update precedes final text and final response matches non-streaming shape. | `test_approval_resume_returns_result_without_mutating_inputs[streaming-approved]`, `test_streaming_approval_resume_yields_terminal_result_before_model_text[approved]` |
| Rejected streaming resume | Rejection result update precedes final text and tool executes zero times. | `test_approval_resume_returns_result_without_mutating_inputs[streaming-rejected]`, `test_streaming_approval_resume_yields_terminal_result_before_model_text[rejected]` |
| Mixed approved/rejected batch | Every call gets one correctly correlated terminal result. | `packages/core/tests/core/test_function_invocation_logic.py::test_rejected_approval` |
| Persisted approval replay | Resume executes with the prior call available. | `test_persisted_approval_messages_replay_correctly` |
| Hosted approval pass-through | Hosted requests/responses are bound to the recorded provider request and are not processed as local calls. | `test_hosted_tool_approval_response`, `test_hosted_mcp_approval_response_passthrough`, `test_session_approval_binding_reconstructs_hosted_response`, `test_mixed_local_and_hosted_approval_flow` |
| Approval-time user input | Every user-input request from one approved execution returns in order with assistant role and no extra model call; the execution consumes one call-budget unit. | `packages/core/tests/core/test_harness_tool_approval.py::test_approval_resume_returns_all_user_input_requests_without_another_model_call`, `packages/core/tests/core/test_function_invocation_logic.py::test_approval_resume_user_input_counts_toward_function_call_budget` |
| Mixed terminal result and follow-up input | Completed siblings remain tool-role while only follow-up input requests use assistant-role messages/updates. | `packages/core/tests/core/test_function_invocation_logic.py::test_approval_resume_separates_terminal_results_from_follow_up_requests`, `packages/openai/tests/openai/test_openai_chat_completion_client.py::test_mixed_approval_resume_roles_serialize_function_result_as_tool` |
| Approval-time middleware termination | Terminal result returns with no extra model call in either response mode. | `packages/core/tests/core/test_function_invocation_logic.py::test_approval_resume_honors_middleware_termination` |
| Approval re-entry after iteration budget | Pending approved calls resolve once even when prior model calls consumed `max_iterations`. | `packages/core/tests/core/test_harness_tool_approval.py::test_auto_approval_resolves_after_iteration_budget_is_exhausted` |
| Approval resume with reasoning | Model-bound resume history retains reasoning before the call and terminal result in both modes. | `packages/core/tests/core/test_harness_tool_approval.py::test_approval_resume_replays_reasoning_with_function_call_group` |
| Session-bound substituted response | A response is rebound to the immutable recorded call and cannot replace its call id, tool name, or arguments. | `packages/core/tests/core/test_function_invocation_logic.py::test_session_approval_binding_rebinds_consumes_and_rejects_duplicates` |
| Truthy non-boolean decision | Strings, integers, null, and other non-booleans do not authorize execution. | `packages/core/tests/core/test_function_invocation_logic.py::test_session_approval_binding_treats_truthy_non_boolean_as_rejection`, `packages/core/tests/core/test_types.py::test_function_approval_response_deserialization_rejects_non_boolean_decisions`, `packages/ag-ui/tests/ag_ui/test_message_adapters.py::test_function_approval_requires_real_boolean`, `packages/ag-ui/tests/ag_ui/test_approval_result_event.py::test_resolve_approval_responses_treats_non_boolean_decision_as_rejection` |
| Active batch replacement | A newly surfaced model batch replaces abandoned approval authority instead of growing session state. | `packages/core/tests/core/test_function_invocation_logic.py::test_session_approval_binding_replaces_abandoned_batch` |
| Duplicate request id | Ambiguous request IDs within one active batch fail explicitly. | `packages/core/tests/core/test_function_invocation_logic.py::test_session_approval_batch_rejects_duplicate_request_ids` |
| Tool registry changes | Same-name upgrades may execute the recorded operation; removing the recorded name executes nothing. | `packages/core/tests/core/test_harness_tool_approval.py::test_approval_resume_allows_same_name_tool_upgrade`, `test_approval_resume_does_not_execute_when_recorded_tool_disappears` |
### Approval correlation and replay
| Scenario | Required invariant | Primary regression test |
|---|---|---|
| Result matching without placeholders | Results match calls by id even when the result list is reordered. | `test_replace_approval_contents_with_results_uses_result_call_ids_without_placeholders` |
| Reused id after completion | A later round with the same id creates a second valid pair. | `test_replace_approval_contents_with_results_allows_reused_call_id_after_completion` |
| Replayed approval wrapper | A duplicated wrapper does not restore another function call. | `test_replace_approval_contents_with_results_deduplicates_replayed_approval_request` |
| Historical resolved response plus new round | The old response is removed from normalized input and is not converted into a rejection result. | `test_replace_approval_contents_with_results_ignores_already_resolved_response` |
| Multiple reused-id rounds | Approved and rejected rounds retain separate call/result occurrences. | `test_replace_approval_contents_with_results_correlates_reused_call_id_occurrences` |
| Multi-content result with reused id | Every content produced by one execution stays with that approval occurrence and cannot bleed into the next reused-id round. | `test_replace_approval_contents_with_results_keeps_multi_content_group_with_reused_call_id` |
| Follow-up request closes one occurrence | A user-input follow-up consumes only the preceding approval authority and leaves a later reused-id response pending. | `test_collect_approval_responses_consumes_matching_follow_up_request_occurrence` |
| Reused-id placeholders | Placeholder results consume approved results by occurrence. | `test_replace_approval_contents_with_results_correlates_reused_call_id_placeholders` |
| Rejected placeholder | Rejection replaces the pending placeholder instead of adding a second result. | `test_replace_approval_contents_with_results_replaces_rejected_placeholder` |
| Results reordered with placeholders | Results still match the correct call ids. | `test_replace_approval_contents_with_results_uses_result_call_ids_for_placeholders` |
| Missing result call id | A malformed result does not steal another approval's result. | `test_replace_approval_contents_with_results_skips_results_without_call_id` |
| Empty approval message cleanup | Fully consumed approval messages are removed from normalized model input. | `test_replace_approval_contents_with_results_prunes_emptied_messages` |
| Later stateless turn | A prior terminal approval response cannot execute again. | `test_resolved_approval_response_is_inert_on_later_stateless_turn` |
| Unbound or duplicate response | A response with no pending session request is removed; one request authorizes at most one response. | `test_session_approval_binding_rebinds_consumes_and_rejects_duplicates` |
| Forged inbound request history | A caller-supplied request wrapper cannot replace the server snapshot or resurrect consumed authority. | `test_session_approval_binding_does_not_trust_inbound_request_history` |
| Pending history turn | An unresolved approval batch is omitted atomically from unrelated model input while a later decision can still resume it once. | `packages/core/tests/core/test_harness_tool_approval.py::test_pending_approval_from_file_history_stays_resumable_without_model_orphan` |
| Duplicate function-call prevention | Approval normalization does not create a second call for one round. | `test_no_duplicate_function_calls_after_approval_processing` |
| Rejection call id | Rejection result uses the function call id, not only the approval id. | `test_rejection_result_uses_function_call_id` |
### Mixed batches and approval middleware
| Scenario | Required invariant | Primary regression test |
|---|---|---|
| Safe and approval-required calls in one batch | Hidden safe calls replay only with the matching visible approval. | `packages/core/tests/core/test_harness_tool_approval.py::test_mixed_batch_hides_already_approved_request_until_approval_replay` |
| Restored approval state | Serialized `ToolApprovalState` restores mixed-batch behavior. | `test_mixed_batch_accepts_restored_tool_approval_state` |
| Unrelated turn before approval | Hidden calls do not execute on an unrelated turn. | `test_hidden_mixed_batch_requests_do_not_replay_on_unrelated_turn` |
| Multiple abandoned batches | Hidden calls replay only for the matching batch. | `test_hidden_mixed_batch_requests_replay_only_for_matching_visible_approval` |
| Queued approvals | One unresolved approval is surfaced per run without premature execution. | `test_tool_approval_middleware_queues_multiple_approval_requests`, `test_tool_approval_middleware_queues_streamed_approval_requests` |
| Middleware state plus hidden core state | State saves do not discard hidden mixed-batch calls. | `test_tool_approval_middleware_preserves_hidden_mixed_batch_requests` |
| Auto-approval callback | Callback receives the original function call and executes the approved set once. | `test_tool_approval_middleware_auto_approval_rule_receives_function_call` |
| Shared call budget | Auto-approved re-entry does not reset `max_function_calls`, and every executed approval group counts even when it pauses for input. | `test_tool_approval_middleware_auto_approved_loops_share_function_call_budget`, `test_approval_resume_user_input_counts_toward_function_call_budget` |
| Standing tool rule | Tool-level approval applies only to later matching tools. | `test_tool_approval_middleware_always_approve_tool_rule` |
| Forged standing rule | An unbound or substituted hosted response cannot create a standing middleware approval rule for caller-selected metadata. | `test_tool_approval_middleware_drops_forged_standing_approval`, `test_tool_approval_middleware_rebinds_hosted_standing_approval` |
| Hosted server boundary | Standing approval does not cross `server_label`. | `test_tool_approval_middleware_standing_rules_include_hosted_server_boundary` |
| Argument-scoped rule | Exact arguments are required; empty arguments are not tool-wide. | `test_tool_approval_middleware_always_approve_tool_with_arguments_rule`, `test_tool_approval_middleware_empty_arguments_rule_is_not_tool_wide` |
| Provider-injected approval tool | A tool added during `before_run` defers to in-run resolution, executes once, and emits one result. | `packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_agent_approval_deferred_provider_tool_executes` |
| AG-UI provider boundary | Completed local approval controls from AG-UI request and snapshot replay are absent from raw chat-client input while deferred and hosted approvals keep their respective in-run/provider paths. | `packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_does_not_forward_resolved_local_approval_control_to_chat_client`, `packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_agent_approval_deferred_provider_tool_executes`, `packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_canonical_resume_preserves_hosted_approval_for_provider`, `packages/ag-ui/tests/ag_ui/test_run.py::test_filter_local_approval_responses_for_provider_removes_duplicate_completed_controls`, `packages/ag-ui/tests/ag_ui/test_run.py::test_filter_local_approval_responses_for_provider_pairs_reused_call_ids_by_occurrence`, `packages/ag-ui/tests/ag_ui/test_run.py::test_canonical_hosted_approval_resume_rejects_edited_arguments_without_mutating_pending` |
| AG-UI standard approval payload | Agent and workflow tool approvals emit canonical `tool_call` interrupts. `approved` plus full-replacement `editedArgs` executes once and replays idempotently, while legacy `accepted` plus direct partial edits remains supported. Hosted approvals remain decision-only. | `packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_agent_approval_resume_entry_applies_standard_full_replacement_edited_args`, `test_endpoint_agent_approval_replayed_standard_edited_resume_is_idempotent`, `test_endpoint_agent_approval_resume_entry_applies_edited_arguments`, `test_workflow_endpoint_emits_canonical_tool_approval_interrupt`, `test_workflow_endpoint_accepts_canonical_tool_approval_resume`, `test_workflow_endpoint_applies_canonical_approval_edited_args`, `test_workflow_endpoint_accepts_legacy_partial_approval_edits`, `test_workflow_endpoint_hosted_approval_rejects_argument_edits` |
| AG-UI cancellation | A cancelled interrupt executes zero times and completes normally, including an identical retry during retained cancellation state; resolved siblings in the same complete resume still execute once. Workflow cancellation clears both runner correlation and the owning agent executor's pending request so later approvals remain resumable. | `packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_agent_approval_cancelled_resume_entry_completes_without_execution`, `test_endpoint_agent_approval_replayed_cancellation_completes_idempotently`, `test_endpoint_agent_approval_mixed_cancelled_and_resolved_resume_executes_resolved_tool`, `test_endpoint_workflow_request_info_cancelled_resume_completes_normally`, `test_workflow_endpoint_cancelled_agent_approval_does_not_block_next_approval` |
| AG-UI approval retention and capacity | Pending authority expires automatically, indeterminate outcomes remain non-retryable until their safety window permits reclamation, and one trusted scope cannot consume another scope's occurrence quota. | `packages/ag-ui/tests/ag_ui/test_approval_lifecycle.py::test_abandoned_pending_occurrence_expires_and_releases_capacity`, `test_indeterminate_occurrence_is_reclaimed_after_its_safety_window`, `test_capacity_is_enforced_per_trusted_scope` |
| AG-UI local executor unavailable on resume | A claimed local occurrence whose executor disappeared releases its unstarted claim, reports temporary unavailability, and remains safely retryable. | `packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_agent_approval_resume_remains_retryable_when_local_tool_is_temporarily_unavailable` |
| AG-UI forwarded execution interruption | A provider failure, cancellation, or stream close after forwarding an approval recovers the open occurrence as indeterminate when no idempotency key proves retry safety. | `packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_hosted_approval_becomes_indeterminate_when_provider_stream_fails` |
### Errors, control flow, and limits
| Scenario | Required invariant | Primary regression test |
|---|---|---|
| Rejected execution | Rejection is a normal terminal result, not an exception to the caller. | `test_unapproved_tool_execution_raises_exception` |
| Approved tool exception | Generic and detailed error modes preserve one result and one execution. | `test_approved_function_call_with_error_without_detailed_errors`, `test_approved_function_call_with_error_with_detailed_errors` |
| Approved validation error | Validation failure returns one result without invoking the function body. | `test_approved_function_call_with_validation_error` |
| Approved success | Successful approved execution returns one result. | `test_approved_function_call_successful_execution` |
| Consecutive error cap | Error threshold stops repeated failures, submits collected results, and makes only the required final no-tool model call. | `test_function_invocation_config_max_consecutive_errors`, `test_streaming_function_invocation_config_max_consecutive_errors`, `test_approval_resume_error_limit_forces_final_no_tool_response` |
| Unknown call handling | Configured false returns an error result; configured true raises. | `test_function_invocation_config_terminate_on_unknown_calls_false`, `test_function_invocation_config_terminate_on_unknown_calls_true`, streaming equivalents |
| Middleware termination | Normal non-approval loop stops without a second model call. | `test_terminate_loop_single_function_call`, `test_terminate_loop_multiple_function_calls_one_terminates`, `test_terminate_loop_streaming_single_function_call` |
| Maximum iterations | No orphan calls; a final no-tool response or deterministic fallback is returned. | `test_max_iterations_limit`, `test_max_iterations_no_orphaned_function_calls`, `test_max_iterations_makes_final_toolchoice_none_call`, `test_max_iterations_blank_final_fallback_synthesizes_message`, streaming equivalents |
| Maximum function calls | Parallel overshoot is bounded after the batch; every executed result group counts even without a `function_result`; blank final responses get fallback content. | `test_max_function_calls_limits_parallel_invocations`, `test_max_function_calls_single_calls_per_iteration`, `test_user_input_request_multiple_contents_propagate`, `test_approval_resume_user_input_counts_toward_function_call_budget`, `test_max_function_calls_blank_final_fallback_synthesizes_message`, streaming equivalent |
| Provider tool content after an active limit | Locally actionable calls and local approval requests returned despite `tool_choice="none"` are removed in both response modes. Provider-executed informational call/result pairs, hosted approval requests, and metadata-only streaming updates remain visible; fallback text never replaces retained transcript content. | `test_function_invocation_limit_drops_unexecutable_tool_content`, `test_streaming_function_invocation_limit_drops_unexecutable_tool_content`, `test_streaming_function_invocation_limit_preserves_metadata_after_tool_content_is_dropped`, `test_function_invocation_limit_preserves_provider_executed_tool_pair`, `test_streaming_function_invocation_limit_preserves_provider_executed_tool_pair`, `test_function_invocation_limit_appends_fallback_after_provider_executed_tool_pair`, `test_streaming_function_invocation_limit_appends_fallback_after_provider_executed_tool_pair`, `test_function_invocation_limit_preserves_hosted_approval_request`, `test_streaming_function_invocation_limit_preserves_hosted_approval_request` |
| Conversation continuation | Conversation id updates between iterations and is cleared on stop where required. | `test_conversation_id_updated_in_options_between_tool_iterations`, `test_function_invocation_stop_clears_conversation_id_non_stream`, `test_streaming_function_invocation_stop_clears_conversation_id` |
### History and provider serialization
| Scenario | Required invariant | Primary regression test |
|---|---|---|
| Append-only history replay | Resolved approval wrappers do not reach a later model call; one call/result pair remains. | `packages/core/tests/core/test_harness_tool_approval.py::test_approval_resume_filters_resolved_control_items_from_file_history` |
| Pending placeholder history | An approval response remains replayable while its only result is `[APPROVAL_PENDING]`. | `packages/core/tests/core/test_sessions.py::test_filter_approval_controls_keeps_response_for_pending_placeholder` |
| Pending hosted history replay | Stateless hosted approval requests remain replayable until a response is recorded, then both controls become inert. | `packages/openai/tests/openai/test_openai_chat_client.py::test_stateless_history_preserves_pending_hosted_approval_request_until_response` |
| Non-history provider plus session | Local history is still auto-injected for approval resume. | `packages/core/tests/core/test_agents.py::test_non_history_context_provider_still_injects_inmemory` |
| Hosted per-service-call persistence | A host-managed transcript remains available throughout a local function-call loop without being persisted into the framework session and replayed on the next hosted request. | `packages/foundry_hosting/tests/test_responses.py::TestAgentSessionPersistence::test_per_service_call_persistence_preserves_function_loop_history` |
| Streaming message injection with per-service-call persistence | A streaming response rebuilt from updates mirrors the inner conversation id exactly, including clearing it, and keeps its internal marker, so the next iteration appends only the latest message rather than replaying the whole turn on top of provider-held history, and never persists a conversation id from an earlier injected service call. | `packages/core/tests/core/test_middleware_with_chat.py::TestChatMiddleware::test_message_injection_middleware_streaming_preserves_inner_continuation_state`, `test_message_injection_middleware_streaming_keeps_service_conversation_id_external`, `test_message_injection_middleware_streaming_clears_conversation_id_when_final_call_has_none`, `test_message_injection_middleware_conversation_id_matches_across_streaming_modes`, `packages/core/tests/core/test_harness_agent.py::test_streaming_harness_tool_call_does_not_duplicate_transcript` |
| Service-side approval decision | Stored hosted request is skipped; the current approved or rejected hosted response is sent, while local approval controls are omitted from provider input. | `packages/openai/tests/openai/test_openai_chat_client.py::test_prepare_messages_strips_approval_request_but_keeps_response_under_storage`, `test_prepare_messages_drops_local_approval_controls` |
| OpenAI approval serialization | Hosted approval id and decision serialize to `mcp_approval_response`; local approvals remain in-process. | `test_prepare_message_for_openai_with_function_approval_response`, `test_prepare_content_for_opentool_approval_response`, `test_function_approval_response_with_mcp_tool_call` |
| OpenAI end-to-end hosted approval | Hosted request parses, response sends, and continuation completes. | `test_end_to_end_mcp_approval_flow` |
| Stored function call/result | Service-side storage drops server-issued calls but keeps new outputs. | `test_prepare_options_with_conversation_id_strips_server_issued_items`, `test_prepare_messages_for_openai_full_conversation_with_reasoning` |
| Stateless reasoning replay | Replay reconstructs reasoning, call, and result together; missing required reasoning fails before the request. | `test_tool_loop_store_false_replays_encrypted_reasoning_group`, `test_stateless_request_rejects_non_replayable_reasoning_bound_mcp_output`, `test_prepare_messages_for_openai_full_conversation_with_reasoning` |
| Foundry encrypted reasoning opt-in | Foundry clients omit `reasoning.encrypted_content` by default and preserve an explicit caller opt-in. | `packages/foundry/tests/foundry/test_foundry_chat_client.py::test_get_response_does_not_request_encrypted_reasoning_by_default`, `test_get_response_preserves_explicit_encrypted_reasoning_opt_in`, `packages/foundry/tests/foundry/test_foundry_agent.py::test_foundry_agent_basic_call_does_not_request_unsupported_encrypted_reasoning`, `test_foundry_agent_preserves_caller_requested_encrypted_reasoning`, `packages/foundry_hosting/tests/test_responses_int.py::TestReasoningHostedMcpReplay::test_second_turn_replays_mcp_call_with_encrypted_reasoning` |
| Opaque reasoning signature replay | Provider-specific opaque reasoning metadata is captured and restored on reconstructed calls. | `packages/gemini/tests/test_gemini_client.py::test_function_call_part_captures_thought_signature_as_reasoning_content`, `test_reconstructed_function_call_replays_thought_signature_from_reasoning_content` |
| Chat Completions approval wrappers | Framework approval wrappers are not sent as chat messages. | `packages/openai/tests/openai/test_openai_chat_completion_client.py` approval serialization tests |
| AG-UI approval result event | Approved result emits once with content and persists in snapshot. | `packages/ag-ui/tests/ag_ui/test_approval_result_event.py::test_approved_call_emits_one_live_result_under_original_identity`, `packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_agent_approval_resume_persists_replayable_tool_results`, `test_endpoint_agent_approval_replayed_resume_entry_reprojects_retained_result` |
| AG-UI rejection/mixed decision | Transport emits only the events defined for approved and rejected calls without duplicates. | `packages/ag-ui/tests/ag_ui/test_approval_result_event.py::test_rejected_call_does_not_execute_or_emit_live_result`, `test_mixed_batch_preserves_approved_result_identity_and_order`, `packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_agent_approval_rejection_releases_already_approved_sibling` |
| AG-UI approval-time follow-up | The full grouped user-input pause remains in message history and emits no synthetic `TOOL_CALL_RESULT`. | `packages/ag-ui/tests/ag_ui/test_approval_result_event.py::test_approval_follow_up_group_remains_in_history_without_live_tool_result` |
| AG-UI approval execution failure | A grouped executor failure becomes one deterministic terminal error result for the approved call. | `packages/ag-ui/tests/ag_ui/test_approval_result_event.py::test_approval_execution_failure_emits_one_terminal_error_result` |
| AG-UI no-approval path | Ordinary tool results do not gain an extra approval result event. | `packages/ag-ui/tests/ag_ui/test_approval_result_event.py::test_no_approval_path_emits_no_approval_specific_duplicate_result` |
| AG-UI `confirm_changes` snapshot | An accepted synthetic confirmation is replaced only when its original function call has a real result; rejection is cleaned explicitly, and missing accepted results remain inert. | `packages/ag-ui/tests/ag_ui/test_confirm_changes_snapshot.py` |
| AG-UI malformed `confirm_changes` metadata | Non-list tool-call metadata and malformed argument JSON are ignored without guessing a target call. | `test_confirm_changes_target_ignores_non_list_tool_calls`, `test_confirm_changes_target_rejects_malformed_arguments_json` |
| Compaction pair integrity | Adjacent and non-adjacent pairs, including assistant-embedded results and completed reused-id occurrences, remain atomic without pairing ambiguous or out-of-order ids. | `packages/core/tests/core/test_compaction.py::test_group_annotations_keep_tool_call_and_tool_result_atomic`, `test_group_annotations_include_reasoning_in_tool_call_group`, `test_group_annotations_pair_nonadjacent_function_result_by_call_id`, `test_group_annotations_pair_multiple_nonadjacent_results_with_declaration`, `test_group_annotations_pair_completed_reused_call_id_occurrences`, `test_group_annotations_close_assistant_embedded_result_before_reused_call_id`, `test_sliding_window_does_not_retain_orphan_result_after_assistant_embedded_result`, `test_sliding_window_keeps_reused_call_id_occurrences_atomic`, `test_group_annotations_do_not_pair_ambiguous_duplicate_call_ids` |
## Required coverage gaps
These scenarios are required but are not fully covered by merged tests on `main`:
| Gap | Tracking |
|---|---|
| Service-owned `previous_response_id` continuation cannot execute a terminal approval again on a later turn. | #6851 |
Do not mark these rows covered by nearby tests; each needs a dedicated regression at the owning layer.
## Minimum validation commands
Run from `python/` for any core function-loop change:
```bash
uv run poe test -P core
uv run poe syntax -P core
uv run poe pyright -P core
uv run poe test-typing -P core
```
Also run every affected package. Common approval-loop changes require:
```bash
uv run poe test -P openai
uv run poe syntax -P openai
uv run poe pyright -P openai
uv run poe test-typing -P openai
uv run poe test -P ag-ui
uv run poe test -P declarative
uv run --directory packages/foundry_hosting poe test
```
Run focused regression files first while iterating, but do not substitute them for the full package commands above.
## Review checklist
Before accepting an update, reviewers must confirm:
- the changed behavior is represented in this specification;
- the matrix names a regression test for every affected scenario;
- approved tools cannot execute twice;
- rejected tools cannot execute;
- no call or result becomes orphaned or duplicated;
- call/result matching does not assume `call_id` is globally unique forever;
- reasoning content or opaque signatures remain in the same logical group as the paired call/result, or replay fails
explicitly before sending a lossy provider request;
- caller messages and previous responses remain immutable;
- streaming updates and final response agree with non-streaming output;
- history replay does not reintroduce approval authority;
- full package, syntax, source typing, and test typing checks were run.
## Related issues
- #7241 — approval-resolution result streaming
- #7267 / #7271 and #7304 — replayed calls and reused ids
- #7043 — provider-injected approval execution
- #6828 — AG-UI `confirm_changes` snapshot correlation
- #7212 — non-adjacent and reused-id compaction integrity
- #7125 — service-side approval response serialization
- #7045 — post-limit tool-content transcript integrity
- #6973 — declaration-only streaming metadata and argument integrity
- #6851 — duplicate side effects after approval continuation
- #7383 — bind approval responses to framework-issued requests after this foundation merges
- #6963 / #7095 — opaque reasoning-signature replay
- #6074 / #7233 — reasoning-paired tool-call replay
- #6450 / #6794 — provider message and tool-result serialization
+284
View File
@@ -0,0 +1,284 @@
# Feature-usage bit registry (per-language)
> **Status:** draft, accompanies [ADR-0033](../decisions/0033-feature-usage-bitmask-user-agent.md)
> and [SPEC-004](004-feature-usage-telemetry.md).
> **Version:** `1` per language · **Width:** 128-bit
This document is the proposed human-readable registry for the feature-usage
mask. Until ADR-0033 is accepted and the index declarations ship, these tables
are a **candidate mapping**, not a stable wire contract. The table is the
allocation authority and published decoder contract; package-local private
`FeatureIndex` declarations implement the rows they own. There is no generated
artifact.
This telemetry is intentionally **transparent**: this registry is public, the
emitted value is human-decodable, and a dedicated env var disables the mask
without removing the base User-Agent. Python's existing whole-User-Agent opt-out
also suppresses its mask; see [Opt-out](#opt-out).
## What is collected
A single 128-bit integer (the *feature mask*) describing **which Agent Framework
features were exercised** in a process — not which packages are installed. The
candidate below uses package-level bits plus selected major capabilities: core
agent/workflow/MCP features, stable skill source types, each orchestration
pattern, each individual built-in context/history provider, and distinct Foundry
surfaces. ADR-0033 still leaves the final v1 granularity open. A feature sets its
index at first meaningful activation; the SDK shifts that index, ORs the mask,
and emits the value.
No identifiers, arguments, prompts, payloads, or user data are encoded — only the
coarse Boolean \"this feature was observed at least once in this process\" per
registered bit. A repeated bit on later requests is the same observation, not
another use and not a count.
## Allocation tenet
**An index represents a stable, framework-owned capability whose adoption answers a
concrete product or support question.** It has a clear actual-use mark point in a
public entry path, and the privacy review covers the resulting distinction.
Keep imports, installation state, aliases, wrappers, internal helpers, and
implementation decorators such as caching/filtering/deduplication within their
own capability bit. Customer/runtime values — names, prompts, arguments, URLs,
identifiers, configuration choices — never become bits. A proposed distinction
without a concrete query and named decision owner waits.
Operational clients, tools, providers, and hosts mark on their first real public
operation/participation. Constructor marking is reserved for cases where
construction itself activates or registers the capability; DI instantiation
alone is not usage.
Ids use the package/integration name for a package-level signal and add a
capability suffix only when the row tracks a narrower surface. They describe the
registered feature, not an inheritance hierarchy: for example, Python
`hosting` is the base `agent-framework-hosting` package, while `hosting.a2a` is
the separate hosting-A2A integration.
## Per-language, not shared
The two tables below are **independent**. Feature indexes are **not** shared across
languages — Python bit 13 and .NET bit 13 do not mean the same thing. This is
deliberate: the User-Agent product token already names the language
(`agent-framework-python` vs `agent-framework-dotnet`), so a decoder selects the
right table from the UA and decodes against it. Each SDK numbers and evolves its
features independently — no cross-language synchronization, no null placeholders,
no \"same bit, same meaning\" rule.
## Encoding
- **Width:** 128-bit unsigned integer per language.
- **Versioning:** the emission carries the version so a decoder knows the bit
mapping in effect (version is per language).
- **User-Agent:** the mask is an RFC 7231 **comment** (metadata, not a product
token), placed after the agent-framework product token:
```text
agent-framework-python/1.2.3 (feat=v1.<hex_mask>)
```
where `<hex_mask>` is lowercase hex, no leading zeros, no `0x` prefix. Example
for bits 0, 1, 5 set (`0b100011 = 0x23`):
```text
agent-framework-python/1.2.3 (feat=v1.23)
```
- **Decoding:** read the **language** from the product token, pick that table;
read `vN`, pick that version; test `mask & (1 << index)` for each row. Unknown indexes
(newer SDK than the decoder's copy) are ignored.
## Emission scope (where the mask is sent)
- **Marking is universal:** every feature sets its index at first meaningful
activation, regardless of provider.
- **User-Agent `(feat=...)` comment — approved first-party clients only,
stamped at request time.** Added only when both the **Azure / Foundry**
client/pipeline family and the actual HTTPS origin are approved, re-evaluated
on every request and redirect hop. Custom origins are default-deny and an
unapproved redirect removes the token. It is
**never** sent to third-party providers — a feature fingerprint must not leak
into logs we cannot read. See [SPEC-004](004-feature-usage-telemetry.md#emission).
- **OpenTelemetry: not in v1.** Deferred primarily for privacy (a span attribute
would broadcast the fingerprint into the user's general telemetry / third-party
APM vendors). Left open behind the version prefix; see
[ADR-0033](../decisions/0033-feature-usage-bitmask-user-agent.md#considered-options).
## Index table — Python (`agent-framework-python`, version 1)
Layout: core features 031, orchestration patterns 3247, and
provider/integration packages from 48.
The provider/integration block is intentionally **not** partitioned by vendor
ownership. Some packages span first- and third-party services, ownership can
change, and protocols/storage integrations do not fit a stable first/third-party
taxonomy. Index ranges are allocation space, not privacy or emission policy;
the explicit destination allowlist independently ensures that the mask is sent
only to approved first-party endpoints.
| Index | Id | Feature | Activated at (representative) |
| --- | --- | --- | --- |
| 0 | `core.agent` | Agent | `agent_framework.Agent` |
| 1 | `core.harness_agent` | Harness agent | `agent_framework.create_harness_agent` |
| 2 | `core.workflow` | Workflow engine (custom graphs) | `agent_framework.WorkflowBuilder` |
| 3 | `core.mcp` | MCP tool (any transport) | `agent_framework.MCPStdioTool` |
| 4 | `core.tool_approval` | Tool-approval harness | `agent_framework.ToolApprovalMiddleware` |
| 5 | `core.memory_provider` | Memory context provider | `agent_framework.MemoryContextProvider` |
| 6 | `core.skills_provider` | Skills provider | `agent_framework.SkillsProvider` |
| 7 | `core.file_access_provider` | File-access provider | `agent_framework.FileAccessProvider` |
| 8 | `core.compaction_provider` | Context compaction provider | `agent_framework.CompactionProvider` |
| 9 | `core.todo_provider` | Todo provider | `agent_framework.TodoProvider` |
| 10 | `core.agent_mode_provider` | Agent-mode provider | `agent_framework.AgentModeProvider` |
| 11 | `core.background_agents_provider` | Background-agents provider | `agent_framework.BackgroundAgentsProvider` |
| 12 | `core.in_memory_history_provider` | In-memory history provider | `agent_framework.InMemoryHistoryProvider` |
| 13 | `core.file_history_provider` | File history provider | `agent_framework.FileHistoryProvider` |
| 14 | `core.file_skills_source` | File-backed skills | `agent_framework.FileSkillsSource` |
| 15 | `core.in_memory_skills_source` | In-memory / programmatic skills | `agent_framework.InMemorySkillsSource` |
| 16 | `core.mcp_skills_source` | MCP-backed skills | `agent_framework.MCPSkillsSource` |
| 17 | `core.session_store` | Agent session store | `agent_framework.SessionStore` / `FileSessionStore` |
| 18 | `core.agent_hooks` | Agent Hooks middleware | `agent_framework.create_agent_hooks_middleware` |
| 1931 | _reserved_ | core growth | — |
| 32 | `orchestration.sequential` | Sequential orchestration | `agent_framework_orchestrations.SequentialBuilder` |
| 33 | `orchestration.concurrent` | Concurrent orchestration | `agent_framework_orchestrations.ConcurrentBuilder` |
| 34 | `orchestration.group_chat` | Group-chat orchestration | `agent_framework_orchestrations.GroupChatBuilder` |
| 35 | `orchestration.magentic` | Magentic orchestration | `agent_framework_orchestrations.MagenticBuilder` |
| 36 | `orchestration.handoff` | Handoff orchestration | `agent_framework_orchestrations.HandoffBuilder` |
| 3747 | _reserved_ | orchestration growth | — |
| 48 | `foundry.chat_client` | Foundry chat client | `agent_framework_foundry.RawFoundryChatClient` |
| 49 | `foundry.agent` | Foundry agent | `agent_framework_foundry.FoundryAgent` |
| 50 | `foundry.memory` | Foundry memory provider | `agent_framework_foundry.FoundryMemoryProvider` |
| 51 | `foundry.embedding` | Foundry embedding client | `agent_framework_foundry.RawFoundryEmbeddingClient` |
| 52 | `foundry.evals` | Foundry evaluations | `agent_framework_foundry.FoundryEvals` |
| 53 | `foundry.toolbox` | Foundry Toolbox MCP tool | `agent_framework_foundry_hosting.FoundryToolbox` |
| 54 | `foundry_local` | Foundry Local client | `agent_framework_foundry_local.FoundryLocalClient` |
| 55 | `foundry_hosting` | Foundry hosting layer | `agent_framework_foundry_hosting.ResponsesHostServer` / `InvocationsHostServer` |
| 56 | `openai` | OpenAI clients | `agent_framework_openai` |
| 57 | `anthropic` | Anthropic clients | `agent_framework_anthropic` |
| 58 | `bedrock` | AWS Bedrock clients | `agent_framework_bedrock` |
| 59 | `gemini` | Gemini chat client | `agent_framework_gemini` |
| 60 | `mistral` | Mistral embedding client | `agent_framework_mistral` |
| 61 | `ollama` | Ollama clients | `agent_framework_ollama` |
| 62 | `claude` | Claude Agent SDK agent | `agent_framework_claude` |
| 63 | `copilotstudio` | Copilot Studio agent | `agent_framework_copilotstudio` |
| 64 | `github_copilot` | GitHub Copilot agent | `agent_framework_github_copilot` |
| 65 | `azure_ai_search` | Azure AI Search context provider | `agent_framework_azure_ai_search` |
| 66 | `azure_cosmos` | Azure Cosmos history / checkpoint store | `agent_framework_azure_cosmos` |
| 67 | `azure_contentunderstanding` | Azure Content Understanding context provider | `agent_framework_azure_contentunderstanding.ContentUnderstandingContextProvider` |
| 68 | `redis` | Redis context / history provider | `agent_framework_redis` |
| 69 | `mem0` | Mem0 memory provider | `agent_framework_mem0.Mem0ContextProvider` |
| 70 | `purview` | Purview client | `agent_framework_purview.PurviewClient` |
| 71 | `a2a` | A2A agent / executor | `agent_framework_a2a.A2AAgent` / `A2AExecutor` |
| 72 | `ag_ui` | AG-UI chat client / agent | `agent_framework_ag_ui` |
| 73 | `chatkit` | ChatKit integration | `agent_framework_chatkit` |
| 74 | `devui` | DevUI served | `agent_framework_devui.serve` |
| 75 | `declarative.agent` | Declarative agent definitions | `agent_framework_declarative.AgentFactory` |
| 76 | `declarative.workflow` | Declarative workflow definitions | `agent_framework_declarative.WorkflowFactory` |
| 77 | `durabletask` | Durable task runtime | `agent_framework_durabletask` |
| 78 | `azurefunctions` | Azure Functions agent host | `agent_framework_azurefunctions` |
| 79 | `tools.shell` | Shell tools | `agent_framework_tools.shell.LocalShellTool` / `DockerShellTool` |
| 80 | `monty` | Monty CodeAct provider | `agent_framework_monty.MontyCodeActProvider` |
| 81 | `hyperlight` | Hyperlight CodeAct provider | `agent_framework_hyperlight.HyperlightCodeActProvider` |
| 82 | `azure_cosmos_memory` | Azure Cosmos DB semantic-memory provider | `agent_framework_azure_cosmos_memory.CosmosMemoryContextProvider` |
| 83 | `hosting` | App-owned agent/workflow hosting state | `agent_framework_hosting.AgentState` / `WorkflowState` |
| 84 | `hosting.a2a` | A2A hosting converters | `agent_framework_hosting_a2a.a2a_to_run` / `a2a_from_run` |
| 85 | `hosting.mcp` | MCP hosting adapters | `agent_framework_hosting_mcp.AgentMCPTool` / `WorkflowMCPTool` |
| 86 | `hosting.responses` | OpenAI Responses hosting converters | `agent_framework_hosting_responses.responses_to_run` |
| 87 | `hosting.telegram` | Telegram hosting converters | `agent_framework_hosting_telegram.telegram_to_run` |
| 88 | `lab` | Experimental Agent Framework Lab features | `agent_framework.lab` feature entry points |
| 89127 | _reserved_ | future packages | — |
## Index table — .NET (`agent-framework-dotnet`, version 1)
| Index | Id | Feature | Activated at (representative) |
| --- | --- | --- | --- |
| 0 | `core.agent` | Agent | `Microsoft.Agents.AI.ChatClientAgent` |
| 1 | `core.harness_agent` | Harness agent | `Microsoft.Agents.AI.HarnessAgent` |
| 2 | `core.workflow` | Workflow engine (custom graphs) | `Microsoft.Agents.AI.Workflows.WorkflowBuilder` |
| 3 | `core.tool_approval` | Tool-approval agent | `Microsoft.Agents.AI.ToolApprovalAgent` |
| 4 | `core.chat_history_memory_provider` | Chat-history memory provider | `Microsoft.Agents.AI.ChatHistoryMemoryProvider` |
| 5 | `core.file_memory_provider` | File memory provider | `Microsoft.Agents.AI.FileMemoryProvider` |
| 6 | `core.text_search_provider` | Text-search provider | `Microsoft.Agents.AI.TextSearchProvider` |
| 7 | `core.file_access_provider` | File-access provider | `Microsoft.Agents.AI.FileAccessProvider` |
| 8 | `core.skills_provider` | Skills provider | `Microsoft.Agents.AI.AgentSkillsProviderBuilder` |
| 9 | `core.compaction_provider` | Context compaction provider | `Microsoft.Agents.AI.Compaction.CompactionProvider` |
| 10 | `core.todo_provider` | Todo provider | `Microsoft.Agents.AI.TodoProvider` |
| 11 | `core.agent_mode_provider` | Agent-mode provider | `Microsoft.Agents.AI.AgentModeProvider` |
| 12 | `core.background_agents_provider` | Background-agents provider | `Microsoft.Agents.AI.BackgroundAgentsProvider` |
| 13 | `core.in_memory_history_provider` | In-memory history provider | `Microsoft.Agents.AI.InMemoryChatHistoryProvider` |
| 14 | `core.mcp` | MCP tasks / skills integration | `Microsoft.Agents.AI.Mcp.McpClientTaskExtensions` |
| 15 | `core.file_skills_source` | File-backed skills | `Microsoft.Agents.AI.AgentFileSkillsSource` |
| 16 | `core.in_memory_skills_source` | In-memory skills | `Microsoft.Agents.AI.AgentInMemorySkillsSource` |
| 17 | `core.inline_skill` | Inline programmatic skill | `Microsoft.Agents.AI.AgentInlineSkill` |
| 18 | `core.class_skill` | Class-based programmatic skill | `Microsoft.Agents.AI.AgentClassSkill` |
| 19 | `core.mcp_skills_source` | MCP-backed skills | `Microsoft.Agents.AI.AgentSkillsProviderBuilderMcpExtensions.UseMcpSkills` |
| 2031 | _reserved_ | core growth | — |
| 32 | `orchestration.sequential` | Sequential orchestration | `Microsoft.Agents.AI.Workflows.SequentialWorkflowBuilder` |
| 33 | `orchestration.concurrent` | Concurrent orchestration | `Microsoft.Agents.AI.Workflows.ConcurrentWorkflowBuilder` |
| 34 | `orchestration.group_chat` | Group-chat orchestration | `Microsoft.Agents.AI.Workflows.GroupChatWorkflowBuilder` |
| 35 | `orchestration.magentic` | Magentic orchestration | `Microsoft.Agents.AI.Workflows.MagenticWorkflowBuilder` |
| 36 | `orchestration.handoff` | Handoff orchestration | `Microsoft.Agents.AI.Workflows.HandoffWorkflowBuilder` |
| 3747 | _reserved_ | orchestration growth | — |
| 48 | `foundry.chat_client` | Foundry chat client | `Microsoft.Agents.AI.Foundry.FoundryChatClient` |
| 49 | `foundry.agent` | Foundry agent | `Microsoft.Agents.AI.Foundry.FoundryAgent` |
| 50 | `foundry.memory` | Foundry memory provider | `Microsoft.Agents.AI.Foundry.FoundryMemoryProvider` |
| 51 | `foundry.evals` | Foundry evaluations | `Microsoft.Agents.AI.Foundry.FoundryEvals` |
| 52 | `foundry.toolbox` | Foundry Toolbox MCP tool | `Microsoft.Agents.AI.Foundry.HostedMcpToolboxAITool` |
| 53 | `foundry_hosting` | Foundry hosting layer | `Microsoft.Agents.AI.Foundry.Hosting.FoundryHostingExtensions.AddFoundryResponses` |
| 54 | `openai` | OpenAI integration | `Microsoft.Agents.AI.OpenAI` |
| 55 | `anthropic` | Anthropic integration | `Microsoft.Agents.AI.Anthropic` |
| 56 | `copilotstudio` | Copilot Studio agent | `Microsoft.Agents.AI.CopilotStudio.CopilotStudioAgent` |
| 57 | `github_copilot` | GitHub Copilot agent | `Microsoft.Agents.AI.GitHub.Copilot.GitHubCopilotAgent` |
| 58 | `azure_cosmos` | Cosmos history / checkpoint store | `Microsoft.Agents.AI.CosmosChatHistoryProvider` |
| 59 | `valkey` | Valkey chat-history provider | `Microsoft.Agents.AI.Valkey.ValkeyChatHistoryProvider` |
| 60 | `mem0` | Mem0 memory provider | `Microsoft.Agents.AI.Mem0.Mem0Provider` |
| 61 | `purview` | Purview integration | `Microsoft.Agents.AI.Purview` |
| 62 | `a2a` | A2A agent | `Microsoft.Agents.AI.A2A.A2AAgent` |
| 63 | `hosting.ag_ui` | AG-UI hosting endpoint | `Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.AGUIEndpointRouteBuilderExtensions.MapAGUIServer` |
| 64 | `devui` | DevUI served | `Microsoft.Agents.AI.DevUI` |
| 65 | `declarative.agent` | Declarative agent definitions | `Microsoft.Agents.AI.PromptAgentFactory.CreateAsync` |
| 66 | `declarative.workflow` | Declarative workflow definitions | `Microsoft.Agents.AI.Workflows.Declarative.DeclarativeWorkflowBuilder.Build` |
| 67 | `durabletask` | Durable task runtime | `Microsoft.Agents.AI.DurableTask` |
| 68 | `azurefunctions` | Azure Functions agent host | `Microsoft.Agents.AI.Hosting.AzureFunctions` |
| 69 | `tools.shell` | Shell tools | `Microsoft.Agents.AI.Tools.Shell.ShellExecutor` |
| 70 | `hyperlight` | Hyperlight CodeAct provider | `Microsoft.Agents.AI.Hyperlight.HyperlightCodeActProvider` |
| 71 | `hosting.agent` | Hosted AF agent wrapper | `Microsoft.Agents.AI.Hosting.AIHostAgent` |
| 72 | `local_codeact` | Local Python CodeAct provider | `Microsoft.Agents.AI.LocalCodeAct.LocalCodeActProvider` |
| 73 | `hosting.a2a` | A2A hosting endpoints | `Microsoft.AspNetCore.Builder.A2AEndpointRouteBuilderExtensions.MapA2AJsonRpc` |
| 74 | `hosting.openai` | OpenAI-compatible hosting endpoints | `Microsoft.AspNetCore.Builder.MicrosoftAgentAIHostingOpenAIEndpointRouteBuilderExtensions.MapOpenAIResponses` |
| 75127 | _reserved_ | future packages | — |
## Opt-out
The dedicated mask-only environment variable is shared by both SDKs:
- `AGENT_FRAMEWORK_FEATURE_MASK_DISABLED=true|1` — drops **only** the feature
mask; the base `agent-framework-<lang>/{version}` User-Agent is still sent.
The dedicated flag lets a privacy-conscious user keep contributing SDK
identity/version (useful for support and compatibility triage) while withholding
the feature-usage signal. Python's existing
`AGENT_FRAMEWORK_USER_AGENT_DISABLED=true|1` also suppresses its entire Agent
Framework User-Agent contribution, mask included. Adding a matching .NET
whole-User-Agent opt-out is outside this design.
## Governance
1. One index per package/feature, **numbered independently per language**, in the
table for that language. New indexes are added by editing this file in a reviewed
PR; indexes are never reused within a `(language, version)`.
2. Each package owns a private `FeatureIndex` declaration containing only its
rows. Core owns the accumulator API and core indexes, but never imports
optional packages. Adding a new optional-package index therefore does not
require a core release once the marker API exists.
3. Adding a feature: apply the [allocation tenet](#allocation-tenet), name the
concrete query/decision owner, add the package-local index and table row, and mark the
stable public entry point where actual use begins.
4. Widening beyond 128-bit or re-partitioning bumps that language's version; old
decoders keep working because the version prefix disambiguates the mapping.
5. A repository validation test gathers all package-local declarations for each
`(language, version)` and asserts exact table parity, complete non-reserved
coverage, `0..127` range, and **no duplicate/overlapping indexes**.
> **No machine-readable registry file ships today.** Nothing consumes one at
> runtime (packages own private declarations). If/when a programmatic decoder is built, this
> table is the contract to export to JSON for it then.
+116
View File
@@ -0,0 +1,116 @@
---
name: pull-requests
description: >
Guidance for creating pull requests and handling PR review comments in the
Agent Framework repository. Use this when writing a PR description (filling out
the PR template) or when responding to and resolving review comments on an
existing PR.
---
# Pull Request Workflow
This skill covers two tasks: (1) writing a high-quality PR description, and
(2) handling review comments on an existing PR.
## 1. Writing the PR description
Always follow the repository PR template at
[`.github/pull_request_template.md`](../../../../.github/pull_request_template.md). Keep its
exact structure and headings. Fill every section:
### `### Motivation & Context`
Explain *why* the change is needed: the problem it solves and the scenario it
contributes to. Describe the net change relative to `main` — this is implied, so
do **not** spell out "vs main" explicitly.
### `### Description & Review Guide`
Describe the changes, the overall approach, and the design. Answer the three
prompts:
- **What are the major changes?**
- **What is the impact of these changes?**
- **What do you want reviewers to focus on?** — This item is for **human
reviewers only**. Automated/AI reviewers must ignore it and review the entire
change rather than narrowing scope to it.
### `### Related Issue`
Link the issue the PR fixes using a GitHub closing keyword (`Fixes #123` /
`Closes #123`) so it closes automatically on merge. A PR with no linked issue may
be closed regardless of how valid the change is. Before opening, confirm there is
no other open PR for the same issue; if there is, explain how this PR differs.
### `### Contribution Checklist`
Check every item that applies. For the breaking-change item:
- Leave **"This is not a breaking change."** checked for the common case.
- If the change **is** breaking, add the `breaking change` label **or** put
`[BREAKING]` in the title prefix, before or after a language prefix such as
`Python:` or `.NET:` — workflows keep the label and the title prefix in sync
automatically (see `.github/workflows/label-title-prefix.yml` and
`.github/workflows/label-pr.yml`).
### Do not
- Do **not** add ad-hoc sections such as "Validation" or "Tests run"; CI/CD and
the checklist already cover validation status.
- Do **not** remove or reorder the template's headings.
### Creating the PR
Open new PRs as **drafts** until they are ready for review. Example:
```bash
gh pr create --repo microsoft/agent-framework --base main \
--head <your-fork-owner>:<branch> --draft \
--title "<concise title>" --body "<body following the template>"
```
## 2. Handling review comments
When a PR receives review comments, follow this sequence — **do not start editing
code before the user has reviewed the plan**:
1. **Review the comments.** Read every review comment and thread on the PR,
including inline code comments and general review summaries.
2. **Make a plan.** Produce a concrete plan describing how each comment will be
addressed (or why it should not be, with reasoning).
3. **Let the user review the plan.** Present the plan and wait for the user's
approval or adjustments before implementing anything.
4. **Implement.** Make the agreed changes.
5. **Reply to every comment.** Add a reply to **all** comments explaining how it
was addressed (or the agreed outcome) — leave none unanswered.
6. **Resolve resolved threads.** Mark a review thread as resolved only when the
comment has actually been addressed.
### Useful commands
List review comments and threads:
```bash
# Inline review comments
gh api repos/{owner}/{repo}/pulls/{pr}/comments
# Review threads with resolution state (GraphQL)
gh api graphql -f query='
query($owner:String!,$repo:String!,$pr:Int!){
repository(owner:$owner,name:$repo){
pullRequest(number:$pr){
reviewThreads(first:100){
nodes{ id isResolved comments(first:50){ nodes{ id body author{login} } } }
}
}
}
}' -F owner={owner} -F repo={repo} -F pr={pr}
```
Reply to an inline review comment:
```bash
gh api repos/{owner}/{repo}/pulls/{pr}/comments/{comment_id}/replies \
-f body="Addressed in <commit>: <explanation>"
```
Resolve a review thread (needs the thread node id from the GraphQL query above):
```bash
gh api graphql -f query='
mutation($threadId:ID!){
resolveReviewThread(input:{threadId:$threadId}){ thread{ isResolved } }
}' -F threadId={thread_id}
```
+4 -2
View File
@@ -1,4 +1,4 @@
---
---
name: verify-samples-tool
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.
---
@@ -157,7 +157,7 @@ new SampleDefinition
new SampleDefinition
{
Name = "Agent_With_AzureOpenAIChatCompletion",
ProjectPath = "samples/02-agents/AgentProviders/Agent_With_AzureOpenAIChatCompletion",
ProjectPath = "samples/02-agents/AgentProviders/azure/Agent_With_AzureOpenAIChatCompletion",
RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
ExpectedOutputDescription =
@@ -223,3 +223,5 @@ new SampleDefinition
SkipReason = "Runs as an MCP stdio server that does not exit on its own.",
},
```
+4
View File
@@ -10,6 +10,10 @@ See `./.github/skills/build-and-test/SKILL.md` for detailed instructions on buil
See `./.github/skills/project-structure/SKILL.md` for an overview of the project structure.
## Pull Requests
See `./.github/skills/pull-requests/SKILL.md` for guidance on writing PR descriptions and handling/resolving PR review comments.
### Core types
- `AIAgent`: The abstract base class that all agents derive from, providing common methods for interacting with an agent.
+42 -43
View File
@@ -11,8 +11,8 @@
</PropertyGroup>
<ItemGroup>
<!-- Aspire.* -->
<PackageVersion Include="Anthropic" Version="12.20.0" />
<PackageVersion Include="Anthropic.Foundry" Version="0.5.0" />
<PackageVersion Include="Anthropic" Version="12.35.1" />
<PackageVersion Include="Anthropic.Foundry" Version="0.7.1" />
<PackageVersion Include="Aspire.Hosting" Version="$(AspireAppHostSdkVersion)" />
<PackageVersion Include="Aspire.Azure.AI.OpenAI" Version="13.0.0-preview.1.25560.3" />
<PackageVersion Include="Aspire.Azure.AI.Inference" Version="13.1.0-preview.1.25616.3" />
@@ -21,15 +21,16 @@
<PackageVersion Include="Aspire.Hosting.Azure.CognitiveServices" Version="$(AspireAppHostSdkVersion)" />
<PackageVersion Include="Aspire.Microsoft.Azure.Cosmos" Version="$(AspireAppHostSdkVersion)" />
<PackageVersion Include="CommunityToolkit.Aspire.OllamaSharp" Version="13.0.0" />
<PackageVersion Include="MessagePack" Version="3.1.7" /> <!-- Transitive dependency of Aspire pinned to newer version due to vulnerability in 2.5.192 -->
<!-- Azure.* -->
<PackageVersion Include="Azure.AI.AgentServer.Core" Version="1.0.0-beta.25" />
<PackageVersion Include="Azure.AI.AgentServer.Invocations" Version="1.0.0-beta.4" />
<PackageVersion Include="Azure.AI.AgentServer.Responses" Version="1.0.0-beta.5" />
<PackageVersion Include="Azure.AI.AgentServer.Core" Version="1.0.0-beta.26" />
<PackageVersion Include="Azure.AI.AgentServer.Invocations" Version="1.0.0-beta.5" />
<PackageVersion Include="Azure.AI.AgentServer.Responses" Version="1.0.0-beta.6" />
<PackageVersion Include="Azure.Search.Documents" Version="12.0.0" />
<PackageVersion Include="Azure.AI.Projects" Version="2.1.0-beta.2" />
<PackageVersion Include="Azure.AI.Projects" Version="2.1.0-beta.4" />
<PackageVersion Include="Azure.AI.Agents.Persistent" Version="1.2.0-beta.10" />
<PackageVersion Include="Azure.AI.OpenAI" Version="2.9.0-beta.1" />
<PackageVersion Include="Azure.Core" Version="1.56.0" />
<PackageVersion Include="Azure.Core" Version="1.60.0" />
<PackageVersion Include="Azure.Identity" Version="1.21.0" />
<PackageVersion Include="DotNetEnv" Version="3.1.1" />
<PackageVersion Include="Azure.Monitor.OpenTelemetry.Exporter" Version="1.5.0" />
@@ -37,23 +38,29 @@
<PackageVersion Include="Google.GenAI" Version="1.6.0" />
<PackageVersion Include="Mscc.GenerativeAI.Microsoft" Version="2.9.3" />
<!-- Microsoft.Azure.* -->
<PackageVersion Include="Microsoft.Azure.Cosmos" Version="3.54.0" />
<PackageVersion Include="Microsoft.Azure.Cosmos" Version="3.61.0" />
<!-- Newtonsoft.Json -->
<PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />
<!-- System.* -->
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.6" />
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.9" />
<PackageVersion Include="Microsoft.Bcl.HashCode" Version="6.0.0" />
<PackageVersion Include="Microsoft.Bcl.Memory" Version="10.0.5" />
<PackageVersion Include="System.ClientModel" Version="1.12.0" />
<PackageVersion Include="System.ClientModel" Version="1.14.0" />
<PackageVersion Include="System.CodeDom" Version="10.0.0" />
<PackageVersion Include="System.Collections.Immutable" Version="10.0.1" />
<PackageVersion Include="System.CommandLine" Version="2.0.0-rc.2.25502.107" />
<PackageVersion Include="System.Diagnostics.DiagnosticSource" Version="10.0.6" />
<PackageVersion Include="System.Diagnostics.DiagnosticSource" Version="10.0.9" />
<PackageVersion Include="System.Linq.AsyncEnumerable" Version="10.0.5" />
<PackageVersion Include="System.Net.Http.Json" Version="10.0.0" />
<PackageVersion Include="System.Net.ServerSentEvents" Version="10.0.5" />
<PackageVersion Include="System.Text.Json" Version="10.0.6" />
<PackageVersion Include="System.Threading.Channels" Version="10.0.6" />
<PackageVersion Include="System.Net.ServerSentEvents" Version="10.0.8" />
<!-- AG-UI .NET SDK packages (published by the AG-UI team). -->
<PackageVersion Include="AGUI.Abstractions" Version="0.0.3" />
<PackageVersion Include="AGUI.Formatting" Version="0.0.3" />
<PackageVersion Include="AGUI.Protobuf" Version="0.0.3" />
<PackageVersion Include="AGUI.Client" Version="0.0.3" />
<PackageVersion Include="AGUI.Server" Version="0.0.3" />
<PackageVersion Include="System.Text.Json" Version="10.0.9" />
<PackageVersion Include="System.Threading.Channels" Version="10.0.9" />
<PackageVersion Include="System.Threading.Tasks.Extensions" Version="4.6.3" />
<PackageVersion Include="System.Net.Security" Version="4.3.2" />
<!-- OpenTelemetry -->
@@ -69,15 +76,16 @@
<!-- Microsoft.AspNetCore.* -->
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="10.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="10.0.9" />
<PackageVersion Include="Microsoft.OpenApi" Version="2.7.5" /> <!-- Pin patched OpenAPI.NET to remediate GHSA-v5pm-xwqc-g5wc -->
<PackageVersion Include="Swashbuckle.AspNetCore.SwaggerUI" Version="10.0.0" />
<!-- Microsoft.Extensions.* -->
<PackageVersion Include="Microsoft.Extensions.AI" Version="10.5.1" />
<PackageVersion Include="Microsoft.Extensions.AI.Abstractions" Version="10.5.1" />
<PackageVersion Include="Microsoft.Extensions.AI.Evaluation" Version="10.4.0" />
<PackageVersion Include="Microsoft.Extensions.AI.Evaluation.Quality" Version="10.4.0" />
<PackageVersion Include="Microsoft.Extensions.AI.Evaluation.Safety" Version="10.3.0-preview.1.26109.11" />
<PackageVersion Include="Microsoft.Extensions.AI.OpenAI" Version="10.5.1" />
<PackageVersion Include="Microsoft.Extensions.AI" Version="10.7.0" />
<PackageVersion Include="Microsoft.Extensions.AI.Abstractions" Version="10.7.0" />
<PackageVersion Include="Microsoft.Extensions.AI.Evaluation" Version="10.7.0" />
<PackageVersion Include="Microsoft.Extensions.AI.Evaluation.Quality" Version="10.7.0" />
<PackageVersion Include="Microsoft.Extensions.AI.Evaluation.Safety" Version="10.7.0" />
<PackageVersion Include="Microsoft.Extensions.AI.OpenAI" Version="10.6.0" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Compliance.Abstractions" Version="10.5.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="10.0.1" />
@@ -86,20 +94,21 @@
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="10.0.1" />
<PackageVersion Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.1" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.1" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.6" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.9" />
<PackageVersion Include="Microsoft.Extensions.FileSystemGlobbing" Version="10.0.6" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="10.0.1" />
<PackageVersion Include="Microsoft.Extensions.Http.Resilience" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.1" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.6" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.9" />
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="10.0.1" />
<PackageVersion Include="Microsoft.Extensions.ServiceDiscovery" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.VectorData.Abstractions" Version="9.7.0" />
<PackageVersion Include="Microsoft.Extensions.VectorData.Abstractions" Version="10.7.0" />
<!-- Vector Stores -->
<PackageVersion Include="Microsoft.SemanticKernel.Connectors.InMemory" Version="1.67.0-preview" />
<PackageVersion Include="Microsoft.SemanticKernel.Connectors.Qdrant" Version="1.67.0-preview" />
<PackageVersion Include="CommunityToolkit.VectorData.CosmosNoSql" Version="1.0.0" />
<PackageVersion Include="CommunityToolkit.VectorData.InMemory" Version="1.0.0" />
<PackageVersion Include="CommunityToolkit.VectorData.Qdrant" Version="1.0.0" />
<!-- Agent SDKs -->
<PackageVersion Include="GitHub.Copilot.SDK" Version="1.0.0" />
<PackageVersion Include="GitHub.Copilot.SDK" Version="1.0.5" />
<PackageVersion Include="Microsoft.Agents.CopilotStudio.Client" Version="1.3.171-beta" />
<!-- M365 Agents SDK -->
<PackageVersion Include="AdaptiveCards" Version="3.1.0" />
@@ -114,34 +123,24 @@
<PackageVersion Include="Hyperlight.HyperlightSandbox.Api" Version="0.4.0" />
<PackageVersion Include="Hyperlight.HyperlightSandbox.Guest.Python" Version="0.4.0" />
<!-- Inference SDKs -->
<PackageVersion Include="Dapr.AI.Microsoft.Extensions" Version="1.18.4" />
<PackageVersion Include="Microsoft.ML.OnnxRuntimeGenAI" Version="0.10.0" />
<PackageVersion Include="Microsoft.ML.Tokenizers" Version="2.0.0" />
<PackageVersion Include="OllamaSharp" Version="5.4.8" />
<PackageVersion Include="OpenAI" Version="2.10.0" />
<!-- Identity -->
<PackageVersion Include="Microsoft.Identity.Client.Extensions.Msal" Version="4.83.1" />
<PackageVersion Include="Microsoft.Identity.Client.Extensions.Msal" Version="4.84.2" />
<!-- Workflows -->
<PackageVersion Include="Microsoft.Agents.ObjectModel" Version="2026.2.4.1" />
<PackageVersion Include="Microsoft.Agents.ObjectModel.Json" Version="2026.2.4.1" />
<PackageVersion Include="Microsoft.Agents.ObjectModel.PowerFx" Version="2026.2.4.1" />
<PackageVersion Include="Microsoft.PowerFx.Interpreter" Version="1.8.1" />
<!-- Durable Task -->
<PackageVersion Include="Microsoft.DurableTask.Client" Version="1.18.0" />
<PackageVersion Include="Microsoft.DurableTask.Client.AzureManaged" Version="1.18.0" />
<PackageVersion Include="Microsoft.DurableTask.Worker" Version="1.18.0" />
<PackageVersion Include="Microsoft.DurableTask.Worker.AzureManaged" Version="1.18.0" />
<!-- Azure Functions -->
<PackageVersion Include="Microsoft.Azure.Functions.Worker" Version="2.50.0" />
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask" Version="1.12.1" />
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask.AzureManaged" Version="1.0.1" />
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.3.0" />
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore" Version="2.1.0" />
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Extensions.Mcp" Version="1.0.0" />
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Sdk" Version="2.0.7" />
<!-- Redis -->
<PackageVersion Include="StackExchange.Redis" Version="2.10.1" />
<!-- Valkey -->
<PackageVersion Include="Valkey.Glide" Version="1.1.0" />
<!-- Console UX -->
<PackageVersion Include="Spectre.Console" Version="0.49.1" />
<!-- AWS -->
<PackageVersion Include="AWSSDK.Extensions.Bedrock.MEAI" Version="4.0.6.10" />
<!-- Test -->
<PackageVersion Include="FluentAssertions" Version="8.8.0" />
<PackageVersion Include="Microsoft.AspNetCore.TestHost" Condition="'$(TargetFramework)' == 'net8.0'" Version="8.0.22" />
+106 -112
View File
@@ -15,27 +15,27 @@
<Project Path="samples/01-get-started/03_multi_turn/03_multi_turn.csproj" />
<Project Path="samples/01-get-started/04_memory/04_memory.csproj" />
<Project Path="samples/01-get-started/05_first_workflow/05_first_workflow.csproj" />
<Project Path="samples/01-get-started/06_host_your_agent/06_host_your_agent.csproj" />
</Folder>
<Folder Name="/Samples/02-agents/">
<File Path="samples/02-agents/README.md" />
</Folder>
<Folder Name="/Samples/02-agents/AgentProviders/">
<File Path="samples/02-agents/AgentProviders/README.md" />
<Project Path="samples/02-agents/AgentProviders/Agent_With_A2A/Agent_With_A2A.csproj" />
<Project Path="samples/02-agents/AgentProviders/Agent_With_Anthropic/Agent_With_Anthropic.csproj" />
<Project Path="samples/02-agents/AgentProviders/Agent_With_AzureAIAgentsPersistent/Agent_With_AzureAIAgentsPersistent.csproj" />
<Project Path="samples/02-agents/AgentProviders/Agent_With_AzureAIProject/Agent_With_AzureAIProject.csproj" />
<Project Path="samples/02-agents/AgentProviders/Agent_With_AzureFoundryModel/Agent_With_AzureFoundryModel.csproj" />
<Project Path="samples/02-agents/AgentProviders/Agent_With_AzureOpenAIChatCompletion/Agent_With_AzureOpenAIChatCompletion.csproj" />
<Project Path="samples/02-agents/AgentProviders/Agent_With_AzureOpenAIResponses/Agent_With_AzureOpenAIResponses.csproj" />
<Project Path="samples/02-agents/AgentProviders/Agent_With_CustomImplementation/Agent_With_CustomImplementation.csproj" />
<Project Path="samples/02-agents/AgentProviders/Agent_With_GitHubCopilot/Agent_With_GitHubCopilot.csproj" />
<Project Path="samples/02-agents/AgentProviders/Agent_With_GoogleGemini/Agent_With_GoogleGemini.csproj" />
<Project Path="samples/02-agents/AgentProviders/Agent_With_Ollama/Agent_With_Ollama.csproj" />
<Project Path="samples/02-agents/AgentProviders/Agent_With_ONNX/Agent_With_ONNX.csproj" />
<Project Path="samples/02-agents/AgentProviders/Agent_With_OpenAIChatCompletion/Agent_With_OpenAIChatCompletion.csproj" />
<Project Path="samples/02-agents/AgentProviders/Agent_With_OpenAIResponses/Agent_With_OpenAIResponses.csproj" />
<Project Path="samples/02-agents/AgentProviders/a2a/Agent_With_A2A/Agent_With_A2A.csproj" />
<Project Path="samples/02-agents/AgentProviders/anthropic/Agent_With_Anthropic/Agent_With_Anthropic.csproj" />
<Project Path="samples/02-agents/AgentProviders/azure/Agent_With_AzureAIProject/Agent_With_AzureAIProject.csproj" />
<Project Path="samples/02-agents/AgentProviders/azure/Agent_With_AzureFoundryModel/Agent_With_AzureFoundryModel.csproj" />
<Project Path="samples/02-agents/AgentProviders/azure/Agent_With_AzureOpenAIChatCompletion/Agent_With_AzureOpenAIChatCompletion.csproj" />
<Project Path="samples/02-agents/AgentProviders/azure/Agent_With_AzureOpenAIResponses/Agent_With_AzureOpenAIResponses.csproj" />
<Project Path="samples/02-agents/AgentProviders/custom/Agent_With_CustomImplementation/Agent_With_CustomImplementation.csproj" />
<Project Path="samples/02-agents/AgentProviders/dapr/Agent_With_Dapr/Agent_With_Dapr.csproj" />
<Project Path="samples/02-agents/AgentProviders/github-copilot/Agent_With_GitHubCopilot/Agent_With_GitHubCopilot.csproj" />
<Project Path="samples/02-agents/AgentProviders/github-copilot/Agent_With_GitHubCopilot_BYOK/Agent_With_GitHubCopilot_BYOK.csproj" />
<Project Path="samples/02-agents/AgentProviders/google-gemini/Agent_With_GoogleGemini/Agent_With_GoogleGemini.csproj" />
<Project Path="samples/02-agents/AgentProviders/ollama/Agent_With_Ollama/Agent_With_Ollama.csproj" />
<Project Path="samples/02-agents/AgentProviders/onnx/Agent_With_ONNX/Agent_With_ONNX.csproj" />
<Project Path="samples/02-agents/AgentProviders/openai/Agent_With_OpenAIChatCompletion/Agent_With_OpenAIChatCompletion.csproj" />
<Project Path="samples/02-agents/AgentProviders/openai/Agent_With_OpenAIResponses/Agent_With_OpenAIResponses.csproj" />
</Folder>
<Folder Name="/Samples/05-end-to-end/DevUIAspireIntegration/">
<Project Path="samples/05-end-to-end/DevUIAspireIntegration/DevUIIntegration.AppHost/DevUIIntegration.AppHost.csproj" />
@@ -66,28 +66,12 @@
<Project Path="samples/02-agents/Agents/Agent_Step19_InFunctionLoopCheckpointing/Agent_Step19_InFunctionLoopCheckpointing.csproj" />
<Project Path="samples/02-agents/Agents/Agent_Step20_DynamicFunctionTools/Agent_Step20_DynamicFunctionTools.csproj" />
<Project Path="samples/02-agents/Agents/Agent_Step21_ShellWithEnvironment/Agent_Step21_ShellWithEnvironment.csproj" />
<Project Path="samples/02-agents/Agents/Agent_Step22_AgentMode/Agent_Step22_AgentMode.csproj" />
<Project Path="samples/02-agents/Agents/Agent_Step23_TodoList/Agent_Step23_TodoList.csproj" />
</Folder>
<Folder Name="/Samples/02-agents/DeclarativeAgents/">
<Project Path="samples/02-agents/DeclarativeAgents/ChatClient/DeclarativeChatClientAgents.csproj" />
</Folder>
<Folder Name="/Samples/04-hosting/DurableWorkflows/" />
<Folder Name="/Samples/04-hosting/DurableWorkflows/ConsoleApps/">
<Project Path="samples/04-hosting/DurableWorkflows/ConsoleApps/01_SequentialWorkflow/01_SequentialWorkflow.csproj" />
<Project Path="samples/04-hosting/DurableWorkflows/ConsoleApps/02_ConcurrentWorkflow/02_ConcurrentWorkflow.csproj" />
<Project Path="samples/04-hosting/DurableWorkflows/ConsoleApps/03_ConditionalEdges/03_ConditionalEdges.csproj" />
<Project Path="samples/04-hosting/DurableWorkflows/ConsoleApps/04_WorkflowAndAgents/04_WorkflowAndAgents.csproj" />
<Project Path="samples/04-hosting/DurableWorkflows/ConsoleApps/05_WorkflowEvents/05_WorkflowEvents.csproj" />
<Project Path="samples/04-hosting/DurableWorkflows/ConsoleApps/06_WorkflowSharedState/06_WorkflowSharedState.csproj" />
<Project Path="samples/04-hosting/DurableWorkflows/ConsoleApps/07_SubWorkflows/07_SubWorkflows.csproj" />
<Project Path="samples/04-hosting/DurableWorkflows/ConsoleApps/08_WorkflowHITL/08_WorkflowHITL.csproj" />
</Folder>
<Folder Name="/Samples/04-hosting/DurableWorkflows/AzureFunctions/">
<Project Path="samples/04-hosting/DurableWorkflows/AzureFunctions/01_SequentialWorkflow/01_SequentialWorkflow.csproj" />
<Project Path="samples/04-hosting/DurableWorkflows/AzureFunctions/02_ConcurrentWorkflow/02_ConcurrentWorkflow.csproj" />
<Project Path="samples/04-hosting/DurableWorkflows/AzureFunctions/03_WorkflowHITL/03_WorkflowHITL.csproj" />
<Project Path="samples/04-hosting/DurableWorkflows/AzureFunctions/04_WorkflowMcpTool/04_WorkflowMcpTool.csproj" />
<Project Path="samples/04-hosting/DurableWorkflows/AzureFunctions/05_WorkflowAndAgents/05_WorkflowAndAgents.csproj" />
</Folder>
<Folder Name="/Samples/GettingStarted/">
<File Path="samples/GettingStarted/README.md" />
</Folder>
@@ -118,9 +102,13 @@
<Project Path="samples/02-agents/AgentSkills/Agent_Step04_MixedSkills/Agent_Step04_MixedSkills.csproj" />
<Project Path="samples/02-agents/AgentSkills/Agent_Step05_SkillsWithDI/Agent_Step05_SkillsWithDI.csproj" />
<Project Path="samples/02-agents/AgentSkills/Agent_Step06_McpBasedSkills/Agent_Step06_McpBasedSkills.csproj" />
<Project Path="samples/02-agents/AgentSkills/Agent_Step07_SkillsAutoApproval/Agent_Step07_SkillsAutoApproval.csproj" />
</Folder>
<Folder Name="/Samples/02-agents/Harness/">
<File Path="samples/02-agents/Harness/README.md" />
<Project Path="samples/02-agents/Harness/BuildYourOwnClaw/Claw_Step01_MeetYourClaw/Claw_Step01_MeetYourClaw.csproj" />
<Project Path="samples/02-agents/Harness/BuildYourOwnClaw/Claw_Step02_WorkingWithData/Claw_Step02_WorkingWithData.csproj" />
<Project Path="samples/02-agents/Harness/BuildYourOwnClaw/Claw_Step03_ScalingCapabilities/Claw_Step03_ScalingCapabilities.csproj" />
<Project Path="samples/02-agents/Harness/ConsoleReactiveComponents/ConsoleReactiveComponents.csproj" />
<Project Path="samples/02-agents/Harness/ConsoleReactiveFramework/ConsoleReactiveFramework.csproj" />
<Project Path="samples/02-agents/Harness/Harness_Shared_Console/Harness_Shared_Console.csproj" />
@@ -129,6 +117,7 @@
<Project Path="samples/02-agents/Harness/Harness_Step02_Research_WithBackgroundAgents/Harness_Step02_Research_WithBackgroundAgents.csproj" />
<Project Path="samples/02-agents/Harness/Harness_Step03_DataProcessing/Harness_Step03_DataProcessing.csproj" />
<Project Path="samples/02-agents/Harness/Harness_Step04_CodeExecution/Harness_Step04_CodeExecution.csproj" />
<Project Path="samples/02-agents/Harness/Harness_Step05_Loop/Harness_Step05_Loop.csproj" />
</Folder>
<Folder Name="/Samples/02-agents/AGUI/Step05_StateManagement/">
<Project Path="samples/02-agents/AGUI/Step05_StateManagement/Client/Client.csproj" />
@@ -138,43 +127,43 @@
<File Path="samples/02-agents/DevUI/README.md" />
<Project Path="samples/02-agents/DevUI/DevUI_Step01_BasicUsage/DevUI_Step01_BasicUsage.csproj" />
</Folder>
<Folder Name="/Samples/02-agents/AgentWithAnthropic/">
<File Path="samples/02-agents/AgentWithAnthropic/README.md" />
<Project Path="samples/02-agents/AgentWithAnthropic/Agent_Anthropic_Step01_Running/Agent_Anthropic_Step01_Running.csproj" />
<Project Path="samples/02-agents/AgentWithAnthropic/Agent_Anthropic_Step02_Reasoning/Agent_Anthropic_Step02_Reasoning.csproj" />
<Project Path="samples/02-agents/AgentWithAnthropic/Agent_Anthropic_Step03_UsingFunctionTools/Agent_Anthropic_Step03_UsingFunctionTools.csproj" />
<Project Path="samples/02-agents/AgentWithAnthropic/Agent_Anthropic_Step04_UsingSkills/Agent_Anthropic_Step04_UsingSkills.csproj" />
<Folder Name="/Samples/02-agents/AgentProviders/anthropic/">
<File Path="samples/02-agents/AgentProviders/anthropic/README.md" />
<Project Path="samples/02-agents/AgentProviders/anthropic/Agent_Anthropic_Step01_Running/Agent_Anthropic_Step01_Running.csproj" />
<Project Path="samples/02-agents/AgentProviders/anthropic/Agent_Anthropic_Step02_Reasoning/Agent_Anthropic_Step02_Reasoning.csproj" />
<Project Path="samples/02-agents/AgentProviders/anthropic/Agent_Anthropic_Step03_UsingFunctionTools/Agent_Anthropic_Step03_UsingFunctionTools.csproj" />
<Project Path="samples/02-agents/AgentProviders/anthropic/Agent_Anthropic_Step04_UsingSkills/Agent_Anthropic_Step04_UsingSkills.csproj" />
</Folder>
<Folder Name="/Samples/02-agents/AgentsWithFoundry/">
<File Path="samples/02-agents/AgentsWithFoundry/README.md" />
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step00_FoundryAgentLifecycle/Agent_Step00_FoundryAgentLifecycle.csproj" />
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step01_Basics/Agent_Step01_Basics.csproj" />
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step02.1_MultiturnConversation/Agent_Step02.1_MultiturnConversation.csproj" />
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step02.2_MultiturnWithServerConversations/Agent_Step02.2_MultiturnWithServerConversations.csproj" />
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step03_UsingFunctionTools/Agent_Step03_UsingFunctionTools.csproj" />
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step04_UsingFunctionToolsWithApprovals/Agent_Step04_UsingFunctionToolsWithApprovals.csproj" />
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step05_StructuredOutput/Agent_Step05_StructuredOutput.csproj" />
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step06_PersistedConversations/Agent_Step06_PersistedConversations.csproj" />
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step07_Observability/Agent_Step07_Observability.csproj" />
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step08_DependencyInjection/Agent_Step08_DependencyInjection.csproj" />
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step09_UsingMcpClientAsTools/Agent_Step09_UsingMcpClientAsTools.csproj" />
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step10_UsingImages/Agent_Step10_UsingImages.csproj" />
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step11_AsFunctionTool/Agent_Step11_AsFunctionTool.csproj" />
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step12_Middleware/Agent_Step12_Middleware.csproj" />
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step13_Plugins/Agent_Step13_Plugins.csproj" />
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step14_CodeInterpreter/Agent_Step14_CodeInterpreter.csproj" />
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step15_ComputerUse/Agent_Step15_ComputerUse.csproj" />
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step16_FileSearch/Agent_Step16_FileSearch.csproj" />
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step17_OpenAPITools/Agent_Step17_OpenAPITools.csproj" />
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step18_BingCustomSearch/Agent_Step18_BingCustomSearch.csproj" />
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step19_SharePoint/Agent_Step19_SharePoint.csproj" />
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step20_MicrosoftFabric/Agent_Step20_MicrosoftFabric.csproj" />
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step21_WebSearch/Agent_Step21_WebSearch.csproj" />
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step22_MemorySearch/Agent_Step22_MemorySearch.csproj" />
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step23_LocalMCP/Agent_Step23_LocalMCP.csproj" />
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step24_CodeInterpreterFileDownload/Agent_Step24_CodeInterpreterFileDownload.csproj" />
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step25_FoundryToolboxMcp/Agent_Step25_FoundryToolboxMcp.csproj" />
<Project Path="samples/02-agents/AgentsWithFoundry/Agent_Step26_FoundryToolboxMcpSkills/Agent_Step26_FoundryToolboxMcpSkills.csproj" />
<Folder Name="/Samples/02-agents/AgentProviders/foundry/">
<File Path="samples/02-agents/AgentProviders/foundry/README.md" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step00_FoundryAgentLifecycle/Agent_Step00_FoundryAgentLifecycle.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step01_Basics/Agent_Step01_Basics.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step02.1_MultiturnConversation/Agent_Step02.1_MultiturnConversation.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step02.2_MultiturnWithServerConversations/Agent_Step02.2_MultiturnWithServerConversations.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step03_UsingFunctionTools/Agent_Step03_UsingFunctionTools.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step04_UsingFunctionToolsWithApprovals/Agent_Step04_UsingFunctionToolsWithApprovals.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step05_StructuredOutput/Agent_Step05_StructuredOutput.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step06_PersistedConversations/Agent_Step06_PersistedConversations.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step07_Observability/Agent_Step07_Observability.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step08_DependencyInjection/Agent_Step08_DependencyInjection.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step09_UsingMcpClientAsTools/Agent_Step09_UsingMcpClientAsTools.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step10_UsingImages/Agent_Step10_UsingImages.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step11_AsFunctionTool/Agent_Step11_AsFunctionTool.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step12_Middleware/Agent_Step12_Middleware.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step13_Plugins/Agent_Step13_Plugins.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step14_CodeInterpreter/Agent_Step14_CodeInterpreter.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step15_ComputerUse/Agent_Step15_ComputerUse.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step16_FileSearch/Agent_Step16_FileSearch.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step17_OpenAPITools/Agent_Step17_OpenAPITools.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step18_BingCustomSearch/Agent_Step18_BingCustomSearch.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step19_SharePoint/Agent_Step19_SharePoint.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step20_MicrosoftFabric/Agent_Step20_MicrosoftFabric.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step21_WebSearch/Agent_Step21_WebSearch.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step22_MemorySearch/Agent_Step22_MemorySearch.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step23_LocalMCP/Agent_Step23_LocalMCP.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step24_CodeInterpreterFileDownload/Agent_Step24_CodeInterpreterFileDownload.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step25_FoundryToolboxMcp/Agent_Step25_FoundryToolboxMcp.csproj" />
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step26_FoundryToolboxMcpSkills/Agent_Step26_FoundryToolboxMcpSkills.csproj" />
</Folder>
<Folder Name="/Samples/02-agents/Evaluation/">
<Project Path="samples/02-agents/Evaluation/Evaluation_CustomEvals/Evaluation_CustomEvals.csproj" />
@@ -192,17 +181,22 @@
<File Path="samples/02-agents/AgentWithMemory/README.md" />
<Project Path="samples/02-agents/AgentWithMemory/AgentWithMemory_Step01_ChatHistoryMemory/AgentWithMemory_Step01_ChatHistoryMemory.csproj" />
<Project Path="samples/02-agents/AgentWithMemory/AgentWithMemory_Step02_MemoryUsingMem0/AgentWithMemory_Step02_MemoryUsingMem0.csproj" />
<Project Path="samples/02-agents/AgentWithMemory/AgentWithMemory_Step03_MemoryUsingValkey/AgentWithMemory_Step03_MemoryUsingValkey.csproj" />
<Project Path="samples/02-agents/AgentWithMemory/AgentWithMemory_Step03_MemoryUsingValkey_Bedrock/AgentWithMemory_Step03_MemoryUsingValkey_Bedrock.csproj" />
<Project Path="samples/02-agents/AgentWithMemory/AgentWithMemory_Step04_MemoryUsingFoundry/AgentWithMemory_Step04_MemoryUsingFoundry.csproj" />
<Project Path="samples/02-agents/AgentWithMemory/AgentWithMemory_Step05_BoundedChatHistory/AgentWithMemory_Step05_BoundedChatHistory.csproj" />
<Project Path="samples/02-agents/AgentWithMemory/AgentWithMemory_Step06_MemoryUsingAgentMemory/AgentWithMemory_Step06_MemoryUsingAgentMemory.csproj" />
<Project Path="samples/02-agents/AgentWithMemory/AgentWithMemory_Step07_FileMemoryProvider/AgentWithMemory_Step07_FileMemoryProvider.csproj" />
<Project Path="samples/02-agents/AgentWithMemory/AgentWithMemory_Step08_MemoryUsingCosmosNoSql/AgentWithMemory_Step08_MemoryUsingCosmosNoSql.csproj" />
</Folder>
<Folder Name="/Samples/02-agents/AgentWithOpenAI/">
<File Path="samples/02-agents/AgentWithOpenAI/README.md" />
<Project Path="samples/02-agents/AgentWithOpenAI/Agent_OpenAI_Step01_Running/Agent_OpenAI_Step01_Running.csproj" />
<Project Path="samples/02-agents/AgentWithOpenAI/Agent_OpenAI_Step02_Reasoning/Agent_OpenAI_Step02_Reasoning.csproj" />
<Project Path="samples/02-agents/AgentWithOpenAI/Agent_OpenAI_Step03_CreateFromChatClient/Agent_OpenAI_Step03_CreateFromChatClient.csproj" />
<Project Path="samples/02-agents/AgentWithOpenAI/Agent_OpenAI_Step04_CreateFromOpenAIResponseClient/Agent_OpenAI_Step04_CreateFromOpenAIResponseClient.csproj" />
<Project Path="samples/02-agents/AgentWithOpenAI/Agent_OpenAI_Step05_Conversation/Agent_OpenAI_Step05_Conversation.csproj" />
<Project Path="samples/02-agents/AgentWithOpenAI/Agent_OpenAI_Step06_CodeInterpreterFileDownload/Agent_OpenAI_Step06_CodeInterpreterFileDownload.csproj" />
<Folder Name="/Samples/02-agents/AgentProviders/openai/">
<File Path="samples/02-agents/AgentProviders/openai/README.md" />
<Project Path="samples/02-agents/AgentProviders/openai/Agent_OpenAI_Step01_Running/Agent_OpenAI_Step01_Running.csproj" />
<Project Path="samples/02-agents/AgentProviders/openai/Agent_OpenAI_Step02_Reasoning/Agent_OpenAI_Step02_Reasoning.csproj" />
<Project Path="samples/02-agents/AgentProviders/openai/Agent_OpenAI_Step03_CreateFromChatClient/Agent_OpenAI_Step03_CreateFromChatClient.csproj" />
<Project Path="samples/02-agents/AgentProviders/openai/Agent_OpenAI_Step04_CreateFromOpenAIResponseClient/Agent_OpenAI_Step04_CreateFromOpenAIResponseClient.csproj" />
<Project Path="samples/02-agents/AgentProviders/openai/Agent_OpenAI_Step05_Conversation/Agent_OpenAI_Step05_Conversation.csproj" />
<Project Path="samples/02-agents/AgentProviders/openai/Agent_OpenAI_Step06_CodeInterpreterFileDownload/Agent_OpenAI_Step06_CodeInterpreterFileDownload.csproj" />
</Folder>
<Folder Name="/Samples/02-agents/AgentWithRAG/">
<File Path="samples/02-agents/AgentWithRAG/README.md" />
@@ -215,6 +209,7 @@
<Folder Name="/Samples/02-agents/ModelContextProtocol/">
<File Path="samples/02-agents/ModelContextProtocol/README.md" />
<Project Path="samples/02-agents/ModelContextProtocol/Agent_MCP_LongRunningTask_Client/Agent_MCP_LongRunningTask_Client.csproj" />
<Project Path="samples/02-agents/ModelContextProtocol/Agent_MCP_PerRun_AuthHeaders/Agent_MCP_PerRun_AuthHeaders.csproj" />
<Project Path="samples/02-agents/ModelContextProtocol/Agent_MCP_Server/Agent_MCP_Server.csproj" />
<Project Path="samples/02-agents/ModelContextProtocol/Agent_MCP_Server_Auth/Agent_MCP_Server_Auth.csproj" />
<Project Path="samples/02-agents/ModelContextProtocol/FoundryAgent_Hosted_MCP/FoundryAgent_Hosted_MCP.csproj" />
@@ -237,10 +232,10 @@
</Folder>
<Folder Name="/Samples/03-workflows/Declarative/">
<File Path="samples/03-workflows/Declarative/README.md" />
<Project Path="samples/03-workflows/Declarative/AotCheckpointing/AotCheckpointing.csproj" />
<Project Path="samples/03-workflows/Declarative/ConfirmInput/ConfirmInput.csproj" />
<Project Path="samples/03-workflows/Declarative/CustomerSupport/CustomerSupport.csproj" />
<Project Path="samples/03-workflows/Declarative/DeepResearch/DeepResearch.csproj" />
<Project Path="samples/03-workflows/Declarative/ExecuteCode/ExecuteCode.csproj" />
<Project Path="samples/03-workflows/Declarative/ExecuteWorkflow/ExecuteWorkflow.csproj" />
<Project Path="samples/03-workflows/Declarative/FunctionTools/FunctionTools.csproj" />
<Project Path="samples/03-workflows/Declarative/HostedWorkflow/HostedWorkflow.csproj" />
@@ -306,7 +301,19 @@
<Project Path="samples/03-workflows/Evaluation/Evaluation_WorkflowEval/Evaluation_WorkflowEval.csproj" />
<Project Path="samples/03-workflows/Evaluation/Evaluation_WorkflowExpectedOutputs/Evaluation_WorkflowExpectedOutputs.csproj" />
</Folder>
<Folder Name="/Samples/04-hosting/">
<Folder Name="/Samples/04-hosting/" />
<Folder Name="/Samples/04-hosting/af-hosting/">
<File Path="samples/04-hosting/af-hosting/README.md" />
</Folder>
<Folder Name="/Samples/04-hosting/af-hosting/local_responses/">
<File Path="samples/04-hosting/af-hosting/local_responses/README.md" />
<Project Path="samples/04-hosting/af-hosting/local_responses/Server/Server.csproj" />
<Project Path="samples/04-hosting/af-hosting/local_responses/Client/Client.csproj" />
</Folder>
<Folder Name="/Samples/04-hosting/af-hosting/local_responses_workflow/">
<File Path="samples/04-hosting/af-hosting/local_responses_workflow/README.md" />
<Project Path="samples/04-hosting/af-hosting/local_responses_workflow/Server/Server.csproj" />
<Project Path="samples/04-hosting/af-hosting/local_responses_workflow/Client/Client.csproj" />
</Folder>
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/" />
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/invocations/" />
@@ -320,6 +327,9 @@
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/responses/Hosted-ChatClientAgent/">
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Hosted-ChatClientAgent/HostedChatClientAgent.csproj" />
</Folder>
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/responses/Hosted-ChatClientAgent-Dockerfile/">
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Hosted-ChatClientAgent-Dockerfile/HostedChatClientAgentDocker.csproj" />
</Folder>
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/responses/Hosted-FoundryAgent/">
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Hosted-FoundryAgent/HostedFoundryAgent.csproj" />
</Folder>
@@ -329,6 +339,9 @@
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/responses/Hosted-LocalTools/">
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Hosted-LocalTools/HostedLocalTools.csproj" />
</Folder>
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/responses/Hosted-LocalCodeAct/">
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Hosted-LocalCodeAct/HostedLocalCodeAct.csproj" />
</Folder>
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/responses/Hosted-McpTools/">
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Hosted-McpTools/HostedMcpTools.csproj" />
</Folder>
@@ -344,6 +357,9 @@
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/responses/Hosted-Toolbox/">
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Hosted-Toolbox/HostedToolbox.csproj" />
</Folder>
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/responses/Hosted-Toolbox-AuthPaths/">
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Hosted-Toolbox-AuthPaths/Hosted-Toolbox-AuthPaths.csproj" />
</Folder>
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/responses/Hosted-ToolboxMcpSkills/">
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Hosted-ToolboxMcpSkills/HostedToolboxMcpSkills.csproj" />
</Folder>
@@ -357,6 +373,7 @@
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Hosted-Workflow-Simple/HostedWorkflowSimple.csproj" />
</Folder>
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/">
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/Hosted-Toolbox-AuthPaths-Client/Hosted-Toolbox-AuthPaths-Client.csproj" />
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/SessionFilesClient/SessionFilesClient.csproj" />
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/SimpleAgent/SimpleAgent.csproj" />
</Folder>
@@ -366,29 +383,6 @@
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/responses/Hosted-AgentSkills/">
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Hosted-AgentSkills/HostedAgentSkills.csproj" />
</Folder>
<Folder Name="/Samples/04-hosting/DurableAgents/" />
<Folder Name="/Samples/04-hosting/DurableAgents/AzureFunctions/">
<File Path="samples/04-hosting/DurableAgents/AzureFunctions/.editorconfig" />
<File Path="samples/04-hosting/DurableAgents/AzureFunctions/README.md" />
<Project Path="samples/04-hosting/DurableAgents/AzureFunctions/01_SingleAgent/01_SingleAgent.csproj" />
<Project Path="samples/04-hosting/DurableAgents/AzureFunctions/02_AgentOrchestration_Chaining/02_AgentOrchestration_Chaining.csproj" />
<Project Path="samples/04-hosting/DurableAgents/AzureFunctions/03_AgentOrchestration_Concurrency/03_AgentOrchestration_Concurrency.csproj" />
<Project Path="samples/04-hosting/DurableAgents/AzureFunctions/04_AgentOrchestration_Conditionals/04_AgentOrchestration_Conditionals.csproj" />
<Project Path="samples/04-hosting/DurableAgents/AzureFunctions/05_AgentOrchestration_HITL/05_AgentOrchestration_HITL.csproj" />
<Project Path="samples/04-hosting/DurableAgents/AzureFunctions/06_LongRunningTools/06_LongRunningTools.csproj" />
<Project Path="samples/04-hosting/DurableAgents/AzureFunctions/07_AgentAsMcpTool/07_AgentAsMcpTool.csproj" />
<Project Path="samples/04-hosting/DurableAgents/AzureFunctions/08_ReliableStreaming/08_ReliableStreaming.csproj" />
</Folder>
<Folder Name="/Samples/04-hosting/DurableAgents/ConsoleApps/">
<File Path="samples/04-hosting/DurableAgents/ConsoleApps/README.md" />
<Project Path="samples/04-hosting/DurableAgents/ConsoleApps/01_SingleAgent/01_SingleAgent.csproj" />
<Project Path="samples/04-hosting/DurableAgents/ConsoleApps/02_AgentOrchestration_Chaining/02_AgentOrchestration_Chaining.csproj" />
<Project Path="samples/04-hosting/DurableAgents/ConsoleApps/03_AgentOrchestration_Concurrency/03_AgentOrchestration_Concurrency.csproj" />
<Project Path="samples/04-hosting/DurableAgents/ConsoleApps/04_AgentOrchestration_Conditionals/04_AgentOrchestration_Conditionals.csproj" />
<Project Path="samples/04-hosting/DurableAgents/ConsoleApps/05_AgentOrchestration_HITL/05_AgentOrchestration_HITL.csproj" />
<Project Path="samples/04-hosting/DurableAgents/ConsoleApps/06_LongRunningTools/06_LongRunningTools.csproj" />
<Project Path="samples/04-hosting/DurableAgents/ConsoleApps/07_ReliableStreaming/07_ReliableStreaming.csproj" />
</Folder>
<Folder Name="/Samples/02-agents/A2A/">
<File Path="samples/02-agents/A2A/README.md" />
<Project Path="samples/02-agents/A2A/A2AAgent_AsFunctionTools/A2AAgent_AsFunctionTools.csproj" />
@@ -403,6 +397,7 @@
<Folder Name="/Samples/05-end-to-end/Evaluation/">
<Project Path="samples/05-end-to-end/Evaluation/Evaluation_ConversationSplits/Evaluation_ConversationSplits.csproj" />
<Project Path="samples/05-end-to-end/Evaluation/Evaluation_FoundryQuality/Evaluation_FoundryQuality.csproj" />
<Project Path="samples/05-end-to-end/Evaluation/Evaluation_FoundryRubric/Evaluation_FoundryRubric.csproj" />
<Project Path="samples/05-end-to-end/Evaluation/Evaluation_MixedProviders/Evaluation_MixedProviders.csproj" />
</Folder>
<Folder Name="/Samples/05-end-to-end/A2AClientServer/">
@@ -591,14 +586,12 @@
<Project Path="src/Aspire.Hosting.AgentFramework.DevUI/Aspire.Hosting.AgentFramework.DevUI.csproj" />
<Project Path="src/Microsoft.Agents.AI.A2A/Microsoft.Agents.AI.A2A.csproj" />
<Project Path="src/Microsoft.Agents.AI.Abstractions/Microsoft.Agents.AI.Abstractions.csproj" />
<Project Path="src/Microsoft.Agents.AI.AGUI/Microsoft.Agents.AI.AGUI.csproj" />
<Project Path="src/Microsoft.Agents.AI.Anthropic/Microsoft.Agents.AI.Anthropic.csproj" />
<Project Path="src/Microsoft.Agents.AI.AzureAI.Persistent/Microsoft.Agents.AI.AzureAI.Persistent.csproj" />
<Project Path="src/Microsoft.Agents.AI.CopilotStudio/Microsoft.Agents.AI.CopilotStudio.csproj" />
<Project Path="src/Microsoft.Agents.AI.CosmosNoSql/Microsoft.Agents.AI.CosmosNoSql.csproj" />
<Project Path="src/Microsoft.Agents.AI.Declarative/Microsoft.Agents.AI.Declarative.csproj" />
<Project Path="src/Microsoft.Agents.AI.DevUI/Microsoft.Agents.AI.DevUI.csproj" />
<Project Path="src/Microsoft.Agents.AI.DurableTask/Microsoft.Agents.AI.DurableTask.csproj" />
<Project Path="src/Microsoft.Agents.AI.Foundry.Hosting/Microsoft.Agents.AI.Foundry.Hosting.csproj" />
<Project Path="src/Microsoft.Agents.AI.Foundry/Microsoft.Agents.AI.Foundry.csproj" />
<Project Path="src/Microsoft.Agents.AI.GitHub.Copilot/Microsoft.Agents.AI.GitHub.Copilot.csproj" />
@@ -606,16 +599,17 @@
<Project Path="src/Microsoft.Agents.AI.Hosting.A2A.AspNetCore/Microsoft.Agents.AI.Hosting.A2A.AspNetCore.csproj" />
<Project Path="src/Microsoft.Agents.AI.Hosting.A2A/Microsoft.Agents.AI.Hosting.A2A.csproj" />
<Project Path="src/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.csproj" />
<Project Path="src/Microsoft.Agents.AI.Hosting.AzureFunctions/Microsoft.Agents.AI.Hosting.AzureFunctions.csproj" />
<Project Path="src/Microsoft.Agents.AI.Hosting.OpenAI/Microsoft.Agents.AI.Hosting.OpenAI.csproj" />
<Project Path="src/Microsoft.Agents.AI.Hosting.AspNetCore/Microsoft.Agents.AI.Hosting.AspNetCore.csproj" />
<Project Path="src/Microsoft.Agents.AI.Hosting.OpenAI/Microsoft.Agents.AI.Hosting.OpenAI.csproj" />
<Project Path="src/Microsoft.Agents.AI.Hosting/Microsoft.Agents.AI.Hosting.csproj" />
<Project Path="src/Microsoft.Agents.AI.Hyperlight/Microsoft.Agents.AI.Hyperlight.csproj" />
<Project Path="src/Microsoft.Agents.AI.LocalCodeAct/Microsoft.Agents.AI.LocalCodeAct.csproj" />
<Project Path="src/Microsoft.Agents.AI.Mcp/Microsoft.Agents.AI.Mcp.csproj" />
<Project Path="src/Microsoft.Agents.AI.Mem0/Microsoft.Agents.AI.Mem0.csproj" />
<Project Path="src/Microsoft.Agents.AI.OpenAI/Microsoft.Agents.AI.OpenAI.csproj" />
<Project Path="src/Microsoft.Agents.AI.Purview/Microsoft.Agents.AI.Purview.csproj" />
<Project Path="src/Microsoft.Agents.AI.Tools.Shell/Microsoft.Agents.AI.Tools.Shell.csproj" />
<Project Path="src/Microsoft.Agents.AI.Valkey/Microsoft.Agents.AI.Valkey.csproj" />
<Project Path="src/Microsoft.Agents.AI.Workflows.Declarative.Foundry/Microsoft.Agents.AI.Workflows.Declarative.Foundry.csproj" />
<Project Path="src/Microsoft.Agents.AI.Workflows.Declarative.Mcp/Microsoft.Agents.AI.Workflows.Declarative.Mcp.csproj" />
<Project Path="src/Microsoft.Agents.AI.Workflows.Declarative/Microsoft.Agents.AI.Workflows.Declarative.csproj" />
@@ -623,7 +617,9 @@
<Project Path="src/Microsoft.Agents.AI.Workflows/Microsoft.Agents.AI.Workflows.csproj" />
<Project Path="src/Microsoft.Agents.AI/Microsoft.Agents.AI.csproj" />
</Folder>
<Folder Name="/Tests/" />
<Folder Name="/Tests/">
<Project Path="tests/Microsoft.Agents.AI.Hosting.OpenAI.IntegrationTests/Microsoft.Agents.AI.Hosting.OpenAI.IntegrationTests.csproj" />
</Folder>
<Folder Name="/Tests/IntegrationTests/">
<Project Path="tests/AgentConformance.IntegrationTests/AgentConformance.IntegrationTests.csproj" />
<Project Path="tests/AnthropicChatCompletion.IntegrationTests/AnthropicChatCompletion.IntegrationTests.csproj" />
@@ -632,10 +628,8 @@
<Project Path="tests/Foundry.Hosting.IntegrationTests.TestContainer/Foundry.Hosting.IntegrationTests.TestContainer.csproj" />
<Project Path="tests/Foundry.Hosting.IntegrationTests/Foundry.Hosting.IntegrationTests.csproj" />
<Project Path="tests/Foundry.IntegrationTests/Foundry.IntegrationTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.DurableTask.IntegrationTests/Microsoft.Agents.AI.DurableTask.IntegrationTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.GitHub.Copilot.IntegrationTests/Microsoft.Agents.AI.GitHub.Copilot.IntegrationTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Hosting.AzureFunctions.IntegrationTests/Microsoft.Agents.AI.Hosting.AzureFunctions.IntegrationTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Hyperlight.IntegrationTests/Microsoft.Agents.AI.Hyperlight.IntegrationTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Mem0.IntegrationTests/Microsoft.Agents.AI.Mem0.IntegrationTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Tools.Shell.IntegrationTests/Microsoft.Agents.AI.Tools.Shell.IntegrationTests.csproj" />
@@ -648,32 +642,32 @@
<Project Path="tests/Aspire.Hosting.AgentFramework.DevUI.UnitTests/Aspire.Hosting.AgentFramework.DevUI.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.A2A.UnitTests/Microsoft.Agents.AI.A2A.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Abstractions.UnitTests/Microsoft.Agents.AI.Abstractions.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.AGUI.UnitTests/Microsoft.Agents.AI.AGUI.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Anthropic.UnitTests/Microsoft.Agents.AI.Anthropic.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.AzureAI.Persistent.UnitTests/Microsoft.Agents.AI.AzureAI.Persistent.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.CosmosNoSql.UnitTests/Microsoft.Agents.AI.CosmosNoSql.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Declarative.UnitTests/Microsoft.Agents.AI.Declarative.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.DevUI.UnitTests/Microsoft.Agents.AI.DevUI.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.DurableTask.UnitTests/Microsoft.Agents.AI.DurableTask.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Foundry.UnitTests/Microsoft.Agents.AI.Foundry.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.GitHub.Copilot.UnitTests/Microsoft.Agents.AI.GitHub.Copilot.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Harness.UnitTests/Microsoft.Agents.AI.Harness.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/Microsoft.Agents.AI.Hosting.A2A.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Hosting.AzureFunctions.UnitTests/Microsoft.Agents.AI.Hosting.AzureFunctions.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Hosting.UnitTests/Microsoft.Agents.AI.Hosting.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Hyperlight.UnitTests/Microsoft.Agents.AI.Hyperlight.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.LocalCodeAct.UnitTests/Microsoft.Agents.AI.LocalCodeAct.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Mcp.UnitTests/Microsoft.Agents.AI.Mcp.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Mem0.UnitTests/Microsoft.Agents.AI.Mem0.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.OpenAI.UnitTests/Microsoft.Agents.AI.OpenAI.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Purview.UnitTests/Microsoft.Agents.AI.Purview.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Tools.Shell.UnitTests/Microsoft.Agents.AI.Tools.Shell.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.UnitTests/Microsoft.Agents.AI.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Valkey.UnitTests/Microsoft.Agents.AI.Valkey.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Workflows.Declarative.Mcp.UnitTests/Microsoft.Agents.AI.Workflows.Declarative.Mcp.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Workflows.Generators.UnitTests/Microsoft.Agents.AI.Workflows.Generators.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Workflows.UnitTests/Microsoft.Agents.AI.Workflows.UnitTests.csproj" />
</Folder>
</Solution>
+2 -3
View File
@@ -4,7 +4,6 @@
"projects": [
"src\\Microsoft.Agents.AI.A2A\\Microsoft.Agents.AI.A2A.csproj",
"src\\Microsoft.Agents.AI.Abstractions\\Microsoft.Agents.AI.Abstractions.csproj",
"src\\Microsoft.Agents.AI.AGUI\\Microsoft.Agents.AI.AGUI.csproj",
"src\\Microsoft.Agents.AI.Anthropic\\Microsoft.Agents.AI.Anthropic.csproj",
"src\\Microsoft.Agents.AI.GitHub.Copilot\\Microsoft.Agents.AI.GitHub.Copilot.csproj",
"src\\Microsoft.Agents.AI.Harness\\Microsoft.Agents.AI.Harness.csproj",
@@ -15,20 +14,20 @@
"src\\Microsoft.Agents.AI.CosmosNoSql\\Microsoft.Agents.AI.CosmosNoSql.csproj",
"src\\Microsoft.Agents.AI.Declarative\\Microsoft.Agents.AI.Declarative.csproj",
"src\\Microsoft.Agents.AI.DevUI\\Microsoft.Agents.AI.DevUI.csproj",
"src\\Microsoft.Agents.AI.DurableTask\\Microsoft.Agents.AI.DurableTask.csproj",
"src\\Microsoft.Agents.AI.Hosting.A2A.AspNetCore\\Microsoft.Agents.AI.Hosting.A2A.AspNetCore.csproj",
"src\\Microsoft.Agents.AI.Hosting.A2A\\Microsoft.Agents.AI.Hosting.A2A.csproj",
"src\\Microsoft.Agents.AI.Hosting.AGUI.AspNetCore\\Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.csproj",
"src\\Microsoft.Agents.AI.Hosting.AspNetCore\\Microsoft.Agents.AI.Hosting.AspNetCore.csproj",
"src\\Microsoft.Agents.AI.Hosting.AzureFunctions\\Microsoft.Agents.AI.Hosting.AzureFunctions.csproj",
"src\\Microsoft.Agents.AI.Hosting.OpenAI\\Microsoft.Agents.AI.Hosting.OpenAI.csproj",
"src\\Microsoft.Agents.AI.Hosting\\Microsoft.Agents.AI.Hosting.csproj",
"src\\Microsoft.Agents.AI.LocalCodeAct\\Microsoft.Agents.AI.LocalCodeAct.csproj",
"src\\Microsoft.Agents.AI.Mcp\\Microsoft.Agents.AI.Mcp.csproj",
"src\\Microsoft.Agents.AI.Mem0\\Microsoft.Agents.AI.Mem0.csproj",
"src\\Microsoft.Agents.AI.OpenAI\\Microsoft.Agents.AI.OpenAI.csproj",
"src\\Microsoft.Agents.AI.Purview\\Microsoft.Agents.AI.Purview.csproj",
"src\\Microsoft.Agents.AI.Tools.Shell\\Microsoft.Agents.AI.Tools.Shell.csproj",
"src\\Microsoft.Agents.AI.Valkey\\Microsoft.Agents.AI.Valkey.csproj",
"src\\Microsoft.Agents.AI.Workflows.Declarative.Foundry\\Microsoft.Agents.AI.Workflows.Declarative.Foundry.csproj",
"src\\Microsoft.Agents.AI.Workflows.Declarative.Mcp\\Microsoft.Agents.AI.Workflows.Declarative.Mcp.csproj",
"src\\Microsoft.Agents.AI.Workflows.Declarative\\Microsoft.Agents.AI.Workflows.Declarative.csproj",
+3
View File
@@ -26,6 +26,9 @@
<ItemGroup Condition="'$(InjectSharedDiagnosticIds)' == 'true'">
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\Shared\DiagnosticIds\*.cs" LinkBase="Shared\DiagnosticIds" />
</ItemGroup>
<ItemGroup Condition="'$(InjectSharedUsage)' == 'true'">
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\Shared\Usage\*.cs" LinkBase="Shared\Usage" />
</ItemGroup>
<ItemGroup Condition="'$(InjectSharedRedaction)' == 'true'">
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\Shared\Redaction\*.cs" LinkBase="Shared\Redaction" />
</ItemGroup>
+3 -3
View File
@@ -26,7 +26,7 @@
When specified, only test projects whose filename matches this pattern are kept.
.PARAMETER TestProjectNameExcludeFilter
Optional wildcard pattern(s) to exclude test projects by name (e.g., *DurableTask.IntegrationTests*).
Optional wildcard pattern(s) to exclude test projects by name (e.g., *Slow.IntegrationTests*).
When specified, test projects whose filename matches any of these patterns are removed.
Applied after TestProjectNameIncludeFilter. Can be a single string or an array of strings.
@@ -50,8 +50,8 @@
dotnet test --solution (./dotnet/eng/scripts/New-FilteredSolution.ps1 -Solution dotnet/agent-framework-dotnet.slnx -TargetFramework net472) --no-build -f net472
.EXAMPLE
# Generate integration tests excluding DurableTask and AzureFunctions
./dotnet/eng/scripts/New-FilteredSolution.ps1 -Solution dotnet/agent-framework-dotnet.slnx -TargetFramework net10.0 -TestProjectNameIncludeFilter "*IntegrationTests*" -TestProjectNameExcludeFilter "*DurableTask.IntegrationTests*","*AzureFunctions.IntegrationTests*" -OutputPath filtered-other-integration.slnx
# Generate integration tests while excluding a long-running test project
./dotnet/eng/scripts/New-FilteredSolution.ps1 -Solution dotnet/agent-framework-dotnet.slnx -TargetFramework net10.0 -TestProjectNameIncludeFilter "*IntegrationTests*" -TestProjectNameExcludeFilter "*Slow.IntegrationTests*" -OutputPath filtered-integration.slnx
#>
[CmdletBinding()]
+194 -69
View File
@@ -14,7 +14,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "Agent_With_CustomImplementation",
ProjectPath = "samples/02-agents/AgentProviders/Agent_With_CustomImplementation",
ProjectPath = "samples/02-agents/AgentProviders/custom/Agent_With_CustomImplementation",
RequiredEnvironmentVariables = [],
ExpectedOutputDescription =
[
@@ -27,7 +27,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "Agent_With_AzureOpenAIChatCompletion",
ProjectPath = "samples/02-agents/AgentProviders/Agent_With_AzureOpenAIChatCompletion",
ProjectPath = "samples/02-agents/AgentProviders/azure/Agent_With_AzureOpenAIChatCompletion",
RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
ExpectedOutputDescription =
@@ -40,7 +40,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "Agent_With_AzureOpenAIResponses",
ProjectPath = "samples/02-agents/AgentProviders/Agent_With_AzureOpenAIResponses",
ProjectPath = "samples/02-agents/AgentProviders/azure/Agent_With_AzureOpenAIResponses",
RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
ExpectedOutputDescription =
@@ -50,23 +50,10 @@ internal static class AgentsSamples
],
},
new SampleDefinition
{
Name = "Agent_With_AzureAIAgentsPersistent",
ProjectPath = "samples/02-agents/AgentProviders/Agent_With_AzureAIAgentsPersistent",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
ExpectedOutputDescription =
[
"The output should contain a joke about a pirate.",
"The output should not contain error messages or stack traces.",
],
},
new SampleDefinition
{
Name = "Agent_With_AzureAIProject",
ProjectPath = "samples/02-agents/AgentProviders/Agent_With_AzureAIProject",
ProjectPath = "samples/02-agents/AgentProviders/azure/Agent_With_AzureAIProject",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
MustContain = ["Latest agent version id:"],
@@ -80,7 +67,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "Agent_With_AzureFoundryModel",
ProjectPath = "samples/02-agents/AgentProviders/Agent_With_AzureFoundryModel",
ProjectPath = "samples/02-agents/AgentProviders/azure/Agent_With_AzureFoundryModel",
RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_OPENAI_API_KEY", "AZURE_AI_MODEL_DEPLOYMENT_NAME"],
ExpectedOutputDescription =
@@ -342,14 +329,88 @@ internal static class AgentsSamples
],
},
new SampleDefinition
{
Name = "Agent_Step20_DynamicFunctionTools",
ProjectPath = "samples/02-agents/Agents/Agent_Step20_DynamicFunctionTools",
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
MustContain =
[
"=== Dynamic Function Tools Sample ===",
"=== Non-Streaming Mode ===",
"=== Streaming Mode ===",
"[User]",
"[Agent]",
],
ExpectedOutputDescription =
[
"The output should show the agent starting with only a RequestTools function and dynamically loading additional tools (weather, time, temperature) as needed.",
"The output should contain weather information for Seattle and London, the current time in New York, and a Fahrenheit-to-Celsius temperature conversion.",
"The output should demonstrate both non-streaming and streaming modes.",
"The output should not contain error messages or stack traces.",
],
},
new SampleDefinition
{
Name = "Agent_Step21_ShellWithEnvironment",
ProjectPath = "samples/02-agents/Agents/Agent_Step21_ShellWithEnvironment",
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
MustContain =
[
"### Stateless mode",
"### Persistent mode",
"--- Captured environment snapshot ---",
],
ExpectedOutputDescription =
[
"The output should show an agent using a shell tool to print the current working directory.",
"The output should demonstrate that in stateless mode side effects (such as changing directory) do not carry between calls, while in persistent mode the working directory and an environment variable (DEMO_TOKEN set to 'hello-world') carry across calls.",
"The output should include a captured environment snapshot describing the OS, shell, and working directory.",
"The output should not contain error messages or stack traces.",
],
},
new SampleDefinition
{
Name = "Agent_Step22_AgentMode",
ProjectPath = "samples/02-agents/Agents/Agent_Step22_AgentMode",
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
SkipReason = "Interactive sample that reads console input in a loop and does not exit on its own.",
},
new SampleDefinition
{
Name = "Agent_Step23_TodoList",
ProjectPath = "samples/02-agents/Agents/Agent_Step23_TodoList",
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
MustContain =
[
"User:",
"Agent:",
"--- Current todo list ---",
],
ExpectedOutputDescription =
[
"The output should show an agent planning a team offsite by breaking the work into a todo list.",
"The output should show the todo list being updated as progress is reported (for example marking items complete after the venue is booked and invites are sent) and adjusted when the plan changes to skip catering and add a group hike.",
"The current todo list should be printed after each turn, showing item status.",
"The output should not contain error messages or stack traces.",
],
},
// ── AgentSkills ─────────────────────────────────────────────────────
new SampleDefinition
{
Name = "Agent_Step01_FileBasedSkills",
ProjectPath = "samples/02-agents/AgentSkills/Agent_Step01_FileBasedSkills",
RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
MustContain =
[
"Converting units with file-based skills",
@@ -367,8 +428,8 @@ internal static class AgentsSamples
{
Name = "Agent_Step06_McpBasedSkills",
ProjectPath = "samples/02-agents/AgentSkills/Agent_Step06_McpBasedSkills",
RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
MustContain =
[
"Discovering MCP-based skills",
@@ -440,6 +501,57 @@ internal static class AgentsSamples
],
},
new SampleDefinition
{
Name = "AgentWithMemory_Step06_MemoryUsingAgentMemory",
ProjectPath = "samples/02-agents/AgentWithMemory/AgentWithMemory_Step06_MemoryUsingAgentMemory",
RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_OPENAI_API_KEY", "FOUNDRY_MODEL", "FOUNDRY_EMBEDDING_MODEL", "NEO4J_URI", "NEO4J_USER", "NEO4J_PASSWORD"],
SkipReason = "Requires a running Neo4j instance; standalone sample outside the repo's CPM build.",
},
new SampleDefinition
{
Name = "AgentWithMemory_Step07_FileMemoryProvider",
ProjectPath = "samples/02-agents/AgentWithMemory/AgentWithMemory_Step07_FileMemoryProvider",
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
MustContain =
[
"Memory files will be written to:",
"=== First conversation ===",
"=== Memory files on disk ===",
"=== Second conversation (new session) ===",
],
ExpectedOutputDescription =
[
"The output should acknowledge that the user is vegetarian and travels with a dog, indicating the agent stored these preferences.",
"The memory files section should list at least one memory file written by the agent, such as a file about the user's preferences.",
"The second conversation should recommend a hotel and a restaurant in Paris that are consistent with the remembered preferences, for example a pet-friendly hotel and a restaurant with vegetarian options, even though it is a new session.",
"The output should not contain error messages or stack traces.",
],
},
new SampleDefinition
{
Name = "AgentWithMemory_Step08_MemoryUsingCosmosNoSql",
ProjectPath = "samples/02-agents/AgentWithMemory/AgentWithMemory_Step08_MemoryUsingCosmosNoSql",
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT", "COSMOS_ENDPOINT"],
OptionalEnvironmentVariables = ["FOUNDRY_MODEL", "FOUNDRY_EMBEDDING_MODEL", "COSMOS_DATABASE_NAME"],
MustContain =
[
"First session:",
"Second session (recalling prior chat history from Cosmos DB):",
],
ExpectedOutputDescription =
[
"The output should contain two joke responses.",
"The first joke should be about a pirate (as explicitly requested).",
"The second joke should also be pirate-themed or similar to what the user likes, since chat history from the first session should be recalled from Cosmos DB.",
"The output should not contain error messages or stack traces.",
],
},
// ── AgentWithRAG ────────────────────────────────────────────────────
new SampleDefinition
@@ -490,12 +602,12 @@ internal static class AgentsSamples
],
},
// ── AgentsWithFoundry ────────────────────────────────────────────────
// ── Foundry ───────────────────────────────────────────────────────────
new SampleDefinition
{
Name = "FoundryAgent_Step00_FoundryAgentLifecycle",
ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step00_FoundryAgentLifecycle",
ProjectPath = "samples/02-agents/AgentProviders/foundry/Agent_Step00_FoundryAgentLifecycle",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
ExpectedOutputDescription =
@@ -508,7 +620,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "FoundryAgent_Step01_Basics",
ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step01_Basics",
ProjectPath = "samples/02-agents/AgentProviders/foundry/Agent_Step01_Basics",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
ExpectedOutputDescription =
@@ -521,7 +633,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "FoundryAgent_Step02.1_MultiturnConversation",
ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step02.1_MultiturnConversation",
ProjectPath = "samples/02-agents/AgentProviders/foundry/Agent_Step02.1_MultiturnConversation",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
ExpectedOutputDescription =
@@ -535,7 +647,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "FoundryAgent_Step02.2_MultiturnWithServerConversations",
ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step02.2_MultiturnWithServerConversations",
ProjectPath = "samples/02-agents/AgentProviders/foundry/Agent_Step02.2_MultiturnWithServerConversations",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
ExpectedOutputDescription =
@@ -548,7 +660,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "FoundryAgent_Step03_UsingFunctionTools",
ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step03_UsingFunctionTools",
ProjectPath = "samples/02-agents/AgentProviders/foundry/Agent_Step03_UsingFunctionTools",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
ExpectedOutputDescription =
@@ -563,7 +675,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "FoundryAgent_Step04_UsingFunctionToolsWithApprovals",
ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step04_UsingFunctionToolsWithApprovals",
ProjectPath = "samples/02-agents/AgentProviders/foundry/Agent_Step04_UsingFunctionToolsWithApprovals",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
Inputs = ["Y", "Y", "Y"],
@@ -579,7 +691,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "FoundryAgent_Step05_StructuredOutput",
ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step05_StructuredOutput",
ProjectPath = "samples/02-agents/AgentProviders/foundry/Agent_Step05_StructuredOutput",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
MustContain = ["Assistant Output:", "Name:"],
@@ -594,7 +706,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "FoundryAgent_Step06_PersistedConversations",
ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step06_PersistedConversations",
ProjectPath = "samples/02-agents/AgentProviders/foundry/Agent_Step06_PersistedConversations",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
ExpectedOutputDescription =
@@ -607,7 +719,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "FoundryAgent_Step08_DependencyInjection",
ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step08_DependencyInjection",
ProjectPath = "samples/02-agents/AgentProviders/foundry/Agent_Step08_DependencyInjection",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
Inputs = ["Tell me a joke about a pirate", ""],
@@ -622,7 +734,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "FoundryAgent_Step10_UsingImages",
ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step10_UsingImages",
ProjectPath = "samples/02-agents/AgentProviders/foundry/Agent_Step10_UsingImages",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
ExpectedOutputDescription =
@@ -636,7 +748,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "FoundryAgent_Step11_AsFunctionTool",
ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step11_AsFunctionTool",
ProjectPath = "samples/02-agents/AgentProviders/foundry/Agent_Step11_AsFunctionTool",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
ExpectedOutputDescription =
@@ -650,7 +762,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "FoundryAgent_Step12_Middleware",
ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step12_Middleware",
ProjectPath = "samples/02-agents/AgentProviders/foundry/Agent_Step12_Middleware",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
Inputs = ["Y", "Y", "Y"],
@@ -666,7 +778,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "FoundryAgent_Step13_Plugins",
ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step13_Plugins",
ProjectPath = "samples/02-agents/AgentProviders/foundry/Agent_Step13_Plugins",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
ExpectedOutputDescription =
@@ -680,7 +792,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "FoundryAgent_Step14_CodeInterpreter",
ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step14_CodeInterpreter",
ProjectPath = "samples/02-agents/AgentProviders/foundry/Agent_Step14_CodeInterpreter",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
ExpectedOutputDescription =
@@ -695,7 +807,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "FoundryAgent_Step16_FileSearch",
ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step16_FileSearch",
ProjectPath = "samples/02-agents/AgentProviders/foundry/Agent_Step16_FileSearch",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
MustContain = ["--- Running File Search Agent ---"],
@@ -709,12 +821,12 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "FoundryAgent_Step17_OpenAPITools",
ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step17_OpenAPITools",
ProjectPath = "samples/02-agents/AgentProviders/foundry/Agent_Step17_OpenAPITools",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
ExpectedOutputDescription =
[
"The output should contain a list of countries or information about countries that use the EUR currency.",
"The output should contain the current EUR exchange rate against USD and GBP as numeric values.",
"The output should not contain error messages or stack traces.",
],
},
@@ -733,7 +845,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "Agent_With_A2A",
ProjectPath = "samples/02-agents/AgentProviders/Agent_With_A2A",
ProjectPath = "samples/02-agents/AgentProviders/a2a/Agent_With_A2A",
RequiredEnvironmentVariables = ["A2A_AGENT_HOST"],
SkipReason = "Requires an external A2A agent host.",
},
@@ -741,7 +853,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "Agent_With_Anthropic",
ProjectPath = "samples/02-agents/AgentProviders/Agent_With_Anthropic",
ProjectPath = "samples/02-agents/AgentProviders/anthropic/Agent_With_Anthropic",
RequiredEnvironmentVariables = ["ANTHROPIC_API_KEY"],
OptionalEnvironmentVariables = ["ANTHROPIC_CHAT_MODEL_NAME", "ANTHROPIC_RESOURCE"],
ExpectedOutputDescription =
@@ -754,7 +866,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "Agent_With_GitHubCopilot",
ProjectPath = "samples/02-agents/AgentProviders/Agent_With_GitHubCopilot",
ProjectPath = "samples/02-agents/AgentProviders/github-copilot/Agent_With_GitHubCopilot",
RequiredEnvironmentVariables = [],
// The sample prompts for shell command approval; provide "Y" for each possible permission request
Inputs = ["Y", "Y", "Y"],
@@ -766,10 +878,23 @@ internal static class AgentsSamples
],
},
new SampleDefinition
{
Name = "Agent_With_GitHubCopilot_BYOK",
ProjectPath = "samples/02-agents/AgentProviders/github-copilot/Agent_With_GitHubCopilot_BYOK",
RequiredEnvironmentVariables = ["BYOK_BASE_URL", "BYOK_API_KEY"],
OptionalEnvironmentVariables = ["BYOK_PROVIDER_TYPE", "BYOK_MODEL_ID"],
ExpectedOutputDescription =
[
"The output should contain a user prompt and a response about the benefits of BYOK.",
"The output should not contain error messages or stack traces.",
],
},
new SampleDefinition
{
Name = "Agent_With_GoogleGemini",
ProjectPath = "samples/02-agents/AgentProviders/Agent_With_GoogleGemini",
ProjectPath = "samples/02-agents/AgentProviders/google-gemini/Agent_With_GoogleGemini",
RequiredEnvironmentVariables = ["GOOGLE_GENAI_API_KEY"],
OptionalEnvironmentVariables = ["GOOGLE_GENAI_MODEL"],
MustContain =
@@ -787,7 +912,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "Agent_With_ONNX",
ProjectPath = "samples/02-agents/AgentProviders/Agent_With_ONNX",
ProjectPath = "samples/02-agents/AgentProviders/onnx/Agent_With_ONNX",
RequiredEnvironmentVariables = ["ONNX_MODEL_PATH"],
SkipReason = "Requires local ONNX model.",
},
@@ -795,7 +920,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "Agent_With_Ollama",
ProjectPath = "samples/02-agents/AgentProviders/Agent_With_Ollama",
ProjectPath = "samples/02-agents/AgentProviders/ollama/Agent_With_Ollama",
RequiredEnvironmentVariables = ["OLLAMA_ENDPOINT", "OLLAMA_MODEL_NAME"],
SkipReason = "Requires local Ollama server.",
},
@@ -803,7 +928,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "Agent_With_OpenAIChatCompletion",
ProjectPath = "samples/02-agents/AgentProviders/Agent_With_OpenAIChatCompletion",
ProjectPath = "samples/02-agents/AgentProviders/openai/Agent_With_OpenAIChatCompletion",
RequiredEnvironmentVariables = ["OPENAI_API_KEY"],
OptionalEnvironmentVariables = ["OPENAI_CHAT_MODEL_NAME"],
ExpectedOutputDescription =
@@ -816,7 +941,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "Agent_With_OpenAIResponses",
ProjectPath = "samples/02-agents/AgentProviders/Agent_With_OpenAIResponses",
ProjectPath = "samples/02-agents/AgentProviders/openai/Agent_With_OpenAIResponses",
RequiredEnvironmentVariables = ["OPENAI_API_KEY"],
OptionalEnvironmentVariables = ["OPENAI_CHAT_MODEL_NAME"],
ExpectedOutputDescription =
@@ -850,13 +975,13 @@ internal static class AgentsSamples
ProjectPath = "samples/02-agents/Agents/Agent_Step15_DeepResearch",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT", "AZURE_AI_MODEL_DEPLOYMENT_NAME", "AZURE_AI_BING_CONNECTION_ID"],
OptionalEnvironmentVariables = ["AZURE_AI_REASONING_DEPLOYMENT_NAME"],
SkipReason = "Requires Azure AI Foundry project with Bing search connection.",
SkipReason = "Requires Microsoft Foundry project with Bing search connection.",
},
new SampleDefinition
{
Name = "Agent_Anthropic_Step01_Running",
ProjectPath = "samples/02-agents/AgentWithAnthropic/Agent_Anthropic_Step01_Running",
ProjectPath = "samples/02-agents/AgentProviders/anthropic/Agent_Anthropic_Step01_Running",
RequiredEnvironmentVariables = ["ANTHROPIC_API_KEY"],
OptionalEnvironmentVariables = ["ANTHROPIC_CHAT_MODEL_NAME"],
ExpectedOutputDescription =
@@ -870,7 +995,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "Agent_Anthropic_Step02_Reasoning",
ProjectPath = "samples/02-agents/AgentWithAnthropic/Agent_Anthropic_Step02_Reasoning",
ProjectPath = "samples/02-agents/AgentProviders/anthropic/Agent_Anthropic_Step02_Reasoning",
RequiredEnvironmentVariables = ["ANTHROPIC_API_KEY"],
OptionalEnvironmentVariables = ["ANTHROPIC_CHAT_MODEL_NAME"],
MustContain =
@@ -893,7 +1018,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "Agent_Anthropic_Step03_UsingFunctionTools",
ProjectPath = "samples/02-agents/AgentWithAnthropic/Agent_Anthropic_Step03_UsingFunctionTools",
ProjectPath = "samples/02-agents/AgentProviders/anthropic/Agent_Anthropic_Step03_UsingFunctionTools",
RequiredEnvironmentVariables = ["ANTHROPIC_API_KEY"],
OptionalEnvironmentVariables = ["ANTHROPIC_CHAT_MODEL_NAME"],
ExpectedOutputDescription =
@@ -907,7 +1032,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "Agent_Anthropic_Step04_UsingSkills",
ProjectPath = "samples/02-agents/AgentWithAnthropic/Agent_Anthropic_Step04_UsingSkills",
ProjectPath = "samples/02-agents/AgentProviders/anthropic/Agent_Anthropic_Step04_UsingSkills",
RequiredEnvironmentVariables = ["ANTHROPIC_API_KEY"],
OptionalEnvironmentVariables = ["ANTHROPIC_CHAT_MODEL_NAME"],
MustContain =
@@ -934,7 +1059,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "Agent_OpenAI_Step01_Running",
ProjectPath = "samples/02-agents/AgentWithOpenAI/Agent_OpenAI_Step01_Running",
ProjectPath = "samples/02-agents/AgentProviders/openai/Agent_OpenAI_Step01_Running",
RequiredEnvironmentVariables = ["OPENAI_API_KEY"],
OptionalEnvironmentVariables = ["OPENAI_CHAT_MODEL_NAME"],
ExpectedOutputDescription =
@@ -947,7 +1072,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "Agent_OpenAI_Step02_Reasoning",
ProjectPath = "samples/02-agents/AgentWithOpenAI/Agent_OpenAI_Step02_Reasoning",
ProjectPath = "samples/02-agents/AgentProviders/openai/Agent_OpenAI_Step02_Reasoning",
RequiredEnvironmentVariables = ["OPENAI_API_KEY"],
OptionalEnvironmentVariables = ["OPENAI_CHAT_MODEL_NAME"],
MustContain =
@@ -967,7 +1092,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "Agent_OpenAI_Step03_CreateFromChatClient",
ProjectPath = "samples/02-agents/AgentWithOpenAI/Agent_OpenAI_Step03_CreateFromChatClient",
ProjectPath = "samples/02-agents/AgentProviders/openai/Agent_OpenAI_Step03_CreateFromChatClient",
RequiredEnvironmentVariables = ["OPENAI_API_KEY"],
OptionalEnvironmentVariables = ["OPENAI_CHAT_MODEL_NAME"],
ExpectedOutputDescription =
@@ -981,7 +1106,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "Agent_OpenAI_Step04_CreateFromOpenAIResponseClient",
ProjectPath = "samples/02-agents/AgentWithOpenAI/Agent_OpenAI_Step04_CreateFromOpenAIResponseClient",
ProjectPath = "samples/02-agents/AgentProviders/openai/Agent_OpenAI_Step04_CreateFromOpenAIResponseClient",
RequiredEnvironmentVariables = ["OPENAI_API_KEY"],
OptionalEnvironmentVariables = ["OPENAI_CHAT_MODEL_NAME"],
ExpectedOutputDescription =
@@ -995,7 +1120,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "Agent_OpenAI_Step05_Conversation",
ProjectPath = "samples/02-agents/AgentWithOpenAI/Agent_OpenAI_Step05_Conversation",
ProjectPath = "samples/02-agents/AgentProviders/openai/Agent_OpenAI_Step05_Conversation",
RequiredEnvironmentVariables = ["OPENAI_API_KEY"],
OptionalEnvironmentVariables = ["OPENAI_CHAT_MODEL_NAME"],
MustContain =
@@ -1042,7 +1167,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "FoundryAgent_Step07_Observability",
ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step07_Observability",
ProjectPath = "samples/02-agents/AgentProviders/foundry/Agent_Step07_Observability",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME", "APPLICATIONINSIGHTS_CONNECTION_STRING"],
SkipReason = "Requires Application Insights / OpenTelemetry infrastructure.",
@@ -1051,7 +1176,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "FoundryAgent_Step09_UsingMcpClientAsTools",
ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step09_UsingMcpClientAsTools",
ProjectPath = "samples/02-agents/AgentProviders/foundry/Agent_Step09_UsingMcpClientAsTools",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
ExpectedOutputDescription =
@@ -1064,16 +1189,16 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "FoundryAgent_Step15_ComputerUse",
ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step15_ComputerUse",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
ProjectPath = "samples/02-agents/AgentProviders/foundry/Agent_Step15_ComputerUse",
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT", "AZURE_AI_COMPUTER_USE_DEPLOYMENT_NAME"],
OptionalEnvironmentVariables = [],
ExpectedOutputDescription = ["The output should show a computer automation session processing simulated browser screenshots with iteration steps and a final response describing search results."],
},
new SampleDefinition
{
Name = "FoundryAgent_Step18_BingCustomSearch",
ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step18_BingCustomSearch",
ProjectPath = "samples/02-agents/AgentProviders/foundry/Agent_Step18_BingCustomSearch",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT", "AZURE_AI_MODEL_DEPLOYMENT_NAME", "AZURE_AI_CUSTOM_SEARCH_CONNECTION_ID", "AZURE_AI_CUSTOM_SEARCH_INSTANCE_NAME"],
SkipReason = "Requires Bing Custom Search connection.",
},
@@ -1081,7 +1206,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "FoundryAgent_Step19_SharePoint",
ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step19_SharePoint",
ProjectPath = "samples/02-agents/AgentProviders/foundry/Agent_Step19_SharePoint",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT", "AZURE_AI_MODEL_DEPLOYMENT_NAME", "SHAREPOINT_PROJECT_CONNECTION_ID"],
SkipReason = "Requires SharePoint connection.",
},
@@ -1089,7 +1214,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "FoundryAgent_Step20_MicrosoftFabric",
ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step20_MicrosoftFabric",
ProjectPath = "samples/02-agents/AgentProviders/foundry/Agent_Step20_MicrosoftFabric",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT", "AZURE_AI_MODEL_DEPLOYMENT_NAME", "FABRIC_PROJECT_CONNECTION_ID"],
SkipReason = "Requires Microsoft Fabric connection.",
},
@@ -1097,7 +1222,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "FoundryAgent_Step21_WebSearch",
ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step21_WebSearch",
ProjectPath = "samples/02-agents/AgentProviders/foundry/Agent_Step21_WebSearch",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
ExpectedOutputDescription =
@@ -1110,7 +1235,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "FoundryAgent_Step22_MemorySearch",
ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step22_MemorySearch",
ProjectPath = "samples/02-agents/AgentProviders/foundry/Agent_Step22_MemorySearch",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT", "AZURE_AI_MODEL_DEPLOYMENT_NAME", "AZURE_AI_EMBEDDING_DEPLOYMENT_NAME"],
OptionalEnvironmentVariables = ["AZURE_AI_MEMORY_STORE_ID"],
MustContain = ["Agent created with Memory Search tool. Starting conversation..."],
@@ -1124,7 +1249,7 @@ internal static class AgentsSamples
new SampleDefinition
{
Name = "FoundryAgent_Step23_LocalMCP",
ProjectPath = "samples/02-agents/AgentsWithFoundry/Agent_Step23_LocalMCP",
ProjectPath = "samples/02-agents/AgentProviders/foundry/Agent_Step23_LocalMCP",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
ExpectedOutputDescription = ["The output should show an agent using the Microsoft Learn MCP server to search for documentation and provide a response."],
+8 -17
View File
@@ -26,8 +26,8 @@ internal static class GetStartedSamples
{
Name = "01_hello_agent",
ProjectPath = "samples/01-get-started/01_hello_agent",
RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
ExpectedOutputDescription =
[
"The output should contain a joke about a pirate.",
@@ -40,8 +40,8 @@ internal static class GetStartedSamples
{
Name = "02_add_tools",
ProjectPath = "samples/01-get-started/02_add_tools",
RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
MustContain = [],
ExpectedOutputDescription =
[
@@ -56,8 +56,8 @@ internal static class GetStartedSamples
{
Name = "03_multi_turn",
ProjectPath = "samples/01-get-started/03_multi_turn",
RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
ExpectedOutputDescription =
[
"The output should contain a joke about a pirate.",
@@ -71,8 +71,8 @@ internal static class GetStartedSamples
{
Name = "04_memory",
ProjectPath = "samples/01-get-started/04_memory",
RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
MustContain =
[
">> Use session with blank memory",
@@ -92,14 +92,5 @@ internal static class GetStartedSamples
"The output should not contain error messages or stack traces.",
],
},
new SampleDefinition
{
Name = "06_host_your_agent",
ProjectPath = "samples/01-get-started/06_host_your_agent",
RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
SkipReason = "Requires Azure Functions Core Tools runtime and starts a web server.",
},
];
}
+33 -11
View File
@@ -18,13 +18,14 @@
// Note: By default, this tool expects sample build outputs to already exist.
// Pre-build the solution before running, or pass --build to avoid missing build output failures.
//
// Required environment variables (for AI-powered samples):
// AZURE_OPENAI_ENDPOINT
// AZURE_OPENAI_DEPLOYMENT_NAME (optional, defaults to gpt-5-mini)
// Required environment variables (for AI-powered verification):
// FOUNDRY_PROJECT_ENDPOINT — Your Microsoft Foundry project endpoint
// FOUNDRY_MODEL — Model deployment name (optional, defaults to gpt-5.4-mini)
using System.Diagnostics;
using Azure.AI.OpenAI;
using Azure.AI.Projects;
using Azure.Identity;
using Microsoft.Agents.AI;
using VerifySamples;
var options = VerifyOptions.Parse(args);
@@ -43,14 +44,33 @@ if (!File.Exists(Path.Combine(dotnetRoot, "agent-framework-dotnet.slnx")))
}
// Set up the AI verifier
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5-mini";
var foundryEndpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT");
var foundryModel = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
OpenAI.Chat.ChatClient? chatClient = null;
if (!string.IsNullOrEmpty(endpoint))
AIAgent? verifierAgent = null;
if (!string.IsNullOrEmpty(foundryEndpoint))
{
chatClient = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential())
.GetChatClient(deploymentName);
verifierAgent = new AIProjectClient(new Uri(foundryEndpoint), new DefaultAzureCredential())
.AsAIAgent(
model: foundryModel,
instructions: """
You are a test output verifier. You will be given:
1. The actual stdout output of a program
2. The stderr output (if any)
3. A list of expectations about what the output should contain or demonstrate
Your job is to determine whether the actual output satisfies each expectation.
Be reasonable the output comes from an LLM so exact wording won't match, but the
semantic intent should be clearly satisfied.
In your response, you MUST:
- Always provide ai_reasoning with a brief overall assessment.
- Always provide exactly one entry in expectation_results for each expectation,
in the same order as the input list.
- For each expectation_results entry, echo the expectation text in the expectation
field and explain your assessment in the detail field, citing evidence from the output.
""",
name: "OutputVerifier");
}
// Set up optional log file writer
@@ -61,11 +81,13 @@ if (options.LogFilePath is not null)
await logWriter.WriteHeaderAsync();
}
Console.WriteLine($"Foundry endpoint: {foundryEndpoint ?? "(not set AI verification disabled)"}, Model: {foundryModel}");
try
{
// Run all samples
var reporter = new ConsoleReporter();
var verifier = new SampleVerifier(chatClient);
var verifier = new SampleVerifier(verifierAgent);
var orchestrator = new VerificationOrchestrator(verifier, reporter, dotnetRoot, TimeSpan.FromMinutes(3), logWriter, buildSamples: options.BuildSamples);
var run = await orchestrator.RunAllAsync(options.Samples, options.MaxParallelism);
+3 -26
View File
@@ -3,8 +3,6 @@
using System.ComponentModel;
using System.Text.Json.Serialization;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using OpenAI.Chat;
namespace VerifySamples;
@@ -17,33 +15,12 @@ internal sealed class SampleVerifier
private readonly AIAgent? _verifierAgent;
/// <summary>
/// Creates a verifier. If <paramref name="chatClient"/> is provided,
/// Creates a verifier. If <paramref name="verifierAgent"/> is provided,
/// AI-based verification is available for non-deterministic samples.
/// </summary>
public SampleVerifier(ChatClient? chatClient = null)
public SampleVerifier(AIAgent? verifierAgent = null)
{
if (chatClient is not null)
{
this._verifierAgent = chatClient.AsAIAgent(
instructions: """
You are a test output verifier. You will be given:
1. The actual stdout output of a program
2. The stderr output (if any)
3. A list of expectations about what the output should contain or demonstrate
Your job is to determine whether the actual output satisfies each expectation.
Be reasonable the output comes from an LLM so exact wording won't match, but the
semantic intent should be clearly satisfied.
In your response, you MUST:
- Always provide ai_reasoning with a brief overall assessment.
- Always provide exactly one entry in expectation_results for each expectation,
in the same order as the input list.
- For each expectation_results entry, echo the expectation text in the expectation
field and explain your assessment in the detail field, citing evidence from the output.
""",
name: "OutputVerifier");
}
this._verifierAgent = verifierAgent;
}
/// <summary>
+53 -63
View File
@@ -30,8 +30,8 @@ internal static class WorkflowSamples
{
Name = "Workflow_StartHere_02_AgentsInWorkflows",
ProjectPath = "samples/03-workflows/_StartHere/02_AgentsInWorkflows",
RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
ExpectedOutputDescription =
[
"The output should show agent responses from a translation workflow.",
@@ -43,8 +43,8 @@ internal static class WorkflowSamples
{
Name = "Workflow_StartHere_03_AgentWorkflowPatterns",
ProjectPath = "samples/03-workflows/_StartHere/03_AgentWorkflowPatterns",
RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
Inputs = ["sequential"],
InputDelayMs = 3000,
ExpectedOutputDescription =
@@ -81,8 +81,8 @@ internal static class WorkflowSamples
{
Name = "Workflow_StartHere_06_MixedWorkflowAgentsAndExecutors",
ProjectPath = "samples/03-workflows/_StartHere/06_MixedWorkflowAgentsAndExecutors",
RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
Inputs = ["What is 2 plus 2?"],
InputDelayMs = 3000,
ExpectedOutputDescription =
@@ -96,8 +96,8 @@ internal static class WorkflowSamples
{
Name = "Workflow_StartHere_07_WriterCriticWorkflow",
ProjectPath = "samples/03-workflows/_StartHere/07_WriterCriticWorkflow",
RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
MustContain = ["=== Writer-Critic Iteration Workflow ==="],
ExpectedOutputDescription =
[
@@ -115,8 +115,8 @@ internal static class WorkflowSamples
{
Name = "Workflow_Agents_CustomAgentExecutors",
ProjectPath = "samples/03-workflows/Agents/CustomAgentExecutors",
RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
ExpectedOutputDescription =
[
"The output should show custom workflow events including slogan generation and feedback.",
@@ -128,17 +128,17 @@ internal static class WorkflowSamples
{
Name = "Workflow_Agents_FoundryAgent",
ProjectPath = "samples/03-workflows/Agents/FoundryAgent",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
SkipReason = "Requires Azure AI Foundry project endpoint.",
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
SkipReason = "Requires Microsoft Foundry project endpoint.",
},
new SampleDefinition
{
Name = "Workflow_Agents_GroupChatToolApproval",
ProjectPath = "samples/03-workflows/Agents/GroupChatToolApproval",
RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
MustContain = ["Starting group chat workflow for software deployment..."],
ExpectedOutputDescription =
[
@@ -153,8 +153,8 @@ internal static class WorkflowSamples
{
Name = "Workflow_Agents_WorkflowAsAnAgent",
ProjectPath = "samples/03-workflows/Agents/WorkflowAsAnAgent",
RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
Inputs = ["hello", "exit"],
InputDelayMs = 5000,
ExpectedOutputDescription =
@@ -219,8 +219,8 @@ internal static class WorkflowSamples
{
Name = "Workflow_Concurrent_Concurrent",
ProjectPath = "samples/03-workflows/Concurrent/Concurrent",
RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
ExpectedOutputDescription =
[
"The output should show results from concurrent agent processing.",
@@ -247,8 +247,8 @@ internal static class WorkflowSamples
{
Name = "Workflow_ConditionalEdges_01_EdgeCondition",
ProjectPath = "samples/03-workflows/ConditionalEdges/01_EdgeCondition",
RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
ExpectedOutputDescription =
[
"The output should show an email being classified as spam or not spam and processed accordingly.",
@@ -260,8 +260,8 @@ internal static class WorkflowSamples
{
Name = "Workflow_ConditionalEdges_02_SwitchCase",
ProjectPath = "samples/03-workflows/ConditionalEdges/02_SwitchCase",
RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
ExpectedOutputDescription =
[
"The output should show an ambiguous email being classified as spam, not spam, or uncertain.",
@@ -273,8 +273,8 @@ internal static class WorkflowSamples
{
Name = "Workflow_ConditionalEdges_03_MultiSelection",
ProjectPath = "samples/03-workflows/ConditionalEdges/03_MultiSelection",
RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
ExpectedOutputDescription =
[
"The output should show an email being classified and potentially routed to multiple handlers.",
@@ -371,8 +371,8 @@ internal static class WorkflowSamples
{
Name = "Workflow_Observability_WorkflowAsAnAgent",
ProjectPath = "samples/03-workflows/Observability/WorkflowAsAnAgent",
RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
SkipReason = "Interactive console with ReadLine loop; requires OTLP endpoint.",
},
@@ -384,7 +384,7 @@ internal static class WorkflowSamples
{
Name = "Workflow_Declarative_ConfirmInput",
ProjectPath = "samples/03-workflows/Declarative/ConfirmInput",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
Inputs = ["hello", "hello"],
InputDelayMs = 8000,
ExpectedOutputDescription = ["The output should show a confirmation prompt and a user response."],
@@ -394,10 +394,10 @@ internal static class WorkflowSamples
{
Name = "Workflow_Declarative_CustomerSupport",
ProjectPath = "samples/03-workflows/Declarative/CustomerSupport",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
Inputs = ["My laptop won't start"],
InputDelayMs = 3000,
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
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
{
Name = "Workflow_Declarative_DeepResearch",
ProjectPath = "samples/03-workflows/Declarative/DeepResearch",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
SkipReason = "Requires external weather API (wttr.in).",
},
new SampleDefinition
{
Name = "Workflow_Declarative_ExecuteCode",
ProjectPath = "samples/03-workflows/Declarative/ExecuteCode",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
Inputs = ["What is 12 * 34?"],
InputDelayMs = 5000,
ExpectedOutputDescription = ["The output should show a declarative workflow executing generated code, processing a math question and producing a result."],
},
new SampleDefinition
{
Name = "Workflow_Declarative_ExecuteWorkflow",
ProjectPath = "samples/03-workflows/Declarative/ExecuteWorkflow",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
SkipReason = "Requires a workflow file path as a CLI argument.",
},
@@ -432,8 +422,8 @@ internal static class WorkflowSamples
{
Name = "Workflow_Declarative_FunctionTools",
ProjectPath = "samples/03-workflows/Declarative/FunctionTools",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
Inputs = ["What are today's specials?", "EXIT"],
InputDelayMs = 8000,
ExpectedOutputDescription = ["The output should show a workflow calling function tools (e.g. a menu plugin) to answer a question about restaurant specials."],
@@ -443,7 +433,7 @@ internal static class WorkflowSamples
{
Name = "Workflow_Declarative_HostedWorkflow",
ProjectPath = "samples/03-workflows/Declarative/HostedWorkflow",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
SkipReason = "Hosts a persistent workflow server that does not exit.",
},
@@ -451,9 +441,9 @@ internal static class WorkflowSamples
{
Name = "Workflow_Declarative_InputArguments",
ProjectPath = "samples/03-workflows/Declarative/InputArguments",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
Inputs = ["I'd like to visit Seattle", "EXIT"],
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
Inputs = ["I'd like to visit Seattle", "Seattle, WA", "EXIT"],
InputDelayMs = 8000,
ExpectedOutputDescription = ["The output should show a workflow capturing location input and providing travel-related information about Seattle."],
},
@@ -462,8 +452,8 @@ internal static class WorkflowSamples
{
Name = "Workflow_Declarative_InvokeFunctionTool",
ProjectPath = "samples/03-workflows/Declarative/InvokeFunctionTool",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
Inputs = ["What's the soup of the day?", "EXIT"],
InputDelayMs = 8000,
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."],
@@ -473,8 +463,8 @@ internal static class WorkflowSamples
{
Name = "Workflow_Declarative_InvokeFoundryToolboxMcp",
ProjectPath = "samples/03-workflows/Declarative/InvokeFoundryToolboxMcp",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME", "FOUNDRY_TOOLBOX_NAME", "FOUNDRY_AGENT_TOOLSET_API_VERSION"],
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["FOUNDRY_MODEL", "FOUNDRY_TOOLBOX_NAME", "FOUNDRY_AGENT_TOOLSET_API_VERSION"],
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."],
@@ -484,8 +474,8 @@ internal static class WorkflowSamples
{
Name = "Workflow_Declarative_InvokeMcpTool",
ProjectPath = "samples/03-workflows/Declarative/InvokeMcpTool",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
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."],
@@ -495,8 +485,8 @@ internal static class WorkflowSamples
{
Name = "Workflow_Declarative_Marketing",
ProjectPath = "samples/03-workflows/Declarative/Marketing",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
Inputs = ["A smart water bottle that tracks hydration"],
InputDelayMs = 3000,
ExpectedOutputDescription = ["The output should show a marketing workflow generating content about a smart water bottle product."],
@@ -506,8 +496,8 @@ internal static class WorkflowSamples
{
Name = "Workflow_Declarative_StudentTeacher",
ProjectPath = "samples/03-workflows/Declarative/StudentTeacher",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
Inputs = ["What is 18 + 27?"],
InputDelayMs = 3000,
ExpectedOutputDescription = ["The output should show a student-teacher workflow where a student asks a math question and a teacher provides the answer."],
@@ -517,8 +507,8 @@ internal static class WorkflowSamples
{
Name = "Workflow_Declarative_ToolApproval",
ProjectPath = "samples/03-workflows/Declarative/ToolApproval",
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
Inputs = ["Search for .NET tutorials", "EXIT"],
InputDelayMs = 8000,
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."],

Some files were not shown because too many files have changed in this diff Show More