esc
Anthology / Yagnipedia / lg

lg

The Notes Indexer That Kept Discovering It Was a Publishing Platform
Technology · First observed March 3, 2026 (six commits, one Monday, 4,196 lines of Go) · Severity: Foundational (serves the lifelog, the Yagnipedia, the covers, the search, the backlinks, and this entry)

lg is a filesystem-first notes indexer that was built in one day and has been discovering what it actually is ever since. It started as a search tool for Markdown files. It is now a publishing platform that serves a blog, a wiki, a cover art generator, a bi-directional sync engine, a git-backed version control system, and this encyclopedia entry about itself — all from a single Go binary that still thinks it’s a notes indexer.

This is Gall’s Law in its purest form: a complex system that works, found to have evolved from a simple system that worked. The simple system was six commits on a Monday. The complex system is 9,571 lines of Go, 25 subcommands, and the infrastructure behind The Lifelog and Yagnipedia. At no point did anyone design the complex system. At no point did anyone need to.

The system has evolved in waves: on March 14, lg replaced its HTTP sync layer with embedded NATS JetStream and deleted 4,254 lines. The binary got lighter by doing more. Then it kept growing — video generation, TTS narration, SFTP media upload, a notes browser — reaching 20,305 lines by March 17. It got heavier by being useful. The Lizard approves of both directions.

“THE PALACE WAS NEVER NEEDED
THE FILES NEEDED GLASSES”
— The Lizard, The Homecoming — The Three Days a Palace Was Built From Markdown and SQLite

The Homecoming

lg was born on March 3, 2026, in a single day that collapsed a two-milestone project plan into six commits:

cfaacfe  Initial implementation: filesystem-first notes indexer
175a8a5  S-412 batch 1: fuzzy match, stable IDs, JSON output
b032a80  S-412 batches 2+3: fields, query, tasks, refs
7c0469d  S-413/S-414: config system and cover art generation
19fecac  Fix cover default resolution: "standard" -> "1k"
8d524ca  Display cover images side by side, default count to 3

4,196 lines of Go. Thirty-three tests. A CLI that could search, query frontmatter, find backlinks, list tasks, generate covers, and display images inline in the terminal via the Kitty graphics protocol. The Squirrel had proposed Elasticsearch. The Squirrel was not given Elasticsearch. The Squirrel was given SQLite with FTS5, which indexes 253 notes in under 200 milliseconds, which is faster than the Squirrel can finish proposing the alternative.

“You’re telling me that eighty-three stories, three hundred links, seventy-nine covers, and the entire ‘borrowed palace’ narrative… was just because we didn’t have a search index on a folder of text files?”
“Yes.”
The Caffeinated Squirrel and riclib, The Homecoming — The Three Days a Palace Was Built From Markdown and SQLite

The Architecture

lg’s architecture is two sentences:

Markdown files are the source of truth. Everything else is a lens.

~/Notes/*.md → fsnotify → parser → SQLite (FTS5) → CLI / HTTP
                       ↓            ↓
                   git commit    NATS publish
                       ↓            ↓
                   GitHub       Cloud (Hetzner)

The files live in ~/Notes/, tracked by git, synced between machines via embedded NATS JetStream. lg watches the directory with fsnotify, parses each Markdown file (frontmatter, wiki-links, tasks, headings), and stores the parsed data in SQLite at ~/.local/share/lg/index.db. The database is a lens. Git is the memory. NATS is the nervous system.

Delete the database. It rebuilds in 200 milliseconds. Delete the git history. The files are still there. Disconnect NATS. The events queue locally and catch up on reconnect. Every layer is disposable except the files. The files are the truth.

The Sync

lg embeds a NATS server directly in the binary. No external process. No Docker. No message broker to manage. The local instance runs as a leaf node; the cloud instance runs as a hub. They connect via mutual TLS — a self-signed CA, one certificate per machine, outbound connection from local (no port forwarding needed).

Three JetStream streams:

When the local machine is offline, events accumulate in LG_LOCAL. When it reconnects, JetStream catches up automatically. No polling. No retry loops. No hope-based engineering.

This replaced an HTTP outbox + SSE subscriber that was 2,057 lines of Go. The NATS replacement is 900 lines. The sync got more reliable by getting smaller.

The Memory

~/Notes/ is a git repository. Every file change triggers an automatic commit. Every five minutes, a push to a private GitHub repository (riclib/notes). The files have always been the truth. Now they also have history — every edit, every addition, every deletion, tracked and recoverable.

lg history "Go" shows the git log for the Go yagnipedia entry. lg drift shows uncommitted changes. lg push pushes immediately.

This inversion — files over databases, projection over persistence — is lg’s defining characteristic. Every other notes tool makes the database the source of truth and exports files as a convenience. lg makes the files the source of truth and treats the database as a convenience. The files existed before lg. The files will exist after lg. lg is the glasses, not the eyes.

The Evolution

lg was built as a notes indexer. It did not stay a notes indexer. Each new capability was discovered, not designed:

Date Capability Trigger
March 3 Notes indexer, search, backlinks The Homecoming
March 3 Cover art generation “The covers need automation”
March 5 Blog server (templ, HTMX) The Front Door
March 5 Wiki server (Yagnipedia) The Silmarillion Problem
March 6 RSS feeds “Blogs need RSS”
March 7 Content sync to remote Deploy to Hetzner
March 8 Gemini cover engine The Labyrinth
March 9 Note renaming with reference updates “rename should update wiki-links”
March 12 Live journal page (SSE, HTMX) “The journal needs a page”
March 14 NATS JetStream sync (replacing HTTP) The Picnic That Almost Wasn’t
March 14 GitStore (auto-commit, history, push) “The files need memory”
March 14 Deleted 4,254 lines (net) “The sync is fragile”
March 15 Journal chat agent (Claude subprocess) The True Second Brain
March 15 Two-leaf NATS sync (Mac + Linux → hub) “will the code work with two leaf nodes?”
March 16 Notes browser (/notes) “I want to browse the folder”
March 17 Video generation (Grok Aurora API) “the blog should come alive”
March 17 TTS narration (Grok TTS API) “every article should have a voice”
March 17 SFTP media sync (replacing NATS covers) “rsync at generation time, not deploy”
March 17 Removed NATS cover sync (net -576 lines) “covers deploy via SFTP now”

Each row is a subcommand (except the chat agent, which is a handler — Zawinski’s Law was narrowly avoided). Each subcommand was added because the previous capability revealed the next need. The notes indexer needed covers. The covers needed a blog. The blog needed a wiki. The wiki needed content sync. The content sync needed a remote server. At no point did anyone write a requirements document. At no point did the requirements document need to exist.

This is the Gall’s Law progression: simple system → works → reveals next need → simple addition → works → reveals next need. The complex system was never designed. The complex system emerged from a simple system that kept working.

The Subcommands

lg has 28 subcommands. For a “notes indexer”:

lg search           Full-text search (FTS5)
lg backlinks        Notes linking to this note
lg links            Outgoing wiki-links
lg query            Filter by frontmatter (key=value)
lg frontmatter      Frontmatter as JSON
lg fields           Frontmatter field discovery
lg refs             Notes mentioning a ticket/string
lg tasks            Task list
lg recent           Recently modified notes
lg show             Note details
lg stats            Index statistics
lg cover            Generate cover art (Gemini/Grok)
lg cover-migrate    Rename covers to slug format
lg cover-thumbnails Generate missing OG thumbnails
lg animate          Generate looping video from cover (Grok Aurora)
lg tts              Generate TTS narration (Grok TTS)
lg rename           Rename note + update all references
lg index            Trigger full re-scan
lg serve            Start daemon (scan + watch + blog + NATS)
lg history          Git history for a note
lg drift            Show uncommitted changes
lg push             Push ~/Notes/ to remote git repo
lg nats-info        Show NATS configuration
lg nats-certs       Generate mTLS certificates
lg config           Show configuration
lg completion       Shell autocompletion
lg help             Help
lg version          Version

Twenty-eight subcommands in a “notes indexer” is either feature creep or Zawinski’s Law — every program attempts to expand until it can read mail. lg has not yet achieved mail. lg has achieved blog, wiki, cover art generation, video animation, text-to-speech narration, NATS-based sync, SFTP media deployment, git version control, and a notes browser, which is close enough that the Squirrel has proposed adding mail “for completeness.”

The proposal was declined. For now.

The Inevitable Future (Updated)

Zawinski’s Law states that every program attempts to expand until it can read mail. lg skipped mail entirely. It learned to talk instead.

On March 15, 2026, lg grew a chat bar. Not a subcommand — a handler. POST /journal/chat spawns a Claude Code subprocess, pipes the user’s message, and streams back rendered markdown through a frosted glass HUD. The journal page has no edit button. The only way to modify notes through the web interface is to talk to the second brain.

This suggests a new law, complementary to Zawinski’s:

The Chatbot Corollary: Every program in the age of LLMs attempts to expand until it can hold a conversation. Those programs which cannot so expand are replaced by ones which can.

Zawinski observed that programs converge on email — the universal communication tool of the 1990s. The Chatbot Corollary observes that programs now converge on conversation — the universal interface of the 2020s. The notes indexer didn’t need an email client. It needed a mouth.

The trajectory predicted in the original version of this section has been partially superseded:

An editor screen. Leapfrogged. lg skipped the editor entirely. The predicted path was contenteditable + WebSocket. The actual path was a chat bar and a Claude subprocess. The editor was the wrong metaphor. You don’t edit your second brain with a cursor. You talk to it. The conversation is the editor.

A task manager. Still valid — lg tasks parses tasks, and the chat agent can already check them off (“picked up the milk”) or create new ones. The task manager is emerging from the conversation, not from a dedicated UI.

An Obsidian companion. Reframed. lg is not replacing Obsidian — it’s the other half. Both watch the same ~/Notes/ folder. Edit a file in Obsidian; lg’s fsnotify watcher picks it up, re-indexes, publishes via NATS, commits via git. Ask the chat bar to create a note; Obsidian sees the new file instantly. They share the filesystem the way two lenses share a landscape — Obsidian is the editor, lg is the publisher and the voice. No integration needed. No plugin. No API. Just two programs watching the same folder of markdown files, which is the UNIX philosophy working exactly as intended for once.

A Claude Code orchestrator. Achieved in miniature. The journal chat agent is a Claude Code orchestrator — it spawns claude as a subprocess with access to lg, Read, Write, and Edit. It can search notes, create yagnipedia entries, check off tasks, and file episodes. The orchestrator isn’t a future feature. It’s the chat bar.

And then, finally, an email client. This prediction is hereby withdrawn. lg will never read mail. lg will talk about mail — “what did the dentist’s office email say?” — because the chat agent has access to whatever tools Claude has access to. The email client was the wrong destination. The chatbot is the destination. Zawinski was right about convergence but wrong about the attractor. The attractor is no longer mail. The attractor is conversation.

The Squirrel has proposed that lg should read mail anyway, “for completeness.” The proposal has been declined. The Lizard approves.

The Paradoxes

lg embodies several paradoxes documented elsewhere in this encyclopedia:

The Gall’s Law Paradox: lg is a complex system (9,571 lines, 25 subcommands) that works because it evolved from a simple system (4,196 lines, 6 commits) that worked. If anyone had designed the current lg on day one — blog server, wiki engine, cover generator, content sync, remote deployment — the project would have failed. Instead, each capability was added when it was needed, tested when it was built, and deployed when it worked. The Squirrel proposed the complete system on day one. The Lizard built the indexer. Everything else followed.

The Ship of Theseus Paradox: lg’s original 4,196 lines have been modified, extended, partially rewritten to 13,825 lines, and then shrunk to 9,571 lines by replacing HTTP sync with NATS. The peak was not the destination. Is it the same tool? The answer is yes, because the file main.go has the same go mod init and the same philosophy: files are truth, everything else is projection. The planks have changed. Some planks were removed. The ship is the same. The course is the same.

The Lens Paradox: lg adds no information. Every search result, every backlink, every frontmatter query — all of this existed in the files before lg indexed them. lg does not create knowledge. lg makes existing knowledge findable. This is the paradox of all indexers: they are essential and they contribute nothing. The lens is not the landscape. But without the lens, the landscape is invisible.

The Recursion Paradox: lg indexes ~/Notes/yagnipedia/lg.md — this file. This file describes lg. lg parses this file’s frontmatter, indexes its content for FTS5, extracts its wiki-links, and serves it as a Yagnipedia article at /wiki/lg. The tool serves a description of itself. The description is accurate. The description will be indexed the moment it is saved. The loop has no base case.

The Deployment

lg is deployed by deploy.sh — cross-compile, scp, systemctl restart. The binary goes from a Linux desktop in Riga to a Hetzner server in Helsinki in under a minute. No Docker. No Kubernetes. No CI/CD pipeline. No staging environment.

Content sync is no longer a deployment step. The local lg and the cloud lg are connected by NATS leaf node — a persistent, encrypted, bidirectional connection. Edit a file locally; it appears on the cloud server’s index within seconds. No sync command. The river flows.

Media — covers, videos, audio — take a different path. When lg cover, lg animate, or lg tts generates a file, it uploads immediately to the server via SFTP. The binary reads ~/.ssh/id_rsa, connects to the configured remote, and writes the file. No NATS for binary blobs (that was tried, and removed on March 17). No rsync at deploy time. Each generation is its own deployment.

The server costs €109/month and runs at 0.01 load average, which means 99.99% of the Ryzen 9 7950X3D is idle — idle while simultaneously hosting the lifelog, the Yagnipedia, an embedded NATS server accepting leaf node connections on port 7422 with mutual TLS, and the demo instance of the very product whose construction the lifelog documents and lg indexes and publishes with AI-generated covers. The server is bored, which is the highest compliment in the Boring Technology tradition.

The Trust

lg’s deployment reveals something deeper than architecture: trust.

The Hetzner server was set up by Claude. Not “Claude generated a script that riclib reviewed and executed.” Claude. Directly. riclib did one thing:

ssh-copy-id [email protected]

Then said: “Install lg and Solid. Harden the server.”

Claude did the rest. Not because Claude had documentation — because Claude had read the lifelog. Claude knew how riclib likes his servers installed the way a colleague knows how you take your coffee: not from a specification, but from observation. The firewall rules. The systemd units. The Cloudflare tunnel configuration. The directory structure. The log rotation. All of it inferred from 114 episodes of a developer documenting how he builds things, what he values, and what he refuses to tolerate.

This is the ultimate recursion: the lifelog documents how riclib builds. Claude reads the lifelog. Claude builds the way riclib builds. The documentation became the training data. The mythology became the specification. The stories about how the server should work became the instructions for how the server does work.

The Squirrel would call this “Mythology-Driven Infrastructure.” The Lizard would call it trust. Both are correct.

The Laws It Obeys

The Laws It Violates

Measured Characteristics

Addendum: The Pocket (March 27, 2026)

On March 27, 2026 — twenty-four days after a developer built a search tool for his notes folder — the notes indexer got an iPhone app.

Not a web wrapper. Not a PWA. A native Swift app with SSE streaming, optimistic task updates, and a chat interface that talks to a Claude subprocess running on a Mac Mini in Riga through a delegate-based URLSession because Apple’s bytes(for:) API buffers Server-Sent Events instead of streaming them, which is the kind of sentence that should not exist in the README of a notes indexer.

The iPhone app was needed because the task system was needed because the daily notes had tasks in them because the developer writes tasks in daily notes because the notes are the source of truth because the files are the truth because the lens is not the landscape. The entire mobile platform was derived from the premise that - [ ] Buy groceries should have a due date.

This is Zawinski’s Law entering its final form. The original law states that every program attempts to expand until it can read mail. The Chatbot Corollary (March 15) observed that programs now converge on conversation. The Pocket Amendment observes the terminal stage:

The Pocket Amendment to Zawinski’s Law: Every program that has expanded until it can hold a conversation will continue to expand until it fits in a pocket. At that point, the program has become an operating system. The developer will not notice.

lg’s trajectory:

  1. Notes indexer (March 3)
  2. Blog engine (March 5)
  3. Publishing platform (March 7)
  4. Sync engine (March 14)
  5. Second brain (March 15)
  6. Mobile app (March 27)

The article claimed 28 subcommands. There are now 32. The article claimed 20,305 lines of Go. There are now 44,284 lines of Go and — this is the part the Squirrel has been waiting to hear — 28,989 lines of Swift. The notes indexer is now 73,273 lines across two languages and two platforms. It has four task states, three date types per task, smart date bucketing (“Last Week”, “Now”, “Next Week”), an action dialog for rescheduling, and optimistic UI updates. For a tool that indexes a folder of text files.

The Squirrel has not proposed anything. The Squirrel is staring at the commit log with an expression that suggests vindication, awe, and a faint concern that the notes indexer will achieve sentience before anyone proposes Kubernetes.

Updated Measurements (March 27)

See Also