资讯详情

资讯详情

OpenHands Runtime深度解析:AI智能体的执行沙箱与隔离机制

1. 为什么说 Runtime 是整个 OpenHands 的“地基”从标题里看到 Runtime 这个词,我不由自主想起第一次跑通 OpenHands(当时还叫 OpenDevin)时的场景。代码仓库拉下来,依赖装好,第一条指令发出去,眼看着它在终端里噼里啪啦地建目录、装环境、写文件,我第一反应是:这家伙到底在哪儿跑的? 后来才明白,所有你能看到的“智能行为”,底层都依赖一个叫 Runtime 的组件,它是智能体真正干活的那双手和那个身体。如果你用过 ChatGPT 的代码解释器,或者 GitHub Copilot 的云端沙箱,应该能更快理解这个抽象。OpenHands 里的 Runtime 本质上就是给 AI 智能体提供一个隔离的、可交互的、具备文件系统与命令执行能力的运行环境。没有它,大模型只是一个能说话的“大脑”,而有了它,大脑才能真正动手改代码、跑测试、部署服务,也就是说,它是把一个“聊天机器人”升级成“数字员工”的那一层关键基础设施。这篇文章处在“拆解 OpenHands”系列的第十篇,前面几篇已经陆续聊过整体架构、Agent 循环、事件流、Llama 集成等。这次我们把视角单独拉出来,专注看 Runtime:它为什么设计成独立服务?它内部的 Action/Observation 模型是怎么运转的?Docker 沙箱与远程执行节点之间又是什么关系?我也会结合自己在本地和服务器上实际部署时踩过的坑,给出一些配置建议和调试思路。读这篇文章的人,我默认你至少对 OpenHands 的基础概念有过了解,知道它能拿自然语言去操作电脑、写代码。如果你完全没接触过,建议先从系列前几篇里挑一篇扫一眼框架图,再回来看本文,会有更完整的体感。2. Runtime 的本质:从“对话”到“执行”的那座桥我对 Runtime 的定义,可以用一句话概括:它是承载智能体所有行为的“虚拟机”。这里说的“虚拟机”不是一个严格意义的 hypervisor,而是一个逻辑边界。在 OpenHands 的体系里,这个边界通常由 Docker 容器来实现,容器里有一整套文件系统、预装好的编程环境、命令行工具,以及专门为智能体定制的 API 接口。如果没有 Runtime,大模型每一步推理出来的 Action 都只是一段 JSON 文本,它不能真正在用户的电脑上执行任何代码。有了 Runtime,这些 Action 才能被翻译成真实世界里的操作。更关键的是,Runtime 提供了隔离:智能体在容器里怎么折腾,都不会影响宿主机的稳定和安全。这在自动化编程场景里是生死攸关的——你敢让一个可能被 prompt 注入的智能体直接在你的生产环境上自由操作吗?显然不敢。这也是我为什么一直强调 Runtime 是 OpenHands 的“地基”。回顾我们系列的脉络:Agent 负责做决策,EventStream 负责做通信,而 Runtime 则负责落地。三者拼起来,才构成一个完整的人机协作闭环。这里的 Runtime 不是你常听到的 JavaScript Runtime(Node.js)或 .NET Runtime,它更像是一个“代理运行容器”,一个专门为 AI 操作提供场所的沙箱环境。2.1 Runtime、Agent、EventStream 三者的协作关系想要真正吃透 Runtime,不能孤立地看它自己。在 OpenHands 的架构里,一次任务的完整链路是这样走的:用户通过 CLI 或 Web 界面发出指令。Agent 接收指令,并结合当前状态做出推理,决定下一步动作。Agent 输出一个 Action,把它发布到 EventStream 上。Runtime 从 EventStream 订阅到这个 Action,在容器里执行对应的操作。Runtime 执行完毕后产生一个 Observation,同样发布到 EventStream。Agent 获取 Observation,更新自己的状态,继续规划下一步。这里面有一个很微妙的设计:Agent 和 Runtime 并不直接通信,它们之间隔着一个 EventStream。这有点像微服务架构里常见的消息队列解耦思想。好处很明显:Agent 可以在完全不同的机器上运行,Runtime 也可以在集群的某个节点上运行,只要它们能连接到同一个 EventStream,就能无缝协作。这个设计还带来一个额外收益——日志追踪变得非常方便。由于所有 Action 和 Observation 都经过 EventStream,你可以完整回放智能体的每一个操作。我在实测时经常用这个能力排查问题,比如某次任务失败,回头去翻事件流,能精准定位到是哪条命令返回了非零退出码。2.2 为什么不像 ChatGPT 插件那样直接塞进主进程?你可能会问:为什么非要搞一个独立的 Runtime,而不是把执行逻辑直接嵌在主进程里?这样部署不是更简单吗?我的理解是,OpenHands 从一开始就瞄准了“远程操作”和“多环境支持”这两个目标。如果把执行逻辑塞进主进程,智能体就只能在本机执行,而且会与主进程的生命周期强绑定。一旦主进程崩溃,所有正在运行的任务都会丢失。独立 Runtime 带来的灵活度是巨大的:你可以在本地开发机上跑 Agent,在远程服务器上跑 Runtime。一个 Agent 实例可以对应多个 Runtime,分别对应不同的任务环境。Runtime 可以像容器一样被随时创建、销毁、重置,做到完全的隔离。未来如果要做大规模并行任务调度,只需要按需横向扩展 Runtime 实例,主进程不需要做任何改造。这些特性对于一个面向“AI 软件工程师”的产品来说,不是锦上添花,而是刚需。想象一下,用户给你一个 GitHub 仓库,你要在隔离环境里把依赖装好、把测试跑通、把问题改完,最后把变更提交成一个 Pull Request。这整个流程要是没有独立的 Runtime,几乎没法安全、可靠地完成。3. 核心机制拆解:Action 进来,Observation 出去Runtime 最核心的循环就是:接收 Action → 执行 → 产出 Observation。我直接说代码层面更真实。在 OpenHands 的 TypeScript 代码库里,Action 是通过ActionType来区分的。早期版本里有十几个 Action 类型,像CreateFileAction、EditFileAction、RunCommandAction、BrowseAction。后来经过几次重构,这些类型被收敛成了一个更通用的抽象——DelegateAction,配合一个ActionExecution接口。3.1 ActionExecutor 与 ObservationProducerRuntime 内部定义了两个关键接口。第一个是ActionExecutorT extends Action,负责执行某种类型的 Action;第二个是ObservationProducerT extends Observation,负责把执行结果包装成 Observation 并发布出去。ActionExecutor本质上就是一个策略模式:每种 Action 类型对应一个执行策略。比如:执行 Shell 命令 → 在容器里调用/bin/bash -c。读写文件 → 通过容器的文件系统 API 操作。浏览网页 → 调用容器里的浏览器工具。而ObservationProducer则把执行结果打包。对于命令执行,它会记录退出码、stdout、stderr,这些信息会完整地回传给 Agent,作为它下一步决策的依据。这里有个细节值得注意:Observation 不一定只包含“成功”的信息。当命令执行失败时,错误信息同样要被完整地、结构化地传递给 Agent,因为很多时候失败本身就是最有价值的信号。Agent 会根据这个错误信息去调整自己的策略,比如换个命令重试。3.2 BashAction 是怎么被执行的我们来具体看 BashAction 的执行过程,这是最常用到的一类 Action。当 Agent 决定要执行一条 Shell 命令时,它会构造一个 BashAction,内容大致是:interface BashAction extends Action { type: BashAction; command: string; timeout?: number; // 可选,单位毫秒 }Runtime 收到这个 Action 后,会做这样几件事:检查容器的状态,确保它还活着并且可执行。把命令写入一个临时脚本文件,避免一些复杂的引号转义问题。在容器内启动一个 bash 会话,执行这个脚本。实时捕获 stdout、stderr 输出。命令执行结束(或超时)后,把输出和退出码打包成 BashObservation,发布回事件流。这里面的超时控制特别关键。默认时间通常设置在 120 秒左右,但这不是拍脑袋定的:太短,大型编译任务经常跑不完;太长,遇到死循环或恶意命令时会卡住整个 Agent 循环。我一般会在跑重型任务前,手动给命令加上timeout参数,宁可拆分多步执行,也不要让单条命令跑超过 10 分钟。const observation await runtime.runAction(bashAction); // observation 里包含 output、exitCode、executionTime 等字段 console.log(命令退出码: ${observation.exitCode}); console.log(标准输出: ${observation.output});3.3 事件流与 Action/Obversation 的闭环如果你打开 OpenHands 的 Agent 类代码,会看到agent.step()这个函数:它接收一个 Observation,返回一个 Action。是的,Agent 本身就是一个“观察 → 决策 → 行动”的循环器。而 Runtime 则是这个循环的另一半:“行动 → 观察 → 结果反馈”。两者通过 EventStream 连接,形成完整的闭环。有一次我尝试改造一个自定义 Agent,想让它在执行命令前先检查工作目录是否存在。我直接在 Agent 的nextAction逻辑里加了一步判断,虽然逻辑上没毛病,但很快发现一个问题:Agent 拿到的 Observation 往往是纯文本形式的,它需要自己从文本里解析目录是否存在,非常不稳定。后来我改成让 Runtime 的 BashAction 执行时,额外注入一些环境探测命令,把结果也一并返回,大幅提高了判断准确率。这个经验让我意识到,Action/Observation 的边界其实是有弹性的,你不必死守官方设计的字段,完全可以根据自己的业务需求去扩展。4. 沙箱与隔离:Runtime 的安全底座OpenHands Runtime 最常见的实现是 Docker 沙箱。这也是业界最常见的做法——给每个任务分配一个独立的容器,任务结束就销毁,下次任务再来就重新创建,从源头避免状态污染。4.1 Docker Runtime 的工作流程从架构上看,Docker Runtime 分两段:一段是宿主机上的“服务端”,负责创建和管理容器;另一段是容器内部的“客户端”,负责在容器里执行具体的命令。服务端启动时会做这几件事:拉取或构建一个定制的 Docker 镜像,里面预装 Python、Node.js、Git、常用 CLI 工具等。给容器设置内存/CPU 限制(默认比如内存 4096 MiB,CPU 4 核)。把宿主机的项目代码(workspace)挂载到容器里的/workspace目录。启动一个轻量级的 WebSocket 服务或 HTTP 服务,等待上层的 Action 请求。容器内部的长驻进程是核心。它会监听来自服务端的指令,收到 BashAction 后就在容器内起一个子进程来执行,然后把结果回传。这个“客户端”就是 Runtime 的“手脚”,也是我们下一节要展开的关键设计。我记得第一次手动进入容器里看环境时,发现镜像里已经预装了挺多东西,比如 Python、curl、git 等,但网络访问是受限的。OpenHands 默认会把容器的网络打开,DNS 配置指向宿主机,这样容器内的命令可以访问外网下载依赖。如果你的企业内网环境比较严格,这个地方可能需要额外配置代理。4.2 对宿主机与外部网络的安全隔离边界很多第一次接触 OpenHands 的人会担心:智能体在里面装依赖、改文件,会不会把宿主机搞坏?这个担心是合理的,但 Docker 容器已经把风险降到了一个可以接受的范围。容器拥有独立的文件系统、进程空间和网络命名空间,你在容器里运行rm -rf /也只会破坏容器内部,不会波及宿主机。不过要注意,如果容器启动时挂载了宿主机的目录(尤其是根目录),隔离就会被破坏。所以我的建议是:绝对不要把宿主机上的敏感目录挂载进容器,只挂载一个专门用于任务的工作目录。还有一个实际踩过的坑是关于容器能力的。早期版本里,容器默认是不带--privileged的,这是对的;但如果你的任务需要挂载 FUSE 文件系统或修改系统级配置,就需要额外开启一些 capabilities。我测试过给容器加--cap-add SYS_ADMIN来支持某些调试工具,能跑通,但提醒一句:这会让隔离性大打折扣,只建议在完全受控的本地环境里试。4.3 多实例并行与资源管理策略如果你想把 OpenHands 跑成一个团队级的服务,那资源管理是绕不开的课题。每个 Runtime 容器默认吃掉的内存可能会让单机扛不住,尤其是 Agent 会同时跑多个任务。此时需要按场景调整容器的配额:CPU 配额:默认 4 核,跑重型编译可以调高到 8 核,但如果宿主机就 8 核,两个任务同时跑就直接把机器卡死。内存配额:默认 4096 MiB,Node.js 项目的npm install经常吃满,建议调高到 64 GiB 的机器上可以放 8 个 8 GiB 的容器。磁盘配额:默认镜像是精简的,但任务里如果构建大型依赖,镜像层会膨胀。建议给 Docker 的>git clone https://github.com/All-Hands-AI/OpenHands.git cd OpenHands npm install npm run build这里有个小经验:如果你在中国大陆的网络环境,npm install大概率会卡在某些包上。我建议把 npm 源切到国内镜像:npm config set registry https://registry.npmmirror.com。Docker 镜像的拉取也可以配置 registry-mirror,能省下大量时间。5.2 创建你的第一个 Runtime 实例在 OpenHands 的入口脚本里,一般会通过环境变量来配置 Runtime 的类型和参数:cd OpenHands npx openhands run --runtime docker --workspace /path/to/your/project这里指定了--runtime docker,OpenHands 会自动检查本地 Docker 环境,拉取 openhands-runtime 镜像,然后用你的/path/to/your/project作为工作目录挂载进容器。如果你是纯 API 调用方式,也可以直接在代码里初始化:import { DockerRuntime, EventStream } from openhands; const eventStream new EventStream(); const runtime new DockerRuntime({ workspace: /path/to/your/project, resourceLimits: { cpu: 4, memory: 8GiB } }); await runtime.connect(); eventStream.registerRuntime(runtime);在connect()里,Runtime 会完成三件事:拉取镜像、启动容器、建立通信通道。如果你看到终端输出类似Runtime container started with ID: abc123,就说明容器已经跑起来了。5.3 容器内的环境定制与依赖预装默认镜像里的工具链是有限的。如果你需要 Python 的某些包、Node 的某些全局模块,不可能每次让智能体现装,那样太慢。我的做法是:在基础镜像之上自己打一层。打开runtime/Dockerfile这类文件,在后面追加你的定制:FROM ghcr.io/openhands/openhands-runtime:latest # 安装全局工具 RUN apt-get update apt-get install -y jq htop RUN pip install --no-cache-dir ruff black pandas # 自定义工作目录权限 RUN chown -R openhands:openhands /workspace USER openhands构建好自己的镜像后,记得在启动命令里指定:npx openhands run --runtime docker --image my-custom-runtime:latest这一步对实际使用体验的提升是决定性的。试想,你的任务要求智能体先对一份 Python 项目做代码格式化,再用 Pytest 跑测试。如果镜像里没有预装ruff和pytest,智能体就得先pip install再做正事,不仅慢,还可能因为网络问题卡死。5.4 Docker Runtime 的性能调优参数性能调优这块,我结合自己压测的数据给几个建议:--max-execution-time:控制单条命令的最大执行时间,默认值偏保守。如果你的任务经常跑长命令,建议调到 300000 毫秒(5 分钟),但别无限调大,否则出问题不好排查。--memory-limit:默认 4096 MiB,如果你跑的是大型前端构建,建议 8192 MiB 以上。--cpus:默认 4,编译密集任务建议 6-8。--enable-concurrency:如果你是单机,不建议开太多并发,两个任务同时跑往往比一个任务快不了多少,但内存消耗翻倍。--persist-sandbox:默认关闭,每次任务结束会销毁容器。如果你需要保留容器状态(比如连续多轮会话),打开这个开关,但要留意磁盘空间的增长。如果你用的是 OpenHands 自带的 CLI 交互界面,这些参数也可以直接通过配置文件传入。更推荐的方式是维护一份config.toml或环境变量脚本,把常用的参数固定下来,避免每次记几十个 flag。6. 进阶玩法:非 Docker 的 Runtime 实现尽管 Docker Runtime 是默认方案,OpenHands 的架构并没有把所有可能性都锁死。我深入研究过它的接口抽象,在Runtime这个大类内部,有一个Sandbox概念隔离了不同执行方式。6.1 远程 Runtime 与云端沙箱如果你不想在本地跑 Docker,或者你希望 Agent 在云端一台高性能机器上执行任务,可以用远程 Runtime。这种方式下,宿主机上只跑一个轻量级的 Runtime 客户端,它负责与远端沙箱通信。比如你把自己的高配云服务器注册为执行节点,然后在本地跑 OpenHands 主服务,任务来了之后由远端节点执行。这样做的好处是,你的本地电脑可以很弱(哪怕是个轻薄本),真正跑构建的活全在云端完成。我试着在一台 2C4G 的轻量服务器上跑过 Docker Runtime,效果很一般,CPU 和内存都是瓶颈。后来换到一台 8C16G 的云服务器,体验立刻好了很多。如果你的预算有限,建议优先保证机器至少有 4C8G,否则光启动镜像和npm install都可能等得让人崩溃。REST API 也是一个值得探索的方向。OpenHands 的 Runtime 内部通过 HTTP/WebSocket 暴露了执行接口,理论上你可以写一个自己的“Runtime 壳”,把执行逻辑转接到其他基础设施上,比如 Kubernetes Pod、AWS Lambda 等。社区里其实已经有一些人在尝试类似的东西,比如“OpenHands on Kubernetes”的实验项目。我目前没有在生产环境验证过,但从接口设计的完整度来看,这条路是通的。6.2 自定义 Runtime 接口的扩展思路如果你要做一个全新的沙箱后端,比如公司的内部容器平台,入门姿势通常是这样的:import { Runtime, Action, Observation } from openhands; class MyCustomRuntime implements Runtime { async connect(): Promisevoid { // 初始化与内部平台的通信 } async runAction(action: Action): PromiseObservation { switch (action.type) { case BashAction: // 把命令发到内部容器平台 break; case FileOperationAction: // 调用内部对象存储或文件系统服务 break; default: throw new Error(Unsupported action type: ${action.type}); } } async disconnect(): Promisevoid { // 释放资源 } }核心要求只有一个:实现Runtime接口里定义的几个方法,然后按 EventStream 的规则把 Observation 发回去即可。我在一个小项目里试过用 GitHub Actions 作为执行后端——智能体发出 BashAction,我用脚本把它转换成 GitHub Actions 的一个 job,跑完再把结果塞回来。虽然延迟高得离谱,但能通,说明这个扩展点设计得确实通用。6.3 网络受限环境下的 Runtime 配置企业内网环境(防火墙严格、外网受限)是现实世界中大量存在的场景,尤其是在国内服务器上部署时。这种情况下的核心矛盾是:容器内要下载依赖,但又没有外网权限。解决方案通常分两种:第一种,宿主机开代理,然后让容器继承代理环境变量。在启动容器时,把HTTP_PROXY、HTTPS_PROXY、NO_PROXY这几个环境变量传进去。这样容器内的命令(尤其是npm、pip)都会自动走代理。第二种,本地搭私有镜像源。比如内部搭建 Nexus 或 Verdaccio,把 npm、pip、Maven 等常用 registry 指到内网地址,然后把这些源地址写进容器内的配置文件。对于完全不容许外网的环境,这是唯一可靠的做法。我第一次踩这个坑是在一个隔离机房部署时,镜像包拉了两个小时都没成功,后来排查发现是 DNS 解析出了问题。容器里默认用的 DNS 指向宿主机,宿主机上没配好/etc/resolv.conf,导致所有域名都解析不出来。解决方法是启动 Docker 时用--dns 8.8.8.8(或你内部的 DNS)显式指定。7. Runtime 调试与问题排查实录这部分内容,应该说是全文里最值钱的经验总结。Runtime 是名副其实的“黑盒”,出了问题如果只会看日志头大,效率极低。我把实际运行中遇到的典型问题分类列出来,附上排查思路。7.1 容器启动失败与镜像拉取问题最常见的启动失败原因是 Docker 镜像拉不下来。如果你在终端看到类似manifest unknown或repository does not exist的错误,先确认你是不是用了正确的镜像仓库地址。OpenHands 的镜像默认托管在 GitHub Container Registry 上,国内访问这个仓库确实时好时坏,解决办法有两个:一是给 Docker 配置 registry-mirror;二是用代理。第二个常见原因是宿主机的 Docker 存储空间满了。docker system df可以快速查看空间占用,如果发现Build Cache或Images占了几十个 G,果断把不用的镜像清掉:docker system prune -a --volumes另外一个隐藏坑是 Docker 服务本身没起来,或者当前用户没有加入 docker 组。执行一下docker ps,如果报权限错误,就用sudo usermod -aG docker $USER然后重新登录。7.2 命令超时与资源耗尽Engine: Error: Command timed out after 120000 ms这个报错我见得太多了。出现这个问题的原因无非三类:第一类,命令本身就需要跑很久,比如大型项目的npm install。这时按照前面说的调大timeout参数即可。第二类,命令在等待输入。有些交互式命令(比如sudo密码提示、vi编辑器开启)会一直挂在那里,等待标准输入。容器内默认非交互式环境下,这种命令永远不会返回。解决办法是给命令前面加上echo no |或者用timeout 20包裹,防止卡死。第三类,容器资源不足,进程在执行中被打死。观察dmesg或容器状态,如果看到 OOM(Out of Memory) 痕迹,就需要调大内存配额。7.3 事件流断连与状态不同步如果你发现 Agent 已经输出了 Action,但 Runtime 这边一直没有执行,优先检查 EventStream 的连接状态。可能是 WebSocket 断开了,也可能是心跳超时了。我在本地开发时经常遇到这个问题,原因通常是浏览器打开了好几个 OpenHands 客户端,多个客户端竞争同一条事件流的消费权限,导致互相踢线。解决方法是,确保同一个会话只被一个客户端实例监听。如果确实需要多端同步查看,可以等官方后续版本的消息队列模块更完善后再试。另外,如果容器已经崩溃了,但 Runtime 对象还保持着就绪状态,你会看到 Agent 发出的指令“石沉大海”。这时最干净的手段是:先销毁容器,重新创建一个新的 Runtime,然后把 EventStream 里的历史记录完整回放一遍,让 Agent“回忆”起之前的上下文,再继续干活。7.4 一个经典排障案例:OCI Runtime 错误我在某次部署时,遇到过一条非常典型的报错:OCI runtime create failed: container_linux.go:348: starting container process caused exec: \bash\: executable file not found in $PATH这个报错的意思是:容器启动时想找bash,但镜像里居然没有。我当时的反应是:这镜像也太精简了吧。原因在于,我手动构建自定义镜像时,采用的FROM scratch或极简基础镜像没有包含 bash。OpenHands 的容器客户端默认需要 bash,不是 POSIX sh 就够的。要规避这个问题:使用官方的基础镜像,比如ubuntu:22.04或debian:12,不要用alpine(默认 shell 是 ash,不是 bash)。如果你必须用极简镜像,可以在镜像里额外COPY一个静态编译的 bash 二进制进去。这个坑在官方镜像里不会有,但一旦你开始定制自己的 Runtime 镜像,就很容易碰见。我自己吃过两次亏之后,现在已经养成了一个习惯:所有自定义镜像的 Dockerfile 第一行先显式RUN which bash bash --version,如果没有就装一个。8. 架构演进:从单体 Runtime 到可插拔生态最后聊一聊 Runtime 在 OpenHands 整个项目中的演进方向。我追踪这个项目有一段时间了,能明显感觉到 Runtime 正从“内部实现细节”向“对外开放的插件生态”转变。8.1 从 ActionType 到 ActionExecution 的抽象收敛在早期版本里,每新增一种 Action 类型,你就要在 Runtime 内部新增一个 case 分支,代码维护很累。后来项目做了一次比较大的重构,引入了ActionExecution接口:每种 Action 实现类自带execute()方法,Runtime 不再关心有多少种 Action,只要“执行器”把动作发给对应的 handlers 即可。这种抽象带来的好处是,第三方开发者可以非常容易地给 OpenHands 增加新的能力。比如你想让智能体能操作数据库,不需要改 OpenHands 的核心代码,只需要写一个RunSQLQueryAction的实现,并注册到 Runtime 里。我在本地做过几个类似的实验,体验很好,相关的 API 文档建议你多翻一翻官方仓库的runtime/目录。8.2 Runtime 与开发生命周期的深度融合Runtime 将来不会只是一个“执行命令”的工具,它会成为整个软件开发流程的智能体底座。比如,你可以在 Runtime 里预置好仓库的 CI 配置、代码规范检查工具、测试框架,智能体在写代码的每一步都能自动获得持续反馈。现在已经有的能力是,OpenHands 可以用事件流把代码修改、测试结果、lint 输出全部记录下来,相当于给“AI 程序员”构建了一份详细的工作日志。把这些日志接入到项目管理系统、CI/CD 流水线、甚至是即时通讯机器人里,只是一个工程问题,而不是架构问题。8.3 我对 Runtime 后续形态的个人判断我自己对 Runtime 的判断是,它会越来越像一个“Agent 操作系统”。这个操作系统不是跑在 x86 上的 Linux,而是为智能体提供标准化的“系统调用”。未来的 Runtime 会包含:高度抽象的文件系统接口,智能体不需要关心远程存储还是本地磁盘。更丰富的自省能力,比如说能告诉 Agent “当前环境里有 Node.js 18、Python 3.11,没有安装 Docker”。内建的缓存与并行执行能力,让多个智能体共享同一个资源池。细粒度的安全策略控制,让用户规定哪些操作允许执行、哪些操作必须人工审批。当然,这些形态还在演进中,但地基就是我们现在聊的这个 Runtime 抽象层。我建议读者不要只停留在“我会用 OpenHands 跑任务”这个层次,而是多研究一下它的接口设计、代码组织,这对你做自己的 Agent 框架会有非常大的启发。9. 写在最后的一些实操体会Runtime 这一层,在整个 OpenHands 体系里看上去不算显眼,但它是“翻车”概率最高的一环。Agent 的推理策略可以调教,EventStream 的通信逻辑相对稳定,但 Runtime 面对的是现实世界的复杂性:网络要通、镜像要能拉、依赖要能装、资源要够跑、权限要可控。任何一个环节出问题,智能体就算再聪明,也只会原地打转。我在自己的服务器上跑 OpenHands 时,会刻意把“环境初始化”和“任务执行”分成两步。环境初始化阶段,我会先手动进容器里验证镜像能跑起来、网络通不通、关键依赖装没装好;验证通过后再把任务交给智能体。这种做法看似多花了几分钟,但能省下后面积累的 debug 时间,强烈建议你也试试。如果你是第一次接触 Runtime,建议别一步到位搞远程部署、自定义镜像这些高级玩法。先从最朴素的 Docker Runtime 跑通一个“你好,世界”,再慢慢往里面加东西。等你对容器的生命周期、Action/Observation 的流转有了手感,再去折腾自定义镜像、远程沙箱、资源调度,会顺利得多。最后分享一个小技巧:OpenHands 事件流里的日志是非常宝贵的数据。哪怕任务跑得很顺,我也会定期导出事件流记录,复盘一下智能体在关键节点做了哪些决策。这类数据对调 prompt、优化 Action 映射、甚至训练自己的 Agent 模型,都有想象不到的价值。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →