Cron scheduler

AICODEBOX_CRON_MODE=1 with AICODEBOX_CRON_MODE_FILE=/path/to/cron.yaml runs the agent on a schedule. It runs as the foreground process, or inside the Telegram bot when Telegram mode is on too. The code is in aicodebox/modes/cron/.

docker run -d --name agent-cron \
  -e AICODEBOX_CRON_MODE=1 \
  -e AICODEBOX_CRON_MODE_FILE=/home/aicode/.aicodebox/cron.yaml \
  -v "$PWD/workspace:/workspace" \
  -v "$PWD/aicodebox-state:/home/aicode/.aicodebox" \
  your/child-image:latest

The jobs file

model: model-a
jobs:
  - name: morning-report
    schedule: "0 0 9 * * *"
    instruction: |
      Summarize yesterday's git activity in this repository.
      The time is {system_datetime}.
    workspace: shared
    telegram_chat_id: -100123
    model: model-b

Top-level keys are defaults for every job: model, system_prompt, append_system_prompt, telegram_chat_id, effort and thinking. Each job takes:

Key Required Default Meaning
name yes   Unique, letters, digits, _ and - only. Used in history paths.
schedule yes   Cron expression, see below.
instruction yes   The prompt. Must not be blank.
workspace no . Subdirectory of the workspace root, created on first run. An absolute path or .. fails each run with an error in <job>.jsonl.
model, system_prompt, append_system_prompt, effort, thinking no top-level value Per-job overrides. thinking wins over effort.
no_continue no false true starts a fresh agent session on every run. Must be a boolean.
telegram_chat_id no top-level value Post the result to this chat.

The file is checked at startup. A missing file, invalid YAML, an empty jobs list, a duplicate or badly formed name, a bad schedule, a blank instruction or a value of the wrong type stops the mode with invalid cron file: <reason>.

Schedules

Fields Resolution Example
5 minute 0 9 * * *, 09:00 every day
6 second, seconds first 0 0 9 * * *, 09:00:00 every day, or */30 * * * * *, every 30 seconds

Schedules use the container’s local time, which is UTC unless the image or the run sets TZ.

If a job is still running when its next time comes, that tick is skipped and logged. Different jobs run in parallel.

Placeholders

instruction, system_prompt and append_system_prompt may contain:

Placeholder Replaced with
{system_datetime} The scheduled time in UTC, as YYYY-MM-DD HH:MM:SS UTC, also when TZ is set.
{job_name} The job’s name.

Nothing else is replaced.

Run history

Each run gets a directory under the cron state root, $HOME/.aicodebox/cron/ by default or AICODEBOX_CRON_MODE_HISTORY_DIR:

history/<workspace-slug>/<YYYYMMDD-HHMMSS>-<job>/
  meta.json       job settings, start and finish times, exit code, error
  stdout.log      raw agent stdout
  stderr.log      raw agent stderr
  result.txt      the agent's final text
  telegram.json   chat_id and message_id, when a notification was sent

The scheduler also appends one JSON line per run to <job>.jsonl in the state root. When a job has run before, the scheduler appends a note to the system prompt with the path of its most recent run and a glob for older ones, so the agent can compare against past output.

Nothing deletes old runs. Prune the history directory yourself.

Telegram notifications

With telegram_chat_id set and AICODEBOX_TELEGRAM_MODE_TOKEN available, each run posts [<job>] and its result to that chat, converted to Telegram HTML, or plain text if Telegram rejects the HTML. An empty result posts finished (no output) or job failed (rc=<n>). This works in cron mode alone: the bot does not need to be running.

The scheduler records each posted message in telegram_messages.json in the state root, keeping the latest 200. When someone replies to one in a chat the bot serves, the bot adds the job’s context to the prompt.

Shutdown

On SIGTERM or SIGINT the scheduler stops starting jobs and waits up to 30 seconds for each running one.