---
name: schedules
description: Use when the user wants a canvas, workflow, or agent to run by itself at set times — daily, weekly, every N minutes, or once at a future date — or asks about, pauses, edits, or deletes an existing schedule.
---

# Schedules

Tela runs a canvas, workflow, or agent on a schedule: recurring (a five-field cron in a timezone) or
once at a set date. Each run is followed on the target's own page, like any other run: **Usage** for
a canvas or workflow, the run's session for an agent, and **Debug** for a workflow.

| Target | What each run executes |
|---|---|
| Canvas, workflow | the **Production** version, resolved when the run starts |
| Agent | the **published** version (`publishedId`), resolved when the run starts |

The version is resolved at run time, so promoting a canvas, publishing a workflow to `production`, or
publishing an agent changes what every existing schedule of it runs next. A draft, a personal draft,
or a version published to another environment is never scheduled. A target without a Production or
published version cannot be scheduled at all.

## Time inside a run is not a schedule

A schedule decides **when a run starts**. It does nothing inside a run. Sort every timing need into one
of these before designing anything:

| The need | Where it lives |
|---|---|
| Start on a recurrence or at a date: "every Monday at 9", "hourly", "on the 1st", "next Friday" | A schedule. Keep timers, cron, and "check the date" logic out of the canvas, workflow, or agent: one start = one run. |
| A short wait inside a step: rate limiting, backoff, polling an API that answers in seconds or minutes | Inside that step, such as a code step that waits and retries. It shares the step's time budget (a workflow code step defaults to 80 minutes), so keep it bounded and fail clearly when it runs out. |
| A long wait: hours or days, or until something external happens (approval, a file arriving, a job finishing) | Split it into separate runs. The first run ends and records what is pending; a recurring schedule starts a run that checks what is ready and continues it, or a `once` schedule starts the follow-up at a known time. Make the follow-up safe to run when nothing is ready. |

There is no wait or delay step in workflows. A run held open for hours is the wrong shape: a workflow
run that stops making progress for 3 hours is marked `timeout`.

## When to mention schedules

Bring it up only where it helps, once per conversation:

- **The request implies time** ("todo dia", "toda segunda", "monitorar todo dia", "relatório semanal",
  "a cada hora", "lembrar de"). Design the canvas, workflow, or agent for a single run, and say that
  the timing is a schedule once it is in Production (agent: published). If they already asked for the
  timing, create the schedule after it is in Production.
- **Right after promoting a canvas, publishing a workflow to `production`, or publishing an agent** that
  looks like recurring work — one line, for example: "Se isso precisa rodar sozinho, posso agendar
  (ex.: toda segunda às 9h)." Do not repeat it on later publishes of the same thing.
- **Before promoting or publishing something that already has active schedules**, say that the next
  scheduled run will use the new version: `tela.listSchedules({ targetId, status: 'active' })`.

Do not mention schedules for one-off runs, test cases, or exploratory work.

Create, edit, pause, resume, or delete a schedule only when the user asks. Each run spends credits, so
a schedule is never a side effect of another request.

## Creating a schedule

```bash
bun --preload ~/.claude/skills/tela-studio/preload.ts -e "
const [target] = await tela.listScheduleTargets({ search: 'Relatório de vendas' })
const { variables } = await tela.getScheduleTarget(target.targetType, target.id)
console.log(target, variables)

const schedule = await tela.createSchedule({
  targetType: target.targetType,
  targetId: target.id,
  name: 'Relatório de vendas — segunda 9h',
  scheduleType: 'recurring',
  cronExpression: '0 9 * * 1',        // every Monday at 09:00
  timezone: 'America/Sao_Paulo',
  inputs: { region: 'Sul' },
})
console.log(schedule.id, schedule.nextRunAt)
console.log(await tela.previewSchedule({ scheduleType: 'recurring', cronExpression: '0 9 * * 1', timezone: 'America/Sao_Paulo', count: 3 }))
console.log(tela.getScheduleUrl(schedule.id))
"
```

- **Target**: `listScheduleTargets({ search })` lists only what can be scheduled and matches a title
  or an exact id. If the target is missing, it has no Production or published version. Say so and
  offer to promote or publish it; that is a separate step the user must approve.
- **Inputs**: `getScheduleTarget` returns the inputs of the version that will run. Every run uses the
  same values. Required inputs must be filled in. Canvas and workflow values take the
  `TaskVariableValue` shapes, agents take `AgentTaskVariableValue` (`{ vaultRef, filename }` for a
  file). Upload files to Vault first and pass the `vault://` ref. The server checks them against the
  published version.
- **Timing**: `recurring` takes a five-field cron (`minute hour day month weekday`) and `once` takes
  `runAt` as an ISO date-time on a whole minute (`:00.000`). For "every N minutes", use a divisor of 60 so runs stay
  evenly spaced. Common forms: daily at 9 → `0 9 * * *`; weekdays at 8:30 → `30 8 * * 1-5`; every
  15 minutes → `*/15 * * * *`; the 1st of each month at 7 → `0 7 1 * *`.
- **Timezone**: an IANA name. Use the one the user gives; otherwise use `America/Sao_Paulo` and say
  so in the answer. Ask only when the day or time is missing, because that changes when it runs.
- **Name**: say what runs and when, in the user's language, so it reads well in the schedules list.
- **Team**: `createSchedule` picks it the way the app does. When the project is restricted to
  several of the user's teams, it throws with the team ids; ask which team should own it.

Report what was created in the user's terms: what runs, when (the next 2–3 times from
`previewSchedule` in the schedule's timezone), with which inputs, and the link.

## Following up

- `listSchedules({ targetId | search | status | attention: true })`: `needsAttention` means the
  latest run failed to start or failed.
- `listScheduleOccurrences(scheduleId)`: each time it fired. The schedule explains only one kind of
  failure: `status: 'failed_to_trigger'` means the run never started, and `errorMessage` says why
  (inputs that no longer match the published version, lost access, an unavailable service).
- **A run that started is an ordinary run.** The schedule only shows its outcome
  (`executionStatus`). Get its links with `tela.getScheduleRunLinks(occurrence)` and look at it, or
  investigate a failure, the same way as any other run of that canvas, workflow, or agent.
- **Auto-pause**: a recurring schedule that keeps failing is paused automatically and its creator
  gets an email. Once the cause is fixed, `resumeSchedule`.
- `updateSchedule(id, changes)` sends only what changes; `inputs` replaces the whole map.
  `pauseSchedule` stops new runs (runs already created still go ahead). `resumeSchedule` continues
  from the next due time without replaying missed ones. `deleteSchedule` cancels pending runs; past
  runs stay in the target's Usage. A completed one-time schedule is read-only.

## Errors

| Error | Meaning |
|---|---|
| `400` … `is a workflow, not a canvas` | Wrong `targetType` for that id. Use the type from `listScheduleTargets`. |
| `400`/`422` on inputs | Missing required input or wrong shape for the published version. Check `getScheduleTarget`. |
| `404` on the target | No Production/published version, or no access to the project. |
| `403` on team | The user is not in that team, or the team has no access to the project. |

## Close with the link

End with `tela.getScheduleUrl(schedule.id)`, which opens its configuration and history. For several
schedules, use `tela.getSchedulesUrl({ search: targetTitle })`.
