我以前一直以为,做一个 Agent,核心问题无非是三件事:

给模型接上工具;

让模型记住历史;

再套一层 ReAct,让它一边思考一边行动。

直到我真正拆解 Hermes Agent 的工程架构,才意识到,这种理解只停留在 Demo 阶段。

一个 Agent 只要开始长期运行,只要它能够调用工具、修改文件、连接企业系统,问题就会迅速发生变化。

你面对的不再只是:

模型下一步应该调用哪个工具?

而是:

这个任务执行到哪一步了?

工具到底有没有成功?

用户点击停止以后,已经发出的邮件怎么办?

两个工具同时修改一个文件,谁先发生?

记忆已经写进数据库,为什么模型还是不知道?

对话压缩以后,哪些细节已经永远丢失?

Agent 总结出的 Skill,究竟是经验,还是一次被永久固化的错误?

到了这里,Agent 工程已经不再是“提示词加工具调用”。

它开始变成一个完整的状态系统、任务系统、并发系统和权限系统。

Hermes 最值得学习的地方,正是在这里。

它没有发明大模型,但它把大模型变成长期运行系统以后,真正困难的问题集中暴露了出来。


一、先放弃一个错误直觉:系统知道,不等于模型知道

假设你做了一个企业 Agent。

用户对它说:

以后生成管理层 PPT,全部使用 16:9,不要再使用 4:3。

Agent 调用 Memory 工具,把这条要求写进了数据库。

此时,大部分人的直觉是:

已经保存了,那模型之后当然就知道。

但事实不一定如此。

因为在一个 Agent 系统中,“信息存在”至少有三种完全不同的含义。

第一种:系统已经保存

例如这条要求已经存在于:

数据库
MEMORY.md
用户配置表
会话记录

这意味着即使程序重启,信息仍然存在。

它是系统真正的持久状态。

第二种:模型当前看到了

模型不会自动读取整套数据库。

每次调用模型时,Agent Runtime 都要从大量信息中挑选一部分,放进本轮 Prompt:

系统规则
最近对话
长期记忆
相关 Skill
检索资料
工具结果

只有真正进入本轮上下文的信息,模型才看得到。

数据库里有,不代表当前 Prompt 里有。

第三种:只在当前运行中临时存在

例如:

用户刚刚点击停止
某个工具正在运行
当前 Token 即将超限
一个子 Agent 还没有返回结果
本轮临时检索到了三份文档

这些信息可能只存在于内存中,任务结束后就消失。

所以长期 Agent 中最重要的一句话是:

系统已经保存,不代表模型当前能看到;模型当前看到,也不代表系统已经永久保存。

这不是文字游戏,而是状态一致性的核心。

假设模型刚刚生成了一份完整分析,但程序还没来得及写入数据库就崩溃了。

结果是:

模型生成过
系统没有保存

反过来,Memory 文件已经更新,但当前会话的 System Prompt 没有重新构建。

结果是:

系统已经保存
模型当前仍然看不到

一旦你理解了这个问题,就会发现很多所谓的“模型失忆”,其实根本不是模型能力不足,而是状态没有正确进入工作上下文。


二、真正成熟的 Agent,会把 Prompt 当成“状态快照”

很多人把 Prompt 理解成一段文字。

在长期 Agent 中,更准确的理解是:

Prompt 是模型在某个时间点,对系统状态的一次观察快照。

假设某次会话开始时,模型看到的是:

用户喜欢简洁汇报
当前项目是面料推广
默认使用集团 PPT 模板

会话进行到一半,Agent 又写入了一条新 Memory:

用户要求管理层汇报必须使用 16:9

问题来了:

是否应该马上重新构建整个 System Prompt?

直觉上当然应该。

否则模型不是看不到最新要求吗?

但如果每写一次 Memory,都重建一次 System Prompt,会带来另一个严重问题:Prompt Cache 失效。

现代模型服务为了降低延迟和推理成本,通常会缓存长 Prompt 的固定前缀。

例如一个 Agent 的 System Prompt 很长,里面包含:

身份规则
工具说明
项目规范
Skill 索引
Memory
用户画像
安全限制

只要这些固定前缀保持不变,后续请求就有机会复用之前的计算结果。

但如果你在 Prompt 最前面放了一个不断变化的信息:

当前时间:11:30

下一轮变成:

当前时间:11:31

即使只变化了一分钟,也可能导致后面大段缓存无法继续复用。

于是 Agent Runtime 必须做一个并不完美的选择:

方案一:实时更新 Prompt

优点是模型立即看到最新状态。

缺点是固定前缀频繁变化,缓存命中率下降,延迟和成本上升。

方案二:会话内冻结 Prompt

会话开始时构造一次状态快照,之后尽量保持不变。

新 Memory 虽然已经写入持久化系统,但可能要到下一次会话或者重新加载后,才完整进入 System Prompt。

这很像数据库中的快照隔离。

一次事务开始以后,看到的是某个时间点的数据版本。即使数据库随后发生了变化,当前事务也不一定立即可见。

因此可以把它理解成:

Memory 文件:最新持久状态
当前 System Prompt:会话开始时的状态快照

这也解释了一个重要的 Prompt 工程原则:

高频变化的信息,不应该轻易放在固定 System Prompt 的前部。

当前时间、Token 剩余量、临时搜索结果、任务进度,更适合以本轮临时上下文的形式追加,而不是反复破坏整个稳定前缀。

普通提示词工程关心一句话怎么写。

Agent Prompt 工程还必须关心:

这段内容放在哪里
多久更新一次
是否影响缓存
是否应该进入持久状态
当前会话是否必须立即可见

这已经不是写文案,而是在设计状态生命周期。


三、上下文压缩不是“记忆”,而是一张有损的工作视图

Agent 执行一个复杂任务时,消息会快速膨胀:

用户要求
模型输出
网页搜索结果
文件内容
终端日志
异常堆栈
工具返回
子 Agent 报告

不可能把这些内容永远完整放进上下文。

于是需要压缩。

很多人的理解是:

把前面的聊天总结一下,节省 Token。

这句话没有错,但过于轻描淡写。

更准确的说法是:

从完整历史中,生成一份更小、更适合模型继续工作的有损视图。

注意,是有损。

假设原始对话里有三句话:

用户最初要求使用集团模板
后来要求这次暂时不用集团模板
最后确认从下次开始恢复集团模板

压缩模型可能总结成:

用户对集团模板的使用要求进行过调整。

这句话表面上没有错,但最重要的信息消失了:

现在到底应该用,还是不用?

如果之后再对这段摘要进行第二次摘要,信息还会继续丢失。

它和 JPEG 图片反复压缩很像。

第一次看起来差别不大。

压缩十次以后,边缘、纹理和细节已经严重失真。

因此,正确的 Agent 系统不应该让压缩摘要替代完整历史。

更合理的结构是:

完整历史
    ↓
永久保存,用于审计、追溯和重新检索

压缩上下文
    ↓
只作为模型当前继续工作的视图

这很像数据库中的原始表和物化视图。

完整 Session 是原始记录。

压缩摘要是为了提高模型工作效率生成的物化视图。

视图可以重建,可以更新,也可能存在误差,但原始数据不能因为生成了视图就被删除。

这也是为什么以下内容绝不能只依赖对话摘要保存:

订单金额
合同条款
付款状态
审批结论
生产任务状态
用户最终确认

这些信息必须进入结构化业务系统。

上下文负责帮助模型理解任务。

它不应该承担数据库的职责。


四、工具并发最危险的地方:日志顺序不等于真实发生顺序

这是 Agent 工程中最容易被忽略的问题。

假设模型一次生成两个 Tool Call:

1. 修改 config.yaml
2. 读取 config.yaml

为了提高速度,Runtime 把两个工具放进线程池,并发执行。

模型的意图似乎很清楚:

先修改
再读取修改后的结果

但并发系统并不理解模型的语义。

真实执行过程可能是:

读取工具先执行,读到了旧配置
修改工具后执行,完成了修改

系统为了维持工具消息格式,最后仍然可能按照模型原始调用顺序,把结果放回上下文:

Tool 1:修改成功
Tool 2:读取到旧配置

模型看到的日志顺序是:

先修改
后读取

现实世界中的发生顺序却是:

先读取
后修改

这里出现了一个关键区别:

消息的展示顺序,不等于副作用的发生顺序。

在并发系统中,真正重要的是两个操作之间是否存在确定的 happens-before 关系。

也就是:

操作 A 是否被系统保证在操作 B 开始前完成?

如果没有依赖、锁或者事务,答案通常是否定的。

再举一个更直观的例子。

账户原始余额是 500。

模型同时调用:

Tool A:余额增加 100
Tool B:读取余额

模型希望 B 读到 600。

但如果 B 先执行,它读到的就是 500。

即使日志最后把 A 的结果放在 B 前面,也不能改变 B 已经读取旧数据的事实。

这意味着,一个真正成熟的 Tool Runtime,不能只记录:

工具名称
工具描述
输入参数
执行函数

还需要理解工具的副作用属性:

是否只读
是否修改外部状态
影响什么资源
是否可以并行
是否幂等
失败后能否重试
是否需要审批
是否支持补偿

例如,一个文档搜索工具可以声明:

name: search_documents
side_effect: read
parallel_safe: true
idempotent: true

一个订单修改工具则可能声明:

name: update_order
side_effect: write
resource: order:{order_id}
parallel_safe: false
idempotent: false
requires_approval: true

有了这些信息,调度器才有可能做出正确判断:

多个独立搜索工具,可以并发
修改同一订单的工具,必须串行
付款工具,执行前必须审批
非幂等写操作,不能因为超时就盲目重试

很多 Agent 框架把 Tool Call 做成了函数调用。

但一旦工具能够改变现实世界,它就不再只是函数。

它开始接近一个分布式事务节点。


五、用户点击停止,并不意味着现实会自动回滚

假设一个 Agent 正在执行任务。

用户突然发现方向错了,点击停止。

很多产品会显示:

任务已取消

但“取消”到底取消了什么?

至少要分成三个层次。

第一层:停止模型生成

系统停止等待模型 API,或者直接丢弃尚未完成的模型输出。

这一层相对容易。

第二层:停止本地执行

例如 Agent 正在运行 Python、处理文件、执行浏览器自动化。

系统可以尝试终止进程或者中止任务。

第三层:撤销已经发生的外部副作用

这一层最难。

如果 Agent 已经:

发送了邮件
创建了日历事件
提交了订单
修改了数据库
调用了支付接口

用户现在按停止,现实不会自动倒退。

所以必须记住:

Cancel 不等于 Rollback。

停止模型,只是让它不要继续做下一步。

已经发生的事情,必须用另外的机制处理。

在分布式系统中,这类问题通常通过补偿事务解决。

例如一个流程包含:

创建订单
    ↓
锁定库存
    ↓
发起付款

如果付款失败,无法使用一个简单数据库事务把所有外部服务恢复到原始状态。

系统只能执行反向补偿:

付款失败
    ↓
释放库存
    ↓
取消订单

Agent 工具同样应该考虑补偿能力。

例如:

create_calendar_event

可以通过:

delete_calendar_event

进行补偿。

而:

send_email

通常无法真正撤回。

最多只能再发送一封更正邮件。

这说明不同工具的可逆性完全不同。

除此之外,还要解决一个极其危险的问题:重复执行。

假设 Agent 调用付款接口。

付款已经成功,但网络响应丢失了。

Agent看到超时,以为失败,于是自动重试。

结果可能变成重复付款。

所以带副作用的工具必须使用幂等键。

例如:

task_id = task_123
step_id = payment_04
idempotency_key = task_123_payment_04

即使同一个请求因为网络问题被发送两次,外部系统也只执行一次。

因此,一个真正可靠的 Agent 任务系统至少应该记录:

任务 ID
步骤 ID
当前状态
幂等键
执行结果
重试次数
补偿动作

到了这里,Agent 已经不再只是“LLM 调用工具”。

它开始变成一个工作流引擎。


六、支持多个模型,远不只是换一个 base_url

Hermes 能够接入不同模型服务。

表面看起来像:

model = "xxx"
base_url = "xxx"
api_key = "xxx"

但真正的 Provider 适配远没有这么简单。

不同模型服务可能使用不同的:

消息格式
Tool Call 格式
Reasoning 字段
Streaming 事件
缓存控制方式
角色顺序要求
Token 统计逻辑
异常类型

如果 Agent Loop 直接理解每一家 Provider 的格式,核心代码很快就会充满判断:

if provider == "openai":
    ...
elif provider == "anthropic":
    ...
elif provider == "other":
    ...

随着 Provider 增加,整个 Runtime 会越来越难维护。

更好的方式是引入统一的内部消息表示。

例如 Agent 内部始终使用:

{
  "role": "assistant",
  "content": "...",
  "tool_calls": []
}

然后由 Provider Adapter 转换成目标服务需要的格式。

这个设计很像编译器。

编译器不会让每一种高级语言直接适配每一种 CPU。

而是:

C / Rust / 其他语言
        ↓
      中间表示 IR
        ↓
x86 / ARM / 其他架构

Agent Runtime 同样可以:

统一消息 IR
      ↓
Provider Adapter
      ↓
OpenAI / Anthropic / 其他模型

核心控制循环只理解稳定的内部协议。

变化被封装在系统边界。

这比“支持多个模型”更有价值。

它体现的是一个经典的软件工程原则:

把变化隔离在适配层,让核心逻辑依赖稳定抽象。


七、为什么 Tool Call 的消息顺序不能随便改

很多人把 Agent 的历史消息看成普通聊天记录。

实际上,一旦引入 Tool Call,消息历史已经变成一份执行事件日志。

标准过程类似:

User:提出任务
Assistant:请求调用工具 A
Tool:返回工具 A 的执行结果
Assistant:根据结果继续处理

工具请求和工具结果通常通过 tool_call_id 建立对应关系。

如果只保留 Tool Result,却删除前面的 Tool Call,系统就不知道这个结果对应哪一次请求。

如果一个 Assistant 发起了两个工具调用:

call_1
call_2

后面必须能够明确返回:

result_for_call_1
result_for_call_2

这和函数调用很像:

result = function(arguments)

不能只有一个 result,却不知道它来自哪个 function、用了什么 arguments。

因此,Agent 的消息存储不能只保存最终文本。

还要保存:

工具调用参数
工具调用 ID
工具执行结果
Reasoning 信息
错误状态
模型使用量

它越来越像 Event Sourcing。

系统通过一连串事件,还原任务发生过什么。

这也是为什么随意删除某段 Tool Call,可能导致整个会话无法正确回放。


八、不是所有 Tool 都属于同一个层级

Hermes 中有些能力表面看也是工具,例如:

memory
todo
session_search
delegate_task

但它们和搜索网页、查询数据库、生成 PPT 并不相同。

前者操作的是 Agent 自己。

后者操作的是外部世界。

可以借用 Kubernetes 的概念理解。

Control Plane

负责系统自身的控制:

保存记忆
管理任务
恢复会话
调度子 Agent
压缩上下文

Data Plane

负责真正的业务执行:

查询订单
读取面料资料
生成图片
调用 Canva
发送邮件

为什么要分开?

因为这两类工具的风险、权限和一致性完全不同。

memory.write 失败,影响的是 Agent 未来会不会记住某件事。

payment.execute 失败,影响的是真实资金。

delegate_task 创建了一个子任务,属于 Runtime 调度。

delete_database_record 修改了业务数据,属于外部副作用。

如果所有东西都被当成普通 Tool,系统很难针对不同类型设置:

权限
审批
日志
重试
回滚
生命周期

一个成熟的 Agent Runtime,需要明确区分:

哪些调用是在控制 Agent,哪些调用是在改变现实。


九、Hermes 的“自我改进”,其实不是模型学会了新知识

Hermes 的一个重要能力是把成功经验沉淀成 Skill。

很多介绍会把它称作:

Agent 可以自我学习、自我进化。

这句话容易产生误解。

因为大多数情况下,模型参数并没有发生变化。

真正发生的是:

系统原来没有这个 Skill
        ↓
Agent 执行任务
        ↓
经历失败和修正
        ↓
把有效流程写入 SKILL.md
        ↓
未来任务加载这个 Skill
        ↓
模型输入发生变化
        ↓
未来行为发生变化

所以更准确的说法是:

系统通过修改外部策略资产,改变未来模型的条件输入和行为分布。

这是一种外部学习,而不是参数学习。

它的优点很明显:

成本低
可以查看
可以修改
可以做版本 Diff
可以人工审批
可以回滚

但它也带来了一个更危险的问题:错误经验固化。

假设某次接口异常,Agent 连续重试五次以后成功了。

它可能把经验总结为:

遇到接口错误时,应连续重试五次。

但真实原因可能只是网络短暂抖动。

这条错误 Skill 一旦进入长期策略库,未来每一次异常都可能触发无意义重试。

因此,一个真正可靠的 Skill,不应该只是一段“经验笔记”。

它应该接近一个可以治理的业务程序:

# Fabric Promotion PPT

## 适用条件

客户需要面料推广方案时使用。

## 输入要求

- 客户品牌
- 目标品类
- 面料候选清单
- 检测报告

## 执行步骤

1. 判断客户定位与价格带
2. 筛选 3—5 款候选面料
3. 从检测报告提取可验证卖点
4. 生成 Proof Card
5. 调用 PPT 工具生成初稿
6. 检查技术表述与证据来源

## 质量标准

- 所有性能数字必须有来源
- 无法验证的数据标记为 Information Not Available
- 不得把实验室指标直接等同于成衣效果

## 失败处理

- 缺少检测报告时停止生成技术结论
- PPT 工具失败时保留结构化文案

这时,Skill 已经不只是提示词。

它包含:

触发条件
输入契约
执行流程
质量门槛
异常处理
禁止事项

它正在接近一段可执行的业务逻辑。


十、没有治理的“自我改进”,可能只是自我污染

随着 Agent 不断创建 Skill,策略库会逐渐出现:

重复 Skill
冲突 Skill
过期 Skill
从未使用的 Skill
粒度过细的 Skill
错误 Skill

例如:

canva-ppt
canva-report
management-ppt
business-presentation
ppt-with-canva

它们可能描述的是同一件事,只是名称不同。

Skill 越多,模型选择正确 Skill 的难度反而越高。

所以 Hermes 中 Curator 这类机制非常重要。

它的本质不是简单整理文件,而是在做 Agent 知识库的垃圾回收和生命周期管理。

成熟的 Skill 系统至少要区分:

active
stale
archived
deprecated

并记录:

谁创建的
基于哪一次任务创建
最后一次使用时间
被使用多少次
成功率如何
适用于哪个业务范围
当前版本是什么

更严格的企业流程应该是:

执行轨迹
    ↓
提取候选经验
    ↓
测试验证
    ↓
人工审批
    ↓
版本化发布
    ↓
监控效果
    ↓
归档或回滚

没有测试、审批和版本控制的自我改进,严格来说不能叫改进。

更准确地说,那只是:

Agent 拥有了修改自己未来行为的能力。

至于改得更好还是更坏,并没有保证。


十一、Hermes 做到了哪里,又没有做到哪里

理解一个开源项目,不能只会赞美。

还要能判断它的设计边界。

Hermes 的架构非常适合:

个人 Agent
研发实验
单机或有限规模部署
中等并发
文件化 Memory 和 Skill
长时间运行的本地 Agent

它使用的很多设计都有明确优势:

SQLite 部署简单
本地文件容易查看
线程池容易实现
中央 Agent 编排容易调试
Sandbox 可以快速隔离执行

但当系统进入多租户、大规模企业平台后,会出现新的问题:

多个节点如何争抢同一个任务
执行节点宕机后任务由谁接管
如何判断任务仍然存活
如何实现租户隔离
如何管理跨节点锁
如何记录完整审计日志
如何统一管理 Skill 版本
如何在不同服务器共享 Artifact

这时,仅靠单机 Runtime 已经不够。

企业级架构可能需要进一步拆成:

Agent API
任务编排服务
模型网关
Tool Runtime
Sandbox Service
Memory Service
Skill Registry
RAG Service
Artifact Storage
Audit Service

还需要引入:

消息队列
任务租约
心跳机制
分布式锁
RBAC
统一审计
版本治理
失败恢复

所以我对 Hermes 的判断是:

它是一套非常值得研究的、单机优先的有状态 Agent Runtime,但不是企业级分布式 Agent 平台的最终形态。

它真正有价值的地方,不是可以直接照搬,而是告诉我们:

长期 Agent 系统的问题应该怎样分层,哪些状态应该分开,哪些能力必须进入 Runtime,而不能继续依赖 Prompt。


十二、拆完 Hermes,我真正学到的五件事

1. Agent 的核心不是模型,而是状态

模型可以从一家 Provider 切换到另一家。

但任务执行到哪里、哪些动作已经完成、哪些结果已经持久化,这些状态一旦混乱,再强的模型也无法补救。

2. 上下文不是数据库

Prompt 只是模型当前看到的工作区。

关键业务状态必须进入结构化系统,不能依赖模型“记得”。

3. Tool Call 不是普通函数调用

只要工具能够改变现实,就必须考虑:

并发
幂等
事务
补偿
审批
权限
审计

4. 停止任务不等于撤销现实

模型生成可以停止。

已经发出的邮件、提交的订单和产生的付款不会自动消失。

5. 自我改进必须可验证、可审批、可回滚

否则 Agent 只是在更高效地积累错误。


结语:真正的 Agent,是一个非确定性大脑外面的确定性系统

大模型有一个非常特殊的特点:

它很聪明,但它不是确定性的。

同一个问题,两次回答可能不同。

它会遗漏信息,会误判工具,会生成不完整计划,也可能因为上下文变化而改变决策。

但真实业务系统不能完全依靠这种不确定性运行。

订单不能“可能创建成功”。

付款不能“模型觉得大概完成了”。

审批不能“上下文摘要里好像通过了”。

所以 Agent 工程真正要做的,是在一个非确定性大脑外面,建立一套尽可能确定的系统:

用状态机约束任务
用数据库保存事实
用 Tool Schema 约束动作
用权限限制能力
用 Sandbox 隔离风险
用幂等保证重试安全
用补偿处理失败
用审计记录全过程
用 Skill 沉淀可复用经验

这也是我拆开 Hermes 之后,得到的最大启发。

Hermes 最值得学习的,不是它接入了多少工具,也不是它支持多少模型。

而是它试图回答一个更本质的问题:

如何让一个无状态、非确定、上下文有限的大模型,长期执行有状态、有副作用、需要恢复和治理的真实任务?

当你开始认真回答这个问题时,你做的就不再是一个聊天机器人。

你正在构建一个真正的 Agent Runtime。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐