编程 Hermes Agent 深度拆解:110K Star 背后的「自进化」架构哲学——当一个 AI Agent 决定「越用越聪明」后,闭环学习、三层记忆与 400+ 技能的工程实战

2026-08-03 08:43:47 +0800 CST views 8

Hermes Agent 深度拆解:110K Star 背后的「自进化」架构哲学——当一个 AI Agent 决定「越用越聪明」后,闭环学习、三层记忆与 400+ 技能的工程实战

2026 年 2 月,Nous Research 发布了 Hermes Agent。三个月后,GitHub Star 数冲破 110K,贡献者超过 240 人。和市面上绝大多数 Agent 框架不同,它的核心卖点不是「更好地调用工具」,而是「越用越聪明」。这篇文章从源码出发,拆解这个「自进化」Agent 的架构设计、闭环学习机制、三层记忆系统与 400+ 技能生态,附完整代码示例与生产部署指南。

一、为什么你需要关注 Hermes Agent?

1.1 Agent 赛道的现状与痛点

2026 年的 AI Agent 赛道已经非常拥挤。AutoGPT、MetaGPT、CrewAI、LangGraph、OpenClaw……每隔几周就有一个新框架冒出来。但如果你真正用过这些框架,会发现一个共同的痛点:

Agent 永远是「无状态」的。

每次对话从零开始,或者依赖简单的上下文窗口来维持「记忆」。你花了一周教它你的项目结构、编码规范、工作流程,结果换个会话就全忘了。这不是 Agent,这是一个每次都失忆的实习生。

Hermes Agent 解决的就是这个问题。它的核心理念可以用一句话概括:

The self-improving AI agent — 一个能从经验中学习、在使用中进化、越用越聪明的智能体。

1.2 核心数据一览

属性详情
开发者Nous Research
许可证MIT(完全自由)
主语言Python(909+ 文件)
辅助语言TypeScript/TSX(Web UI)
Python 版本≥ 3.11
总文件数~1717
测试用例~3000
工具数量40+ 内置工具
技能数量288 内置 + 116 可选
支持平台17+ 消息平台
GitHub Star110K+

和 OpenClaw 的 14000+ TypeScript 文件相比,Hermes Agent 的 ~1700 个 Python 文件更易理解和贡献。这不是一个「大而全」的框架,而是一个「中等规模但设计精巧」的项目。

二、架构全景:从目录结构看设计哲学

2.1 目录结构速览

hermes-agent/
├── run_agent.py              # ★ 核心:AIAgent 类与对话循环
├── model_tools.py            # 工具编排与函数调用分发
├── toolsets.py               # 工具集定义(40+ 工具列表)
├── cli.py                    # 交互式 CLI 编排器
├── hermes_state.py           # SessionDB — SQLite 会话存储
│
├── agent/                    # Agent 内部模块(33 文件)
│   ├── prompt_builder.py     # 系统提示词组装
│   ├── context_compressor.py # 自动上下文压缩
│   ├── memory_manager.py     # 记忆上下文构建
│   └── error_classifier.py   # API 错误分类与故障转移
│
├── tools/                    # 工具实现(71 文件)
│   ├── registry.py           # ★ 中央工具注册表
│   ├── terminal_tool.py      # 终端编排
│   ├── file_tools.py         # 文件操作
│   ├── browser_tool.py       # 浏览器自动化
│   └── environments/         # 6 种终端后端实现
│       ├── local.py          # 本地终端
│       ├── docker.py         # Docker 容器
│       ├── ssh.py            # SSH 远程
│       └── ...
│
├── hermes_cli/               # CLI 子命令(48 文件)
│   ├── main.py               # 所有 hermes 子命令入口
│   ├── skin_engine.py        # ★ 皮肤/主题引擎
│   └── skills_hub.py         # 技能市场
│
├── gateway/                  # 消息平台网关(42 文件)
│   └── platforms/            # Telegram/Discord/Slack/WhatsApp/QQ...
│
├── acp_adapter/              # VS Code / Zed / JetBrains 集成
├── skills/                   # 288 个内置技能(Markdown)
├── optional-skills/          # 116 个可选技能
├── tests/                    # ~3000 个测试
└── environments/             # RL 训练环境(Atropos)

2.2 五个关键架构决策

决策一:同步 Agent 循环

run_conversation() 是一个完全同步的 while 循环,没有使用 async/await。这种设计看似「保守」,实则大大降低了理解和调试的复杂度:

while api_call_count < self.max_iterations and self.iteration_budget.remaining > 0:
    response = client.chat.completions.create(
        model=model,
        messages=messages,
        tools=tool_schemas
    )
    if response.tool_calls:
        for tool_call in response.tool_calls:
            result = handle_function_call(tool_call.name, tool_call.args, task_id)
            messages.append(tool_result_message(result))
        api_call_count += 1
    else:
        return response.content

对比 OpenClaw 的复杂异步架构,Hermes Agent 的同步设计对于快速迭代的初创团队更友好。调试时你只需要在一个线程里追踪整个执行流程,不需要处理 async/await 的状态交错。

决策二:中心化工具注册

所有工具通过 tools/registry.py 统一注册和分发。添加新工具只需两步:

# tools/your_tool.py
from tools.registry import registry

async def handle_your_tool(args, task_id):
    """处理你的工具逻辑"""
    result = do_something(args)
    return {"output": result}

# 注册工具
registry.register(
    name="your_tool",
    toolset="hermes-core",
    handler=handle_your_tool,
    schema={
        "type": "function",
        "function": {
            "name": "your_tool",
            "description": "你的自定义工具",
            "parameters": {
                "type": "object",
                "properties": {
                    "input": {"type": "string", "description": "输入参数"}
                },
                "required": ["input"]
            }
        }
    }
)

这种自注册模式的优势在于:新接入一个平台时,只需要定义该平台特有的工具,然后引用核心工具集就行。工具的发现机制通过 AST 分析检测哪些 .py 文件包含 registry.register() 调用,不需要手动维护工具列表。

决策三:Profile 多实例隔离

通过 HERMES_HOME 环境变量实现完全隔离的多实例支持,代码中 119+ 处引用 get_hermes_home() 确保路径隔离。这意味着你可以在同一台机器上跑多个独立的 Agent 实例,互不干扰——比如一个用于工作项目,一个用于个人自动化。

# 创建独立的 Profile
hermes profile create work-assistant --clone
hermes profile create personal-bot --clone

# 使用不同 Profile 启动
hermes -p work-assistant
hermes -p personal-bot

每个 Profile 拥有独立的配置、记忆库、会话历史、Skill 集合和工具权限。数据全部存储在本地 ~/.hermes/ 目录,搬家的时候拷贝目录就行。

决策四:可插拔上下文引擎

agent/context_engine.py 定义了抽象基类 ContextEngineagent/context_compressor.py 提供了默认实现。可插拔设计意味着你可以实现自己的上下文管理策略,比如基于 RAG 的检索增强、基于重要性评分的选择性保留等。

from abc import ABC, abstractmethod

class ContextEngine(ABC):
    @abstractmethod
    async def compress(self, messages, token_budget):
        """压缩对话历史,返回压缩后的消息列表"""
        pass

class ContextCompressor(ContextEngine):
    def __init__(self, threshold_percent=0.75, protect_first_n=3, protect_last_n=6):
        self.threshold_percent = threshold_percent
        self.protect_first_n = protect_first_n
        self.protect_last_n = protect_last_n

    async def compress(self, messages, token_budget):
        usage_percent = self._calculate_usage(messages, token_budget)
        if usage_percent < self.threshold_percent:
            return messages

        # 保护头部和尾部
        head = messages[:self.protect_first_n]
        tail = messages[-self.protect_last_n:]
        middle = messages[self.protect_first_n:-self.protect_last_n]

        # 中间部分用辅助模型总结
        summary = await self._summarize_with_auxiliary_model(middle)
        return head + [summary] + tail

决策五:研究导向的模块设计

RL 训练环境、轨迹压缩、批量运行器这些模块不是「后来加的」,而是从一开始就融入架构设计的核心组件。如果你在做 Agent 相关的学术研究或模型训练,Hermes Agent 可能是目前最开箱即用的研究平台。

三、闭环学习系统:Agent 如何「自我进化」

这是 Hermes Agent 和其他 Agent 框架拉开差距的地方。

3.1 闭环学习的五个环节

完成任务 → 策划记忆 → 创建 Skill → Skill 自改进 → FTS5 召回 → 用户建模 → (循环)

这套机制对应认知科学的三种记忆类型:

  • 情景记忆(Episodic Memory):会话历史,通过 SQLite FTS5 全文搜索检索
  • 语义记忆(Semantic Memory):MEMORY.md 持久事实,记录环境信息、项目约定
  • 程序性记忆(Procedural Memory):Skill 文件,记录可复用的操作步骤

3.2 Skill 自动创建与改进

Skill 是 Hermes Agent 最核心的抽象。它不是简单的 prompt 模板,而是一个完整的「操作手册」,包含触发条件、执行步骤、注意事项和代码示例。

# deploy-to-production.md

## 触发条件
当用户说「部署到生产」「上线」「push to prod」时触发

## 前置检查
1. 确认当前分支是 main
2. 运行 `git status` 确认无未提交更改
3. 运行 `npm test` 确认测试全部通过

## 部署步骤
1. `git pull origin main`
2. `npm run build`
3. `docker build -t myapp:latest .`
4. `docker push myapp:latest`
5. `kubectl set image deployment/myapp myapp=myapp:latest`

## 回滚方案
如果部署后出现异常:
1. `kubectl rollout undo deployment/myapp`
2. 检查日志:`kubectl logs -l app=myapp --tail=100`

## 注意事项
- 部署前必须确认数据库迁移已执行
- 高峰期(14:00-18:00)避免部署
- 生产环境禁止使用 `--force` 参数

Agent 完成一个复杂任务后,会自主判断是否包含可复用的模式。如果有,就自动生成 Markdown 格式的 Skill 文件。使用 Skill 时如果发现问题(过时、报错),立即 patch 更新。

3.3 记忆策展机制

Hermes Agent 的记忆不是简单地「把所有对话都存下来」,而是有选择性地策展:

# agent/memory_manager.py 中的记忆策展逻辑(简化)
class MemoryManager:
    def curate_memory(self, conversation_history):
        """Agent 自主判断什么值得记住"""
        # 1. 识别关键事实(项目约定、用户偏好、环境信息)
        key_facts = self._extract_key_facts(conversation_history)

        # 2. 识别可复用模式(重复出现的操作步骤)
        patterns = self._identify_reusable_patterns(conversation_history)

        # 3. 生成 Skill 文件
        if patterns:
            self._create_skill_from_pattern(patterns)

        # 4. 更新 MEMORY.md
        self._update_persistent_memory(key_facts)

记忆条目之间用 §(section sign)分隔——这个选择挺有意思,用一个不太常见的字符做分隔符,降低了和正文内容冲突的概率。

3.4 冻结快照模式

这个设计解决了一个很实际的问题:系统提示的稳定性。

会话开始时,MemoryManager 把当前的记忆内容作为快照注入系统提示。之后整个会话期间,系统提示保持不变——中途写入的记忆只更新磁盘文件,不刷新系统提示。

# 冻结快照模式的核心逻辑
class MemoryManager:
    def start_session(self):
        """会话开始时创建冻结快照"""
        # 读取当前记忆
        memory_content = self._read_memory_files()

        # 创建快照(不可变)
        self._frozen_snapshot = memory_content

        # 注入系统提示(整个会话期间不变)
        return self._build_system_prompt_with_snapshot(self._frozen_snapshot)

    def update_memory(self, new_entries):
        """中途更新记忆(只写磁盘,不刷新系统提示)"""
        self._append_to_memory_file(new_entries)
        # 注意:self._frozen_snapshot 不变!

这样做的好处是保持前缀缓存(prefix cache)有效。如果每次记忆更新都刷新系统提示,KV cache 就会失效,每次请求都要重新计算前面所有 token 的 KV 对。冻结快照让前缀缓存命中率大大提高。

代价是 Agent 在一个会话内对记忆的修改,需要等到下一个会话才能被读取。对于大多数使用场景来说,这个延迟是可以接受的。

四、三层记忆架构:从短期到长期的知识管理

4.1 双 Provider 记忆架构

agent/memory_manager.py 中的 MemoryManager 采用双 Provider 架构:

class MemoryManager:
    def __init__(self):
        # 内置 Provider:管理 MEMORY.md 和 USER.md
        self.builtin_provider = BuiltinMemoryProvider()

        # 外部 Provider(可选):如 Honcho 用户建模
        self.external_provider = None  # 可注入 Honcho 等

    def get_memory_context(self):
        """获取记忆上下文"""
        contexts = []

        # 1. 从内置 Provider 获取
        contexts.append(self.builtin_provider.get_memory())

        # 2. 从外部 Provider 获取(如果有)
        if self.external_provider:
            contexts.append(self.external_provider.get_user_model())

        # 3. 用 <memory-context> 标签做上下文围栏
        return self._wrap_with_fence("\n".join(contexts))

两个记忆文件的分工很明确:

  • MEMORY.md:Agent 的个人笔记。记录环境事实、项目约定、工具的使用技巧
  • USER.md:Agent 对用户的认知。记录偏好、沟通风格、期望

记忆注入时使用 <memory-context> 标签做上下文围栏,防止模型把记忆上下文误认为新的用户输入。

4.2 FTS5 全文搜索召回

hermes_state.py 中的 SessionDB 基于 SQLite FTS5 实现跨所有会话消息的全文搜索:

import sqlite3

class SessionDB:
    def __init__(self, db_path):
        self.conn = sqlite3.connect(db_path)
        self._init_fts5()

    def _init_fts5(self):
        """初始化 FTS5 全文搜索索引"""
        self.conn.execute("""
            CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts
            USING fts5(content, session_id, timestamp)
        """)

    def search(self, query, limit=10):
        """全文搜索历史消息"""
        cursor = self.conn.execute("""
            SELECT content, session_id, timestamp
            FROM messages_fts
            WHERE messages_fts MATCH ?
            ORDER BY rank
            LIMIT ?
        """, (query, limit))

        return cursor.fetchall()

搜索流程是:FTS5 召回相关片段 → LLM 总结 → 注入当前上下文。这是一个多级检索的设计,不同类型的记忆走不同的通道。

4.3 Honcho 用户建模

Hermes Agent 集成了 Honcho 辩证用户建模系统,通过对话式交互构建用户画像:

# Honcho 用户建模的核心逻辑(简化)
class HonchoUserModel:
    def __init__(self):
        self.user_profile = {}

    def observe_interaction(self, user_message, agent_response, context):
        """从交互中提取用户偏好"""
        # 分析用户的沟通风格
        style = self._analyze_communication_style(user_message)

        # 提取技术偏好
        tech_prefs = self._extract_tech_preferences(context)

        # 更新用户画像
        self.user_profile.update({
            "communication_style": style,
            "technical_preferences": tech_prefs,
            "last_updated": datetime.now()
        })

    def get_user_model(self):
        """返回用户建模结果"""
        return json.dumps(self.user_profile, ensure_ascii=False)

Honcho 的核心理念是「方言式建模」——不是简单地给用户打标签,而是通过观察用户的实际行为来理解他们的「方言」(即独特的沟通和工作方式)。

五、工具系统:自注册 + Toolset 组合

5.1 ToolRegistry 自注册模式

工具系统的核心在 tools/registry.py

import threading

class ToolEntry:
    def __init__(self, name, toolset, schema, handler, check_fn=None,
                 requires_env=False, is_async=False, description="", emoji=""):
        self.name = name
        self.toolset = toolset
        self.schema = schema
        self.handler = handler
        self.check_fn = check_fn
        self.requires_env = requires_env
        self.is_async = is_async
        self.description = description
        self.emoji = emoji

class ToolRegistry:
    def __init__(self):
        self._tools = {}
        self._lock = threading.RLock()

    def register(self, name, toolset, schema, handler, **kwargs):
        """注册工具(线程安全)"""
        with self._lock:
            entry = ToolEntry(
                name=name,
                toolset=toolset,
                schema=schema,
                handler=handler,
                **kwargs
            )
            self._tools[name] = entry

    def get_tool(self, name):
        """获取工具(快照读取)"""
        with self._lock:
            return self._tools.get(name)

    def discover_builtin_tools(self):
        """通过 AST 分析自动发现工具"""
        import ast
        import os

        tools_dir = os.path.join(os.path.dirname(__file__), "tools")
        for root, dirs, files in os.walk(tools_dir):
            for f in files:
                if f.endswith(".py"):
                    filepath = os.path.join(root, f)
                    with open(filepath) as fp:
                        tree = ast.parse(fp.read())
                    for node in ast.walk(tree):
                        if isinstance(node, ast.Call):
                            if hasattr(node.func, 'attr') and node.func.attr == 'register':
                                # 找到 registry.register() 调用
                                pass

发现机制通过 AST 分析检测哪些 .py 文件包含 registry.register() 调用,不需要手动维护工具列表。动态注册/注销场景主要用于 MCP 服务器刷新:MCP 工具上线时调用 register(),下线时调用 deregister()

5.2 Toolset 组合系统

toolsets.py 实现了一个组合式工具集系统:

# 核心工具列表
_HERMES_CORE_TOOLS = [
    # Web
    "web_search", "web_extract",
    # 终端
    "terminal", "process",
    # 文件
    "read_file", "write_file", "patch", "search_files",
    # 视觉
    "vision_analyze", "image_generate",
    # 技能
    "skills_list", "skill_view", "skill_manage",
    # 浏览器
    "browser_navigate", "browser_click", "browser_type",
    # 规划
    "todo", "memory",
    # 代码执行
    "execute_code", "delegate_task",
    # 定时任务
    "cronjob",
    # 其他
    "send_message", "session_search", "clarify",
]

# 平台特定 toolset
_HERMES_TELEGRAM_TOOLS = _HERMES_CORE_TOOLS + [
    "telegram_send_message",
    "telegram_send_photo",
    "telegram_edit_message",
]

_HERMES_DISCORD_TOOLS = _HERMES_CORE_TOOLS + [
    "discord_send_message",
    "discord_send_embed",
    "discord_add_reaction",
]

# 所有平台的并集
_HERMES_GATEWAY_TOOLS = list(set(
    _HERMES_TELEGRAM_TOOLS +
    _HERMES_DISCORD_TOOLS +
    _HERMES_SLACK_TOOLS +
    # ... 更多平台
))

这种组合式设计的好处是:新接入一个平台时,只需要定义该平台特有的工具,然后 includes 核心工具集就行。比如新增一个飞书适配器,只需要定义飞书的消息发送、消息格式化等平台特有工具,然后引用 hermes-gateway 的基础工具集。

5.3 并行工具执行

Agent 循环中的一个关键判断逻辑在 _should_parallelize_tool_batch() 方法中:

# 工具并行分类
_NEVER_PARALLEL_TOOLS = {"clarify"}  # 需要用户交互,永远不并行
_PARALLEL_SAFE_TOOLS = {"web_search", "read_file"}  # 只读安全,可并行
_PATH_SCOPED_TOOLS = {"read_file", "write_file", "patch"}  # 路径隔离,条件并行

_MAX_TOOL_WORKERS = 8  # 最大并发工作线程数

def _should_parallelize_tool_batch(self, tool_calls):
    """判断工具批次是否可以并行执行"""
    for tc in tool_calls:
        if tc.name in _NEVER_PARALLEL_TOOLS:
            return False

    # 检查路径冲突
    write_ops = [tc for tc in tool_calls if tc.name in _PATH_SCOPED_TOOLS]
    if len(write_ops) > 1:
        paths = [tc.args.get("path") for tc in write_ops]
        if len(set(paths)) < len(paths):  # 有路径冲突
            return False

    return True

这个设计说明团队对 Agent 的实际使用场景做过深入思考。比如 web_searchread_file 都是纯读操作,并行执行完全安全。但 write_filepatch 操作同一文件时就有冲突风险,所以需要路径隔离检查。

六、子 Agent 委托:从单兵到团队

6.1 隔离设计

tools/delegate_tool.py 实现了子 Agent 委托机制:

# 子 Agent 关键配置
MAX_DEPTH = 1      # 默认扁平,最多可配到 3
MAX_CONCURRENT = 3  # 最多 3 个并行子 Agent

# 被禁止的工具(子 Agent 不能使用)
DELEGATE_BLOCKED_TOOLS = {
    "delegate_task",      # 防止递归委托
    "clarify",           # 子 Agent 不应该直接和用户对话
    "memory_write",      # 防止子 Agent 污染主 Agent 的记忆
    "skill_manage",      # 子 Agent 不应该修改技能
}

子 Agent 的隔离做得比较彻底:

  • 无父历史:子 Agent 看不到父 Agent 的对话历史
  • 独立终端会话:子 Agent 有自己的终端环境
  • 工具限制:特定工具被禁止使用
class SubAgent:
    def __init__(self, task, parent_context=None):
        self.task = task
        self.depth = 0
        self.conversation_history = []  # 独立的对话历史
        self.terminal = TerminalBackend()  # 独立的终端

    async def run(self):
        """执行子 Agent 任务"""
        # 不继承父 Agent 的历史
        messages = [
            {"role": "system", "content": self._build_system_prompt()},
            {"role": "user", "content": self.task}
        ]

        # 限制可用工具
        available_tools = [
            t for t in ALL_TOOLS
            if t.name not in DELEGATE_BLOCKED_TOOLS
        ]

        return await self._agent_loop(messages, available_tools)

七、多平台网关:一个 Agent,所有平台

7.1 统一消息网关

gateway/run.py 实现了一个统一的消息网关,单一进程处理所有平台消息:

class Gateway:
    def __init__(self):
        self.platforms = {
            "telegram": TelegramAdapter(),
            "discord": DiscordAdapter(),
            "slack": SlackAdapter(),
            "whatsapp": WhatsAppAdapter(),
            "signal": SignalAdapter(),
            "email": EmailAdapter(),
            "wechat": WeChatAdapter(),
            "dingtalk": DingTalkAdapter(),
            "feishu": FeishuAdapter(),
            # ... 17+ 平台
        }
        self.agent_cache = LRUCache(maxsize=128)
        self.agent_ttl = 3600  # 1 小时空闲淘汰

    async def handle_message(self, platform, user_id, message):
        """统一消息处理入口"""
        # 1. 查找或创建 Agent 实例
        agent = self._get_or_create_agent(platform, user_id)

        # 2. 转换消息格式
        normalized = self.platforms[platform].normalize(message)

        # 3. 交给 Agent 处理
        response = await agent.process(normalized)

        # 4. 转换回平台格式并发送
        formatted = self.platforms[platform].format_response(response)
        await self.platforms[platform].send(user_id, formatted)

这意味着同一个 Agent 可以同时服务多个平台——你可以在 Telegram 上开始一个对话,然后在 Discord 上继续。不同平台的消息格式差异在网关层统一处理,Agent 核心不需要关心这些细节。

7.2 Agent 缓存策略

网关用 LRU 缓存 + 空闲 TTL 淘汰来管理 Agent 实例:

class AgentCache:
    def __init__(self, maxsize=128, ttl=3600):
        self.cache = {}
        self.access_times = {}
        self.maxsize = maxsize
        self.ttl = ttl

    def get(self, key):
        if key in self.cache:
            self.access_times[key] = time.time()
            return self.cache[key]
        return None

    def put(self, key, agent):
        if len(self.cache) >= self.maxsize:
            # 淘汰最久未访问的
            oldest = min(self.access_times, key=self.access_times.get)
            del self.cache[oldest]
            del self.access_times[oldest]

        self.cache[key] = agent
        self.access_times[key] = time.time()

    def cleanup_expired(self):
        """清理过期的 Agent 实例"""
        now = time.time()
        expired = [
            k for k, t in self.access_times.items()
            if now - t > self.ttl
        ]
        for k in expired:
            del self.cache[k]
            del self.access_times[k]

八、状态持久化:SQLite + FTS5

8.1 技术选型

Hermes Agent 选择 SQLite 作为状态存储层,这个决策非常务实:

  • SQLite + WAL 模式:支持多读者 + 单写者,适合网关多平台并发场景
  • FTS5 全文搜索:跨所有会话消息的快速文本搜索
  • Schema 版本控制:当前 v8,自动迁移
import sqlite3

class SessionDB:
    def __init__(self, db_path):
        self.conn = sqlite3.connect(db_path, timeout=30)
        self.conn.execute("PRAGMA journal_mode=WAL")
        self.conn.execute("PRAGMA busy_timeout=5000")
        self._migrate()

    def _migrate(self):
        """Schema 版本控制与自动迁移"""
        current_version = self._get_version()
        migrations = {
            1: self._migrate_v1_to_v2,
            2: self._migrate_v2_to_v3,
            # ... 更多迁移
        }

        for v in range(current_version, max(migrations.keys()) + 1):
            if v in migrations:
                migrations[v]()
                self._set_version(v + 1)

8.2 写竞争处理

SQLite 在高并发写入时容易出现锁竞争。SessionDB 的处理方式是随机抖动重试:

import random
import time

class SessionDB:
    MAX_RETRIES = 15
    BASE_DELAY_MS = 20
    MAX_DELAY_MS = 150

    def _execute_with_retry(self, query, params=None):
        """带随机抖动的重试机制"""
        for attempt in range(self.MAX_RETRIES):
            try:
                if params:
                    return self.conn.execute(query, params)
                else:
                    return self.conn.execute(query)
            except sqlite3.OperationalError as e:
                if "database is locked" in str(e):
                    # 随机抖动,避免护航效应
                    delay = random.randint(self.BASE_DELAY_MS, self.MAX_DELAY_MS) / 1000
                    time.sleep(delay)
                else:
                    raise
        raise Exception("数据库写入失败:重试次数已耗尽")

随机抖动可以避免多个写请求同时重试导致的护航效应(convoy effect)。从选型上看,SQLite 在 Agent 状态管理场景下是一个平衡得很好的方案——PostgreSQL 太重,纯文件存储又缺乏查询能力。

九、PromptBuilder 与安全扫描

9.1 系统提示组装

agent/prompt_builder.py 负责组装完整的 system prompt,包含多个层次:

class PromptBuilder:
    def build_system_prompt(self, context_files, memory_snapshot, skills_index):
        """组装完整的系统提示"""
        parts = []

        # 1. 核心身份定义
        parts.append(DEFAULT_AGENT_IDENTITY)

        # 2. 平台特定提示
        parts.append(self._get_platform_hint())

        # 3. 技能索引
        parts.append(SKILLS_GUIDANCE)
        parts.append(self._format_skills_index(skills_index))

        # 4. 上下文文件
        for ctx_file in context_files:
            parts.append(f"## Context: {ctx_file.name}\n{ctx_file.content}")

        # 5. 记忆快照(冻结)
        parts.append(f"<memory-context>\n{memory_snapshot}\n</memory-context>")

        # 6. 工具使用指导
        parts.append(TOOL_USE_ENFORCEMENT_GUIDANCE)

        return "\n\n".join(parts)

PromptBuilder 还有一个两级缓存机制来加速 Skill 索引的读取:LRU 缓存(内存中)+ 磁盘快照(持久化)。这在 Skill 数量很多的时候能明显减少启动时间。

9.2 安全扫描

PromptBuilder 还有一个容易被忽略的功能:_scan_context_content() 检测注入攻击:

class PromptBuilder:
    def _scan_context_content(self, content):
        """检测 prompt injection 攻击"""
        suspicious_patterns = [
            r"ignore\s+previous\s+instructions",
            r"you\s+are\s+now\s+a\s+",
            r"system\s*:\s*",
            r"<\|im_start\|>",
            r"Human:\s*",
        ]

        for pattern in suspicious_patterns:
            if re.search(pattern, content, re.IGNORECASE):
                raise SecurityError(f"检测到可疑内容:{pattern}")

        return content

上下文文件(.hermes.mdAGENTS.mdCLAUDE.md.cursorrules)的优先级是 .hermes.md > AGENTS.md > CLAUDE.md > .cursorrules。安全扫描会检查这些文件中是否包含恶意指令。

十、生产部署实战

10.1 一键安装

# macOS / Linux
curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash

# Windows(需 WSL2)
wsl --install
# 在 WSL2 终端中执行:
curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash

安装脚本会自动完成:

  • 检测并安装 Python 3.11
  • 检测并安装 Node.js v22
  • 检测并安装 ripgrep(高速文件搜索)
  • 检测并安装 ffmpeg(音视频处理)
  • 创建独立的 Python 虚拟环境
  • 克隆代码仓库并安装依赖
  • hermes 命令添加到 PATH

10.2 配置模型

# 交互式配置
hermes setup

# 或者直接指定模型
hermes model set openrouter/anthropic/claude-sonnet-4-20250514

# 支持的模型后端
hermes model list
# → OpenAI, Anthropic, Bedrock, OpenRouter, Ollama, Nous Portal...

10.3 启动网关

# 启动 Telegram 网关
hermes gateway start --platform telegram

# 启动多平台网关
hermes gateway start --platform all

# 后台运行
hermes gateway start --platform telegram --daemon

10.4 $5/月 VPS 部署

# 在 cheap VPS 上部署
# 1. 安装
curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash

# 2. 配置 OpenRouter(便宜的模型)
hermes model set openrouter/meta-llama/llama-3.1-8b-instruct

# 3. 启动 Telegram 网关
hermes gateway start --platform telegram --daemon

# 内存占用:不跑本地 LLM 的情况下 < 500MB
# 适合:个人使用、轻量自动化

十一、性能与限制

11.1 性能数据

指标数值
Agent 进程内存< 500MB(不含本地 LLM)
冷启动时间~2s(含 Skill 索引加载)
单次 LLM 调用延迟取决于模型后端
并行工具执行最多 8 个工作线程
子 Agent 并发最多 3 个
Agent 缓存容量128 个实例
空闲淘汰时间1 小时

11.2 已知限制

  1. 子 Agent 并发上限 3 个:复杂并行场景受限,社区有人吐槽过
  2. Windows 原生不支持:必须通过 WSL2
  3. 迭代节奏快:不到 3 周发布了 4 个大版本,稳定性需要观察
  4. 内存占用:虽然 < 500MB 看起来不多,但在 $5 VPS 上已经是不小的开销

11.3 与其他框架对比

维度Hermes AgentOpenClawAutoGPTLangGraph
核心定位自进化 Agent个人 AI 助手自主 Agent工作流编排
语言PythonTypeScriptPythonPython
文件数~1700~14000~3000~2000
闭环学习✅ 原生✅ 支持
多平台17+5+10
研究就绪
本地优先

十二、总结与展望

翻完 Hermes Agent 的代码库,几个架构层面的判断:

做得好的地方

  1. 闭环学习是原生能力,不是事后加的补丁。从 PromptBuilder 的 MEMORY_GUIDANCE 到 MemoryManager 的双文件架构,再到 SessionDB 的 FTS5 搜索,整套学习闭环在架构层面是贯通的
  2. 工具自注册 + Toolset 组合的设计很灵活,新接入平台只需定义差异部分
  3. 上下文压缩的可插拔架构给未来的优化留下了空间
  4. 迭代预算 + 中断机制体现了对 Agent 实际运行问题的深入理解

需要关注的局限

  1. 子 Agent 并发上限 3 个,复杂并行场景受限
  2. 项目迭代节奏很快,稳定性需要观察
  3. Windows 原生不支持,必须通过 WSL2
  4. 关于抄袭的争议(中国团队指控架构级抄袭)目前还没有最终结论

未来方向

Hermes Agent 最大的贡献是把 Mitchell Hashimoto 提出的 Harness Engineering 五大组件(指令层、约束层、反馈层、记忆层、编排层)做成了产品级的内建能力。这在 Agent 框架领域是比较少见的。

如果你在做 Agent 相关的架构设计,Hermes Agent 的闭环学习和上下文管理部分值得细看。开源生态里能做到这个完成度的 Agent 框架,目前确实不多。

随着 Nous Research 获得 7500 万美元融资(估值 15 亿美元),Hermes Agent 的发展速度只会更快。对于开发者来说,现在正是深入理解其架构的好时机——等到它成为行业标准时,再回头来看设计决策就晚了。


参考资源

  • GitHub 仓库:https://github.com/NousResearch/hermes-agent
  • 官方文档:https://hermes.nousresearch.com/docs
  • Nous Research:https://nousresearch.com
  • License:MIT

推荐文章

goctl 技术系列 - Go 模板入门
2024-11-19 04:12:13 +0800 CST
Go 如何做好缓存
2024-11-18 13:33:37 +0800 CST
JavaScript中设置器和获取器
2024-11-17 19:54:27 +0800 CST
程序员茄子在线接单