tomo docs
home github

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.

your laptop gpu-box (Linux) your files watcher engine .tomo history store transport peer files watcher engine .tomo history store serve SSH one connection no open ports

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:

  1. 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).
  2. 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".
  3. 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.
  4. 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.
  5. 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.
Latency comes first. The live path always ships the newest bytes right away. Only history capture ever debounces, and even then the last state of a burst is always versioned. More on adaptive capture →

The bootstrap story #

There is nothing to install on the server. When you run tomo sync you@host:/path, Tomo:

  1. Opens the SSH session and detects the remote OS and architecture with uname.
  2. Looks for a matching tomo-<version>-<triple> already in the remote's .tomo/bin/. If the version matches exactly, it reuses it.
  3. Otherwise it pushes the right embedded binary over SFTP, chmod +x, and verifies its SHA-256 after the copy.
  4. 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.

sync session ctl.sock events + commands TUI tomo attach tomo events --json

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.

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 →

Want the real thing? Two pages go under the hood: the engine (vector clocks, multi-value registers, deterministic convergence) and history & transfer (content-addressed storage, adaptive capture, chunked transfer, crash safety).