Hermes架构拆解之Profiles
本文对Hermes的架构拆解是基于 v2026.7.30 版本的源码解析
在同一台服务器上,我们可能希望同时拥有几个用途不同的 Hermes 助手:一个负责写代码,一个负责查资料,还有一个处理日常事务。它们使用同一套 Hermes 程序,却需要不同的模型配置、身份设定和长期记忆。
如果这些助手都读写同一份配置和数据,问题很快就会出现:开发助手刚切换了模型,研究助手也跟着改变;本来只想让办公助手记住的信息,却进入了共同使用的记忆文件。
重新安装几份 Hermes 当然可以分开管理,但程序升级、配置维护也会跟着重复。Profiles 提供了另一种组织方式:共享程序,为不同助手分别保存配置和运行状态。
理解这一机制,可以沿着一条线展开:Profile 定义“这是谁的数据”,HERMES_HOME 告诉程序“去哪里找数据”,Gateway 再决定“这条消息由谁处理”。
一、为什么需要 Profiles:从三个助手说起
先把需求具体化。假设我们要运行三个助手:
| 助手 | 主要用途 | 希望独立维护的内容 |
|---|---|---|
| Coder | 编写代码、分析项目 | 开发模型、代码相关技能、项目记忆 |
| Researcher | 阅读资料、整理研究结果 | 研究模型、检索技能、研究偏好 |
| Assistant | 处理日常事务 | 办公技能、个人偏好、定时任务 |
它们需要分开的内容,可以分成两类。第一类是配置:用什么模型、保存哪些密钥、采用什么身份设定。第二类是状态:积累了哪些记忆、发生过哪些对话、保存了哪些定时任务。
这里的 Memory 是助手长期保留的信息,例如用户偏好和项目约定;Session 则是一段对话及其历史。两者都会影响助手的后续行为,但生命周期和用途不同。
有了 Profiles,三个助手之间的关系可以画成这样:
图中的“同一套程序”表示共享安装和源码。至于运行时使用几个进程,则取决于启动方式。三个 Profile 可以分别启动,也可以由后面介绍的同一个 Gateway 管理。
程序升级时,共享的仍然是 Hermes 这套程序;助手之间的差异主要落在各自的数据目录中。这种安排让“维护软件版本”和“管理助手身份”成为两件可以分别处理的事。
二、Profile 到底是什么:一套独立的配置与状态目录
一个 Profile,可以理解为一套独立的 Hermes Home。 它为助手保存配置与持久状态,运行中的 Hermes 再读取、使用这些内容。
在默认的 Linux / WSL 目录布局下,结构大致如下。这里展示的是使用一段时间后的示意结构,部分状态文件会在相应功能运行后才出现。
~/.hermes/
├── config.yaml # 默认 Profile 的配置
├── .env # 默认 Profile 保存的密钥与环境设置
├── SOUL.md # 默认 Profile 的身份设定
├── state.db # 默认 Profile 的会话数据库
├── memories/
├── skills/
├── cron/
├── active_profile # CLI 保存的当前默认选择
└── profiles/
├── coder/
│ ├── config.yaml
│ ├── .env
│ ├── SOUL.md
│ ├── state.db
│ ├── memories/
│ ├── skills/
│ ├── sessions/
│ ├── cron/
│ └── logs/
├── researcher/
│ └── ...
└── assistant/
└── ...
这里容易看错的一点是:默认 Profile 就在 ~/.hermes,命名 Profile 才放在 ~/.hermes/profiles/ 下。 不需要额外寻找一个 profiles/default 目录。
Windows 原生环境的默认目录为 %LOCALAPPDATA%\hermes;在 WSL 中运行时,通常采用 Linux 的目录布局。后文统一使用 ~/.hermes 说明默认场景。
这些文件各有职责:config.yaml 保存运行配置,.env 保存该 Profile 的密钥和环境设置,SOUL.md 描述助手身份,memories/ 保存长期记忆,state.db 等文件承载会话状态。Skills 是可供助手使用的技能,Cron 则对应定时任务。
因此,仅仅修改 SOUL.md,只是改变身份设定;使用独立 Profile,还会把配置、记忆、会话等数据分开。反过来说,创建了 Profile 也不代表已经启动一个助手:目录可以一直存在,处理任务的进程可以随时启动和退出。
三、HERMES_HOME:程序如何找到当前 Profile
3.1 从目录切换理解运行机制
Hermes 需要知道这次启动该使用哪一套数据。HERMES_HOME 就是它定位运行数据的关键入口。
例如,当有效路径为下面这个目录时,程序就会围绕 Coder 的配置和状态工作:
HERMES_HOME=/home/dev/.hermes/profiles/coder
配置文件:/home/dev/.hermes/profiles/coder/config.yaml
密钥文件:/home/dev/.hermes/profiles/coder/.env
长期记忆:/home/dev/.hermes/profiles/coder/memories/
会话数据:/home/dev/.hermes/profiles/coder/state.db
上面展示的是有效路径与文件位置的关系。日常使用时,通过 hermes -p coder ... 选择 Profile 更直观,也能让 CLI 完成对应的解析。
关键就在于:共享的程序读取不同的根目录,自然就能得到不同的配置和历史状态。代码本身不需要为了 Coder、Researcher 各复制一份。
3.2 为什么选择 Profile 必须发生在启动早期
以启动 Coder 对话为例:
hermes -p coder chat
按本文依据的版本资料,CLI 会在启动早期完成 Profile 选择,其核心流程如下:
这里真正值得关注的是顺序:先确定 Profile,再初始化依赖它的模块。
Python 模块第一次被导入时,可能就会执行顶层代码。假设某个模块在此时根据 Hermes Home 计算了数据库路径,并把结果保存下来,之后再修改环境变量,就不一定能让这份已保存的路径跟着改变。
这也解释了为什么“程序启动后,再随手改一下 os.environ”不能替代完整的 Profile 切换流程。涉及配置和状态的组件,需要在正确的目录上下文中初始化和使用。
3.3 路径解析还有一层:当前执行上下文
在 hermes_constants.py 中,get_hermes_home() 的关键逻辑可以概括为:
# 简化示意:省略路径规范化和平台细节
def get_hermes_home():
override = get_hermes_home_override()
if override:
return Path(override)
return _hermes_home_from_env()
它体现了三层路径来源,优先级由高到低是:
- 当前执行上下文设置的 Home Override。
HERMES_HOME环境变量。- 操作系统对应的默认目录。
“执行上下文”可以先理解为当前正在处理的一段任务。普通 CLI 启动主要通过环境变量确定目录;一个进程同时服务多个 Profile 时,还需要让不同任务各自拿到正确的路径。
这时,Hermes 使用基于 Python ContextVar 的 set_hermes_home_override() 和 reset_hermes_home_override()。ContextVar 可以让不同异步执行上下文持有各自的值,避免为每条消息反复修改进程共享的环境变量。
不过,这不意味着所有 Python 全局变量都会自动隔离。相关组件仍然需要按当前上下文读取和使用 Profile 数据。后面的 Gateway 复用,就是这层机制的具体应用。
四、从创建到使用:走通一个 Profile
4.1 创建、配置,再开始对话
创建开发助手,可以从下面三步开始:
# 创建 Profile
hermes profile create coder
# 为这个 Profile 完成配置
hermes -p coder setup
# 使用这个 Profile 开始对话
hermes -p coder chat
创建命令会初始化 Profile 所需的目录、密钥文件、身份设定和内置技能等内容。模型、凭据等具体配置再通过 setup 完成。创建 Researcher 时,同样使用它自己的名字执行这一组操作即可。
对应的源码入口从 hermes_cli/subcommands/profile.py 进入 hermes_cli/profiles.py,核心创建逻辑由 create_profile() 承担。阅读代码时,可以把它看成“准备数据目录”的阶段,与后续启动对话的阶段分开理解。
4.2 快捷命令与默认选择,各解决什么问题
创建 Profile 时,Hermes 还会生成快捷启动命令。在 Linux 下,通常可以在 ~/.local/bin/ 找到对应脚本。只要这个目录已加入 PATH,就可以这样使用:
coder setup
coder chat
它的转发关系相当于下面的脚本,实际生成内容可能包含更多启动细节:
#!/bin/sh
exec hermes -p coder "$@"
所以,coder chat 仍然调用共享的 Hermes 程序,只是替你带上了 Profile 参数。
如果希望普通的 hermes 命令默认使用 Coder,可以设置当前选择:
hermes profile use coder
hermes chat
在未手动预设 HERMES_HOME 的常规 CLI 使用场景下,后续不显式指定 Profile 的调用就会采用这个选择。CLI 将名字保存到 ~/.hermes/active_profile,后续启动时再读取它。
两种用法适合不同场景:交互使用时,切换默认选择比较方便;脚本和服务配置中,显式写出 -p coder 更容易看出运行目标。profile use 改变的是后续启动的默认选择,并不会把已经运行的 Gateway 或对话进程迁移到另一个 Profile。
4.3 管理与克隆
常见管理命令可以集中记在一张表里:
| 命令 | 用途 |
|---|---|
hermes profile list | 列出 Profiles |
hermes profile show coder | 查看指定 Profile 的详情 |
hermes profile use coder | 设置后续 CLI 启动的默认选择 |
hermes profile rename coder dev | 重命名 Profile |
hermes profile export coder | 导出 Profile |
hermes profile import coder.tar.gz | 导入归档,文件名以实际导出结果为准 |
hermes profile delete coder | 删除指定的命名 Profile 及其数据 |
删除命名 Profile 时,还涉及停止其 Gateway、移除对应服务和快捷命令;默认 Profile 不能通过 profile delete 删除。由于 Profile 包含密钥和历史数据,导出、删除时应按实际需要确认处理对象。
如果已经把 Coder 配置得比较完善,希望再创建一个类似的 DevOps 助手,可以先选择 Coder,再进行克隆:
hermes profile use coder
hermes profile create devops --clone
三种创建方式的差别如下:
| 创建方式 | 主要行为 |
|---|---|
| 不带克隆参数 | 初始化基础 Profile 与内置技能,适合从头配置 |
--clone | 复制配置、密钥文件、身份设定、技能和选定的记忆文件 |
--clone-all | 复制更广泛的 Profile 数据,但仍排除会话历史数据库等运行历史 |
克隆更接近“以现有助手为模板”。新 Profile 后续如何配置、保留哪些记忆,仍然需要按新用途调整。
这里有一个与版本有关的细节:原始资料记录,v2026.7.30 的说明文档与实现对克隆记忆的描述存在差异。实现中的 _CLONE_SUBDIR_FILES 包含 memories/MEMORY.md 和 memories/USER.md,因此不能把 --clone 理解为一定获得空白记忆。
如果目标是一个没有旧记忆的新助手,直接创建不带克隆参数的 Profile,意图更清楚。--clone-all 同样不能理解成完整备份,尤其不能据此假设会话历史也已保存。
五、多个 Profile 如何接入 Gateway
chat 适合直接在终端里对话。要让助手长期接收 Telegram、Discord 等平台的消息,就需要 Gateway:一个负责连接消息平台、接收消息并调度处理的服务。
Profile 决定配置和数据归属,Gateway 决定消息如何进入处理流程。两者可以按不同方式组合。
5.1 两种运行方式
默认方式是为各个 Profile 分别运行 Gateway。例如,在两个 Profile 均已完成平台配置后:
hermes -p coder gateway start
hermes -p researcher gateway start
Coder 和 Researcher 各自使用对应的配置、会话数据和记忆,也可以分别配置自己的 Bot Token,即消息平台分配给机器人的访问凭据。
另一个选择是打开 gateway.multiplex_profiles。本文所依据的版本中,该选项默认关闭;启用后,一个 Gateway 进程可以服务多个 Profile,管理各自的平台适配器、凭据和运行上下文。
两种方式可以从运维需求上比较:
| 维度 | 分别运行 Gateway | 一个 Gateway 服务多个 Profile |
|---|---|---|
| 进程组织 | 每个 Profile 分别运行 | 多个 Profile 共享一个进程 |
| 启停维护 | 可以分别操作各 Gateway | 统一维护承担复用职责的 Gateway |
| Profile 数据 | 各自保存配置和状态 | 仍然各自保存配置和状态 |
| 选择重点 | 便于分别运维 | 减少需要维护的 Gateway 进程 |
共享进程会让这些 Profile 共同依赖该进程的可用性;分开进程则可以分别重启。这是运行方式的取舍,与 Profile 是否拥有独立目录是两个问题。
另外,分开运行时也要避免误用同一份机器人凭据。原始资料描述了重复 Bot Token 的连接保护:后启动的 Gateway 会被阻止建立对应的重复连接,避免两个进程争抢同一个机器人的消息。
5.2 用消息来源选择 Profile
开启复用后,还需要说明消息应该交给谁。profile_routes 提供了根据消息来源选择 Profile 的规则。
例如,希望同一个 Discord 服务器中的开发频道使用 Coder,研究频道使用 Researcher,可以在承担复用职责的 Gateway 所用 config.yaml 中加入以下配置。该片段只展示复用与路由部分,频道 ID 需要替换成实际值,已有的平台连接配置仍需保留。
gateway:
multiplex_profiles: true
profile_routes:
- name: development
platform: discord
guild_id: "10001"
chat_id: "20001"
profile: coder
- name: research
platform: discord
guild_id: "10001"
chat_id: "20002"
profile: researcher
这里的 Guild 可以理解为 Discord 服务器,chat_id 在这个例子中对应频道 ID,Thread 则是频道中的讨论串。
路由会根据消息来源进行匹配。按原始资料对该版本的说明,多条规则命中时优先采用更具体的规则:Thread 优先于 Channel,Channel 优先于 Guild;没有匹配时交给默认 Profile。对应的阅读入口是 gateway/profile_routing.py 中的 match_profile_route()。
5.3 找到目标之后,还要切换运行上下文
选中 coder 只是第一步。真正处理这条消息时,读取配置、访问记忆以及解析密钥,都必须落到 Coder 的上下文中。
_profile_runtime_scope() 把一次消息处理与目标 Profile 的运行环境关联起来。其中的 Home Override 对应前文的 ContextVar 机制,Secret Scope 则确定当前任务使用的密钥作用域。
这样,同一个进程中的不同任务可以分别使用 Coder 和 Researcher 的配置。任务处理结束后恢复先前上下文,也能避免把本次选择意外带到后续工作中。
这层路由解决的是“消息交给哪个助手”。它主要按平台、服务器、频道和讨论串匹配,不能直接承担企业用户身份认证或访问授权。
六、三个目录,三种职责:数据、工具身份与工作位置
理解了 Profile,还需要区分三个经常同时出现的路径概念:
| 配置或变量 | 主要职责 | 示例 |
|---|---|---|
HERMES_HOME | 定位 Hermes 的配置与状态 | /home/dev/.hermes/profiles/coder |
HOME | 为外部程序提供用户目录,很多工具据此查找配置与凭据 | /home/dev |
terminal.cwd | 指定工具执行命令时的默认工作目录 | /workspace/java-demo |
它们可以同时存在,指向三个不同的地方:Coder 从自己的 Profile 读取记忆,在 Java 项目目录执行命令,Git 则从操作系统用户目录读取配置。
6.1 Profile 分开了,外部工具身份仍可能共享
按原始资料,v2026.7.30 在宿主机执行工具子进程时,默认的配置为:
terminal:
home_mode: auto
在这一场景下,子进程保留真实操作系统用户的 HOME。如果 Coder 和 Researcher 都以同一个 Linux 用户运行,它们调用 Git、SSH、GitHub CLI 时,就可能读取同一份用户配置和凭据。
因此,Profile 的 .env 分开保存,并不代表所有外部程序的身份也自动分开。例如,两套不同的模型凭据可以分别属于两个 Profile,但 git push 仍可能使用同一份 SSH 配置。
需要按 Profile 分开这些依赖用户目录的工具配置时,可以使用:
terminal:
home_mode: profile
工具子进程的用户目录此时指向 {HERMES_HOME}/home。Coder 和 Researcher 因而可以分别初始化 Git 配置、SSH 密钥和 GitHub CLI 登录状态。
这仍然只是在改变工具查找用户配置的位置。外部程序还可能从环境变量、系统配置或其他路径获取凭据,具体行为需要结合对应工具理解。
6.2 工作目录不会自动变成访问边界
再看下面的配置:
terminal:
backend: local
cwd: /workspace/java-demo
它表达的是:使用本机执行后端,并从 /workspace/java-demo 这个工作目录开始运行命令。执行 pwd 时,通常会看到这个目录。
但本机进程仍然拥有当前操作系统用户被授予的文件权限。如果这个用户能读取另一个项目,工具进程也可能读取它。把 cwd 指向某个项目,不会自动禁止访问其他目录。
同样,把工具的 HOME 改到 Profile 里面,也不会撤销它对宿主机文件的访问权限。需要限制实际访问范围时,应结合容器、操作系统用户权限或受限制的执行环境配置,并检查挂载目录与外部服务权限。
这三层职责可以分别记住:Profile 管 Hermes 的配置与状态,工具用户目录影响配置和凭据的查找,执行环境与权限控制决定能访问什么。
七、Profile 与 Agent、用户、租户是什么关系
在个人使用中,把一个 Profile 叫作“我的开发助手”很自然。设计服务时,则需要把几个概念分清。
| 概念 | 代表什么 | 主要关心的问题 |
|---|---|---|
| Profile | 一套 Hermes 配置与持久状态 | 使用哪套配置、记忆和数据 |
| Gateway | 接收、调度消息的运行服务 | 消息从哪里进入,如何交给助手 |
| AIAgent | 执行任务的运行对象 | 当前任务如何执行 |
| Session | 一段对话及其历史 | 当前对话延续哪些上下文 |
| User | 业务用户身份 | 谁在使用服务,有哪些权限 |
| Tenant | 企业或组织的业务归属 | 数据和资源属于哪个组织 |
一个 Profile 可以服务多段 Session,也可以在不同时间创建多个 AIAgent 对象。Profile 的数据寿命可以跨越多次进程启动,而一次任务执行结束,只是运行过程结束了。
八、把整条链路连起来
从使用者的角度看,Profiles 让同一套 Hermes 程序可以维护多个长期身份。从运行机制看,这些身份主要通过各自的 Hermes Home 落在磁盘上。
启动时,CLI 先确定 Profile,把对应目录交给后续模块;处理消息时,Gateway 再根据部署方式和路由规则,将任务放入正确的运行上下文。配置、记忆和会话数据由此找到各自的归属。
理解这套机制后,几个实际选择也就容易判断:
- 需要新的长期配置与记忆时,创建 Profile;需要新的对话上下文时,考虑创建 Session。
- 手动使用可以切换默认 Profile,脚本和服务配置中宜明确写出目标 Profile。
- 需要分别运维时,可以分别启动 Gateway;需要集中管理进程时,可以考虑复用模式。
- 涉及外部工具身份、用户授权和租户权限时,继续检查
HOME、执行环境与业务权限设计。
Profiles 的价值,在于把助手的长期配置与状态从共享程序中分离出来。这为同时运行多个助手提供了清楚的组织方式,也为进一步设计消息路由和业务隔离留下了明确的位置。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)