Inspiration

There is one photograph of my grandparents together — the only one. For seventy years, nobody in the family could agree on when it was taken. It passed through four cloud accounts: undated, duplicated, buried among tens of thousands of screenshots and exports. Every family owns a photograph like this.

Trying to fix it forced an uncomfortable realization: I trusted a decade-old SaaS account more than my own ability to preserve and understand my digital life. Photos lived in several libraries. Documents were scattered across disks, exports, mailboxes, and cloud drives. Contacts existed in incompatible forms. The context that made any of it meaningful — dates, places, conversations, relationships — was trapped in filenames and folder structures. Exporting the bytes didn't solve that; it just moved the disorder somewhere else.

And every migration felt like a one-way door. A date could be mangled, a duplicate could slip through, a deletion could propagate silently into every backup. The problem was never only lock-in. It was having no reliable explanation of what happened, and no honest way back.

Self-hosted tools already solve individual domains well. What was missing was the trustworthy layer underneath them: a system for the messy personal past, where automation helps without hiding its decisions.

What it does

NoCloud is a local-first platform that brings personal archives, data, services, and apps together on hardware the user owns — and keeps its automated decisions explainable and reversible. Deletion, the one act with no honest inverse, is gated instead.

One sentence is the whole contract: the AI proposes; the owner decides.

In practice: point NoCloud at a lifetime of exports — photo libraries, mailboxes, chat backups, bank statements. It indexes everything into one local database, proposes dates, duplicates, people, and links with the evidence attached, and stages anything uncertain in a review queue. The owner approves or rejects in batches; every applied change is journaled and reversible.

The product experience is one connected loop:

Home → Journey → Apps → Review → Provenance → Store → back to Home

Home is a calm command center rather than a wall of infrastructure: one meaningful next action, compact app spaces, optional archive activity, and a small trust strip linking private access, local ownership, and explain/undo — without overwhelming the first screen.

Whole-archive search lives in the persistent navigation and is always one keystroke away (/ or Ctrl/Cmd+K). It reaches across seven domains — finance, chats, people, notes, photos, home data, and mail — so nobody has to remember which app owns a fact.

Journey guides setup and maintenance one useful step at a time. Apps provide focused workspaces for photos, files, mail, people, finance, and health. Provenance explains where a record came from, what changed it, and which operation run produced it. Uncertain conclusions wait in review rather than being silently committed.

The Store adds local importers, viewers, automations, and connectors — extensions share the same identity, data contracts, and operation history instead of becoming separate silos.

Nothing is uploaded to a hosted NoCloud account. Most of the portal is generated locally as static HTML and JSON; a small home server can share it across the user's devices behind WireGuard and Authelia single sign-on. Even the public demo runs on a synthetic archive — the real one never leaves home.

How we built it

NoCloud is split across three deliberate repository boundaries:

  • nocloud-core — CLI, SQLite schema, generators, ingest boundary, deployment, portal shell, Desktop app, Agent, and shared contracts.
  • nocloud-store — optional apps and source-specific import connectors.
  • nocloud-private — each installation's configuration, credentials, generated output, and personal data.

Built with OpenAI tools. Codex and GPT-5.6 were my engineering pair throughout. They traced complete flows through the shell, generators, SQLite, APIs, deployment, and service-worker caching; turned product principles into sequenced implementation tasks; implemented the navigation, accessibility, and progressive-disclosure work; surfaced hidden integration failures; and extended the test suite. ChatGPT stress-tested the product story itself. One representative catch: a visual module existed in source but was neither deployed to the home server nor cached by the PWA — and the fix didn't stop at CSS. Deployment, nginx mount verification, offline caching, and regression coverage were corrected together.

The hardest architectural line is ownership. The workstation owns the authoritative SQLite database and archive; the home server serves a replica and is never allowed to pretend it owns writes — a mutation applied only to a replica can look successful and then vanish on the next replication cycle. Server-originated changes enter a durable command queue instead:

  1. Any device sends the edit to the home server.
  2. The server enqueues it — status pending.
  3. The workstation drains the queue on its own schedule, applies the change to SQLite, and appends it to the journal.
  4. The updated portal replicates back.
  5. The device sees an honest complete (or rejected).

The curation model deliberately has no generic EDIT. Changes are expressed through explicit primitives — COPY, MOVE, ANNOTATE, RECORD, LINK, DELETE — and for every recorded reversible primitive p, the append-only journal stores enough to apply its inverse under the recorded preconditions:

p⁻¹(p(s)) = s

Each journal entry records the previous state, the resulting state, and the rationale for the change — the journal is an explanation, not just an undo buffer. DELETE is intentionally separate: once bytes are gone there is no honest inverse, so deletion is gated and audited. Renaming photos, correcting dates, or filing documents becomes a run of primitives; revert <op_run> walks a run backward and applies the recorded inverses in reverse order.

The intelligence layer follows the same principle — it would rather ask than invent. Evidence (EXIF, filenames, documents, timestamps, mail, messages, local indexes) feeds rules that propose facts and classifiers that suggest groupings. Constraints then decide: impossible → rejected; confident → recorded with provenance; uncertain → the review queue, where the owner approves or rejects. Every recorded fact lands in the append-only journal, revertible by run.

Where a rule needs to be taught rather than programmed, a compact local model (Qwen 2.5 through Ollama) turns a plain-English sentence into a constrained, testable rule — which then waits for the owner's approval like everything else. Nothing a model says is ever committed silently.

Store connectors live under the same discipline: a connector parses an export into declared row shapes, but Core validates permissions, performs the write, journals the batch, and reports what was imported or skipped. Connectors never get arbitrary SQL access.

Most pages are static-first; small live services exist only where live state is genuinely needed (settings, search, imports, queued mutations). The browser portal is the source of truth — the PWA, the Tauri Desktop app, and the menu-bar Agent connect to the same product contracts rather than duplicating domain logic. One contract set, four clients.

Challenges we ran into

The hardest challenge was separating display, write access, and ownership across two machines. I spent real time chasing what looked like data corruption before realizing replication was behaving correctly — the wrong machine had simply been allowed to pretend it owned a write. The durable queue fixed the architecture, and it also changed the product: users now see honest states (pending, running, complete, rejected) instead of an optimistic "saved" that might later vanish.

The second challenge was deletion that happens outside NoCloud. An ordinary sync pipeline treats a manual rm -rf as a legitimate change, and the next rsync --delete faithfully copies the mistake into every backup. NoCloud compares the live archive against a known-good index, groups missing files into one reviewable decision, and protects them from backup rotation until a person restores or confirms them.

The third was the interface itself. A local-first product slides easily into feeling like a control panel for infrastructure. The pieces were powerful, but a first-time visitor shouldn't have to reverse-engineer how Home, Journey, apps, Store, and Provenance fit together. I reorganized the experience around one next action, persistent whole-archive search, compact trust paths, and progressive disclosure of the dense parts.

Carrying that across desktop, mobile, and PWA without forking the UI meant drawing careful boundaries: shell behavior isolated from the translation (i18n) runtime, deployed to both entry points, versioned by the service worker. Motion stays restrained, keyboard focus is explicit, and prefers-reduced-motion is respected.

Accomplishments that we're proud of

  • Undo is an equation, not a button: every reversible primitive records its inverse (p⁻¹(p(s)) = s).
  • The workstation/server ownership boundary is explicit and protected by a durable queue with honest states.
  • Drift detection catches the failure mode that ordinary synchronization faithfully propagates.
  • Provenance, review, and recovery are first-class product surfaces, not hidden logs.
  • Whole-archive search makes seven data domains feel like one personal system, and Home greets the owner with a single next action.
  • Store extensions declare permissions and data contracts before they can touch anything.
  • One portal and one contract set serve four clients — browser, PWA, Desktop, and menu-bar Agent.
  • The demo runs entirely on a synthetic archive, documented so a reviewer can understand the whole loop in minutes — no personal data involved.

What we learned

Trust lives in the supposedly boring parts: replication, queues, backup rotation, drift detection, recovery, and honest status.

A personal archive is more than files. Its value is context — dates, places, conversations, identities, relationships, and the reason an item ended up where it did. Apps become more useful when they feed one shared local data model instead of building isolated databases.

The interface should not show everything the system knows. It should show the smallest useful truth, then a clear path to the evidence: Home says what matters, Journey explains the next safe step, apps let you act, review slows uncertain changes down, Provenance explains and undoes.

And automation and control are not opposites. Good automation proposes, explains, and waits when it is uncertain.

What's next for NoCloud

Broaden the ingest layer — more cloud exports, mail archives, documents, financial sources, and photo libraries — with every new connector held inside the same permission and provenance contracts.

Deepen the proof: more round-trip tests for each reversible primitive, more failure injection around the mutation queue and replication, and clearer evidence that a reverted run restores exactly the expected archive state.

Keep moving toward ordinary home users: simpler installation, clearer grouped decisions, better evidence views, stronger local AI-assisted matching that stays private, inspectable, and reviewable — and evaluating OpenAI's open-weight gpt-oss models as the local reasoning engine on machines with the memory to run them.

The long-term goal is not to replace every self-hosted application. It is to be the trustworthy layer underneath them — the place where a photograph that spent seventy years undated finally gets its evidence-backed date, and keeps its story.

Built With

Share this project:

Updates