拆解 OpenHands:AI 编码 Agent 怎么执行代码
拆解开源 AI 编码 Agent OpenHands 如何规划、改文件、在沙箱里跑代码。事件流循环、动作-观察循环,以及为什么隔离才是全部关键。
TL;DR — OpenHands(原 OpenDevin)让 AI 编码 Agent 跑在一个循环上:模型发出一个 Action(跑命令、改文件、浏览网页),沙箱化的 runtime 执行它,结果作为 Observation 返回。这个动作-观察循环,记录到事件流里,就是整个架构。聪明的部分不是模型,而是 runtime 隔离——它让模型能安全地跑自己刚写的任意代码。
OpenHands 到底是什么
OpenHands 是一个面向 AI 软件开发 Agent 的开源平台。你给它一个任务(“修好 auth.py 里失败的测试”),它做的事跟开发者一样:读文件、写代码、跑测试、读报错、再试。它靠在 SWE-bench Verified 上领跑开源阵营拿到了 60K+ GitHub star。
大多数报道停在”它帮你写代码”。这不有趣。有趣的是底下那个循环,因为 2026 年每个严肃的编码 Agent 都收敛到这个循环。理解它,你就知道怎么造一个,而不只是怎么用。
核心循环:动作与观察
剥掉 UI,OpenHands 就是一个循环:
flowchart LR
A[Agent: LLM] -->|发出 Action| B[Runtime]
B -->|在沙箱执行| C[结果]
C -->|包装成 Observation| D[事件流]
D -->|追加到历史| A
每一轮:
- Agent(一个 LLM)看事件流历史,发出一个 Action。
- Runtime 在隔离沙箱里执行该动作。
- 结果变成 Observation。
- 观察被追加到事件流,循环重复,直到 Agent 发出
finish动作。
动作是一个封闭词表。主要的几个:
| 动作 | 做什么 | 返回的观察 |
|---|---|---|
CmdRunAction | 跑 shell 命令 | stdout、stderr、退出码 |
FileEditAction | 改文件(行范围或整体) | 成功或 diff |
FileReadAction | 读文件内容 | 文件文本 |
IPythonRunCellAction | 在 Jupyter kernel 跑 Python | cell 输出 |
BrowseURLAction | 抓取/交互网页 | 页面内容 |
AgentFinishAction | 宣告任务完成 | 终止循环 |
这是关键设计决策:Agent 没有任意能力。它有一组固定动作,每个都带一个返回的带类型观察。这个约束让 Agent 可调试。出问题时,你重放事件流,精确看到哪个动作产生了哪个观察。
事件流就是记忆
没有单独的”记忆模块”。事件流——每个动作和观察的有序日志——就是 Agent 的工作记忆。每一轮 Agent 的上下文都从这条流重建。
这很优雅,也有真实代价。长任务产生长事件流,而流每一轮都被重放进 context window。一个 40 步的调试 session 能轻松冲破 100K token。OpenHands 用 condensation 处理:旧事件被总结,让上下文有界。这跟到处驱动 Agent 记忆架构的 context window 压力是同一个,只是用在编码循环上。
Runtime:真正的工程在这里
这是大多数人忽略的洞察。LLM 挑动作是简单的部分——任何前沿模型都能做。难的、有价值的部分是 runtime:动作执行的沙箱环境。
想想你实际在干什么。一个语言模型写代码,然后在一台机器上跑它。如果那台机器是你的笔记本或生产服务器,一个坏动作——rm -rf、fork bomb、被污染依赖触发的外传脚本——你就完了。模型没有后果的概念;它靠模式匹配走到动作。
OpenHands 把每个动作跑在 Docker 容器(或远程 runtime)里。Agent 拿到一个完整的 Linux 环境:shell、文件系统、Python kernel、网络访问。但这是个一次性环境。最坏情况,Agent 砸了一个你删掉重建的容器。
对任何执行生成代码的 Agent,这是不可商量的。我们在为什么自主 Agent 需要安全沙箱里做了完整论证,而 OpenHands 是这个原则的参考实现:沙箱不是外挂功能,而是整个循环坐落其上的地基。
# runtime 契约的概念形态
class Runtime:
def execute(self, action: Action) -> Observation:
"""在隔离沙箱里跑一个动作,返回发生了什么。
Agent 永远碰不到宿主。每个副作用都被关在这里。"""
...
为什么动作-观察格式胜过自由格式
你可能会问:为什么不直接让模型写个脚本整个跑掉?因为动作-观察循环在每一步都给 Agent 反馈。
当 Agent 跑 pytest 看到 ImportError: no module named requests,这个观察会在下一个动作之前返回。Agent 读到它,发出 pip install requests 作为下一个动作。自由格式的”写脚本然后跑”会让整个脚本失败,被迫从头重启。
这种逐步反馈是编码 Agent 在 2025-2026 年大幅变好的原因。这也是为什么人类开发者不会写 200 行然后跑一次——你增量地跑、对错误做反应。这个架构把那个工作流编码进去了。
如果你要造一个,这意味着什么
你不需要 OpenHands 也能用上它的经验。可迁移的架构:
- 定义封闭的动作词表。 别给 Agent “做任何事”。给它
run_command、edit_file、read_file、finish。受约束的动作就是可调试的动作。 - 让每个动作返回带类型的观察。 观察是 Agent 自我纠错的方式。丰富的错误观察胜过干净的失败。
- 永远在隔离里跑动作。 如果你的 Agent 能执行代码,那代码跑在容器或 microVM 里,绝不在宿主上。没有例外。
- 把事件日志当记忆。 全部追加。增长时总结。重放它来重建上下文。
FAQ
OpenHands 跟 Devin 是一回事吗?
不是。Devin 是 Cognition 的闭源商业 Agent。OpenHands(原名 OpenDevin)是开源项目,最初是社区想造个类似东西的努力。它们目标相同——自主软件工程——但 OpenHands 是 MIT 协议、可自部署。
OpenHands 能不用 Docker 跑吗?
它可以用本地 runtime,但你真的不该不带沙箱跑。整个安全模型依赖 Agent 在隔离环境里执行代码。直接在宿主上跑动作,等于拿掉了保护你免受坏动作的唯一东西。
什么模型能配 OpenHands?
它通过 LiteLLM 做到模型无关,所以任何 OpenAI 兼容端点都行——Claude、GPT-4o、Gemini、DeepSeek 和开源模型。编码表现因模型差很多;最强的 SWE-bench 分数来自前沿模型。你可以通过 SandBase 这样的单一网关路由,换模型不改代码。
Agent 怎么知道自己做完了?
它发出 AgentFinishAction。Agent 自己根据事件流判断任务完成——比如测试通过之后。这也是一个失败模式:Agent 有时会过早宣告胜利,所以在 finish 前加一个验证步骤(跑测试、检查输出)很重要。
这跟 CrewAI 这种多智能体框架有什么不同?
OpenHands 是单个 Agent 在一个紧凑的执行循环里,为编码优化。CrewAI 这类框架编排多个基于角色的 Agent。不同的问题——多智能体那一面见 AutoGen vs CrewAI。你甚至能把 OpenHands 风格的 Agent 当成一个更大 crew 里的节点。
关键要点
- OpenHands 就是一个循环:Agent 发出 Action,沙箱 runtime 执行它,结果作为 Observation 返回,事件流记录一切。
- 事件流就是记忆。没有单独的记忆模块,只有一个 append-only 日志每轮重放进上下文(增长时做 condensation)。
- 工程价值在 runtime,不在模型。隔离让模型跑自己刚写的代码变得安全。
- 造你自己的:封闭动作词表、带类型观察、强制沙箱、事件日志当记忆。这就是模板。


