OpenHands 这套项目,前几篇拆了事件模型、Agent 循环和工具调用,今天这篇轮到服务。为什么把服务放到第四篇才讲?因为如果一上来就看 API 路由,很容易觉得它就是一个普通的 Web 后端;但真正让 OpenHands 跑出 agentic coding 效果的,是它背后那组服务化边界:接入服务、编排服务、执行服务,以及夹在它们之间的事件流通道。理解了这层结构,本地二次开发、接入自己的模型服务、甚至把 OpenHands 改造成公司内部编码平台,都有了落脚点。适合谁看?想把 OpenHands 当工程样板抄、想知道源码从哪里读、以及正在自己设计 AI Coding 后端的人,这篇应该能给你一点参考。
1. 先分清三个"服务":接入、编排与执行
1.1 概念陷阱:它不像传统微服务
很多人一听到"服务"两个字,第一反应是微服务架构,想到一堆独立部署的进程、注册中心、网关、服务间 REST 调用。OpenHands 给我的第一印象是:它并没有走那条路,至少当前开源版本不是。
它更像一个"模块化单体 + 可选远程执行节点"的混合结构。核心服务都在同一个 Python 进程里被装配起来,通过事件流通信,而不是通过一堆 HTTP 接口互相调用。这个差异非常重要,因为你读源码时如果下意识找 service-to-service 的 RPC 定义,大概率会卡住。
我理解 OpenHands 里的"服务"有三种角色:
- 接入服务:面向浏览器和 CLI,管 WebSocket、会话创建、事件推送。
- 编排服务:面向 Agent 循环,管决策、状态、下一步动作。
- 执行服务:面向沙箱,管跑命令、读写文件、操作浏览器和解释器。
这三种角色不是三个进程,更多是三个职责边界。边界划清楚了,后面无论是改成独立服务,还是继续维持单体,都有余地。
1.2 三个角色各自守住的边界
接入服务最典型的入口是 openhands/server 下面的 FastAPI 应用。你打开路由注册文件,会看到一堆 conversation、session 相关的接口。它做的事情很聚焦:把前端发送的用户消息包装成事件,写进事件流;再把事件流里产生的新事件推回给前端。它不应该自己跑 Agent 循环,也不应该直接去 Docker 容器里执行命令。
编排服务的核心是 Agent Controller。它监听事件流里的状态变化,调用 LLM,拿到决策结果后生成 Action 事件。OpenHands 的 Agent 本身可以是不同策略,比如 CodeActAgent、通用对话 Agent、带视觉模型的 Agent,但无论哪种,它们输出的都是结构化 Action,而不是直接操作系统。
执行服务是 Runtime。它负责把 Action 翻译成真实世界的效果。CmdRunAction 到 Shell 命令,FileEditAction 到文件系统修改,BrowserAction 到浏览器控制。Runtime 把执行结果打包成 Observation 事件,写回事件流。到这里,一轮 Agent 循环才算闭合。
1.3 一张表看懂服务拓扑
| 角色 | 典型模块 | 对外表现 | 容易误解的点 |
|---|---|---|---|
| 接入服务 | se |
