> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://help.getzep.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://help.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<string>,
): 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).