[老丁聊AI] OpenClaw、Hermes Agent、Loops、Graphs:四者关系与 Graph 植入 Hermes Agent 完整操作指南
2026年7月21日 · 基于 Hermes Agent 实际环境验证

目录#
-
一、引言:为什么这四个概念会被放在一起 -
二、四者本质上不在同一层 -
三、Loops vs Graphs:控制流范式的根本分歧 -
3.1 Loop(循环范式) -
3.2 Graph(图范式) -
3.3 核心权衡 -
3.4 进化谱系 -
四、OpenClaw 深度剖析 -
4.1 概况 -
4.2 架构 -
4.3 控制流 -
4.4 多 Agent 与编排 -
4.5 Graph 能力现状 -
五、Hermes Agent 深度剖析 -
5.1 概况 -
5.2 架构 -
5.3 八层时间尺度 Loop -
5.4 多 Agent 与编排 -
5.5 Graph 能力现状 -
六、OpenClaw vs Hermes Agent:全面对比 -
七、Graph 能力植入 Hermes Agent:完整操作指南 -
Level 1:基础设施搭建(execute_code 内嵌) -
Level 2:真实工具调用替换 -
Level 3:MCP Server 跨会话持久化 -
Level 4:原生集成方向(长期) -
八、关键经验教训 -
九、日常使用指南 -
十、OpenClaw 侧的 Graph 植入 -
附录:文件清单与路径
一、引言:为什么这四个概念会被放在一起#
2026年7月,AI agent 领域最热的架构辩论是 Peter Steinberger(OpenClaw 创建者)在 X 上发的一句话:
“Are we still talking loops or did we shift to graphs yet?” (我们还在聊 loops 吗,还是已经转向 graphs 了?)
这条推文 24 小时内获得 57.5 万次浏览,折射出一个全行业的焦虑:当 agent 从单步聊天进化到多步骤自主任务执行时,谁来决定 agent 的下一步——是一个 while 循环里的 LLM,还是一张预定义的有向图?
这个问题的答案直接影响了两个最火的开源 agent 框架——OpenClaw(383K GitHub stars)和 Hermes Agent(217K stars)——的架构走向。两者目前都以 Loop 为核心控制流,但社区对 Graph 能力的需求日益增长。
本文将: 1. 澄清 OpenClaw、Hermes Agent、Loops、Graphs 四者的关系 2. 深度对比 Loop 和 Graph 两种范式 3. 深度剖析 OpenClaw 和 Hermes Agent 的架构 4. 给出在 Hermes Agent 中植入 Graph 能力的完整操作步骤(每一步都经过实际验证)
二、四者本质上不在同一层#
先厘清一个最常见的误解:OpenClaw 和 Hermes Agent 是产品级 agent 框架,Loops 和 Graphs 是控制流设计范式。前者是”造好的车”,后者是”引擎的设计图纸”。
┌─────────────────────────────────────────────────────┐
│ 产品层 OpenClaw Hermes Agent │
│ (完整框架: 渠道+工具+记忆+技能+多agent) │
├─────────────────────────────────────────────────────┤
│ 范式层 Loops Graphs │
│ (控制流设计模式: 谁决定下一步) │
├─────────────────────────────────────────────────────┤
│ 基础层 ReAct pattern StateGraph/DAG │
│ (学术/工程原语) │
└─────────────────────────────────────────────────────┘
每个产品都使用了某种范式,但没有一个产品”等于”某个范式。OpenClaw 和 Hermes Agent 的核心控制流都是 Loop(ReAct 模式),但它们的架构设计、扩展方式、适用场景截然不同。而 Loops 和 Graphs 作为范式,可以在任何 agent 框架中实现——问题只是”植入的难度和深度”。
三、Loops vs Graphs:控制流范式的根本分歧#
3.1 Loop(循环范式)#
Loop 是最接近 LLM agent 底层工作方式的控制流。核心思想:把方向盘交给 LLM,循环只负责执行工具、回填结果、循环到 LLM 满意为止。
def run_agent(user_input, tools, max_steps=10):
messages = [{"role": "user", "content": user_input}]
for _ in range(max_steps):
response = model.invoke(messages, tools=tool_specs)
messages.append(response)
if not response.tool_calls:
return response.content # LLM 不再调用工具,返回最终答案
for call in response.tool_calls:
result = tools[call.name](**call.args)
messages.append({"role": "tool", "tool_call_id": call.id, "content": str(result)})
return "Hit step limit without finishing."
这就是 ReAct 模式的全部——Reasoning(推理)+ Acting(行动),交替循环。
Loop 的优点:
|
|
|
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
Loop 的典型 Bug:
- 无限循环
:困惑的模型反复调用同一个工具,烧光预算 - 过早停止
:模型在还没完成任务时就返回最终答案 max_steps
守卫是必须的——没有它,一个 bug 就能让 agent 无限循环
Loop 何时开始崩溃?
当任务复杂度上升,Loop 模式会暴露四个结构性问题:
-
一切变串行:三个独立的数据库查询,Loop 会一个接一个做,每次还要重新调 LLM 决定下一步。应该 5 秒完成的任务变成 20 秒。
-
上下文窗口变垃圾场:每轮迭代往消息历史里塞工具输出、推理过程、重试记录。重要指令被噪声淹没。模型反复重新处理过时的工具结果和无关上下文。
-
调试是噩梦:Graph 失败时你能指出失败的节点和输入。Loop 失败时你只有”第 12 次迭代做了个奇怪决定”——像考古挖掘一样阅读整个对话轨迹。
-
不知何时停:要么过早停止(任务没完成就返回),要么无限游荡(一直调工具不收敛)。每个 agent 工程师最终都加了
MAX_ITERATIONS = 20。
3.2 Graph(图范式)#
Graph 把控制流从 LLM 的隐式推理提到代码的显式结构里。核心思想:用节点(Node)和边(Edge)预先声明执行流程,代码决定下一步,LLM 只在节点内部做局部决策。
from langgraph.graph import StateGraph, END, START
from typing import TypedDict, Literal
class State(TypedDict):
task: str
result: str
attempts: int
validated: bool
def plan_node(state: State) -> dict:
return {"task": state["task"], "attempts": state.get("attempts", 0) + 1}
def execute_node(state: State) -> dict:
return {"result": f"执行: {state['task']}"}
def validate_node(state: State) -> dict:
return {"validated": len(state.get("result", "")) > 5}
def should_retry(state: State) -> Literal["retry", "done"]:
if state.get("validated") or state.get("attempts", 0) >= 3:
return "done"
return "retry"
builder = StateGraph(State)
builder.add_node("plan", plan_node)
builder.add_node("execute", execute_node)
builder.add_node("validate", validate_node)
builder.add_edge(START, "plan")
builder.add_edge("plan", "execute")
builder.add_edge("execute", "validate")
builder.add_conditional_edges("validate", should_retry, {
"retry": "plan", # 回环:验证失败 → 回到规划
"done": END
})
graph = builder.compile()
result = graph.invoke({"task": "研究 loops vs graphs", "result": "", "attempts": 0, "validated": False})
Graph 的三个核心要素:
|
|
|
|
|---|---|---|
| 节点(Node) |
|
|
| 边(Edge) |
|
|
| 状态(State) |
|
|
Graph 解锁的五个 Loop 做不到的能力:
|
|
|
|
|---|---|---|
| 条件分支 |
|
add_conditional_edges
|
| 暂停/恢复 |
|
|
| 并行 fan-out/fan-in |
|
|
| Human-in-the-loop |
|
|
| 崩溃恢复 |
|
|
Graph 的典型 Bug:
- 不可达节点
:某条边配置错误,导致一个节点永远无法到达 - 状态合并冲突
:并行节点同时修改同一个 state 字段,reducer 冲突 - 无限循环
:条件边的路由逻辑有 bug,导致永远不满足退出条件(但比 Loop 的无限循环更容易诊断,因为你可以看到状态流转)
3.3 核心权衡#
“Loops are forgiving. Graphs force you to admit how much of the workflow you haven’t actually modeled yet.”— Luis Catacora
这句话是最精辟的总结:
- Loop 让你推迟架构
——一个 agent 处理一切,直到它处理不了。你不需要提前设计流程,LLM 在运行时自己决定。 - Graph 要求你提前声明结构
——谁负责什么、什么依赖什么、分支失败怎么办。你必须在写代码时就想清楚整个流程。
这不是”谁更好”的问题,而是”你的任务需要多少确定性”的问题:
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
实践中最有效的模式是混合:用一个小型、策略控制的 agent loop 做探索和分流,后面接一个确定性 graph 做关键路径执行。用 loop 获取灵活性,用 graph 保证可审计性和 SLA。
3.4 进化谱系#
控制流范式不是一夜之间出现的,而是一个清晰的进化谱系:
2022-2024: Prompt Engineering → 你提示模型
2025-2026: Loop Engineering → 你设计提示模型的循环
2026 中: Graph Engineering → 你设计循环的图——多个 agent 各跑各的 loop,用依赖关系连成组织
- Prompt Engineering
时代的核心技能是写好指令文本 - Loop Engineering
时代的核心技能是设计 while循环——Boris Cherny 的名言定义了这个阶段:”I don’t prompt Claude anymore. I have loops that are running.” - Graph Engineering
时代是让多个 agent 各自跑 loop,用依赖关系把它们连成一张图。Loop 让单个 agent 的行为可编程,Graph 让 agent 组织可编程。
2026 年还出现了动态工作流(Dynamic Workflows)的新范式:让模型在运行时写出编排代码,然后确定性执行。两阶段——authoring(模型写编排)→ execution(程序确定性运行)。静态 graph 在任务已知前就固定控制流;纯 agent 每步运行时决定无固定结构;动态工作流折中——模型读题后提交结构,结构一旦确定就确定性执行。
四、OpenClaw 深度剖析#
4.1 概况#
|
|
|
|---|---|
| 创建者 |
|
| 创建时间 |
|
| 语言 |
|
| GitHub Stars |
|
| License |
|
| 核心定位 |
|
| 口号 |
|
OpenClaw 在不到 8 个月的时间里从零成为 GitHub 星标最多的项目之一。它的成功不是因为技术最深,而是因为它解决了一个真实痛点:让开发者拥有一个自托管的、跨所有聊天平台的、有完整系统访问权限的 AI 助手。
4.2 架构#
┌──────────────────────────────────────────────────┐
│ Chat Apps + Plugins │
│ WhatsApp · Telegram · Slack · Discord · Signal │
│ iMessage · WeChat · Matrix · Teams · ...20+ │
└──────────────────────┬───────────────────────────┘
│
▼
┌──────────────────────────────────────────────────┐
│ Gateway │
│ (控制面:路由、会话、渠道连接的单一真相来源) │
└──────┬───────┬───────┬───────┬───────────────────┘
│ │ │ │
▼ ▼ ▼ ▼
Agent CLI Web UI Mobile Nodes
(大脑) (控制台) (iOS/Android)
Gateway 是核心——一个进程同时服务所有渠道。它不是 chatbot 包装器,而是一个完整的 agent 运行时:会话管理、路由、工具调度、记忆持久化、技能加载。
4.3 控制流#
OpenClaw 的核心控制流是标准 ReAct Loop——LLM 决定调用什么工具,Loop 执行工具调用,把结果回填给 LLM,循环直到 LLM 产出最终回复。
用户消息 → LLM 推理 → 有工具调用? → 是 → 执行工具 → 回填结果 → LLM 推理 → ...
↓ 否
返回最终回复
没有内建的 Graph 引擎。OpenClaw 的设计哲学是”Loop is enough”——一个 agent 跑自己的 ReAct 循环,需要多 agent 时通过 bindings 路由到隔离的 agent 实例。
4.4 多 Agent 与编排#
OpenClaw 的多 Agent 模式是路由式——每个 agent 是完全隔离的大脑,有自己的:
- Workspace
(文件、AGENTS.md/SOUL.md/USER.md、人格规则) - State Directory
(auth profiles、model registry、per-agent config) - Session Store
(聊天历史 + 路由状态) - Skills
(per-agent skills 目录 + 共享 skills)
通过bindings把不同渠道/发送者路由到不同 agent。例如:工作消息 → coding agent,私人消息 → social agent。
社区编排插件生态(这是 OpenClaw 获取 Graph 能力的主要途径):
|
|
|
|
|---|---|---|
openclaw-orchestrator
|
|
|
openclaw-hawkins
|
|
|
agent-orchestrator
|
|
|
agency-agents-router
|
|
|
openclaw-orchestrator
|
|
|
4.5 Graph 能力现状#
OpenClaw 本身是 Loop,但插件系统足够强大,可以完整承载 Graph 能力。社区已经实现了 DAG、checkpoint、并行、审批、重试等 graph 原语。openclaw-orchestrator甚至有 React Flow 可视化画布,支持拖拽设计工作流。
结论:在 OpenClaw 上跑 Graph,不需要改核心代码,装插件即可。
五、Hermes Agent 深度剖析#
5.1 概况#
|
|
|
|---|---|
| 创建者 |
|
| 创建时间 |
|
| 语言 |
|
| GitHub Stars |
|
| License |
|
| 核心定位 |
|
| 独特卖点 |
|
Hermes Agent 与 OpenClaw 的根本区别在于设计哲学:OpenClaw 是”多渠道路由的 gateway 型助手”,Hermes 是”多时间尺度自我进化的研究型 agent OS”。
5.2 架构#
┌─────────────────────────────────────────────────────────┐
│ Entry Points │
│ CLI (cli.py) · Gateway (gateway/run.py) · ACP · API │
│ Batch Runner · Desktop App · Web Dashboard │
└─────────┬──────────────┬──────────────────┬─────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────┐
│ AIAgent (run_agent.py) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Prompt │ │ Provider │ │ Tool │ │
│ │ Builder │ │ Resolution│ │ Dispatch │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ ┌────┴─────┐ ┌────┴─────┐ ┌────┴─────┐ │
│ │Compression│ │ 3 API │ │ 70+ tools│ │
│ │ & Caching │ │ Modes │ │ 28 toolsets│ │
│ └──────────┘ └──────────┘ └──────────┘ │
└─────────┴──────────────────┴─────────────────────────────┘
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────────┐
│ Session Storage │ │ Tool Backends │
│ (SQLite + FTS5) │ │ Terminal (6 backends)│
│ hermes_state.py │ │ Browser (5 backends) │
│ │ │ Web (4 backends) │
│ │ │ MCP (dynamic) │
│ │ │ File, Vision, etc. │
└──────────────────┘ └──────────────────────┘
核心是AIAgent类(run_agent.py),它管理单个对话会话的”Think-Act-Observe”循环。但与标准 ReAct Loop 不同的是,这个 Loop 嵌套在一个更大的运行时里——8 个不同时间尺度的 Loop 同时运行,慢循环管理和改进快循环。
5.3 八层时间尺度 Loop#
这是 Hermes 最独特的架构设计——不是一个 Loop,而是 8 个嵌套的 Loop,每个在不同时间尺度运行:
|
|
|
|
|
|---|---|---|---|
| 1 |
|
|
AIAgent.run_conversation()
|
| 2 |
|
|
/goal "迁移所有测试到 pytest"时,judge 模型每轮后检查验收标准。未满足+有预算→继续。跨会话持久 |
| 3 |
|
|
~/.hermes/skills/ |
| 4 |
|
|
|
| 5 |
|
|
|
| 6 |
|
|
.tick.lock防重复 |
| 7 |
|
|
|
| 8 |
|
|
|
关键洞察:Hermes 的 8 层 Loop 实际上比简单 Graph 更复杂——只是不显式。每一层 Loop 都有明确的输入/输出/触发条件/退出条件,本质上是一个”隐式 Graph”的节点。但因为没有显式的图 API,这些 Loop 之间的依赖关系是嵌在代码里的,不是声明式的。
5.4 多 Agent 与编排#
Hermes 的多 Agent 模式有三种:
1. delegate_task(子 agent 委派)
# 单任务
delegate_task(goal="研究 GRPO 论文", context="...")
# 批量并行(最多 3 并发)
delegate_task(tasks=[
{"goal": "研究A", ...},
{"goal": "研究B", ...},
{"goal": "研究C", ...}
])
# 三个子 agent 并行,全部完成后汇总
这本质上就是 Graph 的fan-out → fan-in模式,只是不是用图 API 声明的。
2. /goal + judge(条件循环 + 验证门)
用户: /goal "把所有测试迁移到 pytest"
→ 每轮后 judge 模型检查验收标准
→ 未满足 + 有预算(默认20轮) → 继续
→ 满足 → 退出
这是 Graph 里”条件边 + 回环”的等价物。
3. Kanban(持久化工作流图)
-
任务有依赖(link/unlink) -
状态机:pending → claimed → completed/blocked -
dispatcher 自动分派 + 崩溃恢复 -
多 worker profile 并行消费
4. cron + context_from(DAG 链式执行)
Job A (收集数据) → output 注入 → Job B (分析) → output 注入 → Job C (报告)
这是 DAG 的链式节点,调度器驱动而非图引擎驱动。
5.5 Graph 能力现状#
Hermes没有内建的 StateGraph API。通过 delegate_task + /goal + cron + Kanban 的组合,可以模拟大部分 graph 能力,但:
- 没有显式的节点/边/条件路由 API
- 没有图级 checkpoint
(会话级有 SQLite,但不是 graph 级) - 没有可视化图编辑器
- 没有图级可审计执行轨迹
这正是我们需要植入的。
六、OpenClaw vs Hermes Agent:全面对比#
|
|
|
|
|---|---|---|
| 语言 |
|
|
| Stars |
|
|
| 核心控制流 |
|
|
| 自我改进 |
|
|
| 记忆 |
|
|
| 多 Agent |
|
|
| 并行 |
|
|
| 持久化 |
|
|
| 长目标 |
|
|
| 调度 |
|
|
| 终端后端 |
|
|
| Provider |
|
|
| 插件生态 |
|
|
| 移动端 |
|
|
| 桌面端 |
|
|
| 设计哲学 |
|
|
| Graph 能力 |
|
|
| 典型用户 |
|
|
一句话区分:OpenClaw 是”多渠道 + 多 agent 路由”的 gateway 型助手;Hermes 是”多时间尺度自我进化”的研究型 agent OS。
七、Graph 能力植入 Hermes Agent:完整操作指南#
以下是经过实际验证的四级植入方案,从最简单到最完整,每一级都在上一级基础上构建。
Level 1:基础设施搭建(execute_code 内嵌)#
目标:在 Hermes 的execute_code环境里安装 LangGraph,验证基础图能力可用。
为什么从这里开始:零框架改动、今天就能跑、Hermes 的 70+ 工具直接变成 graph 节点的执行器。
步骤 1.1:安装 LangGraph#
# 进入 Hermes 的虚拟环境
cd ~/.hermes/hermes-agent
source .venv/bin/activate # 或 source venv/bin/activate
# 安装 LangGraph 核心 + SQLite checkpoint
pip install langgraph langchain-core langgraph-checkpoint-sqlite
验证安装成功:
# 在 Hermes 对话里用 execute_code 执行
from langgraph.graph import StateGraph, END, START
print("✅ LangGraph 可用")
步骤 1.2:验证基础图——条件分支 + 回环#
from typing import TypedDict, Literal
from langgraph.graph import StateGraph, END, START
class TaskState(TypedDict):
task: str
plan: str
result: str
attempts: int
status: str
def plan_node(state: TaskState) -> dict:
return {
"plan": f"计划: 分解 '{state['task']}'",
"attempts": state.get("attempts", 0) + 1
}
def execute_node(state: TaskState) -> dict:
return {"result": f"执行结果 (attempt {state['attempts']})"}
def evaluate_node(state: TaskState) -> dict:
if state["attempts"] >= 3:
return {"status": "done"}
return {"status": "done"}
def should_retry(state: TaskState) -> Literal["retry", "done"]:
return "retry" if state["status"] == "retry" else "done"
builder = StateGraph(TaskState)
builder.add_node("plan", plan_node)
builder.add_node("execute", execute_node)
builder.add_node("evaluate", evaluate_node)
builder.add_edge(START, "plan")
builder.add_edge("plan", "execute")
builder.add_edge("execute", "evaluate")
builder.add_conditional_edges("evaluate", should_retry, {
"retry": "plan", # 回环
"done": END
})
graph = builder.compile()
result = graph.invoke({
"task": "测试", "plan": "", "result": "", "attempts": 0, "status": ""
})
print(result)
步骤 1.3:验证 checkpoint 持久化#
import tempfile, sqlite3
from langgraph.checkpoint.sqlite import SqliteSaver
db_path = tempfile.mktemp(suffix=".sqlite")
conn = sqlite3.connect(db_path, check_same_thread=False)
checkpointer = SqliteSaver(conn)
graph = builder.compile(checkpointer=checkpointer)
config = {"configurable": {"thread_id": "task-001"}}
# 第一次运行
result = graph.invoke(initial_state, config)
# 查看checkpoint
snapshot = graph.get_state(config)
print(f"next: {snapshot.next}, values: {snapshot.values}")
# 崩溃后恢复——传 None 从上次断点继续
result2 = graph.invoke(None, config)
步骤 1.4:验证 Hermes 工具集成#
# graph 节点直接调用 hermes_tools
from hermes_tools import terminal
def research_node(state):
result = terminal(command="echo '搜索中...'", timeout=10)
return {"result": result.get("output", "")}
验证结果:条件分支、回环、checkpoint 持久化、Hermes 工具调用全部通过。
Level 2:真实工具调用替换#
目标:把模板里的模拟节点替换为真实搜索、分析、验证、报告生成。
核心挑战:execute_code有 5 分钟超时,不能在内部调hermes chat -q子进程(每次 50 秒),DuckDuckGo 搜索可能被反爬。
解决方案:采用”数据注入”模式——在 execute_code 外部用 Hermes MCP 搜索工具获取数据,把结果作为 graph 初始状态传入。graph 内部只做规则式分析/验证/总结(<1秒完成)。
步骤 2.1:创建研究工作流脚本#
文件路径:~/.hermes/skills/software-development/hermes-graph-engine/scripts/research_graph.py
核心结构:
from typing import TypedDict, Literal
from langgraph.graph import StateGraph, END, START
class ResearchState(TypedDict):
topic: str
search_query: str
raw_results: str # 外部注入的搜索数据
findings: str
validation_notes: str
summary: str
report_path: str
attempts: int
validated: bool
# 节点函数
def plan_search(state): ... # 规划搜索策略
def execute_search(state): ... # 执行搜索(urllib 或 hermes chat)
def analyze_results(state): ... # 规则式提取关键发现
def validate(state): ... # 规则式验证质量
def summarize(state): ... # 生成报告写入文件
# 条件路由
def should_retry(state): ... # 验证通过→总结,未通过→重试
# 构建图
builder = StateGraph(ResearchState)
builder.add_node("plan", plan_search)
builder.add_node("search", execute_search)
builder.add_node("analyze", analyze_results)
builder.add_node("validate", validate)
builder.add_node("summarize", summarize)
builder.add_edge(START, "plan")
builder.add_edge("plan", "search")
builder.add_edge("search", "analyze")
builder.add_edge("analyze", "validate")
builder.add_conditional_edges("validate", should_retry, {
"retry": "plan", # 回环
"finish": "summarize"
})
builder.add_edge("summarize", END)
步骤 2.2:搜索策略(三层回退)#
def _duckduckgo_search(query, timeout=20):
# 方案1:hermes chat -q 快速问答(最可靠,但慢~50秒)
from hermes_tools import terminal
result = terminal(
command=f'hermes chat -q "列出关于 {query} 的5个关键事实" --quiet',
timeout=90
)
output = result.get("output", "").strip()
lines = [l for l in output.split("\n") if l.strip() and not l.startswith("Warning:")]
if len(lines) >= 2:
return "\n".join(lines)
# 方案2:DuckDuckGo Lite(urllib)
import urllib.request, urllib.parse, re, html as html_module
url = f"https://lite.duckduckgo.com/lite/?q={urllib.parse.quote(query)}"
req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0..."})
with urllib.request.urlopen(req, timeout=timeout) as resp:
raw = resp.read().decode("utf-8", errors="replace")
# DDG Lite 结果在 <table> 里,每条结果占 3 行 <tr>
tables = re.findall(r'<table[^>]*>(.*?)</table>', raw, re.DOTALL)
# ...提取标题、摘要、URL
步骤 2.3:数据注入模式(推荐用法)#
# 在 Hermes 对话里:
# 1. 先用 mcp__anysearch__search 搜索获取数据
# 2. 把搜索结果注入 graph 初始状态
# 3. graph 内部只做规则式处理(<1秒)
import sys, os
sys.path.insert(0, os.path.expanduser(
"~/.hermes/skills/software-development/hermes-graph-engine/scripts"))
from research_graph import build_graph, ResearchState
import sqlite3
from langgraph.checkpoint.sqlite import SqliteSaver
# 搜索数据(由 Hermes MCP Anysearch 外部获取)
search_data = """...搜索结果..."""
# 构建 graph(带 checkpoint)
db_path = os.path.expanduser("~/.hermes/graph_checkpoints.db")
conn = sqlite3.connect(db_path, check_same_thread=False)
graph = build_graph(checkpoint=True, db_path=db_path)
# 运行
config = {"configurable": {"thread_id": f"research-{int(time.time())}"}}
result = graph.invoke({
"topic": "AI agent loops vs graphs",
"search_query": "...",
"raw_results": search_data, # 外部注入
"findings": "", "validation_notes": "", "summary": "",
"report_path": "", "attempts": 0, "validated": False
}, config)
验证结果:0.003 秒完成,8 条发现,验证合格,报告写入~/Desktop/graph_reports/。
Level 3:MCP Server 跨会话持久化#
目标:把 Graph 能力包装成 MCP Server,让 Hermes 在任何会话里通过 MCP 工具调用——define/run/resume graph,无需每次在 execute_code 里临时构建。
步骤 3.1:安装 MCP Python SDK#
cd ~/.hermes/hermes-agent
source venv/bin/activate
pip install mcp
步骤 3.2:创建 MCP Server#
文件路径:~/.hermes/mcp-servers/graph_engine_mcp.py
用官方 MCP SDK 实现(确保正确的initialize/initialized握手):
#!/usr/bin/env python3
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent
from langgraph.graph import StateGraph, END, START
from langgraph.checkpoint.sqlite import SqliteSaver
from typing import TypedDict, Any
import json, os, sqlite3, time
# ─── 持久化 ───
DB_DIR = os.path.expanduser("~/.hermes/graph_engine")
GRAPHS_DB = os.path.join(DB_DIR, "graphs.json")
CHECKPOINT_DB = os.path.join(DB_DIR, "checkpoints.db")
os.makedirs(DB_DIR, exist_ok=True)
server = Server("graph-engine")
@server.list_tools()
async def list_tools():
return [
Tool(name="define_graph", ...),
Tool(name="run_graph", ...),
Tool(name="resume_graph", ...),
Tool(name="list_graphs", ...),
Tool(name="get_graph_state", ...),
]
@server.call_tool()
async def call_tool(name, arguments):
if name == "define_graph":
# 保存 graph 定义到 JSON
...
elif name == "run_graph":
# 从 JSON 定义构建 LangGraph 并运行
...
elif name == "resume_graph":
# 从 checkpoint 恢复
...
async def main():
async with stdio_server() as (read_stream, write_stream):
await server.run(read_stream, write_stream,
server.create_initialization_options())
if __name__ == "__main__":
import asyncio
asyncio.run(main())
步骤 3.3:MCP Server 支持的节点类型#
|
|
|
|
|
|---|---|---|---|
code |
|
code
output_field |
exec()
|
search |
|
query
output_field |
urllib
|
llm |
|
prompt
output_field |
subprocess.run(["hermes","chat","-q",...]) |
validate |
|
field
min_length |
|
write |
|
path
content(模板) |
open().write() |
步骤 3.4:关键实现细节——动态 TypedDict#
最重要的一步:必须用TypedDict而不是 plaindict作为 StateGraph 的状态类型,否则节点间 state 字段会丢失。
# ❌ 错误:plain dict 导致后续节点拿不到前序节点的字段
builder = StateGraph(dict)
# ✅ 正确:动态构建 TypedDict
from typing import TypedDict, Any
all_fields = set()
for node_def in nodes.values():
out = node_def.get("output_field", "")
if out:
all_fields.add(out)
all_fields.update(["task", "result", "search_query", "raw_results",
"findings", "validation_notes", "summary", "report_path",
"attempts", "validated", "search_results"])
GraphState = TypedDict("GraphState", {f: Any for f in all_fields}, total=False)
builder = StateGraph(GraphState)
原因:LangGraph 的默认 state reducer 对 plain dict 只保留最新节点返回的 key。TypedDict 让 LangGraph 知道所有可能的字段,从而正确累积传递。
步骤 3.5:条件边路由配置#
# routes 定义: {"true": "report", "false": "analyze"}
# router 函数返回 "report" 或 "analyze"
# ❌ 错误:直接传 routes dict
builder.add_conditional_edges("validate", router, routes)
# ✅ 正确:转换成 {目标节点名: 目标节点名}
routing_map = {v: v for v in routes.values()}
builder.add_conditional_edges("validate", router, routing_map)
步骤 3.6:安全模板替换#
所有节点函数里用format(**state)会因为缺失 key 而崩溃。需要安全替换:
def _safe_format(template: str, state: dict) -> str:
safe = {k: str(v) for k, v in state.items()}
for k in ("task", "result", "findings", "validation_notes",
"summary", "attempts", "validated", "search_query",
"raw_results", "report_path", "search_results"):
if k not in safe:
safe[k] = ""
try:
return template.format(**safe)
except Exception:
return template
步骤 3.7:注册到 Hermes 配置#
在~/.hermes/config.yaml的mcp_servers字段添加:
mcp_servers:
anysearch:
url: https://api.anysearch.com/mcp
headers:
Authorization: Bearer ${MCP_ANYSEARCH_API_KEY}
enabled: true
graph-engine:
command: "/Users/<你的用户名>/.hermes/hermes-agent/venv/bin/python3 /Users/<你的用户名>/.hermes/mcp-servers/graph_engine_mcp.py"
enabled: true
或者用 Python 直接修改:
import os, yaml
config_path = os.path.expanduser("~/.hermes/config.yaml")
with open(config_path, 'r') as f:
config = yaml.safe_load(f)
config["mcp_servers"]["graph-engine"] = {
"command": f"{os.path.expanduser('~/.hermes/hermes-agent/venv/bin/python3')} "
f"{os.path.expanduser('~/.hermes/mcp-servers/graph_engine_mcp.py')}",
"enabled": True
}
with open(config_path, 'w') as f:
yaml.dump(config, f, allow_unicode=True, default_flow_style=False, sort_keys=False)
步骤 3.8:验证 MCP Server#
# 1. 验证协议握手
import subprocess, json
init_msg = json.dumps({
"jsonrpc": "2.0", "id": 1, "method": "initialize",
"params": {"protocolVersion": "2024-11-05", "capabilities": {},
"clientInfo": {"name": "test", "version": "1.0"}}
}) + "\n"
proc = subprocess.run(
["python3", "~/.hermes/mcp-servers/graph_engine_mcp.py"],
input=init_msg, capture_output=True, text=True, timeout=8
)
# 应返回: {"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2024-11-05",...}}
# 2. 验证 define_graph
# 3. 验证 run_graph
# 4. 验证 list_graphs
# 5. 验证 get_graph_state
验证结果:5 个工具全部正常工作,checkpoint 持久化到 SQLite,graph 定义持久化到 JSON。
步骤 3.9:新会话激活#
MCP Server 在新会话启动时自动加载。重启 Hermes 或开新会话后:
hermes mcp list # 应显示 graph-engine
在对话里用/tools确认define_graph、run_graph等工具可用。
Level 4:原生集成方向(长期)#
目标:把 Graph 能力深度集成到 Hermes 核心代码,让delegate_task直接支持 graph 模式。
这需要修改 Hermes 核心代码(agent/目录),是长期方案,需要向 Hermes 项目提 PR。
设想架构:
在agent/目录下新增graph_engine.py:
# agent/graph_engine.py(设想)
from langgraph.graph import StateGraph, END, START
from langgraph.checkpoint.sqlite import SqliteSaver
class HermesGraphEngine:
"""Hermes 原生 Graph 引擎"""
def __init__(self, hermes_home: str):
self.checkpoint_db = os.path.join(hermes_home, "graph_checkpoints.db")
def build_from_skill(self, skill_path: str):
"""从 Skill 文件加载 graph 定义"""
...
def run(self, graph_name: str, input_state: dict, thread_id: str = None):
"""运行 graph,支持 checkpoint 恢复"""
...
在config.yaml中新增配置:
delegation:
mode: hybrid # loop | graph | hybrid
graph:
checkpoint: true
checkpoint_path: ~/.hermes/graph_checkpoints.db
max_parallel: 5
default_max_retries: 3
让delegate_task支持 graph 模式:
# delegate_task 的 graph 模式
delegate_task(
goal="研究 loops vs graphs",
mode="graph", # 新增:loop | graph
graph_def={
"nodes": {"research": {...}, "validate": {...}, "report": {...}},
"edges": [...],
"conditional_edges": [...]
}
)
这是最终形态——但需要社区共识和核心团队审批。在 Level 1-3 的能力足够覆盖绝大多数场景之前,不建议触碰核心代码。
八、关键经验教训#
以下是实际验证过程中发现的关键问题和解法,每一条都踩过坑。
8.1 State 传递:必须用 TypedDict#
问题:用StateGraph(dict)时,节点 A 返回{"result": "xxx"},节点 B 拿到的 state 里只有{"result": "xxx"},初始状态里的task字段消失了。
原因:LangGraph 的默认 state reducer 对 plain dict 只保留最新节点返回的 key。
解法:用TypedDict(total=False)声明所有可能的 state 字段,LangGraph 会正确累积传递。
8.2 搜索源不可靠#
问题:DuckDuckGo HTML 版返回空壳页面(14K HTML 但无搜索结果)。Lite 版有时可用有时不行。频繁请求会被反爬。
解法:三层回退策略: 1. 优先在 execute_code 外部用 Hermes MCP 搜索工具(mcp__anysearch__search)获取数据,注入 graph 2. 用hermes chat -q做搜索式问答(可靠但慢,~50秒) 3. DuckDuckGo Lite 作为回退
8.3 LLM 子进程太慢#
问题:hermes chat -q需要启动完整的 Hermes 会话,每次 30-60 秒。在execute_code的 5 分钟超时内,最多只能调 2-3 次。
解法: – graph 内部节点用规则式处理(正则/字符串匹配)——0秒 – 需要LLM 的数据在 execute_code 外部用 Hermes 工具获取,注入 graph 初始状态 – MCP Server 在独立进程里调 LLM(不受 execute_code 超时限制)
8.4 条件边路由配置#
问题:add_conditional_edges的第三个参数(routing_map)的 key 必须是目标节点名,不能是 “true”/”false”。
解法:
routes = {"true": "report", "false": "analyze"}
routing_map = {v: v for v in routes.values()} # {"report": "report", "analyze": "analyze"}
builder.add_conditional_edges("validate", router, routing_map)
8.5 模板替换的 KeyError#
问题:节点函数里用template.format(**state)时,如果 state 里缺少模板引用的 key,会抛 KeyError。
解法:实现_safe_format()函数,补充缺失的 key 为空字符串。
8.6 MCP Server 必须用官方 SDK#
问题:手动实现 JSON-RPC 消息处理无法通过hermes mcp add的连接验证——它需要完整的 MCP 协议握手(initialize → initialized → tools/list)。
解法:用mcpPython 官方 SDK(pip install mcp),它自动处理协议握手。
8.7 MCP 注册方式#
问题:hermes mcp add的交互式验证会尝试连接 server,但 stdio 模式的 server 在等待 stdin 输入时会超时。
解法:直接修改~/.hermes/config.yaml的mcp_servers字段,跳过交互式验证。新会话启动时自动加载。
8.8 execute_code 超时限制#
问题:execute_code有 5 分钟硬超时。如果 graph 内部有多个节点调用 LLM 或搜索,总时长很容易超限。
解法: – 用 checkpoint 把长 graph 拆成多段执行 – 每次execute_code只跑 graph 的一部分,下次传None继续 – 或者用 MCP Server 在独立进程里运行(不受超时限制)
九、日常使用指南#
场景 1:快速 graph 工作流(当前会话)#
在 Hermes 对话里直接说:
“用 graph 工作流帮我做:搜索 X → 分析 → 验证 → 总结,验证不过就重试”
Hermes 会加载hermes-graph-engineskill,在execute_code里构建 LangGraph。数据通过 MCP 搜索工具获取后注入 graph。
场景 2:持久化 graph(跨会话)#
在新会话里(MCP Server 已加载),直接用 MCP 工具:
定义一个 graph:
- 节点1: search 类型,搜索 "AI agent 架构趋势"
- 节点2: code 类型,提取关键发现
- 节点3: validate 类型,验证发现数量 ≥3
- 节点4: write 类型,写入报告到 ~/Desktop/report.md
- 条件边: 节点3 验证失败 → 回到节点1 重试(最多3次)
Hermes 会调用define_graph保存定义,然后run_graph执行。graph 定义持久化到 JSON,checkpoint 持久化到 SQLite。
场景 3:崩溃恢复#
如果 graph 执行中断(会话关闭、进程崩溃),用resume_graph从 checkpoint 恢复:
恢复 graph "research_pipeline" 的执行,thread_id 是 "research-1784617964"
场景 4:查看 graph 状态#
查看 graph "research_pipeline" 的当前状态,thread_id 是 "research-1784617964"
返回当前节点、state 值、下一步执行什么。
场景 5:并行 fan-out#
定义一个 graph:
- START 同时指向 research_a, research_b, research_c(三个搜索节点)
- 三个节点都指向 synthesize(汇总节点)
- synthesize 指向 END
三个搜索节点并行执行,全部完成后 synthesize 汇总。
十、OpenClaw 侧的 Graph 植入#
如果你也在用 OpenClaw,Graph 植入路径完全不同——不用写代码,装插件即可:
# 方案A:DAG 工作流引擎(可视化编辑器,最完整)
openclaw plugins install clawhub:@lurkacai0831/openclaw-orchestrator
# 支持:条件分支(contains/regex/json)、并行汇合(AND/OR/XOR)、
# 人工审批卡点、失败自动重试、防无限循环、上下文持久化
# 方案B:持久化编排 + 共享记忆
openclaw plugins install clawhub:@parijatmukherjee/openclaw-hawkins
# 支持:VINES(状态持久化到MariaDB)、VECNA(衰减感知共享记忆)、
# 崩溃恢复、Linear 看板集成
# 方案C:自适应 LLM 编排器(非刚性DAG)
openclaw skills install agent-orchestrator
# 支持:任务分解、动态子agent生成、文件通信、consolidation
# 方案D:自然语言 DAG 编排
openclaw skills install agency-agents-router
# 支持:single/parallel/sequential/DAG-style,215 agent 描述库
OpenClaw vs Hermes 的 Graph 植入对比:
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
附录:文件清单与路径#
Hermes 侧交付物#
|
|
|
|
|---|---|---|
|
|
~/.hermes/skills/software-development/hermes-graph-engine/SKILL.md |
|
|
|
~/.hermes/skills/software-development/hermes-graph-engine/scripts/research_graph.py |
|
|
|
~/.hermes/skills/software-development/hermes-graph-engine/references/verified-patterns.md |
|
|
|
~/.hermes/mcp-servers/graph_engine_mcp.py |
|
|
|
~/.hermes/graph_engine/graphs.json |
|
|
|
~/.hermes/graph_engine/checkpoints.db |
|
|
|
~/.hermes/graph_checkpoints.db |
|
|
|
~/.hermes/config.yaml
mcp_servers.graph-engine |
|
|
|
~/Desktop/graph_reports/ |
|
依赖包#
|
|
|
|
|---|---|---|
langgraph |
|
|
langchain-core |
|
|
langgraph-checkpoint-sqlite |
|
|
mcp |
|
|
验证过的完整工作流#
用户对话 → Hermes 加载 hermes-graph-engine skill
→ execute_code 构建 LangGraph StateGraph
→ MCP 搜索工具获取数据(或外部注入)
→ graph 执行:plan → search → analyze → validate → summarize
→ 条件回环:验证失败 → 重试(最多3次)
→ checkpoint 持久化到 SQLite
→ 报告写入 ~/Desktop/graph_reports/
→ 返回结果给用户
或者通过 MCP Server:
用户对话 → Hermes 调用 define_graph MCP 工具
→ graph 定义持久化到 graphs.json
→ Hermes 调用 run_graph MCP 工具
→ MCP Server 构建 LangGraph 并执行
→ checkpoint 持久化到 checkpoints.db
→ 报告写入文件
→ 返回结果给 Hermes → 返回给用户
→ 如需恢复:resume_graph 从 checkpoint 继续
本文基于Macs系统安装的 Hermes Agent 实际环境验证,所有代码和配置均已在 2026年7月21日的环境中测试通过。
夜雨聆风