Scheduled Agents

Any Nestor agent can be put on a cron schedule. The scheduler persists the schedule in SQLite, validates every expression at write time, and fires the agent with a stored prompt at the configured cadence.

Overview

Scheduled agents answer a simple need: "run this agent every morning at 8:00", "rescan this topic every two hours", "post the weekly digest every Monday at 09:00". Each agent can carry at most one schedule — the agent_id column in agent_schedules is UNIQUE, so re-posting a schedule overwrites the previous one.

Cron expressions are parsed by parseCronExpression from @nestor/agent before the row is written. An invalid pattern returns a 400 with the parser error attached; no malformed cron reaches the runner.

Data model

The agent_schedules table (source: packages/db) stores one row per scheduled agent. Relevant columns:

ColumnTypeNotes
agent_idTEXTForeign key to agents.id. UNIQUE — one schedule per agent.
cronTEXTStandard 5-field cron expression (min / hour / dom / mon / dow).
promptTEXTOptional. The prompt fed to the agent on each tick.
enabledINTEGERToggle the schedule without deleting it.
tenant_idTEXTMulti-tenant isolation. Defaults to default.

REST endpoints

The scheduling API lives in packages/server/src/routes/agents.ts. All endpoints require tenant context and validate their payloads with Zod.

Set or update a schedule

POST /api/agents/:id/schedule
Content-Type: application/json

{
  "cron": "0 8 * * *",
  "prompt": "Summarise yesterday's commits and post to Obsidian.",
  "enabled": true
}

The cron field is required, capped at 128 characters, and parsed before storage. prompt is optional and capped at 10 000 characters. Repeating a POST on the same agent overwrites the row.

Read a schedule

GET /api/agents/:id/schedule

Returns the current schedule for an agent or 404 if none is set.

Delete a schedule

DELETE /api/agents/:id/schedule

Removes the row from agent_schedules. The agent itself is not affected.

List all scheduled agents

GET /api/agents?scheduled=true

Returns only agents that currently have an active row in agent_schedules, with their schedule metadata attached under the schedule key. This is the shape Studio's Scheduler page consumes.

Note: earlier drafts of this documentation referenced a standalone GET /api/scheduled-agents endpoint. The actual implementation uses the query-parameter form on the agents collection shown above.

Sample cron patterns

Nestor uses the standard 5-field Unix cron syntax: minute hour day-of-month month day-of-week. A few common patterns:

ExpressionFires
*/15 * * * *Every 15 minutes
0 * * * *Top of every hour
0 8 * * *Every day at 08:00
0 9 * * 1Every Monday at 09:00
0 9 1 * *First day of each month at 09:00
0 2 * * 1-5Weeknights at 02:00

Quiet hours & timezone

Nestor honours the user's declared timezone (stored in the user model; see packages/agent/src/memory/user-model.ts). Cron expressions are evaluated in that timezone rather than server UTC, so 0 8 * * * means 08:00 local.

For quiet hours (no autopilot between midnight and 07:00, weekends off, etc.), the current pattern is to encode the allowed window directly in the cron expression:

# Every 30 minutes between 08:00 and 20:00, Mon-Fri
*/30 8-20 * * 1-5

# Hourly, skipping nighttime (08:00 to 22:00)
0 8-22 * * *

Persona-level quiet hours (Nexus refuses to kick off autopilot during night hours) are enforced independently by the orchestrator on top of the cron trigger. A scheduled tick that hits a quiet window is dropped, not deferred.

End-to-end example

# 1. Create an agent
curl -X POST http://localhost:3000/api/agents \
  -H 'Content-Type: application/json' \
  -d '{"name":"digest","adapterType":"claude","adapterConfig":{"model":"claude-sonnet-4-6"}}'

# 2. Schedule it every weekday at 08:00
curl -X POST http://localhost:3000/api/agents/<id>/schedule \
  -H 'Content-Type: application/json' \
  -d '{"cron":"0 8 * * 1-5","prompt":"Summarise yesterday and post to Obsidian.","enabled":true}'

# 3. Inspect the list of scheduled agents
curl http://localhost:3000/api/agents?scheduled=true

# 4. Disable the schedule without deleting it
curl -X POST http://localhost:3000/api/agents/<id>/schedule \
  -H 'Content-Type: application/json' \
  -d '{"cron":"0 8 * * 1-5","enabled":false}'

# 5. Remove the schedule entirely
curl -X DELETE http://localhost:3000/api/agents/<id>/schedule

For related surface area, see Agents, Workflows (for DAG-level scheduling with cron trigger nodes), and the documentation overview.