-
Notifications
You must be signed in to change notification settings - Fork 286
[BUG] Swarm tool silently drops all sub-agent output #437
Description
Checks
- I have updated to the lastest minor and patch version of Strands
- I have checked the documentation and this is not expected behavior
- I have searched ./issues and there are no duplicates of my issue
Strands Version
1.33.0
Tools Package Version
0.3.0
Tools used
swarm
Python Version
3.13.5
Operating System
macOS 15.6.1 (Darwin 24.6.0, arm64)
Installation Method
pip
Steps to Reproduce
Install the strands and strands-tools packages properly and run the following script:
from strands import Agent
from strands_tools import swarm
# 1. Create a parent agent with the swarm tool
orchestrator = Agent(tools=[swarm])
# 2. Run a swarm with a sub-agent that produces known output
orchestrator("Use swarm to create a single agent named 'test_agent' with "
"system prompt 'Reply with exactly: SWARM_OUTPUT_MARKER_98765'. "
"Task: 'Say the marker phrase.'")
# 3. Inspect the swarm tool result returned to the parent agent
# The "Individual Agent Contributions" section is empty despite
# the sub-agent producing output (visible in token counts)Alternatively, inspect the source code directly:
- Open
src/strands_tools/swarm.py - Look at lines 451 and 466
- Both lines check
hasattr(node_result.result, "content")— butAgentResulthas no.contentattribute - The actual field is
.message(a dict with a"content"key) - Because
hasattr()returnsFalse, the entire extraction block is silently skipped
Expected Behavior
The parent agent should receive the full text output from all sub-agents in the swarm tool result. The "Individual Agent Contributions" section should contain each sub-agent's response text, and the "Final Team Result" section should contain the last agent's output.
Example expected output:
🎯 **Custom Agent Team Execution Complete**
📊 **Status:** Status.COMPLETED
⏱️ **Execution Time:** 2343ms
**🤖 Individual Agent Contributions:**
**TEST AGENT:**
SWARM_OUTPUT_MARKER_98765 ← sub-agent output should appear here
**🎯 Final Team Result:**
SWARM_OUTPUT_MARKER_98765 ← and here
**📈 Team Resource Usage:**
• Output tokens: 23
### Actual Behavior
The parent agent receives only execution metadata. All sub-agent text output is silently dropped. The "Individual Agent Contributions" section is empty:
🎯 Custom Agent Team Execution Complete
📊 Status: Status.COMPLETED
⏱️ Execution Time: 2343ms
🤖 Individual Agent Contributions:
← empty — content silently dropped
📈 Team Resource Usage:
• Input tokens: 559
• Output tokens: 23 ← sub-agent DID produce output (23 tokens)
• Total tokens: 582
No error or warning is logged. The sub-agent runs correctly and produces output (reflected in token counts), but the text never reaches the parent agent's context.
### Additional Context
### Root Cause
The swarm tool accesses `AgentResult.content` (which doesn't exist) instead of `AgentResult.message["content"]` (which contains the actual response). The `AgentResult` dataclass is defined in `strands/agent/agent_result.py`:
```python
@dataclass
class AgentResult:
stop_reason: StopReason
message: Message # {"role": "assistant", "content": [...]}
metrics: EventLoopMetrics
state: Any
# No .content attribute
The buggy code at lines 451 and 466:
if hasattr(node_result, "result") and hasattr(node_result.result, "content"):
for content_block in node_result.result.content:
if hasattr(content_block, "text") and content_block.text:
agent_content.append(content_block.text)Because hasattr(node_result.result, "content") returns False, the extraction block is silently skipped.
Why This Went Unnoticed
- Silent failure —
hasattr()returnsFalsewithout any error or warning - Token counts still appear — output tokens suggest the sub-agent worked
- LLMs compensate — parent agents fabricate plausible sub-agent findings from context clues
- No configuration workaround — no parameter controls result formatting
Impact
Affects every user of the swarm tool. The SDK's Swarm class, Graph pattern, and Agent-as-Tool pattern are NOT affected — only the tool wrapper in strands_tools.swarm.
Pattern Reference
The correct pattern already exists in workflow.py (lines 446-452):
content = (
result.message.get("content", [])
if hasattr(result.message, "get")
else getattr(result.message, "content", [])
)Possible Solution
Replace .content with .message and use dict-style access at both locations (lines 451 and 466):
if hasattr(node_result, "result") and hasattr(node_result.result, "message"):
agent_content = []
message = node_result.result.message
content_blocks = message.get("content", []) if hasattr(message, "get") else []
for content_block in content_blocks:
if isinstance(content_block, dict) and content_block.get("text"):
agent_content.append(content_block["text"])This aligns with the AgentResult dataclass structure and follows the pattern already used in workflow.py. Test mocks in tests/test_swarm.py also need updating to use .message instead of .content.
Related Issues
No response