Skip to content

[FEATURE] Python: configurable per-batch concurrency for ConcurrentToolExecutor #4425

Description

@97Three

SDK Language

Python

Problem Statement

ConcurrentToolExecutor starts every tool use in a model-generated batch concurrently. The built-in alternative is SequentialToolExecutor, but there is no built-in way to retain parallel execution while bounding the number of active calls within a batch.

A model can request several independent retrieval or API calls in one response. Starting all of them together can create a burst of connections or downstream requests. Serial execution avoids that burst but also removes useful concurrency.

Proposed Solution

Add an optional, keyword-only max_concurrency: int | None = None to ConcurrentToolExecutor:

from strands import Agent
from strands.tools.executors import ConcurrentToolExecutor

agent = Agent(
    tools=[...],
    tool_executor=ConcurrentToolExecutor(max_concurrency=3),
)
  • Omitted or None: retain the current execution behavior.
  • Positive integer N: admit at most N foreground tool executions at a time within each batch. Admit waiting work as a running execution completes.
  • Preserve streamed events and the current final tool-result ordering by original tool-use order.
  • Keep the configuration on the executor; no new Agent parameter.

A batch-local asyncio.Semaphore would keep admission control separate from the existing event queue and result collection, without storing an event-loop-bound semaphore on a reusable executor.

This is not a global, per-tool, cross-Agent, or requests-per-second limit. It does not introduce a custom thread pool or limit the lifetime of background tasks. Cancellation must preserve existing contracts; it cannot forcibly stop a synchronous tool's underlying thread.

Use Case

An application with capacity for two concurrent downstream operations should be able to process a batch of six tool calls two at a time, without making the model generate smaller batches or replacing the built-in executor with a custom implementation.

Alternatives Solutions

  • SequentialToolExecutor: appropriate for serial execution, but cannot express an intermediate concurrency level.
  • A semaphore inside each application tool: useful for application-wide or service-specific limits, but requires coordination across tools to bound an entire batch.
  • A custom executor: already possible, but requires applications to maintain execution-policy code for this small, reusable capability.
  • Prompting the model to request fewer tools: not an enforced runtime concurrency limit.

Additional Context

Inspected main at 54ca0befa69a1e8e8d7f3083da62a8e9d341f050. Two offline characterization checks passed with single-process pytest (-n 0):

  1. Six calls to an async @tool increment an active counter and wait on a shared asyncio.Event. Before releasing that event, all six have entered the tool body and none has completed. The observed peak is six. After release, all six results are collected in tool-use order.
  2. ConcurrentToolExecutor(max_concurrency=2) raises TypeError because the constructor does not accept the option.

The first check uses an event barrier and a counter, not elapsed execution time; a timeout only guards against a hung test. Neither check calls a real model or service. These characterize current behavior; feature tests and implementation have not yet been completed.

Related work reviewed:

Searches of open/closed Issues and PRs for max_concurrency, ConcurrentToolExecutor, and tool concurrency did not identify an equivalent proposal or implementation as of this filing. Maintainer confirmation of scope is welcome before implementation.

Proposed acceptance tests:

  • Six tool calls with a limit of two reach a peak of exactly two and never exceed it.
  • Completing one call admits waiting work while another call remains active.
  • Default behavior, streamed events, and final tool-result ordering are preserved.
  • Handled tool errors release slots; executor-aborting errors and cancellation clean up active and waiting tasks under the existing contracts.
  • Consecutive batches have independent admission budgets.
  • An offline mock-model Agent test covers tool uses, bounded execution, tool-result messages, and the final Agent result.

Activity

  1. 97Three commented on Sep 20, 2026

    @97Three
    Author

    I've reproduced the current behavior with an offline Event/counter-based check and reviewed the related Issues and PRs. I'm planning to work on a focused implementation with deterministic feature and regression tests, and I'll link the Draft PR here once it's ready.

    Could a maintainer confirm that the proposed per-batch ConcurrentToolExecutor(max_concurrency=...) scope and API are welcome? I'll wait for that confirmation before beginning significant implementation, as requested in CONTRIBUTING.md, and keep the change separate from #4407's cancellation work.

  2. added
    enhancementNew feature or request
    pythonPull requests that update python code
    area-asyncRelated to asynchronous flows or multi-threading
    on Sep 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-asyncRelated to asynchronous flows or multi-threadingarea-toolTool behavior/apienhancementNew feature or requestpythonPull requests that update python codeready for contributionPull requests welcome

    Fields

    Language

    Python

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions