跳转到内容

定时任务

cron 任务是一个按计划自动触发的 agent prompt——“每 5 分钟查一次队列”、“每天早上 9 点写一份 日报”——只要 octo serve 在跑,没人盯着它也会自己触发。

Terminal window
octo serve

每个任务是 ~/.octo/tasks/ 下的一个 JSON 文件,由 octo serve 内部的调度器加载。任务触发时, 调度器会用任务的 prompt 跑一轮 agent,并且每次运行都复用同一个会话,所以任务会在多次运行 之间不断积累历史。每次运行都有一个 30 分钟的墙钟超时——唯一的硬上限。

任务只有在 octo serve 跑着的时候才会触发。 没有 serve 就没有运行——服务器关着期间错过 的那次计划,重启后也不会补跑。

字段 是否必填 含义
name 人类可读的任务名
cron 调度表达式——见下文
prompt 每次运行发给 agent 的 prompt
model 模型覆盖;不填就用 server 的默认模型
agent "general""coding"
directory 运行时所在的工作目录
notify 每次运行的最终回复(或失败信息)要推送到哪些 IM 会话
enabled 这个计划当前是否处于启用状态

这个 prompt 是在自己独立的会话里跑的,拿不到创建这个任务的那个对话的任何上下文,所以它必须 自成一体:做什么、在哪做、输出该长什么样都要写清楚。也要给它一个明确的停止条件——一个开放式 的 prompt 会让模型一直反复确认下去,直到撞上 30 分钟超时,而不是一旦“没什么可汇报的”就自己 收尾。

cron 表达式——6 个字段,秒在最前面

Section titled “cron 表达式——6 个字段,秒在最前面”

调度器用的是 robfig/cron带一个秒字段——标准的 5 字段 crontab 写法在这里是非法的,永远要在最前面加一个秒字段:

seconds minutes hours day-of-month month day-of-week
想要 表达式
每天 09:00 0 0 9 * * *
每 30 分钟 0 */30 * * * *
工作日 18:30 0 30 18 * * 1-5
每月 1 号 08:00 0 0 8 1 * *

也支持描述符写法:@hourly@daily@weekly@every 90m。时间用的是 server 所在的 本地时区。

只要 octo serve 在跑,所有走 API 的改动都会立刻让正在运行的进程重新调度——这是推荐路径。

Terminal window
# 创建——返回 {"id":"task_..."}。任何可选字段(directory、model、
# agent、notify)都可以直接放进创建时的请求体里。
curl -s -X POST http://127.0.0.1:8088/api/tasks \
-H 'Content-Type: application/json' \
-d '{"name":"daily-report","cron":"0 0 9 * * *","prompt":"Summarize ...","directory":"/srv/repo"}'
curl -s http://127.0.0.1:8088/api/tasks # 列表
curl -s -X DELETE http://127.0.0.1:8088/api/tasks/{id} # 删除
# 立即跑一次,跳出计划
curl -s -X POST http://127.0.0.1:8088/api/tasks/{id}/run
# 改任意一部分字段——启用/禁用也是走这条路
curl -s -X PATCH http://127.0.0.1:8088/api/tasks/{id} \
-H 'Content-Type: application/json' \
-d '{"prompt":"new prompt ...","enabled":false}'

PATCH /api/tasks/{id} 接受 enabledcronpromptmodelagentdirectorynotify——只发你要改的那部分就行。Web UI 里的调度器面板就是这套 API 的一个客户端,所以用 curl 创建的任务会出现在面板里,反过来也一样;面板也是试跑一个新任务的 Run 按钮推荐 用的地方,而不是从聊天会话里直接触发 /api/tasks/{id}/run——一次运行是在这个任务自己的 会话里跑一整轮 agent(最多 30 分钟),从对话里触发只会把那个对话卡住,而真正的输出落在了 没人看着的地方。

直接写 ~/.octo/tasks/<id>.jsonid 格式是 task_<unix-millis>;文件名必须等于 <id>.json):

{
"id": "task_1717999999999",
"name": "daily-report",
"cron": "0 0 9 * * *",
"prompt": "Summarize ...",
"directory": "/srv/repo",
"enabled": true,
"created_at": "2026-06-10T09:00:00Z"
}

这个文件会在 octo serve 下次启动时被读取。手写的文件如果 cron 表达式写错了,加载时会 静默失败(只会打到 stderr)。server 已经在跑的时候,对文件的修改会被忽略,直到重启为止—— 一旦起来了,就应该改走 API。

notify 是一个 IM 目标的列表(单个裸对象也接受);每一条都会在运行成功后收到最终回复, 或者在出错时收到一条简短的失败提示。推送失败会记在 server 端日志里,但不会影响这次运行本身。

平台 chat_id 说明
feishu oc_… chat id 需要在 channels.yml 里配好应用凭证;id 可以从会话设置里拿,或者给 bot 发条消息后去看 server 日志
dingtalk 单聊用 staff id,群聊用 cid… conversation id 单聊的 conversation id 用不了——要用 staff id
weixin(iLink) user id 用户必须至少给 bot 发过一次消息
telegram chat id(用户/群组/频道) bot 必须已经能给它发消息
discord channel id bot 需要有那个频道的 Send Messages 权限
wecom 忽略 推送走的是绑定到某个群的群机器人 webhook,而不是这个字段

下一步:如果只是想要一个更短生命周期、不需要扛过重启的会话内重复任务,看看 /loop