Skip to content

[BUG] Hot-reloading fails for first tool when ./tools directory is empty at Agent initialization #264

Open
@cagataycali

Description

@cagataycali
hot-reloading-issue.mp4

Checks

  • I have updated to the lastest minor and patch version of Strands
  • I have checked the documentation and this is not expected behavior
  • I have searched ./issues and there are no duplicates of my issue

Strands Version

0.1.18

Python Version

3.13.3

Operating System

macOS (Darwin with Clang 17.0.0)

Installation Method

git clone

Steps to Reproduce

Hot-reloading of tools from the ./tools directory fails when the directory is empty during Agent initialization. The first tool added to an empty ./tools directory is not automatically loaded, breaking the expected hot-reload functionality.

  1. Setup the environment:

    git clone [email protected]:strands-agents/sdk-python.git /tmp/sdk-python && cd /tmp/sdk-python
    python3 -m venv .venv && source .venv/bin/activate && pip3 install -e ".[dev]"
  2. Ensure ./tools directory exists but is empty:

    mkdir -p tools
    # Ensure tools directory is empty
    rm -f tools/*.py
  3. Create a simple agent in Python REPL:

    from strands import Agent
    agent = Agent()
    print(agent.tool_config)  # Shows: {'tools': [], 'toolChoice': {'auto': {}}}
  4. Add a tool to ./tools directory while REPL is running:

    # Create tools/basic_calculator.py with @tool decorator
    # (see full tool code in Additional Context section)
  5. Check if tool is loaded:

    print(agent.tool_config)  # Still shows: {'tools': [], 'toolChoice': {'auto': {}}}
    print(agent.tool_names)   # Shows: []

Expected Behavior

When a tool is added to the ./tools directory, it should be automatically hot-reloaded and available in:

  • agent.tool_config - should include the new tool specification
  • agent.tool_names - should include the tool name in the list
  • agent.tool.tool_name() - should be callable

This should work regardless of whether the ./tools directory was empty when the Agent was initialized.

Actual Behavior

  • First tool addition: Tool is NOT automatically loaded when added to an initially empty ./tools directory
  • Subsequent tool additions: Once at least one tool exists, additional tools ARE automatically hot-reloaded
  • Workaround: Restarting the Python process loads the tool correctly

Additional Context

Code Analysis

Based on examination of the codebase, the issue appears to be in the ToolWatcher initialization logic:

  1. In Agent.__init__(): The ToolWatcher is only initialized if load_tools_from_directory=True
  2. In ToolRegistry.initialize_tools(): Tool discovery happens, but if no tools exist, the watcher may not be properly set up
  3. In ToolWatcher.start(): The file system watcher is configured, but may not monitor empty directories effectively

Test Case Evidence

# Case 1: Empty tools directory - FAILS
agent = Agent()
# Add tool to empty ./tools/
# Result: tool_config remains empty

# Case 2: Pre-existing tool - WORKS  
agent = Agent()  # with existing tool in ./tools/
# Add another tool
# Result: new tool appears in tool_config

# Case 3: Restart after adding to empty directory - WORKS
# Exit Python, add tool, restart Python
agent = Agent()
# Result: tool_config includes the tool

Proposed Solution Areas

The issue likely resides in one or more of these areas:

  1. ToolWatcher initialization: May need to properly handle empty directories
  2. File system monitoring: The watchdog observer might not be monitoring empty directories
  3. Tool discovery timing: Initial discovery vs. runtime discovery synchronization

Additional Context

Sample tool used for testing:

# tools/basic_calculator.py
from strands import tool

@tool
def basic_calculator(expression: str) -> dict:
    """
    A very simple calculator for basic arithmetic only.
    
    Supports: +, -, *, /, and parentheses
    
    Args:
        expression: Simple math expression (e.g., "2 + 3", "10 / 2", "(5 + 3) * 2")
        
    Returns:
        Dictionary with the calculation result
    """
    try:
        # Only allow basic math characters and numbers
        allowed = set('0123456789+-*/().')
        if not all(c in allowed or c.isspace() for c in expression):
            raise ValueError("Only basic math operations allowed: +, -, *, /, ()")
        
        result = eval(expression)
        
        return {
            "status": "success",
            "content": [{"text": f"{expression} = {result}"}]
        }
        
    except ZeroDivisionError:
        return {
            "status": "error",
            "content": [{"text": "Cannot divide by zero"}]
        }
    except:
        return {
            "status": "error",
            "content": [{"text": "Invalid math expression"}]
        }

Impact

This bug affects the developer experience for:

  • New users setting up their first Strands agent
  • Development workflows where tools are added incrementally
  • Any scenario where the ./tools directory starts empty

Related Files

  • src/strands/agent/agent.py - Agent initialization
  • src/strands/tools/registry.py - Tool discovery and registration
  • src/strands/tools/watcher.py - File system monitoring for hot-reload

Verification

This issue can be consistently reproduced with the steps above. The hot-reload functionality works correctly in all cases except when the initial ./tools directory is empty during Agent initialization.

Possible Solution

No response

Related Issues

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions