Skip to main content
Scheduling lets you run missions automatically at a given time — either once (a deploy next Tuesday at 14:00) or on a recurring basis (nightly tests, daily health checks, weekly security audits). You define a cron expression or an ISO timestamp, and Polpo takes care of the rest. No external cron job or CI pipeline needed.

Quick Start

Use the create_schedule orchestrator tool to schedule a mission. The mission status transitions to scheduled (one-shot) or recurring:
create_schedule(missionId, "0 2 * * *", recurring=true)
This runs every day at 02:00. After each run completes, the mission returns to recurring status and waits for the next scheduled time.

Mission Lifecycle

Scheduled missions use dedicated statuses instead of remaining in draft:
One-shot:   draft → scheduled → active → completed (success) / scheduled (failure, auto-retry)
Recurring:  draft → recurring  → active → recurring (always returns for next tick)
RecurringOne-Shot
Mission statusrecurringscheduled
Schedule expressionCron expression (0 2 * * *)Cron or ISO timestamp (2026-03-15T14:00:00Z)
After successReturns to recurringTransitions to completed, schedule disabled
After failureReturns to recurringReturns to scheduled for retry
Use caseRepeated tasks (tests, audits, health checks)Single future event (deploy, migration)
A recurring mission never reaches completed or failed status — it always returns to recurring after each execution cycle. The individual task statuses reflect success/failure of each run.

Cron Expressions

Polpo uses standard 5-field cron expressions:
┌───────────── minute (0–59)
│ ┌───────────── hour (0–23)
│ │ ┌───────────── day of month (1–31)
│ │ │ ┌───────────── month (1–12)
│ │ │ │ ┌───────────── day of week (0–7, 0 and 7 = Sunday)
│ │ │ │ │
* * * * *

Syntax

SyntaxExampleMeaning
Number30Exact value
**Every value in range
Range1-5Values 1 through 5
Step*/15Every 15th value
List1,3,5Values 1, 3, and 5
Range + Step0-30/100, 10, 20, 30

Common Patterns

ExpressionWhen it runs
0 2 * * *Every day at 02:00
*/15 * * * *Every 15 minutes
0 9 * * 1-5Weekdays at 09:00
0 0 1 * *First day of every month at midnight
30 8,12,18 * * *At 08:30, 12:30, and 18:30 daily
0 22 * * 0Every Sunday at 22:00
0 6 * * 1Every Monday at 06:00
Shorthand aliases like @daily, @weekly, @monthly are not supported — use the 5-field format. Seconds, L, W, and # modifiers are also not supported.

Managing Schedules

ToolDescription
create_scheduleSchedule a mission (sets status to scheduled or recurring)
list_schedulesList all schedule entries
update_scheduleChange expression, recurring/one-shot mode, or enabled flag
delete_scheduleRemove schedule and reset mission to draft

Disabling vs Deleting

  • Disable: update_schedule(missionId, enabled=false) — keeps the schedule configuration but stops execution
  • Re-enable: update_schedule(missionId, enabled=true) — resumes execution
  • Delete: delete_schedule(missionId) — permanently removes the schedule and resets the mission to draft
  • Abort: aborting a scheduled mission automatically removes its schedule
  • Switch mode: update_schedule(missionId, recurring=true) — changes between one-shot and recurring

End Date

Recurring schedules can have an optional end date — an ISO timestamp after which the schedule automatically expires. When the scheduler checks a mission at trigger time and finds the end date has passed, it:
  1. Disables the schedule entry
  2. Transitions the mission to completed
  3. Emits a schedule:expired event

Setting an End Date

create_schedule(missionId, "0 2 * * *", recurring=true, endDate="2026-06-30T23:59:00Z")
This runs every day at 02:00 until June 30, 2026. After that date, the mission is marked as completed automatically.

Removing an End Date

To remove an end date from an existing schedule (making it run indefinitely):
update_schedule(missionId, endDate=null)

Behavior

ScenarioWhat happens
End date in the futureSchedule runs normally
End date in the past at trigger timeSchedule disabled, mission → completed
End date removed (null)Schedule runs indefinitely
One-shot scheduled mission with end dateEnd date is checked before execution
The end date is checked at trigger time, not at registration time. A mission registered with a future end date will run normally until that date passes. The endDate is stored on the Mission object and persisted across restarts.

Examples

Nightly integration tests

create_schedule(missionId, "0 2 * * *", recurring=true)
Mission with tasks:
{
  "tasks": [
    {
      "title": "Run full test suite",
      "description": "Run all integration and unit tests. Report any failures.",
      "assignTo": "test-agent"
    },
    {
      "title": "Generate coverage report",
      "description": "Generate a coverage report and save to coverage-report.md.",
      "assignTo": "report-agent",
      "dependsOn": ["Run full test suite"]
    }
  ]
}

Scheduled one-time deploy

create_schedule(missionId, "2026-03-15T14:00:00Z")
Mission with tasks:
{
  "tasks": [
    {
      "title": "Build release artifacts",
      "assignTo": "build-agent"
    },
    {
      "title": "Deploy to production",
      "assignTo": "devops-agent",
      "dependsOn": ["Build release artifacts"]
    }
  ]
}

Cancelling a Scheduled Run

The before:schedule:trigger hook fires before each scheduled execution. Return a cancellation to skip that run — useful for maintenance windows or conditional logic:
{
  "hooks": {
    "before:schedule:trigger": {
      "command": "node scripts/check-maintenance-window.js"
    }
  }
}
If the hook cancels, the schedule remains active — it will try again at the next cron occurrence. The run is skipped, not deleted.

Internals

The scheduler is tick-driven — it runs on every Polpo tick (default every 30 seconds), checks all registered schedules, and triggers mission execution when a schedule is due.

Lifecycle

  1. Registration — on startup, Polpo scans all missions with a schedule field in scheduled or recurring status and registers them. Missions scheduled later are registered via create_schedule.
  2. Check — on every tick, the scheduler evaluates each active schedule. If nextRunAt is in the past, the mission is triggered.
  3. Trigger — the before:schedule:trigger hook runs (can cancel). If not cancelled, the mission is executed (transitions to active).
  4. After execution — for recurring missions, the status returns to recurring and the next run time is calculated. For one-shot missions on success, the status becomes completed and the schedule is disabled. On failure, the status returns to scheduled for retry.

Triggerable States

The scheduler only triggers missions in scheduled or recurring status:
  • scheduled — one-shot mission waiting for trigger
  • recurring — recurring mission waiting for next cron tick
Missions in active, paused, draft, completed, failed, or cancelled status are skipped. This prevents double execution of active missions.

Events

EventWhenPayload
schedule:createdA schedule is registeredscheduleId, missionId, nextRunAt
schedule:triggeredA scheduled mission starts executingscheduleId, missionId, expression
schedule:completedA scheduled mission finishesscheduleId, missionId
See Events reference for the full event catalog.

Schedule Entry Fields

Each schedule is tracked internally as a ScheduleEntry:
FieldTypeDescription
idstringUnique ID (sched-{missionId})
missionIdstringThe mission to execute
expressionstringCron expression or ISO timestamp
recurringbooleanWhether this schedule repeats (derived from mission status)
enabledbooleanWhether the schedule is active
lastRunAtstring?When it last ran
nextRunAtstring?When it will run next
createdAtstringWhen the schedule was created