会话定时任务
Spark Work 会话内定时任务:在单个会话作用域创建持久化任务,到点自动用本会话当时的 Agent/模型/权限/工作区续接一轮对话。支持 interval(固定间隔)/ cron(5 字段表达式)/ once(单次定时),用户工具栏和 Agent MCP 工具两个入口,调度引擎托管重试、并发与下次执行。
有时候你希望 Agent「过一会儿再来看看」「每天定时跟进一下」「某件长任务跑完了再叫醒我继续」。 Spark Work 的会话内定时任务就是为这类场景准备的:在某个会话作用域里创建持久化任务, 到点后调度器自动在这个会话里排一轮新对话,沿用会话当时的 Agent、模型、权限和工作区继续推进。
1. 会话级作用域:任务跟着会话走
每个定时任务都绑定在创建它的那个会话上:scheduled_tasks 表里的scope 为 'session',session_id 指向会话。 触发时它不会新开会话,而是往原会话里排一轮 turn (内部标记 turnSource: 'scheduled_task'、userMessageVisibility: 'hidden'), 因此这一轮用的是会话当前的 Agent、Provider/模型、权限模式、推理强度和工作区目录。 你之后切换模型或调整权限,下一次触发会自动按最新配置执行。
- 归属校验:Agent 侧工具用「task id + 当前会话 id」双重校验, 任务必须
scope === 'session'且sessionId与当前会话一致才允许读写, 猜 id 也拿不到别的会话或全局任务。 - 删除会话即级联清理:
session_id外键带ON DELETE CASCADE,删除会话时它的任务随之删除。 - 归档会暂停:会话被归档时,启用中的会话任务进入 paused 状态; 取消归档时只有归档前启用的那些会被恢复,并重新计算下次执行时间。
- 会话不存在 / 读不到状态:任务会被停用并记
Bound session no longer exists;只是暂时读不到会话状态则推迟重试并记录原因。
2. 两个入口,别搞混作用域
2.1 会话工具栏:只管理本会话
会话顶部工具栏有一个时钟图标按钮(提示文案「计划任务」),点开是会话计划任务面板。有启用中的任务时按钮上会带一个小红点。 面板支持新建、编辑、启停、立即运行、删除,提交时固定scope: 'session' + 当前 sessionId + concurrencyPolicy: 'queue'。
面板表单的默认值:
- 触发类型
interval,间隔 30 分钟(提交时换算成秒,最小 10 秒)。 - Cron 预填
0 */1 * * *。 skipIfSessionRunning与continueOnError默认勾选。- 名称与任务正文必填;
once必须选一个合法时间。
2.2 侧栏「定时任务」:全局任务,不是会话任务
左侧导航的 「定时任务」 页是另一套作用域:它列出的是scope: 'global' 的任务,新建时也按 global 写入(默认 concurrencyPolicy: 'skip')。 全局任务触发时会新建一个会话(默认落在「无项目对话」目录,标题前缀带任务名), 而不是续在原会话里。想「在某个会话里继续」就必须用会话面板或 Agent 工具创建。
用户侧 IPC 通道:scheduled-task:list / get / create / update / delete / toggle / run-now / export / import / export-to-file / import-from-file, 执行记录用 task-execution:list / get / cancel。 「立即运行」会立刻建一条 trigger_type: 'manual' 的执行记录, 并在 10 秒内把新会话 id 还给界面用于跳转,真正的 turn 在后台跑。
3. 字段与默认值
| 字段 | 含义 | 默认值 / 取值 |
|---|---|---|
name / promptTemplate | 任务名与任务正文(必填,正文最长 10 万字符) | — |
triggerType | 触发方式 | interval / cron / once |
intervalSeconds | 固定间隔秒数 | 最小 10,最大 31536000 |
cronExpression | Cron 表达式 | 必须是 5 字段;非法表达式会在创建/更新时直接报错 |
runAt | 单次执行时间(ISO-8601) | 必须能解析且晚于当前时间 |
timezone | 时区 | 默认 'system'(跟随本机);不是 system 时交给 cron-parser 解析 |
startAt / endAt | 生效时间窗 | 可选;未到 startAt 时下次执行会被推到 startAt |
maxExecutions | 最多执行多少次 | 默认 0(不限);达到上限后任务自动停用 |
timeoutSeconds | 单次执行超时 | 默认 300;Agent 工具可写 10 ~ 86400 |
maxRetries | 失败重试次数 | 默认 0(不重试);上限 100 |
retryDelaySeconds | 重试基础延迟 | 默认 60 |
retryBackoff | 退避策略 | fixed(默认)/ linear / exponential |
concurrencyPolicy | 上一次还在跑时怎么办 | 会话面板与 Agent 工具写 queue;数据表与全局页默认 skip; 可选 skip / queue / cancel |
skipIfSessionRunning | 会话有运行中/排队中的 turn 时跳过本次 | 默认 true(数据库列默认 1) |
continueOnError | 会话报错后是否继续后续触发 | 默认 true;关掉后该任务在会话进入 error 时会被暂停 |
historyRetentionDays | 执行记录保留天数 | 默认 30;每次执行结束后清理过期记录 |
tags / notifications | 标签 / Webhook 通知配置 | 通知可挂 onSuccess / onFailure / onRetry / onDisabled |
会话任务的 agent_id、team_id、model_id、workspace_id一律写 null,permission_mode 写 'auto': 这是刻意的设计——会话任务只跟随会话当时的运行时配置,不给自己绑一份会过期的快照。
4. 三种触发方式怎么选
| 触发类型 | 用法 | 校验与计算 |
|---|---|---|
| interval | 固定间隔轮询,轮询场景建议 ≥ 60 秒 | 需要 intervalSeconds ≥ 10;下次执行 = 当前时间 + 间隔秒数 |
| cron | 「每天 9 点」「工作日每半小时」这类计划 | 必须 5 字段并通过 cron-parser 解析;非 system 时区会带 tz 计算 |
| once | 未来某一刻的一次性唤醒 | 需要未来的 runAt;执行一次后任务自动停用;被跳过的单次任务会在 60 秒后重试 |
会话任务的这层校验只在 scope === 'session' 时强制执行,报错文案分别是 「interval must be at least 10 seconds」「requires a valid five-field cron expression」 「runAt must be in the future」。
5. Agent 也能自己建任务
Agent 通过 mcp__spark_platform__session_schedule_* 这组工具自助管理会话定时任务, 适合「每 5 分钟检查一下部署状态,好了告诉我」这类需求:
| 工具 | 作用 |
|---|---|
session_schedule_list | 列出当前会话的任务(先查再建,避免重复轮询) |
session_schedule_create | 新建任务(interval / cron / once) |
session_schedule_update | 改频率、指令、启停 |
session_schedule_get | 查看单个任务详情(同样做归属校验) |
session_schedule_delete | 删除任务 |
这组工具与用户面板的差异:
- 会话绑定由运行时提供,模型不能传 sessionId:参数里没有会话字段, 工具被注入到哪个会话就只作用于哪个会话;工具说明也明确要求不要询问或编造 session id。
- 入参是严格 schema:多传未知字段会直接报错;
intervalSeconds、cronExpression等都按上面的上限校验。 - 默认值偏向安全:
skipIfSessionRunning与continueOnError默认 true,concurrencyPolicy默认queue,timeoutSeconds默认 300,historyRetentionDays默认 30。 - 生命周期要求写进了系统提示词:创建后简短告知用户并结束本轮; 每次唤醒先看真实状态,还没完成就留着,任务完成后必须调用
session_schedule_delete清理,不能无限空跑。禁用只是暂停,不算清理。
6. 调度引擎的行为
6.1 并发与会话忙
- 调度器是桌面主进程里的定时器,每 1 秒 tick 一次:应用没运行时不会触发。
- 同一任务已有
running的执行记录时:skip跳过本次并重算下次时间;cancel把旧执行标记为 canceled 再开新的;queue直接再开一个。 skipIfSessionRunning生效时,只要会话有正在执行或已排队等待的 turn, 本次触发就顺延(once任务顺延 60 秒后重试)。- 会话报 error 且
continueOnError为 false 时,任务被暂停而不是继续触发。
6.2 重试
失败后按 maxRetries 与退避策略重试,重试的延迟公式:
fixed:baseDelay × 1linear:baseDelay × (attempt + 1)exponential:baseDelay × 2^attempt
每次重试都是一条独立执行记录(trigger_type: 'retry'、带retry_attempt 与 parent_execution_id),所以你能在记录里看出是第几次尝试。 超过 timeoutSeconds 的执行记为 timeout 而不是 failed。
6.3 生命周期与错过的运行
- 错过的运行不补跑:应用关闭期间到点的会话任务,在下次启动时会被跳过—— 周期任务直接挪到下一个未来时间点,错过的单次任务会被停用。
- 执行状态:
running / completed / failed / cancelled / timeout。 - 任务状态:
idle / running / disabled / error。 - 达到
maxExecutions或单次任务执行完成后,任务自动 disabled。 - 每次执行结束按
historyRetentionDays清理历史执行记录。
7. 触发时注入了什么
触发的那一轮不是普通用户消息,系统会把它包成 [Scheduled Task Context]:
- 说明这轮由调度器发起、不是用户手动发送。
- 任务名、任务 id(会话任务专属),并提示任务完成后要调用
session_schedule_delete清理。 - 执行触发来源、配置的计划、下次执行时间与时区。
- 明确要求「不要反过来问用户间隔 / Cron / 触发时间」,按既有计划执行。
- 最后跟
[Task Instructions],即你写的任务正文。
任务正文支持模板变量,会在写入提示词前替换:{{date}}、{{time}}、{{taskName}}、{{triggerType}}、{{executionCount}}、{{interval}}、{{cronExpression}}、{{runAt}}、{{timezone}}、{{nextRunAt}}。 认不出的变量保持原样,方便你发现拼写错误。
8. 使用规范与排查
- 轮询类任务的间隔尽量 ≥ 60 秒,避免空转过快消耗 token。
- 目标达成(长任务完成、条件满足)后必须调用
session_schedule_delete清理, 不要让任务无限空跑;只想临时停机用「停用」。 - 一次性提醒用
once,不要用 interval 模拟。 - 在任务正文里写清「检查什么、状态在哪、什么算完成、还没完成时怎么办、什么时候删任务」, 无人值守的那一轮才不需要向你追问。
- 任务定义与执行记录都落本地 SQLite(
scheduled_tasks/task_executions); 触发时复用本会话的模型与权限配置,不会绕过权限模式或注入额外能力。
常见问题对照:
| 现象 | 原因 | 处理 |
|---|---|---|
| 到点了但没执行 | 应用没运行,或会话有排队/运行中的 turn 被跳过,或任务被停用/暂停 | 看任务的 status、lastError 与 nextRunAt,确认应用在运行 |
| 任务突然自己停了 | 单次任务已执行、达到 maxExecutions、或会话归档被暂停 | 检查任务状态;归档场景取消归档后会恢复 |
| Cron 保存失败 | 表达式不是 5 字段或无法解析 | 改成标准 5 字段,例如 0 9 * * * |
| Agent 说建好了但找不到 | 任务建在别的会话(Agent 工具只作用于当前会话) | 在当前会话的「计划任务」面板里查;会话面板只显示本会话任务 |
| 想跨会话复用同一套计划 | 会话任务绑定创建它的会话 | 用侧栏「定时任务」的全局任务(会新建会话),或每个会话各建一份 |
速查表
- 作用域
- session(scheduled_tasks.scope=session,触发时在原会话续接)
- 触发类型
- interval / cron / once
- interval 下限
- 10 秒(建议 ≥ 60 秒)
- 用户入口
- 会话顶栏「计划任务」时钟按钮(有启用任务时带红点)
- 全局入口
- 侧栏「定时任务」(scope=global,会新建会话)
- Agent 入口
- mcp__spark_platform__session_schedule_{list,get,create,update,delete}
- 默认超时
- timeoutSeconds = 300
- 默认并发策略
- 会话面板与 Agent 工具写 queue;全局页/数据表默认 skip
- 重试
- maxRetries 默认 0;retryBackoff = fixed / linear / exponential
- 历史保留
- historyRetentionDays 默认 30 天
- 调度精度
- 主进程每 1 秒 tick;应用未运行不触发、不补跑
常见问题
展开 6 条常见问题
- 定时任务触发时会新开一个会话吗?
会话任务不会。它绑定创建它的会话,触发时在原会话里排一轮 turn(内部标记 turnSource=scheduled_task、消息默认隐藏),沿用会话当时的 Agent、模型、权限和工作区。侧栏「定时任务」里的全局任务相反:它会新建一个会话。
- 轮询间隔最小能设多少?
会话任务 10 秒(校验文案就是 interval must be at least 10 seconds),Agent 工具上限 31536000 秒。但轮询场景建议 ≥ 60 秒:每次触发都是一轮真实的模型调用。
- Agent 能自己管理定时任务吗?
能。Agent 通过 mcp__spark_platform__session_schedule_{list,get,create,update,delete} 管理当前会话的任务,参数里没有 sessionId——绑定由运行时提供,不接受模型传入的会话 id。任务完成后 Agent 需要自己 delete 清理。
- 应用关掉的时候会补跑吗?
不会。调度器跑在桌面主进程里、每秒 tick,应用关闭期间到点的任务在下次启动时会被跳过:周期任务挪到下一个未来时间点,错过的单次任务直接停用。
- 会话正在执行别的任务时,定时任务会排队还是被跳过?
默认会跳过:skipIfSessionRunning 默认 true,只要会话有运行中或排队中的 turn,本次触发就顺延。单次任务顺延 60 秒后重试。另外 concurrencyPolicy 管的是「同一个任务的上一次执行还没跑完」这种情况,取值 skip / queue / cancel。
- 任务里能用变量吗?
可以。任务正文支持 {{date}}、{{time}}、{{taskName}}、{{triggerType}}、{{executionCount}}、{{interval}}、{{cronExpression}}、{{runAt}}、{{timezone}}、{{nextRunAt}},触发时先替换再注入 [Scheduled Task Context];认不出的变量保持原样。