动手实践与设计哲学
从零跑通 Hermes、写一个 Plugin、写一个 Skill、写一个 MCP server。 然后总结这本书带走的 10 条工程原则。
前 13 章都是读。最后一章是动手。读再多代码不动手,知识是漂浮的。 这一章带你跑通四件事:装 Hermes、写一个工具 Plugin、写一个 Skill、写一个 MCP server。 完成这四件你的"工程肌肉记忆"才算建立。然后我们用 10 条原则做收尾。
14.1安装 Hermes
一键脚本(macOS / Linux / WSL2)
curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash
source ~/.bashrc # 或 ~/.zshrc
脚本会装:uv、Python 3.11、Node.js、ripgrep、ffmpeg、Hermes 本身。安装到 ~/.hermes/。
往 ~/.local/bin/ 加一个 hermes 软链。
从源码(推荐——学习用)
git clone https://github.com/NousResearch/hermes-agent.git
cd hermes-agent
./setup-hermes.sh
# 装 uv、建 .venv、pip install -e .[all]、软链到 ~/.local/bin/hermes
./hermes # wrapper 自动激活 venv
设置 Provider
第一次启动 hermes setup 走 wizard。或者手动:
# ~/.hermes/.env
ANTHROPIC_API_KEY=sk-ant-...
# 或 OpenRouter(覆盖 200+ 模型):
OPENROUTER_API_KEY=sk-or-...
# 或 DeepSeek(便宜大碗):
DEEPSEEK_API_KEY=sk-...
# ~/.hermes/config.yaml
model:
provider: anthropic
model: claude-opus-4.7
fallback_provider: openrouter
fallback_model: anthropic/claude-sonnet-4.6
tools:
cli:
enabled: [web, file, browser, terminal, memory, skills, vision]
首次对话
hermes
# 进入交互
> 列出当前目录下 .py 文件,统计总行数
🛠 terminal: ls *.py
📋 todo: ...
🛠 terminal: wc -l *.py
当前目录下有 23 个 .py 文件,总行数 14,892。
常用 slash 命令
| 命令 | 作用 |
|---|---|
| /new | 新 session,清空历史 |
| /model | 切模型 |
| /tools | 看 / 改启用的 toolset |
| /insights | 看 token 用量 + cache 命中率 |
| /skills | 看已装 skill / 搜 hub |
| /memory | 看 / 写 memory |
| /help | 所有命令 |
14.2实战 1:写一个 Plugin 工具
目标:给 Hermes 加一个 fortune_cookie 工具,让它抽一句励志格言。
走 Plugin 路线(不动主仓库)。
步骤 1:建目录
mkdir -p ~/.hermes/plugins/fortune
步骤 2:写 plugin.yaml
~/.hermes/plugins/fortune/plugin.yaml
name: fortune
version: "0.1.0"
description: "Random programming fortune quotes."
author: "your name"
license: MIT
步骤 3:写 __init__.py
~/.hermes/plugins/fortune/__init__.py
import json
import random
FORTUNES = [
"The best way to predict the future is to invent it. — Alan Kay",
"Premature optimization is the root of all evil. — Donald Knuth",
"Make it work, make it right, make it fast. — Kent Beck",
"Talk is cheap. Show me the code. — Linus Torvalds",
"Simplicity is prerequisite for reliability. — Edsger Dijkstra",
"Programming is the art of telling another human what one wants the computer to do. — Donald Knuth",
]
def fortune_handler(args, **kw):
"""Return a random fortune quote."""
quote = random.choice(FORTUNES)
return json.dumps({"quote": quote})
FORTUNE_SCHEMA = {
"name": "fortune_cookie",
"description": (
"Return a random programming-themed fortune quote. "
"Use when the user explicitly asks for inspiration, "
"a quote, or 'something motivational about coding'. "
"DO NOT use unprompted — let the user invoke it."
),
"parameters": {
"type": "object",
"properties": {},
"required": [],
},
}
def register(ctx):
"""Plugin entry point — called by Hermes at load time."""
ctx.register_tool(
name="fortune_cookie",
toolset="fortune",
schema=FORTUNE_SCHEMA,
handler=fortune_handler,
emoji="🥠",
)
步骤 4:启用 toolset
hermes tools enable fortune
# 或编辑 config.yaml 的 tools.cli.enabled 列表加 "fortune"
步骤 5:试
hermes
> 给我抽一句关于编程的格言
🥠 fortune_cookie() → {"quote": "Talk is cheap. Show me the code. — Linus Torvalds"}
Linus Torvalds 说:"Talk is cheap. Show me the code."
这是他在 LKML 上著名的回应——意思是不要光讨论,把实际代码拿出来。
14.3实战 2:写一个 Skill
目标:让 Agent 知道"按 Conventional Commits 格式写 commit message"。
步骤 1:建目录
mkdir -p ~/.hermes/skills/git/conventional-commit
步骤 2:写 SKILL.md
~/.hermes/skills/git/conventional-commit/SKILL.md
---
name: conventional-commit
description: "Format git commit messages as Conventional Commits."
version: 1.0.0
author: "your name"
license: MIT
metadata:
hermes:
tags: [git, commit, convention]
created_by: user
---
# Conventional Commit Skill
When the user asks for a commit message, output **one** message following
the Conventional Commits format:
\`\`\`
<type>(<scope>): <subject in imperative mood, lowercase, no period>
<optional body explaining WHY, wrapped at 72 cols>
<optional footer: BREAKING CHANGE / refs / issue links>
\`\`\`
## When to Use
- User asks for "a commit message"
- User asks to "write a commit"
- User asks "what should the commit be?"
## When NOT to Use
- User wants a PR title (use a similar but different convention)
- User asks for a changelog entry (different format)
- Project explicitly uses other convention (check CONTRIBUTING.md first)
## Types
| type | use case |
|----------|---------------------------------------|
| feat | new user-facing functionality |
| fix | bug fix |
| refactor | code change with no behavior delta |
| docs | documentation only |
| chore | tooling, build, deps |
| test | adding or fixing tests |
| perf | performance improvement |
| ci | CI configuration changes |
| build | build system / dep changes |
## Subject Rules
- **max 50 chars**
- **imperative mood**: "add X" not "adds X" or "added X"
- **no period at end**
- **lowercase**
## Examples
\`\`\`
feat(auth): add OAuth2 PKCE flow
fix(db): handle null user_id in session lookup
refactor(api): extract pagination helper
docs: clarify env var defaults in README
\`\`\`
## With breaking change
\`\`\`
feat(api): use new pagination schema
The /items endpoint now returns {data, cursor} instead of bare array.
BREAKING CHANGE: callers must read response.data instead of treating
the response as an array.
\`\`\`
步骤 3:用
hermes
> /conventional-commit
[Skill loaded: conventional-commit]
> 我刚修了 auth refresh token 过期没处理导致 crash 的 bug,给个 commit message
fix(auth): handle expired refresh token without crash
The refresh endpoint returns 401 when the token has expired. Previously
we re-raised, crashing the session. Now we clear local tokens and
surface a clean re-auth prompt.
Refs: #842
注意 Hermes 输出 立刻符合规范。fix(auth) 不是它瞎猜的——
是 SKILL.md 告诉它的。
给 Skill 加 Scripts
复杂逻辑别让 LLM inline 写,扔脚本里:
~/.hermes/skills/research/web_scrape/
├── SKILL.md # 决策手册
├── scripts/
│ └── extract_main.py # 用 BeautifulSoup 抽主内容
└── references/
└── selectors.json # 常见网站的 CSS 选择器
SKILL.md 里只说 "用 scripts/extract_main.py 提取",
Agent 通过 execute_code 或 terminal 工具执行。
让 Agent 自己创建 Skill
跑一个复杂任务,结尾问它:
> 这个流程值得存成一个 skill 吗?如果是,请用 skill_manage 创建。
✓ skill_manage(action="create", name="async-task-monitoring", ...)
skill saved to ~/.hermes/skills/devops/async-task-monitoring/
Curator 会自动把这个 skill 标记 created_by: agent,未来 90 天不用就归档。
想保留?用 hermes curator pin async-task-monitoring。
14.4实战 3:写一个 MCP Server
第 7 章和第 13 章讲了 MCP 的重要性。现在我们写一个。
目标:暴露一个 calculate_distance 工具——两个城市的距离。让 Hermes 自动接入。
步骤 1:建项目
mkdir mcp-distance && cd mcp-distance
uv venv .venv
source .venv/bin/activate
uv pip install "mcp>=1.0"
步骤 2:写 server.py
mcp-distance/server.py
import json
from mcp.server import Server
from mcp.server.stdio import stdio_server
import mcp.types as types
server = Server("distance-calculator")
# 简化版"城市坐标",实际可换成调 API
CITY_COORDS = {
"beijing": (39.9042, 116.4074),
"shanghai": (31.2304, 121.4737),
"tokyo": (35.6762, 139.6503),
"san francisco": (37.7749, -122.4194),
"new york": (40.7128, -74.0060),
}
def haversine(lat1, lon1, lat2, lon2) -> float:
"""Great-circle distance in km."""
from math import radians, sin, cos, sqrt, atan2
R = 6371
dlat = radians(lat2 - lat1)
dlon = radians(lon2 - lon1)
a = sin(dlat/2)**2 + cos(radians(lat1)) * cos(radians(lat2)) * sin(dlon/2)**2
return R * 2 * atan2(sqrt(a), sqrt(1-a))
@server.list_tools()
async def handle_list_tools() -> list[types.Tool]:
return [
types.Tool(
name="calculate_distance",
description="Calculate great-circle distance between two cities in km.",
inputSchema={
"type": "object",
"properties": {
"city_a": {"type": "string", "description": "e.g. 'beijing'"},
"city_b": {"type": "string", "description": "e.g. 'tokyo'"},
},
"required": ["city_a", "city_b"],
},
),
]
@server.call_tool()
async def handle_call_tool(name: str, args: dict) -> list[types.TextContent]:
if name != "calculate_distance":
raise ValueError(f"Unknown tool: {name}")
a = args["city_a"].lower().strip()
b = args["city_b"].lower().strip()
if a not in CITY_COORDS or b not in CITY_COORDS:
return [types.TextContent(
type="text",
text=json.dumps({"error": "Unknown city. Known: " + str(list(CITY_COORDS))})
)]
lat1, lon1 = CITY_COORDS[a]
lat2, lon2 = CITY_COORDS[b]
d = haversine(lat1, lon1, lat2, lon2)
return [types.TextContent(
type="text",
text=json.dumps({"distance_km": round(d, 2), "from": a, "to": b})
)]
async def main():
async with stdio_server() as (read, write):
await server.run(read, write, server.create_initialization_options())
if __name__ == "__main__":
import asyncio
asyncio.run(main())
步骤 3:让 Hermes 连接
~/.hermes/config.yaml
mcp:
servers:
distance:
command: python
args: [/path/to/mcp-distance/server.py]
enabled: true
步骤 4:试
hermes
> 北京到东京多远?
🛠 mcp__distance__calculate_distance(city_a="beijing", city_b="tokyo")
→ {"distance_km": 2103.97, "from": "beijing", "to": "tokyo"}
北京到东京约 2104 公里(大圆距离)。
14.5实战 4:给项目写 AGENTS.md
最后一个实战是"教 Agent 怎么在你的项目里工作"。 这是 Hermes / Claude Code / Cursor 都支持的通用方式。
AGENTS.md 是什么
项目根目录下的一份 Markdown 文档,告诉 AI Agent:
- 项目的高层架构(用一两个 ASCII diagram)
- 关键模块的位置("配置在 config/"、"测试在 tests/")
- 常见任务的 idiom("添加 API 端点:1) 改 routes.py 2) 加 test 3) 改 OpenAPI spec")
- 禁忌("不要直接改 generated/ 目录"、"DB 操作必须走 transaction")
- 测试命令、lint 命令、build 命令
最简模板
AGENTS.md(项目根目录)
# Project Agent Guide
## What This Is
A Python service that processes incoming webhooks from Stripe and
writes normalized events to PostgreSQL. Built on FastAPI + SQLAlchemy.
## Architecture
\`\`\`
[Stripe] --webhook--> [FastAPI server (app.py)]
│
│ verify signature
│ parse event
▼
[Event Handler (handlers/)]
│
│ normalize → ORM
▼
[PostgreSQL via SQLAlchemy]
\`\`\`
## Key Files
| Path | Purpose |
|------|---------|
| `app.py` | FastAPI entry point, route definitions |
| `handlers/` | One file per Stripe event type |
| `db/models.py` | SQLAlchemy models |
| `db/migrations/` | Alembic migrations — never edit by hand |
| `tests/` | pytest tests; use fixtures from `tests/conftest.py` |
| `.env.example` | Required env vars |
## Common Tasks
### Add a new Stripe event handler
1. Create `handlers/<event_name>.py` with a `def handle(event: dict) -> None` function.
2. Register it in `handlers/__init__.py`'s `HANDLERS` dict.
3. Add a test in `tests/handlers/test_<event_name>.py` using `stripe_event_factory`.
4. Run `pytest tests/handlers/`.
### Add a new DB column
1. Add column to the model in `db/models.py`.
2. Generate migration: `alembic revision --autogenerate -m "<desc>"`.
3. Review the generated migration file. Edit if needed.
4. Apply: `alembic upgrade head`.
## Commands
\`\`\`bash
make test # pytest with coverage
make lint # ruff + mypy
make migrate # alembic upgrade head
make dev # start FastAPI with reload
\`\`\`
## Conventions
- Use `from __future__ import annotations` in every Python file.
- Type hints required for all public functions.
- Test files mirror source structure: `src/foo/bar.py` → `tests/foo/test_bar.py`.
- Never raise from `handlers/*.py` — log and return.
## Forbidden
- DO NOT edit `db/migrations/*.py` manually after generation (re-generate instead).
- DO NOT use `session.execute()` with raw SQL — use ORM.
- DO NOT add new top-level deps without discussing — we pin everything.
把这份扔到你的项目根目录。Hermes / Cursor / Claude Code 启动时会自动发现并放进 context(第 5 章我们看过——它进 context 层)。Agent 立刻知道你的项目。
14.6实战 5:让 Skill 跨工具兼容
第 8 章我们看过 Skill 在 2025.12 已经成为开放标准。 这一节带你写一份能在 Claude Code、Cursor、Hermes、ChatGPT 都能用的 SKILL.md。
跨工具兼容的 5 个规则
-
frontmatter 只用标准字段:
name、description、version、license、platforms。 Hermes 私有的metadata.hermes.*别用。 -
不引用 host-specific 工具名。引用工具时用通用名("file editor"、"shell"),
不写
browser_navigate这种 Hermes 特定名字。或者在## Prerequisites里写"requires: shell, file read/write",让每个 host 自己 map 到本地工具。 -
用 fenced code 块。所有命令例子用
```bash包, 不要假设 Markdown 处理器支持 Hermes 特定语法。 -
脚本放
scripts/子目录,用相对路径引用。 所有 host 都遵守 SKILL.md 同级目录约定。 -
不依赖私有环境变量。如果 skill 需要 API key,要求在
## Prerequisites里说清楚 env var 名字,让用户自己设。
跨兼容 SKILL.md 模板
~/.hermes/skills/dev/clean-pr-review/SKILL.md
---
name: clean-pr-review
description: "Review a GitHub PR with structured comments grouped by severity."
version: 1.0.0
license: MIT
author: "your name"
---
# Clean PR Review
Generate a structured PR review with comments grouped by severity
(blocker / major / minor / nit).
## When to Use
- User asks "review this PR" or "do a code review"
- User links a GitHub PR URL
- User asks for feedback on a diff
## When NOT to Use
- User asks for a one-line approval comment
- User wants you to merge the PR (use git tooling instead)
## Prerequisites
- Shell access to a tool that can fetch PR diffs (gh CLI, git, or HTTP)
- Read access to the repository
## Procedure
1. Fetch the PR diff. Prefer `gh pr diff <number>` if available;
fall back to `git diff <base>...<head>`.
2. Identify the changed files. For each file, categorize the change:
feat / fix / refactor / docs / test.
3. Find issues by severity:
- **blocker** — bugs, security, broken builds
- **major** — design issues, missing tests for new behavior
- **minor** — code style, naming, readability
- **nit** — purely cosmetic
4. Output in this exact format:
\`\`\`markdown
## Summary
<1-paragraph high-level summary>
## Blockers
- file:line — <issue> (suggest: <fix>)
## Major
- ...
## Minor / Nits
- ...
\`\`\`
5. If no issues at a level, omit that section entirely.
## Pitfalls
- Don't pad: omit empty sections, don't say "no blockers found".
- Don't approve / request changes — just output the review.
这份 SKILL.md 没有任何 Hermes 私有内容。把它扔进任意一个兼容 host:
- Hermes:
~/.hermes/skills/dev/clean-pr-review/ - Claude Code:
~/.claude/skills/dev/clean-pr-review/ - Cursor: 通过
.cursor/rules/引用 - ChatGPT: 上传到 Custom GPT 的 instructions(手动复制)
都能直接生效。这是 2025.12 之后 skill 生态的核心红利。
14.710 条设计哲学
读完前 13 章,把这本书的核心智慧提炼成 10 条原则。 把它们贴你写 Agent 代码的桌面上。
① 不要在 Augmented LLM 之上添加抽象,除非你能说清楚它解决什么具体问题
Anthropic、Simon Willison、Hermes 维护者反复说这条。LangChain 风格的
Agent / Chain / Memory 基类层叠让代码难调试。
直接写循环 + 字典是更好的起点。有需要再抽象,不要预先抽象。
② 缓存是钱的问题,不是性能问题
Agent 经济 90% 取决于 prompt cache 命中率。一次 system prompt 修改可能让账单 ×10。 所有"动态内容"只 append 到末尾,永不 insert/edit 中间。Skill 走 user message 注入而不是改 system——这是 Hermes 工程最核心的一招。
③ 错误返回值 > 错误异常
工具抛错 → 包成 {"error": "..."} JSON 给模型看。模型大概率自己改正。
抛异常给框架等于浪费 LLM 这层智能。"Tool 'web_seach' doesn't exist. Available: web_search, ..."
让模型下一轮自己改成 web_search。
④ 永远给 LLM 留余地
预算耗尽给 grace call、空响应有三层 fallback、幻觉工具名让它重试 3 次—— 永远不要"硬性失败给用户看"。注入合成消息让对话继续比留沉默好。
⑤ 把"自我改进"拆成三件事
Skill(程序性记忆)+ Memory(情景记忆)+ Curator(维护)。没有黑魔法, 只是"系统地把每次新经验存到合适的地方,并定期整理"。三者缺一不可: 没有 Curator 你的 skill 库会腐烂;没有 Memory 你每次从零开始;没有 Skill 你只有事实没有方法。
⑥ 把"提示注入防御"做到工具层
不要试图教 LLM 防御 prompt injection。限制它能做的事。Webhook 来的内容只暴露
safe toolset——就算 LLM 被骗,它根本看不见 terminal 工具。Capability-based
security 比 LLM-side 防御靠谱 100 倍。
⑦ Registry-based discovery 优于显式注册
工具、provider、platform adapter、skill——都是"把文件丢进目录自动起效"。 你不维护一份导入列表,添加新东西的摩擦力降到最低。 这也是 plugin 系统能蓬勃发展的基础。
⑧ Hook 化的扩展点优于继承
pre_tool_call、transform_tool_result、on_session_start...
Plugin 通过订阅生命周期事件扩展,核心保持稳定。
改不到的"特殊需求"先想"能不能加个 hook",不要往 core 塞特定 plugin 逻辑。
⑨ Provider quirks 隔离在 Profile 子类
不同模型 provider 的怪癖(DeepSeek thinking flag、Gemini tool_choice、reasoning 字段差异)
全部放在ProviderProfile 子类的方法重载里。主循环代码不出现一个 if provider == "..."。
这条规则适用于任何"对接多个 API"的场景。
⑩ 写代码时记住——你写的是给 LLM 看的
Tool 的 schema description、Skill 的 SKILL.md、Provider profile 的注释—— 这些都被 LLM 读到,影响下一轮决策。把它们写清楚比把变量名写好更重要。 Description 模糊 → LLM 用错工具;Skill When to Use 不清 → 该用没用。 "代码不是给人写的,也是给 LLM 写的"——这是 Agent 时代代码风格的新规则。
14.8推荐阅读路径
读完这本书还不够。下面这些资源按优先级排:
必读
- Hermes
AGENTS.md— 53KB 的项目工程指南,是本书没收进来的"细节大全"。 - Hermes
agent/conversation_loop.py— 把 644-3821 行(主 while)扫一遍。 - Anthropic Building Effective Agents。
- Anthropic Effective Context Engineering。
- MCP 协议规范 modelcontextprotocol.io。
论文(按重要性)
- ReAct (Yao 2022) — arXiv:2210.03629
- Voyager (Wang 2023) — arXiv:2305.16291
- Generative Agents (Park 2023) — arXiv:2304.03442
- MemGPT (Packer 2023) — arXiv:2310.08560
- Reflexion (Shinn 2023) — arXiv:2303.11366
工业博客
- Anthropic Engineering — anthropic.com/engineering
- Simon Willison — 持续在 simonwillison.net 综述
- Cognition / Devin team blog
- Cursor team blog
- Hermes "What's New" — hermes-agent.nousresearch.com/docs
动手项目
- 给你自己的项目写
AGENTS.md,让 Hermes / Claude Code / Cursor 都能用。 - 写一个 MCP server 暴露你常用的 API(GitHub、Linear、自家 CMS……)。
- 试 Hermes 的
delegate_task:让 Agent 把一个大任务自己切给 sub-agent。 - 跑 Hermes 一周后看
~/.hermes/skills/——看 Agent 自己创建了什么。 - 把 Hermes 部署到 VPS,连 Telegram bot,开始用手机远程让它干活。
14.9结语
你读完了一本 14 章、约 8 万字的 Agent 工程教材。如果你做了所有的练习和实践, 你现在拥有的不只是"知道 Agent 是什么"——你拥有"看到任何 Agent 系统都能拆开 评估"的能力。
Hermes 不是终点。它是 2024–2026 这段时期工程沉淀的样本。明年、后年、五年后, 会有更好的 Agent framework 出现。但底层规律不会变——
- 循环 + 工具 + 记忆是骨架;
- 缓存是经济命脉;
- 错误回传给模型是处理失败的核心打法;
- 能力限制比"教 LLM 别犯错"靠谱;
- "自动化但可逆"是可信 AI 的底线;
- 少抽象、多代码、可审计、可分享。
把这些原则握在手里,下次你看到任何 Agent 项目都知道该看什么。 这就是本书的价值。
祝你 build 出更好的 Agent。
章末练习(也是给你自己的"出师题")
- Medium 把本章的 4 个实战全部跑通:装 Hermes、写 Plugin、写 Skill、写 MCP server。
-
Medium
在你最熟悉的项目里加一份
AGENTS.md,让 AI Agent 能在里面工作。 用一周观察它的表现,然后迭代这份 AGENTS.md。 - Medium 把第 14.6 节的 10 条原则印出来贴桌上。下次写 Agent 代码时,逐条对照—— 你违反了几条?
- Hard 跑 SWE-Bench Verified 上的一个简单任务,用你自己的小 harness。看你的分数。 想想哪条原则没做到。
- Hard 给本书写一个章节"如何不读 Hermes 源码也能学 Agent 工程"——你会推荐什么书 / 论文 / 项目? 把你的版本与本书做对比。
- Hard 回头读第 1 章。问自己:Agent 工程这个领域,五年后会变成什么样? 写 500 字预测。一年后回来看你猜中几条。
—— End of Book ——