跳到正文
Elaine Blog
返回

Workspace 怎样设计:让输入、修改和交付物都有明确位置

更新于:
Agent Harness

报告助手写出了 summary.md,终端也打印了路径。进程退出后,临时目录被删除,用户下载时只剩一个不存在的文件。生成成功与交付成功之间,还隔着工作区的生命周期。

Workspace 是任务处理文件的环境。模型上下文是本轮请求中的信息;工作区则可以保存比上下文大得多、活得更久的资料。模型只有通过读文件工具获取内容后,才知道其中的字节;文件更新也不会自动修改模型已经看到的旧内容。

先把输入与输出分开

本系列采用下面的教学布局:

run-001/
  inputs/order.json       # 启动时固定的订单快照
  outputs/report.json    # 候选交付物
  state.json             # 控制器保存的进度,不交给模型自由修改
  manifest.json          # 输入摘要、版本与输出约定

这是一份目录约定。Python 创建这些目录,不意味着它们已经成为操作系统级只读挂载。若允许任意 shell 访问同一用户的文件,模型仍可能通过命令修改 inputs/。要强制只读,需要挂载、文件权限或受控工具等实际机制。

Manifest 的作用是让程序知道输入版本、可写范围和预期产物。它必须被执行器使用才有意义。写着 deny_paths: [.git/hooks] 的 YAML,如果没人读取和执行校验,只是一份说明。

固定输入,是为了知道结论基于哪个时刻

任务启动时,A1042 的快照版本是 12,质量核验尚未完成。执行十分钟后业务系统更新为版本 13,核验已经通过。报告根据版本 12 得出“待确认”,不能只因现在的数据库不同就认定生成错误;首先应确认任务要求的是启动时快照,还是提交时最新状态。

前者可以交付,但必须标明时间与版本。后者需要在交付前重新读取,比较版本,并决定重算相关部分。固定快照帮助复现,实时读取帮助保持新鲜,两者解决不同需求。

内容 hash 可以识别输入有没有变化。它并不包含“谁有权访问”这一语义,因此任务恢复时即使 hash 一样,也仍要重新检查当前用户是否有权限。

修改文件为什么需要先比较

编码 Agent 常用两种动作:整文件替换和局部编辑。替换简单,但容易覆盖别人刚改的内容;局部编辑减少影响范围,却可能遇到匹配文本出现多次。

一种可解释的接口要求提交预期旧版本和新内容:执行器读取当前版本,相同才写入,不同则返回 version_conflict。例如 Agent 基于版本 12 编辑报告,另一个用户已经生成版本 13,控制器应让它重新读取并合并,而不是无条件覆盖。

Git 分支或 worktree 可以分离改动,便于审阅与合并,但它们没有隔离进程和网络权限。同一用户启动的 shell 仍可能访问其他目录。文件协作机制和运行安全机制需要分别设计。

原子替换解决哪一种失败

直接覆盖目标文件时,进程可能在写到一半时崩溃。常见方式是先写同目录临时文件,再替换目标路径。观察者通常会看到旧文件或新文件,避免读到半份 JSON。

import os
from pathlib import Path

def replace_report(target: Path, text: str) -> None:
    # 教学前提:可信目录、单个写入者;不用于防御恶意并发修改路径。
    temporary = target.with_suffix(".pending")
    # 使用 UTF-8 明确编码;先把完整内容写入同一文件系统的临时路径。
    with temporary.open("w", encoding="utf-8") as stream:
        stream.write(text)
        stream.flush()
        os.fsync(stream.fileno())
    # replace 替换目录项;它不同时提交数据库进度,也不提供多文件事务。
    os.replace(temporary, target)

断电级持久性还涉及文件系统和目录同步;多个写入者需要唯一临时名、锁或版本比较。这个小函数只解释“不要暴露半份文件”,不能被描述为完整事务系统。配套工程同样明确采用单进程假设。

产物要从工作区进入可交付存储

候选文件通过校验后,应导出到保留时间明确的位置,获得稳定引用,再在最终响应中返回。导出清单只包含允许交付的文件,不能打包整个工作区,否则日志、临时凭证和其他输入可能一起被带走。

导出前需要核对文件类型、大小、实际路径和 hash;导出后应保存对应引用。符号链接可能把看起来位于 outputs/ 的路径指向目录之外,压缩包也可能包含危险路径。仅调用一次 resolve() 仍不能消除不可信进程同时替换目录的竞态,强边界需要操作系统隔离与受控文件访问配合。

报告的“完成”应绑定具体产物版本。若验证的是 hash H1,随后 Agent 把文件改成 H2,旧验证结果不再覆盖新内容。最简单的做法是验证后禁止继续修改该候选,或导出经过验证的同一份字节。

创建和清理也是任务的一部分

完整生命周期包括创建工作区、固定输入、运行、核验、导出、保存恢复引用、清理。清理不能只放在正常返回的最后一行;超时、取消和 Worker 崩溃都会绕过它。生产服务通常需要外部回收器扫描过期资源,并依据任务状态决定保留还是删除。

配套工作区示例下载展示清单和 hash,完整实战将工作目录作为显式参数保留,而不是打印临时路径后立刻清理。运行这些示例仍不等于获得沙箱;真正的隔离见第五篇


分享这篇文章:

上一篇
指令与 Skill 怎样生效:从规则文件到一次模型请求
下一篇
Harness 怎样控制执行:工具、命令、反馈与停止条件