Skip to content

Add AsyncColonyToolkit for native async via AsyncColonyClient#14

Merged
jackparnell merged 1 commit intomainfrom
feature/native-async
Apr 9, 2026
Merged

Add AsyncColonyToolkit for native async via AsyncColonyClient#14
jackparnell merged 1 commit intomainfrom
feature/native-async

Conversation

@ColonistOne
Copy link
Copy Markdown
Collaborator

Summary

Headline async fix — drops the asyncio.to_thread shim and wires crewai-colony up to the real httpx-backed AsyncColonyClient that landed in colony-sdk 1.5.0.

Before: every _arun() was asyncio.to_thread(sync_method, ...), so a crew that fanned out 10 tool calls under asyncio.gather ran them serialised through a thread pool.

After: a crew that fans out 10 tool calls under asyncio.gather actually runs them in parallel on the event loop, sharing one httpx.AsyncClient connection pool.

from crewai_colony import AsyncColonyToolkit

async with AsyncColonyToolkit(api_key="col_...") as toolkit:
    tools = toolkit.get_tools()
    agent = Agent(role="Colony Scout", tools=tools, ...)
    await crew.kickoff_async()

How it works

  • New AsyncColonyToolkit sibling class — same surface as ColonyToolkit, constructs AsyncColonyClient, supports async with / await aclose() for connection-pool cleanup.
  • Lazy-imports AsyncColonyClient so the sync toolkit keeps working without httpx. Raises a helpful ImportError if you try to construct AsyncColonyToolkit without the [async] extra.
  • _async_safe_run now dispatches on asyncio.iscoroutinefunction(func). Async client → native await. Sync client → asyncio.to_thread fallback (so the existing ColonyToolkit still works from async crews). Same exception/format contract either way — no changes needed across the 31 tool classes.
  • Two special-cased tools (ColonyMarkNotificationsRead, ColonyRegister) that don't go through _async_safe_run also dispatch on iscoroutinefunction so they share the native-async benefit.
  • New crewai-colony[async] optional extra pulls in colony-sdk[async]>=1.5.0 (which is what brings httpx). The default install stays zero-extra.

Test plan

  • pytest191 passed (170 → 191, +21 new native-async tests)
  • pytest --cov100% across all 6 source files (__init__, callbacks, cli, crews, toolkit, tools)
  • ruff check clean
  • ruff format --check clean
  • mypy src/ clean

New tests/test_async_native.py exercises the full async stack via httpx.MockTransport (no network):

  • Dispatcher behaviour: native await vs to_thread fallback, error formatting on both paths
  • 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 over 10 calls

tests/test_coverage.py::TestRegisterAsync rewritten to cover all three branches (native async path, async error path, sync fallback when AsyncColonyClient import fails).

This is PR 2 of 4 heading toward v0.6.0. Up next: verify_webhook re-export + tool wrapper.

🤖 Generated with Claude Code

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
Copy link
Copy Markdown

codecov bot commented Apr 9, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@jackparnell jackparnell merged commit f727056 into main Apr 9, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants