fix: support async before/after_kickoff_callbacks in akickoff#6482
fix: support async before/after_kickoff_callbacks in akickoff#6482magiccao wants to merge 1 commit into
Conversation
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.
📝 WalkthroughWalkthroughAdds async-aware handling of ChangesAsync kickoff callback support
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
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
ErenAta16
left a comment
There was a problem hiding this comment.
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.
Summary
Fixes #6481
Crew.akickoff()is a native async execution path, butbefore_kickoff_callbacksandafter_kickoff_callbackswere invoked synchronously, silently discarding async callables and blocking the event loop on IO-bound sync callbacks.This PR brings
akickoffinto alignment with the existing async callback handling fortask_callbackandstep_callback.Changes
crews/utils.pyRefactors
prepare_kickoffby 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, planningAdds
aprepare_kickoff(async counterpart): identical toprepare_kickoffexcept thebefore_kickoff_callbacksloop usesinspect.isawaitable()+awaitto support coroutine callbacks.crew.pyakickoff: replacesprepare_kickoff(...)withawait aprepare_kickoff(...)akickoff: addsinspect.isawaitablecheck in theafter_kickoff_callbacksloopBehavior
akickoffkickoff_asyncNote on
kickoff_async: this PR only fixes async callback support inakickoff.kickoff_asyncrunskickoff()in a worker thread viaasyncio.to_thread, so sync callbacks won't block the event loop — but asyncbefore/after_kickoff_callbacksare still not awaited there. Fixingkickoff_asyncwould require a separate effort.Test plan
test_async_crew.pytests pass (sync callbacks unaffected)test_akickoff_calls_async_before_callbacks— async before callback is awaited; modified inputs reach task interpolationtest_akickoff_calls_async_after_callbacks— async after callback is awaited; result remainsCrewOutputtest_akickoff_mixed_sync_async_callbacks— mixed pipeline (sync_before → async_before → async_after → sync_after) executes in correct order and preserves the transform chain