Drive9 has two high-priority requirements for agent workspaces:
git cloneinto a mounted directory must be fast, without checking out the full working tree into Drive9's generic file tables.- After a sandbox or container is replaced, the agent's partially edited working tree and
.gitstate must be recoverable from Drive9 sogit status,git add,git commit, andgit pushcan continue.
This is therefore not a pure generic filesystem optimization. It is a Git-aware fast workspace layered on top of the generic Drive9 FUSE semantics. The design intentionally reuses Git's own object model: the clean tree is represented by a Git manifest, dirty working tree changes are represented by a Drive9 overlay, and .git is kept in the local overlay with backend checkpoints.
drive9 git clone --fast <repo-url> <mounted-path>registers only the HEAD tree manifest and does not check out clean file content intofile_nodes.- The default local
.gitobject database is a full clone, while the working tree checkout is virtual. Build-time reads are served from local Git objects instead of Drive9 file rows. drive9 git clone --fast --bloblessis an opt-in mode that keeps clone latency low with a partial local clone, then hydrates clean blob content out of the FUSE hot path.- Blobless mode avoids build-time and Git-porcelain lazy-fetch amplification by hydrating clean blobs into both a hidden local source tree cache and the local
.git/objectsdatabase. - Working tree changes such as edit/create/delete/chmod/symlink are persisted to the Drive9 backend overlay.
.gitkeeps local disk read/write performance while lightweight state and small local-only Git objects are checkpointed to Drive9 for cross-sandbox recovery.git add,git commit, andgit pushshould remain as close as possible to the native Git workflow, without introducing a separate push API.drive9 git worktree add --fastshould create linked worktrees without native checkout writes into FUSE.drive9 git worktree remove --fastshould remove a linked fast workspace without recursively unlinking every virtual clean file.
- Do not add a full artifact-style command suite or turn this into a general content-addressed checkout tool.
- Do not write clean tree file content into
file_nodesorcontents. - Do not persist the full Git object database in Drive9. Cross-sandbox recovery may depend on the original Git remote, such as GitHub, to repopulate clean objects.
- Do not store remote clean blobs in Drive9. The local
.git/objectshydrate is a rebuildable cache; the only Git objects Drive9 may store are local-only objects needed to restore staged or local Git state. - Do not treat the hidden hydrated clean-tree cache as authoritative state. It is local, rebuildable, and may be discarded with the sandbox or local root.
- Do not introduce local SQLite. Local state continues to use the existing local overlay, shadow files, and
journal.wal; authoritative state lives in Drive9 backend Git workspace tables. - Do not implement complete semantics for complex Git features in the first version, such as optimized submodule/LFS handling, merge conflict assistance, or automatic remote branch synchronization.
git_workspaces
- One row represents one fast workspace.
- Key fields:
workspace_id,root_path,repo_url,remote_name,branch_name,base_commit,head_commit,mode, andstatus=active. mode=fastis the default full-object local clone;mode=fast-bloblessis the opt-in blobless local clone.root_pathis unique and maps to the repo root directory inside the mounted tree.workspace_kind=mainis a primary fast clone.workspace_kind=linkedis a Git linked worktree with its own clean tree manifest and dirty overlay.common_workspace_idpoints from a linked worktree to the main/common workspace that owns the shared Git common dir.worktree_nameis the stable name under the common Git dir'sworktrees/directory.gitdir_relstores the linked gitdir path relative to the common.gitdirectory, normallyworktrees/<worktree_name>.
git_workspace_tree_nodes
- Stores the base/head commit tree manifest.
- Paths are relative to the workspace root and do not have a leading
/. path_hashandparent_path_hashare SHA-256 hex hashes used for TiDB/MySQL indexes so long Git paths do not exceed key-length limits. The originalpathandparent_pathremain authoritative for exact semantics and API responses.- Includes
kind,mode,object_sha, andsize_bytesfor directories, files, symlinks, and submodules. - Clean tree file content is not stored in Drive9's generic file content tables.
size_bytesis still populated without relying on checkout file content. Manifest generation usesgit ls-tree -r -t -z HEADand must not usegit ls-tree -l;-lasks Git to print blob sizes and is unsafe if a future mode uses a partial clone again.- For GitHub repositories, the CLI fills blob sizes through the GitHub Trees API. If the recursive response is truncated, it falls back to walking trees level by level. This preserves accurate
statandgit statussizes while still avoiding blob downloads during clone. - For non-GitHub repositories, unavailable GitHub API responses, or temporarily missing sizes,
size_bytes=-1means unknown size. FUSE reports size 0 to the kernel attr path as a fallback, but real read/write/truncate/rename-copy paths load the base blob on demand and update the local inode size from the actual content length. This avoids treating unknown-size files as empty files.
git_workspace_overlay
- Stores working tree changes relative to the clean tree.
op=upsertmeans a file or directory was created or modified;op=whiteoutmeans a clean tree entry was deleted.path_hashis the SHA-256 hex hash used as the durable key; the originalpathis still stored and checked on lookup.- File payloads are currently stored inline in
content_blob; large-file support can later extend this to S3/storage refs. - Directory creation is stored as an overlay entry with
kind=dir. - Dirty overlay entries include file content and metadata changes such as chmod, directory creation, symlink creation, rename copy/whiteout, and delete whiteouts. FUSE applies them to the in-memory workspace view immediately, while the remote backend write follows the same
--durability/ write policy used by ordinary files.
git_workspace_git_state
- Stores
.gitdirectory checkpoints. - The checkpoint intentionally excludes Git object databases such as
.git/objectsand.git/modules/*/objects. - The current format is
storage_type=tar.gz-no-objects, with the lightweight payload incontent_blob. - When sandbox B mounts the same drive, FUSE recreates local
.gitfromrepo_url, applies any Drive9 local-only object packs, then overlays the Drive9 checkpoint to restore refs, index, config, logs, and other lightweight state.
git_workspace_object_packs
- Stores inline Git packfiles for local-only objects that are not available from the remote.
pack_idis the SHA-256 checksum of the pack bytes. Uploads are idempotent by(workspace_id, pack_id).- V1 stores packs inline in
content_blob; no S3/storage-ref path is used. - Per local-only blob cap: 5 MiB. Total inline pack cap: 256 MiB.
- Oversized staged objects are not packed. Their working-tree content still survives through
git_workspace_overlay, but staged state is downgraded to unstaged on restore. - Local refs, stash, or reflog state that would require omitted oversized objects is dropped from the checkpoint rather than restoring broken refs.
file_nodes
- Still represents ordinary Drive9 filesystem directories and files.
- Clean or dirty Git file content under a fast workspace should not leak into
file_nodesfile rows. - The repo parent directory and repo root directory may exist as ordinary directory rows.
Hidden local clean cache
- Blobless workspaces use a hidden, rebuildable cache under
<local-root>/git-workspaces/<workspace-id>/<head-commit>/. tree/stores a materialized clean source tree for fast build-time reads.blobs/<sha-prefix>/<sha>stores read-through clean blob cache entries when a file is read before hydrate finishes.hydrate.jsonrecords the last hydrate provider, tree status, object database status, file count, byte count, object count, skipped/mismatch/fallback counts, duration, and error string.- The cache key includes workspace ID and head commit so old clean content is not reused after a manifest change.
Coding-agent local overlay policy
- The coding-agent mount profile routes heavyweight local state and generated output to
<local-root>/overlayinstead of Drive9 backend storage. - Default local-only paths include VCS state (
.git,.hg,.svn), dependency directories (node_modules,.venv,.pnpm-store), build outputs (dist,build,target,coverage), temporary/cache directories (tmp,.tmp,.cache,.turbo,.next/cache,.vitepress/cache), and tool-specific generated output such as.tmp-api-extractor. - These local-only paths are still merged into FUSE directory listings with tracked Git workspace entries, so generated directories under a tracked source directory remain visible to local build tools without being uploaded to Drive9.
- Local-only dependency and generated-output files are a rebuildable performance layer. Their ordinary FUSE
Flushpath does not forcefsync; it refreshes local inode metadata only. ExplicitFsyncstill syncs the local file. - Lightweight
.gitstate is checkpointed asynchronously and coalesced per workspace. ForegroundFlush,Fsync,Release,Rename, andUnlinkon local.gitfiles only perform the necessary local filesystem operation and schedule a checkpoint;FlushAll/unmount drains pending checkpoints. - Linked worktree
.gitis a file, not a directory. It is local-only too. On restore, Drive9 rewrites it with a relativegitdir:target that points through the mounted path back to the common workspace's.git/worktrees/<name>directory, so later Git metadata writes still pass through FUSE and can be checkpointed. - Transient Git lock files such as
.git/index.lock,.git/packed-refs.lock,.git/HEAD.lock, andrefs/**/*.lockare never checkpointed. The durable state is captured after Git atomically renames the lock file to its final destination. .git/objectsremains a local object database/cache and is not checkpointed directly.
-
The user first mounts Drive9:
drive9 mount --mode=fuse --profile=coding-agent --local-root <local-root> --cache-dir <cache> --durability=write-sync :/ <mountpoint>
-
The user runs one of:
drive9 git clone --fast https://github.com/org/repo.git <mountpoint>/<path>/repo drive9 git clone --fast --blobless https://github.com/org/repo.git <mountpoint>/<path>/repo drive9 git clone --fast --blobless --hydrate=sync https://github.com/org/repo.git <mountpoint>/<path>/repo
Blobless hydrate modes are:
--hydrate=background(default for--blobless): return after workspace registration and hydrate the clean tree plus local Git object database in the background.--hydrate=sync: materialize the clean tree and local Git object database before returning, useful for deterministic diagnostics and benchmarking.--hydrate=off: preserve the pure lazy-fetch behavior.
-
The CLI runs a no-checkout clone under the mounted path:
git clone --no-checkout <repo-url> <target> git clone --filter=blob:none --no-checkout <repo-url> <target> # with --blobless
.gitis routed to local disk by the coding-agent local overlay and does not enter Drive9's generic file tables. In default mode the local object database is complete; in blobless mode the clone starts with a partial object database and hydrate later fills clean blobs from the hidden clean tree cache into.git/objects. -
The CLI reads
HEAD, branch, andgit ls-tree -r -t -z HEAD, generates the tree manifest, and fills file sizes according to thegit_workspace_tree_nodes.size_bytesrules. -
The CLI initializes the Git index:
git read-tree --reset HEAD
This tells Git the clean tree object IDs without checking out file content. The CLI also sets repo-local performance knobs such as
gc.auto=0,maintenance.auto=false,core.untrackedCache=truewhen supported, andcore.splitIndex=truewhen supported. Automatic foreground GC is disabled because local.git/objectsis a rebuildable cache and Git porcelain should not unexpectedly auto-pack while FUSE is servicing.git/index.lockoperations. -
The CLI calls Drive9 APIs:
- upsert
git_workspaces - replace
git_workspace_tree_nodes - archive the local overlay
.gitdirectory without object databases and upsertgit_workspace_git_state
- upsert
-
In blobless mode, the CLI starts
drive9 git hydrate <mounted-path>in the background unless--hydrate=syncor--hydrate=offwas selected. Hydrate runs in two stages:- Materialize
tree/. GitHub repositories use codeload tarballs for batch hydrate; non-GitHub repositories fall back to a separate Git index plusgit checkout-indexinto the hidden local cache. - Populate local
.git/objectsfrom the hydrated tree usinggit hash-object -w, verifying each generated blob SHA against the manifest. Mismatches or unsupported entries are counted and may fall back to Git's own object lookup.
- Materialize
-
After FUSE rediscovers the workspace, directory listings come from the synthetic view of
git_workspace_tree_nodesplusgit_workspace_overlay. FUSE also best-effort starts hydrate formode=fast-bloblessworkspaces so replacement sandboxes still warm their local cache. -
In the coding-agent mount profile, FUSE treats repository-ignored generated paths as local-only. The policy first applies explicit local/remote patterns; for otherwise remote-default paths inside a Git workspace, it runs cached
git check-ignoreagainst the hidden hydrated clean tree and the local.gitstate. Paths that Git would ignore are routed to the local overlay, while tracked clean files and durable Git overlay entries keep their normal Git workspace semantics.
V1 uses explicit Drive9 commands and does not transparently intercept native git worktree add/remove:
drive9 git worktree add --fast [-b <branch>] [--detach] [--blobless] [--hydrate=auto|background|sync|off] <base-repo-path> <worktree-path> [<commit-ish>]
drive9 git worktree remove --fast [--force] <worktree-path>drive9 git worktree add --fast:
-
Resolves both paths through Drive9 mount metadata and requires them to be in the same mount.
-
Looks up
<base-repo-path>as an activeworkspace_kind=mainfast workspace. -
Runs native Git only for linked worktree metadata:
git -C <base-repo-path> worktree add --no-checkout [-b <branch>|--detach] <worktree-path> <resolved-commit> git -C <worktree-path> read-tree --reset <resolved-commit>
The command intentionally avoids a checkout. It creates only the worktree root, the linked
.gitfile, the common.git/worktrees/<name>metadata, and the Git index. -
Generates a clean tree manifest with
git ls-tree -r -t -z <commit>, enriches sizes through the GitHub Trees API when possible, and registers a newgit_workspacesrow withworkspace_kind=linked. -
Stores linked
.gitstate as a separategit_workspace_git_staterow and also checkpoints the common workspace.git, because Git updated common metadata such asworktrees/<name>and refs. -
Uses the same hydrate behavior as fast clone. A linked workspace inherits blobless behavior from a blobless common workspace;
--bloblessis a validation flag rather than a way to convert a full common object database into a partial one.
drive9 git worktree remove --fast:
- Resolves
<worktree-path>to a linked fast workspace. - Refuses removal when
git status --porcelainreports staged, unstaged, or untracked changes, unless the caller passes--force. - Removes the common
.git/worktrees/<name>metadata from the local overlay and runsgit worktree pruneas best-effort local cleanup. - Checkpoints the common workspace
.git. - Marks the linked
git_workspacesrow deleted and removes the linked local overlay root. - Best-effort removes the empty mounted worktree root. It does not generate clean-tree whiteouts and does not recursively unlink virtual manifest files.
Linked worktree restore:
- FUSE matches workspaces by longest
root_path, so the main repo and each linked worktree get independent runtimes. - Access to a linked worktree
.gitfile, or to the common path.git/worktrees/<name>/..., triggers linked restore. - FUSE restores the common workspace
.gitfirst fromrepo_url, common object packs, and the common Git state checkpoint. - FUSE recreates the linked
.gitfile, restores the linked gitdir under the common.git/worktrees/<name>directory, unpacks linked local-only object packs into the common object database, and extracts the linked lightweight Git state. - Dirty working tree files restore from the linked workspace's own
git_workspace_overlay, isolated from the common workspace and other linked worktrees.
Performance coverage for issue #476:
git worktree add --fastavoids checkout writes for every clean source file.git worktree remove --fastavoids recursive unlink/rmdir over virtual clean tree entries.git statusandgit addread tracked metadata from the manifest and overlay instead of round-tripping to generic remotefile_nodes..gitprobes, lock files, linked gitdir files, and common worktree metadata stay on local overlay hot paths with coalesced checkpoints.- The expected perf-counter movement is lower aggregate
remote stat,remote mutation,remote write, and FUSE unlink time for worktree smoke workflows.
Read a clean file:
- FUSE looks up the workspace manifest and constructs a virtual inode.
- On read, FUSE first checks the hidden materialized clean tree, then the local blob cache, and only then falls back to
git cat-file blob <sha>. - If fallback reads a clean blob, FUSE writes it to the local blob cache with singleflight protection so concurrent reads of the same object do not fan out into repeated Git lazy fetches.
- In blobless mode, hydrate is intended to move remote blob download off the build and Git command hot paths. If hydrate has not finished, fallback Git lazy fetch still works for correctness.
- File content is not written into
file_nodes.
Edit a file:
- When a tracked clean file is written, FUSE stores the new content in a
git_workspace_overlayupsertentry. - New files and directories also enter the overlay.
- In the coding-agent profile, untracked paths matched by the repository's
.gitignoreare local-only. This keeps repo-specific build outputs such as generated web assets, package build directories, and temporary tool output off Drive9 without hand-maintaining per-repo mount patterns. - Deleting a clean file writes a
whiteout. - Writable opens for tracked files preload from the Git workspace manifest or dirty overlay entry into a hidden local dirty mirror under
LocalRoot. FUSE serves the write handle from that local file and converts it into a Drive9 overlay snapshot on flush/release. This is especially important for append-style writes: local file semantics preserve the complete current file content before appending the new bytes, while Drive9 still owns the durable cross-sandbox overlay. - Clean tracked files may use the kernel page cache because their content is immutable for a given manifest entry. Dirty overlay files are different: they are the authoritative edited working tree view, so read-only opens for content-changing overlay entries bypass the kernel page cache. This keeps
git addand index refresh from reading stale clean pages or seeing size/content mismatches after append-heavy edits, while preserving cache hits for clean build reads. - Dirty overlay file content and metadata use one durability policy:
writebackwith interactive durability updates the local runtime overlay before returning and commits the backendgit_workspace_overlayentry on an ordered background path.FlushAll/unmount drains pending overlay writes.close-sync,write-sync, and strict durability wait for the backend overlay write before reporting success on the relevant FUSE operation.- Background overlay writes are ordered per mount, so rename copy/whiteout sequences reach the backend in the same order they became visible locally.
git add:
- Git reads the clean+overlay synthetic working tree through FUSE.
.git/index, objects, logs, and related files are written to the local overlay.- Writable lightweight
.githandles schedule a coalesced checkpoint instead of blocking the close/rename/unlink hot path. The checkpoint packs local-only Git objects first, then writesgit_workspace_git_state. - Git lock files are local-only and transient; closing or unlinking them must not run checkpoint work. This prevents
git status,git diff, andgit addfrom payingrev-list/object-pack cost on every.git/index.locklifecycle. .git/objects/**is not checkpointed directly and does not enter Drive9 backend storage. Clean objects are rebuilt from remote or hydrate cache; local-only objects needed by staged state or local refs are packed during the next lightweight.gitcheckpoint.- Read-only
.githandles do not checkpoint, which prevents commands such asgit statusfrom repeatedly uploading the full.gitarchive. - If a staged blob is larger than 5 MiB, restore downgrades that staged state to unstaged. The file content remains durable through the Drive9 dirty overlay.
git commit:
- Git updates local
.gitthrough its native workflow. - When the working tree overlay matches the Git index after commit,
git statusreports clean. - The first version does not automatically write the new commit tree back as the new clean manifest or clear the overlay. The overlay remains relative to the original base tree.
git push:
- Git pushes natively from the local
.git. - Drive9 does not participate in the push protocol.
-
Sandbox A mounts Drive9 and fast-clones a repo.
-
The agent edits files, runs
git add,git commit, andgit push, then leaves another uncommitted change. -
A unmounts or stops.
-
Sandbox B mounts the same Drive9 path with a new local root and cache.
-
When B accesses the repo path, FUSE loads from the backend:
git_workspacesgit_workspace_tree_nodesgit_workspace_overlaygit_workspace_git_state
-
FUSE runs a no-checkout clone from
repo_urlinto B's local overlay:mode=fast: full clone.mode=fast-blobless: blobless partial clone.
-
FUSE downloads and unpacks local-only object packs, then extracts the lightweight
.gitcheckpoint over the local clone. -
B sees working tree = clean tree manifest + durable overlay;
git status,git log, and file content are restored to A's last persisted state, subject to the explicit oversized staged/local-ref downgrade rules.
For linked worktrees, B restores the common workspace first, then each linked gitdir on demand. Edited files come from the linked workspace overlay, staged state comes from the linked .git checkpoint plus small object packs, and local commits/refs are recovered through the common and linked checkpoints together.
The current implementation does not introduce SQLite.
Local fast workspace state includes:
<local-root>/overlay/.../.git: local.git.<local-root>/overlay/.../<ignored-path>: coding-agent local-only files for paths matched by static local-only patterns or repository.gitignorerules.<local-root>/git-workspaces/<workspace-id>/<head-commit>/tree: hidden hydrated clean source tree cache.<local-root>/git-workspaces/<workspace-id>/<head-commit>/blobs: hidden read-through clean blob cache.<local-root>/overlay/.../.git/objects: local Git object database. In blobless mode hydrate fills clean blobs here from the hidden tree cache sogit status,git diff,git add, andgit commitdo not fan out into promisor lazy fetches.<cache-dir>/<mount-id>/journal.wal: the existing FUSE write/cache journal.- shadow/cache files used by the existing write-back and strict durability mechanisms.
Authoritative cross-sandbox working tree state lives in backend DB Git workspace tables. Local files are the current sandbox's performance layer and restore target. Clean Git object content is recovered from the Git remote or rebuilt into .git/objects from a local hidden hydrate cache; local-only Git object content may be recovered from inline Drive9 object packs under the v1 size limits.
Validated in the dev environment on 2026-05-27:
- endpoint:
http://example-k8s-endpoint.local - repo:
https://github.com/example/repo.git - run_id:
fastclone-e2e-YYYYMMDDHHMMSS - tenant_id:
TENANT_ID_PLACEHOLDER - workspace_id:
WORKSPACE_ID_PLACEHOLDER
Covered scenario:
- A mounts Drive9 and runs
drive9 git clone --fast. - Modify
README.txt, createnotes/drive9-note.txt, and deletefix.txt. - Run
git add,git commit, and push to a pre-mirrored bare remote. - After the commit, modify
build.gradleand leave it uncommitted. - A unmounts; B mounts the same Drive9 path with a new local root/cache.
- B restores
.git;git log -1shows the new commit andgit status --porcelain=v1showsM build.gradle.
Backend table checks:
git_workspaces: active fast workspace row exists.git_workspace_tree_nodes: 26 rows, equal to the base commit tree count.git_workspace_git_state:storage_type=tar.gz-no-objects; payload contains lightweight.gitstate and excludes object databases.git_workspace_object_packs: empty for the historical full-clone pushed-commit validation unless staged/local-only objects need recovery.git_workspace_overlaycontains:README.txtupsert filenotesupsert dirnotes/drive9-note.txtupsert filefix.txtwhiteout filebuild.gradleupsert file
file_nodes: file row count under the workspace is 0; only the run directory and repo root directory rows exist.- Local SQLite file count is 0.
- Object packs are inline only in v1. Local-only Git objects above the 5 MiB per-blob cap or 256 MiB pack cap are downgraded instead of being preserved as staged/local refs.
- Restoring large local-only Git objects without downgrades will require S3/storage refs or an object/pack dedup layer.
- After
git commit, the overlay remains relative to the base tree. Later versions can add an explicitdrive9 git checkpointor an automatic manifest-advance mechanism. - Large overlay files need to move from inline
content_blobto S3/storage refs. - Directory renames inside a Git workspace are capped at 100 affected tree/overlay entries in v1 and return
EXDEVabove that limit. This avoids thousands of sequential overlay RPCs; a later batch endpoint can lift the limit. - Branch switching, merge, rebase, conflicts, submodules, and LFS need deeper semantic validation.
- V1 hydrate does not optimize LFS or submodule object fetching. Non-GitHub repositories use a Git checkout-index fallback, which can be slower than GitHub codeload but still keeps the work out of FUSE read calls.
- Object database hydrate is a performance layer, not authoritative storage. If a sandbox loses its local root, replacement sandboxes rebuild clean objects from the remote or hidden tree hydrate and restore local-only objects through the existing object-pack checkpoint path.
- The repeatable live smoke path is
e2e/git-workspace-smoke-test.sh. It covers fast clone, agent edit/add/commit, sandbox replacement, and fast linked worktree add/edit/stage/commit/remount/remove. Release gates still need to decide when to enable this heavier FUSE workflow by default.