Create an agent that responds to user messages given conversation history:
src/my_chatbot/agents/chat_agent.py
from crewai import Agent, LLMllm = LLM(model="openrouter/anthropic/claude-sonnet-4")chat_agent = Agent( role="Helpful Assistant", goal="Assist users by answering their questions clearly and helpfully", backstory=( "You are a friendly and knowledgeable assistant. You provide clear, " "concise, and helpful responses to user questions." ), llm=llm, respect_context_window=True, verbose=False,)
Optionally, add a second agent that suggests follow-up questions:
src/my_chatbot/agents/suggest_agent.py
from crewai import Agent, LLMllm = LLM(model="openrouter/anthropic/claude-sonnet-4")suggest_agent = Agent( role="Follow-up Question Generator", goal="Generate 3 relevant follow-up questions based on a conversation", backstory=( "You analyze conversations and suggest exactly 3 brief follow-up " "questions the user might want to ask next. Return only the 3 questions, " "one per line, without numbering or bullet points." ), llm=llm, respect_context_window=True, verbose=False,)
The flow manages conversation state — it appends each user message and assistant response to a messages list that persists across runs via thread state:
src/my_chatbot/flows/chat_flow.py
from crewai.flow.flow import Flow, listen, startfrom pydantic import BaseModelfrom my_chatbot.agents.chat_agent import chat_agentfrom my_chatbot.agents.suggest_agent import suggest_agentclass ChatState(BaseModel): query: str = "" messages: list[dict] = [] suggested_questions: list[str] = []class ChatFlow(Flow[ChatState]): @start() def chat(self): """Add the user query to messages, call the agent, and return the response.""" user_msg = {"role": "user", "content": self.state.query} history = self.state.messages + [user_msg] result = chat_agent.kickoff(history) assistant_msg = {"role": "assistant", "content": result.raw} self.state.messages = self.state.messages + [user_msg, assistant_msg] return result.raw @listen(chat) def suggest(self, chat_response: str): """Generate 3 follow-up questions based on the conversation.""" recent = self.state.messages[-6:] conversation = "\n".join(f"{m['role']}: {m['content']}" for m in recent) result = suggest_agent.kickoff( f"Based on this conversation, suggest exactly 3 brief follow-up questions " f"the user might want to ask next. Return only the 3 questions, one per line, " f"without numbering or bullet points.\n\n{conversation}" ) questions = [q.strip() for q in result.raw.strip().split("\n") if q.strip()][:3] self.state.suggested_questions = questions return { "messages": self.state.messages, "suggested_questions": self.state.suggested_questions, }
How state works: When running inside a thread, Crewship passes the previous thread state (including messages) into the flow. The flow appends the new exchange and returns the updated state, which Crewship saves as a checkpoint.
# Create a threadcrewship thread create dep_abc123# Chat in the threadcrewship invoke dep_abc123 --thread thr_xyz789 -i '{"query": "What are AI agents?"}'crewship invoke dep_abc123 --thread thr_xyz789 -i '{"query": "How do they differ from simple chatbots?"}'