Skip to content

[BUG] Configurable Threads #769

@pgrayy

Description

@pgrayy

Problem Statement

Currently, Strands uses threads in 6 locations:

  • Agent Invoke (src): A thread is used to isolate the async event loop for synchronous execution.
  • Direct Tool Invocation (src): A thread are used to isolate the async event loop for synchronous execution.
  • Bedrock Stream (src): A thread is used to call bedrock converse_stream in a non-blocking manner.
  • Python Tool Invocation (src): A thread is used to call synchronous tool functions in a non-blocking manner.
  • Decorated Tool Invocation (src): A thread is used to call synchronous tool functions in a non-blocking manner.
  • MCP (src)

Use of these threads leads to the following issues/inconveniences:

  • A single keyboard interrupt from the main thread does not close child threads. Consequently, users must send a follow up signal (i.e., user must press ctrl-c multiple times).
  • Thread local variables in the main thread are not passed to the child threads.

Proposed Solution

Allow users to pass in their own thread pools or, in the case of bedrock streaming and python tool invocation, turn off threading. The argument type could be defined as follows:

from concurrent.futures.ThreadPoolExecutor
from typing import Literal

ThreadPool = ThreadPoolExecutor | Literal["asyncio"] | None = "asyncio"
  • ThreadPoolExecutor: Strands will use the users custom thread pool.
  • "asyncio": Strands will use the asyncio thread pool (accessed with asyncio.to_thread).
  • None: Strands will not spawn a thread and instead will use the calling thread.

There are 2 ways we could have users pass this in:

  • Separately: Users pass thread pools separately to each component (e.g., BedrockModelProvider(thread_pool), ConcurrentToolExecutor(thread_pool), Agent(thread_pool), etc.
  • Hybrid: We have users pass in thread pool to Agent init and use it everywhere needed with the option to override.

Use Case

  • Keyboard Interrupt: Exit cleanly when sending an interrupt signal. This is useful for UIs such as agent-builder.
  • Thread Local Variables: Make accessible thread local context in the execution of the agentic loop.

Alternatives Solutions

  • Daemon Threads: We tried using deamon threads to address the keyboard interrupt issue (PR). Daemon threads unfortunately are tricky to get setup with thread pools.

Additional Context

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions