* feat(dotnet): agent-hooks interception contract as an experimental package
Add Microsoft.Agents.AI.AgentHooks, implementing the AGENT-HOOKS-0.1
control contract on the framework's native decorator seams, mirroring
the merged Python feature (#7515) in .NET idiom:
- One public factory (CreateAIAgentWithAgentHooks, per-run and
host-owned-session overloads) composes agent, chat and function
seams as one indivisible unit; the seam decorators are internal, so
partial installs are impossible by construction.
- All eight interception points: input/output at the agent seam,
pre/post_model_call below the function-invocation loop (every model
service call bracketed individually), pre/post_tool_call via the
function-invocation middleware seam, agent_startup/agent_shutdown
bracketing each run.
- Fail-closed enforcement throughout: transforms write back into the
native messages/arguments/results or throw; rich content is
preserved as AIContent objects; interceptor crashes surface as
host_error denies; enforcement-layer failures halt the run through
FunctionInvocationContext.Terminate (the loop's only loud escape).
- Streaming is fully buffered per spec buffered_output semantics: a
deny releases zero updates; transformed responses re-derive the
released updates so egress never diverges from verdicted content.
- Verdict-before-durability: end-of-run history and context-provider
writes defer behind the output verdict via gating provider wrappers
(flushed post-transform with verdicted-message substitution for
streams, dropped on deny); per-service-call persistence sits above
the chat seam and is covered by its own post_model_call verdict;
per-run history-provider overrides in run options are wrapped too;
nested guarded sub-agents persist inline at their own boundaries.
- Opt-in dependency: ResponsibleAI.AgentHooks 0.1.0-alpha.4 (bundles
native runtimes) referenced only by the new package; no existing
framework source is modified.
- 58 tests: deny-before-execution and transform write-back per seam,
rich-content preservation, streaming ordering with zero egress on
deny, error bracketing, concurrency isolation, host-owned sessions,
evaluate_only, approval-seam lift, persistence gating, misuse
fail-closed paths, and codec units.
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>
* fix(dotnet): close structural bypasses at the ChatClientAgent boundary
Address both reviewers' probe-confirmed findings; the runtime
enforcement held everywhere, every fix is at the structural boundary:
- Gate the implicit default ChatHistoryProvider: with no provider
configured, ChatClientAgent creates an InMemoryChatHistoryProvider
the factory never saw, so denied output became durable session
history and replayed to the model on the zero-config path (both
stream modes). The factory now materializes and gates the default,
setting the history-conflict flags to mimic implicit-default
semantics for service-managed-history agents.
- Wrap per-run provider overrides on BOTH dictionaries: base
AgentRunOptions.AdditionalProperties is merged into the chat options
with precedence, so a base-level override bypassed (and displaced)
the wrapped ChatOptions-level entry. Plain AgentRunOptions is
covered too, and the wrap is copy-on-write — the caller's options
and dictionaries are never mutated.
- Reject per-run ChatClientFactory on guarded agents (fail closed): it
would replace the guarded chat pipeline and the tool-wrapping stage
riding it, silently removing the chat and tool seams.
- Reject a supplied client already containing a
FunctionInvokingChatClient: it would execute tools below the chat
seam, before any post_model_call verdict and outside the tool seam.
- Run wire projections inside the guarded blocks at the chat and
function seams: a poisoned value whose serialization throws now
fails the run closed (function seam: host_error halt; chat seam:
gated persistence refused before the failure propagates).
- Suppress provider failure notifications once a run-level deny or
halt stands, so the denied turn's request messages never reach
provider code.
- Document the deferred-OpenTelemetry observer channel (request-side
spans capture pre-transform content under sensitive-data telemetry).
- Rename the factory to AsAIAgentWithAgentHooks per repo convention.
10 new boundary regression tests mined from the review probes
(default-provider durability in both stream modes with session-replay
assertions, both override dictionaries incl. the displacement shape,
plain-run-options override, copy-on-write, factory and supplied-FICC
rejections, poisoned-projection fail-closed); 68 total, all green.
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>
* fix(dotnet): redact denied-run failure notifications for both provider kinds
The deny/halt handling of provider failure notifications only covered
the chat-history wrapper; a context provider still received the denied
turn's request messages on its failure notification. Both gating
wrappers now REDACT instead of suppress: the notification is forwarded
with empty request messages and the original exception, preserving the
documented failure-cleanup contract (providers releasing per-run
resources on the failure signal keep working) while the denied turn's
request messages never reach provider code.
Regression tests assert both provider kinds receive the redacted
notification (zero request messages) on a denied run and full
notifications on ordinary, verdict-free failures. 70 tests total.
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>
* fix(dotnet): address Copilot review on the agent-hooks PR
- Run options: always clone chat-typed run options (the framework's
function-invocation middleware chains its per-run factory onto the
instance it receives, so forwarding the caller's instance leaked
that factory into it — reuse tripped the rejection, concurrent
reuse raced), and recognize the framework middleware's own factory
as legitimate: it wraps the guarded pipeline (tool rewriting), so
outer function-middleware composition now works, while its chained
factories are walked so a caller-supplied factory cannot ride in
unnoticed.
- Streaming: re-derived (transformed) updates preserve the response's
ContinuationToken (ToAgentResponseUpdates does not project it), so
transformed background streaming responses remain resumable; a
message-less response releases a metadata-only update carrying it.
- Codecs: transformed tool calls are validated for complete shape and
uniqueness before reconciliation (non-empty string id and name,
object-valued args, distinct ids) — malformed shapes fail closed
instead of becoming invalid native calls. Deliberately stricter
than the merged Python codec, which coerces added-call shapes.
- Role defaulting in message write-backs is confirmed exact Python
parity (user/assistant defaults per the merged codecs) and is now
locked by tests rather than changed.
- ADR 0035 records the seam order, persistence gating, fail-closed
behavior, alternatives and known limitations.
14 new tests (options reuse, outer function-middleware composition,
smuggled-factory rejection, continuation-token preservation, 8
malformed tool-call shapes, 2 role-default parity); 84 total, green.
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>
* feat(dotnet): project the per-call tool set on pre_model_call emissions
Context providers can register additional tools during run preparation,
after agent_startup has been emitted, so tools_registered is inherently
a run-start snapshot and can be a partial view of the tools eventually
offered to the model.
- Emit the spec's optional pre_model_call tools field ({name,
description?}) from the per-call effective ChatOptions.Tools — the
completed set for each call, including provider-added tools.
- Document tools_registered as the run-start snapshot on the agent
seam (dynamic registrations surface per call and are bracketed by
the tool seam when invoked).
- Probe-confirm enforcement completeness for provider-added tools:
they flow through the guarded pipeline's tool-wrapping stage, emit
pre/post_tool_call, and a pre_tool_call deny blocks their
invocation exactly like constructor-registered tools.
Two new tests (bracketing + audit projections, deny-blocks); 86
total, green.
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>
* refactor(dotnet): one artifact per file; rewrap foreign gating wrappers
Per review:
- Split the three multi-type files (AgentHooksGatingProviders.cs,
AgentHooksRunState.cs, AgentHooksWireCodecs.cs) into one type per
file, file name matching the type name, per repo convention. No
behavior changes; namespaces and access levels unchanged.
- Close a validation asymmetry at the provider gate: the per-run
override wrap skipped any gating wrapper, including one owned by a
DIFFERENT agent-hooks installation — which runs inline under this
run's state (its own gate is not covering here), so a denied run's
history could persist straight through it. Overrides are now
re-wrapped unless the wrapper belongs to this installation
(reference-equal configuration). The provider seam's inline
behavior for foreign/absent state is otherwise deliberate: inline
is the safe direction there (content of unguarded or differently
guarded runs is covered by its own verdicts or none), and throwing
would break the legitimate double-wrap flush flow.
One new regression test (foreign wrapper as per-run override on a
denied run persists nothing); 87 total, green.
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>
* refactor(dotnet): accept params IEnumerable for agent-hooks interceptors
Per review: the constructor only iterates the interceptors, so widen
the parameter from params IInterceptor[] to the C# 13 params
IEnumerable<IInterceptor>. The sequence is enumerated exactly once
into the internal registration list (sequences may be
single-enumeration); per-item null validation and the factory's
at-least-one-interceptor check are unchanged, and an explicit null
sequence now throws ArgumentNullException. Params-form call sites are
source-compatible.
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>
* build(dotnet): ship Microsoft.Agents.AI.AgentHooks as a preview package
Per maintainer review on the PR:
- Add the project to agent-framework-release.slnf and import the
shared packaging props so the package ships. Version follows the
repo default for unmarked packages (preview suffix), matching the
package's [Experimental] surface and alpha upstream dependency:
1.17.0-preview.<date>.1.
- Package metadata: sibling-style title, fuller description, tags;
shared icon and NUGET.md readme via the packaging props. Verified
dotnet pack locally: ResponsibleAI.AgentHooks 0.1.0-alpha.4 flows
as a normal dependency and the project references become 1.17.0
package dependencies.
- Update ADR 0035: shipping as preview per maintainer decision
replaces the build-only-pending-maturity stance.
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>
* build(dotnet): version the agent-hooks package as alpha
Per maintainer review: the package's maturity marker follows the
ResponsibleAI.AgentHooks dependency it is built on (alpha), rather
than the repo's default preview suffix. Packs as
1.17.0-alpha.260804.1; ADR 0035 updated.
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>
* refactor(dotnet): group agent-hooks internals into Core and Codecs folders
Per review: only the public surface (the factory extensions and
options) stays at the project root; the internal seam decorators, run
state and gating providers move to Core/, and the wire projection
codecs to Codecs/. Pure file moves — namespaces stay flat per the
core package's folder convention (ChatClient/, Memory/); no content
changes.
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>
* docs(dotnet): clarify session scoping and name the sessionId argument
Per review:
- Name the AgentContextBuilder arguments at the run-state factory so
the GUID reads as what it is (the per-run agent-hooks session id).
- Document both branches of CreateRunState: session-scoped means the
host owns the emitter/builder and the session boundaries (one
session spanning runs, no agent_startup/agent_shutdown emitted by
the agent); the default is one session per run with a fresh
emitter, fresh sequence and isolated record trail, which is what
keeps concurrent runs' emissions from interleaving.
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>
* fix(dotnet): harden agent-hooks factory and input projection per review
- Input projection returns (payload, content, role) as one typed
result so the emission site never re-reads payload properties by
name: the both-fields-exist invariant holds by construction. (The
previous reads were fail-closed even hypothetically — JsonObject's
indexer yields null, and a null content is rejected by the SDK's
envelope validation — but reading back what we just produced was
needlessly fragile-looking.)
- Reject UseProvidedChatClientAsIs on the factory: it signals a fully
custom, do-not-touch client stack, which is incompatible with a
factory whose job is to decorate the supplied client and rely on
the agent's default pipeline above the chat seam. Honoring it would
silently change where (and whether) the seams sit.
- Log swallowed agent_shutdown emission failures (logger resolved the
same way the agent resolves its own: services, then the chat
client, then null) so incomplete session trails are trackable;
OutOfMemoryException stays unswallowed. The swallow remains
correct: the run's own outcome is already propagating and the
trail closure is best-effort by contract.
89th test: UseProvidedChatClientAsIs rejection.
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>
* build(dotnet): attribute the Agent-Hooks protocol in the package identity
Per review:
- Title per suggestion: 'Microsoft Agent Framework - Responsible AI
Agent-Hooks Protocol Support'; description names the protocol
precisely (AGENT-HOOKS-0.1, maintained by the Responsible AI
project at github.com/responsibleai/agent-hooks) so the package
reads as protocol support, not a MAF-owned feature; tags aligned.
- Drop the [Experimental] attributes: per repo convention the
attribute gates unstable surface inside released packages
(Harness, core), while pre-release packages (Valkey and Mcp at
alpha, Mem0 and LocalCodeAct at preview) carry none — the version
suffix is the maturity signal.
- Drop the describing comment on the central package version entry.
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>
* refactor(dotnet): split agent-hooks test fixtures into Support files
Per review: one type per file under Support/ (mock client, guards,
recording providers, helpers), matching the src-side convention; pure
mechanical split, flat namespace.
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>
---------
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>
* Python: Preserve structured instructions when merging chat options
`instructions` is declared as `str` on `ChatOptions`, but chat clients may widen it
to a provider-native structured form. Three merge paths combined it with an f-string,
which coerced any non-string value to its `repr`, turning structured metadata into
literal text before any client could see it:
- `merge_chat_options` (`_types.py`)
- `_merge_options` (`_agents.py`, agent defaults + per-run options)
- provider-contributed instructions in `_prepare_session_and_messages` (`_agents.py`)
The last of these is the reported case: once any context provider (for example
`SkillsProvider`) contributes instructions, structured instructions were replaced by
their `repr`, so the model received Python dict syntax as its system prompt and
Anthropic prompt caching silently stopped working.
Add a shared `_append_instructions` helper that concatenates strings as before and
otherwise extends element-wise, always appending so the leading portion stays
unchanged for providers that treat it as a stable, structure-sensitive prefix. A lone
mapping is treated as a single element rather than iterated into its keys.
On the Anthropic side, `_extract_structured_instructions` now normalizes bare strings
into text blocks, since appended instructions arrive alongside caller-supplied blocks.
Fixes#7700
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f428b149-a306-484a-b423-4e9de69f0b90
* Python: address review feedback on structured instructions fix
Parameterize the Anthropic regression test over both the with- and
without-SkillsProvider configurations so the structure-preserving behavior
is asserted in the baseline case too.
Normalize structured instructions in `_get_instructions_from_options` so
telemetry records the instruction text for provider-native block shapes,
extracting only `text` values to keep provider metadata out of spans.
Use `cast` for the structured `default_options` in both regression tests so
the test type checkers resolve the client options type correctly.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f428b149-a306-484a-b423-4e9de69f0b90
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
Copilot-Session: f428b149-a306-484a-b423-4e9de69f0b90
HandoffAgentExecutor clones each participant agent to attach handoff
tools, but the clone rebuilt the Agent without forwarding
additional_properties, so middleware and integrations observing
context.agent.additional_properties during handoff runs saw an empty
dict while the original agent retained its configuration.
Pass a deepcopy of the original agent's additional_properties into the
clone so handoff-executed agents keep their configured metadata and the
original agent stays untouched.
Fixes#7750
* .NET: Migrate 6 hosted-agent samples to source (ZIP) deploy
Extend the source (ZIP) deploy pattern established for Hosted-ChatClientAgent to Hosted-LocalTools, Hosted-Workflow-Simple, Hosted-TextRag, Hosted-Observability, Hosted-Files and Hosted-FoundryAgent. Each gains an azure.yaml (codeConfiguration/remote_build, ASPNETCORE_URLS, model env) and the canonical .agentignore, a self-contained csproj (single target, CPM opt-out, explicit published package versions, AgentFrameworkVersion), a Program.cs that drops the shared contributor scaffolding for DefaultAzureCredential, an updated .env.example and README, and drops the container-mode files (agent.yaml, agent.manifest.yaml, Dockerfile, Dockerfile.contributor). LocalTools, Workflow-Simple, TextRag, Observability and Files were verified deploying live via remote_build; Workflow-Simple returns a workflow runtime error at invoke that is unrelated to the deploy mode.
* .NET: Migrate Hosted-Invocations-EchoAgent and Hosted-LocalCodeAct to source (ZIP) deploy
EchoAgent (Invocations protocol) and LocalCodeAct migrated to the zip/code-deploy pattern (azure.yaml, .agentignore, self-contained csproj, README, container files removed). EchoAgent maps /readiness explicitly because the Invocations SDK does not auto-map it. Both verified live via remote_build on a Foundry project; LocalCodeAct's execute_code ran server-side (compute 21+21 -> 42).
* .NET: Migrate remaining hosted-agent samples to source (ZIP) deploy
Migrate Hosted-McpTools, Hosted-MemoryAgent, Hosted-AgentSkills, Hosted-AzureSearchRag, Hosted-Toolbox, Hosted-Toolbox-AuthPaths and Hosted-ToolboxMcpSkills to the zip/code-deploy pattern (azure.yaml with codeConfiguration + sample-specific env passthrough, canonical .agentignore, self-contained csproj, Program.cs dropping the shared contributor scaffolding for DefaultAzureCredential, updated .env.example and README, container files removed). Also restore the Hosted-Invocations-EchoAgent csproj filename the solution references. McpTools verified live via remote_build against the public Microsoft Learn MCP server; the memory/search/toolbox/skills samples build locally and deploy via remote_build but need their external resources (memory store, search index, toolbox connections, skills) provisioned to exercise end to end.
* .NET: Migrate Hosted-Workflow-Handoff to source (ZIP) deploy
Migrate the triage handoff workflow sample to the zip/code-deploy pattern (azure.yaml with codeConfiguration and Azure OpenAI env passthrough, canonical .agentignore, self-contained csproj using AgentFrameworkVersion for Foundry/Foundry.Hosting/Hosting, Program.cs dropping the shared contributor scaffolding for DefaultAzureCredential, updated .env.example and README, container files removed). Builds via remote_build; live needs an Azure OpenAI resource (AZURE_OPENAI_ENDPOINT/AZURE_OPENAI_DEPLOYMENT).
* .NET: Copy Hosted-AgentSkills skills/ into build output
The startup provisioning helper reads SKILL.md files from AppContext.BaseDirectory/skills, but the project did not copy the skills/ folder to the build/publish output, so at runtime the source directory did not exist and provisioning was silently skipped. Add a Content include (PreserveNewest), matching the resources/ pattern already used by Hosted-Files.
* .NET: Suppress OPENAI001 in Hosted-Workflow-Handoff for standalone ZIP build
The repo-wide Directory.Build.props suppresses OPENAI001, but that file does
not travel in the code/ZIP deploy package. The standalone dotnet publish the
Foundry code deploy runs then fails with error OPENAI001 on the experimental
GetResponsesClient().AsIChatClient() call. Add OPENAI001 to the project NoWarn
so the sample builds in the code-deploy pipeline, matching SimpleAgent.csproj.
* .NET: Document live-verified idiosyncrasies in Foundry hosted sample READMEs
Align every FoundryHostedAgents sample README with the documented azd flow and
add the idiosyncrasies found while live-testing each sample on a Foundry project:
- All samples: 'azd down' reports success but does not delete the hosted agent;
document the explicit REST DELETE needed to remove it.
- Hosted-Workflow-Handoff: it builds its own AzureOpenAIClient (data-plane), so
the agent identity needs the 'Cognitive Services OpenAI User' role on the
Azure OpenAI account. azd only grants 'Foundry User' on the project, so add a
step to grant the data-plane role and explain the triage-step failure without it.
- Hosted-Toolbox / Toolbox-AuthPaths / ToolboxMcpSkills: the toolbox must already
exist and the agent identity must be able to read it; toolboxes with OAuth-gated
tools return an oauth_consent_request and response.incomplete on first invoke.
* .NET: Address Foundry hosted sample review feedback
Make sample configuration reject blank azd substitutions and document every required environment value inside the scaffolded project flow.
Separate the hosted endpoint name from the Foundry managed prompt-agent name, fix standalone MemoryAgent diagnostics, and complete the contributor local package feed for Hosting, LocalCodeAct, and MCP.
Use azd for agent invocation and az rest for authenticated administration without exposing tokens. Add native MCP approval handling to the toolbox consent client and make its local path target the standard responses endpoint.
Validated all changed samples locally, the contributor flow in PowerShell and Bash, and the supported live scenarios on the TAO cace project.
* .NET: Fix advanced hosted sample project access
Document and validate the Foundry User grant required by hosted version identities that access project data plane APIs.
Add the Skills preview feature header and use a writable temporary directory for downloaded skills because source deployments mount the application directory read only.
Update AgentSkills, MemoryAgent, FoundryAgent, and ToolboxMcpSkills deployment guides with the post deploy identity grant. All four scenarios passed live on the TAO cace project.
* Simplify AG-UI Step04 human-in-the-loop sample to idiomatic pattern
The Step04 sample previously wrapped both the server and client agents in
custom ServerFunctionApproval*Agent middleware (~470 lines across two files)
to marshal a bespoke approval protocol over AG-UI. This is no longer needed:
MapAGUIServer natively emits the tool-approval interrupt when the model calls
an ApprovalRequiredAIFunction, and AGUIChatClient natively transports the
client's ToolApprovalResponseContent decision back to resume the run.
Changes:
- Server: map the ChatClientAgent directly with MapAGUIServer; remove the
ServerFunctionApprovalAgent wrapper, the JsonOptions plumbing, and the
ApprovalJsonContext registration.
- Client: use the AGUIChatClient-backed agent directly; the existing loop
already handles ToolApprovalRequestContent -> CreateResponse idiomatically.
- Delete ServerFunctionApprovalServerAgent.cs and
ServerFunctionApprovalClientAgent.cs.
Verified end-to-end (approval request -> approve -> tool executes -> final
response) against GitHub Models. Both projects build with 0 warnings.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: eeb2168d-2ecc-4f8d-9830-c287072eb7e1
* Update AG-UI Step04 README to describe native approval flow
The Step04 human-in-the-loop sample no longer uses the custom ServerFunctionApprovalServerAgent / ServerFunctionApprovalClientAgent wrappers. Update the README so it describes the idiomatic native flow: the server maps a plain agent with MapAGUIServer and relies on ApprovalRequiredAIFunction to raise the approval interrupt, and the client handles ToolApprovalRequestContent and replies with ToolApprovalResponseContent.
* Fix AG-UI Step04 README server port to match client default
The Step04 client defaults to http://localhost:5100 (and the server launchSettings also uses 5100), but the README told users to run the server on port 8888, so the client could not reach it. Align the Step04 server run command to 5100. Other steps intentionally keep 8888 because their clients default to that port.
* Update AG-UI .NET samples for latest MAF + AG-UI SDK and align with docs
- Bump AGUI.* packages 0.0.3 to 0.0.4 (Directory.Packages.props)
- Step01/02/03: drop AddHttpClient().AddLogging() server noise and simplify the
client run-started output to match the getting-started doc (no thread plumbing)
- Step04 (HITL): remove HTTP body logging and MEAI001 pragmas, give the approval
tool an explicit name, and align the resume decision message with the doc
- Step05 (state): replace the custom SharedStateAgent/StatefulAgent DataContent
pattern (dropped by released AGUI.Server) with declarative
AGUIStreamOptions.MapResultAsStateSnapshot plus a thin RecipeStateAgent that
reads RunAgentInput.State, and align the Recipe models with the docs
- Refresh README to the shipped API (MapAGUIServer, ApprovalRequiredAIFunction,
declarative state)
Verified: all 10 sample projects build; Step04 approval/resume and Step05 state
snapshot round-trip run end-to-end against GitHub Models.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: eeb2168d-2ecc-4f8d-9830-c287072eb7e1
* Name the Step02 backend tool search_restaurants to match the docs
Give the SearchRestaurants tool an explicit "search_restaurants" name so the
client displays an accurate tool name (not a compiler-mangled local-function
name) and stays aligned with the backend-tool-rendering doc.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: eeb2168d-2ecc-4f8d-9830-c287072eb7e1
* Add UTF-8 BOM to Step05 sample files to satisfy check-format
The check-format CI job enforces the repository's utf-8-bom charset rule via
dotnet format. The Step05 files added in this PR were saved without a BOM.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: eeb2168d-2ecc-4f8d-9830-c287072eb7e1
* Fix AG-UI sample conversation history
Let AgentSession own prior messages so clients send only each new turn, and give the frontend location tool a stable protocol name.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Roger Barreto <19890735+RogerBarreto@users.noreply.github.com>
Copilot-Session: eeb2168d-2ecc-4f8d-9830-c287072eb7e1
* fix: prevent superlinear history growth by deduplicating messages in save_messages
* fix: address review feedback for history deduplication
* fix: Prevent superlinear history growth by deduplicating messages
* fix: add list[Message] type hints
* fix(sessions): resolve deduplication churn and collapsing of identical message
* fix(sessions): replace uuid/seen-set dedup with sequence aware filtering
* fix: use forward-scan sequence alignment in filter_new_messages
* fix(core): annotate new_msgs type to resolve pyright errors
---------
Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com>
Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
AG-UI clients send plain JSON, but structured response types were only
accepted as already-built instances, and core's coercion stopped at the
outer object, letting raw dicts sit inside typed fields. Coercion now
walks declared annotations and returns the input untouched whenever it
cannot satisfy them.
* feat(core): first-class fatal signal (MiddlewareFailure) for function middleware
The function-invocation loop converts every exception raised by
function middleware into a tool-error result and keeps looping, so
middleware that needs fail-closed semantics (enforcement layers,
guardrails) had no loud escape: the agent-hooks feature simulated one
by mutating shared run state, raising MiddlewareTermination, and
re-raising the real failure two hops away at the run boundary.
Introduce MiddlewareFailure (a MiddlewareException sibling of
MiddlewareTermination) as the loop's explicit fail-closed escape:
- _auto_invoke_function re-raises it (both the direct and the
pipeline path) instead of absorbing it into a tool-error result;
ordinary exceptions keep the absorb-and-continue contract.
- A failing call fails the whole parallel batch: in-flight sibling
tool tasks are cancelled and awaited before the failure propagates.
- Every existing MiddlewareTermination absorb site (agent/chat
pipelines, _execute_single_function_call, harness loop, purview)
passes it through untouched by construction, and agent/chat
middleware exceptions already propagate, so one exception type
gives uniform fail-loud semantics across all three categories.
Migrate the agent-hooks feature to the new signal: delete the
_RunState.halted back-channel and its three run-boundary re-raise
checks, drop the halted arm of the termination special case in the
function middleware (the approval-request pass-through moves to the
single approval check on the normal path), and fail partial installs
loudly. Tool-seam host_error blocks keep surfacing as
InterceptionBlocked at the run boundary via the exception cause chain
(one deny surface at every seam, pinned by tests).
Spec 004 gains the middleware-failure invariants and matrix rows.
Closes#7522
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>
* fix(core): harden tool-seam unwrap and pin review findings
Review round follow-ups for the MiddlewareFailure feature:
- Only agent-hooks' own tagged tool-seam halts (_ToolSeamBlockFailure)
authorize re-raising the chained InterceptionBlocked at the run
boundary; a third-party MiddlewareFailure with a crafted
InterceptionBlocked cause now propagates as raised instead of
laundering an attacker-shaped interception record into the feature's
deny surface (regression test added, verified by mutation).
- Document that middleware must not catch MiddlewareFailure (docstring
and spec 004): swallowing it converts a fail-closed abort back into
a running, possibly unguarded loop.
- Pin the trailing termination re-raise in the agent-hooks function
middleware: an inner short-circuit is bracketed and still propagates,
skipping outer middleware post-code (test fails with the re-raise
removed).
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>
* fix(core): acyclic tool-seam unwrap chain; document cooperative batch cancellation
Address two automated-review findings on the MiddlewareFailure PR,
both confirmed empirically:
- _reraise_tool_seam_block created a two-object exception-chain cycle
(block.__cause__ -> wrapper -> block) by re-raising the chained
InterceptionBlocked `from` its transport wrapper. Detach the
wrapper's back-links and re-raise bare, recording the wrapper as
the block's __context__ — acyclic, both exceptions still visible in
tracebacks. Regression test walks the chain and pins finiteness
(verified to fail against the cyclic re-raise).
- Batch cancellation is cooperative: a synchronous tool body already
running in a worker thread (asyncio.to_thread) cannot be interrupted
by task cancellation and may complete its side effects after the
failure reached the caller; its result is discarded either way and
propagation is not delayed behind it. Narrow the stated contract
(MiddlewareFailure docstring, loop comment, spec 004) and pin it
with a blocking-sync-sibling regression test.
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>
* fix(core): settle dangling calls on service-managed conversations on abort
Address maintainer review on the MiddlewareFailure PR:
- A MiddlewareFailure escaping a tool batch on a service-managed
conversation left the hosted thread ending in unresolved
function_call items: _update_continuation_state persists
session.service_session_id when the model turn completes (before
tool execution), and probe-verified the next run sends only the new
user message against that conversation — OpenAI-style continuations
reject such a request, so a routine policy abort left the session
permanently stuck. Both loops now settle the thread before
propagating: one error function_result per dangling call, submitted
with tool_choice="none" in a single extra request whose response is
discarded; a settlement failure never masks the abort, and runs
without a service-managed conversation make no extra request.
Pinned by three regression tests (non-streaming, streaming, and the
no-conversation no-cost case); spec 004 and the MiddlewareFailure
docstring updated.
- Make the three tool-bracket escape tuples in the agent-hooks
function middleware identical (MiddlewareTermination,
MiddlewareFailure, CancelledError): a MiddlewareFailure raised
inside the post/error-bracket emit bodies is unreachable today, but
the uniform tuples remove the need to reason about why they would
differ, and preserve the exact exception (including the private
tool-seam tag) if the emitter ever surfaces one.
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>
* fix(core): advance settled continuation; settle approved-replay aborts
Address maintainer review on the MiddlewareFailure settlement path,
both probe-verified (branch rebased onto current main first):
- Advance the persisted continuation to the settlement response. For
response-ID continuations (OpenAI Responses store=True, where the
response id is the continuation handle) the settlement response is
the first endpoint whose chain includes the synthetic tool outputs;
leaving session.service_session_id on the pre-settlement response
made the settlement ineffective — the next run would continue from
the still-unresolved turn. The settlement response now runs through
_update_function_invocation_continuation_state (a no-op for stable
conversation-object ids). Pinned by a regression test that fails
with the advance removed.
- Cover the approval-resolution phase: a MiddlewareFailure raised
while an approved tool is replayed escapes loudly (probe-verified,
already the case) but executed before the loops' settlement seams,
leaving the original — already service-persisted — call unresolved.
_resolve_approval_responses now takes a settle_dangling_calls
callback invoked with the approved batch on abort; the settlement
helper became a layer method taking explicit calls
(approval-response wrappers unwrap to their underlying calls,
hosted-tool approvals are left to their provider protocol) and
carries its own best-effort containment. Pinned by deny-during-
replay regression tests in both response modes, mutation-verified.
Spec 004 invariants and matrix rows updated accordingly.
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>
---------
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>
Registers the repository with Engineering System inventory via the
InventoryAsCode provider, mapping it to its Service Tree service and
routing compliance work items to the owning team.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 05291438-8e37-49d6-84b6-5ffb7814abb8
All four AddAIAgent overloads in AgentHostingServiceCollectionExtensions
created a ChatClientAgent without forwarding the IServiceProvider, so the
FunctionInvokingChatClient in the agent's pipeline had no service provider
and tools could not resolve their dependencies at invocation time.
Fixes#4453
Co-authored-by: Max Montes Soza <max-montes@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Guard the hosted storage error log before evaluating the agent name and update the SDK to the servicing release containing the net9 ILLink analyzer fix.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: e3a6cce8-e1e8-4cf2-9cf4-3c0c8f3ed6d8
* fix(workflows): preserve all trace contexts in FanInEdgeRunner aggregation
FanInEdgeRunner collected trace contexts and source span IDs using the
singular backward-compat properties (msg.trace_context / msg.source_span_id),
which return only the first element of the plural lists. When a message
arriving at a fan-in already carries multiple trace contexts (e.g. from
a prior fan-in aggregation), all but the first were silently dropped.
Iterate over the plural fields (trace_contexts / source_span_ids) and
extend the aggregated lists so every trace context and source span ID
from every source message is preserved. This keeps distributed tracing
links intact for nested fan-in topologies.
Added test_fan_in_preserves_multiple_trace_contexts_per_message that
sends a message with two trace contexts through a fan-in and asserts
all three contexts (2 + 1) reach the target executor.
* fix: address Copilot review comments on trace context aggregation
1. Pair trace_contexts and source_span_ids per-message (via zip) instead
of flattening independently. This prevents misalignment when a message
has mismatched counts — orphans are dropped per-message rather than
shifting all subsequent pairs out of alignment.
2. Remove TraceCapturingAggregator's override of Executor.execute()
(documented as "do not override"). Capture trace data from the
WorkflowContext passed to the handler instead.
---------
Co-authored-by: weed33834 <weed33834@users.noreply.github.com>
Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
Resolve request-info type names only from exact caller-provided mappings or already-loaded module namespaces. Remove payload-selected imports and add focused regression coverage for both request and response type fields.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a53fe20b-c3f0-4583-badc-d5deac7c1049
* .NET: Add Foundry session and user identity pass-through
Let user agents pin hosted agent_session_id on AgentSession and
send x-ms-user-identity per call for Foundry hosted agents.
* .NET: Add live ITs for Foundry session and user identity
Cover service-managed and admin-pinned hosted sandboxes, sticky
hosted session id, and per-call x-ms-user-identity isolation with
separate AgentSessions sharing one sandbox. Echo container avoids
model quota for identity assertions.
* .NET: Reject Foundry hosted session switch when sticky
Persist sticky id in finally, clone run options before factory wrap,
validate whitespace pin on CreateHostedSessionAsync, and throw on
unexpected hosted session id change in the response. Docs: distinct
AgentSessions per user identity may share one sandbox.
* .NET: Clear nested user identity and preserve run options
Always assign UserIdentityScope including null so nested runs do not
inherit a parent identity. When upgrading plain AgentRunOptions, keep
background, format, and additional properties on the specialized clone.
* .NET: Clarify previous_response_id user binding in docs
Align WithUserIdentity guidance with Foundry Learn multiplex docs:
response chains are bound to the creating user even inside a shared
hosted sandbox.
* refactor(foundry): clarify hosted agent APIs
The ag-ui-protocol `InputContentUrlSource` carries the URL in `source.value`,
but `_extract_multimodal_source_fields` only read `source.url`/`source.uri`
for url-typed sources, so attachments sent in the spec shape were dropped
during the AG-UI to MAF conversion. The base64 branch already read
`source.value` correctly.
Read `source.value` first, keeping `url`/`uri` as fallbacks for the non-spec
shape. Adds tests for both.
Fixes#7653