Skip to content

Commit 98bb3d0

Browse files
Merge pull request #913 from MicrosoftDocs/main
Merge main into live
2 parents fc084a8 + 2e7458a commit 98bb3d0

1 file changed

Lines changed: 108 additions & 8 deletions

File tree

agent-framework/agents/skills.md

Lines changed: 108 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ zone_pivot_groups: programming-languages
55
author: SergeyMenshykh
66
ms.topic: conceptual
77
ms.author: semenshi
8-
ms.date: 03/05/2026
8+
ms.date: 03/11/2026
99
ms.service: agent-framework
1010
---
1111

@@ -74,13 +74,13 @@ This pattern keeps the agent's context window lean while giving it access to dee
7474

7575
## Providing skills to an agent
7676

77-
The Agent Framework includes a skills provider that discovers skills from filesystem directories and makes them available to agents as a context provider. It searches configured paths recursively (up to two levels deep) for `SKILL.md` files, validates their format and resources, and exposes two tools to the agent: `load_skill` and `read_skill_resource`.
78-
79-
> [!NOTE]
80-
> Script execution is not yet supported and will be added in a future release.
77+
The Agent Framework includes a skills provider that discovers skills from filesystem directories and makes them available to agents as a context provider. It searches configured paths recursively (up to two levels deep) for `SKILL.md` files, validates their format and resources, and exposes tools to the agent: `load_skill`, `read_skill_resource`, and (when scripts are present) `run_skill_script`.
8178

8279
:::zone pivot="programming-language-csharp"
8380

81+
> [!NOTE]
82+
> Script execution is not yet supported in C# and will be added in a future release.
83+
8484
### Basic setup
8585

8686
Create a `FileAgentSkillsProvider` pointing to a directory containing your skills, and add it to the agent's context providers:
@@ -231,7 +231,7 @@ skills_provider = SkillsProvider(
231231
```
232232

233233
> [!NOTE]
234-
> The custom template must contain a `{skills}` placeholder where the skill list is inserted.
234+
> The custom template must contain a `{skills}` placeholder where the skill list is inserted and a `{runner_instructions}` placeholder where script-related instructions are inserted.
235235
236236
:::zone-end
237237

@@ -291,20 +291,43 @@ project_info_skill = Skill(
291291
)
292292

293293
@project_info_skill.resource
294-
def environment() -> str:
294+
def environment() -> Any:
295295
"""Get current environment configuration."""
296296
env = os.environ.get("APP_ENV", "development")
297297
region = os.environ.get("APP_REGION", "us-east-1")
298298
return f"Environment: {env}, Region: {region}"
299299

300300
@project_info_skill.resource(name="team-roster", description="Current team members")
301-
def get_team_roster() -> str:
301+
def get_team_roster() -> Any:
302302
"""Return the team roster."""
303303
return "Alice Chen (Tech Lead), Bob Smith (Backend Engineer)"
304304
```
305305

306306
When the decorator is used without arguments (`@skill.resource`), the function name becomes the resource name and the docstring becomes the description. Use `@skill.resource(name="...", description="...")` to set them explicitly.
307307

308+
### Code-defined scripts
309+
310+
Use the `@skill.script` decorator to register a function as an executable script on a skill. Code-defined scripts run **in-process** and do not require a script executor. Both sync and async functions are supported:
311+
312+
```python
313+
from agent_framework import Skill
314+
315+
unit_converter_skill = Skill(
316+
name="unit-converter",
317+
description="Convert between common units using a conversion factor",
318+
content="Use the convert script to perform unit conversions.",
319+
)
320+
321+
@unit_converter_skill.script(name="convert", description="Convert a value: result = value × factor")
322+
def convert_units(value: float, factor: float) -> str:
323+
"""Convert a value using a multiplication factor."""
324+
import json
325+
result = round(value * factor, 4)
326+
return json.dumps({"value": value, "factor": factor, "result": result})
327+
```
328+
329+
When the decorator is used without arguments (`@skill.script`), the function name becomes the script name and the docstring becomes the description. The function's typed parameters are automatically converted into a JSON Schema that the agent uses to pass arguments.
330+
308331
### Combining file-based and code-defined skills
309332

310333
Pass both `skill_paths` and `skills` to a single `SkillsProvider`. File-based skills are discovered first; if a code-defined skill has the same name as an existing file-based skill, the code-defined skill is skipped:
@@ -327,6 +350,83 @@ skills_provider = SkillsProvider(
327350

328351
:::zone-end
329352

353+
:::zone pivot="programming-language-python"
354+
355+
## Script execution
356+
357+
Skills can include executable scripts that the agent runs via the `run_skill_script` tool. How a script runs depends on how it was defined:
358+
359+
- **Code-defined scripts** (registered via `@skill.script`) run **in-process** as direct function calls. No runner is needed.
360+
- **File-based scripts** (`.py` files discovered in skill directories) require a `SkillScriptRunner` — any callable matching `(skill, script, args) -> Any` — that determines how the script is run (for example, as a local subprocess).
361+
362+
### File-based script execution
363+
364+
To enable execution of file-based scripts, pass a `script_runner` to `SkillsProvider`. Any sync or async callable that satisfies the `SkillScriptRunner` protocol can be used:
365+
366+
```python
367+
from pathlib import Path
368+
from agent_framework import Skill, SkillScript, SkillsProvider
369+
370+
def my_runner(skill: Skill, script: SkillScript, args: dict | None = None) -> str:
371+
"""Run a file-based script as a subprocess."""
372+
import subprocess, sys
373+
cmd = [sys.executable, str(Path(skill.path) / script.path)]
374+
if args:
375+
for key, value in args.items():
376+
if value is not None:
377+
cmd.extend([f"--{key}", str(value)])
378+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
379+
return result.stdout.strip()
380+
381+
skills_provider = SkillsProvider(
382+
skill_paths=Path(__file__).parent / "skills",
383+
script_runner=my_runner,
384+
)
385+
```
386+
387+
The runner receives the resolved `Skill`, `SkillScript`, and an optional `args` dictionary. File-based scripts are automatically discovered from `.py` files in skill directories.
388+
389+
> [!WARNING]
390+
> The runner above is provided for **demonstration purposes only**. For production use, consider adding:
391+
>
392+
> - Sandboxing (for example, containers, `seccomp`, or `firejail`)
393+
> - Resource limits (CPU, memory, wall-clock timeout)
394+
> - Input validation and allow-listing of executable scripts
395+
> - Structured logging and audit trails
396+
397+
> [!NOTE]
398+
> If file-based skills with scripts are provided but no `script_runner` is set, `SkillsProvider` raises a `ValueError`.
399+
400+
## Script approval
401+
402+
Use `require_script_approval=True` on `SkillsProvider` to gate all script execution behind human approval. Instead of executing immediately, the agent pauses and returns approval requests:
403+
404+
```python
405+
from agent_framework import Agent, Skill, SkillsProvider
406+
407+
# Create provider with approval enabled
408+
skills_provider = SkillsProvider(
409+
skills=[my_skill],
410+
require_script_approval=True,
411+
)
412+
413+
# Run the agent — script calls pause for approval
414+
result = await agent.run("Deploy version 2.5.0 to production", session=session)
415+
416+
# Handle approval requests
417+
while result.user_input_requests:
418+
for request in result.user_input_requests:
419+
print(f"Script: {request.function_call.name}")
420+
print(f"Args: {request.function_call.arguments}")
421+
422+
approval = request.to_function_approval_response(approved=True)
423+
result = await agent.run(approval, session=session)
424+
```
425+
426+
When a script is rejected (`approved=False`), the agent is informed that the user declined and can respond accordingly.
427+
428+
:::zone-end
429+
330430
## Security best practices
331431

332432
Agent Skills should be treated like any third-party code you bring into your project. Because skill instructions are injected into the agent's context — and skills can include scripts — applying the same level of review and governance you would to an open-source dependency is essential.

0 commit comments

Comments
 (0)