vaultspec-rag
Storage and maintenanceLink to Storage and maintenance
vaultspec-rag keeps semantic search indexes so agents and operators can search your projects by meaning instead of by exact keyword. In server mode, the default, one background service per machine owns a single managed vector store, and every project you index - each one a root - lives inside that one store. This guide covers how to inspect what that store holds, how the service reclaims dead data on its own, how to restore an index from an archive, how to prune space by hand, and how to watch maintenance as it runs.
Everything here needs the background service running. Start it with uv run vaultspec-rag server start. If you haven’t set the service up yet, work through getting started first, then run the background service for the full startup and configuration walkthrough.
Maintenance operates on the shared managed store. Migration also accesses a project’s local-only store.
Examples use the uv run prefix, which runs the command inside a project environment.
If you installed vaultspec-rag as a standalone tool, drop the prefix and call
vaultspec-rag directly; see the installation guide for lane selection.
VocabularyLink to Vocabulary
Term |
Meaning |
|---|---|
root |
A project directory that has been indexed. |
collection prefix |
|
namespace |
All collections in the store sharing one root’s prefix (typically a vault collection and a codebase collection). |
live |
The namespace’s recorded root directory exists. |
orphaned |
The recorded root is gone, and its drive or share is reachable - a true deletion. |
unknown |
The store holds the namespace, but no root can be attributed to it. Never touched automatically. |
unverifiable |
The root’s volume or network share is offline, so existence cannot be checked. Never touched automatically. |
debris |
A half-written collection directory the server cannot load, left behind by a crash. Removable only with |
temp_rooted |
A survey flag marking a namespace whose root lives under an operating-system temp directory. Subject to the extra idle clock. |
dangling data |
Namespaces whose source root no longer exists. They occupy disk but can never serve a useful search. |
grace window |
The continuous time a root must stay orphaned before automatic reclamation may act. The clock survives restarts and resets when the root reappears. |
maintenance cycle |
One scheduled pass of the service’s storage maintenance: classify, advance grace clocks, reclaim, sweep archives, report. |
snapshot archive |
A recoverable copy of a data-bearing namespace’s collections, written immediately before automatic reclamation drops them. |
Why disk usage growsLink to Why disk usage grows
Creating a namespace preallocates a large block of storage immediately, before a single document is indexed. Every root you have ever indexed - including throwaway ones like test directories, temporary worktrees, and scratch checkouts - keeps costing that space until it is reclaimed. One development machine accumulated 79 dead namespaces totalling 167.9 GiB, all holding zero documents.
That preallocation is why disk usage tracks the number of roots you have indexed far more closely than the amount of code and documents in them. It also means there are two different ways to get space back: removing namespaces you no longer need, and shrinking the ones you are keeping. Both have their own sections, Reclaim space manually and Shrinking collections you keep, and the service does both on its own.
The store lives at ~/.vaultspec-rag/qdrant-server/storage by default; VAULTSPEC_RAG_QDRANT_STORAGE_DIR relocates it. Any location works, including deeply nested ones - on Windows the service hands the storage engine extended-length paths, so the classic 260-character path limit does not constrain where the store lives.
Inspect what is storedLink to Inspect what is stored
List every namespace with its classification, document count, footprint, and attributed root:
Command
uv run vaultspec-rag server storage survey
Captured output
34 namespaces (orphaned=10 unknown=0 unverifiable=0 live=24) 33.4 GiB on disk [19 temp-rooted]
orphaned r02c5d80096c3_ 2 pts 318.6 MiB C:\Users\me\AppData\Local\Temp\.tmpMum3wV [temp]
live r45b56789f389_ 12322 pts 1020.3 MiB C:\projects\my-project
Two of the thirty-four rows are shown, and the namespace prefixes and root paths are stand-ins; the classifications, counts, and footprints are as the tool reported them, and the two progress lines it prints while it works are cut. Each row reads left to right as the classification, the namespace prefix, the document count, the on-disk footprint, and the attributed root path; a namespace no root can be attributed to shows (unattributable) in the final column. A row whose root lives under an operating-system temp directory carries a trailing [temp], and the summary line counts those separately. --orphaned and --unknown narrow the list to those states. With a running daemon the survey is answered by the service itself, so the CLI, the MCP tools, and HTTP consumers all see one classification; without a daemon the CLI reads the store directly.
A running daemon answers from a cached survey snapshot rather than re-measuring every namespace per call, so the survey stays fast (sub-second) no matter how many namespaces the store holds. The snapshot is computed shortly after startup and refreshed by every maintenance cycle; the HTTP response carries computed_at (when the underlying survey ran) and source (cache or fresh) so a consumer can see exactly how old the data is. The CLI’s --json carries the namespaces and the counts rather than those two fields, so a script that needs the age of the answer should read it over HTTP. Survey data is therefore eventually-consistent, up to one maintenance interval behind. When you need up-to-the-second truth - for example immediately after indexing or deleting a namespace - pass --fresh (HTTP: ?fresh=true), which recomputes the survey and reseeds the cache.
To look up a single root - which namespace and collection prefix belong to it - pass --root:
Command
uv run vaultspec-rag server storage survey --root C:\projects\my-project
The output leads with Queried root: <resolved path> prefix: r..._, and the same lookup is available as queried_root in --json output. This works even for a root that has never been indexed: the service computes the authoritative prefix, so an external consumer never has to reimplement the hash.
The full flag and exit-code table is in the CLI reference.
Automatic reclamationLink to Automatic reclamation
The running service reclaims confirmed-dangling namespaces on its own: a maintenance cycle runs every 60 minutes by default, with the first cycle one interval after startup. Set VAULTSPEC_RAG_STORAGE_AUTOPRUNE=0 to disable it.
A namespace is only ever reclaimed automatically when all of the following hold:
it is attributed to a known root,
that root is classified
orphaned- gone, on a reachable volume,and it has been orphaned continuously for its full grace window. The clock is persisted, so restarting the service does not reset it; the root reappearing resets it to zero instantly.
Reclamation is tiered. A namespace holding zero documents is dropped after 24 hours of continuous orphan-hood. A namespace holding data waits 7 days, then each of its collections is written to a snapshot archive, and the namespace is dropped only if every snapshot succeeded - a failed archive always cancels the drop.
One class waits less. A namespace whose root lived under the OS temp directory was a throwaway sandbox, and an orphaned one is a sandbox that was torn down, so it is reclaimed after 24 hours whatever it holds. That shortens the waiting period and nothing else: a point-bearing temp-rooted namespace is still archived first and still not dropped if the archive fails. An absent root on an unreachable volume classifies unverifiable rather than orphaned and so never draws this window.
The cycle never touches unknown namespaces, unverifiable namespaces (an unplugged drive looks exactly like a deleted root, so it is never treated as one), or - with one exception, temp-rooted namespaces - anything live. At most 16 namespaces are reclaimed per cycle; the remainder waits for the next one.
The exception is temp-rooted namespaces. A harness temp directory that still exists classifies live and would otherwise survive every prune forever, which is exactly how leaked harness namespaces once filled a disk. A namespace whose root lives under an OS temp directory therefore runs on an additional clock: every successful index run stamps a persisted last_indexed time, and once that stamp is older than the ephemeral idle TTL (72 hours by default) the namespace is treated as dangling even though its root exists. The same tiers then apply - empty ones drop, data-bearing ones are archived first - under the same per-cycle cap, with ordinary orphans taking priority. An actively re-indexed temp root keeps refreshing its stamp and is never touched; set VAULTSPEC_RAG_STORAGE_AUTOPRUNE_EPHEMERAL_IDLE_HOURS=0 to disable the tier.
The interval, all three grace windows, the ephemeral idle TTL, the per-cycle cap, and the archive bounds are tunable - see the storage maintenance knobs.
Shrinking collections you keepLink to Shrinking collections you keep
Reclamation removes namespaces you no longer need. Geometry reconcile is the other half: it shrinks the preallocation of namespaces you are keeping.
A collection’s on-disk cost is dominated by a fixed floor rather than by its contents - the storage engine preallocates a set of memory-mapped pages per segment, and it sizes the segment count from the host’s CPU count unless told otherwise. On a 24-core machine that meant eight segments and roughly 1.2 GiB for a collection holding zero documents. Newer versions cap this at creation, but a collection carries the geometry it was created with for its whole life, so upgrading does not shrink anything that already exists.
The service converges them for you. Each maintenance cycle reconciles up to four drifted collections onto the bounded geometry, so a backend full of oversized collections shrinks over the following few hours without any action. Measured across collection sizes from empty to 20,000 documents, this reclaims 63-84% of each collection’s footprint.
Reconcile is not destructive. It changes a setting and lets the storage engine merge segments in the background: no document is moved or deleted, and the collection stays searchable throughout. It is also idempotent - a collection already at the target is skipped, so once your backend has converged the stage does nothing.
To converge everything at once instead of waiting for the cycles, preview and then apply:
Command
uv run vaultspec-rag server storage reconcile --dry-run
After reviewing the preview, apply the changes:
Command
uv run vaultspec-rag server storage reconcile --yes
Captured output
Reconciled 6 collections (23.4 GiB reclaimed); 0 still converging.
reconciled r45b56789f389_vault_docs 8->1 segments, 1.0 GiB freed
Merging is a background operation, so the command waits for each collection to settle before reporting what it saved. That wait matters: while the engine restructures a collection, both its segment count and its on-disk size briefly rise above where they started before falling well below. A figure read mid-merge would report a reclamation in progress as growth, so vaultspec-rag only ever reports a size it has watched stop changing - and it waits for the merge to actually begin before it starts watching, because a merge still queued behind a busy engine looks exactly as motionless as a finished one.
Pass --no-wait to issue the changes and return immediately; the collections still converge on their own, but that run reports no reclaimed bytes, because at that moment there is no honest number to give. --limit bounds how many collections one run touches.
A merge needs room to inflate before it shrinks, so a collection is skipped with insufficient_headroom when the volume is too full to absorb it safely. If you hit that, prune first and reconcile afterwards.
Set VAULTSPEC_RAG_STORAGE_RECONCILE=0 to disable the automatic stage; the per-cycle cap and the convergence budget are tunable alongside it.
One residue is left behind deliberately. The write-ahead log size is fixed when a collection is created and cannot be changed in place, so a reconciled collection keeps a 32 MiB log where a freshly created one takes 16 MiB. That is a small fraction of a floor measured in gigabytes, and removing it would mean recreating the collection - a far riskier operation for far less space.
Archives and how to restore themLink to Archives and how to restore them
Snapshot archives land in ~/.vaultspec-rag/qdrant-server/archive/<prefix>/, one subdirectory per reclaimed namespace and one .snapshot file per collection. Each maintenance cycle deletes archives older than 30 days, then evicts oldest-first if the archive directory exceeds 64 GiB; both bounds are configurable. The setting for the second is named
VAULTSPEC_RAG_STORAGE_AUTOPRUNE_ARCHIVE_MAX_GB, and the number you give it is
multiplied by 1024 three times, so it is read as GiB despite the name.
Reindex first. The index is derived data, so if the root still exists, or came back from your own backup, indexing it rebuilds everything.
To restore the archived index directly, use the storage command. Point it at the archive directory holding the snapshot manifest, and name the root the restored namespace is keyed to:
Command
uv run vaultspec-rag server storage restore ~/.vaultspec-rag/qdrant-server/archive/r0123456789ab --root /path/to/project --dry-run
After checking the destination collections, restore them:
Command
uv run vaultspec-rag server storage restore ~/.vaultspec-rag/qdrant-server/archive/r0123456789ab --root /path/to/project --yes
The destination root must hold no collections. There is no override, so restore into a root you have already pruned or one you have not indexed. Preview with --dry-run to see the destination collections before anything is written.
Reclaim space manuallyLink to Reclaim space manually
Manual pruning deletes; it does not archive. Automatic reclamation snapshots a data-bearing namespace before dropping it. prune and delete do not: they remove the namespace outright, with no grace window, because the operator running the command is the confirmation. If you want a copy, take one before you run these.
Work in this order:
Survey the store to see what is there.
Prune the orphaned namespaces.
Delete a single namespace only when prune will not remove it.
Preview first, then apply:
Command
uv run vaultspec-rag server storage prune --dry-run
After checking which namespaces will be deleted, apply the prune:
Command
uv run vaultspec-rag server storage prune --yes
Captured output
Reclaimed 79 orphaned namespaces (167.9 GiB); 0 unknown left untouched.
Prune targets only orphaned namespaces; unknown and unverifiable are never touched. To remove one specific namespace, name its prefix:
Command
uv run vaultspec-rag server storage delete r0123456789ab_ --yes
delete refuses a prefix the manifest cannot attribute to a root unless you pass --allow-unknown.
A crash can leave a half-written collection directory whose config file never landed. The server cannot load it, skips it at startup, and it sits on disk indefinitely. The survey lists such directories with status debris.
prune --debrisis an unrecoverable filesystem delete. Qdrant cannot snapshot or drop a collection it never loaded, so there is no archive and no restore path. Debris removal is never automatic: it has no manifest attribution, so it stays behind the explicit flag as well as the prune confirmation.
Command
uv run vaultspec-rag server storage prune --debris --dry-run
After checking the listed directories, delete them:
Command
uv run vaultspec-rag server storage prune --debris --yes
You can also address a namespace by its root path instead of its prefix - the sanctioned teardown for test harnesses and consumers that register throwaway roots against the resident service:
Command
uv run vaultspec-rag server storage delete --root C:\Temp\my-throwaway-root --yes --json
The path is resolved and hashed exactly as indexing does, so it removes precisely the namespace that root’s indexing created. Deletion is idempotent: an already-absent namespace reports already_absent and exits 0, so a teardown hook can run unconditionally. A harness that deletes its temp roots instead also works - the scheduled reclamation removes the leftover namespaces once their grace window passes.
Harnesses must isolate or tear downLink to Reclaim space manually, Harnesses must isolate or tear down
A test, demo, or acceptance harness that indexes a throwaway root against the
resident service mints a real namespace in the shared backend - often gigabytes
once fully indexed - and as long as the temp directory still exists it
classifies live and survives every prune. Left alone, leaked harness
namespaces can exhaust the disk (the issue-242 incident: 36 temp-rooted
namespaces, ~74 GiB). Every harness must do one of these, in order of
preference:
Isolate: point
VAULTSPEC_RAG_STATUS_DIRandVAULTSPEC_RAG_QDRANT_STORAGE_DIRat harness-owned temp paths so its indexing never reaches the shared backend at all.Tear down: run
vaultspec-rag server storage delete --root <dir> --yesunconditionally in the harness’s cleanup (idempotent, exits0when already absent).Delete the root and wait: removing the temp directory itself lets the scheduled reclamation collect the namespace after its grace window.
server storage survey marks suspect entries: any namespace whose root lives
under an OS temp directory is flagged temp_rooted in --json and [temp]
in the human table, so leaked harness indexes are visible before they become a
disk incident.
Flags and exit codes are in the CLI reference.
Migrate a root between backendsLink to Migrate a root between backends
Copy one project’s index between local-only storage and managed Qdrant. The source collections are retained.
Keep managed Qdrant reachable. Close processes holding the local store, and prevent indexing, watchers, and maintenance from writing to either participating index. Migration does not pause writers.
In the migration shell, set
VAULTSPEC_RAG_QDRANT_SERVERto1andVAULTSPEC_RAG_LOCAL_ONLYto0for either direction. These settings configure the invoking CLI, not the running service.Replace the sample path with your project directory and preview the copy to managed Qdrant. For the other direction, use
--to localin both commands.Command
uv run vaultspec-rag server storage migrate "C:\code\my-project" --to server --dry-run
Preview opens the local store but copies nothing. Inspect the results before proceeding.
Apply the copy:
Command
uv run vaultspec-rag server storage migrate "C:\code\my-project" --to server --yes
Inspect every collection’s result. Existing destinations are skipped, including partial copies left by failed runs; rerunning does not repair them. See the result and exit semantics. For unresolved failures, open an issue.
Once the required collections have copied successfully, change the backend and run a search. Keep the source collections until the destination is verified.
Observe maintenanceLink to Observe maintenance
Every cycle is a job: uv run vaultspec-rag server jobs lists it as a storage maintenance cycle with a result summary like removed=2 failed=0 pending=5 reclaimed_bytes=4508876800. Every cycle also writes one structured service.maintenance log line with the same counts plus archive activity and free disk, and logs an explicit disk_low warning when the store’s volume drops under 10 GiB free - only a few namespaces of headroom, so treat the warning as a prompt to prune or add capacity.
The token-gated /metrics route exports the rollup in Prometheus text format. All names carry the vaultspec_rag_ prefix:
Metric |
Type |
Meaning |
|---|---|---|
|
counter |
Cycles run since service start |
|
counter |
Namespaces reclaimed since service start |
|
gauge |
Free disk on the store’s volume at the last cycle |
|
gauge |
Total footprint of currently orphaned namespaces |
|
gauge |
Orphans still inside their grace window |
|
gauge |
Orphaned namespace count at the last cycle |
|
gauge |
Bytes reclaimed by the last cycle |
|
gauge |
Whole-backend on-disk footprint (all statuses) |
|
gauge |
Total namespace count at the last cycle |
|
counter |
Collections shrunk onto the bounded geometry |
|
counter |
Bytes reclaimed by geometry reconcile |
|
gauge |
Collections not yet converged onto the bounded geometry |
store_drifted_collections counts both collections still carrying oversized geometry and collections whose setting is already correct but whose merge is still running - so it reaching zero genuinely means the backend has finished converging, not merely that the settings have been written. Note that maintenance_reconciled_bytes_total credits only merges a cycle watched to completion; a merge that outlives its convergence budget still finishes, but its bytes go uncounted.
The GET /storage/survey route carries a whole-backend rollup as a totals object: total bytes, namespace count, total collection count, a per-status byte breakdown, the ephemeral backlog - the footprint still held by orphaned temp-rooted namespaces, the population next eligible for reclamation - and the count of namespaces whose point count could not be fully read - so a pile of live-but-leaked namespaces is visible even though it never counts as dangling, and an unread count is never mistaken for a verified zero. The CLI --json survey emits the per-namespace list (namespaces, returned, total, queried_root) without that totals rollup.
Tuning the schedule, grace windows, cap, and archive bounds is covered by the storage maintenance knobs.
Where to go nextLink to Where to go next
CLI reference - full flag and exit-code tables for
server storage survey,server storage prune,server storage delete, andserver stop.Configuration - every tunable, including the storage maintenance knobs.
Storage backends - the server-first backend model and the managed Qdrant server.
Run the background service - service lifecycle, status, jobs, and logs.
Support: open an issue at github.com/nevenincs/vaultspec-rag/issues.