定时任务

Cell 内的定时 Run 调度器,支持按 cron 表达式或延迟触发 Agent Run。


Cron 概念

每个 Cell 拥有独立的定时任务调度器。Cron 任务在指定时间触发 Agent Run,常用于:

Cron 是 per-cell 装配——部门 A 的定时任务对部门 B 物理不可见。


CellSpec.Cron 声明

CellSpec.Cron 是出生声明(INV-SEED-01):

spec := wesgine.CellSpec{
    Cron: []cron.JobSpec{
        {
            Name:     "weekly-report",
            Schedule: "0 9 * * 1",
            AgentID:  "reporter",
            Prompt:   "生成本周工作报告",
        },
    },
}

为什么这样设计? 如果每次 boot 都重新播种,用户运行时删掉的任务下次 boot 会复活——"删除"静默地只等于"到重启为止"。标记在探测成功后无条件写,含声明为空。


Cron API

基础 CRUD

方法路径说明
GET/cron定时任务列表(含 states)
POST/cron添加定时任务
DELETE/cron/{id}删除任务
PATCH/cron/{id}更新任务

调度控制

方法路径说明
POST/cron/{id}/trigger立即触发
POST/cron/{id}/enable启用
POST/cron/{id}/disable禁用

观测

方法路径说明
GET/cron/{id}/runs运行记录
GET/cron/stats调度器统计

SDK 调用

// 添加
err := cell.Cron().Add(ctx, actor, cron.Job{
    Name:     "daily-check",
    Schedule: "0 8 * * *",
    AgentID:  "checker",
    Prompt:   "检查今日待办事项",
})

// 列出
jobs, err := cell.Cron().ListAllStates(ctx, actor)

// 触发
err := cell.Cron().Trigger(ctx, actor, jobID)

// 启用/禁用
err := cell.Cron().Enable(ctx, actor, jobID)
err := cell.Cron().Disable(ctx, actor, jobID)

Actor 边界(INV-CRON-10)

Cron 是 Actor 边界的第四个面——泄漏的不是数据而是执行身份。

executeJob 把 entry.Actor 抄进 JobRequest,job 以所有者身份跑。一次不查主的 Trigger 不是"改了别人的配置",是以别人的身份跑代码。

判据设计

概念实现
可见性单点jobVisible(actor)
by-ID 单点ownedJob(ctx, id, actor)
不存在与不属于同返 ErrJobNotFound
无主 job仅 admin 可见
工具侧全 action 取 tc.Actor
HTTP 侧一律 actorFromRequestOrContext(req)
// Manager 每个方法都收 actor
manager.Add(ctx, actor, job)
manager.Remove(ctx, actor, jobID)
manager.Trigger(ctx, actor, jobID)
manager.Update(ctx, actor, jobID, patch)

ClaimOwnerless — 认领无主任务

升级面:actor 列是 DEFAULT '' 加的,本条生效前写入的每一行都无主。

无主行的症状:

解决:boot 期 ClaimOwnerless(ctx, actor) 认领。

约束

规则原因
只给单 actor 部署用多 actor 盲调 = 把某员工的任务判给另一个
actor 必须是持久身份占位 actor 只是把幽灵搬个家
拿不到持久身份就跳过wesclaw 未登录时的占位名不可用
// 桌面产品 boot 时调用
if uid := authSvc.OwnerID(); uid != "" {
    cell.Cron().ClaimOwnerless(ctx, uid)
}

配额(INV-QUOTA-05)

MaxCronJobs 的判据位置遵循外设配额统一规则:

规则说明
唯一增长点判定Scheduler.Add 是唯一能让集合变大的地方
QuotaFn 提问适配器不知道 cap 数值,只收 QuotaFn(current, delta)
delta 按行存不存在新增 delta=1,覆盖已有名字 delta=0
问与长同临界区防止两个写者各读到 cap-1 各加一

创建时由 validateSeedDeclarationCaps 拒绝装不下自己的声明。