Skip to content

Scheduled Tasks

DuckAgent exposes cron-style scheduled tasks as built-in capabilities. The scheduler is program-internal: it runs inside the long-lived DuckAgent service process and does not create operating system cron entries.

Scheduled tasks are real Agent work, not passive notifications. When a task becomes due, DuckAgent writes run state, injects a scheduled event into the target session, and starts a normal Agent loop. That loop can call capabilities, read the prior conversation, update files, ask for approvals, and send a final user-facing reply through the same session or gateway route.

Scheduled tasks can represent reminders and automations:

User requestCapability shape
”Remind me in five minutes to buy groceries.”cron_create with a once schedule using an absolute RFC3339 timestamp.
”Every day at 8 AM, summarize what we talked about before.”cron_create with a daily schedule at 08:00 and an agent prompt.
”Pause that reminder.”cron_pause for the job id.
”Change it to 9 AM.”cron_update with the latest job revision.

The model receives a CURRENT TIME dynamic context block on every request. It uses that block to convert relative scheduling requests into durable schedules. For example, “in five minutes” becomes a once schedule with an absolute RFC3339 timestamp, while “every morning at 8” becomes a daily schedule with time: "08:00" and timezone: "local" unless the user named another timezone.

CapabilityPurpose
cron_createCreate a durable scheduled task.
cron_listList jobs with next run time, revision, and recent run state.
cron_getRead one job by id.
cron_updateAppend a new job revision.
cron_deleteAppend a tombstone; old records remain in JSONL.
cron_pauseDisable a job without deleting it.
cron_resumeRe-enable a paused job.

Typical capability shapes:

{
"name": "Morning conversation summary",
"schedule": {
"kind": "daily",
"time": "08:00",
"timezone": "local"
},
"prompt": "Summarize what we talked about before and send the user a concise update."
}
{
"name": "Buy groceries reminder",
"schedule": {
"kind": "once",
"at": "2026-06-01T08:05:00+08:00"
},
"prompt": "Remind the user to buy groceries."
}

Cron state lives under the active profile:

~/.duckagent/profiles/<name>/cron/jobs.jsonl
~/.duckagent/profiles/<name>/cron/runs.jsonl

Both files are append-only.

  • jobs.jsonl records create, update, pause, resume, and delete events.
  • runs.jsonl records started, finished, failed, and skipped runs.
  • Deletes are tombstones; old records remain available for audit and recovery.
  • Updates use revisions so stale changes can be rejected instead of silently replacing newer state.
  • The replayed state is derived from the JSONL log; old rows are not edited in place.

The scheduler keeps one program timer armed for the nearest enabled task. When the timer fires, it runs due jobs, records run state, recomputes the next due time, and rearms the timer.

Supported schedule kinds:

KindFields
onceat, an RFC3339 timestamp.
intervalevery_seconds, with optional anchor.
dailytime plus optional timezone.
weeklyweekdays, time, and optional timezone.

Timezone values support local, UTC, and fixed offsets like +08:00. Named timezone strings are accepted as labels and evaluated with the process-local timezone.

Each job also has execution policy:

PolicyDefaultMeaning
overlapskipIf the previous run is still active, skip the new due run instead of running the same job concurrently. parallel allows overlap.
missed_runrun_onceIf DuckAgent was not running when a task became due after the job was created, run the latest missed occurrence once. skip advances to the next future occurrence.
timeout_seconds1800Mark an asynchronous run as failed if it does not finish before the timeout.

The scheduler starts with:

Terminal window
duck gateway service start

Tasks fire while that service is running. If the service is stopped, no OS-level cron entry wakes DuckAgent. On the next service start, missed jobs follow their missed_run policy.

A task can target a normal session, but gateway-created sessions can also recover their channel route so the final response is delivered back to the same conversation after a service restart.

Approvals are not bypassed. If a scheduled task needs approval and the target route is available, the approval prompt is delivered through that channel. If no route exists, the approval request is denied rather than silently escalating permissions.

If the target session is already running an Agent loop when a scheduled event fires, DuckAgent queues the cron event as a separate user input for that session. It does not merge the scheduled task into an unrelated user turn, and it keeps the cron-specific approval provider with that queued input.