feat(plugins): add MultiAgentPlugin for Swarm and Graph orchestrators#2280
feat(plugins): add MultiAgentPlugin for Swarm and Graph orchestrators#2280zastrowm wants to merge 7 commits into
Conversation
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
|
Issue: This PR introduces a new public class ( Suggestion: Add the |
| side_effect=lambda callback, event_type=None: agent.hooks.add_callback(event_type, callback) | ||
| ) | ||
| agent.tool_registry = unittest.mock.MagicMock() | ||
| return agent |
There was a problem hiding this comment.
Issue: No test covers the TypeError raised when an orchestrator without a hooks attribute is passed to _MultiAgentPluginRegistry.__init__. This is a defensive check (line 61-64) that codecov flags as missing coverage.
Suggestion: Add a test like:
def test_registry_raises_type_error_without_hooks_attribute():
orch = MagicMock(spec=[]) # no hooks attribute
del orch.hooks
with pytest.raises(TypeError, match="does not have a 'hooks' attribute"):
_MultiAgentPluginRegistry(orch)| Scans the class for methods decorated with @hook and stores references | ||
| for later registration when the plugin is attached to an orchestrator. | ||
|
|
||
| Uses a guard to prevent double-discovery when used with multiple inheritance |
There was a problem hiding this comment.
Issue: The double-discovery guard (if not hasattr(self, "_hooks")) is a good defensive measure for multiple inheritance, but there's no test that exercises the code path where _hooks is already set before MultiAgentPlugin.__init__ runs (i.e., when Plugin.__init__ runs first in MRO). The test_dual_plugin_discovers_hooks_once test verifies the result is correct, but doesn't prove the guard is what prevents double-discovery vs. simple MRO ordering.
Suggestion: Consider adding a test that explicitly verifies the guard works by checking that a second __init__ call doesn't overwrite previously discovered hooks (e.g., by mocking discover_hooks and asserting it's called only once).
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| def discover_hooks(instance: object, plugin_name: str) -> list[HookCallback]: |
There was a problem hiding this comment.
Issue: The discover_hooks and discover_tools functions have nearly identical structure (MRO walk, seen-set deduplication, attribute scanning). This is a textbook case for a generic helper that takes a predicate function.
Suggestion: Consider consolidating into a single _discover_methods(instance, plugin_name, predicate, label) helper to eliminate the duplication. For example:
def _discover_methods(instance, plugin_name, predicate, label):
results = []
seen = 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 resultsThis is a minor refactoring suggestion and non-blocking.
|
Assessment: Comment Well-designed feature that aligns with the SDK's composability tenet and mirrors the existing Review Categories
Good work extracting shared discovery logic into |
|
Follow-up Review after The test coverage gaps I flagged have been addressed — the new tests for the The remaining open threads (type safety for The |
|
Assessment: Comment (close to Approve) The latest commit ( Review Categories
The architecture is now clean and symmetric — both agent and orchestrator plugin registries follow the same pattern of routing hook registration through the owner's public |
|
Follow-up Review after Good simplification — removing the The only remaining item from my previous reviews is the dead |
|
Assessment: Approve All feedback from previous review rounds has been addressed. The dead The only remaining codecov gaps are acceptable: the Pending the |
Description
The existing
Pluginsystem only targets individual agents. When building observability, guardrails, or custom behavior for multi-agent orchestrators (Swarm, Graph), there's no composable mechanism — you're forced to manually wire upHookProviderinstances and manage initialization yourself. This PR introducesMultiAgentPlugin, the orchestrator-level counterpart toPlugin, giving orchestrators the same declarative plugin experience that agents already have.Public API Changes
New
MultiAgentPluginbase class (exported fromstrandsandstrands.plugins):Both
SwarmandGraphaccept a newpluginsparameter:A single class can implement both
PluginandMultiAgentPluginfor dual-use across agents and orchestrators:Related Issues
Documentation PR
Type of Change
New feature
Testing
How have you tested the change? Verify that the changes do not break functionality or introduce warnings in consuming repositories: agents-docs, agents-tools, agents-cli
hatch run prepareChecklist
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.