We're hiring! Come build with us →
Zep

Zep v4: the Context API for agents

Zep v4 lets your agent navigate a graph by its connections and read how Zep built it. Every error has a stable code that the agent can act on. v3 keeps working while you migrate.

Patrick Kemp · 7 min read

Why Zep v4

Business knowledge is scattered across systems, and agents piece it together to make decisions. Zep combines business data, documents, and conversations into Zep graphs that agents use as context.

Agents now use the Zep API in two ways. Coding agents write integration code for developers, and run-time agents call Zep as they work: they search a graph, read the results, and decide which call to make next.

What's new in v4

  • Hyperedges. A hyperedge stores a fact about three or more entities as one fact, so an agent does not find one pair of the fact and miss the rest. See Hyperedges.
  • Hierarchical entity types. Custom entity types can have parent types and subtypes. The ontology can record that one type is a kind of another. The feature is in closed beta for select accounts. See Entity type hierarchy.
  • Documents. A document_id groups the chunks of one file, so Zep extracts each chunk with the earlier chunks as context. See Documents.
  • Identity and protected properties. Zep matches entities on the values that identify them, such as an account ID, so one customer does not become two nodes. Ingestion does not replace a protected value that you imported from a system of record. strict_ontology: true makes the graph conform to the entity and edge types that you defined. See Customize graph structure.
  • Event time. reference_time records when an event occurred, separate from the time that Zep received the data, so a backfill keeps the correct order of events. See Preserve original event time.
  • Also in v4. One call can add up to 100 nodes or 100 edges. Each entity node lists the episodes that mention it in episode_uuids. Auto search can turn relative calendar language into a time window in the time zone of the user or graph.

We'll be posting throughout the week to cover these changes in more detail.

The three navigation calls on a sales graph, first in Python and then in the dashboard's graph assistant.

Search returns what matches a query. Many agent questions are about structure instead: which accounts connect to this person, or what is two hops from this deal. With search alone, an agent answers them with a chain of calls, and it cannot tell when it has seen the full neighborhood. v4 adds three navigation calls:

  • graph.node.list with order_by="degree" lists the most connected nodes first. Each node has a degree field: the number of entity edges that touch the node.
  • graph.node.list_neighbors returns each node that connects to an anchor node, with every edge that connects the two.
  • graph.get_subgraph expands from up to 20 seed nodes, to a depth of up to three hops. When the expansion reaches the node limit or the edge limit, the response sets truncated and names the limit in truncation_reason. An agent cannot mistake a partial neighborhood for a complete one.
PYTHON
import os from zep_cloud.client import Zep client = Zep(api_key=os.environ["ZEP_API_KEY"])graph_uuid = "YOUR_GRAPH_UUID" hubs = client.graph.node.list(    graph_uuid,    order_by="degree",    order="desc",    page_size=5,).itemsif not hubs:    raise SystemExit("The graph has no nodes yet.")anchor = hubs[0] neighbors = client.graph.node.list_neighbors(graph_uuid, anchor.uuid_, page_size=25).itemsfor neighbor in neighbors:    print(neighbor.node.name, len(neighbor.edges)) subgraph = client.graph.get_subgraph(    graph_uuid,    seed_node_uuids=[anchor.uuid_],    depth=2,)if subgraph.truncated:    print("Stopped by:", subgraph.truncation_reason)

Agents that connect through the Context MCP Server get the same navigation as tools. get_node_neighbors lists the entities that connect directly to one entity, with the connecting facts. get_subgraph explores a bounded, multi-hop neighborhood around seed entities.

In the Zep dashboard, the graph visualizer assistant answers questions about the graph that you have open, such as how two entities connect. The assistant is read-only, and it shows each retrieval step as it runs.

See how Zep built each episode

A wrong fact in the graph has a cause in the episode text or in an extraction decision. Without a record of what each extraction step saw and decided, a fix is guesswork. In v4, debug logs and Ingestion Traces are available through the API and the SDKs, and a developer or an agent can read them in code.

debug_log.enable turns on debug mode for the project for a limited window: 60 minutes for standard accounts and 24 hours for Enterprise accounts. Zep captures workflow logs for the episodes that it processes during the window. On Flex, Flex Plus, and Enterprise plans, the same call also enables Ingestion Traces.

PYTHON
enabled = client.debug_log.enable() # Add episodes, and then read the logs and traces for one episode.logs = client.graph.episode.get_debug_logs(graph_uuid, episode_uuid)traces = client.graph.episode.list_ingestion_traces(    graph_uuid,    episode_uuid,    page_size=50,)

graph.episode.get_debug_logs returns the status, the start and completion times, the error, and the log entries for one episode. graph.episode.list_ingestion_traces returns a page of trace records, oldest first. Each record has step, status, input, output, error, and created_at.

Ingestion Traces explain why each traced extraction step made its decision. A trace connects each decision to the episode context, prior episodes, ontology, or deduplication candidates that informed it. A developer can find the cause of a wrong fact in the trace before they change the ontology or the data.

Debug mode applies only to episodes that Zep processes while the window is active. Zep keeps workflow logs for 12 hours, or 7 days for Enterprise accounts, and keeps Ingestion Traces for 30 days. Logs and traces hold episode content, so handle them as you handle the source data. For more information, see Debug mode.

An API that agents can call safely

An agent that calls an API in a loop has to retry failed calls and react to errors without a person to help.

Every resource has a UUID that Zep generates

v4 addresses every user, thread, and graph by a UUID that Zep generates. A create call returns the UUID, and every later call takes it. The v3 user_id, thread_id, and graph_id values become legacy names. Each UUID is opaque and holds no tenant data. An identifier in a URL does not put your customer names or internal IDs into access logs, proxy logs, referrer headers, or error reports. Store the Zep UUID next to your own record, and pass it on each call.

PYTHON
from datetime import datetime, timezone from zep_cloud import AddMessage zep_user = client.user.create(    email="jane.smith@example.com",    first_name="Jane",    last_name="Smith",)# Store this value with the user record in your application database.zep_user_uuid = zep_user.uuid_ zep_thread = client.thread.create(user_uuid=zep_user_uuid) client.thread.add_messages(    zep_thread.uuid_,    messages=[        AddMessage(            reference_time=datetime.now(timezone.utc).isoformat(),            name="Jane Smith",            role="user",            content="Which plan is my team on?",        )    ],)

Retries that do not make a second resource

A v4 create call carries no identifier, so each call makes a new resource. The SDK sends an idempotency key on its own retries, so a retried request does not make a second user, thread, or graph. When your application can repeat a create call, for example in a repair job, you can also pass your own Idempotency-Key. In Python, the option is idempotency_key.

Errors with stable codes

Every v4 error response has an error object with the same fields: code, message, param, details, and request_id. The code is a stable value that your code can compare. When one request field causes the error, param names that field.

HTTP statusExample codeWhat the caller can do
400invalid_identifierPass a UUID, not a legacy name.
400invalid_requestCorrect the field that param names.
404not_foundStop, or check the UUID. The resource does not exist.
429rate_limitedWait, and then send the request again.

Examples of v4 error codes and the action for each.

An agent does not have to parse a message to decide its next step. Every v4 response also has an X-Zep-Request-ID header with the same value as request_id.

One search method for each scope, and one pagination form

Each search scope has its own method: graph.search_edges, graph.search_nodes, graph.search_episodes, graph.search_observations, and graph.search_thread_summaries. graph.get_context returns a Context Block. One method for each scope gives each call one purpose and one response type. A run-time agent can expose each method as a separate tool.

List methods return a pager that follows an opaque cursor. You do not increment a page number. Lists and search results default to 50 items. A delete returns a task. Poll the task until it reaches succeeded, partial, or failed.

Get started and migrate from v3

The Python, TypeScript, and Go SDKs support v4. Install the v4 SDK for your language:

BASH
pip install --upgrade "zep-cloud>=4.0.0"npm install @getzep/zep-cloud@^4go get github.com/getzep/zep-go/v4

For more information, see the Zep documentation.

v3 and v4 serve the same project data at the same time, so you can move your integration to v4 in steps. First, add a column for the Zep UUID to each table that refers to a Zep resource. Then store the UUID of each new resource while your code still uses v3. The v3 response already contains the UUID that v4 uses. Next, give the existing rows their UUIDs with a backfill job.

The migration guide gives each step, the method map, and the changes that need more than a rename. If you use a coding agent, the Build with Zep plugin for Claude Code, Codex, and Cursor includes a skill that helps your agent migrate v3 code to v4.

FAQ

When does the v3 API retire?

No retirement date is set. The v3 API will be deprecated on April 13, 2027, and it keeps working after that date. v3 and v4 serve the same project data at the same time, so you can move your integration to v4 in steps: store the Zep UUID with each new record while your code still uses v3, and then backfill the existing rows.

Do my existing users get new UUIDs?

No. User and thread UUIDs have the same value in v3 and v4. For a graph, v4 uses the value that v3 returns as canonical_graph_uuid, not the v3 Graph.uuid. v3 already returns these values, so you can store them in your own database before you move any calls to v4. Use v3 SDK version 3.30.0 or later.

Is lookup part of the long-term API?

No. lookup is a migration tool, and it will become deprecated when the v3 API retires. A later major version will remove it. Use lookup only in the backfill job that gives your existing rows their UUIDs. lookup.batch resolves up to 1,000 legacy names in one request. After the migration, read the UUID from your own database.

Which plans include Ingestion Traces?

Ingestion Traces are available on the Flex, Flex Plus, and Enterprise plans. On those plans, debug_log.enable turns on traces along with debug mode, and Zep traces selected extraction steps for the episodes that it processes during the window: 60 minutes, or 24 hours for Enterprise accounts. On other plans, debug mode captures workflow logs only. Zep keeps traces for 30 days.

Written byPatrick KempMarketing Engineer, Zep

Start building with Zep

Zep unifies business data, documents, and conversations into shared, governed context so agents can complete tasks correctly.

Sign up
Albert Bierstadt, Sunset in the Yosemite Valley

More from the blog