Commit Graph

2759 Commits

Author SHA1 Message Date
Tao Chen ccd649946a Address comments 2026-08-14 01:27:41 +00:00
Tao Chen f8851d0557 Apply new assignments after feedback 2026-08-13 23:53:07 +00:00
Tao Chen aacf75adda Draft: Enforce code owner 2026-08-12 20:59:00 +00:00
westey 56d13bce4e .NET: Add BackgroundAgentsProvider.ReleaseSessionAsync to cancel and release per-session background tasks (#7602)
* Add the abilty for the caller to release and cancel background tasks

* Improve param validation

* Address PR comments

* Address PR comments.

* Address PR comments: cancel tasks before publishing the release

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

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

---------

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

* test: collect workspace script tests

* test: remove standalone script test

---------

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

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

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

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

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

* Python: Bind Claude SDK client ownership to each run

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

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

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

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

* Python: Close remaining Claude session-isolation gaps

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

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

Update and extend the tests accordingly.

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

* Python: Bind injected Claude client on provider conversation identity

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

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

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

---------

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

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

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

* Fix tests

* Address comments and fix tests

* Fix typing

* Show how to use user created sessions

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

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

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

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

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

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

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

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

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

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

* Address the review comments left open on the merged PR

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

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

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

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

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

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

* Address the review on #7572

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

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

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

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

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

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

* Let the chat history provider carry the conversation

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* Address Cosmos memory sample review feedback

* Fix Cosmos NoSQL memory sample build

---------

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

* Fix PR comments

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

* Fix session id error

* Fix tests

* Improve tests

* Fix copilot comments

* Address comments

* Revert sample changes

* Address comments

* Add ContextScopedStoreProvider

* Fix type check

* Fix type check

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* Rename store isolation key provider

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

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

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

* Rename to AgentIsolationKeyProvider per review feedback

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

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

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

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

* Update hosting spec for AgentIsolationKeyProvider rename

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

---------

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

* Address PR review feedback on executable function bypassing

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

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

* Address PR comments

---------

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

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

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

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

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

* Add regression tests for the duplicated hosted chat history

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

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

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

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

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

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

Give the provider both halves instead of choosing one:

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

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

* Refuse a stored turn once a conversation holds unstored ones

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

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

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

* Say plainly that kept turns belong to the session

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

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

* Show which half of the conversation each provider decides

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

Add two tests that separate those from the session:

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

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

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

* Ask the session store whether a turn is a resume

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

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

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

* Drop the experimental marker from an internal type

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* Leave the conversation to the AgentServer storage provider alone

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

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

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

* Narrow the history skip to a resumed workflow

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

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

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

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

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

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

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

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

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

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

* Add max tool approval loop fixes

* Fix net472 build break in usage aggregation tests

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

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

* Address PR comments

---------

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

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

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

* .NET: Handle inaccessible skill directories

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

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

---------

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

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

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

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

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

* Python: apply read_bufsize default to supplied CopilotStudio settings

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

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

---------

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* Fix ruff used-dummy-variable: rename _skip_structured_siblings

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

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

* Fix missing newline at end of test file

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

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

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

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

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

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

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

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

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

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

* Skip non-string content in default text parsing

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

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

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

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

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

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

---------

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

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

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

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

* style(python): unquote ResponseStream annotation per pyupgrade

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

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

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

Reworks the agent-hooks feature per PR review:

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

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

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

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

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

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

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

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

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

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

* fix(python): gate ownership survives retrying middleware

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

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

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

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

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

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

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

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

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

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

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

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

---------

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

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

* Apply suggestions from code review

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

---------

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

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

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

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

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

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

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

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

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

* Increase default max auto approval iterations to 40

* Apply suggestion from @westey-m

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

* Update comments in ToolApprovalAgent.cs

---------

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

* Fix AG-UI conversation correlation across runs

* Address PR review and code quality feedback

* Correlate AG-UI chat spans across runs

---------

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

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

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

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

* fix formatting issue

---------

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

* Python: Reject empty Ollama approval resume payload

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

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

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

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

* Python: keep excluded tool results out of compaction digests

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

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

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

* Python: align compaction digest review cleanup

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

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

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

* Python: collapse tool result digest scan

---------

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

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

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

* Python: Clarify approval serialization boundaries

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

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

---------

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

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

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

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

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

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

* .NET: consolidate #7280 AgentMemory.AgentFramework 1.3.0

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

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

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

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

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

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

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

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

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

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

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

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

* Bump actions/cache from 5.0.5 to 6.1.0

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

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

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

* Bump actions/checkout from 6.0.2 to 7.0.1

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

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

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

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

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

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

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

* Bump ty from 0.0.60 to 0.0.64 in /python

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

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

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

* Bump prek from 0.4.10 to 0.4.11 in /python

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

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

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

* Bump uv from 0.11.29 to 0.11.32 in /python

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

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

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

* Bump ruff from 0.15.22 to 0.16.0 in /python

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

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

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

* update uv-build requirement in /python

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

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

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

* Python: support ty 0.0.64 diagnostics for #7436

* Python: apply Ruff 0.16 formatting for #7439

* Update workflow action version annotations

---------

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

* Include more samples and fix migration samples part 1

* Fix migration samples

* Replace Foundry hosted agent validation skill

* Fix hosted agent file sample

* Fix agent result format

* Reorganize jobs

* Update discovery heuristic for apps

* Split agents into even more jobs

* Add toolbox endpoint

* Add more pre configured resources

* Fix using deployed agent sample

* Add sample status

* Add playbook

* Exclude hidden folder in sample discovery

* Install autogen dependencies

* Grant azure search RBAC role

* Increase timeout for magentic

* Build search resouce id deterministically

* Remove grant in the workflow

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

* Refactor playbook

* Fix using deployed agent sample

* Actually save the playbooks

* Fix action syntax error

* Fix magentic sample

* Address copilot comments

* Fix link inspection

* Address comments

* Correct README

* Fix playbook path

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

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

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

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

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

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

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

* Python: update hosted harness sample for current APIs

---------

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

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

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