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

cp → api: HTTPS + X-Vectorless-Orgsdk → api: REST / gRPC / X-LLM-Api-Keyapi → pg: documents row (idempotent)api → obj: source bytesapi → queue: enqueue ingestqueue → worker: job / signed webhookworker → pg: section tree, status, progressworker → obj: section text, pages.json, PNGsworker → jev: confirm contents, place pagesapi → retrieval: query / answerretrieval → jev: batched rankingsretrieval → llm: answer over evidence pagesControl planeControl plane (client) only caller; injects org/store headersSDK / local UISDK / local UI (client) standalone cmd/engine, BYOKcmd/server HTTP +Connect-RPCcmd/server HTTP + Connect-RPC (service) 22 REST routes, 13 RPCsQueue (River / Asynq /QStash)Queue (River / Asynq / QStash) (queue) ingest jobsjudgewalk + pincitejudgewalk + pincite (service) rank sections, pages, quotesIngest workerIngest worker (worker) parse, TOC, split, page imagesGenerative LLMGenerative LLM (model) Anthropic / OpenAI / Gemini / GLM, answer onlyPostgresPostgres (store) documents, sections, settings, River jobsObject storageObject storage (store) GCS / S3 / local: sources, section text, pagesTypeSafe JevTypeSafe Jev (model) Judge: selection onlyHTTPS + X-Vectorless-…REST / gRPC / X-LLM-A…documents row (idempo…source bytesenqueue ingestjob / signed webhooksection tree, status,…section text, pages.j…confirm contents, pla…query / answerbatched rankingsanswer over evidence … cp → api: HTTPS + X-Vectorless-Orgsdk → api: REST / gRPC / X-LLM-Api-Keyapi → pg: documents row (idempotent)api → obj: source bytesapi → queue: enqueue ingestqueue → worker: job / signed webhookworker → pg: section tree, status, progressworker → obj: section text, pages.json, PNGsworker → jev: confirm contents, place pagesapi → retrieval: query / answerretrieval → jev: batched rankingsretrieval → llm: answer over evidence pagesControl planeControl plane (client) only caller; injects org/store headersSDK / local UISDK / local UI (client) standalone cmd/engine, BYOKcmd/server HTTP +Connect-RPCcmd/server HTTP + Connect-RPC (service) 22 REST routes, 13 RPCsQueue (River / Asynq/ QStash)Queue (River / Asynq / QStash) (queue) ingest jobsjudgewalk + pincitejudgewalk + pincite (service) rank sections, pages, quotesIngest workerIngest worker (worker) parse, TOC, split, page imagesGenerative LLMGenerative LLM (model) Anthropic / OpenAI / Gemini / GLM, answer onlyPostgresPostgres (store) documents, sections, settings, River jobsObject storageObject storage (store) GCS / S3 / local: sources, section text, pagesTypeSafe JevTypeSafe Jev (model) Judge: selection onlyHTTPS + X-Vectorless-…REST / gRPC / X-LLM-A…documents row (idempo…source bytesenqueue ingestjob / signed webhooksection tree, status,…section text, pages.j…confirm contents, pla…query / answerbatched rankingsanswer over evidence …
clientservicequeueworkerstoremodelasync
  • [*]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

  1. 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:214 internal/handler/documents.go:354 pkg/db/migrations/0007_documents_idempotency_key.up.sql internal/api/server.go:241
  2. 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:94 pkg/queue/river.go:102 pkg/ingest/ingest.go:1286
  3. 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:77 pkg/queue/qstash_verify.go:101 pkg/queue/qstash_verify.go:155
  4. 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:30 pkg/retrieval/fallback.go:88
  5. 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:81 pkg/models/registry.go:84 pkg/models/registry.go:310 pkg/db/migrations/0009_instance_settings.up.sql
  6. 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:38 pkg/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