OpenHands (formerly OpenDevin)

一句话定位

任务给的种子 URL github.com/All-Hands-AI/OpenHands 会 301 跳转到 github.com/OpenHands/OpenHands(组织改名 All-Hands-AIOpenHands),但截至 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 SHAcommit 日期
Agent Canvas(原”OpenHands”)github.com/All-Hands-AI/OpenHands(跳转到 github.com/OpenHands/OpenHands)4bde696f1fbac42b59083d612b90bb515813a6402026-07-06 09:14:21 -0600
software-agent-sdk(真正的 harness)github.com/OpenHands/software-agent-sdk2ca3b7742c5b42928e50df75038a30e8ccb110172026-07-06 19:32:57 +0200

software-agent-sdkuv 管理的 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 不会触发确认模式的门控。

独立的 StuckDetectorconversation/stuck_detector.py)扫描活动分支最近 20 个事件,检测 5 种 重复模式(重复的 action-observation 循环、重复的 action-error 循环、agent 自言自语、交替的 action-observation、上下文窗口报错循环),一旦命中直接把状态强制打成 STUCK,阈值可配置。

记忆与上下文管理(压缩、长期记忆、会话持久化)

压缩/凝缩LLMSummarizingCondenseropenhands-sdk/openhands/sdk/context/condenser/llm_summarizing_condenser.py, 492 行通读)——用一次 LLM 生成的摘要替换事件日志较早的一半,生成”墓碑”式 Condensation 事件; 底层事件日志本身保持 append-only/可重放,读取时通过 View 抽象懒惰地套用凝缩结果。触发分两类: SOFT(事件数上限,默认 max_size=240keep_first=4,可跳过重试)与 HARD(token 超限,或 agent/用户/异常历史恢复流程显式请求,必须成功否则整轮失败)。若首次摘要本身就溢出, hard_context_reset() 会以逐步截断字符串的方式重试(最多 5 次,每次按 0.8 倍缩放)。 default_condenser() 工厂默认 max_size=80, keep_first=4

长期/持久记忆:不是向量库,而是仓库根目录的 AGENTS.md——系统提示的 <MEMORY> 段明确点名 它为”仓库特定知识的持久记忆”,每次对话自动作为”repository skill”加载。

会话持久化EventLogconversation/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 风格标签块):SoulSectionRoleSectionMemorySection (指向 AGENTS.md 作为持久仓库记忆)、EfficiencySectionFileSystemSectionCodeQualitySectionVersionControlSectionPullRequestsSectionProblemSolvingSection (五步工作流 EXPLORATION→ANALYSIS→TESTING→IMPLEMENTATION→VERIFICATION)、 SelfDocumentationSectionSecuritySection(显式的 allow/consent-required/never-do 策略)、 SecurityRiskAssessmentSection(LOW/MEDIUM/HIGH 风险分级,CLI 模式与沙箱模式措辞不同,含”仓库上下文 供应链规则”——对来自 AGENTS.md/.cursorrules/skills 的、疑似 prompt-injection 的模式升级为 HIGH)、 BrowserSectionExternalServicesSection(强制要求对外发布内容附 AI 披露说明)、 EnvironmentSetupSectionTroubleshootingSectionProcessManagementSectionModelSpecificSection(见”与模型协同设计”节)。

Dynamic 段:DateTimeSectionRepoContextSection(把仓库提供的上下文显式包在 <UNTRUSTED_CONTENT> 警告里——prompt-injection 防御)、AvailableSkillsSection(渐进式披露, 按模型家族门控)、CustomSecretsSectionCustomSuffixSection。此外仍保留一条遗留路径: 可通过 Agent(system_prompt=...) 用单一 Jinja2 模板(system_prompt.j2)整体覆盖。

Router / 编排(任务分解、多 agent、子 agent)

两套独立机制:

1. 进程内子 agentdelegate 内建工具,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 私有协议)——ACPAgentopenhands-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 / 插件体系

两套有重叠但不同的系统:

Skillsopenhands-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。

Pluginsopenhands-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 + IterativeRefinementConfigopenhands-sdk/openhands/sdk/critic/base.py, 115 行通读,另有 critic/impl/agent_finished.pyempty_patch.pypass_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/astepconversation.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.npmrcpip.conf = 需征得同意; 挖矿或规避安全机制 = 绝不允许);

(b) 风险标注动作——要求 LLM 对每个工具调用自报 security_risk(LOW/MEDIUM/HIGH)字段, 由 <SECURITY_RISK_ASSESSMENT> 段定义(CLI 模式与沙箱模式措辞不同),含显式”仓库上下文供应链 规则”——对来自不可信仓库上下文(AGENTS.md/.cursorrules/skills)中涉及包管理器配置、自定义 registry、内嵌凭据、pipe-to-shell 模式的内容一律升级为 HIGH;

(c) 确定性兜底——PatternSecurityAnalyzersecurity/defense_in_depth/pattern.py,492 行) 用正则扫描工具调用参数(“可执行语料”,仅参数)中的破坏性命令特征(rm -rfsudo rmmkfsdd 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) 确认门——ConfirmationPolicyBaseAlwaysConfirm/NeverConfirm/ ConfirmRisky(threshold, confirm_unknown))决定某个风险等级是否需要人工确认才能执行 (agent.py_requires_user_confirmation);只读工具(ToolAnnotations.readOnlyHint) 完全跳过风险门控;

(e) hooks——PreToolUse/UserPromptSubmit hook(openhands-sdk/openhands/sdk/hooks/types.pyHookEventType 枚举:PreToolUsePostToolUseUserPromptSubmitSessionStartSessionEndStop——直接照搬 Claude Code 的 hook 事件名HookDecision: ALLOW/DENYASK 变体被注释掉留作未来扩展)可以在 LLM 自报风险之外独立地程序化 ALLOW/DENY 一个动作或消息 (agent.py 会为被 hook 拦下的动作产出 UserRejectObservation(rejection_source="hook")local_conversation.pyrun() 通过 pop_blocked_message 检查用户消息是否被 hook 拦截; Stop hook 可以否决 agent 结束,回灌一条消息把状态弹回 RUNNING);

(f) 密钥管理——SecretRegistryconversation/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 包内)——DockerWorkspacedocker/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 池(TmuxPanePoolDEFAULT_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/ 仓库根):
    • AGENTS.md
    • openhands-sdk/openhands/sdk/agent/agent.py
    • openhands-sdk/openhands/sdk/agent/base.py, critic_mixin.py, parallel_executor.py, response_dispatch.py, utils.py
    • openhands-sdk/openhands/sdk/agent/acp_agent.py(存在性确认,未深入逐行读)
    • openhands-sdk/openhands/sdk/conversation/impl/local_conversation.py
    • openhands-sdk/openhands/sdk/conversation/stuck_detector.py
    • openhands-sdk/openhands/sdk/conversation/event_store.py
    • openhands-sdk/openhands/sdk/conversation/secret_registry.py
    • openhands-sdk/openhands/sdk/context/condenser/README.md, llm_summarizing_condenser.py
    • openhands-sdk/openhands/sdk/context/prompts/registry.py, section.py, sections/static.py, sections/dynamic.py, sections/planning.py
    • openhands-sdk/openhands/sdk/tool/tool.py, registry.py
    • openhands-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.md
    • openhands-tools/openhands/tools/delegate/definition.py, impl.py
    • openhands-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.py
    • openhands-sdk/openhands/sdk/plugin/plugin.py
    • openhands-sdk/openhands/sdk/skills/__init__.py
    • openhands-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.pycontext_condenser_README.mdcontext_llm_summarizing_condenser.pyconversation_event_store.pyconversation_local_conversation.pyconversation_secret_registry.pyconversation_stuck_detector.pycritic_base.pyhooks_types.pyobservability_laminar.pyplugin_plugin.pyprompts_registry.pyprompts_sections_dynamic.pyprompts_sections_static.pyrepo_root_AGENTS.mdsecurity_analyzer.pysecurity_confirmation_policy.pysecurity_defense_in_depth_pattern.pyskills_init.pysubagent_AGENTS.mdtool_registry.pytool_tool.pytools_delegate_definition.pytools_delegate_impl.pyworkspace_docker_workspace.py

官方 blog/文档(未落盘为文件,URL 记录于 NOTES.md,均已通读):

未覆盖/待补(下一阶段如需更深可继续):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)。