Local-first apps over a store you own.
An app's whole data set is one CRDT document on your machine, complete enough to work with the network off. Sign in on a second device and the two converge. No server holds the only copy, and no app owns your storage.
Honeycrisp, a local-first notes app, is the app built on it today.
Run the apps freely under AGPL-3.0; build on the developer toolkit freely under MIT. What that means.
The Store | Toolkit | Status | Trust | Repo Map | Development | License
The hard problem with local-first apps is synchronization. If each device has its own SQLite file, how do you keep them in sync?
Epicenter's answer: an application is one Yjs document, replayed in full before any handle exists, and the surface over it is synchronous. A read is a property access, not a round trip, so nothing is awaited and nothing needs cache invalidation or race protection.
import { openDevice } from '@epicenter/data/browser';
import { defineTable, defineWorkspace } from '@epicenter/workspace';
const notes = defineTable({
title: 'string',
pinned: 'boolean',
folderId: 'string|null = null',
});
const notesWorkspace = defineWorkspace({
namespace: 'com.example.notes',
tables: { notes },
});
// Every verb returns a Result; the error arm is elided here for length.
const { data: db } = await openDevice(notesWorkspace); // the only await
db.tables.notes.create({ title: 'Hello', pinned: false }, { document: ['body'] });
const listed = db.tables.notes.list(); // synchronous: { rows, nonconforming }
const stop = db.tables.notes.subscribe(read); // fires with the row ids a commit touchedA workspace is one application's declaration of its durable data: pure JSON, arktype expression strings, no storage and no lifecycle of its own. It is release-local and never migrates your data. A row it cannot read is reported beside the rows it can, with the reason and the raw values intact, and an ordinary write repairs it.
Prose merges per character: a row is allocated a document container when it is
created, and db.tables.notes.document(id)?.get('body') hands an editor a live type to
bind to. Epicenter never looks inside it.
Sync is one Cloudflare Durable Object per (account, application). Being signed in on two devices is the entire sharing model: nothing is paired, invited, or approved.
Read the data package docs | What it replaced, and why
The developer toolkit is MIT: build anything on it, including closed-source and commercial products, and you own what you build, with no obligation back to Epicenter. @epicenter/data, @epicenter/workspace, and @epicenter/ui are the packages meant to leave this repo. They are pre-1.0 and tuned for our own apps, so treat them as fork-and-own rather than a stability-guaranteed SDK for now.
There is one runtime: a desktop SPA in a WebView, over a store the client owns. A host serves bundles and brokers credentials; it owns no application data. A hosted web runtime with a host-owned replica is refused, and so are third-party installed apps, for now.
Honeycrisp is the app running on the store, and its README is the worked example.
Whispering, vocab, skills, and the Epicenter host do not compile right now. The superseded data stack was deleted before they were migrated, deliberately: the new store has no row-document HTTP path and no multi-process observation carrier, so there was nowhere for them to move until those refusals landed. Data they held on the old stack is accepted as lost; there is no importer.
Matter edits user-owned Markdown folders directly and keeps a
disposable matter.sqlite query mirror beside them. Local
Books and Local Mail are headless mirrors
that pull a hosted account into local SQLite. Those three do not use the store.
Pick the trust model you want.
| Path | What leaves your device |
|---|---|
| Signed out | Nothing. The store is complete on the machine it opened on, and every read comes from a document already in memory. |
| Signed in | Your application's document, as opaque update bytes, to one authority per account. |
| Hosted Epicenter | That authority is ours, along with account and session data and any hosted feature you enable. |
| Self-hosted instance | You control the server, secrets, deployment, and infrastructure boundary. |
| A provider an app calls | Whatever that app sends it: transcript text to an LLM, audio to a transcription provider. Epicenter servers are not in that path. |
Signed-in sync sends your data to a trusted server that reads it in plaintext. On hosted Epicenter the authority is ours, so that data sits inside our trust boundary; self-hosting puts it on infrastructure you control, so Epicenter never holds it. See the trust model for the details, including where this is heading with the anchor.
| App | Status | Notes |
|---|---|---|
| Honeycrisp | Runs on the store | Local-first notes. Folders and notes are rows; a note's prose is a rich-text type inside its row. |
| Matter | Runs, separately | Typed grid over user-owned Markdown folders. It edits ordinary .md files directly; matter.sqlite is a disposable query mirror. |
| Local Books, Local Mail | Run, separately | Headless CLI mirrors that pull QuickBooks and Gmail into local SQLite. |
| API | Hosted infrastructure | Personal cloud Worker. Owns the store authority binding, hosted-only billing, and the dashboard. |
| Self-host | Reference deployable | Community-supported single-partition instance without hosted billing. |
| Whispering, vocab, skills, Epicenter | Do not compile | Awaiting migration onto the store. See Status. |
| Other app folders | Research and prototypes | Useful history and experiments, not the current product lineup. |
These packages carry the main architecture.
| Package | Role | License |
|---|---|---|
@epicenter/data |
The store: one Yjs document per application, a synchronous surface over it, and the transport that carries it. | MIT |
@epicenter/workspace |
The inert workspace declaration vocabulary: arktype JSON, row addresses, and nonconformance. | MIT |
@epicenter/sqlite |
Neutral embedded-SQLite driver with Browser, Bun, and Durable Object adapters. It owns no product schema. | MIT |
@epicenter/sync |
The WebSocket subprotocol vocabulary both halves of a handshake must agree on. | MIT |
@epicenter/ui |
Shared Svelte component library used by multiple apps. | MIT |
@epicenter/server |
Shared Hono server library composed by the hosted API and the self-host reference deployable. | AGPL-3.0-or-later |
The server side is split into one shared library and two deployable folders:
packages/server
shared Hono library
route composition for auth, sessions, store sync, blobs,
and provider-backed inference and transcription
apps/api
hosted personal Cloudflare Worker
composes packages/server with a Better Auth principal resolver
owns hosted-only dashboard and billing code
apps/self-host
self-hosted single-partition instance reference deployable
composes packages/server with the instance principal resolver
community-supported
no hosted billing surfaceFull architecture walkthrough | Trust model
Use Bun in this repo.
git clone https://github.com/EpicenterHQ/epicenter.git
cd epicenter
bun installEvery app starts from the repo root. bun dev:<app> runs every process the app needs; for apps that talk to the hosted API, that includes the API worker on localhost:8787. bun dev:<app>:ui runs the app's frontend alone when that split exists, and bun dev:api runs just the backend. Bare bun dev is bun dev:honeycrisp, and bun run with no arguments lists every target.
| Command | Starts | App port |
|---|---|---|
bun dev:honeycrisp |
API + Honeycrisp desktop | 5175 |
bun dev:honeycrisp:ui |
Honeycrisp in the browser, no API or Tauri shell | 5175 |
bun dev:api |
Hosted API worker alone | 8787 |
bun dev:api-dashboard |
API + dashboard UI | 5178 |
bun dev:landing |
Landing site, standalone | 4321 |
bun dev:matter |
Matter desktop, standalone | 5180 |
bun dev:posthog-reverse-proxy |
PostHog reverse proxy Worker | wrangler default |
bun dev:self-host |
Self-host server (needs INSTANCE_TOKEN) |
8787 |
bun dev:whispering, bun dev:vocab, bun dev:skills, and bun dev:epicenter still exist, but those apps do not compile until they are migrated onto the store.
The API needs local Postgres and Infisical; see apps/api/README.md. Rust is needed for Tauri apps such as Honeycrisp and Matter. Local Books and Local Mail run their own multi-process dev flows; their READMEs document them.
bun run check is the gate. It runs lint, typecheck, every workspace test, and the structural checks, and it is the same gate CI runs, so a green local run predicts a green pull request. Formatting is handled separately by the autofix workflow.
bun run checkRun the pieces on their own while you work:
bun run format # rewrite formatting (CI autofixes this for you)
bun run lint:check
bun run typecheck
bun run test
bun run check:structure # doc paths, catalog pins, API paths, licenses, UI boundary, boot purityTwo checks sit outside the gate on purpose. bun run check:doc-hygiene flags specs and ADRs that time has made stale, so it belongs to review rather than to merge. bun run smoke:local boots the API against local services.
Durable decisions and their reasoning live in docs/adr/. Specs in specs/ are in-flight design scaffolding rather than current truth; when a spec and an ADR disagree, the ADR wins. Start with docs/README.md.
Contributions are welcome. Good entry points are docs, local-first infrastructure, Svelte interfaces, migrating a broken app onto the store, and small changes that make the repo easier to understand.
Contributors coordinate in Discord.
Epicenter uses a two-tier split by how you use the code:
- MIT for code you build with: the toolkit roots (
@epicenter/data,@epicenter/workspace,@epicenter/ui) and the toolkit-internal contracts they carry (@epicenter/field,@epicenter/sqlite,@epicenter/sync,@epicenter/identity,@epicenter/agent-protocol,@epicenter/chat). - AGPL-3.0 or later for code we ship or run: every app, the shared server library, and the rest of the internal packages.
- There is no proprietary tier today. Revenue is intended to come from hosting and services, not from selling closed licenses.
Every dependency of the toolkit packages is MIT-compatible, enforced by bun run check:licenses. The license split follows the same broad pattern as Plausible and PostHog for hosted open-source services, and Yjs for MIT core libraries with copyleft server pieces.
See the root LICENSE, FINANCIAL_SUSTAINABILITY.md, and the licensing strategy for the full model.
Contact: github@bradenwong.com | Discord | @braden_wong_
Your data outlives the app that wrote it. Local-first, open source, built on Yjs.