Hermes Agent /goal 长任务运行时架构拆解:状态持久化、Judge 闭环与自主续航

拆解 Hermes /goal 如何用状态持久化、Judge 判定和自动续航,让 Agent 真正跑完长任务。
原文链接:AI 小老六

导语

Agent 长任务 最让人烦的地方,往往不是它不会做,而是它太容易停下来。

修一个类型错误,它修三处就回来说“完成了”;跑一轮测试失败,它等你再敲一句“继续”;做一次依赖迁移,中间需要人反复盯着终端,像给半自动机器喂指令。到了长程任务里,这种交互方式会把开发者拖回最原始的监督岗位:人不再写代码,但要不停判断 Agent 是否该接着干。

Hermes Agent v0.13.0 里加入的 /goal,解决的不是“让模型更聪明”这个泛问题,而是一个更具体的工程问题:怎样把一次目标变成一个可持续推进、可暂停、可恢复、可判定完成的运行时流程。

这件事的关键不在命令本身,而在命令背后的四个部件:外部状态、生命周期管理、Judge 判定、继续执行队列。它们合在一起,才让 Agent 从“等用户说继续”变成“知道自己还没干完”。

inline-01-goal-control-plane.png

图:/goal 把目标状态、Judge 判定和继续调度放到同一个控制平面里。

长任务瓶颈:不在 Token,而在会话记忆

很多人第一次做 长任务 Agent 时,会本能地把问题归因给上下文窗口不够大。窗口再长一点,模型是不是就能把事情做完?实践里往往不是这样。

当一个任务在同一会话里滚动几十轮,系统会累积大量中间输出、错误日志、临时计划和已经过期的判断。上下文变厚后,模型并不会稳定地“记得更多”,反而更容易把噪声当成线索,把未完成事项压到注意力边缘。开发者社区常把这段质量下滑区域叫做 ​Dumb Zone​:不是模型真的变笨了,而是会话里堆了太多会干扰判断的东西。

Ralph Loop 给出的方向很朴素:​不要把长期记忆放在聊天记录里​。

mermaid-01.png

图:目标、外部状态与显式停止条件构成长任务执行闭环。

这个循环背后的判断很硬:​文件系统和 Git 比模型上下文更适合承载长期状态​。Agent 每一轮从干净的输入开始,通过代码、文档、测试结果和提交记录接住上一轮的进展。会话可以丢,工作目录不能丢。

原始 Ralph Loop 甚至可以简化成一个 Bash 循环:

while :; do cat PROMPT.md | claude -p --dangerously-skip-permissions; done

这当然不够产品化,但它把长期自主执行的骨架说明白了:固定目标、外部状态、循环执行、显式停止条件。

Hermes 的 /goal 是在这个骨架上补了一套可用的运行时。

/goal 本质:给 Agent 加一个目标控制平面

普通对话里,用户的每条消息都会触发一次推理。推理结束后,系统默认认为这一轮就结束了。/goal 改掉的是这个默认假设。

当用户输入:

/goal 修复 auth.ts 中所有 TypeScript 错误,确保 lint 和单测全部通过

系统不再把它当作一条普通 prompt,而是创建一份可持久化的目标状态。之后每一轮结束时,Hermes 都会追问一个问题:最近这一轮的结果,是否已经满足最初目标?如果没有,系统自动把“继续完成目标”的提示放回执行队列。

可以把 /goal 看成一个目标控制平面:

运行时部件 负责的问题 Hermes 中的体现
目标状态 当前要完成什么,剩余多少轮,是否暂停 GoalState
生命周期接口 目标如何开始、暂停、恢复、清理、完成 GoalManager
完成判定 最近一轮是否足够满足目标 judge_goal()
继续调度 未完成时如何触发下一轮 CLI pending input / Gateway FIFO
目标细化 执行中如何追加验收标准 /subgoal

这个设计的好处是,Agent 不需要在聊天里“记住自己还有一个目标”。目标被系统外化 了,后续每一轮都由运行时重新注入必要信息。

GoalState:把目标写进数据库

Hermes 没有把 /goal 做成会话内的临时变量。它把目标落到了 ​SessionDB.state_meta​,key 类似 goal:<session_id>。这一步看起来不花哨,但很关键。

@dataclass
class GoalState:
    goal: str
    status: str = "active"          # active | paused | done | cleared
    turns_used: int = 0
    max_turns: int = 20
    created_at: float = 0.0
    last_turn_at: float = 0.0
    last_verdict: Optional[str] = None
    last_reason: Optional[str] = None
    paused_reason: Optional[str] = None
    consecutive_parse_failures: int = 0
    subgoals: List[str] = field(default_factory=list)

这份状态解决了几个实际问题。

第一,​任务可以跨会话​。今天设置的目标,中途关掉终端,明天重新打开后仍然能通过 /goal resume 接着跑。

第二,执行有预算。max_turns 默认 20 轮,避免目标因为判定异常或描述过宽而无限循环。

第三,系统能记住失败模式。比如 Judge 连续返回不可解析内容,consecutive_parse_failures 会增长,超过阈值后目标自动进入暂停态,而不是继续烧钱。

第四,目标可以变细。subgoals 让用户在执行过程中补充约束,不必推翻原目标重来。

这也是 Hermes 相比只做 session-based goal 的方案更完整的地方:它没有把“长期任务”寄托在一个长会话里,而是把任务本身变成了可查询、可恢复、可审计的状态对象。
inline-02-persistent-state.png
图:目标不再依赖聊天记忆,而是沉淀为可恢复、可审计的外部状态。

GoalManager:长任务需要状态机,而不是 while true

有了状态,还需要一个管生命周期的编排器。Hermes 的 GoalManager 提供了这组接口:

class GoalManager:
    def set(goal: str, *, max_turns: int = 20) -> GoalState
    def pause(reason: str = "user-paused") -> GoalState
    def resume(*, reset_budget: bool = True) -> GoalState
    def clear() -> None
    def mark_done(reason: str) -> None

    def add_subgoal(text: str) -> str
    def remove_subgoal(index_1based: int) -> str
    def clear_subgoals() -> int

    def evaluate_after_turn(last_response: str) -> Decision
    def status_line() -> str
    def next_continuation_prompt() -> str

从状态机角度看,/goal 的生命周期并不复杂,但每个状态都对应真实场景。
mermaid-02.png
图:/goal 在 active、paused、done 与 cleared 之间流转。

这里最容易被低估的是 ​paused​。一个长任务运行时不能只有“继续”和“结束”。网络抖动、预算耗尽、用户临时插话、Judge 配置异常,都不该把目标直接判死。暂停态给了系统一个缓冲层:任务没有完成,但也不再盲目推进。

Judge 闭环:宁可多跑一轮,也别提前庆功

/goal 能不能可靠,核心看完成判定。Hermes 把这件事交给一个辅助模型,也就是 Judge。

每一轮结束后,系统把两段信息交给 Judge:原始目标和 Agent 最近一轮回复。Judge 只需要输出一个很窄的 JSON:

{"done": false, "reason": "仍有测试失败,需要继续修复"}

判定规则故意偏保守。只有三类情况会被认为完成:

判定为 DONE 的情况 例子
Agent 明确确认目标已经完成 “所有 TypeScript 错误已修复,lint 和测试均通过”
最终交付物已经产生,且能看出满足目标 代码、报告、文档或构建产物已经落地
目标无法继续推进,需要外部输入 权限缺失、依赖不可访问、需求本身矛盾

除此之外,默认继续。

这条策略很像工程里的 false positive 控制:​误判完成的代价通常比多跑一轮更高​。多跑一轮只是多花一点时间和 token;提前结束会把半成品交给用户,后续还要人工排查它到底漏了什么。

Hermes 的 Judge prompt 也基本围绕这个原则设计:

JUDGE_SYSTEM_PROMPT = """
A goal is DONE only when:
- The response explicitly confirms the goal was completed, OR
- The response clearly shows the final deliverable was produced, OR
- The response explains the goal is unachievable / blocked / needs user input

Otherwise the goal is NOT done: CONTINUE.
"""

这不是追求“聪明判定”,而是追求“别乱放行”。在长任务里,这个取舍很对。

fail-open:Judge 挂了,任务不该跟着死

真实系统里,Judge 不是永远可靠。API 超时、网络抖动、模型返回非 JSON,都很常见。Hermes 没有把这些异常直接当成任务失败,而是设计了几层保护。

异常场景 处理方式 设计意图
Judge API 调用失败 返回 continue 外部依赖偶发失败时不中断主任务
最近一轮响应为空 返回 continue 没有足够证据时不判完成
Judge 返回非 JSON 计数并重试 容忍短期格式漂移
连续解析失败达到阈值 自动暂停 避免配置错误导致无意义循环
轮次预算耗尽 自动暂停 防止失控续跑

典型代码大概是这样的风格:

try:
    resp = client.chat.completions.create(...)
except Exception as exc:
    logger.info("goal judge: API call failed; continuing")
    return "continue", f"judge error: {type(exc).__name__}", False

这里的 fail-open 不是无脑继续。它只是在短暂异常下选择不打断执行;一旦异常变成连续模式,系统会切到 paused,让用户检查 Judge 模型或配置。

这个细节很工程化。长任务最怕两种极端:遇到一点波动就停止,或者明知判定链路坏了还继续跑。Hermes 在两者之间留了一个合理的台阶。
inline-03-judge-fail-open.png

图:Judge 失败时先保守继续,连续异常再暂停,避免长任务误停或失控。

CLI 与 Gateway:自动继续不能抢用户的话

/goal 还有一个容易被忽略的问题:当系统准备自动注入“继续执行”提示时,如果用户这时发来新消息怎么办?

Hermes 的处理原则是​用户优先​。

在 CLI 里,继续提示会进入 _pending_input 队列;在 Gateway 场景里,继续事件通过 adapter FIFO 排队。系统会确保用户显式输入优先于自动续跑提示。这样做能避免一种很糟糕的体验:用户刚想纠正方向,Agent 已经因为自动续跑又开了一轮。

mermaid-03.png

图:Judge 判定后投递继续提示,但用户输入始终优先处理。

这让 /goal 更像一个可控的后台任务,而不是一个关不掉的自动驾驶。它可以自己推进,但不会压过用户的显式指令。

Hermes 和同类方案的差异:真正拉开距离的是 持久化

/goal 这类能力在 2026 年开始快速变成 Agent 产品的标配。Codex CLI、Claude Code、Hermes 都在做,差异不在概念,而在实现边界。

能力维度 Hermes Agent Codex CLI Claude Code
目标状态 SessionDB 持久化 偏 session-based 偏 session-based
子目标 支持 /subgoal 未突出 未突出
Judge 模型 可配置 provider / model 固定策略为主 固定策略为主
多平台 CLI 与 20+ Gateway 平台 主要面向 CLI 主要面向官方环境
异常处理 fail-open、解析失败计数、自动暂停 有继续策略 有继续策略
用户中断 可转暂停 依实现而定 依实现而定

Hermes 的优势不是“也有 /goal”,而是它把 goal 做成了一个跨会话、跨平台的状态系统。尤其是 SessionDB、/subgoal 和可配置 Judge 这三点,让它更适合自托管和多入口使用。

如果只是本地 CLI 玩一轮任务,session-based 方案够用;如果希望目标跨天、跨终端、跨聊天平台继续存在,状态持久化就不再是加分项,而是基础设施。

适合交给 /goal 的任务,必须能写出验收条件

/goal 不适合所有事。它适合边界清楚、可检查、需要多轮推进的任务。

适合使用 /goal 不适合使用 /goal
大型重构、依赖迁移、补测试 一两轮就能完成的小改动
安全漏洞修复、审计整改 需要高频人工选择的产品讨论
性能优化,有明确指标 目标暂时说不清的探索性研究
长时间构建、报告生成、资料整理 需要实时确认权限或风险的操作

​目标写得越像验收单​,Judge 越容易工作。下面这类差异会直接影响 /goal 的效果。

模糊目标 可判定目标
修复 bug 修复 login 在空密码时返回 500 的问题,并补充回归测试
优化性能 将主 API P95 降到 200ms 以下,并保留现有单测通过
写文档 在 docs/api.md 补齐所有 REST 端点的路径、参数、返回值和示例
做重构 将 auth.ts 改为依赖注入模式,公开接口不变,覆盖率保持 85% 以上

一个好 /goal 最好包含四类信息:任务对象、完成条件、验证方式、边界约束。

/goal 将 auth.ts 的用户校验逻辑迁移到 AuthService,保持现有 public API 不变;补齐空密码、过期 token、重复登录三个用例;运行 pnpm lint 和 pnpm test 全部通过后停止。

这类目标不需要 Judge 猜。它只需要检查 Agent 是否给出了足够清楚的完成证据。

Judge 模型不必昂贵,但必须 稳定

Hermes 允许为 goal_judge 单独配置模型,例如:

# ~/.hermes/config.yaml
goals:
  max_turns: 20

auxiliary:
  goal_judge:
    provider: openrouter
    model: google/gemini-3-flash-preview

这是一种很实用的拆分。主任务模型要负责写代码、读文档、查问题,能力需要强;Judge 只读目标和最近一轮回复,然后判断是否继续,输入短、输出短,速度和稳定性更重要。

便宜的快速模型未必能做复杂推理,但做“是否已经明确交付”这类窄判定通常够用。真正需要关注的是格式稳定性。如果模型经常输出不可解析内容,Hermes 会靠连续解析失败保护把目标暂停,但这也说明 Judge 选择不合适。

快速体验:从更新到第一个目标

要使用 /goal,先把 Hermes 更新到 v0.13.0 或更新版本:

hermes update

启动后输入 /goal,如果能看到 goal 指令,就说明功能可用。可以先用一个风险较低的任务试水:

/goal 生成一份当前项目的 DeepResearch 风格技术报告,包含架构概览、关键模块、运行方式和潜在风险;
输出为 HTML,并在本地打开预览确认无明显渲染错误。

这类任务的好处是验收边界清楚,失败也不会破坏代码库。等确认工具链、搜索能力和前端渲染能力都配置好后,再把 /goal 用到重构、补测试或安全修复上。

如果希望报告更好看,工具配置很重要。WebSearch、文件读写、前端设计相关 skill、HTML 渲染能力都会影响最终产物。/goal 只是让任务持续推进,不能凭空补齐 Agent 没有的工具。

我对 /goal 的判断:它把 Agent 的交互单位从“回复”改成了 “完成条件”

/goal 最有价值的地方,不是少敲几次“继续”。少敲字只是表面收益。

真正的变化是,用户和 Agent 之间的契约从“回答我这一轮”变成了“持续工作,直到满足这个条件”。为了支撑这个契约,系统必须把目标、状态、预算、判定和恢复机制都工程化。Hermes 的实现恰好把这些东西补齐了大半。

它也不是银弹。目标写不清,Judge 就会摇摆;工具不给力,Agent 只能原地打转;任务涉及高风险操作,用户仍然要介入确认。但对于重构、补测、报告生成、批量修复这类长链路任务,/goal 已经把 Agent 往“能放手一段时间”的方向推了一大步。

更准确地说,/goal 让 Agent 不再只是一轮对话里的聪明助手,而开始像一个有状态的执行进程:有目标,有预算,有暂停恢复,有完成判定,也有失败保护。

这才是长程 Agent 真正需要的底座。

参考资料

  • Hermes Agent v0.13.0 Release Notes[1]
  • Claude Code: Keep Claude working toward a goal[2]
  • Codex: Follow a goal[3]
  • Hermes Agent's /goal Command Makes AI Stop Quitting Halfway Through the Job[4]
posted @ 2026-05-19 09:02  AI小老六  阅读(361)  评论(0)    收藏  举报