本文对Hermes的架构拆解是基于 v2026.7.30 版本的源码解析

在同一台服务器上,我们可能希望同时拥有几个用途不同的 Hermes 助手:一个负责写代码,一个负责查资料,还有一个处理日常事务。它们使用同一套 Hermes 程序,却需要不同的模型配置、身份设定和长期记忆。

如果这些助手都读写同一份配置和数据,问题很快就会出现:开发助手刚切换了模型,研究助手也跟着改变;本来只想让办公助手记住的信息,却进入了共同使用的记忆文件。

重新安装几份 Hermes 当然可以分开管理,但程序升级、配置维护也会跟着重复。Profiles 提供了另一种组织方式:共享程序,为不同助手分别保存配置和运行状态。

理解这一机制,可以沿着一条线展开:Profile 定义“这是谁的数据”,HERMES_HOME 告诉程序“去哪里找数据”,Gateway 再决定“这条消息由谁处理”。

一、为什么需要 Profiles:从三个助手说起

先把需求具体化。假设我们要运行三个助手:

助手主要用途希望独立维护的内容
Coder编写代码、分析项目开发模型、代码相关技能、项目记忆
Researcher阅读资料、整理研究结果研究模型、检索技能、研究偏好
Assistant处理日常事务办公技能、个人偏好、定时任务

它们需要分开的内容,可以分成两类。第一类是配置:用什么模型、保存哪些密钥、采用什么身份设定。第二类是状态:积累了哪些记忆、发生过哪些对话、保存了哪些定时任务。

这里的 Memory 是助手长期保留的信息,例如用户偏好和项目约定;Session 则是一段对话及其历史。两者都会影响助手的后续行为,但生命周期和用途不同。

有了 Profiles,三个助手之间的关系可以画成这样:

同一套 Hermes 程序

Coder 的运行实例

Researcher 的运行实例

Assistant 的运行实例

coder Profile:配置、记忆、会话

researcher Profile:配置、记忆、会话

assistant Profile:配置、记忆、会话

图中的“同一套程序”表示共享安装和源码。至于运行时使用几个进程,则取决于启动方式。三个 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 选择,其核心流程如下:

hermes -p coder chat

进入 hermes_cli/main.py

_apply_profile_override() 解析 Profile 参数

resolve_profile_env() 得到 coder 的目录

设置 HERMES_HOME

导入和初始化其余 Hermes 模块

通过 get_hermes_home() 获取数据目录

使用 coder 的配置、密钥和状态

启动对话

这里真正值得关注的是顺序:先确定 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()

它体现了三层路径来源,优先级由高到低是:

  1. 当前执行上下文设置的 Home Override。
  2. HERMES_HOME 环境变量。
  3. 操作系统对应的默认目录。

“执行上下文”可以先理解为当前正在处理的一段任务。普通 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 服务多个 Profile

各平台消息入口

一个 GatewayRunner 进程

coder 的运行上下文

researcher 的运行上下文

coder Profile

researcher Profile

分别运行 Gateway

开发助手的消息入口

Gateway 进程 A

coder Profile

研究助手的消息入口

Gateway 进程 B

researcher 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 的上下文中。

开发频道

研究频道

无匹配

Gateway 收到消息

profile_routes 匹配结果

选择 coder

选择 researcher

选择默认 Profile

进入 _profile_runtime_scope()

设置当前上下文的 Home Override 与密钥作用域

使用目标 Profile 处理消息

处理结束,恢复先前上下文

_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 的价值,在于把助手的长期配置与状态从共享程序中分离出来。这为同时运行多个助手提供了清楚的组织方式,也为进一步设计消息路由和业务隔离留下了明确的位置。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐