Files
Roger Barreto dc4bafbc1e .NET: Add Hosted-AgentSkills sample with Foundry Skills integration (#6013)
* .NET: Add Hosted-AgentSkills sample for Foundry Skills integration

Add a new hosted agent sample that demonstrates how to load behavioral
guidelines from Foundry Skills at startup using AgentSkillsProvider and
the progressive disclosure pattern (advertise -> load on demand).

The sample:
- Downloads SKILL.md files from Foundry via ProjectAgentSkills SDK
- Extracts ZIP archives with zip-slip protection
- Wires skills into AgentSkillsProvider as an AIContextProvider
- Hosts the agent via the Responses protocol

Ships two Contoso Outdoors skills matching the Python sample (PR #5822):
- support-style: tone, formatting, signature guidelines
- escalation-policy: when and how to escalate tickets

Includes convenience provisioning gated behind PROVISION_SAMPLE_SKILLS
env var, clearly documented as NOT a production pattern.

Closes #5776

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

* .NET: Add unit tests and integration test for Hosted-AgentSkills

Unit tests (14 tests, all passing):
- ZIP extraction with zip-slip guard (valid archive, traversal attack,
  sibling-prefix attack, directory entries)
- Skill name validation (rejects dots, separators, traversal patterns)
- AgentSkillsProvider with downloaded skills (advertises both skills,
  load_skill returns canary tokens, unknown skill returns error)

Container integration test:
- New 'agent-skills' scenario in the test container that creates
  Contoso Outdoors skills on disk and wires AgentSkillsProvider
- AgentSkillsHostedAgentFixture + 4 integration tests verifying:
  - Routine questions load support-style skill (STYLE-CANARY-3318)
  - Escalation triggers load escalation-policy (ESC-CANARY-7742)
  - Skills are advertised in system prompt
  - load_skill tool is invoked via FunctionCallContent

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

* .NET: Add smoke test, bootstrap, and docs for agent-skills integration

- Add scripts/smoke.ps1 for local Docker smoke testing: builds the
  contributor image, runs the container, verifies both skills are loaded
  via canary tokens (STYLE-CANARY-3318, ESC-CANARY-7742)
- Add 'agent-skills' to the bootstrap script scenario list
- Add agent-skills row to the integration test README scenarios table
- Exclude HostedAgentSkillsPatternTests from net472 (uses net8.0+ APIs)

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

* .NET: Update commented-out package versions to latest across all hosted samples

Update the end-user PackageReference versions (in the commented-out
sections) from 1.0.0 to the current latest NuGet versions:

- Microsoft.Agents.AI: 1.6.1
- Microsoft.Agents.AI.Foundry: 1.6.1-preview.260514.1
- Microsoft.Agents.AI.Foundry.Hosting: 1.6.1-preview.260514.1
- Microsoft.Agents.AI.Hosting: 1.6.1-preview.260514.1
- Microsoft.Agents.AI.OpenAI: 1.6.1
- Microsoft.Agents.AI.Workflows: 1.6.1

Also adds explicit versions to Hosted-Workflow-Handoff which had bare
PackageReference entries without Version attributes.

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

* .NET: Fix broken markdown links in Hosted-AgentSkills README

Remove references to non-existent ../../README.md. Replace with
inline instructions matching other hosted samples that don't have
a parent README.

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

* .NET: Use OS-appropriate string comparison in zip-slip guard

Use Ordinal on Unix (case-sensitive FS) and OrdinalIgnoreCase on
Windows to prevent case-based path bypass on Linux containers.

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

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
dc4bafbc1e · 2026-05-25 09:32:04 +00:00
History
..

Hosted-Workflow-Handoff

A hosted agent server demonstrating two patterns in a single app:

  • tool-agent — an agent with local tools (time, weather) plus remote Microsoft Learn MCP tools
  • triage-workflow — a handoff workflow that routes conversations to specialist agents (code expert or creative writer) using AgentWorkflowBuilder

Both agents are served over the Responses protocol. The server also exposes interactive web demos at /tool-demo and /workflow-demo.

Unlike the other samples in this folder, this one connects to an Azure OpenAI resource directly (not an Azure AI Foundry project endpoint).

Prerequisites

  • .NET 10 SDK
  • An Azure OpenAI resource with a deployed model (e.g., gpt-4o)
  • Azure CLI logged in (az login)

Configuration

Copy the template and fill in your values:

cp .env.example .env

Edit .env:

AZURE_OPENAI_ENDPOINT=https://<your-account>.openai.azure.com/
AZURE_OPENAI_DEPLOYMENT=gpt-4o
AZURE_BEARER_TOKEN=DefaultAzureCredential
ASPNETCORE_URLS=http://+:8088
ASPNETCORE_ENVIRONMENT=Development

AZURE_BEARER_TOKEN=DefaultAzureCredential is a sentinel value that tells the app to skip the bearer token and fall through to DefaultAzureCredential (requires az login). Set it to a real token only when running in Docker.

Note: .env is gitignored. The .env.example template is checked in as a reference.

Running directly (contributors)

cd dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Workflow-Handoff
dotnet run

The server starts on http://localhost:8088. Open http://localhost:8088 to see the demo index page.

Test it

Using the Azure Developer CLI (invokes triage-workflow — the primary/default agent):

azd ai agent invoke --local "Write me a short poem about coding"

To target a specific agent by name, use curl:

# Invoke triage-workflow explicitly
curl -X POST http://localhost:8088/responses \
  -H "Content-Type: application/json" \
  -d '{"input": "Write me a haiku about autumn", "model": "triage-workflow"}'
# Invoke tool-agent (local tools + MCP)
curl -X POST http://localhost:8088/responses \
  -H "Content-Type: application/json" \
  -d '{"input": "What time is it in Tokyo?", "model": "tool-agent"}'

Running with Docker

1. Publish for the container runtime

dotnet publish -c Debug -f net10.0 -r linux-musl-x64 --self-contained false -o out

2. Build the Docker image

docker build -f Dockerfile.contributor -t hosted-workflow-handoff .

3. Run the container

export AZURE_BEARER_TOKEN=$(az account get-access-token --resource https://ai.azure.com --query accessToken -o tsv)

docker run --rm -p 8088:8088 \
  -e AZURE_BEARER_TOKEN=$AZURE_BEARER_TOKEN \
  --env-file .env \
  hosted-workflow-handoff

4. Test it

azd ai agent invoke --local "Explain async/await in C#"

How the triage workflow works

User message
     │
     ▼
┌──────────────┐
│ Triage Agent │  ──routes──▶  ┌─────────────┐
│  (router)    │               │ Code Expert │
└──────────────┘               └─────────────┘
     ▲                                │
     │◀──────────────────────────────┘
     │
     └──routes──▶  ┌─────────────────┐
                   │ Creative Writer │
                   └─────────────────┘

The triage agent receives every message and hands off to the appropriate specialist. Specialists route back to the triage agent after responding, allowing for multi-turn conversations.

NuGet package users

Use the standard Dockerfile instead of Dockerfile.contributor. See the commented section in HostedWorkflowHandoff.csproj for the PackageReference alternative.