本页目录
Lesson 07 — 时段预算与常驻可观测:睡觉时的钱和心跳
本课目标:管住常驻的最后两个风险——成本累积(每班都正常,一天 N 班加起来烧穿)和静默失败(进程死了,「没消息」被误读成「没变化」)。四件套:时段钱包、自适应扫描、心跳缺勤、日报。
学完你能回答面试官那句:「你的 Agent 半夜自己跑,怎么保证不烧穿账单?它死了你怎么知道?」
0. 起点:agent-ops 管不到的两件事
agent-ops L02 的钱包管一条轨迹——单次运行超预算会刹车。但常驻 Agent 一天自主跑 N 次,每次都在预算内,总量照样失控;agent-ops L07 的 run summary 是跑了才有——进程死了连 summary 都不出,没人问就没人发现。这两个风险都活在「轨迹之间」,是 L00 风险地图里成本累积与静默失败的最后收口。
1. 时段钱包:第三层预算
请求级(ops-L04 限流) 单次调用 —— 管 QPS
轨迹级(agent-ops L02 钱包)单次运行 —— 管这次别跑飞
时段级(本课) 一天 N 次 —— 管今天别烧穿
check_budget() 三态:ok(<80%)→ degrade(软线,该省着花)→ pause(硬线,今天到此为止)。关键的分层纪律:pause 挡的是下一班要不要开跑,不打断进行中的班次——进行中的自有轨迹钱包管,两层各自执法不越权。code.py Part 1 实录:
第3班花 2000 → 累计 10500/10000(105%)🛑 pause
第4班到点 → budget_paused(挡在门口,连 job 都不开)
次日到班 → researched(时段钱包按日翻篇)
每班结束,daemon 把轨迹钱包的 token_usage 记进当日总账(add_usage)——轨迹层的计量成为时段层的记账凭证,三层预算是串起来的,不是三套平行系统。
2. 自适应扫描:安静的信源不配每天扫
L02 证明了大多数日子「确认无变化」——那为什么还每天扫?note_scan_result 把每班的结局反馈给调度表:
- 连续无变化:
streak+1,有效间隔 = 基础间隔 × min(2^streak, cap),下一班 = 本班 + 有效间隔; - 有变化:streak 归零,立即回到基础网格(
mark_fired已排好的班点); - 信源故障:streak 不动——「没能看到」既不证明世界安静(不该退避)也不证明有变化(不该回冲)。L02 的纪律在退避决策里再次生效。
🎯 核心认知:退避倍数取 2 的幂 → 班点仍落在 L01 的基础网格上(网格没被破坏,只是跳过一些格子);cap=8 保证最长 8 个基础周期必有一扫(退避不能退到「实际上不再盯了」)。取舍:退避省的是扫描成本(本来就便宜)+ 无变化研究判断成本,代价是变化发现延迟最坏拉长到 cap 倍——盯得越松,醒得越晚。cap 的紧松就是这两者的汇率。
3. 心跳与缺勤:安静和死了必须可区分
L04 教会 Agent 沉默——但沉默的系统和死掉的系统,从外面看一模一样。心跳把两者分开:
beat():daemon 每 tick 一笔(「我还活着」的最便宜证明——沉默是判断,心跳是证明);check_absence():启动体检问「上次心跳距今多久」——超过absence_alert_hours= 缺勤,alert 落箱:「缺勤期的世界变化没人看」。首次启动无心跳历史不算缺勤(没上过班谈不上旷工)。
这是 L02 纪律的服务级重演:内容层「没能看到 ≠ 没有变化」,服务层「没有通知 ≠ 一切正常」。局限的诚实标注:自己给自己测缺勤只能在「复活后」发现(死着的时候没人报警)——实时发现需要外部 watchdog(进程管理器健康检查 / 另一个进程盯心跳表),本课给了数据结构,外部盯梢留练习。
4. 日报:一天一行服务体检
📊 日报 2026-07-17 | 班次: 4成/0败/0待批 | 花费: 14000/200000 token(7%,4 班)
| 打扰: 0立即+4摘要(配额尽降级 0)| 告警: 0 | 心跳: 4 tick | ✅
聚合四本账:班次(jobs)、花费(时段钱包)、打扰(inbox,含 quota_exhausted 审计)、心跳。与 agent-ops L07 的分层:run summary 是一次运行一行(轨迹体检),日报是一天 N 次运行一行(服务体检)。日报本身走 digest 通道投递——它是「今晚看」的内容,不配打扰。
⚠️ 诚实标注:jobs 的时间戳走真实时钟(agent-ops 资产不为本课改),日报按真实日期聚合;
code.pyPart 4 因此把 FakeClock 对齐到真实 now。
5. 流派对比:常驻的成本怎么管?
| 流派 | 做法 | 取舍 |
|---|---|---|
| ① 不管,月底看账单 | 云账单做事后审计 | ✅ 零开发;🚫 发现即既成事实(L00 风险地图的原样) |
| ② 压低单次预算 | 把轨迹钱包调小 | ✅ 复用现有机制;🚫 量纲错了——单次 5k×一天 40 班照样 200k;且挤压正常班次质量 |
| ③ 时段钱包 + 降频(本课) | 日预算三态 + 无变化退避 | ✅ 量纲正确(管总量)、退避从源头省(少跑班)、pause 有底线;🚫 多一张表多一层判断;变化发现延迟被拉长(cap 控制) |
| ④ 平台配额(API 供应商月限额) | 供应商侧硬顶 | ✅ 最后保险丝,必设;🚫 粒度太粗(全应用共享)、触发即全停——它该是兜底不是日常闸门 |
选 ③ + ④ 兜底:与 agent-ops L01 的「步数预算 + recursion_limit」同构——业务限位(可配、能收尾)在前,保险丝(粗暴、崩溃式)在后,永远不该靠保险丝过日子。
6. 跑起来
cd ambient-agent-lessons/07_period_budget
python code.py # 零 API、零联网、零等待
7. 落地清单
| 文件 | 改动 | 如何验证 |
|---|---|---|
src/research_assistant/period_budget.py |
新增:时段钱包(add_usage/check_budget 三态)+ 自适应扫描(note_scan_result)+ 心跳(beat/check_absence)+ 日报(build_daily_report) | 见下 |
src/research_assistant/daemon.py |
四处开关内接线:cycle 前预算门(pause→budget_paused);cycle 后记账;step 后心跳+退避反馈;startup 缺勤体检 | pytest tests/test_daemon.py -q(17 个不回归) |
src/research_assistant/config.py |
新增:enable_period_budget/period_budget_tokens/period_soft_ratio/enable_adaptive_scan/adaptive_backoff_cap/enable_heartbeat/absence_alert_hours(默认全关) |
— |
tests/test_period_budget.py |
新增:17 个测试(累计与跨日/三态/退避封顶与回网格/failed 不动 streak/心跳/首启≠缺勤/日报聚合/daemon 四处接线) | pytest tests/test_period_budget.py -q |
验收
cd portfolio-projects/research-assistant
python -m pytest -q # 306 + 17 = 323 passed
python -m pytest tests/test_period_budget.py -q # 17 passed
8. 本课在两条主线上的位置
- 倒置主线:第⑥环(谁守着它)收口——守夜从「人守着进程」变成「预算守钱、心跳守命、日报守账」。至此六个环节全部倒置完毕,L08 开始对着 L00 基线算总账。
- 注意力经济主线:本课管的是这条主线的钱的一半:时段钱包给「睡觉时的钱」设硬顶,退避让安静的信源少花钱;日报把两种预算的消耗(钱 + 注意力配额)合并成一行可审计的账。
🎯 面试话术
「常驻 Agent 的成本我管三层:请求级限流管 QPS、轨迹级钱包管单次运行、时段级钱包管一天总量——单次都正常、总量烧穿的洞就是第三层补的。时段钱包三态:80% 软线提醒省着花,100% 硬线只挡下一班、不打断进行中的班次,两层预算各自执法不越权。再配自适应退避:连续无变化间隔翻倍封顶 8 倍,安静的信源从源头少跑班,一有变化立即回基础网格;信源故障不动退避——没能看到既不证明安静也不证明有变化。
它死没死怎么知道?daemon 每 tick 写心跳——沉默是判断,心跳是证明;重启体检时心跳缺口超阈值就发缺勤告警,『这两天的世界变化没人看』是显式信息。每天一行日报聚合班次、花费、打扰、心跳四本账,走摘要通道不打扰人。」