在冬眠中进化:Agent 的休眠、唤醒与 Token 节流策略
What(本文讲什么)
“休眠/唤醒”不是 UI 效果,而是长期运行 agent 的控制回路:在不需要大模型参与时,把上下文卸载、会话关闭、资源释放;在触发事件到来时,恢复检查点、重建上下文,并从正确的步骤继续执行。
这篇文章把它落到可实现的系统结构:
- 一条可恢复的状态机(checkpoint -> unload -> wait -> hydrate -> resume)。
- 一套可靠的唤醒源(timer/webhook/queue/file events)与它们的重放语义。
- 一套防止重试风暴与资源泄露的门禁(超时、重试、退避抖动、降级、审计)。
Problem(要解决的工程问题)
长期运行 agent 的成本与事故通常来自“无意义的活跃”:
- 频繁轮询:小事件也触发全量上下文 + 大模型推理。
- 等待期间仍占用资源:会话、KV cache、连接、锁、文件句柄不释放(资源释放)。
- 中断后从头重跑:没有检查点与幂等,重跑会重复提交副作用(幂等、审计)。
- 超时后的重试风暴:网络抖动/下游慢导致层层重试,最终把成本与延迟放大(超时、重试、降级)。
因此“休眠/唤醒”的目标不是装饰,而是:
- 把长任务变成可中断、可恢复的执行(durable execution)。
- 把等待阶段的成本与风险降到可控(资源释放、权限)。
- 把唤醒变成可审计的触发链,而不是黑盒(审计、观测)。
Principle(把休眠写成状态机:checkpoint 是第一性)
你不能指望一个 agent “一直不崩”。工程上要接受中断,并把中断变成正常路径:
- 在副作用前写检查点(checkpoint)。
- 在副作用提交点写 WAL + 幂等 key(幂等、审计)。
- 中断后从检查点恢复,且不会重复提交副作用(幂等)。
LangGraph 的 durable execution 文档明确强调了检查点/恢复/可中断执行的工程路线,适合作为这一章的“机制基底”。
参考: https://docs.langchain.com/oss/python/langgraph/durable-execution
Usage(怎么做:睡眠/唤醒的最小可行实现)
1) 状态机与数据模型
建议把任务状态至少拆成两类数据:
TaskState(可恢复状态):- 当前 step
- 已完成 steps
- 下一步候选
- 失败次数与失败原因标签
WAL(提交记录):- idempotency_key
- 资源目标(resource_targets)
- 提交结果与错误码
只有 TaskState 没有 WAL,你恢复后仍然会重复提交副作用。
2) 休眠流程(unload)
休眠的关键动作不是 sleep,而是“释放”:
- 写 checkpoint:落盘当前 TaskState(审计)。
- 关闭会话:释放 LLM client、数据库连接、浏览器 session(资源释放)。
- 只保留守护进程:用最低成本组件监听唤醒源(timer/webhook/queue)。
3) 唤醒流程(hydrate + resume)
唤醒要做三件事:
- 判定是否需要唤醒(L1 规则/小模型 gating),避免无效唤醒(降级)。
- 读取 checkpoint 并水合上下文(hydrate),但只注入必要片段(token budget)。
- 从“下一步 step”继续执行,而不是重跑(幂等)。
4) 唤醒源:timer 不是唯一答案
常见唤醒源及其语义差异:
- webhook:事件驱动,延迟低,但可能重复投递,需要幂等 key(幂等)。
- queue:至少一次投递常见,需要去重与重放处理(幂等、审计)。
- timer:可靠唤醒,但要明确“错过触发是否补触发”的语义。
- file events:适合本机工作区变更,但要防抖与合并。
timer 的可靠性在工程上很重要。systemd timers 支持持久化定时语义(例如错过触发后补触发),是常用的可靠唤醒器之一。
参考: https://www.freedesktop.org/software/systemd/man/systemd.timer.html
如果你在 Kubernetes 环境里,CronJob 是另一类常用唤醒器,它明确了 job 生命周期与并发策略边界(例如是否允许并发、失败后的处理)。
参考: https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/
5) 超时、重试与退避抖动:防止“醒来就烧干系统”
长期运行系统最常见的事故是“重试风暴”。工程上必须把重试当作危险动作:
- 每个阶段都要有超时(超时)。
- 重试必须有限次(重试)。
- 重试必须做退避 + 抖动(jitter),避免同步重试造成级联故障(降级)。
这一点在 AWS Builders' Library 里有非常工程化的总结:超时/重试/退避/抖动是系统稳定性的基本防线。
参考: https://aws.amazon.com/builders-library/timeout-retries-and-backoff-with-jitter/
一个最小生命周期管理器(伪代码)
这段伪代码强调三个边界:checkpoint、资源释放、幂等恢复。
class AgentLifecycleManager:
"""
生命周期管理器:
把长任务变成可中断、可恢复的执行。
"""
async def hibernate(self, task_id: str) -> None:
# 1) 写 checkpoint(可恢复状态)
await self.state_store.save_checkpoint(task_id)
# 2) 释放资源(会话/连接/句柄)
await self.runtime.close_sessions(task_id)
# 3) 保留低功耗监听(不包含大模型逻辑)
await self.wakeup_daemon.arm(task_id)
async def wake_up(self, task_id: str, event: dict) -> None:
if not self.gate.should_wake(event):
return
# 1) 读取 checkpoint
state = await self.state_store.load_checkpoint(task_id)
# 2) 重建上下文(仅注入必要信息)
await self.runtime.hydrate(task_id, state, event)
# 3) 从下一步继续执行(与 WAL/幂等配合)
await self.runtime.resume(task_id)
Pitfall(常见坑与防错)
- 没有 WAL:恢复后重复提交副作用(幂等、审计)。
- 没有超时:唤醒后卡死导致资源释放失败(超时、资源释放)。
- 无限制重试:失败时把系统拖进重试风暴(重试、降级)。
- 唤醒源不去重:webhook/queue 重放导致反复唤醒(幂等)。
- 休眠不释放连接:表面休眠,实际仍在耗资源(资源释放)。
Debug(排查“休眠/唤醒”系统)
排查顺序建议:
- 看 checkpoint:是否真的写入了可恢复状态?恢复后 step 是否正确?
- 看 WAL:是否生成了幂等 key?是否存在重复提交?
- 看唤醒源:是否重复投递?是否存在 missed run?
- 看超时/重试:是否出现重试风暴?退避抖动是否生效?
- 看资源释放:是否有连接/句柄泄露导致机器越来越慢?
指标与告警(把“节流”变成可验证的工程收益)
休眠/唤醒做完后,你应该能用指标证明它在生效。建议至少记录:
sleep_rate:任务进入休眠的比例。wake_rate:唤醒次数(按触发源分类:webhook/timer/queue)。false_wake_rate:唤醒后立刻判定不需要处理的比例(说明 gating 失效)。resume_success_rate:从 checkpoint 恢复后成功继续执行的比例。duplicate_commit_count:同一idempotency_key的重复提交次数(幂等)。timeout_rate/retry_count:超时与重试分布(超时、重试)。open_handles/open_connections:资源释放是否生效(资源释放)。
把这些指标接进 tracing/span 或结构化日志后,你才能在成本与稳定性上做迭代,而不是靠感觉调参(观测)。
一份可落地的 systemd timer(示例)
下面示例展示“可靠唤醒器”的形态:定时触发一个轻量守护任务,它只负责判断是否需要唤醒真正的 agent。
# /etc/systemd/system/agent-wakeup.service
[Unit]
Description=Agent wakeup gate
[Service]
Type=oneshot
ExecStart=/usr/local/bin/agent-wakeup-gate
# /etc/systemd/system/agent-wakeup.timer
[Unit]
Description=Agent wakeup timer
[Timer]
OnCalendar=*:0/5
Persistent=true
[Install]
WantedBy=timers.target
注意:Persistent=true 的意义是避免错过触发后永远不再执行(可靠性)。真实环境仍需你验证“触发语义”是否符合预期(审计)。
一份可落地的 Kubernetes CronJob(示例)
CronJob 适合做集群级唤醒门铃,它的并发策略必须显式设置,避免并发触发导致重复唤醒(并发、幂等)。
apiVersion: batch/v1
kind: CronJob
metadata:
name: agent-wakeup-gate
spec:
schedule: "*/5 * * * *"
concurrencyPolicy: Forbid
jobTemplate:
spec:
template:
spec:
restartPolicy: Never
containers:
- name: gate
image: your/agent-gate:latest
args: ["--mode=wakeup-gate"]
这类“外部唤醒器”的共同点是:它本身必须极轻量,且所有触发都必须幂等(幂等)。
Source(资料来源)
- durable execution: https://docs.langchain.com/oss/python/langgraph/durable-execution
- systemd timer: https://www.freedesktop.org/software/systemd/man/systemd.timer.html
- Kubernetes CronJob: https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/
- backoff with jitter: https://aws.amazon.com/builders-library/timeout-retries-and-backoff-with-jitter/