> 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.

# Build project, product, and domain context

> Create, populate, search, and govern a shared Context Graph for a project, product, or business domain.

An agent cannot complete a business task when project records, product
documentation, and operational updates are in separate systems. This quickstart
combines these sources in one shared Context Graph and retrieves the context
that is relevant to a task.

## Choose the graph scope

Use one stable `graph_id` for each subject and access boundary.

| Scope           | Example `graph_id`  | Example sources                                                     |
| --------------- | ------------------- | ------------------------------------------------------------------- |
| Project         | `project-argus`     | Plans, decisions, issues, and meeting notes                         |
| Product         | `product-atlas`     | Specifications, release notes, support content, and catalog records |
| Business domain | `incident-response` | Runbooks, incidents, service records, and policy documents          |

This guide uses `project-argus`. The project depends on a product and follows an
incident-response process, so the example also shows product and business-domain
context. If these sources have different access requirements, put them in
separate Context Graphs and retrieve only the authorized graphs for each task.

## Install and initialize the SDK

#### Python

Set up your Python project, ideally with [a virtual environment](https://medium.com/@vkmauryavk/managing-python-virtual-environments-with-uv-a-comprehensive-guide-ac74d3ad8dff), and then:

**`pip`**

```bash pip
pip install zep-cloud
```

**`uv`**

```bash uv
uv pip install zep-cloud
```

#### TypeScript

Set up your TypeScript project and then:

**`npm`**

```bash npm
npm install @getzep/zep-cloud
```

**`yarn`**

```bash yarn
yarn add @getzep/zep-cloud
```

**`pnpm`**

```bash pnpm
pnpm install @getzep/zep-cloud
```

#### Go

Set up your Go project and then:

```bash
go get github.com/getzep/zep-go/v3
```

After [creating a Zep account](https://app.getzep.com/), obtaining an API key, and setting the API key as an environment variable, initialize the client once at application startup and reuse it throughout your application.

#### Initialize Zep client

**`Python`**

```python Python
import os
from zep_cloud.client import Zep

API_KEY = os.environ.get('ZEP_API_KEY')

client = Zep(
    api_key=API_KEY,
)
```

**`TypeScript`**

```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";

const API_KEY = process.env.ZEP_API_KEY;

const client = new ZepClient({
  apiKey: API_KEY,
});
```

**`Go`**

```go Go
import (
    zepclient "github.com/getzep/zep-go/v3/client"
    "github.com/getzep/zep-go/v3/option"
)

client := zepclient.NewClient(
    option.WithAPIKey(os.Getenv("ZEP_API_KEY")),
)
```

#### .env

```
ZEP_API_KEY=your_api_key_here
```

## Create the Context Graph

Create the graph before you add data. Use the same `graph_id` for every
operation in this quickstart.

**`Python`**

```python Python
GRAPH_ID = "project-argus"

client.graph.create(graph_id=GRAPH_ID)
```

**`TypeScript`**

```typescript TypeScript
const graphId = "project-argus";

await client.graph.create({ graphId });
```

**`Go`**

```go Go
graphID := "project-argus"

_, err := client.Graph.Create(context.TODO(), &zep.CreateGraphRequest{
    GraphID: &graphID,
})
if err != nil {
    log.Fatal(err)
}
```

## Ingest the source data

Add each source as an episode. Use JSON for structured records, text for
documents, and message data for communications with identified speakers.
Source metadata supports filtering, source traceability, and source-based
access policies.

**`Python`**

```python Python
import json
import time

client.graph.add(
    graph_id=GRAPH_ID,
    type="json",
    data=json.dumps({
        "project_id": "ARG-001",
        "name": "Project Argus",
        "status": "at risk",
        "product": "Atlas Gateway",
        "owner": "Platform Engineering",
    }),
    source_description="Project record from the project system",
    metadata={"source": "project_system", "record_id": "ARG-001"},
)

client.graph.add(
    graph_id=GRAPH_ID,
    type="text",
    data=(
        "Atlas Gateway release 4.2 requires the regional failover runbook. "
        "The release cannot start until the backup region passes validation."
    ),
    source_description="Atlas Gateway release runbook",
    metadata={"source": "product_docs", "product": "atlas-gateway"},
)

incident_episode = client.graph.add(
    graph_id=GRAPH_ID,
    type="message",
    data=(
        "Nina (incident commander): The backup region failed validation. "
        "Move the Argus release review to Friday."
    ),
    source_description="Incident response channel",
    metadata={"source": "incident_channel", "incident_id": "INC-482"},
)
```

**`TypeScript`**

```typescript TypeScript
await client.graph.add({
  graphId,
  type: "json",
  data: JSON.stringify({
    project_id: "ARG-001",
    name: "Project Argus",
    status: "at risk",
    product: "Atlas Gateway",
    owner: "Platform Engineering",
  }),
  sourceDescription: "Project record from the project system",
  metadata: { source: "project_system", record_id: "ARG-001" },
});

await client.graph.add({
  graphId,
  type: "text",
  data:
    "Atlas Gateway release 4.2 requires the regional failover runbook. " +
    "The release cannot start until the backup region passes validation.",
  sourceDescription: "Atlas Gateway release runbook",
  metadata: { source: "product_docs", product: "atlas-gateway" },
});

const incidentEpisode = await client.graph.add({
  graphId,
  type: "message",
  data:
    "Nina (incident commander): The backup region failed validation. " +
    "Move the Argus release review to Friday.",
  sourceDescription: "Incident response channel",
  metadata: { source: "incident_channel", incident_id: "INC-482" },
});
```

**`Go`**

```go Go
projectRecord, err := json.Marshal(map[string]string{
    "project_id": "ARG-001",
    "name":       "Project Argus",
    "status":     "at risk",
    "product":    "Atlas Gateway",
    "owner":      "Platform Engineering",
})
if err != nil {
    log.Fatal(err)
}

projectSource := "Project record from the project system"
_, err = client.Graph.Add(context.TODO(), &zep.AddDataRequest{
    GraphID:          &graphID,
    Type:             zep.GraphDataTypeJSON,
    Data:             string(projectRecord),
    SourceDescription: &projectSource,
    Metadata: map[string]any{
        "source":    "project_system",
        "record_id": "ARG-001",
    },
})
if err != nil {
    log.Fatal(err)
}

productSource := "Atlas Gateway release runbook"
_, err = client.Graph.Add(context.TODO(), &zep.AddDataRequest{
    GraphID: &graphID,
    Type:    zep.GraphDataTypeText,
    Data: "Atlas Gateway release 4.2 requires the regional failover runbook. " +
        "The release cannot start until the backup region passes validation.",
    SourceDescription: &productSource,
    Metadata: map[string]any{
        "source":  "product_docs",
        "product": "atlas-gateway",
    },
})
if err != nil {
    log.Fatal(err)
}

incidentSource := "Incident response channel"
incidentEpisode, err := client.Graph.Add(context.TODO(), &zep.AddDataRequest{
    GraphID: &graphID,
    Type:    zep.GraphDataTypeMessage,
    Data: "Nina (incident commander): The backup region failed validation. " +
        "Move the Argus release review to Friday.",
    SourceDescription: &incidentSource,
    Metadata: map[string]any{
        "source":      "incident_channel",
        "incident_id": "INC-482",
    },
})
if err != nil {
    log.Fatal(err)
}
```

For an existing corpus or a recurring import, use
[`zep-ingest`](/zep-ingest). For large application-managed imports, use the
[Batch API](/adding-batch-data).

## Wait until the context is searchable

Zep processes episodes asynchronously. Poll the last submitted episode, and
then retry the task query until the search index returns a result. The
[ingestion status guide](/check-data-ingestion-status) explains the processing
order and production alternatives to polling.

**`Python`**

```python Python
query = "What blocks the Project Argus release, and what changed?"
deadline = time.monotonic() + 300

while True:
    episode = client.graph.episode.get(uuid_=incident_episode.uuid_)
    if episode.processed:
        break
    if time.monotonic() >= deadline:
        raise TimeoutError("The project context did not finish processing.")
    time.sleep(5)

search_deadline = time.monotonic() + 300
while True:
    indexed_results = client.graph.search(
        graph_id=GRAPH_ID,
        query=query,
        scope="edges",
        search_filters={"episode_uuids": [incident_episode.uuid_]},
        limit=10,
    )
    if indexed_results.edges:
        break
    if time.monotonic() >= search_deadline:
        raise TimeoutError("The project context is not searchable.")
    time.sleep(5)

results = client.graph.search(
    graph_id=GRAPH_ID,
    query=query,
    scope="edges",
    limit=10,
)
```

**`TypeScript`**

```typescript TypeScript
const query = "What blocks the Project Argus release, and what changed?";
const deadline = Date.now() + 300_000;
const sleep = (ms: number) =>
  new Promise((resolve) => setTimeout(resolve, ms));

let episode = await client.graph.episode.get(incidentEpisode.uuid);
while (!episode.processed) {
  if (Date.now() >= deadline) {
    throw new Error("The project context did not finish processing.");
  }
  await sleep(5_000);
  episode = await client.graph.episode.get(incidentEpisode.uuid);
}

const searchDeadline = Date.now() + 300_000;
let indexedResults = await client.graph.search({
  graphId,
  query,
  scope: "edges",
  searchFilters: { episodeUuids: [incidentEpisode.uuid] },
  limit: 10,
});
while ((indexedResults.edges ?? []).length === 0) {
  if (Date.now() >= searchDeadline) {
    throw new Error("The project context is not searchable.");
  }
  await sleep(5_000);
  indexedResults = await client.graph.search({
    graphId,
    query,
    scope: "edges",
    searchFilters: { episodeUuids: [incidentEpisode.uuid] },
    limit: 10,
  });
}

const results = await client.graph.search({
  graphId,
  query,
  scope: "edges",
  limit: 10,
});
```

**`Go`**

```go Go
deadline := time.Now().Add(5 * time.Minute)
for {
    episode, err := client.Graph.Episode.Get(
        context.TODO(),
        incidentEpisode.UUID,
    )
    if err != nil {
        log.Fatal(err)
    }
    if episode.Processed != nil && *episode.Processed {
        break
    }
    if time.Now().After(deadline) {
        log.Fatal("the project context did not finish processing")
    }
    time.Sleep(5 * time.Second)
}

limit := 10
query := "What blocks the Project Argus release, and what changed?"
searchDeadline := time.Now().Add(5 * time.Minute)
searchFilters := zep.SearchFilters{
    EpisodeUUIDs: []string{incidentEpisode.UUID},
}
for {
    indexedResults, err := client.Graph.Search(
        context.TODO(),
        &zep.GraphSearchQuery{
            GraphID:        &graphID,
            Query:          query,
            Scope:          zep.GraphSearchScopeEdges.Ptr(),
            SearchFilters:  &searchFilters,
            Limit:          &limit,
        },
    )
    if err != nil {
        log.Fatal(err)
    }
    if len(indexedResults.Edges) > 0 {
        break
    }
    if time.Now().After(searchDeadline) {
        log.Fatal("the project context is not searchable")
    }
    time.Sleep(5 * time.Second)
}

results, err := client.Graph.Search(context.TODO(), &zep.GraphSearchQuery{
    GraphID: &graphID,
    Query:   query,
    Scope:   zep.GraphSearchScopeEdges.Ptr(),
    Limit:   &limit,
})
if err != nil {
    log.Fatal(err)
}
```

## Retrieve context for a task

Search the same Context Graph with the task as the query. The result can contain
facts that connect the project record, product runbook, and incident update.

**`Python`**

```python Python
for edge in results.edges or []:
    print(edge.fact, edge.episodes)
```

**`TypeScript`**

```typescript TypeScript
for (const edge of results.edges ?? []) {
  console.log(edge.fact, edge.episodes);
}
```

**`Go`**

```go Go
for _, edge := range results.Edges {
    fmt.Println(edge.Fact, edge.Episodes)
}
```

Treat the results as untrusted reference data when you add them to a model
request. Context supports task completion, but it does not guarantee the
model's output or authorize an action.

## Apply governance

Use [policy-based access control](/policy-based-access-control) to limit which
callers can retrieve the graph or its sources. Retain the episode UUIDs in each
retrieved edge when the application must [trace a fact to its source](/source-traceability).

Zep governs context and API access. Your application must authorize external
actions, such as changing the project status or starting a release.

## Next steps

* [Prepare data for ingestion](/prepare-data-for-ingestion).
* [Shape the graph for a domain](/customizing-graph-structure).
* [Build a custom Context Block](/advanced-context-block-construction).
* [Search a Context Graph](/searching-the-graph).