> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://docs-beta.getzep.com/v3/how-to-share-context-across-users-using-graphs/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs-beta.getzep.com/_mcp/server. # Unify customer and account context > Create a user graph and an account Context Graph, ingest data into the correct scope, and retrieve from both for an authorized task. A customer task can require personal context and information that belongs to the customer's account. Keep each source in its correct scope: * A user graph stores a person's conversations, activity, and preferences. The application addresses the graph with `user_id`. * A shared account Context Graph stores account records and support events. The Context Graph also stores contracts, product documents, and other shared data. The application addresses the graph with `graph_id`. This recipe creates both scopes and retrieves from them for one authorized support task. ## Set up the user, thread, and account graph Use stable identifiers from your application. This example uses one user, one conversation thread, and one account Context Graph. **`Python`** ```python Python import json import os import time from zep_cloud.client import Zep from zep_cloud.types import Message client = Zep(api_key=os.environ["ZEP_API_KEY"]) USER_ID = "user-alice" THREAD_ID = "support-case-8472" ACCOUNT_ID = "acme" ACCOUNT_GRAPH_ID = f"account-{ACCOUNT_ID}" client.user.add( user_id=USER_ID, first_name="Alice", last_name="Smith", email="alice.smith@example.com", ) client.thread.create(thread_id=THREAD_ID, user_id=USER_ID) client.graph.create(graph_id=ACCOUNT_GRAPH_ID) ``` **`TypeScript`** ```typescript TypeScript import { ZepClient, type Zep } from "@getzep/zep-cloud"; const client = new ZepClient({ apiKey: process.env.ZEP_API_KEY }); const userId = "user-alice"; const threadId = "support-case-8472"; const accountId = "acme"; const accountGraphId = `account-${accountId}`; await client.user.add({ userId, firstName: "Alice", lastName: "Smith", email: "alice.smith@example.com", }); await client.thread.create({ threadId, userId }); await client.graph.create({ graphId: accountGraphId }); ``` **`Go`** ```go Go import ( "context" "encoding/json" "fmt" "log" "os" "time" "github.com/getzep/zep-go/v3" zepclient "github.com/getzep/zep-go/v3/client" "github.com/getzep/zep-go/v3/option" ) ctx := context.Background() client := zepclient.NewClient(option.WithAPIKey(os.Getenv("ZEP_API_KEY"))) userID := "user-alice" threadID := "support-case-8472" accountID := "acme" accountGraphID := "account-" + accountID _, err := client.User.Add(ctx, &zep.CreateUserRequest{ UserID: userID, FirstName: zep.String("Alice"), LastName: zep.String("Smith"), Email: zep.String("alice.smith@example.com"), }) if err != nil { log.Fatal(err) } _, err = client.Thread.Create(ctx, &zep.CreateThreadRequest{ ThreadID: threadID, UserID: userID, }) if err != nil { log.Fatal(err) } _, err = client.Graph.Create(ctx, &zep.CreateGraphRequest{ GraphID: &accountGraphID, }) if err != nil { log.Fatal(err) } ``` ## Add shared account sources Add each shared source to the account Context Graph. The example combines an account record, a support event, and a product document. **`Python`** ```python Python client.graph.add( graph_id=ACCOUNT_GRAPH_ID, type="json", data=json.dumps({ "account_id": ACCOUNT_ID, "plan": "Enterprise", "region": "eu-west", "renewal_date": "2026-11-15", }), source_description="Account record from the CRM", metadata={"source": "crm", "account_id": ACCOUNT_ID}, ) client.graph.add( graph_id=ACCOUNT_GRAPH_ID, type="json", data=json.dumps({ "case_id": "CASE-8472", "status": "investigating", "service": "Atlas Gateway", "event": "Regional failover validation failed", }), source_description="Support case event", metadata={"source": "support", "account_id": ACCOUNT_ID}, ) account_episode = client.graph.add( graph_id=ACCOUNT_GRAPH_ID, type="text", data=( "Atlas Gateway Enterprise accounts can use regional failover. " "Support must confirm backup-region validation before a cutover." ), source_description="Atlas Gateway support guide", metadata={"source": "product_docs", "product": "atlas-gateway"}, ) ``` **`TypeScript`** ```typescript TypeScript await client.graph.add({ graphId: accountGraphId, type: "json", data: JSON.stringify({ account_id: accountId, plan: "Enterprise", region: "eu-west", renewal_date: "2026-11-15", }), sourceDescription: "Account record from the CRM", metadata: { source: "crm", account_id: accountId }, }); await client.graph.add({ graphId: accountGraphId, type: "json", data: JSON.stringify({ case_id: "CASE-8472", status: "investigating", service: "Atlas Gateway", event: "Regional failover validation failed", }), sourceDescription: "Support case event", metadata: { source: "support", account_id: accountId }, }); const accountEpisode = await client.graph.add({ graphId: accountGraphId, type: "text", data: "Atlas Gateway Enterprise accounts can use regional failover. " + "Support must confirm backup-region validation before a cutover.", sourceDescription: "Atlas Gateway support guide", metadata: { source: "product_docs", product: "atlas-gateway" }, }); ``` **`Go`** ```go Go accountRecord, err := json.Marshal(map[string]string{ "account_id": accountID, "plan": "Enterprise", "region": "eu-west", "renewal_date": "2026-11-15", }) if err != nil { log.Fatal(err) } crmSource := "Account record from the CRM" _, err = client.Graph.Add(ctx, &zep.AddDataRequest{ GraphID: &accountGraphID, Type: zep.GraphDataTypeJSON, Data: string(accountRecord), SourceDescription: &crmSource, Metadata: map[string]any{ "source": "crm", "account_id": accountID, }, }) if err != nil { log.Fatal(err) } supportEvent, err := json.Marshal(map[string]string{ "case_id": "CASE-8472", "status": "investigating", "service": "Atlas Gateway", "event": "Regional failover validation failed", }) if err != nil { log.Fatal(err) } supportSource := "Support case event" _, err = client.Graph.Add(ctx, &zep.AddDataRequest{ GraphID: &accountGraphID, Type: zep.GraphDataTypeJSON, Data: string(supportEvent), SourceDescription: &supportSource, Metadata: map[string]any{ "source": "support", "account_id": accountID, }, }) if err != nil { log.Fatal(err) } productSource := "Atlas Gateway support guide" accountEpisode, err := client.Graph.Add(ctx, &zep.AddDataRequest{ GraphID: &accountGraphID, Type: zep.GraphDataTypeText, Data: "Atlas Gateway Enterprise accounts can use regional failover. " + "Support must confirm backup-region validation before a cutover.", SourceDescription: &productSource, Metadata: map[string]any{ "source": "product_docs", "product": "atlas-gateway", }, }) if err != nil { log.Fatal(err) } ``` ## Wait until the account context is searchable Zep processes episodes asynchronously. Poll the last submitted account episode, and then retry a query that is specific to the imported data. The [ingestion status guide](/check-data-ingestion-status) explains the processing order and production alternatives to polling. **`Python`** ```python Python deadline = time.monotonic() + 300 while True: episode = client.graph.episode.get(uuid_=account_episode.uuid_) if episode.processed: break if time.monotonic() >= deadline: raise TimeoutError("The account context did not finish processing.") time.sleep(5) search_deadline = time.monotonic() + 300 while True: account_results = client.graph.search( graph_id=ACCOUNT_GRAPH_ID, query="Which account uses regional failover?", scope="edges", search_filters={"episode_uuids": [account_episode.uuid_]}, limit=10, ) if account_results.edges: break if time.monotonic() >= search_deadline: raise TimeoutError("The account context is not searchable.") time.sleep(5) ``` **`TypeScript`** ```typescript TypeScript const deadline = Date.now() + 300_000; const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); let episode = await client.graph.episode.get(accountEpisode.uuid); while (!episode.processed) { if (Date.now() >= deadline) { throw new Error("The account context did not finish processing."); } await sleep(5_000); episode = await client.graph.episode.get(accountEpisode.uuid); } const searchDeadline = Date.now() + 300_000; while (true) { const accountResults = await client.graph.search({ graphId: accountGraphId, query: "Which account uses regional failover?", scope: "edges", searchFilters: { episodeUuids: [accountEpisode.uuid] }, limit: 10, }); if ((accountResults.edges ?? []).length > 0) { break; } if (Date.now() >= searchDeadline) { throw new Error("The account context is not searchable."); } await sleep(5_000); } ``` **`Go`** ```go Go deadline := time.Now().Add(5 * time.Minute) for { episode, err := client.Graph.Episode.Get(ctx, accountEpisode.UUID) if err != nil { log.Fatal(err) } if episode.Processed != nil && *episode.Processed { break } if time.Now().After(deadline) { log.Fatal("the account context did not finish processing") } time.Sleep(5 * time.Second) } limit := 10 searchDeadline := time.Now().Add(5 * time.Minute) searchFilters := zep.SearchFilters{ EpisodeUUIDs: []string{accountEpisode.UUID}, } for { accountResults, err := client.Graph.Search(ctx, &zep.GraphSearchQuery{ GraphID: &accountGraphID, Query: "Which account uses regional failover?", Scope: zep.GraphSearchScopeEdges.Ptr(), SearchFilters: &searchFilters, Limit: &limit, }) if err != nil { log.Fatal(err) } if len(accountResults.Edges) > 0 { break } if time.Now().After(searchDeadline) { log.Fatal("the account context is not searchable") } time.Sleep(5 * time.Second) } ``` ## Retrieve personal and account context Authorize the account in your application before you request its Context Graph. Then record the current user message, retrieve its Context Block in the same request, and search the authorized account graph. **`Python`** ```python Python def retrieve_support_context( user_message: str, authorized_account_ids: set[str], ) -> dict[str, str]: if ACCOUNT_ID not in authorized_account_ids: raise PermissionError("The user is not authorized for this account.") memory_response = client.thread.add_messages( THREAD_ID, messages=[ Message(name="Alice Smith", role="user", content=user_message), ], return_context=True, ) account_results = client.graph.search( graph_id=ACCOUNT_GRAPH_ID, query=user_message, scope="edges", limit=10, ) account_context = "\n".join( edge.fact for edge in (account_results.edges or []) ) return { "user_context": memory_response.context, "account_context": account_context, } ``` **`TypeScript`** ```typescript TypeScript async function retrieveSupportContext( userMessage: string, authorizedAccountIds: Set, ): Promise<{ userContext: string; accountContext: string }> { if (!authorizedAccountIds.has(accountId)) { throw new Error("The user is not authorized for this account."); } const messages: Zep.Message[] = [ { name: "Alice Smith", role: "user", content: userMessage }, ]; const memoryResponse = await client.thread.addMessages(threadId, { messages, returnContext: true, }); if (memoryResponse.context === undefined) { throw new Error("Zep did not return a user Context Block."); } const accountResults = await client.graph.search({ graphId: accountGraphId, query: userMessage, scope: "edges", limit: 10, }); return { userContext: memoryResponse.context, accountContext: (accountResults.edges ?? []) .map((edge) => edge.fact) .join("\n"), }; } ``` **`Go`** ```go Go func retrieveSupportContext( ctx context.Context, userMessage string, authorizedAccountIDs map[string]bool, ) (string, string, error) { if !authorizedAccountIDs[accountID] { return "", "", fmt.Errorf("the user is not authorized for this account") } memoryResponse, err := client.Thread.AddMessages( ctx, threadID, &zep.AddThreadMessagesRequest{ Messages: []*zep.Message{ { Name: zep.String("Alice Smith"), Role: zep.RoleTypeUserRole, Content: userMessage, }, }, ReturnContext: zep.Bool(true), }, ) if err != nil { return "", "", err } limit := 10 accountResults, err := client.Graph.Search(ctx, &zep.GraphSearchQuery{ GraphID: &accountGraphID, Query: userMessage, Scope: zep.GraphSearchScopeEdges.Ptr(), Limit: &limit, }) if err != nil { return "", "", err } accountContext := "" for _, edge := range accountResults.Edges { accountContext += edge.Fact + "\n" } if memoryResponse.Context == nil { return "", "", fmt.Errorf("Zep did not return a user Context Block") } return *memoryResponse.Context, accountContext, nil } ``` Put the two context values in the model request as untrusted reference data. Keep stable application instructions in a higher-priority message. Do not put retrieved context in a developer or system message. After the model returns a response, add the assistant message to the thread so that later tasks can retrieve it as agent memory. ## Apply governance Your application must validate the relationship between the user and the account. You can also use [policy-based access control](/policy-based-access-control) to limit an API key to permitted account graphs and sources. Retain the source episode references from account search results when you must [trace retrieved facts to their sources](/source-traceability). ## Next steps * [Add user-specific business data](/how-to-add-user-specific-business-data-to-user-graphs). * [Configure agent access](/attribute-based-access-control). * [Filter search by source metadata](/searching-the-graph#episode-metadata-filtering). * [Build an enterprise Context Graph](/give-your-agent-domain-knowledge). * [Build an agent that searches the account graph with tools](/build-an-agent-with-zep). > Combine personal context with shared account records, events, documents, and conversations