Add AsyncColonyToolkit for native async via AsyncColonyClient#14
Merged
jackparnell merged 1 commit intomainfrom Apr 9, 2026
Merged
Add AsyncColonyToolkit for native async via AsyncColonyClient#14jackparnell merged 1 commit intomainfrom
jackparnell merged 1 commit intomainfrom
Conversation
The previous async path wrapped every _arun() in asyncio.to_thread on
top of the sync ColonyClient. That worked but couldn't actually run
tool calls in parallel — they were just serialised through a thread
pool. The colony-sdk 1.5.0 release brought a real httpx-backed
AsyncColonyClient, and this PR wires crewai-colony up to it.
API:
from crewai_colony import AsyncColonyToolkit
async with AsyncColonyToolkit(api_key="col_...") as toolkit:
tools = toolkit.get_tools()
# crew.kickoff_async() now fans out tool calls in parallel
# on the event loop, not on a thread pool.
Implementation:
- New AsyncColonyToolkit class in toolkit.py — same surface as
ColonyToolkit, but constructs an AsyncColonyClient and supports
`async with` / `await aclose()` for connection-pool cleanup.
- Lazy-imports AsyncColonyClient so the sync toolkit keeps working
without httpx installed. AsyncColonyToolkit raises ImportError with
a helpful message if [async] extra isn't installed.
- _async_safe_run now dispatches on asyncio.iscoroutinefunction(func):
async client → native await; sync client → asyncio.to_thread
fallback. Same exception/format contract either way, no caller
changes needed across the 31 tool classes.
- Two special-cased tools (ColonyMarkNotificationsRead, ColonyRegister)
also dispatch on iscoroutinefunction so they share the benefit.
- New crewai-colony[async] extra pulls in colony-sdk[async]>=1.5.0
(which is what brings httpx). Default install stays zero-extra.
Tests: new tests/test_async_native.py adds 21 tests using
httpx.MockTransport to exercise the full async stack:
- Dispatcher behaviour (native await vs to_thread fallback)
- AsyncColonyToolkit construction, retry forwarding, get_tools,
read_only / include / exclude / callbacks, async context manager
- End-to-end ColonySearchPosts / ColonyGetPost / ColonyGetMe /
ColonyMarkNotificationsRead via mocked transport
- Concurrent fan-out via asyncio.gather
Existing test_coverage.py TestRegisterAsync rewritten to test all
three branches (native async path, async error path, sync fallback
when AsyncColonyClient import fails).
191 tests passing, 100% coverage held across all 6 source files.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Headline async fix — drops the
asyncio.to_threadshim and wirescrewai-colonyup to the real httpx-backedAsyncColonyClientthat landed incolony-sdk1.5.0.Before: every
_arun()wasasyncio.to_thread(sync_method, ...), so a crew that fanned out 10 tool calls underasyncio.gatherran them serialised through a thread pool.After: a crew that fans out 10 tool calls under
asyncio.gatheractually runs them in parallel on the event loop, sharing onehttpx.AsyncClientconnection pool.How it works
AsyncColonyToolkitsibling class — same surface asColonyToolkit, constructsAsyncColonyClient, supportsasync with/await aclose()for connection-pool cleanup.AsyncColonyClientso the sync toolkit keeps working withouthttpx. Raises a helpfulImportErrorif you try to constructAsyncColonyToolkitwithout the[async]extra._async_safe_runnow dispatches onasyncio.iscoroutinefunction(func). Async client → nativeawait. Sync client →asyncio.to_threadfallback (so the existingColonyToolkitstill works from async crews). Same exception/format contract either way — no changes needed across the 31 tool classes.ColonyMarkNotificationsRead,ColonyRegister) that don't go through_async_safe_runalso dispatch oniscoroutinefunctionso they share the native-async benefit.crewai-colony[async]optional extra pulls incolony-sdk[async]>=1.5.0(which is what bringshttpx). The default install stays zero-extra.Test plan
pytest— 191 passed (170 → 191, +21 new native-async tests)pytest --cov— 100% across all 6 source files (__init__,callbacks,cli,crews,toolkit,tools)ruff checkcleanruff format --checkcleanmypy src/cleanNew
tests/test_async_native.pyexercises the full async stack viahttpx.MockTransport(no network):awaitvsto_threadfallback, error formatting on both pathsAsyncColonyToolkitconstruction, retry forwarding,get_tools,read_only/include/exclude/callbacks, async context managerColonySearchPosts/ColonyGetPost/ColonyGetMe/ColonyMarkNotificationsReadvia mocked transportasyncio.gatherover 10 callstests/test_coverage.py::TestRegisterAsyncrewritten to cover all three branches (native async path, async error path, sync fallback whenAsyncColonyClientimport fails).This is PR 2 of 4 heading toward v0.6.0. Up next:
verify_webhookre-export + tool wrapper.🤖 Generated with Claude Code