Polarize
GitHub Get in touch

September 15, 2026 · Method · v1

A research assistant that keeps nothing

zdr-research-chat: a voice research assistant where the model, embeddings, speech-to-text and text-to-speech all run on one machine — how the zero-retention claim is checked, and the four places it deliberately is not true.

  • [A]
A closed laptop lying on brick paving, wrapped crosswise in a heavy chain held shut with a padlock.
Photograph by Santeri Viinamäki, CC BY-SA 4.0. Desaturated for this site.

In short

Most private AI is private because a vendor promises not to look. This stack is private because the conversation never leaves the machine — a property checked with a socket listing rather than a policy. It keeps a git-tracked research journal, answers by voice from a phone, and ships a firewall, because the machine it was built on turned out to be answering strangers.

A tool write-up. zdr-research-chat is the research assistant behind this site: a chat and voice interface to a language model where every component that touches the conversation runs on the author’s own machine. Built for work that produces a written record.

What runs where

function runs on leaves the machine
language model Ollama, host-native, on the GPU no
embeddings Ollama, host-native no
chat interface and retrieval Open WebUI, in a container no
vector store Qdrant, in a container no
speech-to-text faster-whisper, inside Open WebUI no
text-to-speech Kokoro, host-native no
phone access Tailscale private network, HTTPS encrypted end to end between own devices
journal a local git repository only on push, to its own private remote

The model and the voice synthesis run outside containers because containers on Apple Silicon cannot reach the GPU. Everything else is in containers and bound to loopback.

Checking the claim

Zero retention is stated as something to verify, not something to trust. Three checks:

  1. List live connections during a conversation. Nothing in the chat path should connect anywhere but the local machine.
  2. Turn off Wi-Fi. Chat, retrieval, speech-to-text and text-to-speech keep working. Only phone access and journal pushes need a network.
  3. Read the switches. Open WebUI’s telemetry, analytics, update check and web search are off in the environment file. Qdrant’s usage statistics are off in the compose file.

The Qdrant switch was missing until 2026-09-15. Qdrant ships with usage statistics on, and the stack never turned them off, so one of its two containers was sending anonymised usage data while the documentation said telemetry was off. Found while checking this write-up against the code rather than the README. The switch is now set, and the repository’s self-check fails if it is ever removed.

Where it deliberately is not zero-retention

Four exceptions, each off unless switched on, each documented in the file that makes the request.

tool reaches sends
literature search OpenAlex, Europe PMC, Crossref, DANDI search terms and DOIs
web search DuckDuckGo the query; disabled in the shipped config, and per chat when enabled
air quality OpenAQ, and a geocoder if given a place name coordinates or the place name
video transcripts YouTube a public video id

None of these sees the conversation. A search query is still data, and those services log requests like any website.

A tier, not a slogan

“Zero data retention” describes where the stack is pointed, not the code. Every model, embedding, speech and voice backend is a URL. The deployment declares which tier it is in:

tier means checkable how
local every backend on this machine a connection listing during a conversation
self-hosted own hardware, own jurisdiction own network
managed zero-retention a vendor with a contractual guarantee not checkable — a contract, not a measurement
cloud no guarantee —

Only local can be verified from the machine itself. It is the default.

Historical drawing: the layers of the retina, with its receptor, bipolar and ganglion cells drawn one by one.
Santiago Ramón y Cajal, drawing of the retina (Tafel III). CC BY 4.0, Wellcome Collection. Inverted and toned for this site.

The journal

The assistant can make three kinds of write — a finding, a theory update, a session log — into a separate git repository.

  • Only on an explicit request (“log that”, “save this”). Otherwise the write is refused and the assistant says nothing was written.
  • One commit per write, append-only. No amend, no rebase, no force-push, no whole-file rewrite. The history of a theory file is the history of the thinking.
  • The container never runs git. The tool writes the file and queues a request; a committer on the host turns each request into one commit. A crashed container cannot leave a repository half-written.

Literature: the model only asks

In the literature tool the model’s whole job is to turn a request into search terms and pick a function. The archive ranks the results. The tool draws titles, authors, abstracts and retraction notices straight from the archive’s records into a panel. The model is told the reader can already see the panel and not to restate it.

A citation on screen never passed through the model, so the model cannot misquote it. Tools that place a language model between the reader and the paper are deliberately not used. The archives are a fixed allowlist of four hosts, enforced in code.

Memory is the design constraint

On a 16 GB machine the model shares memory with the containers, the embedding model and the operating system. If the machine is swapping, the model is too big, and no quantisation setting recovers it. A smaller model that fits in memory beats a larger one that pages.

Two further rules from running it:

  • Avoid models that reason before calling a tool. Journal writes and searches are tool calls; a model that thinks at length before each one turns a voice exchange into a wait.
  • Raise the context length. The runtime’s default context is small enough that a system prompt plus a few tool definitions can silently push the tools out.

Why a firewall ships with it

A laptop can hold a publicly routable address with no router in front of it. Then every service listening on all interfaces is on the open internet — including a model server that must listen beyond loopback so the containers can reach it.

On the machine this was built on, that was not hypothetical. Counted from the model server’s own access log: 1,009 successful inference requests from 160 addresses outside any private or tailnet range, 5,985 outside requests in all, between 15 August and 9 September 2026. The last is on 9 September, the day a default-deny firewall for the public interface was committed. Local requests continue in the same log after it.

So the firewall travels with the stack: loopback, the tailnet and the container bridge stay open, the public interface does not. A network check reports in one line whether a newly joined network is hiding the machine or exposing it.

Limits

  • An 8B-class model on a laptop is not a frontier model. The trade is capability for being usable with material that should not be uploaded.
  • macOS on Apple Silicon only, 16 GB minimum. The GPU paths assume it.
  • Tailscale’s coordination service sees device metadata, not content.
  • The four exceptions above are real egress whenever they are switched on.
Version history 1 revision

Generated from this site's git history. with Claude means the commit carries a co-author line for an Anthropic model: an LLM took part in that change, not necessarily in every word of it, and git cannot say which. The same history on GitHub →

  1. v1 2026-09-15 07:04 Post: a research assistant that keeps nothing; Suunto figure rebuilt on tokens Joshua Anderton with Claude Opus 5 first version

    v1: the original version, as first committed. Read it on GitHub →

    Commit 926c580 on GitHub →