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

# Migrations

> How Khora's database schema is created and kept current: Alembic for PostgreSQL and the portable subset for sqlite_lance.

Khora ships its schema as **Alembic migrations bundled inside the package** at
`khora/db/migrations/`. This applies to **PostgreSQL-backed** deployments. The embedded
`sqlite_lance` stack runs the portable subset of the same migration chain.

## Who runs migrations?

The schema must exist before you call `Khora()`. Two options:

### Let Khora run them

```python theme={null}
async with Khora(run_migrations=True) as kb:
    ...
```

Khora takes a PostgreSQL advisory lock (60s timeout), applies any pending migrations,
and releases it. Safe under concurrent startup: one process migrates, the others wait
and no-op. Good for single-process apps and tests.

### Run them out-of-band

Pre-deploy with the Alembic CLI. The migration directory resolves via
`script_location = khora:db/migrations` when Khora is installed as a dependency:

```bash theme={null}
uv run alembic upgrade head
```

## The version table

Khora's migrations live in **`khora_alembic_version`**, not the default
`alembic_version`. This means Khora's history never collides with your own
application's Alembic migrations. Point your app at `alembic_version` and leave
Khora's table untouched.

## Skip-ahead and fresh databases

* **Fresh database**: `run_migrations()` detects the absence of
  `khora_alembic_version` (via `information_schema`) and creates every table from
  scratch.
* **Skip-ahead**: if the database is at a revision newer than the installed Khora
  package (another service already migrated it), Khora skips gracefully and returns
  `MigrationResult(success=True, skipped=True, ...)`.

<Warning>
  Skip-ahead is a safety net, not a coordination tool: **don't pin different services
  to different Khora major versions against a shared PostgreSQL database.** Run the same
  major across services.
</Warning>

## Dialect-conditional migrations

A few migrations use PostgreSQL-only features (BRIN/GIN indexes,
`CREATE INDEX CONCURRENTLY`, partial indexes over bi-temporal columns) that have no
SQLite analogue. Those scripts gate on `dialect.name == "postgresql"` and **skip
silently on `sqlite_lance`**, so the embedded stack runs the same chain without errors.
Portable column additions run on both dialects. You don't manage this. It's automatic.

## Migrating from the removed `graphrag` engine

The `graphrag` engine was removed; `vectorcypher` is the drop-in replacement. Pass
`engine_kwargs={"skeleton_core_ratio": 1.0}` for graphrag-equivalent full extraction,
or accept VectorCypher's default selective extraction (cheaper). Existing
graphrag-ingested data stays queryable via `vectorcypher` against the same database.
The table shapes are identical. See [VectorCypher](/khora/engines/vectorcypher).

<Note>
  Per-release migration details (which revision added which column/index) live in the
  khora repo's `CHANGELOG.md`, not here. This page is the stable operational contract.
</Note>

<CardGroup cols={2}>
  <Card title="Storage backends" icon={<span className="material-symbols-sharp">database</span>} href="/khora/storage-backends">
    The PostgreSQL + Neo4j stack the migrations target, and the embedded alternative.
  </Card>

  <Card title="Quickstart" icon={<span className="material-symbols-sharp">rocket_launch</span>} href="/khora/quickstart">
    Bring up the databases and run your first migration.
  </Card>
</CardGroup>
