Skip to content

[BUG] Agent.kickoff_async guardrail retry calls sync invoke inside event loop #7252

Description

@jquentino-ambev

Description

When an Agent is executed with kickoff_async() and its guardrail rejects the
first output, CrewAI attempts the guardrail retry through the synchronous
executor path.

Because the retry is still running inside an event loop, AgentExecutor.invoke()
returns the invoke_async() coroutine instead of a result dictionary. CrewAI
then passes that coroutine to _build_output_from_result(), which calls
result.get("output", "").

This raises:

AttributeError: 'coroutine' object has no attribute 'get'
RuntimeWarning: coroutine 'AgentExecutor.invoke_async' was never awaited

As a result, guardrail_max_retries does not work for kickoff_async(): the
first rejected output crashes before the retry can complete.

Steps to Reproduce

  1. Install crewai==1.15.18.
  2. Configure a valid LLM provider/API key.
  3. Run the following script:
import asyncio

from crewai import Agent


def always_reject(_result):
    return False, "Force a guardrail retry."


async def main():
    agent = Agent(
        role="Test agent",
        goal="Return a short answer.",
        backstory="Minimal reproduction for an async guardrail retry.",
        llm="openai/gpt-4o-mini",
        guardrail=always_reject,
        guardrail_max_retries=1,
        verbose=False,
    )

    await agent.kickoff_async(
        messages=[{"role": "user", "content": "Say hello."}],
    )


if __name__ == "__main__":
    asyncio.run(main())
  1. Wait for the first agent response to be rejected by always_reject.
  2. Observe that the retry crashes with AttributeError instead of executing
    asynchronously.

Expected behavior

After a guardrail rejects output produced by Agent.kickoff_async(), CrewAI
should execute the retry through the asynchronous executor path, await it, and
then evaluate the guardrail again.

With guardrail_max_retries=1, the agent should either:

  1. Return a valid second output if it passes the guardrail; or
  2. Raise the normal guardrail-validation error after the configured retry limit.

It should not attempt to use a coroutine as a dictionary.

Screenshots/Code snippets

Relevant execution path in crewai==1.15.18:

  1. Agent.kickoff_async() executes the initial run asynchronously:

    output = await self._execute_and_build_output_async(
        executor, inputs, response_format, usage_baseline
    )
    return self._finalize_kickoff(...)
  2. _finalize_kickoff() invokes the synchronous guardrail handler:

    output = self._process_kickoff_guardrail(...)
  3. When the guardrail fails, _process_kickoff_guardrail() retries through
    the synchronous method:

    retried = self._execute_and_build_output(
        executor, inputs, response_format, usage_baseline
    )
  4. That method calls:

    result = executor.invoke(inputs)
  5. However, AgentExecutor.invoke() explicitly returns self.invoke_async(inputs)
    when it detects an existing event loop:

    if is_inside_event_loop():
        return self.invoke_async(inputs)

The returned coroutine is then treated as a dictionary by
_build_output_from_result():

output = result.get("output", "")

Resulting traceback excerpt:

File ".../crewai/agent/core.py", line 2008, in _process_kickoff_guardrail
    retried = self._execute_and_build_output(...)
File ".../crewai/agent/core.py", line 1933, in _execute_and_build_output
    return self._build_output_from_result(...)
File ".../crewai/agent/core.py", line 1856, in _build_output_from_result
    output = result.get("output", "")
AttributeError: 'coroutine' object has no attribute 'get'

RuntimeWarning: coroutine 'AgentExecutor.invoke_async' was never awaited

Operating System

Ubuntu 22.04

Python Version

3.10

crewAI Version

1.15.18

crewAI Tools Version

1.15.18

Virtual Environment

Venv

Evidence

This occurred in a production-style Flow where a synchronous custom guardrail
validated that the final answer cited available internal evidence.

The first guardrail rejection worked as expected.

The failure happened only when CrewAI tried to retry the agent after that
rejection:

AttributeError: 'coroutine' object has no attribute 'get'
RuntimeWarning: coroutine 'AgentExecutor.invoke_async' was never awaited

The issue is independent of the guardrail's validation rule: any guardrail that
returns (False, "...") after kickoff_async() should exercise the same path.

Traceback (most recent call last):
  File "/home/jucelio-quentino/projects/internal-research/.venv/bin/kickoff", line 10, in <module>
    sys.exit(kickoff())
  File "/home/jucelio-quentino/projects/internal-research/cli/kickoff.py", line 9, in kickoff
    asyncio.run(ChatSession().run())
  File "/usr/lib/python3.10/asyncio/runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "/usr/lib/python3.10/asyncio/base_events.py", line 649, in run_until_complete
    return future.result()
  File "/home/jucelio-quentino/projects/internal-research/cli/chat_session.py", line 41, in run
    report = await self._run_flow(user_input)
  File "/home/jucelio-quentino/projects/internal-research/cli/chat_session.py", line 72, in _run_flow
    result = await flow.akickoff()
  File "/home/jucelio-quentino/projects/internal-research/.venv/lib/python3.10/site-packages/crewai/flow/runtime/__init__.py", line 2757, in akickoff
    return await self.kickoff_async(
  File "/home/jucelio-quentino/projects/internal-research/src/internal_research/main.py", line 41, in kickoff_async
    result = await super().kickoff_async(
  File "/home/jucelio-quentino/projects/internal-research/.venv/lib/python3.10/site-packages/crewai/flow/runtime/__init__.py", line 2390, in kickoff_async
    await asyncio.gather(*tasks)
  File "/home/jucelio-quentino/projects/internal-research/.venv/lib/python3.10/site-packages/crewai/flow/runtime/__init__.py", line 2833, in _execute_start_method
    result, finished_event_id = await self._execute_method(
  File "/home/jucelio-quentino/projects/internal-research/.venv/lib/python3.10/site-packages/crewai/flow/runtime/__init__.py", line 3071, in _execute_method
    raise e
  File "/home/jucelio-quentino/projects/internal-research/.venv/lib/python3.10/site-packages/crewai/flow/runtime/__init__.py", line 2967, in _execute_method
    result = await method(*args, **kwargs)
  File "/home/jucelio-quentino/projects/internal-research/.venv/lib/python3.10/site-packages/crewai/flow/runtime/__init__.py", line 2880, in enhanced_method
    return await original_method(*args, **kwargs)
  File "/home/jucelio-quentino/projects/internal-research/src/internal_research/main.py", line 79, in research
    answer_markdown = await researcher.kickoff(
  File "/home/jucelio-quentino/projects/internal-research/src/internal_research/solo_researcher.py", line 134, in kickoff
    output = await self.agent().kickoff_async(messages=messages)
  File "/home/jucelio-quentino/projects/internal-research/.venv/lib/python3.10/site-packages/crewai/agent/core.py", line 2108, in kickoff_async
    self._emit_kickoff_error(agent_info, e)
  File "/home/jucelio-quentino/projects/internal-research/.venv/lib/python3.10/site-packages/crewai/agent/core.py", line 1793, in _emit_kickoff_error
    raise e
  File "/home/jucelio-quentino/projects/internal-research/.venv/lib/python3.10/site-packages/crewai/agent/core.py", line 2097, in kickoff_async
    return self._finalize_kickoff(
  File "/home/jucelio-quentino/projects/internal-research/.venv/lib/python3.10/site-packages/crewai/agent/core.py", line 1764, in _finalize_kickoff
    output = self._process_kickoff_guardrail(
  File "/home/jucelio-quentino/projects/internal-research/.venv/lib/python3.10/site-packages/crewai/agent/core.py", line 2008, in _process_kickoff_guardrail
    retried = self._execute_and_build_output(
  File "/home/jucelio-quentino/projects/internal-research/.venv/lib/python3.10/site-packages/crewai/agent/core.py", line 1933, in _execute_and_build_output
    return self._build_output_from_result(
  File "/home/jucelio-quentino/projects/internal-research/.venv/lib/python3.10/site-packages/crewai/agent/core.py", line 1856, in _build_output_from_result
    output = result.get("output", "")
AttributeError: 'coroutine' object has no attribute 'get'
sys:1: RuntimeWarning: coroutine 'AgentExecutor.invoke_async' was never awaited

Possible Solution

Introduce an async guardrail-finalization/retry path for kickoff_async().

For example, kickoff_async() should call an async equivalent of
_finalize_kickoff() / _process_kickoff_guardrail() that retries with:

await self._execute_and_build_output_async(...)

instead of calling _execute_and_build_output(...).

Alternatively, make _process_kickoff_guardrail() detect whether
executor.invoke(inputs) returned an awaitable and await it before building the
output. A dedicated async implementation would be clearer and avoid mixing the
sync and async execution paths.

Additional context

The guardrail is configured directly on the Agent with
guardrail_max_retries=2. The first guardrail evaluation is emitted correctly,
but the retry fails before a second evaluation occurs.

I have not tested a newer CrewAI version yet. The reproduction and source-path
analysis above are from the installed 1.15.18 release.

Activity

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

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions