1. 先搞清楚AI 智能体为什么总能把沙箱跑崩上周有个朋友找我说他的 AI 智能体项目出事了Agent 在沙箱里跑了一段“看似无害”的 Python 脚本结果沙箱彻底失联连宿主机都差点被拖挂最后只能重启整台机器。他问我“E2B 不是号称安全沙箱吗为什么还能失控”这问题我太熟了——我早期接的某数据分析助手项目就是被这种“沙箱失控”反复折磨后来才逐步摸清了 AI 智能体跑代码环境的那些坑。先说结论AI 智能体跑代码和你自己写代码跑脚本完全不是一回事。你自己写代码最多是逻辑错了、崩溃了、报错了但 AI 智能体生成代码是“不确定的”。它可能在某个工具调用里生成一个while True可能往沙箱里下载几个 GB 的模型文件可能突然访问一个外网接口把你的数据带出去可能开着几十个并发进程把 CPU 打满。这些行为不是 AI 故意的而是大模型推理的不确定性带来的“副作用”。所以给 AI 智能体用的沙箱必须有明确的边界、配额和可回滚机制。这不是可选项是刚需。这篇文章我会结合自己在“某自动化分析助手”项目里用 E2B 做沙箱的完整过程把这几件事讲透AI 智能体沙箱“失控”到底有哪几种形态为什么会发生E2B 这类专门给 Agent 设计的执行环境它的隔离和生命周期设计好在哪怎么在 E2B 上搭一个不容易炸的智能体运行环境包括参数配置和代码示例真实运行中碰到的沙箱故障我一步步排查、解决的经验记录。适合谁看正在给 AI 智能体写工具调用、做代码解释器、做自动化数据处理流程的人或者已经被自己搭的沙箱坑过、想找一个更省心方案的人。我尽量不堆术语但涉及关键概念时会把原理说透。2. 沙箱失控的三种典型形态以及失控背后的深层原因2.1 我最早遇到的一次“失控”当时我们做了一个让 AI 自动分析报表的小功能。流程是用户丢一个表格上来Agent 生成 Python 代码去处理再把结果拿回来展示。一开始我图省事直接用宿主机上的 Python 环境去执行 Agent 生成的代码结果第三轮测试就翻车了Agent 生成了一段带os.remove()的“清理临时文件”代码它以为自己删的是临时目录里的文件实际上把项目目录下的一个缓存目录给清掉了。虽然没丢核心数据但给我敲了警钟——AI 生成的代码你永远无法通过“审核”来保证安全。后来换成 Docker 容器跑安全上稍微好一点但又遇到另一个问题Agent 在容器里跑一个死循环CPU 打到 100%容器活活把宿主机拖到 OOM。Docker 本身虽然有资源限制参数但你需要每一项都手动配置好漏一个就出问题。再后来我才意识到Docker 这种通用容器方案本质上是为“你信任的代码”设计的不是为“AI 这个你没法完全信任的执行者”设计的。2.2 我总结的三种典型失控形态这些年和沙箱打交道我把“失控”归纳成三种你可以对照自己的项目看看中了几个资源失控。这是最常见的一种。AI 生成代码时没有“这段代码会耗尽多少资源”的概念。死循环、无限递归、递归生成超大列表、把大文件整个读进内存这些都会瞬间拉满 CPU 或内存。更隐蔽的是磁盘Agent 可能试图下载一个很大的文件或者日志越写越多几分钟就把沙箱的磁盘占满。资源失控的直接后果是沙箱被杀严重时会影响宿主机上其他服务。进程失控。沙箱里的进程不只是“你主动运行的那一个”。Agent 可能用subprocess拉起子进程可能创建常驻服务可能设置定时任务。如果沙箱会话结束时没有把所有子进程都清理干净就会出现僵尸进程或者残留进程。沙箱销毁的时候看起来干净了但如果有共享文件系统或端口残留进程依然可能在宿主层面产生干扰。状态失控。指的是沙箱内部环境的“脏”和“不确定”。比如上次运行留下的临时文件、安装了一半的依赖、被修改过的全局配置、被占用的端口都会污染下一次运行。我见过最典型的问题是Agent 第一次运行时安装了依赖 A 的 1.0 版本第二次运行同一段代码时依赖悄悄变成了 1.1 版本行为完全不一样。对于 Agent 来说它需要的是一个可预期的、干净的执行环境如果沙箱状态不可控输出质量就很难保证。2.3 失控的本质传统隔离思路和 AI 执行需求不匹配为什么失控这么难防因为传统沙箱方案有个默认假设运行代码的人是有意识控制代码的。你手动部署一个服务你会设置端口、资源、环境变量出了问题你会自己修。但 Agent 不会。Agent 只关心“完成这个工具调用”它不关心你机器上还有什么别的服务也不关心资源配额是多少。它的每一个动作都可能踩到边界之外。所以给 Agent 用的沙箱需要的不仅仅是“隔离”还需要三层能力可强制终止不管 Agent 生成了什么我都要能在沙箱层面把它杀掉而且杀得干净。可快速重建出问题之后十几秒内就能拉起一个全新的干净环境而不是花几分钟去修原环境。可审计追踪所有代码执行过程、输入输出、资源消耗都有记录出了问题能定位。这三点传统的“代码审核 手动隔离”很难同时满足。E2B 这类专门为 AI 设计的执行环境恰恰就是围绕这三层能力来设计的。这也是为什么我后来把整个 Agent 代码执行层从“自制沙箱方案”迁移到 E2B 上。3. E2B 的执行环境设计它到底怎么做到“不易失控”3.1 E2B 的核心思路把运行环境当成一次性用品E2B 给 Agent 提供的不是一个持久化的服务器而是一个“随用随建、用完即弃”的隔离环境。它的核心抽象就叫 Sandbox你可以把它理解成一个轻量级的虚拟机但专门为代码执行优化过。每个 Sandbox 是独立的文件系统、独立的进程空间、独立的网络栈彼此之间完全隔离。关键点来了E2B 的沙箱不是容器而是基于轻量级虚拟化技术构建的“微型虚拟机”。容器虽然也能隔离但容器共享宿主内核如果内核本身有漏洞或者某个挂载点配置不当隔离就可能被突破。微型虚拟机多了一层硬件虚拟化边界Agent 在沙箱里跑的任何代码都只能接触到虚拟出来的 CPU、内存、磁盘物理层面和宿主机的边界更干净。这种感觉有点像容器是把大家安排在合租公寓里门锁再结实也共用走廊微型虚拟机是给每个人一套独立公寓上下水电气全部独立入户。3.2 快照恢复机制让“失控”变成可回滚的普通事件E2B 另一个让我觉得“这钱花得值”的设计是快照Snapshot。沙箱运行过程中你随时可以把它当前的文件系统状态保存成一个快照下一次创建沙箱时直接基于这个快照恢复。恢复一个快照的启动时间能做到非常短几百毫秒到一秒左右基本可以当作“秒级重建”。这个能力对 AI 智能体特别重要因为它解决了一个长期困扰我的问题Agent 执行任务需要一定的状态比如已经装好的依赖、已经下载的模型文件、已经配置好的环境变量。但如果保留同一个沙箱让 Agent 反复使用环境必然会越用越脏。快照机制给了一个折中方案把一个“既有状态又干净”的环境保存下来每次运行都从同一份干净状态开始。我自己的用法是每次跑 Agent 的代码前先从一个“干净基础镜像 项目依赖已安装”的快照创建一个新沙箱完成任务后直接销毁。这样既避免了每次重新安装依赖的耗时又保证了每次执行的环境一致性。多跑几个任务每个任务一个独立沙箱哪个出问题了就丢哪个完全不影响其他任务。3.3 资源配额与执行控制给 Agent 装“刹车”E2B 的沙箱在创建时就可以指定 CPU 和内存上限。它底层用的是轻量级虚拟机的资源调度机制所以这个限制不是“软限制”而是真正在虚拟化层面对 CPU 时间和内存使用的硬隔离。你给一个沙箱分配 1 核 512MB 内存那它在沙箱里把 CPU 打爆也只是这一个沙箱打爆对宿主机和其他沙箱没有影响。命令执行层面E2B 的 SDK 也支持超时控制。你可以在执行一个命令时带上超时参数如果 Agent 生成的代码在指定时间内没有跑完调用就会返回超时错误你可以在上层决定是重试还是放弃。我在项目里几乎给所有 Agent 工具调用都加了超时这是防止进程失控的第一道保险。还有一个容易被忽略的点沙箱外部的网络策略。E2B 允许你配置沙箱是否可以访问外网以及是否允许外部访问沙箱内的服务。如果 Agent 只需要处理本地数据我会直接在模板里把外网访问关掉这样它就算想往外传数据也传不出去。这个“默认拒绝”的思路比事后去分析流量要省心太多。3.4 为什么不用自己封装的执行服务在转用 E2B 之前我试过两条路。第一条路是直接用 Docker 跑 Agent 生成的代码自己封装一个 HTTP 服务前端把代码传过来后端起一个容器执行。这条路的问题在于容器启动开销大高并发时特别明显资源限制要自己一项一项调容器内部的文件系统要自己挂载、清理还要考虑容器逃逸的防御安全配置相当繁琐。第二条路是直接用虚拟机但启动一个完整虚拟机要几十秒甚至几分钟Agent 每次工具调用等这么久用户早就没耐心了。E2B 的价值在于它把“轻量”“隔离”“可重建”这几件事一次性封装好了并且提供了简单的 Python/JavaScript SDK。我不需要自己研究虚拟化技术不需要自己处理快照、恢复、网络策略只需要调用一个工具类接口就可以拿到一个可用的执行环境。这就是它作为“AI 智能体执行基础设施”的意义。4. 实战在 E2B 上搭一个不容易炸的智能体代码执行环境4.1 基础准备SDK 安装和环境配置正式写代码之前先说明一下依赖前提你需要先有一个 E2B 的账号和对应的 API Key这个 Key 用来在服务端认证你的调用请求。另外E2B 的沙箱运行环境是云端的所以你的本机需要有对外访问网络的能力这个就不展开了只要网络连通性没问题即可。E2B 提供了 Python 和 JavaScript 两种主流 SDK我自己用的是 Python以下示例都基于 Python 写法。安装很简单pip install e2b初始化连接就一行代码SDK 会从环境变量里读取 API Keyimport os from e2b import Sandbox os.environ[E2B_API_KEY] 你的_API_KEY # 也可以不设环境变量直接把 key 传给 Sandbox 构造函数提示API Key 属于敏感凭证建议通过服务端的环境变量注入不要写进代码仓库更不要放在前端页面里。4.2 创建沙箱从最小的可用环境开始创建一个默认沙箱非常简单from e2b import Sandbox # 创建默认沙箱 sb Sandbox() print(sb.id)这一步实际上是在 E2B 云端创建了一个轻量级虚拟机实例默认模板一般自带 Python、Node 等几个常用运行时。你不需要关心它底层是怎么启动的SDK 返回之后就可以直接用了。但这个默认沙箱有一个问题没用资源限制。如果你就在生产环境里这么裸跑一个 Agent那失控的概率还是挺高的。所以我建议创建沙箱时就把资源和超时参数定死from e2b import Sandbox sb Sandbox( cpus1, # 1 核 CPU memory_mb512, # 512MB 内存 timeout60, # 沙箱总超时 60 秒 )这里有一个容易踩的坑timeout参数是沙箱的最大存活时间不是单条命令的超时时间。如果一个沙箱设置了 60 秒超时那不管里面跑什么代码到了 60 秒沙箱都会被回收。所以这个参数要根据你任务的实际耗时来设置不能拍脑袋设一个很小的数否则任务还没跑完沙箱就先被销毁了。4.3 在沙箱里跑命令一个带刹车的执行函数沙箱创建好之后最常用的操作就是执行命令。E2B SDK 的commands.run可以直接执行 shell 命令也可以直接跑 Python 代码。基础用法是# 执行一段 shell 命令 result sb.commands.run(python3 --version) print(result.stdout) # 执行 Python 代码 code data [i*i for i in range(10)] print(data) result sb.commands.run(fpython3 -c {code}) print(result.stdout)但如果你像我一样要把这段逻辑封装给 Agent 当工具调用不能这么裸用。我给 Agent 封装一个execute_code_in_sandbox工具加上了超时和输出截断这样 Agent 再怎么乱来到我这一层就能给它“刹车”import time from e2b import Sandbox def execute_code_in_sandbox(code: str, timeout: int 30) - str: 在 E2B 沙箱中执行 Python 代码带超时和输出限制 sb Sandbox( cpus1, memory_mb256, timeouttimeout 10, # 沙箱总超时比命令超时留一点余量 ) try: start time.time() # 命令超时由 timeout 控制防止恶意死循环 result sb.commands.run( fpython3 -c {code!r}, timeouttimeout, ) # 限制返回给 Agent 的输出长度防止刷屏 output result.stdout[-2000:] return output except Exception as e: return f执行异常: {type(e).__name__}: {e} finally: # 任务结束立即销毁沙箱 sb.close()几个关键点命令超时timeout参数可以保证单个命令最多跑指定秒数。如果 Agent 生成死循环命令会在超时后被强制中断沙箱本身也还在资源限制内运作不会影响宿主机。输出截断AI 生成的代码经常会有大量打印输出如果全量返回给模型既浪费 token 又影响模型判断。截断到最近 2000 字符足够模型了解运行结果了。及时关闭sb.close()放在finally里保证无论执行成功还是失败沙箱最后都会被销毁不会遗留资源。注意这个示例中每条命令都新建一个沙箱比较安全但也会增加每次调用的启动开销。如果你的 Agent 工具调用频率很高可以考虑用“沙箱复用”策略后面我会详细讲。4.4 文件系统与数据持久化怎么把结果拿出来AI 执行任务经常不只是打印文本它可能会生成一个 CSV 文件、一张图片、一个模型结果。E2B 的沙箱有独立的文件系统你可以直接通过 SDK 读写沙箱内的文件# 写入文件到沙箱 sb.files.write(/tmp/raw_data.csv, id,name\n1,alice\n2,bob) # 读取沙箱内文件内容 data sb.files.read(/tmp/raw_data.csv) print(data) # 列出目录 entries sb.files.list(/tmp) print(entries)如果是大文件或者 Agent 在沙箱里生成了图表文件可以这样把它拉出来# 把沙箱内的文件保存到本地 with open(output.png, wb) as f: sb.files.download(/tmp/chart.png, f)这里我吃过一次亏一开始我以为沙箱是持久化的写完文件后没有及时下载结果下一次创建新沙箱时文件全没了。后来我养成了一个习惯——所有需要保留的数据都在沙箱关闭之前同步到外部存储。E2B 沙箱本身是设计成“用完即弃”的你不能指望它的文件系统来承载核心业务数据。如果你确实需要“沙箱销毁后某些环境状态还在”正确的做法是做成模板或快照把安装好依赖的沙箱保存为模板后续创建沙箱时直接指定这个模板把某次运行后的文件系统状态保存为快照下次从快照恢复。快照保存的逻辑我通常放在任务成功之后一次性做完# 任务执行完成把沙箱当前状态保存为快照 snapshot_id sb.snapshot() print(f快照 ID: {snapshot_id}) # 下次创建沙箱时基于这个快照恢复 sb2 Sandbox(snapshotsnapshot_id)快照机制特别适合“预装好环境再跑 Agent”的场景你先在自己本地或临时沙箱里把依赖装好、连好网络代理配置、写好初始化脚本然后保存成快照。之后每次跑 Agent直接从快照恢复几百毫秒就能拿到一个“装好一切但完全干净”的环境一举解决状态失控问题。4.5 模板定制预装依赖减少重复劳动如果你的 Agent 每次都要用到pandas、requests这些库每次都现场pip install既慢又容易失败。E2B 支持构造自定义模板把依赖提前装好。做法大致是先创建一个沙箱在沙箱里安装所有依赖然后把沙箱保存成模板。后续创建沙箱时指定这个模板名即可。# 第一步在沙箱里安装依赖 setup_sb Sandbox() setup_sb.commands.run(pip install pandas requests matplotlib) # 第二步保存为模板 template_id setup_sb.save_template(data-analysis-v1) # 第三步后续使用这个模板创建沙箱 sb Sandbox(templatedata-analysis-v1)模板机制的好处不仅仅是省时间更重要的是可复现性。依赖版本、系统配置、初始化脚本都固化在模板里了Agent 每次运行面对的都是同一个环境基础。“环境不一致导致的问题”就能被彻底消除。4.6 把沙箱工具接进 Agent 的工具调用流程最后一步是把前面的执行函数接进 Agent 的工具调用循环。这一步比较模式化主要看你的框架怎么定义 tool。一般套路是定义一个 tool schema 给模型看模型决定调用时传参你的执行逻辑收到参数后去跑沙箱。一个简化版的工具定义长这样用 JSON schema 描述{ name: execute_python_code, description: 在安全沙箱中执行 Python 代码适合数据分析、数据处理、文件操作等任务代码会在隔离环境中运行。, parameters: { type: object, properties: { code: { type: string, description: 要执行的 Python 代码 }, timeout: { type: integer, description: 执行超时秒数默认 30 } }, required: [code] } }然后你在 Agent 收到工具调用请求时将参数传给execute_code_in_sandbox把结果回传给模型。这里我没有绑定任何特定框架因为工具调用接口各家大同小异核心就是“模型产生工具调用参数 → 你执行 → 回传结果”。我建议在回传结果时加上一个标记比如输出是否被截断、执行耗时多少。这样模型会知道“输出不完整”从而可以自行决定下一步动作而不是误会结果已经结束。5. 沙箱故障排查实录那些让我半夜起来处理的“失控”时刻5.1 现象一沙箱连接直接超时不知是死循环还是网络问题有一次线上 Agent 工具调用突然大面积返回超时看起来像是沙箱不可用但其实原因在代码逻辑上Agent 生成了一个死循环命令超时后 SDK 抛错了但我没有在上层重试或降级导致整个工具调用失败。排查的时候我是分三步做的先看是不是 E2B 服务端问题——用最简单的方式创建一个沙箱执行echo hello能通就说明平台没问题再看是不是单个命令卡住——给命令加较短超时比如 5 秒如果 5 秒内能返回超时错误说明沙箱本身是活的只是命令卡住了最后看是不是 Agent 生成的代码有问题——把这次工具调用的入参 code 抽出来人工本地跑一遍确认它是不是死循环。最终定位就是 Agent 生成代码里的while True循环。其实这类问题没有办法完全避免因为大模型的代码生成带有随机性偶尔出现循环太正常了。关键是在执行层把超时机制做好并且在上层做好重试策略。我的对策是给所有 Agent 工具调用统一加异常捕获和重试。一次失败后让模型重新生成一段代码再跑一次通常第二次就能成功。如果连续失败三次就返回错误给用户而不是无限重试消耗资源。5.2 现象二沙箱关了但进程好像还在有一次我发现某个沙箱虽然已经close()了但 E2B 控制台里显示这个 ID 仍然有资源占用。排查了下问题出在我把close()放在了异常处理的一个分支里另一个分支漏掉了。也就是说异常发生时沙箱没有被销毁导致它在云端一直挂着直到自身超时被回收。这件事之后我给自己定了一个纪律沙箱的关闭操作必须写在finally块里或者用上下文管理器。SDK 本身是支持上下文管理语法来使用的with Sandbox(timeout60) as sb: result sb.commands.run(python3 -c print(1))这个写法可以保证无论代码块里发生什么沙箱都会在退出时被自动回收。如果你用旧式写法一定要检查每个return分支是否都执行了close()。5.3 现象三并发跑 Agent沙箱数量爆了另一个让我头疼的问题是并发。早期我在一个服务里同时放了十几个 Agent 任务每个任务创建自己的沙箱结果一上线云端沙箱数量瞬间飙升触发平台的沙箱数量限制大批任务失败。复盘之后我发现问题不在于“并发需要沙箱”而在于我没有对沙箱做复用和池化。有些任务只是需要跑一段短代码完全没必要每次新建沙箱。我的调整方案是用一个简单的池化策略预先创建几个可复用的沙箱任务来了直接从池里取高耗时、高风险的任务用独立沙箱短小、安全的工具调用用共享沙箱池为沙箱池设置一个最大数量上限防止无脑扩容。池化的实现不复杂但要注意一点共享沙箱里的环境会被多个任务污染所以只适合本身就不依赖状态的代码。如果你的任务需要干净环境还是老老实实新建沙箱吧。5.4 现象四沙箱里拿不到数据文件“丢”了有一个阶段我频繁遇到沙箱里生成的文件不见了。排查后我发现并不是文件真的丢了而是我把它写在了沙箱内存文件系统的一个临时路径上沙箱销毁后自然就没了。后来我改用/tmp和项目工作目录之前先确认这个目录是否会被沙箱重置机制清理。更稳妥的办法是**所有需要跨任务保留的数据都不放在沙箱里放在外部的对象存储或数据库里。**沙箱只负责“计算和临时加工”不负责“存储”。你可以用 SDK 把文件从沙箱拉回到本地服务再转发到持久化存储也可以让 Agent 直接通过外部 API 把数据写进数据库。这样就算沙箱被销毁数据也还在。5.5 沙箱故障速查表我把上面各种故障整理成一个速查表方便你直接参考现象可能原因排查步骤对策连接超时死循环、命令执行时间过长1. 创建沙箱执行简单命令探活2. 单独执行卡住的命令3. 人工审查 Agent 入参代码给命令加超时失败后让模型重写代码沙箱资源占用不释放close()未执行到检查每个异常分支是否都关闭了沙箱使用finally或上下文管理器并发任务失败沙箱数量超过平台配额查看任务并发数、沙箱创建频率沙箱池化、设置并发上限沙箱文件丢失文件写到临时路径且沙箱已销毁检查文件路径、沙箱生命周期数据落外部存储沙箱内只做临时计算沙箱执行结果不完整输出被截断、命令超时查看输出长度和超时返回码截断输出并告知模型或提高超时上限Agent 生成代码访问外网沙箱未配置网络隔离检查沙箱模板的网络策略对不需要外网的任务配置禁止外网访问6. 少踩坑的方法我跑 Agent 沙箱的几条习惯最后分享几个这段时间摸索出来的习惯不一定多高级但确实比较实用。第一短生命周期沙箱优先。能不复用就不复用。一个 Agent 任务从创建沙箱到销毁沙箱生命周期控制在几十秒到几分钟是最舒服的。一旦沙箱存活时间太长状态污染、资源泄漏这些问题都会冒出来。你可能会觉得每次都重新建沙箱很浪费但实际上 E2B 的快照恢复速度非常快整体成本远低于你花时间去维护常驻沙箱。第二资源上限宁可小一点。给沙箱分配 1 核 512MB确实可能让某些大任务跑得慢一点但换来的是隔离的确定性。很多 AI 生成的代码其实不需要那么大内存真正需要大内存的场景比如加载大模型做推理也不适合放在通用执行沙箱里而是应该单独规划一个“推理专用环境”。第三所有执行都要有审计记录。我每次执行 Agent 工具调用时都会把代码、参数、输出前几百字符、耗时、沙箱 ID 一起存进日志。出了纠纷或者问题回溯起来非常方便。之前有一次客户质疑某个处理结果我靠日志里的执行记录直接给出了完整链路几句话就澄清了。第四对 AI 生成的代码保持“有罪推定”。不要觉得“这次模型生成得挺正常就放松警惕”。任何代码进沙箱之前都当它是不可信的。不做“先跑起来再说”的决定默认关闭不需要的权限默认限制网络和资源。有罪推定虽然听起来反直觉但确实是跑 AI 智能体最稳妥的心理建设。这些习惯帮我躲过了不少坑尤其是“沙箱失控”这类的故障。现在再遇到 Agent 跑崩我的第一反应已经不是慌而是按部就班地看超时、看资源、看审计日志定位问题然后重建沙箱。如果你也在做类似的智能体项目希望这套思路能给你一些参考。