
先说个真实经历。上个月我把 OpenClaw 的社区分支 ZeroClaw 拉下来跑第一次让它做一个接近桌面上的障碍物就亮灯的具身硬件小任务。结果它没有像传统对话Agent那样给我一段解释性文字而是直接生成了一段 Python 代码自己调了 GPIO 引脚、写了循环然后执行完把日志甩给我看。那一刻我突然意识到在具身硬件场景里代码执行不是锦上添花的功能而是 Agent 从会聊天到会动手的分水岭。这篇源码阅读笔记我拖了很久才写就是因为代码执行这块代码是整个 ZeroClaw 里离硬件最近、也最容易踩坑的部分。它看起来只是个把字符串丢给解释器的模块但真读进去你会发现里面塞满了意图解析、命令白名单、沙箱隔离、超时控制、结果回传这一整条链路。无论你是想把 OpenClaw 接到自己的机器龙虾、树莓派小车上还是只想搞清楚 Agent 的代码执行能力到底怎么设计才安全这篇都能给你一些参考。1. 为什么具身Agent必须有自己的代码执行通道1.1 从意图到动作的最后一公里先花点时间说清楚背景。OpenClaw 这套框架的核心思路是把大模型当作决策大脑而真正的执行落地靠的是代码。ZeroClaw 作为开源生态里的一个极简分支保留了这条主线Agent 收到自然语言指令后不是直接把指令发给硬件而是转成结构化的代码片段由框架内置的执行引擎去跑。为什么非得走代码这一层我给你举个反例。假设你让 Agent控制舵机转到 90 度如果框架只做输出意图那意图可能长这样{action: servo_set, angle: 90}这种设计看着干净但一旦任务复杂度上来就崩了。比如你要让机器龙虾沿着墙走遇到拐角就转 60 度连续转 3 次后停下来如果走 JSON 意图通道Agent 和硬件之间要来回传十几个回合每一回合都有大模型推理延迟整个流程慢得像卡带。而代码执行通道只需要让 Agent 生成一小段控制脚本一次执行完中途不用模型介入实时性和可靠性完全不在一个量级。1.2 对比直接调用API vs 让Agent写代码再执行我前几年做过一阵子智能家居的语音控制那时候主流方案是给每个设备封装 API让大模型做 function calling。这种方式对开灯调温度这种单步指令很好用但对组合操作就很吃力。ZeroClaw 里保留了函数调用和代码执行两条路函数调用负责高层的工具分发代码执行负责真正的批量控制和计算。两条路有几个决定性差异组合能力函数调用每一轮只能选一个函数而代码执行可以在一个片段里循环、分支、组合多个硬件操作。状态管理代码执行天然能持有局部变量和状态函数调用则要靠外部存储把状态搬来搬去。调试成本函数调用的中间过程对开发者是个黑盒代码执行则可以把整段日志打出来出问题一眼就能定位。所以在 ZeroClaw 里代码执行更像是一个万能执行器专门处理那些自包含、可重复、有明确输入输出边界又不能被拆成单步交互的任务。2. ZeroClaw 代码执行的三层管线拆解读源码的时候我习惯先把一个模块拆成输入、处理、输出三个断面。ZeroClaw 的代码执行模块被拆得更细核心可以看成三层意图解析层、执行器分发层、结果回传层。2.1 第一层意图解析——把自然语言摘成可执行动作这里要说明一下我读的 ZeroClaw 版本的代码执行入口并不是直接接收大模型吐出来的代码字符串而是先经过一个动作封装。整个链路简化后是这样class CodeExecutor: def __init__(self, sandbox, allowlist, hardware_registry): self.sandbox sandbox self.allowlist allowlist self.hardware hardware_registry def execute(self, action: AgentAction) - ExecutionResult: if action.kind ! code_execution: raise UnsupportedAction(action.kind) payload action.payload # 先做静态分析再做白名单过滤最后进入沙箱执行 plan self._analyze(payload) ...AgentAction是框架里统一封装的动作对象payload 里装着代码片段、执行方式shell、python、内置命令等、超时时间、以及授权令牌。之所以不直接传裸字符串是为了在执行前有一个抓手去挂载过滤器和审计日志。官方主线的实现思路也类似只是 ZeroClaw 把 payload 结构再精简了一层。意图解析层的核心不是解析编程语法而是把大模型给的模糊指令归一化成可执行的动作描述。比如大模型可能输出用python脚本读取当前传感器值如果距离小于10cm就亮红灯意图解析会把这段拆成执行环境python、依赖的硬件资源dist_sensor、led_red、预期输出布尔/日志、安全级别允许访问GPIO。这一步做得好不好直接影响后面的白名单匹配会不会误杀。这部分的源代码注释里有一句我很赞同凡是不能在执行前静态描述的动态行为宁可拒绝不交给运行时去赌。这是整套安全设计的底层逻辑。2.2 第二层执行器分发——白名单命令、脚本片段与硬件指令解析完成后执行器拿到一个已经半结构化的请求然后根据请求里的类型字段分发给不同的执行后端。我总结了一张表方便对照理解执行类型典型场景后端实现关键风险builtin内置工具调用如文件读写、网络请求Python 函数直接调用参数注入shell系统命令如ls、dfsubprocess shellFalse命令注入python_script用户自定义逻辑、数据处理隔离解释器长期运行、资源耗尽hardwareGPIO、I2C、串口操作硬件注册表分发硬件误操作、物理损坏分发的底层逻辑其实不复杂核心就是一张注册表 一堆执行后端。让我印象比较深的是 ZeroClaw 虽然薄但硬件执行这一层做得一点也不含糊。每个执行后端都要先声明自己能做什么、不能做什么注册进全局执行器Agent 请求里带上目标设备的标识执行器查表确认该设备在这个安全级别下是否可用。拿 GPIO 场景举个例子ZeroClaw 的硬件执行后端注册方式类似于hardware_backend.register(gpio) class GpioBackend(ExecutionBackend): def can_handle(self, request: ExecutionRequest) - bool: return request.target gpio def execute(self, request: ExecutionRequest): # 校验权限、引脚范围、电平值合法后才真正操作硬件 return self.hardware.gpio_set(request.params)这层设计有一个容易被忽略但很实用的点后端可以做参数合法性前置校验。比如引脚号是否在允许列表里、PWM 频率是否超限、舵机角度是否在 0~180 之间。这些校验提前到分发阶段而不是等到代码真正跑起来能避免大量硬件损坏事故。2.3 第三层结果回传——状态、错误与退出码如何反馈给Agent执行完代码之后结果不是简单地把 stdout 塞给大模型就完事儿。ZeroClaw 用了一个统一的结果结构完整保存退出码、标准输出、标准错误、执行耗时、资源使用量。这套结构是后面 Agent 做反思的重要依据——它能根据 exit code 判断要不要换一种方案而不是只看有没有文字输出。dataclass class ExecutionResult: success: bool exit_code: int stdout: str stderr: str duration_ms: int resource_usage: dict我读源码时专门确认了这个结果类还会被序列化进上下文供多轮对话继续使用。说白了就是Agent 第一次执行失败后它会把 stderr 里的报错信息和当前代码片段重新组合交给大模型做二次修正。当整个循环里 执行失败 - 读取错误 - 改代码 - 重新执行 的闭环跑通后你就发现 Agent 的自主性真正落地了。这里要特别留意一个点回传结果不能无脑全塞给大模型。ZeroClaw 对 stdout 做了长度截断太长的日志只保留头部和尾部避免上下文被无关日志撑爆。这个小设计很实用实际部署时你会发现大模型的上下文窗口再大也不够挥霍做截断和摘要几乎是必须的。3. 源码里的安全防线白名单过滤与沙箱隔离以及过滤绕过问题搜索我标题的人里有一半是被rce代码执行过滤绕过这个词带进来的。这确实是个非常实在的威胁场景。既然代码执行模块的终极能力是给 Agent 一段代码它就能跑那么安全设计就是整套系统的生死线。ZeroClaw 在这一块用了三层叠加的方案我逐个给你拆。3.1 黑名单正则为什么会被绕过先说一种非常常见、但几乎必被绕过的做法黑名单正则。也就是在代码执行前匹配有没有import os、有没有subprocess、有没有eval(这些危险字样匹配到就拒绝执行。这种方案在玩具项目里够用但拿到真实环境里完全不够。原因很简单大模型不需要知道什么精妙的攻击技巧它随便试探几轮就能构造出正则匹配不到的等价写法。举个例子eval(__import__(os).system(ls))可以直接绕过大意只匹配import os的正则。getattr(__builtins__, eval)又是一种避开直接写eval的方式。甚至可以用exec配合chr()拼出敏感命令让字符串层面的检查彻底失效。ZeroClaw 源码里早期版本确实有用过一段正则黑名单做快速过滤但只作为第一道粗筛而不是唯一防线。因为作者很清楚只要执行端是完备的 Python 解释器字符串匹配就不可能完整覆盖所有执行路径。想靠正则拦住所有恶意代码本质上是在和整个语言做对抗必输。3.2 正确的过滤姿势AST静态检查 白名单 沙箱叠加ZeroClaw 真正依赖的是第二道防线AST 静态分析。Python 的ast模块可以把代码解析成语法树然后在语法树层面禁止特定节点类型。这种方式不关心字符串长什么样只关心语法结构上允不允许出现这种操作。我在笔记里复刻了一个简化版的检查器import ast class SafetyAnalyzer(ast.NodeVisitor): def __init__(self): self.violations [] def visit_Import(self, node): for alias in node.names: if alias.name not in self.ALLOWED_IMPORTS: self.violations.append(fimport {alias.name} is not allowed) self.generic_visit(node) def visit_Call(self, node): if isinstance(node.func, ast.Name): if node.func.id in self.BLOCKED_FUNCS: self.violations.append(fcall {node.func.id} is not allowed) self.generic_visit(node)这个思路的关键在于AST 分析检查的是代码的结构不跟你玩字符串文字游戏。__import__和eval都是在语法树上能直接命中的节点换个写法也躲不掉。第三道防线是沙箱隔离。AST 分析再严格总会有漏网之鱼所以最终执行必须放在约束环境里。我在 ZeroClaw 里看到的默认实现是先尝试用容器隔离执行容器不可用时降级到受限的子进程用setrlimit限制 CPU 时间和内存。resource.setrlimit(resource.RLIMIT_CPU, (timeout, timeout)) resource.setrlimit(resource.RLIMIT_AS, (memory_limit, memory_limit))整个过滤流程合起来是黑名单粗筛 - AST 结构检查 - 白名单命令表 - 沙箱执行 - 资源限额。这一套叠加下来即便是过滤绕过的高手也得多花不少功夫而不是随手几行就能得手。3.3 具身硬件场景的安全边界硬件场景比纯软件场景多一个麻烦代码执行带来的后果不可回滚。软件删错了文件可以从备份恢复但 GPIO 引脚输出错误电平、舵机超行程转动可能直接烧板子。ZeroClaw 在硬件这块额外做了一层安全边界所有硬件操作必须显式声明目标设备没有声明的不给执行。引脚号、角度值、PWM 频率都做范围校验越权直接拒绝。硬件操作默认不启用无限重复循环要跑循环必须加超时限制。每次硬件操作都写审计日志方便事后复盘。这些边界在源码里只占了很小一段但实际价值极高。我在自己的机器龙虾上试过如果不加角度的-45~225校验大模型在上下文混乱时真能生成把舵机逼到物理极限的代码。加了校验之后最多就是任务失败不会被硬件问题反咬一口。4. 和硬件打交道代码执行在GPIO、串口与舵机上的实际落法4.1 硬件工具注册与权限控制ZeroClaw 处理硬件的方式不是把所有引脚直接暴露给 Agent而是先进工具注册表。每个硬件能力都要被显式注册成工具注册时声明输入参数的类型和取值范围。Agent 想用某个硬件必须先通过工具名去调用拿不到工具名就无法执行。hardware_tool.register(servo_set) def servo_set(pin: int, angle: float, min_angle: float 0, max_angle: float 180): # 校验 pin 是否在允许列表、angle 是否越界 ...这套设计最大的好处是Agent 不需要知道底层细节只知道工具名和参数。当它生成代码时代码里调用的是servo_set(pin18, angle90)这种高度语义化、可校验的接口而不是直接操作寄存器或写内存。另外权限控制是分层级的。ZeroClaw 的配置里会给 Agent 分配不同的安全等级有的 Agent 只能读传感器有的可以写 LED只有机器人的主控 Agent才有权限操作舵机。这种分权策略避免了一次越权操作导致整个硬件失控。4.2 长任务与状态回读具身任务里的代码执行有很多是长任务。比如机器龙虾走迷宫代码要跑几秒钟甚至十几秒中间涉及传感器的实时判断。传统的一次执行、一次返回模式不适用因为 Agent 需要中途知道当前状态。ZeroClaw 的做法是给长任务注册一个状态回调。代码执行期间硬件后端会不断更新执行状态到共享状态区Agent 可以随时查询代码结束后状态区导出的快照会作为执行结果的一部分回传。我自己的实践是让 Agent 写一段带断点日志的巡检代码每经过一个传感器就打印一行checkpoint: sensor_3 distance12.5。等代码跑完Agent 把 checkpoint 拉出来做分析即便中途出了异常也能定位到到底是在哪一步出的问题。这种方式比只看最终 stdout能早好几个量级地发现硬件异常。4.3 异常恢复与看门狗硬件执行最需要防的就是卡死。Python 代码里一个while True就能让整个 Agent 停摆。ZeroClaw 在代码执行模块里内置了一个看门狗机制每个执行任务都有硬超时超时后不是直接 kill 进程而是先尝试发送软中断信号让代码有机会执行清理逻辑比如把舵机归零、关闭 GPIO清理不成功再强杀。这里有个很容易忽略的坑如果 Agent 在代码里开了串口或 SPI强杀之后文件描述符不释放下次想复用同一个串口就会被占用。ZeroClaw 的看门狗会在强杀后主动做资源回收把所有与当前进程关联的硬件句柄关掉。这个细节是实打实从故障报告里长出来的——我翻 commit 历史时看到过好几次fix file descriptor leak after force kill这种提交。5. Windows部署执行环境的连环坑从缺少dll说起如果你是在 Windows 上装 OpenClaw 或 ZeroClaw应该没少在社区里刷到由于找不到 xxx.dll无法继续执行代码这类帖子。顺着热搜词列表看vcruntime140.dll、mfc140.dll、rstrtmgr.dll这几兄弟出现频率极高。这其实不是 ZeroClaw 的问题而是 Python 生态在 Windows 上的经典环境坑但因为它直接影响代码执行模块能否启动所以我把它专门写一节。5.1 vcruntime140.dll、mfc140.dll这类问题的根因这三个 dll 的根因各不相同但表现相似报错文件承载组件典型触发原因vcruntime140.dllMicrosoft Visual C 2015-2022 Redistributable缺 VC 运行库mfc140.dllMicrosoft Foundation Classes 库缺 MFC 组件VC 运行库不完整rstrtmgr.dllWindows Restart Manager系统组件未启用或版本过旧如果你只是单纯缺运行库去下载对应版本的 Visual C Redistributable 装一遍基本就好。但我实际遇到的情况往往没那么单纯Python 包在安装时编译扩展模块依赖了某个特定版本的 VC 工具链而系统中同时存在多套 VC 运行库加载时选错版本。这种情况下单装最新版不一定能解决需要先看是哪个包引入的依赖。ZeroClaw 在 Windows 上跑的代码执行模块底层依赖subprocess调用 Python 解释器而解释器本身是嵌入在宿主程序里的。如果宿主程序是打包的离线整合包对 VC 运行库的依赖会更敏感因为无法动态去系统里找缺失的组件。5.2 定位执行失败的通用排查链路遇到代码执行模块起不来的情况我一般按下面这个顺序排查能解决掉九成问题先确认是框架启动失败还是只执行某段代码时失败。如果启动就崩优先查运行库和系统组件。查看 Windows 事件查看器里的应用程序日志里面会记录具体的缺失模块名而不只是弹窗上的那一个文件名。用Dependencies这个工具打开报错的 exe 或 pyd 文件看依赖树的哪个节点断了。它比旧版depends.exe好用很多能直接看到每个依赖 dll 是否存在、版本是否匹配。确认 Python 环境位数一致。32 位 Python 不能加载 64 位扩展模块反之亦然这个错也经常伪装成找不到 dll。如果确认是 VC 运行库问题装 Visual C Redistributable 最新版后重启不要只装 x64x86 也顺手装上很多混合依赖只装一边是没用的。还有一个我踩过的坑下载了整合包后直接解压到带中文或空格的路径。某些本地扩展模块编译时的相对路径逻辑遇到带空格的路径会加载异常表现就是找不到 dll。这个问题的解法很傻但很有效把整个目录挪到C:\zeroclaw这种纯英文、无空格的路径下再试一次。5.3 离线环境与整合包的注意事项热搜词里还有离线整合包 夸克网盘这种说法说明不少人是下载的整合包。整合包的好处是省去配置环境的工夫但坏处是它对系统的假设比较强。我在离线环境里部署 ZeroClaw 的经验有这么几条离线安装前先在一台能联网的机器上把pip download出来的所有依赖包打包包括wheels离线环境里直接装包避免安装时连不上 PyPI 卡死。如果整合包里带了 Python 解释器注意它是不是嵌入式环境。嵌入式的python.exe和完整安装版在加载site-packages时的行为有差异容易导致能启动解释器但找不到已安装的包。离线环境通常没有 C 编译器所以尽量选带预编译.whl的依赖包避免源码安装时卡在编译阶段。如果你打算让 Agent 执行任意 Python 代码最好把执行器指向一个单独、干净的虚拟环境而不是直接用整合包的主环境避免 Agent 的执行代码污染整个安装。这些细节看着琐碎但都是我在几台不同 Windows 机器上跑出来的血泪教训。尤其是嵌入式解释器那个坑第一次遇到时我整整排查了两天最后才发现整合包用的 Python 模式不对。6. 实测心得与调优建议最后分享几个我在实际跑 ZeroClaw 代码执行模块时的心得都是文档里不会写的东西。第一给代码执行加试运行模式。在正式控制硬件之前如果能跑一遍语法检查和 AST 安全校验再在模拟环境里执行一次能省下大量烧硬件的风险。ZeroClaw 没有默认开启这个模式但我在自己的分支里加了实际效果非常好。让 Agent 先用自己的沙箱跑一个 dry run确认逻辑没问题再放行到真实硬件。第二日志里一定要带执行编号。每次代码执行生成一个唯一编号日志文件、审计记录、结果回传统一用这个编号关联。排查问题时按编号拉全文比按时间戳大海捞针快得多。ZeroClaw 源码里已经埋了 trace_id 的概念但默认打印得不够显眼建议自己调高日志级别。第三超时时间要按执行类型差异化设置。像 GPIO 读传感器这种轻量操作5 秒超时已经很多了但你让 Agent 跑一段图像识别算法给 5 秒就太苛刻动不动误杀。我给自己的配置表里区分了quick、normal、long三档配合执行类型自动选择实测下来任务成功率提升明显。第四大模型生成的代码一定要强制它包含结束条件。我在 ZeroClaw 的提示词里额外加了一条任何循环必须带明确的退出条件。少了这条Agent 在上下文混乱时容易写死循环把看门狗当成常规手段用整个流程会变得很不可控。加了结束条件约束后死循环问题几乎绝迹。ZeroClaw 这套代码执行模块优点和缺陷都很明显。优点是管线完整、安全分层、硬件抽象做得干净缺点是按开源标准来看文档太少不少关键设计只能靠读代码慢慢推敲。但如果你愿意花点时间把这块啃下来收获是巨大的——它几乎涵盖了一个生产级 Agent 执行器该有的所有核心要素从静态分析到沙箱隔离从结果回传到硬件安全边界都能在这一个模块里看到完整落地。下一篇笔记我打算写 ZeroClaw 的事件驱动模型聊聊 Agent 是如何感知环境变化并触发新一轮决策的。代码执行是动手事件驱动是感知两者往往是配合着工作的。到时候见。