定时任务(Cron Jobs)
当前阶段:仅 API + Tool,无独立管理界面
本特性的 REST API、调度器、智能体可调用工具均已实现,但前端管理页面尚未开发。当前可通过:
- AI 在对话里使用
cronjob_tool工具创建 / 列出 / 暂停 / 删除(最常见) - API 直接 POST
/cron(运维 / 集成场景)
待 UI 上线后本页将更新。
让定时任务被一个 应用 + 编排器 自动触发——例如 "每天 9 点生成销售报告"、"每周一 8 点查邮件队列"。每次触发都会以指定的 prompt 调用对应编排器,把结果按渠道推送回原会话或指定接收人。
能调度什么
每个任务绑定到 一个 Application 或一个 Orchestrator(必须二选一)。触发时:
- 把
prompt作为user_message发给OrchestratorEngine.execute() - 结果按
deliver字段推送
定时任务 ≠ 通用 LLM 调用
你不能让定时任务"自由发挥",它必须复用一个已经搭好的编排器。这避免了"权限通过定时任务被绕过"。
调度表达式
支持四种写法,按输入形态自动识别:
| 表达式 | 类型 | 示例 |
|---|---|---|
every <时长> | interval | every 30m、every 2h、every 1d |
5 字段 cron(数字 + * - , /) | cron | 0 9 * * *、0 9 * * 1-5 |
ISO 时间戳(含 T 或 YYYY-MM-DD) | once | 2026-04-20T10:00 |
裸时长(不含 every) | once(相对) | 30m、2h、1d |
支持:
- 标准 5 字段 cron(
croniter校验) - 时长单位:
m/min、h/hr、d/day
不支持:
- 7 字段 / Quartz 风格 /
@daily这类别名 (none found) - 秒级精度(最小调度粒度 60 秒)
- 不同任务用不同时区——
timezone字段虽存在,调度器实际按服务端本地时区计算
最小频率没有强制
代码上没拦截 every 1m,但调度器本身每 60 秒一个 tick——比这更频繁也跑不起来。
投递(deliver)
deliver 字段是逗号分隔的多目标,常用值:
| Token | 含义 |
|---|---|
origin | 推回创建任务时所在的会话(默认值) |
local | 仅入库,不推 |
wecom:<user_id> | 推到指定企微用户 |
feishu:<open_id> | 推到指定飞书用户 |
failure_deliver 单独配置失败时的接收人——例如"日常推回员工,失败时同时通知运维群"。
执行流程
60 秒 tick
│
▼
锁住 /tmp/sira/cron/.tick.lock (fcntl.flock)
│
▼
扫描 next_run_at <= now 的启用任务
│
▼
for 每条到期任务:
更新 next_run_at(at-most-once)
放入 ThreadPoolExecutor (max_workers=4)
│
▼
释放锁,并发执行
│
▼ 单任务执行:
├─ Wake Gate 脚本(可选):跑 python <script>,30s 超时
│ 最后一行 stdout 是 {"wakeAgent": false} → 标记 SILENT 并跳过 LLM
├─ 拼 prompt = cron_hint + Script Output + job.prompt
├─ OrchestratorEngine.execute(...)(强制 disabled_tools=["cronjob_tool"] 防递归)
├─ 全文存到 /tmp/sira/cron/output/<job_id>/<timestamp>.md
├─ 检测 SILENT 标记 → 不推送
└─ 按 deliver 推送到对应渠道Wake Gate(可选)
某些任务只在特定条件下才有意义——例如"工单队列里有新工单时才汇报"。script 字段允许指定一个本地 Python 脚本预判:
- 脚本路径必须在
/app/scripts/目录下(限定权限) - 30 秒超时(环境变量
SIRA_CRON_SCRIPT_TIMEOUT可调) - 最后一行 stdout 解析为 JSON
- 若
{"wakeAgent": false}→ 不调 LLM,标 SILENT 跳过
SILENT 抑制
执行结果中包含 [SILENT] 字符串时,不会推送给员工——适合"AI 判断没必要打扰"。
HITL(人工审批)行为
cron 是无人值守,命中 HITL × Card 中断时默认自动按"批准"续跑——没人在线,工具该跑就跑。
- 自动 approve 续跑次数上限:
SIRA_CRON_HITL_MAX_AUTO_RESUME(默认 10) - 超出仍中断 → job 标失败,error 信息为
HITL auto-resume exceeded N attempts(通常意味着编排器配置异常,agent 反复触发新 interrupt) - 不会推送 HITL 卡片到任何 channel(cron 没 target_user)
如果你不希望 cron 触发某些高危工具,请在编排器层面别把这些工具放进 tool_ids,而不是依赖 HITL 拒绝——HITL 在 cron 场景被设计成"全放过"。
执行失败 / 重试
| 情况 | 行为 |
|---|---|
| 编排器抛异常 | consecutive_errors +1,构造 ⚠️ 定时任务「xxx」执行失败... 强制推送 |
| 超过 Grace Window 还没跑(机器宕机一段时间) | once 任务直接 mark completed;interval/cron 任务 fast-forward next_run_at 跳过这一轮 |
| 重试策略 | (none found) ——失败不会自动重试 |
| 自动暂停阈值 | (none found) ——consecutive_errors 累加但不会自动停 |
Grace Window =
clamp(period/2, 120s, 7200s)。
API
所有路径都在 /cron 前缀下,全部要求登录,自动按 organization_id 过滤。
| Method | Path | 用途 |
|---|---|---|
POST | /cron | 创建(创建时会跑 prompt-injection scan,命中返回 400) |
GET | /cron | 列表(filters: application_id / state / 分页) |
GET | /cron/{id} | 详情 |
PATCH | /cron/{id} | 修改 |
DELETE | /cron/{id} | 软删除 |
POST | /cron/{id}/run | 立即触发(设 next_run_at = now) |
POST | /cron/{id}/pause | 暂停 |
POST | /cron/{id}/resume | 恢复,重算 next_run_at |
GET | /cron/{id}/runs | 运行历史 |
创建示例
curl -X POST https://your-sira/api/agent-system/cron \
-H "Authorization: Bearer <token>" \
-d '{
"name": "每日工单汇总",
"schedule_expr": "0 9 * * *",
"prompt": "查询昨天新增的工单,按优先级排序输出 Markdown",
"application_id": "<app-uuid>",
"deliver": "wecom:user_123",
"failure_deliver": "wecom:ops_group"
}'智能体工具:cronjob_tool
平台向 LangChain 编排自动注入一个名为 cronjob_tool 的结构化工具,让 AI 在对话里直接管理定时任务。当 context.cron_enabled 为真且未在 disabled_tools 中时启用。
支持动作:
| action | 参数 | 说明 |
|---|---|---|
create | name, schedule, prompt, deliver | 创建任务 |
list | limit | 列出当前会话可见的任务 |
pause | job_id | 暂停 |
resume | job_id | 恢复 |
remove | job_id | 删除(软) |
runs | job_id, limit | 查看运行历史 |
会话级隔离
工具按 当前对话上下文 自动过滤可见任务:
- 群聊:只能看 / 操作
origin_chat_id == 当前群的任务 - 单聊:只能看 / 操作
origin_user_id == 当前用户 AND origin_chat_id IS NULL的任务
避免跨群 / 跨用户泄露任务。
防递归
定时任务自身的执行上下文里 disabled_tools = ["cronjob_tool"]——AI 不能让定时任务再创建定时任务。
数据模型
cron_jobs
记录任务定义和当前状态:schedule_kind (once/interval/cron)、schedule_expr、next_run_at、enabled、state (scheduled/paused/completed)、total_runs、successful_runs、consecutive_errors、last_status、last_error、last_delivery_error、output_file_path 等。
cron_job_runs
每次执行一条:status (running/ok/error/silent/skipped)、error、started_at / finished_at / duration_ms、delivery_status、delivered_to (JSON 数组)、summary (响应前 500 字)、output_file_path、token_usage (JSON)。
限制速查
| 项目 | 值 |
|---|---|
| 单 tick 并发 | 4(ThreadPoolExecutor) |
| 调度粒度 | 60 秒 |
| Wake Gate 超时 | 30 秒(SIRA_CRON_SCRIPT_TIMEOUT) |
| 任务 prompt | 创建 / 修改时强制 prompt-injection 扫描 |
| 多副本部署 | 当前是文件锁,单实例安全;多 pod 部署需要后续替换为 Redis 锁 |
| 每组织最大任务数 | (none found) |
| Prometheus 指标 | (none found) |
可观测性
- 全文输出:
/tmp/sira/cron/output/<job_id>/<timestamp>.md - 进程锁:
/tmp/sira/cron/.tick.lock - 标准日志:失败、grace window fast-forward、锁竞争都打到 logger
- 数据库:每次执行写
cron_job_runs,可走GET /cron/{id}/runs拉取
