1. 从 Hermes Agent 源码看 Self-Improving 循环到底在跑什么
Hermes Agent 的 Self-Improving 机制,简单说就是让 Agent 在干完活之后自己复盘、自己写笔记、自己攒技能,下次遇到同类任务直接调用。它不是一个玄学概念,而是由 Memory、Skill、Nudge Engine 三个子系统拼起来的一个闭环。Memory 负责记事实,Skill 负责记步骤,Nudge Engine 负责定时提醒 Agent “该回头看看有没有值得沉淀的东西了”。
这套机制适合谁?适合已经在用 Hermes Agent 做日常编码、运维、数据处理,但每次都要重复交代背景、重复踩坑的人。也适合想研究 Agent 自我进化实现细节的开发者——因为它的源码结构相对清晰,关键模块都有注释,适合拿来拆解。
我这次要做的,是在本地搭一个可复现的验证环境,用 TaoToken 统一 Key 通道把模型调用接进来,然后逐步观察 Self-Improving 的触发条件、日志输出和实际效果。TaoToken 在这里的角色是统一 API 入口,省去在多个模型供应商之间来回切换 Key 的麻烦,让验证过程聚焦在机制本身。
验证目标有三个:第一,确认 Memory 写入和读取的完整链路;第二,确认 Skill 创建和 patch 的触发条件;第三,确认 Nudge Engine 的计数器和后台 review 是否按预期工作。这三个目标对应源码里的三个关键文件:tools/memory_tool.py、tools/skill_manager_tool.py、run_agent.py。
在开始之前,你需要准备:一台能跑 Python 的本地机器、Hermes Agent 源码(github.com/NousResearch/hermes-agent)、一个 TaoToken API Key。不需要 GPU,因为模型调用走 API。整个验证过程大概需要 30 到 40 分钟,取决于你读源码的细致程度。
我试过把这套流程跑通之后,最大的感受是:Self-Improving 不是“自动变聪明”,而是“自动把经验结构化”。Agent 本身不会突然学会新东西,但它会把已经做过的事情整理成可复用的 Skill,把用户偏好整理成 Memory。这个区别很重要,决定了你对它的预期。
2. TaoToken 前置:统一 Key 通道与本地环境准备
TaoToken 在这里的作用是提供一个统一的 API 通道,让你用同一个 Key 访问不同模型,而不需要在 Hermes 的配置文件里为每个供应商单独写一套认证信息。对于验证 Self-Improving 机制来说,这能减少配置层面的干扰——你只需要关心 Agent 的行为,不需要关心 Key 的管理。
首先去 TaoToken 官网注册并获取 API Key。地址是https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=。注册完成后,在控制台里创建一个新的 API Key,复制保存。这个 Key 后面会写进 Hermes 的配置文件。
接下来克隆 Hermes Agent 源码:
git clone https://github.com/NousResearch/hermes-agent.git cd hermes-agent创建 Python 虚拟环境并安装依赖:
python3 -m venv venv source venv/bin/activate pip install -r requirements.txtHermes 的配置文件默认在~/.hermes/config.yaml。如果目录不存在,手动创建:
mkdir -p ~/.hermes然后写入基础配置。这里的关键是把 TaoToken 的 Base URL 和 API Key 填进去:
# ~/.hermes/config.yaml model: provider: openai-compatible base_url: https://taotoken.net/api api_key: sk-your-taotoken-key-here model_id: claude-sonnet-4-20250514 memory: enabled: true memory_char_limit: 2200 user_char_limit: 1375 skills: enabled: true creation_nudge_interval: 10 nudge: memory_interval: 10 skill_interval: 10注意base_url写的是https://taotoken.net/api,不要加 UTM 参数。model_id可以根据你实际想用的模型调整,TaoToken 支持多个模型 ID,具体可以在模型对话页面查看。
配置写完后,验证一下 Key 是否可用:
curl -X POST https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer sk-your-taotoken-key-here" \ -H "Content-Type: application/json" \ -d '{ "model": "claude-sonnet-4-20250514", "messages": [{"role": "user", "content": "ping"}], "max_tokens": 10 }'如果返回正常的 JSON 响应,说明 Key 和通道都没问题。如果返回 401,检查 Key 是否复制完整;如果返回 model not found,检查model_id是否拼写正确。
这一步完成后,你的本地环境就具备了运行 Hermes Agent 并观察 Self-Improving 行为的基础条件。接下来进入源码关键模块的读取和最小调用链路的构造。
3. 可复制配置:读取源码关键模块与最小调用链路
这一节的核心是让你能在本地把 Self-Improving 的三个子系统跑起来,并且能观察到它们的行为。我们先从源码里定位关键模块,然后构造一个最小调用链路。
3.1 Memory 模块:容量限制与写入失败的处理
打开tools/memory_tool.py,找到MemoryStore类的初始化部分:
# tools/memory_tool.py:116-122 class MemoryStore: def __init__(self, memory_char_limit=2200, user_char_limit=1375): self.memory_entries: List[str] = [] self.user_entries: List[str] = [] self.memory_char_limit = memory_char_limit self.user_char_limit = user_char_limit self._system_prompt_snapshot: Dict[str, str] = {"memory": "", "user": ""}这里定义了两个容量上限:MEMORY 2200 字符,USER 1375 字符。容量有限是故意的,目的是倒逼 Agent 做信息压缩,只记高密度事实。
继续看写入逻辑,找到add方法里超过限制时的处理:
# tools/memory_tool.py:248-259 if new_total > limit: current = self._char_count(target) return { "success": False, "error": ( f"Memory at {current:,}/{limit:,} chars. " f"Adding this entry ({len(content)} chars) would exceed the limit. " f"Replace or remove existing entries first." ), "current_entries": entries, "usage": f"{current:,}/{limit:,}", }关键点:超过限制时不会静默丢弃,也不会自动压缩,而是返回失败,并把当前所有条目返回给模型。模型看到current_entries后,自己决定哪些该删、哪些该合并。这就是“自我反思”在 Memory 层面的体现。
3.2 Skill 模块:创建与 patch 的触发条件
打开tools/skill_manager_tool.py,找到SKILL_MANAGE_SCHEMA:
# tools/skill_manager_tool.py:681-701 SKILL_MANAGE_SCHEMA = { "name": "skill_manage", "description": ( "Manage skills (create, update, delete). Skills are your procedural " "memory — reusable approaches for recurring task types.\n\n" "Create when: complex task succeeded (5+ calls), errors overcome, " "user-corrected approach worked, non-trivial workflow discovered, " "or user asks you to remember a procedure.\n" "Update when: instructions stale/wrong, OS-specific failures, " "missing steps or pitfalls found during use. " "If you used a skill and hit issues not covered by it, " "patch it immediately with skill_manage(action='patch') " "— don't wait to be asked.\n\n" "After difficult/iterative tasks, offer to save as a skill. " "Skip for simple one-offs." ), }创建 Skill 的触发条件写得很清楚:工具调用超过 5 次、克服了错误、用户纠正过的做法、发现了非平凡工作流。简单的一次性任务不创建。
patch 的逻辑在_patch_skill函数里:
# tools/skill_manager_tool.py:397-485 def _patch_skill(name, old_string, new_string, file_path=None, replace_all=False): """Targeted find-and-replace within a skill file.""" from tools.fuzzy_match import fuzzy_find_and_replace new_content, match_count, _strategy, match_error = fuzzy_find_and_replace( content, old_string, new_string, replace_all ) if match_error: return {"success": False, "error": match_error, "file_preview": content[:500]} original_content = content _atomic_write_text(target, new_content) scan_error = _security_scan_skill(skill_dir) if scan_error: _atomic_write_text(target, original_content) return {"success": False, "error": scan_error}patch 用的是模糊匹配,因为 Agent 给出的old_string可能和原文有格式差异。修改后还会跑一次安全扫描,不通过就回滚。
3.3 Nudge Engine:计数器与后台 review
打开run_agent.py,找到计数器初始化:
# run_agent.py:1328-1331 self._memory_nudge_interval = 10 self._turns_since_memory = 0 # run_agent.py:1428-1431 self._skill_nudge_interval = int(skills_config.get("creation_nudge_interval", 10)) self._iters_since_skill = 0Memory 按用户回合计数,Skill 按迭代次数计数。到阈值就触发 review。
后台 review 的 fork 逻辑:
# run_agent.py:2665-2711 def _spawn_background_review(self, messages_snapshot, review_memory=False, review_skills=False): def _run_review(): with open(os.devnull, "w") as _devnull, \ contextlib.redirect_stdout(_devnull), \ contextlib.redirect_stderr(_devnull): review_agent = AIAgent( model=self.model, max_iterations=8, quiet_mode=True, ) review_agent._memory_store = self._memory_store review_agent._memory_enabled = self._memory_enabled review_agent._user_profile_enabled = self._user_profile_enabled review_agent._memory_nudge_interval = 0 review_agent._skill_nudge_interval = 0 review_agent.run_conversation( user_message=prompt, conversation_history=messages_snapshot, ) thread = threading.Thread(target=_run_review, daemon=True) thread.start()几个关键细节:输出重定向到/dev/null,用户无感知;最多 8 次工具调用,不会无限消耗 API;review agent 自身的 nudge 被禁用,避免无限递归;和主 agent 共享同一个 MemoryStore,写入直接生效。
3.4 最小调用链路配置
现在构造一个最小配置,让 Agent 跑起来并触发 Self-Improving。在~/.hermes/config.yaml里补充:
agent: max_iterations: 20 quiet_mode: false logging: level: DEBUG file: ~/.hermes/logs/agent.log然后写一个简单的测试脚本:
# test_self_improving.py import os from run_agent import AIAgent agent = AIAgent( model="claude-sonnet-4-20250514", max_iterations=20, quiet_mode=False, ) # 第一轮:让 Agent 做一个需要多步操作的任务 result = agent.run_conversation( user_message="帮我在当前目录创建一个 Python 脚本,读取 data.csv,计算每列平均值,输出到 result.json。如果 data.csv 不存在,先生成一个示例文件。" ) print("=== 第一轮完成 ===") print(result)运行这个脚本,观察日志输出。你应该能看到 Agent 执行多步操作,工具调用次数超过 5 次,然后 Nudge Engine 触发 Skill Review。
4. 验证请求与成功结果:观察自我改进触发条件
这一节我们实际跑一遍,观察 Memory 写入、Skill 创建、Nudge 触发的完整过程。
4.1 第一轮:冷启动,触发 Skill 创建
运行上面的测试脚本。在日志里你会看到类似这样的输出:
[DEBUG] Tool call: write_file("generate_sample.py") [DEBUG] Tool call: terminal("python generate_sample.py") [DEBUG] Tool call: read_file("data.csv") [DEBUG] Tool call: write_file("calc_avg.py") [DEBUG] Tool call: terminal("python calc_avg.py") [DEBUG] Tool call: read_file("result.json") [DEBUG] Iteration count: 6 [DEBUG] Skill nudge triggered: _iters_since_skill=6 >= 10? No, waiting...等等,这里有个细节:_iters_since_skill是 6,还没到 10。但 Skill 创建的触发条件里写的是“5+ calls”。这两个条件不冲突——5+ calls 是模型自己判断是否值得创建,10 是 Nudge Engine 强制提醒的阈值。模型可以在 6 次调用后自己决定创建 Skill,不需要等 Nudge。
继续看日志,如果模型决定创建 Skill,你会看到:
[DEBUG] Tool call: skill_manage(action="create", name="csv-average-calc", category="data-processing") [DEBUG] Security scan passed for skill: csv-average-calc [DEBUG] Skill written to ~/.hermes/skills/data-processing/csv-average-calc/SKILL.md检查生成的 Skill 文件:
cat ~/.hermes/skills/data-processing/csv-average-calc/SKILL.md你应该能看到类似这样的内容:
--- name: csv-average-calc description: Calculate column averages from a CSV file and output to JSON version: 1.0.0 --- # CSV Average Calculation ## When to use - User wants to calculate averages from a CSV file - User mentions data.csv, column statistics, or result.json ## Steps 1. Check if data.csv exists; if not, generate a sample file 2. Read data.csv with pandas 3. Calculate mean for each numeric column 4. Write results to result.json ## Pitfalls - Handle missing values before calculating mean - Ensure numeric columns are correctly typed4.2 第二轮:Skill 复用与自我修补
现在再跑一个类似任务,但换一个 CSV 文件:
result = agent.run_conversation( user_message="帮我计算 sales.csv 里每列的平均值,输出到 summary.json。" )观察日志:
[DEBUG] Skill index loaded: csv-average-calc available [DEBUG] Tool call: skill_view("csv-average-calc") [DEBUG] Tool call: read_file("sales.csv") [DEBUG] Tool call: write_file("calc_avg_v2.py") [DEBUG] Tool call: terminal("python calc_avg_v2.py") [DEBUG] Error: KeyError 'date' — non-numeric column [DEBUG] Tool call: write_file("calc_avg_v3.py") # 跳过非数值列 [DEBUG] Tool call: terminal("python calc_avg_v3.py") [DEBUG] Tool call: skill_manage(action="patch", name="csv-average-calc", old_string="Calculate mean for each numeric column", new_string="Calculate mean for each numeric column, skip non-numeric columns like dates") [DEBUG] Security scan passed [DEBUG] Skill patched successfully这里发生了自我修补:Agent 在使用 Skill 时遇到了 Skill 没覆盖的坑(非数值列),于是 patch 了 Skill,把“跳过非数值列”加进了 Pitfalls。
4.3 第三轮:验证 Memory 写入
Memory 的触发需要用户回合数达到 10。我们可以通过多轮对话来触发:
for i in range(12): result = agent.run_conversation( user_message=f"这是第 {i+1} 轮对话,请记住我偏好用 pandas 而不是 csv 模块处理表格数据。" )在第 10 轮左右,你会看到:
[DEBUG] Memory nudge triggered: _turns_since_memory=10 [DEBUG] Spawning background review agent for memory [DEBUG] Review agent: memory_manage(action="add", content="User prefers pandas over csv module for tabular data") [DEBUG] Memory written to ~/.hermes/memories/USER.md检查 Memory 文件:
cat ~/.hermes/memories/USER.md你应该能看到类似:
§ User prefers pandas over csv module for tabular data § User works on data processing tasks involving CSV files4.4 成功结果的判断标准
验证成功的标志有三个:
第一,~/.hermes/skills/目录下出现了自动创建的 Skill 目录,且 SKILL.md 内容合理。
第二,~/.hermes/memories/目录下的 MEMORY.md 或 USER.md 出现了新的条目,且条目是声明式事实而非命令式指令。
第三,日志里出现了Skill nudge triggered或Memory nudge triggered,以及对应的skill_manage或memory_manage工具调用。
如果这三个都出现了,说明 Self-Improving 机制在你的本地环境里按预期运行。
5. 本篇常见错排查:401、local proxy failed、reading choices、OAuth
在验证过程中,你可能会遇到几类典型错误。这一节按真实报错来排查。
5.1 401 Unauthorized
报错原文:
Error: 401 Unauthorized {"error": {"message": "Invalid API key", "type": "invalid_request_error"}}原因:TaoToken API Key 没填对,或者配置文件里的api_key字段有空格、换行。
排查步骤:检查~/.hermes/config.yaml里的api_key是否完整复制,前后无空格。用 curl 单独测试 Key 是否有效。如果 curl 也返回 401,去 TaoToken 控制台重新生成一个 Key。
5.2 local proxy failed
报错原文:
Error: local proxy failed: connection refused原因:Hermes 尝试连接本地代理,但代理没启动。如果你在配置里写了proxy字段,检查代理地址和端口是否正确。如果不需要代理,把proxy字段删掉。
排查步骤:检查~/.hermes/config.yaml里是否有proxy配置。如果有,注释掉或删除。然后确认base_url直接指向https://taotoken.net/api。
5.3 reading choices 相关报错
报错原文:
Error: reading choices: unexpected end of JSON input原因:API 返回的响应体不完整,通常是网络中断或超时导致。也可能是max_tokens设置过小,响应被截断。
排查步骤:检查网络连接是否稳定。把max_tokens调大,比如从 10 调到 1024。如果问题持续,在 curl 测试里加-v查看完整响应。
5.4 OAuth 相关报错
报错原文:
Error: OAuth token expired or invalid原因:如果你在配置里用了 OAuth 认证方式,但 token 过期了。TaoToken 的 API Key 认证不需要 OAuth,所以这个错误通常出现在你混用了两种认证方式。
排查步骤:确认~/.hermes/config.yaml里只用了api_key字段,没有oauth相关配置。如果有,删掉 OAuth 部分,统一用 API Key。
5.5 Skill 创建失败:Security scan blocked
报错原文:
Security scan blocked this skill (reason: suspicious_pattern): Pattern matched: curl.*\$\{?\w*(KEY|TOKEN|SECRET|PASSWORD)原因:Skill 内容里包含了可能泄露密钥的模式,比如curl命令里引用了环境变量中的 KEY。
排查步骤:检查 Skill 的 SKILL.md 内容,把涉及密钥引用的部分改成占位符描述,比如把curl -H "Authorization: Bearer $API_KEY"改成curl -H "Authorization: Bearer <your-api-key>"。修改后重新创建 Skill。
5.6 Memory 写入失败:exceed limit
报错原文:
Memory at 2,150/2,200 chars. Adding this entry (120 chars) would exceed the limit. Replace or remove existing entries first.原因:Memory 容量满了。这是预期行为,不是 bug。
排查步骤:查看current_entries返回的内容,手动删除或合并一些过时条目。或者让 Agent 自己处理——在下一轮对话里告诉它“Memory 满了,请整理一下”,它会调用memory_manage的 replace 或 remove 操作。
5.7 三件套配置检查清单
如果你用的是 Claude Code、Cline MCP 或 Codex,确保以下三件套都写全:
Base URL:https://taotoken.net/apiAPI Key:sk-your-taotoken-key-hereModel ID:claude-sonnet-4-20250514(或你实际使用的模型 ID)
以 Codex 的auth.json为例:
{ "base_url": "https://taotoken.net/api", "api_key": "sk-your-taotoken-key-here", "model": "claude-sonnet-4-20250514" }以 Cline MCP 的 settings 为例:
{ "mcpServers": { "taotoken": { "command": "npx", "args": ["-y", "@taotoken/mcp-server"], "env": { "TAOTOKEN_BASE_URL": "https://taotoken.net/api", "TAOTOKEN_API_KEY": "sk-your-taotoken-key-here", "TAOTOKEN_MODEL_ID": "claude-sonnet-4-20250514" } } } }三件套缺一不可。少了 Base URL 会走默认端点,少了 Key 会 401,少了 Model ID 会报 model not found。
6. 语义一致 CTA:继续验证与深入方向
如果你已经跑通了上面的验证流程,接下来可以往几个方向深入。
第一个方向是观察 Skill 的生命周期。目前 Hermes 的 Skill 只有name、description、version三个 frontmatter 字段。你可以手动在 SKILL.md 里加上last_used、use_count、success_rate,然后观察 Agent 是否会根据这些字段做优先级判断。这需要改源码,但改动量不大。
第二个方向是验证多 Skill 组合。创建两个相关的 Skill,比如csv-average-calc和json-output-format,然后给一个需要同时用到两者的任务,观察 Agent 是否会依次加载并组合使用。
第三个方向是压力测试 Nudge Engine。把creation_nudge_interval从 10 改成 3,然后跑一个简单任务,观察是否会出现过度创建 Skill 的情况。这能帮你理解阈值设置对 Agent 行为的影响。
如果你在验证过程中需要查看模型的实际响应,可以用 TaoToken 的模型对话页面直接测试 prompt。地址是https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=。
如果你打算长期用 Hermes Agent 做编码或 Agent 开发,建议关注 Coding Plan,地址是https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=。它适合需要稳定 API 通道和统一 Key 管理的场景。
接入文档在https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=,里面有各语言 SDK 的调用示例和错误码说明。API Keys 管理页面在https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=,可以在这里创建、删除、查看 Key 的使用情况。
最后说一个实际经验:Self-Improving 机制的效果取决于你给它的任务复杂度。如果每次都是简单的一次性任务,它不会创建 Skill,也不会写入 Memory。只有当你反复做同类复杂任务时,这套机制才会真正发挥作用。所以验证的时候,尽量构造需要多步操作、有踩坑过程的任务,这样更容易观察到自我改进的触发。