cron/¶
In-process cron scheduling with JSON persistence and one-shot CLI execution.
Files¶
manager.py:CronJob,CronManagerCRUD/persistenceobserver.py:CronObserverscheduling, watcher, execution pipelineexecution.py: provider command builders, result parsing, one-shot subprocess helperdependency_queue.py: shared dependency locks (cron + webhook cron_task)infra/task_runner.py(shared): folder checks + one-shot task execution for cron/webhook/background
Cron job model¶
Core fields:
id,title,description,scheduletask_folder,agent_instruction,enabledtimezone(optional per-job IANA override)created_at,last_run_at,last_run_status
Routing fields:
chat_id(default0) -- target chat for result deliverytopic_id(defaultNone) -- optional forum topic within target chattransport(default"tg") -- transport identifier ("tg"or"mx")
Execution overrides:
providermodelreasoning_effortcli_parameters
Scheduling guards:
quiet_start,quiet_enddependency
Persistence¶
File: ~/.sygen/cron_jobs.json
- format:
{ "jobs": [...] } - atomic writes via temp+replace
Observer lifecycle¶
start():
- schedule enabled jobs
- start mtime watcher loop
Watcher:
- polls file mtime every 5s
- on change: reload + full reschedule
reschedule_now() is used by interactive cron toggles and updates mtime baseline first to avoid watcher race.
Execution path¶
When a job fires:
- acquire dependency lock when configured
- quiet-hour gate (only when
job.quiet_*is set; no fallback to global heartbeat quiet hours) - resolve/validate task folder (
workspace/cron_tasks/<task_folder>) - resolve
TaskExecutionConfigviaresolve_cli_config(...) - enrich prompt with
<task_folder>_MEMORY.mdinstructions - build provider command (
build_cmd) - execute one-shot subprocess with timeout
- parse provider output
- invoke optional result callback when the execution path reaches callback emission
- update run status (
last_run_status,last_run_at) - schedule next occurrence
Command builders (execution.py)¶
Supported providers:
- Claude
- Codex
- Gemini
Examples:
- Claude:
claude -p --output-format json ... --no-session-persistence -- <prompt> - Codex:
codex exec --json ... -- <prompt> - Gemini:
gemini -p "" --output-format json --include-directories . ...(prompt passed via stdin)
bypassPermissions behavior:
- Codex:
--dangerously-bypass-approvals-and-sandbox - Gemini:
--approval-mode yolo
Status values¶
Typical values:
successerror:folder_missingerror:cli_not_found_claudeerror:cli_not_found_codexerror:cli_not_found_geminierror:timeouterror:exit_<code>
Quiet-hour skips are silent:
- no
last_run_statusupdate - no result callback
Folder-missing nuance:
error:folder_missingupdateslast_run_status- no result callback is emitted for that path
Result routing¶
Cron results are delivered through MessageBus using Envelope objects built by bus/adapters.py::cron_result_envelope(...).
- UNICAST: when
chat_idis non-zero, the result is delivered to that specific chat/topic on the matching transport. - BROADCAST: when
chat_idis0(default), the result is broadcast to all authorized users.
Empty result handling:
- when CLI returns
successbut empty text (e.g. model performed tool calls without a final answer), a fallback message is delivered instead of silence - the enriched prompt explicitly requires the model to always produce a text response
Fallback behavior (Telegram):
- if unicast delivery fails (e.g. bot removed from group, topic deleted), the result falls back to the main user's private chat (
allowed_user_ids[0]) with an explanation of the delivery failure.
Fallback behavior (Matrix):
- if the target room cannot be resolved, the result falls back to broadcast across all allowed rooms.
Environment variables¶
The CLI subprocess receives routing context via environment variables:
SYGEN_CHAT_ID-- current chat ID (set whenchat_idis non-zero)SYGEN_TOPIC_ID-- current topic ID (set whentopic_idis non-None)SYGEN_TRANSPORT-- transport identifier ("tg"or"mx")
These are injected by _build_subprocess_env() (host mode) and docker_wrap() (container mode).
The cron_add.py tool script auto-reads these env vars to populate the job's routing fields, so jobs created from within a chat/topic automatically route results back to that location.
Timezone resolution¶
Per-job scheduling resolution:
CronJob.timezone- global
user_timezone - host timezone
- UTC fallback
Cron expressions are evaluated in resolved local wall-clock time.
Dependency queue¶
Shared queue key behavior:
- same dependency key -> FIFO serialization
- different/no key -> parallel execution
- shared with webhook
cron_taskruns
Telegram interaction¶
/cron uses interactive selector (crn:* callbacks):
- paging
- refresh
- per-job enable/disable
- bulk all-on/all-off