---
title: "Getting Started with XI Lucent"
description: "Install Xio.Lucent, provision assets, ingest your first document, and run your first query."
published: 2026-05-14T12:11:27.420936+00:00
updated: 2026-05-14T12:11:27.420936+00:00
tags: ["getting-started", "lucent", "tutorial"]
url: https://xiobjects.com/docs/xio/lucent/getting-started
source: XI Objects
---

<!-- xion:doctype xion+markdown -->
<!-- xion:metadata
{
  "version": "1.0",
  "content_type": "application/xion\u002Bmarkdown",
  "source_type": "xi-content/doc",
  "generator": "xio-content-publisher/1.0.0",
  "generated": "2026-05-14T12:10:20.0079976\u002B00:00",
  "encoding": "utf-8",
  "render_intent": "markdown",
  "title": "Getting Started with XI Lucent",
  "slug": "xio/lucent/getting-started",
  "copyright": "\u00A9 2026 XI Objects Inc"
}
-->

# Getting Started with XI Lucent

This guide gets you from a blank project to a working semantic search pipeline in a few minutes.

## Prerequisites

- .NET 10.0 or later
- A project that uses dependency injection (ASP.NET Core, Worker Service, or a test host)

## Install

```bash
dotnet add package Xio.Lucent
```

## Register the engine

One call registers everything: the ONNX embedder, semantic chunker, SQLite vector store, FTS5 full-text store, and hybrid retrieval strategy.

```csharp
builder.Services.AddLucent();
```

By default, Lucent stores its databases under `.lucent/` relative to the process working directory. To move them somewhere else:

```csharp
builder.Services.AddLucent(opts =>
{
    opts.StorageRoot = "/var/data/lucent";
});
```

## Provision assets

Before the engine can embed anything, it needs two assets: the `nomic-embed-text-v1.5` ONNX model and the sqlite-vec native extension. Call `ProvisionAsync` once at startup.

```csharp
var engine = app.Services.GetRequiredService<IKnowledgeEngine>();
await engine.ProvisionAsync();
```

This downloads and verifies both assets against pinned checksums. Once downloaded, subsequent restarts skip the download. You can also provision ahead of time and ship the assets with your container image.

## Ingest a document

`AddDocumentAsync` accepts a collection ID and a request. Collections are created implicitly on first write.

```csharp
var result = await engine.AddDocumentAsync("docs", new AddDocumentRequest
{
    Content = "# XI Lucent\n\nA semantic ingestion and retrieval library for .NET 10.",
    DocumentId = "readme",
    ContentTypeHint = "text/markdown"
});

Console.WriteLine($"Chunked into {result.ChunkCount} chunks in {result.ChunkingDuration.TotalMilliseconds:F0}ms");
```

`DocumentId` is optional. If you omit it, Lucent generates one from a BLAKE3 hash of the content. If you ingest the same document ID twice with unchanged content and the same embedder model, Lucent short-circuits and returns immediately without re-embedding.

## Run a query

```csharp
var result = await engine.QueryAsync("docs", new QueryRequest
{
    Text = "what is lucent",
    TopK = 5
});

foreach (var hit in result.Chunks)
{
    var chunk = hit.Chunk;
    var meta  = chunk.Metadata;

    Console.WriteLine($"[{hit.Score:F3}] {chunk.Content}");
    Console.WriteLine($"  Document: {chunk.DocumentId}");
    Console.WriteLine($"  Heading:  {meta.Heading}");
    Console.WriteLine($"  Section:  {meta.SectionPath}");
    Console.WriteLine($"  Page:     {meta.PageNumber}");   // null for non-paginated content
}
```

Every result carries where in the source material the chunk came from: which document, which heading, which section, which page (for PDFs), which slide (for PPTX), which row (for CSV), which DOM element (for HTML). The content never comes back without its origin.

## Ingest from a file

For binary formats like PDF or Word, pass a stream:

```csharp
await using var file = File.OpenRead("spec.pdf");
var result = await engine.AddDocumentAsync("docs", new AddDocumentRequest
{
    ContentStream = file,
    Source = new SourceInfo { FileName = "spec.pdf" }
});
```

Lucent detects the format, decomposes the file to text, and threads per-page provenance through the chunking stage. A chunk from page 12 of the PDF comes back with `PageNumber = 12`. A chunk from slide 4 of a PPTX comes back with `SlideIndex = 4`. You don't configure any of this; it comes from the decomposer automatically.

## What the default setup gives you

| Component | Default |
|-----------|---------|
| Embedder | `OnnxEmbedder` — `nomic-embed-text-v1.5`, 768 dimensions, runs locally |
| Chunking | `SemanticChunker` — splits on cosine similarity drop-offs, respects structural boundaries |
| Vector store | `SqliteVectorStore` — sqlite-vec cosine KNN |
| Full-text store | `SqliteFtsStore` — FTS5 BM25 in the same database |
| Retrieval | `HybridRetrievalStrategy` — vector + FTS5 fused via Reciprocal Rank Fusion |
| Re-ranker | `NoOpScorer` — pass-through; swap in `CrossEncoderScorer` if you need it |

## Next Steps

- Read how the [Ingestion & Retrieval Pipeline](/docs/xio/lucent/concepts/pipeline) works end to end
- Explore [Chunking Strategies](/docs/xio/lucent/concepts/chunking) and when to choose each one
- Learn to [Swap Adapters](/docs/xio/lucent/guides/swap-adapters) to replace the embedder or vector store
- Expose Lucent over HTTP with [Xio.Lucent.Api](/docs/xio/lucent/api/rest) or as an MCP server with [Xio.Lucent.Mcp](/docs/xio/lucent/api/mcp)
<!-- xion:trust
{
  "v": 1,
  "canon_v": 1,
  "ctx": "xiobjects.com/content",
  "hash_blake3_hex": "e21c563da254e74752af928a91542cb91318550359f448129b25a525113a78d4",
  "hash_sha256_hex": null,
  "sig_alg": "ed25519",
  "sig_b64": "BN0YlNRaogBd5Iv5CaS1agLm9gMQQfFi9cZyrCG5EqaRnqXx3P15H_-BUlNJwqQXTeLrXITxVUkdv2ZbiiuTDQ",
  "pubkey_b64": "h-awvV8Rn-juph_c2Y7UH5A6e7NaFia3zBiMrJUOMOo",
  "x509_chain_pem": [
    "-----BEGIN CERTIFICATE-----\r\nMIIB9DCCAaagAwIBAgIQBrrNsmRlBvKQdA4idEliJjAFBgMrZXAwLjEsMCoGA1UE\r\nAwwjWEkgT2JqZWN0cyBJbmMgQ29udHJvbCBJbnRlcm1lZGlhdGUwHhcNMjYwNTEz\r\nMjI0NjA1WhcNMjYwNjEyMjI0NjA1WjBLMR4wHAYDVQQDDBV4aW8tY29udGVudC1w\r\ndWJsaXNoZXIxFzAVBgNVBAoMDlhJIE9iamVjdHMgSW5jMRAwDgYDVQQLDAdDb250\r\nZW50MCowBQYDK2VwAyEAh\u002BawvV8Rn\u002Bjuph/c2Y7UH5A6e7NaFia3zBiMrJUOMOqj\r\ngbwwgbkwDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYI\r\nKwYBBQUHAyQwZQYDVR0jBF4wXIAUOym3mFmw/qs1fgKrujCkxhrTk7KhLqQsMCox\r\nKDAmBgNVBAMMH0luc3RpdHV0ZSBvZiBQcm92ZW5hbmNlIFJvb3QgQ0GCFFJgN/ix\r\nQn72H6h3T5lEr9f8lJQFMB0GA1UdDgQWBBS1LSJi5\u002BeqBq8h974Ht9HTgIcdgTAF\r\nBgMrZXADQQCKjXbPwnk/DZHmLQstUWRzU6GSf\u002BSHTXTTZCtRLbmJKxT17Qlbpexc\r\nsRgdSpxNWpJPe9Fr4vwhRkESMqMIpgQO\r\n-----END CERTIFICATE-----\r\n",
    "-----BEGIN CERTIFICATE-----\r\nMIIByDCCAXqgAwIBAgIUUmA3\u002BLFCfvYfqHdPmUSv1/yUlAUwBQYDK2VwMCoxKDAm\r\nBgNVBAMMH0luc3RpdHV0ZSBvZiBQcm92ZW5hbmNlIFJvb3QgQ0EwHhcNMjUxMTAy\r\nMDMxNzEyWhcNMzAxMTAxMDMxNzEyWjAuMSwwKgYDVQQDDCNYSSBPYmplY3RzIElu\r\nYyBDb250cm9sIEludGVybWVkaWF0ZTAqMAUGAytlcAMhAFSS/pggSRmTcAMko7uc\r\nATH8OHgxVymd5mBFlPXbJkgio4GtMIGqMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYD\r\nVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBQ7KbeYWbD\u002BqzV\u002BAqu6MKTGGtOTsjBlBgNV\r\nHSMEXjBcgBQAZRTDswSVORu\u002BkUOKX6WvrOvmQKEupCwwKjEoMCYGA1UEAwwfSW5z\r\ndGl0dXRlIG9mIFByb3ZlbmFuY2UgUm9vdCBDQYIUJqoJlpiSFg\u002B7W5IJLMrLttgR\r\nQp4wBQYDK2VwA0EA5FOht7YOsVRPp/FOKMQ\u002B3Mo9JxrvGR3ylKWAWNm6OUV7N3DB\r\nI9cD62wU5I0d0EKDBy0CX9DnoqUyxv5yguraAA==\r\n-----END CERTIFICATE-----\r\n",
    "-----BEGIN CERTIFICATE-----\r\nMIIBaTCCARugAwIBAgIUJqoJlpiSFg\u002B7W5IJLMrLttgRQp4wBQYDK2VwMCoxKDAm\r\nBgNVBAMMH0luc3RpdHV0ZSBvZiBQcm92ZW5hbmNlIFJvb3QgQ0EwHhcNMjUxMTAy\r\nMDMwNTEyWhcNMzUxMDMxMDMwNTEyWjAqMSgwJgYDVQQDDB9JbnN0aXR1dGUgb2Yg\r\nUHJvdmVuYW5jZSBSb290IENBMCowBQYDK2VwAyEAEWNZl\u002Br3IC7\u002BgBh90Yo1kWk1\r\npZCVzVuFdFT7qBBU8W2jUzBRMB0GA1UdDgQWBBQAZRTDswSVORu\u002BkUOKX6WvrOvm\r\nQDAfBgNVHSMEGDAWgBQAZRTDswSVORu\u002BkUOKX6WvrOvmQDAPBgNVHRMBAf8EBTAD\r\nAQH/MAUGAytlcANBAO6QeydOFNrN75qNyftggYudsxMyl4w9qWkSdZ6hlhrRcbSr\r\niG9Si0kbrIJOwYB/LTBU0RM4Rl\u002Bo9PM3Qp0mPwo=\r\n-----END CERTIFICATE-----\r\n"
  ],
  "key_id": "SDyVO7FvlAM-6CvQ62VZYOBO7JADFqLquUunUABRgKg",
  "created_at": "2026-05-14T12:10:20Z"
}
-->