Skip to main content
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.
Migrating a large amount of data? Chat with us and we’ll help you plan it.

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:
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. You never construct COGX records by hand. You hand a source object to cognee.remember() and it does the rest:

Quickstart: import from Mem0

The import itself is a single call — construct a Mem0Source from your export and hand it to cognee.remember():
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.
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.
Requires LLM_API_KEY to be set (in .env or your environment) — both the import and recall() use the LLM.
migrate_from_mem0.py
A fully runnable version of this walkthrough ships with the repo: examples/tutorials/migrate_from_mem0_tutorial.py. It imports a bundled sample export of four mem0 memories (examples/tutorials/data/mem0_export.json) in preserve mode — calling cognee.cognify() right after, since a preserve import alone isn’t recall-queryable (see 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:

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.
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.
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 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().
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.
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
  • message history → one conversation episode per agent
  • archival memory → one document per passage
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
ZepSource reads a JSON export of a Zep or Graphiti knowledge graph and imports:
  • episodes (verbatim ingested content)
  • entity nodes
  • relation edges (“facts”), 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.
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.

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:

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

Next steps

remember()

How import and ingestion work under the hood

recall()

Query your migrated memory

COGX Exchange Format

The portable format behind every import and export

Configuration

Configure LLM, embedding, and storage backends