{"id":5462,"date":"2026-05-22T01:34:12","date_gmt":"2026-05-22T08:34:12","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/agent-framework\/?p=5462"},"modified":"2026-05-22T01:34:12","modified_gmt":"2026-05-22T08:34:12","slug":"agent-skills-for-python-file-code-and-class-composed-in-one-provider","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/agent-framework\/agent-skills-for-python-file-code-and-class-composed-in-one-provider\/","title":{"rendered":"Agent Skills for Python: File, Code, and Class &#8211; Composed in One Provider"},"content":{"rendered":"<p>Python developers working with Agent Skills can now author skills as files on disk, as inline Python code, or as reusable classes &#8211; and mix them freely through composable source classes that handle discovery, filtering, and deduplication. A skill living in your local repository, one installed from your organization&#8217;s internal package index, and a quick inline bridge you wrote ten minutes ago all plug into the same provider.<\/p>\n<p>This is the third post in our Agent Skills series. The <a href=\"https:\/\/devblogs.microsoft.com\/agent-framework\/give-your-agents-domain-expertise-with-agent-skills-in-microsoft-agent-framework\/\">first post<\/a> introduced file-based skills; the <a href=\"https:\/\/devblogs.microsoft.com\/agent-framework\/whats-new-in-agent-skills-code-skills-script-execution-and-approval-for-python\/\">second<\/a> added code-defined skills, script execution, and approval for Python. This post walks through the two additions that complete the picture: <strong>class-based skills<\/strong> and <strong>multi-source composition<\/strong>.<\/p>\n<p>If you&#8217;ve been following the .NET side, the companion post <a href=\"https:\/\/devblogs.microsoft.com\/agent-framework\/agent-skills-in-net-three-ways-to-author-one-provider-to-run-them\/\">Agent Skills in .NET: Three Ways to Author, One Provider to Run Them<\/a> covers the same capabilities for C#. Everything shown here is the Python equivalent &#8211; same concepts, idiomatic Python API.<\/p>\n<h2>The scenario<\/h2>\n<p>Imagine you&#8217;re responsible for an HR self-service agent at your company. The first version has a single file-based skill that guides new hires through onboarding. Over the next few weeks, the HR systems team publishes a benefits enrollment skill as an installable Python package on your organization&#8217;s internal package index, and you want to slot it in next to the onboarding skill without touching existing code. Meanwhile, you learn they&#8217;re also building a time-off balance skill &#8211; but the packaged version won&#8217;t ship for another sprint. The HR data you need is already reachable through an internal client your application uses elsewhere, so you write a quick inline skill that wraps it. Once the official package lands, you swap out your bridge and move on.<\/p>\n<p>Every step here is independent. Adding one skill never means rewriting another.<\/p>\n<h2>Step 1: Start with a file-based skill<\/h2>\n<p>The onboarding guide is a skill directory with a <code>SKILL.md<\/code> file, a Python script that checks whether IT accounts have been provisioned, and a reference document containing the checklist:<\/p>\n<pre class=\"wp-block-code\"><code>skills\/\r\n\u2514\u2500\u2500 onboarding-guide\/\r\n    \u251c\u2500\u2500 SKILL.md\r\n    \u251c\u2500\u2500 scripts\/\r\n    \u2502   \u2514\u2500\u2500 check-provisioning.py\r\n    \u2514\u2500\u2500 references\/\r\n        \u2514\u2500\u2500 onboarding-checklist.md<\/code><\/pre>\n<pre class=\"wp-block-code\"><code class=\"language-yaml\">---\r\nname: onboarding-guide\r\ndescription: &gt;-\r\n  Walk new hires through their first-week setup checklist. Use when a new\r\n  employee asks about system access, required training, or onboarding steps.\r\n---\r\n\r\n## Instructions\r\n\r\n1. Ask for the employee's name and start date if not already provided.\r\n2. Run the `scripts\/check-provisioning.py` script to verify their IT accounts are active.\r\n3. Walk through the steps in the `references\/onboarding-checklist.md` reference.\r\n4. Follow up on any incomplete items.<\/code><\/pre>\n<p>To let the agent execute that script, provide a <code>script_runner<\/code> when creating the <code>SkillsProvider<\/code> and pass the provider to an agent:<\/p>\n<pre class=\"wp-block-code\"><code class=\"language-python\">import os\r\nfrom pathlib import Path\r\nfrom agent_framework import Agent, SkillsProvider\r\nfrom agent_framework.foundry import FoundryChatClient\r\nfrom azure.identity import AzureCliCredential\r\n\r\ndef my_runner(skill, script, args=None):\r\n    \"\"\"Run a file-based script as a subprocess.\"\"\"\r\n    import subprocess, sys\r\n    script_path = Path(script.full_path)\r\n    cmd = [sys.executable, str(script_path)]\r\n    if isinstance(args, list):\r\n        cmd.extend(args)\r\n    result = subprocess.run(\r\n        cmd, capture_output=True, text=True, timeout=30, cwd=str(script_path.parent)\r\n    )\r\n    return result.stdout.strip()\r\n\r\n# Discover skills from the 'skills' directory\r\nskills_provider = SkillsProvider.from_paths(\r\n    skill_paths=Path(__file__).parent \/ \"skills\",\r\n    script_runner=my_runner,\r\n)\r\n\r\nendpoint = os.environ[\"FOUNDRY_PROJECT_ENDPOINT\"]\r\ndeployment = os.environ.get(\"FOUNDRY_MODEL\", \"gpt-4o-mini\")\r\n\r\nclient = FoundryChatClient(\r\n    project_endpoint=endpoint,\r\n    model=deployment,\r\n    credential=AzureCliCredential(),\r\n)\r\n\r\nagent = Agent(\r\n    client=client,\r\n    instructions=\"You are a helpful HR self-service assistant.\",\r\n    context_providers=[skills_provider],\r\n)<\/code><\/pre>\n<p>When a new hire asks about onboarding, the agent matches the request to the skill description, loads the instructions, and calls the provisioning script to verify account status.<\/p>\n<p>The runner shown here is deliberately simple. In production, wrap it with sandboxing, resource limits, input validation, and logging.<\/p>\n<h2>Step 2: Bring in a class-based skill from a Python package<\/h2>\n<p>A few weeks later, the HR systems team publishes <code>contoso-skills-hr-enrollment<\/code> to your internal Python package index. Class-based skills package everything &#8211; metadata, instructions, resources, and scripts &#8211; inside a single Python class. They subclass <code>ClassSkill<\/code> and rely on <code>@ClassSkill.resource<\/code> and <code>@ClassSkill.script<\/code> decorators for automatic discovery:<\/p>\n<pre class=\"wp-block-code\"><code class=\"language-python\"># Inside the contoso-skills-hr-enrollment package\r\nimport json\r\nfrom textwrap import dedent\r\nfrom agent_framework import ClassSkill, SkillFrontmatter\r\n\r\nclass BenefitsEnrollmentSkill(ClassSkill):\r\n    \"\"\"Enroll employees in health, dental, or vision plans.\"\"\"\r\n\r\n    def __init__(self) -&gt; None:\r\n        super().__init__(\r\n            frontmatter=SkillFrontmatter(\r\n                name=\"benefits-enrollment\",\r\n                description=(\r\n                    \"Enroll an employee in health, dental, or vision plans. \"\r\n                    \"Use when asked about benefits sign-up, plan options, or coverage changes.\"\r\n                ),\r\n            ),\r\n        )\r\n\r\n    @property\r\n    def instructions(self) -&gt; str:\r\n        return dedent(\"\"\"\\\r\n            Use this skill when an employee asks about enrolling in or changing their benefits.\r\n\r\n            1. Read the available-plans resource to review current offerings and pricing.\r\n            2. Confirm the plan the employee wants to enroll in.\r\n            3. Use the enroll script to complete the enrollment.\r\n        \"\"\")\r\n\r\n    @property\r\n    @ClassSkill.resource(description=\"Health, dental, and vision plan options with monthly pricing.\")\r\n    def available_plans(self) -&gt; str:\r\n        return dedent(\"\"\"\\\r\n            ## Available Plans (2026)\r\n            - Health: Basic HMO ($0\/month), Premium PPO ($45\/month)\r\n            - Dental: Standard ($12\/month), Enhanced ($25\/month)\r\n            - Vision: Basic ($8\/month)\r\n        \"\"\")\r\n\r\n    @ClassSkill.script(description=\"Enrolls an employee in the specified benefit plan. Returns a JSON confirmation.\")\r\n    def enroll(self, employee_id: str, plan_code: str) -&gt; str:\r\n        success = HrClient.enroll_in_plan(employee_id, plan_code)\r\n        return json.dumps({\"success\": success, \"employee_id\": employee_id, \"plan_code\": plan_code})<\/code><\/pre>\n<p>A bare <code>@ClassSkill.resource<\/code> decorator (no arguments) uses the method name as the resource name, converting underscores to hyphens. Pass <code>name=\"...\"<\/code> and <code>description=\"...\"<\/code> explicitly when you want different values. The same applies to <code>@ClassSkill.script<\/code>. Resources work as regular methods or <code>@property<\/code> descriptors &#8211; when combining the two, put <code>@property<\/code> first.<\/p>\n<p>Now wire the class-based skill into the same provider that already serves the file-based onboarding guide. This is where source composition comes in &#8211; import <code>BenefitsEnrollmentSkill<\/code> from the installed package and combine the sources:<\/p>\n<pre class=\"wp-block-code\"><code class=\"language-python\">from contoso_skills_hr_enrollment import BenefitsEnrollmentSkill\r\nfrom agent_framework import (\r\n    AggregatingSkillsSource,\r\n    DeduplicatingSkillsSource,\r\n    FileSkillsSource,\r\n    InMemorySkillsSource,\r\n    SkillsProvider,\r\n)\r\n\r\nskills_provider = SkillsProvider(\r\n    DeduplicatingSkillsSource(\r\n        AggregatingSkillsSource([\r\n            FileSkillsSource(\r\n                Path(__file__).parent \/ \"skills\",    # file-based: onboarding guide\r\n                script_runner=my_runner,\r\n            ),\r\n            InMemorySkillsSource([BenefitsEnrollmentSkill()]),  # class-based: benefits enrollment from internal package\r\n        ])\r\n    )\r\n)<\/code><\/pre>\n<p>Here <code>AggregatingSkillsSource<\/code> merges the file-based and in-memory sources into a single stream, and <code>DeduplicatingSkillsSource<\/code> ensures that if two sources happen to supply a skill with the same name, the first one takes priority. The agent sees both skills in its system prompt and picks the right one based on the employee&#8217;s question &#8211; no routing logic on your side.<\/p>\n<h2>Step 3: Bridge the gap with an inline skill<\/h2>\n<p>The HR systems team is also building a time-off balance skill, but the package won&#8217;t be published to the internal index for another sprint. The underlying data is already reachable through the shared <code>HrDatabase<\/code> client your application uses elsewhere &#8211; it&#8217;s the same source the official skill will read from. Instead of waiting, you wrap it in an inline skill defined in your application code with <code>InlineSkill<\/code>:<\/p>\n<pre class=\"wp-block-code\"><code class=\"language-python\">import json\r\nfrom textwrap import dedent\r\nfrom agent_framework import InlineSkill, SkillFrontmatter\r\n\r\ntime_off_skill = InlineSkill(\r\n    frontmatter=SkillFrontmatter(\r\n        name=\"time-off-balance\",\r\n        description=\"Calculate an employee's remaining vacation and sick days. Use when asked about available time off or leave balances.\",\r\n    ),\r\n    instructions=dedent(\"\"\"\\\r\n        Use this skill when an employee asks how many vacation or sick days they have left.\r\n        1. Ask for the employee ID if not already provided.\r\n        2. Use the calculate-balance script to get the remaining balance.\r\n        3. Present the result clearly, showing both used and remaining days.\r\n    \"\"\"),\r\n)\r\n\r\n@time_off_skill.script(description=\"Calculate remaining leave balance for an employee.\")\r\ndef calculate_balance(employee_id: str, leave_type: str) -&gt; str:\r\n    # Temporary implementation - replace with the packaged skill when available\r\n    total_days = HrDatabase.get_annual_allowance(employee_id, leave_type)\r\n    days_used = HrDatabase.get_days_used(employee_id, leave_type)\r\n    remaining = total_days - days_used\r\n    return json.dumps({\r\n        \"employee_id\": employee_id,\r\n        \"leave_type\": leave_type,\r\n        \"total_days\": total_days,\r\n        \"days_used\": days_used,\r\n        \"remaining\": remaining,\r\n    })<\/code><\/pre>\n<p>Fold it into the existing provider alongside the other two skills:<\/p>\n<pre class=\"wp-block-code\"><code class=\"language-python\">skills_provider = SkillsProvider(\r\n    DeduplicatingSkillsSource(\r\n        AggregatingSkillsSource([\r\n            FileSkillsSource(\r\n                Path(__file__).parent \/ \"skills\",    # file-based: onboarding guide\r\n                script_runner=my_runner,\r\n            ),\r\n            InMemorySkillsSource([\r\n                BenefitsEnrollmentSkill(),            # class-based: benefits enrollment from internal package\r\n                time_off_skill,                       # code-defined: temporary bridge\r\n            ]),\r\n        ])\r\n    )\r\n)<\/code><\/pre>\n<p>From the agent&#8217;s perspective, this skill looks identical to the file-based and class-based ones. When the official package eventually ships, swap out <code>time_off_skill<\/code> for the class-based version &#8211; nothing else changes.<\/p>\n<p><code>InlineSkill<\/code> also fits naturally when you need resources that execute logic at read time rather than serving static files, when skill definitions must be constructed at runtime from data (for example, a personalized skill per user session based on role or permissions), or when a skill needs to close over call-site state (local variables, closures) rather than resolve services through <code>**kwargs<\/code>.<\/p>\n<h2>Step 4: Add human approval for script execution<\/h2>\n<p>Some of these scripts carry real weight: <code>check-provisioning<\/code> hits production infrastructure, and <code>enroll<\/code> writes to the HR system. Before going live, you&#8217;ll want a human to sign off on each script call. Set <code>require_script_approval=True<\/code> on the provider:<\/p>\n<pre class=\"wp-block-code\"><code class=\"language-python\">skills_provider = SkillsProvider(\r\n    DeduplicatingSkillsSource(\r\n        AggregatingSkillsSource([\r\n            FileSkillsSource(\r\n                Path(__file__).parent \/ \"skills\",    # file-based: onboarding guide\r\n                script_runner=my_runner,\r\n            ),\r\n            InMemorySkillsSource([\r\n                BenefitsEnrollmentSkill(),            # class-based: benefits enrollment from internal package\r\n                time_off_skill,                       # code-defined: temporary time-off balance bridge\r\n            ]),\r\n        ])\r\n    ),\r\n    require_script_approval=True,\r\n)<\/code><\/pre>\n<p>With this flag set, the agent pauses whenever it wants to run a script and hands your application an approval request. You present it to a reviewer, collect a decision, and resume. If approved, execution proceeds normally. If rejected, the agent is told the call was declined and can adjust its response accordingly. For the complete approval-handling pattern, see <a href=\"https:\/\/learn.microsoft.com\/en-us\/agent-framework\/agents\/tools\/tool-approval?pivots=programming-language-python\">Tool approval<\/a> in the documentation.<\/p>\n<h2>Why this matters<\/h2>\n<p><strong>Independent skill ownership.<\/strong> Different teams author and publish skills on their own schedule &#8211; as directories in a shared repo or as Python packages on your internal index &#8211; and source composition stitches them together without cross-team coordination.<\/p>\n<p><strong>Grow the agent one skill at a time.<\/strong> Each new skill is additive. You don&#8217;t refactor existing skills to accommodate new ones; the agent selects the right skill at runtime.<\/p>\n<p><strong>Prototype quickly, replace cleanly.<\/strong> <code>InlineSkill<\/code> lets you ship behavior the same day you need it. When the official package arrives, the swap is a one-line change &#8211; the agent can&#8217;t tell the difference.<\/p>\n<p><strong>Human oversight where it counts.<\/strong> Script approval inserts a review step before any script with side effects executes &#8211; a practical safeguard for sensitive environments.<\/p>\n<p><strong>Selective exposure from shared libraries.<\/strong> When your organization maintains a central skill repository but individual agents should only see a subset, <code>FilteringSkillsSource<\/code> handles it with a predicate:<\/p>\n<pre class=\"wp-block-code\"><code class=\"language-python\">from agent_framework import (\r\n    DeduplicatingSkillsSource,\r\n    FileSkillsSource,\r\n    FilteringSkillsSource,\r\n    SkillsProvider,\r\n)\r\n\r\napproved_skills = {\"onboarding-guide\", \"benefits-enrollment\"}\r\n\r\nskills_provider = SkillsProvider(\r\n    DeduplicatingSkillsSource(\r\n        FilteringSkillsSource(\r\n            FileSkillsSource(Path(__file__).parent \/ \"all-skills\"),\r\n            predicate=lambda skill: skill.frontmatter.name in approved_skills,\r\n        )\r\n    )\r\n)<\/code><\/pre>\n<h2>Wrapping up<\/h2>\n<p>The Python SDK for Agent Skills now gives you three authoring options &#8211; file-based, code-defined, and class-based &#8211; along with composable source classes to combine, filter, and deduplicate them however you need. Start with a skill directory, pull in a packaged class from your internal index, fill gaps with inline code, and let the provider handle the rest. Add script approval when the stakes call for it.<\/p>\n<ul>\n<li>\ud83d\udcd6 <a href=\"https:\/\/learn.microsoft.com\/en-us\/agent-framework\/agents\/skills\">Agent Skills documentation on Microsoft Learn<\/a><\/li>\n<li>\ud83d\udcbb <a href=\"https:\/\/github.com\/microsoft\/agent-framework\/tree\/main\/python\/samples\/02-agents\/skills\">Python samples on GitHub<\/a><\/li>\n<li>\ud83d\udde3\ufe0f <a href=\"https:\/\/github.com\/microsoft\/agent-framework\/discussions\">GitHub Discussions<\/a> &#8211; share feedback and connect with the community<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Python developers working with Agent Skills can now author skills as files on disk, as inline Python code, or as reusable classes &#8211; and mix them freely through composable source classes that handle discovery, filtering, and deduplication. A skill living in your local repository, one installed from your organization&#8217;s internal package index, and a quick [&hellip;]<\/p>\n","protected":false},"author":157200,"featured_media":5169,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143,145,34],"tags":[],"class_list":["post-5462","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-agent-framework","category-agent-skills","category-python-2"],"acf":[],"blog_post_summary":"<p>Python developers working with Agent Skills can now author skills as files on disk, as inline Python code, or as reusable classes &#8211; and mix them freely through composable source classes that handle discovery, filtering, and deduplication. A skill living in your local repository, one installed from your organization&#8217;s internal package index, and a quick [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/posts\/5462","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/users\/157200"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/comments?post=5462"}],"version-history":[{"count":1,"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/posts\/5462\/revisions"}],"predecessor-version":[{"id":5470,"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/posts\/5462\/revisions\/5470"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/media\/5169"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/media?parent=5462"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/categories?post=5462"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/tags?post=5462"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}