* .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>
What this sample demonstrates
An Agent Framework agent that loads its behavioral guidelines from Foundry Skills at startup, hosted using the Responses protocol. Skills are authored once as SKILL.md files, uploaded to your Foundry project through the Skills REST API, and downloaded by the agent on boot so updates ship without code changes.
How It Works
Authoring skills
Each skill is a Markdown file with a YAML front matter block. This sample ships two source skills under skills/:
| Skill | Purpose |
|---|---|
support-style |
Voice, formatting, and signature rules for Contoso Outdoors support replies. |
escalation-policy |
When and how to escalate a customer ticket. |
Each SKILL.md includes a unique *-CANARY-* token that the model is asked to echo, so you can prove the skill was loaded from Foundry (not hallucinated) by checking the response.
The
nameanddescriptionvalues in the YAML front matter must be unquoted — quoting them causes the Skills REST API to return HTTP 500 on import.
Uploading skills
The sample includes a convenience provisioning step that checks whether each skill exists in Foundry and uploads it if not, gated behind the PROVISION_SAMPLE_SKILLS=true env var. In production, skill provisioning is an external concern — it is NOT the hosted agent's responsibility. A real deployment pipeline would provision skills separately (e.g., via a CI/CD step, a CLI script, or a management portal).
The provisioning uses ProjectAgentSkills.CreateSkillFromPackageAsync(directoryPath) from the Azure.AI.Projects.Agents SDK. The method packages the SKILL.md file as a ZIP and uploads it to Foundry.
Downloading skills at agent startup
Program.cs reads the comma-separated SKILL_NAMES env var and for each skill name downloads the ZIP archive from Foundry via ProjectAgentSkills.DownloadSkillAsync(name), then unpacks it into a separate runtime directory at downloaded_skills/<name>/ (kept distinct from the static skills/ source folder).
An AgentSkillsProvider is then built over downloaded_skills/ and attached to the agent as a context provider. The provider follows the Agent Skills progressive-disclosure pattern:
- Advertise — skill names and descriptions are injected into the system prompt at session start (~100 tokens per skill).
- Load — the model calls the
load_skilltool when it decides a skill is relevant to the user's turn, and the fullSKILL.mdbody is returned.
This means the model only pays the token cost for a skill's full body when it actually needs it, and updating a skill in Foundry + restarting the agent is enough to pick up the change — no code redeploy required.
Note: This sample supports instruction-only and resource-based skills. If your downloaded skills contain scripts, add a script runner when constructing the
AgentSkillsProvider.
Agent Hosting
The agent is hosted using the Agent Framework with the Responses API hosting layer (AddFoundryResponses / MapFoundryResponses).
Prerequisites
- An Azure AI Foundry project with a deployed model (e.g.,
gpt-4o) - Azure CLI logged in (
az login)
Required RBAC
Your identity (or the Managed Identity running the container in production) needs Azure AI User on the Foundry project scope. This single role covers both authoring skills and downloading them.
Running the Agent Host
Set the required environment variables and run the sample with dotnet run:
export AZURE_AI_PROJECT_ENDPOINT="https://<account>.services.ai.azure.com/api/projects/<project>"
export AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-4o"
export SKILL_NAMES="support-style,escalation-policy"
export PROVISION_SAMPLE_SKILLS="true" # First run only — provisions skills to Foundry
Or in PowerShell:
$env:SKILL_NAMES="support-style,escalation-policy"
$env:PROVISION_SAMPLE_SKILLS="true" # First run only — provisions skills to Foundry
You can also place these in a .env file next to Program.cs — see .env.example.
On startup you should see:
Skill 'support-style' already exists in Foundry.
Skill 'escalation-policy' already exists in Foundry.
Downloading skill 'support-style' from Foundry...
Downloading skill 'escalation-policy' from Foundry...
The downloaded SKILL.md files land under downloaded_skills/<name>/SKILL.md next to the published output. This directory is recreated from scratch on every run, so deleting it manually is never necessary.
Interacting with the agent
Send a POST request to the server with a JSON body containing an
"input"field to interact with the agent. For example:
curl -X POST http://localhost:8088/responses -H "Content-Type: application/json" -d '{"input": "Hi, I am Alex. I just want to confirm I can return my tent within 30 days."}'
curl -X POST http://localhost:8088/responses -H "Content-Type: application/json" -d '{"input": "I want a $750 refund on Order #A-1042 right now or I am calling my lawyer."}'
| Prompt mentions | Skill that should drive the response |
|---|---|
| Routine return / shipping / care question | Model loads support-style (canary STYLE-CANARY-3318) — no escalation. |
| Injury, legal threat, press, or refund > $500 | Model loads escalation-policy (canary ESC-CANARY-7742) and support-style. |
Because skills are loaded on demand, the canary token in a response also proves the model actually invoked load_skill for the matching skill (not just saw its name in the advertised list).
Deploying the Agent to Foundry
When deploying to Foundry, make sure SKILL_NAMES is set in your azd environment so it gets injected into the hosted container per agent.manifest.yaml:
azd env set SKILL_NAMES "support-style,escalation-policy"
The deployed agent's Managed Identity needs Azure AI User on the Foundry project to download skills at startup.
The
skills/source folder is not deployed to Foundry — only the downloaded skills are used at runtime. The provisioning step must have been run against the same Foundry project before the agent can download the skills.