> ## 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.

# Quickstart

> Install Khora, bring up storage, and run your first remember / recall.

<Tabs>
  <Tab title="Default Setup">
    This walks the production path: PostgreSQL + pgvector + Neo4j with the default `VectorCypher` engine. For embedded options (SQLite + LanceDB), see [Configuration](/khora/configuration#embedded-backends).

    <Steps>
      <Step title="Install Khora">
        ```bash theme={null}
        uv init khora-quickstart 
        cd khora-quickstart
        uv add khora
        ```

        The Khora library requires Python version 3.13+ 
      </Step>

      <Step title="Setup databases">
        As Khora is meant to be used as a library, it has no built-in server and is made to work with  existing databases.

        For local development, the [Khora repo](https://github.com/DeytaHQ/khora) ships a `compose.yaml` that get's you up and running with our recommended stack: Postgres (with pgvector) and Neo4j 

        ```bash theme={null}
        git clone https://github.com/DeytaHQ/khora
        cd khora
        make dev
        ```

        Then export connection URLs via environment variables in `.env`

        ```dotenv theme={null}
        KHORA_DATABASE_URL=postgresql://khora:khora@localhost:5434/khora
        KHORA_NEO4J_URL=bolt://neo4j:pleaseletmein@localhost:7688
        OPENAI_API_KEY=sk-...
        ```
      </Step>

      <Step title="Run migrations">
        Khora ships its schema as Alembic migrations. Run them once per database:

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

        Or instantiate `Khora(..., run_migrations=True)` to apply on connect under an advisory lock, useful for single-process apps and tests.
      </Step>

      <Step title="Store a memory">
        ```python theme={null}
        import asyncio
        from khora import Khora

        async def main() -> None:
            async with Khora() as kb:  # reads KHORA_DATABASE_URL / KHORA_NEO4J_URL
                ns = await kb.create_namespace()
                await kb.remember(
                    "Marie Curie won the Nobel Prize in Physics in 1903.",
                    namespace=ns.namespace_id,
                    entity_types=["PERSON", "ORGANIZATION", "CONCEPT", "LOCATION", "EVENT"],
                    relationship_types=["RELATES_TO", "PART_OF", "MENTIONS"],
                )
                result = await kb.recall(
                    "What did Curie win?",
                    namespace=ns.namespace_id,
                )
                for chunk in result.chunks:
                    print(chunk.content, chunk.score)

        asyncio.run(main())
        ```

        `remember()` runs the 3-phase ingestion pipeline (stage → enrich → expand). `recall()` returns a `RecallResult` projection with `chunks` (typed `RecallChunk`), `entities`, `relationships`, and `documents` (deduplicated source documents). Build prompt context by iterating `result.chunks`. Each carries `.content`, `.score`, `.id`, and `.document_id`.
      </Step>

      <Step title="Batch ingestion (optional)">
        For higher throughput, stage documents and let a background processor pick them up:

        ```python theme={null}
        async with Khora() as kb:
            kb.start_pending_processor()   # opt-in; write-path services only
            handle = await kb.submit_batch(
                [{"content": "doc 1"}, {"content": "doc 2"}],
                on_result=lambda completed, total, result: print(result),
                namespace=ns.namespace_id,
                entity_types=["PERSON", "ORGANIZATION", "CONCEPT", "LOCATION", "EVENT"],
                relationship_types=["RELATES_TO", "PART_OF", "MENTIONS"],
            )
            await handle.wait()
        ```

        The processor is **opt-in**. Read-only services don't need it.
      </Step>
    </Steps>
  </Tab>

  <Tab title="Embedded Setup">
    This walks the embedded path: SQLite + LanceDB, in-process, no external services. Use it for demos and evaluation. For the scale ceiling and known gaps, see [Configuration](/khora/configuration#embedded-backends).

    <Steps>
      <Step title="Install Khora">
        ```bash theme={null}
        uv init khora-quickstart 
        cd khora-quickstart
        uv add khora[embedded] python-dotenv
        ```

        The `embedded` extra pulls in `aiosqlite`, `lancedb`, and `pyarrow`. `python-dotenv` is included so the script can load `.env` itself (more on that in step 3). Requires Python 3.13+.
      </Step>

      <Step title="Configure storage">
        Point Khora at the embedded backend via `.env`:

        ```dotenv theme={null}
        KHORA_STORAGE_BACKEND=sqlite_lance
        KHORA_STORAGE_SQLITE_LANCE_DB_PATH=./khora.db
        OPENAI_API_KEY=sk-...
        ```

        The LanceDB directory defaults to a sibling `.lance` folder next to the SQLite file. Override with `KHORA_STORAGE_SQLITE_LANCE_LANCE_PATH` if you want vectors on different storage.
      </Step>

      <Step title="Store a memory">
        Two things to know before the snippet. First, `uv run` does not auto-load `.env`, and Khora's settings class does not point at one either, so the script calls `load_dotenv()` explicitly. Second, `Khora()` defaults to `run_migrations=False`. Pass `run_migrations=True` so connect builds the SQLite schema on first run.

        ```python theme={null}
        import asyncio
        from dotenv import load_dotenv
        from khora import Khora

        load_dotenv()

        async def main() -> None:
            async with Khora(run_migrations=True) as kb:
                ns = await kb.create_namespace()
                await kb.remember(
                    "Marie Curie won the Nobel Prize in Physics in 1903.",
                    namespace=ns.namespace_id,
                    entity_types=["PERSON", "ORGANIZATION", "CONCEPT", "LOCATION", "EVENT"],
                    relationship_types=["RELATES_TO", "PART_OF", "MENTIONS"],
                )
                result = await kb.recall(
                    "What did Curie win?",
                    namespace=ns.namespace_id,
                )
                for chunk in result.chunks:
                    print(chunk.content, chunk.score)

        asyncio.run(main())
        ```

        Run it with `uv run main.py`. The `remember()` / `recall()` surface is identical to the Postgres + Neo4j stack. Switching backends later is a config change, not a code change.

        If you'd rather not add `python-dotenv`, drop `load_dotenv()` and run `uv run --env-file .env main.py` instead.
      </Step>
    </Steps>
  </Tab>
</Tabs>

<Tip>
  **Pre-fetch the reranker model.** Reranking is on by default, so the first `recall()` that runs it downloads the cross-encoder `BAAI/bge-reranker-v2-m3` from Hugging Face (a couple of gigabytes). Pre-fetch it so that first query doesn't pay the download cost, using the [`hf` CLI](https://huggingface.co/docs/huggingface_hub/guides/cli):

  ```bash theme={null}
  pip install -U "huggingface_hub"   # provides the hf CLI
  hf auth login                      # optional: authenticated, rate-limit-free downloads
  hf download BAAI/bge-reranker-v2-m3
  ```

  Pin a different model with `KHORA_QUERY_RERANKING_MODEL`, or turn reranking off with `KHORA_QUERY_ENABLE_RERANKING=false`.
</Tip>

## Next steps

<CardGroup cols={2}>
  <Card title="Configuration" icon={<span className="material-symbols-sharp">tune</span>} href="/khora/configuration">
    Every `KHORA_*` knob: storage, LLM, pipeline, query, telemetry.
  </Card>

  <Card title="VectorCypher" icon={<span className="material-symbols-sharp">settings_input_component</span>} href="/khora/engines/vectorcypher">
    How the retrieval engine fuses vector, graph, and keyword search with query routing and RRF.
  </Card>
</CardGroup>
