开源自托管 Agent 项目 Hermes Agent 装机实录与必选配置
本文基于 hermes-agent 仓库 commit 2d40494(2026-07-29)梳理,该项目仍在高频迭代,具体行为以仓库 https://github.com/NousResearch/hermes-agent 最新代码与文档为准。
NousResearch/hermes-agent 这个仓库里的 setup-hermes.sh 跑完之后,你手上其实只有三样东西:一个 Python 3.11 虚拟环境、一个指向 venv/bin/hermes 的软链接、以及一份被种进 ~/.hermes/skills/ 的技能副本——它一个字都没往配置文件里写。 也就是说,脚本退出时你仍处在零配置状态,模型服务商、命令在哪台机器上执行、消息网关要不要装,全都留给你在向导或 config.yaml 里做决定,而这三个决定决定了它到底是个能干活的常驻助手,还是一个开着终端权限的隐患。
先消歧:Hermes 这个名字在 Nous Research 那边同时对应一套开源模型系列,市面上还有若干同名商标和同名库。本文说的是 NousResearch/hermes-agent 这个仓库——一个 MIT 许可(LICENSE 署名 Nous Research)、用 Python 写的、可以常驻在自己机器上、能连聊天平台并自己往磁盘写记忆和技能的 Agent 项目。
站内已经有几篇相邻的文章:pi 的安装上手实录讲的是另一个项目的装机路径,ECC 的安装方式选择讲的是那套工具箱在不同 harness 上怎么落地,Claude Code 教程讲的是一个闭源工具的用法。本篇不重复这些,只回答一件事:hermes-agent 这个具体仓库,从 clone 到第一轮对话跑通,中间哪几步会真的把你卡住。
一、安装脚本逐段读:它动了你机器上的什么
setup-hermes.sh 的开头就 set -e,然后 cd 到脚本自己所在目录,并导出 UV_NO_CONFIG=1——注释里说明这是为了防止在 sudo -u <user> 下运行时,uv 跑去读错误用户家目录里的 uv.toml / pyproject.toml。接着它做的事按顺序是这样的:
平台分叉。 脚本用 is_termux() 判断环境:看 TERMUX_VERSION 是否非空,或者 PREFIX 里是否含 com.termux/files/usr。这一个判断决定了后面每一步走哪条路——桌面/服务器用 uv,Termux 用 Python 自带的 venv 加 pip。
准备 uv 与 Python。 非 Termux 路径上,它依次找 PATH 里的 uv、$HOME/.local/bin/uv、$HOME/.cargo/bin/uv;找不到就装。装法值得一提:它没有直接 curl | sh,而是拆成两段——先把安装脚本下载到 mktemp 出来的临时文件,再用 sh 执行,全过程日志写另一个临时文件。注释说明了原因:管道方式下 sh 对空 stdin 会以 0 退出,curl 的失败会被吞掉,用户只能看到一句”装不上”却拿不到任何诊断。Python 这边用 uv python find 3.11,找不到就 uv python install 3.11。Termux 路径则要求现成的 python 至少是 3.11,否则直接退出并提示 pkg install python。
建虚拟环境。 这一步有个容易吃亏的细节:如果当前目录已经有 venv,脚本会 rm -rf venv 之后重建,而不是复用。
装依赖。 非 Termux 路径优先走 uv sync --extra all --locked,也就是照 uv.lock 做哈希校验安装;脚本自己会提示”首次在干净 venv 上可能要几分钟”。注释里专门解释了为什么用 --extra all 而不是 --all-extras:后者会把 pyproject.toml 里每一个可选依赖组都装上,绕过精选过的 [all],把 [matrix](python-olm 在 Windows 上需要 make)和 [rl](含 git+https 依赖,离线必挂)也拖进来。一旦 lockfile 同步失败,它降级到 PyPI 重解析:先试 .[all],再试一个由 _ALL_EXTRAS 减去 _BROKEN_EXTRAS 拼出来的安全集合(_ALL_EXTRAS 里列着 modal、daytona、vercel、messaging、matrix、cron、cli、dev、slack、mcp、honcho、voice、bedrock、web、youtube 等一长串),最后兜底裸装 .。注释写明 _BROKEN_EXTRAS 这个数组的用意:上游某个包被隔离时,把它单独摘出去,而不是让整套安装静默退化成”只有内核”。Termux 路径则用 constraints-termux.txt 约束 .[termux],失败再退到基础安装。
收尾三件事。 一是问你要不要装 ripgrep(不装就退回 grep 做文件搜索);二是若仓库根目录没有 .env,从 .env.example 复制一份并 chmod 600;三是把 venv/bin/hermes 软链到 ~/.local/bin/hermes(Termux 上是 $PREFIX/bin),并按你的 $SHELL 往 .zshrc / .bashrc / .bash_profile 里追加一行 PATH 导出。
种技能。 最后它调 tools/skills_sync.py,把仓库 skills/ 下的内容同步进 ${HERMES_HOME:-$HOME/.hermes}/skills;脚本失败时兜底用 cp -rn 拷一遍。然后打印 hermes setup、hermes status、hermes doctor、hermes cron list、hermes gateway install 这几条后续命令,并问你要不要现在就用 venv/bin/python -m hermes_cli.main setup 跑向导。
看完这一段你应该已经有判断了:这个脚本负责”能跑”,不负责”配好”。它甚至没碰 config.yaml。
二、这套东西由哪几块组成,各自在仓库哪里
| 组成部分 | 它负责什么 | 对应仓库位置 | 你什么时候会碰到它 |
|---|---|---|---|
| 安装脚本 | 建 venv、装依赖、软链 hermes、种技能 | setup-hermes.sh | 第一次装,或每次想重装 |
| 入口点加固 | Windows 上改 stdio 编码、清掉当前目录对 sys.path 的污染 | hermes_bootstrap.py | Windows 乱码、莫名 ImportError |
| 出厂配置全表 | 每一个配置项不写时的默认值 | hermes_cli/config_defaults.py | 想知道”我没配的那项现在是多少” |
| 带注释的配置样例 | 可以逐段抄进 config.yaml 的注释版 | cli-config.yaml.example | 决定要改哪一项时 |
| 数据目录解析 | 定位 ~/.hermes(Windows 是 %LOCALAPPDATA%\hermes)与 profile | hermes_constants.py | 多 profile、做成服务、写定时任务 |
| 环境文件加载 | 决定两个 .env 谁覆盖谁 | hermes_cli/env_loader.py | 密钥”明明写了却不生效” |
| 安装向导 | 交互问模型、终端后端、网关、工具 | hermes_cli/setup.py | hermes setup |
| 自检 | 检查 Python、依赖、各家凭据环境变量 | hermes_cli/doctor.py | hermes doctor |
| 技能种子同步 | 按清单加哈希决定覆盖还是跳过 | tools/skills_sync.py | 升级后技能没更新,或自改被盖 |
| 技能素材 | 内置 14 个分类共 70 份 SKILL.md;可选 21 个分类共 111 份 | skills/、optional-skills/ | 想让它照既定流程干活 |
| 插件与外部工具 | 18 个顶层插件目录,另有 6 个可选 MCP 配置 | plugins/、optional-mcps/ | 接平台、接外部服务 |
顺带一句规模感:tests/ 下有 2499 个 test_ 开头的测试文件。这个数量本身说明它不是一个周末项目,也说明你面对的配置面很宽——下一节只挑必须先决定的。
表格最后两行值得单独提醒一句代价。optional-skills/ 里那 111 份 SKILL.md 和 optional-mcps/ 里那几份配置都不是装完就白得的能力:技能的本质是一段会被模型当成操作规程照着执行的文本,你把哪一份放进它能读到的目录,就等于默认认可了那份文本里写的每一步;optional-mcps/ 下挂的几个(blender、comfy-cloud、figma、linear、n8n、unreal-engine)全都是外部服务或外部进程,每接一个就多一份凭据要保管、多一条出站连接要允许、多一个第三方进程能被这个 Agent 驱动。所以这两行的正确读法不是”内置了多少”,而是”默认没开多少”——先跑通最小闭合的那一条链路,缺什么再一件件加,比一次全开好排查得多。
三、第一次启动前必须先定下来的几项
cli-config.yaml.example 开头就交代了规则:把里面的段落抄进 ~/.hermes/config.yaml,或者用 hermes config set <section.key> <value> 改当前 profile;只有文档里明确列出的那些密钥类环境变量,才会盖过配置文件里的对应项。真正拦路的是下面这几组。
第一组,模型与服务商。 model.default 是默认模型,model.provider 默认是 auto(从现有凭据自动判断),也可以显式写成 OpenRouter、Nous Portal(OAuth 或 API key 两条路)、Anthropic、Gemini、GitHub Copilot、LM Studio 等等,注释里逐条标注了各自需要哪个环境变量或哪条登录命令;本地服务统一走 custom 并配 base_url,ollama / vllm / llamacpp 都是它的别名。这一组里最容易配错的是两个看着像的键:context_length 是输入加输出的总窗口,注释明确建议留空让它自动探测,只在探测不准(比如本地服务改了上下文长度、或代理不暴露模型列表)时才手填;max_tokens 是单次输出上限,和历史能留多长毫无关系。至于各家服务商的计费、额度与限流规则,各家不同且会调整,以官方最新说明为准,配置里只该关心机制。
第二组,终端后端。 terminal.backend 默认 local,向导里可选的还有 Docker、Modal、SSH、Daytona、Vercel Sandbox,Linux 上多一个 Singularity/Apptainer。这一项等于在回答”它执行的命令落在谁的机器上”。同一段里的 terminal.cwd 有两套语义,注释写得很直白:CLI 用的是你敲 hermes 时所在的目录,网关、消息、定时任务用的才是 terminal.cwd。要留意向导的行为——选 local 时它会把 cwd 兜底设成你的家目录。另外两个默认值值得记住:docker_mount_cwd_to_workspace 默认关(把宿主目录塞进沙箱会削弱隔离,所以要显式开),sudo_password 一旦写就是明文,注释里挂着安全警告。
第三组,数据目录与 profile。 hermes_constants.py 里的解析顺序是:进程内的上下文覆盖 → HERMES_HOME 环境变量 → 平台默认路径。有一处设计取向很值得抄走:当 HERMES_HOME 没设、而默认目录下的 active_profile 文件指向一个非 default 的 profile 时,它会往 stderr 打一条一次性的醒目警告,说明”这个进程写出去的数据会落进错的 profile”,但不会抛异常——因为有几十处模块在导入期就调它,抛了会直接把进程弄死。代价就是:你写 systemd 单元或定时任务时如果忘了显式传 HERMES_HOME,只会得到一行 stderr,然后数据静静地写到别处。
第四组,密钥落在哪个 .env。 这是最反直觉的一项,下一节单独讲。
第五组,谁能拿到哪些工具。 platform_toolsets 按平台分配工具集,cli、telegram、discord、slack、signal 等各有默认预设,也可以自己组合 web / terminal / file / browser / skills / todo / cronjob 这些单元。注释里给的示例就包括”只给 Discord 只读工具”和”CLI 去掉浏览器”。这一项本质上是权限决策,不是偏好设置——一个能从群聊里被触发、又拿着 terminal 的配置,等于把 shell 挂在了公网侧。相关的取舍我在最小权限设计里写过通用做法。
第六组,跑多久、压多狠。 agent.max_turns 默认 500,注释建议聚焦任务用 20-30;compression.threshold 默认 0.50,protect_last_n 默认 20,protect_first_n 默认 3;tool_loop_guardrails 默认只发软警告不硬停(hard_stop_enabled 是 false),硬停是给自主/定时会话准备的开关。还有一个容易被忽略的:checkpoints.enabled 在出厂配置里是 False,也就是说 /rollback 想用得先自己打开。
四、常驻加自我改写,机制在哪、代价在哪
它跟”开一个终端问几句”的工具不一样的地方,在配置文件里能直接读出来。
记忆是两份有上限的文本:MEMORY.md 存它自己的环境事实与约定,USER.md 存你的偏好,字符上限分别是 2200 和 1375,写满了得由它自己整合或替换;nudge_interval 默认每 10 个用户回合提醒它考虑存一次,flush_min_turns 默认 6,意思是会话够长时退出/重置前给它一回合把该记的落盘。分层记忆的通用取舍见Agent 记忆分层。
技能是可以被它自己写出来的:skills.creation_nudge_interval 默认 15,每 15 轮工具调用提醒它把复杂任务沉淀成技能;新技能一律写进 ~/.hermes/skills/,external_dirs 里挂的共享目录是只读的,同名时本地优先。
真正决定”它会不会偷偷改自己”的是辅助模型那一节:auxiliary.background_review 是一次回合结束后的自我复盘分叉,注释写明它负责判断该不该存记忆、该不该改技能;auxiliary.curator 是技能使用情况的复盘分叉,超时给到 600 秒,因为在几百个候选技能上做审查确实慢。
常驻那一半靠 hermes gateway install(安装脚本末尾的提示行把它标注为”装网关服务:消息加定时任务”,Termux 上给出的是前台运行的 hermes gateway)和 hermes cron list。消息平台的会话默认不自动清:session_reset.mode 是 none,上下文一直活着直到你 /reset 或压缩触发;注释同时提醒长上下文会推高每条消息的成本。group_sessions_per_user 默认 true,同一个群里的人不共享上下文——这是安全默认,改成 false 就是让一屋子人共用一个”房间大脑”。
把这几段连起来看,设计取向很清楚:它假设你愿意让一个进程长期活着,并允许它在你不盯着的时候修改自己下次要读的素材。这是能力,也是这个项目最需要你先想清楚的代价。
五、边界与代价:这个设计放弃了什么
放弃了轻。 它不是一个下载即用的单文件。Python 3.11 的 venv、一长串可选依赖组、哈希校验的 lockfile、可选的 ripgrep,都是前置条件。hermes_bootstrap.py 的存在本身就是证据——为了让 Windows 上的入口点不崩,它得在导入期设 PYTHONUTF8 / PYTHONIOENCODING、重配 stdio、还要把 platform._syscmd_ver 换成不起子进程的版本。
放弃了默认隔离。 出厂后端是 local,也就是拿你当前用户的权限直接执行命令。容器化是选项而不是默认,而且即使选了 Docker,默认也不把你的启动目录挂进去。想要更强的边界得自己搭,这块的一般思路见工作区隔离。
放弃了”什么都不落盘”。 配置样例里关于会话日志的那一节写得毫不含糊:轨迹一直写到 logs/ 下的 session_*.json,没有配置项可关,要关得改源码。加上记忆文件、技能文件、检查点目录,它在你磁盘上的足迹是持续增长的。
放弃了无损上下文。 压缩是有损的:中段回合被摘要成一条消息插回去,只有系统提示、头部若干条和最近一段尾巴被保护。长会话里你不能假设它还”记得”原话。
它明确不管的事。 不管你在各家服务商那边的额度与合规——那是你和服务商之间的事;不管命令安全审计,命令层扫描是接外部工具 tirith 的可选项,而且 tirith_fail_open 的默认取向是扫不动就放行;也不替你保证 Windows 与 POSIX 完全等价——hermes_bootstrap.py 的文档里自己承认,它只修 stdio 和子进程环境,当前进程里的 open() 仍然按本地编码走,需要调用点显式写 encoding。
六、上手避坑清单
1. 装完敲 hermes 提示找不到命令。 为什么会踩:软链接落在 ~/.local/bin,脚本只是往 rc 文件追加了一行 PATH 导出,当前这个 shell 并没有重载。怎么避:照脚本末尾提示 source 对应的 rc 文件,或者先用 venv/bin/hermes 直接验证一次装没装成——把”没装上”和”没进 PATH”分开。
2. 在没有 TTY 的环境跑向导,什么都没配上。 为什么会踩:向导检测到非交互(无 TTY 的 SSH、容器、CI)就打印一段指引后直接返回,不报错也不写配置,看起来像”跑完了”。怎么避:改用它给的三条命令定型——hermes config set model.provider、model.base_url、model.default,或者先把服务商密钥放进环境变量。
3. 密钥写了不生效。 为什么会踩:安装脚本在仓库根目录创建了一个 .env,而向导里显示的密钥文件路径是数据目录下的那个。加载顺序写在 env_loader.py 的文档里:~/.hermes/.env 存在时会覆盖已有的 shell 变量;仓库里那个 .env 只在用户级文件存在时充当”补空缺”的开发兜底,填不进已经有值的键。怎么避:统一往数据目录的 .env 写,把仓库根的那个当模板留着。
4. 重跑安装脚本,之前手装的包全没了。 为什么会踩:脚本对已存在的 venv 是删掉重建,不是增量修复。怎么避:把额外装过的包记成清单;遇到问题先跑 hermes doctor 定位,别把安装脚本当修复工具用。
5. 依赖装完像是少了东西,还不知道少在哪。 为什么会踩:lockfile 同步失败时它会降级到 PyPI 重解析,且只用一行黄色警告告诉你”transitive 依赖不再做哈希校验”;再失败还会往更小的集合退。整个过程退出码是 0。怎么避:装的时候盯住那几行输出而不是只看最后的对勾;在意供应链的场合,坚持让哈希校验那条路走通再用。
6. 网关起来了,但它在错的目录里干活。 为什么会踩:CLI 用你启动时的目录,网关和定时任务用的是配置里的 terminal.cwd,而向导在选 local 后端时会把它兜底设成家目录。怎么避:装网关之前先把 terminal.cwd 显式写成你真想让它动的那个目录。
7. 服务和定时任务把数据写进了错的 profile。 为什么会踩:子进程没继承 HERMES_HOME 时会回落到平台默认目录,而这件事只体现为一行 stderr 警告。怎么避:所有 systemd 单元、cron 环境、自己写的 spawner,都显式传 HERMES_HOME。
8. Windows 上莫名的 ImportError。 为什么会踩:这个仓库根目录放着名字很常见的顶层模块(hermes_bootstrap.py 的文档字符串点名了 utils 这类命名,仓库根上就摆着一个 utils.py),而 Python 总会把当前目录塞进 sys.path。入口点靠 hermes_bootstrap.harden_import_path 把相对形式的路径项摘掉、并把真正的源码根挪到最前面;但如果你从一个自带同名包的项目目录里以非入口点方式启动,就绕开了这层保护。怎么避:换个目录启动,或者确认走的是打过软链的那个入口。
9. 出了事想 /rollback 却发现没快照。 为什么会踩:文件检查点在出厂配置里是关的,注释里写明改成 opt-in 的理由是多数人从不用它。怎么避:需要就提前开,并同时看一眼保留数量与容量上限那几项。
10. 自己改过的内置技能,升级后要么没更新、要么被盖。 为什么会踩:同步脚本用一份清单加 MD5 做三态判断——你改过的就跳过,你删掉的不会被重新塞回来,仓库里删掉的从清单清理。这个逻辑本身是保守且合理的,但它意味着”你改过的那份永远停在你改的那一刻”。怎么避:自定义技能另起名字放进用户目录,别原地改内置那份。
收尾:三条自检与接着读哪个文件
跑通第一轮之后,用三个问题自检一遍,比看任何输出都实在。一,hermes doctor 有没有干净通过,Python、依赖、凭据三块分别在说什么。二,terminal.backend 和 terminal.cwd 现在的值,是不是你想让它执行命令的那台机器和那个目录——尤其在你已经装了网关之后。三,密钥到底生效的是哪个文件里那份,把用户级 .env 和仓库根 .env 里的同名键对一遍。
接着读什么:把 cli-config.yaml.example 从头到尾通读一遍,它是这个仓库里注释密度最高的文件,很多设计理由只写在注释里;查某项的出厂值就翻 hermes_cli/config_defaults.py;想弄清数据目录和 profile 的边界就读 hermes_constants.py。这三个文件读完,你对它在你机器上的行为就基本没有黑箱了。
本篇属于一个把开源常驻自托管 Agent 项目 Hermes Agent逐层拆开讲的系列,整体地图见 开源自托管 Agent 项目 Hermes Agent 是什么;沿着这条线往下,还可以看 开源自托管 Agent 项目 Hermes Agent 起不来或断流 和 开源自托管 Agent 项目 Hermes Agent 怎么换模型与换供应商。