Quivr V2
An open-source engine that turns continuous content streams into search and monitoring.
Quivr V2 ingests content durably, makes it searchable within seconds, enriches it in the background and lets you follow a topic over time. The core stays generic: formats, AI models and business rules belong in plugins, so you can adapt Quivr to your domain without forking the platform.
Status: evaluation stage. The API is v0 and may change without notice. Do not
run it in production yet.
Why Quivr V2
- Durable before fast. A write is acknowledged only once it is committed; outages
- Idempotent everywhere. Every write carries an idempotency key. Replays return the
- Useful early, richer later. Text is lexically searchable as soon as it is
- Rebuildable indexes. PostgreSQL and S3 hold the canonical data; the search index
- Honest search. Every hit is rehydrated from canonical storage and re-authorized.
- Generic core, extensible edges. Connectors, normalizers, enrichers, retrievers and
Architecture at a glance
flowchart LR
client[Client / connector] -->|REST v0| api[Go API]
api -->|commit receipt + intent| pg[(PostgreSQL<br/>catalog, receipts, changes)]
api --> temporal[Temporal]
temporal --> worker[Go worker]
worker -->|canonical bytes, artifacts| s3[(S3-compatible storage)]
worker -->|segments| weaviate[(Weaviate<br/>lexical + vector)]
worker -->|text parts| ingest[core.ingest plugin]
ingest -->|passages| tei[TEI · E5 embeddings]
api -->|search, rehydrate, recheck| weaviate
api -->|changes: polling / SSE| client
A single quivr binary provides the api, worker and migrate commands.
| Concern | Choice |
| --- | --- |
| Core | Go modular monolith (cmd/quivr, internal/…) |
| Transactional catalog | PostgreSQL 17 |
| Canonical bytes and artifacts | S3-compatible storage (SeaweedFS locally) |
| Durable orchestration | Temporal |
| Lexical and vector search | Weaviate |
| Embeddings | Local TEI serving pinned multilingual-e5-small (384 dimensions) |
| Contract | OpenAPI 3.1 in contracts/http/v0 |
| Local runtime | Docker Compose |
Quickstart
Requirements (Linux x86_64, or macOS on Apple Silicon for make dev): Go 1.27.1, Docker with Compose v2, Python 3 with venv,
Node.js 22+ and jq. The first run downloads pinned images and the E5 model (~1 GB).
make dev # start dependencies, run migrations, launch API + worker
make check # docs, contracts, vet and unit tests without Docker (about 2 min); run before pushing
make verify # make check, then end-to-end journeys on an isolated stack
make down # stop everything, keep data (make reset also deletes volumes)
make verify runs every feature's acceptance suite and one assembled monitoring
journey on an isolated stack. It removes only its own project, even after a
failure or Ctrl+C. It then prints the path of a report.md that names any failed
step, the pinned versions and the dependency inventory. It runs on Linux x86_64
only, as in CI; see the remaining limits.
make dev prints the API address and the path of a generated config.json holding
throwaway local keys; eval "$(make -s env)" exports the address, a key and a webhook
destination. Then follow the Quickstart:
create a Corpus, add an article, search it and get an alert, with commands that
make verify replays against a real stack.
For a browser UI over the same API, run make demo and open http://127.0.0.1:5183
(see quivr-search/).
What works today
- Corpora with scoped API keys per Organization, action and Corpus.
- Durable, idempotent ingestion: inline text, bounded batches with per-entry
- Corrections and withdrawals with immutable Versions and fenced withdrawn Records.
- Search: lexical, semantic and hybrid, with canonical rehydration and access
- Change feed through polling and resumable SSE, plus catalog resync after
- Saved Queries and Subscriptions, pinned and versioned; enabled Subscriptions turn
/v0/matches), each with a
Delivery. Matching is decided by a pinned alert-rule plugin (the
subscription Contribution), batched per article. Both can be renamed without a
new Version.
- Keyword alerts through the first-party plugin
plugins/alerts,
"Acme" AND (grève OR strike) NOT sport, with exact phrases,
"any of", "none of" and grouping;
- case, accents and punctuation are ignored, and words match whole;
- metadata filters such as source:wire or author:"Jane Doe" (names mapped in the
plugin configuration), and a filter alone is a valid alert;
- each Match's evidence names the matched terms and the Parts where they matched
(guide).
- the browser demo's Alertes tab writes these alerts and shows what each one
caught, live (quivr-search/).
- Described alerts through the same plugin: a plain-language description such as
quivr-search/).
- Local meaning alerts through the same plugin: the
meaningkind with
meaning_check: vectors compares
a description with stored embeddings to catch rephrased or translated articles
without an external classifier (text stays local only with a local embedding provider). keywords_or_meaning and keywords_and_meaning
combine keyword and meaning checks
(guide).
- Subscription previews (
POST /v0/subscription-previews): before saving an alert,
- Subscription owners: an application can create a Subscription for one of its
owner such as user-123) or a global one, see the owner on
the Subscription, its Matches, webhooks and change feed to route each alert, and list
a user's active Subscriptions with GET /v0/subscriptions?owner=….
- Correction and withdrawal notices for alerted Records: a correction that still
match.corrected), one that no longer matches
gets match.no_longer_matches without a new Match, and a withdrawal gets
match.withdrawn. Earlier Matches stay readable.
- Signed webhook delivery (Standard Webhooks) to deployment-configured destinations,
/metrics).
- Projection rebuilds from durable artifacts as recoverable Operations, with cancel
- Document step times: each Version reports when it was accepted, materialized, cut
steps). A key with observability:read lists the latest documents with
their steps (GET /v0/admin/documents) and reads one document's timeline with each
step's duration and plugin.
- Typed retrieval mappings per Corpus (
PUT /v0/corpora/{id}/retrieval): logical
- Connector Instances: scheduled pull acquisition into a Corpus, with write-only
rss (RSS and Atom feeds), m365_mail (Microsoft 365 mailboxes)
and x_list (first-party plugin plugins/x-list), which polls an X list: edits become
corrections, deleted or protected posts are withdrawn, and health shows daily reads
(guide). In webhook mode, X posts arrive in near real time
through Filtered Stream webhooks relayed by the core to the plugin, with polling as the
fallback (guide).
The deployment credential_key is optional. Without it, credential deposits are
refused with 503 credentials_unavailable, and everything else works.
GET /v0/connector-kinds publishes each enabled kind's config and credential JSON
Schemas, PUT /v0/connectors/{id}/schedule changes the polling interval,
POST /v0/connectors/{id}/runs checks a source again now, and validation errors name the offending field as a JSON Pointer.
- Secure source API routes (Plugin API 0.12): connector plugins declare POST push and GET challenge routes at
/v0/connectors/{id}/api/. The engine checks a collection-scopedconnector:pushkey, an instance-scoped bearer token, or provider signature policy, with timestamp and replay protection for signed pushes; accepted pushes return202with ingestion Receipts. Author guide. - Sources page in the web app (
quivr-search, Sources tab): paste a site
DEMO_FEED_SUGGESTIONS.
Each source shows its health and last article, and can be paused, resumed or
removed; a failing one can be checked again at once (Réessayer). Other kinds keep forms generated from their schemas, so new kinds need
no UI change (guide).
- Live feed page in the web app (Veille tab): everything entering the demo
- Operational metrics and correlated logs on each process's private probe
/metrics, Prometheus text, bounded labels):
- API: accepted commands and the pending-ingestion backlog;
- worker: processing outcomes, time from acceptance to searchable, and delivery
attempts and durations.
JSON logs link request, Receipt, Record and Version IDs (harness).
- Retrieval measurement with a frozen workload (
make measure), and **search
make eval, guide).
Share measurements through MLflow with an offline outbox, paired comparisons and a
Pareto leaderboard (results guide).
- Bounded search campaigns explore settings on public development or encrypted
- Private news evaluation builder: pluggable generation with configurable targets
- Plugin Protocol v0 contract (
contracts/plugins/v0/) andquivr plugin inspect,
quivr-plugin.yaml and reports its compatibility, Contributions,
schemas, secrets and limits.
- Plugin registry and activation without restart: the plugins pinned at startup are
plugins:admin, which no Organization key gets, registers a plugin version running at an
address; Quivr checks it with the Contract Runner, and one call activates it as a new plan
that api and worker follow without restarting
(Switch plugins without restarting).
Work already started (a receipt's processing, a connector run, a rebuild) finishes on the
plan it started on, even across a worker restart. The replaced version shows draining
with the count of work still pinned to it, then inactive. Work whose pinned plugin
disappears is quarantined with a diagnostic naming the plan and the plugin, never moved to
the new version. Quivr never starts a plugin process. One call rolls back to the previous
plan. A nightly run (make measure-upgrade) upgrades, drains, rolls back and backfills
under continuous ingestion and checks that no article is lost and the API keeps answering
(Upgrade a plugin with no downtime).
- Backfill and vector space promotion: an operator fills a new embedding model's
POST /v0/admin/backfills). The backfill runs paced
below live ingestion, can be paused, resumed or cancelled, and resumes from its
checkpoint after a restart. One call then makes search use the space, and the same
call on the former space goes back
(Fill a new vector space for past articles).
- Plugin and search counters: each process counts every plugin call, search,
observability.flush_interval, default 5 s, the most a crash can lose). A key with
observability:read reads its Organization's last hour, day or week from
GET /v0/admin/stats/plugins, searches, steps, received, matches and
top-queries; nothing is kept beyond 7 days. Top queries need observability.record_query_text, off
by default because it stores query text. The same counters are on /metrics.
- External normalizer: the startup configuration pins one plugin and routes Blob
provenance.normalization. An unavailable plugin is retried and never blocks the
API or other ingestion. A plugin error, invalid output or exhausted retries quarantine
the Version with a structured diagnostic, and an optional text route falls back to
the built-in text path (guide).
- Reprocessing quarantined Versions: after a plugin fix or rollback, an operator
- Plugin-owned extension namespaces: the pinned plugin's declared namespaces are
422 extension_namespace_owned), and retrieval mappings can map them into search.
- Go and Python Plugin SDKs serve all five Contributions, with named source route handlers and an offline push-source sample. Both kits support push
quivr plugin init scaffolds normalizers, alert rules and pull or push sources, and quivr plugin dev, which runs it locally, checks its
discovery digest and replays a fixture through the engine's Manifest validation,
without a Quivr stack (SDK guide).
- Plugin Contract Runner (
quivr plugin test), which certifies a normalizer over
--endpoint . It runs
health, discovery, normative and plugin fixtures, deterministic replay, the declared
deadline, terminal errors for invalid requests, and compatibility ranges. It judges
output with the engine's own validation: Manifest rules, response size, input-Blob-only
Blob Parts and declared namespaces. It writes a JSON report with --report, and CI
publishes one for the quivr plugin init template.
- Searchable PDFs through the reference plugin
plugins/pdf-text
application/pdf Blob becomes one body Part per page with
text, and a phrase is found on its page's Part. Blank or scanned pages give warnings;
encrypted or damaged PDFs are quarantined with a diagnostic naming the plugin.
make dev pins it by default; there is no OCR
(guide).
quivr searchfrom the command line: setQUIVR_API_URLandQUIVR_API_KEY,
quivr search --corpus "query" prints ranked hits with their
excerpt and Record / Version / Part provenance, or the unchanged API response with
--json. Failures exit with one code per class (rejected key or scope, invalid
request, unreachable server). It uses a Go client generated from the contract
(package client) and never touches the stack's storage
(guide).
- AI agents search and cite Quivr over MCP:
quivr mcp --profile readserves an
--profile ingest adds text to a
Corpus and follows its Ingestion Receipt until it is searchable; retries never
duplicate, and no tool deletes. The API key alone decides access
(Connect an AI agent).
- A guide to writing a normalizer: scaffold, run, certify, pin, ingest and observe
- Source collectors as plugins, in Go (Plugin API 0.3): the connector contract
fetch a page after an opaque checkpoint, check_credential, classified errors),
a Go Plugin SDK (sdks/go) that redacts credentials, and
quivr plugin test checks that pages resume from their checkpoint and that no
credential leaks. The core calls these plugins for scheduled and on-demand collection.
- Protected source pushes: per-instance token buckets, TTL replay of
Idempotency-Key answers, optional CIDR allowlists with trusted proxy resolution,
and accepted/refused audit events with per-instance admin statistics.
- Segmentation and embedding as a plugin (Plugin API 0.6, the
ingestion
GET /v0/corpora/{id}/vector-spaces shows each space's owner, role
and coverage (Write an ingestion plugin).
The first-party core.ingest plugin (token windows,
E5) is pinned by default; the engine segments and embeds nothing itself.
Optional hosted.embed selects a hosted model
or OpenAI-compatible server by configuration, with OpenAI and Cohere v2 formats.
- Search ranked by a plugin (Plugin API 0.7, the
retrievalContribution): a
GET /v0/search/profiles). Several retrieval plugins can be pinned together:
search accepts full plugin/profile names or short names configured in
retrieval.profiles, including default (Write a retrieval plugin).
The first-party core.retrieve plugin (keywords,
vectors or both) is pinned by default; optional settings select vector weight,
candidate depth and relative-score or RRF fusion (defaults: alpha 0.5, search limit,
relative score). The engine ranks nothing itself.
What comes next
- Filtering on typed field mappings (filter roles are validated and stored today).
Documentation
The documentation site, docs.quivr.thevibecompany.co,
is written for people who use Quivr and write plugins: an introduction, the Quickstart,
core concepts, plugin guides, task guides and the reference. Its source is
docs-site/; the HTTP, CLI, MCP and plugin references there are generated
from the contracts.
This repository keeps the documentation for contributors, listed per reader on the
start pages generated from docs/inventory.toml:
- Using Quivr: the READMEs of the contracts, the demo and the
- Writing plugins: the READMEs of the SDKs, the plugin
- Contributing to Quivr: change this repository, as a person
Repository layout
cmd/quivr/ single binary: API, worker, migrations
internal/ domain modules (content, corpus, retrieval, changes, monitoring…)
contracts/http/v0/ OpenAPI contract, examples and checks
contracts/plugins/v0/ Plugin Protocol v0 schemas and normative fixtures
sdks/go/ Go Plugin SDK for every Contribution
sdks/python/ Python Plugin SDK
plugins/pdf-text/ reference normalizer: PDF text, one Part per page
migrations/ ordered PostgreSQL migrations (UTC-stamped; legacy 0xx_ first)
scripts/ local stack, verification and measurement tooling
quivr-search/ demo web UI
deploy/ Docker Compose and Railway deployment
docs-site/ the public documentation site (Mintlify): authored MDX pages and generated references
docs/ contributor documentation, ADRs (docs/adr/) and dated documents (docs/dated/)
multimodal-rag/ earlier exploration (submodule), not the target architecture
Contributing
- Read
AGENTS.mdandCONTEXT.mdfirst; use the domain
- Change the contract in
contracts/http/v0/openapi.yaml, then runmake generate. - Run
make checkbefore pushing and keepmake verifygreen; add tests with
- Declare every new living doc page in
docs/inventory.toml
make start-pages; make docs fails on an undeclared page, a stale start
page, a broken relative link or a missing repository path, and names the fix.
- Document user-facing behaviour on the site, in
docs-site/, in the same pull request;
make docs-site after a contract change. Show API requests there as
runnable blocks, which make verify replays.
- Never edit an accepted ADR or a dated document under
docs/dated/: supersede it
make docs compares them with where your branch forked from origin/main.
- Pull request titles follow Commitizen conventions, for example
feat(ingestion): accept record versions.
- Keep customer-specific formats and rules out of the core; they belong in plugins.
License
MIT — see LICENSE.