CrewAI integration

Add long-term agent memory and knowledge graphs to CrewAI agents

The zep-crewai package gives CrewAI agents persistent memory backed by Zep’s temporal knowledge graph. You persist conversation turns and business data with Zep storage adapters, and give your agents Zep tools so they can retrieve relevant context when they need it. This lets agents carry context across executions, share a common knowledge base, and ground their decisions in what was learned before.

Core benefits

  • Persistent memory — Conversations and knowledge persist across sessions and crew runs.
  • On-demand retrieval — Agents search Zep through tools and pull in context exactly when a task calls for it.
  • Dual storage — User-specific memory for individuals and shared knowledge graphs for organizational data.
  • Tool integration — Search and add-data tools let agents read from and write to Zep during execution.

How it works

Memory in this integration is explicit and tool-driven. There are two distinct steps, and you control both.

Persisting context. Create a ZepUserStorage or ZepGraphStorage adapter and call storage.save(value, metadata={"type": ...}). The adapter routes each item by its type:

Metadata typeRoutes toUse for
messageThread APIConversation turns (role-based)
jsonKnowledge graphStructured data
textKnowledge graphFacts, preferences, free text

Retrieving context. Attach create_search_tool (and optionally create_add_data_tool) to an Agent(tools=[...]). The agent searches Zep when it decides the task needs it. You can also call ZepUserStorage.get_context() directly to fetch the prompt-ready context block that Zep auto-assembles for a thread.

There is no automatic retrieval or storage, and no external_memory= Crew wiring. CrewAI 1.x removed the ExternalMemory(storage=...) wrapper and the storage interface it depended on, so context is never injected behind the scenes. You decide what to save with save(...), and the agent decides what to search through its tools.

Installation

$pip install zep-crewai

Requires Python 3.11+, zep-crewai>=1.1.2, crewai>=1.0.0, and zep-cloud>=3.23.0, plus a Zep Cloud API key. Get your API key from app.getzep.com.

Set your API key in the environment:

$export ZEP_API_KEY="your-zep-api-key"

Storage types

User storage

Use ZepUserStorage for an individual user’s conversation history and personal context. A thread_id is required and ties message storage to a conversation thread.

Python
1import os
2from zep_cloud.client import Zep
3from zep_crewai import ZepUserStorage, create_search_tool
4from crewai import Agent
5
6zep_client = Zep(api_key=os.getenv("ZEP_API_KEY"))
7
8# Create the user and thread up front
9zep_client.user.add(user_id="alice_123", first_name="Alice")
10zep_client.thread.create(user_id="alice_123", thread_id="project_456")
11
12# Create user storage
13user_storage = ZepUserStorage(
14 client=zep_client,
15 user_id="alice_123",
16 thread_id="project_456",
17)
18
19# Persist a conversation turn (routes to the thread)
20user_storage.save(
21 "How can I help you today?",
22 metadata={"type": "message", "role": "assistant", "name": "Helper"},
23)
24
25# Persist a preference as graph data
26user_storage.save(
27 "Alice prefers morning meetings",
28 metadata={"type": "text"},
29)
30
31# Give an agent a Zep search tool so it can retrieve this context on demand
32assistant = Agent(
33 role="Personal Assistant",
34 goal="Help Alice using what you know about her",
35 backstory="You know Alice's preferences and conversation history.",
36 tools=[create_search_tool(zep_client, user_id="alice_123")],
37)

To fetch the auto-assembled context block for the thread directly, call get_context():

Python
1# Returns a prompt-ready context block string (or None if empty)
2context = user_storage.get_context()
3print(context)

Graph storage

Use ZepGraphStorage for shared organizational knowledge that multiple agents can read and write.

Python
1from zep_cloud import SearchFilters
2from zep_crewai import ZepGraphStorage, create_search_tool
3from crewai import Agent
4
5# Create the graph
6zep_client.graph.create(
7 graph_id="company_knowledge",
8 name="Company Knowledge Graph",
9 description="Shared organizational knowledge and insights.",
10)
11
12# Create graph storage for shared knowledge
13graph_storage = ZepGraphStorage(
14 client=zep_client,
15 graph_id="company_knowledge",
16 search_filters=SearchFilters(node_labels=["Technology", "Project"]),
17)
18
19# Persist knowledge
20graph_storage.save(
21 "Project Alpha uses Python and React",
22 metadata={"type": "text"},
23)
24
25# Let agents search it through a tool
26knowledge_agent = Agent(
27 role="Knowledge Assistant",
28 goal="Answer questions from the shared knowledge graph",
29 backstory="You maintain and search the team's shared knowledge.",
30 tools=[create_search_tool(zep_client, graph_id="company_knowledge")],
31)

You can also search a graph directly. search returns a list whose entries include a composed context string:

Python
1results = graph_storage.search("project status", limit=5)
2for item in results:
3 print(item.get("context", ""))

Tool integration

Tools are the supported extension point for exposing Zep to CrewAI agents. Bind a tool to a single user or a single graph at creation time, then add it to an agent’s tools list.

Python
1from zep_crewai import create_search_tool, create_add_data_tool
2from crewai import Agent
3
4# Tools bound to user storage
5user_search_tool = create_search_tool(zep_client, user_id="alice_123")
6user_add_tool = create_add_data_tool(zep_client, user_id="alice_123")
7
8# Tools bound to graph storage
9graph_search_tool = create_search_tool(zep_client, graph_id="knowledge_base")
10graph_add_tool = create_add_data_tool(zep_client, graph_id="knowledge_base")
11
12curator = Agent(
13 role="Knowledge Curator",
14 goal="Search existing knowledge and record new findings",
15 backstory="You maintain the organization's knowledge base.",
16 tools=[graph_search_tool, graph_add_tool],
17 llm="gpt-4o-mini",
18)

Search tool parameters:

  • query — Natural language search query.
  • limit — Maximum results (default: 10).
  • scope — Search scope: edges (default), nodes, episodes, or all.

Add-data tool parameters:

  • data — Content to store (text, JSON, or message).
  • data_type — Explicit type: text (default), json, or message.

Structured data with ontologies

Define entity models so Zep organizes graph data into typed entities. The SDK requires a docstring on each entity class to describe it.

Python
1from pydantic import Field
2from zep_cloud import SearchFilters
3from zep_cloud.external_clients.ontology import EntityModel, EntityText
4from zep_crewai import ZepGraphStorage
5
6class ProjectEntity(EntityModel):
7 """A project tracked in the knowledge graph."""
8
9 status: EntityText = Field(description="project status")
10 priority: EntityText = Field(description="priority level")
11 team_size: EntityText = Field(description="team size")
12
13# Apply the ontology to one or more graphs
14zep_client.graph.set_ontology(
15 graph_ids=["projects"],
16 entities={"Project": ProjectEntity},
17 edges={},
18)
19
20# Use the graph with filtered search and context limits
21graph_storage = ZepGraphStorage(
22 client=zep_client,
23 graph_id="projects",
24 search_filters=SearchFilters(node_labels=["Project"]),
25 facts_limit=20,
26 entity_limit=5,
27)

Configuration options

ZepUserStorage parameters

  • client — Zep client instance (required).
  • user_id — User identifier (required).
  • thread_id — Thread identifier (required); ties message storage to a conversation thread.
  • search_filters — Filter search results by node labels or attributes.
  • facts_limit — Maximum facts (edges) for context (default: 20).
  • entity_limit — Maximum entities (nodes) for context (default: 5).

ZepGraphStorage parameters

  • client — Zep client instance (required).
  • graph_id — Graph identifier (required).
  • search_filters — Filter by node labels, for example SearchFilters(node_labels=["Technology"]).
  • facts_limit — Maximum facts (edges) for context (default: 20).
  • entity_limit — Maximum entities (nodes) for context (default: 5).

Complete example

This example mirrors the simple_example.py from the integration repository. It persists conversation turns and business data to a user’s memory, then runs an agent that searches that memory through a Zep tool before answering.

Python
1import os
2import sys
3import time
4import uuid
5
6from crewai import Agent, Crew, Process, Task
7from zep_cloud.client import Zep
8
9from zep_crewai import ZepUserStorage, create_search_tool
10
11
12def main():
13 api_key = os.environ.get("ZEP_API_KEY")
14 if not api_key:
15 print("Error: set your ZEP_API_KEY environment variable")
16 print("Get your API key from: https://app.getzep.com")
17 sys.exit(1)
18
19 zep_client = Zep(api_key=api_key)
20
21 # Set up a unique user and thread
22 user_id = "demo_user_" + str(uuid.uuid4())
23 thread_id = "demo_thread_" + str(uuid.uuid4())
24
25 zep_client.user.add(
26 user_id=user_id,
27 first_name="John",
28 last_name="Doe",
29 email="[email protected]",
30 )
31 zep_client.thread.create(user_id=user_id, thread_id=thread_id)
32
33 # Initialize the Zep storage adapter
34 user_storage = ZepUserStorage(client=zep_client, user_id=user_id, thread_id=thread_id)
35
36 # Persist context with metadata-based routing
37 # JSON data routes to the graph
38 user_storage.save(
39 '{"trip_type": "business", "destination": "New York", "duration": "3 days", '
40 '"budget": 2000, "accommodation_preference": "mid-range hotels"}',
41 metadata={"type": "json"},
42 )
43
44 # Messages route to the thread
45 user_storage.save(
46 "Hi, I need help planning a business trip to New York. I'll be there for 3 "
47 "days and prefer mid-range hotels.",
48 metadata={"type": "message", "role": "user", "name": "John Doe"},
49 )
50 user_storage.save(
51 "I'd be happy to help you plan your New York business trip!",
52 metadata={"type": "message", "role": "assistant", "name": "Travel Planning Assistant"},
53 )
54
55 # Text data routes to the graph
56 user_storage.save(
57 "John Doe prefers mid-range hotels with business amenities, enjoys local "
58 "cuisine, and values convenient locations near business districts.",
59 metadata={"type": "text"},
60 )
61 user_storage.save(
62 "John Doe's budget constraint: around $2000 total for the trip including "
63 "flights and accommodation. Looking for good value rather than luxury.",
64 metadata={"type": "text"},
65 )
66
67 # Allow time for indexing before the agent searches
68 time.sleep(20)
69
70 # Give the agent a Zep search tool bound to this user
71 search_tool = create_search_tool(zep_client, user_id=user_id)
72
73 travel_agent = Agent(
74 role="Travel Planning Assistant",
75 goal="Help plan business trips efficiently and within budget",
76 backstory="""You are an experienced travel planner who specializes in business
77 trips. You always consider the user's preferences, budget, and trip context.
78 Use the Zep memory search tool to recall what you know about the user before
79 answering.""",
80 tools=[search_tool],
81 verbose=True,
82 llm="gpt-4.1-mini",
83 )
84
85 planning_task = Task(
86 description="""First, search Zep memory for the user's saved preferences and
87 trip context. Then provide 3 specific hotel recommendations in New York that
88 would be good for a business traveler. Include hotel names and locations, price
89 range per night, why each fits the user's preferences, and any business
90 amenities.""",
91 expected_output="A list of 3 hotel recommendations with detailed explanations",
92 agent=travel_agent,
93 )
94
95 crew = Crew(
96 agents=[travel_agent],
97 tasks=[planning_task],
98 process=Process.sequential,
99 verbose=True,
100 )
101
102 result = crew.kickoff()
103 print(result)
104
105 # Optionally persist the result for future runs
106 user_storage.save(str(result), metadata={"type": "message", "role": "assistant"})
107
108
109if __name__ == "__main__":
110 main()

Best practices

Storage selection

  • Use ZepUserStorage for personal preferences, conversation history, and user-specific context.
  • Use ZepGraphStorage for shared knowledge, organizational data, and collaborative information.

Memory management

  • Set up ontologies for structured graph data, and give every entity class a docstring.
  • Use search filters to target specific node types and improve relevance.
  • Combine storage types for comprehensive memory coverage.

Tool usage

  • Bind tools to a specific user or graph at creation time.
  • Use search scope all sparingly — it queries edges, nodes, and episodes and is more expensive.
  • Save data with the right type (message, json, or text) so it routes correctly.
  • Allow time for indexing — Zep extracts knowledge asynchronously, so facts from a turn are not instantly searchable.

Next steps