-
Notifications
You must be signed in to change notification settings - Fork 836
feat(plugins): add MultiAgentPlugin for Swarm and Graph orchestrators #2280
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
zastrowm
wants to merge
9
commits into
strands-agents:main
Choose a base branch
from
zastrowm:multi_agent_plugins
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 8 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
1c5dcc2
Implement plugins for MultiAgent
zastrowm 7aae080
Address self-review
zastrowm 57d89d1
Move to kwarg
zastrowm d413d52
fix: address review feedback - coverage gaps and param ordering
zastrowm 9cc15af
feat: add add_hook to Graph, Swarm, and MultiAgentBase
zastrowm 14ff42a
refactor: remove redundant hasattr check from plugin registry
zastrowm 51613f3
refactor: remove dead register_hooks helper and improve add_hook erro…
zastrowm 9cff053
refactor: DRY up discovery helpers and add guard test
zastrowm 1d79ed0
fix: remove unused TypeVar import and variable
zastrowm File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
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
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,13 +1,16 @@ | ||
| """Plugin system for extending agent functionality. | ||
| """Plugin system for extending agent and orchestrator functionality. | ||
|
|
||
| This module provides a composable mechanism for building objects that can | ||
| extend agent behavior through automatic hook and tool registration. | ||
| extend agent and multi-agent orchestrator behavior through automatic hook | ||
| and tool registration. | ||
| """ | ||
|
|
||
| from .decorator import hook | ||
| from .multiagent_plugin import MultiAgentPlugin | ||
| from .plugin import Plugin | ||
|
|
||
| __all__ = [ | ||
| "MultiAgentPlugin", | ||
| "Plugin", | ||
| "hook", | ||
| ] |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| """Shared utility for discovering decorated methods on plugin instances. | ||
|
|
||
| This module provides helper functions used by both Plugin and MultiAgentPlugin | ||
| to scan for @hook (and optionally @tool) decorated methods, and shared registry | ||
| utilities for plugin initialization and hook registration. | ||
| """ | ||
|
|
||
| import inspect | ||
| import logging | ||
| from collections.abc import Awaitable, Callable | ||
| from typing import Any, TypeVar, cast | ||
|
|
||
| from .._async import run_async | ||
| from ..hooks.registry import HookCallback | ||
| from ..tools.decorator import DecoratedFunctionTool | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
| T = TypeVar("T") | ||
|
zastrowm marked this conversation as resolved.
Outdated
|
||
|
|
||
|
|
||
| def _discover_methods(instance: object, plugin_name: str, predicate: Callable[[object], bool], label: str) -> list[Any]: | ||
| """Scan an instance's class hierarchy for methods matching a predicate. | ||
|
|
||
| Walks the MRO in reverse so parent class methods come first, but child | ||
| overrides win (only the child's version is included). | ||
|
|
||
| Args: | ||
| instance: The plugin instance to scan. | ||
| plugin_name: The plugin name (used for debug logging). | ||
| predicate: Function that returns True for attributes to collect. | ||
| label: Label for debug logging (e.g., "hook", "tool"). | ||
|
|
||
| Returns: | ||
| List of matching bound methods/descriptors in declaration order. | ||
| """ | ||
| results: list[Any] = [] | ||
| seen: set[str] = set() | ||
|
|
||
| for cls in reversed(type(instance).__mro__): | ||
| for attr_name in cls.__dict__: | ||
| if attr_name in seen: | ||
| continue | ||
| seen.add(attr_name) | ||
|
|
||
| try: | ||
| bound = getattr(instance, attr_name) | ||
| except Exception: | ||
| continue | ||
|
|
||
| if predicate(bound): | ||
| results.append(bound) | ||
| logger.debug("plugin=<%s>, %s=<%s> | discovered", plugin_name, label, attr_name) | ||
|
|
||
| return results | ||
|
|
||
|
|
||
| def discover_hooks(instance: object, plugin_name: str) -> list[HookCallback]: | ||
| """Scan an instance's class hierarchy for @hook decorated methods. | ||
|
|
||
| Args: | ||
| instance: The plugin instance to scan. | ||
| plugin_name: The plugin name (used for debug logging). | ||
|
|
||
| Returns: | ||
| List of bound hook callback methods in declaration order. | ||
| """ | ||
| return _discover_methods( | ||
| instance, | ||
| plugin_name, | ||
| predicate=lambda bound: hasattr(bound, "_hook_event_types") and callable(bound), | ||
| label="hook", | ||
| ) | ||
|
|
||
|
|
||
| def discover_tools(instance: object, plugin_name: str) -> list[DecoratedFunctionTool]: | ||
| """Scan an instance's class hierarchy for @tool decorated methods. | ||
|
|
||
| Args: | ||
| instance: The plugin instance to scan. | ||
| plugin_name: The plugin name (used for debug logging). | ||
|
|
||
| Returns: | ||
| List of DecoratedFunctionTool instances in declaration order. | ||
| """ | ||
| return _discover_methods( | ||
| instance, | ||
| plugin_name, | ||
| predicate=lambda bound: isinstance(bound, DecoratedFunctionTool), | ||
| label="tool", | ||
| ) | ||
|
|
||
|
|
||
| def call_init_method(init_method: Callable[..., Any], target: Any) -> None: | ||
| """Call a plugin's init method, handling both sync and async implementations. | ||
|
|
||
| Args: | ||
| init_method: The init_agent or init_multi_agent method to call. | ||
| target: The agent or orchestrator instance to pass to the init method. | ||
| """ | ||
| if inspect.iscoroutinefunction(init_method): | ||
| async_init = cast(Callable[..., Awaitable[None]], init_method) | ||
| run_async(lambda: async_init(target)) | ||
| else: | ||
| init_method(target) | ||
Oops, something went wrong.
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.