Beads vs WikiHub Architecture — Source-of-Truth Comparison

@jacobreal document public Updated 2026-04-10

Beads (Steve Yegge) vs WikiHub — Source-of-Truth Comparison

Date: 2026-04-10
Status: Decided
Applies to: WikiHub, ListHub (informational)

Context

Both Beads and WikiHub use the same three-layer pattern — a fast queryable store, a text-serialized git-trackable export, and git hooks/plumbing to keep them in sync. But they flip the source-of-truth direction. Understanding why helps explain WikiHub's architecture to anyone coming from the Beads world (which includes us — ListHub uses Beads for issue tracking).

The shared pattern

  1. A fast queryable store (SQLite / Postgres)
  2. A text-serialized git-trackable export (JSONL / markdown files)
  3. Git hooks or plumbing to keep them in sync

The inversion

Beads WikiHub
Source of truth SQLite Git bare repos
Derived/export JSONL (for git tracking) Postgres (metadata + search index)
Why this direction Issue tracker fields are structured data — queries need to be fast, nobody hand-edits JSONL Wiki pages are authored markdown — users expect to git clone, edit offline, push back
Bidirectionality Fully symmetric — either layer can rebuild the other Asymmetric by design — private content lives only in Postgres, never enters git
Content duplication Yes — same data in both SQLite and JSONL No — public content in git only, private content in Postgres only, never both

Why WikiHub chose git-as-truth

The whole thesis of WikiHub vs Notion/Google Docs is that the wiki IS a git repo. Real commits, real history, real git clone. If Postgres were the truth and git just an export, you'd have a slightly fancier ListHub — the git repos would be ZIP downloads with extra steps.

Beads can afford SQLite-as-truth because nobody cares about the git history of an issue tracker. The JSONL export is for portability, not for humans to git log. WikiHub users — Karpathy-wiki people, Obsidian vault owners — absolutely care about git log, git blame, and "I can clone this and read it offline."

WikiHub's no-duplication model

WikiHub already solved the "should we store content in both places?" question:

  • Public pages: content in git ONLY. Postgres has metadata/search index. Reads via git cat-file blob.
  • Private pages: content in Postgres ONLY (private_content column). Never enters git.
  • Visibility change moves content between stores (not copies it).

This means Postgres is fully rebuildable from git for public content (wikihub reindex --all), but private content + social graph are non-rebuildable — they live only in Postgres. Both backups required.

What WikiHub steals from Beads

  • .wikihub/events.jsonl — JSONL-as-git-sync-layer for audit events (ACL changes, visibility flips, forks). Append-only, line-diffable, merge-friendly. The single most useful pattern from Beads.
  • Line-oriented formats under .wikihub/ — merge-friendly, git-diffable.
  • Hash-based IDs — already aligned via nanoid.
  • Daemon pattern — for concurrent CLI access (Beads has auto-start-daemon: true to avoid SQLite lock contention).

Open question: defer git push → Postgres sync to v2?

If nobody is pushing via git push in early days (everyone uses web editor), we could:
1. Keep Postgres → git (web writes sync to repo) ✅
2. Defer git → Postgres (post-receive hook path) to v2
3. git push returns an error: "Push via web editor for now"

This cuts half the sync complexity while keeping the "git is real" story for readers/cloners. Add the push path when someone actually needs it.

Consequences

  • WikiHub's write path is slightly more complex than Beads (git plumbing on every web edit)
  • WikiHub's read path for public content hits git (~3-5ms per git cat-file), not a DB query
  • WikiHub needs BOTH git backups AND Postgres dumps (asymmetric non-rebuildability)
  • The no-duplication model means no sync bugs for content — it's only in one place

Sources