Run one AI build across two machines without lying to yourself

Level
pro
Time to complete
60 min
Prerequisites
5

What you will buildA two-machine bench that tells you the truth: a mirror, a git channel, and the checks a confident report cannot fool. ~60 min, pro.

What You Will Build

A two-machine development bench, driven by AI task windows on both machines, where work done on either reaches the other in bounded time - and, more importantly, where you can always tell whether it actually did. Two transport planes: a continuous file mirror for working files, and git for committed source. Plus the handful of checks that catch the specific way distributed systems fail, which is not loudly. They fail by reporting success while doing nothing, and the cost is measured in how long nobody looks.

By the end you will have one folder mirroring both directions, a per-device ignore list you know exists on every peer, a fast-forward-only git pull on a cadence, path resolution that fails closed instead of guessing, a heartbeat line on every daemon cycle, a size-and-count reconciliation you run before trusting anything, and a definition of "done" that lives on the far machine. I will call the two machines roro and sb; substitute your own.

Prerequisites

  • Two machines you control, each a full working environment (the examples are Windows and PowerShell; the shapes port to macOS or Linux).
  • Syncthing installed on both, or another peer-to-peer file-sync tool you can configure per folder.
  • Git on both, and one shared remote per repo (GitHub or similar).
  • Python 3 on both, for the pull daemon.
  • A decision made in advance: which folders are shared, and in which direction.

Tip

Do not start by syncing everything. The whole skill here is deciding what must replicate and what must never. A drive that mirrors .git, node_modules and caches will corrupt itself within a day. Pick one folder first.

Mental Model

Two planes, on purpose. The mirror is fast, bidirectional, and has no notion of intent - it will propagate a half-written file or a deletion without asking. Git is slower, refuses to guess, and treats a commit as a claim. You run both so each covers the other's blind spot: the mirror carries what git should not (logs, scratch, machine-local config), and git carries what a mirror would corrupt (branch state, history, anything two machines might write at once).

The one rule that makes the planes independent: .git never enters the mirror. A mirrored index means two machines writing one index, which corrupts. So a mirrored copy of a repo has every file and no .git - it is the files, not the repo-ness.

The build ladder: mirror one folder, exclude what cannot replicate, add the git channel, resolve by identity, add a heartbeat, reconcile, then define done on the far side.

Steps

Build in this order. Each step adds a check that catches a specific, real failure.

Mirror one folder on both machines

Install the sync tool on both machines with separate home directories, pair them by device identity, and add exactly one folder with the same folder ID on each side, set to send-and-receive. Leave everything else unsynced for now.

Prove the link before you trust it:

# On roro: drop a sentinel
Set-Content -Path "D:\bench\__sentinel.txt" -Value (Get-Date -Format o)
# On sb, within ~30 seconds, it should appear:
Get-Content "C:\bench\__sentinel.txt"

You know it worked when the sentinel appears on sb within about thirty seconds, and deleting it on sb removes it from roro too. Delete the sentinel before moving on.

Exclude what cannot survive replication

Add an ignore list and put .git, node_modules, build output and editor caches in it first. Then learn the trap that catches everyone: the ignore file itself is usually per-device local config, and the sync tool refuses to replicate it. Each peer has its own, and they are allowed to diverge.

# Run this on EVERY peer, not just the one you configured.
Test-Path C:\bench\.stignore
Get-Item C:\bench\.stignore | Select-Object Length, LastWriteTime

You know it worked when the ignore file exists on both machines. Different sizes and timestamps are the expected, correct state - each machine sees different local churn. Absence on a peer is the red flag: it means that machine has been mirroring with no exclusions at all.

Trap

Never blanket-glob a class that holds both disposable and load-bearing members. Ignoring *.lock to silence churn will also stop mirroring every package-lock.json and flake.lock - the build pins whose entire job is to be byte-identical across machines. Enumerate the disposable ones by name instead.