nanobot Cron 与 Heartbeat

4 阅读8分钟

nanobot Cron 与 Heartbeat


核心区别

CronHeartbeat
任务来源agent/用户通过对话创建,存于 jobs.json用户手动编辑 HEARTBEAT.md 或者通过对话创建
调度精度精确:一次性时间戳、固定间隔、cron 表达式固定轮询间隔(默认 30 分钟)
"要不要执行"时间到了就执行,无条件LLM 读取 HEARTBEAT.md 后自主判断
结果推送可选 deliver=true 推送到指定 channel执行后再经 LLM 评估,值得通知才推送
持久化workspace/cron/jobs.json,重启后自动恢复workspace/HEARTBEAT.md,手动维护
适合场景定时提醒、定期报告、延迟一次性任务条件监控、智能提醒、"如果…则…"逻辑

Cron

工作原理

  1. CronService 启动时加载 jobs.json,计算每个 job 的 next_run_at_ms
  2. asyncio 定时器(精确到最近一个 job 的触发时间)唤醒
  3. 到期的 job 触发 on_job 回调 → 将 payload.message 作为 prompt 发给 agent loop
  4. 执行完后计算下次运行时间,更新状态写回磁盘

三种调度模式

模式参数说明
everyevery_seconds固定间隔重复,如每 20 分钟
croncron_expr + 可选 tz标准 5 字段 cron 表达式
atat(ISO 时间)一次性,执行后自动删除

通过对话创建 Cron

直接告诉 nanobot,agent 会调用内置 cron 工具:

每隔 20 分钟提醒我喝水
→ cron(action="add", message="该喝水了!", every_seconds=1200)

每天早上 9 点发一个天气预报
→ cron(action="add", message="查询今日北京天气并汇报", cron_expr="0 9 * * *", tz="Asia/Shanghai")

工作日下午 5 点提醒下班
→ cron(action="add", message="下班时间到!", cron_expr="0 17 * * 1-5", tz="Asia/Shanghai")

30 分钟后提醒我开会
→ cron(action="add", message="会议提醒", at="<ISO datetime>")

查看所有定时任务
→ cron(action="list")

删除某个任务
→ cron(action="remove", job_id="abc123")

直接编辑 jobs.json

~/.nanobot/workspace/cron/jobs.json 结构如下,修改后 CronService 会自动检测文件变更并热重载:

{
  "version": 1,
  "jobs": [
    {
      "id": "a1b2c3d4",
      "name": "每日天气播报",
      "enabled": true,
      "schedule": {
        "kind": "cron",
        "expr": "0 8 * * *",
        "tz": "Asia/Shanghai"
      },
      "payload": {
        "kind": "agent_turn",
        "message": "查询今日北京天气并汇报",
        "deliver": true,
        "channel": "telegram",
        "to": "12345678"
      },
      "state": {},
      "createdAtMs": 1744000000000,
      "updatedAtMs": 1744000000000,
      "deleteAfterRun": false
    }
  ]
}

3分钟后提醒我睡觉,更新后的jobs.json

{
  "version": 1,
  "jobs": [
    {
      "id": "6347cfbb",
      "name": "3分钟到啦!该睡觉了~ 早点休息,晚安!🌙",
      "enabled": true,
      "schedule": {
        "kind": "at",
        "atMs": 1776004200000,
        "everyMs": null,
        "expr": null,
        "tz": null
      },
      "payload": {
        "kind": "agent_turn",
        "message": "3分钟到啦!该睡觉了~ 早点休息,晚安!🌙",
        "deliver": true,
        "channel": "feishu",
        "to": "ou_32921a3ccfafac6cb1fa899c6598a3e0"
      },
      "state": {
        "nextRunAtMs": 1776004200000,
        "lastRunAtMs": null,
        "lastStatus": null,
        "lastError": null,
        "runHistory": []
      },
      "createdAtMs": 1776004072491,
      "updatedAtMs": 1776004072491,
      "deleteAfterRun": true
    }
  ]
}

关键字段说明:

字段说明
schedule.kind"every" / "cron" / "at"
schedule.everyMs间隔毫秒(every 模式)
schedule.exprcron 表达式(cron 模式)
schedule.tzIANA 时区,如 "Asia/Shanghai"(仅 cron 模式)
schedule.atMs触发时间戳 ms(at 模式)
payload.message触发时作为 prompt 发给 agent loop
payload.delivertrue 时将 agent 响应推送给用户
payload.channel推送目标渠道,如 "telegram"
payload.to推送目标 ID(chat_id / 手机号等)
deleteAfterRuntrue 时执行一次后自动删除(at 模式默认开启)

jobs.json 持久化与生命周期

写盘时机:每次状态变化都会立即写盘,包括:

  • 创建/删除/启用/禁用任务
  • 每次执行后(更新 last_run_at_msnext_run_at_msrun_history

一次性任务(at 模式)的清理行为

deleteAfterRun执行完后
true(通过对话创建时默认)立即从 jobs.json 中删除 ✅
false任务保留,但 enabled 置为 false,不再触发

启动时不会自动清理CronService 启动时原样加载所有 jobs,不过滤过期或已禁用的任务。残留任务需手动通过 cron(action="remove", job_id=...) 清理。

热重载:运行时直接编辑 jobs.jsonCronService 会检测文件 mtime 变化并自动重载,无需重启。

配置项

Cron 无独立配置节,依赖 agent 配置中的模型与 workspace 路径,存储路径固定为:

{workspace}/cron/jobs.json

Heartbeat

工作原理

按固定间隔(默认 30 分钟)轮询,分三个阶段执行:

每 interval_s 秒
  │
  ├─ 读取 HEARTBEAT.md
  │   └─ 文件为空 → 直接跳过
  │
  ├─ Phase 1 — 决策(轻量 LLM 调用)
  │   System: "You are a heartbeat agent..."
  │   发送当前时间 + HEARTBEAT.md 内容,调用虚拟 heartbeat 工具
  │   返回 action = "skip" 或 "run" + tasks 描述
  │   └─ skip → 结束本轮
  │
  ├─ Phase 2 — 执行(完整 agent loop)
  │   System: 标准系统提示(AGENTS.md、memory、skills 全部注入)
  │   将 tasks 描述作为 prompt 发给 agent loop
  │   可用所有标准工具:文件、exec、web、cron、message、spawn、MCP
  │   session_key = "heartbeat"(独立于用户对话)
  │
  └─ Phase 3 — 通知评估(轻量 LLM 调用)
      System: "You are a notification gate..."
      发送原始 tasks + Phase 2 执行结果
      调用 evaluate_notification 工具,返回 should_notify
      ├─ true  → 推送结果到用户渠道(on_notify)
      └─ false → 静默丢弃(异常时默认 true,不丢消息)

Heartbeat 三阶段工作流 (1).png

Phase 1 决策阶段:System Prompt 与 Tools

Phase 1 是一次极简的 LLM 调用,不走完整 agent loop

System Prompt(固定):

You are a heartbeat agent. Call the heartbeat tool to report your decision.

User Message:

Current Time: <当前时间含时区>

Review the following HEARTBEAT.md and decide whether there are active tasks.

<HEARTBEAT.md 全文内容>

唯一可用工具(虚拟 tool call):

{
  "name": "heartbeat",
  "parameters": {
    "action": "skip" | "run",
    "tasks": "需要执行的任务描述(run 时必填)"
  }
}

LLM 必须调用此工具,返回 skip(无任务)或 run(有任务,附上任务描述)。若未调用工具,默认视为 skip


Phase 2 执行阶段:System Prompt 与 Tools

Phase 2 调用 agent.process_direct(tasks, session_key="heartbeat"),走完整标准 AgentLoop

System Prompt:与普通对话完全相同,包含:

  • workspace 路径、运行时信息
  • AGENTS.mdSOUL.md 等 bootstrap 文件
  • memory/MEMORY.md 长期记忆
  • 所有 always: true 的 skill(如 memory skill)

可用 Tools(与普通对话完全相同):

工具说明
read_file / write_file / edit_file / list_dir文件读写
execShell 命令(若 config 中 enable)
web_search / web_fetch网络搜索与抓取
message主动推送消息到渠道
spawn启动子 agent
cron创建/查询/删除定时任务
MCP tools若配置了 MCP server

Session 隔离:使用独立的 session_key="heartbeat",与用户对话 session 分开。每次执行后仅保留最近 keep_recent_messages(默认 8)条消息,避免 context 无限增长。


Phase 3 通知评估阶段:System Prompt 与 Tools

Phase 2 执行完后,结果会经过第三次轻量 LLM 调用,决定是否推送给用户(nanobot/utils/evaluator.py):

System Prompt(固定):

You are a notification gate for a background agent.
You will be given the original task and the agent's response.
Call the evaluate_notification tool to decide whether the user should be notified.

Notify when the response contains actionable information, errors,
completed deliverables, or anything the user explicitly asked to be reminded about.

Suppress when the response is a routine status check with nothing
new, a confirmation that everything is normal, or essentially empty.

User Message:

## Original task
<Phase 1 返回的 tasks 描述>

## Agent response
<Phase 2 执行结果>

唯一可用工具:

{
  "name": "evaluate_notification",
  "parameters": {
    "should_notify": true | false,
    "reason": "一句话说明原因"
  }
}

失败兜底:若 LLM 未调用工具或发生异常,默认 should_notify=true,确保重要消息不会被静默丢弃。


Heartbeat 能做什么 vs 不能做什么

能做 ✅不能做 ❌
定时轮询检查(GitHub issue、磁盘空间等)监听用户消息事件
时间段感知(只在上午 9 点执行)精确时间触发(取决于轮询间隔)
读写文件、执行 shell、调用 web在用户发消息时同步响应
创建 cron 任务(cron 工具可用)"当用户说 X 时做 Y"(应写在 AGENTS.md
读取 memory/HISTORY.md 做上下文判断访问用户当前对话 session

自定义 HEARTBEAT.md

~/.nanobot/workspace/HEARTBEAT.md 中描述你希望 agent 定期关注的事项。格式自由,LLM 会阅读并判断是否有需要处理的任务。

示例 1 — 简单监控

# 我的待办关注

## 每日提醒
- 每天早上检查一次 GitHub Trending,汇报今日热门 Python 项目

## 条件触发
- 如果当前时间是工作日上午 9 点到 10 点之间,发送今日工作计划提醒

示例 2 — 项目监控

# 项目巡检

检查以下内容,如有异常则汇报:
- workspace/scraper.py 对应的 GitHub repo 有无新 issue
- 服务器磁盘使用率(通过 shell 命令查询)是否超过 80%

如果没有异常,静默跳过,不需要通知我。

示例 3 — 智能提醒

# 健康提醒

每隔一段时间提醒用户:
- 如果距上次提醒超过 2 小时,提醒喝水
- 如果是下午 3 点左右,提醒站起来活动 5 分钟

注意:判断"距上次提醒多久"可以通过读取 memory/HISTORY.md 来估算。

配置

~/.nanobot/config.jsongateway.heartbeat 节中配置:

{
  "gateway": {
    "heartbeat": {
      "enabled": true,
      "interval_s": 1800
    }
  }
}
字段默认值说明
enabledtrue是否启用 heartbeat
interval_s1800(30 分钟)轮询间隔,单位秒
keep_recent_messages8执行时携带的最近消息数

Heartbeat 仅在 nanobot gateway 模式下运行,nanobot agent(CLI 模式)不启动 HeartbeatService。


选择建议

  • 需要精确时间触发 → 用 Cron(every / cron / at
  • 需要 LLM 判断"现在该不该做" → 用 Heartbeat + HEARTBEAT.md
  • 两者可以组合:用 Cron 定期触发固定任务,用 Heartbeat 处理需要上下文判断的弹性提醒

关键源文件

文件职责
nanobot/cron/service.pyCronService:调度循环、job 持久化、执行回调
nanobot/cron/types.pyCronJob / CronSchedule / CronPayload 数据结构
nanobot/agent/tools/cron.pyCronTool:agent 调用的 cron 工具(add/list/remove)
nanobot/skills/cron/SKILL.mdCron skill 说明,agent 读取后了解如何使用 cron 工具
nanobot/heartbeat/service.pyHeartbeatService:决策 + 执行两阶段循环
nanobot/config/schema.pyHeartbeatConfig:enabled / interval_s / keep_recent_messages
{workspace}/cron/jobs.jsonCron 持久化存储
{workspace}/HEARTBEAT.mdHeartbeat 任务描述文件(用户手动编辑)