Continuous, peer-to-peer sync of coding-agent session files across your own machines.
Work with an AI coding agent on your desktop, walk away, and resume the same session on your laptop — automatically, no manual commands, no server to run.
ssync watches where your agent stores its sessions, keeps them encrypted and in sync across
all your machines over a direct peer-to-peer connection (via iroh),
and writes incoming sessions back so the agent can --resume them anywhere.
Early. Supports the pi and omp agents (lossless merge) plus Claude Code and Codex (newest-wins by policy, DECISIONS §8), under active construction. See docs/DECISIONS.md for the design rationale.
- ✅ Syncs session files across your machines, peer-to-peer, automatically.
- ✅ Encrypted at rest by default (age).
- ✅ Self-converging: diverged append-only sessions are merged losslessly, and deletions propagate.
- ✅ No central server, no VPS, no relay you have to run. Internet sync uses iroh's free public discovery + hole-punching (which only ever sees ciphertext).
- ❌ Not a live "continue a running process on another machine" tool.
- ❌ Not an agent orchestrator or dashboard (see cctl, ccmanager, etc. for that).
Every machine runs the same daemon — there is no hub. Each daemon watches the agent's session directory, encrypts changed sessions with age, and publishes them into a shared, self-converging index (iroh-docs) with the file contents moved as content-addressed blobs (iroh-blobs). Sessions are identified by the agent's own session id, so "the same session" is coherent on every machine. Peers connect from a shared cluster file (recommended mode) or a one-off pairing ticket (ad-hoc alternative); clan assembles the cluster file automatically (distributing the namespace secret and each machine's age recipient and node-id), then sync happens directly via iroh discovery and NAT hole-punching.
flowchart LR
A["<b>Machine A</b><br>ssync daemon + full replica<br>⇄ session dirs (~/.pi, ~/.omp, …)"]
B["<b>Machine B</b><br>ssync daemon + full replica<br>⇄ session dirs"]
C["<b>Machine C</b><br>ssync daemon + full replica<br>⇄ session dirs"]
A <--> B
B <--> C
A <--> C
No hub, no server: the machine-to-machine links are direct p2p sync carrying only age ciphertext. Every daemon holds a full replica, adding a machine is just one more identical daemon joining the namespace, and the swarm self-meshes via gossip regardless of which machine you paired through.
The same daemon, three ways to get it:
-
From source (no nix required) — a plain Rust workspace. The only runtime dependency is
age/age-keygen≥ 1.3 (post-quantum support) onPATH:cargo build --release && ./target/release/ssync --helpWith nix (flakes), the package wraps the
agedependency for you:nix run github:fosskar/ssync -- --help # try it nix profile install github:fosskar/ssync # imperative install
Declaratively, add
inputs.ssync.url = "github:fosskar/ssync";to your flake and useinputs.ssync.packages.<system>.default— or better, use a module: -
NixOS or home-manager module (flake input, as above) — the same binary as a hardened systemd service; the module writes the config from nix options, you pair once with a cluster file (recommended) or a one-off ticket.
-
clan service — wraps the NixOS module and additionally generates and distributes every key via clan.vars, assembling the cluster file on each machine automatically: no tickets, no manual pairing, just a peer list.
Step-by-step instructions for all three: docs/setup.md.
ssync encrypts with age keys: either one shared key on all your machines, or a key per machine with each peer's recipient listed in the cluster file (enables per-device revocation). Each synced project must live at the same absolute path everywhere (see docs/identity.md).
With clan: just list the peer machines — the clan service generates a per-machine age key (peers encrypt to each other's recipients), a shared namespace secret, and each machine's node-id, then assembles the cluster file automatically, so peers auto-connect with no manual pairing.
Standalone (from source / NixOS / home-manager), pair with a cluster file (recommended mode):
# machine A
ssync init # writes config.toml, generates the age key, prints recipient + node-id
ssync cluster init # creates the cluster file, points the config at it
ssync daemon
# machine B
ssync init # prints this machine's recipient and node-id
# back on machine A: add machine B and redistribute the file
ssync cluster add <recipient-from-B> --node-id <node-id-from-B>
scp ~/.config/ssync/cluster.toml machineB:... # your secret channel
# machine B
ssync cluster join cluster.toml
ssync daemonFull instructions: docs/setup.md. Pairing details: docs/pairing.md.
Ad-hoc alternative: tickets. Without a cluster file, ssync ticket on a running
daemon prints a pairing ticket and ssync join '<ticket>' + ssync daemon on the other
machine joins it — tickets carry no recipient list, so either copy one shared age key
everywhere or maintain each machine's recipients by hand (see
docs/pairing.md).
ssync status # namespace, session count, conflicts
ssync conflicts # sessions that diverged across machines
ssync cleanup --keep 3m # list sessions older than 3 months (dry run)
ssync cleanup --keep 3m --apply # delete them; propagates to all peerscleanup selects by session creation time (from the filename; mtime is
meaningless — the engine rewrites files on sync), optionally per agent
(--agent pi), by date (--before 2026-06-08), or sessions never given a
title (--unnamed). It refuses to delete an agent's last session, since that
deletion would not propagate (the empty-dir safety guard).
Sessions often contain secrets (API keys, file contents). ssync encrypts every session at
rest with age before it ever leaves the machine, so peers and any relay see only ciphertext.
See docs/threat-model.md. Sessions are encrypted with post-quantum hybrid keys by default
(ML-KEM-768 + X25519, via age-keygen -pq). The NixOS module runs the daemon under a strict
systemd sandbox (docs/DECISIONS.md §12).
MIT. See LICENSE.