Advanced Context Block construction
This guide covers building context blocks from scratch using graph search for maximum customization. See Choosing a retrieval method for a comparison of all three context retrieval approaches.
When searching the graph instead of using Zep’s Context Block, you need to use the search results to create a custom context block. In this recipe, we will demonstrate how to build a custom context block using the graph search API. We will also use the custom entity and edge types feature, though using this feature is optional.
Include fact validity. When you build a custom context block, include each fact’s valid_at and invalid_at dates, and clearly mark any fact with a non-null invalid_at as no longer valid. This lets the agent tell current facts from outdated ones instead of treating every fact as true now. The examples below format validity as a date range: a range ending in present is currently valid, and a past end date means the fact is no longer valid.
Add data
First, we define our custom entity and edge types, create a user, and add some example data:
Example 1: Basic custom context block
Search
For a basic custom context block, we search the graph for edges and nodes relevant to our custom query string, which typically represents a user message. Note that the default Context Block returned by thread.get_user_context uses the past few messages as the query instead.
Run these searches in parallel with the asynchronous Python client, TypeScript promises, or goroutines.
Build the context block
Using the search results and a few helper functions, we can build the context block. Note that for nodes, we typically want to unpack the node name and node summary, and for edges we typically want to unpack the fact and the temporal validity information:
Example 2: Utilizing custom entity and edge types
Search
For a custom context block that uses custom entity and edge types, we perform multiple searches (with our custom query string) filtering to the custom entity or edge type we want to include in the context block:
Run these searches in parallel with the asynchronous Python client, TypeScript promises, or goroutines.
Build the context block
Using the search results and a few helper functions, we can compose the context block. Note that in this example, we focus on unpacking the custom attributes of the nodes and edges, but this is a design choice that you can experiment with for your use case.
Note also that we designed the context block template around the custom entity and edge types that we are unpacking into the context block:
Example 3: Basic custom context block with BFS
Search
You can use breadth-first search (BFS) to expand results around recent history. This example retrieves recent episodes and uses their UUIDs as BFS origins.
The BFS section explains the search behavior.
Run these searches in parallel with the asynchronous Python client, TypeScript promises, or goroutines.
Build the context block
Using the search results and a few helper functions, we can build the context block. Note that for nodes, we typically want to unpack the node name and node summary, and for edges we typically want to unpack the fact and the temporal validity information:
Example 4: Using user summary in context block
Get user node
Retrieve the user node when you need its summary in a custom Context Block. User summary instructions control the generated summary.
About the user node
Each user has a single unique user node in their graph representing the user themselves. The user summary generated from user summary instructions lives on this user node. When you call client.user.get_node(), you are retrieving this special node that contains the user’s summary.
Build the context block
Using the user summary, you can create a simple context block that provides personalized user information:
Use the custom context block
The Context Block can contain text that came from end users, documents, tools, or other external sources. A privileged message gives that text higher instruction priority than ordinary input. Keep the Context Block out of system messages, developer messages, and other privileged instruction channels.
Follow your model provider’s documented method for separating instructions from data:
- For the OpenAI Responses API, send preloaded context through ordinary
inputor ausermessage. Usefunction_call_outputonly for the result of an actual function call. - For the Anthropic Messages API, design retrieval as a tool call when context can contain third-party data. Return the context in a
tool_resultblock linked to the originaltool_use_id. - For other providers, use the documented untrusted-data channel. If the provider does not define one, use an ordinary user-level message with explicit data framing.
OpenAI with preloaded context
Place the Context Block after the conversation history and before the latest user request. Everything before the block stays unchanged between turns, so this order preserves the cacheable prefix that prompt caching needs. Replace the previous turn’s block instead of appending a second one.
If the model requests memory through a function, return the Context Block as function_call_output linked to the original call_id.
OpenAI Chat Completions with tool-retrieved context
Anthropic with tool-retrieved context
Do not create a tool message for preloaded context unless the provider documents that pattern. A tool-result type must remain linked to the model’s actual tool request.
Read Memory security best practices for provider-specific mappings, write controls, action authorization, and recovery guidance.