openai-agents-python 沙箱 Workspace Entries 完全指南:从 Dir/File 到多云 Mount 的清单化工作区构建
agents.sandbox.entries 是 openai-agents-python 沙箱体系中定义"工作区内容"的核心模块:一个 Manifest 通过一组 entry 声明沙箱启动时应当拥有的文件、目录、Git 仓库与远程存储挂载。本文将基于该模块的 API 参考文档与仓库源码,逐一剖析 Dir、File、GitRepo、LocalDir、LocalFile、Mount 及 AzureBlobMount、GCSMount、R2Mount、S3Mount、S3FilesMount、BoxMount 等全部成员的设计意图、字段语义与底层实现,帮助你精确构造可移植、可复现、安全可控的沙箱工作区。
Workspace Entries 在沙箱体系中的定位
沙箱 Agent 的核心价值在于"让模型在真实文件系统上工作"。在 沙箱指南 中,Manifest 被定义为"全新沙箱会话的工作区契约"——它声明了工作区 root、文件与目录、需要克隆的 Git 仓库、远程存储挂载、环境变量、用户与组,以及工作区之外的绝对路径访问授权。
这些声明逐条落在 Manifest.entries 上,而 entries 的值全部来自 agents.sandbox.entries 模块。参考文档 docs/ref/sandbox/entries.md 列出的 12 个公开成员正是该模块的全部"内容类型":
| 类别 | Entry | 用途 |
|---|---|---|
| 合成内容 | Dir、File |
在沙箱内直接声明目录与二进制文件内容 |
| 本地来源 | LocalDir、LocalFile |
把 SDK 宿主机器上的目录/文件实体化进沙箱 |
| 代码来源 | GitRepo |
按 tag / branch / commit 拉取远程仓库 |
| 远程存储 | Mount 及 AzureBlobMount、GCSMount、R2Mount、S3Mount、S3FilesMount、BoxMount |
把对象存储/文件存储挂载进工作区 |
entry 的路径语义有一条铁律:Manifest 条目路径是工作区相对的,既不允许绝对路径,也不允许用 .. 逃逸出工作区(见 guide.md 的 Manifest 一节),这使得同一份清单可以在本地、Docker 与托管客户端之间保持可移植。
一切条目的基类:BaseEntry 与公共字段
所有 entry 都继承自 BaseEntry(定义于 src/agents/sandbox/entries/base.py),这是一个基于 pydantic 的抽象模型,其公共字段决定了每个条目在清单中的通用行为:
type: str:条目类型标识。每个子类都必须在type字段上给出非空字符串默认值,模块通过"子类注册表"(_subclass_registry)把字符串与类绑定;BaseEntry.parse()在反序列化时按type字段分派到正确的子类,遇到未知类型会抛出包含全部已注册类型的ValueError(见 base.py)。description: str | None:条目的可选说明,默认None。ephemeral: bool:是否视作临时内容。Mount固定为True——挂载是运行时附加的外部文件系统,不属于持久化工作区状态,因此快照与persist_workspace()都不会包含挂载内容(见 mounts/base.py 的注释说明)。group: Group | User | None:把条目归属到某个清单用户或组,用于配合run_as实现文件级共享规则。is_dir: bool:是否在沙箱文件系统中作为目录对待。Dir、LocalDir、GitRepo、Mount及其子类都覆盖为True。permissions: Permissions:条目在沙箱内的文件权限。默认值是 owner 全部权限(FileMode.ALL),group 与 other 为READ | EXEC(见 base.py),即"属主可读写执行、组与其他可读可执行"。
这些字段最终在 _apply_metadata() 中落盘:如果设置了 group,会先执行 chgrp,随后用八进制模式执行 chmod(见 base.py)。
Permissions 本身存储 owner / group / other 三组位标志以及是否为目录,支持直接构造、Permissions.from_str(...) 从模式字符串解析、Permissions.from_mode(...) 从 OS 模式派生。沙箱指南给出了一个"私有笔记"示例——owner 可读写、组和其他完全无权限:
from agents.sandbox import FileMode, Permissions
from agents.sandbox.entries import File
private_notes = File(
content=b"internal notes",
permissions=Permissions(
owner=FileMode.READ | FileMode.WRITE,
group=FileMode.NONE,
other=FileMode.NONE,
),
)
合成内容:Dir 与 File
Dir(artifacts.py)声明一个沙箱目录,type 固定为 "dir"。它的关键字段是 children: dict[str | Path, BaseEntry]——一个"路径片段 → 条目"的映射,可以在声明目录的同时内联其全部子内容。应用时先 mkdir(parents=True),再以批次方式逐个应用子条目,因此 Dir 天然支持嵌套结构,例如构建一个带任务说明与输出位置的目录树:
from agents.sandbox import Manifest
from agents.sandbox.entries import Dir, File
manifest = Manifest(
entries={
"repo": Dir(
children={
"task.md": File(content=b"# Task spec\nSummarize this workspace."),
"src": Dir(children={"main.py": File(content=b"print('hello')")}),
}
),
"output": Dir(),
}
)
File(artifacts.py)声明一个内容直接内联的沙箱文件,type 固定为 "file",字段为 content: bytes。应用时通过会话的 write() 写入字节流,再套用权限元数据。适合小体积的合成输入、辅助脚本与说明文件;沙箱指南建议把长任务说明放进 repo/task.md 这类工作区文件,保持清单"窄而精"。
本地来源:LocalFile 与 LocalDir
当内容已经存在于 SDK 宿主机上时,使用 LocalFile(artifacts.py)与 LocalDir(artifacts.py)。
LocalFile.src: Path:宿主文件路径,实体化时以 1 MiB 分块流式计算 SHA-256 校验和,返回MaterializedFile(path=dest, sha256=checksum)作为实体化"回执"。LocalDir.src: Path | None:宿主目录路径,src为空时仅创建一个空目录。非空时递归列出目录下所有常规文件并拷贝进沙箱,每个文件同样附带 SHA-256 校验;多文件拷贝通过gather_in_order(..., max_concurrency=session._max_local_dir_file_concurrency)受控并发执行(artifacts.py),并发上限可由SandboxConcurrencyLimits(local_dir_files=...)调节。
src 的解析边界与 extra_path_grants
LocalFile.src 与 LocalDir.src 默认相对于 SDK 进程的工作目录解析,源路径必须位于该基准目录之内,除非被 extra_path_grants 显式授权。授权场景例如 /tmp 的临时工具输出、/opt/toolchain 的只读运行时、或需要实体化的技能目录:
from agents.sandbox import Manifest, SandboxPathGrant
manifest = Manifest(
extra_path_grants=(
SandboxPathGrant(path="/tmp"),
SandboxPathGrant(path="/opt/toolchain", read_only=True),
),
)
注意:extra_path_grants 属于"受信任配置",不应从模型输出等不可信载荷加载;且被授权的路径只是运行时访问权,快照与 persist_workspace() 仍然只包含工作区根目录(guide.md 的 Manifest 一节)。
符号链接与 TOCTOU 防护
LocalDir 的实现值得关注其安全设计:它拒绝符号链接。遍历目录时若 lstat 发现符号链接,直接抛出 LocalDirReadError,并携带 "symlink_not_supported" 原因(artifacts.py)。
在支持 dir_fd 的平台上,它还采用"目录描述符固定"策略:以 os.O_RDONLY | O_DIRECTORY | O_NOFOLLOW 打开目录并逐级下钻,所有子路径操作都基于 fd 完成,从而把"路径在拷贝期间被替换"("path_changed_during_copy")与符号链接攻击的窗口压缩到最小(见 _open_local_dir_src_root_fd 与 _list_local_dir_files_from_dir_fd,artifacts.py)。在不支持 dir_fd 的平台上则回退到基于 lstat 的校验路径(_open_local_dir_file_for_copy_fallback)。
一个把本地数据室目录只读地暴露给分析员用户、同时提供可写输出目录的完整示例见 guide.md 的 Permissions 小节,其核心片段为:
from agents.sandbox import FileMode, Manifest, Permissions, SandboxAgent, SandboxRunConfig, User
from agents.sandbox.entries import Dir, LocalDir
from agents.sandbox.sandboxes.unix_local import UnixLocalSandboxClient
from agents.run import RunConfig
from agents import Runner
analyst = User(name="analyst")
agent = SandboxAgent(
name="Dataroom analyst",
instructions="Review the files in `dataroom/` and write findings to `output/`.",
default_manifest=Manifest(
users=[analyst],
entries={
"dataroom": LocalDir(
src="./dataroom",
group=analyst,
permissions=Permissions(
owner=FileMode.READ | FileMode.EXEC,
group=FileMode.READ | FileMode.EXEC,
other=FileMode.NONE,
),
),
"output": Dir(
group=analyst,
permissions=Permissions(
owner=FileMode.ALL,
group=FileMode.ALL,
other=FileMode.NONE,
),
),
},
),
run_as=analyst,
)
代码来源:GitRepo
GitRepo(artifacts.py)把远程仓库拉取进工作区,字段包括:
host: str = "github.com":Git 托管主机,可覆盖为任意主机。repo: str:形如"owner/name"的仓库路径(或主机特定路径)。ref: str:tag、branch 或 commit SHA。subpath: str | None:只取仓库中的子目录。
ref 的两种拉取路径
实现会先检查 ref 是否匹配 7–40 位十六进制(_COMMIT_REF_RE,见 artifacts.py):
- 若是 commit ref:先
git init+remote add origin+git fetch --depth 1 --no-tags origin <sha>+git checkout --detach FETCH_HEAD;若该流程失败(例如服务端不支持按 SHA fetch),回退到普通命名 ref 克隆。 - 若是 tag/branch:直接
git clone --depth 1 --no-tags --branch <ref> <url> <tmp>。
克隆统一在临时目录 /tmp/sandbox-git-<session_id>-<uuid> 进行,随后用 cp -R 把(可选的)子路径内容复制到目标位置,最后清理临时目录。拉取失败抛 GitCloneError,复制失败抛 GitCopyError,而沙箱镜像缺少 git 时抛 GitMissingInImageError(此时错误上下文会附带镜像名,便于排查镜像选择问题)。
subpath 校验
subpath 不是简单字符串拼接,_validate_subpath()(artifacts.py)会拒绝:空字符串(去空白后)、绝对路径(POSIX 或 Windows 盘符)、包含反斜杠的 Windows 风格路径、以及含 .. 的父目录穿越,全部以 GitSubpathError 报出。仓库内示例 examples/sandbox/docs/coding_task.py 展示了 GitRepo 与 LocalDir 混用的真实清单。
远程存储:Mount 体系
Mount(mounts/base.py)是"把外部存储暴露进沙箱工作区"的抽象基类。它的设计哲学是**"挂载什么"与"如何挂载"解耦**:
Mount及其子类描述要挂载的存储元数据(桶名、账号、凭据、前缀等);mount_strategy描述沙箱后端如何把它挂上去。
mount_strategy:两种挂载策略
策略基类 MountStrategyBase 同样采用 type 分派注册机制("in_container" / "docker_volume"),并提供完整的生命周期钩子:validate_mount、activate、deactivate、teardown_for_snapshot、restore_after_snapshot,以及 Docker 卷驱动的配置生成(mounts/base.py)。
InContainerMountStrategy(type="in_container"):挂载由沙箱容器内部的命令完成,通过pattern: MountPattern指定具体技术。激活前会经过validate_mount_activation_credential_boundary的凭据边界校验(mounts/base.py)。DockerVolumeMountStrategy(type="docker_volume",字段driver与driver_options):卷由宿主容器运行时在会话启动前附加,activate/deactivate对不支持该能力的后端直接抛MountConfigError,支持时则为空操作(因为挂载在会话开始前已完成)(mounts/base.py)。
每种 Mount 子类通过 supported_in_container_patterns() 与 supported_docker_volume_drivers() 声明自己接受哪些模式/驱动;两者皆空时构造直接抛错,且 mount_strategy 与挂载类型不匹配会在校验阶段被拒绝。
Mount 的公共字段
mount_path: Path | None:挂载点在沙箱内的显式路径。绝对路径原样使用;相对路径按"工作区根目录内"解释,从而保证清单在不同后端(根前缀不同)之间可移植(见_resolve_mount_path,mounts/base.py)。read_only: bool = True:默认只读;读写挂载需显式置False。ephemeral: bool = True:固定为临时,快照不包含挂载内容。permissions:对Mount无效——若显式设置非默认权限会触发UserWarning("Mount permissions are not enforced")并回退到默认值,访问控制应交给云厂商侧的权限配置(mounts/base.py)。
四种容器内挂载模式
MountPattern 的四种实现定义在 patterns.py:
FuseMountPattern(type="fuse"):基于blobfuse2,用于 Azure Blob。字段包括cache_type(block_cache/file_cache,默认 block_cache)、cache_size_mb、block_cache_block_size_mb(默认 16)、block_cache_disk_timeout_sec(默认 3600)、file_cache_timeout_sec(默认 120)、attr/entry/negative-entry 缓存超时等。激活时在.sandbox-blobfuse-cache/<session_id>/...下放置缓存、在.sandbox-blobfuse-config/下生成带0600权限的 YAML 配置(cache 目录须在挂载点之外),然后执行blobfuse2 mount(patterns.py)。MountpointMountPattern(type="mountpoint"):基于 AWS 的mount-s3。支持--no-sign-request(无凭据时)、--read-only、--region、--endpoint-url、--prefix;GCS 挂载会附加--upload-checksums off(GCS XML API 不兼容默认校验和流程)。凭据通过写入.sandbox-mountpoint-env/<session_id>/<hash>.env的export脚本注入,错误信息中的敏感值会被替换为REDACTED(patterns.py)。RcloneMountPattern(type="rclone"):基于rclone,支持mode="fuse"(rclone mount --daemon)与mode="nfs"(先rclone serve nfs再内核mount -t nfs,默认挂载选项vers=4.1,tcp,port=2049,soft,timeo=50,retrans=1)。remote_name未指定时按sandbox_<kind>_<session_id>生成确定性名称,保证多挂载互不污染;配置写入.sandbox-rclone-config/<session_id>/下的独立文件(patterns.py)。S3FilesMountPattern(type="s3files"):基于mount.s3files(S3 Express One Zone 文件系统),支持mounttargetip、accesspoint、region等挂载选项,只读时附加ro(patterns.py)。
六大云存储 Provider
Provider 子类把云侧字段翻译成上述模式所需的运行时配置:
S3Mount(providers/s3.py):bucket(必填)、access_key_id、secret_access_key、session_token、prefix、region、endpoint_url、s3_provider(默认"AWS")。支持容器内 rclone/mountpoint 与 Docker 卷驱动 rclone/mountpoint。AzureBlobMount(providers/azure_blob.py):account(必填)、container(必填)、endpoint、identity_client_id(对应AZURE_CLIENT_ID)、account_key(对应AZURE_STORAGE_ACCOUNT_KEY)。支持 rclone 与 fuse(blobfuse2)模式。R2Mount(providers/r2.py):Cloudflare R2 桶,容器内走 rclone。GCSMount:Google Cloud Storage 桶,容器内支持 rclone 与 mountpoint 模式。S3FilesMount:S3 Express One Zone 文件系统挂载,使用s3files模式。BoxMount:Box 云盘挂载。
后端还可通过扩展挂载策略(如 examples/sandbox/extensions/cloudflare_runner.py 中的 CloudflareBucketMountStrategy、Modal 的 ModalCloudBucketMountStrategy)为 R2Mount/S3Mount/GCSMount 提供"原生云桶挂载",见 examples/sandbox/extensions/README.md。
仓库中的读写挂载冒烟示例展示了真实参数组合,例如 S3(s3_mount_read_write.py):
from agents.sandbox.entries import DockerVolumeMountStrategy, S3Mount
mount = S3Mount(
bucket=bucket,
access_key_id=os.getenv("AWS_ACCESS_KEY_ID"),
secret_access_key=os.getenv("AWS_SECRET_ACCESS_KEY"),
session_token=os.getenv("AWS_SESSION_TOKEN"),
prefix=os.getenv("S3_MOUNT_PREFIX"),
region=os.getenv("AWS_REGION") or os.getenv("AWS_DEFAULT_REGION"),
endpoint_url=os.getenv("S3_ENDPOINT_URL"),
mount_strategy=DockerVolumeMountStrategy(driver="rclone"),
read_only=False,
)
以及 Azure Blob(azure_mount_read_write.py):
from agents.sandbox.entries import AzureBlobMount, DockerVolumeMountStrategy
mount = AzureBlobMount(
account=account,
container=container,
endpoint=endpoint,
identity_client_id=identity_client_id,
account_key=account_key,
mount_strategy=DockerVolumeMountStrategy(driver="rclone"),
read_only=False,
)
路径安全与校验:resolve_workspace_path
所有条目路径在实体化前都要经过 resolve_workspace_path()(base.py)的强校验,这是工作区可移植性与安全性的底层保障:
- Windows 绝对路径(如
C:\...)直接拒绝,原因标记为"absolute"; - 绝对路径默认拒绝;
allow_absolute_within_root=True时允许"工作区根内的绝对路径",但仍要求规范化后落在根内,且宿主侧若存在该路径还需通过resolve()相对性检查(防符号链接逃逸); - 相对路径中含
..片段一律拒绝("escape_root"),确保任何条目都无法逃出工作区边界; - 校验失败统一抛
InvalidManifestPathError(定义于 errors.py),错误信息携带具体的拒绝原因。
Manifest 组装(manifest.py)在解析 entries 时即调用该函数,同时为远程挂载维护默认命令白名单(DEFAULT_REMOTE_MOUNT_COMMAND_ALLOWLIST,含 ls、find、cat、grep、cp 等只读/常规命令,见 manifest.py)。
与 Manifest、快照和生命周期的联动
理解 entries 还需看到它上下游的配合:
- 快照语义:
Mount恒为ephemeral=True,LocalDir/GitRepo等实体化内容则属于工作区根内的持久状态,会被快照与persist_workspace()收录;挂载点与挂载生成的.sandbox-*内部目录通过register_persist_workspace_skip_path()排除在外(见 patterns.py 中各模式的实现)。 - 资源控制:大清单或大目录拷贝可通过
SandboxConcurrencyLimits(manifest_entries=..., local_dir_files=...)收紧实体化并行度,任一值设为None即禁用对应限制(guide.md 的 SandboxRunConfig 一节)。 - 能力注入:沙箱 capabilities(如
LocalDirLazySkillSource(source=LocalDir(src=...)))在运行前把技能目录实体化进工作区——此时LocalDir.src指向 SDK 宿主上的目录,而skills_path才是沙箱内的目标位置(guide.md 的 Capabilities 一节)。
参考资源
- API 参考:docs/ref/sandbox/entries.md
- 沙箱指南(Manifest / Permissions / 生命周期):docs/sandbox/guide.md
- 条目实现:src/agents/sandbox/entries/base.py、src/agents/sandbox/entries/artifacts.py
- 挂载实现:src/agents/sandbox/entries/mounts/base.py、src/agents/sandbox/entries/mounts/patterns.py、src/agents/sandbox/entries/mounts/providers/
- 清单组装:src/agents/sandbox/manifest.py
- 实战示例:examples/sandbox/docs/coding_task.py、examples/sandbox/docker/mounts/、examples/sandbox/extensions/cloudflare_runner.py
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0631
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
video-shotcraftAI宣传片skill,使用 Remotion 制作电影级产品视频:提供106 张镜头配方卡和可复用的视频魔板。适用于 Claude Code 与 Codex以及所有其他智能体Markdown00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python09
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00