跳到主要内容

数据截至 (上游 commit 4ac938ddecce)

Hermes Agent — 架构与原理(总览)

30 秒导读: Hermes Agent 是 Nous Research 开源的自托管 AI agent。它把"终端里的 AI 助手""挂在 Telegram/Discord 上的机器人""半夜自己跑任务的定时器"做成了同一个大脑的不同外壳;更特别的是,它每回完一次话,会在后台再 fork 一个自己去复盘刚才那轮,把值得记的写进记忆、把值得沉淀的写成技能——所以它是"越用越熟"的。这个分叉在下文统称复盘 fork

本页的位置: 六章正文与本页同目录,章末的"回总览"落在这里,所以本页是这套文档的导航中枢。上级的书架条目只负责把读者送到这儿,不承担章节导航。


1. 这是什么(零基础也能懂)

一句话定义。 Hermes Agent 是一个你自己部署、自己接模型的通用 AI agent:它能读写文件、开终端跑命令、上网、调 MCP 工具,并且能从任何你常用的聊天软件里被使唤。

给谁用、解决什么问题。 想象你想要一个"随身的技术助理":你在笔记本前用终端跟它结对写代码;出门在地铁上用 Telegram 让它去查个东西、改个配置;睡觉时它按点自己跑一遍晨间简报发给你。市面上多数 agent 只满足其中一条——要么绑死终端,要么是个云端聊天机器人。Hermes 的取舍是:agent 跑在你自己的机器上(一台 5 美元的 VPS 就够),外壳随便换(README.md:19-21)。

它能做什么。

能力白话
终端里对话全功能 TUI:多行编辑、斜杠命令补全、流式工具输出、随时打断改口
二十来个聊天平台Telegram / Discord / Slack / WhatsApp / Signal / 飞书 / 邮件…… 全由一个网关进程托管(plugins/platforms/ 下 20 个插件目录 + gateway/platforms/ 内置若干)
自我进化闭环复杂任务后自动写技能,用的过程中自动改技能,定期提醒自己存记忆,还能搜自己的历史会话
定时自动化内置 cron 调度器,用自然语言写任务,跑完投递到任意平台
到处能跑六种终端后端:本地、Docker、SSH、Singularity、Modal、Daytona
换模型不换代码Nous Portal / OpenRouter / OpenAI / 自建端点,hermes model 一句话切换

用起来什么样。 装完之后就三条命令起步(README.md:107-116):

hermes # 进终端界面,直接开聊
hermes model # 挑模型和 provider
hermes gateway # 起消息网关,然后从 Telegram 给机器人发消息

包安装时注册的可执行入口只有三个(pyproject.toml:303-306):

命令指向用途
hermeshermes_cli.main:main面向人的多命令 CLI,裸跑时进 TUI 对话
hermes-agentrun_agent:main直接跑 agent 本体,给脚本/容器用
hermes-acpacp_adapter.entry:mainACP 协议适配器,让编辑器把 Hermes 当后端

注意 hermes gateway 不是第四个可执行文件,它是 hermes 底下的子命令。

一句话直觉。 把它想成一台装了大脑的服务器:AIAgent 是大脑(只有一个类),CLI、网关、cron、编辑器适配器都只是插在大脑上的输入输出口;而"技能库 + 记忆文件 + 会话数据库"是它的长期记忆盘,每轮对话结束后由复盘 fork 负责往里写。


2. 顶层全景(它大概怎么转)

2.1 一张图看清分层

怎么读这张图:左边五个是"从哪进来",中间是唯一的大脑,右下是"活干在哪",最下面是"学到的东西存哪"

┌── 终端 TUI ───┐
├── 聊天网关 ───┤
├── 定时任务 ───┼──► AIAgent ──► run_conversation(一轮)
├── 编辑器 ACP ─┤ 一个大脑 循环:问模型 ⇄ 跑工具
└── MCP 服务 ───┘ │ │
(入口层) │ ▼
│ 执行环境:本地 / 容器 / 远端
│ (命令真正落地的地方)

回完话之后,复盘 fork 再跑一遍本轮


回写层:技能库 · 记忆文件 · 会话数据库

"一个大脑"不是修辞,是可以 grep 验证的事实:所有外壳都在构造同一个 AIAgent 类——网关 gateway/run.py:22506:16405、定时任务 cron/scheduler.py:6010、编辑器 acp_adapter/session.py:687、TUI tui_gateway/server.py:7177、一次性脚本 hermes_cli/oneshot.py:476

关键是那条竖着往下的路径:用户拿到回复之后,主循环并没有结束——它会另起一条守护线程,让 agent 复盘刚才那一轮,决定要不要写记忆、要不要沉淀技能。这条路径是 Hermes 区别于同类 agent 的地方,细节见 自我进化闭环

2.2 部件一句话职责

部件干什么文件与符号
CLI 门面解析 hermes xxx 子命令;裸跑时转交给对话主程序hermes_cli/main.py:12418 main(:2379 from cli import main as cli_main)
终端对话程序TUI 交互、斜杠命令、把用户输入喂给 agentcli.py:15186 main
消息网关一个进程同时托管全部聊天平台适配器,收消息→喂 agent→回消息gateway/run.py:6726 GatewayRunner:18801 main
平台适配器每个聊天平台一份收发实现plugins/platforms/(telegram、discord、slack…)、gateway/platforms/
定时调度到点取出任务、拼提示词、跑一轮、把结果投递到指定平台cron/scheduler.py:7199 tick:1969 run_job
编辑器适配把 Hermes 包成 ACP 服务,供编辑器调用acp_adapter/entry.py:220 main
MCP 服务端反过来把 Hermes 自己暴露成 MCP servermcp_serve.py:623 create_mcp_server
AIAgent大脑本体:持有模型配置、工具集、会话状态、记忆句柄run_agent.py:412 class AIAgent
一轮对话循环"问模型 → 有工具就跑 → 结果塞回去 → 再问",直到模型不再要工具agent/conversation_loop.py:1766 run_conversation
模型接入各家 API 的方言适配(Anthropic / chat-completions / Bedrock / Codex…)agent/transports/
工具分发按名字找到工具函数并调用,过一遍护栏与审批model_tools.py:1192 handle_function_call
工具执行并发或串行地执行本轮的一批 tool_callsagent/tool_executor.py:1070 execute_tool_calls_concurrent
执行环境命令实际落到哪:本机、Docker、SSH、Modal…… 统一抽象tools/environments/base.py:597 BaseEnvironment
系统提示装配把身份/指南/技能/记忆拼成一整条系统提示,并整段缓存agent/system_prompt.py:903 build_system_prompt
收尾器一轮结束时同步记忆、判断要不要触发复盘agent/turn_finalizer.py:798(调用 _spawn_background_review)
复盘 forkfork 一个 agent 重放本轮,只许它动记忆和技能工具agent/background_review.py:1478 spawn_background_review_thread
会话存储所有会话消息落 SQLite,支持全文检索与跨会话回忆hermes_state.py:3258 class SessionDB

2.3 主线走一遍(高层,不进代码)

跟着一条真实消息走一遍。假设你在 Telegram 里发了句"帮我看看这个仓库的测试为什么挂了"。

① 进门 Telegram 适配器收到消息 → 网关按聊天窗口找到/新建会话
gateway/run.py:16949 调 agent.run_conversation
② 拼提示 系统提示整段从缓存取(不重拼,保住 prefix cache)
agent/system_prompt.py:470
③ 一轮开始 进入 while 循环:调模型 → 拿回复
agent/conversation_loop.py:1922
④ 分岔 回复里有 tool_calls?
├─ 没有 → 这就是最终答复,跳到 ⑥
└─ 有 → 执行工具,把结果作为新消息追加,回到 ③
agent/conversation_loop.py:7237
⑤ 工具落地 比如 terminal("pytest") → 交给当前执行环境去跑
tools/environments/base.py:290
⑥ 回话 文本经网关发回 Telegram;消息写进 SessionDB
⑦ 复盘 收尾器判断该不该复盘;该的话拉起复盘 fork,
让它问自己"这轮有什么值得记/值得沉淀的?"
agent/turn_finalizer.py:455 → background_review.py:839

从终端进来的路径只有第 ① 步和第 ⑥ 步不同——cli.py 直接调同一个 run_conversation;cron 也一样,由 cron/scheduler.py:6109 调进来。这就是"一个大脑、多种外壳"的实际含义。

顺带一个容易忽略的事实:cron 的心跳跑在网关进程里(gateway/run.py:30153 _start_cron_ticker,默认 60 秒一跳),所以想让定时任务自动跑,得让网关活着。


3. 阅读地图

六章按"由浅入深"排。建议顺序:先 01 建立"一轮怎么跑"的骨架,再 02 看它怎么省 token,03 才是这个项目最有辨识度的部分。

顺序章节这章讲什么
1一轮对话是怎么跑完的一轮对话的完整生命周期:主循环、迭代预算、模型 transports、流式与中断、收尾
2上下文工程系统提示的三层结构、缓存不变量、上下文压缩、工具结果截断
3自我进化闭环复盘 fork、技能的产生与自改、记忆策展、会话全文检索
4工具层与执行环境工具注册/分发/并发执行,以及六种终端后端的统一抽象
5到处都能找到它网关多平台托管、SessionDB、cron 调度与投递路由
6信任边界危险命令审批、提示注入防线、凭据与沙箱边界

"我只想看 X" 的跳转表:

我想知道……跳这章
模型返回 tool_calls 之后到底发生了什么01
它凭什么能少烧 token / prompt cache 怎么保住的02
"自我进化"是营销词还是真机制03
我想加一个自己的工具 / 换成 Docker 里跑命令04
我想把它接到某个聊天平台 / 让它定时干活05
它会不会被一封邮件里的指令劫持06

4. 巧妙之处速览

这不是完整清单——每章末尾都有自己那一节。这里从六章里各挑一条最能说明设计品味的,一句话点出妙在哪,展开看对应章节。

  • 复盘 fork 复用父进程的 prompt cache。 复盘 fork 默认就用主模型 + 父进程的活运行时,把整段对话重放一遍——因为这段前缀在上游缓存里还是热的,重放几乎只花 cache-read 的钱;只有当用户特意把复盘路由到另一个模型时(缓存键必然 miss)才改成重放一份压缩摘要(agent/background_review.py:187-197 的策略注释、:45 _resolve_review_runtime)。连 tools[] 都要跟父进程逐字节一致,因为它进缓存键(agent/background_review.py:1128-1129)。→ 03

  • 记忆快照在会话内冻结。 系统提示每个会话只拼一次并整段缓存,只有上下文压缩才会重建;记忆块虽然属于"volatile"层,但既然整条提示不重拼,会话中途写的记忆就不会立刻改变提示,prefix cache 因此不被打断(agent/system_prompt.py:903 build_system_prompt 的文档串、:496 invalidate_system_prompt 在压缩后才重载磁盘记忆)。同源的小心思:时间戳只精确到(另附当天不变的时区/UTC 偏移),让压缩边界、网关新 agent、会话恢复这几条重建路径仍能共享同一段缓存前缀(agent/system_prompt.py:847-863)。→ 02

  • tool_search 的渐进式工具披露。 MCP 和非核心插件工具不再全量塞进 tools 数组,而是换成 tool_search / tool_describe / tool_call 三个桥接工具按需捞取;核心工具永不延迟,且有个阈值闸门——可延迟工具占不到上下文窗口 10%(默认 threshold_pct)时整套机制直接不启用(tools/tool_search.py:1-41 设计约束、:234 should_activate:529 assemble_tool_defs)。→ 04

  • execute_code 让中间结果永不进上下文。 模型写一段 Python,脚本在子进程里跑,脚本里调 Hermes 工具是通过 RPC 回打到父进程(本地走 Unix domain socket,远端后端走文件轮询);只有脚本的 stdout 回给模型,十几次工具往返的中间结果一个字都不占上下文(tools/code_execution_tool.py:1-27 架构说明、:1076 execute_code)。→ 04

  • shadow-git 快照对模型完全不可见。 每轮第一次要改文件前,自动把工作目录快照进 ~/.hermes/checkpoints/ 下的共享 shadow git 仓库,靠 GIT_DIR + GIT_WORK_TREE + GIT_INDEX_FILE 三件套做到"一点 git 状态都不漏进用户项目";而且它明确不是一个工具——模型从头到尾不知道有这回事(tools/checkpoint_manager.py:1-11:38-39:608 class CheckpointManager)。共享单仓库还让多个 worktree 的相同 blob 被 git 自动去重。→ 05


5. 顶层代码地图

按"你想改什么"排,给人和 agent 当跳转表。这里只给符号不给行号:符号名比行号抗漂移,定位时优先 grep 符号;要精确到行的引用在各章正文里。

主题文件符号
可执行入口注册pyproject.toml[project.scripts](303-306)
CLI 子命令分发hermes_cli/main.pymain
终端对话主程序cli.pymain
agent 本体run_agent.pyAIAgentmain
一轮对话循环agent/conversation_loop.pyrun_conversation
系统提示装配agent/system_prompt.pybuild_system_prompt_partsbuild_system_promptinvalidate_system_prompt
模型方言适配agent/transports/anthropic.pychat_completions.pybedrock.pycodex.py
工具定义与分发model_tools.pyget_tool_definitionshandle_function_call
工具执行agent/tool_executor.pyexecute_tool_calls_concurrentexecute_tool_calls_sequential
渐进式工具披露tools/tool_search.pyshould_activateassemble_tool_defs
代码执行沙箱tools/code_execution_tool.pyexecute_codegenerate_hermes_tools_module
执行环境抽象tools/environments/base.pyBaseEnvironment
六种后端实现tools/environments/LocalEnvironmentDockerEnvironmentSSHEnvironmentSingularityEnvironmentModalEnvironmentDaytonaEnvironment
一轮收尾agent/turn_finalizer.pyfinalize_turn_spawn_background_review 调用点
复盘 forkagent/background_review.pyspawn_background_review_thread_run_review_in_thread_resolve_review_runtime
技能策展agent/curator.pyrun_curator_reviewapply_automatic_transitions
记忆存储tools/memory_tool.pyMemoryStorememory_tool
技能读写tools/skills_tool.pytools/skill_manager_tool.pyskills_listskill_viewskill_manage
跨会话检索tools/session_search_tool.pysession_search
会话数据库hermes_state.pySessionDB
文件快照/回滚tools/checkpoint_manager.pyCheckpointManagerprune_checkpoints
消息网关gateway/run.pyGatewayRunnermain_start_cron_ticker
聊天平台适配plugins/platforms/gateway/platforms/每个平台一个目录/模块
定时任务cron/scheduler.pytickrun_job_build_job_prompt
ACP 适配器acp_adapter/entry.pymain
MCP 服务端mcp_serve.pycreate_mcp_server

引用说明: 本文所有 path:line 相对克隆根 aiRef/repos/hermes-agent/,as-of sourceCommit: 9098f6777b93b7881216a9b7d8fb402899f62400

从这里开始读: 01 一轮对话是怎么跑完的