From f3b224dfd8bf9dc414329671df5c2157fb096aab Mon Sep 17 00:00:00 2001 From: Murat Kaan Meral Date: Thu, 25 Sep 2025 13:33:35 +0200 Subject: [PATCH 1/4] Add multi-agent shared state docs --- docs/user-guide/concepts/agents/hooks.md | 38 ++++++++ docs/user-guide/concepts/multi-agent/graph.md | 32 +++++++ docs/user-guide/concepts/multi-agent/swarm.md | 36 +++++++ .../user-guide/concepts/tools/python-tools.md | 95 +++++++++++++++++++ 4 files changed, 201 insertions(+) diff --git a/docs/user-guide/concepts/agents/hooks.md b/docs/user-guide/concepts/agents/hooks.md index 2afcbe03..56da9438 100644 --- a/docs/user-guide/concepts/agents/hooks.md +++ b/docs/user-guide/concepts/agents/hooks.md @@ -130,6 +130,44 @@ Some events come in pairs, such as Before/After events. The After event callback ## Advanced Usage +### Accessing Invocation State in Hooks + +Hook events that involve tool execution include access to `invocation_state`, which provides configuration and context data passed through the agent invocation. This is particularly useful for: + +1. **Configuration and Credentials**: Access API keys, database configurations, or other settings without hardcoding them +2. **Request Context**: Access session IDs, user information, or request-specific data +3. **Multi-Agent Shared State**: In [Graph](../multi-agent/graph.md) and [Swarm](../multi-agent/swarm.md) patterns, access state shared across all agents +4. **Custom Parameters**: Pass any additional data that hooks might need + +```python +from strands.experimental.hooks import BeforeToolInvocationEvent + +def log_with_context(event: BeforeToolInvocationEvent) -> None: + """Log tool invocations with context from invocation state.""" + # Access invocation state from the event + user_id = event.invocation_state.get("user_id", "unknown") + session_id = event.invocation_state.get("session_id") + environment = event.invocation_state.get("environment", "production") + + # Log with context + logger.info( + f"User {user_id} in session {session_id} ({environment}) " + f"invoking tool: {event.tool_use['name']}" + ) + +# Register the hook +agent = Agent(tools=[my_tool]) +agent.hooks.add_callback(BeforeToolInvocationEvent, log_with_context) + +# Execute with context in invocation state +result = agent( + "Process the data", + user_id="user123", + session_id="sess456", + environment="staging" +) +``` + ### Fixed Tool Arguments Enforce specific arguments for tools, ensuring they always use particular values regardless of what the agent specifies: diff --git a/docs/user-guide/concepts/multi-agent/graph.md b/docs/user-guide/concepts/multi-agent/graph.md index 5c92ffba..56f65d27 100644 --- a/docs/user-guide/concepts/multi-agent/graph.md +++ b/docs/user-guide/concepts/multi-agent/graph.md @@ -319,6 +319,38 @@ From [another_node_id]: - [Agent name]: [Result text] ``` +## Shared State + +Graphs support passing shared state to all agents through the `invocation_state` parameter. This enables sharing configuration, credentials, and other data across agents without exposing it to the LLM. + +```python +# Execute graph with shared state +result = graph( + "Analyze customer data", + invocation_state={ + "api_key": "secret-key-123", + "database_config": {"host": "db.example.com", "port": 5432}, + "session_id": "user-session-456" + } +) +``` + +The `invocation_state` is automatically propagated to: + +- All agents in the graph via their `**kwargs` +- Tools via `ToolContext` when using `@tool(context=True)` - see [Python Tools](../tools/python-tools.md#accessing-invocation-state-in-tools) +- Tool-related hooks (BeforeToolInvocationEvent, AfterToolInvocationEvent) - see [Hooks](../agents/hooks.md#accessing-invocation-state-in-hooks) + +```python +# Tools access shared state through ToolContext +@tool(context=True) +def api_call(endpoint: str, tool_context: ToolContext) -> str: + api_key = tool_context.invocation_state.get("api_key") + # Use api_key for authenticated requests... +``` + +Use `invocation_state` for configuration and credentials that shouldn't appear in prompts, while using normal Graph input propagation for data the LLM should reason about. + ## Graphs as a Tool Agents can dynamically create and orchestrate graphs by using the `graph` tool available in the [Strands tools package](../tools/community-tools-package.md). diff --git a/docs/user-guide/concepts/multi-agent/swarm.md b/docs/user-guide/concepts/multi-agent/swarm.md index a5376c03..5ff59db6 100644 --- a/docs/user-guide/concepts/multi-agent/swarm.md +++ b/docs/user-guide/concepts/multi-agent/swarm.md @@ -166,6 +166,42 @@ Agent name: security_specialist. Agent description: Focuses on secure coding pra You have access to swarm coordination tools if you need help from other agents. ``` +## Shared State + +Swarms support passing shared state to all agents through the `invocation_state` parameter. This enables sharing configuration and credentials across agents without exposing them to the LLM, keeping them separate from the shared context used for collaboration. + +```python +# Execute swarm with shared state +result = swarm( + "Research and analyze the data", + invocation_state={ + "api_key": "bearer-token-xyz", + "api_base_url": "https://api.example.com", + "cache_enabled": True + } +) +``` + +The `invocation_state` is automatically propagated to: + +- All agents in the swarm via their `**kwargs` +- Tools via `ToolContext` when using `@tool(context=True)` - see [Python Tools](../tools/python-tools.md#accessing-invocation-state-in-tools) +- Tool-related hooks (BeforeToolInvocationEvent, AfterToolInvocationEvent) - see [Hooks](../agents/hooks.md#accessing-invocation-state-in-hooks) + +```python +# Tools access shared state through ToolContext +@tool(context=True) +def fetch_data(source: str, tool_context: ToolContext) -> dict: + api_key = tool_context.invocation_state.get("api_key") + base_url = tool_context.invocation_state.get("api_base_url") + # Use configuration for API calls... +``` + +**Important distinction:** + +- **Shared Context**: Information agents share through handoffs, visible in prompts for collaboration +- **Shared State**: Hidden configuration passed via `invocation_state`, not visible in prompts + ## Asynchronous Execution You can also execute a Swarm asynchronously by calling the [`invoke_async`](../../../api-reference/multiagent.md#strands.multiagent.swarm.Swarm.invoke_async) function: diff --git a/docs/user-guide/concepts/tools/python-tools.md b/docs/user-guide/concepts/tools/python-tools.md index c8796580..3739c1e1 100644 --- a/docs/user-guide/concepts/tools/python-tools.md +++ b/docs/user-guide/concepts/tools/python-tools.md @@ -172,6 +172,101 @@ agent("What is the tool use id?") agent("What is the invocation state?", custom_data="You're the best agent ;)") ``` +#### Accessing Invocation State in Tools + +The `invocation_state` attribute in `ToolContext` provides access to data passed through the agent invocation. This is particularly useful for: + +1. **Configuration and Credentials**: Access API keys, database configurations, or other settings without hardcoding them in tools +2. **Request Context**: Access session IDs, user information, or request-specific data +3. **Multi-Agent Shared State**: In [Graph](../multi-agent/graph.md) and [Swarm](../multi-agent/swarm.md) patterns, access state shared across all agents +4. **Custom Parameters**: Pass any additional data that tools might need + +```python +@tool(context=True) +def secure_api_call(endpoint: str, tool_context: ToolContext) -> dict: + """Make a secure API call using credentials from invocation state. + + Args: + endpoint: The API endpoint to call + tool_context: Context containing invocation state with credentials + """ + # Access credentials and configuration from invocation state + api_key = tool_context.invocation_state.get("api_key") + base_url = tool_context.invocation_state.get("api_base_url", "https://api.example.com") + timeout = tool_context.invocation_state.get("timeout", 30) + + if not api_key: + return {"error": "API key not provided in invocation state"} + + # Make the API call using the credentials + response = requests.get( + f"{base_url}/{endpoint}", + headers={"Authorization": f"Bearer {api_key}"}, + timeout=timeout + ) + + return response.json() + +# Single agent usage +agent = Agent(tools=[secure_api_call]) +result = agent( + "Get user profile data", + api_key="secret-key-123", + api_base_url="https://myapi.example.com", + timeout=60 +) + +# Multi-agent usage (Graph or Swarm) +# The invocation_state is automatically propagated to all agents +graph_result = graph( + "Analyze user data", + invocation_state={ + "api_key": "secret-key-123", + "api_base_url": "https://myapi.example.com", + "timeout": 60 + } +) +``` + +#### Invocation State vs. Tool Parameters + +It's important to understand when to use invocation state versus regular tool parameters: + +- **Tool Parameters**: Use for data that the LLM should reason about and provide +- **Invocation State**: Use for configuration, credentials, and context that should not appear in prompts + +```python +@tool(context=True) +def database_query( + query: str, # LLM provides this based on user request + tool_context: ToolContext +) -> str: + """Execute a database query. + + Args: + query: SQL query to execute (provided by LLM) + tool_context: Context with database configuration (from invocation state) + """ + # Database config comes from invocation state (hidden from LLM) + db_config = tool_context.invocation_state.get("database", {}) + + # Query comes from LLM reasoning about the user's request + connection = create_connection(**db_config) + return execute_query(connection, query) + +# The LLM decides what query to run, but uses hidden configuration +agent = Agent(tools=[database_query]) +result = agent( + "Show me all users created this month", + database={ + "host": "db.example.com", + "port": 5432, + "user": "readonly", + "password": "secret" + } +) +``` + ### Tool Streaming Async tools can yield intermediate results to provide real-time progress updates. Each yielded value becomes a [streaming event](../streaming/overview.md), with the final value serving as the tool's return result: From 5b588d8ea02bb44744b59925c2b76b6f07fac13e Mon Sep 17 00:00:00 2001 From: Murat Kaan Meral Date: Thu, 25 Sep 2025 17:54:37 +0200 Subject: [PATCH 2/4] Update example add guidance for invcation state in tools --- .../user-guide/concepts/tools/python-tools.md | 95 +++++-------------- 1 file changed, 22 insertions(+), 73 deletions(-) diff --git a/docs/user-guide/concepts/tools/python-tools.md b/docs/user-guide/concepts/tools/python-tools.md index 3739c1e1..72e9b872 100644 --- a/docs/user-guide/concepts/tools/python-tools.md +++ b/docs/user-guide/concepts/tools/python-tools.md @@ -176,96 +176,45 @@ agent("What is the invocation state?", custom_data="You're the best agent ;)") The `invocation_state` attribute in `ToolContext` provides access to data passed through the agent invocation. This is particularly useful for: -1. **Configuration and Credentials**: Access API keys, database configurations, or other settings without hardcoding them in tools -2. **Request Context**: Access session IDs, user information, or request-specific data -3. **Multi-Agent Shared State**: In [Graph](../multi-agent/graph.md) and [Swarm](../multi-agent/swarm.md) patterns, access state shared across all agents -4. **Custom Parameters**: Pass any additional data that tools might need +1. **Request Context**: Access session IDs, user information, or request-specific data +2. **Multi-Agent Shared State**: In [Graph](../multi-agent/graph.md) and [Swarm](../multi-agent/swarm.md) patterns, access state shared across all agents +3. **Per-Invocation Overrides**: Override behavior or settings for specific requests ```python +from strands import tool, Agent, ToolContext +import requests + @tool(context=True) -def secure_api_call(endpoint: str, tool_context: ToolContext) -> dict: - """Make a secure API call using credentials from invocation state. +def api_call(query: str, tool_context: ToolContext) -> dict: + """Make an API call with user context. Args: - endpoint: The API endpoint to call - tool_context: Context containing invocation state with credentials + query: The search query to send to the API + tool_context: Context containing user information """ - # Access credentials and configuration from invocation state - api_key = tool_context.invocation_state.get("api_key") - base_url = tool_context.invocation_state.get("api_base_url", "https://api.example.com") - timeout = tool_context.invocation_state.get("timeout", 30) - - if not api_key: - return {"error": "API key not provided in invocation state"} + user_id = tool_context.invocation_state.get("user_id") - # Make the API call using the credentials response = requests.get( - f"{base_url}/{endpoint}", - headers={"Authorization": f"Bearer {api_key}"}, - timeout=timeout + "https://api.example.com/search", + headers={"X-User-ID": user_id}, + params={"q": query} ) return response.json() -# Single agent usage -agent = Agent(tools=[secure_api_call]) -result = agent( - "Get user profile data", - api_key="secret-key-123", - api_base_url="https://myapi.example.com", - timeout=60 -) - -# Multi-agent usage (Graph or Swarm) -# The invocation_state is automatically propagated to all agents -graph_result = graph( - "Analyze user data", - invocation_state={ - "api_key": "secret-key-123", - "api_base_url": "https://myapi.example.com", - "timeout": 60 - } -) +agent = Agent(tools=[api_call]) +result = agent("Get my profile data", user_id="user123") ``` -#### Invocation State vs. Tool Parameters +##### Invocation State Compared To Other Approaches -It's important to understand when to use invocation state versus regular tool parameters: +It's important to understand how invocation state compares to other approaches that impact tool execution: -- **Tool Parameters**: Use for data that the LLM should reason about and provide -- **Invocation State**: Use for configuration, credentials, and context that should not appear in prompts +- **Tool Parameters**: Use for data that the LLM should reason about and provide based on the user's request. Examples include search queries, file paths, calculation inputs, or any data the agent needs to determine from context. -```python -@tool(context=True) -def database_query( - query: str, # LLM provides this based on user request - tool_context: ToolContext -) -> str: - """Execute a database query. - - Args: - query: SQL query to execute (provided by LLM) - tool_context: Context with database configuration (from invocation state) - """ - # Database config comes from invocation state (hidden from LLM) - db_config = tool_context.invocation_state.get("database", {}) - - # Query comes from LLM reasoning about the user's request - connection = create_connection(**db_config) - return execute_query(connection, query) - -# The LLM decides what query to run, but uses hidden configuration -agent = Agent(tools=[database_query]) -result = agent( - "Show me all users created this month", - database={ - "host": "db.example.com", - "port": 5432, - "user": "readonly", - "password": "secret" - } -) -``` +- **Invocation State**: Use for context and configuration that should not appear in prompts but affects tool behavior. Best suited for parameters that can change between agent invocations. Examples include user IDs for personalization, session IDs, or user flags. + +- **[Class-based tools](#class-based-tools)**: Use for configuration that doesn't change between requests and requires initialization. Examples include API keys, database connection strings, service endpoints, or shared resources that need setup. ### Tool Streaming From 5c6e662f2ec4b9135a99580c326f6baa1873d3a7 Mon Sep 17 00:00:00 2001 From: Mackenzie Zastrow <3211021+zastrowm@users.noreply.github.com> Date: Fri, 26 Sep 2025 12:31:43 -0400 Subject: [PATCH 3/4] Update documentation for production-ready hooks (#267) In strands-agents/sdk-python/pull/926 we've released these hooks (and done a rename) so update the docs to account for it Part of strands-agents/sdk-python#667 Co-authored-by: Mackenzie Zastrow --- docs/user-guide/concepts/agents/hooks.md | 58 ++++++++++-------------- 1 file changed, 24 insertions(+), 34 deletions(-) diff --git a/docs/user-guide/concepts/agents/hooks.md b/docs/user-guide/concepts/agents/hooks.md index 56da9438..a983a4db 100644 --- a/docs/user-guide/concepts/agents/hooks.md +++ b/docs/user-guide/concepts/agents/hooks.md @@ -16,7 +16,7 @@ Hooks enable use cases such as: ## Basic Usage -Hook callbacks are registered against specific event types and receive strongly-typed event objects when those events occur during agent execution. Each event carries relevant data for that stage of the agent lifecycle - for example, `BeforeInvocationEvent` includes agent and request details, while `BeforeToolInvocationEvent` provides tool information and parameters. +Hook callbacks are registered against specific event types and receive strongly-typed event objects when those events occur during agent execution. Each event carries relevant data for that stage of the agent lifecycle - for example, `BeforeInvocationEvent` includes agent and request details, while `BeforeToolCallEvent` provides tool information and parameters. ### Registering Individual Hook Callbacks @@ -69,19 +69,19 @@ flowchart LR end subgraph Model["Model Events"] direction TB - AfterModelInvocationEvent["AfterModelInvocationEvent"] - BeforeModelInvocationEvent["BeforeModelInvocationEvent"] + AfterModelCallEvent["AfterModelCallEvent"] + BeforeModelCallEvent["BeforeModelCallEvent"] ModelMessage["MessageAddedEvent"] - BeforeModelInvocationEvent --> AfterModelInvocationEvent - AfterModelInvocationEvent --> ModelMessage + BeforeModelCallEvent --> AfterModelCallEvent + AfterModelCallEvent --> ModelMessage end subgraph Tool["Tool Events"] direction TB - AfterToolInvocationEvent["AfterToolInvocationEvent"] - BeforeToolInvocationEvent["BeforeToolInvocationEvent"] + AfterToolCallEvent["AfterToolCallEvent"] + BeforeToolCallEvent["BeforeToolCallEvent"] ToolMessage["MessageAddedEvent"] - BeforeToolInvocationEvent --> AfterToolInvocationEvent - AfterToolInvocationEvent --> ToolMessage + BeforeToolCallEvent --> AfterToolCallEvent + AfterToolCallEvent --> ToolMessage end subgraph End["Request End Events"] direction TB @@ -103,25 +103,16 @@ The hooks system provides events for different stages of agent execution: | `BeforeInvocationEvent` | Triggered at the beginning of a new agent request (`__call__`, `stream_async`, or `structured_output`) | | `AfterInvocationEvent` | Triggered at the end of an agent request, regardless of success or failure. Uses reverse callback ordering | | `MessageAddedEvent` | Triggered when a message is added to the agent's conversation history | - -Additional *experimental events* are also available: - -!!! note "Experimental events are subject to change" - - These events are exposed experimentally in order to gather feedback and refine the public contract. Because they are experimental, they are subject to change between releases. - -| Experimental Event | Description | -|------------------------------|-------------| -| `BeforeModelInvocationEvent` | Triggered before the model is invoked for inference | -| `AfterModelInvocationEvent` | Triggered after model invocation completes. Uses reverse callback ordering | -| `BeforeToolInvocationEvent` | Triggered before a tool is invoked. | -| `AfterToolInvocationEvent` | Triggered after tool invocation completes. Uses reverse callback ordering | +| `BeforeModelCallEvent` | Triggered before the model is invoked for inference | +| `AfterModelCallEvent` | Triggered after model invocation completes. Uses reverse callback ordering | +| `BeforeToolCallEvent` | Triggered before a tool is invoked. | +| `AfterToolCallEvent` | Triggered after tool invocation completes. Uses reverse callback ordering | ## Hook Behaviors ### Event Properties -Most event properties are read-only to prevent unintended modifications. However, certain properties can be modified to influence agent behavior. For example, `BeforeToolInvocationEvent.selected_tool` allows you to change which tool gets executed, while `AfterToolInvocationEvent.result` enables modification of tool results. +Most event properties are read-only to prevent unintended modifications. However, certain properties can be modified to influence agent behavior. For example, `BeforeToolCallEvent.selected_tool` allows you to change which tool gets executed, while `AfterToolCallEvent.result` enables modification of tool results. ### Callback Ordering @@ -174,8 +165,7 @@ Enforce specific arguments for tools, ensuring they always use particular values ```python from typing import Any -from strands.hooks import HookProvider, HookRegistry -from strands.experimental.hooks import BeforeToolInvocationEvent +from strands.hooks import HookProvider, HookRegistry, BeforeToolCallEvent class ConstantToolArguments(HookProvider): """Use constant argument values for specific parameters of a tool.""" @@ -192,9 +182,9 @@ class ConstantToolArguments(HookProvider): self._tools_to_fix = fixed_tool_arguments def register_hooks(self, registry: HookRegistry, **kwargs: Any) -> None: - registry.add_callback(BeforeToolInvocationEvent, self._fix_tool_arguments) + registry.add_callback(BeforeToolCallEvent, self._fix_tool_arguments) - def _fix_tool_arguments(self, event: BeforeToolInvocationEvent): + def _fix_tool_arguments(self, event: BeforeToolCallEvent): # If the tool is in our list of parameters, then use those parameters if parameters_to_fix := self._tools_to_fix.get(event.tool_use["name"]): tool_input: dict[str, Any] = event.tool_use["input"] @@ -221,9 +211,9 @@ Modify or replace tools before execution: ```python class ToolInterceptor(HookProvider): def register_hooks(self, registry: HookRegistry) -> None: - registry.add_callback(BeforeToolInvocationEvent, self.intercept_tool) + registry.add_callback(BeforeToolCallEvent, self.intercept_tool) - def intercept_tool(self, event: BeforeToolInvocationEvent) -> None: + def intercept_tool(self, event: BeforeToolCallEvent) -> None: if event.tool_use.name == "sensitive_tool": # Replace with a safer alternative event.selected_tool = self.safe_alternative_tool @@ -237,9 +227,9 @@ Modify tool results after execution: ```python class ResultProcessor(HookProvider): def register_hooks(self, registry: HookRegistry) -> None: - registry.add_callback(AfterToolInvocationEvent, self.process_result) + registry.add_callback(AfterToolCallEvent, self.process_result) - def process_result(self, event: AfterToolInvocationEvent) -> None: + def process_result(self, event: AfterToolCallEvent) -> None: if event.tool_use.name == "calculator": # Add formatting to calculator results original_content = event.result["content"][0]["text"] @@ -271,7 +261,7 @@ class RequestLoggingHook(HookProvider): def register_hooks(self, registry: HookRegistry) -> None: registry.add_callback(BeforeInvocationEvent, self.log_request) registry.add_callback(AfterInvocationEvent, self.log_response) - registry.add_callback(BeforeToolInvocationEvent, self.log_tool_use) + registry.add_callback(BeforeToolCallEvent, self.log_tool_use) ... ``` @@ -283,9 +273,9 @@ When modifying event properties, log the changes for debugging and audit purpose ```python class ResultProcessor(HookProvider): def register_hooks(self, registry: HookRegistry) -> None: - registry.add_callback(AfterToolInvocationEvent, self.process_result) + registry.add_callback(AfterToolCallEvent, self.process_result) - def process_result(self, event: AfterToolInvocationEvent) -> None: + def process_result(self, event: AfterToolCallEvent) -> None: if event.tool_use.name == "calculator": original_content = event.result["content"][0]["text"] logger.info(f"Modifying calculator result: {original_content}") From 3829da97ee2f0661b87fbffea64dc11b0290c066 Mon Sep 17 00:00:00 2001 From: Murat Kaan Meral Date: Wed, 1 Oct 2025 13:35:03 +0200 Subject: [PATCH 4/4] docs(multi-agent): update hooks and reorganize sections --- docs/user-guide/concepts/agents/hooks.md | 45 +++++++++++---- docs/user-guide/concepts/multi-agent/graph.md | 30 +--------- .../multi-agent/multi-agent-patterns.md | 55 +++++++++++++++++++ docs/user-guide/concepts/multi-agent/swarm.md | 34 +----------- 4 files changed, 92 insertions(+), 72 deletions(-) diff --git a/docs/user-guide/concepts/agents/hooks.md b/docs/user-guide/concepts/agents/hooks.md index a983a4db..2521b0b2 100644 --- a/docs/user-guide/concepts/agents/hooks.md +++ b/docs/user-guide/concepts/agents/hooks.md @@ -103,6 +103,15 @@ The hooks system provides events for different stages of agent execution: | `BeforeInvocationEvent` | Triggered at the beginning of a new agent request (`__call__`, `stream_async`, or `structured_output`) | | `AfterInvocationEvent` | Triggered at the end of an agent request, regardless of success or failure. Uses reverse callback ordering | | `MessageAddedEvent` | Triggered when a message is added to the agent's conversation history | + +Additional *experimental events* are also available: + +!!! note "Experimental events are subject to change" + + These events are exposed experimentally in order to gather feedback and refine the public contract. Because they are experimental, they are subject to change between releases. + +| Experimental Event | Description | +|------------------------------|-------------| | `BeforeModelCallEvent` | Triggered before the model is invoked for inference | | `AfterModelCallEvent` | Triggered after model invocation completes. Uses reverse callback ordering | | `BeforeToolCallEvent` | Triggered before a tool is invoked. | @@ -125,37 +134,49 @@ Some events come in pairs, such as Before/After events. The After event callback Hook events that involve tool execution include access to `invocation_state`, which provides configuration and context data passed through the agent invocation. This is particularly useful for: -1. **Configuration and Credentials**: Access API keys, database configurations, or other settings without hardcoding them -2. **Request Context**: Access session IDs, user information, or request-specific data -3. **Multi-Agent Shared State**: In [Graph](../multi-agent/graph.md) and [Swarm](../multi-agent/swarm.md) patterns, access state shared across all agents +1. **Custom Objects**: Access database client objects, connection pools, or other Python objects +2. **Request Context**: Access session IDs, user information, settings, or request-specific data +3. **Multi-Agent Shared State**: In multi-agent patterns, access state shared across all agents - see [Shared State Across Multi-Agent Patterns](../multi-agent/multi-agent-patterns.md#shared-state-across-multi-agent-patterns) 4. **Custom Parameters**: Pass any additional data that hooks might need ```python -from strands.experimental.hooks import BeforeToolInvocationEvent +from strands.hooks import BeforeToolCallEvent +import logging -def log_with_context(event: BeforeToolInvocationEvent) -> None: +def log_with_context(event: BeforeToolCallEvent) -> None: """Log tool invocations with context from invocation state.""" # Access invocation state from the event user_id = event.invocation_state.get("user_id", "unknown") session_id = event.invocation_state.get("session_id") - environment = event.invocation_state.get("environment", "production") - # Log with context + # Access non-JSON serializable objects like database connections + db_connection = event.invocation_state.get("database_connection") + logger_instance = event.invocation_state.get("custom_logger") + + # Use custom logger if provided, otherwise use default + logger = logger_instance if logger_instance else logging.getLogger(__name__) + logger.info( - f"User {user_id} in session {session_id} ({environment}) " - f"invoking tool: {event.tool_use['name']}" + f"User {user_id} in session {session_id} " + f"invoking tool: {event.tool_use['name']} " + f"with DB connection: {db_connection is not None}" ) # Register the hook agent = Agent(tools=[my_tool]) -agent.hooks.add_callback(BeforeToolInvocationEvent, log_with_context) +agent.hooks.add_callback(BeforeToolCallEvent, log_with_context) + +# Execute with context including non-serializable objects +import sqlite3 +custom_logger = logging.getLogger("custom") +db_conn = sqlite3.connect(":memory:") -# Execute with context in invocation state result = agent( "Process the data", user_id="user123", session_id="sess456", - environment="staging" + database_connection=db_conn, # Non-JSON serializable object + custom_logger=custom_logger # Non-JSON serializable object ) ``` diff --git a/docs/user-guide/concepts/multi-agent/graph.md b/docs/user-guide/concepts/multi-agent/graph.md index 56f65d27..709dd1df 100644 --- a/docs/user-guide/concepts/multi-agent/graph.md +++ b/docs/user-guide/concepts/multi-agent/graph.md @@ -321,35 +321,9 @@ From [another_node_id]: ## Shared State -Graphs support passing shared state to all agents through the `invocation_state` parameter. This enables sharing configuration, credentials, and other data across agents without exposing it to the LLM. +Graphs support passing shared state to all agents through the `invocation_state` parameter. This enables sharing context and configuration across agents without exposing it to the LLM. -```python -# Execute graph with shared state -result = graph( - "Analyze customer data", - invocation_state={ - "api_key": "secret-key-123", - "database_config": {"host": "db.example.com", "port": 5432}, - "session_id": "user-session-456" - } -) -``` - -The `invocation_state` is automatically propagated to: - -- All agents in the graph via their `**kwargs` -- Tools via `ToolContext` when using `@tool(context=True)` - see [Python Tools](../tools/python-tools.md#accessing-invocation-state-in-tools) -- Tool-related hooks (BeforeToolInvocationEvent, AfterToolInvocationEvent) - see [Hooks](../agents/hooks.md#accessing-invocation-state-in-hooks) - -```python -# Tools access shared state through ToolContext -@tool(context=True) -def api_call(endpoint: str, tool_context: ToolContext) -> str: - api_key = tool_context.invocation_state.get("api_key") - # Use api_key for authenticated requests... -``` - -Use `invocation_state` for configuration and credentials that shouldn't appear in prompts, while using normal Graph input propagation for data the LLM should reason about. +For detailed information about shared state, including examples and best practices, see [Shared State Across Multi-Agent Patterns](./multi-agent-patterns.md#shared-state-across-multi-agent-patterns). ## Graphs as a Tool diff --git a/docs/user-guide/concepts/multi-agent/multi-agent-patterns.md b/docs/user-guide/concepts/multi-agent/multi-agent-patterns.md index 061fca93..f53b875c 100644 --- a/docs/user-guide/concepts/multi-agent/multi-agent-patterns.md +++ b/docs/user-guide/concepts/multi-agent/multi-agent-patterns.md @@ -72,6 +72,61 @@ Some Examples: - Automated Data Pipelines: A fixed set of tasks to extract, analyze, and report on data, where independent analysis steps can run in parallel. - Standard Business Processes: Onboarding a new employee by creating accounts, assigning training, and sending a welcome email, all triggered by a single agent action. +## Shared State Across Multi-Agent Patterns + +Both Graph and Swarm patterns support passing shared state to all agents through the `invocation_state` parameter. This enables sharing context and configuration across agents without exposing it to the LLM. + +### How Shared State Works + +The `invocation_state` is automatically propagated to: + +- All agents in the pattern via their `**kwargs` +- Tools via `ToolContext` when using `@tool(context=True)` - see [Python Tools](../tools/python-tools.md#accessing-invocation-state-in-tools) +- Tool-related hooks (BeforeToolCallEvent, AfterToolCallEvent) - see [Hooks](../agents/hooks.md#accessing-invocation-state-in-hooks) + +### Example Usage + +```python +# Same invocation_state works for both patterns +shared_state = { + "user_id": "user123", + "session_id": "sess456", + "debug_mode": True, + "database_connection": db_connection_object +} + +# Execute with Graph +result = graph( + "Analyze customer data", + invocation_state=shared_state +) + +# Execute with Swarm (same shared_state) +result = swarm( + "Analyze customer data", + invocation_state=shared_state +) +``` + +### Accessing Shared State in Tools + +```python +from strands import tool, ToolContext + +@tool(context=True) +def query_data(query: str, tool_context: ToolContext) -> str: + user_id = tool_context.invocation_state.get("user_id") + debug_mode = tool_context.invocation_state.get("debug_mode", False) + # Use context for personalized queries... +``` + +### Important Distinctions + +- **Shared State**: Configuration and objects passed via `invocation_state`, not visible in prompts +- **Pattern-Specific Data Flow**: Each pattern has its own mechanisms for passing data that the LLM should reason about including shared context for swarms and agent inputs for graphs + +Use `invocation_state` for context and configuration that shouldn't appear in prompts, while using each pattern's specific data flow mechanisms for data the LLM should reason about. + ## Conclusion This guide has explored the three primary multi-agent patterns in Strands: Graph, Swarm, and Workflow. Each pattern serves distinct use cases based on how execution paths are determined and controlled. When choosing between patterns, consider your problem's complexity, the need for deterministic vs. emergent behavior, and whether you require cycles, parallel execution, or specific error handling approaches. diff --git a/docs/user-guide/concepts/multi-agent/swarm.md b/docs/user-guide/concepts/multi-agent/swarm.md index 5ff59db6..b62c261b 100644 --- a/docs/user-guide/concepts/multi-agent/swarm.md +++ b/docs/user-guide/concepts/multi-agent/swarm.md @@ -168,39 +168,9 @@ You have access to swarm coordination tools if you need help from other agents. ## Shared State -Swarms support passing shared state to all agents through the `invocation_state` parameter. This enables sharing configuration and credentials across agents without exposing them to the LLM, keeping them separate from the shared context used for collaboration. +Swarms support passing shared state to all agents through the `invocation_state` parameter. This enables sharing context and configuration across agents without exposing them to the LLM, keeping them separate from the shared context used for collaboration. -```python -# Execute swarm with shared state -result = swarm( - "Research and analyze the data", - invocation_state={ - "api_key": "bearer-token-xyz", - "api_base_url": "https://api.example.com", - "cache_enabled": True - } -) -``` - -The `invocation_state` is automatically propagated to: - -- All agents in the swarm via their `**kwargs` -- Tools via `ToolContext` when using `@tool(context=True)` - see [Python Tools](../tools/python-tools.md#accessing-invocation-state-in-tools) -- Tool-related hooks (BeforeToolInvocationEvent, AfterToolInvocationEvent) - see [Hooks](../agents/hooks.md#accessing-invocation-state-in-hooks) - -```python -# Tools access shared state through ToolContext -@tool(context=True) -def fetch_data(source: str, tool_context: ToolContext) -> dict: - api_key = tool_context.invocation_state.get("api_key") - base_url = tool_context.invocation_state.get("api_base_url") - # Use configuration for API calls... -``` - -**Important distinction:** - -- **Shared Context**: Information agents share through handoffs, visible in prompts for collaboration -- **Shared State**: Hidden configuration passed via `invocation_state`, not visible in prompts +For detailed information about shared state, including examples and best practices, see [Shared State Across Multi-Agent Patterns](./multi-agent-patterns.md#shared-state-across-multi-agent-patterns). ## Asynchronous Execution