rift
Developer

Configuration

Since v0.0.5

Rift reads rift.toml from the workspace root. The generated rift.schema.json defines the file and rejects any key it doesn't know, so a typo fails at load rather than being silently ignored. Changes take effect for later requests once validation passes; while the file is invalid, requests fail with configuration_invalid until you fix it.

An editor that supports TOML schema directives can load the generated schema:

#:schema https://volar.sh/rift/rift.schema.json

Treat rift.toml as executable workspace configuration: a selected LSP process starts with the server's OS permissions on its first request. Inspect the file before running Rift in a cloned repository.

Each section states its defaults below its example. Durations take ms, s, m, h, or d; byte sizes take b, kb, mb, gb, or tb - binary units, so 1kb is 1024 bytes. The collection settings keep the existing defaults and collection algorithms. Their examples show those defaults; the stated ranges are the supported bounds, so a limit can be lowered or raised within its range. A value outside that range fails configuration validation.

Environment variables

Since v0.0.44

A CI job or a container often needs one bound raised without editing the workspace's rift.toml. Every key has an environment variable for that: RIFT_, then the key's path with its members joined by _ and uppercased. [providers.syntax] max_nodes is RIFT_PROVIDERS_SYNTAX_MAX_NODES, and [server] port_range.min is RIFT_SERVER_PORT_RANGE_MIN:

RIFT_PROVIDERS_SYNTAX_MAX_NODES=5000000 RIFT_PROVIDERS_SYNTAX_MAX_FILE=16mb rift server start
  • a variable's value replaces the file's value for its key; a key no variable names keeps the file's value, or its default
  • a key that takes text, such as a size or a duration, takes the value as written, such as 16mb; any other key takes one TOML value, such as 5000000, true, or ["vendor/**"]
  • the merged configuration passes the same checks the file does, so a value outside its range fails with configuration_invalid naming the variable
  • a variable that starts with a table's prefix, such as RIFT_PROVIDERS_, and names none of its keys fails the same way, listing the variables that table accepts - a misspelled key can't be silently ignored
  • [lsp.<name>] entries have no variables, and neither does a [languages.<name>] entry for a language Rift doesn't ship

The server reads the variables from the environment of the rift server start or rift mcp that started it. A running server keeps that environment, so a changed variable applies after rift server stop and the next start.

Since v0.0.46

A [languages.<name>] entry for a language Rift ships takes variables too, spelled RIFT_LANGUAGES_<NAME>_<KEY>: RIFT_LANGUAGES_PYTHON_STDLIB=false sets the stdlib key of [languages.python]. A dialect's : becomes _, so RIFT_LANGUAGES_TYPESCRIPT_TSX_<KEY> sets a key of [languages."typescript:tsx"], such as RIFT_LANGUAGES_TYPESCRIPT_TSX_STDLIB. A variable naming a language Rift doesn't ship fails with configuration_invalid, as a misspelled key does.

Server

Since v0.0.8

server bounds the server's own blocking work - the filesystem scans and parses behind reads, and index rebuilds:

[server]
num_workers = 4
worker_queue_timeout = "30s"
idle_timeout = "30m"
readiness_timeout = "30s"
  • num_workers caps concurrent blocking operations and sizes the parser worker pool, 1 to 64
  • worker_queue_timeout bounds one request's wait for a free worker, 1ms to 1h - when it expires, the request fails with temporarily_unavailable and the client can retry without stacking another waiter
  • idle_timeout stops the server once no request remains active and that span has passed after the last response completed, 1s to 1d; the next rift mcp or rift server start brings it back
  • readiness_timeout bounds one request's waiting, 1s to 1h. A read takes the deadline once, where it enters. Workspace index validation and language-engine reference waits spend what is left of that one budget. A search that meets a pending lexical commit returns identifier matching with lexical_ranking_unavailable instead of waiting for the transaction
  • port pins the exact loopback port, 1024 or above; omitted, the server picks the first free port from port_range - port_range = { min = 12000, max = 13000 } is the default - and the two keys exclude each other

The worker pool exists before the first request, so the server sizes it once at startup: a change to this table applies on the next start, while every other table applies to later requests as soon as the file is accepted.

Since v0.0.47

Initial source preparation runs in the background. A local read can return prepared files with local_index_preparing; a targeted nodes read spends its remaining readiness_timeout budget preparing the requested file. The warning reports incomplete source independently of lexical and vector readiness.

Three additional keys bound background validation and repository serving:

[server]
validation_interval = "30s"
version_control_timeout = "3s"
workspaces = 16
  • validation_interval sets the deadline of each background filesystem check, 1s to 1h. Requests and filesystem events do not reset the deadline. A due check waits behind the rebuild that follows a superseded one, so it starts at most one interval after its deadline, plus the rebuild running when that interval ends. An unchanged check without a recorded failure queues no rebuild.
  • version_control_timeout bounds the wait for the worktree's Git index.lock, 1ms to 30s. A lock retained past the bound is passed and named in the log; the server leaves the lock in place.
  • workspaces caps the workspaces retained by one repository process, 1 to 64. A new workspace is refused once that limit is reached; concurrent admission waits within worker_queue_timeout for an available worker.

An explicitly started repository server accepts its [server] table from the main Git worktree, including environment overrides. A workspace uses that process only when its accepted table matches those startup settings; conflicting keys are logged, and the proxy starts a workspace server.

A missing, bare, removed, or unreadable main worktree, or invalid main-worktree configuration, also selects a workspace server. Existing connections retain their startup settings until restart.

Logs

Since v0.0.22

logs bounds what the server records about itself. Diagnostics admitted by capture enter a bounded queue and land in the metrics database at .rift/metrics. rift server logs reads those records directly, with or without a running server:

[logs]
retention_records = 50000
queue_records = 4096
flush_interval = "250ms"
retry_interval = "500ms"
cardinality_limit = 2000
capture = "rift=info,rift_mcp=info,rift_server=info,rift_index=warn"
stall_delay = "10s"
stderr_limit = "1mb"
  • retention_records caps the records the store keeps, 100 to 1000000; a write past the cap drops the oldest records first
  • queue_records caps the records each log subscription holds, 1 to 65536. A full subscription drops and counts excess records independently of other subscriptions
  • flush_interval is how long the drain waits before writing a partial batch, 1ms to 1h
  • retry_interval is how long the drain waits before retrying a batch the store refused, 1ms to 1h. The drain keeps the batch until the store accepts it or the drain stops
  • cardinality_limit caps attribute sets each metric instrument holds, 1 to 65536. Measurements past it join the otel.metric.overflow=true series
  • capture selects which targets are recorded, in the RUST_LOG spelling tracing takes - rift_mcp=debug records the index lane's own rebuild decisions. A target this filter excludes never reaches the store, whatever the terminal shows
  • stall_delay Since v0.0.62 is how long an operation, lock wait, or held lock stays open before the server reports it, 1s to 1h. The server checks every quarter of the delay, and at least every 5s, and reports each such entry once, in one record of the operations in flight with their age, so a request that runs out its deadline leaves the work it waited on in the store. A lock the server keeps for as long as it runs is never reported
  • stderr_limit Since v0.0.62 is how many bytes a foreground server writes to a standard error that is not a terminal, 1kb to 1gb. Past it the server prints one notice there and discards the rest, counting the discarded bytes in log.stderr.discarded; the store keeps every record capture admits either way. The server's stop records the count as standard error bytes discarded

The server reads this table at startup, so a change applies on the next start. The records outlive the process that wrote them. The metrics database is a file apart from the search index, so a workspace whose index will not settle, or whose index database another process holds locked, still answers rift server logs.

Source

Since v0.0.6

source controls which files below the workspace root the index and reads consider visible. include and exclude take exact project-relative paths or globs, matched the same way search's paths selector matches them:

[source]
include = []
exclude = []
force_include = []
respect_gitignore = true
files = 100000
workspace_size = "512mb"
declarations = 1000000
relationships = 10000000
directory_depth = 64

The four glob lists decide in one order:

  1. exclude drops a file, whatever else matched it.
  2. force_include keeps a file the repository ignore rules would hide, and keeps it without an include match.
  3. respect_gitignore, on by default, applies root and nested .gitignore files and the repository-local .git/info/exclude file. Linked worktrees share the repository-local exclusions. The server never reads global or parent-directory ignore sources.
  4. include, when it is not empty, is what a remaining file has to match. Empty include includes every file the steps above leave standing.

force_include is what an operator reaches for when a directory belongs in the index and outside version control at once - design documents, a scratch corpus, a generated tree the build ignores:

[source]
force_include = [".plans/**"]

The server walks force_include subtrees a second time without consulting repository ignore rules, spending the same files budget as the first walk.

.git, .rift, and target stay invisible whatever this table says: no include, exclude, force_include, or .gitignore entry can make the server serve them.

files bounds how many files the index holds, 1000 to 5000000, and workspace_size bounds the bytes they hold together, 16mb to 64gb. The server refuses to index a workspace past either bound and names the key in the refusal; a change to either key rebuilds the index, as a change to include, exclude, or force_include does.

declarations bounds how many declarations those files publish together, 10000 to 50000000. This one is a capacity, not a refusal: a workspace declaring more indexes the files that fit, leaves the rest out, and reports each one as an unavailable source naming this key. The default holds a full index in about 5 GiB, which is room for a workspace twice the size of the largest Rift is tested against - raise it on a machine with memory to spare, lower it on one without.

relationships bounds relationship edges retained together, 1 to 500000000. The index keeps the edges that fit and counts the omitted edges. directory_depth bounds the directory levels scanned below the workspace root, 1 to 4096; a deeper directory refuses the rebuild naming this key.

source shapes what the index walks: a file it excludes stops reaching search and get_symbol results. A single search request can still reach one such file on demand, through the request-side force_include override on search's paths selector. The two differ in reach and lifetime: the request field pulls at most 256 files into one answer, while this table's force_include puts them in the index every read serves from.

Documentation

Since v0.0.46

documentation describes which documentation files the index collects for search with target: "documentation" and for the documentation field of a get_symbol hit. By default the index collects every visible Markdown, MDX, reStructuredText, and notebook file, and the .txt files [search.text].include selects, except:

  • change logs, licenses, and codes of conduct, wherever they sit: CHANGELOG.md, CHANGELOG.mdx, LICENSE.md, and CODE_OF_CONDUCT.md, each also spelled in lower case
  • archived and superseded copies: any folder named old, docs/old, and any folder whose name holds archive, deprecated, legacy, previous, outdated, or superseded
  • translations: language folders below a root i18n folder, such as i18n/zh-cn or i18n/fr, and folders named zh-cn, zh-tw, zh-hk, zh-mo, or zh-sg

The table overrides that default:

[documentation]
enabled = true
exclude = []
force_include = []
max_sources = 100000
max_file = "4mb"
max_total = "512mb"
max_blocks = 250000
max_references = 250000
max_text = "4kb"
max_heading_depth = 512
max_warnings = 256
max_nodes = 250000
max_depth = 512
max_progress = 131072
max_layer_blocks = 2000000
max_mappings = 6000000
  • enabled defaults to true; false collects no documentation file, while the documentation comments attached to declarations stay
  • exclude leaves out what its globs match beside the default exclusions, at most 512 patterns
  • force_include collects what a default exclusion leaves out, at most 512 patterns; a match exclude also names stays out

The patterns match project-relative paths the way [source] patterns do. A workspace whose change log belongs in search adds it back:

[documentation]
force_include = ["CHANGELOG.md"]

The collection bounds apply to local documentation and exact-package documentation:

  • max_sources caps selected sources, 1 to 5000000
  • max_file caps one source's bytes, 1b to 64mb; max_total caps their bytes together, 1b to 64gb
  • max_blocks caps retained blocks, 1 to 50000000
  • max_references caps each collection's links, fragments, and reference candidates, 1 to 50000000
  • max_text caps one heading, language, destination, or reference spelling, 1b to 1mb
  • max_heading_depth caps a block's heading path and heading levels, 1 to 65536
  • max_warnings caps retained collection warnings, 1 to 65536
  • max_nodes caps a documentation parser's syntax nodes, 1 to 100000000; max_depth caps its nesting, 1 to 65536
  • max_progress caps one reStructuredText parser's progress callbacks, 1 to 4294967295
  • max_layer_blocks caps blocks held across a documentation layer's collections, 1 to 100000000
  • max_mappings caps the ranking identity mappings that layer holds, 1 to 300000000

The collector leaves out a source whose parsing or facts pass a bound and retains a warning within max_warnings. A selected-source count past max_sources refuses collection. Attached comments use the declaration syntax pass, whose parsing bounds come from [providers.syntax].

A documentation layer maps ranking identities to their documentation blocks. Its block and mapping bounds apply when the index builds that layer; an accepted configuration change also changes the keys used to reuse cached parser facts.

Collection bounds do not change the exact-symbol response shape: documentation context returns at most 32 references and 16kb of excerpts. Notebook cell identity and source-range validation, and license metadata validation, retain their protocol bounds.

A change to the table rebuilds the index. Every key has its RIFT_DOCUMENTATION_* variable, such as RIFT_DOCUMENTATION_MAX_FILE and RIFT_DOCUMENTATION_MAX_NODES, under the environment variable rules. To raise documentation parser bounds for the next server start:

RIFT_DOCUMENTATION_MAX_FILE=16mb RIFT_DOCUMENTATION_MAX_NODES=5000000 rift server start

Dependencies

Since v0.0.39 · Rust, Python, TypeScript

dependencies shapes the dependency context a global or all read sends to the global index: which packages you name beside the ones the workspace's own files state, and whether a standard library version probe runs. The keys are shown at their defaults:

[dependencies]
resolution = "auto"
packages = []
command_timeout = "2m"
  • resolution selects whether a standard library version probe runs: auto runs rustc --version, and node --version when no Node.js pin names a version, in the workspace root; static runs no program, so every version comes from the pins and project files alone, or goes out as the requirement >=0
  • packages names packages beside the ones the workspace's manifests and lockfiles state, at most 20000 of them. Each entry carries manager, name, and exactly one of version and requirement; an entry naming both, or neither, is refused
  • command_timeout bounds one version probe, 1s to 1h; a probe past it is killed and the entry goes out with its static reading

A configured entry looks like this:

[[dependencies.packages]]
manager = "cargo"
name = "serde"
version = "1.0.228"

[[dependencies.packages]]
manager = "npm"
name = "typescript"
requirement = "^5.9.0"

Dependency collection

Dependency manifests, lockfiles, installed environment listings, and version probes use [dependencies.collection]. Its defaults are:

[dependencies.collection]
lockfile_size = "16mb"
toolchain_output = "64kb"
manifests = 256
packages = 20000
directory_entries = 16384
pin_size = "64kb"
project_size = "1mb"
record_size = "4mb"
nesting_depth = 32
  • lockfile_size caps one manifest or lockfile read, 1b to 64mb
  • toolchain_output caps retained bytes from each version probe stream, 1b to 1mb
  • manifests caps manifests read by one resolver, 1 to 4096; ancestor comparisons take at most manifests squared work
  • packages caps packages held in one dependency context, 1 to 20000, within the global API's package bound
  • directory_entries caps entries in one installed environment directory listing, 1 to 5000000
  • pin_size caps one toolchain or version pin file, 1b to 64mb
  • project_size caps one standard library project file, 1b to 64mb
  • record_size caps one installed distribution's RECORD file, 1b to 64mb
  • nesting_depth caps package names nested in one Bun package key, 1 to 1024

These keys use variables such as RIFT_DEPENDENCIES_COLLECTION_LOCKFILE_SIZE and RIFT_DEPENDENCIES_COLLECTION_MANIFESTS. They pass the same merged configuration validation as the other dependency keys.

Package collection

Exact-package collection separates input and parsing bounds from publication capacity. [source] bounds selected files, source bytes, declarations, and relationships; [providers.syntax] bounds each syntax parse; [documentation] bounds collected documentation. [package] bounds the records that analysis publishes:

[package]
units = 100000
symbols = 1000000
documents = 1100000
warnings = 256
identifier_terms = 1024
retained_source = "1mb"
  • units caps published source units, 1 to 5000000
  • symbols caps published declaration records, 1 to 50000000
  • documents caps published search documents, 1 to 55000000
  • warnings caps retained publication warnings, 1 to 65536
  • identifier_terms caps identifier terms retained per search document, 1 to 65536
  • retained_source caps source bytes retained per record, 1b to 64mb. Analysis cuts larger source at a UTF-8 boundary and reports the dropped bytes
  • retained_total, absent by default, caps retained source bytes across units, symbols, and documents together. It accepts retained_source to 18446744073709551615b; a smaller total fails validation

The total counts every published copy before allocation. A file retained in both its source unit and its search document counts twice; a declaration with a published search document also counts twice. A record without that search document counts once, and each published notebook cell document counts its retained decoded source once.

An omitted retained_total keeps the existing aggregate behavior. To bound retained copies together while allowing larger records:

[package]
retained_source = "8mb"
retained_total = "2gb"

Collection refuses before allocating a retained copy that would pass the total. Source input bytes, parser input bytes, and retained publication bytes are independent limits, so raising retained_source alone does not raise the parser's max_file.

Every package key uses the environment variable rules, including RIFT_PACKAGE_SYMBOLS, RIFT_PACKAGE_RETAINED_SOURCE, and RIFT_PACKAGE_RETAINED_TOTAL.

Archives

Archive acquisition runs before source selection and package analysis. The defaults are:

[package.archive]
compressed_size = "64mb"
expanded_size = "512mb"
member_size = "64mb"
extension_size = "16kb"
members = 100000
expansion_ratio = 200
  • compressed_size caps compressed archive bytes, 1b to 64gb
  • expanded_size caps expanded bytes together, 1b to 64gb
  • member_size caps one member's bytes, 1b to expanded_size, at most 64gb
  • extension_size caps one GNU or PAX extended header, 1b to 64mb, checked before allocating its contents; that header must also fit member_size
  • members caps all archive members, including directories, links, and extended headers, 1 to 5000000
  • expansion_ratio caps expanded bytes per compressed byte, 1 to 4294967295; expanded_size still bounds the result

These keys use variables such as RIFT_PACKAGE_ARCHIVE_COMPRESSED_SIZE and RIFT_PACKAGE_ARCHIVE_EXPANSION_RATIO. Archive limits apply before the selected source budgets, so raising a publication limit does not raise archive admission.

Telemetry

telemetry controls daily counters derived from existing cloud and download requests. Rift sends no separate activity report, collects no additional local metadata, and creates no telemetry marker. Privacy describes the counters and retention.

[telemetry]
disabled = false

disabled defaults to false. Set it to true to exclude this project's requests from counters:

rift config set --telemetry.disabled=true

rift config set updates the current directory's rift.toml, preserving other values and comments. The telemetry.disabled setting takes boolean true or false; malformed documents and symbolic-link targets are refused without replacement. The command also applies LSP selections.

For every project, write the setting to ~/.rift/config.toml:

rift config set --telemetry.disabled=true --user

A global disable takes precedence over a project's false value. Set the global value to false to allow each project to choose its own value. RIFT_TELEMETRY_DISABLED=true also suppresses counters; false cannot override a disable in either file.

Standalone installers accept RIFT_TELEMETRY_DISABLED=true for the same opt-out. They do not read the CLI's TOML files. Local-only use creates no telemetry request, so these counters cannot measure offline activity or unique daily active users. A disable affects future requests; existing counters expire under their retention policy.

Global

Since v0.0.43

global controls package reads through the configured REST API. The local server creates this client on the first global or all read that can return package declarations.

The default global API records daily request counts by country, agent category, operation, method, and response status. Privacy explains the collection and retention.

[global]
enabled = true
endpoint = "https://api.volar.sh/rift/rest"
token_env = "RIFT_API_TOKEN"
connect_timeout = "3s"
request_timeout = "15s"
attempts = 3
max_in_flight = 4
max_request = "4mb"
max_response = "32mb"
max_source = "1mb"
capabilities_ttl = "15m"
resolution_ttl = "15m"
failure_ttl = "30s"
  • enabled defaults to true; false sends no package read to the global API, so a package-scope read answers project hits alone and reports global_access_disabled
  • endpoint defaults to https://api.volar.sh/rift/rest; it must be an absolute base URL without user information, query, fragment, or trailing slash
  • token_env defaults to RIFT_API_TOKEN and names the environment variable holding the optional bearer token. An unset variable uses anonymous access
  • connect_timeout defaults to "3s" and accepts 100ms to 30s
  • request_timeout defaults to "15s" and accepts 1s to 5m; it covers connection, response, retry delays, and every attempt
  • attempts defaults to 3 and accepts 1 to 5
  • max_in_flight defaults to 4 and accepts 1 to 32
  • max_request caps one request body before sending, 1b to 64mb, default 4mb
  • max_response caps one response body while reading, 1b to 1gb, default 32mb
  • max_source caps one source payload, including documentation source and excerpts, 1b to 64mb, default 1mb; the client applies the smaller of this value and the source bound the global API advertises. Documentation excerpts also keep their separate response bound
  • capabilities_ttl and resolution_ttl default to "15m" and accept 1m to 24h
  • failure_ttl defaults to "30s" and accepts 1s to 1h

When token_env names a non-empty valid value, the client sends it as the Authorization: Bearer header to the configured endpoint. Empty or malformed values leave global access unavailable before the client sends a request, and the token never enters logs, errors, metrics, or cache keys.

The client does not contact the endpoint at server startup. A local read and a file-only search also make no global request.

A connection failure, timeout, retry exhaustion, authentication failure, invalid response, or non-success response marks global access unavailable for failure_ttl. The package read that met it, and every package read until failure_ttl passes, answers the project hits it has and reports a typed warning with that failure's class. A valid remote page keeps its items and reports each bounded service condition as global_page_warning.

A valid resolution answers package reads from the global index, and the nearest collected release answers an entry whose version it lacks, reporting package_substituted. A package with no collected release reports package_absent, or package_requirement_absent for a declared requirement. A package no public registry serves - a path outside the workspace, a git repository, a private registry, or a URL - never reaches the global API and reports package_unavailable.

Languages and LSP

Since v0.0.25

An exact language entry connects visible paths to shipped syntax and an optional LSP process. Its key is a language name, or name:dialect for an exact dialect:

[languages.rust]
enabled = true
include = ["**/*.rs"]
exclude = ["vendor/**"]
lsp = "rust"

[lsp.rust]
command = ["rustup", "run", "1.98", "rust-analyzer"]
environment = {}
startup_timeout = "30s"
request_timeout = "1m"
settle_delay = "500ms"
output_limit = "4kb"

[lsp.rust.retry]
attempts = 8
delay = "250ms"
delay_limit = "2s"

[lsp.rust.restart]
attempts = 3
window = "5m"

Every shipped syntax provider contributes its file patterns. An absent include keeps those patterns; a present list replaces them, and an empty list matches no file. exclude removes matches from the effective list. enabled defaults to true, exclude defaults empty, and lsp is absent by default. A bare language and each of its dialect entries are independent. enabled = false turns off syntax and LSP service for paths the entry matches.

Since v0.0.66 · HTML, CSS, Vue, Svelte, Angular, C, C++, Cython, JSONC

Shipped providers select .html, .htm, .css, .vue, .svelte, .c, C++ source and header extensions, .pyx, .pxd, .pxi, and .jsonc. Extension selection ignores ASCII case; configured path patterns retain their own matching rules. Rift selects C for .h files by default; that extension does not establish whether a header contains C or C++. Select C++ headers explicitly, and give C an include list that leaves those paths to C++:

[languages.c]
include = ["**/*.c", "include/c/**/*.h"]

[languages.cpp]
include = ["**/*.cpp", "**/*.hpp", "include/cpp/**/*.h"]

The html:angular provider claims no extension by default. Explicit Angular template selection uses an exact dialect entry and removes the same paths from ordinary HTML selection:

[languages.html]
exclude = ["src/components/**/*.html"]

[languages."html:angular"]
include = ["src/components/**/*.html"]

A language no shipped syntax provider knows must carry a nonempty include list. It can still select an LSP process. One visible path may match one exact language; a current path two entries claim refuses the workspace candidate, naming the path and both identities. The previously published revision keeps serving. A visible file no language claims remains available to generic reads, and joins lexical search as plain text when [search.text].include selects it. So does a file whose entry is disabled, or whose language ships no grammar: what those entries drop is syntax analysis, not the file.

Since v0.0.46 · Rust, JavaScript, TypeScript, Python

stdlib decides whether the dependency context names the language's standard library, so a global or all read can answer for it: stdlib/rust for rust, stdlib/node and the npm typescript package for javascript, typescript, and typescript:tsx, and stdlib/python for python. It defaults to true. A Rust repository whose Python is a few build scripts can leave Python's standard library out of package reads:

[languages.python]
stdlib = false

A library stays in the context while any language the workspace uses that names it keeps the key on. A repository holding .ts and .js files therefore still names stdlib/node when only [languages.typescript] sets stdlib = false, because javascript names it too.

lsp accepts an inline process table or the name of one shared top-level process. The inline table collapses to one key when the program takes no arguments:

[languages.rust]
lsp.command = "rust-analyzer"

A named process may serve several exact identities, including dialects:

[languages.typescript]
lsp = "typescript"

[languages."typescript:tsx"]
lsp = "typescript"

[lsp.typescript]
command = ["node_modules/.bin/typescript-language-server", "--stdio"]

[lsp.typescript.initialization_options.tsserver]
useSyntaxServer = "never"

LSP recommendations

Since v0.0.71 · Rust, Python, JavaScript, TypeScript

Rift recommends language servers for the source and package directories in the project:

rift config recommend

The command identifies Cargo, Bun, npm, and uv projects from their manifests and lockfiles. It prints rift config set commands for languages the configuration does not already provide. The command reads visible files and installed executables, leaves rift.toml unchanged, and starts no language server or package installation.

Cargo selects rust-analyzer, using the project's pinned Rust toolchain when available. Bun and npm select the installed native TypeScript language server, then typescript-language-server from node_modules/.bin before PATH. uv selects ty, then pyright-langserver, from the project's .venv before PATH. A missing executable leaves the recommendation unavailable.

The recommendation command reads each directly registered project's own configuration and visible files. Registered names must resolve to distinct directories, each different from the primary project root.

The map lists actionable recommendations in recommendations, with each entry's language, root, and command. Once the matching configuration is accepted, the next map publication leaves that recommendation out.

Select a package manager for a language:

rift config set --language=rust --lsp-package-manager=cargo

The command writes package_manager in the language's inline lsp table. Add --profile and --profile-root to select a language server for one directory:

rift config set --language=typescript --profile=docs --profile-root=docs --lsp-package-manager=bun

A profile accepts repeated --include and --exclude patterns relative to its root. Omitted patterns preserve the profile's existing language filters. The command writes the profile in the root rift.toml. Each repository uses one configuration file.

A custom process remains available through --lsp-program. Repeat --lsp-argument for each literal argument:

rift config set --language=typescript --lsp-program=node_modules/.bin/typescript-language-server --lsp-argument=--stdio

LSP profiles

Since v0.0.71

A project can keep separate package installations in its root and in docs/. Define both language selections in the root rift.toml:

[languages.rust.lsp]
package_manager = "cargo"

[profiles.docs]
root = "docs"

[profiles.docs.languages.typescript.lsp]
package_manager = "bun"

[profiles.docs.languages."typescript:tsx".lsp]
package_manager = "bun"

The docs profile starts its language server in docs/, using that directory's package installation. Profile source paths and language include and exclude patterns are relative to docs/. The primary project leaves the profile directory to that profile, so all returns each source once.

Since v0.0.72

Search selects the profile with scope: "local:docs". Its symbol identities carry the local:docs owner, for example rift://symbol/local:docs/typescript/page/beacon. The primary project's symbols keep the local owner. A query search with scope: "all" includes the primary project, profiles, registered extra projects with their profiles, and global facts.

After creating a named profile, reconnect the MCP client before searching its scope or including it in all results. Existing profiles accept language selections on later requests.

Profile roots stay below the root project and cannot overlap another profile root. Each profile selects its own languages and can refer to shared top-level lsp definitions. Rift reads the profile from the root configuration when serving that directory. A profile can override dependency and execution settings. Profile roots hold at most 63 path segments and cannot cross a Git repository boundary; register a separate repository through [mcp.extra].

Profiles inherit shared server and provider settings. Profile servers use the default port range; the root's server.port and server.port_range apply to the primary project.

Extra projects

[mcp.extra] registers additional workspaces, each with its own root configuration. A relative path starts at the primary project root. rift mcp --extra name=path adds a registration for one connection and replaces a saved one with the same name.

Since v0.0.72

Each extra project also serves the profiles its own rift.toml defines. Rift serves each such profile from its folder with that project's profile settings, as the extra project's own server would. Rift does not follow the extra project's own [mcp.extra] registrations.

Consider a primary project that registers two repositories next to it:

[mcp.extra]
api = "../api"
web = "../web"

The web repository keeps a documentation site in docs/ and defines a profile for it in its own rift.toml:

[profiles.docs]
root = "docs"

[profiles.docs.languages.typescript.lsp]
package_manager = "bun"

The connection then accepts these scopes:

ScopeSearches
localThe primary project
local@api../api
local@web../web, without its docs/ folder
local@web:docs../web/docs, with the docs profile's settings
allEvery scope above and global facts

Symbols carry the same owner as the scope that serves them, for example rift://symbol/local@web:docs/typescript/page/beacon. A scope naming a project or profile the connection did not register, such as local@web:api, fails with parameter_invalid listing the accepted scopes.

rift mcp reads each extra project's rift.toml when it starts. When that file can't be read or fails validation, the connection still starts and local@name stays registered; its server reports the refusal when a request reaches it. That project serves no profiles: all leaves them out, and a scope such as local@web:docs fails with the same refusal.

A profile whose root is already registered, as the primary project or another extra project, stops the connection from starting with configuration_invalid. So does a primary project profile whose folder holds, or lies inside, an extra project or one of its profiles.

A saved registration whose folder lies below the primary project root leaves that folder out of the primary project's index, so its files are served once, under local@name. rift server reads only the saved rift.toml, so rift mcp refuses a --extra registration below the primary project root, and one that replaces a saved registration below it, with configuration_invalid. Save such a registration in [mcp.extra] instead.

Language engines

Since v0.0.31 · Python

An entry may select the embedded engine instead of a spawned command. The build links the engine in, so nothing is installed or started beside the server:

[languages.python.lsp]
embedded = "ty"

embedded names an engine this build carries; ty serves Python. Exactly one of command, package_manager, and embedded is present. An embedded engine takes no environment and no initialization_options: both configure a spawned process, so a table naming them beside embedded is refused.

The embedded engine reads ty's configuration from the served tree alone: the root's own ty.toml, or the [tool.ty] table in its pyproject.toml. A ty.toml or pyproject.toml in a directory above the root is never read, so the project the engine analyzes is the served tree.

Its Python environment is the one that configuration names, else the project environment in .venv when its pyvenv.cfg stands, else none, which leaves the tree's own modules and the typeshed stubs the engine ships. The server's VIRTUAL_ENV, CONDA_PREFIX, PYTHONPATH, and PATH never reach it, so the engine reads the same .venv the uv resolver reads beside a uv.lock at the tree's root. Creating, removing, or recreating .venv, and each distribution uv sync installs, removes, or upgrades in it, takes effect on the engine's next request, with no server restart.

command accepts a program string without whitespace or a nonempty list holding the program and its literal arguments. The list holds one program and at most 64 arguments; each argument holds at most 4096 bytes. Rift starts the command directly without a shell. Absolute programs are refused. A bare name resolves through the server's PATH, with environment applied on top. initialization_options is absent by default and must be a JSON object when present; environment defaults empty and holds at most 64 entries.

startup_timeout defaults to "30s" and takes "1s" to "10m". request_timeout defaults to "1m" and takes "1s" to "10m". settle_delay defaults to "500ms" and takes "10ms" to "1m". output_limit defaults to "4kb" and takes "1kb" to "8mb"; Rift still reports the full size, so you can tell a truncated log from a short one.

retry bounds repeated requests while an answer stays unsettled. attempts includes the first request; waits start at delay, double, and stop at delay_limit. restart bounds replacement processes after a process ends or stops answering. A replacement older than window stops counting. The server starts a process on its first request and keeps it across later requests. Configuration reload reuses unchanged process definitions and ends replaced processes.

retry.attempts defaults to 8 and takes 1 to 64. retry.delay defaults to "250ms" and takes "1ms" to "1m"; retry.delay_limit defaults to "2s" and takes "1ms" to "10m". restart.attempts defaults to 3 and takes 0 to 16; restart.window defaults to "5m" and takes "1s" to "1d".

A named process that no language selects is accepted but never started. A language that names a missing process is refused during configuration validation. The top-level table holds at most 16 processes. The workspace holds at most 64 language entries. Each language include and exclude list holds at most 64 patterns.

A language server may decline a request it's already serving, report that the file moved under it, or answer before it has loaded the declaration. Every such refusal gets the configured retry schedule. If every attempt refuses, Rift returns the language server's latest words.

settle_delay decides when an answer stops being provisional. A language server reports what it is loading over work-done progress, and rust-analyzer ends and begins those reports in bursts: its outstanding work empties several times inside one project load, well before the phase that resolves references has run. So Rift calls a process loaded only once its work has stayed announced and empty for settle_delay, measured at the last output Rift read from it. Raise it for a process whose load falls silent for longer than half a second between phases; the retry schedule above is what carries a request across the wait.

A capability the server never advertised is not a refusal and returns at once.

restart bounds what Rift does when the process itself fails. One that ended, that couldn't be started, or that stopped answering is replaced automatically, and the replacement serves the request. Each replacement spends one of restart.attempts, and a replacement older than restart.window stops counting, so a process that dies once a week never runs out.

One that crash-loops does: once the budget is spent, requests for its languages fail temporarily_unavailable until the window passes, instead of starting a process that dies again. The first process a workspace starts is the start, not a replacement, and a refused command program, empty or an absolute path, is reported at once rather than started again.

A symbol identity carries its exact language, including the dialect for a declaration in a .tsx file. The server looks the language entry up by that exact identity, so the bare language and the dialect are two independent entries, each selecting its own process, and both may select the same named one. Each dialect needs its own entry.

A minimal entry names its process inline. This one names tombi, a TOML formatter, linter, and language server:

[languages.toml.lsp]
command = ["tombi", "lsp"]

lsp is the subcommand that starts tombi's language server over stdio.

package_manager selects an installed language server for that manager's language family:

  • cargo serves Rust through rust-analyzer. When rustup and a toolchain pin are available, Rift runs rustup run <channel> rust-analyzer with automatic installation disabled.
  • bun and npm serve JavaScript, TypeScript, and typescript:tsx through the installed native TypeScript executable with --lsp --stdio, falling back to typescript-language-server --stdio.
  • uv serves Python through ty server, falling back to pyright-langserver --stdio.

Rift resolves these executables from the selected project's installation and then PATH, and starts them directly. The selection installs no packages. Pin the language server and its runtime packages in the project's manifest and install them before using the language server.

typescript-language-server refuses its initialize request unless a typescript package resolves from the workspace root, answering Could not find a valid TypeScript installation. Install that package in the workspace, or name an existing one with the tsserver.path initialization option; without either, language-server initialization fails with that message.

The index still walks what source leaves visible, so the installed package directory needs an exclude:

[source]
exclude = ["node_modules/**"]

Without it the walk reaches typescript's own sources, including files past the index's per-file byte bound. Any workspace with an installed package directory needs the same entry.

tsserver.useSyntaxServer = "never" keeps the process to one semantic server. The default auto also runs a syntax-only server.

Providers

Since v0.0.7 · Rust, JavaScript, TypeScript, Markdown, JSON, YAML, TOML, Python

The built-in providers run without configuration; the providers table exists to bound or disable them:

[providers.history]
enabled = true
strategy = "everything"
max_revisions = 100
batch_commits = 25
batch_size = "1000000b"
cpu_share = 0.25

enabled = false turns the history provider off: the server refuses a revision-addressed read - rev on get_symbol, search, or nodes - a change comparison, and a commit search with capability_unavailable. A workspace without version control cannot serve those reads whatever this table says.

Since v0.0.46

The history provider fills the history store in the background, and the configuration allows you to describe:

  • strategy - which revisions the store holds: everything, the newest first-parent commits of every live worktree, or selective, the releases releases names
  • releases - tag names or tag patterns such as v1.*, at most 64, which selective requires and no other strategy accepts; a pattern that does not compile as a glob, such as the unclosed class v[1, is refused
  • max_revisions - how many revisions the store holds, 1 to 100000: commits under everything, releases under selective
  • batch_commits - how many commits one store write holds, 1 to 10000
  • batch_size - how many parsed bytes one store write holds past its first commit, 1kb to 1gb
  • cpu_share - the share of one core the fill parses at, above 0.0 up to 1.0

selective orders the releases by the version each tag name spells once the pattern's literal text up to its first digit or wildcard is stripped: 1.* reads 1.80.0 whole, and v0.0.* strips the v. A matched tag whose rest is no semantic version, such as 0.10, is left out, and the server records it under rift server logs --component history.

A fill waits a bounded time for the server to run no request before each batch, then parses at cpu_share, pausing after each commit. The server reads this table at startup, so a change applies on the next start. The store keeps one file per server build and strategy, so changing strategy or releases fills a new store file.

Since v0.0.44

A syntax provider's cost grows with the source it parses, so [providers.syntax] bounds each source every syntax provider takes:

[providers.syntax]
max_file = "4mb"
max_nodes = 250000
max_depth = 512
  • max_file bounds the bytes of one source, 1b to 64mb
  • max_nodes bounds the syntax nodes one source produces, 1 to 100000000
  • max_depth bounds how deep one source's syntax tree nests, 1 to 65536

A source past any bound is left out of the index while the rest of the workspace serves: a generated file of deeply nested literals answers no search, and every other file still does. The server records each such file under rift server logs --component index, with the path and the bound it crossed; raise that key to index the file.

Since v0.0.66 · Angular, Tailwind

Framework context is separate from language selection. Angular component imports establish ownership for literal inline and external templates; Tailwind requires a captured package version and applicable configuration or stylesheet evidence. An unresolved version or dynamic expression appears as framework_context_unresolved in read warnings, with its source and framework.

providers.syntax.frameworks supplies explicit context for selected paths:

[[providers.syntax.frameworks]]
include = ["src/components/**/*.html", "src/components/**/*.ts"]
angular = true

[[providers.syntax.frameworks]]
include = ["src/styles/**/*.css", "src/views/**/*.vue", "src/views/**/*.svelte"]
tailwind = "v4"

The list defaults empty and holds at most 256 entries. Each include list holds at most 256 patterns; an empty list selects no source. angular defaults false; tailwind is absent by default and accepts "v3" or "v4". One entry can supply both frameworks. More than one entry matching a source leaves context unresolved and records warnings, so combined selections belong in one entry.

Explicit Tailwind selection supplies the supported major version without package or stylesheet evidence. Explicit Angular selection applies to matched HTML and templates owned by matched TypeScript components. It does not change the TypeScript class's parser identity. Paths outside selected or established context retain ordinary syntax. Static classes and supported CSS directives publish authored names; dynamic expressions retain source ranges and warnings. Compiler resolution remains outside syntax analysis.

Provider configuration does not set merge order or freshness. A provider rebuilding after a file change can serve its previous immutable revision; the result then carries a stale_index entry in warnings naming the two tree revision digests.

Since v0.0.9

search runs on a lexical index. [search.text] selects the plain-text files that join it through include and bounds their chunks. The lexical index lives in the index database at .rift/index, and the vector ranking's rows in the vectors database at .rift/vectors. pool_slots, busy_timeout, and journal_size_limit bound each of the two on its own, and writes to one wait for that database's turn alone.

Since v0.0.25

A visible file joins the lexical index through its effective language entry, or through [search.text].include when no language claims it. Documentation collection also selects visible .md, .markdown, .mdx, .rst, and .ipynb sources. Its .txt selection follows include. Narrow that list where the workspace holds large unclaimed files search should not carry:

[search.text]
include = ["**/*.md", "**/*.mdx", "**/*.txt"]
max_chunk = "1mb"

[search]
results = 1000
symbol_alternatives_work = 16777216
symbol_alternatives_lowercase_work = 12
pool_slots = 4
busy_timeout = "5s"
journal_size_limit = "64mb"
  • include selects text files from the visible paths no language entry claimed, at most 64 patterns. It defaults to ["**"], so every such path joins the lexical text index; an explicit empty list leaves only language-selected files and required documentation sources
  • max_chunk bounds how large a selected text file may grow before it's split into several indexed chunks, 1kb to 16mb
  • results caps results collected by one local search or symbol lookup, 1 to 10000, default 1000; an answer that reaches it reports results_truncated
  • symbol_alternatives_work bounds one complete closest-declaration ranking, 1 to 1073741824, default 16777216. Every visited file and declaration costs one work unit, including language exclusions. Lowercase allocation reserves the configured weight per input byte; a distance comparison reserves (Q + 1) * L units for the lowercase query and candidate character counts. Stable ordering reserves three times the sum of qualified-name bytes, path bytes, and one byte. Exhaustion discards all alternatives; symbol_not_found.detail reports the configured bound, required work, remaining work, and recovery action
  • symbol_alternatives_lowercase_work sets that reservation weight, 12 to 1024, default 12. Rust lowercase mappings return at most three characters, each at most four UTF-8 bytes. Values below 12 are refused; larger values reserve more work without changing Unicode mappings. Both keys apply to local project alternatives, including a revision read
  • pool_slots bounds SQLite connections the index database may hold open at once, and separately the vectors database, 1 to 16
  • busy_timeout bounds how long a read or write waits for one of those connections to come free, and how long a connection waits for a database lock held by another process before failing with SQLITE_BUSY, 100ms to 30s
  • journal_size_limit Since v0.0.62 bounds the size the write-ahead log of the index database, and separately of the vectors database, is cut back to at the commit that restarts it after a checkpoint, 0b to 64gb, default 64mb. A transaction larger than the limit still grows the log past it until that commit
Since v0.0.46

large_files decides what search does with a file whose text runs past max_chunk, and excluded_lockfiles names the lockfiles search leaves out:

[search.text]
large_files = "split"
excluded_lockfiles = ["Cargo.lock", "uv.lock", "package-lock.json", "bun.lock"]
  • large_files is split, the default, or skip. Under split the file is indexed as whole-line chunks of at most max_chunk, and a file past [providers.syntax] max_file is held as text the syntax provider does not parse, up to [source] workspace_size; every answer names such files in a large_file_unparsed warning. Under skip the file leaves the text index, a file past max_file leaves the index entirely, and an answer whose paths reach one counts it in a large_file_skipped warning
  • excluded_lockfiles names the lockfiles search leaves out, matched against each visible file's final path segment, at most 64 names of up to 255 bytes. The default lists Cargo.lock, uv.lock, package-lock.json, bun.lock, pnpm-lock.yaml, yarn.lock, poetry.lock, Pipfile.lock, composer.lock, Gemfile.lock, go.sum, and deno.lock; an empty list indexes every lockfile

A lockfile search leaves out answers no search and no symbol lookup, while rift://map still carries the versions it pins. A search whose paths.include selects one warns lockfile_excluded, and naming it in paths.force_include searches it for one request.

A regex pattern search runs under four [search] keys:

[search]
pattern_compiled_size = "1mb"
pattern_candidate_rows = 10000
pattern_verified_size = "128mb"
pattern_matches_per_file = 1000
  • pattern_compiled_size bounds the bytes one pattern's matcher compiles to, 64kb to 64mb
  • pattern_candidate_rows bounds the trigram index rows one search reads to select the files it verifies, 100 to 1000000; a file split into chunks counts once per chunk
  • pattern_verified_size bounds the file text one search verifies, 1mb to 64gb
  • pattern_matches_per_file bounds the matches one file contributes, 1 to 100000

A search that would pass one of the first three refuses naming the key. A file past pattern_matches_per_file is cut there, and the answer warns pattern_matches_truncated.

While the trigram index catches up after a write, the rows it lacks are verified within what the selection leaves of pattern_candidate_rows and pattern_verified_size. Past either, the search answers from the rows the index holds and warns pattern_index_preparing rather than refusing.

Since v0.0.16 · Rust, JavaScript, TypeScript, Markdown, JSON, YAML, TOML, Python

The rankings are combined by reciprocal-rank fusion: a result scores as the weighted sum of 1 / (k + rank) over the rankings that returned it. [search.ranking] carries the weights and the k:

[search.ranking]
identifier_weight = 0.35
lexical_weight = 0.35
vector_weight = 0.30
fusion_k = 60
  • each weight is that ranking's share of a fused score, 0.0 to 1.0. They don't have to sum to 1: the server normalizes the weights of the rankings that actually answered, so an unavailable vector ranking doesn't rescale what the other two returned. At least one weight must be above 0
  • identifier_weight weighs identifier matching, which ranks the identifiers a query carried against the indexed declaration and qualified names. A question naming a declaration in prose reaches it through this ranking
  • lexical_weight weighs the full-text ranking over declaration names, signatures, and attached documentation, and over the text of every indexed file
  • vector_weight weighs the vector ranking, which reaches code sharing no word with the query
  • fusion_k sets how sharply a top rank counts, 1 to 1000; a larger value flattens the contribution curve and lets agreement between the rankings outweigh one ranking's top position

[search.vector] bounds the vector ranking, and [search.vector.embedding] names the model it runs on:

[search.vector]
candidates = 200
candidates_per_file = 3
max_vectors = 200000

[search.vector.embedding]
kind = "hf"
model = "minishlab/potion-retrieval-32M"
download_timeout = "5m"
download_attempts = 3
batch_inputs = 32
max_tokens = 256
  • candidates bounds how many declarations the vector ranking returns before the rankings are fused, 1 to 1000
  • candidates_per_file bounds how many declarations one file contributes to that list, 1 to 64; the rest of the file's declarations are dropped, so one file whose declarations all rank well can't fill the list on its own and leave every other file unreachable
  • max_vectors bounds how many vectors the workspace holds, 1000 to 1000000; each vector costs the model's dimension in single-precision floats
  • kind selects which embedding runs, and each kind carries only the keys its execution reads: hf downloads the weights from a Hugging Face repository and caches them where every other Hugging Face client on the machine caches them, directory reads a directory the workspace already holds and downloads nothing, and openai_compatible sends embedding inputs to a service implementing the OpenAI embedding shape
  • model names which weights, in the form kind sets: under hf a repository identifier such as minishlab/potion-retrieval-32M, optionally carrying a revision after @; under directory a workspace-relative directory; under openai_compatible the model the service exposes. The server stores vectors per model - two models' vectors aren't comparable - so changing the value embeds the workspace again
  • The model's own config.json decides how the server reads it. A model_type of model2vec names a static model, which embeds a declaration by averaging one row per token; anything else is read as a BERT checkpoint, which runs a forward pass per batch. The shipped default is static, because a forward pass over every declaration in a workspace costs minutes on a laptop CPU and a row gather costs seconds
  • download_timeout and download_attempts bound one model acquisition under hf, 10s to 1h and 1 to 10; a spent budget leaves search lexical for the life of the server
  • batch_inputs and max_tokens bound one embedding pass, 1 to 256 and 32 to 512 under a local model. A BERT checkpoint's attention memory grows with the square of the token window, so a raised window wants a lowered batch; a static model reads one row per token, so its cost grows with the window rather than with its square

An openai_compatible embedding sends declaration text outside the project while vectors are built, and query text while a search runs. The local default sends neither: it starts without a key and makes no request once the weights are acquired.

[search.vector.embedding]
kind = "openai_compatible"
endpoint = "https://api.openai.com/v1"
model = "text-embedding-3-small"
revision = "2024-01-25"
dimensions = 1536
api_key_env = "OPENAI_API_KEY"
request_timeout = "30s"
attempts = 3
batch_inputs = 64
max_in_flight = 4
  • endpoint is the base URL the request path is appended to. It uses https; http is accepted only when the host is localhost, 127.0.0.1, or ::1
  • revision is a deployment identifier the operator states, such as a dated release. It participates in vector invalidation, so an endpoint serving new weights under one model name gets a new value here
  • dimensions is how many coordinates one vector carries, 1 to 16384
  • api_key_env names the environment variable holding the credential, and never carries the credential itself
  • request_timeout, attempts, batch_inputs, and max_in_flight bound one embedding request and the requests running together: 1s to 5m, 1 to 10, 1 to 2048 inputs, and 1 to 32 requests
  • the service must answer one vector per input and declare each one's input index. The server associates by that index rather than by array order, and refuses a response that repeats an index, omits one, or names one past the request's own length: any of those would attach a vector to the wrong declaration

disabled keeps the workspace lexical without emptying the rest of the table, and no vectors database is created. With vector ranking enabled, .rift/vectors appears at the first vector operation. Set disabled where the workspace must not fetch model weights:

[search.vector]
disabled = true
Since v0.0.32

[search.lexical].units_max bounds how many units the lexical index holds, one per indexed file, text chunk, and declaration, 1000 to 50000000:

[search.lexical]
units_max = 1000000
transaction_units = 5000
transaction_size = "16mb"
mmap_size = "1gb"

A workspace past it publishes its index without the lexical ranking: the store refuses the commit, and search answers from identifier matching with a lexical_ranking_unavailable warning naming units_max, the observed count, and the maximum. Raise the key, or narrow [source] so fewer files reach the index.

transaction_units, 100 to 1000000, and transaction_size, 1mb to 1gb, bound one transaction of a lexical write. A write past either commits in several transactions, and the last one stamps the tree it answers for. One file's units always share a transaction, so a file holding more takes one of its own. The bounds are what a server asked to stop waits on: it aborts the transaction running, and the next start writes what that transaction did not reach. The trigram index behind regex search is filled after each write in transactions under the same two bounds.

One unit holds at most 16mb of content, the largest chunk max_chunk accepts, so every chunk the configuration derives fits. A declaration's unit carries its names, signature, and documentation alone; its text lives in its file's chunks.

Since v0.0.46

mmap_size sets how much of the index database each connection reads through a memory map, 0b to 2147418112b, the most the bundled SQLite maps; the default 1gb maps the whole store of a workspace the size of the Bun or Next.js repositories. 0b reads through SQLite's page cache alone. Every pooled connection maps the file on its own, so resident memory counts the mapped pages once per open connection, and an I/O error on the mapped file ends the server with a signal rather than an error; SQLite's memory-map page states both costs.

Connections of the vectors database map nothing and read through the page cache alone.

On this page