Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/default-lancedb-search.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@kilocode/cli": minor
"@kilocode/kilo-indexing": minor
"kilo-code": minor
---

Use embedded LanceDB as the default semantic search vector store so indexing works without a separate Qdrant server. Existing Qdrant users and Intel Mac users can select `qdrant` with `indexing.vectorStore`.
23 changes: 12 additions & 11 deletions packages/kilo-docs/pages/customize/context/codebase-indexing.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ This enables natural language queries like "user authentication logic" or "datab
1. Open Kilo Code **Settings** → **Indexing**, or click the indexing indicator at the bottom of the prompt input panel.
2. Turn on **Global Enable** to index every workspace, or turn on **Enable for This Project** to index only the current workspace. Both toggles are off until explicitly enabled.
3. Pick an **Embedding Provider** and fill in its required fields.
4. Pick a **Vector Store** (`Qdrant` or `LanceDB`) and configure it.
4. Pick a **Vector Store** (`LanceDB` or `Qdrant`) and configure it.
5. Optionally adjust **Tuning Parameters** (search score, batch size, retries, max results).
6. Save to start the initial scan.

Expand Down Expand Up @@ -74,8 +74,12 @@ You can also edit the `indexing` section in `kilo.jsonc` directly:

### Vector stores

- **Qdrant** (default) — external server. Recommended for team deployments and larger codebases. See [Setting Up Qdrant](#setting-up-qdrant).
- **LanceDB** — embedded, file-based. No server to run. Stores data under your Kilo data directory by default.
- **LanceDB** (default). Embedded and file-based, with no server to run. Stores data under your Kilo data directory by default.
- **Qdrant**. External server recommended for team deployments and larger codebases. See [Setting Up Qdrant](#setting-up-qdrant).

{% callout type="warning" title="Intel Macs" %}
LanceDB does not support Intel Macs. Select **Qdrant** and configure a Qdrant server instead.
{% /callout %}

{% callout type="tip" %}
For a fully local, zero-cost setup, combine **Ollama** (embeddings) with **LanceDB** (vector store — no separate server needed).
Expand Down Expand Up @@ -106,7 +110,7 @@ This opens an interactive configuration dialog where you can:
- Choose an **Embedding Provider** and fill in provider settings (API key, base URL, AWS region, etc.)
- Set the **Embedding Model** (blank = provider default)
- Set the **Vector Dimension** (blank = auto-detect from the model)
- Choose a **Vector Store** (`Qdrant` or `LanceDB`) and configure its connection
- Choose a **Vector Store** (`LanceDB` or `Qdrant`) and configure its connection
- Adjust **Tuning Parameters** (search threshold, batch size, retries, max results)

All changes are written to your `kilo.jsonc` config and take effect immediately.
Expand All @@ -120,14 +124,11 @@ You can also edit the `indexing` section directly. This is the full shape of the
"provider": "voyage",
"model": "voyage-code-3",
"dimension": 1024,
"vectorStore": "qdrant",
"vectorStore": "lancedb",
"voyage": {
"apiKey": "pa-..."
},
"qdrant": {
"url": "http://localhost:6333",
"apiKey": ""
},
"lancedb": {},
"searchMinScore": 0.4,
"searchMaxResults": 50,
"embeddingBatchSize": 60,
Expand All @@ -152,8 +153,8 @@ You can also edit the `indexing` section directly. This is the full shape of the

### Vector stores

- `qdrant` — `{ url?, apiKey? }` (default). See [Setting Up Qdrant](#setting-up-qdrant).
- `lancedb` — `{ directory? }` — embedded, file-based. No server to run. Uses a default Kilo data directory when omitted.
- `lancedb` uses `{ directory? }` and is the default. It is embedded and file-based, with no server to run. Kilo uses its data directory when `directory` is omitted.
- `qdrant` uses `{ url?, apiKey? }`. See [Setting Up Qdrant](#setting-up-qdrant).

{% callout type="tip" %}
For a fully local, zero-cost setup, combine **Ollama** (embeddings) with **LanceDB** (vector store — no separate server needed).
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 6 additions & 3 deletions packages/kilo-indexing/src/config.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { Schema } from "effect"
import z from "zod"
import type { IndexingConfigInput } from "./indexing/config-manager"
import { DEFAULT_VECTOR_STORE } from "./indexing/constants"
import type { EmbedderProvider } from "./indexing/interfaces/manager"

export { DEFAULT_VECTOR_STORE } from "./indexing/constants"

const providers = [
"kilo",
"openai",
Expand All @@ -29,7 +32,7 @@ export const IndexingConfig = z
.nullable()
.optional()
.describe("Override embedding vector dimension (auto-detected from model if omitted)"),
vectorStore: z.enum(stores).optional().describe("Vector store backend (default: qdrant)"),
vectorStore: z.enum(stores).optional().describe("Vector store backend (default: lancedb)"),
kilo: z
.object({
apiKey: z.string().optional(),
Expand Down Expand Up @@ -147,7 +150,7 @@ export const IndexingSchema = Schema.Struct({
dimension: Schema.optional(Schema.NullOr(PositiveInt)).annotate({
description: "Override embedding vector dimension (auto-detected from model if omitted)",
}),
vectorStore: Schema.optional(Store).annotate({ description: "Vector store backend (default: qdrant)" }),
vectorStore: Schema.optional(Store).annotate({ description: "Vector store backend (default: lancedb)" }),
kilo: Schema.optional(
Schema.Struct({
apiKey: Schema.optional(Schema.String),
Expand Down Expand Up @@ -237,7 +240,7 @@ export function toIndexingConfigInput(cfg: IndexingConfig | undefined): Indexing
return {
enabled: cfg?.enabled ?? false,
embedderProvider: provider,
vectorStoreProvider: cfg?.vectorStore,
vectorStoreProvider: cfg?.vectorStore ?? DEFAULT_VECTOR_STORE,
modelId: cfg?.model ?? undefined,
modelDimension: cfg?.dimension ?? undefined,
lancedbVectorStoreDirectory: cfg?.lancedb?.directory,
Expand Down
10 changes: 5 additions & 5 deletions packages/kilo-indexing/src/indexing/config-manager.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { EmbedderProvider } from "./interfaces/manager"
import type { CodeIndexConfig, PreviousConfigSnapshot } from "./interfaces/config"
import { DEFAULT_SEARCH_MIN_SCORE, DEFAULT_MAX_SEARCH_RESULTS } from "./constants"
import { DEFAULT_SEARCH_MIN_SCORE, DEFAULT_MAX_SEARCH_RESULTS, DEFAULT_VECTOR_STORE } from "./constants"
import { getDefaultModelId, getModelDimension, getModelScoreThreshold } from "./model-registry"
import { isEmbeddingProfileEqual, resolveEmbeddingProfile } from "./embedding-profile"

Expand Down Expand Up @@ -50,7 +50,7 @@ export interface IndexingConfigInput {
export class CodeIndexConfigManager {
private enabled = false
private embedderProvider: EmbedderProvider = "openai"
private vectorStoreProvider: "lancedb" | "qdrant" = "qdrant"
private vectorStoreProvider: "lancedb" | "qdrant" = DEFAULT_VECTOR_STORE
private lancedbVectorStoreDirectory?: string
private modelId?: string
private modelDimension?: number
Expand Down Expand Up @@ -88,7 +88,7 @@ export class CodeIndexConfigManager {
private applyInput(input: IndexingConfigInput): void {
this.enabled = input.enabled
this.embedderProvider = input.embedderProvider
this.vectorStoreProvider = input.vectorStoreProvider ?? "qdrant"
this.vectorStoreProvider = input.vectorStoreProvider ?? DEFAULT_VECTOR_STORE
this.lancedbVectorStoreDirectory = input.lancedbVectorStoreDirectory
this.qdrantUrl = input.qdrantUrl ?? "http://localhost:6333"
this.qdrantApiKey = input.qdrantApiKey
Expand Down Expand Up @@ -196,7 +196,7 @@ export class CodeIndexConfigManager {
if (prevProvider !== this.embedderProvider) return true

// Vector store provider change
if ((prev.vectorStoreProvider ?? "qdrant") !== this.vectorStoreProvider) return true
if ((prev.vectorStoreProvider ?? DEFAULT_VECTOR_STORE) !== this.vectorStoreProvider) return true

// LanceDB path change
if (
Expand Down Expand Up @@ -258,7 +258,7 @@ export class CodeIndexConfigManager {
return {
isConfigured: this.isConfigured(),
embedderProvider: this.embedderProvider,
vectorStoreProvider: this.vectorStoreProvider ?? "qdrant",
vectorStoreProvider: this.vectorStoreProvider,
lancedbVectorStoreDirectoryPlaceholder: this.lancedbVectorStoreDirectory,
modelId: this.modelId,
modelDimension: this.modelDimension,
Expand Down
2 changes: 2 additions & 0 deletions packages/kilo-indexing/src/indexing/constants/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
/**
* Codebase Index Constants
*/
export const DEFAULT_VECTOR_STORE = "lancedb" as const

export const CODEBASE_INDEX_DEFAULTS = {
MIN_SEARCH_RESULTS: 10,
MAX_SEARCH_RESULTS: 200,
Expand Down
4 changes: 2 additions & 2 deletions packages/kilo-indexing/src/indexing/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { VectorStoreSearchResult } from "./interfaces"
import type { IndexingState } from "./interfaces/manager"
import type { IndexingTelemetryEvent, IndexingTelemetryMeta, IndexingTelemetryTrigger } from "./interfaces/telemetry"
import { CodeIndexConfigManager, type IndexingConfigInput } from "./config-manager"
import { INITIAL_MANAGER_RECOVERY_DELAY_MS, MAX_MANAGER_RECOVERY_ATTEMPTS } from "./constants"
import { DEFAULT_VECTOR_STORE, INITIAL_MANAGER_RECOVERY_DELAY_MS, MAX_MANAGER_RECOVERY_ATTEMPTS } from "./constants"
import { CodeIndexStateManager } from "./state-manager"
import { CodeIndexServiceFactory } from "./service-factory"
import { CodeIndexSearchService } from "./search-service"
Expand Down Expand Up @@ -60,7 +60,7 @@ export class CodeIndexManager {
const cfg = this._configManager.getConfig()
return {
provider: cfg.embedderProvider,
vectorStore: cfg.vectorStoreProvider ?? "qdrant",
vectorStore: cfg.vectorStoreProvider ?? DEFAULT_VECTOR_STORE,
modelId: cfg.modelId,
}
}
Expand Down
3 changes: 2 additions & 1 deletion packages/kilo-indexing/src/indexing/orchestrator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import type { CacheManager } from "./cache-manager"
import type { Disposable } from "./runtime"
import { Log } from "../util/log"
import { sanitizeErrorMessage } from "./shared/validation-helpers"
import { DEFAULT_VECTOR_STORE } from "./constants"

const log = Log.create({ service: "indexing-orchestrator" })

Expand All @@ -39,7 +40,7 @@ export class CodeIndexOrchestrator {
const cfg = this.configManager.getConfig()
return {
provider: cfg.embedderProvider,
vectorStore: cfg.vectorStoreProvider ?? "qdrant",
vectorStore: cfg.vectorStoreProvider ?? DEFAULT_VECTOR_STORE,
modelId: cfg.modelId,
}
}
Expand Down
3 changes: 2 additions & 1 deletion packages/kilo-indexing/src/indexing/service-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import type { CacheManager } from "./cache-manager"
import type { IndexingTelemetryMeta, IndexingTelemetryReporter } from "./interfaces/telemetry"
import {
BATCH_SEGMENT_THRESHOLD,
DEFAULT_VECTOR_STORE,
OLLAMA_EMBEDDER_REQUEST_TIMEOUT_MS,
REMOTE_EMBEDDER_VALIDATION_TIMEOUT_MS,
} from "./constants"
Expand Down Expand Up @@ -55,7 +56,7 @@ export class CodeIndexServiceFactory {
const cfg = this.configManager.getConfig()
return {
provider: cfg.embedderProvider,
vectorStore: cfg.vectorStoreProvider ?? "qdrant",
vectorStore: cfg.vectorStoreProvider ?? DEFAULT_VECTOR_STORE,
modelId: cfg.modelId,
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { describe, expect, test } from "bun:test"
import { toIndexingConfigInput } from "../../../src/config"
import { CodeIndexConfigManager, type IndexingConfigInput } from "../../../src/indexing/config-manager"

function createInput(input: Partial<IndexingConfigInput> = {}): IndexingConfigInput {
Expand All @@ -25,9 +26,21 @@ describe("CodeIndexConfigManager", () => {
expect(cfg.getConfig().ollamaOptions?.baseUrl).toBe("http://localhost:11434")
})

test("defaults vector store to qdrant when omitted", () => {
test("defaults vector store to LanceDB when omitted", () => {
const cfg = new CodeIndexConfigManager(createInput({ vectorStoreProvider: undefined }))

expect(cfg.getConfig().vectorStoreProvider).toBe("lancedb")
})

test("normalizes omitted vector store config to LanceDB for hosts", () => {
expect(toIndexingConfigInput(undefined).vectorStoreProvider).toBe("lancedb")
})

test("preserves an explicit Qdrant override", () => {
const input = toIndexingConfigInput({ vectorStore: "qdrant" })
const cfg = new CodeIndexConfigManager(input)

expect(input.vectorStoreProvider).toBe("qdrant")
expect(cfg.getConfig().vectorStoreProvider).toBe("qdrant")
})

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Component, For, Show, createMemo, createSignal } from "solid-js"
import { Card } from "@kilocode/kilo-ui/card"
import { DEFAULT_VECTOR_STORE } from "@kilocode/kilo-indexing/config"
import { formatKiloEmbeddingModelLabel, getKiloEmbeddingModel } from "@kilocode/kilo-indexing/embedding-models"
import { Select } from "@kilocode/kilo-ui/select"
import { Switch } from "@kilocode/kilo-ui/switch"
Expand Down Expand Up @@ -32,8 +33,8 @@ const allProviders: { value: ProviderId; label: string }[] = [
]

const stores: Option[] = [
{ value: "qdrant", label: "Qdrant (default)" },
{ value: "lancedb", label: "LanceDB" },
{ value: "lancedb", label: "LanceDB (default)" },
{ value: "qdrant", label: "Qdrant" },
]

const tuning: Array<{ key: TuningKey; label: string; placeholder: string }> = [
Expand Down Expand Up @@ -91,7 +92,7 @@ const IndexingTab: Component = () => {
updateConfig({ indexing: { ...cfg(), ...partial } })
}

const vectorStore = () => cfg().vectorStore ?? "qdrant"
const vectorStore = () => cfg().vectorStore ?? DEFAULT_VECTOR_STORE
const kiloDefault = () =>
getKiloEmbeddingModel(embeds.catalog().defaultModel, embeds.catalog())?.id ?? embeds.catalog().defaultModel
const kiloModels = createMemo(() =>
Expand Down
14 changes: 7 additions & 7 deletions packages/opencode/src/kilocode/components/dialog-indexing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import { useDialog } from "@tui/ui/dialog"
import { DialogSelect, type DialogSelectOption } from "@tui/ui/dialog-select"
import { DialogPrompt } from "@tui/ui/dialog-prompt"
import { DEFAULT_VECTOR_STORE } from "@kilocode/kilo-indexing/config"
import { formatKiloEmbeddingModelLabel } from "@kilocode/kilo-indexing/embedding-models"
import { fetchKiloEmbeddingModelCatalog } from "@kilocode/kilo-gateway"
import { useSync } from "@tui/context/sync"
Expand Down Expand Up @@ -62,8 +63,8 @@ const PROVIDER_FIELDS: Record<EmbeddingProvider, ProviderFieldDef[]> = {
}

const VECTOR_STORE_LABELS: Record<string, string> = {
qdrant: "Qdrant (default)",
lancedb: "LanceDB",
lancedb: "LanceDB (default)",
qdrant: "Qdrant",
}

function maskSecret(value: string | undefined): string {
Expand Down Expand Up @@ -316,14 +317,14 @@ function VectorStoreSelect(props: SubDialogProps) {
const options: DialogSelectOption<string>[] = Object.entries(VECTOR_STORE_LABELS).map(([value, title]) => ({
value,
title,
description: value === (indexing.vectorStore ?? "qdrant") ? "(current)" : undefined,
description: value === (indexing.vectorStore ?? DEFAULT_VECTOR_STORE) ? "(current)" : undefined,
}))

return (
<DialogSelect
title="Vector Store"
options={options}
current={indexing.vectorStore ?? "qdrant"}
current={indexing.vectorStore ?? DEFAULT_VECTOR_STORE}
onSelect={async (option) => {
const store = option.value as "lancedb" | "qdrant"
if (store === "lancedb") {
Expand Down Expand Up @@ -480,9 +481,8 @@ export function DialogIndexing(props: DialogIndexingProps) {
const indexing = defaultIndexing(sync, globalCfg())

const providerLabel = indexing.provider ? PROVIDER_LABELS[indexing.provider] : "not set"
const storeLabel = indexing.vectorStore
? (VECTOR_STORE_LABELS[indexing.vectorStore] ?? indexing.vectorStore)
: "Qdrant (default)"
const store = indexing.vectorStore ?? DEFAULT_VECTOR_STORE
const storeLabel = VECTOR_STORE_LABELS[store] ?? store

const tuningCount = TUNING_PARAMS.filter((p) => indexing[p.key] !== undefined).length
const tuningDesc = tuningCount > 0 ? `${tuningCount} customized` : "defaults"
Expand Down
5 changes: 5 additions & 0 deletions packages/opencode/src/kilocode/lancedb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ export namespace LanceDBRuntime {
export async function ensure(store?: string) {
if (store !== "lancedb") return
if (process.env[env]) return
if (process.platform === "darwin" && process.arch === "x64") {
throw new Error(
'LanceDB is not supported on Intel Macs. Set "indexing.vectorStore" to "qdrant" and configure a Qdrant server.',
)
}
if (box.ready) return box.ready

box.ready = (async () => {
Expand Down
18 changes: 18 additions & 0 deletions packages/opencode/test/kilocode/lancedb-runtime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,24 @@ describe("LanceDBRuntime", () => {
expect(process.env[env]).toBeUndefined()
})

test("guides Intel Mac users to Qdrant", async () => {
const { LanceDBRuntime } = await import("../../src/kilocode/lancedb")
const platform = Object.getOwnPropertyDescriptor(process, "platform")!
const arch = Object.getOwnPropertyDescriptor(process, "arch")!
Object.defineProperty(process, "platform", { ...platform, value: "darwin" })
Object.defineProperty(process, "arch", { ...arch, value: "x64" })

try {
await expect(LanceDBRuntime.ensure("lancedb")).rejects.toThrow(
'LanceDB is not supported on Intel Macs. Set "indexing.vectorStore" to "qdrant" and configure a Qdrant server.',
)
expect(add).not.toHaveBeenCalled()
} finally {
Object.defineProperty(process, "platform", platform)
Object.defineProperty(process, "arch", arch)
}
})

test("installs the pinned package and exports a file URL for lancedb", async () => {
const { LanceDBRuntime } = await import("../../src/kilocode/lancedb")

Expand Down
Loading