为什么用 Hermes 讲这一章:它把 LLM 内核 + ReAct 循环 + Tools 层 + Memory 层 + Skill 层 + Reflexion 外挂组装成一个能跑起来、能配本地模型、能采轨迹的进程,恰好覆盖的全部子主题;且"本地模型友好 + 端侧可跑"让它成为落地低成本实证的首选载体。

阅读路径:§1 定位与架构(教师定位),§2 环境接入(动手前的坑),§3–§7 逐件拆五件套(每节配最小可跑代码),§8 整合实验跑真实任务,§9 预留轨迹 schema(为 pr08 仪表盘 / pr09 失败编码 / pr14 因子分析埋接口),§10 一页架构对比。文中模型名与 Hermes 版本、接口签名以官方仓库为准,本文只做工程性介绍、不作绝对判断。


1. Hermes 定位与架构总览

Hermes Agent 是一个 MIT 开源的通用 Agent 框架:它把LLM 内核(对接任意 OpenAI 兼容后端)、ReAct 循环(推理-行动交替)、Tools 层(shell / python / MCP)、Memory 层(短期对话 + 长期存储 + 压缩)、Skill 层(SKILL.md 可复用过程知识)与 Reflexion 外挂(失败后自我批评重试)组装成可跑进程。

Hermes 进程 · 六件套分层 ① LLM 内核 OpenAI 兼容接口:backend / model / api_base —— llama.cpp · vLLM · Ollama 均可 内核 ② ReAct 循环(编排中枢) Thought → Action → Observation 三段式,驱动一次任务反复迭代直到收敛 ③ Tools 层 shell · python 沙箱 · MCP ④ Memory 层 短期 / 长期 / 压缩 ⑤ Skill 层 SKILL.md 过程知识 ⑥ Reflexion 外挂(可选) · 失败 → 自我批评 → 记忆化改进 → 重试,包裹在 ReAct 之外 运行底座:本地 backend(llama.cpp / vLLM / Ollama / OpenAI 兼容 API) · context_length / min_ctx / auxiliary.compression 数据流:任务 → ReAct 编排 → 读写 Memory → 命中 Skill / 调 Tools → Observation 回填 → (失败则 Reflexion) → 收敛输出
图 1-1 · Hermes 六件套分层与一次任务的数据流(示意)

与同期几个框架相比,Hermes 的定位差异不在于"更强",而在于取向不同:

  • LangChain:偏重组件库与积木式拼装,链式抽象丰富,适合快速搭原型;编排语义更"框架化"。
  • AutoGPT:早期的自主性探索,长目标自拆解、循环自驱动,是"Agent 能自主到什么程度"的先声,但可控性与可观测性代价高。
  • Claude Code:面向 IDE / 编码场景的深度整合,工具与上下文围绕代码工作流裁剪。
  • Hermes:取向落在"本地模型友好 + 端侧可跑"——同一套五件套抽象,把 backend 换成 llama.cpp / Ollama 就能在本地 GPU 甚至端侧跑起来,便于实操做可复现的对照实验。

工程性提醒:这几者不是"谁替代谁"的关系。选型的判据应当回到你的约束——是否需要本地模型、是否需要端侧部署、是否需要强可观测性去支撑度量与失败分析。Hermes 被选作本篇主线,只是因为它把五件套暴露得足够直白、可拆可采。


2. 环境与最小接入

本地 backend 常见三种:llama.cpp(llama-server,C++,省内存)vLLM(高吞吐,批量友好)OpenAI 兼容 API(含 Ollama,最通用的对接面)。Hermes 侧关键配置项通常包括:backend、model、api_base、context_length、min_ctx,以及 auxiliary.compression(辅助压缩,§6 会回过来讲)。具体键名与取值随版本变动,以官方仓库样例配置为准。

2.1 一个真实的坑:MINIMUM_CONTEXT_LENGTH

有些模型侧会写死一个最小上下文门限。比如 Ternary-Bonsai-2-27B 一类模型带 MINIMUM_CONTEXT_LENGTH = 64_000:你若把 context_length 或 min_ctx 设得比它小,服务端会直接拒绝请求,报的是"context too small"这类含糊错,而现场往往是"本地 8GB 显存根本开不了 64k ctx"——两端约束打架,接不上。

排障口诀:先看 backend 愿不愿意接你的 ctx,再看 Hermes 配的对不对。遇到离奇的空响应 / 400,优先怀疑上下文长度门限,而不是提示词。这个坑也是 §8 里我们要把 fail_code 单独记一列的原因。

2.2 最小接入脚本

下面这段是"起一个本地 backend + 拉起 hermes 打一次 hello"的最小骨架,把 backend 与 Hermes 解耦成两个进程:

Bash
#!/usr/bin/env bash
# 0) 依赖:已装 llama.cpp(提供 llama-server) 与 hermes CLI
#    Hermes 版本 / llama.cpp 版本以官方仓库为准

# 1) 起 llama.cpp backend:暴露 OpenAI 兼容端点 :8080/v1
#    ctx 要给够,避开 §2.1 的 MINIMUM_CONTEXT_LENGTH 门限
llama-server \
  --model   ./models/Qwen2.5-14B-Instruct-Q4_K_M.gguf \
  --host    127.0.0.1 --port 8080 \
  --ctx-size 32768 \
  --n-gpu-layers 999 &        # 后台跑;生产建议 systemd/keepalive

# 2) 等后端就绪(简易探活,实际用 health check 更稳)
until curl -sf http://127.0.0.1:8080/v1/models >/dev/null; do sleep 1; done

# 3) 让 Hermes 指向这个 backend(键名示意,以官方 config 为准)
export HERMES_BACKEND="openai"
export HERMES_API_BASE="http://127.0.0.1:8080/v1"
export HERMES_MODEL="qwen2.5-14b"
export HERMES_CTX="32768"
export HERMES_MIN_CTX="4096"

# 4) 单发冒烟测试
hermes run --task "用一句话解释什么是 ReAct 循环" \
           --max-steps 3 --trace ./runs/smoke.jsonl

# 5) 收尾:记录本次 backend / 模型哈希,保证 §9 轨迹可复现
echo "backend=llama.cpp model=$(sha1sum ./models/*.gguf | cut -c1-8)" \
     >> ./runs/env.lock

换 backend 只改 llama-server 那一段(vLLM 用 python -m vllm.entrypoints.openai.api_server,Ollama 用 ollama serve + /v1);Hermes 侧逻辑一行不动——这正是 §1 说的"backend 可替换"的架构收益。


3. 五件套 · ReAct

ReAct 的核心是把"想"和"做"交替起来,每轮输出三段式:Thought(我该怎么推进)→ Action(调哪个工具、传什么参数)→ Observation(工具返回什么),再回到 Thought。Hermes 的提示词模板结构大致是 system(角色/规则) + tools(工具清单与 schema) + task(本次目标),循环控制器负责拼装、解析、执行、回填。

3.1 手写最小 ReAct loop(不依赖框架)

先用 OpenAI 兼容 SDK 手写一个 ≤80 行的 loop,看清"prompt 拼装 / 解析 / 工具执行 / observation 回填"这四步到底在干什么:

Python
# file: mini_react.py  —— 手写最小 ReAct(示意,约 60 行)
import json, re
from openai import OpenAI          # 任意 OpenAI 兼容后端

llm = OpenAI(base_url="http://127.0.0.1:8080/v1", api_key="sk-none")
MODEL = "qwen2.5-14b"

# 1) 注册工具:name -> callable(args:dict)->str
def _add(a, b): return str(float(a) + float(b))
TOOLS = {"add": {"fn": _add,
                 "desc": "两数相加",
                 "schema": '{"a":"number","b":"number"}'}}

SYSTEM = (
  "你是一个会用工具的助手。每轮严格按此格式输出:\n"
  "Thought: <推理>\n"
  "Action: <工具名>\n"
  "Action Input: \n"
  "以 'Final Answer:' 结束任务。\n"
  "可用工具:\n" +
  "\n".join(f"- {k}: {v['desc']} 入参 {v['schema']}" for k, v in TOOLS.items)
)

def parse(text):
    t = re.search(r"Thought:\s*(.+)", text)
    a = re.search(r"Action:\s*(\w+)", text)
    i = re.search(r"Action Input:\s*(\{.*\})", text, re.S)
    fin = re.search(r"Final Answer:\s*(.+)", text, re.S)
    return (t.group(1) if t else "", a.group(1) if a else None,
            json.loads(i.group(1)) if i else {}, fin.group(1).strip if fin else None)

def run(task, max_steps=6):
    msgs = [{"role": "system", "content": SYSTEM},
            {"role": "user",   "content": task}]
    for step in range(max_steps):
        out = llm.chat.completions.create(
            model=MODEL, messages=msgs, temperature=0).choices[0].message.content
        thought, action, args, final = parse(out)
        msgs.append({"role": "assistant", "content": out})
        print(f"[{step}] thought={thought!r} action={action}")
        if final is not None:
            return final
        if action in TOOLS:                        # 工具执行
            try: obs = TOOLS[action]["fn"](**args)
            except Exception as e: obs = f"ERROR: {e}"
        else:
            obs = f"未知工具 {action!r}"            # 错误也回填,让模型自纠
        msgs.append({"role": "user", "content": f"Observation: {obs}"})
        print(f"     observation={obs}")
    return "(未收敛)"

if __name__ == "__main__":
    print(run("37 加 55 等于多少?"))

3.2 换成 Hermes 内置

同样的循环交给 Hermes,业务代码塌缩成"给任务 + 给工具"两件事,可控性从"自己管解析回填"变成"配置 + 回调钩子":

Python
# file: hermes_react.py —— 用 Hermes 内置 ReAct 循环(示意)
from hermes import Agent, Tool           # 包名/入口以官方仓库为准

def _add(a, b): return str(float(a) + float(b))

agent = Agent(
    backend="openai",
    api_base="http://127.0.0.1:8080/v1",
    model="qwen2.5-14b",
    context_length=32768,
    tools=[Tool(name="add", description="两数相加",
                parameters={"a": "number", "b": "number"}, fn=_add)],
    max_steps=6,
    trace="./runs/react.jsonl",          # 轨迹直接落 §9 的 schema
    on_step=lambda s: None,               # 钩子:可在此埋点/截断
)

print(agent.run("37 加 55 等于多少?"))
# 对比 §3.1:解析、拼装、observation 回填、终止判定都由框架接管,
# 代码量从 ~60 行降到 ~15 行;代价是解析策略/停止条件要读框架文档去调。

可控性 ↔ 简洁度的权衡:手写 loop 让你完全掌控"怎么解析、错了怎么办、什么时候停",适合学习与排障;框架内置省去样板,但一旦行为不符预期,你得先搞清楚它内部做了什么假设。建议先手写一遍再切框架,理解"抽象替你做了什么"。


4. 五件套 · 工具调用与 MCP

Hermes 的工具是一份带 name / description / parameters(JSON Schema)的声明;模型据此决定"调不调、怎么传参"。三类典型接入方式:本地 shell(最直接、权限最大)、python 沙箱(隔离执行、适合数据处理)、MCP server(跨进程标准协议工具)。

4.1 注册一个 MCP 工具并让 Hermes 调用

Python
# file: hermes_mcp.py —— 接入 MCP server 暴露的工具(示意)
from hermes import Agent
from hermes.mcp import MCPServer        # MCP 客户端封装以官方仓库为准

# 1) 声明一个 MCP server(stdio 或 sse 均可)
stock = MCPServer(
    name="finance",
    transport="stdio",
    command="python -m my_finance_mcp_server",   # 该 server 自带 tools/list
)

agent = Agent(
    backend="openai",
    api_base="http://127.0.0.1:8080/v1",
    model="qwen2.5-14b",
    mcp_servers=[stock],                 # 框架自动 tools/list 拉取 schema
    tool_allowlist=["finance.get_quote", "finance.history"],  # 白名单收敛权限
    trace="./runs/mcp.jsonl",
)

# 2) 模型会自行发现并调用 finance.get_quote
print(agent.run("查一下 600519 的最新报价并转成一句话"))
# 关键:MCP 只负责"工具如何被发现/调用/返回",
# 不定义"多 Agent 谁先谁后"——那是编排范式(见 pr16),别混为一谈。

4.2 三种接入方式对比

接入方式配置成本权限面隔离性典型用途
本地 shell低高(宿主全权)弱nvidia-smi、文件搬运、调系统命令
python 沙箱中受限(可禁网/禁 IO)较好数据清洗、pandas 计算、画图
MCP server中高(需起进程/约定协议)由 server 与白名单控制强(进程边界)跨语言/跨机复用能力、第三方工具集成

范畴提醒:MCP 是"工具接入协议",不是"多 Agent 编排范式"。它能规定 tool 怎么被发现、怎么调用、返回什么格式,但不规定"多个 Agent 谁调度谁"。后者属于编排层,我们在 pr16 专门讲;本篇只要理解"MCP = 标准化工具面"即可。


5. 五件套 · Reflexion

Reflexion 的思想:失败不是终点,而是信号。一轮任务失败(或结果不达标)后,让模型对这轮的 trace 做自我批评("我错在哪、下次该怎么改"),把批评记忆化(写入本轮上下文或短期记忆),再重试。它像一层包在 ReAct 外面的"复盘循环",用少量额外 token 换 pass@1 的提升。

Reflexion 反思轮次 → pass@1 ↑ 0 1 2 3 4 1.0 0.6 0.6 0.72 0.8 0.84 token 成本随轮次累积 ↑ 前 1–2 轮增益最陡,随后饱和
图 5-1 · Reflexion 的 pass@1 随反思轮次先陡增后饱和,而 token 成本持续累积——"值不值"要看这条增益/成本比(示意,非实测值)

5.1 给 ReAct loop 加一层 Reflexion

Python
# file: react_reflexion.py —— 在 mini_react 外包 Reflexion(约 45 行)
from mini_react import run as react_run, llm, MODEL

def self_critique(task, trace, verdict):
    """让模型对失败 trace 做自我批评,产出一条可执行的改进提示。"""
    msgs = [
      {"role": "system",
       "content": "你是复盘器。给定任务、执行轨迹、判分结果,"
                  "用不超过 2 句话指出根因并给出下一轮的具体改进指令。"},
      {"role": "user",
       "content": f"任务:{task}\n轨迹:{trace[-6:]}\n判分:{verdict}\n改进提示:"},
    ]
    return llm.chat.completions.create(
        model=MODEL, messages=msgs, temperature=0.3
    ).choices[0].message.content.strip

def solve_with_reflexion(task, verify, max_reflect=3, max_steps=6):
    memory = ""                          # 记忆化的反思(跨重试)
    best = None
    for r in range(max_reflect + 1):
        probe = f"{task}\n\n[注意事项]\n{memory}" if memory else task
        result, trace = react_run(probe, max_steps, return_trace=True)
        ok, verdict = verify(task, result)
        best = result if ok else best
        print(f"[reflex {r}] ok={ok} verdict={verdict}")
        if ok:
            return best, r + 1           # 成功,返回重试轮数
        if r < max_reflect:              # 失败且还有预算 -> 复盘
            memory += ("- " + self_critique(task, trace, verdict) + "\n")
    return best, max_reflect + 1

# GSM8K 5 题小样本:pass@1 0.6 -> 0.8 的提升示意
def verify_math(q, a):                 # 判分器:抽数字比对(示意)
    import re
    gold = float(re.findall(r"#### (\-?[\d\.]+)", a) and
                 re.findall(r"#### (\-?[\d\.]+)", a)[0] or "nan")
    got  = re.findall(r"-?\d+\.?\d*", a)
    return (got and float(got[-1]) == gold), f"gold={gold} got={got[-1:]}"

# 跑 5 题,统计 base 与 reflexion 的 pass@1(示意数据:0.6 -> 0.8)

诚实标注:上面的"0.6 → 0.8"是示意曲线,非某模型在某题集上的实测结论。Reflexion 的增益强依赖于判分器质量与可归因的失败——判分不准时,复盘会把模型越"改进"越偏。工程上务必给重试设预算上限,并记录"多花多少 token 换回多少正确"。


6. 五件套 · Memory

Hermes 的 memory 分三层:短期(本次 conversation 的消息窗口)、长期(向量库或 SQLite 持久化,跨会话可检索)、压缩(超窗时自动摘要旧轮,把 token 压回预算内)。配置片段(示意):

Python
# Hermes memory backend 配置片段(键名以官方仓库为准)
memory = {
  "short_term": {"window": 40},                 # 保留最近 40 条
  "long_term":  {"backend": "sqlite",           # 或 "vector" / "chroma"
                 "path": "./mem/agent.db",
                 "embed_model": "bge-m3"},
  "auxiliary":  {"compression": True,           # 超窗自动摘要旧轮
                 "compress_at": 0.85,            # 用到 85% ctx 时触发
                 "keep_recent": 8},
}

6.1 跨会话记忆最小实现

Python
# file: cross_session_mem.py —— 跨会话长期记忆最小版
import sqlite3, hashlib

class LongTermMemory:
    def __init__(self, path="./mem/agent.db"):
        self.db = sqlite3.connect(path)
        self.db.execute(
          "CREATE TABLE IF NOT EXISTS mem(key TEXT PRIMARY KEY, val TEXT)")

    def _k(self, text):                      # 语义去重的粗糙版:归一化哈希
        return hashlib.md5(text.strip.lower.encode).hexdigest

    def remember(self, text):
        self.db.execute("INSERT OR REPLACE INTO mem VALUES(?,?)",
                        (self._k(text), text)); self.db.commit

    def recall(self, keys, topk=3):          # 无向量库时的关键词粗检
        rows = self.db.execute("SELECT val FROM mem").fetchall
        scored = [(sum(t.count(k.lower) for k in keys), r[0]) for r in rows]
        return [v for s, v in sorted(scored, reverse=True) if s > 0][:topk]

    def close(self): self.db.close

mem = LongTermMemory
# 会话 A:学到一条稳定事实
mem.remember("用户偏好 metric 单位的表格输出")
# 会话 B(新进程):开机即召回
print(mem.recall(["偏好", "单位", "表格"]))   # -> ['用户偏好 metric 单位的表格输出']
mem.close

6.2 压缩策略与 token 成本曲线

上下文压缩不是免费的:压得越狠,越省 token,也越可能丢掉下游任务依赖的细节。三种典型压缩率对下游的影响(示意):

压缩策略相对 token 成本对下游任务的典型影响
轻度摘要(保留近 N 轮原文 + 早期逐轮要点)约 0.8×几乎无损;长程引用偶尔偏差
分段聚合摘要(按主题合并旧轮)约 0.5×细节召回下降,数值/单位易丢
激进压缩(只留全局 summary)约 0.25×多跳/精确定位任务显著退化

与 §2 的呼应:Hermes 的 auxiliary.compression 之所以是"辅助"而非"默认开满",正是因为压缩率与下游效果是一对权衡。把 compress_at 设太低会频繁丢信息,设太高又会撞 ctx 上限报 §2.1 那类错。压缩策略本身可作为 §8/§9 里的一个实验因子。


7. 五件套 · Skill

Skill 是"可复用的过程知识"。业界常用 agentskills.io 的 SKILL.md 规范,结构是 front-matter + 正文:

Markdown
---
name: gpu-snapshot
description: 采集本机 GPU 利用率/显存快照并输出为 CSV 一行,用于监控汇总。
version: 1.0.0
---

# 步骤
1. 调用本地 shell 工具跑 `nvidia-smi --query-gpu=... --format=csv`
2. 解析为 {gpu, util, mem_used, ts}
3. 追加写入 snapshots.csv

# 参数
- `host`: 目标机器(默认本地)
- `interval_s`: 采样间隔

# 示例
输出:`T4,78,15320,2026-07-29T10:00:00`

front-matter 的 name / description / version 供 Agent 判断"什么时候该用这个 Skill",正文的步骤/参数/示例则把一次性的成功沉淀成可复用能力。Skill 的"蒸馏"——即如何从跑通的任务轨迹反向沉淀成高质量 SKILL.md——我们在 pr04 · Hermes Bot 与 Skill 蒸馏 里深入,本节只需建立"Skill = 带版本的过程知识文件"这一认识。

与 Tool 的区别:Tool 回答"我能做什么动作"(原子能力),Skill 回答"这类任务该怎么一步步做"(编排过的流程知识)。二者正交:一个 Skill 往往引用多个 Tool。


8. 整合实验:跑一个真实任务

任务定义:从三个不同源——① 服务器 nvidia-smi、② 本地 CSV、③ Web API——各抓一份数据 → 清洗合并成一张表 → 汇总 → 触发"发邮件"工具把结果寄出。这条任务把五件套一次性用全:ReAct 编排、Tools(shell + python + MCP)、Memory(跨步留存中间表)、Reflexion(某源抓取失败后重试)、Skill(若命中监控汇总流程)。

一次运行的轨迹骨架(截图式说明):

  • step 0 · Thought:拆解为"三源采集 → 合并 → 汇总 → 发送"四阶段。
  • step 1 · Action:shell.nvidia_smi → Observation:GPU 快照 OK。
  • step 2 · Action:python.load_csv("sales.csv") → Observation:120 行。
  • step 3 · Action:mcp.market.get_api → Observation:HTTP 500(失败点!)。
  • step 3b · Reflexion:判 Web 源不稳 → 指数退避重试 → 第二次 OK。
  • step 4 · Action:python.merge → Observation:合并表 122 行。
  • step 5 · Action:mcp.mail.send → Observation:message_id 返回,任务收敛。

其中失败/恢复那一步落 JSON 片段(示意):

JSON
{
  "run_id": "r-2026-07-29-001",
  "step": 3,
  "phase": "collect",
  "tool": "mcp.market.get_api",
  "args_hash": "a91f2c",
  "observation_hash": "err500",
  "elapsed_s": 1.42,
  "tokens_in": 980,
  "tokens_out": 44,
  "ok": false,
  "fail_code": "TOOL_HTTP_500",
  "reflexion": {
    "critique": "Web 源瞬时 500,属可重试错误,非逻辑缺陷",
    "action": "exp_backoff(retry=2)",
    "recovered": true,
    "retry_step": 3
  }
}

失败点与恢复策略:这一趟暴露了三类典型失败——TOOL_HTTP_500(瞬时,可退避重试)、CTX_TOO_SMALL(§2.1,配置层)、MERGE_SCHEMA_MISMATCH(源列名不一致,需归一列名后再合并)。把"哪一类失败可自动重试、哪一类必须回配置修"区分开,是 Agent 工程的关键设计,也是 pr09 失败编码要吃的字段。


9. 轨迹日志与实验接口预留

上面 §8 的每个 step 都追加写进同一份 CSV。这份 schema 是本篇交付给后续实践的接口契约——pr08 / pr09 / pr14 直接消费它,不必各自再造轮子。

9.1 CSV schema

Python
# file: trace_writer.py —— 统一轨迹落盘(供 pr08/09/14 消费)
import csv, hashlib, json

FIELDS = ["run_id", "step", "phase", "tool",
          "args_hash", "observation_hash",
          "elapsed_s", "tokens_in", "tokens_out",
          "ok", "fail_code"]

def h(x):                                # 参数/观测只存哈希,避免落敏感原文
    return hashlib.sha1(json.dumps(x, sort_keys=True,
                                   ensure_ascii=False).encode).hexdigest[:6]

class TraceWriter:
    def __init__(self, path="./runs/trace.csv"):
        self.f = open(path, "w", newline="", encoding="utf-8")
        self.w = csv.DictWriter(self.f, fieldnames=FIELDS); self.w.writeheader

    def log(self, run_id, step, phase, tool, args, obs,
            elapsed_s, tin, tout, ok, fail_code=""):
        self.w.writerow(dict(
            run_id=run_id, step=step, phase=phase, tool=tool,
            args_hash=h(args), observation_hash=h(obs),
            elapsed_s=round(elapsed_s, 3),
            tokens_in=tin, tokens_out=tout,
            ok=ok, fail_code=fail_code))

    def close(self): self.f.close

# 字段语义(下游契约):
#   phase      ∈ {plan, collect, transform, merge, emit}   -> pr14 因子分层
#   tool       实际调用的工具名                             -> pr09 失败按 tool 聚类
#   fail_code  稳定错误码(非自由文本)                     -> pr09 失败编码
#   elapsed_s / tokens_*                                    -> pr08 仪表盘 + pr14 成本因子
#   run_id      一次端到端任务的唯一标识                     -> 跨步聚合主键
下游拿这份 schema 做什么关心字段
pr08 · 可观测仪表盘按 run_id/phase 聚合耗时与 token,画出端到端瀑布与成本曲线elapsed_s, tokens_*, phase
pr09 · 失败分析以稳定 fail_code 做失败编码/交叉表,把主观缺陷变统计指标fail_code, ok, tool
pr14 · 因子分析把压缩率、重试预算、backend 等当因子,回归其对 pass/成本的影响run_id, phase, ok, tokens_*

为什么现在就定 schema:工程实验最怕"数据采了但没法对齐分析"。本篇在跑通任务的同时把 run_id / step / phase / tool / args_hash / observation_hash / elapsed_s / tokens_in / tokens_out / ok / fail_code 这 11 列固定下来,等于给后续三篇实践预付了接口。注意:fail_code 必须是受控词表里的稳定码,不能写成自由文本——否则 pr09 无法聚类。


10. 选做 · 架构一页对比

把本篇的 Hermes 与另外两个常被拿来做参照的对象放在一起,从五个工程维度平铺。下表只作定位描述,不作优劣裁定;各项以各项目官方文档为准。

维度HermesOpenClawClaude Code
定位通用 Agent 框架,五件套显式可拆偏通用/社区向的 Agent 运行壳面向编码/IDE 场景的 Agent
工具生态shell / python / MCP,白名单收敛MCP 为中心的插件式工具围绕代码工作流裁剪的内建工具
本地模型友好度强(llama.cpp / vLLM / Ollama)取决于其 backend 适配层主要面向其配套云端模型
可观测性trace 原生可落 §9 schema视版本而定,常需外接会话/工具日志,偏 IDE 视图
Skill 支持SKILL.md(agentskills.io)多为插件/配置形式项目级上下文/规则文件

这一页对比的用法是"按约束选工具",而非"排座次":要在端侧做可复现 + 强可观测的实验,Hermes 的五件套抽象与 trace 接口最省力;要贴编码工作流,Claude Code 更顺;要在既有 MCP 生态里做集成,OpenClaw 一类运行壳值得评估。


一页纸总结:Hermes = LLM 内核 + ReAct 循环 + Tools(shell/python/MCP) + Memory(短期/长期/压缩) + Skill(SKILL.md) + Reflexion 外挂,组装成本地可跑的进程。五件套各自的"最小可跑代码"本篇都给了:手写 ReAct → 切框架、MCP 注册、Reflexion 复盘层、跨会话记忆、Skill 结构。把它们在一次真实任务里跑通后,用统一的 11 列 CSV schema 落轨迹——这份 schema 是 pr08 仪表盘、pr09 失败编码、pr14 因子分析的共同接口。全程只做工程性介绍,模型名与 Hermes 接口以官方仓库为准。