深入gnhf编排器架构:状态机如何让AI代理整夜循环不丢一行代码
【免费下载链接】gnhfBefore I go to bed, I tell my agents: good night, have fun项目地址: https://gitcode.com/gh_mirrors/gn/gnhf
gnhf(good night, have fun)是一个 AI 代理过夜运行编排器:睡前一条命令,它就让编码代理(Claude Code、Codex、Copilot 等)朝着你的目标整夜循环工作——每一轮迭代完成一小步可验证的改动,成功即自动提交,失败即自动回滚。你醒来时,得到的是一条装满干净提交的分支和一份完整日志。它的核心秘密,就是 orchestrator.ts 里那台精心设计的状态机。
🌙 gnhf 编排器:AI 代理的“过夜模式”
传统用法是:你在终端里盯着 AI 写代码,它一卡壳、一出错就得你接手。gnhf 换了一个思路——把“盯着”这件事交给状态机。
你只需在一个 Git 仓库里执行一条命令:
gnhf "在不改变功能的前提下降低代码库的复杂度"
然后安心睡觉。gnhf 会自动:
- 校验工作区是否干净,并创建独立的
gnhf/分支 - 每轮迭代生成一份提示词,调用你配置的代理(默认
claude) - 解析代理返回的结构化结果(agents/types.ts),决定提交还是回滚
- 直到达到迭代上限、token 上限、你设定的停止条件,或你按下停止
整个运行过程由Orchestrator类驱动,它继承自 Node.js 的EventEmitter,通过事件把状态推送给终端界面(renderer.ts),所以你能实时看到迭代数、token 消耗和提交计数。
🔁 核心状态机:驱动整夜循环的 4 个状态
gnhf 的状态机定义在 orchestrator.ts 的OrchestratorState接口中,只有 4 个主状态:
| 状态 | 含义 | 触发条件 |
|---|---|---|
running | 正在执行某轮迭代 | 每次迭代开始时 |
waiting | 指数退避中,暂停等待 | 代理连续硬错误后 |
aborted | 运行已中止(达到上限或连续失败) | 连续 3 次失败、token 超限、永久错误 |
stopped | 优雅停止完成 | 两次 Ctrl+C 或 SIGTERM |
主循环的逻辑非常直白(orchestrator.ts):
- 迭代前检查:是否达到
--max-iterations或--max-tokens?是则中止 - 执行迭代:构建提示词 → 调用代理 → 等待结构化结果
- 迭代后检查:记录成败 → 判断是否触发
--stop-when停止条件 - 失败熔断:连续失败达到
maxConsecutiveFailures(默认 3 次)立即中止 - 退避等待:有硬错误时按
60秒 × 2^(n-1)指数等待,然后回到第 1 步
这个“检查—执行—检查”的节奏,正是状态机不丢工作的第一个原因:任何异常都只会把循环推回安全点,而不是带着脏状态继续跑。
✅ 每轮提交与回滚:“不丢一行代码”是如何保证的
这是 gnhf 最巧妙的部分。每轮迭代结束时的处置逻辑:
- 成功→ 立即
git add -A并创建独立提交(git.ts),并把本轮摘要、关键改动、关键学习追加到notes.md - 代理自报失败→
git reset --hard硬回滚(git.ts),工作区恢复原样 - 代理崩溃(硬错误)→ 同样回滚,并进入退避等待
- 提交本身失败→ 唯一不立即回滚的情况:gnhf 保留未提交的工作,并在下一轮提示词里注入“修复指令”,让下一轮代理先修好提交再继续(orchestrator.ts)
也就是说,代理的任何胡作非为都只存活到那一轮迭代结束。成功的工作被固化为提交,失败的工作被彻底清除。你早上醒来时,分支上的每一个提交都是“成功过验证的工作”,可以逐个 cherry-pick 或 revert,互不牵连。
还有一个细节:提交时强制关闭 GPG 签名(-c commit.gpgsign=false),避免自动循环被密码弹窗卡死——这是为“无人值守”场景专门做的防御。
⚠️ 故障处理:三级故障分级与指数退避
整夜运行最怕的不是单次失败,而是“雪崩式失败”。gnhf 的解法是把故障分成三类,各给不同的待遇(orchestrator.ts):
- 代理自报失败(success=false):说明循环本身健康,只是这一步走不通 → 直接进下一轮,不等待
- 硬错误(代理进程崩溃、网络超时等):环境可能暂时性出问题 → 触发指数退避,第 1 次等 60 秒、第 2 次 2 分钟、第 3 次 4 分钟……(orchestrator.ts)
- 永久错误(如 Claude 额度用尽):重试毫无意义 → 立即中止,并打印运行日志路径
另外两个保命设计:
- 零进展熔断:一轮既没改文件、也没产生新学习 = 自报失败,计入连续失败,防止代理在原地打转烧 token
- token 上限中途拦截:
--max-tokens达到时立刻中止当前迭代,账单不会失控
同时 gnhf 会默认开启防休眠(sleep.ts):macOS 用caffeinate、Linux 用systemd-inhibit、Windows 用SetThreadExecutionState,保证机器不会在你睡着时自己“下班”。
🛑 优雅中断:两次 Ctrl+C,两种结局
过夜运行必然要处理“我想停”这个场景。gnhf 的中断本身也是一台状态机(interrupt-state.ts):
| 你的操作 | 状态机处置 | 结果 |
|---|---|---|
| 第 1 次 Ctrl+C | request-graceful-stop | 让当前迭代跑完(或提前结束退避等待),再干净退出 |
| 第 2 次 Ctrl+C | force-stop | 立即中止,回滚未提交内容 |
| SIGTERM | force-stop | 立即强停 |
| 已中止状态下再按 | exit | 直接退出界面 |
优雅停止的关键在于:它只标记、不抢跑(orchestrator.ts)。状态机先记下gracefulStopRequested,等主循环在迭代间隙自然收敛后才真正停机。这样即使你半夜被闹钟吵醒按了一下 Ctrl+C,也不会出现“提交提交到一半”的半成品状态。
🧠 notes.md:跨迭代的共享记忆
状态机保证“不丢代码”,那“不丢上下文”呢?答案是一个纯文本文件notes.md:
- 每轮迭代前,代理被要求先读
notes.md了解历史(iteration-prompt.ts) - 每轮结束后,编排器自动把摘要、关键改动、关键学习追加进去(run.ts)
- 所有提示词、停止条件、提交规范等元数据都保存在
.gnhf/runs/<runId>/下,并且被本地忽略,不会污染你的分支
这让每一轮代理都像是“从昨晚接着干的人”,而不是失忆的陌生人。
📁 关键源码路径速查
想深挖实现?从这几个文件入手:
- 状态机主循环:src/core/orchestrator.ts
- 运行元数据与 notes.md:src/core/run.ts
- 中断状态机:src/core/interrupt-state.ts
- Git 提交与回滚:src/core/git.ts
- 迭代提示词模板:src/templates/iteration-prompt.ts
- 代理输出 schema:src/core/agents/types.ts
- CLI 入口:src/cli.ts
- 代理技能说明:skills/gnhf/SKILL.md
🚀 快速上手 gnhf 过夜运行
- 安装:
npm install -g gnhf - 准备:在 Git 仓库中确保工作区干净(空目录请先
git init) - 启动:执行
gnhf "<你的目标>",建议加上限更安心:
gnhf "reduce complexity of the codebase" --max-iterations 10 --max-tokens 5000000
- 进阶:
--worktree可让多个代理在隔离 worktree 中同时干活;--stop-when支持用自然语言设定停止条件;在已有gnhf/分支上重跑gnhf即可断点续跑
一次运行结束后,gnhf 会打印退出摘要:分支、耗时、迭代数、token 总量、diff 统计和复盘命令。从此,你只需要在睡前说一句:good night, have fun。
【免费下载链接】gnhfBefore I go to bed, I tell my agents: good night, have fun项目地址: https://gitcode.com/gh_mirrors/gn/gnhf
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考