Skip to content

feat(swarm): add AgentBase protocol support#2002

Open
emaan-c wants to merge 2 commits intostrands-agents:mainfrom
emaan-c:feat/swarm-agentbase-support
Open

feat(swarm): add AgentBase protocol support#2002
emaan-c wants to merge 2 commits intostrands-agents:mainfrom
emaan-c:feat/swarm-agentbase-support

Conversation

@emaan-c
Copy link
Copy Markdown

@emaan-c emaan-c commented Mar 29, 2026

Description

Enable Swarm to accept any AgentBase implementation, not just concrete Agent instances. This allows developers to integrate agents from LangGraph, Quick Suite, or custom frameworks into Swarm orchestration alongside native Strands agents, reducing development overhead when extending multi-agent systems with existing agent implementations.

Motivation

Customers have existing agent investments in other frameworks. Currently, using these agents in Strands Swarm requires rebuilding them as native Strands agents, creating significant duplication of effort. This change enables customers to leverage Strands' swarm orchestration while reusing existing agent implementations, providing a migration path and reducing maintenance burden.

Resolves: #1720

Public API Changes

Swarm now accepts AgentBase implementations:

# Before: Only Agent instances
from strands import Agent
swarm = Swarm([agent1, agent2])  # All must be Agent

# After: Mix Agent and AgentBase
from strands.agent.base import AgentBase

class CustomAgent(AgentBase):
    # Implement AgentBase protocol
    ...

swarm = Swarm([strands_agent, custom_agent])  # Mix both types

External agents participate in execution and return results. Full Swarm features (handoffs, state management) require native Agent instances; external agents degrade gracefully. No breaking changes.

Use Cases

Integrate existing agents from other frameworks without rebuilding, enabling quick prototyping and incremental migration from other frameworks to Strands.

Related Issues

Resolves: #1720

Documentation PR

N/A

Type of Change

New feature

Testing

How have you tested the change?

  • I ran hatch run prepare

Checklist

  • I have read the CONTRIBUTING document
  • I have added any necessary tests that prove my fix is effective or my feature works
  • I have updated the documentation accordingly
  • I have added an appropriate example to the documentation to outline the feature, or no new docs are needed
  • My changes generate no new warnings
  • Any dependent changes have been merged and published

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@emaan-c emaan-c force-pushed the feat/swarm-agentbase-support branch from 2fc8209 to 296b485 Compare March 29, 2026 20:33
@github-actions github-actions bot removed the size/l label Mar 29, 2026
@codecov
Copy link
Copy Markdown

codecov bot commented Mar 30, 2026

Codecov Report

❌ Patch coverage is 96.55172% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/strands/multiagent/swarm.py 96.55% 0 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@mkmeral
Copy link
Copy Markdown
Contributor

mkmeral commented Apr 9, 2026

/strands review

injected_count = 0
for node in self.nodes.values():
# Only Agent (not generic AgentBase) has tool_registry attribute
if not isinstance(node.executor, Agent):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how do you determine handoffs for non-Agent AgentBase instances then?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. Looking at the implementation: non-Agent AgentBase instances currently cannot initiate handoffs because _inject_swarm_tools (line 534-577) skips tool injection for nodes that aren't isinstance(node.executor, Agent). They can only be handed to by Agent nodes that do have the handoff tool.

This is a significant capability gap that should be clearly communicated. The _build_node_input method (line 706) also tells all nodes they have "access to swarm coordination tools" regardless of whether tools were injected, which could confuse LLM-backed AgentBase implementations. I've left a separate comment on that.

# Validate entry point if specified
if self.entry_point is not None:
entry_point_node_id = str(self.entry_point.name)
entry_point_node_id = str(getattr(self.entry_point, "name", None))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue: Entry point validation breaks for AgentBase without name attribute.

When entry_point is an AgentBase without a name attribute, str(getattr(self.entry_point, "name", None)) evaluates to the string "None". Meanwhile, nameless nodes are assigned IDs like "node_0", "node_1" etc (line 488). So the lookup on line 503 will never find the entry point, and the user will get a confusing error message.

Suggestion: Use the same logic used during node registration to resolve the entry point's node_id, or look up by identity (is check) instead of by name:

if self.entry_point is not None:
    entry_node = None
    for node in self.nodes.values():
        if node.executor is self.entry_point:
            entry_node = node
            break
    if entry_node is None:
        available_agents = [
            f"{node_id} ({type(node.executor).__name__})" for node_id, node in self.nodes.items()
        ]
        raise ValueError(f"Entry point agent not found in swarm nodes. Available agents: {available_agents}")

This approach is more robust and already has the identity check as a secondary condition (line 504), so consolidating to identity-based lookup avoids the name-based fragility entirely.

injected_count = 0
for node in self.nodes.values():
# Only Agent (not generic AgentBase) has tool_registry attribute
if not isinstance(node.executor, Agent):
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue: Non-Agent nodes silently lose key Swarm capabilities with no user feedback.

When an AgentBase (non-Agent) node is skipped for tool injection, the user isn't informed at Swarm creation time that these nodes won't be able to initiate handoffs. The debug log on line 549 is only visible at DEBUG level. A user might expect their custom AgentBase to participate in handoffs and be surprised when it doesn't.

Suggestion:

  1. Add a logger.warning (not just debug) when non-Agent nodes are skipped, or at minimum a clear log after setup summarizing which nodes have handoff capabilities and which don't.
  2. Consider adding a note in the Swarm.__init__ docstring that AgentBase nodes without tool_registry will not have handoff capabilities and can only serve as entry/exit points or handoff targets.

"model_state": node.executor._model_state,
}
# Only Agent (not generic AgentBase) has _interrupt_state, state, and messages attributes
if isinstance(node.executor, Agent):
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue: Silent degradation for interrupt state on non-Agent nodes.

When an interrupt occurs on a non-Agent AgentBase node, no executor context is saved (the isinstance(node.executor, Agent) guard prevents it). This means if a user tries to resume from an interrupt on a non-Agent node, the resume behavior is undefined — reset_executor_state will try to use self.swarm._interrupt_state.context[self.node_id] (line 112) but that key won't exist, causing a KeyError.

Suggestion: Either:

  1. Raise a clear error if an interrupt is activated on a non-Agent node explaining that interrupt/resume is not supported for AgentBase implementations, or
  2. Save a minimal context for non-Agent nodes (e.g., just marking the interrupt state without Agent-specific attributes), or
  3. Add a guard in reset_executor_state to handle the case where no context exists for non-Agent nodes during interrupt resume.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 9, 2026

Issue: Missing documentation PR.

This PR adds new public API surface — Swarm now accepts AgentBase implementations, which is a new feature that users need to know about to use correctly. The PR description says "N/A" for the Documentation PR section, but per the project's review criteria, a documentation PR is required for new features that add public API surface.

Users need to understand:

  • That Swarm now accepts AgentBase (not just Agent)
  • The capability differences between Agent and AgentBase nodes in a Swarm (handoffs, interrupts, state management)
  • How to implement a minimal AgentBase for use with Swarm
  • The graceful degradation behavior

Suggestion: Please add a documentation PR to https://github.com/strands-agents/docs and link it here.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 9, 2026

Issue: This PR modifies public API and should have the needs-api-review label.

Swarm.__init__ now accepts AgentBase in its nodes and entry_point parameters (previously only Agent). This is a moderate API change — introducing a new class that customers use to achieve new behavior. Per the API Bar Raising guidelines, this should have at minimum informal discussion with an API reviewer.

Key API questions that should be addressed:

  1. Should the capability limitations of non-Agent nodes be explicit in the API (e.g., a warning at creation time, or a capabilities property on nodes)?
  2. Is silent degradation the right pattern, or should the API guide users to understand what works and what doesn't?
  3. Should there be a way for AgentBase implementations to opt into handoff support (e.g., a protocol extension)?

Suggestion: Add the needs-api-review label and ensure an API reviewer evaluates the public contract changes.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 9, 2026

Assessment: Request Changes

Good direction — extending Swarm to accept AgentBase aligns with the Graph pattern and the "Extensible by design" tenet. The core approach of using isinstance(node.executor, Agent) guards to degrade gracefully is sound.

Review Categories
  • Bug — Entry point resolution: Name-based lookup via str(getattr(..., "name", None)) produces "None" string for nameless AgentBase, breaking entry point validation and _initial_node. Switch to identity-based lookup.
  • Correctness — Interrupt resume: No context is saved for non-Agent nodes during interrupt, but reset_executor_state would KeyError when trying to resume. Needs a guard or explicit error.
  • UX — Silent degradation: Non-Agent nodes lose handoff capabilities, interrupt support, and state management with no user feedback beyond DEBUG logs. The prompt also incorrectly tells all nodes they have coordination tools. Users need clearer signals about capability boundaries.
  • API review: This is a moderate public API change (Swarm accepting AgentBase). Should be labeled needs-api-review per bar-raising guidelines.
  • Documentation: A docs PR is required for this new feature — users need to understand what works differently with AgentBase vs Agent nodes.

The pattern is well-aligned with Graph and the test coverage is solid for the happy path. Addressing the entry point bug and the interrupt resume issue would make this ready for a follow-up round.

def _initial_node(self) -> SwarmNode:
if self.entry_point:
return self.nodes[str(self.entry_point.name)]
entry_point_name = getattr(self.entry_point, "name", None)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue: _initial_node (line 1074) has the same name-based lookup fragility as entry point validation.

If entry_point is an AgentBase without a name attribute, getattr(self.entry_point, "name", None) returns None, then str(None) is "None", and "None" in self.nodes will be False. This means it falls through to next(iter(self.nodes.values())) — silently ignoring the user's entry_point configuration.

Suggestion: Use identity-based lookup here too, consistent with the fix for _setup_swarm.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Support for Multi-Framework Agent Integration in Strands Swarm

2 participants