backend / vectorless-engine
Vectorless engine
A Go service that parses PDFs and other documents into a page-numbered section tree in Postgres, then answers questions by selecting sections and pages over that tree and citing the exact line, with no embeddings and no vector store.
2026 · paused · open source
- [*]LanguagesGo · SQL · Protocol Buffers
- [*]Frameworkschi · Connect-RPC (gRPC / gRPC-Web) · pgx · River · Asynq · OpenTelemetry · Prometheus client · llmgate
- [*]StoresPostgres · Google Cloud Storage · S3-compatible (R2, MinIO) · Local filesystem · Redis (Asynq backend) · In-memory LRU (replay, idempotency cache)
- [*]AsyncRiver (Postgres job queue) · Asynq (Redis) · Upstash QStash webhooks · errgroup fan-out with a global LLM semaphore · Polling ticker for settings hot-reload
- [*]InfraDocker · Google Cloud Run · Cloud SQL · Helm chart · GoReleaser · GitHub Actions · Lorx CI · Terraform (in vectorless-deploy)
- [*]Third partiesTypeSafe Jev (Judge) · Anthropic · OpenAI · Google Gemini · Z.ai GLM (Anthropic-compatible) · Upstash QStash · AWS S3 / Cloudflare R2 · Google Cloud Storage
One Go module (about 37,800 lines of non-test Go, 235 commits since 19 April 2026) with two binaries. cmd/server is the multi-tenant HTTP server production ran behind the control plane: 22 REST routes on chi plus 13 Connect-RPC methods served over Connect, gRPC and gRPC-Web from the same handlers. cmd/engine is the standalone build with a zero-config --local mode and the replay route. The earlier separate vectorless-server repo (7 commits) was folded into cmd/server on 25 May.
Ingest is asynchronous. POST /v1/documents writes the bytes to object storage (local disk, S3/R2 or GCS), inserts a documents row and enqueues a job behind a Queue interface with three backends: River on Postgres (what production used), Asynq on Redis, and Upstash QStash pushing signed webhooks to /internal/jobs/{kind}. The worker parses with a pure-Go PDF layout reader, builds the contents tree, splits any leaf longer than 20 pages at its own headings, persists sections and page text, and renders page images with pdftoppm.
Retrieval went through several strategies (single-pass, chunked-tree, agentic, treewalk with up to eight chat-model hops). The current default, judgewalk, ranks sections and then pages with a hosted classifier model (TypeSafe Jev) through llmgate's Judge interface, follows cross-references like 'see Note 7', and returns pages verbatim; a generative model only writes the final answer. Pincites then match each claim to a sentence or table row on the page and return line rectangles in normalised page coordinates. Ten dated evaluation reports in docs/evaluations record the FinanceBench measurements behind each change.
The hosted service on vectorless.store was wound down (decision dated 2026-10-11 in the README); the engine stays public under Apache 2.0 and is no longer actively developed.
How it holds up
- Auth
- cmd/server takes either no auth or one static bearer token compared in constant time, and trusts X-Vectorless-Org / X-Vectorless-Store headers that only the control plane sets (the engine had no public DNS name in production); /admin/v1/models needs the engine token, and callers can bring their own LLM key per request via X-LLM-Api-Key.
- Data
- Three application tables over 9 embedded up/down migrations: documents (status lifecycle, org_id and store_id scoping, toc_tree JSONB, ingest progress JSONB, per-tenant idempotency key), sections (self-referencing tree with page_start/page_end, summary axes, candidate questions; full text lives in object storage), and instance_settings (AES-256-GCM sealed provider keys). River keeps its own job tables in the same database on a separate pool.
- Consistency
- Ingest honours Idempotency-Key twice: an in-memory response cache in middleware and a partial unique index on (org_id, idempotency_key), where a losing concurrent insert deletes its orphaned source object and returns the winner. Each migration runs in its own transaction against a schema_migrations ledger. The documents insert and the job enqueue are separate statements (River's transactional insert is not used), and River ignores the DedupeKey that Asynq honours.
- Failure
- Handlers wrap deterministic failures (encrypted or textless PDFs) in PermanentError, which the River worker turns into JobCancel so retries stop at once; transient failures keep the document in 'parsing' until the queue's last attempt, and the terminal status write uses a fresh 5-second context. If the Judge provider refuses (401/402/403/5xx), a FallbackStrategy serves the query with treewalk and logs a fixed message that a Cloud Monitoring alert matches; summary, HyDE and TOC-builder failures are logged and the document still becomes ready.
- Testing
- 86 _test.go files with 560 func Test cases (about 18,000 lines), heaviest in pkg/ingest (19 files), pkg/retrieval (16) and pkg/parser (13); includes Postgres-backed integration tests for idempotency and the settings store, a router-parity test between REST and Connect, and QStash signature tests. CI runs gofmt, go vet, golangci-lint v2.14 and go test ./... on GitHub Actions and Lorx.
Techniques
- Idempotent ingest with race recoveryA retried upload with the same Idempotency-Key returns the existing document; when two land at once, the partial unique index rejects the loser, which deletes the source object it just wrote and returns the winner instead of a 500. Fixed a bug where flaky links ingested the same file up to six times.
internal/handler/documents.go:214internal/handler/documents.go:354pkg/db/migrations/0007_documents_idempotency_key.up.sqlinternal/api/server.go:241 - Permanent vs transient job failures across queue backendsA PermanentError wrapper lets a handler say 'retrying cannot help'; the River worker converts it to JobCancel, and the pipeline only writes a terminal 'failed' status on a permanent error or the final attempt, so polling clients never see a document marked dead that a retry would have saved.
pkg/queue/queue.go:94pkg/queue/river.go:102pkg/ingest/ingest.go:1286 - QStash webhook verification with key rotationHand-written HS256 JWT verification of Upstash-Signature: constant-time HMAC against the current then the next signing key, exp/nbf checks, the sub claim bound to the URL actually called, and the body claim bound to sha256 of the raw body, so a signed delivery cannot be replayed to another endpoint or with another payload.
pkg/queue/qstash_verify.go:77pkg/queue/qstash_verify.go:101pkg/queue/qstash_verify.go:155 - Provider fallback on refusal, with an alert hookFallbackStrategy wraps the Judge-based strategy and, only when the provider refuses service (401/402/403/5xx or an unconfigured key, not a bad request or cancelled context), serves the query with the generative treewalk strategy and logs a stable message that a Terraform-defined alert policy matches. Added after the Judge provider ran out of credit on 2026-10-08 and every production query failed.
pkg/retrieval/fallback.go:30pkg/retrieval/fallback.go:88 - Sealed, hot-reloaded instance settingsModel provider keys entered in the dashboard are sealed with AES-256-GCM (versioned format, purpose-bound AAD) before they reach instance_settings; every replica polls the row's updated_at and swaps an atomic.Pointer to a rebuilt client set, so key changes apply without a restart.
pkg/models/crypto.go:81pkg/models/registry.go:84pkg/models/registry.go:310pkg/db/migrations/0009_instance_settings.up.sql - Order-invariant replay trace tokensEach answer gets sha256 over document id, version, model, prompt version and the sorted selected section ids with NUL separators; /v1/replay returns the stored response bytes verbatim from a TTL-bounded LRU, and rejects a replay whose query or document does not match.
pkg/retrieval/trace.go:38pkg/retrieval/replay.go:107
Counted
- 235Commits on main
git rev-list --count origin/main - 37,767Go LOC, non-test, excluding generated
find . -name '*.go' ! -name '*_test.go' ! -path './gen/*' | xargs wc -l - 55,731Go LOC including tests
git ls-tree -r origin/main *.go minus gen/, wc -l - 86Test files
git ls-tree -r --name-only origin/main | grep -c _test.go - 560func Test cases
grep -rh '^func Test' --include=*_test.go | wc -l - 9 (up + down)Migrations
ls pkg/db/migrations/*.up.sql - 3Application tables
CREATE TABLE in pkg/db/migrations - 22REST routes, cmd/server
grep r.(Get|Post|Put|Delete) internal/handler/router.go - 19REST routes, cmd/engine
grep r.(Get|Post|Put|Delete) internal/api/server.go - 13Connect-RPC methods
grep -c rpc proto/vectorless/v1/*.proto - 3Queue backends
pkg/queue/river.go, asynq.go, qstash.go - 10Dated evaluation reports
ls docs/evaluations - 10Binaries under cmd/
ls cmd