11"""Agent-as-tool adapter.
22
3- This module provides the AgentAsTool class that wraps an Agent (or any AgentBase) as a tool
3+ This module provides the _AgentAsTool class that wraps an Agent as a tool
44so it can be passed to another agent's tool list.
55"""
66
7+ from __future__ import annotations
8+
79import copy
810import logging
911import threading
10- from typing import Any
12+ from typing import TYPE_CHECKING , Any
1113
1214from typing_extensions import override
1315
1618from ..types .content import Messages
1719from ..types .interrupt import InterruptResponseContent
1820from ..types .tools import AgentTool , ToolGenerator , ToolSpec , ToolUse
19- from .base import AgentBase
21+
22+ if TYPE_CHECKING :
23+ from .agent import Agent
2024
2125logger = logging .getLogger (__name__ )
2226
2327
24- class AgentAsTool (AgentTool ):
28+ class _AgentAsTool (AgentTool ):
2529 """Adapter that exposes an Agent as a tool for use by other agents.
2630
2731 The tool accepts a single ``input`` string parameter, invokes the wrapped
@@ -30,18 +34,14 @@ class AgentAsTool(AgentTool):
3034 Example:
3135 ```python
3236 from strands import Agent
33- from strands.agent import AgentAsTool
3437
3538 researcher = Agent(name="researcher", description="Finds information")
3639
37- # Use directly
38- tool = AgentAsTool(researcher, name="researcher", description="Finds information")
39-
40- # Or via convenience method
40+ # Use via convenience method (default: fresh conversation each call)
4141 tool = researcher.as_tool()
4242
43- # Start each invocation with a fresh conversation
44- tool = researcher.as_tool(preserve_context=False )
43+ # Preserve context across invocations
44+ tool = researcher.as_tool(preserve_context=True )
4545
4646 writer = Agent(name="writer", tools=[tool])
4747 writer("Write about AI agents")
@@ -50,36 +50,35 @@ class AgentAsTool(AgentTool):
5050
5151 def __init__ (
5252 self ,
53- agent : AgentBase ,
53+ agent : Agent ,
5454 * ,
5555 name : str ,
56- description : str ,
56+ description : str | None = None ,
5757 preserve_context : bool = False ,
5858 ) -> None :
5959 r"""Initialize the agent-as-tool adapter.
6060
6161 Args:
6262 agent: The agent to wrap as a tool.
6363 name: Tool name. Must match the pattern ``[a-zA-Z0-9_\\-]{1,64}``.
64- description: Tool description.
64+ description: Tool description. Defaults to the agent's description, or a
65+ generic description if the agent has no description set.
6566 preserve_context: Whether to preserve the agent's conversation history across
6667 invocations. When False, the agent's messages and state are reset to the
6768 values they had at construction time before each call, ensuring every
6869 invocation starts from the same baseline regardless of any external
69- interactions with the agent. Defaults to False. Only effective when the
70- wrapped agent exposes a mutable ``messages`` list and/or an ``AgentState``
71- (e.g. ``strands.agent.Agent``).
70+ interactions with the agent. Defaults to False.
7271 """
7372 super ().__init__ ()
7473 self ._agent = agent
7574 self ._tool_name = name
76- self ._description = description
75+ self ._description = (
76+ description or agent .description or f"Use the { name } agent as a tool by providing a natural language input"
77+ )
7778 self ._preserve_context = preserve_context
7879
7980 # When preserve_context=False, we snapshot the agent's initial state so we can
8081 # restore it before each invocation. This mirrors GraphNode.reset_executor_state().
81- # We require an Agent instance for this since AgentBase doesn't guarantee
82- # messages/state attributes.
8382 self ._initial_messages : Messages = []
8483 self ._initial_state : AgentState = AgentState ()
8584 # Serialize access so _reset_agent_state + stream_async are atomic.
@@ -88,15 +87,17 @@ def __init__(
8887 self ._lock = threading .Lock ()
8988
9089 if not preserve_context :
91- from .agent import Agent
92-
93- if not isinstance (agent , Agent ):
94- raise TypeError (f"preserve_context=False requires an Agent instance, got { type (agent ).__name__ } " )
90+ if getattr (agent , "_session_manager" , None ) is not None :
91+ raise ValueError (
92+ "preserve_context=False cannot be used with an agent that has a session manager. "
93+ "The session manager persists conversation history externally, which conflicts with "
94+ "resetting the agent's state between invocations."
95+ )
9596 self ._initial_messages = copy .deepcopy (agent .messages )
9697 self ._initial_state = AgentState (agent .state .get ())
9798
9899 @property
99- def agent (self ) -> AgentBase :
100+ def agent (self ) -> Agent :
100101 """The wrapped agent instance."""
101102 return self ._agent
102103
@@ -259,12 +260,6 @@ def _reset_agent_state(self, tool_use_id: str) -> None:
259260 Args:
260261 tool_use_id: Tool use ID for logging context.
261262 """
262- from .agent import Agent
263-
264- # isinstance narrows the type for mypy; __init__ guarantees this when preserve_context=False
265- if not isinstance (self ._agent , Agent ):
266- return
267-
268263 logger .debug (
269264 "tool_name=<%s>, tool_use_id=<%s> | resetting agent to initial state" ,
270265 self ._tool_name ,
@@ -275,8 +270,7 @@ def _reset_agent_state(self, tool_use_id: str) -> None:
275270
276271 def _is_sub_agent_interrupted (self ) -> bool :
277272 """Check whether the wrapped agent is in an activated interrupt state."""
278- interrupt_state = getattr (self ._agent , "_interrupt_state" , None )
279- return interrupt_state is not None and interrupt_state .activated
273+ return self ._agent ._interrupt_state .activated
280274
281275 def _build_interrupt_responses (self ) -> list [InterruptResponseContent ]:
282276 """Build interrupt response payloads from the sub-agent's interrupt state.
@@ -288,13 +282,9 @@ def _build_interrupt_responses(self) -> list[InterruptResponseContent]:
288282 Returns:
289283 List of interrupt response content blocks for resuming the sub-agent.
290284 """
291- interrupt_state = getattr (self ._agent , "_interrupt_state" , None )
292- if interrupt_state is None :
293- return []
294-
295285 return [
296286 {"interruptResponse" : {"interruptId" : interrupt .id , "response" : interrupt .response }}
297- for interrupt in interrupt_state .interrupts .values ()
287+ for interrupt in self . _agent . _interrupt_state .interrupts .values ()
298288 if interrupt .response is not None
299289 ]
300290
0 commit comments