1. 为什么单 Agent 的学习循环值得单独拆开看
Hermes Agent 是 Nous Research 开源的一个单 Agent 运行时,它和 OpenClaw 那类多 Agent 编排框架走的是两条路。OpenClaw 把能力分散到多个 Agent,靠中央枢纽路由;Hermes 只维护一个 Agent,但让它跑得越久越顺手。这个“越用越强”不是靠改配置,而是靠一套自动运行的学习循环,把做过的事沉淀成可复用的技能,再配合四层内存系统把上下文成本压住。
如果你正在自建 AI Agent,大概率遇到过两个尴尬:一是会话之间上下文留不住,每次都要重新交代背景;二是把什么都塞进内存,Token 账单一路涨。Hermes 的设计恰好卡在这两个痛点中间——它把“发生了什么”和“什么方法有效”分开存,前者进 SQLite 会话存档,后者进技能文件,读取时机也完全不同。
这篇不打算复述官方文档,而是从网关协同的角度,把学习循环和内存系统怎么串起来讲清楚。我会给出可复制的网关路由配置片段、内存读写验证步骤,以及怎么用统一 Key/API 通道完成一次端到端调用验证。适合已经写过基础 Agent 循环、想进一步理解持久化与检索设计的开发者。读完之后,你应该能自己搭一个最小可用的“学习 + 内存 + 网关”骨架,而不是只停留在概念层。
2. TaoToken 统一通道的前置准备
在拆网关之前,先把模型调用这条链路理顺。Hermes 的 Agent 循环里,每一轮都会向推理端点发请求,如果端点不稳定或者 Key 管理混乱,学习循环和内存写入都会受影响。我自己的做法是把模型访问收敛到一个统一通道,这样切换模型、排查 401 都只在一个地方改。
TaoToken 在这里扮演的就是统一 Key/API 通道的角色。它的 API 地址是https://taotoken.net/api,兼容 OpenAI 风格的调用方式,所以 Hermes 里配置自定义 OpenAI 兼容端点时可以直接指向它。官网入口在https://taotoken.net/?utm_source=taotoken_aicg_blog_end,注册后到控制台生成 Key 即可。
具体操作上,你需要先拿到三样东西:Base URL、API Key、Model ID。这三件套在后面的网关配置和验证步骤里都会反复出现,建议先记下来。Base URL 用https://taotoken.net/api,Key 在控制台的 API Keys 页面创建,Model ID 则根据你实际要调的模型填,比如对话类或编码类模型各有对应的标识。
这里有个容易踩的坑:很多人把 Base URL 写成带/v1的完整路径,结果请求拼出来变成/v1/v1/chat/completions。TaoToken 的 API 根地址就是https://taotoken.net/api,客户端库通常会自动补/v1,所以配置时不要再手动加。如果你用的是 Hermes 的hermes model向导,选择“自定义 OpenAI 兼容端点”后,基础 URL 填上面这个,模型 ID 填你选定的模型标识。
另外,Key 不要硬编码进代码或提交到仓库。Hermes 支持从环境变量读取,比如export TAOTOKEN_API_KEY=your_key_here,然后在配置里引用这个变量名。这样网关服务重启、多平台适配器共享时,Key 只维护一份。如果你后面要跑定时自动化,环境变量方式也比写死在 cron 脚本里安全得多。
前置准备做完,你手上应该有一个可用的 Key、一个 Base URL、一个 Model ID。接下来进入网关配置,把学习循环和内存系统的读写路径接上。
3. 可复制的网关路由与内存配置片段
Hermes 的网关是一个持久后台服务,用hermes gateway启动,负责消息传递、会话路由、交付、配对和定时触发。它把 CLI、Telegram、Discord、Slack 等平台的适配器汇入共享的会话路由层,会话绑定的是 ID 而不是平台,所以在 Telegram 上开始的对话可以在终端里继续。
要让学习循环和内存系统经由网关协同,核心是两处配置:一是推理端点,二是内存与技能的存储路径。下面这份config.yaml片段可以直接改改就用,路径和字段名保持和 Hermes 一致。
# ~/.hermes/config.yaml inference: providers: - name: taotoken base_url: "https://taotoken.net/api" api_key_env: "TAOTOKEN_API_KEY" model: "your-model-id" timeout: 60 - name: fallback-local base_url: "http://127.0.0.1:11434/v1" model: "local-model" # 有序列表,主端点失败时自动切换下一个 order: ["taotoken", "fallback-local"] memory: prompt_files: - "~/.hermes/memories/MEMORY.md" - "~/.hermes/memories/USER.md" prompt_char_limit: 3575 session_db: "~/.hermes/state/sessions.db" fts5: true skills: root: "~/.hermes/skills" progressive_disclosure: true gateway: session_routing: "id" adapters: - cli - telegram cron_dir: "~/.hermes/cron"这份配置里有几个点值得展开。inference.providers是一个有序列表,Hermes 在会话中途如果主端点不可用,会自动切到下一个,回合生命周期不中断,上下文也保持完整。这就是为什么把 TaoToken 放在第一位、本地端点作为兜底比较稳。
memory.prompt_char_limit设成 3575,这是 Hermes 对MEMORY.md和USER.md两个文件总字符数的硬限制。它故意设得紧,是为了强制精选而不是无脑堆积。Agent 通过内存工具做三种操作:添加、替换、删除。注意一个细节——会话期间对这两个文件的编辑只在下一个会话生效,不会在对话中间立刻反映。
skills.progressive_disclosure打开后,系统提示里默认只放技能名称和简短摘要,完整内容按需加载。这意味着你有 200 个技能和 40 个技能,上下文成本差不多,因为详细内容只在真正需要时才进上下文。
如果你用 Claude Code 或 Cline 这类工具做辅助开发,配置逻辑是相通的:Base URL 填https://taotoken.net/api,Key 用环境变量,Model ID 填你选的模型。三件套缺一不可,尤其是 Model ID 写错会直接报模型不存在。
配置写完后,用hermes gateway启动服务。它会作为系统服务在后台跑,关掉终端也不影响。启动日志里应该能看到适配器加载和会话路由初始化的信息。如果日志里出现local proxy failed或连接超时,先检查 Base URL 和网络,再确认 Key 是否有效。
4. 验证请求与内存读写结果
配置就位后,别急着跑复杂任务,先用一次最小请求验证整条链路。这一步的目的是确认:网关能路由、推理端点能返回、内存文件能写入、会话能持久化。
第一步,验证推理端点。用 curl 直接打 TaoToken 的 API,确认 Key 和 Model ID 没问题:
curl -s https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "your-model-id", "messages": [{"role": "user", "content": "reply with ok"}], "max_tokens": 16 }'返回里如果能看到choices数组和内容,说明端点通了。如果返回 401,检查 Key;如果返回模型不存在,检查 Model ID。
第二步,通过网关发一条消息,触发 Agent 循环。在 CLI 适配器里输入一句简单指令,比如让它执行一个终端命令。观察日志里是否出现任务 ID 生成、系统提示构建、API 调用、工具执行、结果追加这几个阶段。这就是run_agent.py里的回合生命周期。
第三步,验证内存写入。Hermes 的“定期提醒”机制会在会话期间的设定间隔触发,Agent 收到内部系统级提示,回顾最近活动,判断是否有内容值得持久化。你可以手动触发一次内存操作,或者等提醒触发后检查文件:
cat ~/.hermes/memories/MEMORY.md cat ~/.hermes/memories/USER.md如果 Agent 判断某条信息重要,它会通过内存工具写入这两个文件之一。注意字符数限制,超了会被拒绝或需要替换。
第四步,验证会话搜索。每个会话写入 SQLite 并用 FTS5 索引,检索结果在注入前会经过 LLM 摘要。你可以直接查数据库确认记录存在:
sqlite3 ~/.hermes/state/sessions.db \ "SELECT id, substr(content,1,80) FROM sessions ORDER BY rowid DESC LIMIT 5;"如果表名和字段名和你本地不一致,用.tables和.schema先看一眼。FTS5 的虚拟表通常叫sessions_fts之类,查询时用MATCH语法。
第五步,验证技能创建。当一次任务满足触发条件——五次或更多工具调用、从错误中恢复、用户纠正、或者奏效的非明显工作流——Agent 会在~/.hermes/skills/下创建技能文件。你可以检查目录:
find ~/.hermes/skills -name "SKILL.md" -newer ~/.hermes/config.yaml技能文件遵循 agentskills.io 开放标准,包含名称、描述、步骤和工具引用。Agent 默认用patch而不是edit来更新技能,只传旧字符串和替换字符串,改动小、省 Token,也不容易破坏已经能跑的内容。
走完这五步,你就有了一条可验证的端到端链路:网关路由 → 推理调用 → 内存写入 → 会话持久化 → 技能沉淀。任何一环出问题,都能单独定位。
5. 常见报错与排查对照
实际跑起来,报错基本集中在几个地方。下面按真实错误信息对照排查,每条都给出定位思路。
401 Unauthorized。最常见的原因是 Key 没读到或写错了。先确认环境变量在当前 shell 里生效:echo $TAOTOKEN_API_KEY。如果为空,说明 export 没执行或者写在了别的配置文件里。Hermes 作为后台服务运行时,环境变量需要在服务启动的环境里可见,systemd 用户服务的话要写进 unit 文件的Environment=或EnvironmentFile=。另外检查 Key 有没有多余空格或换行。
local proxy failed。这个通常出现在配置了本地代理或本地端点兜底时。如果你在inference.providers里放了本地端点,但本地服务没启动,切换过去就会失败。排查顺序:先确认主端点是否真的不可用,再确认本地端点进程是否在跑。如果不需要兜底,把order里多余的项去掉,避免误切。
reading choices 相关报错。这类错误一般是响应结构不符合预期,常见于 Base URL 拼错导致返回了 HTML 错误页而不是 JSON。检查 Base URL 是不是https://taotoken.net/api,有没有多写/v1。另外确认请求头Content-Type: application/json带上了。
OAuth 相关报错。如果你用的是需要 OAuth 的客户端或工具,报错通常指向 token 过期或 scope 不足。这类问题不在推理端点本身,而在客户端鉴权层。检查 token 刷新逻辑,或者改用 API Key 方式调用。
模型不存在 / model not found。Model ID 写错,或者该模型在你的账号下没有权限。到控制台确认可用模型列表,复制准确的标识。注意大小写和连字符,很多模型 ID 对大小写敏感。
内存写入被拒绝。MEMORY.md和USER.md总字符数超过 3575 时会写不进去。这时候需要先替换或删除旧内容,而不是继续追加。这也是设计意图——逼你精选。
技能加载不出来。检查~/.hermes/skills/下的目录结构,SKILL.md是必需的,文件名大小写要对。如果开了渐进式披露,系统提示里只有摘要,完整内容要等 Agent 判断相关才加载,所以“看不到全文”不一定是 bug。
会话搜索返回空。确认 FTS5 索引是否建立,SQLite 文件路径是否和配置一致。如果刚写入还没索引,可能需要等一轮或手动触发索引重建。
排查时养成看日志的习惯。网关和 Agent 循环的关键阶段都会打日志,任务 ID、API 调用、工具执行、内存操作这些都能对上。把日志和上面的错误对照表结合,大部分问题十分钟内能定位。
6. 把学习循环接进你的 Agent 工作流
回到最开始的问题:怎么让一个 Agent 越用越顺手,同时不让 Token 成本失控。Hermes 给出的答案是分层——提示内存常开但严格限长,会话搜索按需检索,技能走渐进式披露,用户建模可选。学习循环则负责把完成的工作流转成可复用技能,用patch做增量更新。
如果你要自己实现类似机制,可以从最小闭环开始:一个 SQLite 存档加 FTS5 索引做情景记忆,一个技能目录做程序性记忆,一个定期提醒触发内存精选。推理端点用统一通道收敛,Base URL 指向https://taotoken.net/api,Key 走环境变量,Model ID 按需切换。这样切换模型或排查鉴权问题时,改动面很小。
验证模型行为时,可以到模型对话页面直接试;长期跑编码或 Agent 任务,Coding Plan 更合适;接入和排障过程中需要的 Key 与文档,分别在 API Keys 和接入文档里。把这几条链路分开管理,比把所有东西塞进一个脚本里稳得多。
最后留一个实用技巧:每次改完config.yaml,先跑一遍第 4 节的最小验证请求,确认推理端点通、内存文件可写、会话能持久化,再去跑复杂任务。这样出问题时,你能确定是配置改动引入的,而不是任务本身的复杂度。