> ## Documentation Index
> Fetch the complete documentation index at: https://comis-fix-skill-import-vetting-gate.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Scheduler

> Cron jobs, heartbeat monitoring, and task extraction

The scheduler lets your agents work on a schedule — sending periodic reports, running health checks, and even creating their own tasks from conversations. Think of it as a personal assistant's calendar that agents manage themselves.

## Three Scheduling Systems

Comis includes three complementary scheduling systems, each designed for a different kind of automated work.

### Cron Jobs

Cron jobs are recurring tasks that run on a schedule, like "check email every morning" or "send a daily summary at 5pm." Your agents create and manage cron jobs using the scheduling tools — you configure the limits, and they handle the details.

```yaml theme={}
# config.yaml
scheduler:
  cron:
    enabled: true
    maxRunsPerTick: 3                     # Maximum due occurrences admitted by one tick
    defaultTimezone: "UTC"                # Authoring timezone when no zone is supplied
    maxJobs: 100                           # Positive cap on agent-authored jobs
    maxConsecutiveDependencyErrors: 5     # Suspend after repeated dependency failures
    staggerWindowMs: 0                    # Stable spread for recurring scheduled fires
    # wakeGate: true                       # Optional scheduler-initiated gate override
```

| Setting                          | Default   | What It Does                                                                                                                                |
| -------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `enabled`                        | `true`    | Whether cron scheduling is active                                                                                                           |
| `maxRunsPerTick`                 | `3`       | Maximum due cron occurrences admitted by one scheduler tick                                                                                 |
| `defaultTimezone`                | `"UTC"`   | IANA authoring timezone used when a cron expression does not specify one                                                                    |
| `maxJobs`                        | `100`     | Positive authored-job cap; config-owned jobs and retained terminal records are separate                                                     |
| `maxConsecutiveDependencyErrors` | `5`       | Consecutive scheduled dependency failures before a recurring job is suspended; `0` disables suspension                                      |
| `staggerWindowMs`                | `0`       | Stable eligibility spread, in milliseconds, applied only to recurring scheduled fires                                                       |
| `wakeGate`                       | *(unset)* | Optional scheduler-initiated gate override: `true` enables it, `false` disables it, and unset follows the agent's `autonomy.script` surface |

<Tip>Agents can create their own cron jobs using the scheduling tools. You set the authored-job cap and the number of due occurrences admitted per tick; they decide what to schedule and when.</Tip>

### Heartbeat Monitoring

The heartbeat system runs periodic health checks at regular intervals (every 5 minutes by default). This is how the [monitoring sources](/operations/monitoring) — disk space, CPU, memory, systemd services, and security updates — get checked.

```yaml theme={}
# config.yaml
scheduler:
  heartbeat:
    enabled: true
    intervalMs: 300000        # Check every 5 minutes
    alertThreshold: 2         # Failures before alerting
    alertCooldownMs: 300000   # Wait 5 min between alerts for same source
    staleMs: 120000           # Detect stuck checks after 2 min
    showOk: false             # Don't report when everything is fine
    showAlerts: true          # Do report problems
```

| Setting           | Default        | What It Does                                             |
| ----------------- | -------------- | -------------------------------------------------------- |
| `intervalMs`      | 300000 (5 min) | How often health checks run                              |
| `alertThreshold`  | 2              | Consecutive failures required before sending an alert    |
| `alertCooldownMs` | 300000 (5 min) | Minimum time between repeated alerts for the same issue  |
| `staleMs`         | 120000 (2 min) | If a check runs longer than this, it is considered stuck |
| `showOk`          | `false`        | Whether to include healthy sources in the report         |
| `showAlerts`      | `true`         | Whether to include alerts in the report                  |

The heartbeat uses **wake coalescing** — all monitoring checks are batched together into a single heartbeat cycle rather than each running independently. This prevents multiple checks from overwhelming the system simultaneously.

### Task Extraction

During a conversation, your agent might notice follow-up work — "I should check back on this tomorrow" or "remind the user about that deadline." Task extraction lets agents automatically schedule these follow-ups.

```yaml theme={}
# config.yaml
scheduler:
  tasks:
    enabled: false                      # Explicit opt-in for model-inferred follow-ups
    confidenceThreshold: 0.8            # Minimum extraction confidence (0-1)
    debounceMs: 15000                   # Per-agent batching delay
    batchMax: 8                         # Maximum delivered turns per batch
    maxPerCheck: 3                      # Maximum exact-origin tasks checked at once
    maxPerDayPerConversation: 3         # Rolling visible-send cap per conversation
    defaultWindowMs: 43200000           # Default due-time slack (12 hours)
    preAcceptanceRetryLimit: 3          # Retries after the initial pre-acceptance attempt
```

| Setting                    | Default    | What It Does                                                                    |
| -------------------------- | ---------- | ------------------------------------------------------------------------------- |
| `enabled`                  | `false`    | Explicit opt-in for model-inferred follow-up tasks from delivered conversations |
| `confidenceThreshold`      | `0.8`      | Minimum extraction confidence from `0` to `1`                                   |
| `debounceMs`               | `15000`    | Per-agent delay used to form a bounded extraction batch                         |
| `batchMax`                 | `8`        | Maximum delivered turns in one extraction batch                                 |
| `maxPerCheck`              | `3`        | Maximum exact-origin tasks considered in one proactive check                    |
| `maxPerDayPerConversation` | `3`        | Rolling visible-send cap for one conversation                                   |
| `defaultWindowMs`          | `43200000` | Default slack after the minimum due instant (12 hours)                          |
| `preAcceptanceRetryLimit`  | `3`        | Additional pre-acceptance attempts allowed after the initial attempt            |

<Info>Task extraction requires explicit opt-in with `scheduler.tasks.enabled: true`. Extracted work remains subject to the confidence, batch, retry, and per-conversation delivery limits above.</Info>

## Quiet Hours

Quiet hours control scheduled delivery and task-lane admission during specified times — for example, overnight when you do not want notifications. They do not stop the cron engine or agent heartbeat execution.

```yaml theme={}
# config.yaml
scheduler:
  quietHours:
    enabled: false
    start: "22:00"
    end: "07:00"
    timezone: "UTC"        # Explicit IANA timezone
    criticalBypass: true   # Critical heartbeat delivery may bypass quiet hours
```

| Setting          | Default   | What It Does                                                                 |
| ---------------- | --------- | ---------------------------------------------------------------------------- |
| `enabled`        | `false`   | Whether quiet hours are active                                               |
| `start`          | `"22:00"` | Quiet window start in `HH:MM` format                                         |
| `end`            | `"07:00"` | Quiet window end in `HH:MM` format                                           |
| `timezone`       | `"UTC"`   | Explicit IANA timezone used to evaluate the window                           |
| `criticalBypass` | `true`    | Whether critical heartbeat output can bypass quiet-hour delivery suppression |

When quiet hours are active, cron and agent-heartbeat runs still execute. At the closed delivery boundary, routine and non-critical delivery is suppressed; critical heartbeat output may bypass that boundary when `criticalBypass` is `true`. Task-lane checks defer before the model runs until the configured quiet window ends, avoiding an unnecessary model call during the window.

<Tip>`criticalBypass` applies to qualifying critical heartbeat delivery. It does not disable the quiet window or make task-lane checks run early.</Tip>

## Wake-Gate Efficiency

A polling cron job -- "check the build queue every five minutes," "scan the inbox every ten" -- usually finds nothing changed, and waking the model on every empty fire is pure waste. A [wake-gate](/agent-tools/scheduling#wake-gate-run-the-model-only-when-it-matters) lets a job answer "nothing to do" *before* the model runs: a small sandboxed script inspects the world and returns a verdict, and only a verdict that says "wake" spends a model turn. A gate can also deliver a routine ✓ status straight to the channel with no model turn at all -- and that routine status honors quiet hours, so an all-clear ping never lands overnight.

The gate is an **efficiency** layer, not a suppression control, and its posture is designed to stay legible to an operator:

* **Fail-open.** A gate that errors, times out, over-caps its output, or returns no verdict **wakes** the model. A broken gate can never silently drop a monitored job -- the worst a misbehaving gate does is wake the model too often (spending turns), never too rarely (missing an alert).
* **Skip-rate.** The share of a job's fires the gate skipped. A high skip-rate is usually the gate *working* -- most polls found nothing -- not a fault. A gate at a **100% skip-rate on a monitor you expect to fire** is the signal to inspect: it may be genuinely quiet, or its logic may have broken toward always-skip.
* **Net cost.** A gate's own tool calls (its `web_fetch`/`read`/`grep` cost) sit beside the model turns it saved. A gate whose tool-call cost exceeds the turns it avoids is not paying for itself -- compare the two numbers directly.

These signals roll up per agent in the [`comis system-health`](/reference/cli#comis-system-health) health report (skip-rate, turns-saved, tool-call cost -- counts only, never the gate's gathered findings), and each per-fire decision is visible in the job's run history via [`cron.runs`](/reference/json-rpc#cron-runs). Because the gate runs under the agent's own [autonomy capabilities](/agents/autonomy) in the standard jail, it grants no new reach -- see the [capability model](/security/capability-model#wake-gate-suppression-bound) for the suppression bound.

Scheduler-initiated gates are governed deployment-wide by the **`scheduler.cron.wakeGate`** toggle. It defaults to each agent's autonomy `script` surface -- where an agent may run `orchestrate` scripts, its cron jobs may run wake-gates. Set it to `false` to disable scheduler-initiated pre-run gates while still allowing model-initiated `orchestrate`: a scheduler gate is a distinct trust context (no human or model in the loop at fire time), and with the toggle off a gated job simply runs as it would with no gate. The toggle grants no capability -- it only decides whether the pre-run script runs.

## Safety

Each scheduled occurrence crosses durable safety boundaries before and after runtime execution:

* A **durable claim** prevents a second owner from dispatching the same occurrence and gives startup reconciliation an authoritative recovery record.
* A **start/terminal execution ledger** records the start before runtime dispatch and appends exactly one terminal outcome afterward.
* **`maxRunsPerTick`** limits, per scheduler tick, how many due occurrences are admitted (default: `3`); admitted occurrences execute sequentially.
* **`maxConsecutiveDependencyErrors`** counts only dependency-classified failures from recurring scheduled fires and suspends the job at the configured threshold. Internal failures and manual runs do not advance this breaker.
* **`maxJobs`** is a positive cap on agent-authored jobs (default: `100`).

The claim and immutable ledger are reconciled on startup. A started occurrence without terminal evidence is preserved conservatively rather than reported as successful or dispatched again blindly.

## Execution Ledger

The execution ledger stores complete start/terminal groups. Capacity is bounded by bytes and by the number of complete groups retained; the configured byte limit must also reserve enough room for the per-tick admission limit.

| Setting              | Default   | What It Does                                                            |
| -------------------- | --------- | ----------------------------------------------------------------------- |
| `maxLogBytes`        | `2000000` | Maximum execution ledger size in bytes                                  |
| `retainedExecutions` | `1000`    | Complete start/terminal execution groups retained when capacity permits |

## Full Configuration Reference

```yaml theme={}
# config.yaml
scheduler:
  cron:
    enabled: true
    maxRunsPerTick: 3
    defaultTimezone: "UTC"
    maxJobs: 100
    maxConsecutiveDependencyErrors: 5
    staggerWindowMs: 0
    # wakeGate: true  # Optional; unset follows the agent's autonomy.script surface

  heartbeat:
    enabled: true
    intervalMs: 300000
    showOk: false
    showAlerts: true
    alertThreshold: 2
    alertCooldownMs: 300000
    staleMs: 120000

  quietHours:
    enabled: false
    start: "22:00"
    end: "07:00"
    timezone: "UTC"
    criticalBypass: true

  execution:
    maxLogBytes: 2000000
    retainedExecutions: 1000

  tasks:
    enabled: false
    confidenceThreshold: 0.8
    debounceMs: 15000
    batchMax: 8
    maxPerCheck: 3
    maxPerDayPerConversation: 3
    defaultWindowMs: 43200000
    preAcceptanceRetryLimit: 3
```

## Related Pages

<CardGroup cols={2}>
  <Card title="Monitoring" icon="heart-pulse" href="/operations/monitoring">
    The 5 health sources checked by heartbeat
  </Card>

  <Card title="Daemon" icon="server" href="/operations/daemon">
    The process that runs the scheduler
  </Card>

  <Card title="Agent Lifecycle" icon="arrows-spin" href="/agents/lifecycle">
    How agents process messages and use tools
  </Card>

  <Card title="Scheduler View" icon="calendar" href="/web-dashboard/scheduler-view">
    View and manage scheduled tasks in the dashboard
  </Card>
</CardGroup>
