Create and migrate repo-defined Tessl schedules in a project's tessl.json. Turns a plain-language cadence into a validated schedules entry, and moves an existing UI/CLI schedule into version control.
90
97%
Does it follow best practices?
Impact
94%
0.94xAverage score across 2 eval scenarios
Low
Low-risk findings worth noting
Move an existing schedule (one created in the UI or with tessl schedule create) into the project's tessl.json, so it is reviewed and
version-controlled next to the code. Then, once the repo-defined schedule is
confirmed working, delete the old one.
Read the field mapping reference before writing the entry. It maps live-schedule config fields to file fields and calls out the fields the read API does not expose.
tessl.json lives.tessl.json exists. If not, tell the user to run tessl init first
and stop.tessl.json. Note any existing schedules block and its
keys, so the migrated entry does not collide.tessl schedule --help.tessl schedule list --json --limit 100Show the user a concise list. The key details are the schedule id, its
cron, the skill it runs, and its status. If the user works across
workspaces, pass --workspace <name> to scope the list.
Note each schedule's definition source if the field is present: a schedule
already marked repo is defined by a tessl.json and does not need migrating.
Only app-defined (UI/CLI-created) schedules are candidates.
If the list contains 100 items, there may be more. Tell the user the listing may be partial and let them name a schedule by id if it did not appear.
Ask which schedule to migrate. Migrate one at a time. It keeps the mapping and the confirmation step clear.
tessl schedule view <id> --jsonExtract the fields you need for the file entry. See the field mapping reference for the exact source-to-file mapping. In short:
cron maps to crontimezone maps to timezone (omit if UTC)config.skillRef maps to skillenvironment.name maps to environment (the file names the environment; the
id in the live config is workspace-scoped and not portable)config.baseBranch maps to baseBranch (omit if main)config.snapshot maps to snapshotconfig.workdir maps to workdirdescription maps to descriptionagent and model require an explicit decision. schedule view returns
the resolved values but does not indicate whether they were explicitly set or
inherited from a workspace default. Ask the user: "The live schedule shows
agent <value> and model <value>. Were these set explicitly, or inherited
from workspace defaults?" Set the fields only if the user confirms they were
set explicitly or wants to preserve them. Omitting them lets the repo-defined
schedule pick up current workspace defaults.
Fields the read API does not return. A schedule's inputs and
instructions are hidden on the read paths because they can contain sensitive
values such as credentials, tokens, or personal data. view cannot recover
them.
Before asking the user for these values, warn them: inputs and
instructions will be stored in tessl.json, which is committed to the
repository and visible to everyone with repo access, including history. Ask
whether the values are safe to commit. If they contain credentials or personal
data, the user should instead store them in the workspace environment and
reference them via the environment's variables, rather than embedding them
directly in the file. Only add them to the entry if the user confirms they are
safe to commit, or confirms they are not needed.
Fields with no file equivalent. timeoutMs, label, sandboxSnapshot,
and stored metadata are not part of the schedules file schema and are
dropped in a repo-defined schedule. Tell the user if the source schedule used a
non-default timeoutMs or a label, so the drop is a decision, not a
surprise.
Paused source schedule. Check the source schedule's status field. If it
is paused, flag this explicitly: repo-defined schedules cannot be created in
a paused state: a new repo entry will be active immediately on apply. Ask
the user how to proceed. Options: migrate it as active (the schedule will start
firing), skip the migration, or proceed with migration and immediately pause the
old schedule after.
Before writing the entry, tell the user: Tessl's GitHub App reconciles
tessl.json against every workspace reachable by its installation. If the
app is installed across multiple workspaces, pushing the entry will attempt to
create the schedule in each workspace where its named environment exists. A
workspace where that environment is absent will skip it without error. The user
should confirm their GitHub App installation scope covers only the intended
workspaces, or name the environment such that it only exists in the right one.
The file entry needs a lowercase kebab-case key (a-z, 0-9, single dashes)
as its stable identity. Derive it from the schedule's job or label (e.g.
nightly-sales-report). It must not collide with an existing key in the block.
Add the entry under the top-level schedules object in tessl.json (create the
object if absent). Preserve the rest of the file. skill, cron, and
environment are required; the block is validated strictly, so only use the
fields in the mapping reference. Example:
{
"schedules": {
"nightly-sales-report": {
"skill": "file:tiles/dark-factory-report",
"cron": "0 2 * * *",
"environment": "dark-factory-report",
"agent": "claude",
"model": "claude-sonnet-4-6",
"description": "Nightly sales report"
}
}
}tessl schedule validateFix anything it reports and rerun until it passes. validate checks the file's
shape, cron, and 5-minute-minimum interval only, not whether the environment
exists, the skill installs, or the agent/model are in the catalogue. Those are
checked when the file is applied.
This order matters: verify the repo-defined schedule works before removing the old one, so a mistake never leaves the job unscheduled. However, the two schedules must not run concurrently once the repo one is active, because duplicate execution can duplicate side effects.
Have the user commit the tessl.json change and push it to the repo's
default branch. On that push, Tessl reconciles the file and creates the
repo-defined schedule.
Immediately after the push, before the next cron window fires, pause or delete the old app-defined schedule:
tessl schedule delete <old-id>Use the old app-defined schedule's id from the listing in step 2, not
the new repo-defined one. Deleting is irreversible, so confirm the id with
the user before running it. If the user wants a safety net, they can pause
instead (if the CLI supports it), but pausing still risks firing if the next
cron fires before the pause takes effect. Deletion is the safer choice.
Confirm the repo-defined schedule appeared:
tessl schedule list --jsonThe migrated schedule should now show with definition source repo.
If the repo-defined schedule never appears after the push, restore the old
schedule if you deleted it (tessl schedule create with the original
parameters), or unpause it. The usual causes of a missing repo schedule are a
push to a non-default branch, a missing environment in the workspace, or the
feature not being enabled.