ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Agent 四件套:Skills、Hooks、Plugins、Cron 落地实战

Agent 四件套:Skills、Hooks、Plugins、Cron 落地实战

熟悉我手上这个项目的人应该知道,我一直在维护一个叫 Hermes Agent 的智能体框架。早期它只会对话、调一点外部接口,本质上就是个套了流程外壳的聊天机器人。真正让它从“demo”变成“可交付系统”,是从 Skills、Hooks、Plugins、Cron 这四个机制陆续进到代码库之后开始的。这篇我打算把它们挨个拆开讲透,包括为什么必须这么设计、关键代码怎么写、生产环境里真正会踩到的坑怎么处理,把我自己的取舍逻辑一并写出来。

如果你正在做 Agent 类产品,或者团队里需要长期维护一个自动化机器人,这篇可以当一个落地参考。先给结论:这四个机制不是并列的“锦上添花”,而是一个 Agent 从玩具变成生产力的四个支点——能力、感知、扩展、调度。缺一个,系统都会在某个不预期的时刻让你难受,而且越到后期越难补。

1. 为什么这次进化要同时上 Skills、Hooks、Plugins、Cron

1.1 从对话机器人到可交付 Agent 的四个缺省能力

早期版本的 Hermes 跑在一条很简单的链路上:接收用户输入,把输入塞给底座模型拿到回复,再把回复返回给调用方。这种形态解决“聊天”没问题,但一放进真实业务就会撞上四个问题。

第一道关卡是“模型到底会不会做复杂任务”。模型能规划,但规划最终必须落在稳定的、可复用的原子操作上。如果每种操作都是一段临时拼出来的提示词,不仅每次调用都要重新推理,结果还不可控。Skills 解决的就是这层问题——把操作变成有入参、有出参、有版本、有测试的方法,把“模型碰运气”变成“程序保证”。

第二道关卡是“它能不能在正确的时机介入处理”。例如用户输入里混进了恶意内容,或者某个外部工具调用超时,又或者某次会话需要做限流,你都需要在 Agent 生命周期的特定节点插入干预逻辑。这不是靠改提示词能解决的,因为提示词只是让模型“尽量”去做,而 Hooks 提供的是确定性执行。

第三道关卡是“它能不能变成别人可以独立安装的扩展单元”。很多 Agent 项目交付出去之后,每个业务方都有自己的特殊需求:有人要接内部工作台,有人要接数据库插件,如果你每次都在主干代码里改,分支很快就会失控。Plugins 就是把可变部分打包成独立模块,让主干保持稳定。

第四道关卡是“它能不能在没人叫它的凌晨三点自己起来干活”。对话机器人永远是被动响应的,但生产系统里的报表汇总、数据巡检、任务提醒都是主动行为。没有 Cron 的 Agent 就像只会等人派活的员工,你半夜三点不可能守在电脑前按回车。

1.2 四个机制在 Agent 生命周期里的分工

如果只看表面,这四个东西都像“挂载点”,但它们在 Agent 生命周期里扮演的角色完全不同。Hermes 的执行循环大致是:接收输入 → 规划拆解 → 选择工具或技能 → 执行并获取结果 → 校验输出 → 返回或进入下一轮。

Hooks 覆盖这个循环的每一个关键节点。它不改变 Agent 本身的能力,而是提供“在某个时刻插入额外逻辑”的机会。比如我可以在“执行工具之前”挂一个检查,也可以在“返回结果给用户之前”挂一个敏感信息过滤。这相当于给整个管线安上了传感器。

Skills 则集中在“执行”阶段。它把 Agent 可以调用的具体能力统一注册进一个运行仓库,无论这个能力是查内部账务系统、做数据格式转换,还是调用某个第三方 API,都被抽象成相同形态的方法。Agent 的规划器只需要知道“有哪些技能可用”,不需要理解每个技能内部怎么实现。

Plugins 属于更高一层的组装机制。一个插件可以同时注册多个 Skill、挂载多个 Hook、甚至自带一套配置和依赖。它是把“能力组”打包分发的载体,让一套 Agent 核心可以适配不同场景。

Cron 则完全在生命周期之外工作。它不是 Agent 循环的一部分,而是定时器到点之后主动发一个输入进去,从而启动一轮新的 Agent 执行。用大白话说,Hooks 让 Agent 有了反射神经,Skills 让 Agent 有了双手,Plugins 让 Agent 有了可组装的器官,Cron 给了 Agent 一个生物钟。

1.3 什么场景该用哪个:选型判断

我见过不少团队一上来就把这四个东西全上了,结果维护成本爆炸。实际上它们的性价比取决于你的场景。

如果你只是做一个内部小工具,比如把 Excel 数据自动整理成 Markdown 报告,那只需要 Skills,连 Hooks 都可能多余。如果你面向的是客服系统,用户提问五花八门,那 Hooks 里的安全检测和审计日志就很有必要。如果你要把 Agent 当成产品交付给多个团队用,Plugins 几乎必须,否则每个团队的定制需求都会和主干互相污染。如果业务里有任何“无人值守”的运维需求,Cron 就是刚需,比如每天早上自动生成前一天的运营数据日报。

2. Skills 实操:从“会聊天”到“会干活”的技能引擎

2.1 技能的抽象:为什么它不是普通的函数

我先说一个当初走过弯路的认识:Skill 不能等价于普通函数。普通函数给开发者用的,而 Skill 是给 Agent 的规划器用的,所以它必须额外携带“语义元数据”。

Hermes 里的一个技能至少包含几个部分:技能名称、所属命名空间、版本号、功能描述、输入参数声明、执行逻辑。规划器看到这些元数据后,才能判断当前用户的意图应该匹配哪个技能。描述写得太含糊,Agent 会选错;描述写得太冗长,模型上下文被撑爆;参数声明不严谨,执行阶段就会拿到不合法的值。

举个例子,我早期注册过一个技能叫“查询天气”,描述只写了“天气查询”。结果模型经常在用户问“明天需不需要带伞”的时候不去调用它,因为描述里没有“降雨概率”“带伞建议”这类关键词。后来我把描述改成“根据城市和日期查询天气,返回温度、降雨概率、风力以及是否建议携带雨伞”,调用准确率立刻上来了。这听起来像提示词调优,但本质上是技能作为“模型与真实世界之间的接口”所特有的要求。

2.2 技能注册与调用的标准路径

Hermes 的技能注册采用装饰器加命名空间的模式。这样做的优势是把技能注册逻辑与业务实现解耦,开发者只需要在函数上打一个标记,其余交给框架完成。

from hermes.skills import SkillRegistry from hermes.skills.exceptions import InvalidParameterError registry = SkillRegistry() @registry.register( namespace="utils.time", name="convert_timezone", version="1.2.0", description="将时间从原时区转换到目标时区,返回 ISO 格式时间。适合用来处理跨时区日程、会议时间换算。", parameters={ "time_str": {"type": "string", "required": True, "description": "原时间,ISO 8601 格式"}, "from_tz": {"type": "string", "required": True, "description": "原时区,例如 Asia/Shanghai"}, "to_tz": {"type": "string", "required": True, "description": "目标时区,例如 UTC"} } ) def convert_timezone(time_str: str, from_tz: str, to_tz: str) -> dict: # 这里省略具体时区运算,假设使用可靠的时区库 result = {...} return {"ok": True, "data": result}

注册之后,技能会进入一个全局能力索引。执行阶段,规划器从索引里选出候选技能,然后框架按参数声明自动校验并填充参数。我在框架里单独维护了一个参数校验层,而不是直接调用函数,主要考虑是让“模型生成的参数”和“真实函数需要的参数”之间有一道明确的边界。模型给的字段类型不对、缺字段、甚至多字段,都会被校验层拦住,并生成可供规划器理解的错误信息。

在调用层,我还给技能加了一个可选的中断机制:技能内部可以主动抛出一个SkillRequestSkillUpgrade异常,表示“当前输入我没有能力处理,请把用户引导到人工服务”。这个设计在处理长尾问题时很管用,避免 Agent 用一种看似自信但实际错误的方式硬答。

2.3 技能演进与版本管理

技能数量一多,版本管理就得跟上。Hermes 在每个技能对象上强制要求版本号,并且让注册表天然支持一个技能名称下有多个版本。默认情况下,Agent 调用的是latest版本,但调用日志里必须写明具体是哪个版本执行了任务。

为什么要保留多版本而不是直接覆盖?我实际遇过的场景是:某个数据分析技能升级了算法逻辑,结果旧报告模板的消费者没有同步更新,生成的数据格式不兼容。如果没有版本回溯能力,线上系统就只能干等新代码发布。现在凡是高风险技能,我都会让注册表保留最近三个可用版本,并在技能描述里注明“如果你需要兼容旧格式,请调用 utils.data_analyse v1.1”。

另一个经验是“技能别做得太胖”。一次只做一件事,做好,做可验证。比如“发送通知”技能绝不附带“检查用户权限”的逻辑,权限检查应该由 Hook 或者上层编排去负责。技能太胖,回归测试就难写,而且模型也更容易错误调用。

3. Hooks 实操:用事件钩子做安全检查、审计日志与流量控制

3.1 事件模型:了解 Hook 可以在哪里挂

Hooks 的官方定义就是在 Agent 生命周期事件上注册回调函数。Hermes 把生命周期分成了几个关键事件节点,我用一张表列出来:

事件名触发时机典型用途
pre_receive原始输入进入 Agent 前输入清洗、JSON 解析,提前拦截空会话
post_receive输入被解析成结构化消息后敏感词检测、会话级限流、语种判断
pre_planning规划器启动推理前注入业务约束、屏蔽某些技能
post_planning规划器产出计划后计划校验、预算控制、对计划做二次排序
pre_tool_call即将调用某个工具/技能前工具白名单、鉴权、成本预估
post_tool_call工具执行完成返回结果后结果清洗、错误判断、脱敏
pre_response最终回复发给用户前输出安全过滤、敏感信息脱敏、格式统一
on_error任意环节抛出异常时告警、降级、记录异常上下文

一开始我在 Hermes 里只设计了四个事件节点,后来在生产使用中陆续加了pre_planning和post_tool_call。因为实际运营中我发现,只拦截“输入”和“输出”远远不够,很多问题发生在工具执行的边界。比如某个搜索技能返回了一段包含用户手机号的文本,需要在进入模型上下文之前就把手机号字段抹掉,等输出再做就不合适了,因为模型可能已经把它组织进回答里了。

3.2 注册优先级与异常隔离机制

多个 Hook 挂同一个事件节点时,执行顺序必须可预期。Hermes 使用一个整型priority字段控制,数字越小越先执行。我一般建议把“安全类”Hook 的优先级放到 1 到 50,把“日志类”放到 100 到 150,把“业务类”放到 200 以后。这样即使业务同学在后期加了一堆个性化逻辑,安全基线也不会被绕过去。

代码实现上,事件节点会维护一个按优先级排列的回调链表。这里有一个被很多人忽略的细节:如果某个 Hook 内部抛了异常,框架默认会中断整个调用链,直接进入on_error流程。用术语讲这叫 fail-closed 策略,好处是安全逻辑稳稳兜底,坏处是 Hook 本身挂了会连累主流程。因此我做了两类 Hook 标记:critical=True的失败即中止,critical=False的失败只记日志不阻断。安全检测一律critical=True,缓存和统计类一律critical=False。

from hermes.hooks import hooks, HookRejected @hooks.register(event="post_receive", priority=10, critical=True) def block_sensitive_input(context): text = context.structured_message.text banned_markers = ["内部令牌", "AK/SK样本"] for marker in banned_markers: if marker in text: raise HookRejected("输入包含敏感标识,已拦截。")

3.3 日志、审计、限流:三个高价值实践

讲完机制,来点实际的。我在 Hermes 里长期维护的几个 Hook 可以给大家参考,按性价比排序的话,前三名是审计日志、限流、输出脱敏。

审计日志 Hook 挂在pre_tool_call和post_tool_call上,把每一次工具调用的用户标识、技能名称、入参摘要、出参摘要、耗时、状态码统一打进结构化日志。这个 Hook 是纯透明的,业务调用方完全无感,但它在排查问题的时候作用极大。有一次线上用户反馈“Agent 答非所问”,查了一圈对话记录都没发现异常,后来就是靠工具调用日志发现它把查询日期的入参写错了年份。没有钩子日志,这种问题几乎无法复现。

限流 Hook 我挂在post_receive,按用户维度做计数器。设定一个滑动窗口,比如 1 分钟最多 30 次请求,超过就返回一个明确提示而不是直接报错。设计限流要考虑“Agent 会话”和“轮次提问”的区别,某些深度会话一轮要多次工具调用,所以限流键建议用“会话 ID + 用户 ID”,而不是只按用户 ID。

输出脱敏 Hook 挂在pre_response。做法是配置一组正则,把手机号、身份证号、银行卡号替换成*。这里最容易踩坑的是误伤,比如把订单号里的数字段当手机号抹了,所以规则一定要先用真实样板数据做回归测试。

4. Plugins 实操:给 Agent 装上可独立迭代的插件生态

4.1 Plugins 和 Skills 的分工边界

我在社区里分享 Hermes 架构时,最常被问到的就是“Plugin 和 Skill 有什么区别”。这两者确实长得像,因为一个插件可以包含多个技能,但它们的抽象层级完全不同。

Skills 是单点能力的封装,解决“某件事怎么做”。Plugins 是整套能力组的封装,解决“某个业务怎么接入”。职责边界在哪?我自己定的规则是:插件负责自己业务的配置、依赖、初始化状态、生命周期管理;技能只负责无状态的执行。插件可以注册技能,也可以挂 Hook,还可以暴露自身的健康检查接口。

举个例子,某企业内部工作台插件会做这些事情:读取自己目录下的配置文件,建立与工作台 API 的连接池,注册“创建审批单”“查询审批进度”“撤销审批”三个技能,并在pre_tool_call阶段校验调用者是否有权限访问对应工作台空间。看,这三个职责如果都直接塞进主干,主干就没法看了。而用插件隔离,Agent 核心代码完全不需要知道工作台是什么。

4.2 插件的目录结构与加载流程

Hermes 的插件采用目录 + 清单文件的组织方式。一个最小插件长这样:

plugins/daily_reporter/ __init__.py plugin.json skills/report.py hooks/guard.py

plugin.json声明插件的基础信息与入口:

{ "name": "daily_reporter", "version": "1.0.0", "entry": "main_plugin:DailyReporterPlugin", "dependencies": { "python_modules": ["httpx>=0.27"], "hermes_min_version": "0.9.0" }, "hooks": ["hooks.guard:check_report_permission"], "skills": ["skills.report:register_report_skills"] }

加载流程在框架侧是一个很典型的四步操作。第一步是发现目录,框架会扫描plugins/下所有包含plugin.json的文件夹。第二步是解析清单,校验必需字段。第三步是做依赖检查,缺失第三方库时报错且跳过加载,这一步很重要,避免插件导入阶段抛出异常把整个 Agent 拖垮。第四步才是真正导入入口模块并实例化插件类。

插件类实现一个最简单的生命周期接口:

class DailyReporterPlugin: def __init__(self, context): self.context = context def start(self): # 注册技能、挂载 hook、初始化外部连接 ... def stop(self): # 释放连接、注销技能、清理状态 ...

4.3 插件之间的状态隔离与通信

插件一旦多了,就必须考虑隔离问题。Hermes 默认每个插件实例拥有独立的配置字典和缓存空间,插件之间不能直接拿到对方的内部对象。如果有跨插件通信的需求,只能通过框架提供的SharedEventBus发布订阅。比如销售统计插件需要读取订单插件的数据,不是直接导入对方的类,而是订阅订单插件发出的OrderSettled事件。这个设计的价值在团队协作时非常明显:两个团队开发两个插件,只需要约定好事件结构体,完全不需要关注对方内部实现。

隔离带来的代价是调试变难。为此我给插件框架加了一个debug_mode,打开后会在插件日志里自动带上插件名和来源文件,并且允许通过管理接口在线查看每个插件当前持有的状态对象。在实际运维里,这个功能比断点调试好用得多,尤其是线上环境不可能随便加断点。

4.4 插件与主干版本的兼容策略

插件不是装上就万事大吉。Hermes 给每次发布打上向后兼容标记,插件清单里的hermes_min_version就是干这个的。框架启动时如果发现主版本核心和插件要求的版本有冲突,会把插件标记为“手动启用”而不是默认加载,同时发送一个事件通知管理员。这种做法虽然麻烦了一点,但比插件隐式暗坑安全得多。

这里有一个我强烈建议的规避动作:给插件目录做完整的“虚拟环境”式依赖分离,或者至少是依赖清单严格隔离。Python 项目的环境依赖冲突问题在插件多到一定程度后真的会爆,你根本无法想象某插件升级一个小版本会连带影响多少下游。

5. Cron 实操:定时任务让 Agent 具备主动性

5.1 一个交互式 Agent 为什么需要调度器

如果 Agent 只能靠用户输入驱动,它能处理的事情就永远是“有人提出才做”。可实际业务里大量任务是没有用户在线等待的:凌晨按小时采集行情、每天上午九点生成日报、每天晚上十二点清理过期缓存、每周五下午给团队发工作周报。这些需求不可能挂在一个“对话入口”上等待触发。

Cron 机制就是为解决这种“主动发起”而设计的。它在 Hermes 中的位置独立于执行循环之外,是一个常驻的调度器。调度器到点之后,构造一个新的会话输入,把它投递进 Agent 的执行循环。从核心循环的角度看,这跟用户发来一条消息没有区别,都是从“接收输入”开始走完整条链路。

5.2 调度器的核心设计:表达式、生命周期、持久化

Hermes 的调度器使用六段 cron 表达式,从左到右依次是秒、分、时、日、月、周。例如0 0 9 * * *表示每天上午九点整触发。六段还是五段,不同框架有不同选择,我选六段的理由是更精细的控制,比如某些批次任务希望整分钟但精确到秒的错峰执行。

from hermes.cron import CronScheduler scheduler = CronScheduler() @scheduler.job( job_id="daily_report", schedule="0 0 9 * * *", timezone="UTC", miss_policy="run_latest", ) def daily_report_job(): payload = {"job": "daily_report", "target_date": "last_business_day"} result = scheduler.trigger_agent(payload) return result.status

调度器还必须解决三个工程问题:任务持久化、重启补偿、防止重复执行。

任务持久化:所有 cron 任务定义必须落到数据库或至少是磁盘文件,而不是只存在于内存。这样进程重启后,调度器能够恢复全部任务。我在生产里用了一个简单的 SQLite 表,字段包括 job_id、表达式、目标对象、启用状态、下次运行时间。调度器每次启动都把这张表加载进内存,并重建时间堆。

重启补偿:如果 Agent 进程在触发时间点恰好处于重启状态,这个任务要么错过,要么被补偿执行。两种策略我做成可配置的,miss_policy="skip"是直接跳过,miss_policy="run_latest"是启动后立刻补跑最新一次错过的任务。对于日报类任务,一般用run_latest比较合理;对于告警推送类任务,用skip更稳妥,否则你可能会在凌晨四点把一堆历史告警补发给用户。

防止重复执行:分布式部署时多实例同时运行,同一个任务会被多个进程各触发一次。最简单的防重手段是加“分布式锁”,以 job_id 为锁键,谁抢到锁谁执行。单机部署的话,用进程内锁就够了。我见过把简单问题复杂化的,上一个全局锁服务,结果锁服务一挂整个调度停摆,实际上单实例加数据库唯一约束就能覆盖绝大多数场景。

5.3 时区、错峰与重叠执行的坑

时区是 cron 任务第一大坑。任务表里永远用 UTC 存储表达式,展示给用户时再按业务时区换算。否则一旦有人改服务器时区,全表任务时间都要跟着漂移,排查起来非常痛苦。我在 Hermes 的 job 表里加了一个timezone字段,每次计算下次运行时间时从配置里读取,避免依赖运行环境时区。

错峰是另一个常常被忽略的细节。大量任务如果都安排在整点,会造成一次性请求毛刺。比较好的做法是在任务初始化时给表达式加一个随机偏移,比如把“每小时执行”变成“每小时的第 7 分 30 秒执行”。这个偏移量在 job 创建时就固定下来,不会因为重启而改变,避免重复触发。

重叠执行也要防。比如某个数据同步任务本身跑得比预期久,还没有结束时下一个周期又来了。调度器需要检查任务“当前是否正在运行”,如果正在运行则跳过本轮。Hermes 在 job 执行开始时写入running标记,执行结束清除。如果进程意外崩溃导致标记没清除,则在启动时重置所有running标记。

6. 组合实战:用四件套搭一个准生产级客服助手

6.1 场景假设

假设我要给 A 公司做一个内部客服助手“X 助手”。它的使用场景是员工在工作台里提问:“我的加班申请批了吗”“帮我查一下上个月的报销进度”“每天自动汇总新提交的工单”。我不打算改客服系统的源码,只是把 X 助手作为一个旁路系统接入。

这个场景正好用满四件套:Skills 承载客服业务的具体查询能力,Hooks 做权限与脱敏拦截,Plugins 打包整个客服对接能力,Cron 做每天早上的工单汇总推送。

6.2 四件套的具体装配

先装 Plugins。我写了一个oa_assist_plugin,它负责初始化客服系统 API 客户端,读取一个只读凭据配置,防止 Agent 越权调用写接口。它注册三个技能:query_leave_approval、query_expense_status、query_ticket_stats。这三个技能内部都不关心“这个提问者是谁”,它们只接收员工 ID 作为必填参数。

权限控制放在 Hooks 里。pre_tool_call阶段会检查“当前会话的员工 ID”与“技能参数的员工 ID”是否一致,不一致就拒绝调用。这个设计保证 X 助手可以回答 A 员工关于 B 员工的报销进度,除非 A 是该业务线的审批人。审批人群组关系从插件配置里读取,不写死。

安全问题也由 Hook 兜底。pre_response阶段会把技能返回结果做一次字段级脱敏,所有金额保留最后三位小数以外的部分会被打码。这里我试过很多轮,最后还是放弃用提示词强制模型脱敏,因为模型会遗漏,而是直接在后置 Hook 里做强校验,对 JSON 字段做递归匹配,不匹配的结构直接置空。

Cron 部分配置了两个任务。第一个任务是每天早上九点汇总前一天新增工单,按类型分类后,生成一段 Markdown 推送到客服主管的审批通道。第二个任务是每天凌晨三点巡检一次 API 连接状态,连接异常就发一条告警给运维群。两个任务都用了独立的job_id,时区统一在 UTC,表达式固定带秒数,错峰 30 秒。

最后是 Skills 内部的执行细节。查询类技能统一入口是一个类模板,接收员工 ID 和查询类型,先用内部缓存查一遍,命中就不请求外部 API。缓存过期时间按业务敏感度分别设置,报销进度缓存 15 分钟,审批状态缓存 5 分钟。技能返回结构统一是{ "code": 0, "data": {...}, "meta": {...} },方便 Hook 做统一校验。

6.3 完整效果与回收配置

装配完成后,X 助手的行为是:人工提问走对话链路,Hook 先拦截异常,规划器匹配技能,技能返回结果出 Hook 做权限复查,最后脱敏输出。定时任务每天自己启动一轮会话,主动生成日报。整个系统跑下来,Agent 核心代码不需要知道客服系统长什么样,所有业务差异都压缩在插件目录里。

组装代码的主流程可以简写成这样:

from hermes import HermesAgent from hermes.plugins import PluginLoader agent = HermesAgent() loader = PluginLoader("./plugins") loader.discover() loader.load("oa_assist_plugin") # 四个模块由插件自动注册,不再需要手工挂载 agent.start()

如果你在给自己的 Agent 做架构,我的建议是先照着这个模式搭一遍最小骨架,再往里填业务。先别急着追求自动化智能调度,先把“哪些技能可调、哪些节点可挂、哪些插件可载、哪些任务可跑”这四个索引建立起来,后面加能力就是填表。

7. 高频报错与避坑速查表

最后整理一些我在 Hermes 上实际遇到过的问题,按“现象、原因、解决方案”的格式做成速查表,方便你已经上了系统之后排查。

现象根因解决方案
技能偶尔被调用,偶尔被跳过技能 description 写得含糊,模型无法稳定匹配按“使用场景 + 输入字段 + 输出内容”重写描述
两个插件都注册了同名的技能技能命名空间没有规划好强制技能名带命名空间前缀,例如oa.query_expense
Hook 里抛异常导致主流程中断critical=True的 fail-closed 策略生效确认该 Hook 是否应设为critical=False
日志 Hook 拖慢整体响应日志同步写磁盘阻塞事件循环日志写入改异步队列,批量落盘
cron 任务重启后丢了任务定义只在内存里使用数据库或文件持久化 job 表
同一任务在凌晨被补偿执行了三次miss_policy配置成run_latest,且日志采集任务互相叠加明确业务方需要的补偿策略,告警类任务改为skip
插件更新后其他插件行为异常依赖版本冲突且缺少隔离插件依赖清单固定版本,并在测试环境跑全量回归
定时任务在分布式部署下重复触发没有跨实例锁增加基于数据库的分布式锁或抢占锁

还有一些零散的实用经验,我随手记在这里。写 cron 表达式时,建议先把下次运行时间打印出来确认,我见过太多人把0 0 9 * * *写成0 9 0 * * *,晚上九点跑任务还一脸懵。给技能加参数校验时,尽量用白名单枚举而不是自由字符串,比如查询类型只允许approval、expense、ticket三个值,模型拿到的入口越窄,错误越少。插件开发时,每一个新插件都要准备一个stop()幂等实现,热重载时你会感谢这个决定。

关于四个模块的时间投入比例,我的体会是:Skills 占三成,Hooks 占三成,Plugins 占两成,Cron 占两成。前两者是系统是否可靠的关键,后两者决定了系统能走多远。很多人一开始对 Cron 不以为然,等到线上出现了那种“必须在凌晨自动完成否则业务就断档”的需求,才意识到每天按一次回车然后回家过夜的方案根本撑不住。按我自己的项目节奏,现在凡是新建 Hermes 服务,我都会先把任务表建好,哪怕里面暂时只有一个心跳任务,至少让“主动执行”的路径从第一天就存在。 好,以上这些就是我在 Hermes Agent 上推进 Skills、Hooks、Plugins、Cron 四个机制时完整的实操经验。希望这四件套的组合能帮你把 Agent 从能聊两句的 demo,真正变成能交付、能独立运维、能按时干活的系统。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进