The scheduler polls task databases across all JAT projects and automatically spawns agent sessions for due tasks. It supports both recurring tasks (cron-based) and one-shot scheduled tasks.
┌─────────────────────────────────────────────────────────────────────────────┐
│ SCHEDULER ARCHITECTURE │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ jat-scheduler daemon (Node.js) │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 1. Discover projects (~/code/*/.jat/tasks.db + projects.json) │ │
│ │ 2. Query each DB: next_run_at <= now AND status = 'open' │ │
│ │ 3. For recurring tasks (schedule_cron): │ │
│ │ → Create child instance task │ │
│ │ → Spawn child via POST /api/work/spawn │ │
│ │ → Compute next run from cron expression │ │
│ │ 4. For one-shot tasks (no cron): │ │
│ │ → Spawn directly via POST /api/work/spawn │ │
│ │ → Clear next_run_at │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ Runs in tmux session: jat-scheduler │
│ HTTP control API on port 3334 │
│ Polls every 30 seconds (configurable) │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
# Start scheduler via CLI
jat scheduler start
# Check status
jat scheduler status
# Stop
jat scheduler stop
# View logs (Ctrl+B D to detach)
jat scheduler logs
# Auto-start on IDE launch (set once)
# Add to ~/.config/jat/projects.json:
# "defaults": { "scheduler_autostart": true }Scheduling uses fields on the existing tasks table - no separate scheduling table.
Scheduler-relevant task fields:
| Field | Type | Purpose |
|---|---|---|
schedule_cron |
TEXT | Cron expression for recurring tasks (e.g., 0 9 * * *) |
next_run_at |
TEXT | ISO datetime of next run (set by scheduler or manually) |
command |
TEXT | Command to run (default: /jat:start) |
agent_program |
TEXT | Agent to use (e.g., claude-code) |
model |
TEXT | Model override (e.g., opus, sonnet, haiku) |
A task is "due" when: next_run_at <= now AND status = 'open'
Set schedule_cron to a cron expression. The scheduler will:
- Create a child instance task inheriting parent's command/agent_program/model/labels
- Spawn the child via the IDE spawn API
- Compute the next
next_run_atfrom the cron expression - Parent stays
openand fires again at the next scheduled time
Parent task: "Daily Code Review" (schedule_cron: "0 9 * * *")
│
├─ Child: "Daily Code Review (2/9/2026)" → spawned, agent works on it
├─ Child: "Daily Code Review (2/10/2026)" → next occurrence
└─ ...repeats indefinitely
Child task properties:
- ID:
{project-prefix}-{random5chars}(e.g.,jat-x4k7a) - Title:
{parent title} ({today's date}) - Inherits: priority, issue_type, command, agent_program, model, labels
parent_idset to parent task ID- No
schedule_cron(child is a one-time instance)
When a task's command is null, empty, or '/human', the scheduler treats it as a human task:
- Recurring: Creates the child instance with
due_dateset, but skips agent spawn. The child appears in the user's task list for manual completion. - One-shot: Logs the task as due and clears
next_run_at, but does not spawn an agent.
Use this for recurring reminders like "Take out the trash every Friday" — the scheduler creates the task on schedule, but no AI agent is involved.
Set next_run_at but leave schedule_cron empty. The scheduler will:
- Spawn the task directly
- Clear
next_run_atso it doesn't fire again
Use one-shot for tasks you want to schedule for a specific future time without recurrence.
Standard 5-field cron format: minute hour day-of-month month day-of-week
| Expression | Schedule |
|---|---|
0 9 * * * |
Daily at 9:00 AM |
0 9 * * 1-5 |
Weekdays at 9:00 AM |
*/30 * * * * |
Every 30 minutes |
0 0 * * 0 |
Weekly on Sunday at midnight |
0 9,17 * * * |
Daily at 9 AM and 5 PM |
0 0 1 * * |
Monthly on the 1st at midnight |
Timezone: Cron expressions respect the timezone configured in ~/.config/jat/projects.json under defaults.timezone (default: UTC).
User config: ~/.config/jat/projects.json
{
"defaults": {
"timezone": "America/New_York",
"scheduler_autostart": true
}
}| Setting | Default | Description |
|---|---|---|
defaults.timezone |
UTC |
IANA timezone for cron calculations |
defaults.scheduler_autostart |
false |
Auto-start scheduler when jat IDE launches |
jat scheduler # Show status (default)
jat scheduler start # Start the daemon in tmux session
jat scheduler stop # Kill the tmux session
jat scheduler restart # Stop + start
jat scheduler logs # Attach to tmux session (Ctrl+B D to detach)The scheduler runs in tmux session jat-scheduler. It appears in the IDE's Servers page alongside dev servers.
The daemon (tools/scheduler/index.js) accepts these CLI arguments:
| Argument | Default | Description |
|---|---|---|
--poll-interval 30 |
30 |
Polling interval in seconds |
--port 3334 |
3334 |
HTTP control API port |
--ide-url http://127.0.0.1:3333 |
http://127.0.0.1:3333 |
IDE API base URL |
--verbose |
off | Enable debug logging |
--dry-run |
off | Log what would happen without actually spawning |
The daemon exposes a lightweight HTTP API on port 3334 (localhost only).
| Method | Path | Description |
|---|---|---|
GET |
/status |
Scheduler status, poll count, recent spawns |
POST |
/start |
Resume polling (if paused) |
POST |
/stop |
Pause polling |
POST |
/poll |
Trigger an immediate poll |
Status response:
{
"running": true,
"pollInterval": 30,
"pollCount": 142,
"lastPoll": "2026-02-09T15:30:00.000Z",
"recentSpawns": [
{ "taskId": "jat-abc", "childId": "jat-x4k7a", "project": "jat", "time": "...", "result": "ok" }
],
"projectCount": 3,
"uptime": 7200.5
}The IDE provides REST endpoints for managing the scheduler from the UI.
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/scheduler/start |
Start scheduler (creates tmux session) |
POST |
/api/scheduler/stop |
Stop scheduler (kills tmux session) |
GET |
/api/scheduler/status |
Running state, uptime, scheduled task count, next run |
Status response from IDE:
{
"running": true,
"uptime": 3661,
"sessionCreated": "2026-02-09T12:00:00.000Z",
"scheduledCount": 5,
"nextRun": {
"taskId": "jat-abc",
"taskTitle": "Daily Code Review",
"nextRunAt": "2026-02-10T09:00:00.000Z",
"scheduleCron": "0 9 * * *"
},
"timestamp": "2026-02-09T15:30:00.000Z"
}Servers page (/servers): The scheduler status is displayed alongside dev servers:
- Running/Stopped badge with color indicator
- Uptime counter (auto-refreshing)
- Next scheduled task with countdown timer
- Start/Stop buttons for quick control
Chores page (/chores): Dedicated page for managing recurring tasks:
- Lists all tasks with
schedule_cronset across projects - Shows human-readable schedule descriptions (e.g., "Every weekday at 9:00 AM")
- Displays next run time with countdown
- Quick enable/disable toggles
Recurring tasks use the chore task type. In JAT, chore = recurring scheduled task — a task with a cron schedule that fires automatically.
The task creation UI includes scheduling fields:
- Set Type to
chore(this is the standard type for recurring work) - Set Schedule (Cron) to a cron expression (e.g.,
0 9 * * *) or pick a preset - Optionally set Command (default:
/jat:start) - Optionally set Agent Program and Model overrides
The scheduler picks up the task on the next poll.
# Create a recurring task (chore = recurring scheduled task)
jt create "Daily Code Review" \
--type chore \
--priority 2 \
--description "Review all open PRs and check CI status"
# Then manually set schedule fields (jt doesn't have --schedule flag yet):
# Use the IDE Task Detail drawer to set schedule_cron and next_run_at# Set cron schedule on existing task
sqlite3 .jat/tasks.db "UPDATE tasks SET schedule_cron='0 9 * * *', next_run_at='$(date -u +%Y-%m-%dT%H:%M:%SZ)' WHERE id='jat-abc'"The scheduler discovers projects in two ways:
- Filesystem scan: Scans
~/code/for directories containing.jat/tasks.db - Config file: Reads
~/.config/jat/projects.jsonfor projects with custom paths
Hidden projects ("hidden": true in config) are skipped.
1. Create task with schedule_cron="0 9 * * *"
→ Scheduler sets next_run_at to tomorrow 9 AM
2. Poll at 9:30 AM: next_run_at (09:00) <= now (09:30) ✓
→ Create child: "Daily Report (2/9/2026)" with new ID jat-x4k7a
→ Spawn child via POST /api/work/spawn
→ Update parent: next_run_at = tomorrow 9:00 AM
3. Child task jat-x4k7a runs in its own agent session
→ Agent works on it, completes via /jat:complete
→ Parent task stays open for next recurrence
4. Repeat daily...
1. Create task with next_run_at="2026-02-10T14:00:00Z" (no schedule_cron)
2. Poll at 2:30 PM on Feb 10: next_run_at (14:00) <= now (14:30) ✓
→ Spawn task directly
→ Clear next_run_at (set to NULL)
3. Task runs once, never fires again
- Spawn failures don't prevent the parent's
next_run_atfrom being updated. The next recurrence will still fire on schedule. - Database errors are caught per-project; a broken DB in one project won't affect others.
- Port conflicts: If port 3334 is in use, the scheduler runs without the HTTP API (polling still active).
- Graceful shutdown: Handles SIGINT and SIGTERM cleanly.
| Path | Purpose |
|---|---|
tools/scheduler/index.js |
Main scheduler daemon (238 lines) |
tools/scheduler/lib/db.js |
Database operations (162 lines) |
tools/scheduler/lib/cron.js |
Cron expression utilities (55 lines) |
tools/scheduler/run.sh |
Bash launcher (auto-installs deps) |
tools/scheduler/package.json |
Dependencies: better-sqlite3, cron-parser |
ide/src/routes/api/scheduler/start/+server.js |
IDE start endpoint |
ide/src/routes/api/scheduler/stop/+server.js |
IDE stop endpoint |
ide/src/routes/api/scheduler/status/+server.js |
IDE status endpoint |
ide/src/routes/servers/+page.svelte |
IDE servers page UI |
lib/tasks-schema.sql |
Task table schema (includes scheduling fields) |
lib/tasks.js |
getScheduledTasks() function |
cli/jat |
CLI jat scheduler commands |
| Package | Version | Purpose |
|---|---|---|
better-sqlite3 |
^11.0.0 | Read per-project task databases |
cron-parser |
^4.9.0 | Parse and validate cron expressions |
| Issue | Cause | Fix |
|---|---|---|
| Scheduler not starting | Missing dependencies | cd tools/scheduler && npm install |
| Tasks not firing | next_run_at not set or in the future |
Check with: sqlite3 .jat/tasks.db "SELECT id,next_run_at,status FROM tasks WHERE schedule_cron IS NOT NULL" |
| Wrong timezone | Default is UTC | Set defaults.timezone in ~/.config/jat/projects.json |
| Spawn fails | IDE not running | Start IDE first: jat or cd ide && npm run dev |
| Port 3334 in use | Another scheduler instance | Kill old: tmux kill-session -t jat-scheduler |
| No projects found | Missing .jat directory | Run jt init in your project directory |
| Shows as "active task" in IDE | Tmux session detected as agent | This is a known UI issue - scheduler tmux session jat-scheduler may appear in session lists |