For the complete documentation index, see llms.txt. This page is also available as Markdown.

Schedules

No CI setup, no GitHub Actions config. Declare a schedule in your tessl.json, push to your default branch, and Tessl runs it for you.

Takeaways:

  • A schedule runs a plugin against a repo on a cron, with no CI or GitHub Actions config needed.

  • The primary way to create one is a schedules block in your tessl.json. Push to your default branch and Tessl syncs it.

  • An unparseable or invalid file never deletes anything. Removing a schedule from a valid file is what deletes it.

  • Run tessl schedule validate before you push to catch errors locally.

  • When repo-defined schedules are enabled for your organization, they are read-only in the web app and CLI. Change the file to change them.

What is a schedule?

A schedule runs a plugin on a cron. You pick a plugin, a repo, and a time, and Tessl runs the plugin against that repo on that schedule. Each time the schedule fires, Tessl starts a cloud run and reports the result back so you can see what happened.

Tessl skips a tick if the previous run from the same schedule is still active when the next one would start. There is no queue: a skipped tick does not run later.

Think of it as two parts working together. The plugin describes the task: it tells the agent what to do. The schedule describes the timing: it tells Tessl when to do it.

What you need first

  • A Tessl account with a workspace.

  • GitHub connected to your workspace. See Connect Tessl to GitHub.

  • A plugin that describes the task. See Create a plugin.

  • An environment. A scheduled run cannot mint its own GitHub token, so it needs a GITHUB_TOKEN from its environment to check out your repo and open PRs. The environment also holds any other secrets the plugin needs. See Environments.

Define a schedule in tessl.json

The primary way to create a schedule is to declare it in your project's tessl.json. Add a schedules block, push it to your default branch, and Tessl reads the file and creates the schedule for you. The schedule lives next to the code it runs against, so it is reviewed and version-controlled like any other change.

The name field helps Tessl resolve the manifest to a project. Use the workspace/project format, for example "name": "my-org/my-app". Before schedules sync, an active project must exist in that workspace, be connected to the same GitHub repository, and use the manifest's directory as its repository subpath. The GitHub App installation must currently have access to the workspace. When Tessl resolves that binding, schedules from the manifest appear under the project's Schedules tab in the web app. Schedule sync does not create or reassign projects.

Tessl discovers and syncs each tessl.json in a repository independently. In a monorepo, a manifest such as apps/backend/tessl.json manages only its own schedules, without affecting the root or sibling manifests. Deleting that subdirectory manifest removes only the schedules managed by that manifest in workspaces that the GitHub App installation can still access. Schedules in workspaces it can no longer access are not reconciled by that deletion.

Let the schedule-setup skill do it for you

If you'd rather describe the schedule in plain language than write the JSON yourself, install tessl/schedule-setup.

When invoking the skill, tell your agent what you want to run and when ("run the docs audit every Monday at 9am London time using the production environment"). The skill asks clarifying questions one at a time, picks the most readable schedule format, writes and validates the entry, and explains what happens on push. It can also author a new repo-local skill for your schedule to use if you don't have one yet.

The schedules block

schedules is a map of schedule key to schedule entry. The key is the schedule's stable identity: a lowercase kebab-case slug, not a display label. Renaming a key removes the old schedule from the file and adds the new one. Sync validates the complete change before deleting the old schedule and creating the new one. If the new entry fails validation, neither operation is applied.

Each entry needs skill, environment, and one timing field. Use either cron or its alias, schedule, but not both.

Field
Required
What it is

skill

Yes

The plugin to run: a registry ref workspace/plugin[@version][#skill], or a repo-local file:path.

cron

One of two

A five-field cron expression, supported shorthand, or structured schedule object. The minimum interval between runs is 5 minutes.

schedule

One of two

Alias for cron. Use one timing field, not both.

environment

Yes

The workspace environment the run executes in, referenced by name. It supplies the GitHub token and any other secrets the plugin needs.

timezone

No

IANA timezone the cron is interpreted in (for example America/New_York). Defaults to UTC.

baseBranch

No

The branch each run checks out and opens PRs against. When omitted, each run uses the project's Loop default branch or the repository default.

workdir

No

A subdirectory of the repo the plugin runs in.

agent

No

Which agent runs the plugin (claude-code or claude (default), cursor, codex, gemini, tessl or tessl-agent).

model

No

Which model the agent uses (e.g. claude-sonnet-4-6 for claude, auto for cursor, gpt-5.4 for codex).

inputs

No

Skill input values, as a map of string keys to string values.

instructions

No

Free-text directions appended to the plugin prompt for this schedule.

description

No

A human-readable description of the schedule.

Raw cron expressions have five fields: minute, hour, day of month, month, and day of week. 0 9 * * 5 means "at 9:00 on every Friday". 0 8 * * * means "at 8:00 every day".

You can also use these shorthand strings:

  • hourly

  • daily at HH:MM

  • weekdays at HH:MM

  • weekly on DAY at HH:MM

  • monthly on Nth at HH:MM

  • every N minutes or every N hours, when N divides evenly into 60 or 24

  • every N minutes between HH:MM-HH:MM; the start is inclusive and the end is exclusive, and multi-hour windows use whole-hour endpoints

  • every N minutes on weekdays

For schedules that don't fit those patterns, use an object with minute, hour, day, month, and weekday fields. Omitted fields mean every value. Numeric fields accept a number, a list of numbers, { "every": N }, or { "between": [N, M] }. month and weekday also accept names or lists of names.

Tessl compiles shorthand strings and objects to cron before validation and storage. The timezone and 5-minute minimum apply to every input form.

A complete example

This declares two schedules. weekly-changelog runs the changelog-writer plugin every Friday at 9:00 New York time. api-docs-audit runs the docs-audit plugin every Monday at 8:00 UTC, steered at the API reference by its instructions.

Behaviors to know

Schedule identity is the key name. Renaming a key is therefore a delete and a create, not an update. For example, changing nightly-scan to nightly-check deletes the old schedule and its Temporal schedule, and creates a new schedule with the new key. Prior launch runs are preserved but are no longer linked to a schedule. If the new entry fails validation, neither the deletion nor the creation is applied.

The schedules block is the source of truth. Removing the block from tessl.json, or setting it to {}, means “no schedules” and deletes all schedules managed by that manifest. To recover from an accidental removal, revert the commit and push again.

Validate before you push

Run tessl schedule validate before you push to catch errors locally. You can also run the validator in your CI workflow:

It reads your tessl.json, checks the schedules block, and reports every problem against the schedule key it belongs to. It exits non-zero when the block is invalid, so it also works as a CI check. A file with no schedules block is valid.

validate checks the file only: its shape, its cron expressions, and the minimum interval. It cannot check what your workspace decides when the file syncs, so a file that passes here can still be rejected at sync time. It does not check:

  • whether the plugin reference resolves to a plugin your workspace can use

  • whether the named environment exists in your workspace, or carries a GitHub token

  • whether the agent and model are in your organization's catalogue

  • the limit on active schedules per workspace

How the sync works

When you push to your default branch, Tessl reads the schedules block and reconciles schedules across every workspace reachable through your GitHub App installation. For each workspace where the named environment resolves:

  • A key in the file that has no schedule yet is created.

  • A key whose entry changed is updated.

  • A key that was removed from the file is deleted.

If an entry cannot be applied in a given workspace (for example, the named environment does not exist there, or the workspace has reached its active-schedule limit), that entry is skipped for that workspace. Other entries and other workspaces are still reconciled.

Tessl only reads the schedules block. Changes to other parts of tessl.json are ignored. Most pushes that do not touch tessl.json are also ignored. A force push, branch creation, or push with an empty or truncated commit list bypasses that optimization. Tessl reads the manifest at the pushed revision and attempts reconciliation; individual entries can still be skipped if they cannot be applied.

The sync runs off the default branch only. A schedule declared on a feature branch does not sync until the change reaches your default branch.

The safety model

Tessl never deletes a schedule because it could not understand your file. Deletion only ever follows from a file it read cleanly.

  • Unparseable or invalid: nothing changes. If the file is malformed JSON, cannot be read, or its schedules block fails validation, Tessl makes no changes at all. A typo in the block does not delete your schedules; it leaves them exactly as they are. Fix the file and push again.

  • Valid and read cleanly: the file wins. Once Tessl reads a valid file, the file is the source of truth. Remove one key and that schedule is deleted. Remove the whole schedules block, or delete a tessl.json, and every repo-defined schedule managed by that manifest is deleted.

So the only way to delete a repo-defined schedule is to remove it from a valid file and push. An error in the file is always safe.

Read-only in the web app and CLI

When repo-defined schedules are enabled for your organization, a schedule defined in tessl.json is read-only from the web app and the CLI. Attempts to edit, pause, resume, or delete it through those surfaces return an error telling you it is defined in tessl.json. To change it, change the file and push.

You can still watch a repo-defined schedule and its runs in the web app and CLI. Only mutations are blocked.

A repo-defined schedule reflects the file for entries that were successfully applied at sync time. A skipped new entry is absent until the next sync reconciles it. A skipped update leaves the existing schedule at its previous configuration, and it can continue running until a later sync applies the update.

Create from the CLI or web app (alternative)

You can also create a schedule directly from the CLI or the web app, without a tessl.json block. This is the older path and is not version-controlled. It may be disabled for some users.

Use tessl schedule create. Pass the plugin as the first argument, then the repo, the cron, and the environment:

This runs the changelog-writer plugin against my-org/my-repo every Friday at 9:00 UTC.

Common flags:

Flag
What it does

--repo

The repository the plugin runs against. Required.

--cron

When the schedule fires, as a cron expression. Required.

--environment

The workspace environment the run executes in. It supplies the GitHub token and any other secrets or API keys the plugin needs. Required when you specify the plugin, repo, and environment directly.

--timezone

The timezone for the cron expression. Optional; defaults to UTC.

--agent

Which agent runs the plugin (for example claude-code, cursor, codex, gemini, or tessl-agent). Optional.

--model

Which model the agent uses. Optional.

--instructions

Extra directions layered on top of the plugin for this schedule. Optional.

--base-branch

The branch to check out and open PRs against. Optional.

The cron expression has five fields: minute, hour, day of month, month, and day of week. 0 9 * * 5 means "at 9:00 UTC on every Friday". 0 8 * * * means "at 8:00 UTC every day".

Use --instructions to point a generic plugin at a specific job:

Open Projects > [project] > Schedules and choose Create schedule. The form asks for the schedule (cron), a name, a description, and an environment. These are the primary fields.

Below the environment and mode selection is an Advanced options control. Expand it to set the agent, model, inputs, instructions, and base branch. Leave it collapsed to accept the defaults.

How instructions work

The instructions field layers extra directions on top of whatever the plugin already says. The plugin is the base recipe; instructions are the per-schedule customization. The plugin still runs its normal task. The instructions steer it for this one schedule.

This lets you reuse one plugin for several jobs. The same docs-audit plugin, pointed two ways in one schedules block:

Same plugin, two schedules, two different jobs.

Monitoring your runs

Each schedule keeps a table of its past runs. Open a schedule in the web app to see its full run history, including output and duration. From the CLI, tessl schedule runs <id> lists runs with their ID, status, skill, repo, workspace, creator, and creation time.

Status
What it means
What to do

In Progress

Still running

Wait

Completed

The skill finished and reported a result

Nothing. Check the output.

Agent failed

The skill reported that it could not complete the task

Check the output for why

Error

The platform failed, or the run ended without a skill result

Check the run's error detail

Cancelled

Someone stopped the run

Nothing

Next steps

Last updated