第 8 章:执行环境、仓库、本地目录与 GC
8.1 为什么每个 Task 需要自己的环境
Provider CLI 会写很多隐式状态:
- 工作树和 Git 分支;
CLAUDE.md、AGENTS.md、Skills 与 MCP 配置;- Provider Home、Session、Memory、Settings;
- 日志、输出、临时文件;
- 任务令牌和环境变量。
如果所有 Task 共用用户 HOME 和同一 Checkout,最先出现的不是性能问题,而是上下文串线、凭据泄漏和并发覆盖。
execenv 的职责,就是把每次执行所需的“文件系统世界”显式化。
8.2 标准目录布局
PredictRootDir 计算:
{workspacesRoot}/
└── {workspaceID}/
└── {shortTaskID}/ # Env Root
├── workdir/ # Agent 初始 cwd
├── output/ # 结构化输出/辅助产物
├── logs/ # Task 日志
├── codex-home/ # Provider 专用,按需
├── task-home/ # 隔离 HOME,按需
├── ... provider overlays
├── .multica-sidecars.json # 注入文件清单
├── .managed-env-provenance # 可恢复环境来源
└── .gc_meta.json # 完成后最后写入
实际子目录随 Provider 变化。关键约束是:Env Root 由 Daemon 管理,而 Agent Workdir 可能位于其中,也可能被 local_directory 替换为用户目录。
8.3 Prepare 的主要步骤
execenv.Prepare 大致做:
- 校验 Workspace Root、Workspace ID、Task ID;
- 恢复 Root Marker,确保任务内 CLI 可 Fail Closed;
- 防御性清理同 Task ID 的残留 Env;
- 创建
workdir/output/logs; - 写 Task Context 与 Provider 原生 Brief;
- 写 Project Resources 和 Skills;
- 记录 Sidecar Manifest;
- 对标准 Issue 环境写 Managed Provenance;
- 准备 Codex/Claude/OpenClaw/Cursor/Hermes 等 Provider 专用 Home/Config;
- 返回统一
Environment。
Daemon 在 Prepare 前就把预测 Env Root 标记为 Active,防止 GC 与创建目录竞态。
8.4 根 Marker 与任务内 CLI 的 Fail Closed
Agent 进程使用 multica CLI 操作 Issue、Comment、Repo、Attachment、Squad 等业务。它通常从环境变量拿 Task Token 和 Task ID。
如果某个子进程丢失这些变量,却仍能读取用户的普通 CLI 配置,就可能退化成 Daemon Owner 的长期 PAT,扩大权限。
Workspaces Root 与 Workdir Marker 告诉 CLI:
你处于 Daemon 管理的任务环境。如果任务凭据缺失,不允许静默回落到普通用户配置。
Marker 在 Prepare 和 Reuse 时都会自愈,避免运行中被删除后下一次恢复失去保护。
8.5 Task HOME 与 Provider HOME
Multica 不只改变 cwd,还为不同 Provider 隔离其发现路径:
- 通用 Task HOME:减少 CLI 向真实 HOME 写入;
- Codex:每任务
CODEX_HOME,受控复制配置、Skills、Session Rollout; - Claude:任务级 Settings Path 与项目
.claude内容; - Cursor:独立 Data/MCP 配置;
- OpenClaw:临时 Config/Gateway Pin;
- Hermes:派生
HERMES_HOME与 Skills Overlay; - 其他 Provider:使用其原生
AGENTS.md、Skill Root 或参数。
“隔离”不等于完全复制真实 HOME。某些 Provider 的登录凭据必须可见,系统用白名单、Symlink 或特定文件播种,尽量只暴露运行所需部分。
8.6 Codex 的特殊隔离
Codex 对配置、Session Rollout、Memory、Skill 与 Sandbox 有较强 HOME 语义,所以相关实现拆成多个文件:
几个关键点:
- Session Store 按 Profile/Agent/Issue 建稳定命名空间;
- 只为要 Resume 的 Session 暴露对应 Rollout,避免看见机器上其他对话;
CodexResumeRolloutPresent在启动前验证恢复资料真的存在;- Shell Environment 使用 Allowlist,显式授权变量优先;
- Linux Workspace-write 对外部 Git Metadata 的限制,促成 Isolated Checkout 模式。
这是一个典型的 Provider-native 隔离层:统一接口不代表所有 Provider 用同一文件布局。
8.7 仓库为什么先做 Bare Cache
Workspace Repo 由 repocache.Cache 管理:
{workspacesRoot}/.repos/{workspaceID}/{host+org+repo.git} # bare clone
│
├── task A worktree
├── task B worktree
└── task C isolated checkout
优点:
- 远端对象只下载一次;
- 每 Task 有独立分支和 Worktree;
- 后续 Fetch 快;
- 不同 Repo 可并行;
- 同一 Bare Repo 的变更操作用专用 Mutex 串行,避免 Git Lock 冲突。
Repo 目录名包含 Host、Namespace、Repo,并对端口做无碰撞编码,避免不同远端静默落到同一个缓存目录。
8.8 为什么 Workdir 一开始是空的
Prepare 明确创建空 Workdir,不自动 Checkout 所有 Workspace Repo。Agent 根据 Brief 使用:
multica repo list
multica repo checkout <url> [--ref ...]
按需 Checkout 的好处:
- 多 Repo Workspace 不必每次展开全部仓库;
- Agent 先读任务再选择目标;
- Review Task 可指定精确 Ref;
- Checkout 权限和允许列表由任务内 CLI 校验;
- Repo 操作统一记录在 Daemon Cache,而不是让 Provider 自己猜目录。
8.9 Worktree 创建细节
CreateWorktree 会:
- 在 Workspace Cache 中查 Bare Repo;
- 持有该 Repo 的 Mutex;
- Fetch 最新远端,失败则警告并可使用缓存快照;
- 解析请求 Ref 或远端默认分支;
- 创建
agent/{sanitized-agent-name}/{short-task-id}分支; - 在 Task Workdir 下创建 Repo 目录;
- 若是复用环境,更新现有 Worktree;
- 排除
.agent_context、CLAUDE.md、AGENTS.md、Provider Skill 目录等注入文件; - 按 Workspace 设置安装/移除 Co-authored-by Hook。
Fetch 使用 remote-tracking Refs,而不是让远端 Head 覆盖 refs/heads/*,避免与 Agent Worktree 锁定的分支冲突。
8.10 Isolated Git Metadata
普通 git worktree 的 .git 指向 Bare Cache 中的外部 Admin Dir。某些 Sandbox(尤其 Linux Codex Workspace-write)即使把 Worktree 标为可写,也可能把解析后的外部 Gitdir 视为只读。
IsolatedGitMetadata 模式创建本地 .git 元数据,仍从 Cache 加速对象,但把需要写的 Git 控制文件放进 Workdir。它牺牲一点空间,换取 Sandbox 内可预测的 Git 写入。
一旦某个复用目录已经迁移为 Isolated Checkout,后续即使旧客户端不再传 Hint,也继续沿用安全形状。
8.11 local_directory:为什么它不是普通 Resource
用户可以把 Project 绑定到某台 Daemon 的已有目录:
{
"resource_type": "local_directory",
"resource_ref": {
"local_path": "/Users/alice/src/project",
"daemon_id": "machine-7"
}
}
此时 Agent 直接在用户目录工作,不创建 Git Worktree。适合:
- 已有未提交修改;
- 私有或无法 Clone 的目录;
- 大型 Monorepo;
- 本地工具链强依赖绝对路径。
代价是风险显著上升:它会触碰用户真实文件,因此需要比普通 Worktree 更强的验证、串行和清理。
8.12 Local Directory 的选择规则
localDirectoryAssignmentForTask 只选择:
resource_type=local_directory;daemon_id与当前 Daemon 相同;- 当前 Project 对该 Daemon 恰好一条匹配记录。
若两条同时匹配,Daemon Fail Fast,不“随便取第一条”。
Squad Leader Task 特意不绑定本地目录,因为 Leader 是协调者,它可能只创建子 Issue/Comment。让 Leader 长时间持有 Repo 锁会阻塞真正写代码的 Worker。
8.13 路径验证
Daemon 对本地路径执行:
- 必须是绝对路径;
- 清理
./..; - 禁止
/、整段$HOME、/Users、/home、系统目录和过宽根目录; - 解析 Symlink 后再次对 Canonical Path 做黑名单检查;
- 必须存在且是 Directory;
- 用实际读写探针验证进程权限。
为什么 Symlink 后还要再检查?
/Users/alice/project/home-link -> /Users/alice
字面路径看起来安全,真实目标却是整个 HOME。锁也必须使用 Real Path 作 Key,否则两个别名会绕过互斥。
8.14 路径锁
LocalPathLocker 是进程内、按 Canonical Path 的锁:
- 第一个 Task 成为 Holder;
- 后续 Task 回调
onWait(holder),将服务端状态改为waiting_local_directory; - 等待支持 Context Cancel;
- 释放函数幂等且清理空 Entry;
- 获锁后才 Start Provider。
锁是 Daemon 内存级的,因为同一 local_directory 被绑定到特定 Daemon。若允许多个 Daemon 访问共享网络盘,还需要分布式文件锁;当前资源模型没有承诺该用法。
8.15 Sidecar Manifest:如何不污染用户目录
Context/Skill 注入会在 Workdir 写文件。对标准 Env,GC 最终删除整个 Root;对本地目录,绝不能 rm -rf 用户 Repo。
系统记录每个自己创建的文件和目录:
- Task Context Marker;
CLAUDE.md/AGENTS.md等 Brief;.agent_context;- Provider Skill 目录;
- Project Resource 文件。
结束或 Reuse 前调用 CleanupSidecars:
- 删除仍与记录匹配的托管文件;
- 对被 Agent/用户填入内容的目录保守保留;
- 不删除未登记文件;
- Manifest 自身也清理。
这是“记录自己写了什么,再精确撤销”,而不是靠文件名猜测。
8.16 环境复用
同一 Agent/Issue 的后续 Task 可尝试复用旧 Workdir:
- 路径必须存在;
- 必须是 Daemon 管理的安全目标;
- Managed Provenance 与 Workspace/Issue/Agent 匹配;
local_directory走自己的共享目录语义,不使用普通 Reuse;- Provider Session 未被 Failure Poison;
- Context、Skills、MCP 必须重新写入,不能沿用旧快照;
- 旧 Sidecars 先回滚,再写新 Manifest。
复用的目的不是节省创建目录时间,而是保留代码修改与 Provider 会话连续性。
8.17 Active Root Guard
GC 与任务启动并发时可能发生:
- Task 预测将使用某个 Env Root;
- GC 同时把它识别为旧目录;
- Prepare 正在写文件时,GC 删除目录。
Daemon 在 Prepare/Run 前 markActiveEnvRoot,GC 删除前必须 reserveEnvRootForGC。两者在锁内仲裁,Active Root 无法被 GC 预留。
Codex Session Store 也有类似 Active/Reserve 防护。
8.18 GC 不是只看 mtime
gc.go 读取 .gc_meta.json,按任务种类询问 Server:
- Issue 是否仍存在、状态是否允许清理;
- Chat Session 是否删除;
- Autopilot Run 是否终态;
- Quick Create/Task 是否仍需保留;
- Server 不可用时是否只能做保守判断。
可能动作包括:
- 保留全部;
- 只清理大 Artifact;
- 删除 Daemon 管理的 Env;
- 对孤儿使用 mtime 兜底。
local_directory Override 永远不删除用户 Workdir;即使父对象 404,也只处理 Env Root 中的托管 Scratch/Sidecar。
8.19 GC Meta 为什么最后写
.gc_meta.json 包含 Kind、关联对象、完成时间、是否 Local Directory 等。它只有在:
- Provider 已结束;
- 消息/用量尽量上报;
- Server 终态已处理;
- Sidecar/结果信息稳定;
之后才写。Meta 的存在表示“可进入业务感知 GC 决策”,不是简单的“目录创建过”。
8.20 本章不变量
- 每个 Task 有独立 Env Root;用户目录只是 Workdir 替换,不归 Daemon 所有。
- Agent Context 与 Provider Home 必须按任务刷新。
- Repo Cache 可共享,对同一 Bare Repo 的变更必须串行;Task Worktree 不共享。
- 本地目录必须经过字面路径和 Canonical Path 双重验证。
- Sidecar 只删除自己登记的内容。
- Active Env/Session Store 不能被 GC。
- GC 先查业务状态,再用时间兜底;本地目录永不整目录删除。
8.21 本章结论
执行环境把“Agent 能看到和能写什么”变成了文件系统级契约:
- 标准 Task 用 Daemon 所有的隔离目录和 Worktree;
- 特殊 Task 可使用用户本地目录,但要验证、互斥和精确回滚;
- Provider 的 HOME、Session、Skill 和 MCP 各自按原生语义隔离;
- 环境可恢复,但恢复必须有 Provenance;
- GC 只清理经过所有权与业务状态证明的内容。
下一章看这些环境最终怎样交给 17 类不同 Agent CLI,并被压成一个统一运行接口。
上一章:Daemon、Runtime 与任务抢占
下一章:Agent 后端与协议适配。