Skip to content

fix(agent): build a call-scoped pool for concurrent execution (#1793) - #1909

Merged
kyegomez merged 1 commit into
kyegomez:masterfrom
Steve-Dusty:fix/agent-concurrent-executor-1793
Aug 20, 2026
Merged

fix(agent): build a call-scoped pool for concurrent execution (#1793)#1909
kyegomez merged 1 commit into
kyegomez:masterfrom
Steve-Dusty:fix/agent-concurrent-executor-1793

Conversation

@Steve-Dusty

Copy link
Copy Markdown
Contributor

Closes #1793.

run_concurrent_tasks and talk_to_multiple_agents both submit to self.executor, an attribute __init__ never assigns. Neither method has ever worked. They now open a ContextThreadPoolExecutor scoped to the call, matching how the rest of the codebase runs concurrent work.

Problem

swarms/structs/agent.py:2151 and :3407 both call self.executor.submit(...). The only assignment to self.executor anywhere in the class is at :2488, inside _reinitialize_after_load — a method that runs only on state load, and which assigns the executor inside a with block, so the object it stores is already shut down by the time the block exits.

The two methods fail differently, and the first one is the reason this went unnoticed:

Method Behaviour on master
run_concurrent_tasks catches the AttributeError, logs it, falls out of the function → returns None
talk_to_multiple_agents no handler → raises AttributeError to the caller

On master @ a5764139 (v14.0.0):

has attribute 'executor'   : False
run_concurrent_tasks ->    : None (swallowed, no results)
talk_to_multiple_agents    : AttributeError: 'Agent' object has no attribute 'executor'

run_concurrent_tasks returning None is what disguised the bug. The existing test TestAgentFeatures::test_agent_concurrent_execution (tests/structs/test_agent.py:392) asserts len(concurrent_responses) == 3 and fails with TypeError: object of type 'NoneType' has no len() — which reads like a missing-credentials problem, not a method that cannot work.

Note on the issue's stated reproducer

#1793 reports the symptom as run(imgs=[...]) raising AttributeError. That is not reproducible on current master: Agent.run accepts imgs, but its body never references executor ("executor" in inspect.getsource(Agent.run)False), and there is no run_multiple_images/run_many_images method. That path appears to have been refactored since the issue was filed on 2026-08-01. The underlying defect the issue identifies — self.executor never assigned — is real and is what this PR fixes; only the entry point named in the reproducer has moved.

Fix

  • Both methods build a call-scoped pool. with ContextThreadPoolExecutor(max_workers=os.cpu_count()), the pattern already used by heavy_swarm.py:689, majority_voting.py:286 and multi_agent_router.py:502.
  • The attribute is not reinstated. An Agent-level pool would hold idle threads for the process lifetime of every agent a swarm constructs — a HierarchicalSwarm with 100 workers would carry 100 pools. Call-scoped costs nothing when the methods are unused, which is the common case.
  • The dead assignment in _reinitialize_after_load is removed, not repaired. It stored an already-closed executor; leaving it would be a trap for the next reader.
  • run_concurrent_tasks re-raises. The bare except that logged and returned None is the thing that hid this for so long.

Behavior-change note

run_concurrent_tasks previously returned None when anything raised; it now propagates the exception. Any caller relying on the None return to mean "batch failed" must catch instead. Given the method could not complete successfully at all before this PR, no working caller can depend on that path.

talk_to_multiple_agents keeps its per-agent isolation — one failed conversation still contributes None rather than failing the batch.

Verification

  • New coverage: TestConcurrentExecutionPool added to the existing tests/structs/test_agent.py (no new file, per prior review guidance). Six network-free tests built on Agent.__new__ — no model client, no provider call: result-per-task ordering, failure propagation, per-agent result ordering, isolation of one failing agent, absence of the executor attribute, and no thread-pool leak across repeated calls.
  • They are real regression tests: run against unfixed master, 4 of the 6 fail. The other two (no_executor_attribute_is_required, pool_is_shut_down_after_each_call) pass vacuously there, since master never creates a pool to leak.
  • Full file: tests/structs/test_agent.py goes from 57 passed / 20 failed / 8 errors on clean master to 64 passed / 19 failed / 8 errors. The remaining failures and errors are the pre-existing live-API tests, identical on both sides; a set difference of the two failure lists shows nothing newly broken.
  • One pre-existing test repaired: TestAgentFeatures::test_agent_concurrent_execution now passes without credentials — each run returns its error string, so the batch is a list of 3 and the assertion holds, which is what the test was written to check.
  • Lint with the CI-pinned versions (black==24.2.0, ruff==0.2.1, line-length 70): black --check clean on both files, ruff check . clean repo-wide. Ruff findings in agent.py drop from 247 to 245.

Not verified here: no live provider call was made — no API keys are present in this environment. Every test stubs Agent.run / Agent.talk_to at the method boundary, so the executor plumbing is exercised for real while the LLM call is not.

…ez#1793)

run_concurrent_tasks and talk_to_multiple_agents submitted to self.executor,
which __init__ never assigns. run_concurrent_tasks caught the resulting
AttributeError, logged it and fell out of the function returning None;
talk_to_multiple_agents had no handler and raised it to the caller. Neither
method has worked.

Both now open a ContextThreadPoolExecutor for the duration of the call, which
is how the rest of the codebase runs concurrent work (heavy_swarm,
majority_voting, multi_agent_router). A pool held on the Agent would keep idle
threads alive for the process lifetime of every agent a swarm builds, so the
attribute is not reinstated.

_reinitialize_after_load assigned self.executor inside a `with` block, so the
executor it stored had already been shut down on exit; that assignment is
removed rather than repaired, since nothing reads the attribute now.

run_concurrent_tasks also re-raises instead of returning None. The bare except
returning None is what hid this bug: the existing coverage at
TestAgentFeatures::test_agent_concurrent_execution asserts len(results) == 3
and died with "object of type 'NoneType' has no len()", which read as a
missing-credentials failure rather than a broken method. That test passes now.

Closes kyegomez#1793

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Steve-Dusty
Steve-Dusty force-pushed the fix/agent-concurrent-executor-1793 branch from 8814897 to 8cd5ebb Compare August 19, 2026 04:04
@Steve-Dusty

Copy link
Copy Markdown
Contributor Author

Rebased onto 375d8081. The only conflict was in tests/structs/test_agent.py, where #1794 landed TestToolExecutionRetry at the end of the file — both classes are kept, they cover different methods.

Re-verified on the new base: TestConcurrentExecutionPool + TestToolExecutionRetry 13 passed together; full file 71 passed against the same pre-existing live-API failures as master. black --check clean and ruff check . clean with the CI-pinned versions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug][Agent][self.executor is never assigned in __init__: run(imgs=[...]) raises AttributeError]

2 participants