Self-hosting
Run the Vectorless engine yourself with one docker compose command. Same ingest, retrieval, answers and line-level pincites as the hosted service, as an HTTP API with your own model keys.
The engine's selfhost/ folder runs the server the hosted service ran, with
Postgres beside it. You get the same pipeline: the contents found and placed
by Jev, judgewalk retrieval, answers, line-level pincites, page images and
live ingest progress. It is an HTTP API protected by one bearer token. You
call it with curl, the SDKs or the MCP server. There is no web
dashboard: the hosted dashboard and control plane are not part of the open
source release. Nothing else to provision: no vector database, no object
store, no queue service.
The compose file and its full reference live in the
selfhost/ folder of the engine.
Quickstart
Get the engine
git clone https://github.com/hallelx2/vectorless-engine.git
cd vectorless-engine/selfhost
cp .env.example .envSet a token and your model keys
In .env, set VECTORLESS_API_KEY to a long random string (for example
openssl rand -hex 32), one answer-model key, and VECTORLESS_TYPESAFE_API_KEY
for Jev. Compose refuses to start without the token.
VECTORLESS_API_KEY=<openssl rand -hex 32>
VECTORLESS_LLM_PROVIDER=anthropic
VECTORLESS_ANTHROPIC_API_KEY=<your key>
VECTORLESS_TYPESAFE_API_KEY=<your TypeSafe key>Start it
docker compose up -d
docker compose ps # the engine is ready when it reports (healthy)The API listens on http://localhost:8080, bound to this machine only.
Ingest a document and ask it something
K=<your VECTORLESS_API_KEY>
curl -s -H "Authorization: Bearer $K" -F file=@report.pdf http://localhost:8080/v1/documents
# {"document_id":"doc_…","status":"pending"}
curl -s -H "Authorization: Bearer $K" http://localhost:8080/v1/documents/doc_…
# poll until "status":"ready"; "progress" lists each ingest stage
curl -s -H "Authorization: Bearer $K" -H 'Content-Type: application/json' \
-d '{"document_id":"doc_…","query":"What was capital expenditure in 2018?"}' \
http://localhost:8080/v1/answerOn 13 October 2026 this setup ran the 3M 2018 10-K (160 pages) with GLM-4.5-Air writing answers. The filing was ready 10 seconds after upload. The capital expenditure question was answered in 6.1 seconds, with pincites at line precision on the cash-flow statement and the segment table.
What runs
| Service | Image | Role |
|---|---|---|
engine | ghcr.io/hallelx2/vectorless-engine | Ingest, retrieval, answers, pincites, page images. The job queue runs inside Postgres. |
postgres | postgres:16-alpine | Documents, structure, layout and the job queue. Not published to the host. |
Uploaded files live in the documents volume and everything else in
pgdata.
Reading the answer
POST /v1/answer returns the answer with [n] markers, the evidence pages,
and a pincites list. Each pincite has a page, a precision (line or
page), the quote taken word for word from the page, and regions:
rectangles in 0–1 page coordinates. To show a citation, draw its regions over
GET /v1/documents/{id}/pages/{n}/image. GET /v1/documents/{id}/pages gives
each page's size.
The engine is single-tenant here. Behind a control plane, each request names
its tenant in X-Vectorless-Org. The self-host config sets auth.default_org
instead, so requests need only the bearer token.
Model keys
| Variable | What it does |
|---|---|
VECTORLESS_LLM_PROVIDER | anthropic, openai or gemini: the model that writes answers. |
VECTORLESS_ANTHROPIC_API_KEY, _BASE_URL, _MODEL | Anthropic, or an Anthropic-compatible gateway. For GLM on z.ai the base URL is https://api.z.ai/api/anthropic/v1, /v1 included. |
VECTORLESS_OPENAI_API_KEY, _BASE_URL, _MODEL | OpenAI, or any OpenAI-compatible endpoint. |
VECTORLESS_GEMINI_API_KEY, _MODEL | Gemini. |
VECTORLESS_ANSWER_MODEL | Optional: a different model for answers only. |
Callers can also send their own answer-model key per request with
X-LLM-Api-Key (plus optional X-LLM-Provider, X-LLM-Base-Url and
X-LLM-Model). Nothing about it is logged or stored.
Use a non-reasoning model for answers
The answer budget is 1,200 tokens. A reasoning model such as GLM-4.6 can spend it all thinking and return no text. The engine then fails the request with "the answer model returned no text", and the server log says why. GLM-4.5-Air answered the 3M question in about 3 seconds. GLM-4.6 took 37 seconds and returned nothing.
Every variable has a VECTORLESS_ prefix on purpose. Docker Compose lets
the shell override .env, and a shell set up for another tool often exports
ANTHROPIC_BASE_URL or OPENAI_API_KEY, which would otherwise win silently.
The structure model
Line-level citations need it
TypeSafe's Jev is a System One model: it makes fast, structured decisions and writes no prose. It finds the contents page, places every section on its pages, ranks sections and pages for a question, and picks the exact line each citation points to. Get a key from typesafe.ai. It is a hosted API, so page text is sent to TypeSafe when the key is set.
| With a TypeSafe key | Without | |
|---|---|---|
| Ingest | A few batched calls to read structure | One generative call per page: minutes slower on long filings |
| Retrieval | Ranks sections and pages in one or two calls | Your generative model walks the pages, up to eight hops |
| Citations | Line-level, quotes chosen by Jev | Quotes chosen by word overlap |
If TypeSafe refuses a request (no credit, a revoked key, an outage), the engine takes the generative path for that request instead of failing it.
Configuration
| Variable | Default | |
|---|---|---|
VECTORLESS_PORT | 8080 | Host port. |
VECTORLESS_BIND | 127.0.0.1 | 0.0.0.0 serves beyond this machine. Put TLS in front. |
VECTORLESS_VERSION | latest | Or a 12-character commit tag, to pin one build. |
VECTORLESS_POSTGRES_PASSWORD | vectorless | Postgres is not published to the host. |
VECTORLESS_LOG_LEVEL | info | debug logs each navigation step. |
Everything else is in selfhost/engine.yaml; the engine's
config.server.example.yaml
documents every key.
TLS with Caddy
Keep the engine on loopback and let Caddy terminate TLS:
vectorless.example.com {
encode zstd gzip
request_body {
max_size 64MB
}
reverse_proxy 127.0.0.1:8080 {
flush_interval -1
transport http {
read_timeout 10m
write_timeout 10m
}
}
}flush_interval -1 keeps streamed answers (/v1/query/stream,
"stream": true on /v1/answer) flowing as they are written.
Backups
docker compose stop engine
docker compose exec postgres pg_dump -U vectorless -Fc engine > engine.dump
docker run --rm -v "$(basename "$PWD")_documents:/data" -v "$PWD:/out" alpine tar czf /out/documents.tgz -C /data .
docker compose start engineUpgrading
docker compose pull
docker compose up -dMigrations run when the server starts.
Building from source
git clone https://github.com/hallelx2/vectorless-engine.git
cd vectorless-engine
docker build -f Dockerfile.server -t vectorless-engine:local .
cd selfhost
VECTORLESS_IMAGE=vectorless-engine VECTORLESS_VERSION=local docker compose up -d