Local-first sync engine for apps that work offline, store data on-device, and replicate directly between trusted peers — no central backend required.
ACORDE is a Go library and CLI that gives your app conflict-tolerant, peer-to-peer sync without a server. It combines:
- SQLite — durable, on-device storage
- CRDTs — automatic conflict resolution via Last-Write-Wins registers and OR-Sets
- libp2p — peer-to-peer transport with mDNS discovery or explicit pairing
- Encrypted vaults — optional at-rest encryption per data directory
- Per-entry ACLs — ownership and writer permissions baked into the data model
The device is the source of truth. Sync is a replication problem, not a request/response dependency. Conflicts are merged, not rejected.
- Offline-capable note or log apps
- Local-network peer-to-peer tools
- Private, user-owned data stores
- Prototypes that need sync semantics without standing up a backend
go install github.com/amaydixit11/acorde/cmd/acorde@latestOr build from source:
git clone https://github.com/amaydixit11/acorde.git
cd acorde
go build -o acorde ./cmd/acordeOr run with Docker:
docker compose upRequirements: Go 1.25+, CGO enabled (for SQLite). See docs/setup.md for platform-specific notes.
# Start the daemon (local engine + p2p sync + REST API)
acorde daemon --data /tmp/acorde-a --port 4001 --api-port 7331
# In another terminal
acorde add --data /tmp/acorde-a --type note --content "hello"
acorde list --data /tmp/acorde-a
curl -s http://localhost:7331/entries# Terminal 1
acorde daemon --data /tmp/acorde-a --port 4001 --api-port 7331
# Terminal 2
acorde daemon --data /tmp/acorde-b --port 4002 --api-port 7332
# Terminal 3 — write on A, verify it reached B
acorde add --data /tmp/acorde-a --type note --content "hello from A"
sqlite3 /tmp/acorde-b/acorde.db \
'select id,type,updated_at,deleted from entries order by id;'Both nodes discover each other automatically via mDNS on the same LAN.
No sync, no daemon — just a local SQLite-backed store.
acorde add --data ./data --type note --content "draft"
acorde list --data ./data
acorde get --data ./data <entry-id>
acorde update --data ./data <entry-id> --content "revised"
acorde delete --data ./data <entry-id>Run a single process that manages local state, sync, and an HTTP API together.
acorde daemon --data ./data --port 4001 --api-port 7331This starts the local engine, the p2p sync service, and the REST API at http://localhost:7331.
For cross-network pairing or when you want explicit trust without broadcast discovery.
# Device A — generate an invite link
acorde daemon --data /tmp/a --port 4021 --api-port 7351 --mdns=false
acorde invite --data /tmp/a
# prints: acorde://...
# Device B — pair using the link, then start daemon
acorde pair --data /tmp/b 'acorde://...'
acorde daemon --data /tmp/b --port 4022 --api-port 7352 --mdns=falseacorde init --data /tmp/enc-a # initializes an encrypted vault
acorde add --data /tmp/enc-a --type note --content "secret"
acorde get --data /tmp/enc-a <entry-id>| Command | Description |
|---|---|
acorde daemon |
Start the sync daemon and REST API |
acorde add |
Create a new entry |
acorde list |
List readable entries |
acorde get <id> |
Get a single entry |
acorde update <id> |
Update content or tags |
acorde delete <id> |
Tombstone an entry (replicates to peers) |
acorde authorize <id> <peer-id> |
Grant write access to a peer |
acorde invite |
Generate a pairing invite link |
acorde pair <link> |
Accept an invite and add a trusted peer |
acorde init |
Initialize an encrypted vault |
Common flags:
| Flag | Description |
|---|---|
--data <path> |
Data directory (required) |
--port <n> |
p2p listen port (default: 4001) |
--api-port <n> |
REST API port |
--mdns=false |
Disable mDNS peer discovery |
--name <name> |
Human-readable node name |
Start the daemon with --api-port to enable the HTTP API.
| Method | Endpoint | Description |
|---|---|---|
GET |
/entries |
List readable entries |
POST |
/entries |
Create an entry |
GET |
/entries/:id |
Get a single entry |
PUT |
/entries/:id |
Update an entry |
DELETE |
/entries/:id |
Tombstone an entry |
POST |
/entries/:id/authorize |
Grant write access to a peer |
GET |
/status |
Node status |
GET |
/events |
Server-sent event stream |
# Create
curl -s -X POST http://localhost:7331/entries \
-H 'Content-Type: application/json' \
-d '{"type":"note","content":"hello","tags":["demo"]}'
# List
curl -s http://localhost:7331/entries
# Stream live events
curl -s http://localhost:7331/eventsACORDE's CRDT layer ensures all peers converge to the same state regardless of the order operations arrive.
- Entries use a Last-Write-Wins (LWW) register — the update with the highest logical timestamp wins on merge.
- Tags use an Observed-Remove Set (OR-Set) — concurrent adds from different peers are preserved; removes only remove what a peer has seen.
- Deletes are tombstones that replicate and are never silently lost.
- Private entries sync to peers as opaque records; they won't appear in
list,get, or/entrieson a peer that hasn't been authorized. - ACLs are part of the CRDT state and replicate along with entries.
For a deeper explanation, see docs/WHY_CRDT.md and docs/ARCHITECTURE.md.
ACORDE exposes a Go package so you can embed the sync engine directly instead of shelling out to the CLI.
import "github.com/amaydixit11/acorde/internal/engine"
e, err := engine.New(engine.Config{
DataDir: "./data",
})
if err != nil {
log.Fatal(err)
}
defer e.Close()
entry, err := e.AddEntry(engine.AddEntryInput{
Type: core.Note,
Content: []byte("hello from Go"),
Tags: []string{"example"},
})See docs/API.md for the full engine interface and examples/ for working CLI and web examples.
# Build and run a single node
docker compose up
# Or build the image directly
docker build -t acorde .
docker run -p 4001:4001 -p 7331:7331 -v $(pwd)/data:/home/acorde/data acordeThe container exposes port 4001 for p2p (TCP/UDP) and 7331 for the REST API. mDNS is disabled by default in Docker; use explicit pairing for multi-container setups.
ACORDE runs on Android via Termux (install from F-Droid, not the Play Store).
pkg update && pkg upgrade
pkg install golang git
git clone https://github.com/amaydixit11/acorde.git
cd acorde/cmd/acorde
go build -o acorde .
./acorde daemon --data ~/acorde-data --port 4001 --api-port 7331See docs/ANDROID.md for troubleshooting Go version issues on newer Android releases and tips for keeping the daemon running in the background.
- New entries are private by default. Use
authorizeorPOST /entries/:id/authorizeto grant access to a peer. - Writers can also read the entries they're authorized on.
- CLI writes made against a vault while its daemon is running are picked up and synced automatically.
- Tombstoned entries replicate to all peers; deletion is not silent.
| Doc | Description |
|---|---|
| docs/setup.md | Installation and platform setup |
| docs/API.md | REST API and Go engine reference |
| docs/ARCHITECTURE.md | System design overview |
| docs/DEVELOPER_GUIDE.md | Contributing and dev workflow |
| docs/WHY_CRDT.md | Why CRDTs and which ones |
| docs/WHY_NO_SERVER.md | Design rationale |
| docs/FEATURES.md | Feature notes and status |
| docs/SECURITY.md | Security model |
| docs/ANDROID.md | Running on Android via Termux |
| ROADMAP.md | Planned features |
| CONTRIBUTING.md | Contribution guide |