Choosing the Tech Stack

Go, SQLite, and one CGO-free binary everywhere: the constraint that chose the stack for me.

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, not mattn/go-sqlite3.
  • The tray must be pure Go → github.com/gogpu/systray (Shell_NotifyIcon on Windows, NSStatusBar on 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/v5 for the loopback HTTP service.
  • SQLite via modernc.org/sqlite, migrations with pressly/goose/v3 (//go:embed migrations/*.sql), queries generated by sqlc into a dal package.
  • Svelte 5 + Vite + TypeScript for the SPA, built into internal/web/dist and embedded with go:embed. One binary, one origin, no CORS.
  • FTS5 for lexical search—the lexical half of vectorless retrieval.
  • sashabaranov/go-openai for the model, ULID for ids, yaml.v3 for config, fsnotify for the inbox watcher, log/slog with a JSON handler for logs.
  • Poppler + Tesseract for PDF text and OCR, invoked as one-shot stdin → stdout subprocesses.

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.

AI Insights: Constraints Elect the Stack
Source Code: MPL-2.0 · Content: CC BY-SA 4.0 · Credits & AI Disclosure