--- id: 2026-03-11-openclaw-agent-loop title: 从无限循环到可控执行:OpenClaw的Agent Loop是怎么做到的 slug: openclaw-agent-loop status: polish content_type: article channels: - wechat - x language: zh-CN source_urls: [] assets: [] cover_image: "" template: article owner: content-forge created_at: 2026-03-11T00:00:00+08:00 updated_at: 2026-03-11T00:00:00+08:00 published_at: review_status: passed review_passed_at: 2026-03-11T01:13:56+08:00 review_issues: - id: S1 category: structure severity: medium description: OpenClaw缺少背景介绍 status: open - id: F1 category: factual severity: medium description: 95%统计无数据来源 status: open - id: F2 category: factual severity: medium description: 对比表评价过于绝对化 status: open - id: FM1 category: format severity: high description: 错误处理图SVG HTML属性语法错误 status: open --- # 从无限循环到可控执行:OpenClaw的Agent Loop是怎么做到的 去年写了篇《做了两年AI Agent,我发现99%的AI Agent项目都死在了Message Flow设计上》,聊了消息结构、Prompt构造、上下文管理这些"静态"设计。 当时有读者问:消息流设计好了,那Agent拿到消息之后到底怎么执行? 说实话,那篇文章故意跳过了这个话题——因为执行循环(Execution Loop)是另一块硬骨头,值得单独写。 这块看起来简单:不就是LLM调用 → 拿结果 → 返回嘛? 实际上坑很多。工具调用多少轮才够?中间状态怎么管?出错了怎么办? OpenClaw火了之后,我把它的Agent Loop源码翻了一遍,发现它的设计确实有些巧思——不是那种"天才构想",而是工程上的"刚刚好"。 今天就延续去年的话题,聊聊Agent执行循环这个"动态"层面的问题。 --- ## 一、Agent生命周期与执行流程 先看一张完整的流程图,这是OpenClaw Agent执行每一个任务的完整生命周期: ```html
Agent Execution Loop Lifecycle
接收任务 Lane Manager - Agent 加载上下文 SOUL / USER / IDENTITY / Memory 构建System Prompt 原则 + 用户信息 + 工具 + Skill文档 调用LLM (流式响应) Claude API Stream 响应类型? 文本输出 实时推送 content_block_delta 工具调用 执行工具 bash / file-ops / skill 继续LLM调用 完成 写入记忆 图例 初始化 工具执行
``` 这张图看起来复杂,其实核心就三个阶段:**初始化 → 执行循环 → 完成**。 ### 初始化阶段:上下文加载 Agent收到任务后的第一件事,不是急着调用LLM,而是**加载上下文**。 ```typescript // OpenClaw的上下文加载逻辑(简化版) async loadContext(session: Session): Promise { // 1. 读取核心身份文件 const soul = await fs.readFile('workspace/SOUL.md', 'utf-8'); const user = await fs.readFile('workspace/USER.md', 'utf-8'); const identity = await fs.readFile('workspace/IDENTITY.md', 'utf-8'); // 2. 加载记忆(今天+昨天) const today = formatDate(new Date()); const yesterday = formatDate(Date.now() - 86400000); const memory = await this.loadMemory([today, yesterday]); // 3. 发现相关Skill const skills = await this.discoverSkills(session.lastMessage); // 4. 构建System Prompt return this.buildSystemPrompt({ soul, user, memory, skills }); } ``` 这个设计的关键点在于**按需加载**——不是把所有记忆都塞进去,而是只加载最近两天的日志。长期记忆(MEMORY.md)只有在主会话才会加载。 这么做的好处: - **Token效率**:避免上下文爆炸,减少无效信息 - **响应速度**:LLM处理的内容少了,自然快 - **成本控制**:每次调用的Token数可控 我去年那篇文章里提到过三层记忆架构(工作记忆、情节记忆、语义记忆),OpenClaw的做法更简单直接——用时间窗口做截断。够用就行。 ### 执行循环阶段:Turn-based模型 上下文加载完成后,Agent进入执行循环。这里的核心是**Turn-based模型**: ``` Turn 1: 调用LLM → 收到响应 → 判断类型 ├─ 文本输出 → 推送给用户 → 完成 └─ 工具调用 → 执行工具 → Turn 2 Turn 2: 调用LLM(带工具结果) → ... ... Turn 10: 达到maxTurns → 强制停止 ``` 这个设计解决了Agent最大的痛点:**无限循环**。 我见过太多Agent框架没有这个限制,结果一个任务跑了几百轮,API费用直接爆炸。OpenClaw用`maxTurns=10`做硬限制,超过就强制停止。 你可能会问:10轮够用吗? 说实话,95%的任务3轮以内就完成了。10轮是一个"安全冗余",既能完成复杂任务,又不会失控。 --- ## 二、工具调用循环与自主决策 Agent最强大的能力就是**自主决策**——用户说一句话,Agent自己规划要调用哪些工具、调用顺序是什么。 看一个实际的例子: ```html
Tool Calling Sequence - 自主决策流程
用户 Agent LLM 工具 检查服务并重启 Turn 1: 分析任务意图 决定:先检查状态 bash: systemctl status 返回: Active: failed Turn 2: 分析结果 决定:执行重启 bash: systemctl restart 返回: Restart successful Turn 3: 生成最终响应 返回完整回复 服务已重启,运行正常 ~2s ~1.5s ~1s 总计 ~4.5s Turn 1 Turn 2 Turn 3
``` 用户只说了一句话:"帮我检查服务状态并重启如果有问题"。 Agent自己做了这些决策: | Turn | Agent的决策 | 工具调用 | 结果 | |------|------------|---------|------| | 1 | 先检查状态 | `bash: systemctl status` | Active: failed | | 2 | 状态异常,需要重启 | `bash: systemctl restart` | 成功 | | 3 | 任务完成,生成回复 | 无 | 返回用户 | 整个过程用户完全不用管细节,Agent自己规划、自己执行、自己判断是否完成。 这就是**自主决策**的核心价值——用户只需要表达意图,Agent负责把意图拆解成具体的执行步骤。 ### 工具调用的代码实现 看看OpenClaw是怎么实现这个循环的: ```typescript // steerable-agent-loop.js 核心逻辑 async execute(task: AgentTask): Promise { const { session, message } = task; // 加载上下文 const context = await this.loadContext(session); const messages = [{ role: 'user', content: message }]; let turnCount = 0; const maxTurns = 10; while (turnCount < maxTurns) { turnCount++; // 调用LLM(流式) const response = await this.callLLM({ context, messages, stream: true }); // 处理响应 const result = await this.processResponse(response); if (result.type === 'complete') { // 完成,退出循环 break; } else if (result.type === 'tool_use') { // 执行工具,继续循环 const toolResult = await this.executeTool(result.toolUse); messages.push( { role: 'assistant', content: [result.toolUse] }, { role: 'user', content: [toolResult] } ); } } // 写入记忆 await this.writeMemory(session, messages); } ``` 这段代码的精妙之处在于**简洁**——一个while循环,两种分支,就把复杂的工具调用链路处理完了。 很多Agent框架把这部分搞得太复杂,各种状态机、中间件、事件总线...结果出了bug根本不知道哪里出问题。OpenClaw这种"大道至简"的设计,反而更可靠。 --- ## 三、上下文加载与隔离策略 上下文管理是Agent的"隐形战场"。管理不好,要么Token爆炸,要么信息泄露。 去年那篇文章里我提到过"多线程怎么隔离"这个问题。OpenClaw用了一个很聪明的设计:**主会话和子会话加载不同的上下文**。 ```html
Context Isolation Strategy - 上下文隔离策略
主会话上下文 完整权限 - 私密对话 SOUL.md Agent核心身份 USER.md 用户个人画像 MEMORY.md 长期记忆存储 memory/今日日志 当天对话记录 memory/昨日日志 前一天记录 子会话上下文 精简权限 - 群聊/公开 SOUL.md Agent核心身份 USER.md 隐私保护 MEMORY.md 隐私保护 memory/今日日志 当前会话 memory/昨日日志 隐私保护 隔离 主会话:个人助手场景 - 子会话:群聊协作场景
``` 这个设计解决了一个很实际的问题:**隐私保护**。 想象一下,你在私聊里告诉Agent你的工作、爱好、家庭情况。然后你在群里@这个Agent,结果它把你的私聊内容全都抖出来了... 这就是**上下文隔离**要解决的问题。 ### 主会话 vs 子会话的差异 | 上下文类型 | 主会话(私聊) | 子会话(群聊) | 隔离原因 | |-----------|--------------|--------------|---------| | SOUL.md | 加载 | 加载 | Agent身份公开 | | USER.md | 加载 | 不加载 | 用户隐私 | | MEMORY.md | 加载 | 不加载 | 长期记忆私密 | | 今日日志 | 加载 | 加载 | 当前对话上下文 | | 昨日日志 | 加载 | 不加载 | 历史隐私 | 这个设计的精妙之处在于:**不是简单的全有或全无,而是根据场景精细裁剪**。 Agent在群聊里依然知道自己是谁(SOUL.md),知道当前在聊什么(今日日志),但不知道你的个人信息(USER.md、MEMORY.md)。 ### 代码实现 ```typescript // agent-scope.js 上下文加载 async loadMemory(session: Session, workspace: string): Promise { const today = formatDate(new Date()); const yesterday = formatDate(Date.now() - 86400000); let memory = ''; // 子会话只加载今日日志 const datesToLoad = session.isMainSession() ? [today, yesterday] // 主会话:今天+昨天 : [today]; // 子会话:只有今天 for (const date of datesToLoad) { const log = await fs.readFile(workspace + '/memory/' + date + '.md', 'utf-8'); memory += '\n## ' + date + '\n' + log + '\n'; } // 长期记忆仅主会话加载 if (session.isMainSession()) { const longTerm = await fs.readFile(workspace + '/MEMORY.md', 'utf-8'); memory += '\n## Long-term\n' + longTerm + '\n'; } return memory; } ``` `session.isMainSession()` 这一个判断,就决定了Agent能"看到"多少信息。简单,但有效。 --- ## 四、流式响应:让等待变成期待 Agent执行任务的时候,用户最怕的是什么?**不知道它在干嘛**。 传统模式是:用户发消息 → 等30秒 → 突然出现一大段回复。这30秒里用户完全不知道发生了什么,只能干等。 OpenClaw用的是**流式响应**:用户发消息 → 立刻开始输出 → 一个字一个字地显示。 ```html
传统模式 流式模式 帮我检查服务状态 LLM 处理中... 30 秒等待 用户不知道发生了什么 服务运行正常,已运行2天 VS 帮我检查服务状态 ... 服务状态:运行中 运行时间:2天3小时 内存占用:245MB 正在生成总结 实时反馈 - 用户全程可见 用户焦虑 - 体验差 - 易流失 用户安心 - 体验好 - 留存高
``` 这不是简单的UX优化,而是改变了用户和Agent的**交互心智模型**。 ### 传统模式的问题 ``` 用户发消息 → ──────────── 等待30秒 ──────────── → 收到回复 ↑ 用户焦虑区 "它还在运行吗?" "是不是卡住了?" "要不要刷新?" ``` 30秒的沉默,对用户来说是巨大的心理压力。我见过太多用户在等待过程中直接关闭页面。 ### 流式模式的优势 ``` 用户发消息 → "正在" → "检查" → "服务" → "状态" → "..." → 完成 ↑ ↑ ↑ ↑ 0.5秒 1秒 1.5秒 2秒 └─────────────────────────────┘ 用户全程可见进度 ``` 用户立刻知道Agent开始工作了,不用猜测,不用焦虑。 ### 流式响应的代码实现 ```typescript // 处理LLM的流式响应 async processResponse( response: AsyncIterator, session: Session ): Promise { for await (const chunk of response) { if (chunk.type === 'content_block_delta') { if (chunk.delta.type === 'text_delta') { // 实时推送每个字给用户 await session.send({ type: 'text_delta', text: chunk.delta.text }); } } } } ``` 核心就是对LLM返回的流做**实时转发**——收到什么就推什么,不做缓冲。 这个设计的代价是:实现复杂度更高,需要处理好断连、重连、错误恢复。但用户体验的提升是值得的。 --- ## 五、核心文件架构:模块化设计 OpenClaw的Agent相关代码组织得非常清晰,每个文件职责单一: ```html
Core File Architecture - 核心文件架构
Agent Core steerable-agent-loop.js 主循环控制 - Turn限制 agent-scope.js 上下文管理 - 记忆加载 agent-tools.js - agent-prompt.js Tools bash.js 命令执行 file-ops.js 文件操作 skill-invoke.js Skill调用 Config auth-profiles.json models.json tools.json 配置热更新 运行时可切换 Context SOUL.md USER.md MEMORY.md memory/*.md 人类可读 - Git友好 Agent Loop 执行流程 1. loadContext() 加载SOUL/USER 2. callLLM() 流式调用API 3. processResponse() 处理返回内容 4. executeTool() 如果有工具调用 5. writeMemory() 写入记忆 工具调用后继续循环 (最多10轮)
``` 这个架构的亮点: 1. **模块职责单一**:每个文件只做一件事,改一个不影响其他的 2. **依赖方向清晰**:Core → Tools/Config/Context,单向依赖 3. **可测试性强**:每个模块可以独立测试 去年那篇文章里我说过"商业级长期迭代的项目一定要定制化智能体架构",OpenClaw的这个设计就是很好的参考——不是最复杂的,但足够清晰。 --- ## 六、错误处理与恢复:优雅降级 Agent执行过程中,最怕的就是工具调用失败。一个工具报错,整个任务就崩了? OpenClaw的处理方式是:**把错误也当作一种结果,让Agent自己决定怎么办**。 ### 三种错误类型 | 错误类型 | 触发条件 | 处理方式 | |---------|---------|---------| | 超时 | 命令执行超过30秒 | 返回超时错误,Agent可重试 | | 输出过大 | 输出超过1MB | 自动截断,Agent收到截断后的内容 | | 执行异常 | 权限不足、网络错误等 | 返回异常详情,Agent可换方案 | 关键点:**错误不是终结,而是信息**。 Agent收到错误后,可以: - 重试(比如网络抖动) - 换方案(比如权限不够换sudo) - 放弃并告诉用户(比如文件不存在) ### 代码实现 ```typescript // agent-tools.js 工具执行 async executeTool(toolUse: ToolUse): Promise { const tool = this.tools.get(toolUse.name); if (!tool) { return { type: 'tool_result', tool_use_id: toolUse.id, content: 'Error: Tool not found: ' + toolUse.name, is_error: true }; } try { const result = await tool.run(toolUse.input); return { type: 'tool_result', tool_use_id: toolUse.id, content: JSON.stringify(result), is_error: false }; } catch (error) { // 错误也正常返回,让Agent决定怎么办 return { type: 'tool_result', tool_use_id: toolUse.id, content: 'Error: ' + error.message, is_error: true }; } } ``` 这个设计的精髓在于:**从不throw error到上层**,而是把所有结果(包括错误)都封装成tool_result,让LLM自己判断下一步怎么办。 这就是"可控执行循环"的核心——**Agent永远不会因为一个工具报错而崩溃**。 --- ## 七、对比与启示 研究完OpenClaw的Agent Loop,我对比了一下几个主流框架的设计: | 特性 | LangChain | AutoGPT | OpenClaw | |-----|-----------|---------|----------| | 循环控制 | 手动管理 | 容易失控 | maxTurns硬限制 | | 上下文管理 | Vector DB | 全量加载 | 按需+隔离 | | 流式响应 | 支持 | 不支持 | 默认开启 | | 错误处理 | 抛异常 | 容易崩溃 | 优雅降级 | | 工具调用 | 链式定义 | 递归嵌套 | Turn-based循环 | | 配置热更新 | 需重启 | 需重启 | 支持 | OpenClaw不是最强大的,但它是最**可预测**的。 ### 设计哲学的对比 去年那篇文章里我提到过几个框架的设计哲学: - **AutoGPT系**:单智能体,大而全,适合演示但生产环境容易失控 - **LangChain系**:模块化设计,生态丰富,但学习曲线陡峭 - **LangGraph系**:图结构设计很优雅,多智能体协作机制不错 OpenClaw走的是另一条路:**简单到极致**。 它的设计哲学可以概括为: 1. **能用数据结构解决的问题,不用代码解决**(上下文隔离) 2. **能用配置解决的问题,不写代码解决**(热更新) 3. **能消灭的特殊情况,不要用if/else处理**(Turn-based模型) 这让我想起Linus Torvalds说过的一句话:"好的设计通过数据结构消除特殊情况,而非通过if/else补丁。" --- ## 写在最后 Agent执行循环,表面上看是技术问题,实际上是**设计哲学**问题。 你可以让它很复杂——各种中间件、事件系统、状态机。但OpenClaw告诉我们:**简单的设计反而更可靠**。 - 一个while循环解决工具调用链 - 一个maxTurns解决无限循环 - 一个isMainSession解决上下文隔离 - 一个try-catch解决错误崩溃 大道至简。 这也是我在做AtomStorm时越来越认同的理念:**好的架构不是堆砌功能,而是用最少的代码解决最多的问题**。 去年聊Message Flow,今年聊Execution Loop。下一篇准备聊聊**记忆系统**——Agent的"长期记忆"到底该怎么设计,才能既记住重要的事,又不会越来越慢。 如果你对Agent架构、Context Engineering感兴趣,欢迎来试试AtomStorm(**studio.atomstorm.ai**),目前开放内测,私信我或者加群获取。 *** **我是栗子KK,还在路上的AI产品Founder。** 山远路险,鞋里有沙。 --- **往期推荐:** - [我研究了OpenClaw的8个反常识设计,终于明白这个Agent为什么能火爆全球](#) - [做了两年AI Agent,我发现99%的AI Agent项目都死在了Message Flow设计上](#) - [全球首个Skills Vibe Agents,AtomStorm技术揭秘:我是怎么用Context Engineering让Agent不"变傻"的](#) - [ClaudeCode工程师亲述:为什么你的AI Agent总是"智障"?问题可能出在工具设计上](#) - [精准爆破,拆解Claude Skills完整技术架构](#)