Skip to content

Releases: strands-agents/sdk-python

v1.23.0

21 Jan 20:10
f87925b

Choose a tag to compare

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

Full Changelog: v1.22.0...v1.23.0

v1.22.0

13 Jan 21:29
06c3297

Choose a tag to compare

Major Features

MCP Resource Operations - PR#1117

The MCP client now supports resource operations, enabling agents to list, read, and work with resources provided by MCP servers. This includes static resources, binary resources, and parameterized resource templates.

from strands.tools.mcp import MCPClient

with MCPClient(server_transport) as client:
    # List available resources
    resources = client.list_resources_sync()
    for resource in resources.resources:
        print(f"Resource: {resource.name} at {resource.uri}")
    
    # Read a specific resource
    content = client.read_resource_sync("file://documents/report.txt")
    text = content.contents[0].text
    
    # List resource templates (parameterized resources)
    templates = client.list_resource_templates_sync()
    for template in templates.resourceTemplates:
        print(f"Template: {template.uriTemplate}")

Bedrock Guardrails Latest Message Option - PR#1224

Bedrock models now support the guardrail_latest_message parameter, which sends only the latest user message to AWS Bedrock Guardrails for evaluation instead of the entire conversation history. This reduces token usage and enables conversation recovery after guardrail interventions.

from strands.models.bedrock import BedrockModel

model = BedrockModel(
    model_id="us.anthropic.claude-sonnet-4-20250514-v1:0",
    guardrail_id="your-guardrail-id",
    guardrail_version="DRAFT",
    guardrail_latest_message=True  # Only evaluate the latest user message
)

See the Bedrock Guardrails documentation for more details.

LiteLLM Non-Streaming Support - PR#512

The LiteLLM model provider now correctly handles non-streaming responses, fixing an issue where stream=False would raise an error. Both streaming and non-streaming modes now work seamlessly.

from strands.models.litellm import LiteLLMModel

# Use non-streaming mode for simpler response handling
model = LiteLLMModel(
    model_id="gpt-3.5-turbo",
    params={"stream": False}
)

# Works correctly now - no more ValueError
agent = Agent(model=model)
result = agent("What is 2+2?")

Major Bug Fixes

  • Concurrent Agent Invocations - PR#1453
    Fixed critical agent state corruption when multiple concurrent invocations occurred on the same agent instance. A new ConcurrencyException is now raised to prevent concurrent invocations and protect agent state integrity.

  • Gemini Empty Stream Handling - PR#1420
    Fixed UnboundLocalError crash when Gemini returns an empty event stream by properly initializing variables before the stream loop.

  • Deprecation Warning on Import - PR#1380
    Fixed unwanted deprecation warnings appearing when importing strands by using lazy __getattr__ to emit warnings only when deprecated aliases are actually accessed.


What's Changed

  • docs: update github agent action to reference S3_SESSION_BUCKET by @dbschmigelski in #1418
  • feat: provide extra command content as the the prompt to the agent by @zastrowm in #1419
  • [FEATURE] add MCP resource operations in MCP Tools by @xiehust in #1117
  • fix: import errors for models with optional imports by @mehtarac in #1384
  • add BidiGeminiLiveModel and BidiOpenAIRealtimeModel to the init by @mehtarac in #1383
  • bidi - async - remove cancelling call by @pgrayy in #1357
  • feat(bedrock): add guardrail_latest_message option by @aiancheruk in #1224
  • fix(gemini): UnboundLocal Exception Fix by @emattiza in #1420
  • fix! Litellm handle non streaming response fix for issue #477 by @schleidl in #512
  • feat(agent-interface): introduce AgentBase Protocol as the interface for agent classes to implement by @awsarron in #1126
  • ci: update pytest requirement from <9.0.0,>=8.0.0 to >=8.0.0,<10.0.0 in the dev-dependencies group by @dependabot[bot] in #1161
  • feat: pass invocation_state to model providers by @tirth14 in #1414
  • Add Security.md file by @yonib05 in #1454
  • chore: Update release notes sop by @zastrowm in #1456
  • fix(integ): make calculator tool more robust to LLM output variations by @cagataycali in #1445
  • fix: resolve string formatting error in MCP client error handling by @cagataycali in #1446
  • bidi - move 3.12 check to nova sonic module by @pgrayy in #1439
  • ci: update sphinx requirement from <9.0.0,>=5.0.0 to >=5.0.0,<10.0.0 by @dependabot[bot] in #1426
  • fix: add concurrency protection to prevent parallel invocations from corrupting agent state by @zastrowm in #1453
  • fix(mcp): propagate contextvars to background thread by @cagataycali in #1444
  • Update to opus 4.5 by @Unshure in #1471

New Contributors

Full Changelog: v1.21.0...v1.22.0

v1.21.0

02 Jan 19:26
b5d9468

Choose a tag to compare

Major Features

Custom HTTP Client Support for OpenAI and Gemini - PR#1366

The OpenAI and Gemini model providers now accept a pre-configured client via the client parameter, enabling connection pooling, proxy configuration, custom timeouts, and centralized observability across all model requests. The client is reused for all requests and its lifecycle is managed by the caller, not the model provider.

from strands.models.openai import OpenAIModel
import httpx

# Create custom client with proxy and timeout configuration
custom_client = httpx.AsyncClient(
    proxy="http://proxy.example.com:8080",
    timeout=60.0
)

model = OpenAIModel(model_id="gpt-4o-mini", client=custom_client)

Gemini Built-in Tools (Google Search, Code Execution) - PR#1050

Gemini models now support native Google tools like GoogleSearch and CodeExecution through the gemini_tools parameter. These tools integrate directly with Gemini's API without requiring custom function implementations, enabling agents to search the web and execute code natively.

from strands.models.gemini import GeminiModel
from google.genai import types

model = GeminiModel(
    model_id="gemini-2.0-flash-exp",
    gemini_tools=[types.Tool(google_search=types.GoogleSearch())]
)

Hook-based Model Retry on Exceptions - PR#1405

Hooks can now retry model invocations by setting event.retry = True in the AfterModelCallEvent handler, enabling custom retry logic for transient errors, rate limits, or quality checks. This provides fine-grained control over model retry behavior beyond basic exception handling.

class RetryOnServiceUnavailable(HookProvider):
    def __init__(self, max_retries=3):
        self.max_retries = max_retries
        self.retry_count = 0

    def register_hooks(self, registry, **kwargs):
        registry.add_callback(BeforeInvocationEvent, self.reset_counts)
        registry.add_callback(AfterModelCallEvent, self.handle_retry)

    def reset_counts(self, event = None):
      	self.retry_count = 0

    async def handle_retry(self, event):
        if event.exception:
            if "ServiceUnavailable" in str(event.exception):
                logger.info("ServiceUnavailable encountered")
                count = self.retry_count
                if count < self.max_retries:
                    logger.info("Retrying model call")
                    self.retry_count = count + 1
                    event.retry = True
                    await asyncio.sleep(2 ** count)  # Exponential backoff
        else:
            # reset counts in the succesful case
            self.reset_counts(None)

Per-Turn Conversation Management - PR#1374

Conversation managers now support mid-execution management via the per_turn parameter, applying conversation window management before each model call rather than only at the end. This prevents context window overflow during multi-turn conversations with tools or long responses.

from strands import Agent
from strands.agent.conversation_manager import SlidingWindowConversationManager

# Enable management before every model call
manager = SlidingWindowConversationManager(
    per_turn=True,
    window_size=40
)

# Or manage every N turns
manager = SlidingWindowConversationManager(
    per_turn=3,  # Manage every 3 model calls
    window_size=40
)

agent = Agent(model=model, conversation_manager=manager)

Agent Invocation Metrics - PR#1387

Metrics now track per-invocation data through agent_invocations and latest_agent_invocation properties, providing granular insight into each agent call's performance, token usage, and execution time. This enables detailed performance analysis for multi-invocation workflows.

from strands import Agent

agent = Agent(model=model)
result = agent("Analyze this data")

# Access invocation-level metrics
latest = result.metrics.latest_agent_invocation
print(f"Cycles: {len(latest.cycles)}")
print(f"Tokens: {latest.usage}")

# Access all invocations
for invocation in result.metrics.agent_invocations:
    print(f"Invocation usage: {invocation.usage}")

ToolRegistry Replace Method - PR#1182

The ToolRegistry now supports replacing existing tools via the replace() method, enabling dynamic tool updates without re-registering all tools. This is particularly useful for hot-reloading tool implementations or updating tools based on runtime conditions.

from strands.tools.registry import ToolRegistry
from strands import tool

registry = ToolRegistry()

@tool
def calculate(x: int, y: int) -> int:
    """Calculate sum."""
    return x + y

registry.register_tool(calculate)

# Later, replace with updated implementation
@tool
def calculate(x: int, y: int) -> int:
    """Calculate product (new implementation)."""
    return x * y

registry.replace(calculate)

Web and Search Result Citations - PR#1344

Citations now support web locations (WebLocation) and search result positions (SearchResultLocation), enabling agents to reference specific URLs and search result indices when grounding responses in retrieved information.

from strands.types.citations import WebLocation, SearchResultLocation

# Create web location citation
web_citation: WebLocation = {
    "url": "https://docs.example.com/api",
    "domain": "docs.example.com"
}

# Create search result citation
search_citation: SearchResultLocation = {
    "searchResultIndex": 0,
    "start": 150,
    "end": 320
}

A2A Server FastAPI/Starlette Customization - PR#1250

A2A servers now support passing additional kwargs to FastAPI and Starlette app constructors via app_kwargs, enabling customization of documentation URLs, debug settings, middleware, and other application-level configuration.

from strands.multiagent.a2a import A2AServer

server = A2AServer(agent)

# Customize FastAPI app
app = server.to_fastapi_app(
    app_kwargs={
        "title": "My Custom Agent API",
        "docs_url": None,  # Disable docs
        "redoc_url": None  # Disable redoc
    }
)

# Customize Starlette app
app = server.to_starlette_app(
    app_kwargs={"debug": True}
)

Major Bug Fixes

  • Citation Streaming and Union Type Fix - PR#1341
    Fixed CitationLocation to properly handle union types and correctly join citation chunks during streaming, resolving issues where citations were malformed or lost in streamed responses.

  • Usage Metrics Double Counting - PR#1327
    Fixed telemetry double counting of usage metrics, ensuring accurate token usage tracking and preventing inflated cost calculations.

  • Tools Returning Image Content - PR#1079
    Fixed OpenAI model provider to properly support tools that return image content, enabling vision-capable tools to work correctly.

  • Deprecation Warning Timing - PR#1380
    Fixed deprecation warnings to only emit when deprecated aliases are actually accessed, eliminating spurious warnings for users not using deprecated features.


What's Changed

  • Add issue-responder action agent by @afarntrog in #1319
  • feat(a2a): support passing additional keyword arguments to FastAPI and Starlette constructors by @snooyen in #1250
  • feat(tools): add replace method to ToolRegistry by @Ratish1 in #1182
  • feat: add meta field support to MCP tool results by @vamgan in #1237
  • fix: remove unnecessary None from dict.get() calls by @Ratish1 in #956
  • chore: Expose Status from .base for easier imports by @zastrowm in #1356
  • fix: CitationLocation is UnionType, and correctly joining citation chunks when streaming is being used by @ericfzhu in #1341
  • fix(telemetry): prevent double counting of usage metrics by @rajib76 in #1327
  • feat(citations): Add support for web and search result citations by @danilop in #1344
  • feat: add gemini_tools field to GeminiModel with validation and tests by @pshiko in #1050
  • Port PR guidelines from sdk-typescript by @zastrowm in #1373
  • feat: allow custom-client for OpenAIModel and GeminiModel by @poshinchen in #1366
  • fix: Pass CODECOV_TOKENS through for code-coverage stats by @zastrowm in #1385
  • ci: bump actions/checkout from 5 to 6 by @dependabot[bot] in #1222
  • ci: update pytest-asyncio requirement from <1.3.0,>=1.0.0 to >=1.0.0,<1.4.0 by @dependabot[bot] in #1166
  • ci: bump actions/upload-artifact from 4 to 6 by @dependabot[bot] ...
Read more

v1.20.0

15 Dec 20:36
2a02388

Choose a tag to compare

Major Features

Swarm Interrupts for Human-in-the-Loop - PR#1193

Swarm multi-agent systems now support interrupts, enabling Human-in-the-Loop patterns for approval workflows and user interaction during agent execution. Interrupts can be triggered via BeforeNodeCallEvent hooks or directly within agent tools using ToolContext.

from strands import Agent, tool
from strands.experimental.hooks.multiagent import BeforeNodeCallEvent
from strands.hooks import HookProvider
from strands.multiagent import Swarm
from strands.multiagent.base import Status

# Example 1: Interrupt via Hook
class ApprovalHook(HookProvider):
    def register_hooks(self, registry):
        registry.add_callback(BeforeNodeCallEvent, self.approve)

    def approve(self, event):
        response = event.interrupt("approval", reason=f"{event.node_id} needs approval")
        if response != "APPROVE":
            event.cancel_node = "rejected"

swarm = Swarm([agent1, agent2], hooks=[ApprovalHook()])
result = swarm("Task requiring approval")

# Handle interrupts
while result.status == Status.INTERRUPTED:
    for interrupt in result.interrupts:
        user_input = input(f"{interrupt.reason}: ")
        responses = [{"interruptResponse": {"interruptId": interrupt.id, "response": user_input}}]
    result = swarm(responses)

# Example 2: Interrupt via Tool
@tool(context=True)
def get_user_info(tool_context: ToolContext) -> str:
    response = tool_context.interrupt("user_info", reason="need user name")
    return f"User: {response}"

user_agent = Agent(name="user", tools=[get_user_info])
swarm = Swarm([user_agent])
result = swarm("Who is the user?")
# Resume with interrupt response as shown above

See the interrupts documentation for more details.

AgentResult Access in AfterInvocationEvent - PR#1125

Hooks can now access the complete AgentResult in AfterInvocationEvent, enabling post-invocation actions based on the agent's output, stop reason, and metrics. This enhancement allows for richer observability and custom handling of agent results.

from strands import Agent
from strands.hooks import AfterInvocationEvent, HookProvider

class ResultLoggingHook(HookProvider):
    def register_hooks(self, registry):
        registry.add_callback(AfterInvocationEvent, self.log_result)

    def log_result(self, event: AfterInvocationEvent):
        # Access the complete AgentResult
        if event.result:
            print(f"Stop reason: {event.result.stop_reason}")
            print(f"Tokens used: {event.result.usage}")
            print(f"Response: {event.result.text}")

agent = Agent(hooks=[ResultLoggingHook()])
result = agent("What is 2+2?")

Major Bug Fixes

  • Structured Output Display Fix - PR#1290
    Fixed AgentResult.__str__() to return structured output JSON when no text content is present, resolving issues where print(agent_result) showed empty output and structured output was lost in multi-agent graph propagation.

  • Tool Spec Composition Keywords Fix - PR#1301
    Fixed tool specification handling for JSON Schema composition keywords (anyOf, oneOf, allOf, not), preventing models from incorrectly returning string-encoded JSON for optional parameters like Optional[List[str]].

  • MCP Client Resource Leak Fix - PR#1321
    Fixed file descriptor leak in MCP client by properly closing the asyncio event loop, preventing resource exhaustion in multi-tenant applications that create many MCP clients.


All Changes

New Contributors

Full Changelog: v1.19.0...v1.20.0

v1.19.0

03 Dec 18:43
62534de

Choose a tag to compare

What's New

  • Bidirectional Agents (Experimental): This release introduces BidiAgent for real-time voice conversations with AI agents through persistent connections that support continuous audio streaming, natural interruptions, and concurrent tool execution. This experimental feature allows developers to build voice assistants and interactive applications with support for Amazon Nova Sonic, OpenAI Realtime API, and Google Gemini Live.

  • Steering (Experimental): Enables modular prompting with progressive disclosure for complex agent workflows through just-in-time feedback loops. Instead of front-loading all instructions into monolithic prompts, steering handlers provide contextual guidance that appears when relevant, maintaining agent effectiveness on multi-step tasks while preserving adaptive reasoning capabilities.

What's Changed

New Contributors

Full Changelog: v1.18.0...v1.19.0

v1.18.0

21 Nov 21:25
eaa6efb

Choose a tag to compare

What's Changed

  • multi agent input by @pgrayy in #1196
  • interrupt - activate - set context separately by @pgrayy in #1194
  • In PrintingCallbackHandler, make the verbose description and counting… by @marcbrooker in #1211
  • fix: fix swarm session management integ test. by @JackYPCOnline in #1155
  • move tool caller definition out of agent module by @pgrayy in #1215
  • interrupt - interruptible multi agent hook interface by @pgrayy in #1207
  • security(tool_loader): prevent tool name and sys modules collisions i… by @dbschmigelski in #1214
  • fix(mcp): protect connection on non-fatal client side timeout error by @dbschmigelski in #1231
  • fix(litellm): populate cacheWriteInputTokens from cache_creation_input_token not cache_creation_tokens by @dbschmigelski in #1233
  • fix: fix integ test for mcp elicitation_server by @JackYPCOnline in #1234

New Contributors

Full Changelog: v1.17.0...v1.18.0

v1.17.0

18 Nov 19:09
95ac650

Choose a tag to compare

Strands Agents SDK v1.17.0 Release Notes

Features

Configurable Timeout for MCP Agent Tools - PR#1184

You can now set custom timeout values when creating MCP (Model Context Protocol) agent tools, providing better control over tool execution time limits and improving reliability when working with external MCP servers.

from datetime import timedelta
from strands.tools.mcp import MCPAgentTool

# Create MCP tool with custom 30-second timeout
mcp_tool = MCPAgentTool(
    ...,
    timeout=timedelta(seconds=30)
)

agent = Agent(tools=[mcp_tool])

This feature is especially useful when working with MCP servers that may have varying response times, allowing you to fine-tune timeout behavior for different use cases.


Bug Fixes

  • Swarm Handoff Timing - PR#1147
    Fixed swarm handoff behavior to only switch to the handoff node after the current node completes execution. Previously, the switch occurred mid-execution, causing incorrect event emissions and invalid swarm state when tools were interrupted concurrently with handoff tools.

  • LiteLLM Stream Parameter Validation - PR#1183
    Added validation for the stream parameter in LiteLLM to prevent TypeError when stream=False is provided. The SDK now properly handles both streaming and non-streaming responses with clear error messaging.

  • Optional MetadataEvent Fields - PR#1187
    Fixed handling of MetadataEvents when custom model implementations omit optional usage or metrics fields. The SDK now provides sensible defaults, preventing KeyError exceptions and enabling greater flexibility for custom model providers.

  • A2A Protocol File Data Decoding - PR#1195
    Fixed A2A (Agent-to-Agent) executor to properly base64 decode file bytes from A2A messages before passing to Strands agents. Previously, agents were receiving base64-encoded strings instead of actual binary file content.


All changes

  • feat: allow setting a timeout when creating MCPAgentTool by @AnirudhKonduru in #1184
  • fix(litellm): add validation for stream parameter in LiteLLM by @dbschmigelski in #1183
  • fix(event_loop): handle MetadataEvents without optional usage and metrics by @dbschmigelski in #1187
  • swarm - switch to handoff node only after current node stops by @pgrayy in #1147
  • fix(a2a): base64 decode byte data before placing in ContentBlocks by @dbschmigelski in #1195

New Contributors

Full Changelog: v1.16.0...v1.17.0

v1.16.0

12 Nov 21:06
8cae18c

Choose a tag to compare

Major Features

Async Hooks Support - PR#1119

Hooks now support asynchronous callbacks, allowing your hook code to run concurrently with other async tasks without blocking the event loop. This is particularly beneficial for async agent invocations and scenarios where hooks perform I/O operations.

import asyncio
from strands import Agent
from strands.hooks import BeforeInvocationEvent, HookProvider, HookRegistry

class AsyncHook(HookProvider):
    def register_hooks(self, registry: HookRegistry, **kwargs) -> None:
        registry.add_callback(BeforeInvocationEvent, self.async_callback)

    async def async_callback(self, event: BeforeInvocationEvent) -> None:
        # Perform async operations without blocking the event loop
        await asyncio.sleep(1)
        print("Hook executed asynchronously!")

agent = Agent(hooks=[AsyncHook()])
await agent.invoke_async("Hello!")

Thread Context Sharing - PR#1146

Context variables (contextvars) are now automatically copied from the main thread to agent threads when using synchronous invocations. This ensures that context-dependent tools and hooks work correctly.

from contextvars import ContextVar
from strands import Agent, tool

request_id = ContextVar('request_id')

@tool
def my_tool():
    # Context variables are now accessible within tools
    current_request_id = request_id.get()
    return f"Processing request: {current_request_id}"

request_id.set("abc-123")
agent = Agent(tools=[my_tool])
response = agent.invoke_async("Use my tool")  # Context is properly propagated

Enhanced Telemetry with Tool Definitions - PR#1113

Tool definitions are now included in OpenTelemetry traces via semantic convention opt-in, providing better observability into agent tool usage, following the OpenTelemetry semantic conventions for GenAI.

Opt-in via environment variable:

OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_tool_definitions
from strands import Agent, tool
from strands_tools import calculator

# Tool definition will appear in telemetry traces
agent = Agent(tools=[calculator])
agent("What is 5 + 3?")

String Descriptions in Annotated Tool Parameters - PR#1089

You can now use simple string descriptions directly in Annotated type hints for tool parameters, improving code readability and reducing boilerplate.

from typing import Annotated
from strands import tool

@tool
def get_weather(
    location: Annotated[str, "The city and state, e.g., San Francisco, CA"],
    units: Annotated[str, "Temperature units: 'celsius' or 'fahrenheit'"] = "celsius"
):
    """Get weather for a location."""
    return f"Weather in {location}: 72°{units[0].upper()}"

# Previously required more verbose Field() syntax or using a doc-string

Major Bug Fixes

  • Anthropic "Prompt Too Long" Error Handling - PR#1137
    The SDK now properly handles and surfaces Anthropic's "prompt is too long" errors, making it easier to diagnose and fix context window issues.
  • MCP Server 5xx Error Resilience - PR#1169
    The SDK no longer hangs when Model Context Protocol (MCP) servers return 5xx errors, improving reliability when working with external services.
  • Gemini Non-JSON Error Message Handling - PR#1062
    The Gemini model provider now gracefully handles non-JSON error responses, preventing unexpected crashes.

All changes

  • fix(models/gemini): handle non-JSON error messages from Gemini API by @Ratish1 in #1062
  • fix: Handle "prompt is too long" from Anthropic by @zastrowm in #1137
  • feat(telemetry): Add tool definitions to traces via semconv opt-in by @Ratish1 in #1113
  • fix: Strip argument sections out of inputSpec top-level description by @zastrowm in #1142
  • share thread context by @pgrayy in #1146
  • async hooks by @pgrayy in #1119
  • feat(tools): Support string descriptions in Annotated parameters by @Ratish1 in #1089
  • chore(telemetry): updated opt-in attributes to internal by @poshinchen in #1152
  • feat(models): allow SystemContentBlocks in LiteLLMModel by @dbschmigelski in #1141
  • share interrupt state by @pgrayy in #1148
  • fix: Don't hang when MCP server returns 5xx by @zastrowm in #1169
  • fix(models): allow setter on system_prompt and system_prompt_content by @dbschmigelski in #1171

Full Changelog: v1.15.0...v1.16.0

v1.15.0

04 Nov 18:21
9f10595

Choose a tag to compare

Major Features

SystemContentBlock Support for Provider-Agnostic Caching - PR#1112

System prompts now support SystemContentBlock arrays, enabling provider-agnostic caching and advanced multi-prompt system configurations. Cache points can be defined explicitly within system content.

from strands import Agent
from strands.types.content import SystemContentBlock

# Define system content with cache points
system_content: list[SystemContentBlock] = [
    {"text": "You are a helpful assistant with extensive knowledge."},
    {"text": "Your responses should be concise and accurate."},
    {"text": "Always cite sources."},
    {"cachePoint": {"type": "default"}},
]

agent = Agent(system_prompt=system_content)
agent('What is the capital of Franch?')

Multi-Agent Session Management and Persistence - PR#1071, PR#1110

Multi-agent systems now support session management and persistence, enabling agents to maintain state across invocations and support long-running workflows.

from strands import Agent
from strands.multiagent import GraphBuilder
from strands.multiagent.base import Status
from strands.session import FileSessionManager

def build_graph(max_nodes: int):
    session_manager = FileSessionManager(session_id="my_session_1", storage_dir="./sessions")

    builder = GraphBuilder()
    builder.add_node(Agent(name="analyzer", system_prompt="Explain using 2 paragraphs. "))
    builder.add_node(Agent(name="summarizer", system_prompt="Summerize and be concise.  10 words or less"))

    builder.add_edge("analyzer", "summarizer")
    builder.set_entry_point("analyzer")
    builder.set_max_node_executions(max_nodes)
    builder.set_session_manager(session_manager)

    return builder.build()

# Simulate failure because after the first node we exceed max_nodes
result = await build_graph(max_nodes=1).invoke_async("Analyze why 2+2=4")
assert result.status == Status.FAILED

# Simulate that we're resuming from a failure with a fresh session - this picks at the summerizer
result = await build_graph(max_nodes=10).invoke_async("Analyze why 2+2=4")
assert result.status == Status.COMPLETED

Async Streaming for Multi-Agent Systems - PR#961

Multi-agent systems now support stream_async, enabling real-time streaming of events from agent teams as they collaborate.

from strands import Agent
from strands.multiagent import GraphBuilder

# Create multi-agent graph
analyzer = Agent(name="analyzer")
processor = Agent(name="processor")

builder = GraphBuilder()
builder.add_node(analyzer)
builder.add_node(processor)
builder.add_edge("analyzer", "processor")
builder.set_entry_point("analyzer")

graph = builder.build()

# Stream events as agents process
async for event in graph.stream_async("Analyze this data"):
    print(f"Event: {event.get('type', 'unknown')}")

Major Bug Fixes

  • Guardrails Redaction Fix - PR#1072
    Fixed input/output message redaction when guardrails_trace="enabled_full", ensuring sensitive data is properly protected in traces.

  • Tool Result Block Redaction - PR#1080
    Properly redact tool result blocks to prevent conversation corruption when using content filtering or PII redaction.

  • Orphaned Tool Use Fix - PR#1123
    Fixed broken conversations caused by orphaned toolUse blocks, improving reliability when tools fail or are interrupted.

  • Reasoning Content Handling - PR#1099
    Drop reasoningContent from requests to prevent errors with providers that don't support extended thinking modes.

  • Swarm Initialization Fix - PR#1107
    Don't initialize agents during Swarm construction, preventing unnecessary resource allocation and improving startup performance.

  • Structured Output Context Fix - PR#1128
    Allow None structured output context in tool executors, fixing edge cases where tools don't require structured responses.

What's Changed

  • fix: (bug): Drop reasoningContent from request by @mehtarac in #1099
  • fix: Dont initialize an agent on swarm init by @Unshure in #1107
  • feat: add multiagent session/repository management. by @JackYPCOnline in #1071
  • feat(multiagent): Add stream_async by @mkmeral in #961
  • Fix #1077: properly redact toolResult blocks to avoid corrupting the conversation by @leotac in #1080
  • linting by @pgrayy in #1120
  • Fix input/output message not redacted when guardrails_trace="enabled_full" by @leotac in #1072
  • fix: Allow none structured output context in tool executors by @mkmeral in #1128
  • fix: Fix broken converstaion with orphaned toolUse by @Unshure in #1123
  • feat: Enable multiagent session persistent in Graph/Swarm by @JackYPCOnline in #1110
  • feat(models): add SystemContentBlock support for provider-agnostic caching by @dbschmigelski in #1112

New Contributors

Full Changelog: v1.14.0...v1.15.0

v1.14.0

29 Oct 14:15
c2ba0f7

Choose a tag to compare

Major Features

Structured Output via Agentic Loop

Agents can now validate responses against predefined schemas using JSON Schema or Pydantic models. Validation occurs at response generation time with configurable retry behavior for non-conforming outputs.

agent = Agent()
result = agent(
    "John Smith is a 30 year-old software engineer",
    structured_output_model=PersonInfo
)

# Access the structured output from the result
person_info: PersonInfo = result.structured_output

See more in the docs for Structured Output.

Interrupting Agents

Interrupts now provide first-class support for Human-in-the-loop patterns in Strands. They can be raised in Hooks or directly in tool definitions. Related, MCP elicitation has been exposed on the MCPClient.

import json
from typing import Any

from strands import Agent, tool
from strands.hooks import BeforeToolCallEvent, HookProvider, HookRegistry

from my_project import delete_files, inspect_files

class ApprovalHook(HookProvider):
    def __init__(self, app_name: str) -> None:
        self.app_name = app_name

    def register_hooks(self, registry: HookRegistry, **kwargs: Any) -> None:
        registry.add_callback(BeforeToolCallEvent, self.approve)

    def approve(self, event: BeforeToolCallEvent) -> None:
        if event.tool_use["name"] != "delete_files":
            return

        approval = event.interrupt(f"{self.app_name}-approval", reason={"paths": event.tool_use["input"]["paths"]})
        if approval.lower() != "y":
            event.cancel_tool = "User denied permission to delete files"


agent = Agent(
    hooks=[ApprovalHook("myapp")],
    system_prompt="You delete files older than 5 days",
    tools=[delete_files, inspect_files],
)

paths = ["a/b/c.txt", "d/e/f.txt"]
result = agent(f"paths=<{paths}>")

while True:
    if result.stop_reason != "interrupt":
        break

    responses = []
    for interrupt in result.interrupts:
        if interrupt.name == "myapp-approval":
            user_input = input(f"Do you want to delete {interrupt.reason["paths"]} (y/N): ")
            responses.append({
                "interruptResponse": {
                    "interruptId": interrupt.id, 
                    "response": user_input
                }
            })

    result = agent(responses)

Managed MCP Connections

We've introduced MCP Connections via ToolProviders, an experimental interface that addresses the requirement to use context managers with MCP tools. The Agent now manages connection lifecycles automatically, enabling simpler syntax:

agent = Agent(tools=[stdio_mcp_client])
agent("do something")

While this feature is experimental, we aim to mark it as stable soon and welcome user testing of this and other new features.

Agent Config

Users can now define and create agents using configuration files or dictionaries:

{
  "model": "us.anthropic.claude-3-5-sonnet-20241022-v2:0",
  "prompt": "You are a coding assistant. Help users write, debug, and improve their code. You have access to file operations and can execute shell commands when needed.",
  "tools": ["strands_tools.file_read", "strands_tools.editor", "strands_tools.shell"]
}

All Changes

  • models - litellm - start and stop reasoning by @pgrayy in #947
  • feat: add experimental AgentConfig with comprehensive tool management by @mr-lee in #935
  • fix(telemetry): make strands agent invoke_agent span as INTERNAL spanKind by @poshinchen in #1055
  • feat: add multiagent hooks, add serialize & deserialize function to multiagent base & agent result by @JackYPCOnline in #1070
  • feat: Add Structured Output as part of the agent loop by @afarntrog in #943
  • integ tests - interrupts - remove asyncio marker by @pgrayy in #1045
  • interrupt - docstring - fix formatting by @pgrayy in #1074
  • ci: add pr size labeler by @dbschmigelski in #1082
  • fix: Don't bail out if there are no tool_uses by @zastrowm in #1087
  • feat(mcp): add experimental agent managed connection via ToolProvider by @dbschmigelski in #895
  • fix (bug): retry on varying Bedrock throttlingexception cases by @mehtarac in #1096
  • feat: skip model invocation when latest message contains ToolUse by @Unshure in #1068
  • direct tool call - interrupt not allowed by @pgrayy in #1097
  • mcp elicitation by @pgrayy in #1094
  • fix(litellm): enhance structured output handling by @Arindam200 in #1021
  • Transform invalid tool usages on sending, not on initial detection by @zastrowm in #1091

New Contributors

Full Changelog: v1.13.0...v1.14.0