python-1.14.0
2770 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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 floorpython-1.14.0 |
||
|
|
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 |
||
|
|
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 |
||
|
|
5fafa18569 | Python: track agent-hooks feature usage (#7558) | ||
|
|
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 |
||
|
|
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> |
||
|
|
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 |
||
|
|
6d25fb1e9c | Remove clear and package source mapping from nuget.config to allow user level config inheritance (#7646) | ||
|
|
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 |
||
|
|
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
|
||
|
|
3221011427 | fix(core): warn when advertised MCP archives are rejected (#7622) | ||
|
|
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> |
||
|
|
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 |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
8a0731ad92 |
.NET: Prevent telemetry serialization failures from failing workflows (#7612)
* Prevent telemetry serialization failures from failing workflows * Address PR comments |
||
|
|
6fff2c9b1f | Fix misleading workflow protocol attribute diagnostics (#7609) | ||
|
|
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> |
||
|
|
db979b616a |
Python: Improve Json parsing for declarative workflow (#7550)
* Json parsing improvement * Fix PR comments * Address PR comments. |
||
|
|
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 |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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 |
||
|
|
5eb3eb745e | Improve string parsing in declarative workflows (#7535) | ||
|
|
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 |
||
|
|
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> |
||
|
|
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. |
||
|
|
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> |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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> |
||
|
|
422160eabe |
Python: Add windows junction detection for skills (#7507)
* Add windows junction detection for skills * Address PR comment |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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 |
||
|
|
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 |
||
|
|
1da571860a | Updating version for dotnet release 1.17.0 (#7514) dotnet-1.17.0 | ||
|
|
d56e81357e | Fail declarative workflows when an agent returns an error (#7497) | ||
|
|
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 |