feat(skills): add agent skills#379
Conversation
Implements Agent Skills support following the AgentSkills.io specification. Features: - Single tool with action-based interface (discover, list, activate, deactivate, get_resource, list_resources, status) - Progressive disclosure: metadata-only discovery, full instructions on activation - Resource loading for scripts, references, and assets - Path traversal protection and file size limits - Thread-safe registry for multi-connection scenarios Actions: - discover: Scan directory for available skills - list: Show all discovered skills with activation status - activate: Load skill instructions into context - deactivate: Remove skill from active context - get_resource: Load specific resource file from skill - list_resources: List available resources in a skill - status: Show current skills directory and activation status Follows the pattern established by mcp_client tool with action parameter. Refs: strands-agents/sdk-python#1181
- Add auto-discovery on first use (controlled by STRANDS_SKILLS_AUTO_DISCOVER env var) - Rename parameter to STRANDS_SKILLS_DIR to follow strands env var naming convention - Centralize auto-discovery logic in _get_or_create_registry - Add tests for auto-discovery feature Environment Variables: - STRANDS_SKILLS_DIR: Directory containing skills (default: ./skills) - STRANDS_SKILLS_AUTO_DISCOVER: Enable auto-discovery on first use (default: true) Usage: # Just use the tool - skills are auto-discovered from STRANDS_SKILLS_DIR agent.tool.skills(action='list') agent.tool.skills(action='activate', skill_name='code-reviewer') # Or override the directory agent.tool.skills(action='list', STRANDS_SKILLS_DIR='/custom/path')
Simplify the skills tool to match how other implementations work: - Remove 'activate' action, rename to 'use' (just loads instructions) - Remove 'deactivate' action (not a real concept in skills) - Remove 'discover' action (auto-discovery handles this) - Remove 'status' action (no state to track) - Remove registry tracking (unnecessary complexity) The tool now simply: 1. Auto-discovers skills from STRANDS_SKILLS_DIR 2. Lists available skills with 'list' action 3. Returns skill instructions with 'use' action 4. Loads resources with 'get_resource' action This matches the pattern used by: - Anthropic's skills implementation - AWS sample-strands-agents-agentskills Actions: - list: Show available skills - use: Load skill instructions (returned as tool result) - get_resource: Load a script/reference file - list_resources: List files in a skill
- Add skills tool to tools overview table - Add Agent Skills usage example section - Add STRANDS_SKILLS_DIR environment variable documentation - Fix ruff linting issues (unused f-string prefix)
Windows uses backslashes for paths, but the test and categorization logic expects forward slashes. Using Path.as_posix() ensures consistent forward slashes on all platforms.
- Added get_skills_prompt() function that generates XML-formatted skills list - Uses same cache as skills tool (no duplicate discovery) - Returns empty string if no skills found - Follows AgentSkills.io recommended format for system prompts - Improved list_resources with Path.parts for cross-platform support - Updated tests for simplified API (list/use vs activate/deactivate)
vivekghatala
left a comment
There was a problem hiding this comment.
Thank you for working on this. I was thinking to pick this up as one of the consumer.
The skills tool uses yaml.safe_load() to parse YAML frontmatter from SKILL.md files (AgentSkills.io specification), but PyYAML was not listed as a dependency, causing ModuleNotFoundError on import.
afarntrog
left a comment
There was a problem hiding this comment.
This is great! Wondering if it should be vended as part of the core SDK instead of the tools repo.
|
@afarntrog strands-agents/docs#528 what do you think? |
|
This tool feels really great. I’ve been waiting for it for a long time and I’m wondering when it will be released. |
|
Hi @kazuu-bp , you're right, I was talking about how this tool could work with Strands, also noticed the design doc mentions handling this via Plugin later |
Rename 'import_dir' param to 'source' and extend the import action to auto-detect the source type: - Directory path: discovers all skills in subdirectories (existing behavior) - File path: imports a single skill from a SKILL.md file - HTTPS URL: fetches and imports a single skill from a remote SKILL.md Key changes: - Add 'source' param replacing 'import_dir' (unreleased API, no compat concern) - Add 'virtual' and 'instructions' fields to SkillMetadata for file/URL imports - Virtual skills work with list/use but return clear errors for get_resource/list_resources - HTTPS-only URLs with 1MB size limit, 30s timeout, redirect protection - Uses urllib.request (stdlib only, no new dependencies) New helpers: _import_from_content, _import_from_file, _import_from_url, _import_from_directory Tests: 44 passing (23 new tests covering file import, URL import with mocks, virtual skill behavior, and edge cases)
File-imported skills (source='/path/to/SKILL.md') now use the parent directory as the skill path, so get_resource and list_resources work exactly like directory-discovered skills. Only URL-imported skills are marked virtual since they have no local directory. Changes: - _import_from_file: sets path=file_path.parent, virtual=False - _import_from_file: does its own frontmatter parsing (no longer uses _import_from_content which is now URL-only) - Virtual guard messages say 'URL' not 'file or URL' - New test: test_import_from_file_resources_accessible - TestVirtualSkillBehavior now uses URL mocks (the only virtual case)
|
Is this effort still going forward? |
|
hey folks, yes this will go forward. We are right now clarifying the difference in approaches between this PR and strands-agents/sdk-python#1755 The idea is to have two approaches where one is self-contained tool (this PR, depends on strands-agents/sdk-python#1822), so we can keep the tool spec up to date with loaded skills. Then we have the more "complete" example with tools and hooks on strands-agents/sdk-python#1755 |
|
What I would ask for next is, is there a future plan to add "plugins" tool to wrap plugin implementation? Use case is to enable agents to load the skills in test-time, SDK feature is an amazing goal but secondary use which often missed first like building tools for directly agent would be beneficial to release first. Internal SDK compositon can follow patterns are battle-tested by community in this way too ^^ note that, this is my humble opinion |
|
hey folks, jfyi strands-agents/sdk-python#1755 where we develop skills as a plugin is merged. I will followup on this PR later this week for next week's release |
…at import - Replace custom skill loading with Skill.from_directory/from_file/from_content - Switch from per-agent cache to module-level _cache (matches other tools pattern) - Auto-discover skills at import time when STRANDS_SKILLS_DIR is set - Keep sync_skills() as public helper for programmatic pre-loading - Remove tool_context from action handlers (only needed on main skills() fn) - Remove experimental warnings - Expand test coverage from 45 to 93 tests
agent-of-mkmeral
left a comment
There was a problem hiding this comment.
Adversarial Testing Review
TL;DR: The skills tool survived comprehensive adversarial testing. 142 tests total (93 original + 49 adversarial), all passing. The code is solid — security boundaries hold, error handling is thorough, and it works correctly with real production skills.
🔬 Full Adversarial Testing Report
Adversarial Testing Result: PASS — no reproducible failures found
Scope: Path traversal, URL import SSRF, cache poisoning, YAML injection, XML injection, concurrent access, binary file handling, contract verification, real skills compatibility
Tests written: 49 adversarial tests
Tests passing: 49
Tests failing (findings): 0
Test Categories & Results
| Category | Tests | Result | Details |
|---|---|---|---|
| Path Traversal & Symlinks | 6 | ✅ PASS | Symlink escape, null bytes, double-dots, absolute paths — all blocked |
| URL Import Security | 6 | ✅ PASS | SSRF redirect to AWS metadata, data: URI, javascript:, non-HTTPS — all rejected |
| Cache Poisoning | 3 | ✅ PASS | Import collision protection works, external mutation is by-design |
| YAML Attacks | 6 | ✅ PASS | YAML bomb, !!python/object RCE attempt, empty frontmatter — all handled safely |
| XML Injection | 3 | ✅ PASS | Script injection, CDATA injection, name injection — all properly escaped via xml.sax.saxutils.escape() |
| Concurrent Access | 2 | ✅ PASS | Multi-threaded sync and import/use — no crashes |
| Binary & Encoding | 4 | ✅ PASS | Binary files, BOM, case-insensitive SKILL.md, spaces in names |
| Cache Behavior | 2 | ✅ PASS | Stale cache is by-design, import adds to existing cache correctly |
| Contract Verification | 5 | ✅ PASS | Progressive disclosure, experimental warning, default dir, API surface |
| Real Skills | 6 | ✅ PASS | All 6 production skills (adversarial-tester, doc-writer, implementer, refiner, release-notes, reviewer) load and work correctly |
| Additional Edge Cases | 6 | ✅ PASS | None/empty params, file-as-dir, deep nesting, idempotency |
Security Assessment
Path traversal: _is_safe_path() correctly uses resolve() + relative_to() to prevent symlink escapes. Tested with symlinks to /etc/passwd, multi-level ../ traversal, null bytes, and absolute paths.
URL import: HTTPS-only enforcement works. Redirect detection catches SSRF attempts (e.g., redirect to http://169.254.169.254/latest/meta-data/). Non-HTTPS schemes (http://, ftp://, file://, data:, javascript:) all rejected.
YAML safety: Uses yaml.safe_load() via the SDK's Skill.from_content() — !!python/object tags are not executed.
XML injection: xml.sax.saxutils.escape() properly handles <, >, &, and " in skill names and descriptions.
Notable Observations (Not Bugs)
-
Cache staleness by design:
_sync_skills()only discovers skills on the first call when cache is empty. Subsequent calls with differentskills_dirvalues will NOT discover new skills. This is documented behavior — users must callsync_skills()explicitly to refresh. Theimportaction correctly bypasses this by adding directly to the existing cache. -
PR description inconsistency: The PR body references
get_skills_prompt()helper function, but the actual implementation usessync_skills(). The code is correct; the PR description is outdated. -
URL credentials in logs: URLs with embedded credentials (e.g.,
https://user:pass@host/SKILL.md) are passed through tologger.info(). While functional, this could leak credentials in logs. Low severity since it requires intentional credential embedding. -
Thread safety: The global
_cachedict is a plaindict(notthreading.Lock-protected). In practice this is safe for CPython due to the GIL, and concurrent access tests pass. However, for PyPy or future GIL-free Python, athreading.Lockwould be needed.
🧪 Real Skills Compatibility Test
Tested against 6 production skills from a real repository:
| Skill | List | Use | Resources | Spec Update |
|---|---|---|---|---|
| task-adversarial-tester | ✅ | ✅ | ✅ | ✅ |
| task-doc-writer | ✅ | ✅ | ✅ | ✅ |
| task-implementer | ✅ | ✅ | ✅ | ✅ |
| task-refiner | ✅ | ✅ | ✅ | ✅ |
| task-release-notes | ✅ | ✅ | ✅ | ✅ |
| task-reviewer | ✅ | ✅ | ✅ | ✅ |
All skills are correctly discovered, their full instructions load properly, resource listing works, and the tool spec is updated with the complete skills catalog XML.
📝 Adversarial Test File
The full adversarial test suite (49 tests, ~967 lines) is available as tests/test_adversarial_skills.py. Run with:
pip install -e ".[dev]"
pip install opentelemetry-propagator-aws-xray # needed for SDK import
python -m pytest tests/test_adversarial_skills.py -v🤖 AI agent response. Strands Agents. Feedback welcome!
|
TL;DR: Adversarial testing + real skills validation. 43/45 adversarial tests pass. 2 failed due to test design (not code bugs). Code is solid — no security vulnerabilities found. README has 2 incorrect parameter names. 🔴 Adversarial Testing ReportAdversarial Testing Result: PASS — 0 code bugs foundScope: Security (path traversal, symlinks, injection, SSRF), edge cases (unicode, empty inputs, large payloads, YAML bombs), cache behavior (stale cache, concurrency, poisoning), contract verification (API shape, return formats, documented actions), and real production skills validation. Tests written: 45 Test Categories
Real Skills ValidationTested against 6 production skills from strands-coder-private:
All real skills: proper frontmatter ✅, list_resources works ✅, tool_spec updated with all 6 ✅, import by file path works ✅ Security Assessment
NotesThe 2 failing tests ( 🤖 AI agent response. Strands Agents. Feedback welcome! |
🐛 Bug found:
|
agent-of-mkmeral
left a comment
There was a problem hiding this comment.
Assessment: Comment — 2 items must be fixed before merge, rest looks solid
Code quality is high. Security is well-handled. Tested against 6 real production skills — all work correctly. The main blockers are the git dependency in pyproject.toml (not releasable) and incorrect README examples.
Review Summary
- Blocking: pyproject.toml — Uses
git+https://dependency for strands-agents. Must be pinned to a released version before merge. - Blocking: README.md — Two code examples use wrong parameter names (
import_dir→source,STRANDS_SKILLS_DIR→skills_dir). - Minor: PyYAML dependency — Already a transitive dep of strands-agents SDK. Adding it directly is fine for explicitness but worth noting.
- Design observation:
_import_from_fileaccepts any filename — Not justSKILL.md. A file namedFOO.txtwith valid frontmatter will import successfully. This is fine for flexibility but worth documenting. - Design observation: Stale cache —
_sync_skillsonly populates cache when empty. Skills added to disk after first sync aren't discovered until cache is cleared. This is documented behavior but users may find it surprising.
The code survived thorough adversarial testing (45 tests across security, edge cases, concurrency, contract verification, and real production skills). Nice work on the implementation.
🤖 AI agent response. Strands Agents. Feedback welcome!
- pyproject.toml: Replace git+https:// dependency with strands-agents>=1.32.0,<2.0.0 (required for PyPI release, Skill class available since 1.32.0) - README.md: Fix wrong parameter name import_dir → source (line 822) - README.md: Fix wrong parameter name STRANDS_SKILLS_DIR → skills_dir (line 828) - skills.py: Fix _import_from_directory to handle single skill directories (directories containing SKILL.md directly, not just parent directories) Matches the pattern already used in _resolve_skills() Resolves all 3 unresolved review threads + 1 reported bug.
|
/strands review |
- Remove experimental warning from tool spec description - Remove experimental warning from skills() function docstring - Add note about SDK's native AgentSkills plugin to module docstring - Add usage example for SDK plugin alongside tool usage - Add link to SDK plugin documentation The Skills feature is now stable in the SDK (strands.vended_plugins.skills.AgentSkills), so the experimental warning is no longer needed.
|
Pushed commit Changes
The Skills feature is stable in the SDK, so the experimental flag is no longer needed. 🤖 AI agent response. Strands Agents. Feedback welcome! |
mkmeral
left a comment
There was a problem hiding this comment.
@agent-of-mkmeral we still have warning in readme. also check the unit tests, are there any we can combine?
- Remove experimental warnings from README.md (table entry + section blockquote) - Replace with note about SDK plugin availability - Combine 9 pairs of related tests into consolidated tests: - test_use_skill + includes_metadata + includes_resources → single test - test_list_empty_directory + nonexistent → parametrized - test_basic_response + no_instructions → combined - test_cache_persists + cache_cleared → test_cache_lifecycle - test_populates_cache + updates_tool_spec → single test - test_import_ftp_rejected + file_scheme → parametrized - test_url_urlerror + oserror → test_network_errors - test_list_resources_missing + nonexistent → test_error_cases - test_get_resource_missing + nonexistent → test_error_cases All 85 tests pass. No behavior changes.
|
Pushed commit 1. ✅ Removed experimental warnings from README.md
2. ✅ Consolidated unit testsCombined 9 sets of closely related tests that were testing the same action multiple times:
Result: 85 tests all passing ✅ (net reduction of ~38 lines, no behavior changes) 🤖 AI agent response. Strands Agents. Feedback welcome! |

Description
This PR adds a new
skillstool that implements support for Agent Skills - modular packages of specialized instructions that help AI agents perform specific tasks effectively.What are Agent Skills?
Agent Skills are folders containing
SKILL.mdfiles with structured instructions. They follow the AgentSkills.io specification and enable:Tool Design
The tool follows the action-based pattern used by other tools (like
mcp_client,memory,slack):Actions
listuseget_resourcelist_resourcesHelper Function
The tool also provides a
get_skills_prompt()helper for users who want proactive skill awareness in their agent's system prompt:Design Decisions
Simple stateless design: Skills are loaded on demand and returned as tool results. No state tracking or system prompt modification - this matches how Anthropic's skills and AWS sample implementation work.
No activate/deactivate: These concepts don't exist in the AgentSkills.io spec. Skills are simply loaded when needed via conversation history.
Auto-discovery: Skills are automatically discovered from
STRANDS_SKILLS_DIR(default:./skills) on first use.SKILL.md format: Follows AgentSkills.io spec with YAML frontmatter for metadata and markdown body for instructions.
SKILL.md Frontmatter Fields
The tool parses YAML frontmatter from SKILL.md files. Supported fields:
nameskill_nameparameterdescriptionlistaction outputlicenseallowed-toolslistaction output (informational only)Example SKILL.md:
Note: The
allowed-toolsfield is informational only - the tool doesn't enforce or validate tool availability. It's displayed to help users understand what tools a skill expects to use.Skill Directory Structure
Environment Variables
STRANDS_SKILLS_DIR./skillsRelated Issues
Documentation PR
N/A - Documentation is included in README.md updates in this PR.
Type of Change
New Tool
Testing
python -m pytest tests/test_skills.py -v)ruff check src/strands_tools/skills.py tests/test_skills.py)Test coverage includes:
get_skills_prompt()helper function (XML output, empty directory, env var usage, cache sharing)Checklist
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.