Releases: strands-agents/sdk-python
v1.32.0
What's Changed
- fix(event-loop): ensure all cycle metrics include end time and duration by @stephentreacy in #1903
- fix: pin upper bound for mistralai dependency by @mkmeral in #1935
- fix: override end_turn stop reason when streaming response contains toolUse blocks by @atian8179 in #1827
New Contributors
- @stephentreacy made their first contribution in #1903
- @atian8179 made their first contribution in #1827
Full Changelog: v1.31.0...v1.32.0
v1.31.0
What's Changed
- feat: pass A2A request context metadata as invocation state by @mkmeral in #1854
- fix: s3session manager bug by @mehtarac in #1915
- fix(graph): only evaluate outbound edges from completed nodes by @giulio-leone in #1846
- fix(openai): always use string content for tool messages by @giulio-leone in #1878
- feat: widen openai dependency to support 2.x for litellm compatibility by @BV-Venky in #1793
- fix: typeError when serializing multimodal prompts with binary content in Graph/Swarm session persistence by @JackYPCOnline in #1870
- fix: lowercase the python language in code snippet by @zastrowm in #1929
- fix: openai repsonses api error handling by @Unshure in #1931
New Contributors
Full Changelog: v1.30.0...v1.31.0
v1.30.0
What's Changed
- feat: add "anthropic" cache strategy to bypass model ID check by @kevmyung in #1808
- feat: serialize tool results as JSON when possible by @clareliguori in #1752
- fix: summary manager using structured output by @pgrayy in #1805
- feat(mcp): expose server instructions from InitializeResult on MCPClient by @ShotaroKataoka in #1814
- fix: added LANGFUSE_BASE_URL check for additinoal attribute by @poshinchen in #1826
- feat(session): add dirty flag to skip unnecessary agent state persistence by @Unshure in #1803
- feat: add public tool_spec setter by @mkmeral in #1822
- feat: add CancellationToken for graceful agent execution cancellation by @jgoyani1 in #1772
- feat(session): optimize session manager initialization by @Unshure in #1829
- fix(mistral): report usage metrics in streaming mode by @jackatorcflo in #1697
- fix(openai_responses): use output_text for assistant messages in multi-turn conversations by @giulio-leone in #1851
- feat(hooks): add resume flag to AfterInvocationEvent by @mkmeral in #1767
- fix: place cache point on last user message instead of assistant by @kevmyung in #1821
- feat(skills): add agent skills as a plugin by @mkmeral in #1755
- feat(steering): move steering from experimental to production by @dbschmigelski in #1853
- fix: break circular references so Agent cleanup doesn't hang with MCPClient by @dbschmigelski in #1830
- fix: Set is_new_session = False at the end of each initialize* method by @mehtarac in #1859
New Contributors
- @ShotaroKataoka made their first contribution in #1814
- @jgoyani1 made their first contribution in #1772
- @jackatorcflo made their first contribution in #1697
- @giulio-leone made their first contribution in #1851
Full Changelog: v1.29.0...v1.30.0
v1.29.0
What's Changed
- test: pin virtualenv to <21 for hatch bug by @clareliguori in #1771
- fix(telemetry): added latest semantic conventions as span attributes for langfuse by @poshinchen in #1768
- fix: preserve guardrail_latest_message wrapping after tool execution by @austinmw in #1658
- feat(conversation-manager): improve tool result truncation strategy by @kevmyung in #1756
- feat(plugins): improve plugin creation devex with @hook and @tool decorators by @Unshure in #1740
- ci: bump actions/upload-artifact from 6 to 7 by @dependabot[bot] in #1777
- ci: bump actions/download-artifact from 7 to 8 by @dependabot[bot] in #1776
- fix: throw exceptions from ConcurrentToolExecutor (#1796) by @charles-dyfis-net in #1797
- feat: add OpenAI Responses API model implementation by @notgitika in #975
New Contributors
Full Changelog: v1.28.0...v1.29.0
v1.28.0
What's Changed
- fix: update region for agentcore in our new account by @afarntrog in #1715
- fix: remove test that fails for python 3.14 by @Unshure in #1717
- feat(hooks): support union types and list of types for add_hook by @Unshure in #1719
- feat: make pyaudio an optional dependency by lazy loading by @mehtarac in #1731
- feat(hooks): add Plugin Protocol for agent extensibility by @Unshure in #1733
- feat: add plugins parameter to Agent by @Unshure in #1734
- refactor(plugins): convert Plugin from Protocol to ABC by @Unshure in #1741
- feat(steering): migrate SteeringHandler from HookProvider to Plugin by @Unshure in #1738
- chore: switch to Sonnet 4.6 for Anthropic provider integ tests by @clareliguori in #1754
- fix: rename init_plugin to init_agent by @Unshure in #1765
Full Changelog: v1.27.0...v1.28.0
v1.27.0
What's Changed
- feat: Propagate exceptions to AfterToolCallEvent for decorated tools (#1565) by @charles-dyfis-net in #1566
- feat(workflows): add conventional commit workflow in PR by @mkmeral in #1645
- fix: the A2AAgent returns empty AgentResult content by @afarntrog in #1675
- auto run review workflow on maintainer PR by @mehtarac in #1673
- fix: correct output reference for approval-env in integration test by @afarntrog in #1685
- fix: update approval env var for strands agent workflows by @Unshure in #1701
- fix: update allowed roles to include maintainer by @afarntrog in #1704
- fix: propagate reasoningSignature on Gemini tool use by @afarntrog in #1703
- ci: bump actions/github-script from 7 to 8 by @dependabot[bot] in #1699
- ci: bump amannn/action-semantic-pull-request from 5 to 6 by @dependabot[bot] in #1684
- fix: handle OpenAI model responses with tool calls and no other assistant content by @clareliguori in #1562
- fix: Update finalize condition for workflow execution by @Unshure in #1708
- fix: upgrade mcp minimum dependency to 1.23.0 for Tasks support by @clareliguori in #1674
- feat(agent): add concurrent_invocation_mode parameter by @zastrowm in #1707
- test: coverage for python 3.14 by @awsarron in #1178
- feat(agent): add add_hook convenience method for hook callback registration by @Unshure in #1706
Full Changelog: v1.26.0...v1.27.0
v1.26.0
What's Changed
- ci: bump aws-actions/configure-aws-credentials from 5 to 6 by @dependabot[bot] in #1632
- docs: add guidance on using Protocol instead of Callable for extensible interfaces by @dbschmigelski in #1637
- feat(mcp): Implement basic support for Tasks by @LucaButBoring in #1475
- fix(multiagent): set empty text part data in
partsforArtifactby @punkyoon in #1643 - fix(summarizing_conversation_manager): use model stream to generate summary by @mkmeral in #1653
- fix(bedrock): add 'prompt is too long' to context window overflow mes… by @eladb3 in #1663
- fix: fix mcp tests by @afarntrog in #1664
New Contributors
- @LucaButBoring made their first contribution in #1475
- @punkyoon made their first contribution in #1643
- @eladb3 made their first contribution in #1663
Full Changelog: v1.25.0...v1.26.0
v1.25.0
Major Features
A2AAgent: First-Class Client for Remote A2A Agents - PR#1441
The new A2AAgent class makes it simple to connect to and invoke remote agents that implement the Agent-to-Agent (A2A) protocol. A2AAgent implements the AgentBase protocol, so it can be called synchronously, asynchronously, or used in streaming mode just like a local Agent. It automatically discovers the remote agent's card to populate its name and description, and manages HTTP client lifecycle for you.
from strands.agent.a2a_agent import A2AAgent
# Connect to a remote A2A agent
a2a_agent = A2AAgent(endpoint="http://localhost:9000")
# Invoke it like any other agent
result = a2a_agent("Show me 10 ^ 6")
print(result.message)
# Or stream events asynchronously
async for event in a2a_agent.stream_async("Summarize this report"):
if event.get("type") == "a2a_stream":
print(f"A2A event: {event['event']}")
elif "result" in event:
print(f"Final: {event['result'].message}")A2AAgent Support in Graph Workflows - PR#1615
Graph nodes now accept any AgentBase implementation as an executor, not just the concrete Agent class. This means A2AAgent instances and other custom AgentBase implementations can participate in graph-based multi-agent workflows alongside local agents. The Agent class also now explicitly extends AgentBase for compile-time protocol verification.
from strands import Agent
from strands.agent.a2a_agent import A2AAgent
from strands.multiagent.graph import GraphBuilder
local_agent = Agent(name="summarizer", system_prompt="Summarize input concisely.")
remote_agent = A2AAgent(endpoint="http://remote-agent:9000")
builder = GraphBuilder()
builder.add_node(remote_agent, "research")
builder.add_node(local_agent, "summarize")
builder.add_edge("research", "summarize")
graph = builder.build()
result = graph("Analyze recent AI trends")Interrupt Support for MultiAgent Graph Nodes - PR#1606
Interrupts now propagate correctly through nested multi-agent graph nodes. When an agent inside a nested Graph, Swarm, or any custom MultiAgentBase node raises an interrupt, the outer graph pauses execution and surfaces the interrupt to the caller. After the caller provides a response, the outer graph resumes execution from where it left off. This builds on the interrupt support for single-agent graph nodes added in v1.24.0.
from strands import Agent, tool
from strands.interrupt import Interrupt
from strands.multiagent import GraphBuilder, Status
from strands.types.tools import ToolContext
@tool(context=True)
def approval_tool(tool_context: ToolContext) -> str:
return tool_context.interrupt("approval", reason="Needs human approval")
agent = Agent(name="reviewer", tools=[approval_tool])
inner_graph = GraphBuilder()
inner_graph.add_node(agent, "reviewer")
outer_graph = GraphBuilder()
outer_graph.add_node(inner_graph.build(), "review_pipeline")
graph = outer_graph.build()
result = graph("Review this document")
while result.status == Status.INTERRUPTED:
responses = [
{"interruptResponse": {"interruptId": i.id, "response": "Approved"}}
for i in result.interrupts
]
result = graph(responses)S3 Location Support for Documents, Images, and Videos - PR#1572
Media content types now support S3 locations as a source, allowing you to reference documents, images, and videos stored in Amazon S3 directly without base64 encoding. The new S3Location type includes a required uri field and an optional bucketOwner field for cross-account access. On Bedrock, S3 locations are passed through to the API natively.
from strands import Agent
agent = Agent()
response = agent([{
"role": "user",
"content": [
{"text": "Summarize this document:"},
{
"document": {
"format": "pdf",
"name": "report",
"source": {
"location": {
"type": "s3",
"uri": "s3://my-bucket/documents/report.pdf",
"bucketOwner": "123456789012" # optional, for cross-account
}
}
}
}
]
}])Configurable Structured Output Prompt - PR#1627
The prompt message the agent uses to request structured output formatting is now configurable via the structured_output_prompt parameter. Previously, the hardcoded message "You must format the previous response as structured output." could trigger Bedrock Guardrails prompt-attack filters. You can now customize this message at the agent level or per invocation to work around guardrail rules or to better suit your use case.
from strands import Agent
from pydantic import BaseModel
class UserInfo(BaseModel):
name: str
age: int
# Custom prompt avoids triggering Bedrock Guardrails prompt attack filter
agent = Agent(
structured_output_model=UserInfo,
structured_output_prompt="Please use the output tool now."
)
# Or override per invocation
result = agent(
"Extract user info from: John is 30 years old",
structured_output_prompt="Format the response using the output tool."
)Major Bug Fixes
-
Nullable Semantics Preserved for Required
Union[T, None]Tool Parameters - PR#1584
When a@toolparameter was typed asT | Nonewithout a default value, the schema cleaning logic was strippingnullfrom theanyOfarray, making the field required with no way to express null. This caused LLMs to fall back to passing"null"as a string. TheanyOfsimplification is now skipped for fields in therequiredarray, preserving proper nullable semantics. -
LedgerProvider Now Handles Parallel Tool Calls Correctly - PR#1559
When agents proposed multiple tool calls in a single model response, the ledger provider only updated the last tool in the batch (ledger['tool_calls'][-1]), leaving earlier pending tools without proper status updates. The provider now correctly tracks and updates each individual tool call. -
Context Overflow Detection for OpenAI-Compatible Bedrock Endpoints - PR#1529
OpenAI-compatible endpoints that wrap Bedrock models (e.g., Databricks Model Serving) return Bedrock-style error messages like"Input is too long for requested model"asAPIErrorinstead ofBadRequestErrorwith codecontext_length_exceeded. These errors are now detected and converted toContextWindowOverflowException, allowing conversation managers likeSummarizingConversationManagerto trigger properly. -
retry_strategy=NoneNow Disables Retries - PR#1630
Passingretry_strategy=Noneto the Agent constructor previously applied the default retry strategy instead of disabling retries. NowNoneexplicitly disables all SDK retries (equivalent toModelRetryStrategy(max_attempts=1)), while omitting the parameter entirely still applies the default strategy. Note: This is a minor breaking change in behavior for code that explicitly passedNone. -
A2A Server Agent Card URL Updated on Host/Port Override - PR#1626
When overridinghostandportinA2AServer.serve(), the agent card at/.well-known/agent-card.jsonstill returned the original URL from constructor defaults, causing A2A clients to connect to the wrong address. The server now updates the agent card URL to match the overridden host/port, unless an explicithttp_urlwas provided in the constructor.
Minor Changes
- Increase pytest timeout to 45 seconds - PR#1586
- Publish integ test results to CloudWatch - PR#1587
- Clone main metrics upload script for integ tests - PR#1600
- Skip S3 location for non-Bedrock model providers - PR#1602
- Add conditional execution for finalize step - PR#1605
- Fix various test warnings - PR#1613
- Fix Bedrock file warnings - PR#1603
- Increase test timeout - PR#1623
- Fix OpenAI test - PR#1624
- Remove broken MCP transport timeout test - PR#1635
- Bump
actions/setup-pythonfrom 4 to 6 - PR#1548 - Bump
aws-actions/configure-aws-credentialsfrom 4 to 5 - PR#1547 - Bump
actions/download-artifactfrom 4 to 7 - PR#1609 - Bump
actions/upload-artifactfrom 4 to 6 - PR#1608
New Contributors
- @charles-dyfis-net made their first contribution in PR#1584
- @dinindu...
v1.24.0
What's Changed
- test: fix flaky openai structured output test by adding Field guidance by @dbschmigelski in #1534
- interrupts - multiagent - do not emit AfterNodeCallEvent on interrupt by @pgrayy in #1539
- ci: add workflow for lambda layer publish by @dbschmigelski in #870
- fix: Populate tool_args correctly for steering by @clareliguori in #1531
- interrupts - graph - agent based by @pgrayy in #1533
- chore: refactor use_span to be closed automatically by @poshinchen in #1293
- ci: limit permission scope on lambda layer github action by @dbschmigelski in #1555
- chore: Enable Auto-close labels on Pull requests as well. by @yonib05 in #1552
- Use devtools actions by @Unshure in #1554
- feat(bedrock): add automatic prompt caching support by @kevmyung in #1438
- feat(hooks): add retry mechanism for tool calls by @dbschmigelski in #1556
- feat(tools): move ToolProvider out of experimental namespace by @Unshure in #1567
- [FIX] models - gemini - start and stop reasoningContent by @JackYPCOnline in #1557
- feat(agent): update AgentResult str priority order by @afarntrog in #1553
- callback handler - fix reporting of tool when missing delta by @pgrayy in #1573
- feat(hooks): Add invocation state by @mkmeral in #1550
- test(steering): Fix failing integ tests by @mkmeral in #1580
New Contributors
Full Changelog: v1.23.0...v1.24.0
v1.23.0
Major Features
Configurable Retry Strategy for Model Calls - PR#1424
Agents now support customizable retry behavior when handling model throttling exceptions. The new ModelRetryStrategy class allows you to configure maximum retry attempts, initial delay, and maximum delay, replacing the previously hardcoded retry logic.
from strands import Agent, ModelRetryStrategy
agent = Agent(
model="anthropic.claude-3-sonnet",
retry_strategy=ModelRetryStrategy(
max_attempts=3,
initial_delay=2,
max_delay=60
)
)Model Response Steering - PR#1429
Steering handlers can now intercept and guide model responses through the new steer_after_model() method. This enables validation of model outputs, enforcement of required tool usage, and conversation flow control based on model responses before the agent completes.
from strands.experimental.steering import Guide, Proceed, SteeringHandler
class ForceToolUsageHandler(SteeringHandler):
def __init__(self, required_tool: str):
super().__init__()
self.required_tool = required_tool
async def steer_after_model(self, *, agent, message, stop_reason, **kwargs):
if stop_reason != "end_turn":
return Proceed(reason="Model still processing")
# Check if required tool was used
for block in message.get("content", []):
if "toolUse" in block and block["toolUse"].get("name") == self.required_tool:
return Proceed(reason="Required tool was used")
# Force tool usage
return Guide(reason=f"You MUST use the {self.required_tool} tool.")
agent = Agent(tools=[log_tool], hooks=[ForceToolUsageHandler("log_activity")])Input Messages in BeforeInvocationEvent - PR#1474
The BeforeInvocationEvent hook now exposes the input messages through a new messages parameter, enabling preprocessing, guardrails, and filters to run before the agent processes user input.
from strands import Agent
from strands.hooks import HookProvider, BeforeInvocationEvent
class PreprocessingHook(HookProvider):
def register_hooks(self, registry):
registry.add_callback(BeforeInvocationEvent, self.preprocess)
def preprocess(self, event):
# Access input messages for preprocessing
messages = event.messages
# Apply guardrails or filters here
pass
agent = Agent(hooks=[PreprocessingHook()])Nova Sonic 2 Support for BidiAgent - PR#1476
BidiAgent now supports Amazon Nova Sonic v2 models with enhanced features including the turn_taking parameter and improved text input handling for bidirectional voice conversations.
from strands.experimental.bidi.models import BidiNovaSonicModel
from strands.experimental.bidi import BidiAgent
model = BidiNovaSonicModel(model_id="us.amazon.nova-sonic-v2:0")
agent = BidiAgent(model=model)Graph Interrupts from Hooks - PR#1478
Graph nodes can now be interrupted from BeforeNodeCallEvent hooks, enabling approval workflows and human-in-the-loop validation before specific agents execute in multi-agent graphs.
from strands import Agent
from strands.hooks import HookProvider, BeforeNodeCallEvent
from strands.multiagent import GraphBuilder, Status
class ApprovalHook(HookProvider):
def register_hooks(self, registry):
registry.add_callback(BeforeNodeCallEvent, self.approve)
def approve(self, event):
if event.node_id == "info_agent":
return
response = event.interrupt("my_interrupt", reason=f"{event.node_id} needs approval")
if response != "APPROVE":
event.cancel_node = "node rejected"
builder = GraphBuilder()
builder.add_node(info_agent, "info_agent")
builder.add_node(weather_agent, "weather_agent")
builder.add_edge("info_agent", "weather_agent")
builder.set_hook_providers([ApprovalHook()])
graph = builder.build()
result = graph("What is the weather?")
while result.status == Status.INTERRUPTED:
# Handle interrupts and resume with responses
result = graph(interrupt_responses)Multiagent Hook Events Graduated - PR#1498
Multiagent hook events (BeforeNodeCallEvent, AfterNodeCallEvent) have been promoted from experimental to the stable API. These events are now available directly from strands.hooks without the experimental import path.
from strands.hooks import BeforeNodeCallEvent, AfterNodeCallEvent, HookProvider
class MyMultiagentHook(HookProvider):
def register_hooks(self, registry):
registry.add_callback(BeforeNodeCallEvent, self.before_node)
registry.add_callback(AfterNodeCallEvent, self.after_node)
def before_node(self, event):
print(f"Starting node: {event.node_id}")
def after_node(self, event):
print(f"Completed node: {event.node_id}")Major Bug Fixes
- MCP Agent Hang Prevention - PR#1396
Fixed indefinite hangs when MCPClient attempted to schedule tool calls to a background event loop that had already finished, particularly when 4xx/5xx errors caused the loop to exit prematurely. - PEP 563 Compatibility with Tool Decorator - PR#1494
Resolved incompatibility between strands-agents 1.16.0+ and Pydantic 2.12+ when tools usefrom __future__ import annotations, which causedPydanticUserErrordue to unresolved string type annotations. - Unique Tool Use IDs for Gemini Models - PR#1201
Fixed Gemini models incorrectly using tool names as tool use IDs instead of generating unique identifiers, ensuring proper tool call tracking and correlation. - Bedrock Thinking Mode with Structured Output - PR#1495
Fixed validation exception when using structured output with thinking mode enabled, by automatically disabling thinking mode when the event loop forces tool_choice during retries.
What's Changed
- fix(mcp): prevent agent hang by checking session closure state by @Ratish1 in #1396
- ci: update sphinx-rtd-theme requirement from <2.0.0,>=1.0.0 to >=1.0.0,<4.0.0 by @dependabot[bot] in #1466
- ci: update websockets requirement from <16.0.0,>=15.0.0 to >=15.0.0,<17.0.0 by @dependabot[bot] in #1451
- Update ruff configuration to apply pyupgrade to modernize python syntax by @maxrabin in #1336
- fix(agent): extract text from citationsContent in AgentResult.str by @tmokmss in #1489
- Expose input messages to BeforeInvocationEvent hook by @Unshure in #1474
- interrupts - graph - hook based by @pgrayy in #1478
- fix: Swap unit test sleeps with explicit signaling by @zastrowm in #1497
- Fix PEP 563 incompatibility with @tool decorated tools by @zastrowm in #1494
- feat: override service name by OTEL_SERVICE_NAME env by @okamototk in #1400
- fix(bedrock): disable thinking mode when forcing tool_choice by @strands-agent in #1495
- fix(a2a): use a2a artifact update event by @brycewcole in #1401
- Add parallel reading support to S3SessionManager.list_messages() by @CrysisDeu in #1186
- feat(steering): allow steering on AfterModelCallEvents by @dbschmigelski in #1429
- fix: provide unique toolUseId for gemini models by @AirswitchAsa in #1201
- gemini - tool_use_id_to_name - local by @pgrayy in #1521
- fix(litellm): handle missing usage attribute on ModelResponseStream by @dbschmigelski in #1520
- feat(agent): add configurable retry_strategy for model calls by @zastrowm in #1424
- fix(swarm): accumulate execution_time across interrupt/resume cycles by @pgrayy in #1502
- Feat: graduate multiagent hook events from experimental by @JackYPCOnline in #1498
- Nova Sonic 2 support for BidiAgent by @lanazhang in #1476
- fix(tests): reduce flakiness in guardrail redact output test by @dbschmigelski in #1505
New Contributors
- @maxrabin made their first contribution in #1336
- @tmokmss made their first contribution in #1489
- @okamototk made their first contribution in #1400
- @strands-agent made their first contribution in ht...