showcase/shell-docs/src/content/docs/integrations/crewai-flows/conversational-flows.mdx
CrewAI Conversational Flows are an execution mode for CrewAI Flows, not a separate CopilotKit integration. Start with the CrewAI Quickstart, then opt your existing Flow and its AG-UI endpoint into conversational execution.
Each CopilotKit turn is passed to CrewAI's stream_turn API. The integration maps the CopilotKit threadId to CrewAI's session_id, so each browser thread gets an isolated, multi-turn conversation.
stream_turn behavior required by this integration.ag-ui-crewai release.pip install -U "crewai>=1.15.11,<2" ag-ui-crewai
Add `conversational = True` to the Flow class you created in the Quickstart. You do not need a second agent or a separate deployment.
```python title="flow.py"
class SupportFlow(Flow[AppState]):
conversational = True # [!code highlight]
# Keep your Flow methods, tools, and state here.
```
Pass the same opt-in when you register the Flow with FastAPI:
```python title="server.py"
from fastapi import FastAPI
from ag_ui_crewai import add_crewai_flow_fastapi_endpoint
from flow import SupportFlow
app = FastAPI()
add_crewai_flow_fastapi_endpoint(
app,
SupportFlow(),
"/",
conversational=True, # [!code highlight]
)
```
The endpoint now calls `stream_turn(message, session_id=thread_id)` instead of starting the Flow through the regular `kickoff` or `astream` path.
Continue using the same `<CopilotKit>`, chat component, hooks, tools, and generative UI components from the Quickstart and feature guides. The execution-mode change is entirely in the CrewAI backend.
Most CopilotKit features require no additional Conversational Flow configuration. These are the exceptions to keep in mind:
| When you use | What to do |
|---|---|
| Custom Pydantic state | Inherit from ag_ui_crewai.CopilotKitState. It includes the conversation fields CrewAI needs alongside CopilotKit messages, tools, and shared state. |
| CrewAI conversational routes | Route a turn with route_turn() and @listen(...). Return the public reply or call append_assistant_message() once; CrewAI already appends the current user message. |
Native interrupts with useInterrupt | Register the endpoint with emit_interrupt_outcome=True and enable_legacy_on_interrupt_event=False, just as you would for a regular CrewAI Flow. |
| CrewAI persistence | Keep using CrewAI's @persist support. CopilotKit supplies a stable threadId, and the integration uses it as the CrewAI session_id. |
For an interrupt-enabled endpoint, the registration looks like this:
add_crewai_flow_fastapi_endpoint(
app,
SupportFlow(),
"/",
conversational=True,
emit_interrupt_outcome=True,
enable_legacy_on_interrupt_event=False,
)
Enabling conversational mode adds CrewAI's turn router to your Flow. If your existing Flow already produces one public response from an @start graph, make sure the conversational router does not also send a built-in converse response. For intent-based conversations, move the per-turn entry points behind route_turn() and @listen(...) handlers.
from crewai.flow import listen
class SupportFlow(Flow[AppState]):
conversational = True
def route_turn(self, _context):
return "support"
@listen("support")
def answer(self):
return self.run_existing_support_logic()
Both opt-ins are required. If the endpoint requests conversational execution but the Flow does not declare conversational = True or expose stream_turn, the integration returns a correlated RUN_ERROR instead of silently falling back to regular Flow execution.