feat(skills): opt-in dynamic skill loading/unloading for AgentSkills#3284
Open
agent-of-mkmeral wants to merge 3 commits into
Open
feat(skills): opt-in dynamic skill loading/unloading for AgentSkills#3284agent-of-mkmeral wants to merge 3 commits into
agent-of-mkmeral wants to merge 3 commits into
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds an opt-in
dynamic_loadingflag to theAgentSkillsvended plugin so an agent can load and unload skills at runtime from directories in its own sandbox — e.g. askills/folder inside a repository it just cloned — without restarting or reconfiguring the plugin.What changes
Flag off (default) — zero behavior change
skills(skill_name)tool, same schema.<available_skills>XML is byte-identical to previous releases (regression-tested).Flag on — action-based
skillstoolThe tool keeps the name
skillsbut becomes action-based:activateskill_nameloadpathpath(skill dir, parent dir of skill dirs, or directSKILL.mdpath), resolved through the agent's sandboxunloadpath<usage>hint is prepended to the injected<available_skills>block so the model discovers the mechanism from the prompt itself.BeforeModelCallEvent, dynamic mode only), so a mid-invocationloadshows up in the very next turn's system prompt. Default mode keeps the existing once-per-invocation injection.Semantics
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.await plugin.load_skills_for(agent, path),plugin.unload_skills_for(agent, path),plugin.get_dynamic_skills(agent).WeakKeyDictionary), multi-agent safe;set_available_skills()resets dynamic origins alongside the per-agent cache.Internals
_load_skill_pathswas 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.@tool-decorated methods sharing the tool nameskills;__init__filtersself._toolsso exactly one registers per configuration.Testing
BeforeModelCallEventre-injection incl. no-op in default mode, programmatic API, byte-identical default XML, usage-hint rendering, multi-agent isolation,set_available_skillsreset).vended_plugins+pluginssuites: 526 passed.ruff format+ruff checkclean; new mypy-visible code type-clean.Related: #2894 (earlier exploration of dynamic skill loading; this PR is based on
mainand 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:
agent.statewholesale onAgentInitializedEvent, after plugininit_agentalready ran, so persisteddynamic_pathswere never restored. Fixed by reconciling recorded-but-not-loaded paths at hook time (BeforeInvocationEvent, andBeforeModelCallEventin dynamic mode) — fail-soft and retried, so a path starts working the moment it appears in the sandbox. Regression-tested with the real ordering.@toolmethods added by subclasses survive.dynamic_loading(model-initiated loading of untrusted SKILL.md reaches the system prompt); enable only for trusted sandbox contents./) normalization, corrupted-state (dynamic_pathsnot a list) guard, unload-after-override semantics documented, refresh non-atomicity noted.vended_plugins+plugins.