> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cognee.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Migrate Memory Systems with COGX

> Import memories from Mem0, LangMem, Letta, Zep, Graphiti, or another Cognee instance using the COGX exchange format.

Already storing memories in another system? Cognee can import them directly into
a knowledge graph — no manual reformatting. This guide explains the **COGX**
format that makes that possible, walks through a runnable Mem0 example, and
lists every other source you can import from.

<Note>
  Migrating a large amount of data? [Chat with us](https://calendly.com/vasilije-topoteretes/) and we'll help you plan it.
</Note>

## What is COGX?

**COGX (the Cognee eXchange format) is a common shape that all memory imports
are translated into before they enter Cognee.** Instead of writing one importer
for every memory tool, Cognee defines a single intermediate format and a single
loader:

```
Mem0 / LangMem / Letta / Zep / Graphiti  ──►  COGX records  ──►  cognee.remember()  ──►  knowledge graph
             (a "source")                    (common shape)        (one loader)         (queryable memory)
```

A **source** is a small adapter that reads one provider's export and emits COGX
records. Because every source produces the same COGX shape, the rest of the
pipeline — loading, graph extraction, storage — is identical no matter where
your data came from. COGX is also what `cognee.export()` writes, so the same
format powers backup, restore, and Cognee-to-Cognee migration. For a full
breakdown of the format and its record kinds, see
[COGX Exchange Format](/core-concepts/further-concepts/cogx).

You never construct COGX records by hand. You hand a source object to
`cognee.remember()` and it does the rest:

```python theme={null}
import cognee
from cognee.migration import Mem0Source

await cognee.remember(Mem0Source("mem0_export.json"), dataset_name="my_memories")
```

## Quickstart: import from Mem0

The import itself is a single call — construct a `Mem0Source` from your export
and hand it to `cognee.remember()`:

```python theme={null}
result = await cognee.remember(
    Mem0Source(MEM0_MEMORIES, mode="re-derive"),
    dataset_name=DATASET,
)
```

That's the whole migration: read the export with `Mem0Source`, pass it to
`cognee.remember()`, then query with `cognee.recall()`. Your Mem0 memories are
now a queryable Cognee knowledge graph.

<Accordion title="Simple runnable example" icon="play">
  This script uses a small **inline sample** in the exact shape Mem0 returns, so
  you can run it without a Mem0 account. Swap in real data using the patterns at
  the [bottom of this page](#using-real-data).

  <Note>
    Requires `LLM_API_KEY` to be set (in `.env` or your environment) — both the
    import and `recall()` use the LLM.
  </Note>

  ```python migrate_from_mem0.py theme={null}
  import asyncio

  import cognee
  from cognee.migration import Mem0Source

  DATASET = "mem0_import"

  # A sample Mem0 export — exactly the shape Mem0 produces: a list of memory
  # objects (the OSS `client.get_all()` result, or the items inside a platform
  # export's {"results": [...]} wrapper).
  MEM0_MEMORIES = [
      {
          "id": "0a1b2c3d",
          "memory": "Alex is a senior backend engineer who owns the payments service.",
          "user_id": "alex",
          "categories": ["work", "role"],
          "created_at": "2026-05-01T10:00:00Z",
      },
      {
          "id": "1b2c3d4e",
          "memory": "Alex prefers Python and is wary of premature microservices.",
          "user_id": "alex",
          "categories": ["preferences"],
          "created_at": "2026-05-02T09:30:00Z",
      },
      {
          "id": "2c3d4e5f",
          "memory": "The payments service had a timeout incident caused by a missing DB index.",
          "user_id": "alex",
          "categories": ["incident"],
          "created_at": "2026-05-10T14:15:00Z",
      },
  ]


  async def main() -> None:
      # Start clean so the example is reproducible.
      await cognee.forget(everything=True)

      # Import the Mem0 memories. mode="re-derive" (the default) runs Cognee's own
      # extraction over each memory, building a real entity/relationship graph.
      print(f"Importing {len(MEM0_MEMORIES)} Mem0 memories...")
      result = await cognee.remember(
          Mem0Source(MEM0_MEMORIES, mode="re-derive"),
          dataset_name=DATASET,
      )
      print(f"Done: {result}\n")

      # Query the migrated memory.
      for question in (
          "What does Alex work on?",
          "What caused the payments service incident?",
          "What are Alex's technical preferences?",
      ):
          answer = await cognee.recall(question, datasets=[DATASET])
          print(f"Q: {question}")
          print(f"A: {answer}\n")


  if __name__ == "__main__":
      asyncio.run(main())
  ```
</Accordion>

A fully runnable version of this walkthrough ships with the repo:
[`examples/demos/ingestion_and_migration/migrate_from_mem0/migrate_from_mem0.py`](https://github.com/topoteretes/cognee/blob/dev/examples/demos/ingestion_and_migration/migrate_from_mem0/migrate_from_mem0.py).
It imports a bundled sample export of four mem0 memories
([`data/mem0_export.json`](https://github.com/topoteretes/cognee/blob/dev/examples/demos/ingestion_and_migration/migrate_from_mem0/data/mem0_export.json))
in `preserve` mode — calling `cognee.cognify()` right after, since a preserve
import alone isn't recall-queryable (see [Import modes](#import-modes) below) —
and verifies the result with two `recall()` queries, then clears everything,
re-imports the same export in `re-derive` mode, and queries again so you can
compare the two modes side by side. It finishes with `forget(everything=True)`
so re-runs start clean. Run it with:

```bash theme={null}
uv run python examples/demos/ingestion_and_migration/migrate_from_mem0/migrate_from_mem0.py
```

## Import modes

Every source accepts a `mode` argument that controls how much work Cognee does
on import. Pick it based on whether your source already has an extracted graph
and how much you want to spend on LLM calls.

| Mode        | What it does                                                                                                                                        | When to use it                                                                                                                             |
| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `re-derive` | Ingests the raw content and runs Cognee's own extraction (`cognify`). The source's own graph (if any) is ignored.                                   | You want the richest graph and don't mind the LLM cost. **Default for Mem0, LangMem, and Letta.**                                          |
| `preserve`  | Maps the source's already-extracted entities and facts straight into the graph with **zero LLM calls**. Raw content is stored but not re-processed. | Your source already has a good graph, or you want a fast, free, deterministic import. **Default for COGX archives.**                       |
| `hybrid`    | Keeps the source's graph **and** re-cognifies the raw content.                                                                                      | Your source has both verbatim content and a derived graph (e.g. Zep/Graphiti) and you want the best of both. **Default for Zep/Graphiti.** |

```python theme={null}
# Override the default for any source:
COGXArchiveSource("./archive", mode="hybrid")  # preserve the graph and re-cognify raw content
ZepSource(data, mode="re-derive")    # ignore Zep's graph, re-extract from scratch
```

Mem0 exports contain short memory records, not a graph, so use the default
`re-derive` mode for Mem0 imports when you want Cognee to build a knowledge
graph from those memories. `preserve` is also valid for Mem0: each memory is
stored as a raw data item with zero LLM calls — the cheapest way to get the
records in, ready for a later `cognify()` run.

That makes `preserve` a **two-call pattern** whenever you want to query the
imported memories: `remember()` lands the raw records, and a separate
`cognify()` builds the graph `recall()` reads from.

```python theme={null}
# 1. Land the raw memories — zero LLM calls, nothing queryable yet.
await cognee.remember(
    Mem0Source(MEM0_MEMORIES, mode="preserve"),
    dataset_name=DATASET,
)

# 2. Run extraction to make them recallable (this one does call the LLM).
await cognee.cognify(datasets=[DATASET])

answer = await cognee.recall("What does Alex work on?", datasets=[DATASET])
```

Skip step 2 and `recall()` returns nothing for the imported memories. With
`re-derive` or `hybrid` the extraction is part of the import, so no extra
`cognify()` call is needed. The
[runnable tutorial](https://github.com/topoteretes/cognee/blob/dev/examples/demos/ingestion_and_migration/migrate_from_mem0/migrate_from_mem0.py)
imports the same export both ways so you can compare.

## Other sources you can import from

Every source has the same interface — construct it from a file path (or
in-memory data) and pass it to `cognee.remember()`.

<AccordionGroup>
  <Accordion title="LangMem (JSON memory exports)" icon="note-sticky">
    `LangMemSource` reads a LangMem memory export and imports each item as an
    atomic **memory** record. It accepts a file path, an already-parsed list, or a
    dict wrapping the list under `memories`, `results`, `items`, or `data` — any
    other shape raises `ValueError`.

    Those aliases are scanned in that order, and only dict items count as records.
    An alias that is present but empty does not shadow a populated one later in the
    order, so `{"memories": [], "results": [...]}` imports the `results` records.
    A wrapper whose recognized aliases are all empty imports zero records without
    raising.

    Per memory:

    * **content** is the first string found among `content`, `text`, `memory`,
      `data`, and `message`; items with none of those are skipped
    * **scope** takes `user_id` (falling back to `namespace`), plus `agent_id`,
      `session_id`, and `run_id` when present
    * **categories** accepts either a single string or a list
    * **timestamps** are read from `created_at` / `createdAt` / `timestamp` and
      `updated_at` / `updatedAt`
    * any **`metadata`** is carried over nested under `langmem_metadata`

    Because LangMem memories are free-form text with no derived graph, it defaults
    to `re-derive` mode.

    ```python theme={null}
    from cognee.modules.migration.sources.langmem import LangMemSource

    await cognee.remember(LangMemSource("langmem_export.json"), dataset_name="langmem_import")
    ```
  </Accordion>

  <Accordion title="Letta / MemGPT (.af agent files)" icon="robot">
    `LettaSource` reads a Letta **Agent File** (`.af`, a JSON serialization of one
    or more agents) and imports:

    * **core memory blocks** → memory blocks in the graph (`COGXMemoryBlock`)
    * **message history** → one conversation episode per agent (`COGXEpisode`)
    * **archival memory** → one document per passage (`COGXDocument`)

    The parser tolerates key-name differences across Letta versions. Per message:

    * **text** is read from `content`, falling back to the `text` alias when
      `content` is missing *or* explicitly `null` — Letta serializers that write
      unset fields as `null` rather than omitting them import correctly. An
      explicitly empty `content` (`""`) counts as a message with no text and does
      not fall through to `text`
    * either key may hold a plain string or a list of typed parts, of which only
      the text parts are imported
    * messages that end up with no text, and messages whose role is `system` or
      `tool`, are skipped; if that leaves an agent with no messages, no
      conversation episode is emitted for it

    ```python theme={null}
    from cognee.migration import LettaSource

    await cognee.remember(LettaSource("my_agent.af"), dataset_name="letta_import")
    ```
  </Accordion>

  <Accordion title="Zep / Graphiti (graph exports)" icon="share-nodes">
    `ZepSource` reads a JSON export of a Zep or Graphiti knowledge graph and
    imports:

    * **episodes** (verbatim ingested content) → `COGXEpisode`
    * **entity nodes** → `COGXEntity`
    * **relation edges** ("facts") → `COGXFact`, including their bi-temporal
      `valid_at` / `invalid_at` validity windows

    All three record types resolve their scope `session_id` the same way: from
    `group_id`, falling back to a `session_id` key when `group_id` is absent. When
    a record carries both, `group_id` wins.

    It defaults to `hybrid` mode because Zep/Graphiti keep both verbatim episodes
    and a derived graph.

    ```python theme={null}
    from cognee.migration import ZepSource, GraphitiSource

    # Zep export
    await cognee.remember(ZepSource("zep_export.json"), dataset_name="zep_import")

    # OSS Graphiti export (same shape; produce the JSON from a Cypher dump of
    # EntityNode / EpisodicNode / RELATES_TO records)
    await cognee.remember(GraphitiSource("graphiti_export.json"), dataset_name="graphiti_import")
    ```
  </Accordion>

  <Accordion title="Another Cognee instance (COGX archive)" icon="box-archive">
    `COGXArchiveSource` re-imports an archive produced by `cognee.export(...,
        format="cogx")`. This is the restore half of backup/restore and the receiving
    end of Cognee-to-Cognee migration. It defaults to `preserve` mode (zero-LLM),
    because a Cognee archive already carries a fully extracted graph.

    ```python theme={null}
    from cognee.migration import COGXArchiveSource

    await cognee.remember(COGXArchiveSource("./my_cogx_archive"), dataset_name="restored")
    ```
  </Accordion>
</AccordionGroup>

### Runnable tutorial: Letta + Zep

A runnable walkthrough of the two accordions above ships with the repo:
[`examples/demos/ingestion_and_migration/migrate_from_letta_and_zep/migrate_from_letta_and_zep.py`](https://github.com/topoteretes/cognee/blob/dev/examples/demos/ingestion_and_migration/migrate_from_letta_and_zep/migrate_from_letta_and_zep.py).
It runs in three parts, each starting from `forget(everything=True)`:

1. **Letta** — imports a bundled sample agent file
   ([`sample_letta_dump.json`](https://github.com/topoteretes/cognee/blob/dev/examples/demos/ingestion_and_migration/migrate_from_letta_and_zep/data/sample_letta_dump.json):
   one agent with two core memory blocks, a three-message history, and two
   archival passages) in `re-derive` mode, then verifies it with two `recall()`
   queries.
2. **Zep** — imports a bundled sample graph export
   ([`sample_zep_dump.json`](https://github.com/topoteretes/cognee/blob/dev/examples/demos/ingestion_and_migration/migrate_from_letta_and_zep/data/sample_zep_dump.json):
   two episodes, four entities, and four facts, each carrying a `valid_at`
   timestamp and none an `invalid_at` end — every sample fact is still valid)
   in `hybrid` mode, then runs three `recall()` queries against it.
3. **Both together** — imports the same two dumps into one graph and queries
   across them, so you can see a combined Letta + Zep memory answer questions
   that draw on both sources.

It finishes with one more `forget(everything=True)`, so it leaves nothing behind
and re-runs start clean.

Because the dumps are bundled, you can run all three parts without a Letta or
Zep account — only `LLM_API_KEY` is needed (`re-derive`, `hybrid`, and `recall()`
all call the LLM).

```bash theme={null}
uv run python examples/demos/ingestion_and_migration/migrate_from_letta_and_zep/migrate_from_letta_and_zep.py
```

## Write your own source

If your memory tool isn't listed above, you can add it yourself. A source is a
small adapter class: subclass `MemorySource`, name the system it reads from,
and implement one async generator — `records()` — that yields COGX records.
Everything else (loading, modes, deduplication, graph storage) is handled by
the shared machinery, exactly as for the built-in sources.

Everything you need is importable from `cognee.migration`: the `MemorySource`
base class and the record models (`COGXDocument`, `COGXEpisode`, `COGXTurn`,
`COGXEntity`, `COGXFact`, `COGXMemory`, `COGXMemoryBlock`, `COGXScope`).

Here is a complete source for a hypothetical notes app that exports a JSON
list of `{"id", "text", "user", "tags"}` objects:

```python theme={null}
import json
from pathlib import Path
from typing import AsyncIterator

from cognee.migration import COGXMemory, COGXRecord, COGXScope, MemorySource


class AcmeNotesSource(MemorySource):
    source_system = "acme_notes"

    def __init__(self, export_path, mode: str = "re-derive"):
        super().__init__(mode=mode)  # validates mode: re-derive | preserve | hybrid
        self._export_path = Path(export_path)

    async def records(self) -> AsyncIterator[COGXRecord]:
        notes = json.loads(self._export_path.read_text(encoding="utf-8"))
        for note in notes:
            yield COGXMemory(
                external_system=self.source_system,
                external_id=str(note["id"]),
                content=note["text"],
                categories=note.get("tags", []),
                scope=COGXScope(user_id=note.get("user")),
            )
```

That's the whole integration — it plugs into `cognee.remember()` like any
built-in source:

```python theme={null}
await cognee.remember(AcmeNotesSource("acme_export.json"), dataset_name="acme_notes")
```

### Choosing record kinds

Pick the record type that matches what the source holds; the
[COGX concept page](/core-concepts/further-concepts/cogx) describes each in
detail. What happens to a record depends on its kind and the import mode:

| Your source holds                 | Emit                             | Processed in                                                                                                                                               |
| --------------------------------- | -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Files, passages, standalone text  | `COGXDocument`                   | `re-derive` / `hybrid` (cognified); stored raw in `preserve`                                                                                               |
| Conversations with turns          | `COGXEpisode` (with `COGXTurn`s) | same as documents — rendered as a timestamped transcript                                                                                                   |
| Short derived facts, notes        | `COGXMemory`                     | same as documents                                                                                                                                          |
| Named core-memory blocks          | `COGXMemoryBlock`                | same as documents                                                                                                                                          |
| An already-extracted entity graph | `COGXEntity` + `COGXFact`        | `preserve` / `hybrid` (mapped directly into the graph, zero LLM calls); in `re-derive` they're re-ingested as digest documents and re-extracted by cognify |

Nothing you emit is silently dropped except raw nodes and entities with no
`description`, both of which carry no standalone text for `re-derive` to
re-extract.

A `COGXFact` references its endpoints by `subject_ref` / `object_ref` — use the
`external_id` of an entity record you also emit, or a plain entity name. A
plain-name reference that doesn't match an emitted entity becomes a new entity
of that name, so a source can emit facts on their own. A reference that looks
like a UUID but matches no emitted record is skipped and logged, never turned
into an entity named by a UUID. A fact's `valid_at` / `invalid_at` validity
window is preserved as edge properties in the graph.

### Rules the loader relies on

* **Stable `external_id`s make re-import idempotent.** Each record's identity
  in Cognee is derived deterministically from
  `(external_system, external_id)`, so importing the same export twice
  doesn't duplicate data. Use the source system's own ids; only fall back to
  synthetic ids (e.g. an index) for records that genuinely have none.
* **`records()` should be re-callable.** The streaming `preserve`-mode import
  passes over the records three times — once to store the raw content, then
  twice for the graph (nodes first, then facts) — calling `records()` once per
  pass. File-backed sources get this for free by re-reading the file. If your
  source is a one-shot cursor (e.g. a live API stream), set the class attribute
  `replayable = False` to make the loader buffer records instead.
* **Preserve scope and timestamps.** Fill `COGXScope`
  (`user_id`/`agent_id`/`session_id`/`run_id`) and `created_at`/`updated_at`
  where the source has them — ownership and time information survive the
  migration only if the source carries them across. Anything that doesn't fit
  a typed field can go in the record's free-form `metadata` dict.
* **Pick a sensible default mode.** Match the [import mode](#import-modes) to
  whether your source carries a pre-built graph; callers can always override.

For a fuller reference implementation, read the built-in sources in
[`cognee/modules/migration/sources/`](https://github.com/topoteretes/cognee/tree/dev/cognee/modules/migration/sources)
— `mem0.py` is the smallest, and `zep.py` shows entity/fact emission. If your
source would be useful to others, PRs adding it there (exported in the
package's `__init__.py`, with a sample-export test) are welcome.

## Export: Cognee → COGX

Migration runs both ways. `cognee.export()` writes a dataset's graph to a
portable COGX archive that you can back up, move to another Cognee instance, or
re-import later with `COGXArchiveSource`:

```python theme={null}
import cognee

# Write a COGX archive directory
await cognee.export(dataset="mem0_import", format="cogx", destination="./my_cogx_archive")

# ...later, on any Cognee instance:
from cognee.migration import COGXArchiveSource
await cognee.remember(COGXArchiveSource("./my_cogx_archive"), dataset_name="restored")
```

## Using real data

The Mem0 example above uses an inline sample. Here's how to point any source at
real data.

**From a provider's client (live API).** Fetch with the provider's own SDK and
pass the response straight in — sources accept already-parsed Python lists and
dicts, not just file paths:

```python theme={null}
from mem0 import MemoryClient
from cognee.migration import Mem0Source

client = MemoryClient(api_key="...")
memories = client.get_all(user_id="alex")      # a list (or {"results": [...]})
await cognee.remember(Mem0Source(memories), dataset_name="mem0_import")
```

`Mem0Source` scans a wrapper dict for `results`, `memories`, then `items`, in
that order, counting only dict items as records; an alias that is present but
empty does not shadow a populated one later in the order, so
`{"results": [], "memories": [...]}` imports the `memories` records. A wrapper
whose recognized aliases are all empty imports zero records without raising,
and any other shape raises `ValueError`.

**From an exported file.** Point the source at the export on disk:

```python theme={null}
await cognee.remember(Mem0Source("mem0_export.json"), dataset_name="mem0_import")
```

## Next steps

<CardGroup cols={2}>
  <Card title="remember()" icon="brain" href="/core-concepts/main-operations/remember">
    How import and ingestion work under the hood
  </Card>

  <Card title="recall()" icon="magnifying-glass" href="/core-concepts/main-operations/recall">
    Query your migrated memory
  </Card>

  <Card title="COGX Exchange Format" icon="arrows-rotate" href="/core-concepts/further-concepts/cogx">
    The portable format behind every import and export
  </Card>

  <Card title="Configuration" icon="gear" href="/setup-configuration/overview">
    Configure LLM, embedding, and storage backends
  </Card>
</CardGroup>
