Skip to content
Contexta
Compare · Migration

Move to Contexta in a weekend.

Migration guides from popular memory and RAG stacks. Each guide preserves your existing ids for dual-write, maps your schema onto the Contexta graph, and ends with the first Reflex you could not author before.

From Letta

Letta

Promote agent-local memory blocks to a shared context layer.

Read the guide →
From custom Pinecone-RAG

Pinecone RAG

Keep the embeddings; add graph, time, and provenance.

Read the guide →
From Mem0

Migrate from Mem0

Mem0 stores extracted facts and supports multi-level memory across user, agent, and session. A Mem0 migration is mostly a mapping exercise: each Mem0 memory becomes a node in Contexta with the original Mem0 id preserved as an `external_ref` for round-tripping. Mem0 metadata (user_id, agent_id, run_id) maps onto Contexta namespaces and tags, so existing slicing keeps working. Once nodes land, the optional second pass extracts edges — relationships Mem0 stored implicitly through fact phrasing. The third pass attaches provenance: source URI, ingestion time, confidence. With the corpus shaped, Reflexes light up the parts that Mem0 cannot express — motif completion, drift, absence. Most teams finish in a weekend with the corpus dual-written for a week before flipping reads. The Mem0 client stays alive during the transition; reads can hit Contexta with a thin shim that adapts the Packet back to a Mem0-style flat list.

Five steps

  1. Export Mem0 memories via the `mem0.get_all()` endpoint, scoped per user_id and agent_id.
  2. Import as Contexta nodes; preserve Mem0 ids in `external_ref` so existing references resolve.
  3. Run the entity-resolution pass to merge duplicates Mem0 deduped only at the string level.
  4. Attach provenance (source, ingested_at, confidence) and dual-write for one week.
  5. Flip reads to Contexta; author the first Reflex on a pattern Mem0 could not detect.
import { Contexta } from '@contexta/sdk';
import { Mem0 } from 'mem0ai';

const ctx = new Contexta({ apiKey: process.env.CONTEXTA_API_KEY });
const mem0 = new Mem0({ apiKey: process.env.MEM0_API_KEY });

const memories = await mem0.getAll({ userId: 'user_123' });

await ctx.import({
  namespace: 'user_123',
  source: 'mem0',
  records: memories.map((m) => ({
    externalRef: m.id,
    content: m.memory,
    metadata: m.metadata,
    createdAt: m.createdAt,
  })),
});
From Zep

Migrate from Zep

Zep ships a temporal knowledge graph (Graphiti) with valid-time edges. The migration story is therefore the easiest of the five — the temporal model carries over almost verbatim. Zep nodes become Contexta nodes; Zep edges become Contexta edges with the same valid-from / valid-to bounds. Session-level chat history maps to a Conversation node with utterance nodes attached. The structural upgrade is what Zep does not express: Reflexes on motif completion, surgical forget with provenance cascade, and policy-aware retrieval. Most teams keep Zep online for a week of dual-write while migrating retrievers; Contexta exposes a Zep-compatible context-assembly endpoint so existing prompt templates keep working until you choose to switch them. The longer-term payoff is governance: namespaces, KMS profiles, audit ledgers, and replayable retraction — features Zep does not currently ship.

Five steps

  1. Export Zep facts and sessions via the management API; preserve valid-time bounds.
  2. Import nodes and edges into Contexta with `external_ref = zep_uuid`.
  3. Map Zep sessions onto Conversation nodes with utterance children.
  4. Point retrievers at the Zep-compatible Contexta endpoint during dual-write.
  5. Cut over reads and author Reflexes on motifs Zep cannot express.
import { Contexta } from '@contexta/sdk';
import { ZepClient } from '@getzep/zep-cloud';

const ctx = new Contexta({ apiKey: process.env.CONTEXTA_API_KEY });
const zep = new ZepClient({ apiKey: process.env.ZEP_API_KEY });

const facts = await zep.graph.search({ groupId: 'tenant_a', limit: 10_000 });

await ctx.importGraph({
  namespace: 'tenant_a',
  source: 'zep',
  nodes: facts.nodes.map((n) => ({ externalRef: n.uuid, ...n })),
  edges: facts.edges.map((e) => ({
    externalRef: e.uuid,
    from: e.sourceNodeUuid,
    to: e.targetNodeUuid,
    validFrom: e.validAt,
    validTo: e.invalidAt,
  })),
});
From Letta

Migrate from Letta

Letta agents own their memory as editable blocks inside the agent runtime. Migration is therefore a re-shaping job: each Letta block becomes a node in Contexta, scoped to the agent's namespace. Block content is parsed for entities and relationships during import so the flat block text gains graph structure. Letta core_memory blocks become long-lived "agent profile" nodes; recall_memory becomes timestamped utterance nodes. Once shaped, the agent calls Contexta for retrieval and writes new observations back as nodes — but you keep Letta for the agent loop itself. The net effect: Letta still runs the agent, but the memory is now graph-native, bi-temporal, and reactive, and it is shared across agents instead of locked inside one runtime.

Five steps

  1. Export Letta agent state via the `letta.agents.export()` endpoint.
  2. Import core_memory blocks as profile nodes; recall_memory as timestamped events.
  3. Run entity extraction over block text to populate the graph.
  4. Swap the Letta memory tool for the Contexta retrieval tool inside the agent.
  5. Author the first Reflex that fires across agents — something Letta cannot express.
import { Contexta } from '@contexta/sdk';
import { LettaClient } from '@letta-ai/letta-client';

const ctx = new Contexta({ apiKey: process.env.CONTEXTA_API_KEY });
const letta = new LettaClient({ apiKey: process.env.LETTA_API_KEY });

const state = await letta.agents.export({ agentId: 'agent_42' });

await ctx.import({
  namespace: 'agent_42',
  source: 'letta',
  records: [
    ...state.coreMemory.map((b) => ({ kind: 'profile', content: b.value })),
    ...state.recallMemory.map((m) => ({
      kind: 'event',
      content: m.text,
      createdAt: m.timestamp,
    })),
  ],
});
From custom Pinecone-RAG

Migrate from Pinecone RAG

Custom Pinecone-RAG stacks are the most common starting point. The shape is familiar: a chunker, an embedder, Pinecone, a retriever, an LLM. The migration does not throw any of it away — Contexta uses embeddings under the hood and can either ingest Pinecone vectors directly or call into your existing index. The lift is the graph: each chunk becomes a node, the source document becomes a parent node, and an entity-extraction pass surfaces edges Pinecone could never store. Citations get a contract — every retrieved fact carries the source URI and span. Bi-temporal validity attaches to each fact, so "what did we believe in March" becomes a query rather than a forensic exercise. Reflexes turn the corpus from a passive index into a reactive substrate.

Five steps

  1. Mirror your Pinecone namespace into Contexta; preserve vector ids in `external_ref`.
  2. Re-attach chunks to source documents so the parent edge exists.
  3. Run the entity-extraction pass to populate edges Pinecone never stored.
  4. Replace your retriever with the Contexta Packet API; keep the chunker.
  5. Author Reflexes for the patterns your existing eval set complained about.
import { Contexta } from '@contexta/sdk';
import { Pinecone } from '@pinecone-database/pinecone';

const ctx = new Contexta({ apiKey: process.env.CONTEXTA_API_KEY });
const pc = new Pinecone({ apiKey: process.env.PINECONE_API_KEY });
const index = pc.index('docs');

const { matches } = await index.query({
  topK: 10_000,
  vector: new Array(1536).fill(0),
  includeMetadata: true,
});

await ctx.importVectors({
  namespace: 'docs',
  source: 'pinecone',
  records: matches.map((m) => ({
    externalRef: m.id,
    embedding: m.values,
    content: String(m.metadata?.text ?? ''),
    sourceUri: String(m.metadata?.source ?? ''),
  })),
});
From LangMem

Migrate from LangMem

LangMem ships memory primitives inside the LangChain ecosystem — namespaces, semantic memories, and reflective writes triggered from agent runs. The migration mirrors the Mem0 path with one twist: LangMem memories are typically already scoped to a LangGraph thread, so the thread maps cleanly onto a Conversation node in Contexta. Memories become nodes within the conversation, and the reflective-write trigger becomes a Reflex that fires on conversation turns. The dual-write window is shorter because LangMem deployments are usually newer and lower-volume than Mem0. The payoff is independence from the LangChain release cadence: Contexta sits behind whatever framework you pick this quarter, and your corpus survives the swap.

Five steps

  1. Export LangMem store contents via the LangGraph store API.
  2. Map thread_id onto a Conversation namespace in Contexta.
  3. Import memories as nodes; preserve `key` as `external_ref`.
  4. Rewrite reflective writes as Contexta Reflexes triggered on turn events.
  5. Point LangGraph nodes at the Contexta retrieval tool and retire LangMem.
import { Contexta } from '@contexta/sdk';
import { InMemoryStore } from '@langchain/langgraph';

const ctx = new Contexta({ apiKey: process.env.CONTEXTA_API_KEY });
const store: InMemoryStore = /* your LangMem-backed store */ undefined!;

const memories = await store.search(['thread_42', 'memories']);

await ctx.import({
  namespace: 'thread_42',
  source: 'langmem',
  records: memories.map((m) => ({
    externalRef: m.key,
    content: JSON.stringify(m.value),
    createdAt: m.createdAt,
  })),
});

Ready to migrate?

Bring your existing memory store. We will help you map your schema, plan the dual-write window, and ship the first Reflex.