在这里插入图片描述

源码定位agent/run_agent.py:7770agent/prompt_builder.pygateway/run.py
阅读建议:配合源码食用,行号基于 v0.9.0 主分支。


1. 引言:无状态是 AI 智能体的"生产力死刑"

作为架构师,你一定见过这种令人沮丧的场景:你花了数周时间调教出一个完美的 AI 助理,它理解你的架构规范、熟悉你的代码癖好。然而,当你关闭终端或开启新 Session 的那一刻,它瞬间患上"七秒记忆"的失忆症,一切归零。你不得不像西西弗斯一样,一遍又一遍地喂入同样的项目背景。

在生产环境下,无状态(Statelessness)是 Agent 的死刑。

Hermes Agent 的定位从来不是一个简单的 OpenAI API 包装器,而是一个具备"进化"能力的分布式运行时系统。它通过"持久化记忆"与"自进化技能库"(SKILL.md),将 AI 从一个冰冷的问答工具,锻造成一个能够随使用而不断成长的数字分身。


2. 核心架构:严格单向依赖的六层全景图

Hermes 的工程稳定性源于极其克制的单向依赖架构。系统被划分为六个严密的层级,杜绝了复杂系统中常见的循环导入(Circular Import)噩梦。

第六层:基础设施

状态存储
SQLite WAL+FTS5

常量定义
hermes_constants.py

日志系统
hermes_logging.py

异步桥接
model_tools.py

第五层:环境抽象

本地环境
local.py

Docker 环境
docker.py

Modal 环境
modal.py

SSH 远程环境
ssh.py

第四层:工具系统

工具注册表
tools/registry.py

终端工具
terminal_tool.py

文件工具
file_tools.py

浏览器工具
browser_tool.py

MCP 工具
mcp_tool.py

第三层:能力扩展

外部记忆插件
plugins/memory/honcho/

上下文引擎
plugins/context_engine/

技能库
skills/ 目录

第二层:Agent 核心

AIAgent 主控
run_agent.py

提示词构建器
prompt_builder.py

上下文压缩器
context_compressor.py

记忆管理器
memory_manager.py

第一层:交互入口

CLI 命令解析
hermes_cli/main.py

异步消息网关
gateway/run.py

编辑器协议适配
acp_adapter/entry.py

批量任务
batch_runner.py

架构哲学:解耦与依赖反转

  1. 零依赖根节点hermes_constants.py 没有任何项目内导入,作为路径解析与环境检测的基石,确保其可被任意层级引用。

  2. 依赖反转(IoC)的工具链ToolRegistry 采用"自注册模式"。工具模块在被 Python import 的瞬间自动向注册表登记,核心逻辑层 model_tools.py 作为编排者最后加载。

实战价值:这种设计实现了"一套代码,全端运行"——无论你是在 VS Code 里写代码,还是在 Telegram 上发指令,底层逻辑完全统一。

工具自注册示例

# tools/file_tools.py
from tools.registry import ToolRegistry

@ToolRegistry.register(
    name="read_file",
    description="读取文件内容",
    parameters={...}
)
def read_file(path: str) -> str:
    with open(path, 'r') as f:
        return f.read()

3. 灵魂组件:run_conversation() 状态机深度拆解

agent/run_agent.py 中,run_conversation()(line 7770)全长约 2900 行,是 Hermes 的心脏。它不是一个简单的对话循环,而是一个包含了深度错误恢复、上下文管理与 Token 预算控制的精密状态机。

3.1 核心骨架逻辑(架构级伪代码)

def run_conversation(self, user_message, ...):
    # 1. 生产级防护:给 stdout/stderr 包上 _SafeWriter
    _install_safe_stdio()
    set_session_context(self.session_id)
    
    # 2. 故障自愈:若上轮触发 Fallback,本轮切回主模型
    self._restore_primary_runtime()
    
    # 3. 系统提示词构建:实例缓存 + SQLite 回读双保险
    if self._cached_system_prompt is None:
        stored_prompt = self._session_db.get_session(self.session_id)
        if stored_prompt:
            self._cached_system_prompt = stored_prompt
        else:
            self._cached_system_prompt = self._build_system_prompt(system_message)
            self._session_db.update_system_prompt(...)
    
    # 4. 预检上下文:estimate_request_tokens_rough 需计入 20K-30K 的 Tool Schemas
    if self.compression_enabled and len(messages) > ...:
        _preflight_tokens = estimate_request_tokens_rough(
            messages,
            system_prompt=active_system_prompt or "",
            tools=self.tools or None,
        )
        if _preflight_tokens >= self.context_compressor.threshold_tokens:
            for _pass in range(3):
                messages, active_system_prompt = self._compress_context(...)
    
    # 5. Hook 注入与缓存优化:pre_llm_call 上下文注入"用户消息"而非"系统提示词"
    _plugin_user_context = ""
    _pre_results = _invoke_hook("pre_llm_call", ...)
    for r in _pre_results:
        if isinstance(r, dict) and r.get("context"):
            _ctx_parts.append(str(r["context"]))
    
    # 6. 主状态循环:API 调用 -> 并发/串行工具执行 -> 结果处理
    while api_call_count < max_iterations:
        response = self._interruptible_api_call(...)
        if not response.tool_calls:
            break
        await self.execute_tool_batch(response.tool_calls)

3.2 技术洞察:故障转移与自愈机制

Hermes 具备极强的系统韧性。当主模型遭遇 API 抖动或限流时,系统会根据 agent/error_classifier.py 的分类策略进行响应:

错误类型 HTTP 状态码 处理策略 典型场景
RATE_LIMIT 429 读取 retry-after header,指数退避重试 API 配额耗尽
AUTH_FAILURE 401, 403 立即触发故障转移到其他提供商 Token 过期/无效
PAYLOAD_TOO_LARGE 413 尝试上下文压缩后重试 请求体超限
CONTEXT_OVERFLOW 400 (context length) 尝试压缩,若失败则提示用户 对话历史过长
SERVER_ERROR 500, 502, 503 指数退避重试,多次失败后转移 服务端临时故障
STREAM_INTERRUPT 连接中断 尝试非流式重试 网络不稳定

自愈机制的核心设计:每次 run_conversation() 开始时,_restore_primary_runtime() 都会强制尝试切回主模型,确保临时网络故障不会导致永久性的能力降级。

实战示例

# 故障转移配置示例(config.yaml)
runtime:
  primary:
    provider: anthropic
    model: claude-sonnet-4-6
  fallback:
    provider: openai
    model: gpt-4-turbo
  
  retry_config:
    max_retries: 3
    backoff_factor: 2  # 2s, 4s, 8s
    max_backoff: 60    # 最大等待 60 秒

4. 内存博弈:冻结快照与前缀缓存保护

在 Memory 层,Hermes 解决了一个微妙的工程矛盾:实时记忆更新 vs API 成本控制。

4.1 冻结快照(Frozen Snapshot)机制

如果 Agent 在对话中频繁修改 MEMORY.md,会导致每一轮对话的系统提示词都发生变化。对于 Anthropic 这种按前缀缓存计费的模型,这意味着缓存持续失效,成本飙升。

Hermes 的解法:

  • AIAgent.__init__ 时,BuiltinMemoryProvider 会将记忆内容加载到内存形成一个快照_system_prompt_snapshot)。
  • 在该 Session 的所有 Turn 中,系统提示词始终引用此快照。
  • Agent 调用工具修改记忆时,修改的是磁盘上的真实文件,但快照在当前 Session 内保持不变。
  • 对于 Gateway 这种每 turn 新建 AIAgent 实例的模式,新实例会从磁盘重新加载,所以实际上每 turn 都能获取到最新记忆。

这种"延迟一致性"换取了极高的缓存命中率与响应速度。

4.2 MemoryManager 的硬性约束

MemoryManager 不是简单的 list,它是一个严格受限的编排器

  • 必须包含一个 BuiltinMemoryProvider(基于文件)
  • 最多只能添加一个外部 provider(如 Honcho、Holographic、Mem0)

这个设计的底层原因是:如果允许多个外部 provider 同时注册,它们可能都提供名为 memory_search 的工具,导致 schema 冲突;而且 Agent 的系统提示词中关于"如何保存记忆"的指导只能有一套,多个后端会造成行为分裂。


5. 智能并发:工具调用的"指挥官"策略

当 LLM 一次性抛出多个文件操作指令时,Hermes 如何避免竞态冲突?它不是简单的字符串匹配,而是通过 _should_parallelize_tool_batch()run_agent.py:267-300)进行物理层级的冲突检测。

5.1 路径冲突检测逻辑

def _should_parallelize_tool_batch(tool_calls) -> bool:
    tool_names = [tc.function.name for tc in tool_calls]
    if any(name in _NEVER_PARALLEL_TOOLS for name in tool_names):
        return False  # clarify 永远串行
    
    reserved_paths = []
    for tool_call in tool_calls:
        tool_name = tool_call.function.name
        function_args = json.loads(tool_call.function.arguments)
        
        if tool_name in _PATH_SCOPED_TOOLS:
            scoped_path = _extract_parallel_scope_path(tool_name, function_args)
            if any(_paths_overlap(scoped_path, existing) for existing in reserved_paths):
                return False  # 文件路径冲突,串行
            reserved_paths.append(scoped_path)
    
    if not all(name in _PARALLEL_SAFE_TOOLS for name in tool_names):
        return False  # 含非安全工具,串行
    
    return True

系统通过 _paths_overlap() 函数对 _PATH_SCOPED_TOOLS(如 read_file, write_file, patch)进行过滤:

  • 不仅是字符串比较:它会解析 Path,检查一个路径是否是另一个路径的父目录,从而防止 write_file("/a/b/c.txt")write_file("/a/b") 被误判为不冲突。
  • 强制串行:一旦检测到重叠,该 Batch 将被迫转为串行执行,确保文件读写的原子性。

6. 存储选型:为什么"单文件 SQLite"才是正义?

在向量数据库被过度神化的今天,Hermes 坚定地选择了 SQLite (FTS5 + WAL)

维度 SQLite (FTS5 + WAL) 向量数据库 (Vector DB)
部署成本 零配置,单文件(适合 $5 VPS/Serverless) 高,需独立服务或重型容器
查询确定性 极高(Agent 直接编写 SQL 进行布尔/短语搜索) 中(基于余弦相似度的模糊联想)
写入性能 WAL 模式支持高并发读写,备份即拷贝 索引重建成本高,离线迁移困难
存储效率 10 万条对话约 50MB 同等数据量通常 200MB+
运维复杂度 无需额外进程,自动 checkpoint 需监控索引健康度、内存占用

高级玩法:Agent 编写自己的 SQL

Hermes 的 Agent 会在检索历史时,将自然语言需求转化为精准的 SQL 查询。通过 FTS5 全文搜索,Agent 能像侦探一样从数万条历史记录中,通过时间戳和关键词硬匹配出"三周前的报错日志",而非向量检索可能给出的"看起来相似的无关代码"。

实战示例

-- Agent 自动生成的查询示例
SELECT message, timestamp 
FROM conversation_history 
WHERE conversation_history MATCH 'docker AND error AND port'
  AND timestamp > datetime('now', '-7 days')
ORDER BY rank
LIMIT 10;

7. 工程底线:_SafeWriter 与 Gateway 的平台锁

在 7x24 小时的生产运行中,架构师必须考虑最极端的边界条件。

7.1 _SafeWriter 的实战价值

当 Hermes 运行在远程 Docker 容器或守护进程模式下,若终端管道断开,Python 的 print() 会抛出 OSError: [Errno 5] 导致整个 Agent 崩溃。_install_safe_stdio() 给 stdout/stderr 包了一层 _SafeWriter,静默吞掉 I/O 异常。这是保证 Agent 不在半夜因为网络抖动而死机的一道防线。

实现原理

class _SafeWriter:
    def __init__(self, stream):
        self._stream = stream
    
    def write(self, data):
        try:
            self._stream.write(data)
            self._stream.flush()
        except (OSError, BrokenPipeError):
            pass  # 静默吞掉管道断开异常
    
    def flush(self):
        try:
            self._stream.flush()
        except (OSError, BrokenPipeError):
            pass

7.2 Gateway 的平台锁(fcntl.flock

在 Gateway 模式下,每个适配器在 connect() 时会尝试获取平台级文件锁

def _acquire_platform_lock(self):
    lock_path = get_hermes_home() / f"gateway_{self.platform.value}.lock"
    lock_file = open(lock_path, "w")
    try:
        fcntl.flock(lock_file.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
        self._lock_file = lock_file
    except BlockingIOError:
        raise RuntimeError(f"Another gateway instance is already running for {self.platform.value}")

关键特性

  • 非阻塞锁LOCK_NB 标志确保立即返回,不会挂起进程
  • 自动释放:进程退出时,操作系统会自动释放文件锁(即使崩溃也不会死锁)
  • 跨进程可见:防止用户误操作同时启动两个 Telegram Bot

常见问题排查

# 检查锁文件状态
ls -la ~/.hermes/gateway_*.lock

# 查看持有锁的进程
lsof ~/.hermes/gateway_telegram.lock

# 强制释放锁(谨慎使用)
rm ~/.hermes/gateway_telegram.lock

8. 常见问题排查

8.1 状态机卡死或无响应

症状:Agent 长时间无输出,进程未退出

排查步骤

# 1. 检查是否在等待 API 响应
tail -f ~/.hermes/logs/hermes.log | grep "Calling API"

# 2. 检查是否有工具执行超时
grep "Tool execution timeout" ~/.hermes/logs/hermes.log

# 3. 查看当前对话状态
sqlite3 ~/.hermes/profiles/default/state.db \
  "SELECT * FROM conversation_state ORDER BY timestamp DESC LIMIT 1;"

常见原因

  • API 请求超时未正确处理(检查网络连接)
  • 工具执行陷入死循环(如 terminal_tool 执行了交互式命令)
  • 上下文过长导致 API 拒绝响应(尝试压缩历史)

8.2 工具调用失败

症状:Agent 报告工具执行错误,但命令本身是正确的

排查步骤

# 1. 检查工具注册状态
hermes --profile default tools list

# 2. 验证环境隔离配置
cat ~/.hermes/profiles/default/config.yaml | grep -A 5 "environment"

# 3. 手动测试工具
hermes --profile default tools test terminal_tool "echo test"

常见原因

  • 环境变量未正确传递到隔离环境
  • Docker 容器未启动或权限不足
  • 工具依赖的外部服务不可用(如 MCP 服务器)

8.3 记忆检索不准确

症状:Agent 无法回忆起之前的对话内容

排查步骤

# 1. 检查 FTS5 索引状态
sqlite3 ~/.hermes/profiles/default/state.db \
  "SELECT COUNT(*) FROM conversation_history_fts;"

# 2. 手动测试全文搜索
sqlite3 ~/.hermes/profiles/default/state.db \
  "SELECT message FROM conversation_history WHERE conversation_history MATCH 'your_keyword' LIMIT 5;"

# 3. 检查记忆插件状态
hermes --profile default memory status

常见原因

  • FTS5 索引未正确创建(重建索引:hermes memory rebuild-index
  • 查询关键词过于宽泛或包含停用词
  • 外部记忆插件(如 Honcho)连接失败

8.4 Gateway 消息丢失

症状:Telegram 发送消息后,Bot 无响应

排查步骤

# 1. 检查 Gateway 进程状态
ps aux | grep "hermes gateway"

# 2. 查看 Gateway 日志
tail -f ~/.hermes/logs/gateway_telegram.log

# 3. 验证 Webhook 配置
curl https://api.telegram.org/bot<YOUR_TOKEN>/getWebhookInfo

常见原因

  • Webhook URL 配置错误或证书过期
  • 平台锁未释放(删除 ~/.hermes/gateway_telegram.lock 后重启)
  • 消息队列积压(检查 gateway/message_queue.py 的队列长度)

9. 结语:迈向自进化的数字分身

穿越 Hermes 的六层架构,我们看到的不是枯燥的代码,而是一个数字免疫系统。通过 SKILL.md,Agent 正在将每一次成功的试错固化为本能。

当 AI 不仅能记住你的答案,还能记住你纠正它的全过程,并将其转化为一种可复用的数字基因时,它到底是你手中的一个工具,还是你意志在数字世界的延伸?

在 Hermes 的系统里,每一条消息的旅程,都是通往这个"数字分身"的一步进化。


Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐