---
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
```
这张图看起来复杂,其实核心就三个阶段:**初始化 → 执行循环 → 完成**。
### 初始化阶段:上下文加载
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自己做了这些决策:
| 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 - 上下文隔离策略
```
这个设计解决了一个很实际的问题:**隐私保护**。
想象一下,你在私聊里告诉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
```
这不是简单的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 - 核心文件架构
```
这个架构的亮点:
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完整技术架构](#)