本文对Hermes的架构拆解是基于v2026.7.30 版本的源码解析
前言
LLM 负责决定调用什么工具,而 Hermes 的 Tool Runtime 负责真正执行工具。
但这又引出了几个问题:
-
当 LLM 调用
terminal执行 Linux 命令时,命令究竟在哪里执行? -
为什么连续多次调用 Terminal,可以访问之前创建的文件?
-
不同 Session 拥有不同的
task_id,为什么仍然可能共享同一个 Docker 容器? -
Environment 什么时候创建?什么时候销毁?
-
Hermes 进程重启之后,原来的容器还能不能继续使用?
这些问题都指向 Hermes 中一个重要的基础组件:Environment。
本文将按照以下主线,逐步拆解 Environment 的运行机制:
架构定位 → 创建流程 → 复用机制 → 并发控制 → 生命周期 → Docker 持久化。
一、Environment 在 Hermes 架构中的定位
1.1 Environment 是什么?
Environment 可以理解为 Hermes 的工具执行环境抽象层。
当 Agent 需要读取文件、执行命令或操作工作区时,真正执行这些操作的不是大模型,而是底层 Environment。
从总体架构来看:

例如用户要求:
"帮我进入项目目录,执行 Maven 测试。"
整个执行链路可以简化为:
-
LLM 生成 Terminal Tool Call。
-
Tool Runtime 找到对应的 Terminal Handler。
-
Terminal Handler 获取执行环境。
-
Environment 在本地、Docker 或其他后端执行命令。
-
执行结果返回 Tool Runtime。
-
Tool Result 回到 Agent Loop,交给 LLM 继续处理。
因此,Environment 的核心职责是:
屏蔽不同执行后端的实现差异,向上层工具提供统一的执行能力。
1.2 Environment 与 Session 的区别
这里需要区分三个概念:
| 概念 | 核心职责 |
|---|---|
| Session | 管理某段会话的历史消息 |
| SessionContext | 描述 Agent 当前运行的会话和平台环境 |
| Environment | 提供实际的命令与文件执行环境 |
例如:
Session A
session_id = S001
聊天历史 = History A
Session B
session_id = S002
聊天历史 = History B
虽然两个 Session 的聊天记录完全独立,但它们使用 Terminal Tool 时,仍然可能复用同一个 Environment。
这是理解 Environment 设计的第一个关键点:
Session 的隔离,不等于工具执行环境的隔离。
二、Environment 是如何创建的?
2.1 Environment 并非随着 Session 创建
一个常见误解是:
"每创建一个 Session,Hermes 就会创建一个新的执行环境。"
实际上并不是。
在 Hermes 中,Environment 采用 **按需创建(Lazy Initialization)**机制。
也就是说:
创建 Session,并不意味着立即创建 Environment。
只有真正需要执行 Terminal 或 File Tools 时,才会尝试获取执行环境。
2.2 Terminal Tool 的执行流程
以 tools/terminal_tool.py 为例。
当 AIAgent 调用 Terminal Tool 时,主要经历以下步骤:
第一步:读取 Environment 配置
通过 _get_env_config() 获取环境类型、工作目录、超时以及资源配置等信息。
例如:
env_type = docker
cwd = /workspace
timeout = 120
第二步:计算环境复用标识
调用:
_resolve_container_task_id(task_id)
计算真正用于环境复用的标识。
这里需要注意:
原始 task_id 不一定就是最终的 Environment Key。
具体原因会在第三章展开。
第三步:查询 Environment 缓存
Hermes 在当前进程维护:
_active_environments
用于保存已创建的 Environment 对象。
如果对应环境已经存在,就尝试直接复用。
如果不存在,才会进入创建流程。
第四步:创建 Environment
调用:
_create_environment(...)
根据配置选择具体实现,例如:
LocalEnvironment
DockerEnvironment
SSHEnvironment
第五步:执行命令
最后调用:
env.execute(command, ...)
获得执行结果并返回 Tool Runtime。
2.3 完整执行流程图

可以看到,Environment 创建只是工具执行流程中的一个环节,并不是每次调用都会发生。
如果缓存中已经存在可用 Environment,就可以跳过创建阶段。
三、Environment 的复用机制
这一部分是整个 Environment 架构中非常重要的内容。
3.1 为什么不同 task_id 可以复用 Environment?
假设用户创建了两个独立 Session:
Session A
session_id = S001
task_id = Task-A
Session B
session_id = S002
task_id = Task-B
直觉上可能会认为:
Task-A → Environment A
Task-B → Environment B
但 Hermes 的默认行为不一定如此。
原因就在:
_resolve_container_task_id(task_id)
这个方法。
它负责将原始 task_id 转换为真正用于环境查找、创建和复用的标识。
在没有要求独立环境的特殊配置时,其逻辑可以简化理解为:
# 用于说明原理的伪代码
def resolve_environment_key(task_id):
if has_isolation_override(task_id):
return task_id
return "default"
也就是说:
不同任务的原始 task_id,可以统一映射到 default。
例如:
Task-A
↓
_resolve_container_task_id()
↓
"default"
Task-B
↓
_resolve_container_task_id()
↓
"default"
虽然原始 ID 不一样,但最终使用的是同一个环境缓存键。
需要注意,是否隔离还受环境类型、镜像等 override 配置影响;仅覆盖工作目录(cwd)并不自动意味着创建一个独立沙箱。
3.2 跨 Session 共享 Environment
下面通过流程图理解:

假设 Session A 先执行:
echo "hello Hermes" > /workspace/test.txt
然后 Session B 执行:
cat /workspace/test.txt
如果两个 Session 复用了同一执行环境,并且访问同一个文件路径,那么 Session B 就能读取 Session A 创建的文件。
这说明:
Environment 复用不仅仅是复用一个 Python 对象,还可能意味着底层文件系统、安装依赖和运行状态的复用。
不过,复用同一容器不代表不同命令具有完全相同的工作目录,实际文件可见性还取决于路径、挂载和后端配置。
3.3 File Tools 为什么也可以共享?
除了 Terminal Tool,Hermes 的 File Tools 同样可以使用 Environment。
tools/file_tools.py 中的 _get_file_ops(task_id) 会复用 Terminal 的环境解析与缓存机制,并在对应执行环境上创建文件操作对象。
因此:
Terminal Tool
│
▼
Environment
▲
│
File Tools
这就解释了:
-
Terminal 创建的文件,File Tools 可以读取。
-
File Tools 修改的文件,Terminal 可以继续操作。
-
两者不需要分别创建一套独立的文件系统。
当然,前提仍然是两者最终指向同一个 Environment,并具有相应的路径访问能力。
3.4 Environment 复用带来的设计问题
共享 Environment 可以提高资源利用率,减少重复创建沙箱的开销。
但是,共享也意味着需要处理隔离问题。
例如企业 Agent 平台中:
员工 A → Agent A
员工 B → Agent B
如果两者不应该访问彼此的工作文件,就不能仅凭不同 session_id 来保证安全。
必须在 Environment Key、容器、文件挂载、执行权限等层面建立明确边界。
四、Environment 如何处理并发创建?
4.1 并发场景
假设:
Session A
Session B
在同一时刻首次调用 Terminal Tool。
两个请求计算得到的环境 Key 都是:
default
如果没有并发保护,就可能发生:
Session A 查询缓存 → 不存在
Session B 查询缓存 → 不存在
Session A 创建 Docker 容器
Session B 创建 Docker 容器
结果就是本应共享的环境被重复创建。
Hermes 为此设计了环境缓存锁以及按环境 Key 划分的创建锁。
4.2 两类锁的职责
核心结构可以概括为:
_active_environments = {}
_last_activity = {}
_env_lock = threading.Lock()
_creation_locks = {}
它们分别解决不同的问题。
| 结构 | 作用 |
|---|---|
_active_environments |
缓存已创建的 Environment |
_last_activity |
记录最近活跃时间 |
_env_lock |
保护共享缓存状态 |
_creation_locks |
控制相同环境键的并发创建 |
4.3 双重检查机制
假设 Session A 和 Session B 同时请求环境:

其中最关键的是:
获取创建锁后,还需要重新检查缓存。
因为第二个线程等待锁的过程中,第一个线程可能已经完成环境创建。
这也就是常见的 Double-Checked Locking 思路。
Hermes 通过这种方式,尽量避免同一个环境被重复创建,同时减少不必要的全局锁竞争。
五、Environment 的生命周期
理解创建和复用之后,接下来就是:
Environment 创建以后,究竟能存活多久?
5.1 生命周期状态
可以简化为以下状态:

需要注意,这是一张概念状态图。
实际代码主要通过缓存是否存在、最后活跃时间和后台清理任务来管理,不一定存在显式的 Active、Idle 状态枚举。
5.2 Environment 什么时候创建?
创建时机是:
工具真正需要执行环境,并且不存在可复用 Environment 时。
比如:
新建 Session
↓
只是聊天
↓
不需要 Terminal
↓
不会因为新建 Session 而创建容器
只有:
LLM 返回 Terminal Tool Call
↓
没有可复用环境
↓
创建 Environment
才进入实际创建阶段。
5.3 Environment 什么时候被判定为空闲?
Hermes 使用:
_last_activity
维护环境最后一次活跃时间。
当后续工具继续使用 Environment 时,会刷新相应的活跃记录。
与此同时,后台清理逻辑会定期检查是否存在长时间未使用的环境。
在该版本中,默认的空闲时间配置为:
TERMINAL_LIFETIME_SECONDS=300
也就是约 5 分钟。
但这不代表:
"环境空闲刚好 300 秒就立即销毁。"
因为后台清理线程大约每 60 秒检查一次,清理也可能受到后台进程等因素影响。
因此更准确的理解是:
空闲期限决定环境何时具备被回收的条件,实际清理由后台检查流程完成。
5.4 Environment 清理流程
核心清理函数为:
_cleanup_inactive_envs()
其主要逻辑可以分成两步:
第一步:清理内存缓存
从以下结构中移除过期记录:
_active_environments
_last_activity
第二步:执行实际清理
在适当的锁边界外进行 File Tools 缓存清理,并调用:
env.cleanup()
但是这里有一个需要特别注意的问题:
调用 **env.cleanup()**并不一定意味着底层 Docker 容器被彻底删除。
具体还要看 Docker Environment 的持久化策略。
六、Docker Environment 的持久化机制
这一部分是 Environment 生命周期里最容易误解的内容。
必须区分:
-
Environment Python 对象
-
Docker 容器
-
容器中的文件系统
这三个东西并不是完全相同的生命周期。
6.1 第一层:进程内 Environment 缓存
Hermes 当前进程中维护:
_active_environments
例如:
default
↓
DockerEnvironment Object A
只要缓存存在,后续同键请求就可以直接复用该对象。
这是最直接的进程内复用。
6.2 第二层:Docker 容器跨进程复用
这个版本支持配置:
TERMINAL_DOCKER_PERSIST_ACROSS_PROCESSES
其默认值为:
true
启用后,Hermes 可以在重新创建 DockerEnvironment 时,尝试查找符合条件的已有 Docker 容器。
例如:
Hermes 进程 A
│
▼
DockerEnvironment A
│
▼
Docker Container X
如果 Hermes 进程结束:
Hermes 进程 A 结束
Python Environment 对象消失
但底层 Docker Container X 符合持久化策略并且仍然存在时,新进程可能:
Hermes 进程 B
│
▼
DockerEnvironment B
│
▼
重新连接 Docker Container X
也就是说:
新的 Environment 对象,不一定对应新的 Docker 容器。
容器复用还需要符合 task、profile、容器标签及相关安全配置等匹配条件,并不是任意已有容器都可以复用。
6.3 第三层:文件系统持久化
还需要继续区分:
Docker 容器还在
和:
Docker 容器被删除后,文件仍然保留
这不是同一个问题。
Hermes 还涉及:
TERMINAL_CONTAINER_PERSISTENT
以及挂载、具体 Environment 后端的存储策略。
可以用一张表区分:
| 层次 | 核心问题 | 关键机制 |
|---|---|---|
| 进程内缓存 | 是否复用 Python 对象 | _active_environments |
| Docker 跨进程复用 | 是否重新连接原容器 | TERMINAL_DOCKER_PERSIST_ACROSS_PROCESSES |
| 文件系统持久化 | 容器变化后哪些文件还存在 | TERMINAL_CONTAINER_PERSISTENT、挂载配置 |
6.4 一个需要避免的误解
不要把:
Environment 被清理
直接等同于:
Docker Container 被删除
同样也不要把:
复用了 Docker Environment
直接等同于:
一直复用同一个 Bash 进程
BaseEnvironment.execute() 会调用后端命令执行逻辑,统一处理执行命令及相关状态。
因此,即使多次命令使用同一 Environment,也需要区分:
-
Docker 容器状态
-
文件系统状态
-
当前工作目录状态
-
单次 Shell 命令进程状态
它们不是一个概念。
七、完整案例:两个 Session 如何复用同一 Environment?
现在将前面的知识串成一个完整场景。
假设 Session A 先创建:
session_id = S001
task_id = Task-A
用户要求:
"在工作区创建一个 test.py 文件。"
LLM 返回 Tool Call:
terminal(...)
Terminal Tool 开始执行:
Task-A
↓
_resolve_container_task_id()
↓
default
↓
检查 _active_environments
↓
缓存不存在
↓
创建 DockerEnvironment
↓
执行文件创建命令
此时:
_active_environments
default → DockerEnvironment A
并且文件已经创建在对应的工作区中。
接下来 Session B:
session_id = S002
task_id = Task-B
用户要求:
"帮我运行刚才创建的 test.py。"
假设当前配置允许复用,且两个任务都没有需要隔离的 override:
Task-B
↓
_resolve_container_task_id()
↓
default
↓
检查 _active_environments
↓
缓存命中
↓
复用 DockerEnvironment A
↓
执行 test.py
因此,即使:
Task-A != Task-B
仍然可能:
Environment(Task-A) == Environment(Task-B)
这里的相等表示复用同一个底层执行环境,并不是原始任务 ID 相等。
但是:
History(S001) != History(S002)
它们的聊天历史仍然独立。
所以最后得到一个很重要的结论:
Session 决定 Agent 能看到哪些会话历史,而 Environment 决定 Tool 实际运行在哪里。二者是不同维度的抽象。
八、总结
8.1 Environment 的设计思想
回顾整个模块,Hermes Environment 主要体现了以下几个设计思想。
第一,统一执行抽象。
通过 BaseEnvironment 屏蔽本地、Docker、SSH 和其他沙箱之间的执行差异。
第二,按需创建。
Environment 不和 Session 创建强绑定,而是在实际调用工具时再初始化。
第三,按 Environment Key 复用。
原始 task_id 会经过 _resolve_container_task_id() 处理,不同任务可能映射到同一个 default。
第四,并发安全。
通过全局状态锁、按 Key 创建锁和双重检查,降低并发环境重复创建的风险。
第五,生命周期分层。
进程内缓存、Docker 容器、文件系统分别具有不同的持久化与清理机制。
8.2 最终架构心智模型
总体架构:
用户消息
│
▼
Gateway / API Server
│
▼
Session / History
│
▼
AIAgent
│
▼
Agent Loop
│
▼
LLM
│
│ Tool Call
▼
Tool Runtime
│
├── Terminal Tool
│
└── File Tools
│
▼
Environment Manager
│
├── 环境配置
├── 环境键解析
├── 环境缓存
├── 并发创建控制
└── 生命周期管理
│
▼
Environment
│
┌─────────┼─────────┐
│ │ │
Local Docker SSH ...
│
▼
执行结果
│
▼
Tool Result
│
▼
Agent Loop
用一句话概括:
Environment 是 Hermes Tool Runtime 背后的执行基础设施。它通过统一后端抽象、按需创建、环境键复用、并发控制及生命周期管理,使不同 Agent 任务能够以一致的方式执行命令,同时支持底层资源复用。