Skip to content

fix: support async before/after_kickoff_callbacks in akickoff#6482

Open
magiccao wants to merge 1 commit into
crewAIInc:mainfrom
magiccao:fix/akickoff-async-callbacks
Open

fix: support async before/after_kickoff_callbacks in akickoff#6482
magiccao wants to merge 1 commit into
crewAIInc:mainfrom
magiccao:fix/akickoff-async-callbacks

Conversation

@magiccao

@magiccao magiccao commented Jul 8, 2026

Copy link
Copy Markdown

Summary

Fixes #6481

Crew.akickoff() is a native async execution path, but before_kickoff_callbacks and after_kickoff_callbacks were invoked synchronously, silently discarding async callables and blocking the event loop on IO-bound sync callbacks.

This PR brings akickoff into alignment with the existing async callback handling for task_callback and step_callback.

Changes

crews/utils.py

Refactors prepare_kickoff by extracting three private helpers:

  • _begin_prepare_kickoff — emission counter reset + resuming flag
  • _normalize_inputs — input type validation and dict conversion
  • _finish_prepare_kickoff — event emission, file handling, input interpolation, agent setup, planning

Adds aprepare_kickoff (async counterpart): identical to prepare_kickoff except the before_kickoff_callbacks loop uses inspect.isawaitable() + await to support coroutine callbacks.

crew.py

  • akickoff: replaces prepare_kickoff(...) with await aprepare_kickoff(...)
  • akickoff: adds inspect.isawaitable check in the after_kickoff_callbacks loop

Behavior

callback before after
sync callable unchanged unchanged
async callable in akickoff now awaited now awaited
async callable in kickoff_async still not awaited ⚠️ still not awaited ⚠️

Note on kickoff_async: this PR only fixes async callback support in akickoff. kickoff_async runs kickoff() in a worker thread via asyncio.to_thread, so sync callbacks won't block the event loop — but async before/after_kickoff_callbacks are still not awaited there. Fixing kickoff_async would require a separate effort.

Test plan

  • Existing test_async_crew.py tests pass (sync callbacks unaffected)
  • test_akickoff_calls_async_before_callbacks — async before callback is awaited; modified inputs reach task interpolation
  • test_akickoff_calls_async_after_callbacks — async after callback is awaited; result remains CrewOutput
  • test_akickoff_mixed_sync_async_callbacks — mixed pipeline (sync_before → async_before → async_after → sync_after) executes in correct order and preserves the transform chain

before_kickoff_callbacks and after_kickoff_callbacks were invoked
synchronously in akickoff, silently dropping async callables and
blocking the event loop on IO-bound sync callbacks.

- Extract _begin_prepare_kickoff, _normalize_inputs, and
  _finish_prepare_kickoff helpers from prepare_kickoff to eliminate
  code duplication between the sync and async paths.
- Add aprepare_kickoff (async counterpart of prepare_kickoff) that
  awaits coroutine before-callbacks via inspect.isawaitable, consistent
  with the existing task_callback pattern in task.py.
- Use aprepare_kickoff in akickoff instead of prepare_kickoff.
- Add inspect.isawaitable check for after_kickoff_callbacks in akickoff.
- Add tests covering async before, async after, and mixed sync+async
  callback pipelines.

kickoff_async is unaffected: it wraps the entire sync kickoff in
asyncio.to_thread, so before/after callbacks already run off the
event loop.
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds async-aware handling of before_kickoff_callbacks and after_kickoff_callbacks in Crew.akickoff(). Introduces aprepare_kickoff alongside refactored shared helpers (_begin_prepare_kickoff, _normalize_inputs, _finish_prepare_kickoff) used by both sync and async kickoff preparation, plus new tests covering async and mixed sync/async callback scenarios.

Changes

Async kickoff callback support

Layer / File(s) Summary
Shared kickoff preparation helpers and async variant
lib/crewai/src/crewai/crews/utils.py
Refactors prepare_kickoff into _begin_prepare_kickoff, _normalize_inputs, and _finish_prepare_kickoff helpers; adds new aprepare_kickoff function that awaits before_kickoff_callbacks results when awaitable before forwarding to shared finish logic.
akickoff() awaits async input prep and callback results
lib/crewai/src/crewai/crew.py
Imports inspect and aprepare_kickoff; replaces prepare_kickoff call with awaited aprepare_kickoff in akickoff(); awaits after_kickoff_callbacks results when they are awaitable.
Async callback tests for akickoff
lib/crewai/tests/crew/test_async_crew.py
Adds asyncio import and three new tests verifying async before-callbacks inject inputs, async after-callbacks are awaited and preserve CrewOutput, and mixed sync/async callbacks execute in correct order.

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant Crew
  participant aprepare_kickoff
  participant BeforeCallback
  participant AfterCallback

  Caller->>Crew: akickoff(inputs)
  Crew->>aprepare_kickoff: await aprepare_kickoff(crew, inputs)
  aprepare_kickoff->>BeforeCallback: invoke callback
  BeforeCallback-->>aprepare_kickoff: value or coroutine
  alt result is awaitable
    aprepare_kickoff->>aprepare_kickoff: await result
  end
  aprepare_kickoff-->>Crew: normalized inputs
  Crew->>Crew: execute tasks, build result
  Crew->>AfterCallback: invoke callback(result)
  AfterCallback-->>Crew: value or coroutine
  alt result is awaitable
    Crew->>Crew: await result
  end
  Crew-->>Caller: CrewOutput
Loading

Suggested reviewers: akaKuruma, vinibrsl

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR satisfies #6481 by awaiting async before/after kickoff callbacks in akickoff and keeping kickoff_async unchanged.
Out of Scope Changes check ✅ Passed The refactor and added tests are directly tied to the async callback fix and do not appear out of scope.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed The title clearly summarizes the main change: async before/after kickoff callbacks in akickoff.
Description check ✅ Passed The description matches the code changes and explains the async callback support and refactor.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@ErenAta16 ErenAta16 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same fix as #6500 and #6494 (also open), and like #6500 this one refactors prepare_kickoff into shared helpers (_begin_prepare_kickoff/_normalize_inputs/_finish_prepare_kickoff) rather than duplicating the whole function body for the new async path, so prepare_kickoff and aprepare_kickoff stay in sync automatically as the shared logic evolves — same quality bar as #6500, just a different helper decomposition. Left the fuller three-way comparison on #6500's thread.

Also flagging the same thing I noted there: the description mentions IO-bound sync callbacks blocking the event loop as motivation, but the actual fix (here and in the other two) only adds the inspect.isawaitable check for async callables — a slow sync callback still runs inline. Worth confirming that is intentionally out of scope for this PR rather than an oversight, since the title/description read as covering both.

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.

[BUG] before/after_kickoff_callbacks do not support async callables in akickoff

2 participants