skills/cache-expert/references/filesync.md
Filesync transfers host files between client and engine. It powers host.directory / host.file imports and local exports.
This doc focuses on protocol flow, engine sync behavior, and cache interactions relevant to correctness/performance.
engine/client/filesync.go)Client exposes two gRPC services per session:
FileSync (source)FileSend (target)internal/fsutil)Wire protocol uses packetized stat/data/request frames:
engine/filesync)FileSyncer.Snapshot)High-level flow:
ClientFilesyncMirror-owned mutable mirror snapshotremoteFS (engine/filesync/remotefs.go)remoteFS reads from client stream:
Walk and ReadFilelocalFS (engine/filesync/localfs.go)localFS applies diff from remote stream into per-client mirror:
There are two distinct cache layers involved now:
core.ClientFilesyncMirror_clientFilesyncMirrorengine/filesync/change_cache.go*ChangeWithStatThe dagql object owns the mirror snapshot's lifecycle. The in-package change cache only dedupes and validates mutations during active syncs against that mirror.
localFS.Sync uses change-cache entries to:
This cache is shared across syncs for the same client mirror via localFSSharedState.
Applied changes are validated against expected remote stats (verifyExpectedChange).
If a cached/applied change does not match expected change, sync fails with conflict instead of silently producing mixed-state snapshot.
With gitignore-enabled import:
After sync operations:
This is separate from dagql call cache; it is snapshot-content reuse at filesync layer on top of the mutable mirror.
For stable clients, host imports route through a persistable _clientFilesyncMirror object keyed by stable client ID and drive. That object owns:
MirrorSharedState)For clients without a stable ID, the engine uses an ephemeral mirror object instead.
Either way, each actual import still returns an immutable directory/file snapshot to the caller. The mutable mirror is just backing state that makes repeated syncs cheaper and more consistent.
noCache does not bypass the mirror. It adds a filesync cache-buster so the sync result is treated as fresh while still using the existing mirror as the base state.
Exports use client FileSend service:
engine/client/filesync.gointernal/fsutil/send.go, internal/fsutil/receive.goengine/filesync/filesyncer.go, engine/filesync/localfs.go, engine/filesync/remotefs.goengine/filesync/change_cache.gocore/schema/host.go, core/host.go