Hermes架构拆解之Environment

本文对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 测试。"

整个执行链路可以简化为:

  1. LLM 生成 Terminal Tool Call。

  2. Tool Runtime 找到对应的 Terminal Handler。

  3. Terminal Handler 获取执行环境。

  4. Environment 在本地、Docker 或其他后端执行命令。

  5. 执行结果返回 Tool Runtime。

  6. 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 生命周期里最容易误解的内容。

必须区分:

  1. Environment Python 对象

  2. Docker 容器

  3. 容器中的文件系统

这三个东西并不是完全相同的生命周期。

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 任务能够以一致的方式执行命令,同时支持底层资源复用。

相关推荐
JAVA_AI人工智能项目实战1 小时前
Agent核心架构(四层架构、ReAct、OTAC、自主决策、Multi-Agent)
人工智能·语言模型·自然语言处理·架构
阿里云云原生1 小时前
云栖盘点丨阿里云云原生 11 项发布加速企业级 Agent 进生产
agent
DO_Community1 小时前
AI Agent 运行环境难搭、空闲还收费?MicroVMs 正式公测
人工智能·agent·ai编程·agi
硅基手札2 小时前
【I2C 技术系列 08】I2C 控制器寄存器与 IP 架构
驱动开发·stm32·单片机·嵌入式硬件·mcu·架构·计算机外设
武子康2 小时前
H3-World 的动作控制,为什么不能只靠一句提示词?
人工智能·agent
Georgewu2 小时前
Selected model is at capacity. Please try a different model. 绕过去不使用Codex, OpenCo
agent
Markus实验室2 小时前
多Agent协作的“审查幻觉”:当两个AI互相点赞,谁来兜底?
agent
Jarming2 小时前
infinite-creation本地AI短剧带货项目推荐-本地运行无费用0负担
agent·qwen·skill·minimax·deepseek·skills·agent-skills·minimax-h3
代码方舟2 小时前
零信任架构实战:基于天远贷前风险报告构建自动化消费信贷资质预审网关
人工智能·架构·自动化