How it works
What happens between hitting save and the file showing up on the other machine. The two internals pages go deeper.
The shape of it #
Tomo runs one process on each machine, joined by a single SSH connection. On each side a watcher turns filesystem events into clean change records, a pure engine decides what to do with them, a transport ships changes across the wire, and a history store keeps every version. The two engines never talk to each other to agree on anything; they just converge.
changes flow both ways over one SSH connection; each side keeps its own history
The sync loop #
When you save a file, here's the path those bytes take:
- The watcher notices. On Linux it's inotify, on macOS FSEvents. Both are messy — coalesced events, directory-granular notifications, editor "atomic saves" that look like delete-then-create. The watcher normalizes all of that into one clean change record: "this path was modified / removed", with a content signature (size, BLAKE3 hash, and the executable bit).
- The engine absorbs it. The change becomes an event fed to the pure engine, which updates its in-memory index and emits actions — "ship this change to the peer", "record this version in history".
- The transport ships it. Small files ride inline in a single frame; large ones are sent as content-defined chunks so a big transfer never blocks a quick one behind it. It all rides the one SSH connection.
- The peer applies it. The other engine absorbs the change, writes the bytes through a staging file, and atomically renames it into place. Its watcher would normally fire on that write — but Tomo journals every write it makes and swallows the echo, so the change doesn't bounce back.
- Both sides record history. Independently, each engine hands the new version to its history store. The result: two machines with the same tree, each holding the full past.
The bootstrap story #
There is nothing to install on the server. When you run tomo sync you@host:/path, Tomo:
- Opens the SSH session and detects the remote OS and architecture with
uname. - Looks for a matching
tomo-<version>-<triple>already in the remote's.tomo/bin/. If the version matches exactly, it reuses it. - Otherwise it pushes the right embedded binary over SFTP,
chmod +x, and verifies its SHA-256 after the copy. - Execs the remote binary and runs a version handshake as the very first message — any skew re-pushes before a single file is exchanged.
Every supported binary is embedded inside the release binary you installed (no external downloads, ever). Linux binaries are fully static musl builds, so there's no glibc-version roulette on an old server. Strictly user-space: no root, no daemon, no listening port.
One session, many clients #
The session on each machine isn't just a sync loop — it's a small server. Alongside its work it binds a local control socket at .tomo/state/ctl.sock (a unix-domain socket, inside .tomo/ so it's never watched or synced). Every interface is a client of it: the terminal UI renders the socket's event stream and issues commands over it, and so can your scripts. Nothing draws the tree or writes the history database from a render path — the socket is the one channel.
one session, many clients — the TUI holds no powers a script doesn't
The event stream carries structured versions of everything the session prints — connect/disconnect, synced/removed, conflicts, transfer progress, a periodic heartbeat — and the command channel exposes every action the CLI does, running the same functions with the same crash-safe write paths. That's what lets you detach and re-attach a session, watch it from three terminals at once, or feed tomo events --json straight into a script. The engine stays a pure state machine underneath; the control server is an adapter, not an engine concern.
What happens on a conflict #
Say you edit config.rs on your laptop while the server edits the same file — neither having seen the other's change yet. That's a genuine conflict. Tomo's rule: it never blocks, and it never loses your work.
- Both machines independently pick the same winner using a deterministic rule (no back-and-forth negotiation), so they converge to an identical tree.
- The losing version isn't thrown away — it's kept in history, alongside the winner and the evidence of the conflict.
- Sync keeps flowing the whole time. You find out non-blockingly: a badge in
tomo status, an entry intomo conflicts list. - To recover the other side,
tomo conflicts show <id>to see the diff, thentomo conflicts resolve <id> --take-loserto adopt it.
Which side "wins" is arbitrary but consistent; correctness comes from the guarantee that nothing is ever silently dropped. There's one special case worth knowing: the very first sync between two trees that already existed — a laptop and a fresh git clone, say — has no shared history to reason about, so Tomo adopts the copy you modified most recently rather than flipping a coin. How that works, and why it's safe →