Database migrations
SkillDatabases & datacognee-migrations is a skill that guides an AI agent through cognee database migrations. It covers the two migration chains, the relational schema handled by Alembic and the graph and vector data migrations, which normally run automatically at startup or on the first write. It also covers checking and repairing migration state with cognee-cli commands and moving data between systems.
Then ask your AI: use the Database migrations skill
Details
Instructions available. Your AI can read the instructions. Execution depends on the setup they require.
Account requirements not reviewed. Check the skill instructions before use; Ahel provides instructions and does not run this skill.
No other account needed.
Have a cognee installation with its relational, graph, and vector stores configured.
What your AI can do with it
- Explain when cognee migrations run automatically and how to disable them
- Check stamped revisions and the last failure with cognee-cli current
- Repair migration state using cognee-cli upgrade, downgrade, and stamp
- Unblock writes refused after a failed migration
- Author a new Alembic revision or a graph and vector data migration
- Move data between systems with relational import, graph exports, and COGX archives
Getting started
- Have a cognee installation with its relational, graph, and vector stores configured.
- Decide whether automatic migrations should stay on; set ENABLE_AUTO_MIGRATIONS=false to turn them off and run cognee-cli upgrade yourself.
- Run cognee-cli current to see the stamped revision per database and the last failure.
- Run cognee-cli upgrade to bring the relational chain and then the data chain to head.
- For a specific data-chain target, pass its slug to cognee-cli upgrade, and use --alembic to pin the relational target.
What this skill tells your AI
The instructions your AI receives, as published by topoteretes/cognee in .agents/skills/cognee-migrations/SKILL.md and read by Ahel’s review.
cognee has two migration chains, run together:
| Chain | Changes | Lives in | Revision stored in |
|---|---|---|---|
| Relational schema (Alembic) | Tables and columns of the relational DB (users, datasets, ACLs, pipeline runs, …) | cognee/alembic/ (alembic.ini is in cognee/) | alembic_version table |
| Graph/vector data | Cross-store data rewrites (re-keying node ids, adding graph columns) across graph DB, vector DB and relational ledger | cognee/modules/migrations/ (registry.py, versions/) | Per dataset: dataset_database.migration_revision (access control on). Globally: global_database_version.global_migration_revision (access control off) |
Use it
They run by themselves
run_migrations() applies the relational chain first, then the data chain.
It runs:
- at API server startup (and in the Docker
entrypoint.shbefore gunicorn); - on the first write in an SDK or CLI process (
remember,add,cognify,improve,memify, memory imports), once per process; - when you call
await cognee.run_migrations().
A fresh database is built by running the whole chain (no stamping). At
head, the first run in each process still does a no-op Alembic upgrade plus
a scan of the per-database revision rows; later calls in the same process
are skipped by an in-memory flag. ENABLE_AUTO_MIGRATIONS=false
turns off all automatic runs; then run cognee-cli upgrade yourself.
Concurrent processes are serialized by a migration lock: a Postgres advisory lock (works across hosts) or a file lock next to the SQLite DB (one host only).
Check and repair
cognee-cli current # stamped revision per database, and the last failure
cognee-cli history # the data chain, newest first
cognee-cli upgrade # relational to head, then data chain to head
cognee-cli upgrade <slug> # data chain up to and including <slug>
cognee-cli upgrade --alembic <rev> # pin the relational target
cognee-cli downgrade <slug|base> [--dataset UUID ...] [--alembic REV] [--force]
cognee-cli stamp <head|base|slug> [--dataset UUID ...] [--force]
- The positional revision is always a data-chain slug; relational
targets go through
--alembic. downgraderewrites data and asks for confirmation. It only reverts spans where every migration defines adown(), and leaves the relational schema alone unless you pass--alembic.stampchanges only the stored data-chain revision, without running anything. Usestamp base --dataset <id>and thenupgradewhen a database's data drifted from its stamp (for example after restoring a backup); the chain is idempotent and converges it.upgraderuns even withENABLE_AUTO_MIGRATIONS=false.
A write is blocked
If a dataset's data migration failed, writes to that dataset are refused
until it succeeds (with access control off, any failure blocks all writes).
The server still starts. Run cognee-cli current to see the error, fix the
cause, then cognee-cli upgrade. A failed run is retried on the next start
or write.
Moving data between systems (not schema migrations)
| Goal | Use |
|---|---|
| Turn an existing relational database into a graph | migrate_relational_database(graph_db, schema) (cognee/tasks/ingestion/migrate_relational_database.py), with the source DB set by MIGRATION_DB_PROVIDER / _PATH / _NAME / _HOST / _PORT / _USERNAME / _PASSWORD. Examples: examples/demos/ingestion_and_migration/ |
| Back up a dataset, or move it to another cognee instance | A COGX archive, see below |
| Export a dataset's graph for other tools | await cognee.export(dataset, format=...): "json", "graphml" or "cypher" write a file (one way: cognee can't import them back); "pydantic" (default) returns typed DataPoint objects in memory |
| Import from another memory system (Mem0, Zep/Graphiti, Letta, LangMem) | Build a MemorySource (cognee/modules/migration/sources/) and pass it to await cognee.remember(source, dataset_name=...) |
There is no tool that moves a whole deployment from one database backend to another.
COGX archives
COGX (Cognee eXchange, cognee/modules/migration/cogx.py) is cognee's
portable memory format and the only export format cognee can import back.
An archive is a directory with a manifest.json (COGX version, source
system, the dataset's data-migration revision) and one JSONL file per record
kind (documents, episodes, entities, facts, memories,
memory_blocks), plus nodes.jsonl with the raw graph nodes. The Mem0,
Zep, Letta and LangMem importers also translate into COGX records first.
Use it to back up and restore a dataset, or to copy one to another cognee instance:
from cognee.migration import COGXArchiveSource
await cognee.export("my_dataset", format="cogx", destination="backup_cogx")
await cognee.remember(COGXArchiveSource("backup_cogx"), dataset_name="my_dataset")
- A restore defaults to
mode="preserve": the archived graph is written back as-is, with no LLM calls.mode="hybrid"also re-cognifies the raw content;mode="re-derive"ignores the archived graph and extracts again (costs LLM tokens). cognee.push()/cognee-cli pushdoes the same to Cognee Cloud: it exports to COGX, packs it as a.cogx.tar.gzand uploads it, and the receiving instance restores it (preserve mode unless you passmode=).export(..., include_permissions=True)also writespermissions.jsonwith the dataset owner and ACL grants, including password hashes, so the restore recreates working accounts. Treat that archive as a secret.- An archive written by a newer major COGX version is rejected with a
ValueError; upgrade cognee on the importing side.
Pitfalls
- Never regenerate
cognee/alembic/frozen_schema.py. It is the certified base schema the initial revision builds from, pinned bycognee/tests/unit/test_frozen_schema_seal.py. Schema changes ship as new revisions at head. - Never hand-type an Alembic revision id. Hand-typed patterns
(
a1b2c3d4e5f6, …) already collided with a downstream chain that vendors this one (b2c3d4e5f6a7). Generate ids withalembic revision. - Never rename, remove, or reorder a data-chain entry. The slug is what deployed databases store; an unknown stored slug disables the chain for that database.
- A model change and its migration land together. CI's "Migration/Model Lockstep Guard" fails otherwise.
- The relational schema cannot be downgraded below the data-bookkeeping
revisions unless the data chain goes to
basein the same call. alembic.iniis always the packaged one;COGNEE_ALEMBIC_PATHor--alembic-pathonly changes the scripts directory (for vendored chains).
How it works
run_migrations() (cognee/modules/migrations/startup.py) takes the
migration lock, decides fresh vs existing (a users or alembic_version
table exists), runs Alembic in-process on a worker thread, then walks the
data chain per database with runner.run_database_migrations, stamping
after every step. After the chain, it syncs vector-adapter storage when the
recorded cognee_version differs from the library's
(versions/adapter_storage_migration.py, not a chain entry).
- Relational:
cognee/alembic.ini,cognee/alembic/env.py,cognee/alembic/versions/,cognee/alembic/frozen_schema.py - Data chain:
cognee/modules/migrations/(README.mdis the authoring contract;registry.py,migration.py,runner.py,startup.py,versions/) - CLI:
cognee/cli/commands/migrate_command.py
Extending it
A new Alembic revision
cd cognee # the directory with alembic.ini
uv run alembic revision -m "add foo to data"
The DB URL comes from the live relational engine, so your normal .env
settings apply. Follow the recent revisions (for example
versions/e7f9a1c3d5b8_add_data_dataset_created_index.py):
- Idempotent and guarded: inspect first (
sa.inspect(op.get_bind())) and skip when the table is missing or the column/index already exists. - Branch on dialect (
conn.dialect.name == "postgresql") for Postgres-only SQL. For Postgres enums usepostgresql.ENUM(..., create_type=False)and create the type up front withcheckfirst. - SQLite cannot drop or alter columns in place: use
op.batch_alter_table(...). - Indexes: plain
CREATE INDEX IF NOT EXISTSinside the migration transaction, notCONCURRENTLY(which releases the version-row lock and lets concurrent workers into the same build). Repair invalid Postgres indexes viapg_index.indisvalid. - A new model module outside the usual import path must be imported in
cognee/alembic/env.py, or autogenerate will not see it.
A new data migration
Read cognee/modules/migrations/README.md first. In short:
- Write a module in
cognee/modules/migrations/versions/withasync def migrate(context)(and optionallyasync def downgrade(context)); step 2 registers them asup=/down=. - Append
Migration(slug=..., cognee_version=..., up=..., down_revision=<previous slug>, down=...)toMIGRATIONSinregistry.py. The chain is validated at import (linear, unique slugs). - Make it idempotent and cheap on empty stores, and crash-safe: re-key derived stores (vectors, ledger) first and rename in the graph last.
- Freeze private copies of any logic you depend on; never import live models that may change later.
Tests
cognee/tests/e2e/migrations/test_migration_model_lockstep.py(+schema_baseline.json): the lockstep CI job.cognee/tests/unit/test_run_migrations.py: single Alembic head, startup behaviour.cognee/tests/unit/test_frozen_schema_seal.py: frozen schema fingerprint.cognee/tests/unit/modules/migrations/: data-chain unit tests.cognee/tests/migrations/test_migration_lockstep.py: data chain against real stores (seed, down, up, verify).
Signals
- GitHub stars
- 32k
- Forks
- 3k
- Last commit
- Oct 2026
Questions
- When do cognee migrations run automatically?
- run_migrations() applies the relational chain first, then the data chain. It runs at API server startup, on the first write in an SDK or CLI process, and when you call await cognee.run_migrations().
- How do I check or repair migration state?
- Use cognee-cli current for stamped revisions and the last failure, cognee-cli history for the data chain, and cognee-cli upgrade, downgrade, or stamp to repair state.
Advanced
- Item type
- skill
- Key
cognee-migrations- Source
- github.com/topoteretes/cognee
Related picks
Skill · sickn33
The pick for Pythonpython-design-patterns
Skill · wshobson
The pick for Pythonsupabase-postgres-best-practices
Skill · supabase
The pick for Postgresperforming-sqlite-database-forensics
Skill · mukul975
The pick for SQLitesqlite-ops
Skill · aiskillstore
The pick for SQLitee2e-testing
Skill · thedaviddias
The pick for End-to-end testing