Skip to main content
Package quivrplugin implements the connector Contribution of the Plugin Protocol v0 (contract, Plugin API 0.3), so a source collector is one Go type. The core keeps the schedules, checkpoints, credentials and health; your plugin only fetches pages from the source. It depends only on gopkg.in/yaml.v3 and santhosh-tekuri/jsonschema/v6, never on Quivr’s engine packages. Go 1.24 or later. It also serves the ingestion Contribution (Plugin API 0.6, below) and the retrieval Contribution (Plugin API 0.7, below); write normalizers and alert rules with the Python SDK.
The sample examples/static-source is a complete plugin: a manifest, a connector, fixtures and a test.

A connector

Declare each kind in quivr-plugin.yaml with the JSON Schemas of its configuration and credential:
Then implement Fetch and CheckCredential and serve:

An ingestion plugin

Declare contributions.ingestion with the vector spaces the plugin owns and register a type with two methods, SegmentAndEmbed (segments of the Version’s text Parts, each with a vector per requested space) and EmbedQuery, through plugin.Ingestion(impl). The SDK refuses undeclared spaces and checks offsets, dimensions and max_segments before answering; a RetryableIngestError delays the Version, a TerminalIngestError blocks it. examples/hash-embedder is certified in CI, and Write an ingestion plugin covers pinning it.

A retrieval plugin

Declare contributions.retrieval with its profiles (default required) and register a Retriever through plugin.Retrieval(impl). Each round, Search answers quivrplugin.Ask(requests...) for candidates or quivrplugin.Rank(hits...) for the final ranking; req.Served holds every candidate served so far. Before answering, the SDK refuses undeclared profiles and checks what the core checks: no requests in the last round, request and k limits, and a ranking of served candidates only. A TerminalSearchError refuses the query. examples/fusion-retriever is certified in CI, and Write a retrieval plugin covers pinning it.

What the SDK does

Checkpoints, items and errors

  • The checkpoint is yours. Any JSON value that resumes after the returned items: a cursor, a timestamp, a set of seen ids. The core stores it and sends it back unchanged, and moves it forward only after your items are accepted, so a crash never loses items. Keep it under 64 KiB, or declare limits.max_checkpoint_bytes (at most 1 MiB).
  • Pages. Return at most max_items items and More: true to be called again at once in the same run, with the checkpoint moved. Use req.Now, not time.Now(), and req.PageInRun and req.ReadsToday to respect source quotas. req.Connector.CorpusID and req.Connector.SourceNamespace (Plugin API 0.3.1) name where the instance writes, to bind Relation targets.
  • Items carry a stable RecordKey, a Revision when the source has one (the core skips an unchanged revision), text or a Manifest of text Parts (quivrplugin.NewManifest), extensions in namespaces your manifest declares, or Withdraw: true for an item deleted at the source.
  • Attachments (Plugin API 0.4): declare contributions.connector.attachments and implement OpenAttachment, which streams req.Attachment.Ref. The SDK reads the bytes once into a temporary file, answers their size and SHA-256 or a skip (optional SkippedAttachment updates the item’s extensions), then uploads them to the core’s grant, which never prints. Set SizeBytes and SHA256 when you already hold the exact bytes.
  • Errors tell the core what to show operators: AccessError for a refused credential (health access_error), TransientError for an outage or a rate limit (retried), SourceError for data you cannot use.
  • Push (Plugin API 0.5): declare modes: [pull, push] and implement Receiver. The core relays each request the source sends to the instance’s public WebhookURL (in fetch requests; register it with the source). Receive verifies it with the credential over req.Request.Body(), then answers Accept(items...), Respond(200, type, body) for a challenge, or Refuse(401, reason), which changes nothing. Map a delivered item exactly as Fetch maps it, so both paths converge on one Receipt. Page.Push (PushIsActive, PushIsPending, PushHasFailed) reports your setup at the source; an active one may relax polling.

Test it

plugintest replays a fixture in process, through the same handler the core talks to:
A fixture (schema (contracts/plugins/v0/connector-fixture.schema.json)) names the kind, config, credential and starting checkpoint, and what to expect: the Record Keys of each page, an error class, the credential check, and for a push kind the receive cases with their expected verdicts. Use test credentials of 8 characters or more, so the leak check can see them. Then certify the plugin with the Contract Runner:
It runs every fixture page by page, feeding each checkpoint back, resumes from the final checkpoint, checks credentials, error classes and invalid requests, and fails when a credential value appears in an answer or in your plugin’s output. CI certifies the sample and publishes its report as the go-connector-contract-report artifact. To run it in Quivr, pin it as in Run a connector plugin.

Rules

  • Import only this SDK and public packages: make plugin-boundary fails when code under plugins/ or sdks/go/ imports Quivr’s internal/ packages or requires the engine module.
  • The schema copies under quivrplugin/schemas come from contracts/; run python3 sdks/go/scripts/sync_schemas.py after a contract change (make test fails when they are stale).