Skip to content

feat(skills): opt-in dynamic skill loading/unloading for AgentSkills#3284

Open
agent-of-mkmeral wants to merge 3 commits into
strands-agents:mainfrom
agent-of-mkmeral:feat/skills-dynamic-loading
Open

feat(skills): opt-in dynamic skill loading/unloading for AgentSkills#3284
agent-of-mkmeral wants to merge 3 commits into
strands-agents:mainfrom
agent-of-mkmeral:feat/skills-dynamic-loading

Conversation

@agent-of-mkmeral

Copy link
Copy Markdown
Contributor

Summary

Adds an opt-in dynamic_loading flag to the AgentSkills vended plugin so an agent can load and unload skills at runtime from directories in its own sandbox — e.g. a skills/ folder inside a repository it just cloned — without restarting or reconfiguring the plugin.

plugin = AgentSkills(skills=["./skills/"], dynamic_loading=True)
agent = Agent(plugins=[plugin])

What changes

Flag off (default) — zero behavior change

  • Same single skills(skill_name) tool, same schema.
  • Injected <available_skills> XML is byte-identical to previous releases (regression-tested).

Flag on — action-based skills tool

The tool keeps the name skills but becomes action-based:

action args behavior
activate skill_name unchanged: returns full skill instructions
load path registers every skill found under path (skill dir, parent dir of skill dirs, or direct SKILL.md path), resolved through the agent's sandbox
unload path removes the skills that path contributed
  • A <usage> hint is prepended to the injected <available_skills> block so the model discovers the mechanism from the prompt itself.
  • The skills block is re-injected before every model call (BeforeModelCallEvent, dynamic mode only), so a mid-invocation load shows up in the very next turn's system prompt. Default mode keeps the existing once-per-invocation injection.

Semantics

  • No shadowing: a dynamic skill can never override a plugin-configured skill (skipped with a warning). Cross-path dynamic collisions: most recent load wins (warned).
  • Refresh: re-loading a path drops its previous contribution first, so edits are picked up and deleted skills disappear.
  • Persistence: loaded paths are recorded in agent.state (under the existing plugin state key) and restored on the next run over the same session. Fail-soft: a recorded path that no longer exists just logs and contributes nothing until re-loaded.
  • Programmatic API available regardless of the flag (the flag only controls the tool surface exposed to the model): await plugin.load_skills_for(agent, path), plugin.unload_skills_for(agent, path), plugin.get_dynamic_skills(agent).
  • Per-agent isolation preserved (WeakKeyDictionary), multi-agent safe; set_available_skills() resets dynamic origins alongside the per-agent cache.

Internals

  • Path-walking logic in _load_skill_paths was factored into a reusable _load_skills_from_path(sandbox, path) (no behavior change) and shared by configured and dynamic loading, including per-skill fail-soft resilience.
  • Both tool variants are @tool-decorated methods sharing the tool name skills; __init__ filters self._tools so exactly one registers per configuration.

Testing

  • 25 new tests (tool selection, tool actions, refresh/unload semantics, shadowing rules, state persistence + restore, fail-soft restore, BeforeModelCallEvent re-injection incl. no-op in default mode, programmatic API, byte-identical default XML, usage-hint rendering, multi-agent isolation, set_available_skills reset).
  • Full skills suite: 174 passed; vended_plugins + plugins suites: 526 passed.
  • ruff format + ruff check clean; new mypy-visible code type-clean.

Related: #2894 (earlier exploration of dynamic skill loading; this PR is based on main and keeps the default path byte-compatible).

Pre-PR review loop

An adversarial critic pass ran before this PR was opened. It caught one real bug and several hardening items, all addressed in the second commit:

  • [BLOCKER] Session-restore ordering: session managers replace agent.state wholesale on AgentInitializedEvent, after plugin init_agent already ran, so persisted dynamic_paths were never restored. Fixed by reconciling recorded-but-not-loaded paths at hook time (BeforeInvocationEvent, and BeforeModelCallEvent in dynamic mode) — fail-soft and retried, so a path starts working the moment it appears in the sandbox. Regression-tested with the real ordering.
  • [MAJOR] Subclassing: the tool-variant filter now removes only the unwanted variant instead of keeping only the wanted one, so @tool methods added by subclasses survive.
  • [MAJOR] Security: explicit prompt-injection warning on dynamic_loading (model-initiated loading of untrusted SKILL.md reaches the system prompt); enable only for trusted sandbox contents.
  • [MINOR] root-path (/) normalization, corrupted-state (dynamic_paths not a list) guard, unload-after-override semantics documented, refresh non-atomicity noted.
  • +8 regression tests → 182 passed in the skills suite, 534 across vended_plugins + plugins.

BLOCKER: reconcile dynamic paths at hook time - session managers replace agent.state wholesale after init_agent, so init-time restore never saw persisted dynamic_paths. MAJOR: tool variant filter now removes only the unwanted variant so subclass @tool methods survive. MAJOR: prompt-injection warning on dynamic_loading docs. MINOR: root-path normalization, corrupted-state guard, unload-after-override docs. +8 regression tests.
@agent-of-mkmeral
agent-of-mkmeral requested a review from a team as a code owner July 15, 2026 18:36
@github-actions github-actions Bot added size/xl area-tool Tool behavior/api area-agent Related to the agent class or general agent questions python Pull requests that update python code enhancement New feature or request labels Jul 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-agent Related to the agent class or general agent questions area-tool Tool behavior/api enhancement New feature or request python Pull requests that update python code size/xl

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants