为什么用 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 的定位差异不在于"更强",而在于取向不同:
- 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 解耦成两个进程:
#!/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 回填"这四步到底在干什么:
# 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,业务代码塌缩成"给任务 + 给工具"两件事,可控性从"自己管解析回填"变成"配置 + 回调钩子":
# 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 调用
# 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 的提升。
5.1 给 ReAct loop 加一层 Reflexion
# 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 压回预算内)。配置片段(示意):
# 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 跨会话记忆最小实现
# 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 + 正文:
---
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 片段(示意):
{
"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
# 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 与另外两个常被拿来做参照的对象放在一起,从五个工程维度平铺。下表只作定位描述,不作优劣裁定;各项以各项目官方文档为准。
| 维度 | Hermes | OpenClaw | Claude 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 接口以官方仓库为准。