2026年4月12日 · 阅读 —
OpenClaw 调用 Hermes CLI 实现过程
OpenClaw 调用 Hermes CLI 实现过程
一、结论
当前这套实现的核心不是 API,也不是 MCP,而是:
OpenClaw
-> 触发词或主入口路由
-> Python 脚本
-> Hermes CLI 子进程
-> result JSON / report
-> OpenClaw 收口回复
一句话:
OpenClaw 通过本机 Python 脚本起 Hermes CLI 子进程,把 Hermes 当作一个“可直接调用的执行引擎”来用。
二、为什么这样实现
这套实现优先解决的是:
- 同机部署下尽快跑通主链路
- 不引入额外 API 服务
- 不先做任务队列系统
- 保留结果留痕
- 让 OpenClaw 可以在需要时显式把任务交给 Hermes
因此最终选择:
- 主入口显式路由
- Python subprocess 调用 Hermes CLI
- 用 JSON 作为稳定结果输出
- 必要时再落报告文件
三、当前涉及的核心文件
1. 触发词路由
07-Scripts/openclaw-hermes/route_by_trigger.py
作用:
- 识别
用hermes .../使用hermes .../hermes: ... - 提取任务正文
- 调用
direct_invoke.py - 返回适合 OpenClaw 回复用户的结果
2. 直接调用入口
07-Scripts/openclaw-hermes/direct_invoke.py
作用:
- 接收 title / summary / target path / instructions
- 构造标准请求对象
- 调用
execute_task.py的核心方法
3. 核心执行脚本
07-Scripts/openclaw-hermes/execute_task.py
作用:
- 把请求转成 Hermes 可执行 prompt
- 真正起 Hermes CLI 子进程
- 处理 stdout / stderr / timeout / fallback result
4. 结果渲染
07-Scripts/openclaw-hermes/collect_result.py
作用:
- 把结果 JSON 转成适合 OpenClaw 对外回复的文本块
5. 工作区规则入口
AGENTS.md
作用:
- 明确声明:命中
用hermes/使用hermes/用hermes来完成/使用hermes来完成/hermes:等前缀时,默认直接转 Hermes - 明确声明推荐执行脚本:
07-Scripts/openclaw-hermes/route_by_trigger.py - 作为工作区级规则入口,为遵守工作区规则的主入口 / Agent 提供第一层触发约束
6. Skill 入口
~/.hermes/skills/openclaw-imports/openclaw-hermes-trigger/SKILL.md- 技能名:
openclaw-imports/openclaw-hermes-trigger
作用:
- 把“显式说用hermes就直转 Hermes CLI”这件事,固化为独立 skill
- 作为零侵入兜底入口:即使主入口源码暂时没改,也可以通过加载 skill 的方式提升命中概率
- 明确约定返回码语义与执行链:
route_by_trigger.py -> direct_invoke.py -> execute_task.py -> Hermes CLI
四、调用链拆解
步骤 1:用户在 Telegram 对 OpenClaw 发消息
例如:
用hermes 检查 03-Projects/demo-project 配置问题并给出修复建议
这条消息先进入 OpenClaw 主入口。
步骤 2:OpenClaw 判断是否命中 Hermes 显式触发
这一步当前有两个入口共同承担:
入口 A:工作区规则入口(AGENTS.md)
工作区里已经明确声明:
用hermes使用hermes用hermes来完成使用hermes来完成hermes:
一旦命中,就不再走普通轻量问答逻辑。
入口 B:Skill 兜底入口(openclaw-hermes-trigger)
技能 openclaw-imports/openclaw-hermes-trigger 也重复固化了同样的规则:
- 遇到上述显式前缀时,直接转 Hermes CLI
- 不当作普通轻量请求处理
- 统一走
route_by_trigger.py
也就是说:
AGENTS.md = 工作区规则入口
Skill = 零侵入兜底入口
步骤 3:OpenClaw 调用触发词脚本
推荐调用方式:
python3 07-Scripts/openclaw-hermes/route_by_trigger.py "<用户原始消息>" --timeout 600
如果主入口是 Python,通常会是:
completed = subprocess.run(
['python3', '07-Scripts/openclaw-hermes/route_by_trigger.py', user_text, '--timeout', '600'],
cwd='[本机路径已隐藏]
capture_output=True,
text=True,
timeout=660,
)
步骤 4:route_by_trigger.py 提取真正任务文本
例如:
用hermes 检查当前工作区规则并只返回一句话
会被提取成:
检查当前工作区规则并只返回一句话
也就是把“触发前缀”和“真正任务正文”分开。
步骤 5:route_by_trigger.py 再调用 direct_invoke.py
内部调用大意如下:
cmd = [
'python3',
str(DIRECT_INVOKE),
'--title', 'OpenClaw 触发词转发任务',
'--summary', task_text,
'--target-path', target_path,
'--instruction', '先读 AGENTS.md',
'--instruction', '默认非破坏性',
'--instruction', '先给结论,再给依据',
'--deliverable', 'summary',
'--print-json',
]
这里已经把 OpenClaw 的约束传给 Hermes 了。
步骤 6:direct_invoke.py 构造标准请求对象
请求里至少会包含这些字段:
task_idtitlesummaryworkspacetarget_pathinstructionsdeliverablesconstraintsoutput.report_pathoutput.result_path
这一步的目的,是把“用户一句自然语言消息”变成“可稳定执行的结构化请求”。
步骤 7:execute_task.py 把请求转成 prompt
execute_task.py 会把上面的结构化请求转成 Hermes CLI 可直接理解的一段 prompt。
Prompt 核心会说明:
- 工作区根目录是什么
- 先读
AGENTS.md - 默认非破坏性
- 要不要生成报告
- 结果 JSON 应该写到哪里
- 最终至少给一句结论
也就是:
结构化请求 -> 标准执行 prompt
步骤 8:真正起 Hermes CLI 子进程
这是整个实现的核心点。
内部最终执行的命令是:
python -m hermes_cli.main chat -q "<prompt>" --source tool
实现方式是 Python 的:
subprocess.run(cmd, cwd=str(workspace), env=env, capture_output=True, text=True, timeout=timeout)
同时会显式设置:
HERMES_HOMETERMINAL_CWDMESSAGING_CWDcwd=workspace
这一步决定了 Hermes 是在正确工作区里运行,而不是在随机目录执行。
步骤 9:Hermes 执行任务并输出结果
Hermes 执行后,优先会产出:
- 结构化结果:
90-System/shared/outbox/result-<task_id>.json - 长报告(如有):
90-System/shared/reports/report-<task_id>.md
结果 JSON 最关键的字段是:
statussummaryartifacts.report_patherror
如果 Hermes 没按约定写 JSON,execute_task.py 还会自动补一个 fallback result,避免 OpenClaw 端拿不到结果。
步骤 10:OpenClaw 拿 stdout / result JSON 收口回复
OpenClaw 主入口建议优先拿:
- 子进程返回码
- stdout 中的 JSON
- 必要时再读
result-*.json - 如果有长报告,再带上
report_path
最终对用户回复建议统一成:
- 结论
- 状态
- 摘要
- 报告路径(如有)
- 错误(如失败)
五、返回码约定
当前触发词脚本建议按这个约定理解:
返回码 0
表示:
- 已命中 Hermes 触发词
- 已成功调用 Hermes
- 可以直接把输出发回用户
返回码 1
表示:
- 没命中 Hermes 触发词
- 应继续走 OpenClaw 默认逻辑
其他非零返回码
表示:
- Hermes 调用失败
- 应返回失败信息和错误摘要
六、为什么还要保留 result JSON / report
虽然当前已经是“直调 CLI”方案,但仍保留:
outbox/result-*.jsonreports/report-*.md
原因是:
- 便于排障
- 便于留痕
- 便于后续结果回查
- 便于未来再演进回异步队列模式
所以现在这套实现是:
同步调用为主
+ 结果文件留痕为辅
七、为什么这套实现适合当前阶段
优势很直接:
- 链路短
- 同机调用成本低
- 不需要额外 API 服务
- 不需要先做 worker / queue
- 失败点清晰
- 便于快速验证
也就是说:
对当前场景来说,Hermes 更像“本机执行引擎”,而不是“远端服务”。
八、当前已验证的事实
本地已验证通过:
1. 触发词命中
输入:
用hermes 检查当前工作区规则并只返回一句话
结果:
- 成功命中触发词
- 成功提取任务正文
- 成功调用 Hermes
2. 执行完成
结果状态:
status: completed
3. 普通消息不会误触发
输入普通消息时,会返回:
{"matched": false, "reason": "no hermes trigger found"}
九、当前文档、规则与 Skill 落点
规则与实现已经分别落在:
- 工作区规则入口:
AGENTS.md - Skill 兜底入口:
~/.hermes/skills/openclaw-imports/openclaw-hermes-trigger/SKILL.md - Skill 名:
openclaw-imports/openclaw-hermes-trigger - 触发词脚本:
07-Scripts/openclaw-hermes/route_by_trigger.py - 直接调用入口:
07-Scripts/openclaw-hermes/direct_invoke.py - 核心执行脚本:
07-Scripts/openclaw-hermes/execute_task.py
十、最终收口
这套实现过程可以概括成一句话:
用户显式说“用hermes ...”
-> OpenClaw 命中触发规则
-> 调 route_by_trigger.py
-> 调 direct_invoke.py
-> 调 execute_task.py
-> subprocess 起 Hermes CLI
-> 返回 JSON / 报告
-> OpenClaw 对外收口
一句话收口:
当前 OpenClaw 调 Hermes CLI 的实现,本质上是一条“显式触发 -> 本机脚本 -> 子进程执行 -> 结构化结果回收”的直连链路。