Nous: my self-hosted second brain (and why it has no public demo)
Project Tech Sheet
Month
Month 2
Project
Nous
Tech Stack
AI Tools
My notes are scattered across the Mac's Notes app, Apple Notes on my phone, and a Notion I abandoned a year ago. I never find anything when I need it, and when I finally do, I can't remember what other note it was related to.
For project #2 of the challenge I wanted something different from Rumbo: not a numbers tool, but a language one. Nous is my second brain — I write plain-text notes and ask questions about them in natural language. Not a search box with templates on top, but a genuinely generated answer, always anchored to what's actually written there.
Nous: your notes aren't searched, they're talked to.
What does Nous do?
It's a personal (single-user, just me) app with two screens: Notes and Chat. Self-hosted end to end — Postgres in Docker on my Mac mini, no cloud service involved anywhere.
- Plain-text notes with tags, no folders or forced hierarchies — the first line is the title, that's it.
- Natural-language chat over your notes: real RAG (embeddings + LLM), not a keyword search with a template on top.
- Wiki-style links between notes (`[[Title]]`) with automatic backlinks.
- 100% local by default: Ollama running on the Mac, no bill, no API key, not a single note leaving the house.
- Installable as a mobile app (PWA) to open it without Safari's chrome — no service worker, since it needs the live backend either way.

The AI provider is a port, not a fixed detail
RAG over one person's private notes doesn't have the same cost justification as it would at a company, where AI spend is offset by savings or revenue — paying per token to search my own notes wasn't worth it to me. That's why `EmbeddingProvider` and `LLMProvider` are two independent ports in the hexagonal architecture: the domain doesn't know or care whether behind them is Ollama running locally, a paid API (Voyage + Claude), or the fake provider the tests use. By default everything runs on the Mac mini with `mxbai-embed-large` + `qwen2.5:7b-instruct`, with real answers and genuine semantic understanding, no bill, and the AI provider never sees a single note.
Semantic search runs on `pgvector` inside the same Postgres — no separate vector database service to stand up, so deployment stays as light as a plain Postgres.
Links between notes, and the bug that almost slipped through
The last feature before calling the project done: writing `[[Another note's title]]` in the content turns it into a real link, and the target note automatically shows who mentions it (backlinks) — no relation table or new endpoint needed, since every note's full content already ships to the client to make editing possible.
While building it I accidentally introduced a bug: the notes screen started fetching the list from the backend twice — once filtered by the active tag to render the view, and once unfiltered to resolve links against notes that filter would otherwise hide. It surfaced during a full repo review before publishing: it broke an existing test, and it also left the list used to resolve links stale after editing a note while a tag filter was active. Fixed by simplifying to a single unfiltered data source, applying the tag filter client-side over that same data — fewer backend requests, and the bug disappears by design instead of being patched over.

Streaming, conversational memory, and no separate "search mode"
The chat persists every question and answer in Postgres as one continuous conversation, and when asking a question the last few turns are passed to the LLM as real role-tagged messages, not hand-concatenated plain text — that's what makes follow-up questions like "what about the trip?" work without repeating context. The answer appears word by word via streaming (SSE), instead of waiting behind a loading spinner for the full text.
The first version did show, under each answer, the source notes plus a separate "Search" screen, meant as proof the answer was anchored to something real. In real use, with a model genuinely generating the answer, that scaffolding was in the way: it broke the feel of a natural conversation without adding anything I'd actually use. The backend still filters the retrieved context by relevance before handing it to the LLM — the anchoring to real notes is still there, it just no longer needs to be shown.

What went wrong getting it ready to show
Before calling the repo publishable I did a full review, and real things came up, not just cosmetic ones:
- The screenshots and the social preview image claimed "fictitious demo data" in the README, but they weren't: there was no seed script (unlike other projects in this challenge), and the screenshots were my actual real notes. I regenerated them against an isolated test database, with made-up notes — and so the chat answer wouldn't be a filler placeholder, I captured it with the real engine (Ollama) actually answering over that test data.
- Dependabot had split the `react` and `react-dom` update into two separate Pull Requests — merging either one alone desynced the versions and broke the build. I applied both together by hand.
- I updated Node to the latest 24.x release so I could safely merge a major version bump of `jsdom`, which required a minimum version my Mac didn't have yet.
The code is public. My notes aren't.
That's why the "View Project" button on this card links to the GitHub repo, not a live demo — and here the reason differs from Rumbo's. It's not that exposing an editable form would be unsafe: it's that Nous's entire value proposition is that your notes never leave your machine. A hosted public demo would contradict its own pitch. The repo is public and clonable, but only I have write access.
Building in public doesn't mean everything has to live on someone else's server — sometimes it means exactly the opposite.
Next month: tappttoo, or whatever ends up feeling more urgent by then. Shall we?