OpenHands
以 Agent 状态机、事件流与中断恢复为核心的 AI 编程 Agent 生态;2026 年重构为多仓——OpenHands/OpenHands 仅前端(agent-canvas React SPA),Python 运行时在 OpenHands/software-agent-sdk,由 OpenHands/openhands-server 托管。本篇记录三仓分工、运行时真实位置与一条 agent run 生命周期。
[!info] related notes
OpenHands
这是什么
以 Agent 状态机、事件流、中断恢复 为核心的 AI 编程 Agent 生态。用户想用它补「Agent Runtime 专项」:一个 Agent run 的生命周期、Action/Observation 数据模型、事件如何驱动状态、用户中断后如何恢复、后端事件如何映射到 React UI、runtime 与 agent policy 怎样隔离。
[!warning] 重要:2026 年已重构为多仓,原仓库不再是单体 经典架构(用户引用,对应历史上的
openhands/Python 包):Agent AgentController State Action Observation EventStream ← 组件通信的中心 Runtime这些 Python 核心已经不在
OpenHands/OpenHands的main分支里。本次实抓确认:OpenHands/OpenHands的main现在是纯前端agent-canvas(React 19 SPA),全仓只有src/下的.tsx/.ts,没有任何openhands/Python 包、没有pyproject.toml。All-Hands-AI/OpenHands是镜像仓,同样已前端化。Python 运行时真实位置(已查实):
OpenHands/software-agent-sdk—— Agent 运行时 SDK(openhands-sdkv1.41.0,Py≥3.12,commit281843c78094,2026-08-10)。含openhands-sdk(核心)、openhands-agent-server(FastAPI 托管)、openhands-tools、openhands-workspace。OpenHands/openhands-server—— 进程宿主(openhands_serverv0.1.0,Py≥3.12,commit6ff130c8317b,2025-09-29),对外提供 API 与事件流(SSE)。所以「读 OpenHands 的 Agent runtime」实际要读 三个仓:前端 UI(
OpenHands/OpenHands)+ SDK(software-agent-sdk)+ 宿主(openhands-server)。这本身就是一个值得学的「前端 / 运行时协调 / Python 模型与 Agent」三栈分工范例。
在你三层参考架构里的位置
用户定位:Agent Runtime 备选教材——专学「Agent 状态机 + 事件流 + 中断恢复 UI」。业务是代码执行 agent(sandbox/terminal/browser/repository),复杂度高于 BodySense,当专项教材而非整体模板。其「三仓分离」恰好对应 BodySense 应坚持的分工。
版本快照
| 仓 | 分支 | commit | 语言/版本 | 角色 |
|---|---|---|---|---|
OpenHands/OpenHands | main | 21f0967c8f7b(2026-08-11) | React 19.2.8 + react-router 7.18.2(@openhands/agent-canvas v1.12.0) | 前端 agent-canvas SPA + Electron 桌面端 |
OpenHands/software-agent-sdk | main | 281843c78094(2026-08-10) | Python >=3.12(openhands-sdk v1.41.0) | Python Agent 运行时 SDK(核心) |
OpenHands/openhands-server | main | 6ff130c8317b(2025-09-29) | Python >=3.12(openhands-server v0.1.0) | 进程宿主:API + 事件流(SSE) |
三仓分工与连接
OpenHands/OpenHands (React SPA, agent-canvas)
└─ ACP client src/api/agent-server-adapter.ts
│ HTTP/SSE(Agent Client Protocol)
▼
OpenHands/openhands-server (Python, openhands_server)
└─ api.py → event_router.py / event_service.py (SSE 事件流)
│ in-process 调用
▼
OpenHands/software-agent-sdk
├─ openhands-agent-server (FastAPI 托管 Agent)
└─ openhands-sdk/openhands/sdk/ ← Python 运行时核心
agent/ conversation/ event/ context/ tool/ llm/
经典架构 → SDK 真实模块映射
| 经典概念 | SDK 真实位置(software-agent-sdk/openhands-sdk/openhands/sdk/) |
|---|---|
Agent | agent/agent.py(+ agent/acp_agent.py) |
AgentController | conversation/goal/controller.py + conversation/goal/runner.py(运行循环) |
State | conversation/state.py |
Action | event/llm_convertible/action.py(+ event/user_action.py、event/acp_tool_call.py) |
Observation | event/llm_convertible/observation.py |
EventStream(中心) | event/(base.py / conversation_state.py / types.py / token.py)+ conversation/event_store.py(持久化) |
Runtime | context/(agent_context.py / memory.py / view/)提供 sandbox/文件系统/workspace 视图;+ 宿主仓 sandbox/ |
[!note] 没有顶层
runtime/包 旧架构里的Runtime在 SDK 里不单独成包,而是拆成了「执行上下文context/」+「宿主仓的sandbox/(docker 沙箱)」。读的时候把这两者合起来理解成 runtime 即可。
仓库鸟瞰:Python 运行时核心(software-agent-sdk)
software-agent-sdk/
├── openhands-sdk/openhands/sdk/ # 核心运行时
│ ├── agent/ # agent.py / acp_agent.py / abstract.py
│ ├── conversation/
│ │ ├── conversation.py state.py # State
│ │ ├── goal/ # controller.py(控制器)+ runner.py(循环)+ judge.py
│ │ ├── event_store.py # 事件流持久化
│ │ ├── cancellation.py # 中断取消
│ │ └── impl/ local_conversation.py / remote_conversation.py
│ ├── event/
│ │ ├── base.py conversation_state.py token.py types.py
│ │ ├── llm_convertible/ # action.py(Action) / observation.py(Observation)/ message.py
│ │ ├── user_action.py acp_tool_call.py
│ │ └── resume_transcript.py # 中断恢复:重放事件流
│ ├── context/ # agent_context.py / memory.py / view/(Runtime 视图)
│ ├── tool/ llm/ critic/ mcp/ skills/ workspace/
│ └── pyproject.toml
├── openhands-agent-server/openhands/agent_server/ # FastAPI 托管
│ ├── api.py event_router.py event_service.py sockets.py pub_sub.py
│ ├── conversation_router.py conversation_service.py
│ └── pyproject.toml
└── openhands-tools/ openhands-workspace/ examples/ tests/
宿主仓 OpenHands/openhands-server 的 openhands_server/:
openhands_server/
├── api.py # FastAPI 装配
├── event/ # event_router.py / event_service.py(SSE 事件流)
├── sandbox/ # docker 沙箱(Runtime 的执行环境)
├── sandboxed_conversation/ # 托管对话的生命周期
├── user/ services/ utils/ dependency.py database.py
前端仓 OpenHands/OpenHands 的 src/(agent-canvas):api/(ACP client)、stores/、types/(action/observation/agent-state)、components/、routes/。
顶层边界职责
| 仓 / 目录 | 回答什么问题 | 备注 |
|---|---|---|
OpenHands/OpenHands/src/ | 事件如何渲染、对话状态怎么存 | React SPA + Electron |
openhands-server/openhands_server/ | API 与事件流怎么对前端暴露 | SSE 是关键通道 |
software-agent-sdk/.../sdk/agent/ | 一次 run 怎么驱动 LLM | Agent 抽象 |
.../sdk/conversation/goal/ | run 循环与控制器 | controller + runner |
.../sdk/event/ + conversation/event_store.py | Action/Observation 与事件流 | 通信中心 |
.../sdk/context/ + 宿主 sandbox/ | Runtime 视图与执行环境 | 合起来 = 旧 Runtime |
分层与依赖方向
flowchart TB
UI[OpenHands/OpenHands src/ React] -->|ACP/SSE| SRV[openhands-server api.py]
SRV --> EVT[event_service SSE]
SRV --> AS[software-agent-sdk agent_server]
AS --> AGENT[sdk/agent/agent.py Agent.run]
AGENT --> CTRL[conversation/goal/controller+runner]
CTRL --> STATE[conversation/state.py]
CTRL --> ACT[event/llm_convertible/action.py Action]
ACT --> LLM[(LLM)]
LLM --> OBS[event/llm_convertible/observation.py Observation]
OBS --> STREAM[event/ + event_store.py EventStream]
STREAM -->|SSE| UI
关键入口与调用链
| 角色 | 路径 | 说明 |
|---|---|---|
| 前端 ACP 适配 | OpenHands/OpenHands/src/api/agent-server-adapter.ts | 前后端协议适配 |
| 前端事件 store | src/stores/event-message-store.ts | 事件流如何进 React |
| 宿主 API | openhands-server/openhands_server/api.py | FastAPI 装配 |
| 宿主事件流 | openhands-server/openhands_server/event/event_service.py | SSE 推事件 |
| Agent 抽象 | software-agent-sdk/.../sdk/agent/agent.py | Agent.run |
| 运行控制器 | .../sdk/conversation/goal/controller.py + runner.py | run 循环 |
| 状态 | .../sdk/conversation/state.py | State |
| Action / Observation | .../sdk/event/llm_convertible/{action,observation}.py | 数据模型 |
| 事件流持久化 | .../sdk/conversation/event_store.py | EventStream 落盘 |
| 中断 / 恢复 | .../sdk/conversation/cancellation.py / event/resume_transcript.py |
阅读路线(聚焦 Agent Runtime)
- 前端
src/types/的action-type/observation-type/agent-state(数据模型,对应经典架构)—— 但注意这是前端镜像,真实定义在下一条。 software-agent-sdk/.../sdk/event/llm_convertible/{action,observation}.py(真实 Action/Observation 来源)sdk/agent/agent.py(Agent.run入口)+sdk/conversation/goal/{controller,runner}.py(运行循环)sdk/conversation/state.py+event_store.py(State 与 EventStream)sdk/conversation/cancellation.py+event/resume_transcript.py(中断与恢复)openhands-server/openhands_server/event/event_service.py(事件如何经 SSE 到前端)- 回到前端
src/stores/event-message-store.ts+src/api/agent-server-adapter.ts(事件如何映射到 React)
深挖一条线:一次 agent run 的生命周期(跨三仓)
选「用户发一条消息 → Agent 跑一轮 → 流式回结果 → 中途可中断/恢复」作为主线。这条线穿过了全部三个仓,正好印证三栈分工。
[前端 OpenHands/OpenHands]
src/components/ 用户提交消息
→ src/api/agent-server-adapter.ts ACP client 发 HTTP/SSE 请求
│
▼ (Agent Client Protocol over HTTP/SSE)
[宿主 OpenHands/openhands-server]
openhands_server/api.py FastAPI 接收
→ event/event_router.py → event_service.py 建立 SSE 事件流通道
│ in-process 调用
▼
[SDK OpenHands/software-agent-sdk]
openhands-agent-server/.../conversation_service.py 托管对话
→ sdk/agent/agent.py Agent.run(ctx, input)
→ sdk/conversation/goal/controller.py 进入运行控制器
→ goal/runner.py 运行循环:取 State
→ conversation/state.py 读取/更新 State
→ event/llm_convertible/action.py 构造 Action(调 LLM)
→ LLM 返回
→ event/llm_convertible/observation.py 构造 Observation
→ event/ + conversation/event_store.py 追加进 EventStream(中心)
→ 每个事件经 SSE 推回前端
│
▼ (SSE 回传)
[前端]
event_service.py → SSE → src/stores/event-message-store.ts → components 增量渲染
中断(用户取消):sdk/conversation/cancellation.py 置取消标志 → goal/runner.py 循环在下一轮检查到 → 跳出 → 写一条取消 Observation 进 EventStream。
恢复(resume):sdk/event/resume_transcript.py 重放 EventStream 中的历史事件 → 重建 conversation/state.py 的 State → 从断点继续,无需重跑全部。
[!tip] 跟这条线时的断点建议
sdk/conversation/goal/runner.py循环体 —— 看一轮 run 如何被切成 Action→LLM→Observation。event/llm_convertible/action.py构造处 —— Action 与 LLM 请求如何对应。event/event_store.py追加处 —— EventStream 作为「通信中心」为什么是单一真相源。openhands-server/.../event_service.pySSE 推送处 —— 后端事件如何变成前端能消费的事件。- 前端
src/stores/event-message-store.ts—— 同一个事件如何驱动 React 重渲染。
自检(能不看代码回答就过关):
- 一个 Agent run 为什么是「事件序列」而非「一次返回」?(EventStream 是中心)
- Action 与 Observation 谁产生谁消费?中间隔了什么(LLM)?
- 中断为什么能「无损」恢复?(EventStream 可重放 → 重建 State)
- 前端
action-type.tsx与 SDKaction.py是否同源 schema?(决定前后端契约如何对齐——待验证)
值得偷师 / 不建议照抄
| 做法 | 评价 | 我的判断 |
|---|---|---|
| Action / Observation 显式数据模型 | 强 | BodySense 的 runtime_event / interaction 可直接对照 |
| EventStream 作为通信中心(单一真相源) | 强 | 事件驱动 UI 的清晰范式,且天然支持重放/恢复 |
| 中断恢复 = 重放事件流重建 State | 强 | 对照 BodySense 的 checkpoint / ask_user / resume |
| 三仓分离:前端 / 运行时协调 / Python 运行时 | 强 | 与 BodySense 应坚持的分工同构 |
| SSE 做后端→前端事件流 | 中 | 与 BodySense 当前 SSE 方案可对齐 |
| 代码执行 agent 的 sandbox/terminal/browser | 中 | 业务远超 BodySense,不当模板 |
| 仓库规模与 Electron 双端 | 弱 | 不抄 |
我的疑问与待验证
Python Agent 运行时在哪?已查实:在OpenHands/software-agent-sdk(核心)+OpenHands/openhands-server(宿主),不在OpenHands/OpenHands的main(那里已纯前端化)。- 前端
src/types/action-type.tsx的 Action 模型与 SDKevent/llm_convertible/action.py是否同源生成(共享 schema)?这决定了前后端契约如何对齐,也决定「改一处要不要改两处」。值得读openhands-agent-server的 openapi/类型生成确认。 software-agent-sdk与openhands-server两个仓的版本(v1.41.0vsv0.1.0)演进节奏不一致,宿主如何锁定 SDK 版本?对照 BodySense 的 Go/Python 协同发版。