Skip to content

Migration

This guide provides detailed instructions for migrating memories from other AI memory providers to MIF format.

MIF supports migration from:

ProviderDifficultyData Loss
Mem0LowMinimal
ZepLowNone
Letta (Agent File)MediumMinimal
SubcogLowNone
Basic MemoryLowMinimal
LangMemMediumMinimal

MIF uses three base memory types. When migrating, map provider-specific categories to these types:

Cognitive TypeUse ForProvider Category Examples
semanticFacts, decisions, preferences, knowledgepreference, fact, decision, context, learning
episodicEvents, sessions, incidents, conversationsepisode, event, session, conversation
proceduralProcesses, runbooks, patterns, how-topattern, procedure, workflow, template

Key Principle: The memoryType field uses one of the three base types, while specific categorization is expressed through the namespace field:

{
"memoryType": "semantic",
"namespace": "_semantic/decisions"
}

This preserves the original provider category in the namespace hierarchy while maintaining MIF compatibility.


Mem0 stores memories as JSON objects:

{
"id": "mem0_123abc",
"memory": "User prefers dark mode for all applications",
"user_id": "user_456",
"metadata": {
"category": "preference",
"created_at": "2026-01-15T10:30:00Z",
"custom_field": "value"
},
"hash": "abc123..."
}
Mem0 FieldMIF FieldNotes
id@idPrefix with urn:mif:
memorycontentDirect mapping
user_idnamespaceUse base type prefix (e.g., _semantic/preferences)
metadata.categorymemoryTypeMap to base types (semantic, episodic, procedural)
metadata.categorynamespace (second part)Preserve as namespace suffix (e.g., _semantic/{category})
metadata.created_atcreatedDirect mapping
metadata.*extensions.mem0.*Preserve in extensions
hashextensions.mem0.hashPreserve hash
import json
from datetime import datetime
import uuid
def mem0_to_mif(mem0_data: dict) -> dict:
"""Convert Mem0 memory to MIF format with base memory types."""
# Map category to base memoryType and namespace
# MIF uses semantic/episodic/procedural as base types
# Specific categorization is expressed via namespace
category_map = {
"preference": ("semantic", "_semantic/preferences"),
"fact": ("semantic", "_semantic/knowledge"),
"context": ("semantic", "_semantic/knowledge"),
"decision": ("semantic", "_semantic/decisions"),
"conversation": ("episodic", "_episodic/sessions"),
"event": ("episodic", "_episodic/sessions"),
"pattern": ("procedural", "_procedural/patterns"),
"procedure": ("procedural", "_procedural/runbooks"),
None: ("semantic", "_semantic/knowledge")
}
category = mem0_data.get("metadata", {}).get("category")
memory_type, namespace = category_map.get(category, ("semantic", "_semantic/knowledge"))
# Build MIF document
mif = {
"@context": "https://mif-spec.dev/schema/context.jsonld",
"@type": "Memory",
"@id": f"urn:mif:{mem0_data['id']}",
"memoryType": memory_type,
"content": mem0_data["memory"],
"created": mem0_data.get("metadata", {}).get(
"created_at",
datetime.utcnow().isoformat() + "Z"
),
"namespace": namespace,
}
# Preserve original data in extensions
mif["extensions"] = {
"mem0": {
"original_id": mem0_data["id"],
"original_category": category,
"user_id": mem0_data.get("user_id"),
"hash": mem0_data.get("hash"),
"metadata": {
k: v for k, v in mem0_data.get("metadata", {}).items()
if k not in ["category", "created_at"]
}
}
}
return mif
# Example usage
mem0_memory = {
"id": "mem0_123abc",
"memory": "User prefers dark mode",
"user_id": "user_456",
"metadata": {
"category": "preference",
"created_at": "2026-01-15T10:30:00Z"
}
}
mif_memory = mem0_to_mif(mem0_memory)
print(json.dumps(mif_memory, indent=2))
import json
from pathlib import Path
def migrate_mem0_export(input_file: str, output_dir: str):
"""Migrate a Mem0 export file to MIF memories."""
output_path = Path(output_dir)
output_path.mkdir(parents=True, exist_ok=True)
with open(input_file) as f:
mem0_memories = json.load(f)
for mem in mem0_memories:
mif = mem0_to_mif(mem)
# Write to file
memory_id = mif["@id"].split(":")[-1]
output_file = output_path / f"{memory_id}.memory.json"
with open(output_file, "w") as f:
json.dump(mif, f, indent=2)
print(f"Migrated {len(mem0_memories)} memories to {output_dir}")

Zep uses a temporal knowledge graph structure:

{
"uuid": "zep_789xyz",
"content": "User prefers dark mode for all applications",
"created_at": "2026-01-15T10:30:00Z",
"metadata": {
"source": "conversation"
},
"temporal": {
"t_valid": "2026-01-15T00:00:00Z",
"t_invalid": null
},
"entity_edges": [
{
"source": "user:jane",
"target": "concept:dark-mode",
"relation": "prefers"
}
],
"embedding": [0.1, 0.2, ...]
}
Zep FieldMIF FieldNotes
uuid@idPrefix with urn:mif:
contentcontentDirect mapping
created_atcreated, temporal.recordedAtBoth fields
temporal.t_validtemporal.validFromDirect mapping
temporal.t_invalidtemporal.validUntilDirect mapping
entity_edgesrelationshipsTransform to MIF format
embeddingembedding.vectorUriStore externally
def zep_to_mif(zep_data: dict) -> dict:
"""Convert Zep memory to MIF format."""
mif = {
"@context": "https://mif-spec.dev/schema/context.jsonld",
"@type": "Memory",
"@id": f"urn:mif:{zep_data['uuid']}",
"memoryType": "semantic", # Default to semantic for factual memories
"content": zep_data["content"],
"created": zep_data["created_at"],
"namespace": "_semantic/knowledge", # Categorize via namespace
}
# Map temporal data
if "temporal" in zep_data:
mif["temporal"] = {
"@type": "TemporalMetadata",
"validFrom": zep_data["temporal"].get("t_valid"),
"validUntil": zep_data["temporal"].get("t_invalid"),
"recordedAt": zep_data["created_at"]
}
# Map entity edges to relationships
if "entity_edges" in zep_data:
mif["relationships"] = []
for edge in zep_data["entity_edges"]:
mif["relationships"].append({
"@type": "Relationship",
"relationshipType": map_zep_relation(edge["relation"]),
"target": {
"@id": f"urn:mif:entity:{edge['target'].replace(':', ':')}"
},
"metadata": {
"source": edge["source"],
"original_relation": edge["relation"]
}
})
# Reference embedding (store vectors separately)
if "embedding" in zep_data:
mif["embedding"] = {
"@type": "EmbeddingReference",
"model": "zep-default",
"sourceText": zep_data["content"],
"vectorUri": f"urn:mif:vector:{zep_data['uuid']}"
}
return mif
def map_zep_relation(relation: str) -> str:
"""Map Zep relation types to MIF relationship types."""
mapping = {
"prefers": "RelatesTo",
"uses": "Uses",
"knows": "RelatesTo",
"created": "Created",
"part_of": "PartOf",
}
return mapping.get(relation, "RelatesTo")

Letta uses memory blocks within agent files:

{
"agent_state": {
"memory": {
"human": {
"label": "human",
"value": "Name: Alice Johnson. Age: 32. Occupation: Software Engineer. Prefers dark mode. Uses Python and TypeScript.",
"limit": 5000
},
"persona": {
"label": "persona",
"value": "I am a helpful coding assistant...",
"limit": 3000
}
}
}
}

Letta stores concatenated facts in a single block. Migration requires:

  1. Parse the block content into discrete facts
  2. Create individual MIF memories for each fact
  3. Link related memories together
import re
from datetime import datetime
def letta_to_mif(letta_data: dict) -> list:
"""Convert Letta agent memory to MIF memories with base memory types."""
memories = []
memory_blocks = letta_data.get("agent_state", {}).get("memory", {})
for block_name, block in memory_blocks.items():
# Skip persona blocks (those are prompts, not memories)
if block_name == "persona":
continue
# Parse the block value into facts
facts = parse_letta_block(block["value"])
for i, fact in enumerate(facts):
memory_type, namespace = classify_fact(fact)
mif = {
"@context": "https://mif-spec.dev/schema/context.jsonld",
"@type": "Memory",
"@id": f"urn:mif:letta-{block_name}-{i}",
"memoryType": memory_type,
"content": fact,
"created": datetime.utcnow().isoformat() + "Z",
"namespace": namespace,
"provenance": {
"sourceType": "external_import",
"confidence": 0.8,
"trustLevel": "moderate_confidence"
},
"extensions": {
"letta": {
"block": block_name,
"original_limit": block.get("limit")
}
}
}
memories.append(mif)
return memories
def parse_letta_block(value: str) -> list:
"""Parse a Letta block into individual facts."""
# Split by sentence-ending punctuation
sentences = re.split(r'(?<=[.!?])\s+', value)
facts = []
for sentence in sentences:
sentence = sentence.strip()
if sentence and len(sentence) > 10: # Skip very short fragments
facts.append(sentence)
return facts
def classify_fact(fact: str) -> tuple:
"""Classify a fact into base memory type and namespace.
Returns:
Tuple of (memory_type, namespace)
"""
fact_lower = fact.lower()
# Procedural: how-to, processes, patterns
if any(kw in fact_lower for kw in ["step", "process", "how to", "procedure", "workflow"]):
return ("procedural", "_procedural/patterns")
# Episodic: events, sessions, incidents
elif any(kw in fact_lower for kw in ["happened", "occurred", "yesterday", "today", "session"]):
return ("episodic", "_episodic/sessions")
# Semantic: facts, preferences, decisions (default)
elif "prefer" in fact_lower or "like" in fact_lower:
return ("semantic", "_semantic/preferences")
elif "decided" in fact_lower or "chose" in fact_lower:
return ("semantic", "_semantic/decisions")
else:
return ("semantic", "_semantic/knowledge")

Subcog format is closest to MIF (MIF was partially inspired by Subcog):

{
"id": "subcog_abc123",
"content": "Decision: Use PostgreSQL for data storage",
"namespace": "decisions",
"domain": "project",
"tags": ["database", "architecture"],
"created_at": "2026-01-15T10:30:00Z",
"metadata": {
"confidence": 0.95,
"source": "conversation"
}
}
Subcog FieldMIF FieldNotes
id@idPrefix with urn:mif:
contentcontentDirect mapping
namespacememoryType + namespaceMap to base type and MIF namespace
domainextensions.subcog.domainPreserved in extensions
tagstagsDirect mapping
created_atcreatedDirect mapping
metadata.confidenceprovenance.confidenceDirect mapping
from datetime import datetime
def subcog_to_mif(subcog_data: dict) -> dict:
"""Convert Subcog memory to MIF format with base memory types."""
# Map Subcog namespace to base memoryType and MIF namespace
namespace_map = {
"decisions": ("semantic", "_semantic/decisions"),
"patterns": ("procedural", "_procedural/patterns"),
"learnings": ("semantic", "_semantic/knowledge"),
"preferences": ("semantic", "_semantic/preferences"),
"facts": ("semantic", "_semantic/knowledge"),
"context": ("semantic", "_semantic/knowledge"),
"incidents": ("episodic", "_episodic/incidents"),
"sessions": ("episodic", "_episodic/sessions"),
"blockers": ("episodic", "_episodic/blockers"),
"runbooks": ("procedural", "_procedural/runbooks"),
"migrations": ("procedural", "_procedural/migrations"),
}
subcog_ns = subcog_data.get("namespace", "")
memory_type, mif_namespace = namespace_map.get(subcog_ns, ("semantic", "_semantic/knowledge"))
mif = {
"@context": "https://mif-spec.dev/schema/context.jsonld",
"@type": "Memory",
"@id": f"urn:mif:{subcog_data['id']}",
"memoryType": memory_type,
"content": subcog_data["content"],
"created": subcog_data.get("created_at", datetime.utcnow().isoformat() + "Z"),
"namespace": mif_namespace,
"tags": subcog_data.get("tags", []),
}
# Map metadata to provenance
if "metadata" in subcog_data:
mif["provenance"] = {
"sourceType": "external_import",
"confidence": subcog_data["metadata"].get("confidence", 0.8),
}
# Preserve extensions
mif["extensions"] = {
"subcog": {
"original_id": subcog_data["id"],
"original_namespace": subcog_ns,
"domain": subcog_data.get("domain")
}
}
return mif

Basic text files or simple JSON:

preferences.txt
dark mode: enabled
editor: vim
language: python

Or JSON:

{
"preferences": {
"dark_mode": true,
"editor": "vim",
"language": "python"
}
}
def text_to_mif(text_file: str) -> list:
"""Convert a plain text file to MIF memories."""
memories = []
with open(text_file) as f:
lines = f.readlines()
for i, line in enumerate(lines):
line = line.strip()
if not line or line.startswith("#"):
continue
# Parse key: value format
if ":" in line:
key, value = line.split(":", 1)
content = f"{key.strip()}: {value.strip()}"
else:
content = line
mif = {
"@context": "https://mif-spec.dev/schema/context.jsonld",
"@type": "Memory",
"@id": f"urn:mif:import-{uuid.uuid4()}",
"memoryType": "semantic", # Base memory type
"namespace": "_semantic/preferences" if ":" in line else "_semantic/knowledge",
"content": content,
"created": datetime.utcnow().isoformat() + "Z",
"provenance": {
"sourceType": "external_import",
"confidence": 0.7
}
}
memories.append(mif)
return memories

LangMem uses a graph-based structure:

{
"memories": [
{
"id": "mem_123",
"text": "User prefers dark mode",
"embedding": [...],
"metadata": {
"timestamp": "2026-01-15T10:30:00Z",
"type": "semantic"
}
}
],
"relationships": [
{
"source": "mem_123",
"target": "mem_456",
"type": "related"
}
]
}
from datetime import datetime
def langmem_to_mif(langmem_data: dict) -> list:
"""Convert LangMem export to MIF memories with base memory types."""
# Build relationship lookup
relationships = {}
for rel in langmem_data.get("relationships", []):
source = rel["source"]
if source not in relationships:
relationships[source] = []
relationships[source].append({
"target": rel["target"],
"type": rel["type"]
})
# Convert memories
mif_memories = []
for mem in langmem_data.get("memories", []):
langmem_type = mem.get("metadata", {}).get("type")
memory_type, namespace = map_langmem_type(langmem_type)
mif = {
"@context": "https://mif-spec.dev/schema/context.jsonld",
"@type": "Memory",
"@id": f"urn:mif:{mem['id']}",
"memoryType": memory_type,
"namespace": namespace,
"content": mem["text"],
"created": mem.get("metadata", {}).get(
"timestamp",
datetime.utcnow().isoformat() + "Z"
),
}
# Add relationships
if mem["id"] in relationships:
mif["relationships"] = [
{
"@type": "Relationship",
"relationshipType": map_langmem_relation(rel["type"]),
"target": {"@id": f"urn:mif:{rel['target']}"}
}
for rel in relationships[mem["id"]]
]
mif_memories.append(mif)
return mif_memories
def map_langmem_type(langmem_type: str) -> tuple:
"""Map LangMem memory type to MIF base type and namespace.
LangMem already uses base type naming, so we preserve
the type and map to appropriate MIF namespaces.
Returns:
Tuple of (memory_type, namespace)
"""
mapping = {
"semantic": ("semantic", "_semantic/knowledge"),
"episodic": ("episodic", "_episodic/sessions"),
"procedural": ("procedural", "_procedural/patterns"),
}
return mapping.get(langmem_type, ("semantic", "_semantic/knowledge"))
def map_langmem_relation(relation: str) -> str:
"""Map LangMem relation to MIF relationship type."""
mapping = {
"related": "RelatesTo",
"derived": "DerivedFrom",
"supersedes": "Supersedes",
"conflicts": "ConflictsWith",
}
return mapping.get(relation, "RelatesTo")

After migrating, validate your MIF documents:

Terminal window
# Validate all migrated memories
for f in memories/*.json; do
npx ajv validate -s schema/mif.schema.json -d "$f" || echo "FAILED: $f"
done

If validation fails for missing fields, ensure:

  • @context is set to "https://mif-spec.dev/schema/context.jsonld"
  • @type is "Memory"
  • @id starts with urn:mif:
  • created is a valid ISO 8601 datetime

Map provider-specific types to MIF types:

  • semantic -> fact
  • episodic -> episode
  • procedural -> pattern
  • Unknown -> memory

When relationship types don’t map directly:

  1. Use RelatesTo as fallback
  2. Store original type in metadata
  3. Document custom mappings

Store embeddings externally and reference via vectorUri:

"embedding": {
"model": "original-model",
"sourceText": "...",
"vectorUri": "file://vectors/memory-id.bin"
}