智能体 SaaS 沙盒 Workspace 版本管理设计
目标:用户工作运行在沙盒 workspace 中,里面可能包含代码、Word 文档、图片,甚至用户自己创建的 Git 项目。平台希望对 workspace 做版本管理,但不能让平台版本库干扰用户自己的 Git 使用。
1. 结论
推荐方案不是“自己实现一套 Git”,而是:
平台仍然使用 Git 做版本存储,但不要直接对真实 workspace 执行 git add -A。每次需要快照时,先把真实 workspace 同步到一个平台侧的 shadow snapshot 目录,过滤掉用户自己的 .git 等元数据目录,然后在 shadow snapshot 上执行 Git 提交。
用户可见
可包含用户自己的 .git
排除 .git、缓存、大文件策略
平台内部目录
干净文件树
bare repo
用户不可见
用户在 workspace 根目录执行 git status 时,因为真实 workspace 外面没有平台 .git,所以 Git 不会把 workspace 根目录识别成一个仓库。除非用户自己在当前目录或父目录创建了 Git 仓库,否则会提示 not a git repository。
2. 目录结构建议
/sandbox/workspace/ # 用户真实工作区,用户/Agent 在这里工作
repo-a/
.git/ # 用户自己的 Git 仓库
src/main.ts
package.json
docs/方案.docx
images/demo.png
/platform/snapshots/ws-123/ # 平台内部 shadow snapshot
repo-a/
src/main.ts # 注意:没有 repo-a/.git
package.json
docs/方案.docx
images/demo.png
/platform/vcs/ws-123.git # 平台内部 bare Git repo,用户不可见
/platform/objects/ # 可选:大文件对象存储
/platform/metadata/ # 可选:checkpoint、Agent run、nested repo manifest
关键点:平台自己的版本库不在 workspace 内,用户看不到,也不会被用户的 Git 命令误操作。平台只把清洗后的 shadow snapshot 提交到平台 Git 仓库。
3. 你的两个理解是否正确?
理解一:每次快照时,先 rsync 差额同步,再 git commit
正确。 可以这样做 MVP:
# 1. 从真实 workspace 同步到平台 shadow snapshot
rsync -a --delete \
--exclude='**/.git/**' \
--exclude='**/.hg/**' \
--exclude='**/.svn/**' \
--exclude='**/.jj/**' \
--exclude='node_modules/' \
--exclude='.venv/' \
--exclude='__pycache__/' \
/sandbox/workspace/ \
/platform/snapshots/ws-123/
# 2. 在 shadow snapshot 上做平台 Git 提交
git --git-dir=/platform/vcs/ws-123.git \
--work-tree=/platform/snapshots/ws-123 \
add -A
git --git-dir=/platform/vcs/ws-123.git \
--work-tree=/platform/snapshots/ws-123 \
commit -m "checkpoint after agent run"
rsync -a --delete 的作用是让 shadow snapshot 与真实 workspace 的“可保存文件集合”保持一致。用户删除的文件,也会在 shadow snapshot 中删除,然后 Git commit 会记录删除。
理解二:用户在 workspace 根目录调用 git status,不会看到平台 Git
正确。 只要你满足这几个条件:
- 平台
.git或 bare repo 不在/sandbox/workspace里; - 不要在用户 shell 里设置
GIT_DIR、GIT_WORK_TREE指向平台仓库; - 不要在 workspace 父目录放一个用户可见的
.git; - 平台执行 Git 时只在后台服务里显式使用
--git-dir和--work-tree。
因此用户在 workspace 根目录执行:
cd /sandbox/workspace
git status
大概率会看到类似:
fatal: not a git repository (or any of the parent directories): .git
但是如果用户进入自己创建的 Git 项目:
cd /sandbox/workspace/repo-a
git status
那么会正常显示 repo-a 这个用户项目自己的 Git 状态。
4. 为什么不要直接对真实 workspace 执行 git add -A?
即使平台 .git 在 workspace 外部,直接对真实 workspace 执行:
git --git-dir=/platform/vcs/ws-123.git \
--work-tree=/sandbox/workspace \
add -A
仍然可能遇到问题。因为真实 workspace 里面可能有用户自己的 Git 项目:
/sandbox/workspace/
repo-a/
.git/
src/main.ts
package.json
Git 看到 repo-a/.git 时,可能会把 repo-a 识别成 embedded repository,类似 submodule 的语义。这样平台 Git 可能不是递归保存 repo-a/src/main.ts,而是记录一个 commit 指针。
这与平台想要的语义不同。平台想保存的是“用户当前看到的文件系统状态”,而不是“用户内层 Git 仓库的某个 commit 指针”。用户可能还没 commit、没 push、有未跟踪文件,直接记录 Git 指针会带来恢复不完整的风险。
5. 嵌套 Git 项目的默认处理策略
对用户 workspace 里的 Git 项目,建议默认保存工作区内容,但不保存 .git 历史。
| 内容 | 默认是否保存 | 原因 |
|---|---|---|
| 代码文件、配置文件、README | 保存 | 这是用户当前可见、可编辑的工作成果。 |
| Word、PPT、图片、PDF | 保存 | 属于 workspace 成果,需要支持回滚。 |
.git/objects、.git/config、reflog、stash | 默认不保存 | 可能包含敏感历史、token、大对象、用户私有 Git 状态。 |
node_modules、.venv、build output | 通常不保存 | 可重建,体积大,容易污染版本库。 |
| 大文件、二进制文件 | 保存,但可走对象存储 | Git 原生不适合大量二进制版本增量。 |
可以额外记录一份 nested repo manifest:
{
"checkpoint_id": "ckpt_2026_06_26_001",
"nested_git_repos": [
{
"path": "repo-a",
"branch": "main",
"head": "abc1234",
"dirty": true,
"has_untracked_files": true,
"remote_host": "github.com",
"git_history_saved": false
}
]
}
这样用户回滚时,可以明确提示:平台恢复了工作区文件内容,但默认没有恢复用户项目自己的 Git 历史。
6. 快照流程设计
- Agent 任务开始前,创建 before checkpoint。
- Agent 在真实 workspace 中执行工作。
- 任务完成后,创建 after checkpoint。
- checkpoint 服务用 rsync 把真实 workspace 同步到 shadow snapshot。
- 同步时排除用户 Git 元数据、缓存目录、临时文件。
- 在 shadow snapshot 上执行 Git add 和 Git commit。
- 记录 metadata:task id、actor、时间、嵌套 Git 项目状态、大文件索引。
- 前端展示本次 checkpoint 的文件变更和内容 diff。
create_checkpoint(workspace_id, reason):
workspace = "/sandbox/workspace"
snapshot = f"/platform/snapshots/{workspace_id}"
repo = f"/platform/vcs/{workspace_id}.git"
scan_nested_git_repos(workspace)
rsync_filtered(workspace, snapshot)
git_add_all(repo, snapshot)
if git_has_changes(repo, snapshot):
git_commit(repo, snapshot, message=reason)
save_metadata()
else:
save_noop_checkpoint_metadata()
7. 回滚流程设计
不建议在用户正在使用的 workspace 上直接粗暴覆盖。更稳的方式:
- 从平台 Git 指定 commit checkout 到临时恢复目录。
- 从对象存储恢复大文件。
- 比较当前 workspace 与目标版本之间的差异。
- 向用户展示会新增、删除、覆盖哪些文件。
- 用户确认后,将目标版本同步回真实 workspace。
restore_checkpoint(workspace_id, commit):
target = f"/platform/restore-tmp/{workspace_id}/{commit}"
workspace = "/sandbox/workspace"
repo = f"/platform/vcs/{workspace_id}.git"
git_checkout_to_temp(repo, commit, target)
restore_large_objects(target)
preview_diff(workspace, target)
# after confirmation
rsync -a --delete target/ workspace/
产品上建议提供两种操作:
- 恢复到当前 workspace:覆盖当前工作区。
- 从该版本创建新 workspace:更适合智能体任务分支和实验。
8. 大文件、Word、图片的处理建议
Git 可以保存二进制文件,但长期大量保存 Word、PPT、图片、PDF、压缩包会让仓库膨胀。建议分层处理:
| 文件类型 | 存储策略 | Diff 策略 |
|---|---|---|
| 代码、Markdown、JSON、YAML | 直接进入 Git | 文本 diff |
| Word、PPT | 小文件可直接进 Git,大文件进对象存储 | 提取文本后做语义/文本 diff,同时保留原文件 |
| 图片 | 对象存储或 Git LFS-like 存储 | before/after 预览,hash 或 perceptual hash |
| 对象存储或 Git LFS-like 存储 | 文本型 PDF 做文本 diff;扫描型 PDF 做页面图像 diff | |
| node_modules、venv、build | 默认忽略 | 不展示 diff |
9. 高级功能:保存用户自己的 Git 历史
默认不保存用户项目里的 .git 是为了安全和容量。但可以提供高级选项:
Workspace 设置:
[ ] 保存嵌套 Git 仓库历史
[ ] 保存 remote 信息,自动脱敏 token
[ ] 保存 stash
[ ] 保存 ignored files
[ ] 保存 build output / node_modules
如果用户打开“保存嵌套 Git 仓库历史”,可以对每个内层 Git 项目额外生成 bundle:
cd /sandbox/workspace/repo-a
git bundle create /platform/git-bundles/ws-123/repo-a.bundle --all
或者将 .git 压缩、加密后放进对象存储。恢复时再解包。
这个能力不建议默认开启,因为 .git 里可能包含历史敏感文件、remote token、hooks、stash、大对象等。
10. MVP 实现建议
- 每个 workspace 创建一个平台 bare Git repo。
- 每个 workspace 创建一个 shadow snapshot 目录。
- 每次 checkpoint 用
rsync -a --delete同步。 - 排除
**/.git/**、node_modules、.venv、临时文件。 - 对 shadow snapshot 执行
git add -A和git commit。 - 记录 checkpoint metadata,包括 task id、actor、时间、commit id。
- 前端先做文件级 diff,代码文件再做文本 diff。
# 初始化
git init --bare /platform/vcs/ws-123.git
mkdir -p /platform/snapshots/ws-123
# checkpoint
rsync -a --delete \
--exclude='**/.git/**' \
--exclude='node_modules/' \
--exclude='.venv/' \
/sandbox/workspace/ \
/platform/snapshots/ws-123/
git --git-dir=/platform/vcs/ws-123.git \
--work-tree=/platform/snapshots/ws-123 \
add -A
if ! git --git-dir=/platform/vcs/ws-123.git \
--work-tree=/platform/snapshots/ws-123 \
diff --cached --quiet; then
git --git-dir=/platform/vcs/ws-123.git \
--work-tree=/platform/snapshots/ws-123 \
commit -m "checkpoint"
fi
11. 关键注意事项
- 不要把平台 Git 暴露在用户 shell 环境里。
- 用户命令执行环境里不要设置
GIT_DIR、GIT_WORK_TREE。 - 不要直接对真实 workspace 执行平台级
git add -A。 - 默认忽略用户项目里的
.git,但记录 nested repo manifest。 - 恢复时优先创建新 workspace,而不是直接覆盖当前 workspace。
- 大文件不要无限制塞进普通 Git blob。
- 快照要和 Agent run trace 绑定,方便用户理解“这次 Agent 改了什么”。
12. 一句话总结
平台用 Git 管理“清洗后的 workspace 快照”,而不是直接管理真实 workspace;用户自己的 Git 项目可以照常存在于 workspace 中,平台默认保存其当前文件内容,但不保存其 .git 历史。
这不是重写 Git,而是在 Git 前面加一层 snapshot filter,使平台版本管理语义与用户实际工作语义保持一致。