Configuration
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.jsonTreat 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.44A 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 as5000000,true, or["vendor/**"] - the merged configuration passes the same checks the file does, so a value outside its range
fails with
configuration_invalidnaming 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.
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.8server 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_workerscaps concurrent blocking operations and sizes the parser worker pool, 1 to 64worker_queue_timeoutbounds one request's wait for a free worker, 1ms to 1h - when it expires, the request fails withtemporarily_unavailableand the client can retry without stacking another waiteridle_timeoutstops the server once no request remains active and that span has passed after the last response completed, 1s to 1d; the nextrift mcporrift server startbrings it backreadiness_timeoutbounds 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 withlexical_ranking_unavailableinstead of waiting for the transactionportpins the exact loopback port, 1024 or above; omitted, the server picks the first free port fromport_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.47Initial 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 = 16validation_intervalsets 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_timeoutbounds the wait for the worktree's Gitindex.lock, 1ms to 30s. A lock retained past the bound is passed and named in the log; the server leaves the lock in place.workspacescaps the workspaces retained by one repository process, 1 to 64. A new workspace is refused once that limit is reached; concurrent admission waits withinworker_queue_timeoutfor 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.22logs 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_recordscaps the records the store keeps, 100 to 1000000; a write past the cap drops the oldest records firstqueue_recordscaps the records each log subscription holds, 1 to 65536. A full subscription drops and counts excess records independently of other subscriptionsflush_intervalis how long the drain waits before writing a partial batch, 1ms to 1hretry_intervalis 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 stopscardinality_limitcaps attribute sets each metric instrument holds, 1 to 65536. Measurements past it join theotel.metric.overflow=trueseriescaptureselects which targets are recorded, in theRUST_LOGspellingtracingtakes -rift_mcp=debugrecords the index lane's own rebuild decisions. A target this filter excludes never reaches the store, whatever the terminal showsstall_delaySince 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 reportedstderr_limitSince 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 inlog.stderr.discarded; the store keeps every recordcaptureadmits either way. The server's stop records the count asstandard 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.6source 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 = 64The four glob lists decide in one order:
excludedrops a file, whatever else matched it.force_includekeeps a file the repository ignore rules would hide, and keeps it without anincludematch.respect_gitignore, on by default, applies root and nested.gitignorefiles and the repository-local.git/info/excludefile. Linked worktrees share the repository-local exclusions. The server never reads global or parent-directory ignore sources.include, when it is not empty, is what a remaining file has to match. Emptyincludeincludes 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.46documentation 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, andCODE_OF_CONDUCT.md, each also spelled in lower case - archived and superseded copies: any folder named
old,docs/old, and any folder whose name holdsarchive,deprecated,legacy,previous,outdated, orsuperseded - translations: language folders below a root
i18nfolder, such asi18n/zh-cnori18n/fr, and folders namedzh-cn,zh-tw,zh-hk,zh-mo, orzh-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 = 6000000enableddefaults totrue;falsecollects no documentation file, while the documentation comments attached to declarations stayexcludeleaves out what its globs match beside the default exclusions, at most 512 patternsforce_includecollects what a default exclusion leaves out, at most 512 patterns; a matchexcludealso 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_sourcescaps selected sources, 1 to 5000000max_filecaps one source's bytes, 1b to 64mb;max_totalcaps their bytes together, 1b to 64gbmax_blockscaps retained blocks, 1 to 50000000max_referencescaps each collection's links, fragments, and reference candidates, 1 to 50000000max_textcaps one heading, language, destination, or reference spelling, 1b to 1mbmax_heading_depthcaps a block's heading path and heading levels, 1 to 65536max_warningscaps retained collection warnings, 1 to 65536max_nodescaps a documentation parser's syntax nodes, 1 to 100000000;max_depthcaps its nesting, 1 to 65536max_progresscaps one reStructuredText parser's progress callbacks, 1 to 4294967295max_layer_blockscaps blocks held across a documentation layer's collections, 1 to 100000000max_mappingscaps 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 startDependencies
Since v0.0.39 · Rust, Python, TypeScriptdependencies 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"resolutionselects whether a standard library version probe runs:autorunsrustc --version, andnode --versionwhen no Node.js pin names a version, in the workspace root;staticruns no program, so every version comes from the pins and project files alone, or goes out as the requirement>=0packagesnames packages beside the ones the workspace's manifests and lockfiles state, at most 20000 of them. Each entry carriesmanager,name, and exactly one ofversionandrequirement; an entry naming both, or neither, is refusedcommand_timeoutbounds 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 = 32lockfile_sizecaps one manifest or lockfile read, 1b to 64mbtoolchain_outputcaps retained bytes from each version probe stream, 1b to 1mbmanifestscaps manifests read by one resolver, 1 to 4096; ancestor comparisons take at mostmanifestssquared workpackagescaps packages held in one dependency context, 1 to 20000, within the global API's package bounddirectory_entriescaps entries in one installed environment directory listing, 1 to 5000000pin_sizecaps one toolchain or version pin file, 1b to 64mbproject_sizecaps one standard library project file, 1b to 64mbrecord_sizecaps one installed distribution'sRECORDfile, 1b to 64mbnesting_depthcaps 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"unitscaps published source units, 1 to 5000000symbolscaps published declaration records, 1 to 50000000documentscaps published search documents, 1 to 55000000warningscaps retained publication warnings, 1 to 65536identifier_termscaps identifier terms retained per search document, 1 to 65536retained_sourcecaps source bytes retained per record, 1b to 64mb. Analysis cuts larger source at a UTF-8 boundary and reports the dropped bytesretained_total, absent by default, caps retained source bytes across units, symbols, and documents together. It acceptsretained_sourceto 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 = 200compressed_sizecaps compressed archive bytes, 1b to 64gbexpanded_sizecaps expanded bytes together, 1b to 64gbmember_sizecaps one member's bytes, 1b toexpanded_size, at most 64gbextension_sizecaps one GNU or PAX extended header, 1b to 64mb, checked before allocating its contents; that header must also fitmember_sizememberscaps all archive members, including directories, links, and extended headers, 1 to 5000000expansion_ratiocaps expanded bytes per compressed byte, 1 to 4294967295;expanded_sizestill 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 = falsedisabled defaults to false. Set it to true to exclude this project's requests from counters:
rift config set --telemetry.disabled=truerift 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 --userA 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.43global 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"enableddefaults totrue;falsesends no package read to the global API, so a package-scope read answers project hits alone and reportsglobal_access_disabledendpointdefaults tohttps://api.volar.sh/rift/rest; it must be an absolute base URL without user information, query, fragment, or trailing slashtoken_envdefaults toRIFT_API_TOKENand names the environment variable holding the optional bearer token. An unset variable uses anonymous accessconnect_timeoutdefaults to"3s"and accepts100msto30srequest_timeoutdefaults to"15s"and accepts1sto5m; it covers connection, response, retry delays, and every attemptattemptsdefaults to3and accepts1to5max_in_flightdefaults to4and accepts1to32max_requestcaps one request body before sending, 1b to 64mb, default4mbmax_responsecaps one response body while reading, 1b to 1gb, default32mbmax_sourcecaps one source payload, including documentation source and excerpts, 1b to 64mb, default1mb; the client applies the smaller of this value and the source bound the global API advertises. Documentation excerpts also keep their separate response boundcapabilities_ttlandresolution_ttldefault to"15m"and accept1mto24hfailure_ttldefaults to"30s"and accepts1sto1h
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.25An 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.
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.
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 = falseA 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, TypeScriptRift recommends language servers for the source and package directories in the project:
rift config recommendThe 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=cargoThe 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=bunA 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=--stdioLSP profiles
Since v0.0.71A 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.
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.
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:
| Scope | Searches |
|---|---|
local | The primary project |
local@api | ../api |
local@web | ../web, without its docs/ folder |
local@web:docs | ../web/docs, with the docs profile's settings |
all | Every 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 · PythonAn 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:
cargoserves Rust throughrust-analyzer. Whenrustupand a toolchain pin are available, Rift runsrustup run <channel> rust-analyzerwith automatic installation disabled.bunandnpmserve JavaScript, TypeScript, andtypescript:tsxthrough the installed native TypeScript executable with--lsp --stdio, falling back totypescript-language-server --stdio.uvserves Python throughty server, falling back topyright-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, PythonThe 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.25enabled = 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.
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, orselective, the releasesreleasesnamesreleases- tag names or tag patterns such asv1.*, at most 64, whichselectiverequires and no other strategy accepts; a pattern that does not compile as a glob, such as the unclosed classv[1, is refusedmax_revisions- how many revisions the store holds, 1 to 100000: commits undereverything, releases underselectivebatch_commits- how many commits one store write holds, 1 to 10000batch_size- how many parsed bytes one store write holds past its first commit, 1kb to 1gbcpu_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.
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 = 512max_filebounds the bytes of one source, 1b to 64mbmax_nodesbounds the syntax nodes one source produces, 1 to 100000000max_depthbounds 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.
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.
Search
Since v0.0.9search 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.
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"includeselects 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 sourcesmax_chunkbounds how large a selected text file may grow before it's split into several indexed chunks, 1kb to 16mbresultscaps results collected by one local search or symbol lookup, 1 to 10000, default1000; an answer that reaches it reportsresults_truncatedsymbol_alternatives_workbounds one complete closest-declaration ranking, 1 to 1073741824, default16777216. 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) * Lunits 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.detailreports the configured bound, required work, remaining work, and recovery actionsymbol_alternatives_lowercase_worksets that reservation weight, 12 to 1024, default12. 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 readpool_slotsbounds SQLite connections the index database may hold open at once, and separately the vectors database, 1 to 16busy_timeoutbounds 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 withSQLITE_BUSY, 100ms to 30sjournal_size_limitSince 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, default64mb. A transaction larger than the limit still grows the log past it until that commit
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_filesissplit, the default, orskip. Undersplitthe file is indexed as whole-line chunks of at mostmax_chunk, and a file past[providers.syntax]max_fileis held as text the syntax provider does not parse, up to[source]workspace_size; every answer names such files in alarge_file_unparsedwarning. Underskipthe file leaves the text index, a file pastmax_fileleaves the index entirely, and an answer whosepathsreach one counts it in alarge_file_skippedwarningexcluded_lockfilesnames 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 listsCargo.lock,uv.lock,package-lock.json,bun.lock,pnpm-lock.yaml,yarn.lock,poetry.lock,Pipfile.lock,composer.lock,Gemfile.lock,go.sum, anddeno.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 = 1000pattern_compiled_sizebounds the bytes one pattern's matcher compiles to, 64kb to 64mbpattern_candidate_rowsbounds the trigram index rows one search reads to select the files it verifies, 100 to 1000000; a file split into chunks counts once per chunkpattern_verified_sizebounds the file text one search verifies, 1mb to 64gbpattern_matches_per_filebounds 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.
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_weightweighs 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 rankinglexical_weightweighs the full-text ranking over declaration names, signatures, and attached documentation, and over the text of every indexed filevector_weightweighs the vector ranking, which reaches code sharing no word with the queryfusion_ksets 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 = 256candidatesbounds how many declarations the vector ranking returns before the rankings are fused, 1 to 1000candidates_per_filebounds 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 unreachablemax_vectorsbounds how many vectors the workspace holds, 1000 to 1000000; each vector costs the model's dimension in single-precision floatskindselects which embedding runs, and each kind carries only the keys its execution reads:hfdownloads the weights from a Hugging Face repository and caches them where every other Hugging Face client on the machine caches them,directoryreads a directory the workspace already holds and downloads nothing, andopenai_compatiblesends embedding inputs to a service implementing the OpenAI embedding shapemodelnames which weights, in the formkindsets: underhfa repository identifier such asminishlab/potion-retrieval-32M, optionally carrying a revision after@; underdirectorya workspace-relative directory; underopenai_compatiblethe 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.jsondecides how the server reads it. Amodel_typeofmodel2vecnames 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_timeoutanddownload_attemptsbound one model acquisition underhf, 10s to 1h and 1 to 10; a spent budget leaves search lexical for the life of the serverbatch_inputsandmax_tokensbound 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 = 4endpointis the base URL the request path is appended to. It useshttps;httpis accepted only when the host islocalhost,127.0.0.1, or::1revisionis 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 heredimensionsis how many coordinates one vector carries, 1 to 16384api_key_envnames the environment variable holding the credential, and never carries the credential itselfrequest_timeout,attempts,batch_inputs, andmax_in_flightbound 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[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.
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.