Skip to content

Releases: strands-agents/sdk-python

v1.32.0

20 Mar 14:02
38c1ab6

Choose a tag to compare

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

Full Changelog: v1.31.0...v1.32.0

v1.31.0

19 Mar 14:07
1643a62

Choose a tag to compare

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

11 Mar 18:34
2da3f7c

Choose a tag to compare

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

Full Changelog: v1.29.0...v1.30.0

v1.29.0

04 Mar 21:13
31f1e64

Choose a tag to compare

What's Changed

New Contributors

Full Changelog: v1.28.0...v1.29.0

v1.28.0

25 Feb 19:32
37938da

Choose a tag to compare

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

19 Feb 17:10
0a31848

Choose a tag to compare

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

11 Feb 19:49
a43e936

Choose a tag to compare

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 parts for Artifact by @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

Full Changelog: v1.25.0...v1.26.0

v1.25.0

05 Feb 19:48
4f1a8b3

Choose a tag to compare

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 @tool parameter was typed as T | None without a default value, the schema cleaning logic was stripping null from the anyOf array, making the field required with no way to express null. This caused LLMs to fall back to passing "null" as a string. The anyOf simplification is now skipped for fields in the required array, 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" as APIError instead of BadRequestError with code context_length_exceeded. These errors are now detected and converted to ContextWindowOverflowException, allowing conversation managers like SummarizingConversationManager to trigger properly.

  • retry_strategy=None Now Disables Retries - PR#1630
    Passing retry_strategy=None to the Agent constructor previously applied the default retry strategy instead of disabling retries. Now None explicitly disables all SDK retries (equivalent to ModelRetryStrategy(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 passed None.

  • A2A Server Agent Card URL Updated on Host/Port Override - PR#1626
    When overriding host and port in A2AServer.serve(), the agent card at /.well-known/agent-card.json still 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 explicit http_url was 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-python from 4 to 6 - PR#1548
  • Bump aws-actions/configure-aws-credentials from 4 to 5 - PR#1547
  • Bump actions/download-artifact from 4 to 7 - PR#1609
  • Bump actions/upload-artifact from 4 to 6 - PR#1608

New Contributors

Read more

v1.24.0

29 Jan 01:23
4e4534e

Choose a tag to compare

What's Changed

New Contributors

Full Changelog: v1.23.0...v1.24.0

v1.23.0

21 Jan 20:10
f87925b

Choose a tag to compare

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 use from __future__ import annotations, which caused PydanticUserError due 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

Read more