Nanobot(OpenClaw 轻量实现)底层原理是如何巧妙融合策略与高效计算?

摘要:本文以精简版 OpenClaw——Nanobot 为切入点,拆解其核心原理。 其本质是基于循环执行的“提示词构建 + 调用大模型 + 工具操作”的本地 Agent 架构。通过分析消息处理、上下文构建、循环
作者:vivoAI技术开发团队- Lin Weiwei 本文以精简版 OpenClaw——Nanobot 为切入点,拆解其核心原理。 其本质是基于循环执行的“提示词构建 + 调用大模型 + 工具操作”的本地 Agent 架构。通过分析消息处理、上下文构建、循环决策(AgentLoop)与工具调用(Tools)等流程,揭示其运行机制。 1分钟看图掌握核心观点👇 图 1 VS 图 2,您更倾向于哪张图来辅助理解全文呢?欢迎在评论区留言。 一、轻量版的OpenClaw:Nanobot OpenClaw 为何能引爆科技圈,底层是否出现了颠覆性的新技术?这是近期每个 AI 从业者都在好奇的问题。虽然网上已有海量的解析文章,但堆砌的各种新概念往往让人迷失其中。面对技术狂欢,回归代码本源才是看清事物本质的最佳捷径。 1.1 核心结论先行 OpenClaw 本质上是一个运行在用户终端的Agent。它的核心运转逻辑比较常规:循环环调用大模型 API -> 解析输出 -> 本地执行系统命令 -> 结果回传。它并没有引入颠覆性的 AI 新技术,在架构上,与过去运行在服务端的 Agent(智能体)没有本质区别。 既然底层逻辑如此简单,它为何能引发如此大的反响?我认为核心在于以下两点: (1)开放性强(真正接管用户电脑) 过去的服务端 Agent 被限制在云端。而 OpenClaw 极其大胆地开放了本地权限,允许大模型动态生成并执行 Python、Shell 等脚本。这种“放权”让大模型从“只能聊天的智囊”变成了“能敲键盘的双手”,直接操作本地文件和应用,能力边界得到了实质性的突破。 (2)可玩性高(从“云端公共智能体”到“专属机器人”) 过去所有人都能访问,缺乏“拥有感”和“实体感”。 现在,当你把它部署在自己的电脑上,个性化配置(可以逛clawhub市场配置Skills、Tools)、强烈的个人专属感,极大地满足了大众的参与感与“炫耀欲”。 1.2 五脏俱全的Nanobot 由于 OpenClaw 源码庞杂,40万行要看完需要1-2周时间,但抢占商机却需要争分夺秒,等不了太久。因此我们需要快速取其精华。为了降低阅读门槛,本文选择从它的“微缩版”——开源项目 Nanobot 入手。Nanobot 麻雀虽小,五脏俱全。通过拆解这段精简的源码,我们能以极低的认知成本,快速看透 OpenClaw 的底层运行逻辑。 我通过启动Nanobot控制电脑自行安装并部署一个博客网站,一次性通过。核心功能已经与OpenClaw相当。 以下是我实际运行的情况: 二、Nanobot技术架构 2.1 技术流程图 2.2 技术架构图 为了更容易理解,后续内容按一次消息的生命周期顺序来讲解。 2.3 一条消息的完整生命周期 以下是OpenClaw的完整生命周期的6个阶段,Nanobot同样也是以下6个阶段。各环节有一定的简化。 图片来源:链接 三、阶段一:消息接收 channel模块负责建立起与外部渠道的连接、消息收发,是Nanobot重要的模块。 3.1 初始化阶段 (ChannelManager,在gateway命令中执行) 初始化:在 _init_channels 方法中,根据配置动态加载并实例化具体的频道类(如 Telegram- Channel、DiscordChannel 等)。 Config (YAML/JSON) │ ▼ ┌─────────────────┐ │ ChannelManager │ │ .__init__() │ └────────┬────────┘ │ _init_channels() ▼ ┌─────────────────────────────────────────────────────────────┐ │ 遍历配置中 enabled 的 channel,按需动态 import: │ │ • TelegramChannel • DiscordChannel • FeishuChannel │ │ • WhatsAppChannel • SlackChannel • DingTalkChannel│ │ • EmailChannel • QQChannel • MatrixChannel │ │ • MochatChannel │ └─────────────────────────────────────────────────────────────┘ │ ▼ self.channels: dict[str, BaseChannel] 3.2 通信层Channel Channel 是通信模块,底层使用 WebSocket、Http 长轮询(Long Polling)、Webhook等多种技术。 3.3 消息流程: Inbound (接收用户消息) 在实例化具体频道时,ChannelManager 会将 MessageBus(self.bus)作为参数传递给它们。 具体的频道实例从外部平台接收到消息后,会直接使用这个 MessageBus 将消息投递到内部系统的处理队列中。 ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ 聊天平台 │ │ Channel │ │ MessageBus │ │ (Telegram/ │────▶│ (实现类) │────▶│ (队列) │ │ Discord/ │ │ .start() │ │ │ │ Feishu...) │ │ 监听消息 │ │ publish_ │ └─────────────┘ └──────┬──────┘ │ inbound() │ │ └──────┬──────┘ │ │ │ ┌──────▼──────┐ │ │ Agent │ │ │ (处理消息) │ │ └─────────────┘ │ ┌──────────────┴──────────────┐ │ _handle_message() │ │ 1. is_allowed() 权限检查 │ │ 2. 构建 InboundMessage │ │ 3. bus.publish_inbound() │ └─────────────────────────────┘ 四、阶段二:访问控制与路由 此阶段Nanobot实现非常简单,相比之下OpenClaw会复杂一些,做了不同群聊、私聊等不同级别的会话权限控制。 五、阶段三:提示词构建ContextBuilder 我们知道大龙虾是用户私人机器人,那么她怎么做到感知用户,有记忆的? 我们说了很多的session、memory、Skills等在哪个环节被使用的? Skills与Tools的区别是什么? Nanobot将prompt做了拆分,将各种来源的信息(系统身份、历史对话、记忆、技能等)组装成 LLM 能理解的格式。 Agents.md 举例 # 代理指令 你是一个有帮助的 AI 助手。保持简洁、准确和友好。 ## 定时提醒 当用户要求在特定时间提醒时,使用 `exec` 运行: >Nanobot cron add --name "reminder" --message "您的信息" --at "YYYY-MM-DDTHH:MM:SS" --deliver --to "USER_ID" --channel "CHANNEL" 从当前会话中获取 USER_ID 和 CHANNEL(例如,从 `telegram:8281248569` 中获取 `8281248569` 和 `telegram`)。 **不要只是将提醒写入 MEMORY.md** — 那不会触发实际通知。 ## 心跳任务 每 30 分钟检查 `HEARTBEAT.md`。使用文件工具管理周期性任务: - **添加**:使用 `edit_file` 追加新任务 - **移除**:使用 `edit_file` 删除已完成的任务 - **重写**:使用 `write_file` 替换所有任务 当用户要求创建重复/周期性任务时,更新 `HEARTBEAT.md` 而不是创建一次性 cron 提醒。 为什么不直接写成prompt,因为分开写更容易阅读和维护。 5.1 在整个流程中的位置 消息流程中的 ContextBuilder ──────────────────────────────────────────────────────────────────────── ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ Channel │────▶│ MessageBus │────▶│ AgentLoop │ └─────────────┘ └──────┬──────┘ └──────┬──────┘ │ │ │ ▼ │ ┌───────────────┐ │ │ _process_message() │ └───────┬───────┘ │ │ │ ▼ │ ┌───────────────┐ │ │ ContextBuilder│ ← 本文件 │ │ .build_messages│ │ └───────┬───────┘ │ │ ▼ ▼ ┌─────────────┐ ┌─────────────┐ │ Session │ │ LLM │ │ (历史) │ │ Provider │ └─────────────┘ └─────────────┘ 5.2 构建流程 build_system_prompt() build_system_prompt() │ ├─▶ _get_identity() # 核心身份 │ # Nanobot 🐈 │ You are Nanobot, a helpful AI assistant. │ Runtime: macOS x86_64, Python 3.x.x │ Workspace: /path/to/workspace │ ├─▶ _load_bootstrap_files() # 引导文件 │ 加载: AGENTS.md, SOUL.md, USER.md, TOOLS.md, IDENTITY.md │ ├─▶ memory.get_memory_context() # 长期记忆 │ ## Long-term Memory │ (从 memory/MEMORY.md 读取) │ ├─▶ Skills.load_Skills_for_context() # 常用技能 │ # Active Skills │ └─▶ Skills.build_Skills_summary() # 技能摘要 # Skills (可用技能列表,供 LLM 按需加载) 构建系统提示词 (build_system_prompt) 构建prompt通过以下5个步骤分别获取5个部分的prompt内容并拼接。 将多个来源的信息拼接成完整的 system prompt: ① _get_identity()构建本地设备信息、划定存储目录、行为准确prompt 这是源码: def _get_identity(self) -> str: """Get the core identity section.""" workspace_path = str(self.workspace.expanduser().resolve()) system = platform.system() runtime = f"{'macOS' if system == 'Darwin' else system} {platform.machine()}, Python {platform.python_version()}" return f"""# Nanobot 🐈 You are Nanobot, a helpful AI assistant. ## Runtime {runtime} ## Workspace Your workspace is at: {workspace_path} - Long-term memory: {workspace_path}/memory/MEMORY.md (write important facts here) - History log: {workspace_path}/memory/HISTORY.md (grep-searchable). Each entry starts with [YYYY-MM-DD HH:MM]. - Custom Skills: {workspace_path}/Skills/{{Skill-name}}/Skill.md ## Nanobot Guidelines - State intent before tool calls, but NEVER predict or claim results before receiving them. - Before modifying a file, read it first. Do not assume files or directories exist. - After writing or editing a file, re-read it if accuracy matters. - If a tool call fails, analyze the error before retrying with a different approach. - Ask for clarification when the request is ambiguous. Reply directly with text for conversations. Only use the 'message' tool to send to a specific chat channel.""" ② ._load_bootstrap_files() 添加预设的个性、行为准则等prompt Agents.md、Soul.md、User.md是预设的prompt部分内容,可以设定用户个性、设定智能体的行为准则、哪些事情能做,哪些事情不能做,遇到特定事情要怎么做,设定有哪些工具可以用。 ③ memory.get_memory_context() 长期记忆的prompt构建 长期记忆的文件存储在memory/MEMORY.md文件中,程序在运行中会不断维护这个文件。memory.get_memory_context() 为读取该文件内容。 ④ Skills.load_Skills_for_context() 拼接常用技巧的prompt 注意Skill与tools的区别: 在 AI 智能体的开发语境中,Skill(技能)和 Tool(工具)在很多框架(如 OpenAI API、LangChain)中经常被混用,但从概念和设计逻辑上,它们有明显的层级和本质区别: Skill:Agent解决特定问题的一套工作流(Workflow)或经验(Prompt + Logic)。它通常包含“什么时候用”、“怎么用”以及“如何处理结果”的认知。 Skill = prompt + Tools + Workflow。它是prompt中的一个组成部分,可以在开始时传递,也可以在后续过程中传递。 Tool:纯粹的代码逻辑、API 或物理/软件接口。它不具备智能,只负责“输入 A,输出 B”。特点:通用性强,与具体的 Agent 无关。谁都可以调用它。它通过Function Call参数传递,不在prompt中。 ⑤ build_Skills_summary() 构建技能摘要列表 用于生成所有可用技能的摘要列表,以 XML 格式返回,供 LLM 了解有哪些技能可以调用。 采用渐进式加载策略:只给 LLM 看技能名称和描述,完整内容需要时再通过 read_file 读取 以天气技能为例,获得的摘要内容如下: # Skills The following Skills extend your capabilities. To use a Skill, read its Skill.md file using the read_file tool. Skills with available="false" need dependencies installed first - you can try installing them with apt/brew. <Skills> <Skill available="true"> <name>weather</name> <description>Get current weather and forecasts (no API key required).</description> <location>/path/to/Nanobot/Skills/weather/Skill.md</location> </Skill> </Skills> 构建消息列表 (build_messages) 生成发送给 LLM 的完整消息数组: 处理图片 (_build_user_content) 如果用户发送了图片,将图片转为 base64 编码后放入消息: # 输入: 用户消息 + 图片路径列表 # 输出: [ {"type": "image_url", "image_url": {"url": "data:image/png;base64,..."}}, {"type": "text", "text": "用户消息内容"} ] 5.3 数据流示例 用户发送: "帮我看看这个图片里有什么" + 图片文件 │ ▼ ContextBuilder.build_messages() │ ┌───────────────────┼───────────────────┐ │ │ │ ▼ ▼ ▼ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ System │ │ History │ │ Current │ │ Prompt │ │ (Session)│ │ Message │ │ │ │ │ │ + Image │ └──────────┘ └──────────┘ └──────────┘ │ │ │ └───────────────────┼───────────────────┘ │ ▼ [ {"role": "system", "content": "你是 Nanobot..."}, {"role": "user", "content": "之前对话..."}, {"role": "user", "content": "Current Time..."}, {"role": "user", "content": [ {"type": "image_url", "image_url": {"url": "data:image/..."}}, {"type": "text", "text": "帮我看看这个图片里有什么"} ]} ] │ ▼ LLM Provider 5.4 内置Skill清单 当前可用 Skills 列表 六、阶段四、阶段五:AgentLoop Nanobot的奥秘,即 如何循环迭代直至完成任务?循环是否有上限? 是怎么调用Tools来完成的,要调哪些Tools? Token消耗巨大的原因是什么? 答案都在AgentLoop中。 Agent 模块是 Nanobot 的核心处理引擎,负责在接收消息后调用 LLM、执行工具、生成回复,并管理会话状态和长期记忆。 6.1 核心组件 agent/ ├── loop.py # AgentLoop - 核心处理代码 ├── context.py # ContextBuilder - 构建系统提示词 ├── memory.py # MemoryStore - 两层记忆系统 ├── Skills.py # SkillsLoader - 技能加载器 ├── subagent.py # SubagentManager - 子任务管理器 └── tools/ # 工具集 ├── base.py # Tool 抽象基类 ├── registry.py # ToolRegistry 工具注册表 ├── filesystem.py # 文件操作工具 ├── shell.py # ExecTool 命令执行 ├── message.py # MessageTool 消息发送 ├── web.py # WebSearchTool, WebFetchTool ├── spawn.py # SpawnTool 子任务触发 ├── cron.py # CronTool 定时任务 └── mcp.py # MCP 工具连接 6.2 AgentLoop 核心处理流程 核心循环_run_agent_loop的位置 _process_message() 是处理单条消息的核心方法: _process_message(msg: InboundMessage) │ │ 1. 获取或创建 Session ▼ session = sessions.get_or_create(session_key) │ │ 2. 检查是否需要历史记录 │ (当 messages 超过 memory_window) ▼ 触发 _consolidate_memory() 异步任务 │ │ 3. 构建 Context (系统提示词 + 历史) ▼ ContextBuilder.build_messages() │ │ 4. 运行 Agent Loop ▼ _run_agent_loop(messages, on_progress) │ │ 5. 保存会话 & 内存合并 ▼ _save_turn() + sessions.save() + _consolidate_memory() │ │ 6. 返回响应 ▼ OutboundMessage → bus.publish_outbound() 源代码如下: 核心循环_run_agent_loop内部实现 run_agent_loop实现 ReAct 模式(推理 + 行动),用于执行 AI 代理(Agent)的核心迭代循环。它的主要作用是不断与大语言模型(LLM)交互,直到模型给出最终回答或达到最大迭代次数。 ① 参考源码可以看到循环是通过while实现 循环的调用大模型以及执行大模型返回需要运行的工具,持续将大模型结果以及工具执行结果添加到上下文中。 ② 循环的极限是多少?设置最大迭代次数,避免无限循环: 超过前迭代次数小于设定的最大迭代次数(self.max_iterations,设置的是40次),则结束执行并返回已有结果。 现在让我们回答开篇的问题2: 如何循环迭代直至完成任务?循环是否有上限?while实现、上限40次; 并且粗暴的while循环,与快速增加的上下文增长(prompt增长),使得token消耗非常大。 ③ Tools是怎么调用的? # 1.调用大模型API response = await self.provider.chat( messages=messages, tools=self.tools.get_definitions(), # 可用的Tools清单、使用说明书 model=self.model, temperature=self.temperature, max_tokens=self.max_tokens, ) 在prompt中已经将tools清单和使用说明书添加,大模型返回信息中会告诉应该要调用哪些工具。(众所周知,这是大模型的基本特性) 一份说明书通常是一个 JSON Schema,包含: a.工具名称(如 get_weather) b. 功能描述(如“获取指定城市的天气情况”) c. 参数要求(如需要 city 字段,类型为字符串) 大模型自主决策并生成调用指令(触发调用) 大模型会按照约定的格式(通常是 JSON),输出一个包含工具名称和具体参数的特殊响应。 宿主系统(这里为电脑)并反馈(实际执行) 程序提取参数并在本地实际执行对应的函数:await self.tools.execute(tool_call.name, tool_call.arguments),并获得结果。 ④ 如何完成本地电脑操作的与任务执行的? 通过上述分析,我们可以知道是通过大模型返回参数(动态的程序)、并结合本地tool(shell脚本等)来完成的。 包含以下几个关键环节: 大模型生成代码(参数):大模型根据用户的自然语言指令,推理出需要执行的步骤,并编写相应的代码(通常是 Python、Shell 脚本、AppleScript 或 PowerShell)。这段代码就是大模型调用 Tool 时传入的“参数”。 Tool 执行环境(执行器):你提到的“shell脚本运行程序”在实际框架中通常是一个代码执行沙箱。这个 Tool 接收到代码后,会在真实的操作系统或 Docker 容器中拉起对应的解释器(如 Python 解释器、Jupyter 内核或 Bash 终端)来运行这段代码。 备注:这部分我们在Tools章节单独讲解 到这里,我们其实已经知道了Nanobot核心所在了。其他的内容诸如提示词构建、记忆、Skills、tools等只是“噪音”。后面环节为我们继续揭秘。 七、阶段六:发送回复 消息流程: Outbound (发送回复) ChannelManager 在启动时会创建一个后台协程任务 _dispatch_outbound。 该任务通过 self.bus.consume_outbound() 不断从消息总线中监听并拉取待发送的 OutboundMessage。 获取到消息后,通过 msg.channel 识别目标平台,找到对应的频道实例,并调用 channel.send(msg),由具体频道类完成向外部平台的最终网络发送请求。 ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ Agent │ │ MessageBus │ │ Channel │ │ (生成回复) │────▶│ (队列) │────▶│ Manager │ │ │ │ │ │ │ │ publish_ │ │ consume_ │ │ _dispatch │ │ outbound() │ │ outbound() │ │ _outbound() │ └─────────────┘ └──────┬──────┘ └──────┬──────┘ │ │ │ ▼ │ ┌─────────────────┐ │ │ channel.send() │ │ │ (调用具体实现) │ │ └────────┬────────┘ │ │ │ ▼ │ ┌─────────────────┐ │ │ 对应平台 API │ │ │ (Telegram Bot/ │ │ │ Discord REST/ │ │ │ Feishu Web...) │ │ └─────────────────┘ │ │ │ ▼ │ ┌─────────────────┐ └───────────▶│ 用户收到消息 │ └─────────────────┘ 六个阶段回顾 图片来源:链接 八、Agent记忆存储-Memory 8.1 MemoryStore 两层记忆系统 架构设计 MemoryStore 采用两层记忆架构,在会话内存和长期存储之间进行平衡: ┌─────────────────────────────────────────────────────────────────┐ │ 两层记忆架构 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ Session (内存) │ │ ┌─────────────────────────────────────────┐ │ │ │ messages: [...] │ │ │ │ last_consolidated: int │ │ │ │ updated_at: datetime │ │ │ └─────────────────────────────────────────┘ │ │ │ │ │ │ _consolidate_memory() (当 messages 超过 memory_window) │ │ ▼ │ │ ┌──────────────────────┐ ┌────────────────────────────┐ │ │ │ memory/MEMORY.md │ │ memory/HISTORY.md │ │ │ │ (长期记忆) │ │ (可 grep 搜索的历史日志) │ │ │ │ │ │ │ │ │ │ ## Long-term Memory │ │ [2026-03-0510:30] USER: │ │ │ │ - 重要事实 │ │ [2026-03-0510:32] ASSISTANT│ │ │ │ - 用户偏好 │ │ [2026-03-0510:35] USER: │ │ │ │ - 项目知识 │ │ │ │ │ └──────────────────────┘ └────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘ 内存合并触发条件 自动触发: unconsolidated >= memory_window(默认 100 条消息) 手动触发:用户发送 /new 命令(立即归档所有记忆) 合并流程 _consolidate_memory(session, archive_all=False) │ │ 1. 提取待合并的消息 ▼ old_messages = session.messages[last_consolidated:-keep_count] │ │ 2. 构建 prompt 给 LLM ▼ prompt = "Process this conversation and call save_memory tool..." │ │ 3. 调用 LLM 进行记忆抽取 ▼ response = await provider.chat(messages, tools=[save_memory]) │ │ 4. 解析 LLM 返回的工具调用 ▼ args = response.tool_calls[0].arguments │ ├─▶ history_entry → append_history() # 写入 HISTORY.md └─▶ memory_update → write_long_term() # 更新 MEMORY.md 九、能力提升的奥秘-Tools工具 前面提到的热点15个能力,主要靠本章节提到的这些工具。如果缺乏这些工具,那么各类龙虾机器人跟服务端运行的智能体没有太大区别。 那么, 沙箱安全环境真的存在吗,是否影响宿主系统? 9.1 Tool 工具系统 工具基类 所有工具继承自 Tool 抽象基类: deny_patterns = [ r"\brm\s+-[rf]{1,2}\b", # rm -r, rm -rf r"\bdel\s+/[fq]\b", # Windows del /f r"\brmdir\s+/s\b", # Windows rmdir /s r"(?:^|[;&|]\s*)format\b", # format 磁盘 r"\b(mkfs|diskpart)\b", # 磁盘操作 r"\bdd\s+if=", # dd 裸设备 r">\s*/dev/sd", # 写磁盘设备 r"\b(shutdown|reboot|poweroff)\b", # 关机重启 r":\(\)\s*\{.*\};\s*:", # fork bomb ] 内置工具 AgentLoop._register_default_tools() 注册默认工具: 工具执行流程 LLM 返回 tool_calls │ ▼ ToolRegistry.execute(name, arguments) │ ├─▶ 参数校验 (validate_params) │ 对照 JSON Schema 检查类型、必填、范围 │ └─▶ 执行工具 (execute) │ ▼ 返回结果字符串 │ ▼ 添加到 messages: {"role": "tool", "tool_call_id": "...", "name": "...", "content": "..."} 9.2 重点工具Shell介绍 ExecTool 核心解析 重要节点的技术流程 ┌─────────────────────────────────────────────────────────────────────────────┐ │ ExecTool 执行流程 │ └─────────────────────────────────────────────────────────────────────────────┘ LLM 调用 exec(command="...", working_dir="...") │ ▼ ┌─────────────────────────────────────────────────────────────────────────────┐ │ 1. 参数处理 │ ├─────────────────────────────────────────────────────────────────────────────┤ │ cwd = working_dir or self.working_dir or os.getcwd() │ │ env = os.environ.copy() + path_append │ └─────────────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────────┐ │ 2. 安全检查 (_guard_command) │ ├─────────────────────────────────────────────────────────────────────────────┤ │ • 正则匹配 deny_patterns (危险命令) │ │ • 白名单检查 allow_patterns (可选) │ │ • 路径遍历检测 (..) │ │ • 工作区外路径检测 (restrict_to_workspace) │ │ │ │ 返回: None (通过) 或 错误信息 (拦截) │ └─────────────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────────┐ │ 3. 命令执行 │ ├─────────────────────────────────────────────────────────────────────────────┤ │ process = asyncio.create_subprocess_shell( │ │ command, │ │ stdout=PIPE, │ │ stderr=PIPE, │ │ cwd=cwd, │ │ env=env, │ │ ) │ └─────────────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────────┐ │ 4. 超时控制 │ ├─────────────────────────────────────────────────────────────────────────────┤ │ await asyncio.wait_for(process.communicate(), timeout=60) │ │ │ │ 超时处理: │ │ • process.kill() 杀死进程 │ │ • process.wait() 等待完全终止 │ │ • 返回超时错误 │ └─────────────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────────┐ │ 5. 输出处理 │ ├─────────────────────────────────────────────────────────────────────────────┤ │ • stdout/stderr 解码 (UTF-8, errors=replace) │ │ • 拼接 stdout + stderr │ │ • 附加 exit code │ │ • 截断超长输出 (max 10000 chars) │ └─────────────────────────────────────────────────────────────────────────────┘ │ ▼ 返回结果字符串 支持的语言 核心原理:create_subprocess_shell() 调用系统默认 shell,可执行任何系统支持的脚本。 9.3 安全机制 危险命令拦截 deny_patterns = [ r"\brm\s+-[rf]{1,2}\b", # rm -r, rm -rf r"\bdel\s+/[fq]\b", # Windows del /f r"\brmdir\s+/s\b", # Windows rmdir /s r"(?:^|[;&|]\s*)format\b", # format 磁盘 r"\b(mkfs|diskpart)\b", # 磁盘操作 r"\bdd\s+if=", # dd 裸设备 r">\s*/dev/sd", # 写磁盘设备 r"\b(shutdown|reboot|poweroff)\b", # 关机重启 r":\(\)\s*\{.*\};\s*:", # fork bomb ] 白名单模式(可选) allow_patterns = [r"^git", r"^npm", r"^python"] # 只有匹配这些模式的命令才能执行 路径限制(可选) restrict_to_workspace = True # 只能访问工作区 # • 禁止 .. 路径遍历 # • 禁止访问工作区外的绝对路径 运行时保护 9.4 目前的不足 无真正沙箱 正则绕过风险 # 可被绕过的模式 r"\brm\s+-[rf]{1,2}\b" # 绕过方式 rm -rf / # 空格可被特殊字符分割 $(rm -rf /) # 命令替换 `rm -rf /` # 反引号 其他风险 9.5 总结 Nanobot只有简单的安全校验,没有沙箱环境。在 OpenClaw 的架构里,则有沙箱能力。 十、回顾与总结 通过对 Nanobot 源码的剖析,我们已经清晰地看到各类大龙虾华丽外衣下的底层逻辑。 总结来说: 技术上没有魔法,只有工程的巧妙组合:Nanobot并非依赖某种颠覆性的 AI 新算法,而是通过“大模型 API + 循环控制 + 本地脚本执行”的经典架构,完成了从“动嘴”到“动手”的跨越。 真正的突破在于“场景与体验”:它打破了云端沙盒的限制,将 AI 真正下放到了用户的个人电脑中。这种“看得见、摸得着”且能实际操控系统的专属感,才是它引爆科技圈的根本原因。 OpenClaw 的爆火给我们最大的启示是:在底层大模型能力日益强大的今天,如何让 AI 打破边界,与用户的真实环境(系统、文件、应用)产生深度的物理或逻辑交互,提供更具实体感和专属感的应用体验,将是下一代智能体(Agent)落地的核心方向。 备注:Nanobot可能与OpenClaw还有一些细微差异,因此仅能作为参考,请读者注意。