How to Use the Skills System with SkillScript and SkillResource in the Agent Framework
The Skills system in the Microsoft Agent Framework lets you extend agents with reusable capabilities by defining Skill containers that hold static or dynamic SkillResource objects for data lookup and SkillScript callables for executable actions, all bridged to the LLM through the SkillsProvider class.
The Microsoft Agent Framework provides a modular skills system that enables you to package reusable capabilities into self-describing units. By combining SkillScript for executable logic and SkillResource for structured data access, you can create both code-defined and file-based skills that agents invoke through automatic tool discovery according to the microsoft/agent-framework source code.
Understanding Core Components in _skills.py
The core implementation resides in [python/packages/core/agent_framework/_skills.py](https://github.com/microsoft/agent-framework/blob/main/python/packages/core/agent_framework/_skills.py). Three classes work together to form the skills architecture:
Skill– The top-level container holding a name, description, instructions, and optional collections of resources or scriptsSkillResource– Either a static text block supplied at construction time or a callable registered with@skill.resourcethat returns data on demand when the LLM callsread_skill_resourceSkillScript– An executable routine that the agent invokes viarun_skill_script, defined either as a code-defined Python callable decorated with@skill.scriptor discovered automatically from files on disk
Defining Code-Defined Skills with Python
Code-defined skills are instantiated directly in Python and passed to SkillsProvider, allowing you to mix dynamic logic with static configuration.
Creating Static SkillResources
Attach fixed content to a skill using the SkillResource class with the content parameter. These resources are returned verbatim when the LLM requests them.
from agent_framework import Skill, SkillResource
from textwrap import dedent
unit_converter = Skill(
name="unit-converter",
description="Convert between common units",
content="Use this skill when the user asks to convert units.",
resources=[
SkillResource(
name="conversion-tables",
content=dedent("""\
| From | To | Factor |
|------------|------------|----------|
| miles | kilometres | 1.60934 |
| pounds | kilograms | 0.453592|
"""),
),
],
)
Registering Dynamic Resources with @skill.resource
For data that must be generated at runtime, register a callable with the @skill.resource decorator. The framework executes this function when the LLM calls read_skill_resource, forwarding any runtime arguments passed via function_invocation_kwargs.
@unit_converter.resource(name="conversion-policy", description="Formatting policy")
def conversion_policy(**kwargs):
# Access runtime parameters passed during agent.run()
precision = kwargs.get("precision", 4)
return f"Decimal places: {precision}"
Implementing Executable Logic with @skill.script
SkillScripts expose behavior the agent can trigger. Decorate Python functions with @skill.script to register executable routines that accept parameters from the LLM plus optional runtime keyword arguments.
import json
@unit_converter.script(name="convert", description="Convert value using factor")
def convert_units(value: float, factor: float, **kwargs):
precision = kwargs.get("precision", 4)
result = round(value * factor, precision)
return json.dumps({"value": value, "result": result})
Loading File-Based Skills from Disk
The framework automatically discovers skills from directories containing a SKILL.md file, allowing you to manage skills as static assets separate from your application code.
The SKILL.md Convention and Directory Structure
Create a directory structure where each skill lives in its own folder containing a SKILL.md file with YAML front-matter:
my_skills/
└─ greet/
├─ SKILL.md
├─ greetings.md # Static resource
└─ hello.py # File-based script
The SKILL.md file defines the skill metadata and instructions:
---
name: greeting
description: Generate a friendly greeting
---
Use the **greetings** resource for tone and call the **hello** script to produce a personalized message.
Automatic Discovery via SkillsProvider
Point SkillsProvider to the parent directory containing your skill folders. The framework parses SKILL.md files and loads any accompanying resources and scripts matching DEFAULT_SCRIPT_EXTENSIONS (.py by default).
from agent_framework import SkillsProvider
provider = SkillsProvider(skill_paths="./my_skills")
Note: File-based scripts require a SkillScriptRunner to execute. If you omit a runner, the provider raises an error when the LLM attempts to invoke file-based scripts.
Wiring Skills to Your Agent with SkillsProvider
SkillsProvider acts as a ContextProvider that bridges your skills to the agent, exposing three automatic tools: load_skill, read_skill_resource, and run_skill_script.
Exposing Tools to the LLM
Instantiate SkillsProvider with your code-defined skills and optional file-based paths, then add it to the agent's context_providers list:
from agent_framework import Agent, SkillsProvider
from agent_framework.foundry import FoundryChatClient
provider = SkillsProvider(
skills=[unit_converter], # Code-defined skills
skill_paths="./my_file_skills", # Optional file-based discovery
)
async with Agent(
client=client,
instructions="You are a helpful assistant.",
context_providers=[provider],
) as agent:
response = await agent.run(
"How many kilometres is 26.2 miles?",
function_invocation_kwargs={"precision": 2},
)
When the agent runs, the provider automatically injects an XML-escaped list of available skills into the system prompt, enabling the LLM to discover and invoke them.
Implementing Human-in-the-Loop Approval
For sensitive operations, enable require_script_approval=True to force the agent to pause and request human approval before executing any SkillScript. The framework returns a function_approval_request that the host application must approve or reject.
provider = SkillsProvider(
skills=[deployment_skill],
require_script_approval=True,
)
# During execution...
while result.user_input_requests:
for req in result.user_input_requests:
print(f"Approval needed for: {req.function_call.name}")
# Host application decides whether to approve
response = req.to_function_approval_response(approved=True)
result = await agent.run(response, session=session)
Complete Implementation Example
This complete example demonstrates a code-defined skill with static resources, dynamic resources, and executable scripts, including runtime parameter passing:
from agent_framework import Skill, SkillResource, SkillsProvider, Agent
from agent_framework.foundry import FoundryChatClient
from azure.identity import AzureCliCredential
from textwrap import dedent
import json
import os
import asyncio
# 1. Define the skill with static resources
unit_converter = Skill(
name="unit-converter",
description="Convert between metric and imperial units",
content=dedent("""\
Use this skill for unit conversions.
1. Check conversion-tables resource for the factor.
2. Call the convert script with value and factor.
"""),
resources=[
SkillResource(
name="conversion-tables",
content=dedent("""\
miles to km: 1.60934
kg to lbs: 2.20462
"""),
),
],
)
# 2. Add a dynamic resource that uses runtime kwargs
@unit_converter.resource(name="policy")
def get_policy(**kwargs):
precision = kwargs.get("precision", 4)
return f"Use {precision} decimal places."
# 3. Add an executable script
@unit_converter.script(name="convert")
def convert(value: float, factor: float, **kwargs):
precision = kwargs.get("precision", 4)
return json.dumps({"result": round(value * factor, precision)})
async def main():
client = FoundryChatClient(
project_endpoint=os.getenv("FOUNDRY_PROJECT_ENDPOINT"),
credential=AzureCliCredential(),
)
provider = SkillsProvider(skills=[unit_converter])
async with Agent(
client=client,
instructions="You are a unit conversion assistant.",
context_providers=[provider],
) as agent:
result = await agent.run(
"Convert 26.2 miles to kilometres",
function_invocation_kwargs={"precision": 2},
)
print(result)
if __name__ == "__main__":
asyncio.run(main())
Summary
- Skill containers defined in
_skills.pybundle together instructions,SkillResourceobjects for data, andSkillScriptobjects for actions - SkillResources can be static strings supplied at construction time or dynamic callables registered with
@skill.resourcethat execute when the LLM callsread_skill_resource - SkillScripts expose executable logic via
@skill.scriptdecorators for code-defined functions or through file-based scripts discovered from disk SkillsProviderbridges skills to agents as aContextProvider, automatically advertising available capabilities and optionally enforcing human-in-the-loop approval viarequire_script_approval=True- Runtime parameters flow through
function_invocation_kwargsinagent.run()and arrive as**kwargsin dynamic resources and scripts
Frequently Asked Questions
What is the difference between SkillResource and SkillScript?
SkillResource provides data or reference material that the LLM reads using the read_skill_resource tool, returning either static content or the result of a dynamic callable. SkillScript provides executable behavior that the LLM triggers with the run_skill_script tool, performing calculations, API calls, or other actions and returning results to the conversation.
How do I pass runtime parameters to skill resources and scripts?
Pass parameters via the function_invocation_kwargs dictionary in your agent.run() call. The framework forwards these as **kwargs to any dynamic resource callable or script function that accepts them, allowing you to customize behavior like precision, user preferences, or contextual data without modifying the skill definition.
Can I mix code-defined and file-based skills in the same agent?
Yes. The SkillsProvider accepts both a skills list for code-defined instances and a skill_paths parameter for directory discovery. According to the source code in _skills.py, these approaches are supported simultaneously, allowing you to combine dynamic Python logic with static skill assets managed on disk.
What happens if I don't provide a SkillScriptRunner for file-based scripts?
If you attempt to execute a file-based script without supplying a SkillScriptRunner to SkillsProvider, the framework raises a clear error indicating that the script cannot be executed. Code-defined scripts run in-process and do not require a runner, but external script files need a runner to handle execution in a subprocess or other environment.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →