You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The CoS Schedule "Interval Type" dropdown currently offers seven mutually exclusive options (Rotation, Daily, Weekly, Once, On Demand, Cron, Perpetual). Several options duplicate cadence functionality, and the system cannot represent a task that is both perpetual (drains backlog until work-detector idles) and scheduled (starts its drain cycle via cron).
The goal is to simplify task cadence to a two-variant model (On-Demand vs Scheduled) while making perpetual an orthogonal boolean flag on any task schedule.
Target model:
Cadence model:type is either on-demand or cron (Scheduled in the UI).
Daily / Weekly options removed: Replaced by standard 5-field cron expressions via CronInput presets (0 7 * * *, 0 7 * * 1, etc.).
Once option removed: Represented as on-demand (manual trigger only; no auto-run or reset loop).
Perpetual flag:perpetual: boolean becomes an independent toggle available for both on-demand and cron tasks.
Rotation option deprecated: Removed from UI and runtime.
Existing persisted configurations must upgrade seamlessly in-place so no existing task silently changes cadence or starts unexpected execution.
Context
Current UI dropdown screenshot reference: data/screenshots/Screenshot_2026-09-01_at_11.37.49_PM.png.
Current Enums & Runtime Dispatch
Canonical Enum (8 values, 7 in global UI):server/services/taskScheduleConstants.js:6-15 (ROTATION, DAILY, WEEKLY, ONCE, ON_DEMAND, CUSTOM, CRON, PERPETUAL).
Runtime Dispatch:shouldRunTask in server/services/taskSchedule.js:888-1005 handles each type in a switch statement.
Queue Priority:getNextTaskType in server/services/taskSchedule.js:1068-1138 checks cron/custom → perpetual → daily → weekly → once → rotation round-robin.
Default Fallbacks:getTaskInterval (server/services/taskSchedule.js:171) and updateTaskInterval (server/services/taskSchedule.js:182) fallback to rotation when intervals are unknown or unconfigured.
Perpetual vs On-Demand Coupling Today
Switching to Perpetual in GlobalConfigControls.jsx:285-310 writes { type: 'perpetual' } which overwrites and clears cronExpression.
Refill runs queue through on-demand (ON_DEMAND_ORIGINS.REFILL in server/services/taskScheduleConstants.js:22-25).
Drain candidate detection hardcodes special cases across isPerpetualRefillCandidate (cos.js:1466-1476), applyPerpetualDrainCap (cosTaskGenerator.js:2845-2849), and emitOnDemandEmpty (cosTaskGenerator.js:2579-2580): checking type === PERPETUALortype === ON_DEMAND && isReconcileDrainTaskType(analysisType).
UI & API Surfaces Requiring Updates
Global Controls:client/src/components/cos/tabs/schedule/GlobalConfigControls.jsx:285-310, 717-726.
Files on Disk:data/cos/task-schedule.json, data/task-schedule.json, and apps.json (taskTypeOverrides[taskType].interval / intervalMs).
Migration System: Scripts live in scripts/migrations/NNN-*.js. Latest applied migration on tree is 331-codex-text-transport.js. Next migration is 332-schedule-interval-types.js.
loadSchedule() in server/services/taskScheduleStore.js handles runtime loading and defaults merging.
Manual trigger only via "Run Now". Never auto-queued by scheduler (shouldRun: false, reason on-demand-only).
on-demand
true
Drain-until-done backlog execution. When parked, rechecks according to recheckCron or recheckIntervalMs (default 24h). Run Now resets brakes.
cron
false
Clock-scheduled execution. Evaluates cronExpression slots and catch-up rules.
cron
true
A matching cron slot initiates a drain run. While work remains (unparked), task continues draining back-to-back regardless of subsequent cron ticks. Once parked, the same cronExpression gates the next drain attempt (no separate recheckCron required).
Unknown or unconfigured tasks default to on-demand instead of rotation.
2. Consolidate Drain Special Cases
Replace checks for type === PERPETUAL || isReconcileDrainTaskType(...) in isPerpetualRefillCandidate, applyPerpetualDrainCap, emitOnDemandEmpty, and getNextTaskType with an explicit check for Boolean(interval.perpetual).
Shipped defaults for branch-reconcile and issue-reconcile will be set to type: 'on-demand' with perpetual: true.
Per-app interval values matching legacy enums map to the same rules; raw 5-field cron strings remain intact.
Numeric intervalMs to Cron Conversion Helper:
15m (900000ms) → */15 * * * *
30m (1800000ms) → */30 * * * *
1h (3600000ms) → 0 * * * *
N * 1h (where N divides 24) → 0 */N * * *
24h (86400000ms) → 0 7 * * *
7d (604800000ms) → 0 7 * * 1
Other values round to nearest minute */M * * * * (M capped at 59) or hour 0 */H * * * (H capped at 23).
Runtime Normalization:
loadSchedule() in server/services/taskScheduleStore.js will normalize legacy record shapes on read so un-migrated JSON payloads remain valid in memory and update cleanly on next save.
4. UI & Component Updates
GlobalConfigControls.jsx:
Interval Type select displays two options: On Demand (manual trigger only) (on-demand) and Scheduled (cron) (cron).
Render an independent Perpetual checkbox below the cadence select.
Recheck cron input displays when type === 'on-demand' && perpetual === true. Hidden when type === 'cron' && perpetual === true.
Remove the legacy "Once Reset" button (GlobalConfigControls.jsx:717-726).
AppOverrideRow.jsx & AutomationTab.jsx:
Options updated to: Inherit, On Demand, Scheduled.
LayeredIntelligenceTab.jsx:
Update intervalFieldsFromMs to write the 5-field cron string as interval (per-app overrides already treat a space-containing string as cron — taskSchedule.js:846-847). Do not write the UI sentinel interval: 'cron', which only opens the editor and saves no cadence. Keep intervalMs if the numeric picker remains.
scheduleConstants.js & IntervalBadge.jsx:
Update INTERVAL_LABELS and INTERVAL_DESCRIPTIONS to reflect On-Demand and Scheduled.
describeNextRun checks config.perpetual boolean instead of config.type === 'perpetual'.
Render Perpetual as an additional status badge when config.perpetual is true.
Remove completed status group from STATUS_GROUPS and TASK_FILTERS.
UpcomingTasksWidget.jsx & WorkflowTab.jsx:
Update local label mapping and calendar projection logic for cron and perpetual flags.
5. API Routes & Validation
server/routes/cosScheduleRoutes.js:
Add 'perpetual' to SCHEDULE_FIELDS.
Update GET /api/cos/schedule/interval-types to return { ON_DEMAND: 'on-demand', CRON: 'cron' } and updated descriptions.
Update PUT body validation to accept on-demand and cron, with single-release auto-rewrite for legacy strings (rotation, daily, weekly, once, custom, perpetual).
server/routes/apps/taskTypes.js:
Update per-app interval validation to allow on-demand, 5-field cron strings, or null (with legacy fallback rewrite).
Acceptance Criteria
Global config, per-app override, and automation tab select controls display only On Demand and Scheduled options (plus Inherit on per-app dropdowns). Legacy types (Rotation, Daily, Weekly, Once, Perpetual) are removed from all pickers.
Perpetual is an independent toggle active for both On-Demand and Scheduled tasks.
A task configured as Scheduled + Perpetual initiates a drain on its cron schedule, drains until parked, and uses its cronExpression as the recheck schedule.
Migration 332-schedule-interval-types.js (and unit test) transforms existing schedule records in data/cos/task-schedule.json, data/task-schedule.json, and apps.json idempotently.
Un-migrated schedule files loaded at runtime are normalized cleanly by loadSchedule().
once tasks convert to on-demand; daily/weekly/rotation convert to 07:00 cron expressions; perpetual converts to on-demand with perpetual: true.
Default configurations for branch-reconcile and issue-reconcile are set to on-demand with perpetual: true.
Unknown tasks default to on-demand rather than rotation.
Layered Intelligence tab writes a 5-field cron string as the per-app interval (not daily/weekly/custom, and not the 'cron' editor sentinel).
Disabled tasks stay disabled. Already-cron tasks keep their expression. weekdaysOnly is preserved.
All unit and integration test suites (taskSchedule.test.js, cosScheduleRoutes.test.js, scheduleConstants.test.js, component tests) pass with updated models.
docs/API.md and related schedule documentation are updated to reflect the new API schemas and interval types.
Out of Scope
Modifying autonomous job recurrence schemas (createCosJobSchema, cronSchedule, recurrenceRuleSchema) or eventScheduler.
Per-app perpetual flag overrides (the perpetual toggle remains a task-level global property).
Altering drainDispatchCap, work-detectors, or refill origin logic outside of keying on interval.perpetual.
Federation or schemaVersions.js changes (schedules remain machine-local JSON).
rotation maps to 0 7 * * * (daily at 7 AM) as a conservative fallback because rotation is un-shipped in defaults and mapping to perpetual would cause excessive background drain.
weekly maps to 0 7 * * 1 (Mondays at 7 AM) to prevent automated work tasks from scheduling over weekend boundaries.
Problem / Goal
The CoS Schedule "Interval Type" dropdown currently offers seven mutually exclusive options (
Rotation,Daily,Weekly,Once,On Demand,Cron,Perpetual). Several options duplicate cadence functionality, and the system cannot represent a task that is both perpetual (drains backlog until work-detector idles) and scheduled (starts its drain cycle via cron).The goal is to simplify task cadence to a two-variant model (
On-DemandvsScheduled) while makingperpetualan orthogonal boolean flag on any task schedule.Target model:
typeis eitheron-demandorcron(Scheduledin the UI).CronInputpresets (0 7 * * *,0 7 * * 1, etc.).on-demand(manual trigger only; no auto-run or reset loop).perpetual: booleanbecomes an independent toggle available for bothon-demandandcrontasks.Existing persisted configurations must upgrade seamlessly in-place so no existing task silently changes cadence or starts unexpected execution.
Context
Current UI dropdown screenshot reference:
data/screenshots/Screenshot_2026-09-01_at_11.37.49_PM.png.Current Enums & Runtime Dispatch
server/services/taskScheduleConstants.js:6-15(ROTATION,DAILY,WEEKLY,ONCE,ON_DEMAND,CUSTOM,CRON,PERPETUAL).shouldRunTaskinserver/services/taskSchedule.js:888-1005handles each type in aswitchstatement.getNextTaskTypeinserver/services/taskSchedule.js:1068-1138checkscron/custom→perpetual→daily→weekly→once→rotationround-robin.getTaskInterval(server/services/taskSchedule.js:171) andupdateTaskInterval(server/services/taskSchedule.js:182) fallback torotationwhen intervals are unknown or unconfigured.Perpetual vs On-Demand Coupling Today
GlobalConfigControls.jsx:285-310writes{ type: 'perpetual' }which overwrites and clearscronExpression.ON_DEMAND_ORIGINS.REFILLinserver/services/taskScheduleConstants.js:22-25).isPerpetualRefillCandidate(cos.js:1466-1476),applyPerpetualDrainCap(cosTaskGenerator.js:2845-2849), andemitOnDemandEmpty(cosTaskGenerator.js:2579-2580): checkingtype === PERPETUALortype === ON_DEMAND && isReconcileDrainTaskType(analysisType).UI & API Surfaces Requiring Updates
client/src/components/cos/tabs/schedule/GlobalConfigControls.jsx:285-310, 717-726.client/src/components/cos/tabs/schedule/AppOverrideRow.jsx:133-140.client/src/components/apps/tabs/AutomationTab.jsx:17-25.client/src/components/apps/LayeredIntelligenceTab.jsx:181-186(intervalFieldsFromMs).client/src/components/cos/tabs/schedule/scheduleConstants.js:5-25, 56-63, 68-85, 106-161.client/src/components/cos/tabs/schedule/IntervalBadge.jsx.client/src/components/UpcomingTasksWidget.jsx:35-44.server/services/workflow.js:459-470andclient/src/components/cos/tabs/WorkflowTab.jsx:18-40./api/cos/schedule/interval-types:server/routes/cosScheduleRoutes.js:240-255.SCHEDULE_FIELDS):server/routes/cosScheduleRoutes.js:24-32.server/routes/apps/taskTypes.js:241-258.Persistence & Migrations
data/cos/task-schedule.json,data/task-schedule.json, andapps.json(taskTypeOverrides[taskType].interval/intervalMs).scripts/migrations/NNN-*.js. Latest applied migration on tree is331-codex-text-transport.js. Next migration is332-schedule-interval-types.js.loadSchedule()inserver/services/taskScheduleStore.jshandles runtime loading and defaults merging.Proposed Approach
1. Unified Cadence Model
Update
server/services/taskScheduleConstants.js:Task schedule objects store:
type:'on-demand'|'cron'cronExpression: string | nullperpetual: boolean (defaultfalse)Execution Behavior Matrix
typeperpetualon-demandfalseshouldRun: false, reasonon-demand-only).on-demandtruerecheckCronorrecheckIntervalMs(default 24h). Run Now resets brakes.cronfalsecronExpressionslots and catch-up rules.crontruecronExpressiongates the next drain attempt (no separaterecheckCronrequired).Unknown or unconfigured tasks default to
on-demandinstead ofrotation.2. Consolidate Drain Special Cases
Replace checks for
type === PERPETUAL || isReconcileDrainTaskType(...)inisPerpetualRefillCandidate,applyPerpetualDrainCap,emitOnDemandEmpty, andgetNextTaskTypewith an explicit check forBoolean(interval.perpetual).Shipped defaults for
branch-reconcileandissue-reconcilewill be set totype: 'on-demand'withperpetual: true.3. In-Place Migration and Runtime Normalization
File Migration (
scripts/migrations/332-schedule-interval-types.js):data/cos/task-schedule.json,data/task-schedule.json, andapps.jsonoverrides.once→type: 'on-demand',perpetual: false.daily→type: 'cron',cronExpression: '0 7 * * *'.weekly→type: 'cron',cronExpression: '0 7 * * 1'.rotation→type: 'cron',cronExpression: '0 7 * * *'.perpetual→type: 'on-demand',perpetual: true(retainingrecheckCron/recheckIntervalMs).custom→type: 'cron', generatingcronExpressionfromintervalMsusing the conversion helper below.on-demand→type: 'on-demand'(gainingperpetual: trueforbranch-reconcile/issue-reconcile).intervalvalues matching legacy enums map to the same rules; raw 5-field cron strings remain intact.Numeric
intervalMsto Cron Conversion Helper:15m(900000ms) →*/15 * * * *30m(1800000ms) →*/30 * * * *1h(3600000ms) →0 * * * *N * 1h(where N divides 24) →0 */N * * *24h(86400000ms) →0 7 * * *7d(604800000ms) →0 7 * * 1*/M * * * *(M capped at 59) or hour0 */H * * *(H capped at 23).Runtime Normalization:
loadSchedule()inserver/services/taskScheduleStore.jswill normalize legacy record shapes on read so un-migrated JSON payloads remain valid in memory and update cleanly on next save.4. UI & Component Updates
GlobalConfigControls.jsx:On Demand (manual trigger only)(on-demand) andScheduled (cron)(cron).CronInput(seedingcronExpression || '0 7 * * *').type === 'on-demand' && perpetual === true. Hidden whentype === 'cron' && perpetual === true.GlobalConfigControls.jsx:717-726).AppOverrideRow.jsx&AutomationTab.jsx:Inherit,On Demand,Scheduled.LayeredIntelligenceTab.jsx:intervalFieldsFromMsto write the 5-field cron string asinterval(per-app overrides already treat a space-containing string as cron —taskSchedule.js:846-847). Do not write the UI sentinelinterval: 'cron', which only opens the editor and saves no cadence. KeepintervalMsif the numeric picker remains.scheduleConstants.js&IntervalBadge.jsx:INTERVAL_LABELSandINTERVAL_DESCRIPTIONSto reflectOn-DemandandScheduled.describeNextRunchecksconfig.perpetualboolean instead ofconfig.type === 'perpetual'.Perpetualas an additional status badge whenconfig.perpetualis true.completedstatus group fromSTATUS_GROUPSandTASK_FILTERS.UpcomingTasksWidget.jsx&WorkflowTab.jsx:cronandperpetualflags.5. API Routes & Validation
server/routes/cosScheduleRoutes.js:'perpetual'toSCHEDULE_FIELDS.GET /api/cos/schedule/interval-typesto return{ ON_DEMAND: 'on-demand', CRON: 'cron' }and updated descriptions.on-demandandcron, with single-release auto-rewrite for legacy strings (rotation,daily,weekly,once,custom,perpetual).server/routes/apps/taskTypes.js:on-demand, 5-field cron strings, ornull(with legacy fallback rewrite).Acceptance Criteria
Rotation,Daily,Weekly,Once,Perpetual) are removed from all pickers.On-DemandandScheduledtasks.Scheduled+Perpetualinitiates a drain on its cron schedule, drains until parked, and uses itscronExpressionas the recheck schedule.332-schedule-interval-types.js(and unit test) transforms existing schedule records indata/cos/task-schedule.json,data/task-schedule.json, andapps.jsonidempotently.loadSchedule().oncetasks convert toon-demand;daily/weekly/rotationconvert to 07:00 cron expressions;perpetualconverts toon-demandwithperpetual: true.branch-reconcileandissue-reconcileare set toon-demandwithperpetual: true.on-demandrather thanrotation.interval(notdaily/weekly/custom, and not the'cron'editor sentinel).crontasks keep their expression.weekdaysOnlyis preserved.taskSchedule.test.js,cosScheduleRoutes.test.js,scheduleConstants.test.js, component tests) pass with updated models.docs/API.mdand related schedule documentation are updated to reflect the new API schemas and interval types.Out of Scope
createCosJobSchema,cronSchedule,recurrenceRuleSchema) oreventScheduler.drainDispatchCap, work-detectors, or refill origin logic outside of keying oninterval.perpetual.schemaVersions.jschanges (schedules remain machine-local JSON).PROMPT_VERSIONS/PREVIOUS_DEFAULT_PROMPTS).Open Questions
None — decisions are locked:
rotationmaps to0 7 * * *(daily at 7 AM) as a conservative fallback because rotation is un-shipped in defaults and mapping to perpetual would cause excessive background drain.weeklymaps to0 7 * * 1(Mondays at 7 AM) to prevent automated work tasks from scheduling over weekend boundaries.