在构建能够自主执行复杂任务的 AI Agent 时,开发者面临一系列系统性的工程挑战:如何让模型安全地调用外部工具?如何在有限的上下文窗口中保留关键信息?如何让 Agent 跨会话记住用户偏好?如何为调试和优化提供完整的执行轨迹?以及如何将庞大任务分解为可并行处理的子任务?Hermes Agent 通过五个相互协作的设计模块给出了系统性的答案。本文以工作流程为主线,逐一剖析其工具系统、上下文压缩器、记忆管理器、轨迹保存与会话日志以及代理委托工具的设计思想与实现细节。
一、工作流程主线
一个典型会话的执行路径如下:用户输入消息后,Agent 首先通过记忆管理器预取与该用户相关的历史记忆和用户画像,将其包装为<memory-context>块并注入当前用户消息中(此操作仅在 API 调用时生效,不持久化)。随后,Agent 组装系统提示词——该提示词包含固定的身份声明、环境提示、工具使用指导,以及从记忆管理器获取的冻结快照(MEMORY.md 和 USER.md 在会话启动时的内容)。系统提示在整个会话中保持不变,以最大化提示缓存命中率。
进入主循环后,Agent 调用语言模型 API。若模型返回工具调用请求,则通过工具系统进行分发:工具系统根据工具名从注册表中查找对应的处理函数,支持同步与异步处理,并根据工具类型(只读、路径作用域、互斥)自动决定串行或并行执行。工具执行结果被追加到对话历史中,循环继续。若模型返回最终文本响应,则退出循环。
在每轮 API 调用后,Agent 会评估当前上下文 token 使用量。当超过预设阈值(默认为模型上下文窗口的 50%)时,上下文压缩器被触发:它首先修剪过长的旧工具结果(用占位符替换),然后通过 Token 预算保护尾部最近的消息,将中间部分交由辅助语言模型生成结构化摘要,最后组装为“头部 + 摘要 + 尾部”的新消息列表,并修复因压缩可能导致的孤儿工具调用/结果对。
整个会话结束后,Agent 将完整的消息历史(包括系统提示、用户消息、助手回复、工具调用及结果)写入轨迹文件(JSONL 格式),同时将结构化数据同步到 SQLite 会话数据库,支持后续的session_search工具查询。此外,记忆管理器的sync_turn方法被调用,将当前用户输入和助手回复传递给所有注册的记忆提供者(内置文件存储和可选的外部插件),供其学习和更新长期记忆。
对于需要分解的复杂任务,主 Agent 可以通过代理委托工具生成一个或多个子 Agent。每个子 Agent 拥有独立的对话上下文、受限的工具集、自己的终端会话和迭代预算,并行执行子任务。父 Agent 仅接收子 Agent 返回的摘要结果,中间的工具调用细节被完全隔离。这种设计使得主 Agent 可以将耗时的子任务(如代码审查、多源信息检索)委托出去,避免自身上下文被撑爆。
二、工具系统:注册、发现与调度的中枢
工具系统是 Hermes Agent 与外部环境交互的桥梁,其核心是一个名为ToolRegistry的单例注册表。每个工具(如web_search、terminal、read_file)在所属模块被导入时调用registry.register()完成注册,提交的信息包括:工具名称、所属工具集、JSON Schema 参数定义、处理函数(同步或异步)、可用性检查函数(例如检查 API 密钥是否存在)、所需环境变量列表、显示用的表情符号以及结果大小上限。这种“声明式注册”模式将工具的实现细节与调度逻辑完全解耦。
工具系统的第一个设计亮点是工具集过滤与动态 Schema 修正。model_tools.get_tool_definitions()根据用户配置的enabled_toolsets和disabled_toolsets解析出最终要暴露给模型的工具名集合,然后从注册表中获取对应的 Schema。对于execute_code这类工具,其可用子工具列表依赖于当前会话中实际加载了哪些工具(例如web_search可能因缺少 API 密钥而被过滤掉),系统会动态重写其 Schema 中的sandbox_allowed_tools字段,避免模型幻觉调