Skip to main content

Scheduled tasks

Schedule a saved prompt to run on a cron cadence or once at a future time. Each fire starts a fresh headless session with its configured workspace, model, permissions, limits, and mutation setting.

Availability​

Scheduling requires a backend that implements ScheduleStore:

  • local JSONL storage from mecated --store-dir;
  • Redis storage from mecak8s --redis-url;
  • a remote schedule-store driver selected with --schedule-store-url; or
  • an engine embedding that supplies the port.

Without a schedule-capable backend, the Schedule tool and schedule APIs report that scheduling is unavailable. The plain in-memory store does not provide durable schedules.

Storage and delivery​

The schedule store is authoritative. Each schedule contains an immutable prompt, trigger, model selection, workspace, permission mode, limits, mutation setting, and timezone, plus mutable fire state.

Mecatl claims and advances a due slot before starting its run. This prevents two replicas from firing the same slot. A leader lease limits polling to one replica, while the atomic claim remains the duplicate-execution safeguard.

A crash after a claim can skip that slot. Recurring schedules continue at the next slot. By default a one-shot schedule can be lost. Opt-in retry can repeat the work, so tasks using it must tolerate duplicate effects; use an external job system when you need stronger delivery guarantees. Each successful claim creates a fresh session whose record preserves the conversation, tool calls, usage, terminal state, and fire result.

Embeddings can provide port.ScheduleStore. Implementations must make claims atomic and can use the shared engine/adapter/scheduleconformance suite.

Create and manage a schedule​

Use the in-chat Schedule tool, gRPC, REST API, or mecatui /schedule overlay to create, inspect, pause, resume, delete, or immediately fire a schedule. Mecatl has no separate schedules CLI.

A schedule uses exactly one trigger: a cron expression or a one-shot timestamp. It defaults to read-leaning behavior. A schedule that may use Edit, Write, or Shell must explicitly set mutating: true; this is not an implicit allow-all mode.

For an independently created schedule, the server selects placement from its configured default. The create request has no workspace field. Use the HTTP listener for this REST workflow:

curl -X POST http://127.0.0.1:8081/v1/schedules \
-H 'content-type: application/json' \
-d '{"name":"nightly-report",
"prompt":"summarize commits from today",
"mode":"PERMISSION_MODE_PLAN",
"trigger":{"cron":"0 9 * * *"}}'

curl -X POST http://127.0.0.1:8081/v1/schedules/nightly-report/fire
curl http://127.0.0.1:8081/v1/schedules/nightly-report/fires/<FIRE_ID>

The REST routes are under /v1/schedules; the gRPC service is mecatl.v1.ScheduleService. See the gRPC API reference and the schedule proto for exact request and response fields.

The mecatui overlay is available when the connected server advertises a reachable schedule store.

Placement and deletion​

Mecatl resolves placement when it creates the schedule. Every fire reattaches that exact durable placement. An in-chat schedule borrows its originating session's worktree; an independent schedule owns a separately provisioned placement when the deployment provider supports that lifecycle (including microvm-local). Updates cannot change either relationship.

On mecak8s with remote execution, workspace-backed schedules are limited to in-chat schedules that borrow the origin's Kubernetes workspace. Each fire receives its own session binding to that workspace, including across server and execution-provider restarts. Independent remote workspace allocation is unsupported and fails before allocating an environment; independent no-FS schedules remain available. A fire fails while another run holds the borrowed workspace, and a claimed slot is not retried unless its trigger's retry policy allows it.

The original session must remain stored with the same workspace binding for future fires. Deleting it, including through session retention, makes future fires fail even if retained fire sessions keep the workspace alive. Updates cannot retarget the schedule to another session. Create a new in-chat schedule from a surviving session eligible to use that workspace instead.

Each retained fire session consumes one of the environment's 64 reference slots, shared with the origin and other sessions. Configure fire-session retention or delete unneeded fire sessions before that limit blocks further fires. Deleting only the schedule does not release those references. Existing sessions keep their persisted tool authority; start a new session if the Schedule tool is absent from an older session's authority.

Deleting a borrowed schedule leaves its origin untouched. Deleting an independently placed schedule first persists an atomic, restart-safe deletion marker; claimed/running fires must settle before that transition, and inspect/list report deletion pending until conditional completion succeeds.

Before the first claim, deletion cleans the owned placement while preserving dirty state. The first atomic claim hands placement lifetime to the fire-session lineage; after it, deleting the schedule removes only its record and retains the clean or dirty worktree for historical and resumable fire sessions. Deletion never destroys a shared repository VM, rootfs, or sibling worktree. Legacy records with ambiguous ownership keep the prior direct-delete behavior and are never guessed to be owned.

Automatic firing​

The automatic tick loop runs by default when the configured store supports ScheduleStore. Use --no-scheduler to disable automatic polling while keeping manual create, list, inspect, and fire operations available.

Important operator settings include:

FlagDefaultPurpose
--no-schedulerfalseDisable automatic polling; manual management remains available.
--scheduler-tick-interval30sPoll interval for due schedules.
--scheduler-min-interval1mMinimum recurring cadence; 0 disables the floor.
--scheduler-max-concurrent-fires4Maximum due fires processed in parallel per tick.
--schedule-fire-retention7d when unsetRetention for persisted fire sessions.
--schedule-store-urlemptyRemote schedule-store driver, independent of the session store.

Results and recovery​

Each fire creates a top-level session. A durable store preserves its conversation, tool calls, usage, and terminal state. Inspect the result with GetFire, ListFires, the REST routes, or the mecatui overlay.

Scheduled fires are headless: no person is available to approve a tool call. Use a read-leaning mode or explicitly configure the permissions and mutation posture the task requires.

Missed slots and overlap​

The default misfire policy fires once immediately for a missed recurring slot, then resumes the cadence. It does not replay every missed occurrence. Choose skip when a stale run would be misleading.

New schedules suppress overlapping fires by default. The prior fire's session lease determines whether it is still active across replicas. A released or expired lease permits later work; a stale session pointer alone does not block the schedule forever. Manual firing also rejects an overlapping active fire.

One-shot retry​

one_shot_retry is off by default. Enable it for a one-shot task that can safely run more than once: the scheduler can re-arm a fire lost in a crash or ending in an error. one_shot_max_retries defaults to three when retry is enabled and no budget is supplied. The durable retry counter limits re-arming across restarts. Recurring schedules reject this setting and use their misfire policy instead.

A re-armed one-shot starts with fresh context, even when carried context is enabled. Retry is at least once within its configured budget, not an exactly-once guarantee.

Carried context​

carry_context is off by default. When enabled, Mecatl loads the prior fire's conversation and includes it as fenced untrusted data in the new prompt. It is background information, not replayed conversation or fresh authority. If the prior session cannot be loaded, the fire continues with fresh context and a diagnostic. Persist durable task state in memory or files when later runs must reload it.

Deadlines and orphaned fires​

The supplied deployment uses a 30-minute fire deadline unless the schedule supplies its own nonzero fire_timeout. A deadline ends the run in a recoverable timeout state. Inspect the fire before deciding whether to continue it manually.

The scheduler reconciles its latest orphaned in-flight fire after a crash. A claim lost before session creation becomes an error fire record; a fire lost during its run becomes an error record with a cancelled, recoverable session. Failed reconciliation is retried on later ticks. This cleanup records what happened; it is separate from opt-in one-shot retry. Older orphaned fire sessions beyond the latest schedule state are not covered by this sweep.

Delivery to the originating conversation​

Schedules created in a conversation can deliver start and completion notes back to that origin when the deployment supplies a delivery queue and the origin remains authorized. A busy origin drains queued notes at a turn boundary; an origin waiting for approval drains them when its approval pause resumes. An idle or terminal origin can start a delivery turn. Delivery failure does not fail the recorded fire: its result remains available for inspection.

Notes treat scheduled output as untrusted data and preserve the origin's permission policy. Missing, unauthorized, or short-lived child origins fall back to inspecting the fire record. There is no external webhook callback guarantee.

Limitations​

  • Every fire starts a separate session; optional carried context supplies only fenced background data.
  • Fire records remain the inspection source even when origin-conversation delivery is enabled.
  • A shareable multi-replica store needs a suitable lease and its own durability, authentication, and TLS configuration.
  • Retained fire sessions and event logs may contain sensitive plaintext; protect the backing store accordingly.

Next steps​