Case study · Free & open-source desktop tool · 03 / 04

Who Am I

One timeline of what actually happened.

Who Am I is a private, locally-owned record of one person — life and career on a single timeline — that generates a résumé and a personal website on demand. The record is plain Markdown in a Git repo you hold; the app reads and writes those files but never owns the data.

Brutally accurate. My eyes only.

The Who Am I inbox in the dark appearance: 34 candidates staged from 5 sources and grouped by source, a GitHub win expanded with Promote, Reject, and Keep private actions, a frivolous-marked chore, and a bulk bar promoting 3 selected candidates into the record
Product
Local-first desktop application
Role
Product design, system architecture and engineering
Data
Markdown in a Git repo the owner holds
Stack
Tauri, Rust, SvelteKit, Svelte 5
Status
Working product; the record grows daily
Distribution
Free & open source

The founding problem

The year that almost disappeared.

The prompt for this project was a near-blank year: by the time a résumé was due, a year of work at a major employer had collapsed into a few scattered artifacts and faint memory. Most people lose their own story exactly this way—the evidence exists at the time, then quietly evaporates.

The tools that exist for this each solve one layer—brag documents, résumé builders, reputation monitors—on their server, for their slice of your life. None of them fuse the private journey, the professional record, and the generated outputs intoone record the owner physically holds.

That fusion, on those terms, is the entire product.

Capture the truth as it happens.

Two non-negotiable principles

From the original brief

Principle 01

Brutally accurate

The raw truth goes in the record: rough notes, partial memories, unflattering facts, dead ends. A self-flattering log is useless for reconstruction. Polishing happens only when generating a résumé or a site—never in the record itself.

Principle 02

My eyes only

The record is private and local-first—a set of files on the owner’s machine, spanning the professional record and the private life journey. Nothing leaves it unless explicitly exported; public artifacts are generated from it, never expose it wholesale.

The one rule

Text is canonical. The index is a throwaway.

The record is a normal Git repo of Markdown—profile.mdand one file per entry. The app maintains a SQLite index for fast views, but the index is a derived cache, never synced, never authoritative. If text and index disagree, the text wins. Delete the index anytime; it rebuilds with zero data loss.

The application and the record are deliberately separate repositories: app code on one side, a private life on the other. Sync is plain Git—explicit pull, commit, and push, with no engine to trust and no conflict ever auto-resolved.

Fig. R1 — Authority flows one way
entries/*.md  (canonical, yours, in Git)
      ↓ rebuild anytime
index.sqlite  (derived cache, disposable)
      ↓ render
journal · profile · résumé · site

Nothing enters the record without a human decision

Fig. R2 — The promote gate

  1. 01

    Ingest

    Adapters for GitHub, Shortcut, Gmail, LinkedIn, an old résumé, your own website, Claude Code and Warp history, and EEG focus sessions map provider data into normalized candidates.

  2. 02

    Inbox

    Every candidate lands in a review inbox — never the record. Items stage private-by-default; anything without a real date waits as needs-date rather than silently becoming "today."

  3. 03

    Triage

    A deterministic prefilter, plus an optional model pass, marks candidates keep or frivolous. It only sorts — it never deletes, hides, or auto-rejects.

  4. 04

    Promote

    You decide what becomes part of the record. Merge-at-promote can fold N candidates for the same ticket into one entry with full provenance — deterministic, preview first.

Importers are deliberately narrow: Gmail is metadata-only—subjects and snippets, never bodies—and the website importer fetches only the pages you name. Candidates stage private-by-default, flipped per row before promote.

AI on a leash

The model drafts. It never decides.

Three optional passes run through one provider seam—Ollama locally or Anthropic, the key in the OS keyring. With no provider configured the app says so instead of failing; every AI feature is opt-in, and none of them can write to the record directly.

Triage

An optional keep-or-frivolous signal on inbox candidates. Advisory only: it sorts the list and is stripped on promote.

Reduce

A map/reduce pass that rolls a noisy inbox into clean entry drafts — drafts you review, not entries it writes.

Wins

Drafts résumé-grade accomplishments from journal entries you pick. Every draft must cite the entries it came from; ungrounded ones are dropped before you ever see them.

Imported text is treated as data, never instructions— stored verbatim and fenced with an explicit do-not-follow guard, so an imported issue title can’t hijack the model.

The outputs

Polish is an output-stage concern.

From the raw record, the app generates a tailorablerésumé (Markdown, print-ready) and astandalone personal website to self-host—accepted wins nested under the role or project they belong to.

Private entries are excluded from every outward artifact automatically. The record stays brutal; only the outputs are dressed for company.

Technical execution

A core that has never met its own app shell.

Desktop shell
Tauri 2
Backend
Rust
Interface
SvelteKit and Svelte 5
Record
Markdown files in a Git repo the owner holds
Index
SQLite — derived, disposable, never synced
Identity
ULIDs with collision-proof filenames
Sync
Plain Git: pull, commit, push — explicit
LLM providers
Ollama (local) or Anthropic, opt-in
Cloud infrastructure
None

Architecture decisions

  • The core is framework-agnostic TypeScript — it never imports Tauri or the browser.
  • A single Platform interface is the only I/O seam, so the whole storage and ingest layer is unit-tested off-device.
  • Ingest mappers are pure and fixture-tested; the ingest layer never touches the record, secrets, or the index.
  • Merge-at-promote is deterministic — no model in the write path.
  • Secrets live in the OS keyring, never in the record or app config.
  • Imported text is data, never instructions: fenced, guarded, and stored verbatim.

What it deliberately does not do

Fig. R4 — Negative space

  • No custom sync engine or server — sync is plain Git.
  • The index never becomes authoritative; delete it anytime with zero data loss.
  • No auto-push and no auto-resolved Git conflicts.
  • No auto-promotion — every entry in the record is a human decision.
  • No "polishing" of the raw record; polish is an output-stage concern only.
  • Private entries never reach the résumé or the generated site.

Each refusal protects the same thing: a record the owner can still trust, and still read, in thirty years.

The integration is the product.

Every individual layer here exists as someone else’s better-funded product. What doesn’t exist anywhere else is the fusion—life and career, private and public, capture and output—on terms where the owner keeps the data.

Built narrow on purpose: plain text, one gate, one owner. The app can be replaced. The record can’t be taken.

One record. One owner. Outputs on demand.