Daemon
Run scheduled agent or shell tasks without keeping an interactive session open.
afk daemon is a headless process for recurring work, startup work, and commands you enqueue on demand. It loads persisted schedules, records execution history, and can notify you through Telegram. For a complete scheduling walkthrough, see Schedule unattended work; to keep the process running across logouts, see Run as a service.
Start a task
Add a recurring agent task:
afk schedule add --name "Daily review" --command "Review the open issues" --cron "0 9 * * 1-5" --notify failureStart the daemon. Persisted schedules are loaded at startup, so you do not need to repeat the task flags:
afk daemonCheck the task ID and its execution history:
afk schedule list
afk schedule logs daily-reviewYou can also supply an ad hoc task directly: afk daemon --cron "0 */6 * * *" --task "Review the repository". Test one tick with --once; the daemon prints a JSON telemetry record and exits.
Triggers and the pull queue
| Trigger | When it fires |
|---|---|
cron | On a five field cron expression. Requires --cron and a task command for an ad hoc daemon task. |
sessionstart | Once at daemon startup, subject to the session start cooldown. |
both | At startup and on the cron schedule. |
pull | Polls the durable queue, by default every 30 seconds. No --cron or --task needed. This is a daemon flag, not a persisted schedule trigger. |
The daemon's --trigger defaults to cron when --cron is supplied, otherwise sessionstart. Persisted schedules accept cron, sessionstart, and both. To feed a pull daemon:
afk daemon --trigger pullIn another terminal, add work and inspect the FIFO queue:
afk queue add "Review the latest failures" --notify-on failure
afk queue listUse afk queue remove TASK_ID to remove a pending item by the ID shown in the list. afk queue clear asks before removing all pending items; afk queue clear --yes is the noninteractive form. Neither command cancels a task already claimed for execution.
Schedule fields
afk schedule add accepts --name, --command, and a required --cron along with --executor agent|shell, --trigger cron|sessionstart|both, --notify failure|always|never, --cwd, and --disabled. The command line cannot set every field below. Use the create_schedule and update_schedule agent tools for one shots, expiry, retries, and targeted notifications. See CLI reference for the command line options.
| Field | Meaning |
|---|---|
cron | Five field schedule. A date pinned in a yearless cron expression repeats annually; it is not a one shot. |
runAt | ISO 8601 date or timestamp for a one shot. Set this instead of cron through create_schedule. The scheduler checks the clock each minute, runs once at or after the specified time, then disables the task. |
expiresAt | ISO 8601 deadline. A task that reaches this deadline is skipped with an expired telemetry record and disabled, whether recurring or one shot. Expiry takes priority over firing. |
trigger | cron (default), sessionstart, or both on a persisted schedule. |
executor | agent (default) starts an Agent AFK session for the prompt or slash command. shell runs the command directly through the platform shell without a model session. Choose shell for simple backups or health checks. |
notifyOn | failure (default), always, or never. Requires Telegram configuration to deliver. |
notifyChat | Numeric chat ID or a telegram.chatAliases name for this task's completion notification. Invalid or nonallowlisted targets fall back to default routing with a daemon warning. |
cwd | Existing absolute directory or a path beginning with ~/. For agent tasks: task cwd, then AFK_DAEMON_CWD, then the daemon process directory. Shell tasks without either configured directory use the daemon state directory instead. |
maxAttempts | Total attempts per scheduled agent run, from 1 to 5. Defaults to 1, with no retry. Only classified transient failures such as provider 429, network errors, or provider 5xx retry. |
retryDelayMs | Retry backoff base, from 1,000 to 300,000 milliseconds; defaults to 30,000. Delay doubles after each failed attempt, capped at five minutes. |
A runAt task does not need a cron expression in create_schedule; the stored polling placeholder is not its firing condition. On the CLI, afk schedule add still requires --cron, so use the agent tool for a true one shot. update_schedule can change a task's fields, including clearing runAt or expiresAt with null. afk schedule enable, disable, and remove manage persisted tasks; write operations attempt live sync and remain on disk for the next start if the daemon is offline.
Two different backoff paths
Scheduled agent retries happen within one run. A second mechanism applies to pull queue lease recovery: a failed or expired leased item that still has attempts available is requeued with an eligibleAfter timestamp, so the next poll will not immediately claim it. Its retry policy uses fixed 30 second backoff by default, or exponential backoff if configured on the queued record, capped at five minutes. afk queue add does not expose pull retry policy flags; do not confuse this lease recovery with a scheduled task's maxAttempts and retryDelayMs.
Operational limits and safety
Only pin cwd to directories you trust. An agent task loads project hooks and .mcp.json from its effective directory, and daemon sessions run without interactive permission prompts. A shell task executes its command directly. The daemon control HTTP surface is unauthenticated; its default --host is loopback (127.0.0.1). Do not expose it to an untrusted network.
Before a scheduled agent task runs, the daemon checks the subscription used by its model. At or above AFK_DAEMON_BUDGET_SKIP_PCT (default 90), it skips that run, records skipReason: budget-over, and sends at most one Telegram alert per budget episode. Shell and built in tasks are not gated. Missing credentials, network errors, and stale readings fail open. AFK_DAEMON_BUDGET_GATE_DISABLED=1 disables this gate. Check the current readings with afk usage and the skip history with afk schedule logs TASK_ID.
When a task asks a blocking question, the daemon writes a durable handoff before attempting notification. The run does not wait synchronously for your answer. A standalone afk daemon process sends a task aware Telegram notice when configured, but it does not own a bot instance to display interactive answer buttons. An integration that starts the daemon with a Telegram bot can show buttons for choices and accept replies to text questions. Once an answer is recorded, handoff maintenance resumes the task with its original command, working directory, and notification routing. Startup recovery reoffers unanswered questions; unanswered handoffs expire after 24 hours. Without a working Telegram route, delivery is not guaranteed, but the handoff record remains on disk. See Telegram bot.
daemon.verifyDone defaults to true. When the agent calls its own result Done without a successful file write, edit, or command in that turn, the Telegram completion header is marked Done (unverified). This is a heuristic warning, not independent verification of the work. A human can turn it off in afk.config.json for a task whose legitimate result has no such evidence.
The daemon writes task telemetry and supports afk schedule logs TASK_ID. Task completion delivery follows notifyOn; do not assume every successful run sends a Telegram message. Uncaught process crashes also trigger a rate limited Telegram notice when configured.
--predict is not an afk daemon flag. It belongs to afk whatif for supplying operator predictions during behavioral evaluations. See What if.