CtrlK
BlogDocsLog inGet started
Tessl Logo
PluginPublic

tessleng/schedule-management

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.

89

1.01x
Quality

96%

Does it follow best practices?

Impact

90%

1.01x

2 of 3 eval scenarios. Add 1 more for a full score.

SecuritybySnyk

Passed

No findings from the security scan

Overview
Quality
Evals
Security
Files

schedules-block.mdskills/schedule-setup/references/

The schedules block in tessl.json

The schedules block is a top-level object in a project's tessl.json. It maps a schedule key to a schedule entry. Each entry declares one recurring launch run: action says what runs, and the fields beside it say when.

{
  "schedules": {
    "<schedule-key>": {
      "action": {
        "skill": "...",
        "environment": "..."
      },
      "schedule": "weekdays at 09:00"
    }
  }
}

Schedule key

  • A lowercase kebab-case slug: a-z, 0-9, single dashes, 1 to 64 chars (pattern ^[a-z0-9]+(?:-[a-z0-9]+)*$).
  • It is the schedule's stable identity, not a display label. The value ends up in refs, so it must survive without escaping.
  • Renaming a key is a delete-plus-create, never a rename. Pick a durable slug.

Entry fields

The entry is validated strictly: a field name that is not in these lists is a hard error (this catches typos the author would otherwise only notice when the schedule misbehaves). An action field written beside action instead of inside it is refused too.

Beside action, an entry carries only its own timing and label:

FieldTypeNotes
actionobject or stringRequired. What the schedule runs, in one of the forms under The action field.
schedulestring or objectPreferred authoring key. Accepts a supported shorthand string, a raw cron string, or a structured schedule object. The value is interpreted in timezone.
cronstring or objectAlias for schedule. Use it for a raw cron fallback when the shorthand and structured forms cannot express the cadence.
timezonestringOptional, default UTC. IANA timezone the schedule is interpreted in (e.g. America/New_York).
descriptionstringOptional. Human-readable description of the schedule (max 4096 chars).

Use exactly one of schedule or cron, never both. Both forms are compiled to canonical cron before timezone and minimum-interval validation. A shorthand or structured value is easier for humans to review, so prefer schedule for new entries.

Schedule values

The supported shorthand strings are:

FormExample
hourlyhourly
daily at HH:MMdaily at 02:00
weekdays at HH:MMweekdays at 09:00
weekly on DAY at HH:MMweekly on monday at 13:00
monthly on Nth at HH:MMmonthly on 1st at 00:00
every N minutesevery 15 minutes
every N hoursevery 6 hours
every N minutes between HH:MM-HH:MMevery 15 minutes between 09:00-17:00
every N minutes on weekdaysevery 15 minutes on weekdays

For a cadence that does not fit a shorthand, use a structured object. Each field is optional and omitted fields match any value:

{
  "schedule": {
    "minute": [0, 30],
    "hour": 9,
    "day": {"between": [1, 7]},
    "month": ["january", "jun"],
    "weekday": ["mon", "wed", "fri"]
  }
}

Numeric fields (minute, hour, and day) accept a number, a list of numbers, { "every": number }, or { "between": [start, end] }. month and weekday accept numbers, lists, or supported names such as january and monday. Use a raw five-field cron under cron when the structured object cannot express the requested pattern.

The action field

action takes one of three forms:

  • Inline: an object holding the action's fields. The entry is its own Loop. This is the default for a new schedule.
  • A declared action's name: a string such as "action": "nightly-report", naming an action in the manifest's top-level actions block.
  • A declared action with overrides: { "use": "nightly-report", ... }. The given fields merge over the declaration key by key, null removes a field other than skill, and inputs merges per input name.

The action fields, written inside action:

FieldTypeDefaultNotes
skillstringnoneRequired in an inline action. The skill to run: a registry ref workspace/plugin[@version][#skill], or a repo-local file:path. Validated server-side at apply time.
environmentstringnoneThe workspace environment name (not its id). The id is workspace-scoped, so a committed file names the environment and the apply step resolves it to an id in the target workspace.
baseBranchstringLoop default branchBranch each fired run clones and targets its PR against.
workdirstringrepo rootSubdirectory of the repo the skill runs in.
agentstringworkspace defaultAgent to run the skill with. Plain string, validated server-side.
modelstringagent defaultModel to run the skill with. Plain string, validated server-side.
effortstringagent defaultReasoning effort: minimal, low, medium, high, xhigh, or max. Which levels an agent offers is checked at apply time.
inputsobjectnoneSkill input values, keyed by the skill's own placeholder names. String values only; keys must be non-empty.
instructionsstringnoneFree-text instructions appended to the skill prompt.

Two schedules that run the same skill can share one declaration:

{
  "actions": {
    "pr-report": { "skill": "file:tiles/pr-report", "environment": "reports" }
  },
  "schedules": {
    "pr-report-nightly": { "action": "pr-report", "cron": "0 2 * * *" },
    "pr-report-weekly": {
      "action": { "use": "pr-report", "instructions": "Cover the last 7 days." },
      "cron": "0 4 * * 1"
    }
  }
}

An older entry that writes the action's fields beside cron, with no action, still runs and is read as the inline action it describes.

There is no file field that pins a commit or tag. Each fire clones the current head of baseBranch. When baseBranch is omitted, the run uses the linked project's Loop default branch, or the repository default when no project is linked. The project setting reads the repository default at fire time unless it names a branch.

What tessl schedule validate does and does not check

validate reads the file and checks the block's shape, compiles each schedule or cron value, validates the resulting expression against its timezone, and checks the minimum 5-minute interval. It also checks that every action a schedule names, directly or with use, is declared in the actions block, and validates the whole actions block itself. A problem in one declaration is reported against that action (actions.<name>); a problem with the block as a whole, such as an actions value that is not an object, is reported as actions block. It exits non-zero when either block is invalid, so it works as a CI check. A manifest with neither block is valid.

It does not check (these are decided at apply time, in the workspace):

  • whether the skill ref resolves to a plugin the workspace can use;
  • whether the named environment exists in the workspace or carries a GitHub token;
  • whether the agent, model, and effort are in the organization's catalogue;
  • the limit of 100 active schedules per workspace.

So a file that passes validate can still be rejected when applied. A clean validate means the file is well-formed, not that the schedule will fire.

Example with optional fields

{
  "schedules": {
    "weekly-dependency-digest": {
      "action": {
        "skill": "tessleng/dependency-digest",
        "environment": "dependency-digest",
        "agent": "claude",
        "model": "claude-sonnet-4-6"
      },
      "schedule": "weekly on monday at 13:00",
      "timezone": "UTC",
      "description": "Weekly dependency digest, Mondays 13:00 UTC"
    }
  }
}

README.md

tile.json