diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-Invocations-EchoAgent/Dockerfile b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-Invocations-EchoAgent/Dockerfile
new file mode 100644
index 0000000000..24585dec12
--- /dev/null
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-Invocations-EchoAgent/Dockerfile
@@ -0,0 +1,17 @@
+# Use the official .NET 10.0 ASP.NET runtime as a parent image
+FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base
+WORKDIR /app
+
+FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
+WORKDIR /src
+COPY . .
+RUN dotnet restore
+RUN dotnet publish -c Release -o /app/publish
+
+# Final stage
+FROM base AS final
+WORKDIR /app
+COPY --from=build /app/publish .
+EXPOSE 8088
+ENV ASPNETCORE_URLS=http://+:8088
+ENTRYPOINT ["dotnet", "HostedInvocationsEchoAgent.dll"]
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-Invocations-EchoAgent/Dockerfile.contributor b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-Invocations-EchoAgent/Dockerfile.contributor
new file mode 100644
index 0000000000..91a403c26c
--- /dev/null
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-Invocations-EchoAgent/Dockerfile.contributor
@@ -0,0 +1,19 @@
+# Dockerfile for contributors building from the agent-framework repository source.
+#
+# This project uses ProjectReference to the local Microsoft.Agents.AI.Abstractions source,
+# which means a standard multi-stage Docker build cannot resolve dependencies outside
+# this folder. Instead, pre-publish the app targeting the container runtime and copy
+# the output into the container:
+#
+# dotnet publish -c Debug -f net10.0 -r linux-musl-x64 --self-contained false -o out
+# docker build -f Dockerfile.contributor -t hosted-invocations-echo-agent .
+# docker run --rm -p 8088:8088 hosted-invocations-echo-agent
+#
+# For end-users consuming the NuGet package (not ProjectReference), use the standard
+# Dockerfile which performs a full dotnet restore + publish inside the container.
+FROM mcr.microsoft.com/dotnet/aspnet:10.0-alpine AS final
+WORKDIR /app
+COPY out/ .
+EXPOSE 8088
+ENV ASPNETCORE_URLS=http://+:8088
+ENTRYPOINT ["dotnet", "HostedInvocationsEchoAgent.dll"]
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-Invocations-EchoAgent/EchoAIAgent.cs b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-Invocations-EchoAgent/EchoAIAgent.cs
new file mode 100644
index 0000000000..ccbfe72781
--- /dev/null
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-Invocations-EchoAgent/EchoAIAgent.cs
@@ -0,0 +1,85 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Runtime.CompilerServices;
+using System.Text.Json;
+using Microsoft.Extensions.AI;
+
+namespace Microsoft.Agents.AI;
+
+///
+/// A minimal that echoes the user's input text back as the response.
+/// No LLM or external service is required.
+///
+public sealed class EchoAIAgent : AIAgent
+{
+ ///
+ public override string Name => "echo-agent";
+
+ ///
+ public override string Description => "An agent that echoes back the input message.";
+
+ ///
+ protected override Task RunCoreAsync(
+ IEnumerable messages,
+ AgentSession? session = null,
+ AgentRunOptions? options = null,
+ CancellationToken cancellationToken = default)
+ {
+ var inputText = GetInputText(messages);
+ var response = new AgentResponse(new ChatMessage(ChatRole.Assistant, $"Echo: {inputText}"));
+ return Task.FromResult(response);
+ }
+
+ ///
+ protected override async IAsyncEnumerable RunCoreStreamingAsync(
+ IEnumerable messages,
+ AgentSession? session = null,
+ AgentRunOptions? options = null,
+ [EnumeratorCancellation] CancellationToken cancellationToken = default)
+ {
+ var inputText = GetInputText(messages);
+ yield return new AgentResponseUpdate
+ {
+ Role = ChatRole.Assistant,
+ Contents = [new TextContent($"Echo: {inputText}")],
+ };
+
+ await Task.CompletedTask;
+ }
+
+ ///
+ protected override ValueTask CreateSessionCoreAsync(CancellationToken cancellationToken = default)
+ => new(new EchoAgentSession());
+
+ ///
+ protected override ValueTask SerializeSessionCoreAsync(
+ AgentSession session,
+ JsonSerializerOptions? jsonSerializerOptions = null,
+ CancellationToken cancellationToken = default)
+ => new(JsonSerializer.SerializeToElement(new { }, jsonSerializerOptions));
+
+ ///
+ protected override ValueTask DeserializeSessionCoreAsync(
+ JsonElement serializedState,
+ JsonSerializerOptions? jsonSerializerOptions = null,
+ CancellationToken cancellationToken = default)
+ => new(new EchoAgentSession());
+
+ private static string GetInputText(IEnumerable messages)
+ {
+ foreach (var message in messages)
+ {
+ if (message.Role == ChatRole.User)
+ {
+ return message.Text ?? string.Empty;
+ }
+ }
+
+ return string.Empty;
+ }
+
+ ///
+ /// Minimal session for the echo agent. No state is persisted.
+ ///
+ private sealed class EchoAgentSession : AgentSession;
+}
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-Invocations-EchoAgent/EchoInvocationHandler.cs b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-Invocations-EchoAgent/EchoInvocationHandler.cs
new file mode 100644
index 0000000000..9834e7577a
--- /dev/null
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-Invocations-EchoAgent/EchoInvocationHandler.cs
@@ -0,0 +1,33 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using Azure.AI.AgentServer.Invocations;
+using Microsoft.Agents.AI;
+using Microsoft.AspNetCore.Http;
+
+namespace HostedInvocationsEchoAgent;
+
+///
+/// An that reads the request body as plain text,
+/// passes it to the , and writes the response back.
+///
+public sealed class EchoInvocationHandler(EchoAIAgent agent) : InvocationHandler
+{
+ ///
+ public override async Task HandleAsync(
+ HttpRequest request,
+ HttpResponse response,
+ InvocationContext context,
+ CancellationToken cancellationToken)
+ {
+ // Read the raw text from the request body.
+ using var reader = new StreamReader(request.Body);
+ var input = await reader.ReadToEndAsync(cancellationToken);
+
+ // Run the echo agent with the input text.
+ var agentResponse = await agent.RunAsync(input, cancellationToken: cancellationToken);
+
+ // Write the agent response text back to the HTTP response.
+ response.ContentType = "text/plain";
+ await response.WriteAsync(agentResponse.Text, cancellationToken);
+ }
+}
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-Invocations-EchoAgent/Hosted-Invocations-EchoAgent.csproj b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-Invocations-EchoAgent/Hosted-Invocations-EchoAgent.csproj
new file mode 100644
index 0000000000..b84faccf9e
--- /dev/null
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-Invocations-EchoAgent/Hosted-Invocations-EchoAgent.csproj
@@ -0,0 +1,29 @@
+
+
+
+ net10.0
+ enable
+ enable
+ false
+ HostedInvocationsEchoAgent
+ HostedInvocationsEchoAgent
+ $(NoWarn);
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-Invocations-EchoAgent/Program.cs b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-Invocations-EchoAgent/Program.cs
new file mode 100644
index 0000000000..1650253dfc
--- /dev/null
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-Invocations-EchoAgent/Program.cs
@@ -0,0 +1,24 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using Azure.AI.AgentServer.Invocations;
+using HostedInvocationsEchoAgent;
+using Microsoft.Agents.AI;
+
+var builder = WebApplication.CreateBuilder(args);
+
+// Register the echo agent as a singleton (no LLM needed).
+builder.Services.AddSingleton();
+
+// Register the Invocations SDK services and wire the handler.
+builder.Services.AddInvocationsServer();
+builder.Services.AddScoped();
+
+var app = builder.Build();
+
+// Map the Invocations protocol endpoints:
+// POST /invocations — invoke the agent
+// GET /invocations/{id} — get result (not used by this sample)
+// POST /invocations/{id}/cancel — cancel (not used by this sample)
+app.MapInvocationsServer();
+
+app.Run();
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-Invocations-EchoAgent/Properties/launchSettings.json b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-Invocations-EchoAgent/Properties/launchSettings.json
new file mode 100644
index 0000000000..7722815b12
--- /dev/null
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-Invocations-EchoAgent/Properties/launchSettings.json
@@ -0,0 +1,11 @@
+{
+ "profiles": {
+ "Hosted-Invocations-EchoAgent": {
+ "commandName": "Project",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "applicationUrl": "http://localhost:8088"
+ }
+ }
+}
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-Invocations-EchoAgent/README.md b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-Invocations-EchoAgent/README.md
new file mode 100644
index 0000000000..7133c43cf1
--- /dev/null
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-Invocations-EchoAgent/README.md
@@ -0,0 +1,66 @@
+# Hosted-Invocations-EchoAgent
+
+A minimal echo agent hosted as a Foundry Hosted Agent using the **Invocations protocol**. The agent reads the request body as plain text, passes it through a custom `EchoAIAgent`, and writes the echoed text back in the response. No LLM or Azure credentials are required.
+
+## Prerequisites
+
+- [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0)
+
+## Running directly (contributors)
+
+This project uses `ProjectReference` to build against the local Agent Framework source.
+
+```bash
+cd dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-Invocations-EchoAgent
+dotnet run
+```
+
+The agent will start on `http://localhost:8088`.
+
+### Test it
+
+```bash
+curl -X POST http://localhost:8088/invocations \
+ -H "Content-Type: text/plain" \
+ -d "Hello, world!"
+```
+
+Expected response:
+
+```
+Echo: Hello, world!
+```
+
+## Running with Docker
+
+Since this project uses `ProjectReference`, the standard `Dockerfile` cannot resolve dependencies outside this folder. Use `Dockerfile.contributor` which takes a pre-published output.
+
+### 1. Publish for the container runtime (Linux Alpine)
+
+```bash
+dotnet publish -c Debug -f net10.0 -r linux-musl-x64 --self-contained false -o out
+```
+
+### 2. Build the Docker image
+
+```bash
+docker build -f Dockerfile.contributor -t hosted-invocations-echo-agent .
+```
+
+### 3. Run the container
+
+```bash
+docker run --rm -p 8088:8088 hosted-invocations-echo-agent
+```
+
+### 4. Test it
+
+```bash
+curl -X POST http://localhost:8088/invocations \
+ -H "Content-Type: text/plain" \
+ -d "Hello from Docker!"
+```
+
+## NuGet package users
+
+If you are consuming the Agent Framework as a NuGet package (not building from source), use the standard `Dockerfile` instead of `Dockerfile.contributor`. See the commented section in `Hosted-Invocations-EchoAgent.csproj` for the `PackageReference` alternative.
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-Invocations-EchoAgent/agent.manifest.yaml b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-Invocations-EchoAgent/agent.manifest.yaml
new file mode 100644
index 0000000000..09e4b0f885
--- /dev/null
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-Invocations-EchoAgent/agent.manifest.yaml
@@ -0,0 +1,27 @@
+# yaml-language-server: $schema=https://raw.githubusercontent.com/microsoft/AgentSchema/refs/heads/main/schemas/v1.0/AgentManifest.yaml
+name: hosted-invocations-echo-agent
+displayName: "Hosted Invocations Echo Agent"
+
+description: >
+ A minimal echo agent hosted as a Foundry Hosted Agent using the Invocations
+ protocol. Reads the request body as plain text, echoes it back in the response.
+
+metadata:
+ tags:
+ - AI Agent Hosting
+ - Azure AI AgentServer
+ - Invocations Protocol
+ - Agent Framework
+
+template:
+ name: hosted-invocations-echo-agent
+ kind: hosted
+ protocols:
+ - protocol: invocations
+ version: 1.0.0
+ resources:
+ cpu: "0.25"
+ memory: 0.5Gi
+parameters:
+ properties: []
+resources: []
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-Invocations-EchoAgent/agent.yaml b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-Invocations-EchoAgent/agent.yaml
new file mode 100644
index 0000000000..001a19f0ac
--- /dev/null
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-Invocations-EchoAgent/agent.yaml
@@ -0,0 +1,9 @@
+# yaml-language-server: $schema=https://raw.githubusercontent.com/microsoft/AgentSchema/refs/heads/main/schemas/v1.0/ContainerAgent.yaml
+kind: hosted
+name: hosted-invocations-echo-agent
+protocols:
+ - protocol: invocations
+ version: 1.0.0
+resources:
+ cpu: "0.25"
+ memory: 0.5Gi
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Using-Samples/SimpleInvocationsAgent/InvocationsAIAgent.cs b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Using-Samples/SimpleInvocationsAgent/InvocationsAIAgent.cs
new file mode 100644
index 0000000000..54f8022d1b
--- /dev/null
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Using-Samples/SimpleInvocationsAgent/InvocationsAIAgent.cs
@@ -0,0 +1,129 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Runtime.CompilerServices;
+using System.Text.Json;
+using Microsoft.Extensions.AI;
+
+namespace Microsoft.Agents.AI;
+
+///
+/// An that invokes a remote agent hosted with the Invocations protocol
+/// by sending plain-text HTTP POST requests to the /invocations endpoint.
+///
+public sealed class InvocationsAIAgent : AIAgent
+{
+ private readonly HttpClient _httpClient;
+ private readonly Uri _invocationsUri;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The base URI of the hosted agent (e.g., http://localhost:8089).
+ /// The /invocations path is appended automatically.
+ ///
+ /// Optional to use. If , a new instance is created.
+ /// Optional name for the agent.
+ /// Optional description for the agent.
+ public InvocationsAIAgent(
+ Uri agentEndpoint,
+ HttpClient? httpClient = null,
+ string? name = null,
+ string? description = null)
+ {
+ ArgumentNullException.ThrowIfNull(agentEndpoint);
+
+ this._httpClient = httpClient ?? new HttpClient();
+
+ // Ensure the base URI ends with a slash so that combining works correctly.
+ var baseUri = agentEndpoint.AbsoluteUri.EndsWith('/')
+ ? agentEndpoint
+ : new Uri(agentEndpoint.AbsoluteUri + "/");
+ this._invocationsUri = new Uri(baseUri, "invocations");
+
+ this.Name = name ?? "invocations-agent";
+ this.Description = description ?? "An agent that calls a remote Invocations protocol endpoint.";
+ }
+
+ ///
+ public override string? Name { get; }
+
+ ///
+ public override string? Description { get; }
+
+ ///
+ protected override async Task RunCoreAsync(
+ IEnumerable messages,
+ AgentSession? session = null,
+ AgentRunOptions? options = null,
+ CancellationToken cancellationToken = default)
+ {
+ var inputText = GetLastUserText(messages);
+ var responseText = await SendInvocationAsync(inputText, cancellationToken).ConfigureAwait(false);
+ return new AgentResponse(new ChatMessage(ChatRole.Assistant, responseText));
+ }
+
+ ///
+ protected override async IAsyncEnumerable RunCoreStreamingAsync(
+ IEnumerable messages,
+ AgentSession? session = null,
+ AgentRunOptions? options = null,
+ [EnumeratorCancellation] CancellationToken cancellationToken = default)
+ {
+ // The Invocations protocol returns a complete response (no SSE streaming),
+ // so we yield a single update with the full text.
+ var inputText = GetLastUserText(messages);
+ var responseText = await SendInvocationAsync(inputText, cancellationToken).ConfigureAwait(false);
+
+ yield return new AgentResponseUpdate
+ {
+ Role = ChatRole.Assistant,
+ Contents = [new TextContent(responseText)],
+ };
+ }
+
+ ///
+ protected override ValueTask CreateSessionCoreAsync(CancellationToken cancellationToken = default)
+ => new(new InvocationsAgentSession());
+
+ ///
+ protected override ValueTask SerializeSessionCoreAsync(
+ AgentSession session,
+ JsonSerializerOptions? jsonSerializerOptions = null,
+ CancellationToken cancellationToken = default)
+ => new(JsonSerializer.SerializeToElement(new { }, jsonSerializerOptions));
+
+ ///
+ protected override ValueTask DeserializeSessionCoreAsync(
+ JsonElement serializedState,
+ JsonSerializerOptions? jsonSerializerOptions = null,
+ CancellationToken cancellationToken = default)
+ => new(new InvocationsAgentSession());
+
+ private async Task SendInvocationAsync(string input, CancellationToken cancellationToken)
+ {
+ using var content = new StringContent(input, System.Text.Encoding.UTF8, "text/plain");
+ using var response = await this._httpClient.PostAsync(this._invocationsUri, content, cancellationToken).ConfigureAwait(false);
+ response.EnsureSuccessStatusCode();
+ return await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ private static string GetLastUserText(IEnumerable messages)
+ {
+ string? lastUserText = null;
+ foreach (var message in messages)
+ {
+ if (message.Role == ChatRole.User)
+ {
+ lastUserText = message.Text;
+ }
+ }
+
+ return lastUserText ?? string.Empty;
+ }
+
+ ///
+ /// Minimal session for the invocations agent. No state is persisted.
+ ///
+ private sealed class InvocationsAgentSession : AgentSession;
+}
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Using-Samples/SimpleInvocationsAgent/Program.cs b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Using-Samples/SimpleInvocationsAgent/Program.cs
new file mode 100644
index 0000000000..915e73737d
--- /dev/null
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Using-Samples/SimpleInvocationsAgent/Program.cs
@@ -0,0 +1,61 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using DotNetEnv;
+using Microsoft.Agents.AI;
+
+// Load .env file if present (for local development)
+Env.TraversePath().Load();
+
+Uri agentEndpoint = new(Environment.GetEnvironmentVariable("AGENT_ENDPOINT")
+ ?? "http://localhost:8088");
+
+// Create an agent that calls the remote Invocations endpoint.
+InvocationsAIAgent agent = new(agentEndpoint);
+
+// REPL
+Console.ForegroundColor = ConsoleColor.Cyan;
+Console.WriteLine($"""
+ ══════════════════════════════════════════════════════════
+ Simple Invocations Agent Sample
+ Connected to: {agentEndpoint}
+ Type a message or 'quit' to exit
+ ══════════════════════════════════════════════════════════
+ """);
+Console.ResetColor();
+Console.WriteLine();
+
+while (true)
+{
+ Console.ForegroundColor = ConsoleColor.Green;
+ Console.Write("You> ");
+ Console.ResetColor();
+
+ string? input = Console.ReadLine();
+
+ if (string.IsNullOrWhiteSpace(input)) { continue; }
+ if (input.Equals("quit", StringComparison.OrdinalIgnoreCase)) { break; }
+
+ try
+ {
+ Console.ForegroundColor = ConsoleColor.Yellow;
+ Console.Write("Agent> ");
+ Console.ResetColor();
+
+ await foreach (var update in agent.RunStreamingAsync(input))
+ {
+ Console.Write(update);
+ }
+
+ Console.WriteLine();
+ }
+ catch (Exception ex)
+ {
+ Console.ForegroundColor = ConsoleColor.Red;
+ Console.WriteLine($"Error: {ex.Message}");
+ Console.ResetColor();
+ }
+
+ Console.WriteLine();
+}
+
+Console.WriteLine("Goodbye!");
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Using-Samples/SimpleInvocationsAgent/SimpleInvocationsAgent.csproj b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Using-Samples/SimpleInvocationsAgent/SimpleInvocationsAgent.csproj
new file mode 100644
index 0000000000..d509bd2c70
--- /dev/null
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Using-Samples/SimpleInvocationsAgent/SimpleInvocationsAgent.csproj
@@ -0,0 +1,22 @@
+
+
+
+ Exe
+ net10.0
+ enable
+ enable
+ false
+ SimpleInvocationsAgentClient
+ simple-invocations-agent-client
+ $(NoWarn);NU1903;NU1605
+
+
+
+
+
+
+
+
+
+
+