Choosing the Tech Stack
I did not pick this stack component by component. I picked one constraint—no CGO, ever—and the stack assembled itself. Every candidate that needed a C compiler was out, and what remained turned out to be the most boring, most portable option at each layer. Boring is the goal: this tool has to still build in five years.
One Constraint, Many Consequences
No CGO means the whole app cross-compiles statically and there is never a “works on my machine” toolchain story. It ruled out a lot:
- SQLite must be pure Go →
modernc.org/sqlite, notmattn/go-sqlite3. - The tray must be pure Go →
github.com/gogpu/systray(Shell_NotifyIconon Windows,NSStatusBaron macOS, StatusNotifierItem over D-Bus on Linux). - PDF work cannot be a C library → external tools invoked ad hoc.
- The UI cannot be a WebView → a real browser front end, served over HTTP.
That last one is the interesting consequence. A WebView needs WebKitGTK on Linux and WKWebView on macOS—both CGO. So the UI is a PWA: the app hands its address to the operating system’s URL handler, and the installed PWA provides the window. No embedded browser, no duplicate server framework.
The Stack
- Go 1.27 (pinned with
mise), one module,github.com/go-chi/chi/v5for the loopback HTTP service. - SQLite via
modernc.org/sqlite, migrations withpressly/goose/v3(//go:embed migrations/*.sql), queries generated bysqlcinto adalpackage. - Svelte 5 + Vite + TypeScript for the SPA, built into
internal/web/distand embedded withgo:embed. One binary, one origin, no CORS. - FTS5 for lexical search—the lexical half of vectorless retrieval.
sashabaranov/go-openaifor the model, ULID for ids,yaml.v3for config,fsnotifyfor the inbox watcher,log/slogwith a JSON handler for logs.- Poppler + Tesseract for PDF text and OCR, invoked as one-shot
stdin → stdoutsubprocesses.
YAML Stays at the Edge
One rule I set here and have kept: YAML lives only in internal/infrastructure/markdown. The core knows nothing
about YAML, frontmatter, or the OpenAI SDK. If the core imported YAML, the core would be coupled to a file format—and
the vault layout would become an architectural dependency instead of an implementation detail.
Deliberate Non-Choices
Also decided here, in writing: no ORM, no Postgres, no vector store, no embedding model, no Bleve/Lucene, no external
search service, no web framework beyond chi, no CGO OCR bindings (gosseract, go-fitz), no MCP client in v1.
One constraint—no CGO, ever—elects this stack instead of the other way round. The chapter shows what follows: one binary that can be copied to any machine, CI without a container, and a tray, a database and a PDF pipeline that are all the same kind of dependency. The deliberate non-choices (no vector store, no ORM, no external search service) are argued with the same reasoning. The goal is not minimalism for its own sake but a portability that survives years—a tool that still builds in five years is made of boring parts.
