Apache-2.0 · self-hosted · Spring Boot

Answers from your
own documents.
Or none at all.

Ossian is a RAG backend for your agents: every answer carries the passage it came from, and when the corpus cannot support one it says so instead of inventing it. With the ingestion pipeline, memory, credentials, limits and audit trail needed to actually run that.

Runs on your infrastructure Your documents never leave Bring your own model
How it works

Four steps, and a refusal when they cannot be honoured.

Drop documents in, ask questions, get answers you can check against the text they came from.

01

Ingest

PDFs, Word, Markdown, plain text — or a URL, or a change streamed from your database through Debezium and Kafka. Tika reads the format from the bytes, so nothing is converted first.

02

Split and embed

Cut into overlapping passages, into pgvector. Without overlap a fact straddling a boundary belongs to neither passage and is never retrieved.

03

Retrieve

The question is embedded and matched, narrowed to a namespace if you want one. Weak matches are discarded rather than passed on.

04

Answer, with sources

Only the retrieved passages reach the model, numbered per document so its [1] markers map back to sources you can open — three passages of one handbook are one source, not three agreeing.

The value is not that it answers.
It is that it declines.

Below a similarity threshold, Ossian returns nothing and never calls the model. An unsupported answer is worse than no answer, because it is indistinguishable from a good one — and a confident invention presented as company policy is the failure this whole system exists to prevent. Every refusal is recorded, and that list is the most useful ingestion backlog anyone has handed a documentation team.

"I could not find anything about that in your documents."
See it

Thirty seconds, end to end.

A question answered with its citation opened, the same corpus refusing one it cannot support, the vector inspector, agent memory, and the operations console.

The Ossian console: asking a question, opening the cited passage, a refusal, the vector inspector, agent memory and the operations console

Recorded from the running console · MP4

What you get

The boring 90% of RAG, already built.

The demo is a weekend. This is the part that decides whether it survives contact with a real company.

See what the retriever sees

Every stored chunk, its dimensions and norm, and a playground that runs a query with no model in the loop. Retrieval and generation failures look identical from outside and have opposite fixes.

Memory that outlives the chat

Ranked by relevance, weighted by importance, decayed by the time since it was last said — not last read, so a stale preference cannot outrank the newer one that contradicts it. Kept apart from the corpus, so a recollection never surfaces as a citation.

Credentials a machine can hold

API keys with their own roles, confinable to one namespace — a leaked pipeline key reads only what that pipeline was for. Stored hashed, shown once.

Limits measured in tokens

One limiter counts requests; the other counts embedding tokens, because a single upload can be a million of them. Ingestion waits rather than failing.

Ingestion you can operate

Prometheus metrics, failures grouped by cause, and a retry button. It catches documents left processing for an hour — nothing marks those failed, because the process that would have is the one that died. Or skip uploads entirely and stream changes from Kafka.

Tunable without a redeploy

Model, temperature, chunk size, overlap, how many passages and how good a match must be. Chunking is per namespace, because a runbook and a contract want different sizes.

For agents

An MCP server, so your agent can just ask.

Seven tools over stdio. The descriptions tell the model when not to use each one — the failure that matters is not a malformed call but a well-formed call to the wrong tool.

Install

# the MCP server
npm install -g ossian-mcp

Point it at your Ossian

{
  "mcpServers": {
    "ossian": {
      "command": "ossian-mcp",
      "env": {
        "OSSIAN_URL": "http://localhost:8081",
        "OSSIAN_API_KEY": "osk_…"
      }
    }
  }
}

What the agent gets

Tool
For
 
ask_documents
answer from the corpus, with citations — and say so when it cannot
recall
what is relevant now, ranked by importance and recency
remember
record a preference, fact or decision worth keeping
list_namespaces
which slices exist, and how much is in each
list_documents
what is answerable, and what is still ingesting
add_document_from_url
pull a public page into the corpus
forget_session
erase one conversation's memory
From Kafka

A corpus that follows your database.

A knowledge base that is already a table does not need an export job. Debezium streams its changes into Kafka, and the Ossian sink turns each one into an indexed, citable document — or removes it.

Point the sink at a topic

{
  "connector.class":
    "io.github.dockndevai.ossian.kafka.connect.OssianSinkConnector",
  "topics": "kb.public.kb_articles",
  "ossian.url": "http://ossian:8081",
  "ossian.api.key": "${env:OSSIAN_API_KEY}",
  "ossian.text.fields": "title,body",
  "ossian.filename.field": "title",
  "errors.deadletterqueue.topic.name": "ossian-sink-dlq"
}

The key comes from the worker's environment, never the connector config — those live in a Kafka topic and come back from the REST API. Give it a key confined to the namespace it feeds.

What happens to each change

In the database
In Ossian
 
insert, update, snapshot
indexed; an update replaces the old chunks, so nothing stale still answers
delete
the document and its chunks are removed
text blanked
removed, rather than left answering from text the source no longer has
connector restarts
ids come from the Kafka position, so a re-read topic is all duplicates
Ossian slow or down
backs off and retries; offsets never move ahead of delivery
a record Ossian rejects
sent to the dead-letter queue, and the rest keep flowing
What's new · 13 September 2026

Two bugs a question found, one a pipeline found, and a way in from Kafka.

A developer asked what happens when retrieved documents and an agent's memory disagree, or when several pieces of context come from the same source. Answering honestly meant reading the code, and the code had two answers that were wrong.

One document is one citation

Retrieval numbered context per chunk, so three passages of one handbook reached the model — and the reader — as [1] [2] [3]: three sources agreeing, when it was one source said three times. Passages are now grouped under one number per document, every passage kept, ranked by the best. #2

Memory ages from when it was said

Decay ran from when a memory was first written, so restating a fact never refreshed it. It now restarts on restatement — and deliberately not on recall, because a stale preference and the newer one contradicting it are recalled together, and refreshing both would erase the recency that lets the newer one win. #1

Deletes from a pipeline no longer fail first time

A DELETE event recorded the id of the document it had just removed, which broke a foreign key and returned 500 for the whole batch. A retry found nothing left to delete and succeeded, so every retrying pipeline hid it. The Debezium sink's first end-to-end run is what caught it. #5

A Kafka Connect sink for Debezium

New repository, ossian-kafka-connect, tested end to end: a snapshot, updates, deletes, blanked rows and an offset reset replaying the whole topic without a single duplicate.

A notebook that stays put

The notebook's width followed whatever it contained, so it narrowed and re-centred while an answer streamed. It is now fixed to the window, a cited passage opens beside, over or under the conversation depending on the room, and the navigation folds into a bar on a phone.

Still open

Memories do not link back to the document they were learned from, and a contradicting memory does not supersede the old one — both come back, and recency decides. Keeping documents and memory apart is staying; the rest is an open design question.

Get started

Running in about five minutes.

Free and open source, Apache-2.0. Postgres with pgvector, Redis, Keycloak and a model gateway — all in the compose file.

Locally

git clone https://github.com/dockndevai/ossian
cd ossian && docker compose up -d

docker compose exec ollama ollama pull nomic-embed-text
docker compose exec ollama ollama pull qwen2.5:3b

./mvnw -pl backend spring-boot:run
cd frontend && npm install && npm run dev

Then ./scripts/smoke.sh — it ingests a sample corpus and asks five questions. The fifth is deliberately unanswerable; an answer to it means grounding has broken.

On Kubernetes

kubectl create secret generic ossian-postgres \
  --from-literal=password=…
kubectl create secret generic ossian-gateway \
  --from-literal=api-key=…

helm install ossian deploy/ossian \
  --set ingress.enabled=true \
  --set ingress.host=ossian.example.com

The chart ships the application only. Postgres, Redis and Keycloak are referenced rather than deployed — a database packaged inside an app chart is deleted by helm uninstall, and nobody means that.

Questions

The things people ask first.

Is there a self-hosted alternative to NotebookLM?

Ossian runs entirely on your own infrastructure, so documents never leave it. But it is aimed at a different job from a notebook: it is a RAG backend that other software queries, with an API, machine credentials, rate limits and an audit trail. For one person reading twenty papers, Gemini Notebook or Open Notebook are the better answers, and I would rather say so than sell you the wrong tool.

How do I stop a RAG system from hallucinating?

Refuse rather than guess. Ossian discards passages below a similarity threshold, and when nothing clears it the model is never called — you get "I could not find anything about that in your documents". Answers that are produced carry the exact passages they were written from, so they can be checked rather than trusted.

Can an AI agent use it?

That is what it is for. API keys let a process authenticate without a browser login, and the MCP server (npm i -g ossian-mcp) gives an agent seven tools across documents, memory and ingestion. A key can be confined to a single namespace, so a leaked credential reads only what that agent was for.

What does it need to run?

Postgres with pgvector, Redis, Keycloak, and any OpenAI-compatible model endpoint — all in the compose file, with a Helm chart for Kubernetes. Ossian holds no model credentials itself; those live in the gateway it talks to, so swapping models is a routing change rather than a deployment.

Can it stay in sync with my database?

Yes — through Debezium and Kafka. The Kafka Connect sink indexes inserts and updates, removes deleted rows along with their chunks, and removes a row whose text was blanked rather than leaving it to answer. Event ids come from the record's position in Kafka, so a connector that restarts and re-reads a topic creates no duplicates.

Is it really free?

Apache-2.0, source on GitHub. No hosted tier, no licence key, no telemetry. It is also young — well tested, but run in anger by nobody yet, and that is worth knowing before you put it in front of anything that matters.