本文由 莫潇羽@源码七号站(www.fuyuan7.com)撰写,转载请注明出处。
快速摘要
OpenHarness 是香港大学数据科学研究所(HKUDS)开源的一套轻量级 Agent 基础设施,用大约 1.1 万行 Python 代码做出了对标 Claude Code 核心架构的一整套 Agent 运行时——Agent Loop、43+ 工具、Skills 按需加载、MEMORY.md 持久记忆、多级权限治理、PreToolUse / PostToolUse 钩子、多 Agent 协调一应俱全;同时附带一个叫 ohmo 的个人 AI 助手,可以挂进飞书、Slack、Telegram、Discord,让模型真的能在你电脑上 fork 分支、写代码、跑测试、开 PR。
它最值得关注的地方有四点:第一,模型与框架彻底解耦,模型只负责"决定做什么",Harness 负责"安全高效地做",整个调用链全程可观测;第二,兼容性拉满,原生支持 anthropics/skills 格式和 Claude 风格的插件布局,旧资产几乎可以平移过来;第三,Provider 全家桶,从 Claude 官方 API、Claude Code 订阅、Codex 订阅、OpenAI、Copilot,到国内的 Kimi、GLM、MiniMax、DeepSeek,再到本地 Ollama 全都覆盖;第四,白盒化,1.1 万行代码读得动、改得动,对想真正吃透 Agent 内部机制的同学是一份难得的工程范本。
想看完整拆解,往下翻。这篇文章我会按"概念—架构—模块—实操"的顺序,把 OpenHarness 的每一块都剖给你看,并且会把我自己上手过程中踩到的几个小坑写在对应章节里。
一、先把 Agent Harness 这个概念掰开揉碎
聊 OpenHarness 之前,得先把"Agent Harness"这个词聊明白,不然后面所有的内容都会显得悬在空中。
1.1 光有大模型,不等于有 Agent
很多刚入门的同学会有个朴素的误解:以为有了 GPT、有了 Claude、有了 Kimi,就等于有了 Agent。实际上完全不是这么回事。
一个孤立的大模型本质上是一个纯文本生成器:你给它一段输入,它给你一段输出,仅此而已。它读不到你电脑里的文件,跑不了一行 Shell 命令,访问不了任何外部 API,更不可能记住前几天你跟它聊过什么。哪怕模型本身再强,它也只是一颗没装手脚的脑子。
而我们日常在产品里看到的所谓 AI Agent——能查日历、能改代码、能开 PR、能跨会话记得你是谁——这一切都不是模型本体提供的,而是模型外面那一圈"工程层"提供的。
这一圈工程层,就是这篇文章的主角:Harness(框架/挽具)。
1.2 Harness 给模型装了"手眼记忆边界"
我比较喜欢 OpenHarness 官方那句概括,朴素但精准:
模型提供智力,Harness 提供手、眼、记忆和安全边界。
把这句话拆开看:
- 手——工具调用能力。读文件、写文件、跑命令、调 API、发请求,全部由 Harness 实现并暴露给模型选择调用。
- 眼——观察反馈能力。工具执行完,Harness 要把结果回吐给模型,模型才能基于结果决定下一步。
- 记忆——上下文持久化。当前会话的滚动上下文是短期记忆,跨会话的偏好、项目知识、任务进展是长期记忆,都要 Harness 维护。
- 边界——权限与安全。哪些目录可以写、哪些命令禁止跑、哪些操作必须人工确认,全靠 Harness 把住门。
把这四件事做到位,一个孤立的语言模型才真正变成一个能在外部世界中持续闭环运行的执行系统。
1.3 模型层与框架层的清晰分工
OpenHarness 在 README 里把这个理念抽成了一句话,我觉得值得抄下来反复琢磨:
The model is the agent. The code is the harness.
翻译过来就是:模型即 Agent,代码即 Harness。这是一种很干净的职责切分——
|
层级 |
职责 |
谁来做 |
|
模型层(Agent) |
推理、规划、决策、生成 |
LLM 本体 |
|
框架层(Harness) |
工具执行、记忆维护、权限控制、多 Agent 协调 |
框架代码 |
这种分层带来的最大好处,是你可以独立替换其中任意一层。今天用 Claude,明天换 Kimi,后天接 DeepSeek,整套外围基础设施一行代码不用改;反过来你想给现有的模型加一种新工具、加一条新权限规则,也不用动模型本身。
莫潇羽@源码七号站 在折腾 Agent 这一年里反复验证过一件事:真正决定一个 Agent 是否"能干活"的,从来不是你换了多新的模型,而是你外围这层 Harness 写得够不够扎实。模型只决定上限的高度,框架决定下限的稳健。
1.4 为什么这件事现在变得重要
2026 年的今天,顶级 LLM 已经把代码生成、逻辑推理、多模态理解推到一个相当夸张的水平。但与此同时,大多数 LLM 应用还停留在"问答框"里:你提问,它回答,对话结束。
要让模型真正介入生产流程,少不了下面这些围绕在模型外面的能力:
- 感知能力:读文件、看网页、跑命令查环境
- 行动能力:写文件、改代码、调外部 API
- 记忆能力:跨会话保留上下文与项目知识
- 安全边界:权限分级、操作审计、异常拦截
- 协作能力:拆任务、起子 Agent、把活分出去
把这五块能力做齐,才算得上一个真正可以投入使用的 Agent 运行时。而 OpenHarness 干的就是把这五块抽象成一套清晰、开源、可检查、可扩展的 Python 实现。
这也是它跟那些"包了一层 prompt 就敢叫 Agent"的项目最大的区别——它不是在卖概念,是在认真做基础设施。
二、OpenHarness 来头与定位
概念清楚了,咱们来看主角本身:OpenHarness 到底是谁做的、定位是什么、热度如何、放在整个生态里属于什么位置。
2.1 港大 HKUDS 团队出品
OpenHarness 的开发团队是香港大学数据科学研究所(HKUDS)——HKU Data Science Lab,这是一支在数据科学和大模型工程方向都有沉淀的学术团队,做开源项目向来比较扎实。
项目本身完全用 Python 实现,发布在 GitHub 的 HKUDS/OpenHarness 仓库下,采用 MIT 许可证,对商用和二次开发都非常友好。我去翻仓库时的几个直观印象:
- 代码体量大约 1.1 万行,相比之下 Claude Code 据社区拆解大约 50 万行——OpenHarness 体量只有它的 1/44 左右。
- 工具数量 43+,对齐 Claude Code 大约 98% 的工具能力。
- 内置命令 54 个,覆盖了 Claude Code 大约 61% 的命令集。
- 仓库自带 114 个测试和 6 套端到端用例,工程化程度对得起"学术团队出品"这块招牌。
体量小、覆盖全、测试全,这三件事凑在一起就有趣了——它给你一个可以认真拆架构的 Harness 实现。你想搞懂 Agent 内部到底怎么跑,去读 50 万行的 Claude Code 容易劝退,但 1.1 万行的 OpenHarness 是真能读完的。
2.2 GitHub 热度与社区反响
社区接受度也很说明问题。开源没多久,OpenHarness 在 GitHub 上就攒到了 1 万多 Star、2000+ Fork,原文里提到的"13 万人收藏"应该是个数字笔误,准确说法是 1.3 万 Star 起步并且还在涨。
围绕项目已经形成了一定的扩展生态:
- 社区版的 SKILL 包陆续在出,覆盖代码审查、文档总结、单测生成等场景。
- Issue 区里有大量 v0.1.x 的小版本迭代记录,从 React TUI 优化、Docker 沙箱后端,到 MiniMax、Gemini 等新 Provider 的内置 preset,更新频率相当稳。
- 一些做 Agent 中台的团队已经把 OpenHarness 拿去当内部运行底座,自己只写上层业务逻辑。
社区活跃 + 学术团队稳健交付,这是一个组合拳,比"几个月没动静的明星项目"靠谱得多。
2.3 它在整个 Agent 生态里站在哪
为了让你更直观地理解 OpenHarness 的位置,我画了一张简单的对照表,把它跟生态里两个相邻的项目摆在一起:
|
维度 |
OpenClaw |
Hermes Agent |
OpenHarness |
|
核心理念 |
云端大脑 + 本地肢体 |
执行—学习—改进循环 |
模型/框架解耦的 Harness 架构 |
|
架构特征 |
中心化网关 + 插件化 |
自学习闭环 |
分层架构聚焦运行时 |
|
代码体量 |
中等 |
中等 |
极轻(约 1.1 万行) |
|
兼容性 |
自定义协议 |
自有生态 |
兼容 Anthropic Skills + Claude 插件 |
|
主要面向 |
企业级集成 |
自进化 Agent |
想拆架构 + 想白盒自建的开发者 |
简单一句话:OpenClaw 像是企业级网关,Hermes 像是自进化大脑,OpenHarness 像是一套结构干净的运行时教材兼底座。它三者的目标人群其实没怎么重叠,更接近"互补"。
2.4 一句话总结这一节
OpenHarness 不是又一个聊天机器人皮肤,而是一套面向"想搞懂 Agent 内部、想自建 Agent 运行层"开发者的开源基础设施。它把"AI 黑盒"打开成了"AI 白盒"——把权限、工具、记忆、Hook 这些抽象概念落到了一份你可以逐行读懂的 Python 代码上。
接下来我们正式进入它的内部,一层一层往下剖。
三、核心架构拆解:分层目录与五大支柱
把 OpenHarness 仓库 clone 下来,第一眼能让人放心的就是它的目录结构——分得很清楚,每个子目录干一件事,几乎没有"杂物间"。
3.1 目录总览
直接上一份精简过的目录结构图,注释贴在每一块上:
openharness/
├── engine/ # 🧠 Agent Loop — query → stream → tool-call → loop
├── tools/ # 🔧 43 个工具 — 文件 I/O、Shell、搜索、Web、MCP
├── skills/ # 📚 Skills 体系 — 按需加载的 .md 知识包
├── plugins/ # 🔌 插件系统 — commands / hooks / agents / MCP server
├── permissions/ # 🛡 安全系统 — 多级权限模式、路径/命令规则
├── hooks/ # ⚡ 生命周期钩子 — PreToolUse / PostToolUse
├── commands/ # 💬 54 个内置命令 — /help、/commit、/plan、/resume 等
├── mcp/ # 🌐 Model Context Protocol 客户端
├── memory/ # 🧠 持久记忆 — 跨会话知识沉淀
├── tasks/ # 📋 后台任务与生命周期管理
├── coordinator/ # 🤝 多 Agent 协调与子 Agent 派发
├── prompts/ # 📝 系统提示组装、CLAUDE.md / Skills 注入
├── config/ # ⚙ 多层配置与迁移
└── ui/ # 🖥 React/Ink 终端 UI 与后端协议
每个模块都不大,但合在一起就构成了完整的 Agent 运行时。我自己折腾下来的体感是,这个目录结构本身就是一份"如何组织一个 Agent 项目"的教材——哪怕你不打算用 OpenHarness,照着这套划分去搭自己的项目都不亏。
3.2 五大支柱:把目录抽象到概念
如果觉得 14 个子目录有点多,可以把它进一步抽象成五大支柱,这是社区里被引用最多的一种概括方式:
flowchart LR
A[Agent Loop<br/>核心闭环] --> B[Harness Toolkit<br/>43+ 工具]
B --> C[Context & Memory<br/>上下文 + 持久记忆]
C --> D[Governance<br/>权限 + 钩子]
D --> E[Swarm<br/>多 Agent 协调]
E --> A
一句话解释每一块:
- Agent Loop:负责"模型说话—工具执行—结果回吐"的闭环。
- Harness Toolkit:负责"具体能做哪些事"——文件、Shell、搜索、网页、MCP、Notebook、任务、定时……都在这。
- Context & Memory:负责"上下文怎么进、记忆怎么留"——CLAUDE.md 自动注入、MEMORY.md 持久化、上下文自动压缩。
- Governance:负责"哪些事可以做、做之前要不要审批"——多级权限模式、路径规则、Hook 钩子、Docker 沙箱。
- Swarm:负责"事情大了怎么办"——子 Agent 派发、团队注册、任务委派。
记住这五大支柱,你再回头看那 14 个目录就会发现它们其实都在为这五件事服务。
3.3 模型 / 框架解耦的工程实现
OpenHarness 用一种很工程化的方式落地了"模型与框架解耦"这件事——它把模型相关的逻辑全部收敛到 engine 层,剩下的工具、权限、记忆、协调都用普通的 Python 接口暴露给 engine 调用。
具体一点:
|
抽象 |
在代码里的体现 |
|
Provider 抽象 |
engine 里维护一套 provider 接口,支持 Anthropic 协议、OpenAI 协议两套消息格式 |
|
Tool 抽象 |
每个工具是一个独立的注册项,自带 schema、权限要求、副作用标记 |
|
Skill 抽象 |
一个 Markdown 文件 + 一段 YAML frontmatter,按需加载到 prompt |
|
Plugin 抽象 |
一个目录里塞 commands / hooks / agents / mcp 配置,整体被框架挂载 |
|
Permission 抽象 |
模式(mode)+ 规则(rule)+ 决策器(resolver)三件套 |
这套抽象的好处,是任意一层都可以单独替换:你想接一个新模型只需要写一个 provider;你想加一个新工具只需要往 tools/ 里塞一个 Python 函数;你想加一条规则只需要在配置里加一行——而不需要把整套引擎都重写一遍。
3.4 一个直观的数据流:从 prompt 到结果
为了让你对架构有更具象的感受,我画一张从你敲下 prompt 到拿到结果的简化数据流:
你敲下 prompt
│
▼
prompts/ 组装系统提示(注入 CLAUDE.md、匹配的 skills、可用工具列表)
│
▼
engine/ Agent Loop 启动 → 调用 provider → 流式拿到模型响应
│
▼ 如果模型说要调工具
permissions/ 检查权限 → hooks/ PreToolUse 钩子 → tools/ 执行 → hooks/ PostToolUse 钩子
│
▼
工具结果 append 回 messages
│
▼
engine/ 把更新后的 messages 再喂回模型 → 模型决定下一步
│
▼ 循环直到 stop_reason != "tool_use"
最终回答返回给你 / TUI 呈现
这套数据流读懂了,OpenHarness 的整个内部基本就通了。
3.5 这套架构最让我欣赏的一点
我在评测过不少类似项目之后,最欣赏 OpenHarness 这套架构的一点是:它把 Agent 系统中"最不性感但最重要"的几件事——工具调用、记忆、权限、多 Agent 协调——做得干净、可靠、可读。
很多框架喜欢堆花哨功能、堆 UI、堆 demo,但是当你真的想自己扩展一点东西时,你会发现底层乱糟糟的、调不动。而 OpenHarness 反过来:它的上层 TUI 朴素到几乎没有"惊艳感",但你打开 engine 和 tools 目录,每一行代码都像是被工程师认真打磨过的——这种感觉在开源项目里是稀缺品。
四、Agent Loop:心脏到底怎么跳
聊完整体架构,我们聚焦到 OpenHarness 的"心脏"——Agent Loop。这是整套 Harness 真正在运行时跑的核心循环,理解它就理解了 Agent 是怎么从"会说"变成"能做"的。
4.1 一段伪代码看清整个循环
直接上一段 OpenHarness 风格的伪代码,把 Agent Loop 写出来:
messages = [system_prompt, user_input]
tools = harness.list_available_tools()
while True:
# 1. 调用模型,流式拿响应
response = await provider.stream(messages, tools)
# 2. 没有工具调用就跳出循环
if response.stop_reason != "tool_use":
break
# 3. 模型要调工具 —— Harness 接管
for tool_call in response.tool_uses:
await permissions.check(tool_call) # 权限校验
await hooks.run("PreToolUse", tool_call) # 前置钩子
result = await tools.execute(tool_call) # 真正执行
await hooks.run("PostToolUse", result) # 后置钩子
messages.append(result) # 结果回吐
# 4. 回到循环顶端,让模型基于新结果继续决策
短短二十来行,把 ReAct 工具调用循环讲得明明白白。模型负责"想",Harness 负责"做",每一步都安全、可观测、可中断。
4.2 流式响应 + 并行工具调用
光有上面那段循环还不够,OpenHarness 在工程实现上做了几件关键的事,让这个循环跑得既快又稳:
- 流式响应:模型一边吐 token,TUI 一边渲染,工具调用一旦解析完就可以马上派发,不用等模型把整段话讲完。
- 并行执行:一次模型响应里如果包含多个工具调用,Harness 会并行派发,而不是一个一个排队,能显著缩短端到端时间。
- 结果聚合:所有工具的结果会按顺序聚合回 messages,再统一喂给下一轮模型推理。
这套设计在你跑一个"读三个文件、grep 一段关键词、再跑一遍测试"的任务时差别非常明显——串行实现可能要十几秒,OpenHarness 这套并行流式实现常常三五秒就回来了。
4.3 指数退避重试:扛住不稳定的 API
任何一个真正在生产环境跑 Agent 的人都体会过:LLM 的 API 偶尔会抽风。429 限流、502 网关错误、长上下文超时,一个都跑不掉。
OpenHarness 在 engine 层内置了指数退避重试机制,简化版逻辑大概是这样:
attempt = 0
backoff = 1.0 # 起始等待 1 秒
while attempt < max_attempts:
try:
return await provider.call(messages)
except RateLimitError:
await asyncio.sleep(backoff)
backoff *= 2 # 1s → 2s → 4s → 8s → ...
attempt += 1
实际实现里还有抖动(jitter)、错误类型区分、断点续传等细节,但核心思路就是上面这套。这件事看着不性感,但跑过 Agent 的人都知道——没有它你压根没法投入长任务跑批。
4.4 Token 计数与成本追踪
OpenHarness 还把一件特别"工程师良心"的事做了:实时 Token 计数 + 成本追踪。
每一次调用模型、每一次工具往 messages 里塞结果,框架都会统计 token 数量,并按照当前 provider 的计价规则换算成预估成本。你随时可以在 TUI 里看到:
Tokens: 12,481 input / 3,204 output
Estimated cost: $0.072 (this session)
这件事在生产环境里太重要了:
- 让你知道一个任务"贵不贵",便于决定是不是要换更便宜的模型跑。
- 跑长任务时可以提前喊停,避免某个 bug 把模型卡在死循环里烧 token。
- 做内部成本分摊时不用自己去拉账单。
4.5 上下文自动压缩
LLM 的上下文窗口虽然越来越大,但放到 Agent 这种多轮多工具调用的场景下,还是很快就会被工具结果撑爆。OpenHarness 的处理方式是:
- 监控当前会话的上下文长度。
- 接近阈值时自动触发压缩——把早期的对话和工具结果摘要成一段精炼的"会话备忘"。
- 把摘要塞回 messages 顶端,丢掉冗长的原始记录。
这样你跑一个连续好几个小时的任务也不会因为上下文爆掉而被迫重开。压缩策略本身在 commands/ 里也开放了 /compact 命令让你手动触发,方便你在关键时刻主动收一波。
4.6 断点续聊与会话恢复
最后一件 Agent Loop 层做的事是断点续聊。每个会话在框架里都有自己的状态文件,记录 messages、工具调用历史、任务进度。你随时可以:
oh --resume <session-id>
把昨天没跑完的活接着干,模型重新加载完上下文之后就能从断点处继续。对长时间跨度的项目(比如一个跨多天迭代的代码重构)来说,这个能力是刚需。
读懂这一节,你应该已经能从"原理层面"理解 Agent Loop 为什么是 OpenHarness 的心脏了——它把模型推理、工具调用、权限校验、钩子触发、上下文管理、成本追踪、错误恢复塞进了同一个清晰的闭环,让上层应用可以心无旁骛地专注业务逻辑。
五、工具集与 Skills 加载:让模型真正动手
聊完心脏,接下来聊手——OpenHarness 的工具系统,以及配合工具一起用的 Skills 机制。
5.1 43+ 工具长什么样
OpenHarness 内置的 43+ 工具,覆盖了一个 Agent 日常需要的绝大多数操作。按类别分一下:
|
类别 |
代表工具 |
用途 |
|
文件 I/O |
read、write、edit、glob、create_file、str_replace |
读写文件、批量匹配、原地替换 |
|
Shell 与系统 |
bash、grep、ls、background_bash |
跑命令、查目录、起后台进程 |
|
搜索与抓取 |
web_search、web_fetch |
联网搜索 + 抓取网页正文 |
|
浏览器 |
浏览器自动化相关工具 |
模拟点击、表单提交 |
|
Notebook |
notebook_read、notebook_edit |
操作 .ipynb 单元格 |
|
任务 |
task_*、todo_write |
维护 TODO 列表与任务状态 |
|
调度 |
cron_* |
起定时任务 |
|
MCP |
mcp_*(动态注册) |
把 MCP 服务器的工具挂到 Agent 上 |
|
协作 |
spawn_subagent、delegate |
派子 Agent、分发任务 |
每一个工具在 OpenHarness 里都是一个注册项,自带:
- 输入参数 schema(让模型能正确生成调用)
- 副作用标记(这工具是只读、还是会写盘、还是会发起网络请求)
- 权限要求(默认 / 自动 / 严格模式下分别是放行、提示、拦截)
模型能"看到"哪些工具,是由 prompts/ 在系统提示里注入的工具清单决定的;模型"决定调用"哪个工具,由它自己输出 JSON 工具调用决定;最终"是否真的执行",由 permissions/ 把关。这条链路非常清晰。
5.2 工具调用的真实例子
来看一个最常见的"读文件—改文件—跑测试"场景,模型可能会输出这样三条工具调用:
[
{ "name": "read", "input": { "path": "src/main.py" } },
{ "name": "str_replace", "input": {
"path": "src/main.py",
"old_str": "def foo():\n return 1",
"new_str": "def foo():\n return 2"
}},
{ "name": "bash", "input": { "command": "pytest -q tests/test_foo.py" } }
]
OpenHarness 拿到这三条调用之后,会:
- 检查每条调用的权限是否放行(比如
str_replace在严格模式下会触发审批弹窗)。 - 派发执行,把结果按调用顺序聚合。
- 把结果用
tool_result消息塞回 messages,让模型基于"测试是不是通过了"决定下一步动作。
整套流程跟 Claude Code 用户的体感几乎一致——这也是 OpenHarness "兼容性拉满"的直观表现。
5.3 Skills:把"知识"做成可热插拔的 .md 文件
工具解决了"能做什么",Skills 解决了"知道什么"。
OpenHarness 兼容 anthropics/skills 格式,每个 skill 本质就是一个目录 + 一个 SKILL.md:
skills/
└── my-code-reviewer/
├── SKILL.md # 必须,描述触发条件 + 行为指南
├── checklist.md # 可选,参考资料
└── examples/ # 可选,示例
SKILL.md 的开头是一段 YAML frontmatter,描述这个 skill 的名字和触发条件:
---
name: code-reviewer
description: 当用户要求审查 Python 代码、检查 PR、找出潜在 bug 时使用本技能。
---
# Python 代码审查清单
- 检查类型注解是否完整
- 检查异常处理是否覆盖
- 检查测试是否随代码一起更新
- ...
Skill 的精髓是按需加载:
- Agent 在每轮思考时会扫描可用 skill 的 description。
- 当 description 命中当前任务,框架才把对应的 SKILL.md 注入到上下文。
- 没命中的 skill 完全不进上下文,省 token。
这套机制比"把所有提示都堆在系统提示里"高效得多——上下文里只会出现与当前任务真正相关的知识。
5.4 插件生态:Claude 插件几乎可以平移
除了 Skills,OpenHarness 还兼容 Claude-style plugins 的目录布局。一个 plugin 通常长这样:
my-plugin/
├── commands/ # 自定义斜杠命令
├── hooks/ # PreToolUse / PostToolUse 钩子
├── agents/ # 自定义子 Agent
└── mcp/ # MCP server 配置
意味着你已经在 Claude Code 那边写好的插件,只需要少量调整甚至零修改就能搬到 OpenHarness 上用。这一点对老用户的迁移成本是杀手级的——你过去几个月在 Claude 生态里积累的资产并不会作废。
5.5 插件管理命令
实际操作起来非常顺手,几个常用命令:
oh plugin list # 列出本地插件
oh plugin install <path> # 安装本地或远程插件
oh plugin enable <name> # 启用插件
oh plugin disable <name> # 临时禁用
我自己折腾下来的体感是,把 Skills 和插件结合起来用,能让一个 Agent 在不同项目里"换装"的速度非常快——在 A 项目里它是个代码审查员,切到 B 项目就摇身一变成了文档撰写员,靠的就是 skill 命中不同、可用工具不同。
5.6 MCP 协议:把外部世界的工具一口接进来
聊完内置工具和 Skills,还有一块绕不开的内容:MCP(Model Context Protocol,模型上下文协议)。OpenHarness 对 MCP 是一等公民支持,搞懂这一块能让你的 Agent 能力直接翻倍。
简单解释一下 MCP——这是 Anthropic 在 2024 年 11 月推出的一个开放协议,目标是给 LLM 与外部数据源/工具之间提供一个标准化的对接层。打个比方,MCP 之于 LLM 应用,就像 USB-C 接口之于电子设备——一个统一的插口,任何符合协议的工具都可以即插即用。
MCP 服务器主要暴露三类能力:
|
类型 |
说明 |
举例 |
|
Resources |
信息检索,只读 |
查数据库、读文档 |
|
Tools |
信息交换,会有副作用 |
调外部 API、做计算 |
|
Prompts |
可复用的提示模板 |
标准化工作流 |
OpenHarness 内置了 MCP 客户端,你只需要在配置里把 MCP server 的地址写上,框架就会自动把这些远程工具注册到本地 Agent 的工具列表,模型用起来跟用本地工具完全一样。
配置示例大概长这样:
mcp:
servers:
- name: "my-db"
command: "node"
args: ["/path/to/my-mcp-server.js"]
- name: "remote-search"
url: "https://mcp.example.com/sse"
auth:
type: "bearer"
token_env: "MY_MCP_TOKEN"
这意味着只要某个外部服务做了 MCP server,OpenHarness 立刻就能用上——而你不需要为每一个新工具单独写适配代码。这种"工具一次实现、随处可用"的生态价值,是 MCP 这个协议被一堆大厂同时采纳的根本原因。
实操上有几个小技巧值得记一下:
- 先用
oh --dry-run看 MCP server 的配置有没有解析成功,再正式启用,避免启动时卡住。 - 给每个 MCP server 起一个有意义的 name,模型在工具调用日志里更容易识别。
- 鉴权 token 尽量用环境变量,不要硬编码在配置文件里。
- MCP 工具的副作用要在 permissions 里单独标注,跟本地工具一视同仁地管控。
把 MCP 玩明白之后,你的 OpenHarness 就不再是一个"只能在本机干活"的 Agent,而是一个可以延伸到任意外部系统的指挥中心——这才是 Agent 这个概念真正的潜力所在。
六、上下文、记忆与会话恢复
工具决定模型能做什么,记忆决定模型记得什么。OpenHarness 在这一块的设计相当用心,分了三层:项目级上下文、跨会话长期记忆、当前会话状态。
6.1 项目级上下文:CLAUDE.md 自动注入
OpenHarness 沿用了 Claude Code 的一个约定——CLAUDE.md。
简单说:你在项目根目录放一个 CLAUDE.md,框架会在每次启动时自动发现并注入到系统提示里。这个文件你可以放任何"模型来到这个项目应该立刻知道的东西",比如:
# 项目背景
这是一个用 FastAPI 写的内部数据服务。
## 关键约定
- 所有接口必须有完整的 Pydantic schema
- 数据库迁移走 alembic,禁止裸 SQL 改表
- 提交前必须跑 `make test`
## 常用路径
- API 路由:`src/api/`
- 模型层:`src/models/`
- 单测:`tests/unit/`
CLAUDE.md 的好处是:
- 不用每次都重复给模型解释项目背景。
- 团队协作时所有人共享同一份"项目说明"。
- 写在 Markdown 里比写在配置 YAML 里更适合人类阅读。
OpenHarness 还支持嵌套 CLAUDE.md——根目录有一份全局的,子目录可以再放一份局部的,框架会按目录层级合并注入。
6.2 跨会话长期记忆:MEMORY.md
如果说 CLAUDE.md 是"项目说明书",那么 MEMORY.md 就是"个人随身笔记本"。
MEMORY.md 是 OpenHarness 提出的一个相当有特色的设计:
- 它放在用户级别(比如
~/.openharness/MEMORY.md),不绑定具体项目。 - 框架会自动在新会话开始时注入它。
- 模型可以通过工具往里追加内容——比如它发现你偏好用 Python 类型注解,就会把这条偏好写进去。
慢慢地,这个文件就会变成一份关于你的、可被复用的、跨会话的偏好画像。下一次你打开任何项目,模型都能"记得"你是谁、你喜欢什么风格、你不能接受什么。
这件事在 ohmo(后面会聊到)那一边被放大得更彻底:ohmo 自带 ~/.ohmo/soul.md、~/.ohmo/identity.md、~/.ohmo/user.md 三个文件,分别承担长期人格、自我认知、用户画像三种角色,比单一的 MEMORY.md 又分得更细。
6.3 当前会话状态:通道日志与任务表
第三层是当前会话状态——也就是这一次 oh 跑起来之后产生的所有东西,包括:
- messages 滚动历史
- 工具调用日志
- 任务表(TODO 列表)
- 子 Agent 派发记录
- token 计数与成本累计
这些状态会被框架持久化到一个会话目录下,方便后续 --resume 时整体恢复。值得一提的是,OpenHarness 的会话状态是结构化的——不是把所有东西塞在一个 JSON 里,而是分文件分通道存,方便事后审计和回放。
6.4 一张图把三层记忆串起来
我整理了一张表,把三层记忆的特点放在一起对比:
|
层级 |
文件 |
范围 |
谁来写 |
用途 |
|
项目级 |
CLAUDE.md |
单个项目 |
人 |
项目背景与约定 |
|
用户级长期 |
MEMORY.md |
所有会话 |
人 / 模型 |
个人偏好画像 |
|
会话级 |
session 目录 |
单次会话 |
框架 |
messages、工具日志、任务表 |
三层叠加之后,你跟模型的协作就不再是"每次都从零开始"——它知道你是谁、知道项目是什么、也知道之前已经聊到哪一步。这种连续性是普通聊天产品给不了的。
6.5 我自己用 MEMORY.md 的几个小心得
最后分享几个我自己折腾 MEMORY.md 时摸出来的小经验,给你参考:
- 不要把它写成日记。MEMORY.md 是给模型看的,写得越精炼越好,一两句话一条偏好就够了。
- 分小节组织。我自己分了"编码风格"、"语言偏好"、"忌讳事项"、"常用工具栈"四个小节,比一坨乱写有用得多。
- 定期清理。让模型写进去的东西未必都准,定期翻一遍,删掉过时的偏好。
- 不要写敏感信息。MEMORY.md 会被注入到每一次提示里,密钥之类的东西绝对不能写进来。
把这一节内化之后,你就理解了 OpenHarness 是怎么从"一次性问答"进化到"长期协作伙伴"的——靠的就是这套三层记忆 + 自动注入 + 主动维护的机制。
七、权限治理:让 AI 干活也得讲规矩
记忆解决了"记得什么",权限解决了"能做什么、做之前是否要确认"。OpenHarness 在这一块做得相当扎实,它默认就把"危险操作"看得很严,这也是它敢被人放进真实开发流程的底气之一。
7.1 多级权限模式
OpenHarness 一共提供四种权限模式,你可以在启动时指定,也可以在 TUI 里随时切换:
|
模式 |
行为 |
|
default(默认) |
安全操作放行,敏感操作弹审批 |
|
auto(自动) |
大多数操作自动放行,适合纯只读任务 |
|
plan(计划) |
模型只能产出计划,不能真正执行工具 |
|
strict(严格) |
几乎所有写操作、命令执行都要人工确认 |
我个人推荐的用法是:
- 探索阶段用
default,让模型放手干,关键节点确认一下。 - 写文档、做总结这种纯只读任务用
auto,避免老被弹窗打断。 - 第一次跟模型聊一个新项目时用
plan,先让它出方案,确认了再切回 default 执行。 - 在生产环境或者别人的电脑上跑用
strict,每一步都过一遍人工。
7.2 路径级规则与命令规则
光有模式还不够细,OpenHarness 还允许你在配置里定义路径级规则和命令规则:
permissions:
paths:
allow:
- "src/**"
- "tests/**"
deny:
- ".env*"
- "secrets/**"
commands:
deny:
- "rm -rf *"
- "git push --force*"
- "sudo *"
这套规则有几个细节值得说一下:
- 路径匹配支持 glob,能写
**/*.env这种递归通配。 - 命令规则按前缀匹配,可以把整类命令一刀切。
- deny 优先于 allow,写错了也不会放过危险操作。
- 规则可以分层:用户级、项目级、当前会话级层层叠加。
我自己在项目里几乎都会加一条 "禁止 git push --force" 的规则——模型偶尔会脑子一热搞强推,加这一条相当于给自己上个保险。
7.3 PreToolUse / PostToolUse 钩子
权限解决"能不能做",钩子解决"做之前/之后要不要顺便干点别的"。OpenHarness 提供两类钩子:
- PreToolUse:在工具真正执行之前触发。
- PostToolUse:在工具执行完成之后触发。
钩子可以用 Python 写,也可以用一段 shell 命令。一些常见的玩法:
hooks:
PreToolUse:
- matcher: "bash"
action: "echo '[before bash] $tool_input.command' >> ~/.openharness/audit.log"
PostToolUse:
- matcher: "write|edit|str_replace"
action: "git add -A && git commit -m 'auto: agent edit'"
这段配置干了两件事:
- 每次模型要跑 bash 之前,把命令写进审计日志。
- 每次模型改完文件之后,自动
git add并 commit 一次。
第二条尤其有用——它配合 OpenHarness 内置的 /undo 命令,可以让你把 AI 改坏的代码一条命令回滚到上一个 commit。这件事看似简单,但能省你不知道多少次"哎呀刚才让模型改错了重新写一遍"的痛苦。
7.4 交互式审批对话框
在 default 和 strict 模式下,触发到敏感操作时,OpenHarness 的 TUI 会弹一个交互式审批对话框:
┌────────────────────────────────────────────────┐
│ Permission required │
│ │
│ Tool: bash │
│ Command: rm -rf build/ │
│ │
│ [Y] Allow once │
│ [A] Allow this command for this session │
│ [N] Deny │
│ [E] Edit before run │
└────────────────────────────────────────────────┘
这里有几个细节做得很贴心:
- 支持"只允许这一次"和"本次会话内允许"两种粒度。
- 支持"先编辑命令再执行"——AI 想跑的命令你可以微调之后再放行。
- 拒绝时会自动把"为什么拒绝"的原因回吐给模型,让它换一种思路再来一遍。
7.5 Docker 沙箱:更强的隔离
对安全要求更高的场景,OpenHarness 还支持把整套工具执行放到 Docker 沙箱里跑:
sandbox:
backend: docker
image: "openharness/sandbox:latest"
network: "none"
resource_limits:
cpu: 2
memory: "2g"
启用之后,所有 bash、文件写、网络请求都会被限制在容器内,宿主机本身碰不到。这对"让模型跑陌生代码"或者"给团队成员开放只读账号"这种场景特别有用。
7.6 一句话总结这一节
权限治理这一块是 OpenHarness 区别于"玩具项目"最直观的部分。它不是简单地把"危险命令"硬编码进黑名单,而是给你提供了模式、规则、钩子、审批、沙箱五件套,让你可以根据自己的风险偏好把"AI 能做什么"调到刚刚好——既不被弹窗骚扰得跑不下去,也不会某天醒来发现项目目录被某条 rm 命令清空了。
八、Provider 与 Workflow:把模型当配件挂上去
聊完工具和权限,我们换个视角,看看 OpenHarness 是怎么处理"接哪个大模型"这件事的。这一块它给出了一个相当优雅的抽象:workflow + profile。
8.1 为什么不能简单一个 API_KEY 走天下
刚接触 LLM 工程的同学有个常见做法:在环境变量里塞一个 OPENAI_API_KEY,全局走天下。但只要你的需求复杂一点,这种做法立刻就崩了:
- 不同模型协议不一样(Anthropic