From f5ed287fb8250ad52c766ca539e24a6719bd74c2 Mon Sep 17 00:00:00 2001 From: malsabbagh05 Date: Thu, 18 Jun 2026 23:09:44 +0800 Subject: [PATCH 1/2] fix: use writable skills download directory --- .../responses/09_foundry_skills/.env.example | 2 ++ .../responses/09_foundry_skills/README.md | 8 +++++--- .../responses/09_foundry_skills/main.py | 8 ++++++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/python/samples/04-hosting/foundry-hosted-agents/responses/09_foundry_skills/.env.example b/python/samples/04-hosting/foundry-hosted-agents/responses/09_foundry_skills/.env.example index 379c3edd05..b80b157d96 100644 --- a/python/samples/04-hosting/foundry-hosted-agents/responses/09_foundry_skills/.env.example +++ b/python/samples/04-hosting/foundry-hosted-agents/responses/09_foundry_skills/.env.example @@ -2,3 +2,5 @@ FOUNDRY_PROJECT_ENDPOINT="..." AZURE_AI_MODEL_DEPLOYMENT_NAME="..." # Comma-separated list of Foundry skill names to download at startup. SKILL_NAMES="support-style,escalation-policy" +# Optional writable directory for downloaded skills. Defaults to the system temp directory. +# DOWNLOADED_SKILLS_DIR="/tmp/maf_downloaded_skills" diff --git a/python/samples/04-hosting/foundry-hosted-agents/responses/09_foundry_skills/README.md b/python/samples/04-hosting/foundry-hosted-agents/responses/09_foundry_skills/README.md index 0831efdb83..9d04f34ff5 100644 --- a/python/samples/04-hosting/foundry-hosted-agents/responses/09_foundry_skills/README.md +++ b/python/samples/04-hosting/foundry-hosted-agents/responses/09_foundry_skills/README.md @@ -23,9 +23,9 @@ Each `SKILL.md` includes a unique `*-CANARY-*` token that the model is asked to ### Downloading skills at agent startup -[`main.py`](main.py) reads the comma-separated `SKILL_NAMES` env var, opens an `AIProjectClient` (also with `allow_preview=True`), and for each skill name streams the ZIP archive from `beta.skills.download(name)` and unpacks it into a **separate runtime directory** at `downloaded_skills//` (kept distinct from the static `skills/` source folder so the two never get confused — `skills/` is the input to `provision_skills.py`, `downloaded_skills/` is the output of `main.py`'s bootstrap step). +[`main.py`](main.py) reads the comma-separated `SKILL_NAMES` env var, opens an `AIProjectClient` (also with `allow_preview=True`), and for each skill name streams the ZIP archive from `beta.skills.download(name)` and unpacks it into a **separate writable runtime directory**. By default this directory is created under the system temp folder as `maf_downloaded_skills//`, which works in hosted containers where the application directory may be read-only. Set `DOWNLOADED_SKILLS_DIR` to override the location. -A [`SkillsProvider`](../../../../../packages/core/agent_framework/_skills.py) is then built over `downloaded_skills/` and attached to the `Agent` as a context provider. The provider follows the [Agent Skills](https://agentskills.io/) progressive-disclosure pattern: +A [`SkillsProvider`](../../../../../packages/core/agent_framework/_skills.py) is then built over the downloaded skills directory and attached to the `Agent` as a context provider. The provider follows the [Agent Skills](https://agentskills.io/) progressive-disclosure pattern: 1. **Advertise** — skill names and descriptions are injected into the system prompt at session start (~100 tokens per skill). 2. **Load** — the model calls the `load_skill` tool when it decides a skill is relevant to the user's turn, and the full `SKILL.md` body is returned. @@ -100,7 +100,9 @@ Downloading skill 'support-style' from Foundry... Downloading skill 'escalation-policy' from Foundry... ``` -The downloaded `SKILL.md` files land under `downloaded_skills//SKILL.md` next to `main.py`. This directory is recreated from scratch on every run, so deleting it manually is never necessary. +The downloaded `SKILL.md` files land under `//SKILL.md`. The directory is recreated from scratch on every run, so deleting it manually is never necessary. + +By default, the sample uses the system temp directory, for example `/tmp/maf_downloaded_skills` on Linux. To choose a different writable location, set `DOWNLOADED_SKILLS_DIR` before startup. ## Interacting with the agent diff --git a/python/samples/04-hosting/foundry-hosted-agents/responses/09_foundry_skills/main.py b/python/samples/04-hosting/foundry-hosted-agents/responses/09_foundry_skills/main.py index 5cac1493bb..44a7bef4d1 100644 --- a/python/samples/04-hosting/foundry-hosted-agents/responses/09_foundry_skills/main.py +++ b/python/samples/04-hosting/foundry-hosted-agents/responses/09_foundry_skills/main.py @@ -4,7 +4,7 @@ At startup, this agent downloads each Foundry Skill named in ``SKILL_NAMES`` from the project's ``beta.skills`` API, unpacks each -one into a separate runtime directory under ``downloaded_skills/``, and wires +one into a separate writable runtime directory and wires that directory into a :class:`SkillsProvider` so the agent advertises the skills to the model and loads them on demand (progressive disclosure). @@ -17,6 +17,7 @@ import logging import os import shutil +import tempfile import zipfile from pathlib import Path from typing import Final @@ -34,7 +35,10 @@ # Kept separate from the static ``skills/`` source folder so the two never # get confused: the source folder is the input to ``provision_skills.py`` # and the runtime folder is the output of this script's bootstrap step. -DOWNLOADED_SKILLS_DIR: Final = Path(__file__).parent / "downloaded_skills" +# Defaults to a system temp location because hosted containers may mount the +# application directory read-only. Set DOWNLOADED_SKILLS_DIR to override it. +_DEFAULT_DOWNLOADED_SKILLS_DIR: Final = Path(tempfile.gettempdir()) / "maf_downloaded_skills" +DOWNLOADED_SKILLS_DIR: Final = Path(os.environ.get("DOWNLOADED_SKILLS_DIR", str(_DEFAULT_DOWNLOADED_SKILLS_DIR))) logger = logging.getLogger(__name__) From 38baca2239e301932e553af9eb812ba4a1c35ca7 Mon Sep 17 00:00:00 2001 From: malsabbagh05 Date: Fri, 19 Jun 2026 22:37:37 +0800 Subject: [PATCH 2/2] fix: handle empty skills download directory override --- .../responses/09_foundry_skills/README.md | 2 +- .../foundry-hosted-agents/responses/09_foundry_skills/main.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/python/samples/04-hosting/foundry-hosted-agents/responses/09_foundry_skills/README.md b/python/samples/04-hosting/foundry-hosted-agents/responses/09_foundry_skills/README.md index 9d04f34ff5..1a77265273 100644 --- a/python/samples/04-hosting/foundry-hosted-agents/responses/09_foundry_skills/README.md +++ b/python/samples/04-hosting/foundry-hosted-agents/responses/09_foundry_skills/README.md @@ -100,7 +100,7 @@ Downloading skill 'support-style' from Foundry... Downloading skill 'escalation-policy' from Foundry... ``` -The downloaded `SKILL.md` files land under `//SKILL.md`. The directory is recreated from scratch on every run, so deleting it manually is never necessary. +The downloaded `SKILL.md` files land under `DOWNLOADED_SKILLS_DIR//SKILL.md`. The directory is recreated from scratch on every run, so deleting it manually is never necessary. By default, the sample uses the system temp directory, for example `/tmp/maf_downloaded_skills` on Linux. To choose a different writable location, set `DOWNLOADED_SKILLS_DIR` before startup. diff --git a/python/samples/04-hosting/foundry-hosted-agents/responses/09_foundry_skills/main.py b/python/samples/04-hosting/foundry-hosted-agents/responses/09_foundry_skills/main.py index 44a7bef4d1..3573649464 100644 --- a/python/samples/04-hosting/foundry-hosted-agents/responses/09_foundry_skills/main.py +++ b/python/samples/04-hosting/foundry-hosted-agents/responses/09_foundry_skills/main.py @@ -38,7 +38,8 @@ # Defaults to a system temp location because hosted containers may mount the # application directory read-only. Set DOWNLOADED_SKILLS_DIR to override it. _DEFAULT_DOWNLOADED_SKILLS_DIR: Final = Path(tempfile.gettempdir()) / "maf_downloaded_skills" -DOWNLOADED_SKILLS_DIR: Final = Path(os.environ.get("DOWNLOADED_SKILLS_DIR", str(_DEFAULT_DOWNLOADED_SKILLS_DIR))) +_DOWNLOADED_SKILLS_DIR_ENV = os.environ.get("DOWNLOADED_SKILLS_DIR") +DOWNLOADED_SKILLS_DIR: Final = Path((_DOWNLOADED_SKILLS_DIR_ENV or "").strip() or _DEFAULT_DOWNLOADED_SKILLS_DIR) logger = logging.getLogger(__name__)