Create and migrate repo-defined Tessl schedules in a project's tessl.json. Turns a plain-language task and cadence into a validated schedules entry, optionally authors its repo-local skill, and moves an existing UI/CLI schedule into version control.
90
97%
Does it follow best practices?
Impact
97%
1.00xAverage score across 2 eval scenarios
Passed
No findings from the security scan
Turn a plain-language request ("run the nightly report at 2am") into a valid
schedules entry in the project's tessl.json, optionally author the
repo-local skill it runs, validate the entry, and tell the user what happens
next.
A repo-defined schedule is a recurring launch run declared in tessl.json so it
is reviewed and version-controlled next to the code it runs against. On push to
the default branch, Tessl reads the file and creates the schedule.
Read the schedules block reference for the full field list and the cadence reference before writing a schedule.
tessl.json lives.tessl.json, the project is not a
Tessl project yet; tell the user to run tessl init first and stop.tessl.json. Note whether a schedules block already
exists and what keys it holds, so a new entry does not collide with an
existing key.tessl schedule --help rather than
assuming flags.Collect these one at a time. The three the file requires are the skill, the cadence, and the environment.
skill field in one of two forms:
workspace/plugin[@version][#skill] (e.g.
tessleng/dark-factory-report, or acme/reports#weekly to pick one skill
from a multi-skill plugin).file:path/to/plugin (e.g.
file:tiles/dark-factory-report), relative to the repo root.file: ref in the
schedule entry.schedule key for shorthand strings. For patterns that shorthand
cannot express, use a structured schedule object or fall back to a raw
five-field cron. Default the timezone to UTC. If the user gives a local time,
ask which timezone (or use a timezone they have already stated) and set
timezone to an IANA name (e.g. America/New_York).tessl env list --workspace <workspace> and let
them pick.Optional fields, only if the user raises them (see
the schedules block reference for all of them):
agent, model, baseBranch, workdir, inputs,
instructions, description.
Skip this section when the schedule uses an existing skill. Do not edit or add files to that skill.
When the user asks for a new skill:
Scaffold the requested skill as a repo-local plugin with tessl skill new,
using the selected workspace and a path that follows the repository's
existing plugin conventions. If none exist, use tiles/<skill-name>. Write
the requested workflow in the generated SKILL.md, then use the plugin
path as the schedule entry's file: ref.
Identify which of Linear, Slack, and Notion the requested skill calls. For
each provider it uses, create the new skill's scripts/ directory and copy
that provider's .sh file verbatim and unmodified from this skill's
scripts/ directory. If it uses none of the three providers, create no
scripts/ directory and skip the remaining pre-flight steps.
Inspect the selected environment's variable names with
tessl env view --json --workspace <workspace> <environment>. Values are
sealed; only the names for the providers the skill uses are needed.
Make credential pre-flight the generated skill's first step, before any other work. Add one invocation for each copied script:
bash "$(git rev-parse --show-toplevel)/<skill-path>/scripts/linear.sh" "LINEAR_TOKEN"
bash "$(git rev-parse --show-toplevel)/<skill-path>/scripts/slack.sh" "SLACK_BOT_TOKEN"
bash "$(git rev-parse --show-toplevel)/<skill-path>/scripts/notion.sh" "NOTION_TOKEN"Replace <skill-path> with the repo-relative path of the skill directory
that holds the copied scripts (<plugin-path>/skills/<skill-name>), not the
plugin path from step 1. Do not use a bare scripts/... path. Include only
the lines for providers the skill uses. Pass the same variable name that the
generated skill uses for that provider's API calls. Prefer the canonical
name. If it is absent, use a name ending _LINEAR_TOKEN,
_SLACK_BOT_TOKEN, or _NOTION_TOKEN on the matching line. If more than one
accepted name is available, ask which one the skill should use. Match an
exact canonical name or the underscore-prefixed suffix only; a bare
substring is not a match.
State in the generated skill that a non-zero result from any invocation means stop before doing anything else.
This pre-flight covers only the pinned providers the authored skill uses: Linear, Slack, and Notion. A skill that uses another service or none of these three gets no check; do not guess or create checks for other services. It covers only skills created through this branch; existing skills are untouched. Checking every scheduled skill would require a platform launch-preflight hook and is outside this workflow.
The key is the schedule's stable identity in the schedules map: a
lowercase kebab-case slug (a-z, 0-9, single dashes), not a display label.
Derive it from the job (e.g. nightly-sales-report). Renaming a key later is a
delete-plus-create, so pick a durable one. It must not collide with an existing
key in the block.
Add the entry under a top-level schedules object in tessl.json. Create the
schedules object if it does not exist; otherwise add the key alongside the
existing ones. Preserve the rest of the file. Do not reorder or drop other
keys.
A minimal entry:
{
"schedules": {
"nightly-sales-report": {
"skill": "file:tiles/dark-factory-report",
"schedule": "daily at 02:00",
"environment": "dark-factory-report",
"description": "Nightly sales report"
}
}
}Rules that the validator enforces. Get them right the first time:
skill, one of schedule or cron, and environment are required. Use
schedule for shorthand strings and structured objects by default.schedule and cron are aliases; never include both in one entry. Use
cron with a raw expression only when the shorthand and structured forms do
not express the requested cadence.Run the check from the repo root:
tessl schedule validateIt reads tessl.json, validates the schedules block, and reports every
problem against the key it belongs to. It exits non-zero when the block is
invalid. Fix anything it reports and run it again until it passes.
validate checks the file only: shape, schedule expressions, and the minimum
interval. It does not confirm that the environment exists in the workspace,
that the skill's tile is installable, or that the agent/model are in the
catalogue. Those are checked when the file is applied. Tell the user this so a
clean validate is not mistaken for a guarantee the schedule will fire.
Once validate passes, tell the user in plain language:
tessl.json change and push it to the repo's default branch
(usually main). Repo-defined schedules are reconciled only from the
default branch. A push to a feature branch does nothing.tessl.json and creates the
schedule. This needs the Tessl GitHub App installed on the repo and the
repo-defined-schedules feature enabled for the workspace.tessl schedule list and in the UI, and
fires a launch run on its cron.If the schedule does not appear after the push, the usual causes are: the push
was not to the default branch, the named environment does not exist in the
workspace, the feature is not enabled, or the workspace has reached the limit of
100 active schedules. The active-schedule cap is enforced at apply time and
not reported by validate: the schedule is silently skipped, not rejected with
an error. If nothing else explains a missing schedule, run
tessl schedule list --json --limit 100 and count the active entries. Point the
user at these causes rather than re-editing the file.