[08] Hermes Agent 基础设施拆解:Profile 隔离、Cron 调度、MCP 协议与 Plugin 系统

人工智能Agent 2026-06-23 11
预计阅读时间:11 分钟

> TL;DR:让 Agent 7×24 小时跑在生产环境的四根柱子。Profile(多身份隔离,每个 profile 有独立的 config/skills/memory)、Cron(自主定时唤醒,无需人工介入)、MCP(动态扩展工具集,零重启)、Plugin(全生命周期钩子)。这篇是系列收尾——拆完八篇,你看到的不是"一个框架",是一个可以自我运作的 Agent 生态。

前七篇拆了子系统全景、Agent Loop、System Prompt、工具系统、记忆矩阵、模型调度、技能进化。最后一篇拆让这一切能够 7×24 小时自动运转的基础设施

1. Profile:多身份隔离

Profile 是 Hermes 实现"一台机器跑多个人格"的基础设施。每个 Profile 拥有独立的 config.yamlskills/plugins/cron/memories/

# hermes_cli/profiles.py
def _get_profiles_root() -> Path:
    """Return the directory where named profiles are stored.
    Anchored to the hermes root, NOT to the current HERMES_HOME.
    """
    return _get_default_hermes_home() / "profiles"

目录结构:

~/.hermes/
├── config.yaml                  # default profile 的配置
├── skills/                      # default profile 的技能
├── plugins/                     # default profile 的插件
├── cron/                        # default profile 的定时任务
├── memories/                    # default profile 的记忆
└── profiles/
    ├── work/                    # 另一个身份
    │   ├── config.yaml
    │   ├── skills/
    │   ├── cron/
    │   └── memories/
    └── personal/
        └── config.yaml

Profile 切换通过 active_profile 文件持久化:

def _get_active_profile_path() -> Path:
    """Return the path to the sticky active_profile file."""
    return _get_default_hermes_home() / "active_profile"

这个文件简单存一个 Profile 名。Hermes 启动时读这个文件,如果存在就覆盖默认路径——~/.hermes/~/.hermes/profiles/<name>/hermes_constants.pyget_hermes_home() 会根据 active_profile 动态解析路径。而且 Profile 切换是线程安全的——使用 ContextVar 而非全局变量,不同线程可以跑不同 Profile:

_HERMES_HOME_OVERRIDE: ContextVar[str | object] = ContextVar(
    "_HERMES_HOME_OVERRIDE", default=_UNSET
)
def set_hermes_home_override(path: str | Path | None) -> Token:
    """Set a context-local Hermes home override and return its reset token.
    This is for in-process, per-task scoping. It deliberately does not mutate
    os.environ because that is shared by every thread in the process.
    """
    value: str | object = _UNSET if path is None else str(path)
    return _HERMES_HOME_OVERRIDE.set(value)

os.environ 是进程级别的全局状态——所有线程共享,改一次所有线程受影响。ContextVar 是 Python 3.7 引入的协程安全变量,每个协程/线程有自己的副本,互不干扰。 Profile 克隆时还会跳过基础设施工件:

def _clone_all_copytree_ignore(source_dir: Path):
    """Exclude infrastructure artifacts when cloning a profile via --clone-all."""
    def _ignore(directory: str, names: List[str]) -> List[str]:
        ignored: list[str] = []
        for entry in names:
            # 跳过 .git、__pycache__、node_modules、.env、state.db、*.log
            ...
    return _ignore

克隆时跳过 .env(含密码)、state.db(会话数据库)、.git(版本库)等不属于"身份设置"的内容。

2. Cron:自主定时调度

Cron 系统让 Hermes 能在无用户介入的场景下自主运行。实现了 cronjob 工具,Agent 可以自己给自己设置定时任务。

2.1 调度接口

# tools/cronjob_tools.py — cronjob 工具的注册与实现
registry.register(
    name="cronjob",
    toolset="cron",
    schema={
        "description": "管理定时 cron 任务 — 创建、暂停、恢复、删除、执行",
        "parameters": {
            "type": "object",
            "properties": {
                "action": {
                    "type": "string",
                    "enum": ["create", "list", "update", "pause", "resume", "remove", "run"]
                },
                "schedule": {"type": "string", "description": "定时表达式 或 '30m' 等自然格式"},
                "prompt": {"type": "string", "description": "任务触发时执行的 prompt"},
                "name": {"type": "string", "description": "可读名称"},
            }
        }
    },
    handler=cronjob_handler,
)

2.2 调度粒度

Cron 任务支持多种调度格式: | 格式 | 示例 | 说明 | |------|------|------| | 自然语言间隔 | "30m""every 2h" | 从创建时间开始,每 N 分钟/小时 | | Cron 表达式 | "0 9 * * *" | 标准 5 段 cron | | ISO 时间戳 | "2026-07-01T09:00:00" | 一次性定时任务 | 和系统 cron 的区别:

# hermes_cli/cron.py
# schema 中 action="list" 返回所有已注册任务
# action="create" 写入 ~/.hermes/cron/ 下的持久化任务文件

Hermes Cron 的存储不在系统 crontab,而在 ~/.hermes/cron/ 目录下。每个 Profile 有自己的 cron 目录,互不干扰。

2.3 cron 任务执行

Cron 任务在触发时,Hermes 会: 1. 从 ~/.hermes/cron/ 读取任务定义 2. 按 deliver 配置发送结果(origin/local/all/指定平台) 3. 支持 skills 字段——任务运行时先加载指定的 Skill 再执行 prompt 4. 支持 model 字段——用指定模型运行该任务(不依赖当前会话模型) 这种设计让一个 cron 任务可以独立于任何用户会话运行——Agent 在被唤醒时没有上下文,但任务自己的 prompt 已经包含了全部所需信息。


3. MCP:动态工具扩展

Model Context Protocol(MCP)是 Anthropic 提出的开放协议,用于向大模型动态注入工具。Hermes 原生支持 MCP 客户端,MCP 工具和其他内置工具一样进入 ToolRegistry

# hermes_cli/mcp_config.py
# MCP 服务器在 config.yaml 中配置:
# mcp:
#   servers:
#     my-server:
#       transport: "stdio"
#       command: "node"
#       args: ["/path/to/server.js"]

MCP 工具的加载机制:

MCP 服务器启动  通过 JSON-RPC 暴露工具列表
     Hermes MCP 客户端接收工具 Schema
     调用 registry.register() 注册为 toolset="mcp-{server_name}"
     工具立即可用,无需重启 Hermes

MCP 服务器发生变更时,Hermes 能实时处理:

# MCP server 发送 notifications/tools/list_changed
# → registry.deregister() 移除旧工具
# → 重新拉取新列表 → registry.register() 注册新工具
# → self._generation += 1 触发缓存失效

这解决了插件系统的热加载问题。传统 Plugin 系统需要重启 Agent 才能加载新工具,MCP 不需要——工具在运行中增删,下次 LLM 调用时自动生效。

4. Plugin:全生命周期钩子

Plugin 是 Hermes 的最大扩展点。Plugin 可以注册工具、钩子(hooks)和 CLI 命令。

4.1 工具注册

Plugin 也可以通过 registry.register() 注册工具,但有一个限制——跨工具集重名保护。

# tools/registry.py
if existing and existing.toolset != toolset:
    if not override:
        logger.error(
            "Tool registration REJECTED: '%s' (toolset '%s') would "
            "shadow existing tool from toolset '%s'.",
            name, toolset, existing.toolset,
        )
        return

Plugin 的工具如果与内置工具同名,会被拒绝注册,除非 override=True

4.2 Hook 生命周期

Plugin 可以注册以下生命周期钩子: | 钩子 | 触发时机 | 用途 | |------|---------|------| | pre_llm_call | LLM 调用前 | 注入额外上下文 | | post_llm_call | LLM 调用后 | 转换或审计 LLM 输出 | | transform_llm_output | 响应返回前 | 修改 LLM 回复内容 | | Memory Provider | 完整记忆周期 | Hindsight/Honcho 等外部记忆后端 |

4.3 Plugin 配置

Plugin 在 config.yaml 中声明:

# config.yaml
plugins:
  - my-custom-plugin    # 从 ~/.hermes/plugins/ 加载
  - memory:
      provider: hindsight  # 外部记忆 plugin

Plugin 目录:

~/.hermes/plugins/
├── my-custom-plugin/
│   ├── __init__.py
│   └── plugin.py

Plugin 加载逻辑简化:

# hermes_cli/plugins.py(示意)
def load_plugins():
    plugin_dir = HERMES_HOME / "plugins"
    for entry in config.get("plugins", []):
        if isinstance(entry, dict):
            if "memory" in entry:
                # 初始化记忆 provider
                provider = _init_memory_provider(entry["memory"]["provider"])
                memory_manager.add_provider(provider)
        elif isinstance(entry, str):
            # 注册工具和钩子
            importlib.import_module(f"plugins.{entry}")

5. 四根柱子的协同

Profile、Cron、MCP、Plugin 四者不是孤立的:

Profile A(生产环境)
  ├── cron/ → 每天 9 点抓取数据
  │   └── MCP Server → 实时查询数据库
  ├── plugins/memory-hindsight → 记忆持久化
  └── skills/ → 生产运维技能
Profile B(个人写作)
  ├── cron/ → 每 30 分钟检查 CSDN 收益
  ├── plugins/wechat-login → 公众号自动发文
  └── skills/ → 自媒体写作技能

Profile 隔离了不同场景的完整运行环境。每个 Profile 内的 Cron 任务自动继承该 Profile 的配置和技能。MCP 工具扩展了 Agent 在运行时的能力边界。Plugin 为特化场景提供了定制的能力。

系列收官总结

八篇文章,一张完整的 Hermes Agent 底层架构地图: | 篇次 | 子系统 | 核心源码文件 | 行数 | |------|--------|-------------|------| | 01 | 模块全景 | — | 37 个模块速查 | | 02 | Agent Loop | agent/conversation_loop.py | 4,836 | | 03 | System Prompt | agent/system_prompt.py + prompt_builder.py | 406 + 1,756 | | 04 | 工具系统 | tools/registry.py + model_tools.py | 589 + 1,174 | | 05 | 记忆矩阵 | agent/memory_manager.py + memory_provider.py + hermes_state.py | 653 + 296 + 4,216 | | 06 | 模型调度 | hermes_cli/runtime_provider.py + auth.py | 1,694 + 7,706 | | 07 | 技能进化 | tools/skills_tool.py + agent/skill_commands.py | 1,545 + 527 | | 08 | 基础设施 | hermes_cli/profiles.py + tools/cronjob_tools.py + hermes_cli/plugins.py | 多文件 | 总计约 25,000 行源码被拆解覆盖——不是讲 API 怎么说,是把实际跑的代码翻出来看。


本系列基于 Hermes Agent v0.15.2 源码。八篇全部完成。


本文由 admin 原创,转载请注明出处。

相关推荐

评论

0
暂无评论,来发表第一条评论吧

发表评论

登录 后发表评论

发现更多