OpenHands (formerly OpenDevin)
一句话定位
任务给的种子 URL github.com/All-Hands-AI/OpenHands 会 301 跳转到
github.com/OpenHands/OpenHands(组织改名 All-Hands-AI → OpenHands),但截至 2026 年该仓库
已不是 agent harness 本体——其 README 明确写着代码已搬家:“OpenHands Agent 与 Agent Server
的源码在 OpenHands/software-agent-sdk;Agent Canvas 的源码在 OpenHands/agent-canvas”。也就是说:
OpenHands/OpenHands 现在是 “Agent Canvas”——一个可以驱动 OpenHands 自家 agent、也可以通过
ACP(Agent Client Protocol)驱动 Claude Code / Codex / Gemini CLI 等第三方 agent 的自托管控制面
- Web UI(FastAPI
app_server+ 前端),本身不实现 agent 循环。
真正的 agent harness——loop、tools、prompt、context/condenser、安全、子 agent、skill/plugin——都在
另一个仓库 github.com/OpenHands/software-agent-sdk 里。本 dossier 主体基于该仓库撰写,
OpenHands/OpenHands(Agent Canvas)只作为上层消费/编排 UI 提及。
核心架构总览(目录结构关键路径 + 引用的 commit)
两个仓库均为 git clone --depth 1,克隆日期 2026-07-07:
| 仓库 | 克隆地址 | commit SHA | commit 日期 |
|---|---|---|---|
| Agent Canvas(原”OpenHands”) | github.com/All-Hands-AI/OpenHands(跳转到 github.com/OpenHands/OpenHands) | 4bde696f1fbac42b59083d612b90bb515813a640 | 2026-07-06 09:14:21 -0600 |
| software-agent-sdk(真正的 harness) | github.com/OpenHands/software-agent-sdk | 2ca3b7742c5b42928e50df75038a30e8ccb11017 | 2026-07-06 19:32:57 +0200 |
software-agent-sdk 是 uv 管理的 Python monorepo,含 4 个可分发包:
software-agent-sdk/
├── openhands-sdk/openhands/sdk/ — 核心:agent, conversation, context(prompts/condenser),
│ tool, security, hooks, critic, subagent, skills, mcp,
│ observability, event
├── openhands-tools/openhands/tools/ — 具体工具实现:terminal(tmux), delegate(多agent), browser…
├── openhands-workspace/openhands/workspace/ — 执行隔离:local/docker/apptainer/remote_api/cloud
├── openhands-agent-server/ — 把 SDK 包成可远程调用的 Agent Server(FastAPI)
├── .agents/skills/ — 开发侧 skill(如 manage-evals)
├── .github/workflows/run-eval.yml — CI 触发的 SWE-bench/GAIA 等评测
└── AGENTS.md(仓库根) — "Known Races and Gotchas"、eval/CI 策略、各包内 AGENTS.md 指引
官方文档(Mintlify,docs.openhands.dev)实际读过的页面:/sdk/arch/overview(四包架构、
Local vs Production/Sandboxed 两种部署模式)、/sdk/arch/design(V0→V1 重写设计原则文档,
解释可选隔离、默认无状态、agent/app 边界清晰、可组合性等动机)。另有三篇官方 blog 被通读,
见文末”一手源存档”。
Agent Loop(主循环 / 何时继续何时停)
两层循环。外层 LocalConversation.run()(openhands-sdk/openhands/sdk/conversation/impl/local_conversation.py:1613-1783)
是一个 while True,每轮调用一次 agent.step(),在持有状态锁的情况下于每步之后检查终止/暂停条件;
内层 Agent.step()/astep()(openhands-sdk/openhands/sdk/agent/agent.py:613-985)是一次
“LLM 补全 + 工具执行”的完整回合。
继续/停止由 ConversationExecutionStatus 枚举驱动:RUNNING 继续循环;遇到 PAUSED、STUCK、
WAITING_FOR_CONFIRMATION 或硬错误则 break;FINISHED 会被特殊处理——Stop hook 有机会否决
(veto)并把状态弹回 RUNNING 再跑一轮(仓库根 AGENTS.md 的 “Known Races and Gotchas” 一节
明确记录了这里存在的一个已知竞态)。硬性上限:max_iteration_per_run(默认 500,触发硬错误
MaxIterationsReached)与 LLM 成本/token 预算检查(_budget_exceeded_detail)。
FinishTool 是内建工具(tool/builtins/finish.py),其调用是正常把状态翻转为 FINISHED 的途径
(经由 _ActionBatch.finalize);单独一个 FinishAction/ThinkAction 不会触发确认模式的门控。
独立的 StuckDetector(conversation/stuck_detector.py)扫描活动分支最近 20 个事件,检测 5 种
重复模式(重复的 action-observation 循环、重复的 action-error 循环、agent 自言自语、交替的
action-observation、上下文窗口报错循环),一旦命中直接把状态强制打成 STUCK,阈值可配置。
记忆与上下文管理(压缩、长期记忆、会话持久化)
压缩/凝缩:LLMSummarizingCondenser(openhands-sdk/openhands/sdk/context/condenser/llm_summarizing_condenser.py,
492 行通读)——用一次 LLM 生成的摘要替换事件日志较早的一半,生成”墓碑”式 Condensation 事件;
底层事件日志本身保持 append-only/可重放,读取时通过 View 抽象懒惰地套用凝缩结果。触发分两类:
SOFT(事件数上限,默认 max_size=240,keep_first=4,可跳过重试)与 HARD(token 超限,或
agent/用户/异常历史恢复流程显式请求,必须成功否则整轮失败)。若首次摘要本身就溢出,
hard_context_reset() 会以逐步截断字符串的方式重试(最多 5 次,每次按 0.8 倍缩放)。
default_condenser() 工厂默认 max_size=80, keep_first=4。
长期/持久记忆:不是向量库,而是仓库根目录的 AGENTS.md——系统提示的 <MEMORY> 段明确点名
它为”仓库特定知识的持久记忆”,每次对话自动作为”repository skill”加载。
会话持久化:EventLog(conversation/event_store.py,读了约 60 行头部)——每个事件一个 JSON
文件,落在可插拔的 FileStore 之下,用 flock 加锁(文档明确说明在 NFS 上不可靠),启动时通过
目录扫描重建索引。这就是字面意义上的磁盘 trajectory 格式,也是 resume_transcript.py(未深入读)
用来恢复对话的依据。
畸形历史/上下文超限类 LLM 错误在 Agent.step() 中被捕获,若存在支持 handles_condensation_requests()
的 condenser,会被路由成一个 CondensationRequest。
工具体系(定义/调用协议/注册/权限)
ToolDefinition[ActionT, ObservationT](Pydantic 类型化的 Action/Observation/Executor 三元组,
定义于 openhands-sdk/openhands/sdk/tool/tool.py,661 行,读了头部)显式仿照 MCP 的工具规范:
ToolAnnotations(readOnlyHint/destructiveHint/idempotentHint/openWorldHint)直接照搬 MCP 的
ToolAnnotations schema——只读工具会跳过安全风险门控。每个内部工具都能双向转换成三种线格式:
to_mcp_tool()/to_openai_tool()/to_responses_tool()。
注册是一个全局线程安全(RLock)的 name→resolver 映射(tool/registry.py,199 行通读):
register_tool() 既接受固定的 ToolDefinition 实例,也接受带 .create(**params) 类方法的
ToolDefinition 子类(工厂模式,例如 BrowserToolSet 可以展开成多个具体工具)。is_usable()/
list_usable_tools() 提供逐工具的可用性检查(例如浏览器工具依赖 Chromium 探测),不可用工具会
被静默排除而非报错。
原生 MCP server 通过基于 FastMCP 的客户端(openhands-sdk/openhands/sdk/mcp/*.py:tool.py,
client.py, config.py, definition.py,仅读了结构/头部)接入,产出 MCPToolDefinition 实例,
复用同一套注册表/派发路径。
Prompt 设计(系统提示结构、动态组装)
不是单一模板,而是一个分区注册表(context/prompts/registry.py + sections/static.py +
sections/dynamic.py + sections/planning.py):约 20 个具名、各自带 guard(ctx) 布尔守卫的
PromptSection,被组装成 (static, dynamic) 一对字符串,按 CacheTier 分组——STATIC 段跨会话
可复用以命中 Anthropic 式 prompt cache;DYNAMIC 段(当前时间、仓库上下文、密钥、自定义后缀)
故意不参与缓存,避免动态内容把静态半部分的缓存打爆。
已读的 static 段(每段各自是一个 XML 风格标签块):SoulSection、RoleSection、MemorySection
(指向 AGENTS.md 作为持久仓库记忆)、EfficiencySection、FileSystemSection、
CodeQualitySection、VersionControlSection、PullRequestsSection、ProblemSolvingSection
(五步工作流 EXPLORATION→ANALYSIS→TESTING→IMPLEMENTATION→VERIFICATION)、
SelfDocumentationSection、SecuritySection(显式的 allow/consent-required/never-do 策略)、
SecurityRiskAssessmentSection(LOW/MEDIUM/HIGH 风险分级,CLI 模式与沙箱模式措辞不同,含”仓库上下文
供应链规则”——对来自 AGENTS.md/.cursorrules/skills 的、疑似 prompt-injection 的模式升级为 HIGH)、
BrowserSection、ExternalServicesSection(强制要求对外发布内容附 AI 披露说明)、
EnvironmentSetupSection、TroubleshootingSection、ProcessManagementSection、
ModelSpecificSection(见”与模型协同设计”节)。
Dynamic 段:DateTimeSection、RepoContextSection(把仓库提供的上下文显式包在
<UNTRUSTED_CONTENT> 警告里——prompt-injection 防御)、AvailableSkillsSection(渐进式披露,
按模型家族门控)、CustomSecretsSection、CustomSuffixSection。此外仍保留一条遗留路径:
可通过 Agent(system_prompt=...) 用单一 Jinja2 模板(system_prompt.j2)整体覆盖。
Router / 编排(任务分解、多 agent、子 agent)
两套独立机制:
1. 进程内子 agent(delegate 内建工具,openhands-tools/openhands/tools/delegate/definition.py
impl.py,410 行通读):command: "spawn"|"delegate"。spawn创建具名子LocalConversation(各自独立 LLM 实例副本、重置过的 metrics、独立持久化目录subagents/下,继承父级确认策略除非 子 agent 定义覆盖,默认max_children=5封顶)。delegate把任务派发给已 spawn 的子 agent, 在并行 Python 线程中执行,阻塞直到全部完成(通过可选confirmation_handler回调处理WAITING_FOR_CONFIRMATION),把文本结果与子 agent 的 LLM cost/usage 指标合并回父会话的conversation_stats(键名delegate:<agent_id>)。
子 agent 的”类型”(用哪个系统提示/工具)由子 agent 注册表(openhands-sdk/openhands/sdk/subagent/AGENTS.md,
169 行通读)解析:Markdown + YAML frontmatter 文件(字段 name/description(含 <example>
触发标签)/tools/model: inherit|<override>/color),发现顺序为项目 .agents/agents/*.md →
项目 .openhands/agents/*.md → 用户 ~/.agents/agents/*.md → 用户 ~/.openhands/agents/*.md →
SDK 内建,优先级严格”先注册者赢”:程序化 register_agent() > 插件提供的 agent > 项目文件 agent >
用户文件 agent > 内建。Markdown 正文成为子 agent 的 system_prompt(以
AgentContext(system_message_suffix=...) 追加,而非整体替换)。
2. 跨 harness 编排:通过 ACP(Agent Client Protocol,JSON-RPC 2.0,agentclientprotocol.com
上的开放标准,非 OpenHands 私有协议)——ACPAgent(openhands-sdk/openhands/sdk/agent/acp_agent.py,
3792 行,仅确认其存在与角色,未深入逐行读)让一个 OpenHands Conversation 把整轮对话委托给外部
ACP 兼容 agent server(Claude Code、Codex、Gemini CLI),外部 agent 自己管理 LLM 调用、工具和上下文。
这正是 Agent Canvas “use any coding agent” 功能背后的机制(据 2026-06-18 官方 blog),本质是一个
路由/宿主层,不是对其他 harness 的重新实现。
Skill / 插件体系
两套有重叠但不同的系统:
Skills(openhands-sdk/openhands/sdk/skills/__init__.py,137 行通读):显式实现
AgentSkills 规范(与 Anthropic 的 Agent Skills 同名同形)——name/description(按规范上限
1024 字符,仓库 AGENTS.md 备忘也提到这点)+ 触发器(BaseTrigger/KeywordTrigger/TaskTrigger),
可来自项目(.agents/skills/)、用户(~/.openhands/skills/)或公开的”OpenHands extensions”仓库;
支持 install_skill/uninstall_skill/enable_skill/disable_skill(一个轻量本地 skill 市场),
另带资源子目录(scripts/、references/、assets/)。通过渐进式披露(<available_skills>
动态段,而非把 skill 全文前置)渲染进 prompt。
Plugins(openhands-sdk/openhands/sdk/plugin/plugin.py,读了头部):文档明确说明其目录结构
照搬 Claude Code 的插件目录布局(.claude-plugin/plugin.json 或 .plugin/plugin.json,含
commands/、agents/、skills/、hooks/hooks.json、.mcp.json),一个插件可以把 skill、
子 agent、hooks、MCP server、slash command 打包在一起,用于跨工具互操作。
自进化能力(自我改进 / 学习型记忆 / eval 驱动纠错)
三种不同机制并存,都是真实且文档化的,但都不是经典意义上的”在线学习”:
运行时迭代精化:CriticBase + IterativeRefinementConfig(openhands-sdk/openhands/sdk/critic/base.py,
115 行通读,另有 critic/impl/agent_finished.py、empty_patch.py、pass_critic.py)——一个
critic 模型对轨迹打分(0-1),评分时机为 FinishAction/agent 最终消息(默认 mode: "finish_and_message")或每个动作("all_actions",更贵);若得分低于 success_threshold(默认
0.6),Conversation.run() 会自动生成后续 prompt 并重试,最多 max_iterations(默认 3)次。
官方 blog 提到的生产 critic 模型是离线训练在真实生产轨迹上(非实时/在线学习):用 24 个
人工设计的”Critic Rubrics”(可从轨迹直接观测的行为特征)做密集监督,辅以稀疏结果代理信号
(PR 合并覆盖率约 6%,代码存活覆盖率约 4%);仅用 benchmark 数据训练测得在生产结果上比随机还差
(AUC 0.45-0.48),而混入生产监督后达到 0.58-0.69。开放权重模型
huggingface.co/OpenHands/openhands-critic-4b-v1.0,论文 arxiv.org/abs/2603.03800。
下游效果:SWE-bench Verified 混合结果子集上 Best@8 达 73.8%(对比 Random@8 57.9%),提前停止把
平均尝试次数从 8.0 降到 1.35。
开发时 eval 驱动的回归控制:CI 集成的 SWE-bench/GAIA/SWT-bench/Commit0/TerminalBench/
ProgramBench 评测套件(.github/run-eval/、.github/workflows/run-eval.yml、
.agents/skills/manage-evals/SKILL.md,读了 80/209 行)——由 PR 标签
(run-eval-1/50/200/500)或 release 触发,结果落 CDN 按 {benchmark}/{model_slug}/{run_id}/
可跨运行 diff,自动回帖 PR。这改变的是代码库本身(人+agent 依据 benchmark 差异迭代 PR),
不是部署模型在推理时的权重。
仓库级”verification stack”(据 2026-06-22 官方 blog,实现为 OpenHands-Extensions 插件,
不在本 SDK 仓库内):在 OpenHands/software-agent-sdk 自己的每个 PR 上跑自动代码评审 bot +
QA agent——1000+ 次评审 / 900+ PR / 38 个 release,评审精度接近人类水平,平均合并耗时降低 58%,
代码覆盖率 +25%,复杂度/安全坏味道指标下降。/iterate skill 闭环(agent 读 CI + 评审反馈,修复,
再推送,循环至绿)。这是 harness 对自己代码库的自我改进,不是对某次运行中 agent 权重的改进。
可观测性(日志 / trace 格式)
基于 OpenTelemetry/Laminar 的 tracing(openhands-sdk/openhands/sdk/observability/laminar.py,
442 行,读了头部):只要设置了 LMNR_PROJECT_API_KEY 或任一 OTEL_* endpoint 环境变量就自动启用;
@observe(name=..., span_type=...) 装饰器包住 agent.step/astep、conversation.run/arun、
工具执行(extract_action_name 做 span 命名)、以及凝缩过程(get_condensation/
hard_context_reset)——即主循环的每个关键阶段都有对应 span。
另一条独立机制:LLM.log_completions=True 会把原始补全 JSON(prompt/response)写到本地文件夹,
或者——对于远程/容器化会话——以 LLMCompletionLogEvent 的形式流回并落入持久化的事件日志本身
(这样即便容器文件系统本身不持久,trace 也能保留在会话记录里)。
安全与权限(审批门、密钥管理)
分层防御:
(a) prompt 级策略——<SECURITY> 静态段明确规定 allow-without-consent / consent-required /
never-do 三级策略(例如:从官方源安装 = OK;写 ~/.ssh、.npmrc、pip.conf = 需征得同意;
挖矿或规避安全机制 = 绝不允许);
(b) 风险标注动作——要求 LLM 对每个工具调用自报 security_risk(LOW/MEDIUM/HIGH)字段,
由 <SECURITY_RISK_ASSESSMENT> 段定义(CLI 模式与沙箱模式措辞不同),含显式”仓库上下文供应链
规则”——对来自不可信仓库上下文(AGENTS.md/.cursorrules/skills)中涉及包管理器配置、自定义
registry、内嵌凭据、pipe-to-shell 模式的内容一律升级为 HIGH;
(c) 确定性兜底——PatternSecurityAnalyzer(security/defense_in_depth/pattern.py,492 行)
用正则扫描工具调用参数(“可执行语料”,仅参数)中的破坏性命令特征(rm -rf、sudo rm、mkfs、
dd of=/dev/、eval(/exec(/os.system(/subprocess.*(、curl/wget 管道到 shell),并扫描
包括 agent 推理在内的全字段(“全字段语料”)中的 prompt-injection 特征(“ignore previous
instructions”、“you are now in X mode” 等),每条规则带稳定的 DET_* 探测器 ID 用于遥测;
(d) 确认门——ConfirmationPolicyBase(AlwaysConfirm/NeverConfirm/
ConfirmRisky(threshold, confirm_unknown))决定某个风险等级是否需要人工确认才能执行
(agent.py 中 _requires_user_confirmation);只读工具(ToolAnnotations.readOnlyHint)
完全跳过风险门控;
(e) hooks——PreToolUse/UserPromptSubmit hook(openhands-sdk/openhands/sdk/hooks/types.py,
HookEventType 枚举:PreToolUse、PostToolUse、UserPromptSubmit、SessionStart、
SessionEnd、Stop——直接照搬 Claude Code 的 hook 事件名;HookDecision: ALLOW/DENY,
ASK 变体被注释掉留作未来扩展)可以在 LLM 自报风险之外独立地程序化 ALLOW/DENY 一个动作或消息
(agent.py 会为被 hook 拦下的动作产出 UserRejectObservation(rejection_source="hook");
local_conversation.py 的 run() 通过 pop_blocked_message 检查用户消息是否被 hook 拦截;
Stop hook 可以否决 agent 结束,回灌一条消息把状态弹回 RUNNING);
(f) 密钥管理——SecretRegistry(conversation/secret_registry.py,226 行,部分读)只有当
bash 命令文本引用了注册的密钥名时才把该密钥值注入环境变量;任何序列化路径上默认脱敏/加密
(依赖 cipher 对象或 expose_secrets 标志),即便某个密钥来源回调后续失败,也会保留”最近一次
导出值”以维持脱敏展示的一致性。
沙箱与执行隔离
明确可选而非强制——这是文档记载的 V0→V1 设计反转:V0 一律在独立 Docker 沙箱进程中跑工具; V1 默认同进程执行,原因是 MCP 假设的是本地/直接执行(2025-09-04 官方 blog “the-path-to-openhands-v1” 提到 V0 的 EventStream 发布/订阅模式导致线程/异步 bug、强制 Docker 沙箱与 MCP 假设冲突、10GB 镜像、 CLI 安装耗时数分钟)。
Workspace 基类:LocalWorkspace(同进程,SDK 核心内置,零额外安装)vs RemoteWorkspace 系列
子类(在独立的 openhands-workspace 包内)——DockerWorkspace(docker/workspace.py,读了
424 行中的 130 行:拉起 ghcr.io/openhands/agent-server:latest-python 预构建镜像,健康检查后
走 HTTP 通信,支持端口探测、GPU/网络/卷选项)、ApptainerWorkspace(HPC 友好的 Docker 替代)、
RemoteAPIWorkspace(对接已运行的 agent-server,不负责容器生命周期)、CloudWorkspace
(OpenHands Cloud 托管,文件最大 1020 行,未深入读)。同一份 agent 代码在这些 workspace 上不加
修改地运行——只是切换 workspace 对象。
实际命令执行由 tmux 支撑(openhands-tools/openhands/tools/terminal/impl.py,读了较大文件中的
50 行;依赖 libtmux),用一个持久化 pane 池(TmuxPanePool,DEFAULT_MAX_PANES),并有明确的
恢复逻辑应对底层 tmux server/session 消失的情况(例如 agent 跑了一个顶层 exit)。
与模型的协同设计
context/prompts/sections/static.py 中的 ModelSpecificSection 把逐模型家族/逐变体的 prompt
微调直接写死进 SDK:给 anthropic_claude 的 <IMPORTANT> 指导(不要过度/不足地执行动作,避免
不必要的防御性编程,快速失败)明显不同于给 google_gemini 的(避免”过度主动”,只回答被问到的);
gpt-5/gpt-5-codex 还有进一步的逐变体覆盖(详细的 preamble 流式输出指令,以及一段 GitHub
inline-PR-review-reply REST API 代码片段——看起来是因为观察到这个模型家族在这一点上会做错才补上的)。
仓库 AGENTS.md 提到 sdk/llm/utils/model_features.py(未直接读)是存放 provider/model 能力差异
(视觉支持、tool-call 怪癖)的指定位置,仓库政策明确要求把这类逻辑排除在 llm.py 本体之外。
Anthropic 专属的错误处理:Claude 模型产生的畸形 tool-use/tool-result 历史被捕获为独立的
LLMMalformedConversationHistoryError(不与通用的上下文超限错误混为一谈),使恢复/凝缩逻辑
可以对它单独处理(见仓库 AGENTS.md 备忘)。
ACPAgent 的存在本身也是一种模型协同设计的体现:意味着这个 harness 可以让另一个模型+harness
组合(Claude Code 自己那套针对 Claude 调过的 harness)与自己的通用、模型无关路径并存运行——
这是对”某些模型/harness 配对能从供应商专属协同设计中获益、而 OpenHands 自己的通用路径无法完全
复制这种收益”的明确承认。
轨迹利用(session/trajectory 是否反哺训练/评测)
是的,具体体现在两条线:
训练:开放权重 critic 模型(openhands-critic-4b-v1.0)专门用从 OpenHands 自家已部署产品
(OpenHands Cloud / Agent Canvas)拉取的真实生产用户-agent 轨迹训练(而非只用 benchmark 轨迹)——
这是”Learning to Verify AI-Generated Code” blog 的核心论点,用测得的 AUC 差距(仅 benchmark 训练
0.45-0.48 vs 混入生产监督 0.58-0.69)作为直接证据,证明真实轨迹能实质性改变模型质量。切分方法:
每个用户-agent 会话被切成若干”segment”(用户请求 → 动作/工具 → 结束),每个 segment 都会获得
密集的”Critic Rubric”标注(24 个可从轨迹直接观测的行为特征),无论该 segment 是否存在稀疏的
真实结果标签(PR 合并/代码存活)。
评测/回归:CI 触发的 SWE-bench/GAIA 等评测(针对特定 SDK commit/分支,由 GitHub 标签触发)
结果存 CDN,按 {benchmark}/{model_slug}/{run_id}/ 索引、可跨运行 diff——这是用轨迹派生的
benchmark 打分来把关/指导 PR,不是严格意义上的训练数据。
使两者都成为可能的机制是 harness 自身的事件日志格式:一切都被捕获为一个类型化、append-only
的 Event 流(sdk/event/),一个事件一个文件持久化(EventLog),这正是 critic 训练或评测
pipeline 需要从中回放/抽取”轨迹”和”segment”的形状——即轨迹格式不是为评测事后拼凑上去的附加物,
而是对话本身的实时表示。
与同类 harness 的关键差异(1-3 条,可以先留一句概述,后续 synthesis 阶段会做跨 harness 对比)
概述性观察(详细跨 harness 对比留给 synthesis 阶段):(1) OpenHands 把”生产轨迹训练出的离线 critic
模型”做成了公开发表+开放权重的独立研究产出(openhands-critic-4b-v1.0,arXiv:2603.03800),
而不只是内部私有的质量门,这在同类 harness 中较为罕见;(2) 通过 ACP 把 Claude Code / Codex /
Gemini CLI 作为可互换的”外部 agent 后端”接入自己的控制面(Agent Canvas),是一种”harness 套 harness”
的编排层设计,而非试图用自己的通用 prompt 去逼近这些供应商专属 harness 的效果;(3) V0→V1 的
“沙箱从强制变为默认关闭”这一设计反转(动机是 MCP 生态假设本地执行)是一个值得在跨 harness 对比中
标注的、与”安全默认”直觉相反的取舍。
原始源码定位
- repo: https://github.com/OpenHands/software-agent-sdk (另:控制面/UI 层 https://github.com/OpenHands/OpenHands ,原 https://github.com/All-Hands-AI/OpenHands )
- commit/version analyzed:
- software-agent-sdk:
2ca3b7742c5b42928e50df75038a30e8ccb11017(2026-07-06 19:32:57 +0200) - OpenHands/OpenHands (Agent Canvas):
4bde696f1fbac42b59083d612b90bb515813a640(2026-07-06 09:14:21 -0600)
- software-agent-sdk:
- 关键文件列表(相对
software-agent-sdk/仓库根):AGENTS.mdopenhands-sdk/openhands/sdk/agent/agent.pyopenhands-sdk/openhands/sdk/agent/base.py,critic_mixin.py,parallel_executor.py,response_dispatch.py,utils.pyopenhands-sdk/openhands/sdk/agent/acp_agent.py(存在性确认,未深入逐行读)openhands-sdk/openhands/sdk/conversation/impl/local_conversation.pyopenhands-sdk/openhands/sdk/conversation/stuck_detector.pyopenhands-sdk/openhands/sdk/conversation/event_store.pyopenhands-sdk/openhands/sdk/conversation/secret_registry.pyopenhands-sdk/openhands/sdk/context/condenser/README.md,llm_summarizing_condenser.pyopenhands-sdk/openhands/sdk/context/prompts/registry.py,section.py,sections/static.py,sections/dynamic.py,sections/planning.pyopenhands-sdk/openhands/sdk/tool/tool.py,registry.pyopenhands-sdk/openhands/sdk/security/analyzer.py,confirmation_policy.py,defense_in_depth/pattern.py,defense_in_depth/grayswan/(未深读),ensemble.py,llm_analyzer.py(未深读)openhands-sdk/openhands/sdk/subagent/AGENTS.mdopenhands-tools/openhands/tools/delegate/definition.py,impl.pyopenhands-sdk/openhands/sdk/hooks/types.py,config.py,executor.py,manager.py,conversation_hooks.py(后四个未深读)openhands-sdk/openhands/sdk/critic/base.py,critic/impl/agent_finished.py,empty_patch.py,pass_critic.py,critic/api/*(未深读)openhands-sdk/openhands/sdk/observability/laminar.pyopenhands-sdk/openhands/sdk/plugin/plugin.pyopenhands-sdk/openhands/sdk/skills/__init__.pyopenhands-sdk/openhands/sdk/mcp/tool.py,client.py,config.py,definition.py(仅结构/头部)openhands-workspace/openhands/workspace/docker/workspace.py,apptainer/workspace.py,remote_api/workspace.py,cloud/workspace.py(未深读)openhands-tools/openhands/tools/terminal/impl.py.agents/skills/manage-evals/SKILL.md.github/workflows/run-eval.yml
一手源存档(sources/)
/Users/zhao/projects/self-wiki/ai-research/sources/harness/openhands/ 下:
NOTES.md— 完整调研笔记(本 dossier 的信息来源)key-files/— 26 个源文件快照(按路径前缀重命名避免冲突),含:agent_agent.py、context_condenser_README.md、context_llm_summarizing_condenser.py、conversation_event_store.py、conversation_local_conversation.py、conversation_secret_registry.py、conversation_stuck_detector.py、critic_base.py、hooks_types.py、observability_laminar.py、plugin_plugin.py、prompts_registry.py、prompts_sections_dynamic.py、prompts_sections_static.py、repo_root_AGENTS.md、security_analyzer.py、security_confirmation_policy.py、security_defense_in_depth_pattern.py、skills_init.py、subagent_AGENTS.md、tool_registry.py、tool_tool.py、tools_delegate_definition.py、tools_delegate_impl.py、workspace_docker_workspace.py
官方 blog/文档(未落盘为文件,URL 记录于 NOTES.md,均已通读):
- https://docs.openhands.dev/sdk/arch/overview
- https://docs.openhands.dev/sdk/arch/design
- https://openhands.dev/blog/the-path-to-openhands-v1 (2025-09-04)
- https://openhands.dev/blog/20260506-the-verification-stack (2026-06-22)
- https://openhands.dev/blog/20260305-learning-to-verify-ai-generated-code (2026-03-05)
- https://openhands.dev/blog/use-any-coding-agent-in-openhands-with-acp (2026-06-18)
未覆盖/待补(下一阶段如需更深可继续):acp_agent.py(3792 行)的 ACP 线协议细节;
critic/api/* 客户端与 hosted openhands-critic-4b-v1.0 的具体交互;/sdk/arch/* 文档站其余
子页面(agent、conversation、llm、tool-system、events、workspace、skill、condenser、security)。