- 移除 GovAI, nomifun-tauri, 算力盒子 的 submodule 引用 - 添加所有子项目的完整源代码 - 保留原始 .git 为 .git.bak 备份
12 KiB
定时任务 (Cron)
NomiFun 中的一个定时任务是一个在你选择的时间触发的循环 (或一次性) 任务,它会驱动一个 AI agent 去做某件事。你可以从定时任务页面 配置它、按需运行它、给它附加一个个性化的技能让 agent 在 该任务下始终以正确的方式行事,并且你可以使用一个内置的 cron 技能在聊天中让任何 agent 帮你管理任务。
找的是应该尽快运行的一次性异步工作,而不是按时钟来的?参见 AutoWork & Requirements。需要一个 实时 shell?参见 应用内终端。
一个任务做什么
nomifun-cron 是一个后端调度器 + 执行器:
- 调度器用一个 5 字段 (Unix) 或 6 字段 (秒前缀) 的 cron 表达式
为每个已启用的任务计算下一次触发时间 —— 两者都接受;
5 字段表达式会通过在前面加
0作为秒被规范化为 6 字段。 调度也可以是一个绝对时间戳 (At { at_ms }) 或一个固定间隔 (Every { every_ms })。 - 每个任务的时区 (例如
Asia/Shanghai、America/Los_Angeles) 会被尊重,所以0 9 * * MON表示那个时区的 09:00,而不是 UTC。 - 执行器在定时器触发时驱动该任务的 agent。两种执行模式:
new_conversation—— 每次触发开启一个新会话。该任务 携带 workspace、agent、model 和 prompt;执行器会创建会话、 广播一个cron_trigger工件 (这样聊天 UI 会显示 "本会话由一个定时任务发起"),然后发送 prompt。existing—— 复用拥有该任务的会话。每次触发把 prompt 作为一条新消息发送到同一个线程中。适合 "提醒我"、 "总结今天" 或任何延续性重要的任务。
- 一个忙碌守卫防止同一个会话被并发进入。如果上一次运行
在下一次触发到来时还在进行中,新的运行会被跳过 (记录为
skipped)。 - 一个漏触发处理器会在启动时和操作系统从睡眠中醒来后运行
(
/api/cron/internal/system-resume)。它会遍历每个next_run在过去的已启用任务并发出一条系统消息,让你能看到漏掉一次 触发 (例如笔记本休眠时),然后为下一个 cron 节拍重新装定定时器。 - 每次触发会以一个状态被记录 ——
ok/error/skipped/missed—— 并且 (在适用时) 附带一个指向所产生会话的链接, 这样详情页可以向你展示运行历史。
创建一个任务
从侧边栏打开 定时任务 (路由:/scheduled) 并按
New task。对话框涵盖四个区域。
频率
从一组小的预设中选择 —— Manual (无自动调度,仅通过 Run now 触发)、
Hourly、Daily、Weekdays (MON-FRI)、Weekly 或
Custom。预设会在 builder 中渲染出可编辑的 cron 表达式;选
Custom 直接键入。Builder 在你键入时进行校验。
Cron 语法速查 (5 字段 —— 秒字段会自动添加):
* * * * *
│ │ │ │ └─ 星期几 (0–6 或 SUN–SAT,MON-FRI 可用)
│ │ │ └──── 月 (1–12 或 JAN–DEC)
│ │ └─────── 月中第几天 (1–31)
│ └────────── 小时 (0–23)
└───────────── 分钟 (0–59)
任务的时区在创建时设置 (默认是你浏览器的 IANA 时区) 并存储在 该行中;如果一个任务存储的时区因故无效,详情页会提供一键 修复到你的本地时区。
Agent
选择每次触发运行的 agent。选择器中会显示三种类型:
- CLI agent ——
claude/codex/gemini(后端在PATH上检测到的任何一个)。该任务记录后端标签并端到端使用 ACP。 - Nomi (内置) —— 使用 Nomi 自有引擎以及你选择的 provider/model。
- Preset assistant —— 预先配置的 agent 人格;该任务记录 assistant id。
Advanced 部分让你覆盖 workspace (agent 的工作目录)、model
以及任意的 config_options 键值对,它们会被转发给 agent 工厂。
Workspace 路径不能包含空白片段 —— 这一点在服务端被强制;
表单会把错误显式呈现出来。
执行模式
选择 new_conversation 或 existing (在 UI 中当你同时选择具体
是哪一个时被称为 "specified conversation")。详情页之后会向你
展示得到的会话。
Prompt + 名称
Prompt 是每次发送给 agent 的内容。请把它写成一个自包含的 指令 —— agent 看不到你原本 "我想要这个" 的框架,只看到这个 prompt。诸如下面这些模式:
Reply with a short weekly meeting reminder that includes the current date and time.Search for the latest AI news from this week and produce a concise bullet-point summary report.Run the weekly database health check and post the results back here.
…比重述用户愿望要好。Name 只是一个标签。
运行、暂停、删除
列表视图 (/scheduled) 显示每一个任务、它的下一次触发,以及
一个启用开关。在详情页 (/scheduled/:job_id) 你可以:
- Run now —— 立即触发该任务,无视调度。忙碌守卫仍然适用。
- Pause / Resume —— 停止后续触发但不删除该行。
- Edit —— 与创建相同的对话框,处于编辑模式。
- Delete —— 删除该任务及其按任务生成的技能目录。此前运行创建的 会话会保留在会话列表中,可按需单独删除。
详情页还会列出本任务创建的会话,按活跃度排序 —— 当任务
以 new_conversation 模式运行并为每次触发各分出一个线程时
非常有用。
保持唤醒
只有当宿主进程在运行时,cron 任务才会触发。列表页有一个
NomiFun 运行时保持系统唤醒开关,它会请求 OS 抑制睡眠
(Windows:SetThreadExecutionState,macOS:caffeinate,
Linux 上若可用为 systemd-inhibit),这样你在笔记本上设置的
任务不会在合上盖子那一刻悄悄漏触发。
如果一次触发还是因为系统进入睡眠 (或 NomiFun 没在运行) 而漏掉,
下一次启动/唤醒时的漏触发处理器会记录一次 missed 运行并
向受影响的会话中投送一条系统消息,然后为下一次正常触发
重新装定定时器。
附加到任务的技能
一个技能是一个 SKILL.md 文件,agent 会在加入会话时读取它
—— 与 NomiFun 其他地方使用的相同机制,但作用域是按任务的。
你可以在详情页编写/编辑该技能;在幕后该文件会被写入数据
目录下的 cron/skills/cron-<job_id>/SKILL.md,执行器会在每次
触发时把它注入到 agent 的会话中。
用例:
- 任务输出的一致人格 (风格、语气、格式)。
- 工具/MCP 偏好 (启用哪些服务器、忽略哪些)。
- 工作区特定的约定 (commit 信息风格、目录布局、部署细节)。
任务有自己的技能目录 (以 job id 命名,前缀 cron-),所以共享同一
workspace 的两个任务可以承载不同的行为而不冲突。删除任务会
移除其技能目录。
还有一个自动的技能建议检测器,它会在运行期间观察 agent 的
输出;当它产出一个干净的候选技能时 (符合预期格式且不只是占位
模板),检测器会在会话中创建一个 skill_suggest 工件,让你
可以审查并一键将其保存为该任务的技能。
在聊天中管理任务 —— 内置的 cron 技能
NomiFun 附带一个名为 cron 的内置自动注入技能,任何 agent 都可以
在你让它"设置一个提醒"、"每周一安排 X" 等时加载它。
然后会话中间件会观察 agent 的回复中以下的指令块,并通过 cron
服务运行它们:
| 指令 | 含义 |
|---|---|
[CRON_LIST] |
列出当前会话作用域内的 cron 任务。 |
[CRON_CREATE]…[/CRON_CREATE] |
创建一个任务 (字段:name、schedule、schedule_description、message)。 |
[CRON_UPDATE: <id>]…[/CRON_UPDATE] |
原地更新一个已有任务。 |
[CRON_DELETE: <id>] |
按 id 删除一个任务。 |
中间件会从用户看到的内容中剥离这些块,并把系统响应
(Created cron job 'X'、No scheduled tasks 等) 投回到会话中。
所以在聊天里看起来像是正常的来回;幕后是 agent 发出了一个
指令,平台执行了它。
该技能在设计上被限制为每个会话一个任务 —— 这让循环保持 简单 ("查询,然后行动") 并避免了你重新询问时重复任务堆积。 要一次管理多个任务,请直接使用定时任务页面。
路由 & API
| 内容 | 位置 |
|---|---|
| 列表页面 | /scheduled |
| 详情页面 | /scheduled/:job_id |
| 列出 / 创建任务 | GET /api/cron/jobs,POST /api/cron/jobs |
| 获取 / 更新 / 删除 | `GET |
| 立即运行 | POST /api/cron/jobs/:id/run |
| 列出某任务的会话 | GET /api/cron/jobs/:id/conversations |
| 每任务技能 | `GET |
| 系统恢复 (内部) | POST /api/cron/internal/system-resume (需要内部 header) |
UI 订阅的实时事件:cron.job-created、cron.job-updated、
cron.job-removed 和 cron.job-executed。漏触发会作为一次
cron.job-executed 事件上报,payload 中的状态是 missed。
故障排查
- 任务没有按时触发。 那时宿主在运行且处于唤醒状态吗?
如果你合上了笔记本或关闭了应用,请查看唤醒后的下一条会话
条目 —— 漏触发处理器会投送一条
missed通知并重新装定 定时器。 - 我的 cron 表达式被拒绝了。 5 字段 (
m h dom mon dow) 和 6 字段 (s m h dom mon dow) 形式都是合法的。在本地用 crontab.guru 或对话框中的 builder 校验。 - 任务运行了但 agent 做错了事。 重新阅读 prompt,假设你没有 其他上下文。它必须告诉 agent 准确要产出什么。然后考虑附加 一个技能以锁定行为。
- 两次定时触发碰撞了。 忙碌守卫会在
existing模式下跳过 重叠的运行 (该运行被记录为skipped)。如果你预期触发会 长时间运行,请把任务切换到new_conversation模式,让每次 触发各得一个线程。 - 聊天中的
cron指令什么也没做。 如果 cron 服务没有接入 (例如某些测试 harness),中间件会 no-op;在正常 app build 中 它总是接入的。如果指令格式错误 (缺失闭合标签、缺失schedule),它会被静默丢弃 —— 用更干净的输入重新提示 agent。


