这些年我在折腾具身智能相关的项目时,越来越意识到一个问题:大模型再会“想”,最后总得有人帮它“做”。ZeroClaw 这个开源项目最吸引我的地方,就是它把“让 Agent 真正动手干活”这件事做得很扎实。作为源码阅读笔记的第四篇,我这次把目光锁定在代码执行模块上——也就是 Agent 生成代码之后,系统如何安全、稳定地把它跑起来,并把结果反馈回模型。如果你正在研究 OpenClaw 的代码结构,或者想把这类 Agent 框架接到硬件控制、自动化脚本执行的场景里,这篇笔记应该能帮你省下不少翻源码的时间。后面我提到的文件路径和类名都以 ZeroClaw 当前 main 分支为准,建议你边读边打开仓库对照着看。
1. 代码执行在 ZeroClaw 里的定位:Agent 的“手”是怎么长出来的
1.1 从“技能”到“动作”的最后一公里
很多人第一次接触 OpenClaw 这类项目时,容易把注意力都放在“大模型多聪明”上。但实际落地的难点从来不是模型智商,而是模型“说出的话”怎么变成“系统能执行的操作”。ZeroClaw 的做法非常直接:让模型输出结构化的意图(Intent),再由代码执行模块把意图翻译成真实的进程调用、文件操作或者硬件指令。
我个人的理解是,代码执行模块在整个框架里承担的是“小脑”的角色——大脑负责规划,小脑负责把规划变成精确的肌肉运动。如果没有这一层,Agent 始终是个只能聊天的“理论派”。所以源码里这个模块的设计目标有三个:一是能执行模型生成的代码,二是执行过程必须可控可观测,三是执行结果要能结构化地回传给模型继续推理。
1.2 为什么选“代码”作为统一行动语言
在具身硬件场景里,控制 GPIO、读取传感器、驱动舵机,这些操作天然就是代码。ZeroClaw 没有为每一种硬件单独写适配器,而是统一用 Python / Shell 脚本作为 Agent 的行动语言,底层再通过执行器(Executor)把代码跑起来。这个设计的聪明之处在于,它对上层模型非常友好——LLM 最擅长的就是生成文本,Python 脚本本质上也是一种“文本”,两者之间的转换损耗极低。
同时,代码作为行动语言还有一个隐藏优势:可复用性。今天模型生成的一段控制机械臂的脚本,存下来以后,下次可以直接作为 Skill 供其他任务调用。我翻源码的时候发现,ZeroClaw 在 Job 执行完成后会输出非常完整的执行报告,包括退出码、标准输出、标准错误和耗时,这些信息其实就是 Skill 沉淀的基础数据。
1.3 代码执行不是漏洞利用,而是“受控执行”
这里必须说清楚一个容易引起误解的点:热词里经常出现“RCE 代码执行”,那通常说的是漏洞攻击;ZeroClaw 里的代码执行模块是完全不同的东西——它是产品功能的一部分,是 Agent 完成任务的必要通道。源码里对执行边界做了三层约束:来源受控(只执行模型生成的、经过系统封装的代码)、运行环境受控(默认放沙箱)、动作受控(执行前有配置策略可拦截)。后面第 3 部分我会展开讲这三层约束分别落在哪些代码文件里。理解这一点,你才能真正去用这个模块,而不是一看见“代码执行”四个字就紧张。
2. 源码剖析:一次代码执行任务的完整旅程
2.1 JobSystem:所有执行请求的统一入口
在 ZeroClaw 源码里,代码执行的起点不是一大堆函数散落在各处的,而是集中在job_system这个模块。核心抽象是BaseJob,每个要执行的任务都会被包装成一个 Job 实例,然后提交给 Job 管理器去调度。这个设计很像操作系统的进程模型——Job 就是框架里的“进程”,有生命周期、有状态、有退出码。
我建议你从job_system/job.py开始读,里面定义了 Job 的状态机:created -> queued -> running -> succeeded/failed/cancelled。每一笔状态流转都会写日志,这在后面排查问题的时候帮了大忙。要注意的是,默认配置下 Job 管理器是单线程顺序执行的,这是刻意的设计——避免多个模型生成的代码同时跑起来互相干扰。如果你要接多硬件场景,可以调整并发数,但必须自己做好资源隔离。
2.2 Sandbox(沙箱)体系:隔离方案与适用场景
WildCard 里配置的sandbox字段决定了代码究竟跑在哪。ZeroClaw 原生支持三种模式:
| 模式 | 实现方式 | 隔离强度 | 适用场景 |
|---|---|---|---|
| 本地进程 | 直接subprocess拉起 | 低(依赖操作系统权限) | 本地开发、快速验证 |
| Docker 容器 | docker-py 启动一次性容器 | 中(容器级隔离) | 常规托管部署 |
| Firecracker 微虚拟机 | 依赖 firecracker 二进制 | 高(虚拟化隔离) | 不可信代码、生产环境 |
我平时开发用的是 Docker 模式,源码里对应的是sandboxes/docker_sandbox.py。它每次执行都会创建一个新的临时容器,容器内没有网络权限、只挂载临时目录,跑完自动销毁。这个“用完即焚”的策略非常关键,能有效避免模型生成的代码在宿主机上留下不该有的文件。
2.3 代码生成、校验到执行的完整链路
把整条链路串起来看,一次代码执行是这样走的:
- Agent 推理完成后,产出一个
intent.json,里面包含了action: "execute_code"和执行参数。 - 系统根据当前启用的 Skill,在代码生成器(
code_generator)里把 intent 填充成一段具体的 Python / Shell 脚本。 - 脚本经过
execution_policy层做静态检查——目前主要是超时上限、脚本大小限制、是否包含明显危险操作的词法过滤。 - 通过校验后,脚本连带
timeout、workdir等参数打包成Job,提交给 Job 管理器。 - Job 管理器按配置选择 Sandbox,在隔离环境里执行脚本。
- 结果组装成
JobResult,内容包括标准输出、错误信息和结构化 JSON 输出文件,回传给 Agent 作为下一轮推理的依据。
这六步里,我个人最推荐细读的是第 3 步的execution_policy.py。它虽然只有一百多行,但把所有“该防的地方”都集中在一起了。框架的设计哲学是:不在 Python 解释器层面做复杂的安全加固,而是把策略前置,让不合适的代码根本走不到执行那一步。
3. 实操篇:如何把第一段 Agent 代码安全地跑起来
3.1 最小可运行配置清单
想在自己的环境里把代码执行模块跑起来,不需要把整个 OpenClaw 全部功能都配完。我实测下来,最小配置只需要四件事:
- 安装 ZeroClaw 主项目,Python 版本 3.10 以上
- 安装 Docker 并保证当前用户有权限调用(如果用 Docker 沙箱)
- 在
settings.yaml里配置好 LLM 的 API Key - 启用
code_executor这个 Skill,并配置sandbox: docker
配置好后,你直接在对话里让 Agent“写一段 Python 计算斐波那契数列并运行”,它就会走完上面那六步链路,把运行结果打印出来。第一个能在沙箱里跑通的例子,比看十遍源码都管用。
3.2 关键配置项解析:超时、磁盘、网络策略
sandbox配置段是代码执行模块的重头戏。以下是 Docker 模式下我常用的配置模板:
sandbox: mode: docker timeout_seconds: 30 memory_limit: 512m cpu_limit: 0.5 enable_network: false tmpfs_size: 100m这里enable_network: false是默认值,也是我强烈建议你保持关闭的选项。Agent 生成的代码绝大多数场景只需要计算和文件操作,不需要联网。关闭网络能直接规避一大类“代码试图访问外网”的意外情况。memory_limit设 512m 足够跑绝大多数 Python 脚本了。如果你遇到 Agent 生成的代码本身没问题但内存超限,再往上调也不迟。
3.3 一个可复现的硬件控制示例
我是做具身方向多一些的,所以拿一个控制 GPIO 的例子来说明。假设你的机器上有一个通过串口连接的传感器,让 Agent“读取串口数据并做均值滤波”,ZeroClaw 会生成的代码大致是:
import serial import statistics ser = serial.Serial('/dev/ttyUSB0', 115200, timeout=2) samples = [] for _ in range(10): line = ser.readline().decode().strip() if line: samples.append(float(line)) ser.close() result = {"count": len(samples), "mean": statistics.mean(samples)} with open("output.json", "w") as f: json.dump(result, f)注意这里有两个约定俗成的规范:第一,代码要把结构化结果写到一个output.json文件里,ZeroClaw 的执行器会专门收集这个文件的内容回传给模型;第二,串口设备如果挂载在宿主机上,你需要在 Docker 启动参数里加--device /dev/ttyUSB0。这两个细节不处理好,硬件控制类的 Job 经常会“代码没错但执行不对”。
3.4 从日志里看懂每一次执行
跑通之后,要学会看执行日志。ZeroClaw 在job_system里的日志是按 Job ID 聚合的,每次执行会输出三个关键时间点:
job created时打印任务参数,方便你确认 Agent 到底想干什么;sandbox started时打印容器 ID 和资源限制,确认环境对不对;job finished时打印退出码和输出摘要,这是排查错误的主要依据。
我踩过最常见的坑是:Agent 生成的代码里用了print调试,结果打印内容把正常输出结构撑爆了,导致output.json的解析失败。所以后面我在系统配置里加了一条约定——代码实现的函数统一返回 dict,由执行器负责序列化,尽量不依赖标准输出传数据。
4. 排坑实录:代码执行模块的常见问题与解决方案
4.1 高频问题速查表
为了让你少走弯路,我把这段时间遇到的问题整理成了表格:
| 现象 | 根本原因 | 解决方案 |
|---|---|---|
| 任务一直显示 queued 不运行 | Job 管理器并发数为 1,前面有任务阻塞 | 调大并发数,或检查是否有僵尸任务 |
| Docker 模式下提示权限不足 | 当前用户不在 docker 组 | sudo usermod -aG docker $USER后重新登录 |
| 脚本运行超时被 Kill | 默认 timeout 太短或代码死循环 | 调大timeout_seconds,并优化提示词 |
| 容器启动成功后立即退出 | 脚本里抛了未捕获异常 | 查看日志定位 stack trace,让 Agent 改写 |
| 无法读取宿主机的串口/GPIO | 容器没挂载设备 | 在 sandbox 配置里声明需要加载的设备列表 |
| 模型反复生成错误代码 | 提示词里缺少约束条件 | 优化 Skill 描述,写明输入输出格式要求 |
4.2 最容易忽悠人的三个安全细节
第一,静态词法过滤不是安全边界。execution_policy.py里目前主要是过滤一些明显的危险操作关键词,但 Python 这门语言的动态特性决定了这种过滤永远可以被绕过。所以它真正的用途是“防止误操作”而不是“对抗恶意攻击”。真要跑不可信代码,老老实实用 Firecracker。
第二,临时目录没清理会拖垮磁盘。Docker 模式下每次执行会在宿主机的/tmp下创建临时工作目录,如果任务频繁失败,这些目录可能不会被及时回收。我写了个 cron 脚本每小时清理 24 小时前的临时目录,目前没再遇到磁盘爆满。
第三,环境变量会泄露到子进程。容器里的环境变量默认会包含宿主机的部分配置,包括 API Key。如果你的 Agent 代码会打印环境变量,信息就漏出去了。我在配置里强制清空了*_API_KEY相关的环境变量,并给容器设置了独立的只读环境。
4.3 给本地开发者的四条建议
如果你现在还在本地跑代码执行模块做测试,我的建议是:
- 先不用 Docker,直接用本地进程模式快速验证 Agent 的代码生成质量,等逻辑稳定了再切沙箱;
- 在本地模式里把
timeout_seconds设为 10 秒以内,防止模型生成的死循环代码把开发机搞崩; - 给代码执行模块单独建一个工作目录,不要让它直接在项目根目录运行;
- 代码执行的日志和模型对话日志分开存储,否则后面找问题时要翻海量日志,人会疯。
5. 代码执行模块的扩展思路与个人评价
5.1 如何接入自定义执行器
如果你想把代码执行接到自己的硬件控制程序或者专有运行时上,ZeroClaw 预留了扩展点。你只需要实现executor.py里的BaseExecutor抽象类,重写run方法,让它在你的目标环境里执行脚本并把结果按JobResult的格式返回即可。然后在配置里把sandbox指向你的新实现。
我自己试过把执行器接到一个常驻的嵌入式板卡上,做法并不复杂:定义一个网络执行器,它把脚本通过 MQTT 发送到板卡,板卡跑完再把结果发回来。整个过程对上层 Agent 完全透明——模型根本不知道自己的代码跑在远程板卡上。这其实就是具身硬件场景里很实用的一种形态。
5.2 Skill 编写对代码执行成功率的影响
源码读得越多,我越发现代码执行的成功率不只是执行器的问题,更取决于上层 Skill 写得够不够好。好的 Skill 描述应该明确告诉模型:输入是什么、输出要写成什么格式、哪些操作被禁止、运行时有没有超时限制。一个写得含糊的 Skill,会让模型频繁生成不匹配的代码,然后白白浪费执行次数。
我的经验是:如果你连续三到五次发现 Agent 生成的代码都在同一个地方报错,不要急着调代码执行模块的配置,先回头想想 Skill 提示词是不是缺了关键约束。执行器解决的问题是“代码跑得安不安全”,而提示词解决的问题是“代码写得对不对”,两者不能互相替代。
5.3 一句话总结这个模块的价值
在整个 ZeroClaw 框架里,代码执行模块是最不像“AI 产品”的部分,但它恰恰是把 AI 从对话拉进现实的关键。它的实现思路没有什么炫技的地方,胜在用 Job 状态机、沙箱隔离、结构化回传这些非常工程化的手段,把一个容易失控的“让模型写代码并执行”的过程,变得稳定、透明、可排查。对想深入二次开发的人来说,从这个模块入手读源码,性价比相当高——代码量不大,边界清晰,而且很快就能在你的硬件上看到实际效果。
最后补充一个我自己的习惯:每次升级 ZeroClaw 版本后,我会先跑一个简单的“写代码并执行”的回归测试,确认代码执行链路没有被改动破坏。具身硬件项目跟纯软件项目不一样,固件也好、机械结构也好,一旦因为框架升级导致执行失败,排查成本可能成倍上升。提前把回归测试固化下来,能省掉很多不必要的麻烦。