A mirror, not a mount
The analyzer runs on a node across the LAN and needs your code there. Not over a network filesystem, and not by uploading the tree every time: a manifest of hashes, a watermark per node, and a watcher that syncs only when something changed.

On this page · 4 sections
Move the analyzer to a node on the LAN and the next question is immediate: how does it see your code. The two obvious answers are both wrong for this workload. A network filesystem puts a round trip in front of every read, and an analyzer loading a Cargo workspace does hundreds of thousands of them; the thing you moved to a fast machine now waits on your laptop’s disk. Uploading the tree on every query is worse: our own repository is 10.8 MB of source, and an agent asks a question a few times a second.
So the node keeps a copy, and the client’s only job is to keep that copy honest for the least possible work. The interesting part is not the transfer. It is deciding what to send.
First contact is a manifest
A new worktree does not upload anything. It sends a manifest: for every file the sync layer cares about, its path, size and FNV-1a hash. The gateway already holds a copy of the origin repository, so it seeds the new worktree’s directory from that copy, compares it against the manifest, deletes what the client does not have, and answers with the list of files it is missing. Only those are uploaded. A fresh worktree of a 10 MB Rust repository is ready in 0.4 s instead of about 7 s, because a worktree differs from its origin by a handful of files and the gateway can prove which ones.
File content travels as base64 inside the JSON frames. That looks like a step backwards until you measure the alternative: a JSON array of byte integers inflates content roughly four-fold, and it dominated the sync until it was replaced.
| before | after | |
|---|---|---|
| first sync of a 10.8 MB workspace | 8.1 s | 0.76 s |
| a new worktree of that repository | ~7 s | 0.4 s |
| a query with nothing changed | two git subprocesses, ~70 ms |
skipped by the watcher |
After that, a watermark
Once a copy exists, the client remembers two things per node: the commit that copy is based on, and the dirty paths it has sent since. The next sync is git diff --name-only from that commit to HEAD, plus the current dirty set. If HEAD has not moved, the diff is skipped entirely. If the base commit is unreachable, because you rebased or the branch was force-pushed, it falls back to listing the tree.
Two details in there cost us bugs before they were rules. A file that was dirty and has been reverted to its committed content must still be sent, clean, or the node keeps the old edit forever; the watermark tracks reverts, not just changes. And the watermark is per node: a checkout that moves to another node, by placement or failover, uploads in full rather than sending a delta computed against a node it is no longer talking to. The node also answers “fresh” if its copy was pruned, and the client resyncs before the query runs.
The sync is not one-way. A command that runs on the node and changes files there, a formatter, a code generator, an updated lockfile, sends those files back into your checkout and records them in the watermark, so cargo fmt on a 32-core node is indistinguishable from cargo fmt at home. The mirror-side edits the gateway computes for you, a rename or a quick fix applied across the workspace, are deliberately not recorded as synced: the client wrote them, so the next sync uploads them, and a hover after a rename sees the new code.
The cheapest sync is the one you skip
A one-shot CLI query costs about 80 ms, and about 70 ms of that is two git subprocesses on the laptop working out what changed. That is the dominant cost of a query whose server side is single-digit milliseconds, and it is pure overhead when nothing changed at all.
So the MCP server, which is a long-lived process inside the agent session, does not ask git. It watches the workspace tree (ignoring target/, node_modules/ and the rest of the build output) and keeps a generation counter. A tool call syncs only if the counter moved since the last sync, or if the last sync is older than a safety interval. In a session where the agent is reading rather than writing, which is most of them, the pre-flight sync disappears entirely and the tool call is one request on an already-open connection.
The CLI shows what the watcher is skipping. This is the same repository, unchanged since the last call:
$ prod-code sync
⚡ prod-code Fast-Sync Completed in 89ms
Local Workspace: …/prod-code
Server Workspace: prod-code
Files Planned: 0
Files Updated: 0
Files Deleted: 0
Data Transferred: 0.0 KB
Status: SYNCHRONIZED
$ PROD_CODE_TIMING=1 prod-code hover crates/prod-code-gateway/src/shadow.rs 78 8
[timing] total=94.1ms connect=0.5ms preflight_sync=74.7ms handshake=7.3ms
initialize=0.6ms did_open_sent=7.0ms query_response=4.0ms
Eighty-nine milliseconds to transfer nothing, and 74.7 of the query’s 94 ms spent deciding that nothing had changed, against 4 ms of actual analysis. That is the cost the watcher removes.
What it does not do yet
- Whole files, not deltas: a changed file is uploaded in full. For source files this has never been the bottleneck, and it keeps the content hash and the manifest honest.
- Unsaved editor buffers are not part of the mirror. The analyzer can hold a proposed text for a session, which is how validation before writing works, but the file watcher only sees what is on disk.
- The watcher is per MCP process. Two agent processes on the same checkout each keep their own watermark against the node, and the node reconciles by content hash.
- Binary and generated files are filtered by the relevance rules, which are versioned: when we widen them, the version bump forces a manifest probe so nothing stays missing on the node.
- Nothing watches for changes made on the node outside a command we ran. A file edited by hand in the mirror is overwritten on the next sync that touches it.
The rule the design rests on: the client is the only authority on what the code is, and it proves that with hashes rather than asserting it with uploads. Everything else, the seeding, the watermark, the skipped sync, is a way of sending less while still being able to prove it.
Cite this article
Alexander Panasenko (2026-09-22). A mirror, not a mount. https://prod.codes/blog/a-mirror-not-a-mount/