You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@@ -74,13 +74,13 @@ This pattern keeps the agent's context window lean while giving it access to dee
74
74
75
75
## Providing skills to an agent
76
76
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`.
81
78
82
79
:::zone pivot="programming-language-csharp"
83
80
81
+
> [!NOTE]
82
+
> Script execution is not yet supported in C# and will be added in a future release.
83
+
84
84
### Basic setup
85
85
86
86
Create a `FileAgentSkillsProvider` pointing to a directory containing your skills, and add it to the agent's context providers:
> 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.
235
235
236
236
:::zone-end
237
237
@@ -291,20 +291,43 @@ project_info_skill = Skill(
291
291
)
292
292
293
293
@project_info_skill.resource
294
-
defenvironment() -> str:
294
+
defenvironment() -> Any:
295
295
"""Get current environment configuration."""
296
296
env = os.environ.get("APP_ENV", "development")
297
297
region = os.environ.get("APP_REGION", "us-east-1")
298
298
returnf"Environment: {env}, Region: {region}"
299
299
300
300
@project_info_skill.resource(name="team-roster", description="Current team members")
301
-
defget_team_roster() -> str:
301
+
defget_team_roster() -> Any:
302
302
"""Return the team roster."""
303
303
return"Alice Chen (Tech Lead), Bob Smith (Backend Engineer)"
304
304
```
305
305
306
306
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.
307
307
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")
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
+
308
331
### Combining file-based and code-defined skills
309
332
310
333
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:
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
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`)
> - 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)
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
+
330
430
## Security best practices
331
431
332
432
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