Skip to main content

Scheduling

TF Code can run recurring agent jobs on a cron schedule — for example a daily dependency dig, a weekly report, or a periodic cleanup task. Jobs are managed from the desktop app, the TUI, or by asking the agent (which works from any client attached to a server).

Scheduling is powered by three layers working together:

  1. OS scheduler units (launchd on macOS, systemd timers on Linux) — fire the job at the exact cron time, even when TF Code is not running.
  2. Missed-run recovery — if the machine was off or TF Code wasn't running when a job was due, the job is picked up on the next start (optional, see Missed-run policy).
  3. Run tracking — every run records timing, status, an optional sessionID, and logs to disk, so you can audit what ran, when, and whether it succeeded.

Enable

Scheduling is a builtin plugin, disabled by default. Enable it in your config:

{
"builtin": { "scheduler": { "enabled": true } }
}

The server exposes the standard plugin endpoints plus a dedicated /scheduler API:

EndpointDescription
GET /builtinPlugin status (including scheduler.enabled)
POST /builtin/scheduler/enableEnable the scheduler plugin
POST /builtin/scheduler/disableDisable the scheduler plugin
GET /schedulerList jobs + enabled status
GET /scheduler/{id}Job detail + run history
POST /schedulerCreate a job
PATCH /scheduler/{id}Update a job
DELETE /scheduler/{id}Delete a job
POST /scheduler/{id}/runRun a job immediately
GET /scheduler/{id}/logs?lines=NRead the job log tail

The scheduler only manages jobs while the plugin is enabled. If it's disabled, job-management calls return an error and the panels show an enable prompt.

Cron expressions

Jobs use standard 5-field cron expressions. Example cron values:

ExpressionMeaning
0 9 * * 1-5Weekdays at 09:00
30 6 * * *Every day at 06:30
0 0 * * 0Sundays at midnight
*/15 * * * *Every 15 minutes
0 3 1 * *First day of every month at 03:00

Fields: minute hour day-of-month month day-of-week.

Agent selection

Each job runs the agent you pick. Set agent to an agent name — it is matched case-insensitively (so BuiLd and build are the same) and the default is build. Unknown or empty names fall back to build.

{
"name": "nightly-report",
"cron": "0 22 * * *",
"agent": "code",
"prompt": "..."
}

When you don't specify an agent, the job always runs with the default build agent.

Missed-run policy

Each job has a missed-run policy for what happens when a scheduled time passes while the machine is off or TF Code isn't running:

PolicyBehaviour
catch-up(default) On the next start, run the most recent missed occurrence once (best-effort recovery).
exactRun only at the exact scheduled time — if it's missed while offline, it's skipped.

Recovery is safe by design:

  • It only recovers the single most recent missed occurrence — never a backlog.
  • An occurrence that already produced a run is never re-run — including when that run failed. A failing job cannot trigger a continuous re-run loop.
  • Recently-due occurrences are left to the OS scheduler for a short grace window, so a job that is simply about to fire while TF Code is online isn't double-run.
  • Disabled jobs are never recovered.

Where data and logs live

Job definitions, run history, and logs are stored per project scope in the TF Code state directory:

~/.local/state/tfcode/scheduler/scopes/<scope-id>/
<job-id>.json # job definition
<job-id>.runs.json # run history (status, timing, sessionID, errors)
<job-id>.log # combined job log
runs/<job-id>/<run-id>.out # per-run output

On macOS the path may differ depending on your XDG state directory. ~/.local/state is the default.

Each run record captures startedAt, finishedAt, status (running | succeeded | failed | crashed | skipped), an optional exitCode, the sessionID of the agent session that executed the run, and any error. Runs left in running after a crash are reconciled to crashed on the next start — they are never rescheduled.


App (Desktop UI)

The web app has a Schedules panel in the session side panel (next to the file / changes tabs).

Create a job

  1. Open the Schedules panel.
  2. Click New job.
  3. Fill in:
    • Name — a human-readable label.
    • Cron — the schedule, e.g. 0 9 * * 1-5.
    • Prompt — what the agent should do when the job runs.
    • Agent — which agent to run (default build; matched case-insensitively).
    • Timeout (s) — how long the run may take before it's killed (0 = no timeout).
    • Missed-run policyCatch up or Exact.
  4. Click Create.

Manage jobs

Each job row shows its next run, last run, last status, and policy, with actions:

ActionDescription
Run nowTrigger the job immediately.
Pause / ResumeToggle whether the job is scheduled.
DeleteRemove the job and its OS scheduler unit.
(expand)Show the run history for the job.

If the scheduler plugin is disabled, the panel shows an Enable scheduler button instead.


Server

Any client attached to a running server — including a headless server — schedules jobs by asking the agent, which calls the scheduling tools server-side. There are no user-facing CLI commands; the agent manages jobs on your behalf.

Ask the agent to schedule a job

Schedule a job called "daily-dependency-dig" to run at 9am on weekdays
that updates the dependency report and opens a PR if there are changes.

The agent registers the job with the tools:

  • schedule_job — create a job (name, cron, prompt, agent, timeout_seconds, missed_run_policy)
  • list_jobs — list jobs for the project
  • get_job — show one job
  • update_job — change cron, prompt, agent, timeout, enabled, or policy
  • delete_job — remove a job
  • run_job — run immediately (fire-and-forget; skips if already running)
  • job_logs — read the last N lines of a job's log

Examples

Schedule a job called "weekly-cleanup" with cron "0 6 * * 1" using the
"code" agent to delete temporary build artifacts and report disk usage.
Set a timeout of 600 seconds.
List my scheduled jobs and show the run history of "daily-dependency-dig".
Run "nightly-backup" right now, then show me its recent logs.

Exact timing with no catch-up

Schedule "publish-daily-cut" with cron "0 17 * * *" and use the
"exact" missed-run policy so it only runs at 17:00 exactly.

TUI

The TUI exposes scheduling through the /schedules dialog and by asking the agent in-session.

/schedules dialog

Type /schedules and press Enter. The dialog lists jobs with their cron, policy, agent, next run, and status. Use the keys shown in the dialog footer:

KeyAction
SpacePause / resume the selected job
rRun the selected job now
dDelete the selected job
eEnable / disable the plugin

Ask the agent (same as the server)

In the TUI prompt you can schedule, list, update, or delete jobs with natural language, exactly as on a server:

Schedule a job called "morning-standup-brief" to run at 8:30 on weekdays
and summarize the last 24 hours of issues.
Pause "morning-standup-brief" until I say otherwise.

Tips & safety

  • A failing job will not loop. Recovery never re-runs an occurrence that produced a run, so failures result in a recorded failed run — not a retry storm.
  • Pause before deleting. Deleting removes the OS unit and job files; pausing just disables the schedule while keeping the job definition.
  • Pick exact for time-sensitive actions (e.g. a cutover or publish) so an offline period doesn't trigger a late, surprising run.
  • Review logs before re-running. Use job_logs (agent) or the /scheduler/{id}/logs endpoint to see why a run failed.
  • The lock prevents overlap. A job won't start a second run if one is already active.