feat(plugins): add declarative agent plugin support#75
Merged
Conversation
- add `PluginDef` schema and `plugins:` field on agents (inline object + string shorthand) - resolve entries to live `strands.plugins.Plugin` instances via class or factory, passed to `Agent(plugins=[...])` - rewrite plugin `type:` import specs relative to the config file; leave `params` opaque - remove `plugins` from `agent_kwargs` now that it's first-class - require `strands-agents>=1.48.0` across all extras for the plugins API - document in Chapter 19 + full reference and add runnable `examples/15_plugins` - cover class, factory, non-Plugin, and bad-spec paths in tests with a `FakePlugin` fake
… trim docs (#77) Co-authored-by: Kiro Agent <244629292+kiro-agent@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Adds first-class declarative agent plugin support to strands-compose’s YAML schema and resolution pipeline, wiring resolved strands.plugins.Plugin instances into strands.Agent(plugins=[...]) while keeping the library as a thin translator.
Changes:
- Introduces
PluginDefand anagents.<name>.plugins:field (string shorthand or{type, params}), plus path rewriting for plugin import specs. - Adds plugin resolvers (
resolve_plugin,resolve_plugin_entry) and wires resolved plugins through both default and custom agent factory construction paths. - Updates docs + examples with a new Plugins chapter and a runnable
examples/15_plugins/demo; bumpsstrands-agentsdependency floor to>=1.48.0.
Reviewed changes
Copilot reviewed 26 out of 27 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| uv.lock | Raises locked strands-agents floor to >=1.48.0,<2.0.0. |
| pyproject.toml | Raises runtime + extras dependency floor for strands-agents. |
| README.md | Updates badge to reflect new strands-agents minimum. |
| src/strands_compose/config/schema.py | Adds PluginDef and AgentDef.plugins; removes plugins from documented agent_kwargs passthrough list. |
| src/strands_compose/config/loaders/helpers.py | Rewrites relative plugin import specs to absolute paths (mirrors hooks/tools behavior). |
| src/strands_compose/config/resolvers/plugins.py | Implements plugin resolution via load_object + params kwargs, validating return type is Plugin. |
| src/strands_compose/config/resolvers/agents.py | Resolves AgentDef.plugins and passes plugins= into both Agent() and custom factory paths. |
| src/strands_compose/config/resolvers/hooks.py | Aligns hook import error handling/docs to load_object / ImportResolutionError. |
| src/strands_compose/config/resolvers/init.py | Exposes plugin resolvers in the resolvers package public surface. |
| src/strands_compose/config/init.py | Exposes PluginDef in strands_compose.config public surface. |
| tests/fakes/strands.py | Adds FakePlugin and fake_plugin_factory to support plugin wiring/resolution tests. |
| tests/fakes/init.py | Re-exports new fake plugin helpers. |
| tests/resolve/test_plugins.py | Adds unit tests for plugin resolver behavior (malformed spec, non-Plugin, class/factory, string shorthand). |
| tests/resolve/test_agents.py | Adds wiring test proving plugin-contributed @tool reaches agent.tool_names. |
| docs/README.md | Adds docs landing page and links in a chapter table. |
| docs/configuration/README.md | Adds Chapter 19 entry (Plugins). |
| docs/configuration/Chapter_19.md | New Plugins chapter describing YAML shape, factory/class patterns, and error reference. |
| docs/configuration/Chapter_18.md | Updates full reference to include plugins and PluginDef. |
| docs/configuration/Chapter_06.md | Adds hooks ↔ plugins cross-reference. |
| docs/img/index.html | Fixes logo asset path in docs HTML. |
| examples/README.md | Adds example index entry for 15_plugins. |
| examples/15_plugins/config.yaml | New example config demonstrating plugins: usage (skills, context injector factory, goal loop). |
| examples/15_plugins/main.py | Runnable example driver that loads the config and demonstrates plugin behavior. |
| examples/15_plugins/plugins.py | Example factory for building a ContextInjector. |
| examples/15_plugins/README.md | Example documentation explaining plugin use-cases and the demo setup. |
| examples/15_plugins/skills/conventional-commits/SKILL.md | Example skill content used by AgentSkills. |
| .kiro/skills/library-development/references/project-map.md | Updates documented dependency floor and extras list in the project map. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Member
Author
|
@copilot I agree with your proposals to adjust tests as proposed in comments above. Implement! |
Contributor
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.
Description
Adds first-class support for strands plugins as a declarative field on agents.
Agents can now attach reusable behaviour packages — skills, context injection, steering, goal loops, or any custom
strands.plugins.Plugin— straight from YAML, the same wayhooks:already works.Each
plugins:entry is resolved to a livestrands.plugins.Plugininstance and passed through tostrands.Agent(plugins=[...]). strands-compose stays a thin translator: it resolves the import spec, calls it withparams, validates the result is aPlugin, and otherwise gets out of the way.What's included:
PluginDef(type+params) and aplugins: list[PluginDef | str]field onAgentDef. Accepts an inline object (type:+ optionalparams:) or a bare string shorthand, mirroringhooks:.config/resolvers/plugins.pywithresolve_plugin/resolve_plugin_entry. Supports bothmodule.path:Name(installed package) and./file.py:Name(local file) import specs, and resolves either aPluginsubclass or a factory callable returning one.type:is rewritten relative to the config file (like tools/hooks);paramsare treated as opaque kwargs and forwarded verbatim.build_agent_from_defresolves the plugin list and passes it to both the standard and custom-factoryAgent(...)construction paths.pluginsremoved from the documentedagent_kwargspassthrough now that it's a first-class field (prevents a duplicate-keyword error).docs/README.md, and a runnableexamples/15_plugins/demoingAgentSkills, aContextInjectorfactory, andGoalLoop, including a sampleconventional-commitsskill.strands-agentsto>=1.48.0,<2.0.0across all extras, the minimum exposing the plugins API used here.Related Issues
N/A
Type of Change
YAML / API Impact
Yes — this extends both the config schema and the public API, backwards-compatible:
agents.<name>.plugins:list. Defaults to empty, so all existing configs are unaffected.PluginDefmodel andresolve_plugin/resolve_plugin_entryresolvers. Nothing existing is renamed or removed.pluginsis no longer accepted viaagent_kwargs— it's now a dedicated field. Passing it both ways raises a duplicate-keywordTypeErrorfromAgent(), which is the intended guard rather than a silent regression.strands-agents>=1.48.0.Testing
How have you tested the change?
uv run just check(lint + type check) — clean, no issuesuv run just testfor overall testing — 232 passed, 76% coverageexamples/still workNew/updated tests:
tests/resolve/test_plugins.py— covers class resolution withparams, factory resolution, the bare-string shorthand, missing:intype(ValueError), and a non-Pluginresult (TypeError).tests/resolve/test_agents.py— asserts a declared plugin's@toolreachesagent.tool_names, proving the resolved list is wired intoAgent(plugins=[...]).tests/fakes/— adds aFakePluginandfake_plugin_factorycovering both the class and callable resolution paths.Checklist
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.